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

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