Advertisement
Guest User

Untitled

a guest
Dec 1st, 2017
375
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 18.42 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * This file is part of MetaModels/attribute_tags.
  5.  *
  6.  * (c) 2012-2017 The MetaModels team.
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  *
  11.  * This project is provided in good faith and hope to be usable by anyone.
  12.  *
  13.  * @package    MetaModels
  14.  * @subpackage AttributeTags
  15.  * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
  16.  * @author     Christian de la Haye <service@delahaye.de>
  17.  * @author     Andreas Isaak <info@andreas-isaak.de>
  18.  * @author     Andreas Nölke <zero@brothers-project.de>
  19.  * @author     David Maack <david.maack@arcor.de>
  20.  * @author     Patrick Kahl <kahl.patrick@googlemail.com>
  21.  * @author     Stefan Heimes <stefan_heimes@hotmail.com>
  22.  * @author     Christopher Boelter <christopher@boelter.eu>
  23.  * @author     Ingolf Steinhardt <info@e-spin.de>
  24.  * @author     Sven Baumann <baumann.sv@gmail.com>
  25.  * @copyright  2012-2017 The MetaModels team.
  26.  * @license    https://github.com/MetaModels/attribute_tags/blob/master/LICENSE LGPL-3.0
  27.  * @filesource
  28.  */
  29.  
  30. namespace MetaModels\Attribute\Tags;
  31.  
  32. use MetaModels\Attribute\ITranslated;
  33. use MetaModels\Filter\IFilter;
  34. use MetaModels\Filter\Rules\StaticIdList;
  35. use MetaModels\IItem;
  36. use MetaModels\IItems;
  37. use MetaModels\IMetaModel;
  38. use MetaModels\Items;
  39.  
  40. /**
  41.  * This is the MetaModelAttribute class for handling tag attributes.
  42.  */
  43. class MetaModelTags extends AbstractTags
  44. {
  45.     /**
  46.      * The key in the result array where the RAW values shall be stored.
  47.      */
  48.     const TAGS_RAW = '__TAGS_RAW__';
  49.  
  50.     /**
  51.      * The MetaModel we are referencing on.
  52.      *
  53.      * @var IMetaModel
  54.      */
  55.     protected $objSelectMetaModel;
  56.  
  57.     /**
  58.      * {@inheritDoc}
  59.      */
  60.     protected function checkConfiguration()
  61.     {
  62.         return parent::checkConfiguration()
  63.             && (null !== $this->getTagMetaModel());
  64.     }
  65.  
  66.     /**
  67.      * Retrieve the linked MetaModel instance.
  68.      *
  69.      * @return IMetaModel
  70.      */
  71.     protected function getTagMetaModel()
  72.     {
  73.         if (empty($this->objSelectMetaModel)) {
  74.             $this->objSelectMetaModel =
  75.                 $this->getMetaModel()->getServiceContainer()->getFactory()->getMetaModel($this->getTagSource());
  76.         }
  77.  
  78.         return $this->objSelectMetaModel;
  79.     }
  80.  
  81.     /**
  82.      * Retrieve the values with the given ids.
  83.      *
  84.      * @param string[] $valueIds The ids of the values to retrieve.
  85.      *
  86.      * @return array
  87.      */
  88.     protected function getValuesById($valueIds)
  89.     {
  90.         if (empty($valueIds)) {
  91.             return [];
  92.         }
  93.  
  94.         $recursionKey = $this->getMetaModel()->getTableName();
  95.         $metaModel    = $this->getTagMetaModel();
  96.         $filter       = $metaModel->getEmptyFilter();
  97.         $filter->addFilterRule(new StaticIdList($valueIds));
  98.  
  99.         // Prevent recursion.
  100.         static $tables = array();
  101.         if (isset($tables[$recursionKey])) {
  102.             return array();
  103.         }
  104.         $tables[$recursionKey] = $recursionKey;
  105.  
  106.         $items = $metaModel->findByFilter($filter, $this->getSortingColumn(), 0, 0, $this->getSortDirection());
  107.         unset($tables[$recursionKey]);
  108.  
  109.         // Sort items manually for checkbox wizard.
  110.         if ($this->isCheckboxWizard()) {
  111.             // Remove deleted referenced items and flip.
  112.             $orderIds = array_flip(array_filter($valueIds));
  113.  
  114.             foreach ($items as $item) {
  115.                 $orderIds[$item->get('id')] = $item;
  116.             }
  117.             $items = new Items(
  118.                 array_values(
  119.                     array_filter(
  120.                         $orderIds,
  121.                         function ($itemOrId) {
  122.                             return $itemOrId instanceof IItem;
  123.                         }
  124.                     )
  125.                 )
  126.             );
  127.         }
  128.  
  129.         $values = [];
  130.         $count  = 0;
  131.         foreach ($items as $item) {
  132.             $valueId    = $item->get('id');
  133.             $parsedItem = $item->parseValue();
  134.  
  135.             $values[$valueId] = array_merge(
  136.                 array(
  137.                     self::TAGS_RAW => $parsedItem['raw'],
  138.                     'tag_value_sorting' => $count++
  139.                 ),
  140.                 $parsedItem['text']
  141.             );
  142.         }
  143.  
  144.         return $values;
  145.     }
  146.  
  147.     /**
  148.      * Sort a list of value ids by the option column (non-existent ids will get moved to the end).
  149.      *
  150.      * @param array $idList The value id list to sort.
  151.      *
  152.      * @return array
  153.      */
  154.     private function sortIdsBySortingColumn($idList)
  155.     {
  156.         // Only one item, what shall we sort here then?
  157.         if (1 === count($idList)) {
  158.             return $idList;
  159.         }
  160.  
  161.         static $sorting;
  162.         if (isset($sorting[$cacheKey = implode(',', $idList)])) {
  163.             return $sorting[$cacheKey];
  164.         }
  165.  
  166.         // Now sort the values according to our sorting column.
  167.         $filter = $this
  168.             ->getTagMetaModel()
  169.             ->getEmptyFilter()
  170.             ->addFilterRule(new StaticIdList($idList));
  171.  
  172.         $itemIds = $this->getTagMetaModel()->getIdsFromFilter(
  173.             $filter,
  174.             $this->getSortingColumn(),
  175.             0,
  176.             0,
  177.             $this->getSortDirection()
  178.         );
  179.  
  180.         // Manual sorting of items for checkbox wizard.
  181.         if ($this->isCheckboxWizard()) {
  182.             // Keep order from input array, and add non existent ids to the end.
  183.             return $sorting[$cacheKey] = array_merge(
  184.                 // Keep order from input array...
  185.                 array_intersect($idList, $itemIds),
  186.                 // ... and add non existent ids to the end.
  187.                 array_diff($idList, $itemIds)
  188.             );
  189.         }
  190.         // Flip to have id as key and index on value.
  191.         $orderIds = array_flip($idList);
  192.         // Loop over items and set $id => $id
  193.         foreach ($itemIds as $itemId) {
  194.             $orderIds[$itemId] = $itemId;
  195.         }
  196.         // Use new order and add non existent ids to the end.
  197.         return $sorting[$cacheKey] = array_merge($itemIds, array_diff($idList, $itemIds));
  198.     }
  199.  
  200.     /**
  201.      * {@inheritdoc}
  202.      */
  203.     public function valueToWidget($varValue)
  204.     {
  205.         // If we have a tree picker, the value must be a comma separated string.
  206.         if (empty($varValue)) {
  207.             return [];
  208.         }
  209.  
  210.         $aliasColumn    = $this->getAliasColumn();
  211.         $aliasTranslate = function ($value) use ($aliasColumn) {
  212.             if (!empty($value[$aliasColumn])) {
  213.                 return $value[$aliasColumn];
  214.             }
  215.             if (!empty($value[self::TAGS_RAW][$aliasColumn])) {
  216.                 return $value[self::TAGS_RAW][$aliasColumn];
  217.             }
  218.  
  219.             return null;
  220.         };
  221.  
  222.         $alias = [];
  223.         foreach ($varValue as $valueId => $value) {
  224.             $alias[$valueId] = $aliasTranslate($value);
  225.         }
  226.         unset($valueId, $value);
  227.  
  228.         // Sort the values now.
  229.         $sortedIds = $this->sortIdsBySortingColumn(array_keys($varValue));
  230.         $result    = [];
  231.         foreach ($sortedIds as $id) {
  232.             $result[] = $alias[$id];
  233.         }
  234.         if ($this->isTreePicker()) {
  235.             return implode(',', $result);
  236.         }
  237.  
  238.         // We must use string keys.
  239.         return array_map('strval', $result);
  240.     }
  241.  
  242.     /**
  243.      * {@inheritdoc}
  244.      *
  245.      * @throws \RuntimeException When values could not be translated.
  246.      */
  247.     protected function getValuesFromWidget($varValue)
  248.     {
  249.         $model     = $this->getTagMetaModel();
  250.         $alias     = $this->getAliasColumn();
  251.         $attribute = $model->getAttribute($alias);
  252.         $valueIds  = array();
  253.  
  254.         if ($attribute) {
  255.             // It is an attribute, we may search for it.
  256.             foreach ($varValue as $value) {
  257.                 if ($attribute instanceof ITranslated) {
  258.                     $ids = $attribute->searchForInLanguages(
  259.                         $value,
  260.                         array($model->getActiveLanguage(), $model->getFallbackLanguage())
  261.                     );
  262.                 } else {
  263.                     $ids = $attribute->searchFor($value);
  264.                 }
  265.                 // If all match, return all.
  266.                 if (null === $ids) {
  267.                     $valueIds = $model->getIdsFromFilter($model->getEmptyFilter(), $alias);
  268.                     break;
  269.                 }
  270.                 if ($ids) {
  271.                     $valueIds = array_merge($valueIds, $ids);
  272.                 }
  273.             }
  274.         } else {
  275.             // Must be a system column then.
  276.             // Special case first, the id is our alias, easy way out.
  277.             if ($alias === 'id') {
  278.                 $valueIds = $varValue;
  279.             } else {
  280.                 $result = $this->getDatabase()
  281.                     ->prepare(
  282.                         sprintf(
  283.                             'SELECT v.id FROM %1$s AS v WHERE v.%2$s IN (%3$s) LIMIT 1',
  284.                             $model->getTableName(),
  285.                             $alias,
  286.                             $this->parameterMask($varValue)
  287.                         )
  288.                     )
  289.                     ->execute($varValue);
  290.  
  291.                 /** @noinspection PhpUndefinedFieldInspection */
  292.                 if (!$result->numRows) {
  293.                     throw new \RuntimeException('Could not translate value ' . var_export($varValue, true));
  294.                 }
  295.  
  296.                 $valueIds = $result->fetchEach('id');
  297.             }
  298.         }
  299.  
  300.         return $this->getValuesById($valueIds);
  301.     }
  302.  
  303.     /**
  304.      * Calculate the amount how often each value has been assigned.
  305.      *
  306.      * @param IItems $items       The item list containing the values.
  307.      *
  308.      * @param array  $amountArray The target array to where the counters shall be stored to.
  309.      *
  310.      * @return void
  311.      */
  312.     protected function calculateFilterOptionsCount($items, &$amountArray)
  313.     {
  314.         $filter = '';
  315.         $ids    = array();
  316.         foreach ($items as $item) {
  317.             $ids[] = $item->get('id');
  318.         }
  319.  
  320.         if ($ids) {
  321.             $filter = sprintf(' AND value_id IN (%1$s)', $this->parameterMask($ids));
  322.         }
  323.  
  324.         $counts = $this
  325.             ->getDatabase()
  326.             ->prepare(
  327.                 sprintf(
  328.                     'SELECT value_id, COUNT(item_id) AS amount FROM %1$s WHERE att_id="%2$s" %3$s GROUP BY value_id',
  329.                     $this->getReferenceTable(),
  330.                     $this->get('id'),
  331.                     $filter
  332.                 )
  333.             )
  334.             ->execute($ids);
  335.  
  336.         while ($counts->next()) {
  337.             /** @noinspection PhpUndefinedFieldInspection */
  338.             $amountArray[$counts->value_id] = $counts->amount;
  339.         }
  340.     }
  341.  
  342.     /**
  343.      * {@inheritdoc}
  344.      *
  345.      * Fetch filter options from foreign table.
  346.      */
  347.     public function getFilterOptions($idList, $usedOnly, &$arrCount = null)
  348.     {
  349.         if (!$this->isFilterOptionRetrievingPossible($idList)) {
  350.             return array();
  351.         }
  352.  
  353.         $filter = $this->getTagMetaModel()->getEmptyFilter();
  354.  
  355.         $this->buildFilterRulesForFilterSetting($filter);
  356.  
  357.         // Add some more filter rules.
  358.         if ($usedOnly) {
  359.             $this->buildFilterRulesForUsedOnly($filter, $idList ? $idList : array());
  360.         } elseif ($idList && is_array($idList)) {
  361.             $filter->addFilterRule(new StaticIdList($idList));
  362.         }
  363.  
  364.         $objItems = $this->getTagMetaModel()->findByFilter(
  365.             $filter,
  366.             $this->getSortingColumn(),
  367.             0,
  368.             0,
  369.             $this->getSortDirection(),
  370.             [$this->getAliasColumn(), $this->getValueColumn()]
  371.         );
  372.  
  373.         if ($arrCount !== null) {
  374.             $this->calculateFilterOptionsCount($objItems, $arrCount);
  375.         }
  376.  
  377.         return $this->convertItemsToFilterOptions(
  378.             $objItems,
  379.             $this->getValueColumn(),
  380.             $this->getAliasColumn(),
  381.             $arrCount
  382.         );
  383.     }
  384.  
  385.     /**
  386.      * Fetch filter options from foreign table taking the given flag into account.
  387.      *
  388.      * @param IFilter $filter The filter to which the rules shall be added to.
  389.      *
  390.      * @return void
  391.      *
  392.      * @SuppressWarnings(PHPMD.Superglobals)
  393.      * @SuppressWarnings(PHPMD.CamelCaseVariableName)
  394.      */
  395.     public function buildFilterRulesForFilterSetting($filter)
  396.     {
  397.         if (!$this->get('tag_filter')) {
  398.             return;
  399.         }
  400.  
  401.         // Set Filter and co.
  402.         $filterSettings = $this
  403.             ->getMetaModel()
  404.             ->getServiceContainer()
  405.             ->getFilterFactory()
  406.             ->createCollection($this->get('tag_filter'));
  407.  
  408.         if ($filterSettings) {
  409.             $values       = $_GET;
  410.             $presets      = (array) $this->get('tag_filterparams');
  411.             $presetNames  = $filterSettings->getParameters();
  412.             $filterParams = array_keys($filterSettings->getParameterFilterNames());
  413.             $processed    = array();
  414.  
  415.             // We have to use all the preset values we want first.
  416.             foreach ($presets as $presetName => $preset) {
  417.                 if (in_array($presetName, $presetNames)) {
  418.                     $processed[$presetName] = $preset['value'];
  419.                 }
  420.             }
  421.  
  422.             // Now we have to use all FrontEnd filter params, that are either:
  423.             // * not contained within the presets
  424.             // * or are overridable.
  425.             foreach ($filterParams as $parameter) {
  426.                 // Unknown parameter? - next please.
  427.                 if (!array_key_exists($parameter, $values)) {
  428.                     continue;
  429.                 }
  430.  
  431.                 // Not a preset or allowed to override? - use value.
  432.                 if ((!array_key_exists($parameter, $presets)) || $presets[$parameter]['use_get']) {
  433.                     $processed[$parameter] = $values[$parameter];
  434.                 }
  435.             }
  436.  
  437.             $filterSettings->addRules($filter, $processed);
  438.         }
  439.     }
  440.  
  441.     /**
  442.      * Fetch filter options from foreign table taking the given flag into account.
  443.      *
  444.      * @param IFilter $filter The filter to which the rules shall be added to.
  445.      *
  446.      * @param array   $idList The list of ids of items for which the rules shall be added.
  447.      *
  448.      * @return void
  449.      */
  450.     public function buildFilterRulesForUsedOnly($filter, $idList = array())
  451.     {
  452.         $params = array($this->get('id'));
  453.  
  454.         if (empty($idList)) {
  455.             $arrUsedValues = $this
  456.                 ->getDatabase()
  457.                 ->prepare(
  458.                     'SELECT value_id AS value
  459.                     FROM tl_metamodel_tag_relation
  460.                     WHERE att_id = ?
  461.                     GROUP BY value'
  462.                 )
  463.                 ->execute($params)
  464.                 ->fetchEach('value');
  465.         } else {
  466.             $query = sprintf(
  467.                 'SELECT value_id AS value
  468.                    FROM tl_metamodel_tag_relation
  469.                    WHERE att_id = ?
  470.                      AND item_id IN (%s)
  471.                    GROUP BY value',
  472.                 $this->parameterMask($idList)
  473.             );
  474.  
  475.             $arrUsedValues = $this->getDatabase()
  476.                 ->prepare($query)
  477.                 ->execute(array_merge($params, $idList))
  478.                 ->fetchEach('value');
  479.         }
  480.  
  481.         $arrUsedValues = array_filter(
  482.             $arrUsedValues,
  483.             function ($value) {
  484.                 return !empty($value);
  485.             }
  486.         );
  487.  
  488.         $filter->addFilterRule(new StaticIdList($arrUsedValues));
  489.     }
  490.  
  491.     /**
  492.      * Convert a collection of items into a proper filter option list.
  493.      *
  494.      * @param IItems|IItem[] $items        The item collection to convert.
  495.      *
  496.      * @param string         $displayValue The name of the attribute to use as value.
  497.      *
  498.      * @param string         $aliasColumn  The name of the attribute to use as alias.
  499.      *
  500.      * @param null|string[]  $count        The counter array.
  501.      *
  502.      * @return array
  503.      */
  504.     protected function convertItemsToFilterOptions($items, $displayValue, $aliasColumn, &$count = null)
  505.     {
  506.         $result = array();
  507.         foreach ($items as $item) {
  508.             $parsedDisplay = $item->parseAttribute($displayValue);
  509.             $parsedAlias   = $item->parseAttribute($aliasColumn);
  510.  
  511.             $textValue  = isset($parsedDisplay['text'])
  512.                 ? $parsedDisplay['text']
  513.                 : $item->get($displayValue);
  514.             $aliasValue = isset($parsedAlias['text'])
  515.                 ? $parsedAlias['text']
  516.                 : $item->get($aliasColumn);
  517.  
  518.             $result[$aliasValue] = $textValue;
  519.  
  520.             // Clean the count array if alias is different from id value.
  521.             if (null !== $count && isset($count[$item->get('id')]) && $aliasValue !== $item->get('id')) {
  522.                 $count[$aliasValue] = $count[$item->get('id')];
  523.                 unset($count[$item->get('id')]);
  524.             }
  525.         }
  526.  
  527.         return $result;
  528.     }
  529.  
  530.     /**
  531.      * {@inheritdoc}
  532.      */
  533.     public function getDataFor($arrIds)
  534.     {
  535.         if (!$this->isProperlyConfigured()) {
  536.             return array();
  537.         }
  538.  
  539.         $rows = $this
  540.             ->getDatabase()
  541.             ->prepare(
  542.                 sprintf(
  543.                     'SELECT item_id AS id, value_id AS value
  544.                    FROM tl_metamodel_tag_relation
  545.                    WHERE tl_metamodel_tag_relation.item_id IN (%1$s)
  546.                    AND att_id = ?
  547.                    ORDER BY tl_metamodel_tag_relation.value_sorting',
  548.                     $this->parameterMask($arrIds)
  549.                 )
  550.             )
  551.             ->execute(array_merge($arrIds, array($this->get('id'))));
  552.  
  553.         $valueIds     = array();
  554.         $referenceIds = array();
  555.  
  556.         while ($rows->next()) {
  557.             /** @noinspection PhpUndefinedFieldInspection */
  558.             $value = $rows->value;
  559.             /** @noinspection PhpUndefinedFieldInspection */
  560.             $valueIds[$rows->id][] = $value;
  561.             $referenceIds[]        = $value;
  562.         }
  563.  
  564.         $values = $this->getValuesById($referenceIds);
  565.         $result = array();
  566.         foreach ($valueIds as $itemId => $tagIds) {
  567.             foreach ($tagIds as $tagId) {
  568.                 $result[$itemId][$tagId] = $values[$tagId];
  569.             }
  570.         }
  571.  
  572.         return $result;
  573.     }
  574. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement