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

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