vendor/pimcore/pimcore/models/DataObject/Objectbrick/Definition.php line 77

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\Objectbrick;
  15. use Pimcore\Cache;
  16. use Pimcore\Cache\Runtime;
  17. use Pimcore\DataObject\ClassBuilder\PHPObjectBrickClassDumperInterface;
  18. use Pimcore\DataObject\ClassBuilder\PHPObjectBrickContainerClassDumperInterface;
  19. use Pimcore\Logger;
  20. use Pimcore\Model;
  21. use Pimcore\Model\DataObject;
  22. use Pimcore\Model\DataObject\ClassDefinition\Data\FieldDefinitionEnrichmentInterface;
  23. use Pimcore\Tool;
  24. /**
  25.  * @method \Pimcore\Model\DataObject\Objectbrick\Definition\Dao getDao()
  26.  * @method string getTableName(DataObject\ClassDefinition $class, $query)
  27.  * @method void createUpdateTable(DataObject\ClassDefinition $class)
  28.  * @method string getLocalizedTableName(DataObject\ClassDefinition $class, $query)
  29.  */
  30. class Definition extends Model\DataObject\Fieldcollection\Definition
  31. {
  32.     use Model\DataObject\ClassDefinition\Helper\VarExport;
  33.     use DataObject\Traits\LocateFileTrait;
  34.     use DataObject\Traits\FieldcollectionObjectbrickDefinitionTrait;
  35.     /**
  36.      * @var array
  37.      */
  38.     public $classDefinitions = [];
  39.     /**
  40.      * @var array
  41.      */
  42.     private $oldClassDefinitions = [];
  43.     /**
  44.      * @param array $classDefinitions
  45.      *
  46.      * @return $this
  47.      */
  48.     public function setClassDefinitions($classDefinitions)
  49.     {
  50.         $this->classDefinitions $classDefinitions;
  51.         return $this;
  52.     }
  53.     /**
  54.      * @return array
  55.      */
  56.     public function getClassDefinitions()
  57.     {
  58.         return $this->classDefinitions;
  59.     }
  60.     /**
  61.      * @static
  62.      *
  63.      * @param string $key
  64.      *
  65.      * @return self|null
  66.      */
  67.     public static function getByKey($key)
  68.     {
  69.         $brick null;
  70.         $cacheKey 'objectbrick_' $key;
  71.         try {
  72.             $brick \Pimcore\Cache\Runtime::get($cacheKey);
  73.             if (!$brick) {
  74.                 throw new \Exception('ObjectBrick in Registry is not valid');
  75.             }
  76.         } catch (\Exception $e) {
  77.             $def = new Definition();
  78.             $def->setKey($key);
  79.             $fieldFile $def->getDefinitionFile();
  80.             if (is_file($fieldFile)) {
  81.                 $brick = include $fieldFile;
  82.                 \Pimcore\Cache\Runtime::set($cacheKey$brick);
  83.             }
  84.         }
  85.         if ($brick) {
  86.             return $brick;
  87.         }
  88.         return null;
  89.     }
  90.     /**
  91.      * @throws \Exception
  92.      */
  93.     private function checkTablenames()
  94.     {
  95.         $tables = [];
  96.         $key $this->getKey();
  97.         if (!$this->getFieldDefinitions()) {
  98.             return;
  99.         }
  100.         $isLocalized $this->getFieldDefinition('localizedfields') ? true false;
  101.         $classDefinitions $this->getClassDefinitions();
  102.         $validLanguages Tool::getValidLanguages();
  103.         foreach ($classDefinitions as $classDef) {
  104.             $classname $classDef['classname'];
  105.             $class DataObject\ClassDefinition::getByName($classname);
  106.             if (!$class) {
  107.                 Logger::error('class ' $classname " doesn't exist anymore");
  108.                 continue;
  109.             }
  110.             $tables[] = 'object_brick_query_' $key .  '_' $class->getId();
  111.             $tables[] = 'object_brick_store_' $key .  '_' $class->getId();
  112.             if ($isLocalized) {
  113.                 foreach ($validLanguages as $validLanguage) {
  114.                     $tables[] = 'object_brick_localized_query_' $key '_' $class->getId() . '_' $validLanguage;
  115.                     $tables[] = 'object_brick_localized_' $key '_' $class->getId();
  116.                 }
  117.             }
  118.         }
  119.         array_multisort(array_map('strlen'$tables), $tables);
  120.         $longestTablename end($tables);
  121.         $length strlen($longestTablename);
  122.         if ($length 64) {
  123.             throw new \Exception('table name ' $longestTablename ' would be too long. Max length is 64. Current length would be ' .  $length '.');
  124.         }
  125.     }
  126.     /**
  127.      * @param bool $saveDefinitionFile
  128.      *
  129.      * @throws \Exception
  130.      */
  131.     public function save($saveDefinitionFile true)
  132.     {
  133.         if (!$this->getKey()) {
  134.             throw new \Exception('A object-brick needs a key to be saved!');
  135.         }
  136.         if (!preg_match('/[a-zA-Z]+[a-zA-Z0-9]+/'$this->getKey())) {
  137.             throw new \Exception(sprintf('Invalid key for object-brick: %s'$this->getKey()));
  138.         }
  139.         if ($this->getParentClass() && !preg_match('/^[a-zA-Z_\x7f-\xff\\\][a-zA-Z0-9_\x7f-\xff\\\]*$/'$this->getParentClass())) {
  140.             throw new \Exception(sprintf('Invalid parentClass value for class definition: %s',
  141.                 $this->getParentClass()));
  142.         }
  143.         $this->checkTablenames();
  144.         $this->checkContainerRestrictions();
  145.         $fieldDefinitions $this->getFieldDefinitions();
  146.         foreach ($fieldDefinitions as $fd) {
  147.             if ($fd->isForbiddenName()) {
  148.                 throw new \Exception(sprintf('Forbidden name used for field definition: %s'$fd->getName()));
  149.             }
  150.             if ($fd instanceof DataObject\ClassDefinition\Data\DataContainerAwareInterface) {
  151.                 $fd->preSave($this);
  152.             }
  153.         }
  154.         $newClassDefinitions = [];
  155.         $classDefinitionsToDelete = [];
  156.         foreach ($this->classDefinitions as $cl) {
  157.             if (!isset($cl['deleted']) || !$cl['deleted']) {
  158.                 $newClassDefinitions[] = $cl;
  159.             } else {
  160.                 $classDefinitionsToDelete[] = $cl;
  161.             }
  162.         }
  163.         $this->classDefinitions $newClassDefinitions;
  164.         $this->generateClassFiles($saveDefinitionFile);
  165.         $cacheKey 'objectbrick_' $this->getKey();
  166.         // for localized fields getting a fresh copy
  167.         Runtime::set($cacheKey$this);
  168.         $this->createContainerClasses();
  169.         $this->updateDatabase();
  170.         foreach ($fieldDefinitions as $fd) {
  171.             if ($fd instanceof DataObject\ClassDefinition\Data\DataContainerAwareInterface) {
  172.                 $fd->postSave($this);
  173.             }
  174.         }
  175.     }
  176.     private function enforceBlockRules($fds$found = [])
  177.     {
  178.         if (($found['block'] ?? false) && ($found['localizedfield'] ?? false)) {
  179.             throw new \Exception('A localizedfield cannot be nested inside a block and vice versa');
  180.         }
  181.         /** @var DataObject\ClassDefinition\Data $fd */
  182.         foreach ($fds as $fd) {
  183.             $childParams $found;
  184.             if ($fd instanceof DataObject\ClassDefinition\Data\Block) {
  185.                 $childParams['block'] = true;
  186.             } elseif ($fd instanceof DataObject\ClassDefinition\Data\Localizedfields) {
  187.                 $childParams['localizedfield'] = true;
  188.             }
  189.             if (method_exists($fd'getFieldDefinitions')) {
  190.                 $this->enforceBlockRules($fd->getFieldDefinitions(), $childParams);
  191.             }
  192.         }
  193.     }
  194.     private function checkContainerRestrictions()
  195.     {
  196.         $fds $this->getFieldDefinitions();
  197.         $this->enforceBlockRules($fds);
  198.     }
  199.     /**
  200.      * {@inheritdoc}
  201.      */
  202.     protected function generateClassFiles($generateDefinitionFile true)
  203.     {
  204.         if ($generateDefinitionFile && !$this->isWritable()) {
  205.             throw new DataObject\Exception\DefinitionWriteException();
  206.         }
  207.         $definitionFile $this->getDefinitionFile();
  208.         if ($generateDefinitionFile) {
  209.             $this->cleanupOldFiles($definitionFile);
  210.             /** @var self $clone */
  211.             $clone DataObject\Service::cloneDefinition($this);
  212.             $clone->setDao(null);
  213.             unset($clone->oldClassDefinitions);
  214.             unset($clone->fieldDefinitions);
  215.             DataObject\ClassDefinition::cleanupForExport($clone->layoutDefinitions);
  216.             $exportedClass var_export($clonetrue);
  217.             $data '<?php';
  218.             $data .= "\n\n";
  219.             $data .= "\nreturn " $exportedClass ";\n";
  220.             \Pimcore\File::put($definitionFile$data);
  221.         }
  222.         \Pimcore::getContainer()->get(PHPObjectBrickClassDumperInterface::class)->dumpPHPClasses($this);
  223.     }
  224.     /**
  225.      * @param array $definitions
  226.      *
  227.      * @return array
  228.      */
  229.     private function buildClassList($definitions)
  230.     {
  231.         $result = [];
  232.         foreach ($definitions as $definition) {
  233.             $result[] = $definition['classname'] . '-' $definition['fieldname'];
  234.         }
  235.         return $result;
  236.     }
  237.     /**
  238.      * Returns a list of classes which need to be "rebuild" because they are affected of changes.
  239.      *
  240.      * @param self $oldObject
  241.      *
  242.      * @return array
  243.      */
  244.     private function getClassesToCleanup($oldObject)
  245.     {
  246.         $oldDefinitions $oldObject->getClassDefinitions() ? $oldObject->getClassDefinitions() : [];
  247.         $newDefinitions $this->getClassDefinitions() ? $this->getClassDefinitions() : [];
  248.         $old $this->buildClassList($oldDefinitions);
  249.         $new $this->buildClassList($newDefinitions);
  250.         $diff1 array_diff($old$new);
  251.         $diff2 array_diff($new$old);
  252.         $diff array_merge($diff1$diff2);
  253.         $result = [];
  254.         foreach ($diff as $item) {
  255.             $parts explode('-'$item);
  256.             $result[] = ['classname' => $parts[0], 'fieldname' => $parts[1]];
  257.         }
  258.         return $result;
  259.     }
  260.     /**
  261.      * @param string $serializedFilename
  262.      */
  263.     private function cleanupOldFiles($serializedFilename)
  264.     {
  265.         $oldObject null;
  266.         $this->oldClassDefinitions = [];
  267.         if (file_exists($serializedFilename)) {
  268.             $oldObject = include $serializedFilename;
  269.         }
  270.         if ($oldObject && !empty($oldObject->classDefinitions)) {
  271.             $classlist $this->getClassesToCleanup($oldObject);
  272.             foreach ($classlist as $cl) {
  273.                 $this->oldClassDefinitions[$cl['classname']] = $cl['classname'];
  274.                 $class DataObject\ClassDefinition::getByName($cl['classname']);
  275.                 if ($class) {
  276.                     $path $this->getContainerClassFolder($class->getName());
  277.                     @unlink($path '/' ucfirst($cl['fieldname'] . '.php'));
  278.                     foreach ($class->getFieldDefinitions() as $fieldDef) {
  279.                         if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  280.                             $allowedTypes $fieldDef->getAllowedTypes();
  281.                             $idx array_search($this->getKey(), $allowedTypes);
  282.                             if ($idx !== false) {
  283.                                 array_splice($allowedTypes$idx1);
  284.                             }
  285.                             $fieldDef->setAllowedTypes($allowedTypes);
  286.                         }
  287.                     }
  288.                     $class->save();
  289.                 }
  290.             }
  291.         }
  292.     }
  293.     /**
  294.      * Update Database according to class-definition
  295.      */
  296.     private function updateDatabase()
  297.     {
  298.         $processedClasses = [];
  299.         if (!empty($this->classDefinitions)) {
  300.             foreach ($this->classDefinitions as $cl) {
  301.                 unset($this->oldClassDefinitions[$cl['classname']]);
  302.                 if (empty($processedClasses[$cl['classname']])) {
  303.                     $class DataObject\ClassDefinition::getByName($cl['classname']);
  304.                     $this->getDao()->createUpdateTable($class);
  305.                     $processedClasses[$cl['classname']] = true;
  306.                 }
  307.             }
  308.         }
  309.         if (!empty($this->oldClassDefinitions)) {
  310.             foreach ($this->oldClassDefinitions as $cl) {
  311.                 $class DataObject\ClassDefinition::getByName($cl);
  312.                 if ($class) {
  313.                     $this->getDao()->delete($class);
  314.                     foreach ($class->getFieldDefinitions() as $fieldDef) {
  315.                         if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  316.                             $allowedTypes $fieldDef->getAllowedTypes();
  317.                             $idx array_search($this->getKey(), $allowedTypes);
  318.                             if ($idx !== false) {
  319.                                 array_splice($allowedTypes$idx1);
  320.                             }
  321.                             $fieldDef->setAllowedTypes($allowedTypes);
  322.                         }
  323.                     }
  324.                     $class->save();
  325.                 }
  326.             }
  327.         }
  328.     }
  329.     /**
  330.      * @param DataObject\ClassDefinition $class
  331.      *
  332.      * @internal
  333.      *
  334.      * @return array
  335.      */
  336.     public function getAllowedTypesWithFieldname(DataObject\ClassDefinition $class)
  337.     {
  338.         $result = [];
  339.         $fieldDefinitions $class->getFieldDefinitions();
  340.         foreach ($fieldDefinitions as $fd) {
  341.             if (!$fd instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  342.                 continue;
  343.             }
  344.             $allowedTypes $fd->getAllowedTypes() ? $fd->getAllowedTypes() : [];
  345.             foreach ($allowedTypes as $allowedType) {
  346.                 $result[] = $fd->getName() . '-' $allowedType;
  347.             }
  348.         }
  349.         return $result;
  350.     }
  351.     /**
  352.      * @throws \Exception
  353.      */
  354.     private function createContainerClasses()
  355.     {
  356.         $containerDefinition = [];
  357.         if (!empty($this->classDefinitions)) {
  358.             foreach ($this->classDefinitions as $cl) {
  359.                 $class DataObject\ClassDefinition::getByName($cl['classname']);
  360.                 if (!$class) {
  361.                     throw new \Exception('Could not load class ' $cl['classname']);
  362.                 }
  363.                 $fd $class->getFieldDefinition($cl['fieldname']);
  364.                 if (!$fd instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  365.                     throw new \Exception('Could not resolve field definition for ' $cl['fieldname']);
  366.                 }
  367.                 $old $this->getAllowedTypesWithFieldname($class);
  368.                 $allowedTypes $fd->getAllowedTypes() ?: [];
  369.                 if (!in_array($this->key$allowedTypes)) {
  370.                     $allowedTypes[] = $this->key;
  371.                 }
  372.                 $fd->setAllowedTypes($allowedTypes);
  373.                 $new $this->getAllowedTypesWithFieldname($class);
  374.                 if (array_diff($new$old) || array_diff($old$new)) {
  375.                     $class->save();
  376.                 } else {
  377.                     // still, the brick fields definitions could have changed.
  378.                     Cache::clearTag('class_'.$class->getId());
  379.                     Logger::debug('Objectbrick ' $this->getKey() . ', no change for class ' $class->getName());
  380.                 }
  381.             }
  382.         }
  383.         \Pimcore::getContainer()->get(PHPObjectBrickContainerClassDumperInterface::class)->dumpContainerClasses($this);
  384.     }
  385.     /**
  386.      * @param string $classname
  387.      * @param string $fieldname
  388.      *
  389.      * @internal
  390.      *
  391.      * @return string
  392.      */
  393.     public function getContainerClassName($classname$fieldname)
  394.     {
  395.         return ucfirst($fieldname);
  396.     }
  397.     /**
  398.      * @param string $classname
  399.      * @param string $fieldname
  400.      *
  401.      * @internal
  402.      *
  403.      * @return string
  404.      */
  405.     public function getContainerNamespace($classname$fieldname)
  406.     {
  407.         return 'Pimcore\\Model\\DataObject\\' ucfirst($classname);
  408.     }
  409.     /**
  410.      * @param string $classname
  411.      *
  412.      * @internal
  413.      *
  414.      * @return string
  415.      */
  416.     public function getContainerClassFolder($classname)
  417.     {
  418.         return PIMCORE_CLASS_DIRECTORY '/DataObject/' ucfirst($classname);
  419.     }
  420.     /**
  421.      * Delete Brick Definition
  422.      */
  423.     public function delete()
  424.     {
  425.         @unlink($this->getDefinitionFile());
  426.         @unlink($this->getPhpClassFile());
  427.         $processedClasses = [];
  428.         if (!empty($this->classDefinitions)) {
  429.             foreach ($this->classDefinitions as $cl) {
  430.                 unset($this->oldClassDefinitions[$cl['classname']]);
  431.                 if (!isset($processedClasses[$cl['classname']])) {
  432.                     $processedClasses[$cl['classname']] = true;
  433.                     $class DataObject\ClassDefinition::getByName($cl['classname']);
  434.                     if ($class instanceof DataObject\ClassDefinition) {
  435.                         $this->getDao()->delete($class);
  436.                         foreach ($class->getFieldDefinitions() as $fieldDef) {
  437.                             if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  438.                                 $allowedTypes $fieldDef->getAllowedTypes();
  439.                                 $idx array_search($this->getKey(), $allowedTypes);
  440.                                 if ($idx !== false) {
  441.                                     array_splice($allowedTypes$idx1);
  442.                                 }
  443.                                 $fieldDef->setAllowedTypes($allowedTypes);
  444.                             }
  445.                         }
  446.                         $class->save();
  447.                     }
  448.                 }
  449.             }
  450.         }
  451.         // update classes
  452.         $classList = new DataObject\ClassDefinition\Listing();
  453.         $classes $classList->load();
  454.         if (is_array($classes)) {
  455.             foreach ($classes as $class) {
  456.                 foreach ($class->getFieldDefinitions() as $fieldDef) {
  457.                     if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  458.                         if (in_array($this->getKey(), $fieldDef->getAllowedTypes())) {
  459.                             break;
  460.                         }
  461.                     }
  462.                 }
  463.             }
  464.         }
  465.     }
  466.     /**
  467.      * {@inheritdoc}
  468.      */
  469.     protected function doEnrichFieldDefinition($fieldDefinition$context = [])
  470.     {
  471.         //TODO Pimcore 11: remove method_exists BC layer
  472.         if ($fieldDefinition instanceof FieldDefinitionEnrichmentInterface || method_exists($fieldDefinition'enrichFieldDefinition')) {
  473.             if (!$fieldDefinition instanceof FieldDefinitionEnrichmentInterface) {
  474.                 trigger_deprecation('pimcore/pimcore''10.1',
  475.                     sprintf('Usage of method_exists is deprecated since version 10.1 and will be removed in Pimcore 11.' .
  476.                     'Implement the %s interface instead.'FieldDefinitionEnrichmentInterface::class));
  477.             }
  478.             $context['containerType'] = 'objectbrick';
  479.             $context['containerKey'] = $this->getKey();
  480.             $fieldDefinition $fieldDefinition->enrichFieldDefinition($context);
  481.         }
  482.         return $fieldDefinition;
  483.     }
  484.     /**
  485.      * @internal
  486.      *
  487.      * @return bool
  488.      */
  489.     public function isWritable(): bool
  490.     {
  491.         if ($_SERVER['PIMCORE_CLASS_DEFINITION_WRITABLE'] ?? false) {
  492.             return true;
  493.         }
  494.         return !str_starts_with($this->getDefinitionFile(), PIMCORE_CUSTOM_CONFIGURATION_DIRECTORY);
  495.     }
  496.     /**
  497.      * @internal
  498.      *
  499.      * @param string|null $key
  500.      *
  501.      * @return string
  502.      */
  503.     public function getDefinitionFile($key null)
  504.     {
  505.         return $this->locateDefinitionFile($key ?? $this->getKey(), 'objectbricks/%s.php');
  506.     }
  507.     /**
  508.      * @internal
  509.      * @internal
  510.      *
  511.      * @return string
  512.      */
  513.     public function getPhpClassFile()
  514.     {
  515.         return $this->locateFile(ucfirst($this->getKey()), 'DataObject/Objectbrick/Data/%s.php');
  516.     }
  517. }