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

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