vendor/pimcore/pimcore/models/Document.php line 266

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;
  15. use Doctrine\DBAL\Exception\DeadlockException;
  16. use Pimcore\Event\DocumentEvents;
  17. use Pimcore\Event\FrontendEvents;
  18. use Pimcore\Event\Model\DocumentEvent;
  19. use Pimcore\Logger;
  20. use Pimcore\Model\Document\Hardlink;
  21. use Pimcore\Model\Document\Hardlink\Wrapper\WrapperInterface;
  22. use Pimcore\Model\Document\Listing;
  23. use Pimcore\Model\Element\ElementInterface;
  24. use Pimcore\Model\Exception\NotFoundException;
  25. use Pimcore\Tool;
  26. use Pimcore\Tool\Frontend as FrontendTool;
  27. use Symfony\Cmf\Bundle\RoutingBundle\Routing\DynamicRouter;
  28. use Symfony\Component\EventDispatcher\GenericEvent;
  29. /**
  30. * @method \Pimcore\Model\Document\Dao getDao()
  31. * @method bool __isBasedOnLatestData()
  32. * @method int getChildAmount($user = null)
  33. * @method string getCurrentFullPath()
  34. */
  35. class Document extends Element\AbstractElement
  36. {
  37. /**
  38. * all possible types of documents
  39. *
  40. * @internal
  41. *
  42. * @deprecated will be removed in Pimcore 11. Use getTypes() method.
  43. *
  44. * @var array
  45. */
  46. public static $types = ['folder', 'page', 'snippet', 'link', 'hardlink', 'email', 'newsletter', 'printpage', 'printcontainer'];
  47. /**
  48. * @var bool
  49. */
  50. private static $hideUnpublished = false;
  51. /**
  52. * @internal
  53. *
  54. * @var string|null
  55. */
  56. protected $fullPathCache;
  57. /**
  58. * @internal
  59. *
  60. * @var int
  61. */
  62. protected $id;
  63. /**
  64. * @internal
  65. *
  66. * @var int
  67. */
  68. protected $parentId;
  69. /**
  70. * @internal
  71. *
  72. * @var self|null
  73. */
  74. protected $parent;
  75. /**
  76. * @internal
  77. *
  78. * @var string
  79. */
  80. protected string $type;
  81. /**
  82. * @internal
  83. *
  84. * @var string
  85. */
  86. protected $key;
  87. /**
  88. * @internal
  89. *
  90. * @var string
  91. */
  92. protected $path;
  93. /**
  94. * @internal
  95. *
  96. * @var int|null
  97. */
  98. protected ?int $index = null;
  99. /**
  100. * @internal
  101. *
  102. * @var bool
  103. */
  104. protected bool $published = true;
  105. /**
  106. * @internal
  107. *
  108. * @var int
  109. */
  110. protected $creationDate;
  111. /**
  112. * @internal
  113. *
  114. * @var int
  115. */
  116. protected $modificationDate;
  117. /**
  118. * @internal
  119. *
  120. * @var int|null
  121. */
  122. protected ?int $userOwner = null;
  123. /**
  124. * @internal
  125. *
  126. * @var int|null
  127. */
  128. protected ?int $userModification = null;
  129. /**
  130. * @internal
  131. *
  132. * @var array|null
  133. */
  134. protected $properties = null;
  135. /**
  136. * @internal
  137. *
  138. * @var array
  139. */
  140. protected $children = [];
  141. /**
  142. * @internal
  143. *
  144. * @var bool[]
  145. */
  146. protected $hasChildren = [];
  147. /**
  148. * @internal
  149. *
  150. * @var array
  151. */
  152. protected $siblings = [];
  153. /**
  154. * @internal
  155. *
  156. * @var bool[]
  157. */
  158. protected $hasSiblings = [];
  159. /**
  160. * enum('self','propagate') nullable
  161. *
  162. * @internal
  163. *
  164. * @var string|null
  165. */
  166. protected $locked = null;
  167. /**
  168. * @internal
  169. *
  170. * @var int
  171. */
  172. protected $versionCount;
  173. /**
  174. * get possible types
  175. *
  176. * @return array
  177. */
  178. public static function getTypes()
  179. {
  180. $documentsConfig = \Pimcore\Config::getSystemConfiguration('documents');
  181. return $documentsConfig['types'];
  182. }
  183. /**
  184. * @param string $path
  185. * @param bool $force
  186. *
  187. * @return static|null
  188. */
  189. public static function getByPath($path, $force = false)
  190. {
  191. $path = Element\Service::correctPath($path);
  192. $cacheKey = 'document_path_' . md5($path);
  193. if (\Pimcore\Cache\Runtime::isRegistered($cacheKey)) {
  194. return \Pimcore\Cache\Runtime::get($cacheKey);
  195. }
  196. $doc = null;
  197. try {
  198. $helperDoc = new Document();
  199. $helperDoc->getDao()->getByPath($path);
  200. $doc = static::getById($helperDoc->getId(), $force);
  201. \Pimcore\Cache\Runtime::set($cacheKey, $doc);
  202. } catch (NotFoundException $e) {
  203. $doc = null;
  204. }
  205. return $doc;
  206. }
  207. /**
  208. * @internal
  209. *
  210. * @param Document $document
  211. *
  212. * @return bool
  213. */
  214. protected static function typeMatch(Document $document)
  215. {
  216. $staticType = get_called_class();
  217. if ($staticType != Document::class) {
  218. if (!$document instanceof $staticType) {
  219. return false;
  220. }
  221. }
  222. return true;
  223. }
  224. /**
  225. * @param int $id
  226. * @param bool $force
  227. *
  228. * @return static|null
  229. */
  230. public static function getById($id, $force = false)
  231. {
  232. if (!is_numeric($id) || $id < 1) {
  233. return null;
  234. }
  235. $id = (int)$id;
  236. $cacheKey = self::getCacheKey($id);
  237. if (!$force && \Pimcore\Cache\Runtime::isRegistered($cacheKey)) {
  238. $document = \Pimcore\Cache\Runtime::get($cacheKey);
  239. if ($document && static::typeMatch($document)) {
  240. return $document;
  241. }
  242. }
  243. if ($force || !($document = \Pimcore\Cache::load($cacheKey))) {
  244. $document = new Document();
  245. try {
  246. $document->getDao()->getById($id);
  247. } catch (NotFoundException $e) {
  248. return null;
  249. }
  250. $className = 'Pimcore\\Model\\Document\\' . ucfirst($document->getType());
  251. // this is the fallback for custom document types using prefixes
  252. // so we need to check if the class exists first
  253. if (!Tool::classExists($className)) {
  254. $oldStyleClass = 'Document_' . ucfirst($document->getType());
  255. if (Tool::classExists($oldStyleClass)) {
  256. $className = $oldStyleClass;
  257. }
  258. }
  259. /** @var Document $document */
  260. $document = self::getModelFactory()->build($className);
  261. \Pimcore\Cache\Runtime::set($cacheKey, $document);
  262. $document->getDao()->getById($id);
  263. $document->__setDataVersionTimestamp($document->getModificationDate());
  264. $document->resetDirtyMap();
  265. \Pimcore\Cache::save($document, $cacheKey);
  266. } else {
  267. \Pimcore\Cache\Runtime::set($cacheKey, $document);
  268. }
  269. if (!$document || !static::typeMatch($document)) {
  270. return null;
  271. }
  272. return $document;
  273. }
  274. /**
  275. * @param int $parentId
  276. * @param array $data
  277. * @param bool $save
  278. *
  279. * @return static
  280. */
  281. public static function create($parentId, $data = [], $save = true)
  282. {
  283. $document = new static();
  284. $document->setParentId($parentId);
  285. self::checkCreateData($data);
  286. $document->setValues($data);
  287. if ($save) {
  288. $document->save();
  289. }
  290. return $document;
  291. }
  292. /**
  293. * @param array $config
  294. *
  295. * @return Listing
  296. *
  297. * @throws \Exception
  298. */
  299. public static function getList(array $config = []): Listing
  300. {
  301. /** @var Listing $list */
  302. $list = self::getModelFactory()->build(Listing::class);
  303. $list->setValues($config);
  304. return $list;
  305. }
  306. /**
  307. * @deprecated will be removed in Pimcore 11
  308. *
  309. * @param array $config
  310. *
  311. * @return int count
  312. */
  313. public static function getTotalCount(array $config = []): int
  314. {
  315. $list = static::getList($config);
  316. return $list->getTotalCount();
  317. }
  318. /**
  319. * {@inheritdoc}
  320. */
  321. public function save()
  322. {
  323. $isUpdate = false;
  324. try {
  325. // additional parameters (e.g. "versionNote" for the version note)
  326. $params = [];
  327. if (func_num_args() && is_array(func_get_arg(0))) {
  328. $params = func_get_arg(0);
  329. }
  330. $preEvent = new DocumentEvent($this, $params);
  331. if ($this->getId()) {
  332. $isUpdate = true;
  333. \Pimcore::getEventDispatcher()->dispatch($preEvent, DocumentEvents::PRE_UPDATE);
  334. } else {
  335. \Pimcore::getEventDispatcher()->dispatch($preEvent, DocumentEvents::PRE_ADD);
  336. }
  337. $params = $preEvent->getArguments();
  338. $this->correctPath();
  339. $differentOldPath = null;
  340. // we wrap the save actions in a loop here, so that we can restart the database transactions in the case it fails
  341. // if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out
  342. // this is especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...)
  343. $maxRetries = 5;
  344. for ($retries = 0; $retries < $maxRetries; $retries++) {
  345. $this->beginTransaction();
  346. try {
  347. $this->updateModificationInfos();
  348. if (!$isUpdate) {
  349. $this->getDao()->create();
  350. }
  351. // get the old path from the database before the update is done
  352. $oldPath = null;
  353. if ($isUpdate) {
  354. $oldPath = $this->getDao()->getCurrentFullPath();
  355. }
  356. $this->update($params);
  357. // if the old path is different from the new path, update all children
  358. $updatedChildren = [];
  359. if ($oldPath && $oldPath != $this->getRealFullPath()) {
  360. $differentOldPath = $oldPath;
  361. $this->getDao()->updateWorkspaces();
  362. $updatedChildren = $this->getDao()->updateChildPaths($oldPath);
  363. }
  364. $this->commit();
  365. break; // transaction was successfully completed, so we cancel the loop here -> no restart required
  366. } catch (\Exception $e) {
  367. try {
  368. $this->rollBack();
  369. } catch (\Exception $er) {
  370. // PDO adapter throws exceptions if rollback fails
  371. Logger::error($er);
  372. }
  373. // we try to start the transaction $maxRetries times again (deadlocks, ...)
  374. if ($e instanceof DeadlockException && $retries < ($maxRetries - 1)) {
  375. $run = $retries + 1;
  376. $waitTime = rand(1, 5) * 100000; // microseconds
  377. Logger::warn('Unable to finish transaction (' . $run . ". run) because of the following reason '" . $e->getMessage() . "'. --> Retrying in " . $waitTime . ' microseconds ... (' . ($run + 1) . ' of ' . $maxRetries . ')');
  378. usleep($waitTime); // wait specified time until we restart the transaction
  379. } else {
  380. // if the transaction still fail after $maxRetries retries, we throw out the exception
  381. throw $e;
  382. }
  383. }
  384. }
  385. $additionalTags = [];
  386. if (isset($updatedChildren) && is_array($updatedChildren)) {
  387. foreach ($updatedChildren as $documentId) {
  388. $tag = 'document_' . $documentId;
  389. $additionalTags[] = $tag;
  390. // remove the child also from registry (internal cache) to avoid path inconsistencies during long running scripts, such as CLI
  391. \Pimcore\Cache\Runtime::set($tag, null);
  392. }
  393. }
  394. $this->clearDependentCache($additionalTags);
  395. $postEvent = new DocumentEvent($this, $params);
  396. if ($isUpdate) {
  397. if ($differentOldPath) {
  398. $postEvent->setArgument('oldPath', $differentOldPath);
  399. }
  400. \Pimcore::getEventDispatcher()->dispatch($postEvent, DocumentEvents::POST_UPDATE);
  401. } else {
  402. \Pimcore::getEventDispatcher()->dispatch($postEvent, DocumentEvents::POST_ADD);
  403. }
  404. return $this;
  405. } catch (\Exception $e) {
  406. $failureEvent = new DocumentEvent($this, $params);
  407. $failureEvent->setArgument('exception', $e);
  408. if ($isUpdate) {
  409. \Pimcore::getEventDispatcher()->dispatch($failureEvent, DocumentEvents::POST_UPDATE_FAILURE);
  410. } else {
  411. \Pimcore::getEventDispatcher()->dispatch($failureEvent, DocumentEvents::POST_ADD_FAILURE);
  412. }
  413. throw $e;
  414. }
  415. }
  416. /**
  417. * @throws \Exception
  418. */
  419. private function correctPath()
  420. {
  421. // set path
  422. if ($this->getId() != 1) { // not for the root node
  423. // check for a valid key, home has no key, so omit the check
  424. if (!Element\Service::isValidKey($this->getKey(), 'document')) {
  425. throw new \Exception('invalid key for document with id [ ' . $this->getId() . ' ] key is: [' . $this->getKey() . ']');
  426. }
  427. if ($this->getParentId() == $this->getId()) {
  428. throw new \Exception("ParentID and ID is identical, an element can't be the parent of itself.");
  429. }
  430. $parent = Document::getById($this->getParentId());
  431. if ($parent) {
  432. // use the parent's path from the database here (getCurrentFullPath), to ensure the path really exists and does not rely on the path
  433. // that is currently in the parent object (in memory), because this might have changed but wasn't not saved
  434. $this->setPath(str_replace('//', '/', $parent->getCurrentFullPath() . '/'));
  435. } else {
  436. // parent document doesn't exist anymore, set the parent to to root
  437. $this->setParentId(1);
  438. $this->setPath('/');
  439. }
  440. if (strlen($this->getKey()) < 1) {
  441. throw new \Exception('Document requires key, generated key automatically');
  442. }
  443. } elseif ($this->getId() == 1) {
  444. // some data in root node should always be the same
  445. $this->setParentId(0);
  446. $this->setPath('/');
  447. $this->setKey('');
  448. $this->setType('page');
  449. }
  450. if (Document\Service::pathExists($this->getRealFullPath())) {
  451. $duplicate = Document::getByPath($this->getRealFullPath());
  452. if ($duplicate instanceof Document && $duplicate->getId() != $this->getId()) {
  453. throw new \Exception('Duplicate full path [ ' . $this->getRealFullPath() . ' ] - cannot save document');
  454. }
  455. }
  456. $this->validatePathLength();
  457. }
  458. /**
  459. * @internal
  460. *
  461. * @param array $params additional parameters (e.g. "versionNote" for the version note)
  462. *
  463. * @throws \Exception
  464. */
  465. protected function update($params = [])
  466. {
  467. $disallowedKeysInFirstLevel = ['install', 'admin', 'plugin'];
  468. if ($this->getParentId() == 1 && in_array($this->getKey(), $disallowedKeysInFirstLevel)) {
  469. throw new \Exception('Key: ' . $this->getKey() . ' is not allowed in first level (root-level)');
  470. }
  471. // set index if null
  472. if ($this->getIndex() === null) {
  473. $this->setIndex($this->getDao()->getNextIndex());
  474. }
  475. // save properties
  476. $this->getProperties();
  477. $this->getDao()->deleteAllProperties();
  478. if (is_array($this->getProperties()) && count($this->getProperties()) > 0) {
  479. foreach ($this->getProperties() as $property) {
  480. if (!$property->getInherited()) {
  481. $property->setDao(null);
  482. $property->setCid($this->getId());
  483. $property->setCtype('document');
  484. $property->setCpath($this->getRealFullPath());
  485. $property->save();
  486. }
  487. }
  488. }
  489. // save dependencies
  490. $d = new Dependency();
  491. $d->setSourceType('document');
  492. $d->setSourceId($this->getId());
  493. foreach ($this->resolveDependencies() as $requirement) {
  494. if ($requirement['id'] == $this->getId() && $requirement['type'] == 'document') {
  495. // dont't add a reference to yourself
  496. continue;
  497. } else {
  498. $d->addRequirement($requirement['id'], $requirement['type']);
  499. }
  500. }
  501. $d->save();
  502. $this->getDao()->update();
  503. //set document to registry
  504. \Pimcore\Cache\Runtime::set(self::getCacheKey($this->getId()), $this);
  505. }
  506. /**
  507. * @internal
  508. *
  509. * @param int $index
  510. */
  511. public function saveIndex($index)
  512. {
  513. $this->getDao()->saveIndex($index);
  514. $this->clearDependentCache();
  515. }
  516. /**
  517. * {@inheritdoc}
  518. */
  519. public function clearDependentCache($additionalTags = [])
  520. {
  521. try {
  522. $tags = [$this->getCacheTag(), 'document_properties', 'output'];
  523. $tags = array_merge($tags, $additionalTags);
  524. \Pimcore\Cache::clearTags($tags);
  525. } catch (\Exception $e) {
  526. Logger::crit($e);
  527. }
  528. }
  529. /**
  530. * set the children of the document
  531. *
  532. * @param self[] $children
  533. * @param bool $includingUnpublished
  534. *
  535. * @return $this
  536. */
  537. public function setChildren($children, $includingUnpublished = false)
  538. {
  539. if (empty($children)) {
  540. // unset all cached children
  541. $this->hasChildren = [];
  542. $this->children = [];
  543. } elseif (is_array($children)) {
  544. $cacheKey = $this->getListingCacheKey([$includingUnpublished]);
  545. $this->children[$cacheKey] = $children;
  546. $this->hasChildren[$cacheKey] = (bool) count($children);
  547. }
  548. return $this;
  549. }
  550. /**
  551. * Get a list of the children (not recursivly)
  552. *
  553. * @param bool $includingUnpublished
  554. *
  555. * @return self[]
  556. */
  557. public function getChildren($includingUnpublished = false)
  558. {
  559. $cacheKey = $this->getListingCacheKey(func_get_args());
  560. if (!isset($this->children[$cacheKey])) {
  561. $list = new Document\Listing();
  562. $list->setUnpublished($includingUnpublished);
  563. $list->setCondition('parentId = ?', $this->getId());
  564. $list->setOrderKey('index');
  565. $list->setOrder('asc');
  566. $this->children[$cacheKey] = $list->load();
  567. }
  568. return $this->children[$cacheKey];
  569. }
  570. /**
  571. * Returns true if the document has at least one child
  572. *
  573. * @param bool $includingUnpublished
  574. *
  575. * @return bool
  576. */
  577. public function hasChildren($includingUnpublished = null)
  578. {
  579. $cacheKey = $this->getListingCacheKey(func_get_args());
  580. if (isset($this->hasChildren[$cacheKey])) {
  581. return $this->hasChildren[$cacheKey];
  582. }
  583. return $this->hasChildren[$cacheKey] = $this->getDao()->hasChildren($includingUnpublished);
  584. }
  585. /**
  586. * Get a list of the sibling documents
  587. *
  588. * @param bool $includingUnpublished
  589. *
  590. * @return array
  591. */
  592. public function getSiblings($includingUnpublished = false)
  593. {
  594. $cacheKey = $this->getListingCacheKey(func_get_args());
  595. if (!isset($this->siblings[$cacheKey])) {
  596. $list = new Document\Listing();
  597. $list->setUnpublished($includingUnpublished);
  598. // string conversion because parentId could be 0
  599. $list->addConditionParam('parentId = ?', (string)$this->getParentId());
  600. $list->addConditionParam('id != ?', $this->getId());
  601. $list->setOrderKey('index');
  602. $list->setOrder('asc');
  603. $this->siblings[$cacheKey] = $list->load();
  604. $this->hasSiblings[$cacheKey] = (bool) count($this->siblings[$cacheKey]);
  605. }
  606. return $this->siblings[$cacheKey];
  607. }
  608. /**
  609. * Returns true if the document has at least one sibling
  610. *
  611. * @param bool|null $includingUnpublished
  612. *
  613. * @return bool
  614. */
  615. public function hasSiblings($includingUnpublished = null)
  616. {
  617. $cacheKey = $this->getListingCacheKey(func_get_args());
  618. if (isset($this->hasSiblings[$cacheKey])) {
  619. return $this->hasSiblings[$cacheKey];
  620. }
  621. return $this->hasSiblings[$cacheKey] = $this->getDao()->hasSiblings($includingUnpublished);
  622. }
  623. /**
  624. * {@inheritdoc}
  625. */
  626. public function getLocked()
  627. {
  628. if (empty($this->locked)) {
  629. return null;
  630. }
  631. return $this->locked;
  632. }
  633. /**
  634. * {@inheritdoc}
  635. */
  636. public function setLocked($locked)
  637. {
  638. $this->locked = $locked;
  639. return $this;
  640. }
  641. /**
  642. * @internal
  643. *
  644. * @throws \Exception
  645. */
  646. protected function doDelete()
  647. {
  648. // remove children
  649. if ($this->hasChildren()) {
  650. // delete also unpublished children
  651. $unpublishedStatus = self::doHideUnpublished();
  652. self::setHideUnpublished(false);
  653. foreach ($this->getChildren(true) as $child) {
  654. if (!$child instanceof WrapperInterface) {
  655. $child->delete();
  656. }
  657. }
  658. self::setHideUnpublished($unpublishedStatus);
  659. }
  660. // remove all properties
  661. $this->getDao()->deleteAllProperties();
  662. // remove permissions
  663. $this->getDao()->deleteAllPermissions();
  664. // remove dependencies
  665. $d = $this->getDependencies();
  666. $d->cleanAllForElement($this);
  667. // remove translations
  668. $service = new Document\Service;
  669. $service->removeTranslation($this);
  670. }
  671. /**
  672. * {@inheritdoc}
  673. */
  674. public function delete()
  675. {
  676. \Pimcore::getEventDispatcher()->dispatch(new DocumentEvent($this), DocumentEvents::PRE_DELETE);
  677. $this->beginTransaction();
  678. try {
  679. if ($this->getId() == 1) {
  680. throw new \Exception('root-node cannot be deleted');
  681. }
  682. $this->doDelete();
  683. $this->getDao()->delete();
  684. $this->commit();
  685. //clear parent data from registry
  686. $parentCacheKey = self::getCacheKey($this->getParentId());
  687. if (\Pimcore\Cache\Runtime::isRegistered($parentCacheKey)) {
  688. /** @var Document $parent */
  689. $parent = \Pimcore\Cache\Runtime::get($parentCacheKey);
  690. if ($parent instanceof self) {
  691. $parent->setChildren(null);
  692. }
  693. }
  694. } catch (\Exception $e) {
  695. $this->rollBack();
  696. $failureEvent = new DocumentEvent($this);
  697. $failureEvent->setArgument('exception', $e);
  698. \Pimcore::getEventDispatcher()->dispatch($failureEvent, DocumentEvents::POST_DELETE_FAILURE);
  699. Logger::error($e);
  700. throw $e;
  701. }
  702. // clear cache
  703. $this->clearDependentCache();
  704. //clear document from registry
  705. \Pimcore\Cache\Runtime::set(self::getCacheKey($this->getId()), null);
  706. \Pimcore::getEventDispatcher()->dispatch(new DocumentEvent($this), DocumentEvents::POST_DELETE);
  707. }
  708. /**
  709. * {@inheritdoc}
  710. */
  711. public function getFullPath(bool $force = false)
  712. {
  713. $link = $force ? null : $this->fullPathCache;
  714. // check if this document is also the site root, if so return /
  715. try {
  716. if (!$link && \Pimcore\Tool::isFrontend() && Site::isSiteRequest()) {
  717. $site = Site::getCurrentSite();
  718. if ($site instanceof Site) {
  719. if ($site->getRootDocument()->getId() == $this->getId()) {
  720. $link = '/';
  721. }
  722. }
  723. }
  724. } catch (\Exception $e) {
  725. Logger::error($e);
  726. }
  727. $requestStack = \Pimcore::getContainer()->get('request_stack');
  728. $masterRequest = $requestStack->getMasterRequest();
  729. // @TODO please forgive me, this is the dirtiest hack I've ever made :(
  730. // if you got confused by this functionality drop me a line and I'll buy you some beers :)
  731. // this is for the case that a link points to a document outside of the current site
  732. // in this case we look for a hardlink in the current site which points to the current document
  733. // why this could happen: we have 2 sites, in one site there's a hardlink to the other site and on a page inside
  734. // the hardlink there are snippets embedded and this snippets have links pointing to a document which is also
  735. // inside the hardlink scope, but this is an ID link, so we cannot rewrite the link the usual way because in the
  736. // snippet / link we don't know anymore that whe a inside a hardlink wrapped document
  737. if (!$link && \Pimcore\Tool::isFrontend() && Site::isSiteRequest() && !FrontendTool::isDocumentInCurrentSite($this)) {
  738. if ($masterRequest && ($masterDocument = $masterRequest->get(DynamicRouter::CONTENT_KEY))) {
  739. if ($masterDocument instanceof WrapperInterface) {
  740. $hardlinkPath = '';
  741. $hardlink = $masterDocument->getHardLinkSource();
  742. $hardlinkTarget = $hardlink->getSourceDocument();
  743. if ($hardlinkTarget) {
  744. $hardlinkPath = preg_replace('@^' . preg_quote(Site::getCurrentSite()->getRootPath(), '@') . '@', '', $hardlink->getRealFullPath());
  745. $link = preg_replace('@^' . preg_quote($hardlinkTarget->getRealFullPath(), '@') . '@',
  746. $hardlinkPath, $this->getRealFullPath());
  747. }
  748. if (strpos($this->getRealFullPath(), Site::getCurrentSite()->getRootDocument()->getRealFullPath()) === false && strpos($link, $hardlinkPath) === false) {
  749. $link = null;
  750. }
  751. }
  752. }
  753. if (!$link) {
  754. $config = \Pimcore\Config::getSystemConfiguration('general');
  755. $request = $requestStack->getCurrentRequest();
  756. $scheme = 'http://';
  757. if ($request) {
  758. $scheme = $request->getScheme() . '://';
  759. }
  760. /** @var Site $site */
  761. if ($site = FrontendTool::getSiteForDocument($this)) {
  762. if ($site->getMainDomain()) {
  763. // check if current document is the root of the different site, if so, preg_replace below doesn't work, so just return /
  764. if ($site->getRootDocument()->getId() == $this->getId()) {
  765. $link = $scheme . $site->getMainDomain() . '/';
  766. } else {
  767. $link = $scheme . $site->getMainDomain() .
  768. preg_replace('@^' . $site->getRootPath() . '/@', '/', $this->getRealFullPath());
  769. }
  770. }
  771. }
  772. if (!$link && !empty($config['domain']) && !($this instanceof WrapperInterface)) {
  773. $link = $scheme . $config['domain'] . $this->getRealFullPath();
  774. }
  775. }
  776. }
  777. if (!$link) {
  778. $link = $this->getPath() . $this->getKey();
  779. }
  780. if ($masterRequest) {
  781. // caching should only be done when master request is available as it is done for performance reasons
  782. // of the web frontend, without a request object there's no need to cache anything
  783. // for details also see https://github.com/pimcore/pimcore/issues/5707
  784. $this->fullPathCache = $link;
  785. }
  786. $link = $this->prepareFrontendPath($link);
  787. return $link;
  788. }
  789. /**
  790. * @param string $path
  791. *
  792. * @return string
  793. */
  794. private function prepareFrontendPath($path)
  795. {
  796. if (\Pimcore\Tool::isFrontend()) {
  797. $path = urlencode_ignore_slash($path);
  798. $event = new GenericEvent($this, [
  799. 'frontendPath' => $path,
  800. ]);
  801. \Pimcore::getEventDispatcher()->dispatch($event, FrontendEvents::DOCUMENT_PATH);
  802. $path = $event->getArgument('frontendPath');
  803. }
  804. return $path;
  805. }
  806. /**
  807. * {@inheritdoc}
  808. */
  809. public function getCreationDate()
  810. {
  811. return $this->creationDate;
  812. }
  813. /**
  814. * {@inheritdoc}
  815. */
  816. public function getId(): int
  817. {
  818. return (int) $this->id;
  819. }
  820. /**
  821. * {@inheritdoc}
  822. */
  823. public function getKey()
  824. {
  825. return $this->key;
  826. }
  827. /**
  828. * {@inheritdoc}
  829. */
  830. public function getModificationDate()
  831. {
  832. return $this->modificationDate;
  833. }
  834. /**
  835. * {@inheritdoc}
  836. */
  837. public function getParentId()
  838. {
  839. return $this->parentId;
  840. }
  841. /**
  842. * {@inheritdoc}
  843. */
  844. public function getPath()
  845. {
  846. // check for site, if so rewrite the path for output
  847. try {
  848. if (\Pimcore\Tool::isFrontend() && Site::isSiteRequest()) {
  849. $site = Site::getCurrentSite();
  850. if ($site instanceof Site) {
  851. if ($site->getRootDocument() instanceof Document\Page && $site->getRootDocument() !== $this) {
  852. $rootPath = $site->getRootPath();
  853. $rootPath = preg_quote($rootPath, '@');
  854. $link = preg_replace('@^' . $rootPath . '@', '', $this->path);
  855. return $link;
  856. }
  857. }
  858. }
  859. } catch (\Exception $e) {
  860. Logger::error($e);
  861. }
  862. return $this->path;
  863. }
  864. /**
  865. * {@inheritdoc}
  866. */
  867. public function getRealPath()
  868. {
  869. return $this->path;
  870. }
  871. /**
  872. * {@inheritdoc}
  873. */
  874. public function getRealFullPath()
  875. {
  876. $path = $this->getRealPath() . $this->getKey();
  877. return $path;
  878. }
  879. /**
  880. * {@inheritdoc}
  881. */
  882. public function setCreationDate($creationDate)
  883. {
  884. $this->creationDate = (int) $creationDate;
  885. return $this;
  886. }
  887. /**
  888. * {@inheritdoc}
  889. */
  890. public function setId($id)
  891. {
  892. $this->id = (int) $id;
  893. return $this;
  894. }
  895. /**
  896. * {@inheritdoc}
  897. */
  898. public function setKey($key)
  899. {
  900. $this->key = $key;
  901. return $this;
  902. }
  903. /**
  904. * {@inheritdoc}
  905. */
  906. public function setModificationDate($modificationDate)
  907. {
  908. $this->markFieldDirty('modificationDate');
  909. $this->modificationDate = (int) $modificationDate;
  910. return $this;
  911. }
  912. /**
  913. * Set the parent id of the document.
  914. *
  915. * @param int $parentId
  916. *
  917. * @return Document
  918. */
  919. public function setParentId($parentId)
  920. {
  921. $this->parentId = (int) $parentId;
  922. $this->parent = null;
  923. $this->siblings = [];
  924. $this->hasSiblings = [];
  925. return $this;
  926. }
  927. /**
  928. * {@inheritdoc}
  929. */
  930. public function setPath($path)
  931. {
  932. $this->path = $path;
  933. return $this;
  934. }
  935. /**
  936. * Returns the document index.
  937. *
  938. * @return int|null
  939. */
  940. public function getIndex(): ?int
  941. {
  942. return $this->index;
  943. }
  944. /**
  945. * Set the document index.
  946. *
  947. * @param int $index
  948. *
  949. * @return Document
  950. */
  951. public function setIndex($index)
  952. {
  953. $this->index = (int) $index;
  954. return $this;
  955. }
  956. /**
  957. * {@inheritdoc}
  958. */
  959. public function getType()
  960. {
  961. return $this->type;
  962. }
  963. /**
  964. * Set the document type.
  965. *
  966. * @param string $type
  967. *
  968. * @return Document
  969. */
  970. public function setType($type)
  971. {
  972. $this->type = $type;
  973. return $this;
  974. }
  975. /**
  976. * {@inheritdoc}
  977. */
  978. public function getUserModification()
  979. {
  980. return $this->userModification;
  981. }
  982. /**
  983. * {@inheritdoc}
  984. */
  985. public function getUserOwner()
  986. {
  987. return $this->userOwner;
  988. }
  989. /**
  990. * {@inheritdoc}
  991. */
  992. public function setUserModification($userModification)
  993. {
  994. $this->markFieldDirty('userModification');
  995. $this->userModification = (int) $userModification;
  996. return $this;
  997. }
  998. /**
  999. * {@inheritdoc}
  1000. */
  1001. public function setUserOwner($userOwner)
  1002. {
  1003. $this->userOwner = (int) $userOwner;
  1004. return $this;
  1005. }
  1006. /**
  1007. * @return bool
  1008. */
  1009. public function isPublished()
  1010. {
  1011. return $this->getPublished();
  1012. }
  1013. /**
  1014. * @return bool
  1015. */
  1016. public function getPublished()
  1017. {
  1018. return (bool) $this->published;
  1019. }
  1020. /**
  1021. * @param bool $published
  1022. *
  1023. * @return Document
  1024. */
  1025. public function setPublished($published)
  1026. {
  1027. $this->published = (bool) $published;
  1028. return $this;
  1029. }
  1030. /**
  1031. * {@inheritdoc}
  1032. */
  1033. public function getProperties()
  1034. {
  1035. if ($this->properties === null) {
  1036. // try to get from cache
  1037. $cacheKey = 'document_properties_' . $this->getId();
  1038. $properties = \Pimcore\Cache::load($cacheKey);
  1039. if (!is_array($properties)) {
  1040. $properties = $this->getDao()->getProperties();
  1041. $elementCacheTag = $this->getCacheTag();
  1042. $cacheTags = ['document_properties' => 'document_properties', $elementCacheTag => $elementCacheTag];
  1043. \Pimcore\Cache::save($properties, $cacheKey, $cacheTags);
  1044. }
  1045. $this->setProperties($properties);
  1046. }
  1047. return $this->properties;
  1048. }
  1049. /**
  1050. * {@inheritdoc}
  1051. */
  1052. public function setProperties(?array $properties)
  1053. {
  1054. $this->properties = $properties;
  1055. return $this;
  1056. }
  1057. /**
  1058. * {@inheritdoc}
  1059. */
  1060. public function setProperty($name, $type, $data, $inherited = false, $inheritable = false)
  1061. {
  1062. $this->getProperties();
  1063. $property = new Property();
  1064. $property->setType($type);
  1065. $property->setCid($this->getId());
  1066. $property->setName($name);
  1067. $property->setCtype('document');
  1068. $property->setData($data);
  1069. $property->setInherited($inherited);
  1070. $property->setInheritable($inheritable);
  1071. $this->properties[$name] = $property;
  1072. return $this;
  1073. }
  1074. /**
  1075. * {@inheritdoc}
  1076. */
  1077. public function getParent()
  1078. {
  1079. if ($this->parent === null) {
  1080. $this->setParent(Document::getById($this->getParentId()));
  1081. }
  1082. return $this->parent;
  1083. }
  1084. /**
  1085. * Set the parent document instance.
  1086. *
  1087. * @param Document $parent
  1088. *
  1089. * @return Document
  1090. */
  1091. public function setParent($parent)
  1092. {
  1093. $this->parent = $parent;
  1094. if ($parent instanceof Document) {
  1095. $this->parentId = $parent->getId();
  1096. }
  1097. return $this;
  1098. }
  1099. public function __sleep()
  1100. {
  1101. $parentVars = parent::__sleep();
  1102. $blockedVars = ['hasChildren', 'versions', 'scheduledTasks', 'parent', 'fullPathCache'];
  1103. if ($this->isInDumpState()) {
  1104. // this is if we want to make a full dump of the object (eg. for a new version), including children for recyclebin
  1105. $this->removeInheritedProperties();
  1106. } else {
  1107. // this is if we want to cache the object
  1108. $blockedVars = array_merge($blockedVars, ['children', 'properties']);
  1109. }
  1110. return array_diff($parentVars, $blockedVars);
  1111. }
  1112. public function __wakeup()
  1113. {
  1114. if ($this->isInDumpState()) {
  1115. // set current key and path this is necessary because the serialized data can have a different path than the original element (element was renamed or moved)
  1116. $originalElement = Document::getById($this->getId());
  1117. if ($originalElement) {
  1118. $this->setKey($originalElement->getKey());
  1119. $this->setPath($originalElement->getRealPath());
  1120. }
  1121. }
  1122. if ($this->isInDumpState() && $this->properties !== null) {
  1123. $this->renewInheritedProperties();
  1124. }
  1125. $this->setInDumpState(false);
  1126. }
  1127. /**
  1128. * Set true if want to hide documents.
  1129. *
  1130. * @param bool $hideUnpublished
  1131. */
  1132. public static function setHideUnpublished($hideUnpublished)
  1133. {
  1134. self::$hideUnpublished = $hideUnpublished;
  1135. }
  1136. /**
  1137. * Checks if unpublished documents should be hidden.
  1138. *
  1139. * @return bool
  1140. */
  1141. public static function doHideUnpublished()
  1142. {
  1143. return self::$hideUnpublished;
  1144. }
  1145. /**
  1146. * {@inheritdoc}
  1147. */
  1148. public function getVersionCount(): int
  1149. {
  1150. return $this->versionCount ? $this->versionCount : 0;
  1151. }
  1152. /**
  1153. * {@inheritdoc}
  1154. */
  1155. public function setVersionCount(?int $versionCount): ElementInterface
  1156. {
  1157. $this->versionCount = (int) $versionCount;
  1158. return $this;
  1159. }
  1160. /**
  1161. * @internal
  1162. *
  1163. * @param array $args
  1164. *
  1165. * @return string
  1166. */
  1167. protected function getListingCacheKey(array $args = [])
  1168. {
  1169. $unpublished = (bool)($args[0] ?? false);
  1170. $cacheKey = (string)$unpublished;
  1171. return $cacheKey;
  1172. }
  1173. public function __clone()
  1174. {
  1175. parent::__clone();
  1176. $this->parent = null;
  1177. $this->hasSiblings = [];
  1178. $this->siblings = [];
  1179. $this->fullPathCache = null;
  1180. }
  1181. }