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

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