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

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