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

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