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

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