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

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