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

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