vendor/pimcore/pimcore/models/DataObject/ClassDefinition.php line 285

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\Cache;
  16. use Pimcore\Cache\RuntimeCache;
  17. use Pimcore\DataObject\ClassBuilder\FieldDefinitionDocBlockBuilderInterface;
  18. use Pimcore\DataObject\ClassBuilder\PHPClassDumperInterface;
  19. use Pimcore\Db;
  20. use Pimcore\Event\DataObjectClassDefinitionEvents;
  21. use Pimcore\Event\Model\DataObject\ClassDefinitionEvent;
  22. use Pimcore\Event\Traits\RecursionBlockingEventDispatchHelperTrait;
  23. use Pimcore\Logger;
  24. use Pimcore\Model;
  25. use Pimcore\Model\DataObject;
  26. use Pimcore\Model\DataObject\ClassDefinition\Data\FieldDefinitionEnrichmentInterface;
  27. use Pimcore\Model\DataObject\ClassDefinition\Data\ManyToOneRelation;
  28. /**
  29.  * @method \Pimcore\Model\DataObject\ClassDefinition\Dao getDao()
  30.  */
  31. final class ClassDefinition extends Model\AbstractModel
  32. {
  33.     use DataObject\ClassDefinition\Helper\VarExport;
  34.     use DataObject\Traits\LocateFileTrait;
  35.     use RecursionBlockingEventDispatchHelperTrait;
  36.     /**
  37.      * @internal
  38.      *
  39.      * @var string|null
  40.      */
  41.     public $id;
  42.     /**
  43.      * @internal
  44.      *
  45.      * @var string|null
  46.      */
  47.     public $name;
  48.     /**
  49.      * @internal
  50.      *
  51.      * @var string
  52.      */
  53.     public $description = '';
  54.     /**
  55.      * @internal
  56.      *
  57.      * @var int|null
  58.      */
  59.     public $creationDate;
  60.     /**
  61.      * @internal
  62.      *
  63.      * @var int|null
  64.      */
  65.     public $modificationDate;
  66.     /**
  67.      * @internal
  68.      *
  69.      * @var int|null
  70.      */
  71.     public $userOwner;
  72.     /**
  73.      * @internal
  74.      *
  75.      * @var int|null
  76.      */
  77.     public $userModification;
  78.     /**
  79.      * @internal
  80.      *
  81.      * @var string
  82.      */
  83.     public $parentClass = '';
  84.     /**
  85.      * Comma separated list of interfaces
  86.      *
  87.      * @internal
  88.      *
  89.      * @var string|null
  90.      */
  91.     public $implementsInterfaces;
  92.     /**
  93.      * Name of the listing parent class if set
  94.      *
  95.      * @internal
  96.      *
  97.      * @var string
  98.      */
  99.     public $listingParentClass = '';
  100.     /**
  101.      * @internal
  102.      *
  103.      * @var string
  104.      */
  105.     public $useTraits = '';
  106.     /**
  107.      * @internal
  108.      *
  109.      * @var string
  110.      */
  111.     public $listingUseTraits = '';
  112.     /**
  113.      * @internal
  114.      *
  115.      * @var bool
  116.      */
  117.     protected $encryption = false;
  118.     /**
  119.      * @internal
  120.      *
  121.      * @var array
  122.      */
  123.     protected $encryptedTables = [];
  124.     /**
  125.      * @internal
  126.      *
  127.      * @var bool
  128.      */
  129.     public $allowInherit = false;
  130.     /**
  131.      * @internal
  132.      *
  133.      * @var bool
  134.      */
  135.     public $allowVariants = false;
  136.     /**
  137.      * @internal
  138.      *
  139.      * @var bool
  140.      */
  141.     public $showVariants = false;
  142.     /**
  143.      * @internal
  144.      *
  145.      * @var DataObject\ClassDefinition\Data[]
  146.      */
  147.     public array $fieldDefinitions = [];
  148.     /**
  149.      * @internal
  150.      *
  151.      * @var DataObject\ClassDefinition\Layout|null
  152.      */
  153.     public $layoutDefinitions;
  154.     /**
  155.      * @internal
  156.      *
  157.      * @var string|null
  158.      */
  159.     public $icon;
  160.     /**
  161.      * @internal
  162.      *
  163.      * @var string|null
  164.      */
  165.     public $previewUrl;
  166.     /**
  167.      * @internal
  168.      *
  169.      * @var string|null
  170.      */
  171.     public $group;
  172.     /**
  173.      * @internal
  174.      *
  175.      * @var bool
  176.      */
  177.     public $showAppLoggerTab = false;
  178.     /**
  179.      * @internal
  180.      *
  181.      * @var string
  182.      */
  183.     public $linkGeneratorReference;
  184.     /**
  185.      * @internal
  186.      *
  187.      * @var string|null
  188.      */
  189.     public $previewGeneratorReference;
  190.     /**
  191.      * @internal
  192.      *
  193.      * @var array
  194.      */
  195.     public $compositeIndices = [];
  196.     /**
  197.      * @internal
  198.      *
  199.      * @var bool
  200.      */
  201.     public $generateTypeDeclarations = true;
  202.     /**
  203.      * @internal
  204.      *
  205.      * @var bool
  206.      */
  207.     public $showFieldLookup = false;
  208.     /**
  209.      * @internal
  210.      *
  211.      * @var array
  212.      */
  213.     public $propertyVisibility = [
  214.         'grid' => [
  215.             'id' => true,
  216.             'path' => true,
  217.             'published' => true,
  218.             'modificationDate' => true,
  219.             'creationDate' => true,
  220.         ],
  221.         'search' => [
  222.             'id' => true,
  223.             'path' => true,
  224.             'published' => true,
  225.             'modificationDate' => true,
  226.             'creationDate' => true,
  227.         ],
  228.     ];
  229.     /**
  230.      * @internal
  231.      *
  232.      * @var bool
  233.      */
  234.     public $enableGridLocking = false;
  235.     /**
  236.      * @internal
  237.      *
  238.      * @var ClassDefinition\Data[]
  239.      */
  240.     private array $deletedDataComponents = [];
  241.     /**
  242.      * @param string $id
  243.      * @param bool $force
  244.      *
  245.      * @return null|ClassDefinition
  246.      *
  247.      * @throws \Exception
  248.      */
  249.     public static function getById(string $id, $force = false)
  250.     {
  251.         $cacheKey = 'class_' . $id;
  252.         try {
  253.             if ($force) {
  254.                 throw new \Exception('Forced load');
  255.             }
  256.             $class = RuntimeCache::get($cacheKey);
  257.             if (!$class) {
  258.                 throw new \Exception('Class in registry is null');
  259.             }
  260.         } catch (\Exception $e) {
  261.             try {
  262.                 $class = new self();
  263.                 $name = $class->getDao()->getNameById($id);
  264.                 if (!$name) {
  265.                     throw new \Exception('Class definition with name ' . $name . ' or ID ' . $id . ' does not exist');
  266.                 }
  267.                 $definitionFile = $class->getDefinitionFile($name);
  268.                 $class = @include $definitionFile;
  269.                 if (!$class instanceof self) {
  270.                     throw new \Exception('Class definition with name ' . $name . ' or ID ' . $id . ' does not exist');
  271.                 }
  272.                 $class->setId($id);
  273.                 RuntimeCache::set($cacheKey, $class);
  274.             } catch (\Exception $e) {
  275.                 Logger::info($e->getMessage());
  276.                 return null;
  277.             }
  278.         }
  279.         return $class;
  280.     }
  281.     /**
  282.      * @param string $name
  283.      *
  284.      * @return self|null
  285.      *
  286.      * @throws \Exception
  287.      */
  288.     public static function getByName($name)
  289.     {
  290.         try {
  291.             $class = new self();
  292.             $id = $class->getDao()->getIdByName($name);
  293.             return self::getById($id);
  294.         } catch (Model\Exception\NotFoundException $e) {
  295.             return null;
  296.         }
  297.     }
  298.     /**
  299.      * @param array $values
  300.      *
  301.      * @return self
  302.      */
  303.     public static function create($values = [])
  304.     {
  305.         $class = new self();
  306.         $class->setValues($values);
  307.         return $class;
  308.     }
  309.     /**
  310.      * @internal
  311.      *
  312.      * @param string $name
  313.      */
  314.     public function rename($name)
  315.     {
  316.         $this->deletePhpClasses();
  317.         $this->getDao()->updateClassNameInObjects($name);
  318.         $this->setName($name);
  319.         $this->save();
  320.     }
  321.     /**
  322.      * @param mixed $data
  323.      *
  324.      * @internal
  325.      */
  326.     public static function cleanupForExport(&$data)
  327.     {
  328.         if (!is_object($data)) {
  329.             return;
  330.         }
  331.         if ($data instanceof DataObject\ClassDefinition\Data\VarExporterInterface) {
  332.             $blockedVars = $data->resolveBlockedVars();
  333.             foreach ($blockedVars as $blockedVar) {
  334.                 if (isset($data->{$blockedVar})) {
  335.                     unset($data->{$blockedVar});
  336.                 }
  337.             }
  338.             if (isset($data->blockedVarsForExport)) {
  339.                 unset($data->blockedVarsForExport);
  340.             }
  341.         }
  342.         if (method_exists($data, 'getChildren')) {
  343.             $children = $data->getChildren();
  344.             if (is_array($children)) {
  345.                 foreach ($children as $child) {
  346.                     self::cleanupForExport($child);
  347.                 }
  348.             }
  349.         }
  350.     }
  351.     /**
  352.      * @return bool
  353.      */
  354.     private function exists()
  355.     {
  356.         $name = $this->getDao()->getNameById($this->getId());
  357.         return is_string($name);
  358.     }
  359.     /**
  360.      * @param bool $saveDefinitionFile
  361.      *
  362.      * @throws \Exception
  363.      * @throws DataObject\Exception\DefinitionWriteException
  364.      */
  365.     public function save($saveDefinitionFile = true)
  366.     {
  367.         if ($saveDefinitionFile && !$this->isWritable()) {
  368.             throw new DataObject\Exception\DefinitionWriteException();
  369.         }
  370.         $fieldDefinitions = $this->getFieldDefinitions();
  371.         foreach ($fieldDefinitions as $fd) {
  372.             if ($fd->isForbiddenName()) {
  373.                 throw new \Exception(sprintf('Forbidden name used for field definition: %s', $fd->getName()));
  374.             }
  375.             if ($fd instanceof DataObject\ClassDefinition\Data\DataContainerAwareInterface) {
  376.                 $fd->preSave($this);
  377.             }
  378.         }
  379.         if (!$this->getId()) {
  380.             $db = Db::get();
  381.             $maxId = $db->fetchOne('SELECT MAX(CAST(id AS SIGNED)) FROM classes;');
  382.             $maxId = $maxId ? $maxId + 1 : 1;
  383.             $this->setId((string) $maxId);
  384.         }
  385.         if (!preg_match('/[a-zA-Z][a-zA-Z0-9_]+/', $this->getName())) {
  386.             throw new \Exception(sprintf('Invalid name for class definition: %s', $this->getName()));
  387.         }
  388.         if (!preg_match('/[a-zA-Z0-9]([a-zA-Z0-9_]+)?/', $this->getId())) {
  389.             throw new \Exception(sprintf('Invalid ID `%s` for class definition %s', $this->getId(), $this->getName()));
  390.         }
  391.         foreach (['parentClass', 'listingParentClass', 'useTraits', 'listingUseTraits'] as $propertyName) {
  392.             $propertyValue = $this->{'get'.ucfirst($propertyName)}();
  393.             if ($propertyValue && !preg_match('/^[a-zA-Z_\x7f-\xff\\\][a-zA-Z0-9_\x7f-\xff\\\ ,]*$/', $propertyValue)) {
  394.                 throw new \Exception(sprintf('Invalid %s value for class definition: %s', $propertyName,
  395.                     $this->getParentClass()));
  396.             }
  397.         }
  398.         $isUpdate = $this->exists();
  399.         if (!$isUpdate) {
  400.             $this->dispatchEvent(new ClassDefinitionEvent($this), DataObjectClassDefinitionEvents::PRE_ADD);
  401.         } else {
  402.             $this->dispatchEvent(new ClassDefinitionEvent($this), DataObjectClassDefinitionEvents::PRE_UPDATE);
  403.         }
  404.         $this->setModificationDate(time());
  405.         $this->getDao()->save($isUpdate);
  406.         $this->generateClassFiles($saveDefinitionFile);
  407.         foreach ($fieldDefinitions as $fd) {
  408.             // call the method "classSaved" if exists, this is used to create additional data tables or whatever which depends on the field definition, for example for localizedfields
  409.             //TODO Pimcore 11 remove method_exists call
  410.             if (!$fd instanceof ClassDefinition\Data\DataContainerAwareInterface && method_exists($fd, 'classSaved')) {
  411.                 $fd->classSaved($this);
  412.             }
  413.         }
  414.         // empty object cache
  415.         try {
  416.             Cache::clearTag('class_'.$this->getId());
  417.         } catch (\Exception $e) {
  418.         }
  419.         foreach ($fieldDefinitions as $fd) {
  420.             if ($fd instanceof DataObject\ClassDefinition\Data\DataContainerAwareInterface) {
  421.                 $fd->postSave($this);
  422.             }
  423.         }
  424.         if ($isUpdate) {
  425.             $this->dispatchEvent(new ClassDefinitionEvent($this), DataObjectClassDefinitionEvents::POST_UPDATE);
  426.         } else {
  427.             $this->dispatchEvent(new ClassDefinitionEvent($this), DataObjectClassDefinitionEvents::POST_ADD);
  428.         }
  429.         $this->deleteDeletedDataComponentsInCustomLayout();
  430.     }
  431.     /**
  432.      * @param bool $generateDefinitionFile
  433.      *
  434.      * @throws \Exception
  435.      *
  436.      * @internal
  437.      */
  438.     public function generateClassFiles($generateDefinitionFile = true)
  439.     {
  440.         \Pimcore::getContainer()->get(PHPClassDumperInterface::class)->dumpPHPClasses($this);
  441.         if ($generateDefinitionFile) {
  442.             // save definition as a php file
  443.             $definitionFile = $this->getDefinitionFile();
  444.             if (!is_writable(dirname($definitionFile)) || (is_file($definitionFile) && !is_writable($definitionFile))) {
  445.                 throw new \Exception(
  446.                     'Cannot write definition file in: '.$definitionFile.' please check write permission on this directory.'
  447.                 );
  448.             }
  449.             /** @var self $clone */
  450.             $clone = DataObject\Service::cloneDefinition($this);
  451.             $clone->setDao(null);
  452.             $clone->fieldDefinitions = [];
  453.             self::cleanupForExport($clone->layoutDefinitions);
  454.             $exportedClass = var_export($clone, true);
  455.             $data = '<?php';
  456.             $data .= "\n\n";
  457.             $data .= $this->getInfoDocBlock();
  458.             $data .= "\n\n";
  459.             $data .= 'return '.$exportedClass.";\n";
  460.             \Pimcore\File::putPhpFile($definitionFile, $data);
  461.         }
  462.     }
  463.     /**
  464.      * @return string
  465.      *
  466.      * @internal
  467.      */
  468.     protected function getInfoDocBlock(): string
  469.     {
  470.         $cd = '/**' . "\n";
  471.         $cd .= ' * Inheritance: '.($this->getAllowInherit() ? 'yes' : 'no')."\n";
  472.         $cd .= ' * Variants: '.($this->getAllowVariants() ? 'yes' : 'no')."\n";
  473.         if ($description = $this->getDescription()) {
  474.             $description = str_replace(['/**', '*/', '//'], '', $description);
  475.             $description = str_replace("\n", "\n * ", $description);
  476.             $cd .= ' * '.$description."\n";
  477.         }
  478.         $cd .= " *\n";
  479.         $cd .= " * Fields Summary:\n";
  480.         $fieldDefinitionDocBlockBuilder = \Pimcore::getContainer()->get(FieldDefinitionDocBlockBuilderInterface::class);
  481.         foreach ($this->getFieldDefinitions() as $fieldDefinition) {
  482.             $cd .= ' * ' . str_replace("\n", "\n * ", trim($fieldDefinitionDocBlockBuilder->buildFieldDefinitionDocBlock($fieldDefinition))) . "\n";
  483.         }
  484.         $cd .= ' */';
  485.         return $cd;
  486.     }
  487.     public function delete()
  488.     {
  489.         $this->dispatchEvent(new ClassDefinitionEvent($this), DataObjectClassDefinitionEvents::PRE_DELETE);
  490.         // delete all objects using this class
  491.         $list = new Listing();
  492.         $list->setCondition('o_classId = ?', $this->getId());
  493.         $list->load();
  494.         foreach ($list->getObjects() as $o) {
  495.             $o->delete();
  496.         }
  497.         $this->deletePhpClasses();
  498.         // empty object cache
  499.         try {
  500.             Cache::clearTag('class_'.$this->getId());
  501.         } catch (\Exception $e) {
  502.         }
  503.         // empty output cache
  504.         try {
  505.             Cache::clearTag('output');
  506.         } catch (\Exception $e) {
  507.         }
  508.         $customLayouts = new ClassDefinition\CustomLayout\Listing();
  509.         $id = $this->getId();
  510.         $customLayouts->setFilter(function (DataObject\ClassDefinition\CustomLayout $layout) use ($id) {
  511.             return $layout->getClassId() === $id;
  512.         });
  513.         $customLayouts = $customLayouts->load();
  514.         foreach ($customLayouts as $customLayout) {
  515.             $customLayout->delete();
  516.         }
  517.         $brickListing = new DataObject\Objectbrick\Definition\Listing();
  518.         $brickListing = $brickListing->load();
  519.         foreach ($brickListing as $brickDefinition) {
  520.             $modified = false;
  521.             $classDefinitions = $brickDefinition->getClassDefinitions();
  522.             if (is_array($classDefinitions)) {
  523.                 foreach ($classDefinitions as $key => $classDefinition) {
  524.                     if ($classDefinition['classname'] == $this->getId()) {
  525.                         unset($classDefinitions[$key]);
  526.                         $modified = true;
  527.                     }
  528.                 }
  529.             }
  530.             if ($modified) {
  531.                 $brickDefinition->setClassDefinitions($classDefinitions);
  532.                 $brickDefinition->save();
  533.             }
  534.         }
  535.         $this->getDao()->delete();
  536.         $this->dispatchEvent(new ClassDefinitionEvent($this), DataObjectClassDefinitionEvents::POST_DELETE);
  537.     }
  538.     private function deletePhpClasses()
  539.     {
  540.         // delete the class files
  541.         @unlink($this->getPhpClassFile());
  542.         @unlink($this->getPhpListingClassFile());
  543.         @rmdir(dirname($this->getPhpListingClassFile()));
  544.         @unlink($this->getDefinitionFile());
  545.     }
  546.     /**
  547.      * @internal
  548.      *
  549.      * with PIMCORE_CLASS_DEFINITION_WRITABLE set, it globally allow/disallow creation and change in classes
  550.      * when the ENV is not set, it allows modification and creation of new in classes in /var/classes but disables modification of classes in config/pimcore/classes
  551.      * more details in 05_Deployment_Tools.md
  552.      *
  553.      * @return bool
  554.      */
  555.     public function isWritable(): bool
  556.     {
  557.         return $_SERVER['PIMCORE_CLASS_DEFINITION_WRITABLE'] ?? !str_starts_with($this->getDefinitionFile(), PIMCORE_CUSTOM_CONFIGURATION_DIRECTORY);
  558.     }
  559.     /**
  560.      * @internal
  561.      *
  562.      * @param string|null $name
  563.      *
  564.      * @return string
  565.      */
  566.     public function getDefinitionFile($name = null)
  567.     {
  568.         return $this->locateDefinitionFile($name ?? $this->getName(), 'definition_%s.php');
  569.     }
  570.     /**
  571.      * @internal
  572.      */
  573.     public function getPhpClassFile(): string
  574.     {
  575.         return $this->locateFile(ucfirst($this->getName()), 'DataObject/%s.php');
  576.     }
  577.     /**
  578.      * @internal
  579.      */
  580.     public function getPhpListingClassFile(): string
  581.     {
  582.         return $this->locateFile(ucfirst($this->getName()), 'DataObject/%s/Listing.php');
  583.     }
  584.     /**
  585.      * @return string|null
  586.      */
  587.     public function getId()
  588.     {
  589.         return $this->id;
  590.     }
  591.     /**
  592.      * @return string|null
  593.      */
  594.     public function getName()
  595.     {
  596.         return $this->name;
  597.     }
  598.     /**
  599.      * @return int|null
  600.      */
  601.     public function getCreationDate()
  602.     {
  603.         return $this->creationDate;
  604.     }
  605.     /**
  606.      * @return int|null
  607.      */
  608.     public function getModificationDate()
  609.     {
  610.         return $this->modificationDate;
  611.     }
  612.     /**
  613.      * @return int|null
  614.      */
  615.     public function getUserOwner()
  616.     {
  617.         return $this->userOwner;
  618.     }
  619.     /**
  620.      * @return int|null
  621.      */
  622.     public function getUserModification()
  623.     {
  624.         return $this->userModification;
  625.     }
  626.     /**
  627.      * @param string $id
  628.      *
  629.      * @return $this
  630.      */
  631.     public function setId($id)
  632.     {
  633.         $this->id = $id;
  634.         return $this;
  635.     }
  636.     /**
  637.      * @param string $name
  638.      *
  639.      * @return $this
  640.      */
  641.     public function setName($name)
  642.     {
  643.         $this->name = $name;
  644.         return $this;
  645.     }
  646.     /**
  647.      * @param int $creationDate
  648.      *
  649.      * @return $this
  650.      */
  651.     public function setCreationDate($creationDate)
  652.     {
  653.         $this->creationDate = (int)$creationDate;
  654.         return $this;
  655.     }
  656.     /**
  657.      * @param int $modificationDate
  658.      *
  659.      * @return $this
  660.      */
  661.     public function setModificationDate($modificationDate)
  662.     {
  663.         $this->modificationDate = (int)$modificationDate;
  664.         return $this;
  665.     }
  666.     /**
  667.      * @param int $userOwner
  668.      *
  669.      * @return $this
  670.      */
  671.     public function setUserOwner($userOwner)
  672.     {
  673.         $this->userOwner = (int)$userOwner;
  674.         return $this;
  675.     }
  676.     /**
  677.      * @param int $userModification
  678.      *
  679.      * @return $this
  680.      */
  681.     public function setUserModification($userModification)
  682.     {
  683.         $this->userModification = (int)$userModification;
  684.         return $this;
  685.     }
  686.     /**
  687.      * @param array $context
  688.      *
  689.      * @return DataObject\ClassDefinition\Data[]
  690.      */
  691.     public function getFieldDefinitions($context = [])
  692.     {
  693.         if (!\Pimcore::inAdmin() || (isset($context['suppressEnrichment']) && $context['suppressEnrichment'])) {
  694.             return $this->fieldDefinitions;
  695.         }
  696.         $enrichedFieldDefinitions = [];
  697.         foreach ($this->fieldDefinitions as $key => $fieldDefinition) {
  698.             $fieldDefinition = $this->doEnrichFieldDefinition($fieldDefinition, $context);
  699.             $enrichedFieldDefinitions[$key] = $fieldDefinition;
  700.         }
  701.         return $enrichedFieldDefinitions;
  702.     }
  703.     /**
  704.      * @internal
  705.      */
  706.     protected function doEnrichFieldDefinition($fieldDefinition, $context = [])
  707.     {
  708.         //TODO Pimcore 11: remove method_exists BC layer
  709.         if ($fieldDefinition instanceof FieldDefinitionEnrichmentInterface || method_exists($fieldDefinition, 'enrichFieldDefinition')) {
  710.             if (!$fieldDefinition instanceof FieldDefinitionEnrichmentInterface) {
  711.                 trigger_deprecation('pimcore/pimcore', '10.1',
  712.                     sprintf('Usage of method_exists is deprecated since version 10.1 and will be removed in Pimcore 11.' .
  713.                     'Implement the %s interface instead.', FieldDefinitionEnrichmentInterface::class));
  714.             }
  715.             $context['class'] = $this;
  716.             $fieldDefinition = $fieldDefinition->enrichFieldDefinition($context);
  717.         }
  718.         return $fieldDefinition;
  719.     }
  720.     /**
  721.      * @return DataObject\ClassDefinition\Layout|null
  722.      */
  723.     public function getLayoutDefinitions()
  724.     {
  725.         return $this->layoutDefinitions;
  726.     }
  727.     /**
  728.      * @param DataObject\ClassDefinition\Data[] $fieldDefinitions
  729.      *
  730.      * @return $this
  731.      */
  732.     public function setFieldDefinitions(array $fieldDefinitions)
  733.     {
  734.         $this->fieldDefinitions = $fieldDefinitions;
  735.         return $this;
  736.     }
  737.     /**
  738.      * @param string $key
  739.      * @param DataObject\ClassDefinition\Data $data
  740.      *
  741.      * @return $this
  742.      */
  743.     public function addFieldDefinition($key, $data)
  744.     {
  745.         $this->fieldDefinitions[$key] = $data;
  746.         return $this;
  747.     }
  748.     /**
  749.      * @param string $key
  750.      * @param array $context
  751.      *
  752.      * @return DataObject\ClassDefinition\Data|null
  753.      */
  754.     public function getFieldDefinition($key, $context = [])
  755.     {
  756.         if (array_key_exists($key, $this->fieldDefinitions)) {
  757.             if (!\Pimcore::inAdmin() || (isset($context['suppressEnrichment']) && $context['suppressEnrichment'])) {
  758.                 return $this->fieldDefinitions[$key];
  759.             }
  760.             $fieldDefinition = $this->doEnrichFieldDefinition($this->fieldDefinitions[$key], $context);
  761.             return $fieldDefinition;
  762.         }
  763.         return null;
  764.     }
  765.     /**
  766.      * @param DataObject\ClassDefinition\Layout|null $layoutDefinitions
  767.      *
  768.      * @return $this
  769.      */
  770.     public function setLayoutDefinitions($layoutDefinitions)
  771.     {
  772.         $oldFieldDefinitions = null;
  773.         if ($this->layoutDefinitions !== null) {
  774.             $this->setDeletedDataComponents([]);
  775.             $oldFieldDefinitions = $this->getFieldDefinitions();
  776.         }
  777.         $this->layoutDefinitions = $layoutDefinitions;
  778.         $this->fieldDefinitions = [];
  779.         $this->extractDataDefinitions($this->layoutDefinitions);
  780.         if ($oldFieldDefinitions !== null) {
  781.             $newFieldDefinitions = $this->getFieldDefinitions();
  782.             $deletedComponents = [];
  783.             foreach ($oldFieldDefinitions as $fieldDefinition) {
  784.                 if (!array_key_exists($fieldDefinition->getName(), $newFieldDefinitions)) {
  785.                     array_push($deletedComponents, $fieldDefinition);
  786.                 }
  787.             }
  788.             $this->setDeletedDataComponents($deletedComponents);
  789.         }
  790.         return $this;
  791.     }
  792.     /**
  793.      * @param DataObject\ClassDefinition\Layout|DataObject\ClassDefinition\Data $def
  794.      */
  795.     private function extractDataDefinitions($def)
  796.     {
  797.         if ($def instanceof DataObject\ClassDefinition\Layout) {
  798.             if ($def->hasChildren()) {
  799.                 foreach ($def->getChildren() as $child) {
  800.                     $this->extractDataDefinitions($child);
  801.                 }
  802.             }
  803.         }
  804.         if ($def instanceof DataObject\ClassDefinition\Data) {
  805.             $existing = $this->getFieldDefinition($def->getName());
  806.             if (!$existing && method_exists($def, 'addReferencedField') && method_exists($def, 'setReferencedFields')) {
  807.                 $def->setReferencedFields([]);
  808.             }
  809.             if ($existing && method_exists($existing, 'addReferencedField')) {
  810.                 // this is especially for localized fields which get aggregated here into one field definition
  811.                 // in the case that there are more than one localized fields in the class definition
  812.                 // see also pimcore.object.edit.addToDataFields();
  813.                 $existing->addReferencedField($def);
  814.             } else {
  815.                 $this->addFieldDefinition($def->getName(), $def);
  816.             }
  817.         }
  818.     }
  819.     /**
  820.      * @return string
  821.      */
  822.     public function getParentClass()
  823.     {
  824.         return $this->parentClass;
  825.     }
  826.     /**
  827.      * @return string
  828.      */
  829.     public function getListingParentClass()
  830.     {
  831.         return $this->listingParentClass;
  832.     }
  833.     /**
  834.      * @return string
  835.      */
  836.     public function getUseTraits()
  837.     {
  838.         return $this->useTraits;
  839.     }
  840.     /**
  841.      * @param string $useTraits
  842.      *
  843.      * @return ClassDefinition
  844.      */
  845.     public function setUseTraits($useTraits)
  846.     {
  847.         $this->useTraits = (string) $useTraits;
  848.         return $this;
  849.     }
  850.     /**
  851.      * @return string
  852.      */
  853.     public function getListingUseTraits()
  854.     {
  855.         return $this->listingUseTraits;
  856.     }
  857.     /**
  858.      * @param string $listingUseTraits
  859.      *
  860.      * @return ClassDefinition
  861.      */
  862.     public function setListingUseTraits($listingUseTraits)
  863.     {
  864.         $this->listingUseTraits = (string) $listingUseTraits;
  865.         return $this;
  866.     }
  867.     /**
  868.      * @return bool
  869.      */
  870.     public function getAllowInherit()
  871.     {
  872.         return $this->allowInherit;
  873.     }
  874.     /**
  875.      * @return bool
  876.      */
  877.     public function getAllowVariants()
  878.     {
  879.         return $this->allowVariants;
  880.     }
  881.     /**
  882.      * @param string $parentClass
  883.      *
  884.      * @return $this
  885.      */
  886.     public function setParentClass($parentClass)
  887.     {
  888.         $this->parentClass = $parentClass;
  889.         return $this;
  890.     }
  891.     /**
  892.      * @param string $listingParentClass
  893.      *
  894.      * @return $this
  895.      */
  896.     public function setListingParentClass($listingParentClass)
  897.     {
  898.         $this->listingParentClass = (string) $listingParentClass;
  899.         return $this;
  900.     }
  901.     /**
  902.      * @return bool
  903.      */
  904.     public function getEncryption(): bool
  905.     {
  906.         return $this->encryption;
  907.     }
  908.     /**
  909.      * @param bool $encryption
  910.      *
  911.      * @return $this
  912.      */
  913.     public function setEncryption(bool $encryption)
  914.     {
  915.         $this->encryption = $encryption;
  916.         return $this;
  917.     }
  918.     /**
  919.      * @internal
  920.      *
  921.      * @param array $tables
  922.      */
  923.     public function addEncryptedTables(array $tables)
  924.     {
  925.         $this->encryptedTables = array_unique(array_merge($this->encryptedTables, $tables));
  926.     }
  927.     /**
  928.      * @internal
  929.      *
  930.      * @param array $tables
  931.      */
  932.     public function removeEncryptedTables(array $tables)
  933.     {
  934.         foreach ($tables as $table) {
  935.             if (($key = array_search($table, $this->encryptedTables)) !== false) {
  936.                 unset($this->encryptedTables[$key]);
  937.             }
  938.         }
  939.     }
  940.     /**
  941.      * @internal
  942.      *
  943.      * @param string $table
  944.      *
  945.      * @return bool
  946.      */
  947.     public function isEncryptedTable(string $table): bool
  948.     {
  949.         return (array_search($table, $this->encryptedTables) === false) ? false : true;
  950.     }
  951.     /**
  952.      * @return bool
  953.      */
  954.     public function hasEncryptedTables(): bool
  955.     {
  956.         return (bool) count($this->encryptedTables);
  957.     }
  958.     /**
  959.      * @internal
  960.      *
  961.      * @param array $encryptedTables
  962.      *
  963.      * @return $this
  964.      */
  965.     public function setEncryptedTables(array $encryptedTables)
  966.     {
  967.         $this->encryptedTables = $encryptedTables;
  968.         return $this;
  969.     }
  970.     /**
  971.      * @param bool $allowInherit
  972.      *
  973.      * @return $this
  974.      */
  975.     public function setAllowInherit($allowInherit)
  976.     {
  977.         $this->allowInherit = (bool)$allowInherit;
  978.         return $this;
  979.     }
  980.     /**
  981.      * @param bool $allowVariants
  982.      *
  983.      * @return $this
  984.      */
  985.     public function setAllowVariants($allowVariants)
  986.     {
  987.         $this->allowVariants = (bool)$allowVariants;
  988.         return $this;
  989.     }
  990.     /**
  991.      * @return string|null
  992.      */
  993.     public function getIcon()
  994.     {
  995.         return $this->icon;
  996.     }
  997.     /**
  998.      * @param string|null $icon
  999.      *
  1000.      * @return $this
  1001.      */
  1002.     public function setIcon($icon)
  1003.     {
  1004.         $this->icon = $icon;
  1005.         return $this;
  1006.     }
  1007.     /**
  1008.      * @return array
  1009.      */
  1010.     public function getPropertyVisibility()
  1011.     {
  1012.         return $this->propertyVisibility;
  1013.     }
  1014.     /**
  1015.      * @param array $propertyVisibility
  1016.      *
  1017.      * @return $this
  1018.      */
  1019.     public function setPropertyVisibility($propertyVisibility)
  1020.     {
  1021.         if (is_array($propertyVisibility)) {
  1022.             $this->propertyVisibility = $propertyVisibility;
  1023.         }
  1024.         return $this;
  1025.     }
  1026.     /**
  1027.      * @param string|null $previewUrl
  1028.      *
  1029.      * @return $this
  1030.      */
  1031.     public function setPreviewUrl($previewUrl)
  1032.     {
  1033.         $this->previewUrl = $previewUrl;
  1034.         return $this;
  1035.     }
  1036.     /**
  1037.      * @return string|null
  1038.      */
  1039.     public function getPreviewUrl()
  1040.     {
  1041.         return $this->previewUrl;
  1042.     }
  1043.     /**
  1044.      * @return string|null
  1045.      */
  1046.     public function getGroup()
  1047.     {
  1048.         return $this->group;
  1049.     }
  1050.     /**
  1051.      * @param string|null $group
  1052.      *
  1053.      * @return $this
  1054.      */
  1055.     public function setGroup($group)
  1056.     {
  1057.         $this->group = $group;
  1058.         return $this;
  1059.     }
  1060.     /**
  1061.      * @param string $description
  1062.      *
  1063.      * @return $this
  1064.      */
  1065.     public function setDescription($description)
  1066.     {
  1067.         $this->description = $description;
  1068.         return $this;
  1069.     }
  1070.     /**
  1071.      * @return string
  1072.      */
  1073.     public function getDescription()
  1074.     {
  1075.         return $this->description;
  1076.     }
  1077.     /**
  1078.      * @param bool $showVariants
  1079.      *
  1080.      * @return $this
  1081.      */
  1082.     public function setShowVariants($showVariants)
  1083.     {
  1084.         $this->showVariants = (bool)$showVariants;
  1085.         return $this;
  1086.     }
  1087.     /**
  1088.      * @return bool
  1089.      */
  1090.     public function getShowVariants()
  1091.     {
  1092.         return $this->showVariants;
  1093.     }
  1094.     /**
  1095.      * @return bool
  1096.      */
  1097.     public function getShowAppLoggerTab()
  1098.     {
  1099.         return $this->showAppLoggerTab;
  1100.     }
  1101.     /**
  1102.      * @param bool $showAppLoggerTab
  1103.      *
  1104.      * @return $this
  1105.      */
  1106.     public function setShowAppLoggerTab($showAppLoggerTab)
  1107.     {
  1108.         $this->showAppLoggerTab = (bool) $showAppLoggerTab;
  1109.         return $this;
  1110.     }
  1111.     /**
  1112.      * @return bool
  1113.      */
  1114.     public function getShowFieldLookup()
  1115.     {
  1116.         return $this->showFieldLookup;
  1117.     }
  1118.     /**
  1119.      * @param bool $showFieldLookup
  1120.      *
  1121.      * @return $this
  1122.      */
  1123.     public function setShowFieldLookup($showFieldLookup)
  1124.     {
  1125.         $this->showFieldLookup = (bool) $showFieldLookup;
  1126.         return $this;
  1127.     }
  1128.     /**
  1129.      * @return string
  1130.      */
  1131.     public function getLinkGeneratorReference()
  1132.     {
  1133.         return $this->linkGeneratorReference;
  1134.     }
  1135.     /**
  1136.      * @param string $linkGeneratorReference
  1137.      *
  1138.      * @return $this
  1139.      */
  1140.     public function setLinkGeneratorReference($linkGeneratorReference)
  1141.     {
  1142.         $this->linkGeneratorReference = $linkGeneratorReference;
  1143.         return $this;
  1144.     }
  1145.     /**
  1146.      * @return DataObject\ClassDefinition\LinkGeneratorInterface|null
  1147.      */
  1148.     public function getLinkGenerator()
  1149.     {
  1150.         return DataObject\ClassDefinition\Helper\LinkGeneratorResolver::resolveGenerator($this->getLinkGeneratorReference());
  1151.     }
  1152.     /**
  1153.      * @return string|null
  1154.      */
  1155.     public function getPreviewGeneratorReference(): ?string
  1156.     {
  1157.         return $this->previewGeneratorReference;
  1158.     }
  1159.     /**
  1160.      * @param string|null $previewGeneratorReference
  1161.      */
  1162.     public function setPreviewGeneratorReference(?string $previewGeneratorReference): void
  1163.     {
  1164.         $this->previewGeneratorReference = $previewGeneratorReference;
  1165.     }
  1166.     /**
  1167.      * @return DataObject\ClassDefinition\PreviewGeneratorInterface|null
  1168.      */
  1169.     public function getPreviewGenerator()
  1170.     {
  1171.         return DataObject\ClassDefinition\Helper\PreviewGeneratorResolver::resolveGenerator($this->getPreviewGeneratorReference());
  1172.     }
  1173.     /**
  1174.      * @return bool
  1175.      */
  1176.     public function isEnableGridLocking(): bool
  1177.     {
  1178.         return $this->enableGridLocking;
  1179.     }
  1180.     /**
  1181.      * @param bool $enableGridLocking
  1182.      */
  1183.     public function setEnableGridLocking(bool $enableGridLocking): void
  1184.     {
  1185.         $this->enableGridLocking = $enableGridLocking;
  1186.     }
  1187.     /**
  1188.      * @return string|null
  1189.      */
  1190.     public function getImplementsInterfaces(): ?string
  1191.     {
  1192.         return $this->implementsInterfaces;
  1193.     }
  1194.     /**
  1195.      * @param string|null $implementsInterfaces
  1196.      *
  1197.      * @return $this
  1198.      */
  1199.     public function setImplementsInterfaces(?string $implementsInterfaces)
  1200.     {
  1201.         $this->implementsInterfaces = $implementsInterfaces;
  1202.         return $this;
  1203.     }
  1204.     /**
  1205.      * @return array
  1206.      */
  1207.     public function getCompositeIndices(): array
  1208.     {
  1209.         return $this->compositeIndices;
  1210.     }
  1211.     /**
  1212.      * @param array|null $compositeIndices
  1213.      *
  1214.      * @return $this
  1215.      */
  1216.     public function setCompositeIndices($compositeIndices)
  1217.     {
  1218.         $class = $this->getFieldDefinitions([]);
  1219.         foreach ($compositeIndices as $indexInd => $compositeIndex) {
  1220.             foreach ($compositeIndex['index_columns'] as $fieldInd => $fieldName) {
  1221.                 if (isset($class[$fieldName]) && $class[$fieldName] instanceof ManyToOneRelation) {
  1222.                     $compositeIndices[$indexInd]['index_columns'][$fieldInd] = $fieldName . '__id';
  1223.                     $compositeIndices[$indexInd]['index_columns'][] = $fieldName . '__type';
  1224.                     $compositeIndices[$indexInd]['index_columns'] = array_unique($compositeIndices[$indexInd]['index_columns']);
  1225.                 }
  1226.             }
  1227.         }
  1228.         $this->compositeIndices = $compositeIndices ?? [];
  1229.         return $this;
  1230.     }
  1231.     /**
  1232.      * @return bool
  1233.      */
  1234.     public function getGenerateTypeDeclarations()
  1235.     {
  1236.         return (bool) $this->generateTypeDeclarations;
  1237.     }
  1238.     /**
  1239.      * @param bool $generateTypeDeclarations
  1240.      *
  1241.      * @return $this
  1242.      */
  1243.     public function setGenerateTypeDeclarations($generateTypeDeclarations)
  1244.     {
  1245.         $this->generateTypeDeclarations = (bool) $generateTypeDeclarations;
  1246.         return $this;
  1247.     }
  1248.     /**
  1249.      * @return ClassDefinition\Data[]
  1250.      */
  1251.     public function getDeletedDataComponents()
  1252.     {
  1253.         return $this->deletedDataComponents;
  1254.     }
  1255.     /**
  1256.      * @param ClassDefinition\Data[] $deletedDataComponents
  1257.      *
  1258.      * @return $this
  1259.      */
  1260.     public function setDeletedDataComponents(array $deletedDataComponents): ClassDefinition
  1261.     {
  1262.         $this->deletedDataComponents = $deletedDataComponents;
  1263.         return $this;
  1264.     }
  1265.     private function deleteDeletedDataComponentsInCustomLayout(): void
  1266.     {
  1267.         if (empty($this->getDeletedDataComponents())) {
  1268.             return;
  1269.         }
  1270.         $customLayouts = new ClassDefinition\CustomLayout\Listing();
  1271.         $id = $this->getId();
  1272.         $customLayouts->setFilter(function (DataObject\ClassDefinition\CustomLayout $layout) use ($id) {
  1273.             return $layout->getClassId() === $id;
  1274.         });
  1275.         $customLayouts = $customLayouts->load();
  1276.         foreach ($customLayouts as $customLayout) {
  1277.             $layoutDefinition = $customLayout->getLayoutDefinitions();
  1278.             $this->deleteDeletedDataComponentsInLayoutDefinition($layoutDefinition);
  1279.             $customLayout->setLayoutDefinitions($layoutDefinition);
  1280.             $customLayout->save();
  1281.         }
  1282.     }
  1283.     private function deleteDeletedDataComponentsInLayoutDefinition(ClassDefinition\Layout $layoutDefinition): void
  1284.     {
  1285.         $componentsToDelete = $this->getDeletedDataComponents();
  1286.         $componentDeleted = false;
  1287.         $children = &$layoutDefinition->getChildrenByRef();
  1288.         $count = count($children);
  1289.         for ($i = 0; $i < $count; $i++) {
  1290.             $component = $children[$i];
  1291.             if (in_array($component, $componentsToDelete)) {
  1292.                 unset($children[$i]);
  1293.                 $componentDeleted = true;
  1294.             }
  1295.             if ($component instanceof ClassDefinition\Layout) {
  1296.                 $this->deleteDeletedDataComponentsInLayoutDefinition($component);
  1297.             }
  1298.         }
  1299.         if ($componentDeleted) {
  1300.             $children = array_values($children);
  1301.         }
  1302.     }
  1303.     public static function getByIdIgnoreCase(string $id): ClassDefinition|null
  1304.     {
  1305.         try {
  1306.             $class = new self();
  1307.             $name = $class->getDao()->getNameByIdIgnoreCase($id);
  1308.             $definitionFile = $class->getDefinitionFile($name);
  1309.             $class = @include $definitionFile;
  1310.             if (!$class instanceof self) {
  1311.                 throw new \Exception('Class definition with name ' . $name . ' or ID ' . $id . ' does not exist');
  1312.             }
  1313.             $class->setId($id);
  1314.         } catch (\Exception $e) {
  1315.             Logger::info($e->getMessage());
  1316.             return null;
  1317.         }
  1318.         return $class;
  1319.     }
  1320. }