diff --git a/composer.json b/composer.json index 7354d05f0..9dc6564e4 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "pimcore/studio-backend-bundle", + "version": "2025.4.5", "license": "proprietary", "type": "pimcore-bundle", "description": "Pimcore Studio Backend Bundle", diff --git a/config/data_objects.yaml b/config/data_objects.yaml index a3acda580..c03163524 100644 --- a/config/data_objects.yaml +++ b/config/data_objects.yaml @@ -25,6 +25,8 @@ services: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\ReplaceServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\ReplaceService + Pimcore\Bundle\StudioBackendBundle\DataObject\Service\RelationNormalizationContext: ~ + Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataService diff --git a/src/DataIndex/Grid/GridSearch.php b/src/DataIndex/Grid/GridSearch.php index 35f4015ec..bd25d854b 100644 --- a/src/DataIndex/Grid/GridSearch.php +++ b/src/DataIndex/Grid/GridSearch.php @@ -113,8 +113,21 @@ public function searchElementsForUser( UserInterface $user ): AssetSearchResult|DataObjectSearchResult|DocumentSearchResult { $type = $this->getStudioElementType($type); - /** @var AssetQueryInterface|DataObjectQueryInterface|DocumentQueryInterface $query */ - $query = $this->getSearchQuery($type, $gridParameter, $user); + + // When filtering by explicit IDs (relation context), skip workspace and path + // restrictions — the user already has a reference to these objects via the + // relation, so showing their field data does not expose new information. + $filter = $gridParameter->getFilters(); + $byIds = $filter->getSimpleColumnFilterByType('system.ids') !== null; + + if ($byIds) { + $query = $this->queryFactory->create($type); + /** @var AssetQueryInterface|DataObjectQueryInterface|DocumentQueryInterface $query */ + $query = $this->filterService->applyFilters($query, $filter, $type); + } else { + /** @var AssetQueryInterface|DataObjectQueryInterface|DocumentQueryInterface $query */ + $query = $this->getSearchQuery($type, $gridParameter, $user); + } return match($type) { ElementTypes::TYPE_ASSET => $this->assetSearchService->searchAssets($query), diff --git a/src/DataObject/Service/DataService.php b/src/DataObject/Service/DataService.php index 3316c2020..fdcec7dbf 100644 --- a/src/DataObject/Service/DataService.php +++ b/src/DataObject/Service/DataService.php @@ -54,6 +54,7 @@ public function __construct( private DataAdapterServiceInterface $dataAdapterService, private InheritanceServiceInterface $inheritanceService, private WorkflowDetailsServiceInterface $workflowDetailsService, + private RelationNormalizationContext $normalizationContext, ) { } @@ -286,13 +287,18 @@ private function removeEmptyValues(array $previewFields): array */ private function getDetailObjectData(Concrete $dataObject, array $fieldDefinitions): array { - $data = []; - foreach ($fieldDefinitions as $key => $fieldDefinition) { - $data[$key] = $this->getDetailValue( - $dataObject, - $this->getValidFieldValue($dataObject, $key), - $fieldDefinition - ); + $this->normalizationContext->setParent($dataObject); + try { + $data = []; + foreach ($fieldDefinitions as $key => $fieldDefinition) { + $data[$key] = $this->getDetailValue( + $dataObject, + $this->getValidFieldValue($dataObject, $key), + $fieldDefinition + ); + } + } finally { + $this->normalizationContext->setParent(null); } return $data; diff --git a/src/DataObject/Service/RelationNormalizationContext.php b/src/DataObject/Service/RelationNormalizationContext.php new file mode 100644 index 000000000..c2cc7e928 --- /dev/null +++ b/src/DataObject/Service/RelationNormalizationContext.php @@ -0,0 +1,38 @@ +parent = $parent; + } + + public function getParent(): ?Concrete + { + return $this->parent; + } +} diff --git a/src/Element/Schema/RelatedElementData.php b/src/Element/Schema/RelatedElementData.php index f22ac8ac2..fd4cf4628 100644 --- a/src/Element/Schema/RelatedElementData.php +++ b/src/Element/Schema/RelatedElementData.php @@ -22,7 +22,7 @@ #[Schema( schema: 'RelatedElementData', title: 'RelatedElementData', - required: ['id', 'type', 'subtype', 'fullPath', 'isPublished'], + required: ['id', 'type', 'subtype', 'fullPath', 'isPublished', 'hasAccess', 'canEdit'], type: 'object' )] final readonly class RelatedElementData @@ -38,6 +38,10 @@ public function __construct( private string $fullPath, #[Property(description: 'Is the element published', type: 'boolean', example: true)] private ?bool $isPublished = null, + #[Property(description: 'Whether the current user has view access to the element', type: 'boolean', example: true)] + private bool $hasAccess = true, + #[Property(description: 'Whether the current user has save or publish permission on the element', type: 'boolean', example: true)] + private bool $canEdit = true, ) { } @@ -65,4 +69,14 @@ public function getIsPublished(): ?bool { return $this->isPublished; } + + public function getHasAccess(): bool + { + return $this->hasAccess; + } + + public function getCanEdit(): bool + { + return $this->canEdit; + } } diff --git a/src/Element/Service/ElementDataService.php b/src/Element/Service/ElementDataService.php index 74ee90357..f1b742ecb 100644 --- a/src/Element/Service/ElementDataService.php +++ b/src/Element/Service/ElementDataService.php @@ -13,31 +13,58 @@ namespace Pimcore\Bundle\StudioBackendBundle\Element\Service; +use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\RelationNormalizationContext; use Pimcore\Bundle\StudioBackendBundle\Element\Schema\RelatedElementData; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException; +use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Util\Trait\ElementProviderTrait; use Pimcore\Model\DataObject\Concrete; use Pimcore\Model\Document; use Pimcore\Model\Element\ElementInterface; +use Pimcore\Model\User; /** * @internal */ -final readonly class ElementDataService implements ElementDataServiceInterface +readonly class ElementDataService implements ElementDataServiceInterface { use ElementProviderTrait; + public function __construct( + protected SecurityServiceInterface $securityService, + protected RelationNormalizationContext $normalizationContext, + ) { + } + public function getRelatedElementData(ElementInterface $element): RelatedElementData { + $hasAccess = true; + $canEdit = true; + try { + $user = $this->securityService->getCurrentUser(); + /** @var User $user */ + $hasAccess = $element->isAllowed('view', $user); + $parent = $this->normalizationContext->getParent(); + if ($parent !== null) { + $canEdit = $parent->isAllowed('save', $user) || $parent->isAllowed('publish', $user); + } + } catch (UserNotFoundException) { + $hasAccess = false; + $canEdit = false; + } + return new RelatedElementData( $element->getId(), $this->getElementType($element, true), $this->getSubType($element), $element->getRealFullPath(), - $this->getPublished($element) + $this->getPublished($element), + $hasAccess, + $canEdit, ); } - private function getSubType(ElementInterface $element): string + protected function getSubType(ElementInterface $element): string { if ($element instanceof Concrete) { return $element->getClassName(); @@ -46,7 +73,7 @@ private function getSubType(ElementInterface $element): string return $element->getType(); } - private function getPublished(ElementInterface $element): ?bool + protected function getPublished(ElementInterface $element): ?bool { if ($element instanceof Concrete || $element instanceof Document) { return $element->getPublished(); diff --git a/src/Util/Trait/ElementProviderTrait.php b/src/Util/Trait/ElementProviderTrait.php index 2b972546c..875f431f1 100644 --- a/src/Util/Trait/ElementProviderTrait.php +++ b/src/Util/Trait/ElementProviderTrait.php @@ -103,7 +103,7 @@ private function getElementClass(ElementInterface $element): string /** * @throws InvalidElementTypeException */ - private function getElementType(ElementInterface $element, bool $getCoreType = false): string + protected function getElementType(ElementInterface $element, bool $getCoreType = false): string { return match (true) { $element instanceof Asset => ElementTypes::TYPE_ASSET,