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

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