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

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