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 .= $this->getInfoDocBlock();
  220.             $data .= "\n\n";
  221.             $data .= 'return ' $exportedClass ";\n";
  222.             \Pimcore\File::put($definitionFile$data);
  223.         }
  224.         \Pimcore::getContainer()->get(PHPObjectBrickClassDumperInterface::class)->dumpPHPClasses($this);
  225.     }
  226.     /**
  227.      * @param array $definitions
  228.      *
  229.      * @return array
  230.      */
  231.     private function buildClassList($definitions)
  232.     {
  233.         $result = [];
  234.         foreach ($definitions as $definition) {
  235.             $result[] = $definition['classname'] . '-' $definition['fieldname'];
  236.         }
  237.         return $result;
  238.     }
  239.     /**
  240.      * Returns a list of classes which need to be "rebuild" because they are affected of changes.
  241.      *
  242.      * @param self $oldObject
  243.      *
  244.      * @return array
  245.      */
  246.     private function getClassesToCleanup($oldObject)
  247.     {
  248.         $oldDefinitions $oldObject->getClassDefinitions() ? $oldObject->getClassDefinitions() : [];
  249.         $newDefinitions $this->getClassDefinitions() ? $this->getClassDefinitions() : [];
  250.         $old $this->buildClassList($oldDefinitions);
  251.         $new $this->buildClassList($newDefinitions);
  252.         $diff1 array_diff($old$new);
  253.         $diff2 array_diff($new$old);
  254.         $diff array_merge($diff1$diff2);
  255.         $result = [];
  256.         foreach ($diff as $item) {
  257.             $parts explode('-'$item);
  258.             $result[] = ['classname' => $parts[0], 'fieldname' => $parts[1]];
  259.         }
  260.         return $result;
  261.     }
  262.     /**
  263.      * @param string $serializedFilename
  264.      */
  265.     private function cleanupOldFiles($serializedFilename)
  266.     {
  267.         $oldObject null;
  268.         $this->oldClassDefinitions = [];
  269.         if (file_exists($serializedFilename)) {
  270.             $oldObject = include $serializedFilename;
  271.         }
  272.         if ($oldObject && !empty($oldObject->classDefinitions)) {
  273.             $classlist $this->getClassesToCleanup($oldObject);
  274.             foreach ($classlist as $cl) {
  275.                 $this->oldClassDefinitions[$cl['classname']] = $cl['classname'];
  276.                 $class DataObject\ClassDefinition::getByName($cl['classname']);
  277.                 if ($class) {
  278.                     $path $this->getContainerClassFolder($class->getName());
  279.                     @unlink($path '/' ucfirst($cl['fieldname'] . '.php'));
  280.                     foreach ($class->getFieldDefinitions() as $fieldDef) {
  281.                         if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  282.                             $allowedTypes $fieldDef->getAllowedTypes();
  283.                             $idx array_search($this->getKey(), $allowedTypes);
  284.                             if ($idx !== false) {
  285.                                 array_splice($allowedTypes$idx1);
  286.                             }
  287.                             $fieldDef->setAllowedTypes($allowedTypes);
  288.                         }
  289.                     }
  290.                     $class->save();
  291.                 }
  292.             }
  293.         }
  294.     }
  295.     /**
  296.      * Update Database according to class-definition
  297.      */
  298.     private function updateDatabase()
  299.     {
  300.         $processedClasses = [];
  301.         if (!empty($this->classDefinitions)) {
  302.             foreach ($this->classDefinitions as $cl) {
  303.                 unset($this->oldClassDefinitions[$cl['classname']]);
  304.                 if (empty($processedClasses[$cl['classname']])) {
  305.                     $class DataObject\ClassDefinition::getByName($cl['classname']);
  306.                     $this->getDao()->createUpdateTable($class);
  307.                     $processedClasses[$cl['classname']] = true;
  308.                 }
  309.             }
  310.         }
  311.         if (!empty($this->oldClassDefinitions)) {
  312.             foreach ($this->oldClassDefinitions as $cl) {
  313.                 $class DataObject\ClassDefinition::getByName($cl);
  314.                 if ($class) {
  315.                     $this->getDao()->delete($class);
  316.                     foreach ($class->getFieldDefinitions() as $fieldDef) {
  317.                         if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  318.                             $allowedTypes $fieldDef->getAllowedTypes();
  319.                             $idx array_search($this->getKey(), $allowedTypes);
  320.                             if ($idx !== false) {
  321.                                 array_splice($allowedTypes$idx1);
  322.                             }
  323.                             $fieldDef->setAllowedTypes($allowedTypes);
  324.                         }
  325.                     }
  326.                     $class->save();
  327.                 }
  328.             }
  329.         }
  330.     }
  331.     /**
  332.      * @param DataObject\ClassDefinition $class
  333.      *
  334.      * @internal
  335.      *
  336.      * @return array
  337.      */
  338.     public function getAllowedTypesWithFieldname(DataObject\ClassDefinition $class)
  339.     {
  340.         $result = [];
  341.         $fieldDefinitions $class->getFieldDefinitions();
  342.         foreach ($fieldDefinitions as $fd) {
  343.             if (!$fd instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  344.                 continue;
  345.             }
  346.             $allowedTypes $fd->getAllowedTypes() ? $fd->getAllowedTypes() : [];
  347.             foreach ($allowedTypes as $allowedType) {
  348.                 $result[] = $fd->getName() . '-' $allowedType;
  349.             }
  350.         }
  351.         return $result;
  352.     }
  353.     /**
  354.      * @throws \Exception
  355.      */
  356.     private function createContainerClasses()
  357.     {
  358.         $containerDefinition = [];
  359.         if (!empty($this->classDefinitions)) {
  360.             foreach ($this->classDefinitions as $cl) {
  361.                 $class DataObject\ClassDefinition::getByName($cl['classname']);
  362.                 if (!$class) {
  363.                     throw new \Exception('Could not load class ' $cl['classname']);
  364.                 }
  365.                 $fd $class->getFieldDefinition($cl['fieldname']);
  366.                 if (!$fd instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  367.                     throw new \Exception('Could not resolve field definition for ' $cl['fieldname']);
  368.                 }
  369.                 $old $this->getAllowedTypesWithFieldname($class);
  370.                 $allowedTypes $fd->getAllowedTypes() ?: [];
  371.                 if (!in_array($this->key$allowedTypes)) {
  372.                     $allowedTypes[] = $this->key;
  373.                 }
  374.                 $fd->setAllowedTypes($allowedTypes);
  375.                 $new $this->getAllowedTypesWithFieldname($class);
  376.                 if (array_diff($new$old) || array_diff($old$new)) {
  377.                     $class->save();
  378.                 } else {
  379.                     // still, the brick fields definitions could have changed.
  380.                     Cache::clearTag('class_'.$class->getId());
  381.                     Logger::debug('Objectbrick ' $this->getKey() . ', no change for class ' $class->getName());
  382.                 }
  383.             }
  384.         }
  385.         \Pimcore::getContainer()->get(PHPObjectBrickContainerClassDumperInterface::class)->dumpContainerClasses($this);
  386.     }
  387.     /**
  388.      * @param string $classname
  389.      * @param string $fieldname
  390.      *
  391.      * @internal
  392.      *
  393.      * @return string
  394.      */
  395.     public function getContainerClassName($classname$fieldname)
  396.     {
  397.         return ucfirst($fieldname);
  398.     }
  399.     /**
  400.      * @param string $classname
  401.      * @param string $fieldname
  402.      *
  403.      * @internal
  404.      *
  405.      * @return string
  406.      */
  407.     public function getContainerNamespace($classname$fieldname)
  408.     {
  409.         return 'Pimcore\\Model\\DataObject\\' ucfirst($classname);
  410.     }
  411.     /**
  412.      * @param string $classname
  413.      *
  414.      * @internal
  415.      *
  416.      * @return string
  417.      */
  418.     public function getContainerClassFolder($classname)
  419.     {
  420.         return PIMCORE_CLASS_DIRECTORY '/DataObject/' ucfirst($classname);
  421.     }
  422.     /**
  423.      * Delete Brick Definition
  424.      */
  425.     public function delete()
  426.     {
  427.         @unlink($this->getDefinitionFile());
  428.         @unlink($this->getPhpClassFile());
  429.         $processedClasses = [];
  430.         if (!empty($this->classDefinitions)) {
  431.             foreach ($this->classDefinitions as $cl) {
  432.                 unset($this->oldClassDefinitions[$cl['classname']]);
  433.                 if (!isset($processedClasses[$cl['classname']])) {
  434.                     $processedClasses[$cl['classname']] = true;
  435.                     $class DataObject\ClassDefinition::getByName($cl['classname']);
  436.                     if ($class instanceof DataObject\ClassDefinition) {
  437.                         $this->getDao()->delete($class);
  438.                         foreach ($class->getFieldDefinitions() as $fieldDef) {
  439.                             if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  440.                                 $allowedTypes $fieldDef->getAllowedTypes();
  441.                                 $idx array_search($this->getKey(), $allowedTypes);
  442.                                 if ($idx !== false) {
  443.                                     array_splice($allowedTypes$idx1);
  444.                                 }
  445.                                 $fieldDef->setAllowedTypes($allowedTypes);
  446.                             }
  447.                         }
  448.                         $class->save();
  449.                     }
  450.                 }
  451.             }
  452.         }
  453.         // update classes
  454.         $classList = new DataObject\ClassDefinition\Listing();
  455.         $classes $classList->load();
  456.         if (is_array($classes)) {
  457.             foreach ($classes as $class) {
  458.                 foreach ($class->getFieldDefinitions() as $fieldDef) {
  459.                     if ($fieldDef instanceof DataObject\ClassDefinition\Data\Objectbricks) {
  460.                         if (in_array($this->getKey(), $fieldDef->getAllowedTypes())) {
  461.                             break;
  462.                         }
  463.                     }
  464.                 }
  465.             }
  466.         }
  467.     }
  468.     /**
  469.      * {@inheritdoc}
  470.      */
  471.     protected function doEnrichFieldDefinition($fieldDefinition$context = [])
  472.     {
  473.         //TODO Pimcore 11: remove method_exists BC layer
  474.         if ($fieldDefinition instanceof FieldDefinitionEnrichmentInterface || method_exists($fieldDefinition'enrichFieldDefinition')) {
  475.             if (!$fieldDefinition instanceof FieldDefinitionEnrichmentInterface) {
  476.                 trigger_deprecation('pimcore/pimcore''10.1',
  477.                     sprintf('Usage of method_exists is deprecated since version 10.1 and will be removed in Pimcore 11.' .
  478.                     'Implement the %s interface instead.'FieldDefinitionEnrichmentInterface::class));
  479.             }
  480.             $context['containerType'] = 'objectbrick';
  481.             $context['containerKey'] = $this->getKey();
  482.             $fieldDefinition $fieldDefinition->enrichFieldDefinition($context);
  483.         }
  484.         return $fieldDefinition;
  485.     }
  486.     /**
  487.      * @internal
  488.      *
  489.      * @return bool
  490.      */
  491.     public function isWritable(): bool
  492.     {
  493.         if ($_SERVER['PIMCORE_CLASS_DEFINITION_WRITABLE'] ?? false) {
  494.             return true;
  495.         }
  496.         return !str_starts_with($this->getDefinitionFile(), PIMCORE_CUSTOM_CONFIGURATION_DIRECTORY);
  497.     }
  498.     /**
  499.      * @internal
  500.      *
  501.      * @param string|null $key
  502.      *
  503.      * @return string
  504.      */
  505.     public function getDefinitionFile($key null)
  506.     {
  507.         return $this->locateDefinitionFile($key ?? $this->getKey(), 'objectbricks/%s.php');
  508.     }
  509.     /**
  510.      * @internal
  511.      * @internal
  512.      *
  513.      * @return string
  514.      */
  515.     public function getPhpClassFile()
  516.     {
  517.         return $this->locateFile(ucfirst($this->getKey()), 'DataObject/Objectbrick/Data/%s.php');
  518.     }
  519. }