vendor/pimcore/pimcore/models/Document.php line 282

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;
  15. use Doctrine\DBAL\Exception\DeadlockException;
  16. use Pimcore\Event\DocumentEvents;
  17. use Pimcore\Event\FrontendEvents;
  18. use Pimcore\Event\Model\DocumentEvent;
  19. use Pimcore\Logger;
  20. use Pimcore\Model\Document\Hardlink\Wrapper\WrapperInterface;
  21. use Pimcore\Model\Document\Listing;
  22. use Pimcore\Model\Element\ElementInterface;
  23. use Pimcore\Model\Exception\NotFoundException;
  24. use Pimcore\Tool;
  25. use Pimcore\Tool\Frontend as FrontendTool;
  26. use Symfony\Cmf\Bundle\RoutingBundle\Routing\DynamicRouter;
  27. use Symfony\Component\EventDispatcher\GenericEvent;
  28. /**
  29.  * @method \Pimcore\Model\Document\Dao getDao()
  30.  * @method bool __isBasedOnLatestData()
  31.  * @method int getChildAmount($user = null)
  32.  * @method string getCurrentFullPath()
  33.  */
  34. class Document extends Element\AbstractElement
  35. {
  36.     /**
  37.      * all possible types of documents
  38.      *
  39.      * @internal
  40.      *
  41.      * @deprecated will be removed in Pimcore 11. Use getTypes() method.
  42.      *
  43.      * @var array
  44.      */
  45.     public static $types = ['folder''page''snippet''link''hardlink''email''newsletter''printpage''printcontainer'];
  46.     /**
  47.      * @var bool
  48.      */
  49.     private static $hideUnpublished false;
  50.     /**
  51.      * @internal
  52.      *
  53.      * @var string|null
  54.      */
  55.     protected $fullPathCache;
  56.     /**
  57.      * @internal
  58.      *
  59.      * @var int|null
  60.      */
  61.     protected $id;
  62.     /**
  63.      * @internal
  64.      *
  65.      * @var int|null
  66.      */
  67.     protected $parentId;
  68.     /**
  69.      * @internal
  70.      *
  71.      * @var self|null
  72.      */
  73.     protected $parent;
  74.     /**
  75.      * @internal
  76.      *
  77.      * @var string
  78.      */
  79.     protected string $type '';
  80.     /**
  81.      * @internal
  82.      *
  83.      * @var string|null
  84.      */
  85.     protected $key;
  86.     /**
  87.      * @internal
  88.      *
  89.      * @var string|null
  90.      */
  91.     protected $path;
  92.     /**
  93.      * @internal
  94.      *
  95.      * @var int|null
  96.      */
  97.     protected ?int $index null;
  98.     /**
  99.      * @internal
  100.      *
  101.      * @var bool
  102.      */
  103.     protected bool $published true;
  104.     /**
  105.      * @internal
  106.      *
  107.      * @var int|null
  108.      */
  109.     protected $creationDate;
  110.     /**
  111.      * @internal
  112.      *
  113.      * @var int|null
  114.      */
  115.     protected $modificationDate;
  116.     /**
  117.      * @internal
  118.      *
  119.      * @var int|null
  120.      */
  121.     protected ?int $userOwner null;
  122.     /**
  123.      * @internal
  124.      *
  125.      * @var int|null
  126.      */
  127.     protected ?int $userModification null;
  128.     /**
  129.      * @internal
  130.      *
  131.      * @var array|null
  132.      */
  133.     protected $properties null;
  134.     /**
  135.      * @internal
  136.      *
  137.      * @var array
  138.      */
  139.     protected $children = [];
  140.     /**
  141.      * @internal
  142.      *
  143.      * @var bool[]
  144.      */
  145.     protected $hasChildren = [];
  146.     /**
  147.      * @internal
  148.      *
  149.      * @var array
  150.      */
  151.     protected $siblings = [];
  152.     /**
  153.      * @internal
  154.      *
  155.      * @var bool[]
  156.      */
  157.     protected $hasSiblings = [];
  158.     /**
  159.      * enum('self','propagate') nullable
  160.      *
  161.      * @internal
  162.      *
  163.      * @var string|null
  164.      */
  165.     protected $locked null;
  166.     /**
  167.      * @internal
  168.      *
  169.      * @var int
  170.      */
  171.     protected $versionCount 0;
  172.     /**
  173.      * get possible types
  174.      *
  175.      * @return array
  176.      */
  177.     public static function getTypes()
  178.     {
  179.         $documentsConfig \Pimcore\Config::getSystemConfiguration('documents');
  180.         return $documentsConfig['types'];
  181.     }
  182.     /**
  183.      * @internal
  184.      *
  185.      * @param string $path
  186.      *
  187.      * @return string
  188.      */
  189.     protected static function getPathCacheKey(string $path): string
  190.     {
  191.         return 'document_path_' md5($path);
  192.     }
  193.     /**
  194.      * @param string $path
  195.      * @param bool $force
  196.      *
  197.      * @return static|null
  198.      */
  199.     public static function getByPath($path$force false)
  200.     {
  201.         if (!$path) {
  202.             return null;
  203.         }
  204.         $path Element\Service::correctPath($path);
  205.         $cacheKey self::getPathCacheKey($path);
  206.         if (!$force && \Pimcore\Cache\Runtime::isRegistered($cacheKey)) {
  207.             $document \Pimcore\Cache\Runtime::get($cacheKey);
  208.             if ($document && static::typeMatch($document)) {
  209.                 return $document;
  210.             }
  211.         }
  212.         try {
  213.             $helperDoc = new Document();
  214.             $helperDoc->getDao()->getByPath($path);
  215.             $doc = static::getById($helperDoc->getId(), $force);
  216.             \Pimcore\Cache\Runtime::set($cacheKey$doc);
  217.         } catch (NotFoundException $e) {
  218.             $doc null;
  219.         }
  220.         return $doc;
  221.     }
  222.     /**
  223.      * @internal
  224.      *
  225.      * @param Document $document
  226.      *
  227.      * @return bool
  228.      */
  229.     protected static function typeMatch(Document $document)
  230.     {
  231.         $staticType get_called_class();
  232.         if ($staticType != Document::class) {
  233.             if (!$document instanceof $staticType) {
  234.                 return false;
  235.             }
  236.         }
  237.         return true;
  238.     }
  239.     /**
  240.      * @param int $id
  241.      * @param bool $force
  242.      *
  243.      * @return static|null
  244.      */
  245.     public static function getById($id$force false)
  246.     {
  247.         if (!is_numeric($id) || $id 1) {
  248.             return null;
  249.         }
  250.         $id = (int)$id;
  251.         $cacheKey self::getCacheKey($id);
  252.         if (!$force && \Pimcore\Cache\Runtime::isRegistered($cacheKey)) {
  253.             $document \Pimcore\Cache\Runtime::get($cacheKey);
  254.             if ($document && static::typeMatch($document)) {
  255.                 return $document;
  256.             }
  257.         }
  258.         if ($force || !($document \Pimcore\Cache::load($cacheKey))) {
  259.             $document = new Document();
  260.             try {
  261.                 $document->getDao()->getById($id);
  262.             } catch (NotFoundException $e) {
  263.                 return null;
  264.             }
  265.             $className 'Pimcore\\Model\\Document\\' ucfirst($document->getType());
  266.             // this is the fallback for custom document types using prefixes
  267.             // so we need to check if the class exists first
  268.             if (!Tool::classExists($className)) {
  269.                 $oldStyleClass 'Document_' ucfirst($document->getType());
  270.                 if (Tool::classExists($oldStyleClass)) {
  271.                     $className $oldStyleClass;
  272.                 }
  273.             }
  274.             /** @var Document $document */
  275.             $document self::getModelFactory()->build($className);
  276.             \Pimcore\Cache\Runtime::set($cacheKey$document);
  277.             $document->getDao()->getById($id);
  278.             $document->__setDataVersionTimestamp($document->getModificationDate());
  279.             $document->resetDirtyMap();
  280.             \Pimcore\Cache::save($document$cacheKey);
  281.         } else {
  282.             \Pimcore\Cache\Runtime::set($cacheKey$document);
  283.         }
  284.         if (!$document || !static::typeMatch($document)) {
  285.             return null;
  286.         }
  287.         return $document;
  288.     }
  289.     /**
  290.      * @param int $parentId
  291.      * @param array $data
  292.      * @param bool $save
  293.      *
  294.      * @return static
  295.      */
  296.     public static function create($parentId$data = [], $save true)
  297.     {
  298.         $document = new static();
  299.         $document->setParentId($parentId);
  300.         self::checkCreateData($data);
  301.         $document->setValues($data);
  302.         if ($save) {
  303.             $document->save();
  304.         }
  305.         return $document;
  306.     }
  307.     /**
  308.      * @param array $config
  309.      *
  310.      * @return Listing
  311.      *
  312.      * @throws \Exception
  313.      */
  314.     public static function getList(array $config = []): Listing
  315.     {
  316.         /** @var Listing $list */
  317.         $list self::getModelFactory()->build(Listing::class);
  318.         $list->setValues($config);
  319.         return $list;
  320.     }
  321.     /**
  322.      * @deprecated will be removed in Pimcore 11
  323.      *
  324.      * @param array $config
  325.      *
  326.      * @return int count
  327.      */
  328.     public static function getTotalCount(array $config = []): int
  329.     {
  330.         $list = static::getList($config);
  331.         return $list->getTotalCount();
  332.     }
  333.     /**
  334.      * {@inheritdoc}
  335.      */
  336.     public function save()
  337.     {
  338.         $isUpdate false;
  339.         try {
  340.             // additional parameters (e.g. "versionNote" for the version note)
  341.             $params = [];
  342.             if (func_num_args() && is_array(func_get_arg(0))) {
  343.                 $params func_get_arg(0);
  344.             }
  345.             $preEvent = new DocumentEvent($this$params);
  346.             if ($this->getId()) {
  347.                 $isUpdate true;
  348.                 \Pimcore::getEventDispatcher()->dispatch($preEventDocumentEvents::PRE_UPDATE);
  349.             } else {
  350.                 \Pimcore::getEventDispatcher()->dispatch($preEventDocumentEvents::PRE_ADD);
  351.             }
  352.             $params $preEvent->getArguments();
  353.             $this->correctPath();
  354.             $differentOldPath null;
  355.             // we wrap the save actions in a loop here, so that we can restart the database transactions in the case it fails
  356.             // if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out
  357.             // this is especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...)
  358.             $maxRetries 5;
  359.             for ($retries 0$retries $maxRetries$retries++) {
  360.                 $this->beginTransaction();
  361.                 try {
  362.                     $this->updateModificationInfos();
  363.                     if (!$isUpdate) {
  364.                         $this->getDao()->create();
  365.                     }
  366.                     // get the old path from the database before the update is done
  367.                     $oldPath null;
  368.                     if ($isUpdate) {
  369.                         $oldPath $this->getDao()->getCurrentFullPath();
  370.                     }
  371.                     $this->update($params);
  372.                     // if the old path is different from the new path, update all children
  373.                     $updatedChildren = [];
  374.                     if ($oldPath && $oldPath !== $newPath $this->getRealFullPath()) {
  375.                         $differentOldPath $oldPath;
  376.                         $this->getDao()->updateWorkspaces();
  377.                         $updatedChildren array_map(
  378.                             static function (array $doc) use ($oldPath$newPath): array {
  379.                                 $doc['oldPath'] = substr_replace($doc['path'], $oldPath0strlen($newPath));
  380.                                 return $doc;
  381.                             },
  382.                             $this->getDao()->updateChildPaths($oldPath),
  383.                         );
  384.                     }
  385.                     $this->commit();
  386.                     break; // transaction was successfully completed, so we cancel the loop here -> no restart required
  387.                 } catch (\Exception $e) {
  388.                     try {
  389.                         $this->rollBack();
  390.                     } catch (\Exception $er) {
  391.                         // PDO adapter throws exceptions if rollback fails
  392.                         Logger::error($er);
  393.                     }
  394.                     // we try to start the transaction $maxRetries times again (deadlocks, ...)
  395.                     if ($e instanceof DeadlockException && $retries < ($maxRetries 1)) {
  396.                         $run $retries 1;
  397.                         $waitTime rand(15) * 100000// microseconds
  398.                         Logger::warn('Unable to finish transaction (' $run ". run) because of the following reason '" $e->getMessage() . "'. --> Retrying in " $waitTime ' microseconds ... (' . ($run 1) . ' of ' $maxRetries ')');
  399.                         usleep($waitTime); // wait specified time until we restart the transaction
  400.                     } else {
  401.                         // if the transaction still fail after $maxRetries retries, we throw out the exception
  402.                         throw $e;
  403.                     }
  404.                 }
  405.             }
  406.             $additionalTags = [];
  407.             if (isset($updatedChildren) && is_array($updatedChildren)) {
  408.                 foreach ($updatedChildren as $updatedDocument) {
  409.                     $tag self::getCacheKey($updatedDocument['id']);
  410.                     $additionalTags[] = $tag;
  411.                     // remove the child also from registry (internal cache) to avoid path inconsistencies during long-running scripts, such as CLI
  412.                     \Pimcore\Cache\Runtime::set($tagnull);
  413.                     \Pimcore\Cache\Runtime::set(self::getPathCacheKey($updatedDocument['oldPath']), null);
  414.                 }
  415.             }
  416.             $this->clearDependentCache($additionalTags);
  417.             $postEvent = new DocumentEvent($this$params);
  418.             if ($isUpdate) {
  419.                 if ($differentOldPath) {
  420.                     $postEvent->setArgument('oldPath'$differentOldPath);
  421.                 }
  422.                 \Pimcore::getEventDispatcher()->dispatch($postEventDocumentEvents::POST_UPDATE);
  423.             } else {
  424.                 \Pimcore::getEventDispatcher()->dispatch($postEventDocumentEvents::POST_ADD);
  425.             }
  426.             return $this;
  427.         } catch (\Exception $e) {
  428.             $failureEvent = new DocumentEvent($this$params);
  429.             $failureEvent->setArgument('exception'$e);
  430.             if ($isUpdate) {
  431.                 \Pimcore::getEventDispatcher()->dispatch($failureEventDocumentEvents::POST_UPDATE_FAILURE);
  432.             } else {
  433.                 \Pimcore::getEventDispatcher()->dispatch($failureEventDocumentEvents::POST_ADD_FAILURE);
  434.             }
  435.             throw $e;
  436.         }
  437.     }
  438.     /**
  439.      * @throws \Exception
  440.      */
  441.     private function correctPath()
  442.     {
  443.         // set path
  444.         if ($this->getId() != 1) { // not for the root node
  445.             // check for a valid key, home has no key, so omit the check
  446.             if (!Element\Service::isValidKey($this->getKey(), 'document')) {
  447.                 throw new \Exception('invalid key for document with id [ ' $this->getId() . ' ] key is: [' $this->getKey() . ']');
  448.             }
  449.             if ($this->getParentId() == $this->getId()) {
  450.                 throw new \Exception("ParentID and ID is identical, an element can't be the parent of itself.");
  451.             }
  452.             $parent Document::getById($this->getParentId());
  453.             if ($parent) {
  454.                 // use the parent's path from the database here (getCurrentFullPath), to ensure the path really exists and does not rely on the path
  455.                 // that is currently in the parent object (in memory), because this might have changed but wasn't not saved
  456.                 $this->setPath(str_replace('//''/'$parent->getCurrentFullPath() . '/'));
  457.             } else {
  458.                 // parent document doesn't exist anymore, set the parent to to root
  459.                 $this->setParentId(1);
  460.                 $this->setPath('/');
  461.             }
  462.             if (strlen($this->getKey()) < 1) {
  463.                 throw new \Exception('Document requires key, generated key automatically');
  464.             }
  465.         } elseif ($this->getId() == 1) {
  466.             // some data in root node should always be the same
  467.             $this->setParentId(0);
  468.             $this->setPath('/');
  469.             $this->setKey('');
  470.             $this->setType('page');
  471.         }
  472.         if (Document\Service::pathExists($this->getRealFullPath())) {
  473.             $duplicate Document::getByPath($this->getRealFullPath());
  474.             if ($duplicate instanceof Document && $duplicate->getId() != $this->getId()) {
  475.                 throw new \Exception('Duplicate full path [ ' $this->getRealFullPath() . ' ] - cannot save document');
  476.             }
  477.         }
  478.         $this->validatePathLength();
  479.     }
  480.     /**
  481.      * @internal
  482.      *
  483.      * @param array $params additional parameters (e.g. "versionNote" for the version note)
  484.      *
  485.      * @throws \Exception
  486.      */
  487.     protected function update($params = [])
  488.     {
  489.         $disallowedKeysInFirstLevel = ['install''admin''plugin'];
  490.         if ($this->getParentId() == && in_array($this->getKey(), $disallowedKeysInFirstLevel)) {
  491.             throw new \Exception('Key: ' $this->getKey() . ' is not allowed in first level (root-level)');
  492.         }
  493.         // set index if null
  494.         if ($this->getIndex() === null) {
  495.             $this->setIndex($this->getDao()->getNextIndex());
  496.         }
  497.         // save properties
  498.         $this->getProperties();
  499.         $this->getDao()->deleteAllProperties();
  500.         if (is_array($this->getProperties()) && count($this->getProperties()) > 0) {
  501.             foreach ($this->getProperties() as $property) {
  502.                 if (!$property->getInherited()) {
  503.                     $property->setDao(null);
  504.                     $property->setCid($this->getId());
  505.                     $property->setCtype('document');
  506.                     $property->setCpath($this->getRealFullPath());
  507.                     $property->save();
  508.                 }
  509.             }
  510.         }
  511.         // save dependencies
  512.         $d = new Dependency();
  513.         $d->setSourceType('document');
  514.         $d->setSourceId($this->getId());
  515.         foreach ($this->resolveDependencies() as $requirement) {
  516.             if ($requirement['id'] == $this->getId() && $requirement['type'] == 'document') {
  517.                 // dont't add a reference to yourself
  518.                 continue;
  519.             } else {
  520.                 $d->addRequirement($requirement['id'], $requirement['type']);
  521.             }
  522.         }
  523.         $d->save();
  524.         $this->getDao()->update();
  525.         //set document to registry
  526.         \Pimcore\Cache\Runtime::set(self::getCacheKey($this->getId()), $this);
  527.     }
  528.     /**
  529.      * @internal
  530.      *
  531.      * @param int $index
  532.      */
  533.     public function saveIndex($index)
  534.     {
  535.         $this->getDao()->saveIndex($index);
  536.         $this->clearDependentCache();
  537.     }
  538.     /**
  539.      * {@inheritdoc}
  540.      */
  541.     public function clearDependentCache($additionalTags = [])
  542.     {
  543.         try {
  544.             $tags = [$this->getCacheTag(), 'document_properties''output'];
  545.             $tags array_merge($tags$additionalTags);
  546.             \Pimcore\Cache::clearTags($tags);
  547.         } catch (\Exception $e) {
  548.             Logger::crit($e);
  549.         }
  550.     }
  551.     /**
  552.      * set the children of the document
  553.      *
  554.      * @param self[] $children
  555.      * @param bool $includingUnpublished
  556.      *
  557.      * @return $this
  558.      */
  559.     public function setChildren($children$includingUnpublished false)
  560.     {
  561.         if (empty($children)) {
  562.             // unset all cached children
  563.             $this->hasChildren = [];
  564.             $this->children = [];
  565.         } elseif (is_array($children)) {
  566.             $cacheKey $this->getListingCacheKey([$includingUnpublished]);
  567.             $this->children[$cacheKey] = $children;
  568.             $this->hasChildren[$cacheKey] = (bool) count($children);
  569.         }
  570.         return $this;
  571.     }
  572.     /**
  573.      * Get a list of the children (not recursivly)
  574.      *
  575.      * @param bool $includingUnpublished
  576.      *
  577.      * @return self[]
  578.      */
  579.     public function getChildren($includingUnpublished false)
  580.     {
  581.         $cacheKey $this->getListingCacheKey(func_get_args());
  582.         if (!isset($this->children[$cacheKey])) {
  583.             if ($this->getId()) {
  584.                 $list = new Document\Listing();
  585.                 $list->setUnpublished($includingUnpublished);
  586.                 $list->setCondition('parentId = ?'$this->getId());
  587.                 $list->setOrderKey('index');
  588.                 $list->setOrder('asc');
  589.                 $this->children[$cacheKey] = $list->load();
  590.             } else {
  591.                 $this->children[$cacheKey] = [];
  592.             }
  593.         }
  594.         return $this->children[$cacheKey];
  595.     }
  596.     /**
  597.      * Returns true if the document has at least one child
  598.      *
  599.      * @param bool $includingUnpublished
  600.      *
  601.      * @return bool
  602.      */
  603.     public function hasChildren($includingUnpublished null)
  604.     {
  605.         $cacheKey $this->getListingCacheKey(func_get_args());
  606.         if (isset($this->hasChildren[$cacheKey])) {
  607.             return $this->hasChildren[$cacheKey];
  608.         }
  609.         return $this->hasChildren[$cacheKey] = $this->getDao()->hasChildren($includingUnpublished);
  610.     }
  611.     /**
  612.      * Get a list of the sibling documents
  613.      *
  614.      * @param bool $includingUnpublished
  615.      *
  616.      * @return array
  617.      */
  618.     public function getSiblings($includingUnpublished false)
  619.     {
  620.         $cacheKey $this->getListingCacheKey(func_get_args());
  621.         if (!isset($this->siblings[$cacheKey])) {
  622.             if ($this->getParentId()) {
  623.                 $list = new Document\Listing();
  624.                 $list->setUnpublished($includingUnpublished);
  625.                 $list->addConditionParam('parentId = ?'$this->getParentId());
  626.                 if ($this->getId()) {
  627.                     $list->addConditionParam('id != ?'$this->getId());
  628.                 }
  629.                 $list->setOrderKey('index');
  630.                 $list->setOrder('asc');
  631.                 $this->siblings[$cacheKey] = $list->load();
  632.                 $this->hasSiblings[$cacheKey] = (bool) count($this->siblings[$cacheKey]);
  633.             } else {
  634.                 $this->siblings[$cacheKey] = [];
  635.                 $this->hasSiblings[$cacheKey] = false;
  636.             }
  637.         }
  638.         return $this->siblings[$cacheKey];
  639.     }
  640.     /**
  641.      * Returns true if the document has at least one sibling
  642.      *
  643.      * @param bool|null $includingUnpublished
  644.      *
  645.      * @return bool
  646.      */
  647.     public function hasSiblings($includingUnpublished null)
  648.     {
  649.         $cacheKey $this->getListingCacheKey(func_get_args());
  650.         if (isset($this->hasSiblings[$cacheKey])) {
  651.             return $this->hasSiblings[$cacheKey];
  652.         }
  653.         return $this->hasSiblings[$cacheKey] = $this->getDao()->hasSiblings($includingUnpublished);
  654.     }
  655.     /**
  656.      * {@inheritdoc}
  657.      */
  658.     public function getLocked()
  659.     {
  660.         if (empty($this->locked)) {
  661.             return null;
  662.         }
  663.         return $this->locked;
  664.     }
  665.     /**
  666.      * {@inheritdoc}
  667.      */
  668.     public function setLocked($locked)
  669.     {
  670.         $this->locked $locked;
  671.         return $this;
  672.     }
  673.     /**
  674.      * @internal
  675.      *
  676.      * @throws \Exception
  677.      */
  678.     protected function doDelete()
  679.     {
  680.         // remove children
  681.         if ($this->hasChildren()) {
  682.             // delete also unpublished children
  683.             $unpublishedStatus self::doHideUnpublished();
  684.             self::setHideUnpublished(false);
  685.             foreach ($this->getChildren(true) as $child) {
  686.                 if (!$child instanceof WrapperInterface) {
  687.                     $child->delete();
  688.                 }
  689.             }
  690.             self::setHideUnpublished($unpublishedStatus);
  691.         }
  692.         // remove all properties
  693.         $this->getDao()->deleteAllProperties();
  694.         // remove permissions
  695.         $this->getDao()->deleteAllPermissions();
  696.         // remove dependencies
  697.         $d $this->getDependencies();
  698.         $d->cleanAllForElement($this);
  699.         // remove translations
  700.         $service = new Document\Service;
  701.         $service->removeTranslation($this);
  702.     }
  703.     /**
  704.      * {@inheritdoc}
  705.      */
  706.     public function delete()
  707.     {
  708.         \Pimcore::getEventDispatcher()->dispatch(new DocumentEvent($this), DocumentEvents::PRE_DELETE);
  709.         $this->beginTransaction();
  710.         try {
  711.             if ($this->getId() == 1) {
  712.                 throw new \Exception('root-node cannot be deleted');
  713.             }
  714.             $this->doDelete();
  715.             $this->getDao()->delete();
  716.             $this->commit();
  717.             //clear parent data from registry
  718.             $parentCacheKey self::getCacheKey($this->getParentId());
  719.             if (\Pimcore\Cache\Runtime::isRegistered($parentCacheKey)) {
  720.                 /** @var Document $parent */
  721.                 $parent \Pimcore\Cache\Runtime::get($parentCacheKey);
  722.                 if ($parent instanceof self) {
  723.                     $parent->setChildren(null);
  724.                 }
  725.             }
  726.         } catch (\Exception $e) {
  727.             $this->rollBack();
  728.             $failureEvent = new DocumentEvent($this);
  729.             $failureEvent->setArgument('exception'$e);
  730.             \Pimcore::getEventDispatcher()->dispatch($failureEventDocumentEvents::POST_DELETE_FAILURE);
  731.             Logger::error($e);
  732.             throw $e;
  733.         }
  734.         // clear cache
  735.         $this->clearDependentCache();
  736.         //clear document from registry
  737.         \Pimcore\Cache\Runtime::set(self::getCacheKey($this->getId()), null);
  738.         \Pimcore\Cache\Runtime::set(self::getPathCacheKey($this->getRealFullPath()), null);
  739.         \Pimcore::getEventDispatcher()->dispatch(new DocumentEvent($this), DocumentEvents::POST_DELETE);
  740.     }
  741.     /**
  742.      * {@inheritdoc}
  743.      */
  744.     public function getFullPath(bool $force false)
  745.     {
  746.         $link $force null $this->fullPathCache;
  747.         // check if this document is also the site root, if so return /
  748.         try {
  749.             if (!$link && \Pimcore\Tool::isFrontend() && Site::isSiteRequest()) {
  750.                 $site Site::getCurrentSite();
  751.                 if ($site instanceof Site) {
  752.                     if ($site->getRootDocument()->getId() == $this->getId()) {
  753.                         $link '/';
  754.                     }
  755.                 }
  756.             }
  757.         } catch (\Exception $e) {
  758.             Logger::error($e);
  759.         }
  760.         $requestStack \Pimcore::getContainer()->get('request_stack');
  761.         $masterRequest $requestStack->getMainRequest();
  762.         // @TODO please forgive me, this is the dirtiest hack I've ever made :(
  763.         // if you got confused by this functionality drop me a line and I'll buy you some beers :)
  764.         // this is for the case that a link points to a document outside of the current site
  765.         // in this case we look for a hardlink in the current site which points to the current document
  766.         // why this could happen: we have 2 sites, in one site there's a hardlink to the other site and on a page inside
  767.         // the hardlink there are snippets embedded and this snippets have links pointing to a document which is also
  768.         // inside the hardlink scope, but this is an ID link, so we cannot rewrite the link the usual way because in the
  769.         // snippet / link we don't know anymore that whe a inside a hardlink wrapped document
  770.         if (!$link && \Pimcore\Tool::isFrontend() && Site::isSiteRequest() && !FrontendTool::isDocumentInCurrentSite($this)) {
  771.             if ($masterRequest && ($masterDocument $masterRequest->get(DynamicRouter::CONTENT_KEY))) {
  772.                 if ($masterDocument instanceof WrapperInterface) {
  773.                     $hardlinkPath '';
  774.                     $hardlink $masterDocument->getHardLinkSource();
  775.                     $hardlinkTarget $hardlink->getSourceDocument();
  776.                     if ($hardlinkTarget) {
  777.                         $hardlinkPath preg_replace('@^' preg_quote(Site::getCurrentSite()->getRootPath(), '@') . '@'''$hardlink->getRealFullPath());
  778.                         $link preg_replace('@^' preg_quote($hardlinkTarget->getRealFullPath(), '@') . '@',
  779.                             $hardlinkPath$this->getRealFullPath());
  780.                     }
  781.                     if (strpos($this->getRealFullPath(), Site::getCurrentSite()->getRootDocument()->getRealFullPath()) === false && strpos($link$hardlinkPath) === false) {
  782.                         $link null;
  783.                     }
  784.                 }
  785.             }
  786.             if (!$link) {
  787.                 $config \Pimcore\Config::getSystemConfiguration('general');
  788.                 $request $requestStack->getCurrentRequest();
  789.                 $scheme 'http://';
  790.                 if ($request) {
  791.                     $scheme $request->getScheme() . '://';
  792.                 }
  793.                 /** @var Site $site */
  794.                 if ($site FrontendTool::getSiteForDocument($this)) {
  795.                     if ($site->getMainDomain()) {
  796.                         // check if current document is the root of the different site, if so, preg_replace below doesn't work, so just return /
  797.                         if ($site->getRootDocument()->getId() == $this->getId()) {
  798.                             $link $scheme $site->getMainDomain() . '/';
  799.                         } else {
  800.                             $link $scheme $site->getMainDomain() .
  801.                                 preg_replace('@^' $site->getRootPath() . '/@''/'$this->getRealFullPath());
  802.                         }
  803.                     }
  804.                 }
  805.                 if (!$link && !empty($config['domain']) && !($this instanceof WrapperInterface)) {
  806.                     $link $scheme $config['domain'] . $this->getRealFullPath();
  807.                 }
  808.             }
  809.         }
  810.         if (!$link) {
  811.             $link $this->getPath() . $this->getKey();
  812.         }
  813.         if ($masterRequest) {
  814.             // caching should only be done when master request is available as it is done for performance reasons
  815.             // of the web frontend, without a request object there's no need to cache anything
  816.             // for details also see https://github.com/pimcore/pimcore/issues/5707
  817.             $this->fullPathCache $link;
  818.         }
  819.         $link $this->prepareFrontendPath($link);
  820.         return $link;
  821.     }
  822.     /**
  823.      * @param string $path
  824.      *
  825.      * @return string
  826.      */
  827.     private function prepareFrontendPath($path)
  828.     {
  829.         if (\Pimcore\Tool::isFrontend()) {
  830.             $path urlencode_ignore_slash($path);
  831.             $event = new GenericEvent($this, [
  832.                 'frontendPath' => $path,
  833.             ]);
  834.             \Pimcore::getEventDispatcher()->dispatch($eventFrontendEvents::DOCUMENT_PATH);
  835.             $path $event->getArgument('frontendPath');
  836.         }
  837.         return $path;
  838.     }
  839.     /**
  840.      * {@inheritdoc}
  841.      */
  842.     public function getCreationDate()
  843.     {
  844.         return $this->creationDate;
  845.     }
  846.     /**
  847.      * {@inheritdoc}
  848.      */
  849.     public function getId(): ?int
  850.     {
  851.         return $this->id;
  852.     }
  853.     /**
  854.      * {@inheritdoc}
  855.      */
  856.     public function getKey()
  857.     {
  858.         return $this->key;
  859.     }
  860.     /**
  861.      * {@inheritdoc}
  862.      */
  863.     public function getModificationDate()
  864.     {
  865.         return $this->modificationDate;
  866.     }
  867.     /**
  868.      * {@inheritdoc}
  869.      */
  870.     public function getParentId()
  871.     {
  872.         return $this->parentId;
  873.     }
  874.     /**
  875.      * {@inheritdoc}
  876.      */
  877.     public function getPath()
  878.     {
  879.         // check for site, if so rewrite the path for output
  880.         try {
  881.             if (\Pimcore\Tool::isFrontend() && Site::isSiteRequest()) {
  882.                 $site Site::getCurrentSite();
  883.                 if ($site instanceof Site) {
  884.                     if ($site->getRootDocument() instanceof Document\Page && $site->getRootDocument() !== $this) {
  885.                         $rootPath $site->getRootPath();
  886.                         $rootPath preg_quote($rootPath'@');
  887.                         $link preg_replace('@^' $rootPath '@'''$this->path);
  888.                         return $link;
  889.                     }
  890.                 }
  891.             }
  892.         } catch (\Exception $e) {
  893.             Logger::error($e);
  894.         }
  895.         return $this->path;
  896.     }
  897.     /**
  898.      * {@inheritdoc}
  899.      */
  900.     public function getRealPath()
  901.     {
  902.         return $this->path;
  903.     }
  904.     /**
  905.      * {@inheritdoc}
  906.      */
  907.     public function getRealFullPath()
  908.     {
  909.         $path $this->getRealPath() . $this->getKey();
  910.         return $path;
  911.     }
  912.     /**
  913.      * {@inheritdoc}
  914.      */
  915.     public function setCreationDate($creationDate)
  916.     {
  917.         $this->creationDate = (int) $creationDate;
  918.         return $this;
  919.     }
  920.     /**
  921.      * {@inheritdoc}
  922.      */
  923.     public function setId($id)
  924.     {
  925.         $this->id = (int) $id;
  926.         return $this;
  927.     }
  928.     /**
  929.      * {@inheritdoc}
  930.      */
  931.     public function setKey($key)
  932.     {
  933.         $this->key = (string)$key;
  934.         return $this;
  935.     }
  936.     /**
  937.      * {@inheritdoc}
  938.      */
  939.     public function setModificationDate($modificationDate)
  940.     {
  941.         $this->markFieldDirty('modificationDate');
  942.         $this->modificationDate = (int) $modificationDate;
  943.         return $this;
  944.     }
  945.     /**
  946.      * Set the parent id of the document.
  947.      *
  948.      * @param int $parentId
  949.      *
  950.      * @return Document
  951.      */
  952.     public function setParentId($parentId)
  953.     {
  954.         $this->parentId = (int) $parentId;
  955.         $this->parent null;
  956.         $this->siblings = [];
  957.         $this->hasSiblings = [];
  958.         return $this;
  959.     }
  960.     /**
  961.      * {@inheritdoc}
  962.      */
  963.     public function setPath($path)
  964.     {
  965.         $this->path $path;
  966.         return $this;
  967.     }
  968.     /**
  969.      * Returns the document index.
  970.      *
  971.      * @return int|null
  972.      */
  973.     public function getIndex(): ?int
  974.     {
  975.         return $this->index;
  976.     }
  977.     /**
  978.      * Set the document index.
  979.      *
  980.      * @param int $index
  981.      *
  982.      * @return Document
  983.      */
  984.     public function setIndex($index)
  985.     {
  986.         $this->index = (int) $index;
  987.         return $this;
  988.     }
  989.     /**
  990.      * {@inheritdoc}
  991.      */
  992.     public function getType()
  993.     {
  994.         return $this->type;
  995.     }
  996.     /**
  997.      * Set the document type.
  998.      *
  999.      * @param string $type
  1000.      *
  1001.      * @return Document
  1002.      */
  1003.     public function setType($type)
  1004.     {
  1005.         $this->type $type;
  1006.         return $this;
  1007.     }
  1008.     /**
  1009.      * {@inheritdoc}
  1010.      */
  1011.     public function getUserModification()
  1012.     {
  1013.         return $this->userModification;
  1014.     }
  1015.     /**
  1016.      * {@inheritdoc}
  1017.      */
  1018.     public function getUserOwner()
  1019.     {
  1020.         return $this->userOwner;
  1021.     }
  1022.     /**
  1023.      * {@inheritdoc}
  1024.      */
  1025.     public function setUserModification($userModification)
  1026.     {
  1027.         $this->markFieldDirty('userModification');
  1028.         $this->userModification = (int) $userModification;
  1029.         return $this;
  1030.     }
  1031.     /**
  1032.      * {@inheritdoc}
  1033.      */
  1034.     public function setUserOwner($userOwner)
  1035.     {
  1036.         $this->userOwner = (int) $userOwner;
  1037.         return $this;
  1038.     }
  1039.     /**
  1040.      * @return bool
  1041.      */
  1042.     public function isPublished()
  1043.     {
  1044.         return $this->getPublished();
  1045.     }
  1046.     /**
  1047.      * @return bool
  1048.      */
  1049.     public function getPublished()
  1050.     {
  1051.         return (bool) $this->published;
  1052.     }
  1053.     /**
  1054.      * @param bool $published
  1055.      *
  1056.      * @return Document
  1057.      */
  1058.     public function setPublished($published)
  1059.     {
  1060.         $this->published = (bool) $published;
  1061.         return $this;
  1062.     }
  1063.     /**
  1064.      * {@inheritdoc}
  1065.      */
  1066.     public function getProperties()
  1067.     {
  1068.         if ($this->properties === null) {
  1069.             // try to get from cache
  1070.             $cacheKey 'document_properties_' $this->getId();
  1071.             $properties \Pimcore\Cache::load($cacheKey);
  1072.             if (!is_array($properties)) {
  1073.                 $properties $this->getDao()->getProperties();
  1074.                 $elementCacheTag $this->getCacheTag();
  1075.                 $cacheTags = ['document_properties' => 'document_properties'$elementCacheTag => $elementCacheTag];
  1076.                 \Pimcore\Cache::save($properties$cacheKey$cacheTags);
  1077.             }
  1078.             $this->setProperties($properties);
  1079.         }
  1080.         return $this->properties;
  1081.     }
  1082.     /**
  1083.      * {@inheritdoc}
  1084.      */
  1085.     public function setProperties(?array $properties)
  1086.     {
  1087.         $this->properties $properties;
  1088.         return $this;
  1089.     }
  1090.     /**
  1091.      * {@inheritdoc}
  1092.      */
  1093.     public function setProperty($name$type$data$inherited false$inheritable false)
  1094.     {
  1095.         $this->getProperties();
  1096.         $property = new Property();
  1097.         $property->setType($type);
  1098.         $property->setCid($this->getId());
  1099.         $property->setName($name);
  1100.         $property->setCtype('document');
  1101.         $property->setData($data);
  1102.         $property->setInherited($inherited);
  1103.         $property->setInheritable($inheritable);
  1104.         $this->properties[$name] = $property;
  1105.         return $this;
  1106.     }
  1107.     /**
  1108.      * {@inheritdoc}
  1109.      */
  1110.     public function getParent()
  1111.     {
  1112.         if ($this->parent === null) {
  1113.             $this->setParent(Document::getById($this->getParentId()));
  1114.         }
  1115.         return $this->parent;
  1116.     }
  1117.     /**
  1118.      * Set the parent document instance.
  1119.      *
  1120.      * @param Document|null $parent
  1121.      *
  1122.      * @return Document
  1123.      */
  1124.     public function setParent($parent)
  1125.     {
  1126.         $this->parent $parent;
  1127.         if ($parent instanceof Document) {
  1128.             $this->parentId $parent->getId();
  1129.         }
  1130.         return $this;
  1131.     }
  1132.     public function __sleep()
  1133.     {
  1134.         $parentVars parent::__sleep();
  1135.         $blockedVars = ['hasChildren''versions''scheduledTasks''parent''fullPathCache'];
  1136.         if ($this->isInDumpState()) {
  1137.             // this is if we want to make a full dump of the object (eg. for a new version), including children for recyclebin
  1138.             $this->removeInheritedProperties();
  1139.         } else {
  1140.             // this is if we want to cache the object
  1141.             $blockedVars array_merge($blockedVars, ['children''properties']);
  1142.         }
  1143.         return array_diff($parentVars$blockedVars);
  1144.     }
  1145.     public function __wakeup()
  1146.     {
  1147.         if ($this->isInDumpState()) {
  1148.             // 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)
  1149.             $originalElement Document::getById($this->getId());
  1150.             if ($originalElement) {
  1151.                 $this->setKey($originalElement->getKey());
  1152.                 $this->setPath($originalElement->getRealPath());
  1153.             }
  1154.         }
  1155.         if ($this->isInDumpState() && $this->properties !== null) {
  1156.             $this->renewInheritedProperties();
  1157.         }
  1158.         $this->setInDumpState(false);
  1159.     }
  1160.     /**
  1161.      * Set true if want to hide documents.
  1162.      *
  1163.      * @param bool $hideUnpublished
  1164.      */
  1165.     public static function setHideUnpublished($hideUnpublished)
  1166.     {
  1167.         self::$hideUnpublished $hideUnpublished;
  1168.     }
  1169.     /**
  1170.      * Checks if unpublished documents should be hidden.
  1171.      *
  1172.      * @return bool
  1173.      */
  1174.     public static function doHideUnpublished()
  1175.     {
  1176.         return self::$hideUnpublished;
  1177.     }
  1178.     /**
  1179.      * {@inheritdoc}
  1180.      */
  1181.     public function getVersionCount(): int
  1182.     {
  1183.         return $this->versionCount $this->versionCount 0;
  1184.     }
  1185.     /**
  1186.      * {@inheritdoc}
  1187.      */
  1188.     public function setVersionCount(?int $versionCount): ElementInterface
  1189.     {
  1190.         $this->versionCount = (int) $versionCount;
  1191.         return $this;
  1192.     }
  1193.     /**
  1194.      * @internal
  1195.      *
  1196.      * @param array $args
  1197.      *
  1198.      * @return string
  1199.      */
  1200.     protected function getListingCacheKey(array $args = [])
  1201.     {
  1202.         $unpublished = (bool)($args[0] ?? false);
  1203.         $cacheKey = (string)$unpublished;
  1204.         return $cacheKey;
  1205.     }
  1206.     public function __clone()
  1207.     {
  1208.         parent::__clone();
  1209.         $this->parent null;
  1210.         $this->hasSiblings = [];
  1211.         $this->siblings = [];
  1212.         $this->fullPathCache null;
  1213.     }
  1214. }