From 5680cea0cf217c62f2cad04e1f3b7efc3b4493f8 Mon Sep 17 00:00:00 2001 From: lukmzig <30526586+lukmzig@users.noreply.github.com> Date: Wed, 27 Nov 2024 06:59:04 +0100 Subject: [PATCH] [Data Object Editor] Add normalizer for all field types (#578) * feat: add normalizer for data objects * Apply php-cs-fixer changes --------- Co-authored-by: lukmzig --- config/data_objects.yaml | 3 + config/updater.yaml | 3 - src/Asset/Service/AssetService.php | 2 +- src/DataObject/Data/Adapter/BlockAdapter.php | 39 +++++- .../Adapter/ClassificationStoreAdapter.php | 128 ++++++++++++++++-- .../Data/Adapter/FieldCollectionsAdapter.php | 41 +++++- .../Data/Adapter/LocalizedFieldsAdapter.php | 41 +++++- .../Adapter/ManyToManyRelationAdapter.php | 51 ++++++- .../Data/Adapter/ObjectBricksAdapter.php | 41 +++++- src/DataObject/Data/ClassData.php | 51 +++++++ .../Data/DataNormalizerInterface.php | 27 ++++ src/DataObject/Data/RelationData.php | 57 ++++++++ src/DataObject/Schema/DataObject.php | 21 ++- src/DataObject/Service/DataObjectService.php | 34 ++++- .../Service/DataObjectServiceInterface.php | 2 +- src/DataObject/Service/DataService.php | 95 +++++++++++++ .../Service/DataServiceInterface.php | 43 ++++++ src/DataObject/Util/Trait/ClassDataTrait.php | 77 +++++++++++ src/Document/Service/DocumentService.php | 2 +- .../Api/InvalidDataTypeException.php | 39 ++++++ .../Resolver/Metadata/DataObjectResolver.php | 3 +- .../Service/WorkflowDetailsService.php | 9 +- .../WorkflowDetailsServiceInterface.php | 5 +- 23 files changed, 783 insertions(+), 31 deletions(-) create mode 100644 src/DataObject/Data/ClassData.php create mode 100644 src/DataObject/Data/DataNormalizerInterface.php create mode 100644 src/DataObject/Data/RelationData.php create mode 100644 src/DataObject/Service/DataService.php create mode 100644 src/DataObject/Service/DataServiceInterface.php create mode 100644 src/DataObject/Util/Trait/ClassDataTrait.php create mode 100644 src/Exception/Api/InvalidDataTypeException.php diff --git a/config/data_objects.yaml b/config/data_objects.yaml index c9cf10b1..c7cac5dd 100644 --- a/config/data_objects.yaml +++ b/config/data_objects.yaml @@ -32,6 +32,9 @@ services: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\ReplaceServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\ReplaceService + Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface: + class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataService + Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterService diff --git a/config/updater.yaml b/config/updater.yaml index 069dbdc5..24a0b044 100644 --- a/config/updater.yaml +++ b/config/updater.yaml @@ -7,9 +7,6 @@ services: Pimcore\Bundle\StudioBackendBundle\Updater\Service\AdapterLoaderInterface: class: Pimcore\Bundle\StudioBackendBundle\Updater\Service\Loader\TaggedIteratorAdapter - Pimcore\Bundle\StudioBackendBundle\Updater\Service\EditableAdapterLoaderInterface: - class: Pimcore\Bundle\StudioBackendBundle\Updater\Service\Loader\TaggedIteratorEditableAdapter - Pimcore\Bundle\StudioBackendBundle\Updater\Service\UpdateServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\Updater\Service\UpdateService diff --git a/src/Asset/Service/AssetService.php b/src/Asset/Service/AssetService.php index 784f32e8..5d6c2e8d 100644 --- a/src/Asset/Service/AssetService.php +++ b/src/Asset/Service/AssetService.php @@ -110,7 +110,7 @@ public function getAsset( $user = $this->securityService->getCurrentUser(); $asset = $this->assetSearchService->getAssetById($id, $user); if ($getWorkflowAvailable) { - $asset->setHasWorkflowAvailable($this->workflowDetailsService->hasElementWorkflows( + $asset->setHasWorkflowAvailable($this->workflowDetailsService->hasElementWorkflowsById( $id, ElementTypes::TYPE_ASSET, $user diff --git a/src/DataObject/Data/Adapter/BlockAdapter.php b/src/DataObject/Data/Adapter/BlockAdapter.php index 1399972c..584eb9a1 100644 --- a/src/DataObject/Data/Adapter/BlockAdapter.php +++ b/src/DataObject/Data/Adapter/BlockAdapter.php @@ -17,24 +17,30 @@ namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Adapter; use Exception; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataNormalizerInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\FieldContextData; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterLoaderInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidDataTypeException; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\ClassDefinition\Data\Block; use Pimcore\Model\DataObject\Concrete; use Pimcore\Model\DataObject\Data\BlockElement; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; +use function get_class; +use function is_array; /** * @internal */ #[AutoconfigureTag(DataAdapterLoaderInterface::ADAPTER_TAG)] -final readonly class BlockAdapter implements SetterDataInterface +final readonly class BlockAdapter implements SetterDataInterface, DataNormalizerInterface { public function __construct( private DataAdapterServiceInterface $dataAdapterService, + private DataServiceInterface $dataService ) { } @@ -57,6 +63,37 @@ public function getDataForSetter( return $this->processBlockData($element, $fieldDefinition, $blockData, $contextData); } + public function normalize( + mixed $value, + Data $fieldDefinition + ): ?array { + if (!is_array($value)) { + return null; + } + + $resultItems = []; + if (!$fieldDefinition instanceof Block) { + throw new InvalidDataTypeException(Block::class, get_class($fieldDefinition)); + } + $fieldDefinitions = $fieldDefinition->getFieldDefinitions(); + foreach ($value as $block) { + $resultItem = []; + + /** @var BlockElement $fieldValue */ + foreach ($block as $key => $fieldValue) { + $blockDefinition = $fieldDefinitions[$key]; + $resultItems[$key] = $this->dataService->getNormalizedValue( + $fieldValue->getData(), + $blockDefinition, + ); + } + + $resultItems[] = $resultItem; + } + + return $resultItems; + } + /** * @throws Exception */ diff --git a/src/DataObject/Data/Adapter/ClassificationStoreAdapter.php b/src/DataObject/Data/Adapter/ClassificationStoreAdapter.php index cb443c38..5392f4e3 100644 --- a/src/DataObject/Data/Adapter/ClassificationStoreAdapter.php +++ b/src/DataObject/Data/Adapter/ClassificationStoreAdapter.php @@ -17,14 +17,25 @@ namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Adapter; use Exception; +use Pimcore\Bundle\GenericDataIndexBundle\Model\SearchIndexAdapter\MappingProperty; +use Pimcore\Bundle\StaticResolverBundle\Lib\ToolResolverInterface; +use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\ClassificationStore\DefinitionCacheResolverInterface; +use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\ClassificationStore\GroupConfigResolverInterface; use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\ClassificationStore\ServiceResolverInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataNormalizerInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\FieldContextData; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterLoaderInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\DatabaseException; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\ClassDefinition\Data\Classificationstore as ClassificationstoreDefinition; use Pimcore\Model\DataObject\Classificationstore; +use Pimcore\Model\DataObject\Classificationstore as ClassificationstoreModel; +use Pimcore\Model\DataObject\Classificationstore\GroupConfig; +use Pimcore\Model\DataObject\Classificationstore\KeyGroupRelation; +use Pimcore\Model\DataObject\Classificationstore\KeyGroupRelation\Listing as KeyGroupRelationListing; use Pimcore\Model\DataObject\Concrete; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; use function in_array; @@ -34,11 +45,15 @@ * @internal */ #[AutoconfigureTag(DataAdapterLoaderInterface::ADAPTER_TAG)] -final readonly class ClassificationStoreAdapter implements SetterDataInterface +final readonly class ClassificationStoreAdapter implements SetterDataInterface, DataNormalizerInterface { public function __construct( + private DefinitionCacheResolverInterface $definitionCacheResolver, private DataAdapterServiceInterface $dataAdapterService, - private ServiceResolverInterface $serviceResolver + private DataServiceInterface $dataService, + private GroupConfigResolverInterface $groupConfigResolver, + private ServiceResolverInterface $serviceResolver, + private ToolResolverInterface $toolResolver ) { } @@ -68,6 +83,36 @@ public function getDataForSetter( return $container; } + public function normalize( + mixed $value, + Data $fieldDefinition + ): ?array { + if (!$value instanceof ClassificationstoreModel || + !$fieldDefinition instanceof ClassificationstoreDefinition + ) { + return null; + } + + $validLanguages = $this->getValidLanguages($fieldDefinition); + $resultItems = []; + + foreach ($this->getActiveGroups($value) as $groupId => $groupConfig) { + $resultItems[$groupConfig->getName()] = []; + $keys = $this->getClassificationStoreKeysFromGroup($groupConfig); + foreach ($validLanguages as $validLanguage) { + foreach ($keys as $key) { + $normalizedValue = $this->getNormalizedValue($value, $groupId, $key, $validLanguage); + + if ($normalizedValue !== null) { + $resultItems[$groupConfig->getName()][$validLanguage][$key->getName()] = $normalizedValue; + } + } + } + } + + return $resultItems; + } + /** * @throws Exception */ @@ -89,13 +134,10 @@ private function setMapping(Classificationstore $container, array $data): void { $activeGroups = $data['activeGroups']; $groupCollectionMapping = $data['groupCollectionMapping']; - $correctedMapping = []; - foreach ($groupCollectionMapping as $groupId => $collectionId) { - if (isset($activeGroups[$groupId]) && $activeGroups[$groupId]) { - $correctedMapping[$groupId] = $collectionId; - } - } + $correctedMapping = array_filter($groupCollectionMapping, static function ($groupId) use ($activeGroups) { + return isset($activeGroups[$groupId]) && $activeGroups[$groupId]; + }, ARRAY_FILTER_USE_KEY); $container->setGroupCollectionMappings($correctedMapping); } @@ -163,4 +205,74 @@ private function cleanupStoreGroups(Classificationstore $container): void } } } + + private function getValidLanguages(ClassificationstoreDefinition $classificationStore): array + { + $languages = [MappingProperty::NOT_LOCALIZED_KEY]; + if ($classificationStore->isLocalized()) { + $languages = array_merge($languages, $this->toolResolver->getValidLanguages()); + } + + return $languages; + } + + /** + * @return GroupConfig[] + */ + private function getActiveGroups(ClassificationstoreModel $value): array + { + $groups = []; + foreach ($value->getActiveGroups() as $groupId => $active) { + if ($active) { + $groupConfig = $this->groupConfigResolver->getById($groupId); + if ($groupConfig) { + $groups[$groupId] = $groupConfig; + } + } + } + + return $groups; + } + + /** + * @return KeyGroupRelation[] + */ + private function getClassificationStoreKeysFromGroup(GroupConfig $groupConfig): array + { + $listing = new KeyGroupRelationListing(); + $listing->addConditionParam('groupId = ?', $groupConfig->getId()); + + return $listing->getList(); + } + + private function getNormalizedValue( + ClassificationstoreModel $classificationstore, + int $groupId, + KeyGroupRelation $key, + string $language + ): mixed { + try { + $value = $classificationstore->getLocalizedKeyValue( + $groupId, + $key->getKeyId(), + $language, + true, + true + ); + } catch (Exception $exception) { + throw new DatabaseException($exception->getMessage()); + } + + $keyConfig = $this->definitionCacheResolver->get($key->getKeyId()); + if ($keyConfig === null) { + return null; + } + + $fieldDefinition = $this->serviceResolver->getFieldDefinitionFromKeyConfig($keyConfig); + if ($fieldDefinition === null) { + return null; + } + + return $this->dataService->getNormalizedValue($value, $fieldDefinition); + } } diff --git a/src/DataObject/Data/Adapter/FieldCollectionsAdapter.php b/src/DataObject/Data/Adapter/FieldCollectionsAdapter.php index 5df4258e..6a314767 100644 --- a/src/DataObject/Data/Adapter/FieldCollectionsAdapter.php +++ b/src/DataObject/Data/Adapter/FieldCollectionsAdapter.php @@ -17,10 +17,13 @@ namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Adapter; use Exception; +use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\FieldCollection\DefinitionResolverInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataNormalizerInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\FieldContextData; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterLoaderInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\ClassDefinition\Data\Fieldcollections; use Pimcore\Model\DataObject\Concrete; @@ -33,10 +36,12 @@ * @internal */ #[AutoconfigureTag(DataAdapterLoaderInterface::ADAPTER_TAG)] -final readonly class FieldCollectionsAdapter implements SetterDataInterface +final readonly class FieldCollectionsAdapter implements SetterDataInterface, DataNormalizerInterface { public function __construct( private DataAdapterServiceInterface $dataAdapterService, + private DataServiceInterface $dataService, + private DefinitionResolverInterface $fieldCollectionDefinition, private Factory $modelFactory ) { } @@ -75,6 +80,40 @@ public function getDataForSetter( return new Fieldcollection($values, $fieldDefinition->getName()); } + public function normalize( + mixed $value, + Data $fieldDefinition + ): ?array { + if (!$value instanceof Fieldcollection) { + return null; + } + + $resultItems = []; + $items = $value->getItems(); + + foreach ($items as $item) { + $type = $item->getType(); + $fieldCollectionDefinition = $this->fieldCollectionDefinition->getByKey($item->getType()); + if (!$fieldCollectionDefinition) { + continue; + } + $resultItem = ['type' => $type]; + + foreach ($fieldCollectionDefinition->getFieldDefinitions() as $collectionFieldDefinition) { + $getter = 'get' . ucfirst($collectionFieldDefinition->getName()); + $value = $item->$getter(); + $resultItem[$collectionFieldDefinition->getName()] = $this->dataService->getNormalizedValue( + $value, + $collectionFieldDefinition, + ); + } + + $resultItems[] = $resultItem; + } + + return $resultItems; + } + /** * @throws Exception */ diff --git a/src/DataObject/Data/Adapter/LocalizedFieldsAdapter.php b/src/DataObject/Data/Adapter/LocalizedFieldsAdapter.php index 4a186162..6891020b 100644 --- a/src/DataObject/Data/Adapter/LocalizedFieldsAdapter.php +++ b/src/DataObject/Data/Adapter/LocalizedFieldsAdapter.php @@ -17,10 +17,13 @@ namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Adapter; use Exception; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataNormalizerInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\FieldContextData; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterLoaderInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\DatabaseException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException; use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementPermissions; @@ -32,15 +35,17 @@ use Pimcore\Model\User; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; use function in_array; +use function sprintf; /** * @internal */ #[AutoconfigureTag(DataAdapterLoaderInterface::ADAPTER_TAG)] -final readonly class LocalizedFieldsAdapter implements SetterDataInterface +final readonly class LocalizedFieldsAdapter implements SetterDataInterface, DataNormalizerInterface { public function __construct( private DataAdapterServiceInterface $dataAdapterService, + private DataServiceInterface $dataService, private SecurityServiceInterface $securityService, ) { } @@ -88,6 +93,40 @@ public function getDataForSetter( return $localizedField; } + public function normalize( + mixed $value, + Data $fieldDefinition + ): ?array { + if (!$value instanceof Localizedfield || !$fieldDefinition instanceof Localizedfields) { + return null; + } + + $value->loadLazyData(); + $originalValue = $fieldDefinition->normalize($value); + $languages = array_keys($originalValue); + $attributes = array_keys(reset($originalValue)); + $result = []; + foreach ($attributes as $attribute) { + foreach ($languages as $language) { + try { + $localizedValue = $value->getLocalizedValue($attribute, $language); + } catch (Exception $exception) { + throw new DatabaseException( + sprintf( + 'Error while normalizing localized field: %s', + $exception->getMessage() + ) + ); + } + $fieldDefinition = $value->getFieldDefinition($attribute); + $localizedValue = $this->dataService->getNormalizedValue($localizedValue, $fieldDefinition); + $result[$attribute][$language] = $localizedValue; + } + } + + return $result; + } + private function getAllowedLanguages( Concrete $element, array $languageData diff --git a/src/DataObject/Data/Adapter/ManyToManyRelationAdapter.php b/src/DataObject/Data/Adapter/ManyToManyRelationAdapter.php index b8759d41..35ab54fe 100644 --- a/src/DataObject/Data/Adapter/ManyToManyRelationAdapter.php +++ b/src/DataObject/Data/Adapter/ManyToManyRelationAdapter.php @@ -17,13 +17,17 @@ namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Adapter; use Pimcore\Bundle\StaticResolverBundle\Models\Element\ServiceResolverInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataNormalizerInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\FieldContextData; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\RelationData; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterLoaderInterface; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; use Pimcore\Bundle\StudioBackendBundle\Util\Trait\ElementProviderTrait; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\Concrete; +use Pimcore\Model\Document; +use Pimcore\Model\Element\ElementInterface; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; use function is_array; @@ -31,7 +35,7 @@ * @internal */ #[AutoconfigureTag(DataAdapterLoaderInterface::ADAPTER_TAG)] -final readonly class ManyToManyRelationAdapter implements SetterDataInterface +final readonly class ManyToManyRelationAdapter implements SetterDataInterface, DataNormalizerInterface { use ElementProviderTrait; @@ -55,6 +59,33 @@ public function getDataForSetter( return $this->getRelationElements($relationData); } + /** + * @return RelationData[]|null + */ + public function normalize( + mixed $value, + Data $fieldDefinition + ): ?array { + if (!is_array($value)) { + return null; + } + + $data = []; + /** @var ElementInterface[] $value */ + foreach ($value as $relation) { + $elementType = $this->getElementType($relation); + $data[] = new RelationData( + $relation->getId(), + $elementType, + $this->getSubType($relation), + $relation->getFullPath(), + $this->getPublished($relation) + ); + } + + return $data; + } + private function getRelationElements(array $relationData): array { $relations = []; @@ -70,4 +101,22 @@ private function getRelationElements(array $relationData): array return $relations; } + + private function getSubType(ElementInterface $element): string + { + if ($element instanceof Concrete) { + return $element->getClassName(); + } + + return $element->getType(); + } + + private function getPublished(ElementInterface $element): ?bool + { + if ($element instanceof Concrete || $element instanceof Document) { + return $element->getPublished(); + } + + return null; + } } diff --git a/src/DataObject/Data/Adapter/ObjectBricksAdapter.php b/src/DataObject/Data/Adapter/ObjectBricksAdapter.php index 9a6c2aa0..a1b15656 100644 --- a/src/DataObject/Data/Adapter/ObjectBricksAdapter.php +++ b/src/DataObject/Data/Adapter/ObjectBricksAdapter.php @@ -18,10 +18,12 @@ use Exception; use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\Objectbrick\DefinitionResolverInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataNormalizerInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\FieldContextData; use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterLoaderInterface; use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface; use Pimcore\Model\DataObject\ClassDefinition\Data; use Pimcore\Model\DataObject\ClassDefinition\Data\Objectbricks; use Pimcore\Model\DataObject\Concrete; @@ -35,10 +37,11 @@ * @internal */ #[AutoconfigureTag(DataAdapterLoaderInterface::ADAPTER_TAG)] -final readonly class ObjectBricksAdapter implements SetterDataInterface +final readonly class ObjectBricksAdapter implements SetterDataInterface, DataNormalizerInterface { public function __construct( private DataAdapterServiceInterface $dataAdapterService, + private DataServiceInterface $dataService, private DefinitionResolverInterface $definitionResolver ) { } @@ -66,6 +69,42 @@ public function getDataForSetter( return $container; } + public function normalize( + mixed $value, + Data $fieldDefinition + ): ?array { + if (!$value instanceof Objectbrick) { + return null; + } + + $resultItems = []; + $items = $value->getObjectVars(); + foreach ($items as $item) { + if (!$item instanceof AbstractData) { + continue; + } + + $type = $item->getType(); + $resultItems[$type] = []; + $definition = $this->definitionResolver->getByKey($type); + if ($definition === null) { + continue; + } + + $resultItems[$type] = []; + foreach ($definition->getFieldDefinitions() as $brickFieldDefinition) { + $getter = 'get' . ucfirst($brickFieldDefinition->getName()); + $value = $item->$getter(); + $resultItems[$brickFieldDefinition->getName()] = $this->dataService->getNormalizedValue( + $value, + $brickFieldDefinition, + ); + } + } + + return $resultItems; + } + /** * @throws Exception */ diff --git a/src/DataObject/Data/ClassData.php b/src/DataObject/Data/ClassData.php new file mode 100644 index 00000000..4ca473a7 --- /dev/null +++ b/src/DataObject/Data/ClassData.php @@ -0,0 +1,51 @@ +allowInheritance; + } + + public function getAllowVariants(): bool + { + return $this->allowVariants; + } + + public function getShowVariants(): bool + { + return $this->showVariants; + } + + public function getHasPreview(): bool + { + return $this->hasPreview; + } +} diff --git a/src/DataObject/Data/DataNormalizerInterface.php b/src/DataObject/Data/DataNormalizerInterface.php new file mode 100644 index 00000000..0a3c105a --- /dev/null +++ b/src/DataObject/Data/DataNormalizerInterface.php @@ -0,0 +1,27 @@ +id; + } + + public function getType(): string + { + return $this->type; + } + + public function getSubtype(): string + { + return $this->subtype; + } + + public function getFullPath(): string + { + return $this->fullPath; + } + + public function getIsPublished(): ?bool + { + return $this->isPublished; + } +} diff --git a/src/DataObject/Schema/DataObject.php b/src/DataObject/Schema/DataObject.php index df7e4434..be3b64df 100644 --- a/src/DataObject/Schema/DataObject.php +++ b/src/DataObject/Schema/DataObject.php @@ -18,6 +18,7 @@ use OpenApi\Attributes\Property; use OpenApi\Attributes\Schema; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Util\Trait\ClassDataTrait; use Pimcore\Bundle\StudioBackendBundle\Response\Element; use Pimcore\Bundle\StudioBackendBundle\Response\ElementIcon; use Pimcore\Bundle\StudioBackendBundle\Util\Schema\AdditionalAttributesInterface; @@ -38,12 +39,18 @@ 'customAttributes', 'permissions', 'index', + 'objectData', + 'allowInheritance', + 'allowVariants', + 'showVariants', + 'hasPreview', ], type: 'object' )] class DataObject extends Element implements AdditionalAttributesInterface { use AdditionalAttributesTrait; + use ClassDataTrait; use CustomAttributesTrait; use WorkflowAvailableTrait; @@ -75,7 +82,9 @@ public function __construct( ?string $locked, bool $isLocked, ?int $creationDate, - ?int $modificationDate + ?int $modificationDate, + #[Property(description: 'Detail object data', type: 'object', example: ['fieldKey' => 'field value'])] + private array $objectData = [], ) { parent::__construct( $id, @@ -136,6 +145,16 @@ public function getIndex(): int return $this->index; } + public function setObjectData(array $objectData): void + { + $this->objectData = $objectData; + } + + public function getObjectData(): array + { + return $this->objectData; + } + public function getFilename(): string { return $this->key; diff --git a/src/DataObject/Service/DataObjectService.php b/src/DataObject/Service/DataObjectService.php index a070f550..0e9cbc8b 100644 --- a/src/DataObject/Service/DataObjectService.php +++ b/src/DataObject/Service/DataObjectService.php @@ -75,6 +75,7 @@ public function __construct( private ClassDefinitionResolverInterface $classDefinitionResolver, + private DataServiceInterface $dataService, private DataObjectSearchServiceInterface $dataObjectSearchService, private DataObjectServiceResolverInterface $dataObjectServiceResolver, private FactoryInterface $factory, @@ -147,7 +148,7 @@ public function getDataObjects(DataObjectParameters $parameters): Collection /** * @throws SearchException|NotFoundException|UserNotFoundException */ - public function getDataObject(int $id, bool $getWorkflowAvailable = true): DataObject + public function getDataObject(int $id, bool $getDetailData = true): DataObject { $user = $this->securityService->getCurrentUser(); $dataObject = $this->dataObjectSearchService->getDataObjectById( @@ -155,13 +156,10 @@ public function getDataObject(int $id, bool $getWorkflowAvailable = true): DataO $user ); - if ($getWorkflowAvailable) { - $dataObject->setHasWorkflowAvailable($this->workflowDetailsService->hasElementWorkflows( - $id, - ElementTypes::TYPE_OBJECT, - $user - )); + if ($getDetailData) { + $this->setObjectDetailData($dataObject); } + $this->dispatchDataObjectEvent($dataObject); return $dataObject; @@ -344,6 +342,28 @@ private function setTreeSorting(int $parentId, QueryInterface $dataObjectQuery): $dataObjectQuery->orderByPath(strtolower($parent->getChildrenSortOrder())); } + /** + * @throws InvalidElementTypeException|NotFoundException + */ + private function setObjectDetailData(DataObjectFolder|DataObject $dataObject): void + { + $element = $this->getElement($this->serviceResolver, ElementTypes::TYPE_OBJECT, $dataObject->getId()); + $element = $this->getLatestVersionForUser($element, $this->securityService->getCurrentUser()); + + $dataObject->setHasWorkflowAvailable($this->workflowDetailsService->hasElementWorkflows($element)); + + if (!$element instanceof Concrete) { + return; + } + $dataObject->setObjectData($this->dataService->getObjectData($element)); + + $classData = $this->dataService->getObjectClassData($element); + $dataObject->setAllowInheritance($classData->getAllowInheritance()); + $dataObject->setAllowVariants($classData->getAllowVariants()); + $dataObject->setShowVariants($classData->getShowVariants()); + $dataObject->setHasPreview($classData->getHasPreview()); + } + private function dispatchDataObjectEvent(mixed $dataObject): void { $this->eventDispatcher->dispatch( diff --git a/src/DataObject/Service/DataObjectServiceInterface.php b/src/DataObject/Service/DataObjectServiceInterface.php index b43c46f9..f7c13a69 100644 --- a/src/DataObject/Service/DataObjectServiceInterface.php +++ b/src/DataObject/Service/DataObjectServiceInterface.php @@ -61,7 +61,7 @@ public function getDataObjects(DataObjectParameters $parameters): Collection; /** * @throws SearchException|NotFoundException|UserNotFoundException */ - public function getDataObject(int $id, bool $getWorkflowAvailable = true): DataObject; + public function getDataObject(int $id, bool $getDetailData = true): DataObject; /** * @throws SearchException|NotFoundException diff --git a/src/DataObject/Service/DataService.php b/src/DataObject/Service/DataService.php new file mode 100644 index 00000000..4981daa7 --- /dev/null +++ b/src/DataObject/Service/DataService.php @@ -0,0 +1,95 @@ +getClass()->getFieldDefinitions(); + } catch (Exception) { + throw new NotFoundException(type: 'class', id: $dataObject->getClassId()); + } + + foreach ($fieldDefinitions as $key => $fieldDefinition) { + try { + $data[$key] = $this->getNormalizedValue($dataObject->get($key), $fieldDefinition); + } catch (Exception) { + throw new NotFoundException(type: 'field', id: $key); + } + } + + return $data; + } + + /** + * @throws NotFoundException + */ + public function getObjectClassData(Concrete $dataObject): ClassData + { + try { + $class = $dataObject->getClass(); + } catch (Exception) { + throw new NotFoundException(type: 'class', id: $dataObject->getClassId()); + } + + return new ClassData( + $class->getAllowInherit(), + $class->getAllowVariants(), + $class->getShowVariants(), + (bool)$class->getLinkGeneratorReference() + ); + } + + public function getNormalizedValue( + mixed $value, + Data $fieldDefinition + ): mixed { + if (!$fieldDefinition instanceof NormalizerInterface) { + return null; + } + + $adapter = $this->dataAdapterService->getDataAdapter($fieldDefinition->getFieldType()); + if ($adapter instanceof DataNormalizerInterface) { + return $adapter->normalize($value, $fieldDefinition); + } + + return $fieldDefinition->normalize($value); + } +} diff --git a/src/DataObject/Service/DataServiceInterface.php b/src/DataObject/Service/DataServiceInterface.php new file mode 100644 index 00000000..8ebc53b6 --- /dev/null +++ b/src/DataObject/Service/DataServiceInterface.php @@ -0,0 +1,43 @@ +allowInheritance; + } + + public function setAllowInheritance(bool $allowInheritance): void + { + $this->allowInheritance = $allowInheritance; + } + + public function getAllowVariants(): ?bool + { + return $this->allowVariants; + } + + public function setAllowVariants(bool $allowVariants): void + { + $this->allowVariants = $allowVariants; + } + + public function getShowVariants(): ?bool + { + return $this->showVariants; + } + + public function setShowVariants(bool $showVariants): void + { + $this->showVariants = $showVariants; + } + + public function getHasPreview(): ?bool + { + return $this->hasPreview; + } + + public function setHasPreview(bool $hasPreview): void + { + $this->hasPreview = $hasPreview; + } +} diff --git a/src/Document/Service/DocumentService.php b/src/Document/Service/DocumentService.php index c692e40d..6f46e3dc 100644 --- a/src/Document/Service/DocumentService.php +++ b/src/Document/Service/DocumentService.php @@ -56,7 +56,7 @@ public function getDocument(int $id, bool $getWorkflowAvailable = true): Documen ); if ($getWorkflowAvailable) { - $document->setHasWorkflowAvailable($this->workflowDetailsService->hasElementWorkflows( + $document->setHasWorkflowAvailable($this->workflowDetailsService->hasElementWorkflowsById( $id, ElementTypes::TYPE_DOCUMENT, $user diff --git a/src/Exception/Api/InvalidDataTypeException.php b/src/Exception/Api/InvalidDataTypeException.php new file mode 100644 index 00000000..84559a05 --- /dev/null +++ b/src/Exception/Api/InvalidDataTypeException.php @@ -0,0 +1,39 @@ +value, + sprintf( + 'Invalid %s type: should be %s and was %s', + $key, + $type, + $actualType + ) + ); + } +} diff --git a/src/Grid/Column/Resolver/Metadata/DataObjectResolver.php b/src/Grid/Column/Resolver/Metadata/DataObjectResolver.php index 92e1db03..91583803 100644 --- a/src/Grid/Column/Resolver/Metadata/DataObjectResolver.php +++ b/src/Grid/Column/Resolver/Metadata/DataObjectResolver.php @@ -50,7 +50,8 @@ public function resolve(Column $column, ElementInterface $element): ColumnData try { $relatedObject = $this->dataObjectService->getDataObject( - reset($object['object']) + reset($object['object']), + false ); } catch (NotFoundException) { return $this->getColumnData($column, null); diff --git a/src/Workflow/Service/WorkflowDetailsService.php b/src/Workflow/Service/WorkflowDetailsService.php index 254e2b13..910100ec 100644 --- a/src/Workflow/Service/WorkflowDetailsService.php +++ b/src/Workflow/Service/WorkflowDetailsService.php @@ -78,10 +78,15 @@ public function getWorkflowDetails( return $details; } - public function hasElementWorkflows(int $elementId, string $elementType, UserInterface $user): bool + public function hasElementWorkflowsById(int $elementId, string $elementType, UserInterface $user): bool { - $element = $this->getUserElement($elementId, $elementType, $user); + return $this->hasElementWorkflows( + $this->getUserElement($elementId, $elementType, $user) + ); + } + public function hasElementWorkflows(ElementInterface $element): bool + { return count($this->workflowManager->getAllWorkflowsForSubject($element)) > 0; } diff --git a/src/Workflow/Service/WorkflowDetailsServiceInterface.php b/src/Workflow/Service/WorkflowDetailsServiceInterface.php index 5e435343..8a09097d 100644 --- a/src/Workflow/Service/WorkflowDetailsServiceInterface.php +++ b/src/Workflow/Service/WorkflowDetailsServiceInterface.php @@ -18,6 +18,7 @@ use Pimcore\Bundle\StudioBackendBundle\Workflow\MappedParameter\WorkflowDetailsParameters; use Pimcore\Bundle\StudioBackendBundle\Workflow\Schema\WorkflowDetails; +use Pimcore\Model\Element\ElementInterface; use Pimcore\Model\UserInterface; /** @@ -33,5 +34,7 @@ public function getWorkflowDetails( UserInterface $user ): array; - public function hasElementWorkflows(int $elementId, string $elementType, UserInterface $user): bool; + public function hasElementWorkflowsById(int $elementId, string $elementType, UserInterface $user): bool; + + public function hasElementWorkflows(ElementInterface $element): bool; }