vendor/pimcore/pimcore/models/Document/PageSnippet.php line 496

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\Document;
  15. use Pimcore\Document\Editable\EditableUsageResolver;
  16. use Pimcore\Event\DocumentEvents;
  17. use Pimcore\Event\Model\DocumentEvent;
  18. use Pimcore\Http\RequestHelper;
  19. use Pimcore\Logger;
  20. use Pimcore\Messenger\VersionDeleteMessage;
  21. use Pimcore\Model;
  22. use Pimcore\Model\Document;
  23. use Pimcore\Model\Document\Editable\Loader\EditableLoaderInterface;
  24. use Symfony\Component\Messenger\MessageBusInterface;
  25. /**
  26.  * @method \Pimcore\Model\Document\PageSnippet\Dao getDao()
  27.  * @method \Pimcore\Model\Version|null getLatestVersion($userId = null)
  28.  */
  29. abstract class PageSnippet extends Model\Document
  30. {
  31.     use Model\Element\Traits\ScheduledTasksTrait;
  32.     /**
  33.      * @internal
  34.      *
  35.      * @var string
  36.      */
  37.     protected $controller;
  38.     /**
  39.      * @internal
  40.      *
  41.      * @var string
  42.      */
  43.     protected $template;
  44.     /**
  45.      * Contains all content-editables of the document
  46.      *
  47.      * @internal
  48.      *
  49.      * @var array|null
  50.      *
  51.      */
  52.     protected $editables null;
  53.     /**
  54.      * Contains all versions of the document
  55.      *
  56.      * @internal
  57.      *
  58.      * @var array
  59.      */
  60.     protected $versions null;
  61.     /**
  62.      * @internal
  63.      *
  64.      * @var null|int
  65.      */
  66.     protected $contentMasterDocumentId;
  67.     /**
  68.      * @internal
  69.      *
  70.      * @var bool
  71.      */
  72.     protected bool $supportsContentMaster true;
  73.     /**
  74.      * @internal
  75.      *
  76.      * @var null|bool
  77.      */
  78.     protected $missingRequiredEditable null;
  79.     /**
  80.      * @internal
  81.      *
  82.      * @var null|bool
  83.      */
  84.     protected $staticGeneratorEnabled null;
  85.     /**
  86.      * @internal
  87.      *
  88.      * @var null|int
  89.      */
  90.     protected $staticGeneratorLifetime null;
  91.     /**
  92.      * @internal
  93.      *
  94.      * @var array
  95.      */
  96.     protected $inheritedEditables = [];
  97.     /**
  98.      * {@inheritdoc}
  99.      */
  100.     public function save()
  101.     {
  102.         // checking the required editables renders the document, so this needs to be
  103.         // before the database transaction, see also https://github.com/pimcore/pimcore/issues/8992
  104.         $this->checkMissingRequiredEditable();
  105.         if ($this->getMissingRequiredEditable() && $this->getPublished()) {
  106.             throw new Model\Element\ValidationException('Prevented publishing document - missing values for required editables');
  107.         }
  108.         return parent::save();
  109.     }
  110.     /**
  111.      * {@inheritdoc}
  112.      */
  113.     protected function update($params = [])
  114.     {
  115.         // update elements
  116.         $this->getEditables();
  117.         $this->getDao()->deleteAllEditables();
  118.         if (is_array($this->getEditables()) && count($this->getEditables()) > 0) {
  119.             foreach ($this->getEditables() as $name => $editable) {
  120.                 if (!$editable->getInherited()) {
  121.                     $editable->setDao(null);
  122.                     $editable->setDocumentId($this->getId());
  123.                     $editable->save();
  124.                 }
  125.             }
  126.         }
  127.         // scheduled tasks are saved in $this->saveVersion();
  128.         // update this
  129.         parent::update($params);
  130.         // save version if needed
  131.         $this->saveVersion(falsefalse, isset($params['versionNote']) ? $params['versionNote'] : null);
  132.     }
  133.     /**
  134.      * @param bool $setModificationDate
  135.      * @param bool $saveOnlyVersion
  136.      * @param string $versionNote
  137.      * @param bool $isAutoSave
  138.      *
  139.      * @return null|Model\Version
  140.      *
  141.      * @throws \Exception
  142.      */
  143.     public function saveVersion($setModificationDate true$saveOnlyVersion true$versionNote null$isAutoSave false)
  144.     {
  145.         try {
  146.             // hook should be also called if "save only new version" is selected
  147.             if ($saveOnlyVersion) {
  148.                 $preUpdateEvent = new DocumentEvent($this, [
  149.                     'saveVersionOnly' => true,
  150.                     'isAutoSave' => $isAutoSave,
  151.                 ]);
  152.                 \Pimcore::getEventDispatcher()->dispatch($preUpdateEventDocumentEvents::PRE_UPDATE);
  153.             }
  154.             // set date
  155.             if ($setModificationDate) {
  156.                 $this->setModificationDate(time());
  157.             }
  158.             // scheduled tasks are saved always, they are not versioned!
  159.             $this->saveScheduledTasks();
  160.             // create version
  161.             $version null;
  162.             // only create a new version if there is at least 1 allowed
  163.             // or if saveVersion() was called directly (it's a newer version of the object)
  164.             $documentsConfig \Pimcore\Config::getSystemConfiguration('documents');
  165.             if ((is_null($documentsConfig['versions']['days'] ?? null) && is_null($documentsConfig['versions']['steps'] ?? null))
  166.                 || (!empty($documentsConfig['versions']['steps']))
  167.                 || !empty($documentsConfig['versions']['days'])
  168.                 || $setModificationDate) {
  169.                 $saveStackTrace = !($documentsConfig['versions']['disable_stack_trace'] ?? false);
  170.                 $version $this->doSaveVersion($versionNote$saveOnlyVersion$saveStackTrace$isAutoSave);
  171.             }
  172.             // hook should be also called if "save only new version" is selected
  173.             if ($saveOnlyVersion) {
  174.                 $postUpdateEvent = new DocumentEvent($this, [
  175.                     'saveVersionOnly' => true,
  176.                     'isAutoSave' => $isAutoSave,
  177.                 ]);
  178.                 \Pimcore::getEventDispatcher()->dispatch($postUpdateEventDocumentEvents::POST_UPDATE);
  179.             }
  180.             return $version;
  181.         } catch (\Exception $e) {
  182.             $postUpdateFailureEvent = new DocumentEvent($this, [
  183.                 'saveVersionOnly' => true,
  184.                 'exception' => $e,
  185.                 'isAutoSave' => $isAutoSave,
  186.             ]);
  187.             \Pimcore::getEventDispatcher()->dispatch($postUpdateFailureEventDocumentEvents::POST_UPDATE_FAILURE);
  188.             throw $e;
  189.         }
  190.     }
  191.     /**
  192.      * {@inheritdoc}
  193.      */
  194.     protected function doDelete()
  195.     {
  196.         // Dispatch Symfony Message Bus to delete versions
  197.         \Pimcore::getContainer()->get(MessageBusInterface::class)->dispatch(
  198.             new VersionDeleteMessage(Service::getElementType($this), $this->getId())
  199.         );
  200.         // remove all tasks
  201.         $this->getDao()->deleteAllTasks();
  202.         parent::doDelete();
  203.     }
  204.     /**
  205.      * {@inheritdoc}
  206.      */
  207.     public function getCacheTags(array $tags = []): array
  208.     {
  209.         $tags parent::getCacheTags($tags);
  210.         foreach ($this->getEditables() as $editable) {
  211.             $tags $editable->getCacheTags($this$tags);
  212.         }
  213.         return $tags;
  214.     }
  215.     /**
  216.      * {@inheritdoc}
  217.      */
  218.     protected function resolveDependencies(): array
  219.     {
  220.         $dependencies = [parent::resolveDependencies()];
  221.         foreach ($this->getEditables() as $editable) {
  222.             $dependencies[] = $editable->resolveDependencies();
  223.         }
  224.         if ($this->getContentMasterDocument() instanceof Document) {
  225.             $masterDocumentId $this->getContentMasterDocument()->getId();
  226.             $dependencies[] = [
  227.                 'document_' $masterDocumentId => [
  228.                     'id' => $masterDocumentId,
  229.                     'type' => 'document',
  230.                 ],
  231.             ];
  232.         }
  233.         return array_merge(...$dependencies);
  234.     }
  235.     /**
  236.      * @return string
  237.      */
  238.     public function getController()
  239.     {
  240.         if (empty($this->controller)) {
  241.             $this->controller \Pimcore::getContainer()->getParameter('pimcore.documents.default_controller');
  242.         }
  243.         return $this->controller;
  244.     }
  245.     /**
  246.      * @return string
  247.      */
  248.     public function getTemplate()
  249.     {
  250.         return $this->template;
  251.     }
  252.     /**
  253.      * @param string $controller
  254.      *
  255.      * @return $this
  256.      */
  257.     public function setController($controller)
  258.     {
  259.         $this->controller $controller;
  260.         return $this;
  261.     }
  262.     /**
  263.      * @param string $template
  264.      *
  265.      * @return $this
  266.      */
  267.     public function setTemplate($template)
  268.     {
  269.         $this->template $template;
  270.         return $this;
  271.     }
  272.     /**
  273.      * Set raw data of an editable (eg. for editmode)
  274.      *
  275.      * @internal
  276.      *
  277.      * @param string $name
  278.      * @param string $type
  279.      * @param mixed $data
  280.      *
  281.      * @return $this
  282.      */
  283.     public function setRawEditable(string $namestring $type$data)
  284.     {
  285.         try {
  286.             if ($type) {
  287.                 /** @var EditableLoaderInterface $loader */
  288.                 $loader \Pimcore::getContainer()->get(Document\Editable\Loader\EditableLoader::class);
  289.                 $editable $loader->build($type);
  290.                 $this->editables $this->editables ?? [];
  291.                 $this->editables[$name] = $editable;
  292.                 $this->editables[$name]->setDataFromEditmode($data);
  293.                 $this->editables[$name]->setName($name);
  294.                 $this->editables[$name]->setDocument($this);
  295.             }
  296.         } catch (\Exception $e) {
  297.             Logger::warning("can't set element " $name ' with the type ' $type ' to the document: ' $this->getRealFullPath());
  298.         }
  299.         return $this;
  300.     }
  301.     /**
  302.      * Set an element with the given key/name
  303.      *
  304.      * @param Editable $editable
  305.      *
  306.      * @return $this
  307.      */
  308.     public function setEditable(Editable $editable)
  309.     {
  310.         $this->getEditables();
  311.         $this->editables[$editable->getName()] = $editable;
  312.         return $this;
  313.     }
  314.     /**
  315.      * @param string $name
  316.      *
  317.      * @return $this
  318.      */
  319.     public function removeEditable(string $name)
  320.     {
  321.         $this->getEditables();
  322.         if (isset($this->editables[$name])) {
  323.             unset($this->editables[$name]);
  324.         }
  325.         return $this;
  326.     }
  327.     /**
  328.      * Get an editable with the given key/name
  329.      *
  330.      * @param string $name
  331.      *
  332.      * @return Editable|null
  333.      */
  334.     public function getEditable(string $name)
  335.     {
  336.         $editables $this->getEditables();
  337.         if (isset($this->editables[$name])) {
  338.             return $editables[$name];
  339.         }
  340.         if (array_key_exists($name$this->inheritedEditables)) {
  341.             return $this->inheritedEditables[$name];
  342.         }
  343.         // check for content master document (inherit data)
  344.         if ($contentMasterDocument $this->getContentMasterDocument()) {
  345.             if ($contentMasterDocument instanceof self) {
  346.                 $inheritedEditable $contentMasterDocument->getEditable($name);
  347.                 if ($inheritedEditable) {
  348.                     $inheritedEditable = clone $inheritedEditable;
  349.                     $inheritedEditable->setInherited(true);
  350.                     $this->inheritedEditables[$name] = $inheritedEditable;
  351.                     return $inheritedEditable;
  352.                 }
  353.             }
  354.         }
  355.         return null;
  356.     }
  357.     /**
  358.      * @param int|null $contentMasterDocumentId
  359.      *
  360.      * @return $this
  361.      *
  362.      * @throws \Exception
  363.      */
  364.     public function setContentMasterDocumentId($contentMasterDocumentId)
  365.     {
  366.         // this is that the path is automatically converted to ID => when setting directly from admin UI
  367.         if (!is_numeric($contentMasterDocumentId) && !empty($contentMasterDocumentId)) {
  368.             $contentMasterDocument Document::getByPath($contentMasterDocumentId);
  369.             if ($contentMasterDocument instanceof self) {
  370.                 $contentMasterDocumentId $contentMasterDocument->getId();
  371.             }
  372.         }
  373.         if (empty($contentMasterDocumentId)) {
  374.             $contentMasterDocument null;
  375.         }
  376.         if ($contentMasterDocumentId && $contentMasterDocumentId == $this->getId()) {
  377.             throw new \Exception('You cannot use the current document as a master document, please choose a different one.');
  378.         }
  379.         $this->contentMasterDocumentId $contentMasterDocumentId;
  380.         return $this;
  381.     }
  382.     /**
  383.      * @return int|null
  384.      */
  385.     public function getContentMasterDocumentId()
  386.     {
  387.         return $this->contentMasterDocumentId;
  388.     }
  389.     /**
  390.      * @return Document|null
  391.      */
  392.     public function getContentMasterDocument()
  393.     {
  394.         if ($masterDocumentId $this->getContentMasterDocumentId()) {
  395.             return Document::getById($masterDocumentId);
  396.         }
  397.         return null;
  398.     }
  399.     /**
  400.      * @param Document $document
  401.      *
  402.      * @return $this
  403.      */
  404.     public function setContentMasterDocument($document)
  405.     {
  406.         if ($document instanceof self) {
  407.             $this->setContentMasterDocumentId($document->getId());
  408.         } else {
  409.             $this->setContentMasterDocumentId(null);
  410.         }
  411.         return $this;
  412.     }
  413.     /**
  414.      * @param string $name
  415.      *
  416.      * @return bool
  417.      */
  418.     public function hasEditable(string $name)
  419.     {
  420.         return $this->getEditable($name) !== null;
  421.     }
  422.     /**
  423.      * @return Editable[]
  424.      */
  425.     public function getEditables(): array
  426.     {
  427.         if ($this->editables === null) {
  428.             $this->setEditables($this->getDao()->getEditables());
  429.         }
  430.         return $this->editables;
  431.     }
  432.     /**
  433.      * @param array|null $editables
  434.      *
  435.      * @return $this
  436.      *
  437.      */
  438.     public function setEditables(?array $editables)
  439.     {
  440.         $this->editables $editables;
  441.         return $this;
  442.     }
  443.     /**
  444.      * @return Model\Version[]
  445.      */
  446.     public function getVersions()
  447.     {
  448.         if ($this->versions === null) {
  449.             $this->setVersions($this->getDao()->getVersions());
  450.         }
  451.         return $this->versions;
  452.     }
  453.     /**
  454.      * @param array $versions
  455.      *
  456.      * @return $this
  457.      */
  458.     public function setVersions($versions)
  459.     {
  460.         $this->versions $versions;
  461.         return $this;
  462.     }
  463.     /**
  464.      * @see Document::getFullPath
  465.      *
  466.      * @return string
  467.      */
  468.     public function getHref()
  469.     {
  470.         return $this->getFullPath();
  471.     }
  472.     /**
  473.      * {@inheritdoc}
  474.      */
  475.     public function __sleep()
  476.     {
  477.         $finalVars = [];
  478.         $parentVars parent::__sleep();
  479.         $blockedVars = ['inheritedEditables'];
  480.         foreach ($parentVars as $key) {
  481.             if (!in_array($key$blockedVars)) {
  482.                 $finalVars[] = $key;
  483.             }
  484.         }
  485.         return $finalVars;
  486.     }
  487.     /**
  488.      * @param string|null $hostname
  489.      * @param string|null $scheme
  490.      *
  491.      * @return string
  492.      *
  493.      * @throws \Exception
  494.      */
  495.     public function getUrl($hostname null$scheme null)
  496.     {
  497.         if (!$scheme) {
  498.             $scheme 'http://';
  499.             /** @var RequestHelper $requestHelper */
  500.             $requestHelper \Pimcore::getContainer()->get(RequestHelper::class);
  501.             if ($requestHelper->hasMainRequest()) {
  502.                 $scheme $requestHelper->getMainRequest()->getScheme() . '://';
  503.             }
  504.         }
  505.         if (!$hostname) {
  506.             $hostname \Pimcore\Config::getSystemConfiguration('general')['domain'];
  507.             if (empty($hostname)) {
  508.                 if (!$hostname \Pimcore\Tool::getHostname()) {
  509.                     throw new \Exception('No hostname available');
  510.                 }
  511.             }
  512.         }
  513.         $url $scheme $hostname $this->getFullPath();
  514.         $site \Pimcore\Tool\Frontend::getSiteForDocument($this);
  515.         if ($site instanceof Model\Site && $site->getMainDomain()) {
  516.             $url $scheme $site->getMainDomain() . preg_replace('@^' $site->getRootPath() . '/?@''/'$this->getRealFullPath());
  517.         }
  518.         return $url;
  519.     }
  520.     /**
  521.      * checks if the document is missing values for required editables
  522.      *
  523.      * @return bool|null
  524.      */
  525.     public function getMissingRequiredEditable()
  526.     {
  527.         return $this->missingRequiredEditable;
  528.     }
  529.     /**
  530.      * @param bool|null $missingRequiredEditable
  531.      *
  532.      * @return $this
  533.      */
  534.     public function setMissingRequiredEditable($missingRequiredEditable)
  535.     {
  536.         if ($missingRequiredEditable !== null) {
  537.             $missingRequiredEditable = (bool) $missingRequiredEditable;
  538.         }
  539.         $this->missingRequiredEditable $missingRequiredEditable;
  540.         return $this;
  541.     }
  542.     /**
  543.      * @internal
  544.      *
  545.      * @return bool
  546.      */
  547.     public function supportsContentMaster(): bool
  548.     {
  549.         return $this->supportsContentMaster;
  550.     }
  551.     /**
  552.      * Validates if there is a missing value for required editable
  553.      *
  554.      * @internal
  555.      */
  556.     protected function checkMissingRequiredEditable()
  557.     {
  558.         // load data which must be requested
  559.         $this->getProperties();
  560.         $this->getEditables();
  561.         //Allowed tags for required check
  562.         $allowedTypes = ['input''wysiwyg''textarea''numeric'];
  563.         if ($this->getMissingRequiredEditable() === null) {
  564.             /** @var EditableUsageResolver $editableUsageResolver */
  565.             $editableUsageResolver \Pimcore::getContainer()->get(EditableUsageResolver::class);
  566.             try {
  567.                 $documentCopy Service::cloneMe($this);
  568.                 if ($documentCopy instanceof self) {
  569.                     // rendering could fail if the controller/action doesn't exist, in this case we can skip the required check
  570.                     $editableNames $editableUsageResolver->getUsedEditableNames($documentCopy);
  571.                     foreach ($editableNames as $editableName) {
  572.                         $editable $documentCopy->getEditable($editableName);
  573.                         if ($editable instanceof Editable && in_array($editable->getType(), $allowedTypes)) {
  574.                             $editableConfig $editable->getConfig();
  575.                             if ($editable->isEmpty() && isset($editableConfig['required']) && $editableConfig['required'] == true) {
  576.                                 $this->setMissingRequiredEditable(true);
  577.                                 break;
  578.                             }
  579.                         }
  580.                     }
  581.                 }
  582.             } catch (\Exception $e) {
  583.                 // noting to do, as rendering the document failed for whatever reason
  584.             }
  585.         }
  586.     }
  587.     /**
  588.      * @return bool|null
  589.      */
  590.     public function getStaticGeneratorEnabled(): ?bool
  591.     {
  592.         return $this->staticGeneratorEnabled;
  593.     }
  594.     /**
  595.      * @param bool|null $staticGeneratorEnabled
  596.      */
  597.     public function setStaticGeneratorEnabled(?bool $staticGeneratorEnabled): void
  598.     {
  599.         $this->staticGeneratorEnabled $staticGeneratorEnabled;
  600.     }
  601.     /**
  602.      * @return int|null
  603.      */
  604.     public function getStaticGeneratorLifetime(): ?int
  605.     {
  606.         return $this->staticGeneratorLifetime;
  607.     }
  608.     /**
  609.      * @param int|null $staticGeneratorLifetime
  610.      */
  611.     public function setStaticGeneratorLifetime(?int $staticGeneratorLifetime): void
  612.     {
  613.         $this->staticGeneratorLifetime $staticGeneratorLifetime;
  614.     }
  615. }