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

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