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

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