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

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