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

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