vendor/pimcore/pimcore/models/Document/Service.php line 598

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\Config;
  16. use Pimcore\Document\Renderer\DocumentRenderer;
  17. use Pimcore\Document\Renderer\DocumentRendererInterface;
  18. use Pimcore\Event\DocumentEvents;
  19. use Pimcore\Event\Model\DocumentEvent;
  20. use Pimcore\File;
  21. use Pimcore\Image\Chromium;
  22. use Pimcore\Image\HtmlToImage;
  23. use Pimcore\Model;
  24. use Pimcore\Model\Document;
  25. use Pimcore\Model\Document\Editable\IdRewriterInterface;
  26. use Pimcore\Model\Document\Editable\LazyLoadingInterface;
  27. use Pimcore\Model\Element;
  28. use Pimcore\Tool;
  29. use Pimcore\Tool\Serialize;
  30. use Symfony\Component\HttpFoundation\Request;
  31. /**
  32.  * @method \Pimcore\Model\Document\Service\Dao getDao()
  33.  * @method int[] getTranslations(Document $document, string $task = 'open')
  34.  * @method addTranslation(Document $document, Document $translation, $language = null)
  35.  * @method removeTranslation(Document $document)
  36.  * @method int getTranslationSourceId(Document $document)
  37.  * @method removeTranslationLink(Document $document, Document $targetDocument)
  38.  */
  39. class Service extends Model\Element\Service
  40. {
  41.     /**
  42.      * @var Model\User|null
  43.      */
  44.     protected $_user;
  45.     /**
  46.      * @var array
  47.      */
  48.     protected $_copyRecursiveIds;
  49.     /**
  50.      * @var Document[]
  51.      */
  52.     protected $nearestPathCache;
  53.     /**
  54.      * @param Model\User $user
  55.      */
  56.     public function __construct($user null)
  57.     {
  58.         $this->_user $user;
  59.     }
  60.     /**
  61.      * Renders a document outside of a view
  62.      *
  63.      * Parameter order was kept for BC (useLayout before query and options).
  64.      *
  65.      * @static
  66.      *
  67.      * @param Document\PageSnippet $document
  68.      * @param array $attributes
  69.      * @param bool $useLayout
  70.      * @param array $query
  71.      * @param array $options
  72.      *
  73.      * @return string
  74.      */
  75.     public static function render(Document\PageSnippet $document, array $attributes = [], $useLayout false, array $query = [], array $options = []): string
  76.     {
  77.         $container \Pimcore::getContainer();
  78.         /** @var DocumentRendererInterface $renderer */
  79.         $renderer $container->get(DocumentRenderer::class);
  80.         // keep useLayout compatibility
  81.         $attributes['_useLayout'] = $useLayout;
  82.         $content $renderer->render($document$attributes$query$options);
  83.         return $content;
  84.     }
  85.     /**
  86.      * Save document and all child documents
  87.      *
  88.      * @param Document $document
  89.      * @param int $collectGarbageAfterIteration
  90.      * @param int $saved
  91.      *
  92.      * @throws \Exception
  93.      */
  94.     private static function saveRecursive($document$collectGarbageAfterIteration 25, &$saved 0)
  95.     {
  96.         if ($document instanceof Document) {
  97.             $document->save();
  98.             $saved++;
  99.             if ($saved $collectGarbageAfterIteration === 0) {
  100.                 \Pimcore::collectGarbage();
  101.             }
  102.         }
  103.         foreach ($document->getChildren() as $child) {
  104.             if (!$child->hasChildren()) {
  105.                 $child->save();
  106.                 $saved++;
  107.                 if ($saved $collectGarbageAfterIteration === 0) {
  108.                     \Pimcore::collectGarbage();
  109.                 }
  110.             }
  111.             if ($child->hasChildren()) {
  112.                 self::saveRecursive($child$collectGarbageAfterIteration$saved);
  113.             }
  114.         }
  115.     }
  116.     /**
  117.      * @param  Document $target
  118.      * @param  Document $source
  119.      *
  120.      * @return Document|null copied document
  121.      *
  122.      * @throws \Exception
  123.      */
  124.     public function copyRecursive($target$source)
  125.     {
  126.         // avoid recursion
  127.         if (!$this->_copyRecursiveIds) {
  128.             $this->_copyRecursiveIds = [];
  129.         }
  130.         if (in_array($source->getId(), $this->_copyRecursiveIds)) {
  131.             return null;
  132.         }
  133.         if ($source instanceof Document\PageSnippet) {
  134.             $source->getEditables();
  135.         }
  136.         $source->getProperties();
  137.         // triggers actions before document cloning
  138.         $event = new DocumentEvent($source, [
  139.             'target_element' => $target,
  140.         ]);
  141.         \Pimcore::getEventDispatcher()->dispatch($eventDocumentEvents::PRE_COPY);
  142.         $target $event->getArgument('target_element');
  143.         /** @var Document $new */
  144.         $new Element\Service::cloneMe($source);
  145.         $new->setId(null);
  146.         $new->setChildren(null);
  147.         $new->setKey(Element\Service::getSafeCopyName($new->getKey(), $target));
  148.         $new->setParentId($target->getId());
  149.         $new->setUserOwner($this->_user $this->_user->getId() : 0);
  150.         $new->setUserModification($this->_user $this->_user->getId() : 0);
  151.         $new->setDao(null);
  152.         $new->setLocked(null);
  153.         $new->setCreationDate(time());
  154.         if ($new instanceof Page) {
  155.             $new->setPrettyUrl(null);
  156.         }
  157.         $new->save();
  158.         // add to store
  159.         $this->_copyRecursiveIds[] = $new->getId();
  160.         foreach ($source->getChildren(true) as $child) {
  161.             $this->copyRecursive($new$child);
  162.         }
  163.         $this->updateChildren($target$new);
  164.         // triggers actions after the complete document cloning
  165.         $event = new DocumentEvent($new, [
  166.             'base_element' => $source// the element used to make a copy
  167.         ]);
  168.         \Pimcore::getEventDispatcher()->dispatch($eventDocumentEvents::POST_COPY);
  169.         return $new;
  170.     }
  171.     /**
  172.      * @param Document $target
  173.      * @param Document $source
  174.      * @param bool $enableInheritance
  175.      * @param bool $resetIndex
  176.      *
  177.      * @return Document
  178.      *
  179.      * @throws \Exception
  180.      */
  181.     public function copyAsChild($target$source$enableInheritance false$resetIndex false$language false)
  182.     {
  183.         if ($source instanceof Document\PageSnippet) {
  184.             $source->getEditables();
  185.         }
  186.         $source->getProperties();
  187.         // triggers actions before document cloning
  188.         $event = new DocumentEvent($source, [
  189.             'target_element' => $target,
  190.         ]);
  191.         \Pimcore::getEventDispatcher()->dispatch($eventDocumentEvents::PRE_COPY);
  192.         $target $event->getArgument('target_element');
  193.         /**
  194.          * @var Document $new
  195.          */
  196.         $new Element\Service::cloneMe($source);
  197.         $new->setId(null);
  198.         $new->setChildren(null);
  199.         $new->setKey(Element\Service::getSafeCopyName($new->getKey(), $target));
  200.         $new->setParentId($target->getId());
  201.         $new->setUserOwner($this->_user $this->_user->getId() : 0);
  202.         $new->setUserModification($this->_user $this->_user->getId() : 0);
  203.         $new->setDao(null);
  204.         $new->setLocked(null);
  205.         $new->setCreationDate(time());
  206.         if ($resetIndex) {
  207.             // this needs to be after $new->setParentId($target->getId()); -> dependency!
  208.             $new->setIndex($new->getDao()->getNextIndex());
  209.         }
  210.         if ($new instanceof Page) {
  211.             $new->setPrettyUrl(null);
  212.         }
  213.         if ($enableInheritance && ($new instanceof Document\PageSnippet) && $new->supportsContentMaster()) {
  214.             $new->setEditables([]);
  215.             $new->setContentMasterDocumentId($source->getId(), true);
  216.         }
  217.         if ($language) {
  218.             $new->setProperty('language''text'$languagefalsetrue);
  219.         }
  220.         $new->save();
  221.         $this->updateChildren($target$new);
  222.         //link translated document
  223.         if ($language) {
  224.             $this->addTranslation($source$new$language);
  225.         }
  226.         // triggers actions after the complete document cloning
  227.         $event = new DocumentEvent($new, [
  228.             'base_element' => $source// the element used to make a copy
  229.         ]);
  230.         \Pimcore::getEventDispatcher()->dispatch($eventDocumentEvents::POST_COPY);
  231.         return $new;
  232.     }
  233.     /**
  234.      * @param Document $target
  235.      * @param Document $source
  236.      *
  237.      * @return Document
  238.      *
  239.      * @throws \Exception
  240.      */
  241.     public function copyContents($target$source)
  242.     {
  243.         // check if the type is the same
  244.         if (get_class($source) != get_class($target)) {
  245.             throw new \Exception('Source and target have to be the same type');
  246.         }
  247.         if ($source instanceof Document\PageSnippet) {
  248.             /** @var PageSnippet $target */
  249.             $target->setEditables($source->getEditables());
  250.             $target->setTemplate($source->getTemplate());
  251.             $target->setController($source->getController());
  252.             if ($source instanceof Document\Page) {
  253.                 /** @var Page $target */
  254.                 $target->setTitle($source->getTitle());
  255.                 $target->setDescription($source->getDescription());
  256.             }
  257.         } elseif ($source instanceof Document\Link) {
  258.             /** @var Link $target */
  259.             $target->setInternalType($source->getInternalType());
  260.             $target->setInternal($source->getInternal());
  261.             $target->setDirect($source->getDirect());
  262.             $target->setLinktype($source->getLinktype());
  263.         }
  264.         $target->setUserModification($this->_user $this->_user->getId() : 0);
  265.         $target->setProperties(self::cloneProperties($source->getProperties()));
  266.         $target->save();
  267.         return $target;
  268.     }
  269.     /**
  270.      * @param Document $document
  271.      *
  272.      * @return array
  273.      *
  274.      * @internal
  275.      */
  276.     public static function gridDocumentData($document)
  277.     {
  278.         $data Element\Service::gridElementData($document);
  279.         if ($document instanceof Document\Page) {
  280.             $data['title'] = $document->getTitle();
  281.             $data['description'] = $document->getDescription();
  282.         } else {
  283.             $data['title'] = '';
  284.             $data['description'] = '';
  285.             $data['name'] = '';
  286.         }
  287.         return $data;
  288.     }
  289.     /**
  290.      * @internal
  291.      *
  292.      * @param Document $doc
  293.      *
  294.      * @return Document
  295.      */
  296.     public static function loadAllDocumentFields($doc)
  297.     {
  298.         $doc->getProperties();
  299.         if ($doc instanceof Document\PageSnippet) {
  300.             foreach ($doc->getEditables() as $name => $data) {
  301.                 //TODO Pimcore 11: remove method_exists BC layer
  302.                 if ($data instanceof LazyLoadingInterface || method_exists($data'load')) {
  303.                     if (!$data instanceof LazyLoadingInterface) {
  304.                         trigger_deprecation('pimcore/pimcore''10.3',
  305.                             sprintf('Usage of method_exists is deprecated since version 10.3 and will be removed in Pimcore 11.' .
  306.                                 'Implement the %s interface instead.'LazyLoadingInterface::class));
  307.                     }
  308.                     $data->load();
  309.                 }
  310.             }
  311.         }
  312.         return $doc;
  313.     }
  314.     /**
  315.      * @static
  316.      *
  317.      * @param string $path
  318.      * @param string|null $type
  319.      *
  320.      * @return bool
  321.      */
  322.     public static function pathExists($path$type null)
  323.     {
  324.         if (!$path) {
  325.             return false;
  326.         }
  327.         $path Element\Service::correctPath($path);
  328.         try {
  329.             $document = new Document();
  330.             // validate path
  331.             if (self::isValidPath($path'document')) {
  332.                 $document->getDao()->getByPath($path);
  333.                 return true;
  334.             }
  335.         } catch (\Exception $e) {
  336.         }
  337.         return false;
  338.     }
  339.     /**
  340.      * @param string $type
  341.      *
  342.      * @return bool
  343.      */
  344.     public static function isValidType($type)
  345.     {
  346.         return in_array($typeDocument::getTypes());
  347.     }
  348.     /**
  349.      * Rewrites id from source to target, $rewriteConfig contains
  350.      * array(
  351.      *  "document" => array(
  352.      *      SOURCE_ID => TARGET_ID,
  353.      *      SOURCE_ID => TARGET_ID
  354.      *  ),
  355.      *  "object" => array(...),
  356.      *  "asset" => array(...)
  357.      * )
  358.      *
  359.      * @internal
  360.      *
  361.      * @param Document $document
  362.      * @param array $rewriteConfig
  363.      * @param array $params
  364.      *
  365.      * @return Document
  366.      */
  367.     public static function rewriteIds($document$rewriteConfig$params = [])
  368.     {
  369.         // rewriting elements only for snippets and pages
  370.         if ($document instanceof Document\PageSnippet) {
  371.             if (array_key_exists('enableInheritance'$params) && $params['enableInheritance']) {
  372.                 $editables $document->getEditables();
  373.                 $changedEditables = [];
  374.                 $contentMaster $document->getContentMasterDocument();
  375.                 if ($contentMaster instanceof Document\PageSnippet) {
  376.                     $contentMasterEditables $contentMaster->getEditables();
  377.                     foreach ($contentMasterEditables as $contentMasterEditable) {
  378.                         //TODO Pimcore 11: remove method_exists BC layer
  379.                         if ($contentMasterEditable instanceof IdRewriterInterface || method_exists($contentMasterEditable'rewriteIds')) {
  380.                             if (!$contentMasterEditable instanceof IdRewriterInterface) {
  381.                                 trigger_deprecation('pimcore/pimcore''10.3',
  382.                                     sprintf('Usage of method_exists is deprecated since version 10.3 and will be removed in Pimcore 11.' .
  383.                                         'Implement the %s interface instead.'IdRewriterInterface::class));
  384.                             }
  385.                             $editable = clone $contentMasterEditable;
  386.                             $editable->rewriteIds($rewriteConfig);
  387.                             if (Serialize::serialize($editable) != Serialize::serialize($contentMasterEditable)) {
  388.                                 $changedEditables[] = $editable;
  389.                             }
  390.                         }
  391.                     }
  392.                 }
  393.                 if (count($changedEditables) > 0) {
  394.                     $editables $changedEditables;
  395.                 }
  396.             } else {
  397.                 $editables $document->getEditables();
  398.                 foreach ($editables as &$editable) {
  399.                     //TODO Pimcore 11: remove method_exists BC layer
  400.                     if ($editable instanceof IdRewriterInterface || method_exists($editable'rewriteIds')) {
  401.                         if (!$editable instanceof IdRewriterInterface) {
  402.                             trigger_deprecation('pimcore/pimcore''10.3',
  403.                                 sprintf('Usage of method_exists is deprecated since version 10.3 and will be removed in Pimcore 11.' .
  404.                                     'Implement the %s interface instead.'IdRewriterInterface::class));
  405.                         }
  406.                         $editable->rewriteIds($rewriteConfig);
  407.                     }
  408.                 }
  409.             }
  410.             $document->setEditables($editables);
  411.         } elseif ($document instanceof Document\Hardlink) {
  412.             if (array_key_exists('document'$rewriteConfig) && $document->getSourceId() && array_key_exists((int) $document->getSourceId(), $rewriteConfig['document'])) {
  413.                 $document->setSourceId($rewriteConfig['document'][(int) $document->getSourceId()]);
  414.             }
  415.         } elseif ($document instanceof Document\Link) {
  416.             if (array_key_exists('document'$rewriteConfig) && $document->getLinktype() == 'internal' && $document->getInternalType() == 'document' && array_key_exists((int) $document->getInternal(), $rewriteConfig['document'])) {
  417.                 $document->setInternal($rewriteConfig['document'][(int) $document->getInternal()]);
  418.             }
  419.         }
  420.         // rewriting properties
  421.         $properties $document->getProperties();
  422.         foreach ($properties as &$property) {
  423.             $property->rewriteIds($rewriteConfig);
  424.         }
  425.         $document->setProperties($properties);
  426.         return $document;
  427.     }
  428.     /**
  429.      * @internal
  430.      *
  431.      * @param string $url
  432.      *
  433.      * @return Document|null
  434.      */
  435.     public static function getByUrl($url)
  436.     {
  437.         $urlParts parse_url($url);
  438.         $document null;
  439.         if ($urlParts['path']) {
  440.             $document Document::getByPath($urlParts['path']);
  441.             // search for a page in a site
  442.             if (!$document) {
  443.                 $sitesList = new Model\Site\Listing();
  444.                 $sitesObjects $sitesList->load();
  445.                 foreach ($sitesObjects as $site) {
  446.                     if ($site->getRootDocument() && (in_array($urlParts['host'], $site->getDomains()) || $site->getMainDomain() == $urlParts['host'])) {
  447.                         if ($document Document::getByPath($site->getRootDocument() . $urlParts['path'])) {
  448.                             break;
  449.                         }
  450.                     }
  451.                 }
  452.             }
  453.         }
  454.         return $document;
  455.     }
  456.     /**
  457.      * @param Document $item
  458.      * @param int $nr
  459.      *
  460.      * @return string
  461.      *
  462.      * @throws \Exception
  463.      */
  464.     public static function getUniqueKey($item$nr 0)
  465.     {
  466.         $list = new Listing();
  467.         $list->setUnpublished(true);
  468.         $key Element\Service::getValidKey($item->getKey(), 'document');
  469.         if (!$key) {
  470.             throw new \Exception('No item key set.');
  471.         }
  472.         if ($nr) {
  473.             $key $key '_' $nr;
  474.         }
  475.         $parent $item->getParent();
  476.         if (!$parent) {
  477.             throw new \Exception('You have to set a parent document to determine a unique Key');
  478.         }
  479.         if (!$item->getId()) {
  480.             $list->setCondition('parentId = ? AND `key` = ? ', [$parent->getId(), $key]);
  481.         } else {
  482.             $list->setCondition('parentId = ? AND `key` = ? AND id != ? ', [$parent->getId(), $key$item->getId()]);
  483.         }
  484.         $check $list->loadIdList();
  485.         if (!empty($check)) {
  486.             $nr++;
  487.             $key self::getUniqueKey($item$nr);
  488.         }
  489.         return $key;
  490.     }
  491.     /**
  492.      * Get the nearest document by path. Used to match nearest document for a static route.
  493.      *
  494.      * @internal
  495.      *
  496.      * @param string|Request $path
  497.      * @param bool $ignoreHardlinks
  498.      * @param array $types
  499.      *
  500.      * @return Document|null
  501.      */
  502.     public function getNearestDocumentByPath($path$ignoreHardlinks false$types = [])
  503.     {
  504.         if ($path instanceof Request) {
  505.             $path urldecode($path->getPathInfo());
  506.         }
  507.         $cacheKey $ignoreHardlinks implode('-'$types);
  508.         $document null;
  509.         if (isset($this->nearestPathCache[$cacheKey])) {
  510.             $document $this->nearestPathCache[$cacheKey];
  511.         } else {
  512.             $paths = ['/'];
  513.             $tmpPaths = [];
  514.             $pathParts explode('/'$path);
  515.             foreach ($pathParts as $pathPart) {
  516.                 $tmpPaths[] = $pathPart;
  517.                 $t implode('/'$tmpPaths);
  518.                 $paths[] = $t;
  519.             }
  520.             $paths array_reverse($paths);
  521.             foreach ($paths as $p) {
  522.                 if ($document Document::getByPath($p)) {
  523.                     if (empty($types) || in_array($document->getType(), $types)) {
  524.                         $document $this->nearestPathCache[$cacheKey] = $document;
  525.                         break;
  526.                     }
  527.                 } elseif (Model\Site::isSiteRequest()) {
  528.                     // also check for a pretty url in a site
  529.                     $site Model\Site::getCurrentSite();
  530.                     // undo the changed made by the site detection in self::match()
  531.                     $originalPath preg_replace('@^' $site->getRootPath() . '@'''$p);
  532.                     $sitePrettyDocId $this->getDao()->getDocumentIdByPrettyUrlInSite($site$originalPath);
  533.                     if ($sitePrettyDocId) {
  534.                         if ($sitePrettyDoc Document::getById($sitePrettyDocId)) {
  535.                             $document $this->nearestPathCache[$cacheKey] = $sitePrettyDoc;
  536.                             break;
  537.                         }
  538.                     }
  539.                 }
  540.             }
  541.         }
  542.         if ($document) {
  543.             if (!$ignoreHardlinks) {
  544.                 if ($document instanceof Document\Hardlink) {
  545.                     if ($hardLinkedDocument Document\Hardlink\Service::getNearestChildByPath($document$path)) {
  546.                         $document $hardLinkedDocument;
  547.                     } else {
  548.                         $document Document\Hardlink\Service::wrap($document);
  549.                     }
  550.                 }
  551.             }
  552.             return $document;
  553.         }
  554.         return null;
  555.     }
  556.     /**
  557.      * @param int $id
  558.      * @param Request $request
  559.      * @param string $hostUrl
  560.      *
  561.      * @return bool
  562.      *
  563.      * @throws \Exception
  564.      *
  565.      * @internal
  566.      */
  567.     public static function generatePagePreview($id$request null$hostUrl null)
  568.     {
  569.         $success false;
  570.         /** @var Page $doc */
  571.         $doc Document::getById($id);
  572.         if (!$hostUrl) {
  573.             $hostUrl Config::getSystemConfiguration('documents')['preview_url_prefix'];
  574.             if (empty($hostUrl)) {
  575.                 $hostUrl Tool::getHostUrl(null$request);
  576.             }
  577.         }
  578.         $url $hostUrl $doc->getRealFullPath();
  579.         $tmpFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/screenshot_tmp_' $doc->getId() . '.png';
  580.         $file $doc->getPreviewImageFilesystemPath();
  581.         File::mkdir(dirname($file));
  582.         $tool false;
  583.         if (Chromium::isSupported()) {
  584.             $tool Chromium::class;
  585.         } elseif (HtmlToImage::isSupported()) {
  586.             $tool HtmlToImage::class;
  587.         }
  588.         if ($tool) {
  589.             /** @var Chromium|HtmlToImage $tool */
  590.             if ($tool::convert($url$tmpFile)) {
  591.                 $im \Pimcore\Image::getInstance();
  592.                 $im->load($tmpFile);
  593.                 $im->scaleByWidth(800);
  594.                 $im->save($file'jpeg'85);
  595.                 unlink($tmpFile);
  596.                 $success true;
  597.             }
  598.         }
  599.         return $success;
  600.     }
  601. }