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($event, DocumentEvents::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', $language, false, true);
  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($event, DocumentEvents::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($type, Document::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. }