diff --git a/config/class.yaml b/config/class.yaml index ee5409704..aa4cc4307 100644 --- a/config/class.yaml +++ b/config/class.yaml @@ -53,6 +53,9 @@ services: Pimcore\Bundle\StudioBackendBundle\Class\Service\LayoutServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\Class\Service\LayoutService + Pimcore\Bundle\StudioBackendBundle\Class\Service\FieldsByTypeServiceInterface: + class: Pimcore\Bundle\StudioBackendBundle\Class\Service\FieldsByTypeService + # # Repositories # @@ -108,6 +111,9 @@ services: Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\ObjectBrick\ObjectBrickConfigHydratorInterface: class: Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\ObjectBrick\ObjectBrickConfigHydrator + Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\FieldByTypeHydratorInterface: + class: Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\FieldByTypeHydrator + Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\CompactLayoutHydratorInterface: class: Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\CompactLayoutHydrator diff --git a/doc/05_Additional_Custom_Attributes.md b/doc/05_Additional_Custom_Attributes.md index dd8eb8434..1ac47954f 100644 --- a/doc/05_Additional_Custom_Attributes.md +++ b/doc/05_Additional_Custom_Attributes.md @@ -88,6 +88,7 @@ final class AssetEvent extends AbstractPreResponseEvent - `pre_response.class_definition.identifier_data` - `pre_response.class_definition.object_brick_data` - `pre_response.class_definition.tree` +- `pre_response.class_field_by_type` - `pre_response.classification_store.collection` - `pre_response.classification_store.config_collection` - `pre_response.classification_store.group` diff --git a/src/Class/Attribute/Request/CustomLayoutUpdateRequestBody.php b/src/Class/Attribute/Request/CustomLayoutUpdateRequestBody.php index 918f87611..8a283382f 100644 --- a/src/Class/Attribute/Request/CustomLayoutUpdateRequestBody.php +++ b/src/Class/Attribute/Request/CustomLayoutUpdateRequestBody.php @@ -21,10 +21,10 @@ #[Attribute(Attribute::TARGET_METHOD)] final class CustomLayoutUpdateRequestBody extends RequestBody { - public function __construct() + public function __construct(bool $required = true) { parent::__construct( - required: true, + required: $required, content: new JsonContent( ref: CustomLayoutUpdate::class, type: 'object' diff --git a/src/Class/Controller/CollectionController.php b/src/Class/Controller/CollectionController.php index 3f6a7462b..fe4f035e5 100644 --- a/src/Class/Controller/CollectionController.php +++ b/src/Class/Controller/CollectionController.php @@ -68,6 +68,7 @@ public function listClasses(): JsonResponse new PermissionsToCheck([ UserPermissions::CLASS_DEFINITION->value, UserPermissions::DATA_OBJECTS->value, + UserPermissions::OBJECT_BRICKS->value, UserPermissions::USER_MANAGEMENT->value, ]) ); diff --git a/src/Class/Controller/CustomLayout/ClassCollectionController.php b/src/Class/Controller/CustomLayout/ClassCollectionController.php index 20ae0f706..bbfbe9404 100644 --- a/src/Class/Controller/CustomLayout/ClassCollectionController.php +++ b/src/Class/Controller/CustomLayout/ClassCollectionController.php @@ -14,10 +14,11 @@ namespace Pimcore\Bundle\StudioBackendBundle\Class\Controller\CustomLayout; use OpenApi\Attributes\Get; +use Pimcore\Bundle\StudioBackendBundle\Class\MappedParameter\ClassIdsParameters; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\CustomLayout\CustomLayoutCompact; use Pimcore\Bundle\StudioBackendBundle\Class\Service\CustomLayoutServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController; -use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\StringParameter; +use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Query\ClassIdsParameter; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Property\GenericCollection; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\Content\CollectionJson; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses; @@ -27,15 +28,21 @@ use Pimcore\Bundle\StudioBackendBundle\Util\Constant\UserPermissions; use Pimcore\Bundle\StudioBackendBundle\Util\Trait\PaginatedResponseTrait; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Component\Serializer\SerializerInterface; use function count; +/** + * @internal + */ final class ClassCollectionController extends AbstractApiController { use PaginatedResponseTrait; + private const string ROUTE = '/class/custom-layout/collection'; + public function __construct( SerializerInterface $serializer, private readonly CustomLayoutServiceInterface $customLayoutService @@ -44,24 +51,19 @@ public function __construct( } #[Route( - '/class/custom-layout/collection/{dataObjectClass}', + self::ROUTE, name: 'pimcore_studio_api_class_custom_layout_collection', - methods: ['GET'] + methods: ['GET'], )] #[IsGranted(UserPermissions::DATA_OBJECTS->value)] #[Get( - path: self::PREFIX . '/class/custom-layout/collection/{dataObjectClass}', + path: self::PREFIX . self::ROUTE, operationId: 'class_custom_layout_collection', description: 'class_custom_layout_collection_description', summary: 'class_custom_layout_collection_summary', tags: [Tags::ClassDefinition->value], )] - #[StringParameter( - name: 'dataObjectClass', - example: 'CAR', - description: 'class_custom_layout_collection_data_object_class', - required: true - )] + #[ClassIdsParameter] #[SuccessResponse( description: 'class_custom_layout_collection_success_response', content: new CollectionJson(new GenericCollection(CustomLayoutCompact::class)) @@ -69,9 +71,12 @@ public function __construct( #[DefaultResponses([ HttpResponseCodes::UNAUTHORIZED, ])] - public function getCustomLayoutsByClass(string $dataObjectClass): JsonResponse - { - $items = $this->customLayoutService->getCustomLayoutCollection($dataObjectClass); + public function getCustomLayoutsByClass( + #[MapQueryString] ClassIdsParameters $parameters = new ClassIdsParameters() + ): JsonResponse { + $items = $this->customLayoutService->getCustomLayoutCollection( + $parameters->getClassIdsArray() + ); return $this->getPaginatedCollection( $this->serializer, diff --git a/src/Class/Controller/DefinitionConfiguration/ExportController.php b/src/Class/Controller/DefinitionConfiguration/ExportController.php index 19767e595..f9f2d6bb6 100644 --- a/src/Class/Controller/DefinitionConfiguration/ExportController.php +++ b/src/Class/Controller/DefinitionConfiguration/ExportController.php @@ -17,6 +17,7 @@ use Pimcore\Bundle\StudioBackendBundle\Class\Service\ClassDefinitionServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; +use Pimcore\Bundle\StudioBackendBundle\Export\Service\DownloadServiceInterface; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\StringParameter; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\Content\MediaType; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses; @@ -38,6 +39,7 @@ final class ExportController extends AbstractApiController public function __construct( SerializerInterface $serializer, private readonly ClassDefinitionServiceInterface $classDefinitionService, + private readonly DownloadServiceInterface $downloadService, ) { parent::__construct($serializer); } @@ -75,6 +77,11 @@ public function __construct( ])] public function exportClassDefinitionById(string $id): Response { - return $this->classDefinitionService->exportClassDefinition($id); + $export = $this->classDefinitionService->exportClassDefinition($id); + + return $this->downloadService->downloadJSON( + $export->getJson(), + $export->getFileName() + ); } } diff --git a/src/Class/Controller/FieldCollection/ExportController.php b/src/Class/Controller/FieldCollection/ExportController.php index 3dfad89ff..d507071b0 100644 --- a/src/Class/Controller/FieldCollection/ExportController.php +++ b/src/Class/Controller/FieldCollection/ExportController.php @@ -17,6 +17,7 @@ use Pimcore\Bundle\StudioBackendBundle\Class\Service\FieldCollection\FieldCollectionServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; +use Pimcore\Bundle\StudioBackendBundle\Export\Service\DownloadServiceInterface; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\StringParameter; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\Content\MediaType; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses; @@ -41,6 +42,7 @@ final class ExportController extends AbstractApiController public function __construct( SerializerInterface $serializer, private readonly FieldCollectionServiceInterface $fieldCollectionService, + private readonly DownloadServiceInterface $downloadService, ) { parent::__construct($serializer); } @@ -78,6 +80,11 @@ public function __construct( ])] public function exportFieldCollection(string $key): Response { - return $this->fieldCollectionService->exportFieldCollection($key); + $export = $this->fieldCollectionService->exportFieldCollection($key); + + return $this->downloadService->downloadJSON( + $export->getJson(), + $export->getFileName() + ); } } diff --git a/src/Class/Controller/FieldsByTypeController.php b/src/Class/Controller/FieldsByTypeController.php new file mode 100644 index 000000000..f790cc6da --- /dev/null +++ b/src/Class/Controller/FieldsByTypeController.php @@ -0,0 +1,93 @@ +value)] + #[Get( + path: self::PREFIX . self::ROUTE, + operationId: 'class_get_fields_by_type', + description: 'class_get_fields_by_type_description', + summary: 'class_get_fields_by_type_summary', + tags: [Tags::ClassDefinition->value], + )] + #[StringParameter( + name: 'classId', + example: 'EV', + description: 'The class ID to retrieve fields for.', + required: true + )] + #[StringParameter( + name: 'type', + example: 'manyToOneRelation,objectbricks', + description: 'Comma-separated list of field types to filter by.', + required: true + )] + #[SuccessResponse( + description: 'class_get_fields_by_type_success_response', + content: new CollectionJson(new GenericCollection(FieldByType::class)) + )] + #[DefaultResponses([ + HttpResponseCodes::UNAUTHORIZED, + HttpResponseCodes::NOT_FOUND, + ])] + public function getFieldsByType( + #[MapQueryString] FieldsByTypeParameters $parameters + ): JsonResponse { + $fields = $this->fieldsByTypeService->getFieldsByType($parameters); + + return $this->getPaginatedCollection($this->serializer, $fields, count($fields)); + } +} diff --git a/src/Class/Controller/Folder/CollectionController.php b/src/Class/Controller/Folder/CollectionController.php index b162edd0e..8871fc8e0 100644 --- a/src/Class/Controller/Folder/CollectionController.php +++ b/src/Class/Controller/Folder/CollectionController.php @@ -15,7 +15,7 @@ use OpenApi\Attributes\Get; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\Folder\ClassDefinitionFolderItem; -use Pimcore\Bundle\StudioBackendBundle\Class\Service\ClassDefinitionServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\Class\Service\ClassDefinitionTreeServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\IdParameter; @@ -43,7 +43,7 @@ final class CollectionController extends AbstractApiController public function __construct( SerializerInterface $serializer, - private readonly ClassDefinitionServiceInterface $classDefinitionService, + private readonly ClassDefinitionTreeServiceInterface $classDefinitionTreeService, ) { parent::__construct($serializer); @@ -78,7 +78,7 @@ public function __construct( public function getFolderClassList( int $folderId ): JsonResponse { - $collection = $this->classDefinitionService->getClassDefinitionIdsInsideFolder($folderId); + $collection = $this->classDefinitionTreeService->getClassDefinitionIdsInsideFolder($folderId); return $this->getPaginatedCollection( $this->serializer, diff --git a/src/Class/Controller/ObjectBrick/ClassesController.php b/src/Class/Controller/ObjectBrick/ClassesController.php new file mode 100644 index 000000000..94c8e6351 --- /dev/null +++ b/src/Class/Controller/ObjectBrick/ClassesController.php @@ -0,0 +1,81 @@ +value)] + #[Get( + path: self::PREFIX . self::ROUTE, + operationId: 'class_object_brick_classes', + description: 'class_object_brick_classes_description', + summary: 'class_object_brick_classes_summary', + tags: [Tags::ClassDefinition->value], + )] + #[SuccessResponse( + description: 'class_object_brick_classes_success_response', + content: new CollectionJson(new GenericCollection(ClassDefinitionList::class)) + )] + #[DefaultResponses([ + HttpResponseCodes::UNAUTHORIZED, + ])] + public function getClassesWithObjectBricks(): JsonResponse + { + $items = $this->classDefinitionService->getClassDefinitionsWithObjectBricks(); + + return $this->getPaginatedCollection( + $this->serializer, + $items, + count($items) + ); + } +} diff --git a/src/Class/Controller/ObjectBrick/CreateController.php b/src/Class/Controller/ObjectBrick/CreateController.php index 6a733d19a..f914da01f 100644 --- a/src/Class/Controller/ObjectBrick/CreateController.php +++ b/src/Class/Controller/ObjectBrick/CreateController.php @@ -22,6 +22,7 @@ use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ElementExistsException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ElementSavingFailedException; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotWriteableException; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Request\ReferenceRequestBody; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses; @@ -50,7 +51,7 @@ public function __construct( } /** - * @throws ElementExistsException|ElementSavingFailedException|NotWriteableException + * @throws ElementExistsException|ElementSavingFailedException|InvalidArgumentException|NotWriteableException */ #[Route( self::ROUTE, diff --git a/src/Class/Controller/ObjectBrick/CustomLayout/DeleteController.php b/src/Class/Controller/ObjectBrick/CustomLayout/DeleteController.php new file mode 100644 index 000000000..c28c5c524 --- /dev/null +++ b/src/Class/Controller/ObjectBrick/CustomLayout/DeleteController.php @@ -0,0 +1,84 @@ +value)] + #[Delete( + path: self::PREFIX . self::ROUTE, + operationId: 'class_object_brick_custom_layout_delete', + description: 'class_object_brick_custom_layout_delete_description', + summary: 'class_object_brick_custom_layout_delete_summary', + tags: [Tags::ClassDefinition->value], + )] + #[StringParameter( + name: 'key', + example: 'SaleInformation', + description: 'class_object_brick_custom_layout_delete_key', + required: true + )] + #[StringParameter( + name: 'customLayoutId', + example: 'CarTodo', + description: 'class_object_brick_custom_layout_delete_layout_id', + required: true + )] + #[SuccessResponse( + description: 'class_object_brick_custom_layout_delete_success_response', + )] + #[DefaultResponses([ + HttpResponseCodes::NOT_FOUND, + HttpResponseCodes::UNAUTHORIZED, + HttpResponseCodes::INTERNAL_SERVER_ERROR, + ])] + public function deleteBrickCustomLayout(string $key, string $customLayoutId): Response + { + $this->customLayoutService->deleteBrickCustomLayout($key, $customLayoutId); + + return new Response(); + } +} diff --git a/src/Class/Controller/ObjectBrick/CustomLayout/ExportController.php b/src/Class/Controller/ObjectBrick/CustomLayout/ExportController.php new file mode 100644 index 000000000..ac09963d9 --- /dev/null +++ b/src/Class/Controller/ObjectBrick/CustomLayout/ExportController.php @@ -0,0 +1,85 @@ +value)] + #[Get( + path: self::PREFIX . self::ROUTE, + operationId: 'class_object_brick_custom_layout_export', + description: 'class_object_brick_custom_layout_export_description', + summary: 'class_object_brick_custom_layout_export_summary', + tags: [Tags::ClassDefinition->value], + )] + #[StringParameter( + name: 'key', + example: 'SaleInformation', + description: 'class_object_brick_custom_layout_export_key', + required: true + )] + #[StringParameter( + name: 'customLayoutId', + example: 'CarTodo', + description: 'class_object_brick_custom_layout_export_layout_id', + required: true + )] + #[SuccessResponse( + description: 'class_object_brick_custom_layout_export_success_response', + content: [new MediaType(MimeTypes::JSON->value)], + headers: [new ContentDisposition(fileName: 'custom_definition_export.json')] + )] + #[DefaultResponses([ + HttpResponseCodes::NOT_FOUND, + HttpResponseCodes::UNAUTHORIZED, + ])] + public function exportBrickCustomLayout(string $key, string $customLayoutId): Response + { + return $this->customLayoutService->exportBrickCustomLayoutAsJson($key, $customLayoutId); + } +} diff --git a/src/Class/Controller/ObjectBrick/CustomLayout/GetController.php b/src/Class/Controller/ObjectBrick/CustomLayout/GetController.php new file mode 100644 index 000000000..a2a6acfa5 --- /dev/null +++ b/src/Class/Controller/ObjectBrick/CustomLayout/GetController.php @@ -0,0 +1,85 @@ +value)] + #[Get( + path: self::PREFIX . self::ROUTE, + operationId: 'class_object_brick_custom_layout_get', + description: 'class_object_brick_custom_layout_get_description', + summary: 'class_object_brick_custom_layout_get_summary', + tags: [Tags::ClassDefinition->value], + )] + #[StringParameter( + name: 'key', + example: 'SaleInformation', + description: 'class_object_brick_custom_layout_get_key', + required: true + )] + #[StringParameter( + name: 'customLayoutId', + example: 'CarTodo', + description: 'class_object_brick_custom_layout_get_layout_id', + required: true + )] + #[SuccessResponse( + description: 'class_object_brick_custom_layout_get_success_response', + content: new JsonContent(ref: CustomLayout::class) + )] + #[DefaultResponses([ + HttpResponseCodes::NOT_FOUND, + HttpResponseCodes::UNAUTHORIZED, + ])] + public function getBrickCustomLayout(string $key, string $customLayoutId): JsonResponse + { + return $this->jsonResponse( + $this->customLayoutService->getBrickCustomLayout($key, $customLayoutId) + ); + } +} diff --git a/src/Class/Controller/ObjectBrick/CustomLayout/ImportController.php b/src/Class/Controller/ObjectBrick/CustomLayout/ImportController.php new file mode 100644 index 000000000..8022e88cf --- /dev/null +++ b/src/Class/Controller/ObjectBrick/CustomLayout/ImportController.php @@ -0,0 +1,119 @@ +value)] + #[Post( + path: self::PREFIX . self::ROUTE, + operationId: 'class_object_brick_custom_layout_import', + description: 'class_object_brick_custom_layout_import_description', + summary: 'class_object_brick_custom_layout_import_summary', + tags: [Tags::ClassDefinition->value], + )] + #[StringParameter( + name: 'key', + example: 'SaleInformation', + description: 'class_object_brick_custom_layout_import_key', + required: true + )] + #[StringParameter( + name: 'customLayoutId', + example: 'CarTodo', + description: 'class_object_brick_custom_layout_import_layout_id', + required: true + )] + #[MultipartFormDataRequestBody( + [ + new Property( + property: 'file', + description: 'Import file to upload', + type: 'string', + format: 'binary' + ), + ], + ['file'] + )] + #[SuccessResponse( + description: 'class_object_brick_custom_layout_import_success_response', + content: new JsonContent(ref: CustomLayout::class, type: 'object') + )] + #[DefaultResponses([ + HttpResponseCodes::INTERNAL_SERVER_ERROR, + HttpResponseCodes::UNAUTHORIZED, + HttpResponseCodes::NOT_FOUND, + HttpResponseCodes::BAD_REQUEST, + ])] + public function importBrickCustomLayout(string $key, string $customLayoutId, Request $request): JsonResponse + { + $file = $request->files->get('file'); + if (!$file instanceof UploadedFile) { + throw new EnvironmentException('Invalid file found in the request'); + } + + return $this->jsonResponse( + $this->customLayoutService->importBrickCustomLayoutFromJson( + $key, + $customLayoutId, + $file->getContent() + ) + ); + } +} diff --git a/src/Class/Controller/ObjectBrick/CustomLayout/UpdateController.php b/src/Class/Controller/ObjectBrick/CustomLayout/UpdateController.php new file mode 100644 index 000000000..2dd452e90 --- /dev/null +++ b/src/Class/Controller/ObjectBrick/CustomLayout/UpdateController.php @@ -0,0 +1,96 @@ +value)] + #[Put( + path: self::PREFIX . self::ROUTE, + operationId: 'class_object_brick_custom_layout_update', + description: 'class_object_brick_custom_layout_update_description', + summary: 'class_object_brick_custom_layout_update_summary', + tags: [Tags::ClassDefinition->value], + )] + #[StringParameter( + name: 'key', + example: 'SaleInformation', + description: 'class_object_brick_custom_layout_update_key', + required: true + )] + #[StringParameter( + name: 'customLayoutId', + example: 'CarTodo', + description: 'class_object_brick_custom_layout_update_layout_id', + required: true + )] + #[CustomLayoutUpdateRequestBody(false)] + #[SuccessResponse( + description: 'class_object_brick_custom_layout_update_success_response', + content: new JsonContent(ref: CustomLayout::class, type: 'object') + )] + #[DefaultResponses([ + HttpResponseCodes::NOT_FOUND, + HttpResponseCodes::INTERNAL_SERVER_ERROR, + HttpResponseCodes::UNAUTHORIZED, + HttpResponseCodes::BAD_REQUEST, + ])] + public function updateBrickCustomLayout( + string $key, + string $customLayoutId, + #[MapRequestPayload] UpdateParameters $parameters = new UpdateParameters([], []), + ): JsonResponse { + return $this->jsonResponse( + $this->customLayoutService->updateBrickCustomLayout($key, $customLayoutId, $parameters) + ); + } +} diff --git a/src/Class/Controller/ObjectBrick/ExportController.php b/src/Class/Controller/ObjectBrick/ExportController.php index 81a4a1cad..8c45e1759 100644 --- a/src/Class/Controller/ObjectBrick/ExportController.php +++ b/src/Class/Controller/ObjectBrick/ExportController.php @@ -17,6 +17,7 @@ use Pimcore\Bundle\StudioBackendBundle\Class\Service\ObjectBrick\ObjectBrickServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; +use Pimcore\Bundle\StudioBackendBundle\Export\Service\DownloadServiceInterface; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\StringParameter; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\Content\MediaType; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses; @@ -41,6 +42,7 @@ final class ExportController extends AbstractApiController public function __construct( SerializerInterface $serializer, private readonly ObjectBrickServiceInterface $objectBrickService, + private readonly DownloadServiceInterface $downloadService, ) { parent::__construct($serializer); } @@ -78,6 +80,11 @@ public function __construct( ])] public function exportObjectBrick(string $key): Response { - return $this->objectBrickService->exportObjectBrick($key); + $export = $this->objectBrickService->exportObjectBrick($key); + + return $this->downloadService->downloadJSON( + $export->getJson(), + $export->getFileName() + ); } } diff --git a/src/Class/Event/FieldByTypeEvent.php b/src/Class/Event/FieldByTypeEvent.php new file mode 100644 index 000000000..b2502b925 --- /dev/null +++ b/src/Class/Event/FieldByTypeEvent.php @@ -0,0 +1,32 @@ +field; + } +} diff --git a/src/Class/Hydrator/ClassDefinitionHydrator.php b/src/Class/Hydrator/ClassDefinitionHydrator.php index 8ecbb27af..dd240b109 100644 --- a/src/Class/Hydrator/ClassDefinitionHydrator.php +++ b/src/Class/Hydrator/ClassDefinitionHydrator.php @@ -14,6 +14,7 @@ namespace Pimcore\Bundle\StudioBackendBundle\Class\Hydrator; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinition as ClassDefinitionSchema; +use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinitionBrickData; use Pimcore\Bundle\StudioBackendBundle\Icon\Service\IconServiceInterface; use Pimcore\Model\DataObject\ClassDefinition; @@ -59,4 +60,9 @@ public function hydrate(ClassDefinition $data): ClassDefinitionSchema $data->getGroup(), ); } + + public function hydrateBrickData(string $key, string $fieldName): ClassDefinitionBrickData + { + return new ClassDefinitionBrickData($key, $fieldName); + } } diff --git a/src/Class/Hydrator/ClassDefinitionHydratorInterface.php b/src/Class/Hydrator/ClassDefinitionHydratorInterface.php index 466cb9ab8..03c182639 100644 --- a/src/Class/Hydrator/ClassDefinitionHydratorInterface.php +++ b/src/Class/Hydrator/ClassDefinitionHydratorInterface.php @@ -14,6 +14,7 @@ namespace Pimcore\Bundle\StudioBackendBundle\Class\Hydrator; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinition as ClassDefinitionSchema; +use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinitionBrickData; use Pimcore\Model\DataObject\ClassDefinition; /** @@ -22,4 +23,6 @@ interface ClassDefinitionHydratorInterface { public function hydrate(ClassDefinition $data): ClassDefinitionSchema; + + public function hydrateBrickData(string $key, string $fieldName): ClassDefinitionBrickData; } diff --git a/src/Class/Hydrator/FieldByTypeHydrator.php b/src/Class/Hydrator/FieldByTypeHydrator.php new file mode 100644 index 000000000..51127260f --- /dev/null +++ b/src/Class/Hydrator/FieldByTypeHydrator.php @@ -0,0 +1,33 @@ +getConfig()['field'] ?? $column->getKey(); + } +} diff --git a/src/Class/Hydrator/FieldByTypeHydratorInterface.php b/src/Class/Hydrator/FieldByTypeHydratorInterface.php new file mode 100644 index 000000000..7cb078c2c --- /dev/null +++ b/src/Class/Hydrator/FieldByTypeHydratorInterface.php @@ -0,0 +1,27 @@ +classIds; + } + + /** + * @return string[] + */ + public function getClassIdsArray(): array + { + if (empty($this->classIds)) { + return []; + } + + return array_filter( + array_map('trim', explode(',', $this->classIds)), + static fn (string $classId): bool => !empty($classId) + ); + } +} diff --git a/src/Class/MappedParameter/FieldsByTypeParameters.php b/src/Class/MappedParameter/FieldsByTypeParameters.php new file mode 100644 index 000000000..a49df1761 --- /dev/null +++ b/src/Class/MappedParameter/FieldsByTypeParameters.php @@ -0,0 +1,42 @@ +classId; + } + + /** + * @return string[] + */ + public function getTypes(): array + { + return array_filter( + array_map('trim', explode(',', $this->type)), + static fn (string $t): bool => !empty($t) + ); + } +} diff --git a/src/Class/Repository/ClassDefinitionRepository.php b/src/Class/Repository/ClassDefinitionRepository.php index 208b49525..53c5ec2f8 100644 --- a/src/Class/Repository/ClassDefinitionRepository.php +++ b/src/Class/Repository/ClassDefinitionRepository.php @@ -27,8 +27,10 @@ use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Util\Constant\HttpResponseErrorKeys; use Pimcore\Model\DataObject\ClassDefinition; +use Pimcore\Model\DataObject\ClassDefinition\Data\Objectbricks; use Pimcore\Model\DataObject\ClassDefinition\Listing; use Pimcore\Model\DataObject\Exception\DefinitionWriteException; +use Pimcore\Model\DataObject\Objectbrick\Definition\Listing as ObjectBrickListing; use function sprintf; /** @@ -54,6 +56,43 @@ public function getClassDefinitions(): array return $classesList->load(); } + public function getClassDefinitionsWithObjectBricks(): array + { + $result = []; + + foreach ($this->getClassDefinitions() as $class) { + foreach ($class->getFieldDefinitions() as $fieldDefinition) { + if ($fieldDefinition instanceof Objectbricks) { + $result[] = $class; + + break; + } + } + } + + return $result; + } + + public function getObjectBricksByClassName(string $className): array + { + $objectBrickList = new ObjectBrickListing(); + $brickDefinitions = $objectBrickList->load(); + $result = []; + + foreach ($brickDefinitions as $brickDefinition) { + foreach ($brickDefinition->getClassDefinitions() as $brickClass) { + if ($className === $brickClass['classname']) { + $result[] = [ + 'key' => $brickDefinition->getKey(), + 'fieldname' => $brickClass['fieldname'], + ]; + } + } + } + + return $result; + } + /** * {@inheritdoc} */ diff --git a/src/Class/Repository/ClassDefinitionRepositoryInterface.php b/src/Class/Repository/ClassDefinitionRepositoryInterface.php index 14449b9e3..d69e09e72 100644 --- a/src/Class/Repository/ClassDefinitionRepositoryInterface.php +++ b/src/Class/Repository/ClassDefinitionRepositoryInterface.php @@ -34,6 +34,11 @@ interface ClassDefinitionRepositoryInterface */ public function getClassDefinitions(): array; + /** + * @return ClassDefinition[] + */ + public function getClassDefinitionsWithObjectBricks(): array; + /** * @throws NotFoundException */ @@ -60,6 +65,11 @@ public function create(CreateClassDefinitionParameters $parameters): ClassDefini */ public function update(ClassDefinition $classDefinition, UpdateParameters $updateParameters): ClassDefinition; + /** + * @return array + */ + public function getObjectBricksByClassName(string $className): array; + public function exportAsJson(ClassDefinition $classDefinition): string; /** diff --git a/src/Class/Repository/CustomLayoutRepository.php b/src/Class/Repository/CustomLayoutRepository.php index 337e0f5e0..6fbc2f722 100644 --- a/src/Class/Repository/CustomLayoutRepository.php +++ b/src/Class/Repository/CustomLayoutRepository.php @@ -27,6 +27,7 @@ use Pimcore\Model\DataObject\ClassDefinition\CustomLayout; use Pimcore\Model\DataObject\ClassDefinition\CustomLayout\Listing; use Pimcore\Model\DataObject\Exception\DefinitionWriteException; +use function in_array; /** * @internal @@ -42,12 +43,12 @@ public function __construct( ) { } - public function getCustomLayoutsByClass(string $dataObjectClassId): array + public function getCustomLayoutsByClass(array $dataObjectClassIds): array { $customLayoutListing = new Listing(); $customLayoutListing->setFilter( fn (CustomLayout $layout) => - $layout->getClassId() === $dataObjectClassId && + in_array($layout->getClassId(), $dataObjectClassIds, true) && !str_contains($layout->getId(), '.brick.') ); @@ -137,6 +138,12 @@ public function updateCustomLayout( true ); $customLayout->setLayoutDefinitions($layout); + if (empty($values)) { + $customLayout->save(); + + return $customLayout; + } + $customLayout->setName($values['name']); $customLayout->setDescription($values['description'] ?? ''); $customLayout->setDefault($values['default'] ?? false); @@ -180,4 +187,37 @@ public function importCustomLayoutFromJson(CustomLayout $customLayout, string $j throw new InvalidArgumentException($e->getMessage()); } } + + public function createBrickCustomLayout(string $compositeId, CustomLayout $baseLayout): CustomLayout + { + $parts = explode('.brick.', $compositeId); + $brickKey = $parts[1] ?? $compositeId; + + try { + $customLayout = $this->customLayoutResolver->create( + [ + 'id' => $compositeId, + 'name' => $baseLayout->getName() . ' ' . $brickKey, + 'userOwner' => $this->securityService->getCurrentUser()->getId(), + 'classId' => $baseLayout->getClassId(), + ] + ); + $customLayout->save(); + + return $customLayout; + } catch (DefinitionWriteException) { + throw new NotWriteableException(self::NOT_WRITEABLE_EXCEPTION_MESSAGE); + } + } + + public function getBrickLayoutsByBaseId(string $baseLayoutId): array + { + $prefix = $baseLayoutId . '.brick.'; + $listing = new Listing(); + $listing->setFilter( + fn (CustomLayout $layout) => str_starts_with($layout->getId(), $prefix) + ); + + return $listing->load(); + } } diff --git a/src/Class/Repository/CustomLayoutRepositoryInterface.php b/src/Class/Repository/CustomLayoutRepositoryInterface.php index deef3b831..f03428618 100644 --- a/src/Class/Repository/CustomLayoutRepositoryInterface.php +++ b/src/Class/Repository/CustomLayoutRepositoryInterface.php @@ -15,6 +15,7 @@ use Pimcore\Bundle\StudioBackendBundle\Class\MappedParameter\CustomLayoutNewParameters; use Pimcore\Bundle\StudioBackendBundle\Class\MappedParameter\UpdateParameters; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\EnvironmentException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotWriteableException; @@ -27,9 +28,11 @@ interface CustomLayoutRepositoryInterface { /** + * @param string[] $dataObjectClassIds + * * @return CustomLayout[] */ - public function getCustomLayoutsByClass(string $dataObjectClassId): array; + public function getCustomLayoutsByClass(array $dataObjectClassIds): array; public function getAllCustomLayouts(): array; @@ -65,4 +68,14 @@ public function exportCustomLayoutAsJson(CustomLayout $customLayout): string; * @throws NotWriteableException|JsonEncodingException|InvalidArgumentException */ public function importCustomLayoutFromJson(CustomLayout $customLayout, string $json): CustomLayout; + + /** + * @throws NotWriteableException|EnvironmentException + */ + public function createBrickCustomLayout(string $compositeId, CustomLayout $baseLayout): CustomLayout; + + /** + * @return CustomLayout[] + */ + public function getBrickLayoutsByBaseId(string $baseLayoutId): array; } diff --git a/src/Class/Repository/ObjectBrickRepository.php b/src/Class/Repository/ObjectBrickRepository.php index 4c905ed46..7cb458d0f 100644 --- a/src/Class/Repository/ObjectBrickRepository.php +++ b/src/Class/Repository/ObjectBrickRepository.php @@ -14,6 +14,7 @@ namespace Pimcore\Bundle\StudioBackendBundle\Class\Repository; use Exception; +use JsonException; use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\ClassDefinitionServiceResolverInterface; use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\Objectbrick\DefinitionResolverInterface; use Pimcore\Bundle\StudioBackendBundle\Class\MappedParameter\CreateObjectBrickParameters; @@ -27,6 +28,7 @@ use Pimcore\Model\DataObject\ClassDefinition\Listing; use Pimcore\Model\DataObject\Exception\DefinitionWriteException; use Pimcore\Model\DataObject\Objectbrick\Definition; +use function array_key_exists; use function array_map; use function array_unique; use function array_values; @@ -82,6 +84,7 @@ public function getObjectBrickByKey(string $key): Definition public function create(CreateObjectBrickParameters $parameters): Definition { $key = $parameters->getKey(); + $this->validateKey($key); $existingNames = $this->listObjectBrickNames(); foreach ($existingNames as $existingName) { @@ -171,6 +174,8 @@ public function exportAsJson(Definition $definition): string */ public function importFromJson(Definition $definition, string $json): Definition { + $this->validateJson($json); + try { $success = $this->classDefinitionServiceResolver->importObjectBrickFromJson( $definition, @@ -223,4 +228,41 @@ private function listObjectBrickNames(): array { return (new Definition\Listing())->loadNames(); } + + /** + * @throws InvalidArgumentException + */ + private function validateKey(string $key): void + { + if ($key === '') { + throw new InvalidArgumentException('Object brick key must not be empty'); + } + + if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $key)) { + throw new InvalidArgumentException( + 'Object brick key must start with a letter and contain only alphanumeric characters' + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function validateJson(string $json): void + { + try { + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw new InvalidArgumentException( + 'Import file does not contain valid JSON: ' . $e->getMessage(), + $e, + ); + } + + if (!is_array($data) || !array_key_exists('classDefinitions', $data)) { + throw new InvalidArgumentException( + 'Invalid import file: missing required key "classDefinitions"' + ); + } + } } diff --git a/src/Class/Repository/ObjectBrickRepositoryInterface.php b/src/Class/Repository/ObjectBrickRepositoryInterface.php index e71102d68..33acf32c9 100644 --- a/src/Class/Repository/ObjectBrickRepositoryInterface.php +++ b/src/Class/Repository/ObjectBrickRepositoryInterface.php @@ -38,7 +38,7 @@ public function listObjectBricks(): array; public function getObjectBrickByKey(string $key): Definition; /** - * @throws ElementExistsException|ElementSavingFailedException|NotWriteableException + * @throws ElementExistsException|ElementSavingFailedException|InvalidArgumentException|NotWriteableException */ public function create(CreateObjectBrickParameters $parameters): Definition; diff --git a/src/Class/Schema/FieldByType.php b/src/Class/Schema/FieldByType.php new file mode 100644 index 000000000..0c73f4f0e --- /dev/null +++ b/src/Class/Schema/FieldByType.php @@ -0,0 +1,44 @@ +key; + } +} diff --git a/src/Class/Service/ClassDefinitionService.php b/src/Class/Service/ClassDefinitionService.php index 910e3eb19..98de8a329 100644 --- a/src/Class/Service/ClassDefinitionService.php +++ b/src/Class/Service/ClassDefinitionService.php @@ -15,27 +15,18 @@ use Pimcore\Bundle\StudioBackendBundle\Class\Event\ClassDefinitionBrickEvent; use Pimcore\Bundle\StudioBackendBundle\Class\Event\ClassDefinitionEvent; -use Pimcore\Bundle\StudioBackendBundle\Class\Event\ClassDefinitionFolderListEvent; use Pimcore\Bundle\StudioBackendBundle\Class\Event\ClassDefinitionListEvent; use Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\ClassDefinitionHydratorInterface; use Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\ClassDefinitionListHydratorInterface; -use Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\Folder\ClassDefinitionFolderItemHydratorInterface; use Pimcore\Bundle\StudioBackendBundle\Class\MappedParameter\CreateClassDefinitionParameters; use Pimcore\Bundle\StudioBackendBundle\Class\MappedParameter\UpdateParameters; use Pimcore\Bundle\StudioBackendBundle\Class\Repository\ClassDefinitionRepositoryInterface; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinition; -use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinitionBrickData; -use Pimcore\Bundle\StudioBackendBundle\Element\Service\ElementServiceInterface; -use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; -use Pimcore\Bundle\StudioBackendBundle\Export\Service\DownloadServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\OpenApi\Schema\JsonExport; use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface; -use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementTypes; use Pimcore\Bundle\StudioBackendBundle\Util\Constant\UserPermissions; use Pimcore\Model\DataObject\ClassDefinition as CoreClassDefinition; -use Pimcore\Model\DataObject\Folder; -use Pimcore\Model\DataObject\Objectbrick\Definition\Listing as ObjectBrickListing; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\Response; /** * @internal @@ -46,11 +37,8 @@ public function __construct( private ClassDefinitionRepositoryInterface $classDefinitionRepository, private ClassDefinitionHydratorInterface $classDefinitionHydrator, private ClassDefinitionListHydratorInterface $classDefinitionListHydrator, - private ClassDefinitionFolderItemHydratorInterface $classDefinitionFolderListHydrator, - private DownloadServiceInterface $downloadService, - private ElementServiceInterface $elementService, private EventDispatcherInterface $eventDispatcher, - private SecurityServiceInterface $securityService + private SecurityServiceInterface $securityService, ) { } @@ -90,12 +78,12 @@ public function deleteClassDefinition(string $id): void /** * {@inheritdoc} */ - public function exportClassDefinition(string $id): Response + public function exportClassDefinition(string $id): JsonExport { $classDefinition = $this->classDefinitionRepository->getClassDefinitionById($id); $json = $this->classDefinitionRepository->exportAsJson($classDefinition); - return $this->downloadService->downloadJSON( + return new JsonExport( $json, 'class_' . $classDefinition->getName() . '_export.json' ); @@ -118,46 +106,17 @@ public function importClassDefinitionFromJson(string $id, string $json): ClassDe public function getClassDefinitionCollection( bool $creatableOnly = false ): array { - $hydrated = []; - foreach ($this->getClassDefinitions($creatableOnly) as $definition) { - $hydratedDefinition = $this->classDefinitionListHydrator->hydrate($definition); - - $this->eventDispatcher->dispatch( - new ClassDefinitionListEvent($hydratedDefinition), - ClassDefinitionListEvent::EVENT_NAME - ); - $hydrated[] = $hydratedDefinition; - } - - return $hydrated; + return $this->hydrateClassDefinitionList($this->getClassDefinitions($creatableOnly)); } /** * {@inheritdoc} */ - public function getClassDefinitions(bool $creatableOnly = false): array + public function getClassDefinitionsWithObjectBricks(): array { - $cds = $this->classDefinitionRepository->getClassDefinitions(); - if (!$creatableOnly) { - return $cds; - } - - $currentUser = $this->securityService->getCurrentUser(); - $allowedDefinitions = []; - foreach ($cds as $definition) { - if ( - !$currentUser->isAllowed( - $definition->getId(), - UserPermissions::CLASS_DEFINITION->value - ) - ) { - continue; - } - - $allowedDefinitions[] = $definition; - } - - return $allowedDefinitions; + return $this->hydrateClassDefinitionList( + $this->classDefinitionRepository->getClassDefinitionsWithObjectBricks() + ); } /** @@ -181,51 +140,67 @@ public function getClassDefinitionById(string $id): ClassDefinition /** * {@inheritdoc} */ - public function getClassDefinitionIdsInsideFolder( - int $folderId - ): array { - $hydratedClassDefinitions = []; - $folder = $this->elementService->getElementById(ElementTypes::TYPE_DATA_OBJECT, $folderId); - if (!$folder instanceof Folder) { - throw new NotFoundException(ElementTypes::TYPE_DATA_OBJECT . ' Folder', $folderId); - } + public function getClassDefinitionBricks(string $id): array + { + $class = $this->classDefinitionRepository->getClassDefinitionById($id); + $bricks = $this->classDefinitionRepository->getObjectBricksByClassName($class->getName()); + $hydratedBricks = []; - foreach ($folder->getDao()->getClasses() as $classDefinition) { - $class = $this->classDefinitionFolderListHydrator->hydrate($classDefinition); + foreach ($bricks as $brick) { + $brickData = $this->classDefinitionHydrator->hydrateBrickData($brick['key'], $brick['fieldname']); $this->eventDispatcher->dispatch( - new ClassDefinitionFolderListEvent($class), - ClassDefinitionEvent::EVENT_NAME + new ClassDefinitionBrickEvent($brickData), + ClassDefinitionBrickEvent::EVENT_NAME ); - $hydratedClassDefinitions[] = $class; + $hydratedBricks[] = $brickData; } - return $hydratedClassDefinitions; + return $hydratedBricks; } /** - * {@inheritdoc} + * @param CoreClassDefinition[] $classDefinitions */ - public function getClassDefinitionBricks(string $id): array + private function hydrateClassDefinitionList(array $classDefinitions): array { - $class = $this->classDefinitionRepository->getClassDefinitionById($id); - $objectBrickList = new ObjectBrickListing(); - $brickDefinitions = $objectBrickList->load(); - $hydratedBricks = []; - foreach ($brickDefinitions as $brickDefinition) { - $brickClasses = $brickDefinition->getClassDefinitions(); - foreach ($brickClasses as $brickClass) { - if ($class->getName() === $brickClass['classname']) { - $brickData = new ClassDefinitionBrickData($brickDefinition->getKey(), $brickClass['fieldname']); - $this->eventDispatcher->dispatch( - new ClassDefinitionBrickEvent($brickData), - ClassDefinitionBrickEvent::EVENT_NAME - ); - $hydratedBricks[] = $brickData; - } + $hydrated = []; + + foreach ($classDefinitions as $definition) { + $hydratedDefinition = $this->classDefinitionListHydrator->hydrate($definition); + + $this->eventDispatcher->dispatch( + new ClassDefinitionListEvent($hydratedDefinition), + ClassDefinitionListEvent::EVENT_NAME + ); + $hydrated[] = $hydratedDefinition; + } + + return $hydrated; + } + + private function getClassDefinitions(bool $creatableOnly = false): array + { + $cds = $this->classDefinitionRepository->getClassDefinitions(); + if (!$creatableOnly) { + return $cds; + } + + $currentUser = $this->securityService->getCurrentUser(); + $allowedDefinitions = []; + foreach ($cds as $definition) { + if ( + !$currentUser->isAllowed( + $definition->getId(), + UserPermissions::CLASS_DEFINITION->value + ) + ) { + continue; } + + $allowedDefinitions[] = $definition; } - return $hydratedBricks; + return $allowedDefinitions; } private function hydrateClassDefinition(CoreClassDefinition $classDefinition): ClassDefinition diff --git a/src/Class/Service/ClassDefinitionServiceInterface.php b/src/Class/Service/ClassDefinitionServiceInterface.php index 0087c6960..46f153756 100644 --- a/src/Class/Service/ClassDefinitionServiceInterface.php +++ b/src/Class/Service/ClassDefinitionServiceInterface.php @@ -24,8 +24,7 @@ use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotWriteableException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException; -use Pimcore\Model\DataObject\ClassDefinition; -use Symfony\Component\HttpFoundation\Response; +use Pimcore\Bundle\StudioBackendBundle\OpenApi\Schema\JsonExport; /** * @internal @@ -51,7 +50,7 @@ public function deleteClassDefinition(string $id): void; /** * @throws NotFoundException */ - public function exportClassDefinition(string $id): Response; + public function exportClassDefinition(string $id): JsonExport; /** * @throws InvalidArgumentException|ElementSavingFailedException|NotFoundException|NotWriteableException @@ -66,9 +65,9 @@ public function getClassDefinitionCollection( ): array; /** - * @return ClassDefinition[] + * @return ClassDefinitionList[] */ - public function getClassDefinitions(bool $creatableOnly = false): array; + public function getClassDefinitionsWithObjectBricks(): array; /** * @throws NotFoundException @@ -84,9 +83,4 @@ public function getClassDefinitionById(string $id): ClassDefinitionSchema; * @throws NotFoundException */ public function getClassDefinitionBricks(string $id): array; - - /** - * @throws NotFoundException - */ - public function getClassDefinitionIdsInsideFolder(int $folderId): array; } diff --git a/src/Class/Service/ClassDefinitionTreeService.php b/src/Class/Service/ClassDefinitionTreeService.php index 5b627f9a5..6ca21c11e 100644 --- a/src/Class/Service/ClassDefinitionTreeService.php +++ b/src/Class/Service/ClassDefinitionTreeService.php @@ -13,12 +13,20 @@ namespace Pimcore\Bundle\StudioBackendBundle\Class\Service; +use Pimcore\Bundle\StudioBackendBundle\Class\Event\ClassDefinitionEvent; +use Pimcore\Bundle\StudioBackendBundle\Class\Event\ClassDefinitionFolderListEvent; use Pimcore\Bundle\StudioBackendBundle\Class\Event\ClassDefinitionTreeEvent; +use Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\Folder\ClassDefinitionFolderItemHydratorInterface; use Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\Tree\FolderNodeHydratorInterface; use Pimcore\Bundle\StudioBackendBundle\Class\Hydrator\Tree\NodeHydratorInterface; +use Pimcore\Bundle\StudioBackendBundle\Class\Repository\ClassDefinitionRepositoryInterface; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinitionTreeNode; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinitionTreeNodeFolder; +use Pimcore\Bundle\StudioBackendBundle\Element\Service\ElementServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; +use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementTypes; use Pimcore\Model\DataObject\ClassDefinition; +use Pimcore\Model\DataObject\Folder; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use function count; @@ -28,16 +36,18 @@ final readonly class ClassDefinitionTreeService implements ClassDefinitionTreeServiceInterface { public function __construct( - private ClassDefinitionServiceInterface $classDefinitionService, + private ClassDefinitionRepositoryInterface $classDefinitionRepository, + private ClassDefinitionFolderItemHydratorInterface $classDefinitionFolderListHydrator, + private ElementServiceInterface $elementService, private EventDispatcherInterface $eventDispatcher, private FolderNodeHydratorInterface $folderNodeHydrator, - private NodeHydratorInterface $nodeHydrator + private NodeHydratorInterface $nodeHydrator, ) { } public function getTree(bool $grouped = false): array { - $cds = $this->classDefinitionService->getClassDefinitions(); + $cds = $this->classDefinitionRepository->getClassDefinitions(); $groups = $this->getGroups($cds); if (empty($groups)) { @@ -60,6 +70,26 @@ public function getTree(bool $grouped = false): array return $hydrated; } + public function getClassDefinitionIdsInsideFolder(int $folderId): array + { + $folder = $this->elementService->getElementById(ElementTypes::TYPE_DATA_OBJECT, $folderId); + if (!$folder instanceof Folder) { + throw new NotFoundException(ElementTypes::TYPE_DATA_OBJECT . ' Folder', $folderId); + } + + $hydratedClassDefinitions = []; + foreach ($folder->getDao()->getClasses() as $classDefinition) { + $class = $this->classDefinitionFolderListHydrator->hydrate($classDefinition); + $this->eventDispatcher->dispatch( + new ClassDefinitionFolderListEvent($class), + ClassDefinitionEvent::EVENT_NAME + ); + $hydratedClassDefinitions[] = $class; + } + + return $hydratedClassDefinitions; + } + /** * @param ClassDefinition[] $classDefinitions */ diff --git a/src/Class/Service/ClassDefinitionTreeServiceInterface.php b/src/Class/Service/ClassDefinitionTreeServiceInterface.php index 7588dea71..c23654afd 100644 --- a/src/Class/Service/ClassDefinitionTreeServiceInterface.php +++ b/src/Class/Service/ClassDefinitionTreeServiceInterface.php @@ -15,6 +15,7 @@ use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinitionTreeNode; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ClassDefinitionTreeNodeFolder; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; /** * @internal @@ -25,4 +26,9 @@ interface ClassDefinitionTreeServiceInterface * @return ClassDefinitionTreeNode[]|ClassDefinitionTreeNodeFolder[] */ public function getTree(bool $grouped = true): array; + + /** + * @throws NotFoundException + */ + public function getClassDefinitionIdsInsideFolder(int $folderId): array; } diff --git a/src/Class/Service/CustomLayoutService.php b/src/Class/Service/CustomLayoutService.php index 6c885bcb1..53adc656a 100644 --- a/src/Class/Service/CustomLayoutService.php +++ b/src/Class/Service/CustomLayoutService.php @@ -79,10 +79,10 @@ public function getCustomLayoutEditorCollection( return $compactLayouts; } - public function getCustomLayoutCollection(string $dataObjectClass): array + public function getCustomLayoutCollection(array $dataObjectClassIds): array { $compactLayouts = []; - $layouts = $this->customLayoutRepository->getCustomLayoutsByClass($dataObjectClass); + $layouts = $this->customLayoutRepository->getCustomLayoutsByClass($dataObjectClassIds); foreach ($layouts as $layout) { $compactLayouts[] = $this->hydrateCompactLayout($layout); @@ -104,7 +104,7 @@ public function getCustomLayout(string $customLayoutId): CustomLayout public function getUserCustomLayouts(DataObject $dataObject, UserInterface $user, array $allowedLayouts): array { $layouts = $this->handleCustomLayoutPermissions( - $this->customLayoutRepository->getCustomLayoutsByClass($dataObject->getClassId()), + $this->customLayoutRepository->getCustomLayoutsByClass([$dataObject->getClassId()]), $user, $allowedLayouts ); @@ -114,9 +114,14 @@ public function getUserCustomLayouts(DataObject $dataObject, UserInterface $user public function deleteCustomLayout(string $customLayoutId): void { - $this->customLayoutRepository->deleteCustomLayout( - $this->customLayoutRepository->getCustomLayout($customLayoutId) - ); + $customLayout = $this->customLayoutRepository->getCustomLayout($customLayoutId); + + $brickLayouts = $this->customLayoutRepository->getBrickLayoutsByBaseId($customLayoutId); + foreach ($brickLayouts as $brickLayout) { + $this->customLayoutRepository->deleteCustomLayout($brickLayout); + } + + $this->customLayoutRepository->deleteCustomLayout($customLayout); } public function createCustomLayout( @@ -175,15 +180,70 @@ public function getMainAdminLayout(): CoreLayout ->setDefault(false); } - public function getExistingLayoutNames(string $classId): array + public function getBrickCustomLayout(string $key, string $customLayoutId): CustomLayout { - $existingNames = []; - $layouts = $this->customLayoutRepository->getCustomLayoutsByClass($classId); - foreach ($layouts as $layout) { - $existingNames[] = $layout->getName(); + return $this->hydrateLayout( + $this->customLayoutRepository->getCustomLayout( + $this->buildBrickLayoutId($key, $customLayoutId) + ) + ); + } + + public function updateBrickCustomLayout( + string $key, + string $customLayoutId, + UpdateParameters $parameters, + ): CustomLayout { + $compositeId = $this->buildBrickLayoutId($key, $customLayoutId); + + try { + $coreLayout = $this->customLayoutRepository->getCustomLayout($compositeId); + } catch (NotFoundException) { + $baseLayout = $this->customLayoutRepository->getCustomLayout($customLayoutId); + $coreLayout = $this->customLayoutRepository->createBrickCustomLayout($compositeId, $baseLayout); } - return $existingNames; + return $this->hydrateLayout( + $this->customLayoutRepository->updateCustomLayout($coreLayout, $parameters) + ); + } + + public function deleteBrickCustomLayout(string $key, string $customLayoutId): void + { + $this->customLayoutRepository->deleteCustomLayout( + $this->customLayoutRepository->getCustomLayout( + $this->buildBrickLayoutId($key, $customLayoutId) + ) + ); + } + + public function exportBrickCustomLayoutAsJson(string $key, string $customLayoutId): Response + { + $compositeId = $this->buildBrickLayoutId($key, $customLayoutId); + $customLayout = $this->customLayoutRepository->getCustomLayout($compositeId); + $json = $this->customLayoutRepository->exportCustomLayoutAsJson($customLayout); + + return $this->downloadService->downloadJSON( + $json, + 'custom_definition_' . $customLayout->getName() . '_export.json' + ); + } + + public function importBrickCustomLayoutFromJson( + string $key, + string $customLayoutId, + string $json, + ): CustomLayout { + $compositeId = $this->buildBrickLayoutId($key, $customLayoutId); + $customLayout = $this->customLayoutRepository->getCustomLayout($compositeId); + $customLayout = $this->customLayoutRepository->importCustomLayoutFromJson($customLayout, $json); + + return $this->hydrateLayout($customLayout); + } + + private function buildBrickLayoutId(string $key, string $customLayoutId): string + { + return $customLayoutId . '.brick.' . $key; } private function hydrateLayout(CoreLayout $layout): CustomLayout diff --git a/src/Class/Service/CustomLayoutServiceInterface.php b/src/Class/Service/CustomLayoutServiceInterface.php index cff87fc6e..a9ac21b47 100644 --- a/src/Class/Service/CustomLayoutServiceInterface.php +++ b/src/Class/Service/CustomLayoutServiceInterface.php @@ -17,6 +17,7 @@ use Pimcore\Bundle\StudioBackendBundle\Class\MappedParameter\UpdateParameters; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\CustomLayout\CustomLayout; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\CustomLayout\CustomLayoutCompact; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\EnvironmentException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ForbiddenException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; @@ -43,9 +44,11 @@ public function getCustomLayoutEditorCollection( ): array; /** + * @param string[] $dataObjectClassIds + * * @return CustomLayoutCompact[] */ - public function getCustomLayoutCollection(string $dataObjectClass): array; + public function getCustomLayoutCollection(array $dataObjectClassIds): array; /** * @throws NotFoundException @@ -91,4 +94,37 @@ public function importCustomLayoutActionFromJson(string $customLayoutId, string public function getMainLayout(): CoreLayout; public function getMainAdminLayout(): CoreLayout; + + /** + * @throws NotFoundException + */ + public function getBrickCustomLayout(string $key, string $customLayoutId): CustomLayout; + + /** + * @throws NotFoundException|NotWriteableException|InvalidArgumentException|EnvironmentException + */ + public function updateBrickCustomLayout( + string $key, + string $customLayoutId, + UpdateParameters $parameters, + ): CustomLayout; + + /** + * @throws NotFoundException|NotWriteableException + */ + public function deleteBrickCustomLayout(string $key, string $customLayoutId): void; + + /** + * @throws NotFoundException + */ + public function exportBrickCustomLayoutAsJson(string $key, string $customLayoutId): Response; + + /** + * @throws NotFoundException|NotWriteableException|JsonEncodingException|InvalidArgumentException + */ + public function importBrickCustomLayoutFromJson( + string $key, + string $customLayoutId, + string $json, + ): CustomLayout; } diff --git a/src/Class/Service/FieldCollection/FieldCollectionService.php b/src/Class/Service/FieldCollection/FieldCollectionService.php index 647dd0561..0e69885e5 100644 --- a/src/Class/Service/FieldCollection/FieldCollectionService.php +++ b/src/Class/Service/FieldCollection/FieldCollectionService.php @@ -23,11 +23,10 @@ use Pimcore\Bundle\StudioBackendBundle\Class\Repository\FieldCollectionRepositoryInterface; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\FieldCollection\FieldCollectionDetail; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\FieldCollectionUsageData; -use Pimcore\Bundle\StudioBackendBundle\Export\Service\DownloadServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\OpenApi\Schema\JsonExport; use Pimcore\Bundle\StudioBackendBundle\Response\Collection; use Pimcore\Model\DataObject\Fieldcollection\Definition; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\Response; use function count; /** @@ -39,7 +38,6 @@ public function __construct( private FieldCollectionRepositoryInterface $fieldCollectionRepository, private FieldCollectionConfigHydratorInterface $fieldCollectionConfigHydrator, private DetailHydratorInterface $detailHydrator, - private DownloadServiceInterface $downloadService, private EventDispatcherInterface $eventDispatcher, ) { } @@ -102,12 +100,12 @@ public function deleteFieldCollection(string $key): void /** * {@inheritdoc} */ - public function exportFieldCollection(string $key): Response + public function exportFieldCollection(string $key): JsonExport { $definition = $this->fieldCollectionRepository->getFieldCollectionByKey($key); $json = $this->fieldCollectionRepository->exportAsJson($definition); - return $this->downloadService->downloadJSON( + return new JsonExport( $json, 'fieldcollection_' . $definition->getKey() . '_export.json' ); diff --git a/src/Class/Service/FieldCollection/FieldCollectionServiceInterface.php b/src/Class/Service/FieldCollection/FieldCollectionServiceInterface.php index 1730a7434..33184edf7 100644 --- a/src/Class/Service/FieldCollection/FieldCollectionServiceInterface.php +++ b/src/Class/Service/FieldCollection/FieldCollectionServiceInterface.php @@ -22,8 +22,8 @@ use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotWriteableException; +use Pimcore\Bundle\StudioBackendBundle\OpenApi\Schema\JsonExport; use Pimcore\Bundle\StudioBackendBundle\Response\Collection; -use Symfony\Component\HttpFoundation\Response; /** * @internal @@ -55,7 +55,7 @@ public function deleteFieldCollection(string $key): void; /** * @throws NotFoundException */ - public function exportFieldCollection(string $key): Response; + public function exportFieldCollection(string $key): JsonExport; /** * @throws ElementSavingFailedException|InvalidArgumentException|NotFoundException|NotWriteableException diff --git a/src/Class/Service/FieldsByTypeService.php b/src/Class/Service/FieldsByTypeService.php new file mode 100644 index 000000000..7dc156b4f --- /dev/null +++ b/src/Class/Service/FieldsByTypeService.php @@ -0,0 +1,120 @@ +columnConfigurationService->getAvailableDataObjectColumnConfiguration( + $parameters->getClassId(), + self::ROOT_FOLDER_ID, + $this->securityService->getCurrentUser() + ); + + $types = $parameters->getTypes(); + $filtered = $this->filterByTypes($allColumns, $types); + + return $this->deduplicateAndHydrate($filtered); + } + + /** + * @param ColumnConfiguration[] $columns + * + * @return FieldByType[] + */ + private function deduplicateAndHydrate(array $columns): array + { + $results = []; + $seenKeys = []; + foreach ($columns as $col) { + $key = $this->hydrator->resolveFieldKey($col); + if (isset($seenKeys[$key])) { + continue; + } + $seenKeys[$key] = true; + $results[] = $this->hydrateField($key); + } + + return $results; + } + + private function hydrateField(string $key): FieldByType + { + $fieldByType = $this->hydrator->hydrate($key); + $this->eventDispatcher->dispatch( + new FieldByTypeEvent($fieldByType), + FieldByTypeEvent::EVENT_NAME, + ); + + return $fieldByType; + } + + /** + * @param ColumnConfiguration[] $columns + * @param string[] $types + * + * @return ColumnConfiguration[] + */ + private function filterByTypes(array $columns, array $types): array + { + return array_filter( + $columns, + function (ColumnConfiguration $col) use ($types): bool { + foreach ($types as $type) { + if ($type === self::OBJECT_BRICK_TYPE && $this->isObjectBrickColumn($col)) { + return true; + } + + if ($col->getFrontendType() === $type) { + return true; + } + } + + return false; + } + ); + } + + private function isObjectBrickColumn(ColumnConfiguration $col): bool + { + return str_contains($col->getType(), self::OBJECT_BRICK_COLUMN_TYPE); + } +} diff --git a/src/Class/Service/FieldsByTypeServiceInterface.php b/src/Class/Service/FieldsByTypeServiceInterface.php new file mode 100644 index 000000000..9af7846c5 --- /dev/null +++ b/src/Class/Service/FieldsByTypeServiceInterface.php @@ -0,0 +1,31 @@ +customLayoutRepository->getCustomLayoutsByClass($classDefinitionId); + $allLayouts = $this->customLayoutRepository->getCustomLayoutsByClass([$classDefinitionId]); foreach ($allLayouts as $layout) { $existingNames[] = $layout->getName(); } diff --git a/src/Class/Service/ObjectBrick/LayoutDefinitionService.php b/src/Class/Service/ObjectBrick/LayoutDefinitionService.php index ae7165c2c..c61c3b897 100644 --- a/src/Class/Service/ObjectBrick/LayoutDefinitionService.php +++ b/src/Class/Service/ObjectBrick/LayoutDefinitionService.php @@ -32,7 +32,6 @@ use Pimcore\Model\DataObject\ClassDefinitionInterface; use Pimcore\Model\DataObject\Concrete; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use function get_class; use function sprintf; /** @@ -60,7 +59,7 @@ public function getLayoutDefinitionsForObject(int $dataObjectId): array $dataObject = $this->dataObjectResolver->getById($dataObjectId); if (!$dataObject instanceof Concrete) { throw new InvalidElementTypeException( - sprintf('DataObject class (%s) is not a concrete object', get_class($dataObject)) + sprintf('DataObject id (%s) is not a concrete object', $dataObjectId) ); } diff --git a/src/Class/Service/ObjectBrick/ObjectBrickService.php b/src/Class/Service/ObjectBrick/ObjectBrickService.php index 1eeaeabc7..d636ae7f6 100644 --- a/src/Class/Service/ObjectBrick/ObjectBrickService.php +++ b/src/Class/Service/ObjectBrick/ObjectBrickService.php @@ -23,11 +23,10 @@ use Pimcore\Bundle\StudioBackendBundle\Class\Repository\ObjectBrickRepositoryInterface; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ObjectBrick\ObjectBrickDetail; use Pimcore\Bundle\StudioBackendBundle\Class\Schema\ObjectBrickUsageData; -use Pimcore\Bundle\StudioBackendBundle\Export\Service\DownloadServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\OpenApi\Schema\JsonExport; use Pimcore\Bundle\StudioBackendBundle\Response\Collection; use Pimcore\Model\DataObject\Objectbrick\Definition; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\Response; use function count; /** @@ -39,7 +38,6 @@ public function __construct( private ObjectBrickRepositoryInterface $objectBrickRepository, private ObjectBrickConfigHydratorInterface $objectBrickConfigHydrator, private DetailHydratorInterface $detailHydrator, - private DownloadServiceInterface $downloadService, private EventDispatcherInterface $eventDispatcher, ) { } @@ -102,12 +100,12 @@ public function deleteObjectBrick(string $key): void /** * {@inheritdoc} */ - public function exportObjectBrick(string $key): Response + public function exportObjectBrick(string $key): JsonExport { $definition = $this->objectBrickRepository->getObjectBrickByKey($key); $json = $this->objectBrickRepository->exportAsJson($definition); - return $this->downloadService->downloadJSON( + return new JsonExport( $json, 'objectbrick_' . $definition->getKey() . '_export.json' ); diff --git a/src/Class/Service/ObjectBrick/ObjectBrickServiceInterface.php b/src/Class/Service/ObjectBrick/ObjectBrickServiceInterface.php index ac7cbe099..8dd8279ae 100644 --- a/src/Class/Service/ObjectBrick/ObjectBrickServiceInterface.php +++ b/src/Class/Service/ObjectBrick/ObjectBrickServiceInterface.php @@ -22,8 +22,8 @@ use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotWriteableException; +use Pimcore\Bundle\StudioBackendBundle\OpenApi\Schema\JsonExport; use Pimcore\Bundle\StudioBackendBundle\Response\Collection; -use Symfony\Component\HttpFoundation\Response; /** * @internal @@ -38,7 +38,7 @@ public function listObjectBricks(): Collection; public function getObjectBrickByKey(string $key): ObjectBrickDetail; /** - * @throws ElementExistsException|ElementSavingFailedException|NotWriteableException + * @throws ElementExistsException|ElementSavingFailedException|InvalidArgumentException|NotWriteableException */ public function createObjectBrick(CreateObjectBrickParameters $parameters): ObjectBrickDetail; @@ -55,7 +55,7 @@ public function deleteObjectBrick(string $key): void; /** * @throws NotFoundException */ - public function exportObjectBrick(string $key): Response; + public function exportObjectBrick(string $key): JsonExport; /** * @throws ElementSavingFailedException|InvalidArgumentException|NotFoundException|NotWriteableException diff --git a/src/Grid/Service/ColumnConfigurationService.php b/src/Grid/Service/ColumnConfigurationService.php index beed45ddd..4624c1b8a 100644 --- a/src/Grid/Service/ColumnConfigurationService.php +++ b/src/Grid/Service/ColumnConfigurationService.php @@ -69,7 +69,9 @@ public function getAvailableDataObjectColumnConfiguration( UserInterface $user ): array { if (($classId === null && $folderId !== null) || ($classId !== null && $folderId === null)) { - throw new InvalidArgumentException('Either both classId and folderId must be set or both must be null'); + throw new InvalidArgumentException( + 'Either both classId and folderId must be set or both must be null' + ); } if ($classId === null && $folderId === null) { diff --git a/src/OpenApi/Attribute/Parameter/Query/ClassIdsParameter.php b/src/OpenApi/Attribute/Parameter/Query/ClassIdsParameter.php new file mode 100644 index 000000000..0e196d599 --- /dev/null +++ b/src/OpenApi/Attribute/Parameter/Query/ClassIdsParameter.php @@ -0,0 +1,32 @@ +json; + } + + public function getFileName(): string + { + return $this->fileName; + } +} diff --git a/translations/studio_api_docs.en.yaml b/translations/studio_api_docs.en.yaml index b54f66f73..15ef31ab3 100644 --- a/translations/studio_api_docs.en.yaml +++ b/translations/studio_api_docs.en.yaml @@ -299,6 +299,12 @@ class_get_available_visible_fields_description: | The {classNames} must be a comma separated list of existing data object class names. class_get_available_visible_fields_summary: Get available visible fields for classes class_get_available_visible_fields_success_response: Successfully retrieved available visible fields +class_get_fields_by_type_description: | + Get class definition fields filtered by one or more field types.
+ The {classId} must be an ID of an existing data object class.
+ The {type} must be a comma-separated list of field types, e.g. manyToOneRelation,objectbricks. +class_get_fields_by_type_summary: Get class definition fields by type +class_get_fields_by_type_success_response: Successfully retrieved class definition fields for the requested types class_get_selected_visible_fields_description: | Get selected visible fields for the specified data object class and relation field.
The {classId} must be an ID of existing data object class.
@@ -1357,6 +1363,41 @@ class_object_brick_get_usages_description: | Returns all classes and fields that reference this object brick. class_object_brick_get_usages_summary: Get usages of an object brick definition class_object_brick_get_usages_success_response: List of classes and fields using the object brick +class_object_brick_classes_description: | + Get a collection of class definitions that have at least one ObjectBricks field definition. +class_object_brick_classes_summary: Get class definitions with ObjectBricks fields +class_object_brick_classes_success_response: Collection of class definitions with ObjectBricks fields +class_object_brick_custom_layout_get_description: | + Get a custom layout for a specific object brick by brick key and custom layout ID. +class_object_brick_custom_layout_get_summary: Get object brick custom layout +class_object_brick_custom_layout_get_key: Object brick key +class_object_brick_custom_layout_get_layout_id: Id of the base custom layout +class_object_brick_custom_layout_get_success_response: Object brick custom layout data +class_object_brick_custom_layout_update_description: | + Update a custom layout for a specific object brick. If the brick layout does not exist yet, it will be + auto-created from the base custom layout and then updated with the provided data. +class_object_brick_custom_layout_update_summary: Update object brick custom layout +class_object_brick_custom_layout_update_key: Object brick key +class_object_brick_custom_layout_update_layout_id: Id of the base custom layout +class_object_brick_custom_layout_update_success_response: Updated object brick custom layout +class_object_brick_custom_layout_delete_description: | + Delete a custom layout for a specific object brick by brick key and custom layout ID. +class_object_brick_custom_layout_delete_summary: Delete object brick custom layout +class_object_brick_custom_layout_delete_key: Object brick key +class_object_brick_custom_layout_delete_layout_id: Id of the base custom layout +class_object_brick_custom_layout_delete_success_response: Object brick custom layout deleted +class_object_brick_custom_layout_export_description: | + Export a custom layout for a specific object brick as JSON file download. +class_object_brick_custom_layout_export_summary: Export object brick custom layout as JSON +class_object_brick_custom_layout_export_key: Object brick key +class_object_brick_custom_layout_export_layout_id: Id of the base custom layout +class_object_brick_custom_layout_export_success_response: Object brick custom layout data as JSON +class_object_brick_custom_layout_import_description: | + Import a custom layout for a specific object brick from a previously exported JSON file. +class_object_brick_custom_layout_import_summary: Import object brick custom layout from JSON +class_object_brick_custom_layout_import_key: Object brick key +class_object_brick_custom_layout_import_layout_id: Id of the base custom layout +class_object_brick_custom_layout_import_success_response: Successfully imported object brick custom layout unit_quantity_value_list_description: | List of available quantity value units unit_quantity_value_list_summary: List of available quantity value units