vendor/pimcore/pimcore/models/DataObject/AbstractObject.php line 1195

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;
  15. use Doctrine\DBAL\Exception\RetryableException;
  16. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  17. use Pimcore\Cache;
  18. use Pimcore\Cache\RuntimeCache;
  19. use Pimcore\Db;
  20. use Pimcore\Event\DataObjectEvents;
  21. use Pimcore\Event\Model\DataObjectEvent;
  22. use Pimcore\Logger;
  23. use Pimcore\Model;
  24. use Pimcore\Model\DataObject;
  25. use Pimcore\Model\Element;
  26. use Pimcore\Model\Element\DuplicateFullPathException;
  27. /**
  28.  * @method AbstractObject\Dao getDao()
  29.  * @method array|null getPermissions(?string $type, Model\User $user, bool $quote = true)
  30.  * @method bool __isBasedOnLatestData()
  31.  * @method string getCurrentFullPath()
  32.  * @method int getChildAmount($objectTypes = [DataObject::OBJECT_TYPE_OBJECT, DataObject::OBJECT_TYPE_FOLDER], Model\User $user = null)
  33.  * @method array getChildPermissions(?string $type, Model\User $user, bool $quote = true)
  34.  */
  35. abstract class AbstractObject extends Model\Element\AbstractElement
  36. {
  37.     const OBJECT_TYPE_FOLDER 'folder';
  38.     const OBJECT_TYPE_OBJECT 'object';
  39.     const OBJECT_TYPE_VARIANT 'variant';
  40.     const OBJECT_CHILDREN_SORT_BY_DEFAULT 'key';
  41.     const OBJECT_CHILDREN_SORT_BY_INDEX 'index';
  42.     const OBJECT_CHILDREN_SORT_ORDER_DEFAULT 'ASC';
  43.     /**
  44.      * possible types of a document
  45.      *
  46.      * @var array
  47.      */
  48.     public static $types = [self::OBJECT_TYPE_FOLDERself::OBJECT_TYPE_OBJECTself::OBJECT_TYPE_VARIANT];
  49.     /**
  50.      * @var bool
  51.      */
  52.     private static $hideUnpublished false;
  53.     /**
  54.      * @var bool
  55.      */
  56.     private static $getInheritedValues false;
  57.     /**
  58.      * @internal
  59.      *
  60.      * @var bool
  61.      */
  62.     protected static $disableDirtyDetection false;
  63.     /**
  64.      * @internal
  65.      *
  66.      * @var string[]
  67.      */
  68.     protected static $objectColumns = ['o_id''o_parentid''o_type''o_key''o_classid''o_classname''o_path'];
  69.     /**
  70.      * @internal
  71.      *
  72.      * @deprecated
  73.      *
  74.      * @var int|null
  75.      */
  76.     protected $o_id;
  77.     /**
  78.      * @internal
  79.      *
  80.      * @deprecated
  81.      *
  82.      * @var int|null
  83.      */
  84.     protected $o_parentId;
  85.     /**
  86.      * @internal
  87.      *
  88.      * @deprecated
  89.      */
  90.     protected $o_parent;
  91.     /**
  92.      * @internal
  93.      *
  94.      * @var string
  95.      */
  96.     protected $o_type 'object';
  97.     /**
  98.      * @internal
  99.      *
  100.      * @var string|null
  101.      */
  102.     protected $o_key;
  103.     /**
  104.      * @internal
  105.      *
  106.      * @deprecated
  107.      *
  108.      * @var string|null
  109.      */
  110.     protected $o_path;
  111.     /**
  112.      * @internal
  113.      *
  114.      * @var int
  115.      */
  116.     protected $o_index 0;
  117.     /**
  118.      * @internal
  119.      *
  120.      * @deprecated
  121.      *
  122.      * @var int|null
  123.      */
  124.     protected $o_creationDate;
  125.     /**
  126.      * @internal
  127.      *
  128.      * @deprecated
  129.      *
  130.      * @var int|null
  131.      */
  132.     protected $o_modificationDate;
  133.     /**
  134.      * @internal
  135.      *
  136.      * @deprecated
  137.      *
  138.      * @var int|null
  139.      */
  140.     protected ?int $o_userOwner null;
  141.     /**
  142.      * @internal
  143.      *
  144.      * @deprecated
  145.      *
  146.      * @var int|null
  147.      */
  148.     protected ?int $o_userModification null;
  149.     /**
  150.      * @internal
  151.      *
  152.      * @var bool[]
  153.      */
  154.     protected $o_hasChildren = [];
  155.     /**
  156.      * Contains a list of sibling documents
  157.      *
  158.      * @internal
  159.      *
  160.      * @var array
  161.      */
  162.     protected $o_siblings = [];
  163.     /**
  164.      * Indicator if object has siblings or not
  165.      *
  166.      * @internal
  167.      *
  168.      * @var bool[]
  169.      */
  170.     protected $o_hasSiblings = [];
  171.     /**
  172.      * @internal
  173.      *
  174.      * @var array
  175.      */
  176.     protected $o_children = [];
  177.     /**
  178.      * @internal
  179.      *
  180.      * @deprecated
  181.      *
  182.      * @var string
  183.      */
  184.     protected $o_locked;
  185.     /**
  186.      * @internal
  187.      *
  188.      * @var string|null
  189.      */
  190.     protected $o_childrenSortBy;
  191.     /**
  192.      * @internal
  193.      *
  194.      * @var string|null
  195.      */
  196.     protected $o_childrenSortOrder;
  197.     /**
  198.      * @internal
  199.      *
  200.      * @deprecated
  201.      *
  202.      * @var int
  203.      */
  204.     protected $o_versionCount 0;
  205.     /**
  206.      * @internal
  207.      *
  208.      * @deprecated
  209.      *
  210.      * @var array|null
  211.      */
  212.     protected $o_properties null;
  213.     public function __construct()
  214.     {
  215.         $this->o_id = & $this->id;
  216.         $this->o_path = & $this->path;
  217.         $this->o_creationDate = & $this->creationDate;
  218.         $this->o_userOwner = & $this->userOwner;
  219.         $this->o_versionCount = & $this->versionCount;
  220.         $this->o_modificationDate = & $this->modificationDate;
  221.         $this->o_locked = & $this->locked;
  222.         $this->o_parent = & $this->parent;
  223.         $this->o_properties = & $this->properties;
  224.         $this->o_userModification = & $this->userModification;
  225.         $this->o_parentId = & $this->parentId;
  226.     }
  227.     /**
  228.      * {@inheritdoc}
  229.      */
  230.     protected function getBlockedVars(): array
  231.     {
  232.         $blockedVars = ['o_hasChildren''o_versions''o_class''scheduledTasks''o_parent''parent''omitMandatoryCheck'];
  233.         if ($this->isInDumpState()) {
  234.             // this is if we want to make a full dump of the object (eg. for a new version), including children for recyclebin
  235.             $blockedVars array_merge($blockedVars, ['o_dirtyFields']);
  236.         } else {
  237.             // this is if we want to cache the object
  238.             $blockedVars array_merge($blockedVars, ['o_children''properties''o_properties']);
  239.         }
  240.         return $blockedVars;
  241.     }
  242.     /**
  243.      * @static
  244.      *
  245.      * @return bool
  246.      */
  247.     public static function getHideUnpublished()
  248.     {
  249.         return self::$hideUnpublished;
  250.     }
  251.     /**
  252.      * @static
  253.      *
  254.      * @param bool $hideUnpublished
  255.      */
  256.     public static function setHideUnpublished($hideUnpublished)
  257.     {
  258.         self::$hideUnpublished $hideUnpublished;
  259.     }
  260.     /**
  261.      * @static
  262.      *
  263.      * @return bool
  264.      */
  265.     public static function doHideUnpublished()
  266.     {
  267.         return self::$hideUnpublished;
  268.     }
  269.     /**
  270.      * @static
  271.      *
  272.      * @param bool $getInheritedValues
  273.      */
  274.     public static function setGetInheritedValues($getInheritedValues)
  275.     {
  276.         self::$getInheritedValues $getInheritedValues;
  277.     }
  278.     /**
  279.      * @static
  280.      *
  281.      * @return bool
  282.      */
  283.     public static function getGetInheritedValues()
  284.     {
  285.         return self::$getInheritedValues;
  286.     }
  287.     /**
  288.      * @static
  289.      *
  290.      * @param Concrete|null $object
  291.      *
  292.      * @return bool
  293.      */
  294.     public static function doGetInheritedValues(Concrete $object null)
  295.     {
  296.         if (self::$getInheritedValues && $object !== null) {
  297.             $class $object->getClass();
  298.             return $class->getAllowInherit();
  299.         }
  300.         return self::$getInheritedValues;
  301.     }
  302.     /**
  303.      * get possible types
  304.      *
  305.      * @return array
  306.      */
  307.     public static function getTypes()
  308.     {
  309.         return self::$types;
  310.     }
  311.     /**
  312.      * Static helper to get an object by the passed ID
  313.      *
  314.      * @param int|string $id
  315.      * @param array|bool $force
  316.      *
  317.      * @return static|null
  318.      */
  319.     public static function getById($id$force false)
  320.     {
  321.         if (!is_numeric($id) || $id 1) {
  322.             return null;
  323.         }
  324.         $id = (int)$id;
  325.         $cacheKey self::getCacheKey($id);
  326.         $params Model\Element\Service::prepareGetByIdParams($force__METHOD__func_num_args() > 1);
  327.         if (!$params['force'] && RuntimeCache::isRegistered($cacheKey)) {
  328.             $object RuntimeCache::get($cacheKey);
  329.             if ($object && static::typeMatch($object)) {
  330.                 return $object;
  331.             }
  332.         }
  333.         if ($params['force'] || !($object Cache::load($cacheKey))) {
  334.             $object = new Model\DataObject();
  335.             try {
  336.                 $typeInfo $object->getDao()->getTypeById($id);
  337.                 if (!empty($typeInfo['o_type']) && in_array($typeInfo['o_type'], DataObject::$types)) {
  338.                     if ($typeInfo['o_type'] == DataObject::OBJECT_TYPE_FOLDER) {
  339.                         $className Folder::class;
  340.                     } else {
  341.                         $className 'Pimcore\\Model\\DataObject\\' ucfirst($typeInfo['o_className']);
  342.                     }
  343.                     /** @var AbstractObject $object */
  344.                     $object self::getModelFactory()->build($className);
  345.                     RuntimeCache::set($cacheKey$object);
  346.                     $object->getDao()->getById($id);
  347.                     $object->__setDataVersionTimestamp($object->getModificationDate());
  348.                     Service::recursiveResetDirtyMap($object);
  349.                     // force loading of relation data
  350.                     if ($object instanceof Concrete) {
  351.                         $object->__getRawRelationData();
  352.                     }
  353.                     Cache::save($object$cacheKey);
  354.                 } else {
  355.                     throw new Model\Exception\NotFoundException('No entry for object id ' $id);
  356.                 }
  357.             } catch (Model\Exception\NotFoundException $e) {
  358.                 return null;
  359.             }
  360.         } else {
  361.             RuntimeCache::set($cacheKey$object);
  362.         }
  363.         if (!$object || !static::typeMatch($object)) {
  364.             return null;
  365.         }
  366.         \Pimcore::getEventDispatcher()->dispatch(
  367.             new DataObjectEvent($object, ['params' => $params]),
  368.             DataObjectEvents::POST_LOAD
  369.         );
  370.         return $object;
  371.     }
  372.     /**
  373.      * @param string $path
  374.      * @param array|bool $force
  375.      *
  376.      * @return static|null
  377.      */
  378.     public static function getByPath($path$force false)
  379.     {
  380.         if (!$path) {
  381.             return null;
  382.         }
  383.         $path Model\Element\Service::correctPath($path);
  384.         try {
  385.             $object = new static();
  386.             $object->getDao()->getByPath($path);
  387.             return static::getById($object->getId(), Model\Element\Service::prepareGetByIdParams($force__METHOD__func_num_args() > 1));
  388.         } catch (Model\Exception\NotFoundException $e) {
  389.             return null;
  390.         }
  391.     }
  392.     /**
  393.      * @param array $config
  394.      *
  395.      * @return DataObject\Listing
  396.      *
  397.      * @throws \Exception
  398.      */
  399.     public static function getList($config = [])
  400.     {
  401.         $className DataObject::class;
  402.         // get classname
  403.         if (!in_array(static::class, [__CLASS__Concrete::class, Folder::class], true)) {
  404.             /** @var Concrete $tmpObject */
  405.             $tmpObject = new static();
  406.             if ($tmpObject instanceof Concrete) {
  407.                 $className 'Pimcore\\Model\\DataObject\\' ucfirst($tmpObject->getClassName());
  408.             }
  409.         }
  410.         if (is_array($config)) {
  411.             if (!empty($config['class'])) {
  412.                 $className ltrim($config['class'], '\\');
  413.             }
  414.             if ($className) {
  415.                 $listClass $className '\\Listing';
  416.                 /** @var DataObject\Listing $list */
  417.                 $list self::getModelFactory()->build($listClass);
  418.                 $list->setValues($config);
  419.                 return $list;
  420.             }
  421.         }
  422.         throw new \Exception('Unable to initiate list class - class not found or invalid configuration');
  423.     }
  424.     /**
  425.      * @deprecated will be removed in Pimcore 11
  426.      *
  427.      * @param array $config
  428.      *
  429.      * @return int total count
  430.      */
  431.     public static function getTotalCount($config = [])
  432.     {
  433.         $list = static::getList($config);
  434.         $count $list->getTotalCount();
  435.         return $count;
  436.     }
  437.     /**
  438.      * @internal
  439.      *
  440.      * @param AbstractObject $object
  441.      *
  442.      * @return bool
  443.      */
  444.     protected static function typeMatch(AbstractObject $object)
  445.     {
  446.         if (static::class === Concrete::class && !$object instanceof static) {
  447.             trigger_deprecation(
  448.                 'pimcore/pimcore',
  449.                 '10.5',
  450.                 'Loading non-Concrete objects with the Concrete class will not be possible in Pimcore 11'
  451.             );
  452.             return true;
  453.         }
  454.         return static::class === self::class || $object instanceof static;
  455.     }
  456.     /**
  457.      * @param array $objectTypes
  458.      * @param bool $includingUnpublished
  459.      *
  460.      * @return DataObject[]
  461.      */
  462.     public function getChildren(array $objectTypes = [self::OBJECT_TYPE_OBJECTself::OBJECT_TYPE_FOLDER], $includingUnpublished false)
  463.     {
  464.         $cacheKey $this->getListingCacheKey(func_get_args());
  465.         if (!isset($this->o_children[$cacheKey])) {
  466.             if ($this->getId()) {
  467.                 $list = new Listing();
  468.                 $list->setUnpublished($includingUnpublished);
  469.                 $list->setCondition('o_parentId = ?'$this->getId());
  470.                 $list->setOrderKey(sprintf('o_%s'$this->getChildrenSortBy()));
  471.                 $list->setOrder($this->getChildrenSortOrder());
  472.                 $list->setObjectTypes($objectTypes);
  473.                 $this->o_children[$cacheKey] = $list->load();
  474.                 $this->o_hasChildren[$cacheKey] = (bool) count($this->o_children[$cacheKey]);
  475.             } else {
  476.                 $this->o_children[$cacheKey] = [];
  477.                 $this->o_hasChildren[$cacheKey] = false;
  478.             }
  479.         }
  480.         return $this->o_children[$cacheKey];
  481.     }
  482.     /**
  483.      * Quick test if there are children
  484.      *
  485.      * @param array $objectTypes
  486.      * @param bool|null $includingUnpublished
  487.      *
  488.      * @return bool
  489.      */
  490.     public function hasChildren($objectTypes = [self::OBJECT_TYPE_OBJECTself::OBJECT_TYPE_FOLDER], $includingUnpublished null)
  491.     {
  492.         $cacheKey $this->getListingCacheKey(func_get_args());
  493.         if (isset($this->o_hasChildren[$cacheKey])) {
  494.             return $this->o_hasChildren[$cacheKey];
  495.         }
  496.         return $this->o_hasChildren[$cacheKey] = $this->getDao()->hasChildren($objectTypes$includingUnpublished);
  497.     }
  498.     /**
  499.      * Get a list of the sibling documents
  500.      *
  501.      * @param array $objectTypes
  502.      * @param bool $includingUnpublished
  503.      *
  504.      * @return array
  505.      */
  506.     public function getSiblings(array $objectTypes = [self::OBJECT_TYPE_OBJECTself::OBJECT_TYPE_FOLDER], $includingUnpublished false)
  507.     {
  508.         $cacheKey $this->getListingCacheKey(func_get_args());
  509.         if (!isset($this->o_siblings[$cacheKey])) {
  510.             if ($this->getParentId()) {
  511.                 $list = new Listing();
  512.                 $list->setUnpublished($includingUnpublished);
  513.                 $list->addConditionParam('o_parentId = ?'$this->getParentId());
  514.                 if ($this->getId()) {
  515.                     $list->addConditionParam('o_id != ?'$this->getId());
  516.                 }
  517.                 $list->setOrderKey('o_key');
  518.                 $list->setObjectTypes($objectTypes);
  519.                 $list->setOrder('asc');
  520.                 $this->o_siblings[$cacheKey] = $list->load();
  521.                 $this->o_hasSiblings[$cacheKey] = (bool) count($this->o_siblings[$cacheKey]);
  522.             } else {
  523.                 $this->o_siblings[$cacheKey] = [];
  524.                 $this->o_hasSiblings[$cacheKey] = false;
  525.             }
  526.         }
  527.         return $this->o_siblings[$cacheKey];
  528.     }
  529.     /**
  530.      * Returns true if the object has at least one sibling
  531.      *
  532.      * @param array $objectTypes
  533.      * @param bool|null $includingUnpublished
  534.      *
  535.      * @return bool
  536.      */
  537.     public function hasSiblings($objectTypes = [self::OBJECT_TYPE_OBJECTself::OBJECT_TYPE_FOLDER], $includingUnpublished null)
  538.     {
  539.         $cacheKey $this->getListingCacheKey(func_get_args());
  540.         if (isset($this->o_hasSiblings[$cacheKey])) {
  541.             return $this->o_hasSiblings[$cacheKey];
  542.         }
  543.         return $this->o_hasSiblings[$cacheKey] = $this->getDao()->hasSiblings($objectTypes$includingUnpublished);
  544.     }
  545.     /**
  546.      * @internal
  547.      *
  548.      * @throws \Exception
  549.      */
  550.     protected function doDelete()
  551.     {
  552.         // delete children
  553.         $children $this->getChildren(self::$typestrue);
  554.         if (count($children) > 0) {
  555.             foreach ($children as $child) {
  556.                 $child->delete();
  557.             }
  558.         }
  559.         // remove dependencies
  560.         $d = new Model\Dependency;
  561.         $d->cleanAllForElement($this);
  562.         // remove all properties
  563.         $this->getDao()->deleteAllProperties();
  564.     }
  565.     /**
  566.      * @throws \Exception
  567.      */
  568.     public function delete()
  569.     {
  570.         $this->dispatchEvent(new DataObjectEvent($this), DataObjectEvents::PRE_DELETE);
  571.         $this->beginTransaction();
  572.         try {
  573.             $this->doDelete();
  574.             $this->getDao()->delete();
  575.             $this->commit();
  576.             //clear parent data from registry
  577.             $parentCacheKey self::getCacheKey($this->getParentId());
  578.             if (RuntimeCache::isRegistered($parentCacheKey)) {
  579.                 /** @var AbstractObject $parent * */
  580.                 $parent RuntimeCache::get($parentCacheKey);
  581.                 if ($parent instanceof self) {
  582.                     $parent->setChildren(null);
  583.                 }
  584.             }
  585.         } catch (\Exception $e) {
  586.             try {
  587.                 $this->rollBack();
  588.             } catch (\Exception $er) {
  589.                 // PDO adapter throws exceptions if rollback fails
  590.                 Logger::info((string) $er);
  591.             }
  592.             $failureEvent = new DataObjectEvent($this);
  593.             $failureEvent->setArgument('exception'$e);
  594.             $this->dispatchEvent($failureEventDataObjectEvents::POST_DELETE_FAILURE);
  595.             Logger::crit((string) $e);
  596.             throw $e;
  597.         }
  598.         // empty object cache
  599.         $this->clearDependentCache();
  600.         //clear object from registry
  601.         RuntimeCache::set(self::getCacheKey($this->getId()), null);
  602.         $this->dispatchEvent(new DataObjectEvent($this), DataObjectEvents::POST_DELETE);
  603.     }
  604.     /**
  605.      * @return $this
  606.      *
  607.      * @throws \Exception
  608.      */
  609.     public function save()
  610.     {
  611.         // additional parameters (e.g. "versionNote" for the version note)
  612.         $params = [];
  613.         if (func_num_args() && is_array(func_get_arg(0))) {
  614.             $params func_get_arg(0);
  615.         }
  616.         $isUpdate false;
  617.         $differentOldPath null;
  618.         try {
  619.             $isDirtyDetectionDisabled self::isDirtyDetectionDisabled();
  620.             $preEvent = new DataObjectEvent($this$params);
  621.             if ($this->getId()) {
  622.                 $isUpdate true;
  623.                 $this->dispatchEvent($preEventDataObjectEvents::PRE_UPDATE);
  624.             } else {
  625.                 self::disableDirtyDetection();
  626.                 $this->dispatchEvent($preEventDataObjectEvents::PRE_ADD);
  627.             }
  628.             $params $preEvent->getArguments();
  629.             $this->correctPath();
  630.             // we wrap the save actions in a loop here, so that we can restart the database transactions in the case it fails
  631.             // if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out
  632.             // this is especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...)
  633.             $maxRetries 5;
  634.             for ($retries 0$retries $maxRetries$retries++) {
  635.                 // be sure that unpublished objects in relations are saved also in frontend mode, eg. in importers, ...
  636.                 $hideUnpublishedBackup self::getHideUnpublished();
  637.                 self::setHideUnpublished(false);
  638.                 $this->beginTransaction();
  639.                 try {
  640.                     if (!in_array($this->getType(), self::$types)) {
  641.                         throw new \Exception('invalid object type given: [' $this->getType() . ']');
  642.                     }
  643.                     if (!$isUpdate) {
  644.                         $this->getDao()->create();
  645.                     }
  646.                     // get the old path from the database before the update is done
  647.                     $oldPath null;
  648.                     if ($isUpdate) {
  649.                         $oldPath $this->getDao()->getCurrentFullPath();
  650.                     }
  651.                     // if the old path is different from the new path, update all children
  652.                     // we need to do the update of the children's path before $this->update() because the
  653.                     // inheritance helper needs the correct paths of the children in InheritanceHelper::buildTree()
  654.                     $updatedChildren = [];
  655.                     if ($oldPath && $oldPath != $this->getRealFullPath()) {
  656.                         $differentOldPath $oldPath;
  657.                         $this->getDao()->updateWorkspaces();
  658.                         $updatedChildren $this->getDao()->updateChildPaths($oldPath);
  659.                     }
  660.                     $this->update($isUpdate$params);
  661.                     self::setHideUnpublished($hideUnpublishedBackup);
  662.                     $this->commit();
  663.                     break; // transaction was successfully completed, so we cancel the loop here -> no restart required
  664.                 } catch (\Exception $e) {
  665.                     try {
  666.                         $this->rollBack();
  667.                     } catch (\Exception $er) {
  668.                         // PDO adapter throws exceptions if rollback fails
  669.                         Logger::info((string) $er);
  670.                     }
  671.                     // set "HideUnpublished" back to the value it was originally
  672.                     self::setHideUnpublished($hideUnpublishedBackup);
  673.                     if ($e instanceof UniqueConstraintViolationException) {
  674.                         throw new Element\ValidationException('unique constraint violation'0$e);
  675.                     }
  676.                     if ($e instanceof RetryableException) {
  677.                         // we try to start the transaction $maxRetries times again (deadlocks, ...)
  678.                         if ($retries < ($maxRetries 1)) {
  679.                             $run $retries 1;
  680.                             $waitTime random_int(15) * 100000// microseconds
  681.                             Logger::warn('Unable to finish transaction (' $run ". run) because of the following reason '" $e->getMessage() . "'. --> Retrying in " $waitTime ' microseconds ... (' . ($run 1) . ' of ' $maxRetries ')');
  682.                             usleep($waitTime); // wait specified time until we restart the transaction
  683.                         } else {
  684.                             // if the transaction still fail after $maxRetries retries, we throw out the exception
  685.                             Logger::error('Finally giving up restarting the same transaction again and again, last message: ' $e->getMessage());
  686.                             throw $e;
  687.                         }
  688.                     } else {
  689.                         throw $e;
  690.                     }
  691.                 }
  692.             }
  693.             $additionalTags = [];
  694.             if (isset($updatedChildren) && is_array($updatedChildren)) {
  695.                 foreach ($updatedChildren as $objectId) {
  696.                     $tag 'object_' $objectId;
  697.                     $additionalTags[] = $tag;
  698.                     // remove the child also from registry (internal cache) to avoid path inconsistencies during long running scripts, such as CLI
  699.                     RuntimeCache::set($tagnull);
  700.                 }
  701.             }
  702.             $this->clearDependentCache($additionalTags);
  703.             $postEvent = new DataObjectEvent($this$params);
  704.             if ($isUpdate) {
  705.                 if ($differentOldPath) {
  706.                     $postEvent->setArgument('oldPath'$differentOldPath);
  707.                 }
  708.                 $this->dispatchEvent($postEventDataObjectEvents::POST_UPDATE);
  709.             } else {
  710.                 self::setDisableDirtyDetection($isDirtyDetectionDisabled);
  711.                 $this->dispatchEvent($postEventDataObjectEvents::POST_ADD);
  712.             }
  713.             return $this;
  714.         } catch (\Exception $e) {
  715.             $failureEvent = new DataObjectEvent($this$params);
  716.             $failureEvent->setArgument('exception'$e);
  717.             if ($isUpdate) {
  718.                 $this->dispatchEvent($failureEventDataObjectEvents::POST_UPDATE_FAILURE);
  719.             } else {
  720.                 $this->dispatchEvent($failureEventDataObjectEvents::POST_ADD_FAILURE);
  721.             }
  722.             throw $e;
  723.         }
  724.     }
  725.     /**
  726.      * @internal
  727.      *
  728.      * @throws \Exception|DuplicateFullPathException
  729.      */
  730.     protected function correctPath()
  731.     {
  732.         // set path
  733.         if ($this->getId() != 1) { // not for the root node
  734.             if (!Element\Service::isValidKey($this->getKey(), 'object')) {
  735.                 throw new \Exception('invalid key for object with id [ '.$this->getId().' ] key is: [' $this->getKey() . ']');
  736.             }
  737.             if ($this->getParentId() == $this->getId()) {
  738.                 throw new \Exception("ParentID and ID is identical, an element can't be the parent of itself.");
  739.             }
  740.             $parent DataObject::getById($this->getParentId());
  741.             if ($parent) {
  742.                 // use the parent's path from the database here (getCurrentFullPath), to ensure the path really exists and does not rely on the path
  743.                 // that is currently in the parent object (in memory), because this might have changed but wasn't not saved
  744.                 $this->setPath(str_replace('//''/'$parent->getCurrentFullPath().'/'));
  745.             } else {
  746.                 trigger_deprecation(
  747.                     'pimcore/pimcore',
  748.                     '10.5',
  749.                     'Fallback for parentId will be removed in Pimcore 11.',
  750.                 );
  751.                 // parent document doesn't exist anymore, set the parent to to root
  752.                 $this->setParentId(1);
  753.                 $this->setPath('/');
  754.             }
  755.             if (strlen($this->getKey()) < 1) {
  756.                 throw new \Exception('DataObject requires key');
  757.             }
  758.         } elseif ($this->getId() == 1) {
  759.             // some data in root node should always be the same
  760.             $this->setParentId(0);
  761.             $this->setPath('/');
  762.             $this->setKey('');
  763.             $this->setType(DataObject::OBJECT_TYPE_FOLDER);
  764.         }
  765.         if (Service::pathExists($this->getRealFullPath())) {
  766.             $duplicate DataObject::getByPath($this->getRealFullPath());
  767.             if ($duplicate instanceof self && $duplicate->getId() != $this->getId()) {
  768.                 $duplicateFullPathException = new DuplicateFullPathException('Duplicate full path [ '.$this->getRealFullPath().' ] - cannot save object');
  769.                 $duplicateFullPathException->setDuplicateElement($duplicate);
  770.                 throw $duplicateFullPathException;
  771.             }
  772.         }
  773.         $this->validatePathLength();
  774.     }
  775.     /**
  776.      * @internal
  777.      *
  778.      * @param bool|null $isUpdate
  779.      * @param array $params
  780.      *
  781.      * @throws \Exception
  782.      */
  783.     protected function update($isUpdate null$params = [])
  784.     {
  785.         $this->updateModificationInfos();
  786.         // save properties
  787.         $this->getProperties();
  788.         $this->getDao()->deleteAllProperties();
  789.         if (is_array($this->getProperties()) && count($this->getProperties()) > 0) {
  790.             foreach ($this->getProperties() as $property) {
  791.                 if (!$property->getInherited()) {
  792.                     $property->setDao(null);
  793.                     $property->setCid($this->getId());
  794.                     $property->setCtype('object');
  795.                     $property->setCpath($this->getRealFullPath());
  796.                     $property->save();
  797.                 }
  798.             }
  799.         }
  800.         // save dependencies
  801.         $d = new Model\Dependency();
  802.         $d->setSourceType('object');
  803.         $d->setSourceId($this->getId());
  804.         foreach ($this->resolveDependencies() as $requirement) {
  805.             if ($requirement['id'] == $this->getId() && $requirement['type'] === 'object') {
  806.                 // dont't add a reference to yourself
  807.                 continue;
  808.             }
  809.             $d->addRequirement($requirement['id'], $requirement['type']);
  810.         }
  811.         $d->save();
  812.         //set object to registry
  813.         RuntimeCache::set(self::getCacheKey($this->getId()), $this);
  814.     }
  815.     /**
  816.      * {@inheritdoc}
  817.      */
  818.     public function clearDependentCache($additionalTags = [])
  819.     {
  820.         self::clearDependentCacheByObjectId($this->getId(), $additionalTags);
  821.     }
  822.     /**
  823.      * @internal
  824.      *
  825.      * @param int $objectId
  826.      * @param array $additionalTags
  827.      */
  828.     public static function clearDependentCacheByObjectId($objectId$additionalTags = [])
  829.     {
  830.         if (!$objectId) {
  831.             throw new \Exception('object ID missing');
  832.         }
  833.         try {
  834.             $tags = ['object_' $objectId'object_properties''output'];
  835.             $tags array_merge($tags$additionalTags);
  836.             Cache::clearTags($tags);
  837.         } catch (\Exception $e) {
  838.             Logger::crit((string) $e);
  839.         }
  840.     }
  841.     /**
  842.      * @internal
  843.      *
  844.      * @param int $index
  845.      */
  846.     public function saveIndex($index)
  847.     {
  848.         $this->getDao()->saveIndex($index);
  849.         $this->clearDependentCache();
  850.     }
  851.     /**
  852.      * @return string
  853.      */
  854.     public function getFullPath()
  855.     {
  856.         $path $this->getPath() . $this->getKey();
  857.         return $path;
  858.     }
  859.     /**
  860.      * @return string
  861.      */
  862.     public function getRealPath()
  863.     {
  864.         return $this->getPath();
  865.     }
  866.     /**
  867.      * @return string
  868.      */
  869.     public function getRealFullPath()
  870.     {
  871.         return $this->getFullPath();
  872.     }
  873.     /**
  874.      * @return int|null
  875.      */
  876.     public function getParentId()
  877.     {
  878.         $parentId parent::getParentId();
  879.         // fall back to parent if no ID is set but we have a parent object
  880.         if (!$parentId && $this->parent) {
  881.             return $this->parent->getId();
  882.         }
  883.         return $parentId;
  884.     }
  885.     /**
  886.      * @return string
  887.      */
  888.     public function getType()
  889.     {
  890.         return $this->o_type;
  891.     }
  892.     /**
  893.      * @return string|null
  894.      */
  895.     public function getKey()
  896.     {
  897.         return $this->o_key;
  898.     }
  899.     /**
  900.      * @return int
  901.      */
  902.     public function getIndex()
  903.     {
  904.         return $this->o_index;
  905.     }
  906.     /**
  907.      * @param int $parentId
  908.      *
  909.      * @return $this
  910.      */
  911.     public function setParentId($parentId)
  912.     {
  913.         $parentId = (int) $parentId;
  914.         if ($parentId != $this->parentId) {
  915.             $this->markFieldDirty('parentId');
  916.         }
  917.         parent::setParentId($parentId);
  918.         $this->o_siblings = [];
  919.         $this->o_hasSiblings = [];
  920.         return $this;
  921.     }
  922.     /**
  923.      * @param string $o_type
  924.      *
  925.      * @return $this
  926.      */
  927.     public function setType($o_type)
  928.     {
  929.         $this->o_type $o_type;
  930.         return $this;
  931.     }
  932.     /**
  933.      * @param string $o_key
  934.      *
  935.      * @return $this
  936.      */
  937.     public function setKey($o_key)
  938.     {
  939.         $this->o_key = (string)$o_key;
  940.         return $this;
  941.     }
  942.     /**
  943.      * @param int $o_index
  944.      *
  945.      * @return $this
  946.      */
  947.     public function setIndex($o_index)
  948.     {
  949.         $this->o_index = (int) $o_index;
  950.         return $this;
  951.     }
  952.     /**
  953.      * @param string|null $childrenSortBy
  954.      */
  955.     public function setChildrenSortBy($childrenSortBy)
  956.     {
  957.         if ($this->o_childrenSortBy !== $childrenSortBy) {
  958.             $this->o_children = [];
  959.             $this->o_hasChildren = [];
  960.         }
  961.         $this->o_childrenSortBy $childrenSortBy;
  962.     }
  963.     /**
  964.      * @param DataObject[]|null $children
  965.      * @param array $objectTypes
  966.      * @param bool $includingUnpublished
  967.      *
  968.      * @return $this
  969.      */
  970.     public function setChildren($children, array $objectTypes = [self::OBJECT_TYPE_OBJECTself::OBJECT_TYPE_FOLDER], $includingUnpublished false)
  971.     {
  972.         if ($children === null) {
  973.             // unset all cached children
  974.             $this->o_children = [];
  975.             $this->o_hasChildren = [];
  976.         } elseif (is_array($children)) {
  977.             //default cache key
  978.             $cacheKey $this->getListingCacheKey([$objectTypes$includingUnpublished]);
  979.             $this->o_children[$cacheKey] = $children;
  980.             $this->o_hasChildren[$cacheKey] = (bool) count($children);
  981.         }
  982.         return $this;
  983.     }
  984.     /**
  985.      * @return self|null
  986.      */
  987.     public function getParent() /** : ?self **/
  988.     {
  989.         $parent parent::getParent();
  990.         return $parent instanceof AbstractObject $parent null;
  991.     }
  992.     /**
  993.      * @param self|null $parent
  994.      *
  995.      * @return $this
  996.      */
  997.     public function setParent($parent)
  998.     {
  999.         $newParentId $parent instanceof self $parent->getId() : 0;
  1000.         $this->setParentId($newParentId);
  1001.         $this->parent $parent;
  1002.         return $this;
  1003.     }
  1004.     /**
  1005.      * @return string
  1006.      */
  1007.     public function getChildrenSortBy()
  1008.     {
  1009.         return $this->o_childrenSortBy ?? self::OBJECT_CHILDREN_SORT_BY_DEFAULT;
  1010.     }
  1011.     /**
  1012.      * @param string $method
  1013.      * @param array $args
  1014.      *
  1015.      * @return mixed
  1016.      *
  1017.      * @throws \Exception
  1018.      */
  1019.     public function __call($method$args)
  1020.     {
  1021.         // compatibility mode (they do not have any set_oXyz() methods anymore)
  1022.         if (preg_match('/^(get|set)o_/i'$method)) {
  1023.             $newMethod preg_replace('/^(get|set)o_/i''$1'$method);
  1024.             trigger_deprecation(
  1025.                 'pimcore/pimcore',
  1026.                 '10.6',
  1027.                 sprintf('using "%s" is deprecated, please use "%s" instead. using "%s" wont work in Pimcore 11.'$method$newMethod$method)
  1028.             );
  1029.             if (method_exists($this$newMethod)) {
  1030.                 $r call_user_func_array([$this$newMethod], $args);
  1031.                 return $r;
  1032.             }
  1033.         }
  1034.         return parent::__call($method$args);
  1035.     }
  1036.     /**
  1037.      * @return bool
  1038.      */
  1039.     public static function doNotRestoreKeyAndPath()
  1040.     {
  1041.         return self::$doNotRestoreKeyAndPath;
  1042.     }
  1043.     /**
  1044.      * @param bool $doNotRestoreKeyAndPath
  1045.      */
  1046.     public static function setDoNotRestoreKeyAndPath($doNotRestoreKeyAndPath)
  1047.     {
  1048.         self::$doNotRestoreKeyAndPath $doNotRestoreKeyAndPath;
  1049.     }
  1050.     /**
  1051.      * @param string $fieldName
  1052.      * @param string|null $language
  1053.      *
  1054.      * @throws \Exception
  1055.      *
  1056.      * @return mixed
  1057.      */
  1058.     public function get($fieldName$language null)
  1059.     {
  1060.         if (!$fieldName) {
  1061.             throw new \Exception('Field name must not be empty.');
  1062.         }
  1063.         return $this->{'get'.ucfirst($fieldName)}($language);
  1064.     }
  1065.     /**
  1066.      * @param string $fieldName
  1067.      * @param mixed $value
  1068.      * @param string|null $language
  1069.      *
  1070.      * @throws \Exception
  1071.      *
  1072.      * @return mixed
  1073.      */
  1074.     public function set($fieldName$value$language null)
  1075.     {
  1076.         if (!$fieldName) {
  1077.             throw new \Exception('Field name must not be empty.');
  1078.         }
  1079.         return $this->{'set'.ucfirst($fieldName)}($value$language);
  1080.     }
  1081.     /**
  1082.      * @internal
  1083.      *
  1084.      * @return bool
  1085.      */
  1086.     public static function isDirtyDetectionDisabled()
  1087.     {
  1088.         return self::$disableDirtyDetection;
  1089.     }
  1090.     /**
  1091.      * @internal
  1092.      *
  1093.      * @param bool $disableDirtyDetection
  1094.      */
  1095.     public static function setDisableDirtyDetection(bool $disableDirtyDetection)
  1096.     {
  1097.         self::$disableDirtyDetection $disableDirtyDetection;
  1098.     }
  1099.     /**
  1100.      * @internal
  1101.      */
  1102.     public static function disableDirtyDetection()
  1103.     {
  1104.         self::setDisableDirtyDetection(true);
  1105.     }
  1106.     /**
  1107.      * @internal
  1108.      */
  1109.     public static function enableDirtyDetection()
  1110.     {
  1111.         self::setDisableDirtyDetection(false);
  1112.     }
  1113.     /**
  1114.      * @internal
  1115.      *
  1116.      * @param array $args
  1117.      *
  1118.      * @return string
  1119.      */
  1120.     protected function getListingCacheKey(array $args = [])
  1121.     {
  1122.         $objectTypes $args[0] ?? [self::OBJECT_TYPE_OBJECTself::OBJECT_TYPE_FOLDER];
  1123.         $includingUnpublished = (bool)($args[1] ?? false);
  1124.         if (is_array($objectTypes)) {
  1125.             $objectTypes implode('_'$objectTypes);
  1126.         }
  1127.         $cacheKey $objectTypes . (!empty($includingUnpublished) ? '_' '') . (string)$includingUnpublished;
  1128.         return $cacheKey;
  1129.     }
  1130.     /**
  1131.      * @param string | null $o_reverseSort
  1132.      *
  1133.      * @return AbstractObject
  1134.      */
  1135.     public function setChildrenSortOrder(?string $o_reverseSort): Element\ElementInterface
  1136.     {
  1137.         $this->o_childrenSortOrder $o_reverseSort;
  1138.         return $this;
  1139.     }
  1140.     /**
  1141.      * @return string
  1142.      */
  1143.     public function getChildrenSortOrder(): string
  1144.     {
  1145.         return $this->o_childrenSortOrder ?? self::OBJECT_CHILDREN_SORT_ORDER_DEFAULT;
  1146.     }
  1147.     /**
  1148.      * load lazy loaded fields before cloning
  1149.      */
  1150.     public function __clone()
  1151.     {
  1152.         parent::__clone();
  1153.         // renew references when cloning
  1154.         foreach (['id''path''creationDate''userOwner''versionCount''modificationDate''locked''parent''properties''userModification''parentId'] as $referenceField) {
  1155.             $oldValue $this->$referenceField;
  1156.             unset($this->$referenceField);
  1157.             $this->$referenceField $oldValue;
  1158.             $this->{'o_'.$referenceField} = &$this->$referenceField;
  1159.         }
  1160.         $this->o_parent null;
  1161.         // note that o_children is currently needed for the recycle bin
  1162.         $this->o_hasSiblings = [];
  1163.         $this->o_siblings = [];
  1164.     }
  1165.     /**
  1166.      * @param string $method
  1167.      * @param array $arguments
  1168.      *
  1169.      * @return mixed
  1170.      *
  1171.      * @throws \Exception
  1172.      */
  1173.     public static function __callStatic($method$arguments)
  1174.     {
  1175.         $propertyName lcfirst(preg_replace('/^getBy/i'''$method));
  1176.         $realPropertyName 'o_'.$propertyName;
  1177.         $db \Pimcore\Db::get();
  1178.         if (in_array(strtolower($realPropertyName), self::$objectColumns)) {
  1179.             $arguments array_pad($arguments40);
  1180.             [$value$limit$offset$objectTypes] = $arguments;
  1181.             $defaultCondition $realPropertyName.' = '.Db::get()->quote($value).' ';
  1182.             $listConfig = [
  1183.                 'condition' => $defaultCondition,
  1184.             ];
  1185.             if (!is_array($limit)) {
  1186.                 if ($limit) {
  1187.                     $listConfig['limit'] = $limit;
  1188.                 }
  1189.                 if ($offset) {
  1190.                     $listConfig['offset'] = $offset;
  1191.                 }
  1192.             } else {
  1193.                 $listConfig array_merge($listConfig$limit);
  1194.                 $limitCondition $limit['condition'] ?? '';
  1195.                 $listConfig['condition'] = $defaultCondition.$limitCondition;
  1196.             }
  1197.             $list = static::makeList($listConfig$objectTypes);
  1198.             if (isset($listConfig['limit']) && $listConfig['limit'] == 1) {
  1199.                 $elements $list->getObjects();
  1200.                 return isset($elements[0]) ? $elements[0] : null;
  1201.             }
  1202.             return $list;
  1203.         }
  1204.         // there is no property for the called method, so throw an exception
  1205.         Logger::error('Class: DataObject\\AbstractObject => call to undefined static method ' $method);
  1206.         throw new \Exception('Call to undefined static method ' $method ' in class DataObject\\AbstractObject');
  1207.     }
  1208.     /**
  1209.      * @param  array  $listConfig
  1210.      * @param  mixed $objectTypes
  1211.      *
  1212.      * @return Listing
  1213.      *
  1214.      * @throws \Exception
  1215.      */
  1216.     protected static function makeList(array $listConfigmixed $objectTypes): Listing
  1217.     {
  1218.         $list = static::getList($listConfig);
  1219.         // Check if variants, in addition to objects, to be fetched
  1220.         if (!empty($objectTypes)) {
  1221.             if (\array_diff($objectTypes, [static::OBJECT_TYPE_VARIANT, static::OBJECT_TYPE_OBJECT])) {
  1222.                 Logger::error('Class: DataObject\\AbstractObject => Unsupported object type in array ' implode(','$objectTypes));
  1223.                 throw new \Exception('Unsupported object type in array [' implode(','$objectTypes) . '] in class DataObject\\AbstractObject');
  1224.             }
  1225.             $list->setObjectTypes($objectTypes);
  1226.         }
  1227.         return $list;
  1228.     }
  1229.     public function __wakeup()
  1230.     {
  1231.         $propertyMappings = [
  1232.             'o_id' => 'id',
  1233.             'o_path' => 'path',
  1234.             'o_creationDate' => 'creationDate',
  1235.             'o_userOwner' => 'userOwner',
  1236.             'o_versionCount' => 'versionCount',
  1237.             'o_locked' => 'locked',
  1238.             'o_parent' => 'parent',
  1239.             'o_properties' => 'properties',
  1240.             'o_userModification' => 'userModification',
  1241.             'o_modificationDate' => 'modificationDate',
  1242.             'o_parentId' => 'parentId',
  1243.         ];
  1244.         foreach ($propertyMappings as $oldProperty => $newProperty) {
  1245.             if ($this->$newProperty === null) {
  1246.                 $this->$newProperty $this->$oldProperty;
  1247.                 $this->$oldProperty = & $this->$newProperty;
  1248.             }
  1249.         }
  1250.         parent::__wakeup();
  1251.     }
  1252. }