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

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