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

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