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

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