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

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