vendor/pimcore/pimcore/models/DataObject/AbstractObject.php line 312

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\DataObject;
  15. use Doctrine\DBAL\Exception\RetryableException;
  16. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  17. use Pimcore\Cache;
  18. use Pimcore\Cache\Runtime;
  19. use Pimcore\Event\DataObjectEvents;
  20. use Pimcore\Event\Model\DataObjectEvent;
  21. use Pimcore\Logger;
  22. use Pimcore\Model;
  23. use Pimcore\Model\DataObject;
  24. use Pimcore\Model\Element;
  25. /**
  26. * @method AbstractObject\Dao getDao()
  27. * @method array|null getPermissions(string $type, Model\User $user, bool $quote = true)
  28. * @method bool __isBasedOnLatestData()
  29. * @method string getCurrentFullPath()
  30. * @method int getChildAmount($objectTypes = [DataObject::OBJECT_TYPE_OBJECT, DataObject::OBJECT_TYPE_FOLDER], Model\User $user = null)
  31. * @method array getChildPermissions(string $type, Model\User $user, bool $quote = true)
  32. */
  33. abstract class AbstractObject extends Model\Element\AbstractElement
  34. {
  35. const OBJECT_TYPE_FOLDER = 'folder';
  36. const OBJECT_TYPE_OBJECT = 'object';
  37. const OBJECT_TYPE_VARIANT = 'variant';
  38. const OBJECT_CHILDREN_SORT_BY_DEFAULT = 'key';
  39. const OBJECT_CHILDREN_SORT_BY_INDEX = 'index';
  40. const OBJECT_CHILDREN_SORT_ORDER_DEFAULT = 'ASC';
  41. /**
  42. * @internal
  43. *
  44. * @var bool
  45. */
  46. public static $doNotRestoreKeyAndPath = false;
  47. /**
  48. * possible types of a document
  49. *
  50. * @var array
  51. */
  52. public static $types = [self::OBJECT_TYPE_FOLDER, self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_VARIANT];
  53. /**
  54. * @var bool
  55. */
  56. private static $hideUnpublished = false;
  57. /**
  58. * @var bool
  59. */
  60. private static $getInheritedValues = false;
  61. /**
  62. * @internal
  63. *
  64. * @var bool
  65. */
  66. protected static $disableDirtyDetection = false;
  67. /**
  68. * @internal
  69. *
  70. * @var int
  71. */
  72. protected $o_id = 0;
  73. /**
  74. * @internal
  75. *
  76. * @var int
  77. */
  78. protected $o_parentId;
  79. /**
  80. * @internal
  81. *
  82. * @var self|null
  83. */
  84. protected $o_parent;
  85. /**
  86. * @internal
  87. *
  88. * @var string
  89. */
  90. protected $o_type = 'object';
  91. /**
  92. * @internal
  93. *
  94. * @var string
  95. */
  96. protected $o_key;
  97. /**
  98. * @internal
  99. *
  100. * @var string
  101. */
  102. protected $o_path;
  103. /**
  104. * @internal
  105. *
  106. * @var int
  107. */
  108. protected $o_index;
  109. /**
  110. * @internal
  111. *
  112. * @var int
  113. */
  114. protected $o_creationDate;
  115. /**
  116. * @internal
  117. *
  118. * @var int
  119. */
  120. protected $o_modificationDate;
  121. /**
  122. * @internal
  123. *
  124. * @var int|null
  125. */
  126. protected ?int $o_userOwner = null;
  127. /**
  128. * @internal
  129. *
  130. * @var int|null
  131. */
  132. protected ?int $o_userModification = null;
  133. /**
  134. * @internal
  135. *
  136. * @var array|null
  137. */
  138. protected ?array $o_properties = null;
  139. /**
  140. * @internal
  141. *
  142. * @var bool[]
  143. */
  144. protected $o_hasChildren = [];
  145. /**
  146. * Contains a list of sibling documents
  147. *
  148. * @internal
  149. *
  150. * @var array
  151. */
  152. protected $o_siblings = [];
  153. /**
  154. * Indicator if object has siblings or not
  155. *
  156. * @internal
  157. *
  158. * @var bool[]
  159. */
  160. protected $o_hasSiblings = [];
  161. /**
  162. * @internal
  163. *
  164. * @var array
  165. */
  166. protected $o_children = [];
  167. /**
  168. * @internal
  169. *
  170. * @var string
  171. */
  172. protected $o_locked;
  173. /**
  174. * @internal
  175. *
  176. * @var string
  177. */
  178. protected $o_childrenSortBy;
  179. /**
  180. * @internal
  181. *
  182. * @var string
  183. */
  184. protected $o_childrenSortOrder;
  185. /**
  186. * @internal
  187. *
  188. * @var int
  189. */
  190. protected $o_versionCount = 0;
  191. /**
  192. * @static
  193. *
  194. * @return bool
  195. */
  196. public static function getHideUnpublished()
  197. {
  198. return self::$hideUnpublished;
  199. }
  200. /**
  201. * @static
  202. *
  203. * @param bool $hideUnpublished
  204. */
  205. public static function setHideUnpublished($hideUnpublished)
  206. {
  207. self::$hideUnpublished = $hideUnpublished;
  208. }
  209. /**
  210. * @static
  211. *
  212. * @return bool
  213. */
  214. public static function doHideUnpublished()
  215. {
  216. return self::$hideUnpublished;
  217. }
  218. /**
  219. * @static
  220. *
  221. * @param bool $getInheritedValues
  222. */
  223. public static function setGetInheritedValues($getInheritedValues)
  224. {
  225. self::$getInheritedValues = $getInheritedValues;
  226. }
  227. /**
  228. * @static
  229. *
  230. * @return bool
  231. */
  232. public static function getGetInheritedValues()
  233. {
  234. return self::$getInheritedValues;
  235. }
  236. /**
  237. * @static
  238. *
  239. * @param Concrete $object
  240. *
  241. * @return bool
  242. */
  243. public static function doGetInheritedValues(Concrete $object = null)
  244. {
  245. if (self::$getInheritedValues && $object !== null) {
  246. $class = $object->getClass();
  247. return $class->getAllowInherit();
  248. }
  249. return self::$getInheritedValues;
  250. }
  251. /**
  252. * get possible types
  253. *
  254. * @return array
  255. */
  256. public static function getTypes()
  257. {
  258. return self::$types;
  259. }
  260. /**
  261. * Static helper to get an object by the passed ID
  262. *
  263. * @param int $id
  264. * @param bool $force
  265. *
  266. * @return static|null
  267. */
  268. public static function getById($id, $force = false)
  269. {
  270. if (!is_numeric($id) || $id < 1) {
  271. return null;
  272. }
  273. $id = (int)$id;
  274. $cacheKey = self::getCacheKey($id);
  275. if (!$force && Runtime::isRegistered($cacheKey)) {
  276. $object = Runtime::get($cacheKey);
  277. if ($object && static::typeMatch($object)) {
  278. return $object;
  279. }
  280. }
  281. if ($force || !($object = Cache::load($cacheKey))) {
  282. $object = new Model\DataObject();
  283. try {
  284. $typeInfo = $object->getDao()->getTypeById($id);
  285. if (!empty($typeInfo['o_type']) && in_array($typeInfo['o_type'], DataObject::$types)) {
  286. if ($typeInfo['o_type'] == DataObject::OBJECT_TYPE_FOLDER) {
  287. $className = Folder::class;
  288. } else {
  289. $className = 'Pimcore\\Model\\DataObject\\' . ucfirst($typeInfo['o_className']);
  290. }
  291. /** @var AbstractObject $object */
  292. $object = self::getModelFactory()->build($className);
  293. Runtime::set($cacheKey, $object);
  294. $object->getDao()->getById($id);
  295. $object->__setDataVersionTimestamp($object->getModificationDate());
  296. Service::recursiveResetDirtyMap($object);
  297. // force loading of relation data
  298. if ($object instanceof Concrete) {
  299. $object->__getRawRelationData();
  300. }
  301. Cache::save($object, $cacheKey);
  302. } else {
  303. throw new \Exception('No entry for object id ' . $id);
  304. }
  305. } catch (Model\Exception\NotFoundException $e) {
  306. return null;
  307. }
  308. } else {
  309. Runtime::set($cacheKey, $object);
  310. }
  311. if (!$object || !static::typeMatch($object)) {
  312. return null;
  313. }
  314. return $object;
  315. }
  316. /**
  317. * @param string $path
  318. * @param bool $force
  319. *
  320. * @return static|null
  321. */
  322. public static function getByPath($path, $force = false)
  323. {
  324. $path = Model\Element\Service::correctPath($path);
  325. try {
  326. $object = new static();
  327. $object->getDao()->getByPath($path);
  328. return static::getById($object->getId(), $force);
  329. } catch (Model\Exception\NotFoundException $e) {
  330. return null;
  331. }
  332. }
  333. /**
  334. * @param array $config
  335. *
  336. * @return DataObject\Listing
  337. *
  338. * @throws \Exception
  339. */
  340. public static function getList($config = [])
  341. {
  342. $className = DataObject::class;
  343. // get classname
  344. if (!in_array(static::class, [__CLASS__, Concrete::class, Folder::class], true)) {
  345. /** @var Concrete $tmpObject */
  346. $tmpObject = new static();
  347. if ($tmpObject instanceof Concrete) {
  348. $className = 'Pimcore\\Model\\DataObject\\' . ucfirst($tmpObject->getClassName());
  349. }
  350. }
  351. if (is_array($config)) {
  352. if (!empty($config['class'])) {
  353. $className = ltrim($config['class'], '\\');
  354. }
  355. if ($className) {
  356. $listClass = $className . '\\Listing';
  357. /** @var DataObject\Listing $list */
  358. $list = self::getModelFactory()->build($listClass);
  359. $list->setValues($config);
  360. return $list;
  361. }
  362. }
  363. throw new \Exception('Unable to initiate list class - class not found or invalid configuration');
  364. }
  365. /**
  366. * @deprecated will be removed in Pimcore 11
  367. *
  368. * @param array $config
  369. *
  370. * @return int total count
  371. */
  372. public static function getTotalCount($config = [])
  373. {
  374. $list = static::getList($config);
  375. $count = $list->getTotalCount();
  376. return $count;
  377. }
  378. /**
  379. * @internal
  380. *
  381. * @param AbstractObject $object
  382. *
  383. * @return bool
  384. */
  385. protected static function typeMatch(AbstractObject $object)
  386. {
  387. return in_array(static::class, [Concrete::class, __CLASS__], true) || $object instanceof static;
  388. }
  389. /**
  390. * @param array $objectTypes
  391. * @param bool $includingUnpublished
  392. *
  393. * @return self[]
  394. */
  395. public function getChildren(array $objectTypes = [self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_FOLDER], $includingUnpublished = false)
  396. {
  397. $cacheKey = $this->getListingCacheKey(func_get_args());
  398. if (!isset($this->o_children[$cacheKey])) {
  399. $list = new Listing();
  400. $list->setUnpublished($includingUnpublished);
  401. $list->setCondition('o_parentId = ?', $this->getId());
  402. $list->setOrderKey(sprintf('o_%s', $this->getChildrenSortBy()));
  403. $list->setOrder($this->getChildrenSortOrder());
  404. $list->setObjectTypes($objectTypes);
  405. $this->o_children[$cacheKey] = $list->load();
  406. $this->o_hasChildren[$cacheKey] = (bool) count($this->o_children[$cacheKey]);
  407. }
  408. return $this->o_children[$cacheKey];
  409. }
  410. /**
  411. * Quick test if there are children
  412. *
  413. * @param array $objectTypes
  414. * @param bool|null $includingUnpublished
  415. *
  416. * @return bool
  417. */
  418. public function hasChildren($objectTypes = [self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_FOLDER], $includingUnpublished = null)
  419. {
  420. $cacheKey = $this->getListingCacheKey(func_get_args());
  421. if (isset($this->o_hasChildren[$cacheKey])) {
  422. return $this->o_hasChildren[$cacheKey];
  423. }
  424. return $this->o_hasChildren[$cacheKey] = $this->getDao()->hasChildren($objectTypes, $includingUnpublished);
  425. }
  426. /**
  427. * Get a list of the sibling documents
  428. *
  429. * @param array $objectTypes
  430. * @param bool $includingUnpublished
  431. *
  432. * @return array
  433. */
  434. public function getSiblings(array $objectTypes = [self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_FOLDER], $includingUnpublished = false)
  435. {
  436. $cacheKey = $this->getListingCacheKey(func_get_args());
  437. if (!isset($this->o_siblings[$cacheKey])) {
  438. $list = new Listing();
  439. $list->setUnpublished($includingUnpublished);
  440. // string conversion because parentId could be 0
  441. $list->addConditionParam('o_parentId = ?', (string)$this->getParentId());
  442. $list->addConditionParam('o_id != ?', $this->getId());
  443. $list->setOrderKey('o_key');
  444. $list->setObjectTypes($objectTypes);
  445. $list->setOrder('asc');
  446. $this->o_siblings[$cacheKey] = $list->load();
  447. $this->o_hasSiblings[$cacheKey] = (bool) count($this->o_siblings[$cacheKey]);
  448. }
  449. return $this->o_siblings[$cacheKey];
  450. }
  451. /**
  452. * Returns true if the object has at least one sibling
  453. *
  454. * @param array $objectTypes
  455. * @param bool|null $includingUnpublished
  456. *
  457. * @return bool
  458. */
  459. public function hasSiblings($objectTypes = [self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_FOLDER], $includingUnpublished = null)
  460. {
  461. $cacheKey = $this->getListingCacheKey(func_get_args());
  462. if (isset($this->o_hasSiblings[$cacheKey])) {
  463. return $this->o_hasSiblings[$cacheKey];
  464. }
  465. return $this->o_hasSiblings[$cacheKey] = $this->getDao()->hasSiblings($objectTypes, $includingUnpublished);
  466. }
  467. /**
  468. * enum('self','propagate') nullable
  469. *
  470. * @return string|null
  471. */
  472. public function getLocked()
  473. {
  474. return $this->o_locked;
  475. }
  476. /**
  477. * enum('self','propagate') nullable
  478. *
  479. * @param string|null $o_locked
  480. *
  481. * @return $this
  482. */
  483. public function setLocked($o_locked)
  484. {
  485. $this->o_locked = $o_locked;
  486. return $this;
  487. }
  488. /**
  489. * @internal
  490. *
  491. * @throws \Exception
  492. */
  493. protected function doDelete()
  494. {
  495. // delete children
  496. $children = $this->getChildren(self::$types, true);
  497. if (count($children) > 0) {
  498. foreach ($children as $child) {
  499. $child->delete();
  500. }
  501. }
  502. // remove dependencies
  503. $d = new Model\Dependency;
  504. $d->cleanAllForElement($this);
  505. // remove all properties
  506. $this->getDao()->deleteAllProperties();
  507. // remove all permissions
  508. $this->getDao()->deleteAllPermissions();
  509. }
  510. /**
  511. * @throws \Exception
  512. */
  513. public function delete()
  514. {
  515. \Pimcore::getEventDispatcher()->dispatch(new DataObjectEvent($this), DataObjectEvents::PRE_DELETE);
  516. $this->beginTransaction();
  517. try {
  518. $this->doDelete();
  519. $this->getDao()->delete();
  520. $this->commit();
  521. //clear parent data from registry
  522. $parentCacheKey = self::getCacheKey($this->getParentId());
  523. if (Runtime::isRegistered($parentCacheKey)) {
  524. /** @var AbstractObject $parent * */
  525. $parent = Runtime::get($parentCacheKey);
  526. if ($parent instanceof self) {
  527. $parent->setChildren(null);
  528. }
  529. }
  530. } catch (\Exception $e) {
  531. $this->rollBack();
  532. $failureEvent = new DataObjectEvent($this);
  533. $failureEvent->setArgument('exception', $e);
  534. \Pimcore::getEventDispatcher()->dispatch($failureEvent, DataObjectEvents::POST_DELETE_FAILURE);
  535. Logger::crit($e);
  536. throw $e;
  537. }
  538. // empty object cache
  539. $this->clearDependentCache();
  540. //clear object from registry
  541. Runtime::set(self::getCacheKey($this->getId()), null);
  542. \Pimcore::getEventDispatcher()->dispatch(new DataObjectEvent($this), DataObjectEvents::POST_DELETE);
  543. }
  544. /**
  545. * @return $this
  546. *
  547. * @throws \Exception
  548. */
  549. public function save()
  550. {
  551. // additional parameters (e.g. "versionNote" for the version note)
  552. $params = [];
  553. if (func_num_args() && is_array(func_get_arg(0))) {
  554. $params = func_get_arg(0);
  555. }
  556. $isUpdate = false;
  557. $differentOldPath = null;
  558. try {
  559. $isDirtyDetectionDisabled = self::isDirtyDetectionDisabled();
  560. $preEvent = new DataObjectEvent($this, $params);
  561. if ($this->getId()) {
  562. $isUpdate = true;
  563. \Pimcore::getEventDispatcher()->dispatch($preEvent, DataObjectEvents::PRE_UPDATE);
  564. } else {
  565. self::disableDirtyDetection();
  566. \Pimcore::getEventDispatcher()->dispatch($preEvent, DataObjectEvents::PRE_ADD);
  567. }
  568. $params = $preEvent->getArguments();
  569. $this->correctPath();
  570. // we wrap the save actions in a loop here, so that we can restart the database transactions in the case it fails
  571. // if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out
  572. // this is especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...)
  573. $maxRetries = 5;
  574. for ($retries = 0; $retries < $maxRetries; $retries++) {
  575. // be sure that unpublished objects in relations are saved also in frontend mode, eg. in importers, ...
  576. $hideUnpublishedBackup = self::getHideUnpublished();
  577. self::setHideUnpublished(false);
  578. $this->beginTransaction();
  579. try {
  580. if (!in_array($this->getType(), self::$types)) {
  581. throw new \Exception('invalid object type given: [' . $this->getType() . ']');
  582. }
  583. if (!$isUpdate) {
  584. $this->getDao()->create();
  585. }
  586. // get the old path from the database before the update is done
  587. $oldPath = null;
  588. if ($isUpdate) {
  589. $oldPath = $this->getDao()->getCurrentFullPath();
  590. }
  591. // if the old path is different from the new path, update all children
  592. // we need to do the update of the children's path before $this->update() because the
  593. // inheritance helper needs the correct paths of the children in InheritanceHelper::buildTree()
  594. $updatedChildren = [];
  595. if ($oldPath && $oldPath != $this->getRealFullPath()) {
  596. $differentOldPath = $oldPath;
  597. $this->getDao()->updateWorkspaces();
  598. $updatedChildren = $this->getDao()->updateChildPaths($oldPath);
  599. }
  600. $this->update($isUpdate, $params);
  601. self::setHideUnpublished($hideUnpublishedBackup);
  602. $this->commit();
  603. break; // transaction was successfully completed, so we cancel the loop here -> no restart required
  604. } catch (\Exception $e) {
  605. try {
  606. $this->rollBack();
  607. } catch (\Exception $er) {
  608. // PDO adapter throws exceptions if rollback fails
  609. Logger::info($er);
  610. }
  611. // set "HideUnpublished" back to the value it was originally
  612. self::setHideUnpublished($hideUnpublishedBackup);
  613. if ($e instanceof UniqueConstraintViolationException) {
  614. throw new Element\ValidationException('unique constraint violation', 0, $e);
  615. }
  616. if ($e instanceof RetryableException) {
  617. // we try to start the transaction $maxRetries times again (deadlocks, ...)
  618. if ($retries < ($maxRetries - 1)) {
  619. $run = $retries + 1;
  620. $waitTime = random_int(1, 5) * 100000; // microseconds
  621. Logger::warn('Unable to finish transaction (' . $run . ". run) because of the following reason '" . $e->getMessage() . "'. --> Retrying in " . $waitTime . ' microseconds ... (' . ($run + 1) . ' of ' . $maxRetries . ')');
  622. usleep($waitTime); // wait specified time until we restart the transaction
  623. } else {
  624. // if the transaction still fail after $maxRetries retries, we throw out the exception
  625. Logger::error('Finally giving up restarting the same transaction again and again, last message: ' . $e->getMessage());
  626. throw $e;
  627. }
  628. } else {
  629. throw $e;
  630. }
  631. }
  632. }
  633. $additionalTags = [];
  634. if (isset($updatedChildren) && is_array($updatedChildren)) {
  635. foreach ($updatedChildren as $objectId) {
  636. $tag = 'object_' . $objectId;
  637. $additionalTags[] = $tag;
  638. // remove the child also from registry (internal cache) to avoid path inconsistencies during long running scripts, such as CLI
  639. Runtime::set($tag, null);
  640. }
  641. }
  642. $this->clearDependentCache($additionalTags);
  643. $postEvent = new DataObjectEvent($this, $params);
  644. if ($isUpdate) {
  645. if ($differentOldPath) {
  646. $postEvent->setArgument('oldPath', $differentOldPath);
  647. }
  648. \Pimcore::getEventDispatcher()->dispatch($postEvent, DataObjectEvents::POST_UPDATE);
  649. } else {
  650. self::setDisableDirtyDetection($isDirtyDetectionDisabled);
  651. \Pimcore::getEventDispatcher()->dispatch($postEvent, DataObjectEvents::POST_ADD);
  652. }
  653. return $this;
  654. } catch (\Exception $e) {
  655. $failureEvent = new DataObjectEvent($this, $params);
  656. $failureEvent->setArgument('exception', $e);
  657. if ($isUpdate) {
  658. \Pimcore::getEventDispatcher()->dispatch($failureEvent, DataObjectEvents::POST_UPDATE_FAILURE);
  659. } else {
  660. \Pimcore::getEventDispatcher()->dispatch($failureEvent, DataObjectEvents::POST_ADD_FAILURE);
  661. }
  662. throw $e;
  663. }
  664. }
  665. /**
  666. * @internal
  667. *
  668. * @throws \Exception
  669. */
  670. protected function correctPath()
  671. {
  672. // set path
  673. if ($this->getId() != 1) { // not for the root node
  674. if (!Element\Service::isValidKey($this->getKey(), 'object')) {
  675. throw new \Exception('invalid key for object with id [ '.$this->getId().' ] key is: [' . $this->getKey() . ']');
  676. }
  677. if ($this->getParentId() == $this->getId()) {
  678. throw new \Exception("ParentID and ID is identical, an element can't be the parent of itself.");
  679. }
  680. $parent = DataObject::getById($this->getParentId());
  681. if ($parent) {
  682. // use the parent's path from the database here (getCurrentFullPath), to ensure the path really exists and does not rely on the path
  683. // that is currently in the parent object (in memory), because this might have changed but wasn't not saved
  684. $this->setPath(str_replace('//', '/', $parent->getCurrentFullPath().'/'));
  685. } else {
  686. // parent document doesn't exist anymore, set the parent to to root
  687. $this->setParentId(1);
  688. $this->setPath('/');
  689. }
  690. if (strlen($this->getKey()) < 1) {
  691. throw new \Exception('DataObject requires key');
  692. }
  693. } elseif ($this->getId() == 1) {
  694. // some data in root node should always be the same
  695. $this->setParentId(0);
  696. $this->setPath('/');
  697. $this->setKey('');
  698. $this->setType(DataObject::OBJECT_TYPE_FOLDER);
  699. }
  700. if (Service::pathExists($this->getRealFullPath())) {
  701. $duplicate = DataObject::getByPath($this->getRealFullPath());
  702. if ($duplicate instanceof self && $duplicate->getId() != $this->getId()) {
  703. throw new \Exception('Duplicate full path [ '.$this->getRealFullPath().' ] - cannot save object');
  704. }
  705. }
  706. $this->validatePathLength();
  707. }
  708. /**
  709. * @internal
  710. *
  711. * @param bool|null $isUpdate
  712. * @param array $params
  713. *
  714. * @throws \Exception
  715. */
  716. protected function update($isUpdate = null, $params = [])
  717. {
  718. $this->updateModificationInfos();
  719. // save properties
  720. $this->getProperties();
  721. $this->getDao()->deleteAllProperties();
  722. if (is_array($this->getProperties()) && count($this->getProperties()) > 0) {
  723. foreach ($this->getProperties() as $property) {
  724. if (!$property->getInherited()) {
  725. $property->setDao(null);
  726. $property->setCid($this->getId());
  727. $property->setCtype('object');
  728. $property->setCpath($this->getRealFullPath());
  729. $property->save();
  730. }
  731. }
  732. }
  733. // save dependencies
  734. $d = new Model\Dependency();
  735. $d->setSourceType('object');
  736. $d->setSourceId($this->getId());
  737. foreach ($this->resolveDependencies() as $requirement) {
  738. if ($requirement['id'] == $this->getId() && $requirement['type'] === 'object') {
  739. // dont't add a reference to yourself
  740. continue;
  741. }
  742. $d->addRequirement($requirement['id'], $requirement['type']);
  743. }
  744. $d->save();
  745. //set object to registry
  746. Runtime::set(self::getCacheKey($this->getId()), $this);
  747. }
  748. /**
  749. * {@inheritdoc}
  750. */
  751. public function clearDependentCache($additionalTags = [])
  752. {
  753. self::clearDependentCacheByObjectId($this->getId(), $additionalTags);
  754. }
  755. /**
  756. * @internal
  757. *
  758. * @param int $objectId
  759. * @param array $additionalTags
  760. */
  761. public static function clearDependentCacheByObjectId($objectId, $additionalTags = [])
  762. {
  763. if (!$objectId) {
  764. throw new \Exception('object ID missing');
  765. }
  766. try {
  767. $tags = ['object_' . $objectId, 'object_properties', 'output'];
  768. $tags = array_merge($tags, $additionalTags);
  769. Cache::clearTags($tags);
  770. } catch (\Exception $e) {
  771. Logger::crit($e);
  772. }
  773. }
  774. /**
  775. * @internal
  776. *
  777. * @param int $index
  778. */
  779. public function saveIndex($index)
  780. {
  781. $this->getDao()->saveIndex($index);
  782. $this->clearDependentCache();
  783. }
  784. /**
  785. * @return string
  786. */
  787. public function getFullPath()
  788. {
  789. $path = $this->getPath() . $this->getKey();
  790. return $path;
  791. }
  792. /**
  793. * @return string
  794. */
  795. public function getRealPath()
  796. {
  797. return $this->getPath();
  798. }
  799. /**
  800. * @return string
  801. */
  802. public function getRealFullPath()
  803. {
  804. return $this->getFullPath();
  805. }
  806. /**
  807. * @return int
  808. */
  809. public function getId()
  810. {
  811. return $this->o_id;
  812. }
  813. /**
  814. * @return int
  815. */
  816. public function getParentId()
  817. {
  818. // fall back to parent if no ID is set but we have a parent object
  819. if (!$this->o_parentId && $this->o_parent) {
  820. return $this->o_parent->getId();
  821. }
  822. return $this->o_parentId;
  823. }
  824. /**
  825. * @return string
  826. */
  827. public function getType()
  828. {
  829. return $this->o_type;
  830. }
  831. /**
  832. * @return string
  833. */
  834. public function getKey()
  835. {
  836. return $this->o_key;
  837. }
  838. /**
  839. * @return string path
  840. */
  841. public function getPath()
  842. {
  843. return $this->o_path;
  844. }
  845. /**
  846. * @return int
  847. */
  848. public function getIndex()
  849. {
  850. return $this->o_index;
  851. }
  852. /**
  853. * @return int
  854. */
  855. public function getCreationDate()
  856. {
  857. return $this->o_creationDate;
  858. }
  859. /**
  860. * @return int
  861. */
  862. public function getModificationDate()
  863. {
  864. return $this->o_modificationDate;
  865. }
  866. /**
  867. * @return int|null
  868. */
  869. public function getUserOwner()
  870. {
  871. return $this->o_userOwner;
  872. }
  873. /**
  874. * @return int
  875. */
  876. public function getUserModification()
  877. {
  878. return $this->o_userModification;
  879. }
  880. /**
  881. * @param int $o_id
  882. *
  883. * @return $this
  884. */
  885. public function setId($o_id)
  886. {
  887. $this->o_id = (int) $o_id;
  888. return $this;
  889. }
  890. /**
  891. * @param int $o_parentId
  892. *
  893. * @return $this
  894. */
  895. public function setParentId($o_parentId)
  896. {
  897. $o_parentId = (int) $o_parentId;
  898. if ($o_parentId != $this->o_parentId) {
  899. $this->markFieldDirty('o_parentId');
  900. }
  901. $this->o_parentId = $o_parentId;
  902. $this->o_parent = null;
  903. $this->o_siblings = [];
  904. $this->o_hasSiblings = [];
  905. return $this;
  906. }
  907. /**
  908. * @param string $o_type
  909. *
  910. * @return $this
  911. */
  912. public function setType($o_type)
  913. {
  914. $this->o_type = $o_type;
  915. return $this;
  916. }
  917. /**
  918. * @param string $o_key
  919. *
  920. * @return $this
  921. */
  922. public function setKey($o_key)
  923. {
  924. $this->o_key = $o_key;
  925. return $this;
  926. }
  927. /**
  928. * @param string $o_path
  929. *
  930. * @return $this
  931. */
  932. public function setPath($o_path)
  933. {
  934. $this->o_path = $o_path;
  935. return $this;
  936. }
  937. /**
  938. * @param int $o_index
  939. *
  940. * @return $this
  941. */
  942. public function setIndex($o_index)
  943. {
  944. $this->o_index = (int) $o_index;
  945. return $this;
  946. }
  947. /**
  948. * @param string|null $childrenSortBy
  949. */
  950. public function setChildrenSortBy($childrenSortBy)
  951. {
  952. if ($this->o_childrenSortBy !== $childrenSortBy) {
  953. $this->o_children = [];
  954. $this->o_hasChildren = [];
  955. }
  956. $this->o_childrenSortBy = $childrenSortBy;
  957. }
  958. /**
  959. * @param int $o_creationDate
  960. *
  961. * @return $this
  962. */
  963. public function setCreationDate($o_creationDate)
  964. {
  965. $this->o_creationDate = (int) $o_creationDate;
  966. return $this;
  967. }
  968. /**
  969. * @param int $o_modificationDate
  970. *
  971. * @return $this
  972. */
  973. public function setModificationDate($o_modificationDate)
  974. {
  975. $this->markFieldDirty('o_modificationDate');
  976. $this->o_modificationDate = (int) $o_modificationDate;
  977. return $this;
  978. }
  979. /**
  980. * @param int $o_userOwner
  981. *
  982. * @return $this
  983. */
  984. public function setUserOwner($o_userOwner)
  985. {
  986. $this->o_userOwner = (int) $o_userOwner;
  987. return $this;
  988. }
  989. /**
  990. * @param int $o_userModification
  991. *
  992. * @return $this
  993. */
  994. public function setUserModification($o_userModification)
  995. {
  996. $this->markFieldDirty('o_userModification');
  997. $this->o_userModification = (int) $o_userModification;
  998. return $this;
  999. }
  1000. /**
  1001. * @param array|null $children
  1002. * @param array $objectTypes
  1003. * @param bool $includingUnpublished
  1004. *
  1005. * @return $this
  1006. */
  1007. public function setChildren($children, array $objectTypes = [self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_FOLDER], $includingUnpublished = false)
  1008. {
  1009. if ($children === null) {
  1010. // unset all cached children
  1011. $this->o_children = [];
  1012. $this->o_hasChildren = [];
  1013. } elseif (is_array($children)) {
  1014. //default cache key
  1015. $cacheKey = $this->getListingCacheKey([$objectTypes, $includingUnpublished]);
  1016. $this->o_children[$cacheKey] = $children;
  1017. $this->o_hasChildren[$cacheKey] = (bool) count($children);
  1018. }
  1019. return $this;
  1020. }
  1021. /**
  1022. * @return self|null
  1023. */
  1024. public function getParent()
  1025. {
  1026. if ($this->o_parent === null) {
  1027. $this->setParent(DataObject::getById($this->getParentId()));
  1028. }
  1029. return $this->o_parent;
  1030. }
  1031. /**
  1032. * @param self|null $o_parent
  1033. *
  1034. * @return $this
  1035. */
  1036. public function setParent($o_parent)
  1037. {
  1038. $newParentId = $o_parent instanceof self ? $o_parent->getId() : 0;
  1039. $this->setParentId($newParentId);
  1040. $this->o_parent = $o_parent;
  1041. return $this;
  1042. }
  1043. /**
  1044. * @return Model\Property[]
  1045. */
  1046. public function getProperties()
  1047. {
  1048. if ($this->o_properties === null) {
  1049. // try to get from cache
  1050. $cacheKey = 'object_properties_' . $this->getId();
  1051. $properties = Cache::load($cacheKey);
  1052. if (!is_array($properties)) {
  1053. $properties = $this->getDao()->getProperties();
  1054. $elementCacheTag = $this->getCacheTag();
  1055. $cacheTags = ['object_properties' => 'object_properties', $elementCacheTag => $elementCacheTag];
  1056. Cache::save($properties, $cacheKey, $cacheTags);
  1057. }
  1058. $this->setProperties($properties);
  1059. }
  1060. return $this->o_properties;
  1061. }
  1062. /**
  1063. * {@inheritdoc}
  1064. */
  1065. public function setProperties(?array $properties)
  1066. {
  1067. $this->o_properties = $properties;
  1068. return $this;
  1069. }
  1070. /**
  1071. * @param string $name
  1072. * @param string $type
  1073. * @param mixed $data
  1074. * @param bool $inherited
  1075. * @param bool $inheritable
  1076. *
  1077. * @return $this
  1078. */
  1079. public function setProperty($name, $type, $data, $inherited = false, $inheritable = false)
  1080. {
  1081. $this->getProperties();
  1082. $property = new Model\Property();
  1083. $property->setType($type);
  1084. $property->setCid($this->getId());
  1085. $property->setName($name);
  1086. $property->setCtype('object');
  1087. $property->setData($data);
  1088. $property->setInherited($inherited);
  1089. $property->setInheritable($inheritable);
  1090. $this->o_properties[$name] = $property;
  1091. return $this;
  1092. }
  1093. /**
  1094. * @return string
  1095. */
  1096. public function getChildrenSortBy()
  1097. {
  1098. return $this->o_childrenSortBy ?? self::OBJECT_CHILDREN_SORT_BY_DEFAULT;
  1099. }
  1100. public function __sleep()
  1101. {
  1102. $parentVars = parent::__sleep();
  1103. $blockedVars = ['o_hasChildren', 'o_versions', 'o_class', 'scheduledTasks', 'o_parent', 'omitMandatoryCheck'];
  1104. if ($this->isInDumpState()) {
  1105. // this is if we want to make a full dump of the object (eg. for a new version), including children for recyclebin
  1106. $blockedVars = array_merge($blockedVars, ['o_dirtyFields']);
  1107. $this->removeInheritedProperties();
  1108. } else {
  1109. // this is if we want to cache the object
  1110. $blockedVars = array_merge($blockedVars, ['o_children', 'o_properties']);
  1111. }
  1112. return array_diff($parentVars, $blockedVars);
  1113. }
  1114. public function __wakeup()
  1115. {
  1116. if ($this->isInDumpState() && !self::$doNotRestoreKeyAndPath) {
  1117. // 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 )
  1118. $originalElement = DataObject::getById($this->getId());
  1119. if ($originalElement) {
  1120. $this->setKey($originalElement->getKey());
  1121. $this->setPath($originalElement->getRealPath());
  1122. }
  1123. }
  1124. if ($this->isInDumpState() && $this->o_properties !== null) {
  1125. $this->renewInheritedProperties();
  1126. }
  1127. $this->setInDumpState(false);
  1128. }
  1129. /**
  1130. * @param string $method
  1131. * @param array $args
  1132. *
  1133. * @return mixed
  1134. *
  1135. * @throws \Exception
  1136. */
  1137. public function __call($method, $args)
  1138. {
  1139. // compatibility mode (they do not have any set_oXyz() methods anymore)
  1140. if (preg_match('/^(get|set)o_/i', $method)) {
  1141. $newMethod = preg_replace('/^(get|set)o_/i', '$1', $method);
  1142. if (method_exists($this, $newMethod)) {
  1143. $r = call_user_func_array([$this, $newMethod], $args);
  1144. return $r;
  1145. }
  1146. }
  1147. return parent::__call($method, $args);
  1148. }
  1149. /**
  1150. * @return bool
  1151. */
  1152. public static function doNotRestoreKeyAndPath()
  1153. {
  1154. return self::$doNotRestoreKeyAndPath;
  1155. }
  1156. /**
  1157. * @param bool $doNotRestoreKeyAndPath
  1158. */
  1159. public static function setDoNotRestoreKeyAndPath($doNotRestoreKeyAndPath)
  1160. {
  1161. self::$doNotRestoreKeyAndPath = $doNotRestoreKeyAndPath;
  1162. }
  1163. /**
  1164. * @param string $fieldName
  1165. * @param string|null $language
  1166. *
  1167. * @throws \Exception
  1168. *
  1169. * @return mixed
  1170. */
  1171. public function get($fieldName, $language = null)
  1172. {
  1173. if (!$fieldName) {
  1174. throw new \Exception('Field name must not be empty.');
  1175. }
  1176. return $this->{'get'.ucfirst($fieldName)}($language);
  1177. }
  1178. /**
  1179. * @param string $fieldName
  1180. * @param mixed $value
  1181. * @param string|null $language
  1182. *
  1183. * @throws \Exception
  1184. *
  1185. * @return mixed
  1186. */
  1187. public function set($fieldName, $value, $language = null)
  1188. {
  1189. if (!$fieldName) {
  1190. throw new \Exception('Field name must not be empty.');
  1191. }
  1192. return $this->{'set'.ucfirst($fieldName)}($value, $language);
  1193. }
  1194. /**
  1195. * @internal
  1196. *
  1197. * @return bool
  1198. */
  1199. public static function isDirtyDetectionDisabled()
  1200. {
  1201. return self::$disableDirtyDetection;
  1202. }
  1203. /**
  1204. * @internal
  1205. *
  1206. * @param bool $disableDirtyDetection
  1207. */
  1208. public static function setDisableDirtyDetection(bool $disableDirtyDetection)
  1209. {
  1210. self::$disableDirtyDetection = $disableDirtyDetection;
  1211. }
  1212. /**
  1213. * @internal
  1214. */
  1215. public static function disableDirtyDetection()
  1216. {
  1217. self::setDisableDirtyDetection(true);
  1218. }
  1219. /**
  1220. * @internal
  1221. */
  1222. public static function enableDirtyDetection()
  1223. {
  1224. self::setDisableDirtyDetection(false);
  1225. }
  1226. /**
  1227. * @return int
  1228. */
  1229. public function getVersionCount(): int
  1230. {
  1231. return $this->o_versionCount ? $this->o_versionCount : 0;
  1232. }
  1233. /**
  1234. * @param int|null $o_versionCount
  1235. *
  1236. * @return AbstractObject
  1237. */
  1238. public function setVersionCount(?int $o_versionCount): Element\ElementInterface
  1239. {
  1240. $this->o_versionCount = (int) $o_versionCount;
  1241. return $this;
  1242. }
  1243. /**
  1244. * @internal
  1245. *
  1246. * @param array $args
  1247. *
  1248. * @return string
  1249. */
  1250. protected function getListingCacheKey(array $args = [])
  1251. {
  1252. $objectTypes = $args[0] ?? [self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_FOLDER];
  1253. $includingUnpublished = (bool)($args[1] ?? false);
  1254. if (is_array($objectTypes)) {
  1255. $objectTypes = implode('_', $objectTypes);
  1256. }
  1257. $cacheKey = $objectTypes . (!empty($includingUnpublished) ? '_' : '') . (string)$includingUnpublished;
  1258. return $cacheKey;
  1259. }
  1260. /**
  1261. * @param string | null $o_reverseSort
  1262. *
  1263. * @return AbstractObject
  1264. */
  1265. public function setChildrenSortOrder(?string $o_reverseSort): Element\ElementInterface
  1266. {
  1267. $this->o_childrenSortOrder = $o_reverseSort;
  1268. return $this;
  1269. }
  1270. /**
  1271. * @return string
  1272. */
  1273. public function getChildrenSortOrder(): string
  1274. {
  1275. return $this->o_childrenSortOrder ?? self::OBJECT_CHILDREN_SORT_ORDER_DEFAULT;
  1276. }
  1277. /**
  1278. * load lazy loaded fields before cloning
  1279. */
  1280. public function __clone()
  1281. {
  1282. parent::__clone();
  1283. $this->o_parent = null;
  1284. // note that o_children is currently needed for the recycle bin
  1285. $this->o_hasSiblings = [];
  1286. $this->o_siblings = [];
  1287. }
  1288. }