vendor/pimcore/pimcore/models/DataObject/ClassDefinition/Data/Relations/AbstractRelations.php line 176

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Model\DataObject\ClassDefinition\Data\Relations;
  15. use Pimcore\Db;
  16. use Pimcore\Logger;
  17. use Pimcore\Model\DataObject;
  18. use Pimcore\Model\DataObject\ClassDefinition\Data;
  19. use Pimcore\Model\DataObject\ClassDefinition\Data\CustomResourcePersistingInterface;
  20. use Pimcore\Model\Element;
  21. abstract class AbstractRelations extends Data implements
  22.     CustomResourcePersistingInterface,
  23.     DataObject\ClassDefinition\PathFormatterAwareInterface,
  24.     Data\LazyLoadingSupportInterface,
  25.     Data\EqualComparisonInterface,
  26.     Data\IdRewriterInterface
  27. {
  28.     use DataObject\Traits\ContextPersistenceTrait;
  29.     const RELATION_ID_SEPARATOR '$$';
  30.     /**
  31.      * Set of allowed classes
  32.      *
  33.      * @internal
  34.      *
  35.      * @var array
  36.      */
  37.     public $classes = [];
  38.     /**
  39.      * Optional path formatter class
  40.      *
  41.      * @internal
  42.      *
  43.      * @var null|string
  44.      */
  45.     public $pathFormatterClass;
  46.     /**
  47.      * @return array[
  48.      *  'classes' => string,
  49.      * ]
  50.      */
  51.     public function getClasses()
  52.     {
  53.         return $this->classes ?: [];
  54.     }
  55.     /**
  56.      * @param array $classes
  57.      *
  58.      * @return $this
  59.      */
  60.     public function setClasses($classes)
  61.     {
  62.         $this->classes Element\Service::fixAllowedTypes($classes'classes');
  63.         return $this;
  64.     }
  65.     /**
  66.      * {@inheritdoc}
  67.      */
  68.     public function getLazyLoading()
  69.     {
  70.         return true;
  71.     }
  72.     /**
  73.      * {@inheritdoc}
  74.      */
  75.     public function save($object$params = [])
  76.     {
  77.         if (isset($params['isUntouchable']) && $params['isUntouchable']) {
  78.             return;
  79.         }
  80.         if (!isset($params['context'])) {
  81.             $params['context'] = null;
  82.         }
  83.         $context $params['context'];
  84.         if (!DataObject::isDirtyDetectionDisabled() && $object instanceof Element\DirtyIndicatorInterface) {
  85.             if (!isset($context['containerType']) || $context['containerType'] !== 'fieldcollection') {
  86.                 if ($object instanceof DataObject\Localizedfield) {
  87.                     if ($object->getObject() instanceof Element\DirtyIndicatorInterface && !$object->hasDirtyFields()) {
  88.                         return;
  89.                     }
  90.                 } elseif ($this->supportsDirtyDetection() && !$object->isFieldDirty($this->getName())) {
  91.                     return;
  92.                 }
  93.             }
  94.         }
  95.         $data $this->getDataFromObjectParam($object$params);
  96.         $relations $this->prepareDataForPersistence($data$object$params);
  97.         if (is_array($relations) && !empty($relations)) {
  98.             $db Db::get();
  99.             foreach ($relations as $relation) {
  100.                 $this->enrichDataRow($object$params$classId$relation);
  101.                 // relation needs to be an array with src_id, dest_id, type, fieldname
  102.                 try {
  103.                     $db->insert('object_relations_' $classId$relation);
  104.                 } catch (\Exception $e) {
  105.                     Logger::error('It seems that the relation ' $relation['src_id'] . ' => ' $relation['dest_id']
  106.                         . ' (fieldname: ' $this->getName() . ') already exist -> please check immediately!');
  107.                     Logger::error((string) $e);
  108.                     // try it again with an update if the insert fails, shouldn't be the case, but it seems that
  109.                     // sometimes the insert throws an exception
  110.                     throw $e;
  111.                 }
  112.             }
  113.         }
  114.     }
  115.     /**
  116.      * {@inheritdoc}
  117.      */
  118.     public function load($object$params = [])
  119.     {
  120.         $data null;
  121.         $relations = [];
  122.         if ($object instanceof DataObject\Concrete) {
  123.             $relations $object->retrieveRelationData(['fieldname' => $this->getName(), 'ownertype' => 'object']);
  124.         } elseif ($object instanceof DataObject\Fieldcollection\Data\AbstractData) {
  125.             $relations $object->getObject()->retrieveRelationData(['fieldname' => $this->getName(), 'ownertype' => 'fieldcollection''ownername' => $object->getFieldname(), 'position' => $object->getIndex()]);
  126.         } elseif ($object instanceof DataObject\Localizedfield) {
  127.             $context $params['context'] ?? null;
  128.             if (isset($context['containerType']) && (($context['containerType'] === 'fieldcollection' || $context['containerType'] === 'objectbrick'))) {
  129.                 $fieldname $context['fieldname'] ?? null;
  130.                 if ($context['containerType'] === 'fieldcollection') {
  131.                     $index $context['index'] ?? null;
  132.                     $filter '/' $context['containerType'] . '~' $fieldname '/' $index '/%';
  133.                 } else {
  134.                     $filter '/' $context['containerType'] . '~' $fieldname '/%';
  135.                 }
  136.                 $relations $object->getObject()->retrieveRelationData(['fieldname' => $this->getName(), 'ownertype' => 'localizedfield''ownername' => $filter'position' => $params['language']]);
  137.             } else {
  138.                 $relations $object->getObject()->retrieveRelationData(['fieldname' => $this->getName(), 'ownertype' => 'localizedfield''position' => $params['language']]);
  139.             }
  140.         } elseif ($object instanceof DataObject\Objectbrick\Data\AbstractData) {
  141.             $relations $object->getObject()->retrieveRelationData(['fieldname' => $this->getName(), 'ownertype' => 'objectbrick''ownername' => $object->getFieldname(), 'position' => $object->getType()]);
  142.         }
  143.         // using PHP sorting to order the relations, because "ORDER BY index ASC" in the queries above will cause a
  144.         // filesort in MySQL which is extremely slow especially when there are millions of relations in the database
  145.         usort($relations, function ($a$b) {
  146.             if ($a['index'] == $b['index']) {
  147.                 return 0;
  148.             }
  149.             return ($a['index'] < $b['index']) ? -1;
  150.         });
  151.         $data $this->loadData($relations$object$params);
  152.         if ($object instanceof Element\DirtyIndicatorInterface && $data['dirty']) {
  153.             $object->markFieldDirty($this->getName(), true);
  154.         }
  155.         return $data['data'];
  156.     }
  157.     /**
  158.      * @internal
  159.      *
  160.      * @param array $data
  161.      * @param DataObject\Concrete $object
  162.      * @param array $params
  163.      *
  164.      * @return mixed
  165.      */
  166.     abstract protected function loadData(array $data$object null$params = []);
  167.     /**
  168.      * @internal
  169.      *
  170.      * @param array|Element\ElementInterface $data
  171.      * @param DataObject\Concrete $object
  172.      * @param array $params
  173.      *
  174.      * @return mixed
  175.      */
  176.     abstract protected function prepareDataForPersistence($data$object null$params = []);
  177.     /**
  178.      * {@inheritdoc}
  179.      */
  180.     public function delete($object$params = [])
  181.     {
  182.     }
  183.     /**
  184.      * Rewrites id from source to target, $idMapping contains
  185.      * array(
  186.      *  "document" => array(
  187.      *      SOURCE_ID => TARGET_ID,
  188.      *      SOURCE_ID => TARGET_ID
  189.      *  ),
  190.      *  "object" => array(...),
  191.      *  "asset" => array(...)
  192.      * )
  193.      *
  194.      * @internal
  195.      *
  196.      * @param mixed $data
  197.      * @param array $idMapping
  198.      *
  199.      * @return array
  200.      */
  201.     protected function rewriteIdsService($data$idMapping)
  202.     {
  203.         if (is_array($data)) {
  204.             foreach ($data as &$element) {
  205.                 $id $element->getId();
  206.                 $type Element\Service::getElementType($element);
  207.                 if (array_key_exists($type$idMapping) && array_key_exists($id$idMapping[$type])) {
  208.                     $element Element\Service::getElementById($type$idMapping[$type][$id]);
  209.                 }
  210.             }
  211.         }
  212.         return $data;
  213.     }
  214.     /**
  215.      * {@inheritdoc}
  216.      */
  217.     public function getPathFormatterClass(): ?string
  218.     {
  219.         return $this->pathFormatterClass;
  220.     }
  221.     /**
  222.      * @param null|string $pathFormatterClass
  223.      */
  224.     public function setPathFormatterClass($pathFormatterClass)
  225.     {
  226.         $this->pathFormatterClass $pathFormatterClass;
  227.     }
  228.     /**
  229.      * {@inheritdoc}
  230.      */
  231.     public function getDataForSearchIndex($object$params = [])
  232.     {
  233.         return '';
  234.     }
  235.     /**
  236.      * {@inheritdoc}
  237.      */
  238.     public function appendData($existingData$additionalData)
  239.     {
  240.         $newData = [];
  241.         if (!is_array($existingData)) {
  242.             $existingData = [];
  243.         }
  244.         $map = [];
  245.         /** @var Element\ElementInterface $item */
  246.         foreach ($existingData as $item) {
  247.             $key $this->buildUniqueKeyForAppending($item);
  248.             $map[$key] = 1;
  249.             $newData[] = $item;
  250.         }
  251.         if (is_array($additionalData)) {
  252.             foreach ($additionalData as $item) {
  253.                 $key $this->buildUniqueKeyForAppending($item);
  254.                 if (!isset($map[$key])) {
  255.                     $newData[] = $item;
  256.                 }
  257.             }
  258.         }
  259.         return $newData;
  260.     }
  261.     /**
  262.      * {@inheritdoc}
  263.      */
  264.     public function removeData($existingData$removeData)
  265.     {
  266.         $newData = [];
  267.         if (!is_array($existingData)) {
  268.             $existingData = [];
  269.         }
  270.         $removeMap = [];
  271.         /** @var Element\ElementInterface $item */
  272.         foreach ($removeData as $item) {
  273.             $key $this->buildUniqueKeyForAppending($item);
  274.             $removeMap[$key] = 1;
  275.         }
  276.         $newData = [];
  277.         /** @var Element\ElementInterface $item */
  278.         foreach ($existingData as $item) {
  279.             $key $this->buildUniqueKeyForAppending($item);
  280.             if (!isset($removeMap[$key])) {
  281.                 $newData[] = $item;
  282.             }
  283.         }
  284.         return $newData;
  285.     }
  286.     /**
  287.      * @internal
  288.      *
  289.      * @param Element\ElementInterface $item
  290.      *
  291.      * @return string
  292.      */
  293.     protected function buildUniqueKeyForAppending($item)
  294.     {
  295.         $elementType Element\Service::getElementType($item);
  296.         $id $item->getId();
  297.         return $elementType $id;
  298.     }
  299.     /**
  300.      * {@inheritdoc}
  301.      */
  302.     public function isEqual($array1$array2): bool
  303.     {
  304.         $array1 array_filter(is_array($array1) ? $array1 : []);
  305.         $array2 array_filter(is_array($array2) ? $array2 : []);
  306.         $count1 count($array1);
  307.         $count2 count($array2);
  308.         if ($count1 != $count2) {
  309.             return false;
  310.         }
  311.         $values1 array_values($array1);
  312.         $values2 array_values($array2);
  313.         for ($i 0$i $count1$i++) {
  314.             /** @var Element\ElementInterface $el1 */
  315.             $el1 $values1[$i];
  316.             /** @var Element\ElementInterface $el2 */
  317.             $el2 $values2[$i];
  318.             if (! ($el1->getType() == $el2->getType() && ($el1->getId() == $el2->getId()))) {
  319.                 return false;
  320.             }
  321.         }
  322.         return true;
  323.     }
  324.     /**
  325.      * {@inheritdoc}
  326.      */
  327.     public function supportsDirtyDetection()
  328.     {
  329.         return true;
  330.     }
  331.     /**
  332.      * @internal
  333.      *
  334.      * @param DataObject\Fieldcollection\Data\AbstractData $item
  335.      *
  336.      * @throws \Exception
  337.      */
  338.     protected function loadLazyFieldcollectionField(DataObject\Fieldcollection\Data\AbstractData $item)
  339.     {
  340.         if ($item->getObject()) {
  341.             /** @var DataObject\Fieldcollection|null $container */
  342.             $container $item->getObject()->getObjectVar($item->getFieldname());
  343.             if ($container) {
  344.                 $container->loadLazyField($item->getObject(), $item->getType(), $item->getFieldname(), $item->getIndex(), $this->getName());
  345.             } else {
  346.                 // if container is not available we assume that it is a newly set item
  347.                 $item->markLazyKeyAsLoaded($this->getName());
  348.             }
  349.         }
  350.     }
  351.     /**
  352.      * @internal
  353.      *
  354.      * @param DataObject\Objectbrick\Data\AbstractData $item
  355.      *
  356.      * @throws \Exception
  357.      */
  358.     protected function loadLazyBrickField(DataObject\Objectbrick\Data\AbstractData $item)
  359.     {
  360.         if ($item->getObject()) {
  361.             /** @var DataObject\Objectbrick|null $container */
  362.             $container $item->getObject()->getObjectVar($item->getFieldname());
  363.             if ($container) {
  364.                 $container->loadLazyField($item->getType(), $item->getFieldname(), $this->getName());
  365.             } else {
  366.                 $item->markLazyKeyAsLoaded($this->getName());
  367.             }
  368.         }
  369.     }
  370.     /**
  371.      * checks for multiple assignments and throws an exception in case the rules are violated.
  372.      *
  373.      * @internal
  374.      *
  375.      * @param array|null $data
  376.      *
  377.      * @throws Element\ValidationException
  378.      */
  379.     public function performMultipleAssignmentCheck($data)
  380.     {
  381.         if (is_array($data)) {
  382.             if (!method_exists($this'getAllowMultipleAssignments') || !$this->getAllowMultipleAssignments()) {
  383.                 $relationItems = [];
  384.                 $fieldName $this->getName();
  385.                 foreach ($data as $item) {
  386.                     $elementHash null;
  387.                     if ($item instanceof DataObject\Data\ObjectMetadata || $item instanceof DataObject\Data\ElementMetadata) {
  388.                         if ($item->getElement() instanceof Element\ElementInterface) {
  389.                             $elementHash Element\Service::getElementHash($item->getElement());
  390.                         }
  391.                     } elseif ($item instanceof Element\ElementInterface) {
  392.                         $elementHash Element\Service::getElementHash($item);
  393.                     }
  394.                     if ($elementHash === null) {
  395.                         throw new Element\ValidationException('Passing relations without ID or type not allowed anymore!');
  396.                     } elseif (!isset($relationItems[$elementHash])) {
  397.                         $relationItems[$elementHash] = $item;
  398.                     } else {
  399.                         $message 'Passing relations multiple times not allowed anymore: ' $elementHash
  400.                             ' multiple times in field ' $fieldName;
  401.                         if (method_exists($this'getAllowMultipleAssignments')) {
  402.                             $message .= ", Reason: 'Allow Multiple Assignments' setting is disabled in class definition. ";
  403.                         }
  404.                         throw new Element\ValidationException($message);
  405.                     }
  406.                 }
  407.             }
  408.         }
  409.     }
  410.     /**
  411.      * {@inheritdoc}
  412.      */
  413.     public function getParameterTypeDeclaration(): ?string
  414.     {
  415.         return '?array';
  416.     }
  417.     /**
  418.      * {@inheritdoc}
  419.      */
  420.     public function getReturnTypeDeclaration(): ?string
  421.     {
  422.         return 'array';
  423.     }
  424.     /**
  425.      * @return string|null
  426.      */
  427.     public function getPhpdocInputType(): ?string
  428.     {
  429.         if ($this->getPhpdocType()) {
  430.             return $this->getPhpdocType();
  431.         }
  432.         return null;
  433.     }
  434.     /**
  435.      * {@inheritdoc}
  436.      */
  437.     public function getPhpdocReturnType(): ?string
  438.     {
  439.         if ($phpdocType $this->getPhpdocType()) {
  440.             return $phpdocType;
  441.         }
  442.         return null;
  443.     }
  444.     /**
  445.      * @internal
  446.      *
  447.      * @return string
  448.      */
  449.     abstract protected function getPhpdocType();
  450. }