Skip to content

Commit 27f2f13

Browse files
backend change for determing view and edit rights on related objects
1 parent ec80358 commit 27f2f13

8 files changed

Lines changed: 116 additions & 15 deletions

File tree

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "pimcore/studio-backend-bundle",
3+
"version": "2025.4.5",
34
"license": "proprietary",
45
"type": "pimcore-bundle",
56
"description": "Pimcore Studio Backend Bundle",

config/data_objects.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ services:
2525
Pimcore\Bundle\StudioBackendBundle\DataObject\Service\ReplaceServiceInterface:
2626
class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\ReplaceService
2727

28+
Pimcore\Bundle\StudioBackendBundle\DataObject\Service\RelationNormalizationContext: ~
29+
2830
Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataServiceInterface:
2931
class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataService
3032

src/DataIndex/Grid/GridSearch.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,21 @@ public function searchElementsForUser(
113113
UserInterface $user
114114
): AssetSearchResult|DataObjectSearchResult|DocumentSearchResult {
115115
$type = $this->getStudioElementType($type);
116-
/** @var AssetQueryInterface|DataObjectQueryInterface|DocumentQueryInterface $query */
117-
$query = $this->getSearchQuery($type, $gridParameter, $user);
116+
117+
// When filtering by explicit IDs (relation context), skip workspace and path
118+
// restrictions — the user already has a reference to these objects via the
119+
// relation, so showing their field data does not expose new information.
120+
$filter = $gridParameter->getFilters();
121+
$byIds = $filter->getSimpleColumnFilterByType('system.ids') !== null;
122+
123+
if ($byIds) {
124+
$query = $this->queryFactory->create($type);
125+
/** @var AssetQueryInterface|DataObjectQueryInterface|DocumentQueryInterface $query */
126+
$query = $this->filterService->applyFilters($query, $filter, $type);
127+
} else {
128+
/** @var AssetQueryInterface|DataObjectQueryInterface|DocumentQueryInterface $query */
129+
$query = $this->getSearchQuery($type, $gridParameter, $user);
130+
}
118131

119132
return match($type) {
120133
ElementTypes::TYPE_ASSET => $this->assetSearchService->searchAssets($query),

src/DataObject/Service/DataService.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function __construct(
5454
private DataAdapterServiceInterface $dataAdapterService,
5555
private InheritanceServiceInterface $inheritanceService,
5656
private WorkflowDetailsServiceInterface $workflowDetailsService,
57+
private RelationNormalizationContext $normalizationContext,
5758
) {
5859
}
5960

@@ -286,13 +287,18 @@ private function removeEmptyValues(array $previewFields): array
286287
*/
287288
private function getDetailObjectData(Concrete $dataObject, array $fieldDefinitions): array
288289
{
289-
$data = [];
290-
foreach ($fieldDefinitions as $key => $fieldDefinition) {
291-
$data[$key] = $this->getDetailValue(
292-
$dataObject,
293-
$this->getValidFieldValue($dataObject, $key),
294-
$fieldDefinition
295-
);
290+
$this->normalizationContext->setParent($dataObject);
291+
try {
292+
$data = [];
293+
foreach ($fieldDefinitions as $key => $fieldDefinition) {
294+
$data[$key] = $this->getDetailValue(
295+
$dataObject,
296+
$this->getValidFieldValue($dataObject, $key),
297+
$fieldDefinition
298+
);
299+
}
300+
} finally {
301+
$this->normalizationContext->setParent(null);
296302
}
297303

298304
return $data;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Service;
15+
16+
use Pimcore\Model\DataObject\Concrete;
17+
18+
/**
19+
* Holds the parent (owner) object currently being normalized so that nested
20+
* relation adapters can check save/publish permissions against it without
21+
* requiring the parent to be threaded through every normalize() signature.
22+
*
23+
* @internal
24+
*/
25+
class RelationNormalizationContext
26+
{
27+
private ?Concrete $parent = null;
28+
29+
public function setParent(?Concrete $parent): void
30+
{
31+
$this->parent = $parent;
32+
}
33+
34+
public function getParent(): ?Concrete
35+
{
36+
return $this->parent;
37+
}
38+
}

src/Element/Schema/RelatedElementData.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
#[Schema(
2323
schema: 'RelatedElementData',
2424
title: 'RelatedElementData',
25-
required: ['id', 'type', 'subtype', 'fullPath', 'isPublished'],
25+
required: ['id', 'type', 'subtype', 'fullPath', 'isPublished', 'hasAccess', 'canEdit'],
2626
type: 'object'
2727
)]
2828
final readonly class RelatedElementData
@@ -38,6 +38,10 @@ public function __construct(
3838
private string $fullPath,
3939
#[Property(description: 'Is the element published', type: 'boolean', example: true)]
4040
private ?bool $isPublished = null,
41+
#[Property(description: 'Whether the current user has view access to the element', type: 'boolean', example: true)]
42+
private bool $hasAccess = true,
43+
#[Property(description: 'Whether the current user has save or publish permission on the element', type: 'boolean', example: true)]
44+
private bool $canEdit = true,
4145
) {
4246
}
4347

@@ -65,4 +69,14 @@ public function getIsPublished(): ?bool
6569
{
6670
return $this->isPublished;
6771
}
72+
73+
public function getHasAccess(): bool
74+
{
75+
return $this->hasAccess;
76+
}
77+
78+
public function getCanEdit(): bool
79+
{
80+
return $this->canEdit;
81+
}
6882
}

src/Element/Service/ElementDataService.php

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,58 @@
1313

1414
namespace Pimcore\Bundle\StudioBackendBundle\Element\Service;
1515

16+
use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\RelationNormalizationContext;
1617
use Pimcore\Bundle\StudioBackendBundle\Element\Schema\RelatedElementData;
18+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException;
19+
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
1720
use Pimcore\Bundle\StudioBackendBundle\Util\Trait\ElementProviderTrait;
1821
use Pimcore\Model\DataObject\Concrete;
1922
use Pimcore\Model\Document;
2023
use Pimcore\Model\Element\ElementInterface;
24+
use Pimcore\Model\User;
2125

2226
/**
2327
* @internal
2428
*/
25-
final readonly class ElementDataService implements ElementDataServiceInterface
29+
readonly class ElementDataService implements ElementDataServiceInterface
2630
{
2731
use ElementProviderTrait;
2832

33+
public function __construct(
34+
protected SecurityServiceInterface $securityService,
35+
protected RelationNormalizationContext $normalizationContext,
36+
) {
37+
}
38+
2939
public function getRelatedElementData(ElementInterface $element): RelatedElementData
3040
{
41+
$hasAccess = true;
42+
$canEdit = true;
43+
try {
44+
$user = $this->securityService->getCurrentUser();
45+
/** @var User $user */
46+
$hasAccess = $element->isAllowed('view', $user);
47+
$parent = $this->normalizationContext->getParent();
48+
if ($parent !== null) {
49+
$canEdit = $parent->isAllowed('save', $user) || $parent->isAllowed('publish', $user);
50+
}
51+
} catch (UserNotFoundException) {
52+
$hasAccess = false;
53+
$canEdit = false;
54+
}
55+
3156
return new RelatedElementData(
3257
$element->getId(),
3358
$this->getElementType($element, true),
3459
$this->getSubType($element),
3560
$element->getRealFullPath(),
36-
$this->getPublished($element)
61+
$this->getPublished($element),
62+
$hasAccess,
63+
$canEdit,
3764
);
3865
}
3966

40-
private function getSubType(ElementInterface $element): string
67+
protected function getSubType(ElementInterface $element): string
4168
{
4269
if ($element instanceof Concrete) {
4370
return $element->getClassName();
@@ -46,7 +73,7 @@ private function getSubType(ElementInterface $element): string
4673
return $element->getType();
4774
}
4875

49-
private function getPublished(ElementInterface $element): ?bool
76+
protected function getPublished(ElementInterface $element): ?bool
5077
{
5178
if ($element instanceof Concrete || $element instanceof Document) {
5279
return $element->getPublished();

src/Util/Trait/ElementProviderTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ private function getElementClass(ElementInterface $element): string
103103
/**
104104
* @throws InvalidElementTypeException
105105
*/
106-
private function getElementType(ElementInterface $element, bool $getCoreType = false): string
106+
protected function getElementType(ElementInterface $element, bool $getCoreType = false): string
107107
{
108108
return match (true) {
109109
$element instanceof Asset => ElementTypes::TYPE_ASSET,

0 commit comments

Comments
 (0)