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