vendor/pimcore/pimcore/models/DataObject/Concrete.php line 411

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 Pimcore\Db;
  16. use Pimcore\Event\DataObjectEvents;
  17. use Pimcore\Event\Model\DataObjectEvent;
  18. use Pimcore\Logger;
  19. use Pimcore\Messenger\VersionDeleteMessage;
  20. use Pimcore\Model;
  21. use Pimcore\Model\DataObject;
  22. use Pimcore\Model\DataObject\ClassDefinition\Data\LazyLoadingSupportInterface;
  23. use Pimcore\Model\DataObject\ClassDefinition\Data\Link;
  24. use Pimcore\Model\DataObject\ClassDefinition\Data\Relations\AbstractRelations;
  25. use Pimcore\Model\DataObject\Exception\InheritanceParentNotFoundException;
  26. use Pimcore\Model\Element\DirtyIndicatorInterface;
  27. /**
  28. * @method \Pimcore\Model\DataObject\Concrete\Dao getDao()
  29. * @method \Pimcore\Model\Version|null getLatestVersion(?int $userId = null)
  30. */
  31. class Concrete extends DataObject implements LazyLoadedFieldsInterface
  32. {
  33. use Model\DataObject\Traits\LazyLoadedRelationTrait;
  34. use Model\Element\Traits\ScheduledTasksTrait;
  35. /**
  36. * @internal
  37. *
  38. * @var array|null
  39. */
  40. protected $__rawRelationData = null;
  41. /**
  42. * @internal
  43. *
  44. * Necessary for assigning object reference to corresponding fields while wakeup
  45. *
  46. * @var array
  47. */
  48. public $__objectAwareFields = [];
  49. /**
  50. * @internal
  51. *
  52. * @var array
  53. */
  54. public const SYSTEM_COLUMN_NAMES = ['id', 'fullpath', 'key', 'published', 'creationDate', 'modificationDate', 'filename', 'classname', 'index'];
  55. /**
  56. * @internal
  57. *
  58. * @var bool
  59. */
  60. protected $o_published;
  61. /**
  62. * @internal
  63. *
  64. * @var ClassDefinition|null
  65. */
  66. protected ?ClassDefinition $o_class = null;
  67. /**
  68. * @internal
  69. *
  70. * @var string
  71. */
  72. protected $o_classId;
  73. /**
  74. * @internal
  75. *
  76. * @var string
  77. */
  78. protected $o_className;
  79. /**
  80. * @internal
  81. *
  82. * @var array|null
  83. */
  84. protected $o_versions = null;
  85. /**
  86. * @internal
  87. *
  88. * @var bool|null
  89. */
  90. protected $omitMandatoryCheck;
  91. /**
  92. * @internal
  93. *
  94. * @var bool
  95. */
  96. protected $allLazyKeysMarkedAsLoaded = false;
  97. /**
  98. * returns the class ID of the current object class
  99. *
  100. * @return string
  101. */
  102. public static function classId()
  103. {
  104. $v = get_class_vars(get_called_class());
  105. return $v['o_classId'];
  106. }
  107. /**
  108. * {@inheritdoc}
  109. */
  110. protected function update($isUpdate = null, $params = [])
  111. {
  112. $fieldDefinitions = $this->getClass()->getFieldDefinitions();
  113. $validationExceptions = [];
  114. foreach ($fieldDefinitions as $fd) {
  115. try {
  116. $getter = 'get' . ucfirst($fd->getName());
  117. if (method_exists($this, $getter)) {
  118. $value = $this->$getter();
  119. $omitMandatoryCheck = $this->getOmitMandatoryCheck();
  120. //check throws Exception
  121. try {
  122. if ($fd instanceof Link) {
  123. $params['resetInvalidFields'] = true;
  124. }
  125. $fd->checkValidity($value, $omitMandatoryCheck, $params);
  126. } catch (\Exception $e) {
  127. if ($this->getClass()->getAllowInherit() && $fd->supportsInheritance() && $fd->isEmpty($value)) {
  128. //try again with parent data when inheritance is activated
  129. try {
  130. $getInheritedValues = DataObject::doGetInheritedValues();
  131. DataObject::setGetInheritedValues(true);
  132. $value = $this->$getter();
  133. $fd->checkValidity($value, $omitMandatoryCheck, $params);
  134. DataObject::setGetInheritedValues($getInheritedValues);
  135. } catch (\Exception $e) {
  136. if (!$e instanceof Model\Element\ValidationException) {
  137. throw $e;
  138. }
  139. $exceptionClass = get_class($e);
  140. $newException = new $exceptionClass($e->getMessage() . ' fieldname=' . $fd->getName(), $e->getCode(), $e->getPrevious());
  141. $newException->setSubItems($e->getSubItems());
  142. throw $newException;
  143. }
  144. } else {
  145. throw $e;
  146. }
  147. }
  148. }
  149. } catch (Model\Element\ValidationException $ve) {
  150. $validationExceptions[] = $ve;
  151. }
  152. }
  153. $preUpdateEvent = new DataObjectEvent($this, [
  154. 'validationExceptions' => $validationExceptions,
  155. 'message' => 'Validation failed: ',
  156. 'separator' => ' / ',
  157. ]);
  158. \Pimcore::getEventDispatcher()->dispatch($preUpdateEvent, DataObjectEvents::PRE_UPDATE_VALIDATION_EXCEPTION);
  159. $validationExceptions = $preUpdateEvent->getArgument('validationExceptions');
  160. if ($validationExceptions) {
  161. $message = $preUpdateEvent->getArgument('message');
  162. $errors = [];
  163. /** @var Model\Element\ValidationException $e */
  164. foreach ($validationExceptions as $e) {
  165. $errors[] = $e->getAggregatedMessage();
  166. }
  167. $message .= implode($preUpdateEvent->getArgument('separator'), $errors);
  168. throw new Model\Element\ValidationException($message);
  169. }
  170. $isDirtyDetectionDisabled = self::isDirtyDetectionDisabled();
  171. try {
  172. $oldVersionCount = $this->getVersionCount();
  173. parent::update($isUpdate, $params);
  174. $newVersionCount = $this->getVersionCount();
  175. if (($newVersionCount != $oldVersionCount + 1) || ($this instanceof DirtyIndicatorInterface && $this->isFieldDirty('o_parentId'))) {
  176. self::disableDirtyDetection();
  177. }
  178. $this->getDao()->update($isUpdate);
  179. // scheduled tasks are saved in $this->saveVersion();
  180. $this->saveVersion(false, false, isset($params['versionNote']) ? $params['versionNote'] : null);
  181. $this->saveChildData();
  182. } finally {
  183. self::setDisableDirtyDetection($isDirtyDetectionDisabled);
  184. }
  185. }
  186. private function saveChildData(): void
  187. {
  188. if ($this->getClass()->getAllowInherit()) {
  189. $this->getDao()->saveChildData();
  190. }
  191. }
  192. /**
  193. * {@inheritdoc}
  194. */
  195. protected function doDelete()
  196. {
  197. // Dispatch Symfony Message Bus to delete versions
  198. \Pimcore::getContainer()->get('messenger.bus.pimcore-core')->dispatch(
  199. new VersionDeleteMessage(Model\Element\Service::getElementType($this), $this->getId())
  200. );
  201. $this->getDao()->deleteAllTasks();
  202. parent::doDelete();
  203. }
  204. /**
  205. * $callPluginHook is true when the method is called from outside (eg. directly in the controller "save only version")
  206. * it is false when the method is called by $this->update()
  207. *
  208. * @param bool $setModificationDate
  209. * @param bool $saveOnlyVersion
  210. * @param string $versionNote version note
  211. * @param bool $isAutoSave
  212. *
  213. * @return Model\Version
  214. */
  215. public function saveVersion($setModificationDate = true, $saveOnlyVersion = true, $versionNote = null, $isAutoSave = false)
  216. {
  217. try {
  218. if ($setModificationDate) {
  219. $this->setModificationDate(time());
  220. }
  221. // hook should be also called if "save only new version" is selected
  222. if ($saveOnlyVersion) {
  223. $preUpdateEvent = new DataObjectEvent($this, [
  224. 'saveVersionOnly' => true,
  225. 'isAutoSave' => $isAutoSave,
  226. ]);
  227. \Pimcore::getEventDispatcher()->dispatch($preUpdateEvent, DataObjectEvents::PRE_UPDATE);
  228. }
  229. // scheduled tasks are saved always, they are not versioned!
  230. $this->saveScheduledTasks();
  231. $version = null;
  232. // only create a new version if there is at least 1 allowed
  233. // or if saveVersion() was called directly (it's a newer version of the object)
  234. $objectsConfig = \Pimcore\Config::getSystemConfiguration('objects');
  235. if ((is_null($objectsConfig['versions']['days'] ?? null) && is_null($objectsConfig['versions']['steps'] ?? null))
  236. || (!empty($objectsConfig['versions']['steps']))
  237. || !empty($objectsConfig['versions']['days'])
  238. || $setModificationDate) {
  239. $saveStackTrace = !($objectsConfig['versions']['disable_stack_trace'] ?? false);
  240. $version = $this->doSaveVersion($versionNote, $saveOnlyVersion, $saveStackTrace, $isAutoSave);
  241. }
  242. // hook should be also called if "save only new version" is selected
  243. if ($saveOnlyVersion) {
  244. $postUpdateEvent = new DataObjectEvent($this, [
  245. 'saveVersionOnly' => true,
  246. 'isAutoSave' => $isAutoSave,
  247. ]);
  248. \Pimcore::getEventDispatcher()->dispatch($postUpdateEvent, DataObjectEvents::POST_UPDATE);
  249. }
  250. return $version;
  251. } catch (\Exception $e) {
  252. $postUpdateFailureEvent = new DataObjectEvent($this, [
  253. 'saveVersionOnly' => true,
  254. 'exception' => $e,
  255. 'isAutoSave' => $isAutoSave,
  256. ]);
  257. \Pimcore::getEventDispatcher()->dispatch($postUpdateFailureEvent, DataObjectEvents::POST_UPDATE_FAILURE);
  258. throw $e;
  259. }
  260. }
  261. /**
  262. * @return Model\Version[]
  263. */
  264. public function getVersions()
  265. {
  266. if ($this->o_versions === null) {
  267. $this->setVersions($this->getDao()->getVersions());
  268. }
  269. return $this->o_versions;
  270. }
  271. /**
  272. * @param Model\Version[] $o_versions
  273. *
  274. * @return $this
  275. */
  276. public function setVersions($o_versions)
  277. {
  278. $this->o_versions = $o_versions;
  279. return $this;
  280. }
  281. /**
  282. * @param string $key
  283. *
  284. * @return mixed
  285. */
  286. public function getValueForFieldName($key)
  287. {
  288. if (isset($this->$key)) {
  289. return $this->$key;
  290. }
  291. if ($this->getClass()->getFieldDefinition($key) instanceof Model\DataObject\ClassDefinition\Data\CalculatedValue) {
  292. $value = new Model\DataObject\Data\CalculatedValue($key);
  293. $value = Service::getCalculatedFieldValue($this, $value);
  294. return $value;
  295. }
  296. return null;
  297. }
  298. /**
  299. * @param array $tags
  300. *
  301. * @return array
  302. */
  303. public function getCacheTags(array $tags = []): array
  304. {
  305. $tags = parent::getCacheTags($tags);
  306. $tags['class_' . $this->getClassId()] = 'class_' . $this->getClassId();
  307. foreach ($this->getClass()->getFieldDefinitions() as $name => $def) {
  308. // no need to add lazy-loading fields to the cache tags
  309. if (!$def instanceof LazyLoadingSupportInterface || !$def->getLazyLoading()) {
  310. $tags = $def->getCacheTags($this->getValueForFieldName($name), $tags);
  311. }
  312. }
  313. return $tags;
  314. }
  315. /**
  316. * {@inheritdoc}
  317. */
  318. protected function resolveDependencies(): array
  319. {
  320. $dependencies = [parent::resolveDependencies()];
  321. // check in fields
  322. if ($this->getClass() instanceof ClassDefinition) {
  323. foreach ($this->getClass()->getFieldDefinitions() as $field) {
  324. $key = $field->getName();
  325. $dependencies[] = $field->resolveDependencies($this->$key ?? null);
  326. }
  327. }
  328. return array_merge(...$dependencies);
  329. }
  330. /**
  331. * @param ClassDefinition|null $o_class
  332. *
  333. * @return $this
  334. */
  335. public function setClass(?ClassDefinition $o_class)
  336. {
  337. $this->o_class = $o_class;
  338. return $this;
  339. }
  340. /**
  341. * @return ClassDefinition|null
  342. */
  343. public function getClass(): ?ClassDefinition
  344. {
  345. if (!$this->o_class) {
  346. $this->setClass(ClassDefinition::getById($this->getClassId()));
  347. }
  348. return $this->o_class;
  349. }
  350. /**
  351. * @return string
  352. */
  353. public function getClassId()
  354. {
  355. return $this->o_classId;
  356. }
  357. /**
  358. * @param string $o_classId
  359. *
  360. * @return $this
  361. */
  362. public function setClassId($o_classId)
  363. {
  364. $this->o_classId = $o_classId;
  365. return $this;
  366. }
  367. /**
  368. * @return string
  369. */
  370. public function getClassName()
  371. {
  372. return $this->o_className;
  373. }
  374. /**
  375. * @param string $o_className
  376. *
  377. * @return $this
  378. */
  379. public function setClassName($o_className)
  380. {
  381. $this->o_className = $o_className;
  382. return $this;
  383. }
  384. /**
  385. * @return bool
  386. */
  387. public function getPublished()
  388. {
  389. return (bool) $this->o_published;
  390. }
  391. /**
  392. * @return bool
  393. */
  394. public function isPublished()
  395. {
  396. return (bool) $this->getPublished();
  397. }
  398. /**
  399. * @param bool $o_published
  400. *
  401. * @return $this
  402. */
  403. public function setPublished($o_published)
  404. {
  405. $this->o_published = (bool) $o_published;
  406. return $this;
  407. }
  408. /**
  409. * @param bool $omitMandatoryCheck
  410. *
  411. * @return $this
  412. */
  413. public function setOmitMandatoryCheck($omitMandatoryCheck)
  414. {
  415. $this->omitMandatoryCheck = $omitMandatoryCheck;
  416. return $this;
  417. }
  418. /**
  419. * @return bool
  420. */
  421. public function getOmitMandatoryCheck()
  422. {
  423. if ($this->omitMandatoryCheck === null) {
  424. return !$this->isPublished();
  425. }
  426. return $this->omitMandatoryCheck;
  427. }
  428. /**
  429. * @param string $key
  430. * @param mixed $params
  431. *
  432. * @return mixed
  433. *
  434. * @throws InheritanceParentNotFoundException
  435. */
  436. public function getValueFromParent($key, $params = null)
  437. {
  438. $parent = $this->getNextParentForInheritance();
  439. if ($parent) {
  440. $method = 'get' . $key;
  441. if (method_exists($parent, $method)) {
  442. return $parent->$method($params);
  443. }
  444. throw new InheritanceParentNotFoundException(sprintf('Parent object does not have a method called `%s()`, unable to retrieve value for key `%s`', $method, $key));
  445. }
  446. throw new InheritanceParentNotFoundException('No parent object available to get a value from');
  447. }
  448. /**
  449. * @internal
  450. *
  451. * @return Concrete|null
  452. */
  453. public function getNextParentForInheritance()
  454. {
  455. return $this->getClosestParentOfClass($this->getClassId());
  456. }
  457. /**
  458. * @param string $classId
  459. *
  460. * @return self|null
  461. */
  462. public function getClosestParentOfClass(string $classId): ?self
  463. {
  464. $parent = $this->getParent();
  465. if ($parent instanceof AbstractObject) {
  466. while ($parent && (!$parent instanceof Concrete || $parent->getClassId() !== $classId)) {
  467. $parent = $parent->getParent();
  468. }
  469. if ($parent && in_array($parent->getType(), [self::OBJECT_TYPE_OBJECT, self::OBJECT_TYPE_VARIANT], true)) {
  470. /** @var Concrete $parent */
  471. if ($parent->getClassId() === $classId) {
  472. return $parent;
  473. }
  474. }
  475. }
  476. return null;
  477. }
  478. /**
  479. * get object relation data as array for a specific field
  480. *
  481. * @param string $fieldName
  482. * @param bool $forOwner
  483. * @param string $remoteClassId
  484. *
  485. * @return array
  486. */
  487. public function getRelationData($fieldName, $forOwner, $remoteClassId)
  488. {
  489. $relationData = $this->getDao()->getRelationData($fieldName, $forOwner, $remoteClassId);
  490. return $relationData;
  491. }
  492. /**
  493. * @param string $method
  494. * @param array $arguments
  495. *
  496. * @return Model\Listing\AbstractListing|Concrete|null
  497. *
  498. * @throws \Exception
  499. */
  500. public static function __callStatic($method, $arguments)
  501. {
  502. // check for custom static getters like DataObject::getByMyfield()
  503. $propertyName = lcfirst(preg_replace('/^getBy/i', '', $method));
  504. $classDefinition = ClassDefinition::getById(self::classId());
  505. // get real fieldname (case sensitive)
  506. $fieldnames = [];
  507. $defaultCondition = '';
  508. foreach ($classDefinition->getFieldDefinitions() as $fd) {
  509. $fieldnames[] = $fd->getName();
  510. }
  511. $realPropertyName = implode('', preg_grep('/^' . preg_quote($propertyName, '/') . '$/i', $fieldnames));
  512. if (!$classDefinition->getFieldDefinition($realPropertyName) instanceof Model\DataObject\ClassDefinition\Data) {
  513. $localizedField = $classDefinition->getFieldDefinition('localizedfields');
  514. if ($localizedField instanceof Model\DataObject\ClassDefinition\Data\Localizedfields) {
  515. $fieldnames = [];
  516. foreach ($localizedField->getFieldDefinitions() as $fd) {
  517. $fieldnames[] = $fd->getName();
  518. }
  519. $realPropertyName = implode('', preg_grep('/^' . preg_quote($propertyName, '/') . '$/i', $fieldnames));
  520. $localizedFieldDefinition = $localizedField->getFieldDefinition($realPropertyName);
  521. if ($localizedFieldDefinition instanceof Model\DataObject\ClassDefinition\Data) {
  522. $realPropertyName = 'localizedfields';
  523. \array_unshift($arguments, $localizedFieldDefinition->getName());
  524. }
  525. }
  526. }
  527. if ($classDefinition->getFieldDefinition($realPropertyName) instanceof Model\DataObject\ClassDefinition\Data) {
  528. $field = $classDefinition->getFieldDefinition($realPropertyName);
  529. if (!$field->isFilterable()) {
  530. throw new \Exception("Static getter '::getBy".ucfirst($realPropertyName)."' is not allowed for fieldtype '" . $field->getFieldType() . "'");
  531. }
  532. $db = Db::get();
  533. if ($field instanceof Model\DataObject\ClassDefinition\Data\Localizedfields) {
  534. $arguments = array_pad($arguments, 6, 0);
  535. [$localizedPropertyName, $value, $locale, $limit, $offset, $objectTypes] = $arguments;
  536. $localizedField = $field->getFieldDefinition($localizedPropertyName);
  537. if (!$localizedField instanceof Model\DataObject\ClassDefinition\Data) {
  538. Logger::error('Class: DataObject\\Concrete => call to undefined static method ' . $method);
  539. throw new \Exception('Call to undefined static method ' . $method . ' in class DataObject\\Concrete');
  540. }
  541. if (!$localizedField->isFilterable()) {
  542. throw new \Exception("Static getter '::getBy".ucfirst($realPropertyName)."' is not allowed for fieldtype '" . $localizedField->getFieldType() . "'");
  543. }
  544. $defaultCondition = $db->quoteIdentifier($localizedPropertyName) . ' = ' . $db->quote($value) . ' ';
  545. $listConfig = [
  546. 'condition' => $defaultCondition,
  547. ];
  548. if ($locale) {
  549. $listConfig['locale'] = $locale;
  550. }
  551. } else {
  552. $arguments = array_pad($arguments, 4, 0);
  553. [$value, $limit, $offset, $objectTypes] = $arguments;
  554. if (!$field instanceof AbstractRelations) {
  555. $defaultCondition = $db->quoteIdentifier($realPropertyName) . ' = ' . $db->quote($value) . ' ';
  556. }
  557. $listConfig = [
  558. 'condition' => $defaultCondition,
  559. ];
  560. }
  561. if (!is_array($limit)) {
  562. if ($limit) {
  563. $listConfig['limit'] = $limit;
  564. }
  565. if ($offset) {
  566. $listConfig['offset'] = $offset;
  567. }
  568. } else {
  569. $listConfig = array_merge($listConfig, $limit);
  570. $limitCondition = $limit['condition'] ?? '';
  571. $listConfig['condition'] = $defaultCondition . $limitCondition;
  572. }
  573. $list = static::makeList($listConfig, $objectTypes);
  574. if ($field instanceof AbstractRelations) {
  575. $list = $field->addListingFilter($list, $value);
  576. }
  577. if (isset($listConfig['limit']) && $listConfig['limit'] == 1) {
  578. $elements = $list->getObjects();
  579. return isset($elements[0]) ? $elements[0] : null;
  580. }
  581. return $list;
  582. }
  583. try {
  584. return call_user_func_array([parent::class, $method], $arguments);
  585. } catch (\Exception $e) {
  586. // there is no property for the called method, so throw an exception
  587. Logger::error('Class: DataObject\\Concrete => call to undefined static method '.$method);
  588. throw new \Exception('Call to undefined static method '.$method.' in class DataObject\\Concrete');
  589. }
  590. }
  591. /**
  592. * @return $this
  593. *
  594. * @throws \Exception
  595. */
  596. public function save()
  597. {
  598. $isDirtyDetectionDisabled = DataObject::isDirtyDetectionDisabled();
  599. // if the class is newer then better disable the dirty detection. This should fix issues with the query table if
  600. // the inheritance enabled flag has been changed in the meantime
  601. if ($this->getClass()->getModificationDate() >= $this->getModificationDate() && $this->getId()) {
  602. DataObject::disableDirtyDetection();
  603. } elseif ($this->getClass()->getAllowInherit() && $this->isFieldDirty('parentId')) {
  604. // if inherit is enabled and the data object is moved the query table should be updated
  605. DataObject::disableDirtyDetection();
  606. }
  607. try {
  608. $params = [];
  609. if (func_num_args() && is_array(func_get_arg(0))) {
  610. $params = func_get_arg(0);
  611. }
  612. parent::save($params);
  613. if ($this instanceof DirtyIndicatorInterface) {
  614. $this->resetDirtyMap();
  615. }
  616. } finally {
  617. DataObject::setDisableDirtyDetection($isDirtyDetectionDisabled);
  618. }
  619. return $this;
  620. }
  621. /**
  622. * @internal
  623. *
  624. * @return array
  625. */
  626. public function getLazyLoadedFieldNames(): array
  627. {
  628. $lazyLoadedFieldNames = [];
  629. $fields = $this->getClass()->getFieldDefinitions(['suppressEnrichment' => true]);
  630. foreach ($fields as $field) {
  631. if ($field instanceof LazyLoadingSupportInterface && $field->getLazyLoading()) {
  632. $lazyLoadedFieldNames[] = $field->getName();
  633. }
  634. }
  635. return $lazyLoadedFieldNames;
  636. }
  637. /**
  638. * {@inheritdoc}
  639. */
  640. public function isAllLazyKeysMarkedAsLoaded(): bool
  641. {
  642. if (!$this->getId()) {
  643. return true;
  644. }
  645. return $this->allLazyKeysMarkedAsLoaded;
  646. }
  647. public function markAllLazyLoadedKeysAsLoaded()
  648. {
  649. $this->allLazyKeysMarkedAsLoaded = true;
  650. }
  651. public function __sleep()
  652. {
  653. $parentVars = parent::__sleep();
  654. $finalVars = [];
  655. $blockedVars = [];
  656. if (!$this->isInDumpState()) {
  657. $blockedVars = ['loadedLazyKeys', 'allLazyKeysMarkedAsLoaded'];
  658. // do not dump lazy loaded fields for caching
  659. $lazyLoadedFields = $this->getLazyLoadedFieldNames();
  660. $blockedVars = array_merge($lazyLoadedFields, $blockedVars);
  661. }
  662. foreach ($parentVars as $key) {
  663. if (!in_array($key, $blockedVars)) {
  664. $finalVars[] = $key;
  665. }
  666. }
  667. return $finalVars;
  668. }
  669. public function __wakeup()
  670. {
  671. parent::__wakeup();
  672. // renew localized fields
  673. // do not use the getter ($this->getLocalizedfields()) as it somehow slows down the process around a sec
  674. // no clue why this happens
  675. if (property_exists($this, 'localizedfields') && $this->localizedfields instanceof Localizedfield) {
  676. $this->localizedfields->setObject($this, false);
  677. }
  678. // renew object reference to other object aware fields
  679. foreach ($this->__objectAwareFields as $objectAwareField => $exists) {
  680. if (isset($this->$objectAwareField) && $this->$objectAwareField instanceof ObjectAwareFieldInterface) {
  681. $this->$objectAwareField->setObject($this);
  682. }
  683. }
  684. }
  685. /**
  686. * load lazy loaded fields before cloning
  687. */
  688. public function __clone()
  689. {
  690. parent::__clone();
  691. $this->o_class = null;
  692. $this->o_versions = null;
  693. $this->scheduledTasks = null;
  694. }
  695. /**
  696. * @internal
  697. *
  698. * @param array $descriptor
  699. * @param string $table
  700. *
  701. * @return array
  702. */
  703. protected function doRetrieveData(array $descriptor, string $table)
  704. {
  705. $db = Db::get();
  706. $conditionParts = Service::buildConditionPartsFromDescriptor($descriptor);
  707. $query = 'SELECT * FROM ' . $table . ' WHERE ' . implode(' AND ', $conditionParts);
  708. $result = $db->fetchAllAssociative($query);
  709. return $result;
  710. }
  711. /**
  712. * @internal
  713. *
  714. * @param array $descriptor
  715. *
  716. * @return array
  717. */
  718. public function retrieveSlugData($descriptor)
  719. {
  720. $descriptor['objectId'] = $this->getId();
  721. return $this->doRetrieveData($descriptor, DataObject\Data\UrlSlug::TABLE_NAME);
  722. }
  723. /**
  724. * @internal
  725. *
  726. * @param array $descriptor
  727. *
  728. * @return array
  729. */
  730. public function retrieveRelationData($descriptor)
  731. {
  732. $descriptor['src_id'] = $this->getId();
  733. $unfilteredData = $this->__getRawRelationData();
  734. $likes = [];
  735. foreach ($descriptor as $column => $expectedValue) {
  736. if (is_string($expectedValue)) {
  737. $trimmed = rtrim($expectedValue, '%');
  738. if (strlen($trimmed) < strlen($expectedValue)) {
  739. $likes[$column] = $trimmed;
  740. }
  741. }
  742. }
  743. $filterFn = static function ($row) use ($descriptor, $likes) {
  744. foreach ($descriptor as $column => $expectedValue) {
  745. $actualValue = $row[$column];
  746. if (isset($likes[$column])) {
  747. $expectedValue = $likes[$column];
  748. if (strpos($actualValue, $expectedValue) !== 0) {
  749. return false;
  750. }
  751. } elseif ($actualValue != $expectedValue) {
  752. return false;
  753. }
  754. }
  755. return true;
  756. };
  757. $filteredData = array_filter($unfilteredData, $filterFn);
  758. return $filteredData;
  759. }
  760. /**
  761. * @internal
  762. *
  763. * @return array
  764. */
  765. public function __getRawRelationData(): array
  766. {
  767. if ($this->__rawRelationData === null) {
  768. $db = Db::get();
  769. $this->__rawRelationData = $db->fetchAllAssociative('SELECT * FROM object_relations_' . $this->getClassId() . ' WHERE src_id = ?', [$this->getId()]);
  770. }
  771. return $this->__rawRelationData;
  772. }
  773. }