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

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