diff --git a/src/bundle/Controller/Content/ContentTreeController.php b/src/bundle/Controller/Content/ContentTreeController.php index f27bd5afcc..b372a2c9e8 100644 --- a/src/bundle/Controller/Content/ContentTreeController.php +++ b/src/bundle/Controller/Content/ContentTreeController.php @@ -13,12 +13,14 @@ use Ibexa\AdminUi\REST\Value\ContentTree\Node; use Ibexa\AdminUi\REST\Value\ContentTree\NodeExtendedInfo; use Ibexa\AdminUi\REST\Value\ContentTree\Root; +use Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface; use Ibexa\AdminUi\Specification\ContentType\ContentTypeIsUser; use Ibexa\AdminUi\UI\Module\ContentTree\NodeFactory; use Ibexa\Contracts\AdminUi\Permission\PermissionCheckerInterface; use Ibexa\Contracts\Core\Limitation\Target; use Ibexa\Contracts\Core\Repository\LocationService; use Ibexa\Contracts\Core\Repository\PermissionResolver; +use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\Core\Repository\Values\Content\Query; use Ibexa\Contracts\Core\Repository\Values\User\Limitation; @@ -44,13 +46,16 @@ class ContentTreeController extends RestController private ConfigResolverInterface $configResolver; + private SiteaccessResolverInterface $siteaccessResolver; + public function __construct( LocationService $locationService, PermissionCheckerInterface $permissionChecker, LookupLimitationsTransformer $lookupLimitationsTransformer, NodeFactory $contentTreeNodeFactory, PermissionResolver $permissionResolver, - ConfigResolverInterface $configResolver + ConfigResolverInterface $configResolver, + SiteaccessResolverInterface $siteaccessResolver ) { $this->locationService = $locationService; $this->permissionChecker = $permissionChecker; @@ -58,6 +63,7 @@ public function __construct( $this->contentTreeNodeFactory = $contentTreeNodeFactory; $this->permissionResolver = $permissionResolver; $this->configResolver = $configResolver; + $this->siteaccessResolver = $siteaccessResolver; } /** @@ -145,7 +151,15 @@ public function loadNodeExtendedInfoAction(Location $location): NodeExtendedInfo { $locationPermissionRestrictions = $this->getLocationPermissionRestrictions($location); - return new NodeExtendedInfo($locationPermissionRestrictions); + $content = $location->getContent(); + $versionInfo = $content->getVersionInfo(); + $translations = $versionInfo->languageCodes; + $previewableTranslations = array_filter( + $translations, + fn (string $languageCode): bool => $this->isPreviewable($location, $content, $languageCode) + ); + + return new NodeExtendedInfo($locationPermissionRestrictions, $previewableTranslations); } /** @@ -245,6 +259,37 @@ private function canUserHideContent(Location $location): bool [$target] ); } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ + private function isPreviewable( + Location $location, + Content $content, + string $languageCode + ): bool { + $canPreview = $this->permissionResolver->canUser( + 'content', + 'versionread', + $content, + [$location] + ); + + if (!$canPreview) { + return false; + } + + $versionNo = $content->getVersionInfo()->getVersionNo(); + + $siteAccesses = $this->siteaccessResolver->getSiteAccessesListForLocation( + $location, + $versionNo, + $languageCode + ); + + return !empty($siteAccesses); + } } class_alias(ContentTreeController::class, 'EzSystems\EzPlatformAdminUiBundle\Controller\Content\ContentTreeController'); diff --git a/src/lib/REST/Output/ValueObjectVisitor/ContentTree/Node.php b/src/lib/REST/Output/ValueObjectVisitor/ContentTree/Node.php index 3e98d51fbb..41e666161f 100644 --- a/src/lib/REST/Output/ValueObjectVisitor/ContentTree/Node.php +++ b/src/lib/REST/Output/ValueObjectVisitor/ContentTree/Node.php @@ -41,7 +41,7 @@ public function visit(Visitor $visitor, Generator $generator, $data) $generator->valueElement('translations', implode(',', $data->translations)); - $generator->valueElement('previewableTranslations', implode(',', $data->previewableTranslations)); + $generator->valueElement('mainLanguageCode', $data->mainLanguageCode); $generator->startValueElement('name', $data->name); $generator->endValueElement('name'); diff --git a/src/lib/REST/Output/ValueObjectVisitor/ContentTree/NodeExtendedInfoVisitor.php b/src/lib/REST/Output/ValueObjectVisitor/ContentTree/NodeExtendedInfoVisitor.php index 34e3057e6f..3d073f88d7 100644 --- a/src/lib/REST/Output/ValueObjectVisitor/ContentTree/NodeExtendedInfoVisitor.php +++ b/src/lib/REST/Output/ValueObjectVisitor/ContentTree/NodeExtendedInfoVisitor.php @@ -30,10 +30,27 @@ public function visit(Visitor $visitor, Generator $generator, $data): void $visitor->setStatus(Response::HTTP_OK); $this->buildPermissionNode($data->getPermissionRestrictions(), $generator); + $this->buildPreviewableTranslationsNode($data->getPreviewableTranslations(), $generator); $generator->endObjectElement(self::MAIN_ELEMENT); } + /** + * @param string[] $previewableTranslations + */ + protected function buildPreviewableTranslationsNode( + array $previewableTranslations, + Generator $generator + ): void { + $generator->startHashElement('previewableTranslations'); + $generator->startList('values'); + foreach ($previewableTranslations as $value) { + $generator->valueElement('value', $value); + } + $generator->endList('values'); + $generator->endHashElement('previewableTranslations'); + } + /** * @phpstan-param TPermissionRestrictions $permissionRestrictions */ diff --git a/src/lib/REST/Value/ContentTree/Node.php b/src/lib/REST/Value/ContentTree/Node.php index 30201f4274..9aa1755652 100644 --- a/src/lib/REST/Value/ContentTree/Node.php +++ b/src/lib/REST/Value/ContentTree/Node.php @@ -26,9 +26,6 @@ class Node extends RestValue /** @var string[] */ public array $translations; - /** @var string[] */ - public array $previewableTranslations; - /** @var string */ public $name; @@ -56,12 +53,13 @@ class Node extends RestValue public string $pathString; + public string $mainLanguageCode; + /** * @param int $depth * @param int $locationId * @param int $contentId * @param string[] $translations - * @param string[] $previewableTranslations * @param string $name * @param string $contentTypeIdentifier * @param bool $isContainer @@ -76,7 +74,6 @@ public function __construct( int $contentId, int $versionNo, array $translations, - array $previewableTranslations, string $name, string $contentTypeIdentifier, bool $isContainer, @@ -85,6 +82,7 @@ public function __construct( int $totalChildrenCount, int $reverseRelationsCount, bool $isBookmarked, + string $mainLanguageCode, array $children = [], string $pathString = '' ) { @@ -93,7 +91,6 @@ public function __construct( $this->contentId = $contentId; $this->versionNo = $versionNo; $this->translations = $translations; - $this->previewableTranslations = $previewableTranslations; $this->name = $name; $this->isInvisible = $isInvisible; $this->contentTypeIdentifier = $contentTypeIdentifier; @@ -104,6 +101,7 @@ public function __construct( $this->isBookmarked = $isBookmarked; $this->children = $children; $this->pathString = $pathString; + $this->mainLanguageCode = $mainLanguageCode; } } diff --git a/src/lib/REST/Value/ContentTree/NodeExtendedInfo.php b/src/lib/REST/Value/ContentTree/NodeExtendedInfo.php index 5fc578f026..7258507c65 100644 --- a/src/lib/REST/Value/ContentTree/NodeExtendedInfo.php +++ b/src/lib/REST/Value/ContentTree/NodeExtendedInfo.php @@ -28,13 +28,20 @@ final class NodeExtendedInfo extends RestValue /** @phpstan-var TPermissionRestrictions|null */ private ?array $permissions; + /** @var string[] */ + private array $previewableTranslations; + /** * @phpstan-param TPermissionRestrictions|null $permissions + * + * @param string[] $previewableTranslation */ public function __construct( - ?array $permissions = null + ?array $permissions = null, + array $previewableTranslation = [] ) { $this->permissions = $permissions; + $this->previewableTranslations = $previewableTranslation; } /** @@ -44,4 +51,12 @@ public function getPermissionRestrictions(): ?array { return $this->permissions; } + + /** + * @return string[] + */ + public function getPreviewableTranslations(): array + { + return $this->previewableTranslations; + } } diff --git a/src/lib/UI/Module/ContentTree/NodeFactory.php b/src/lib/UI/Module/ContentTree/NodeFactory.php index 5f7ef7e1da..73c2a11fc9 100644 --- a/src/lib/UI/Module/ContentTree/NodeFactory.php +++ b/src/lib/UI/Module/ContentTree/NodeFactory.php @@ -10,7 +10,6 @@ use Ibexa\AdminUi\REST\Value\ContentTree\LoadSubtreeRequestNode; use Ibexa\AdminUi\REST\Value\ContentTree\Node; -use Ibexa\AdminUi\Siteaccess\SiteaccessResolverInterface; use Ibexa\Contracts\Core\Repository\BookmarkService; use Ibexa\Contracts\Core\Repository\ContentService; use Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException; @@ -63,8 +62,6 @@ final class NodeFactory private Repository $repository; - private SiteaccessResolverInterface $siteaccessResolver; - /** @var int */ private $maxLocationIdsInSingleAggregation; @@ -76,7 +73,6 @@ public function __construct( ConfigResolverInterface $configResolver, PermissionResolver $permissionResolver, Repository $repository, - SiteaccessResolverInterface $siteaccessResolver, int $maxLocationIdsInSingleAggregation ) { $this->bookmarkService = $bookmarkService; @@ -86,7 +82,6 @@ public function __construct( $this->configResolver = $configResolver; $this->permissionResolver = $permissionResolver; $this->repository = $repository; - $this->siteaccessResolver = $siteaccessResolver; $this->maxLocationIdsInSingleAggregation = $maxLocationIdsInSingleAggregation; } @@ -397,10 +392,7 @@ private function buildNode( } $translations = $versionInfo->getLanguageCodes(); - $previewableTranslations = array_filter( - $translations, - fn (string $languageCode): bool => $this->isPreviewable($location, $content, $languageCode) - ); + $mainLanguageCode = $versionInfo->getContentInfo()->getMainLanguageCode(); return new Node( $depth, @@ -408,7 +400,6 @@ private function buildNode( $location->getContentId(), $versionInfo->getVersionNo(), $translations, - $previewableTranslations, '', // node name will be provided later by `supplyTranslatedContentName` method null !== $contentType ? $contentType->getIdentifier() : '', null === $contentType || $contentType->isContainer(), @@ -417,6 +408,7 @@ private function buildNode( $totalChildrenCount, $this->getReverseRelationsCount($contentInfo), isset($bookmarkLocations[$location->getId()]), + $mainLanguageCode, $children, $location->getPathString() ); @@ -468,33 +460,6 @@ private function supplyChildrenCount( $this->supplyChildrenCount($child, $aggregationResult, $requestFilter); } } - - /** - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException - */ - private function isPreviewable( - Location $location, - Content $content, - string $languageCode - ): bool { - $versionNo = $content->getVersionInfo()->getVersionNo(); - - $siteAccesses = $this->siteaccessResolver->getSiteAccessesListForLocation( - $location, - $versionNo, - $languageCode - ); - - $canPreview = $this->permissionResolver->canUser( - 'content', - 'versionread', - $content, - [$location] - ); - - return $canPreview && !empty($siteAccesses); - } } class_alias(NodeFactory::class, 'EzSystems\EzPlatformAdminUi\UI\Module\ContentTree\NodeFactory'); diff --git a/tests/integration/REST/GetContentTreeExtendedInfoTest.php b/tests/integration/REST/GetContentTreeExtendedInfoTest.php index 9e8472a918..847e5465b4 100644 --- a/tests/integration/REST/GetContentTreeExtendedInfoTest.php +++ b/tests/integration/REST/GetContentTreeExtendedInfoTest.php @@ -35,6 +35,7 @@ protected function setUp(): void [ 'user/login' => [], 'content/read' => [], + 'content/versionread' => [], 'content/create' => [ new ContentTypeLimitation( ['limitationValues' => ['1', '16']] diff --git a/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.json b/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.json index 632e43c99e..753b688a84 100644 --- a/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.json +++ b/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.json @@ -53,11 +53,25 @@ "hasAccess" ] } - } + }, + "previewableTranslations": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": [ + { + "type": "string" + } + ] + } + } + } }, "required": [ "_media-type", - "permissions" + "permissions", + "previewableTranslations" ] } }, diff --git a/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.xsd b/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.xsd index 38cebade76..cd01c211ce 100644 --- a/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.xsd +++ b/tests/integration/Resources/REST/Schemas/ContentTreeNodeExtendedInfo.xsd @@ -26,6 +26,13 @@ + + + + + + + diff --git a/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.json b/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.json index ceef092019..85be359381 100644 --- a/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.json +++ b/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.json @@ -35,6 +35,9 @@ "_name": "hide", "hasAccess": false } - ] + ], + "previewableTranslations": { + "values": ["eng-GB"] + } } } diff --git a/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.xml b/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.xml index 64fed756ca..926eeef47f 100644 --- a/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.xml +++ b/tests/integration/Resources/REST/Snapshots/ContentTreeNodeExtendedInfo.xml @@ -23,4 +23,7 @@ false + + eng-GB + diff --git a/tests/integration/Resources/REST/Snapshots/ContentTreeRoot.json b/tests/integration/Resources/REST/Snapshots/ContentTreeRoot.json index 2fdf2a13f9..1a7c7c051d 100644 --- a/tests/integration/Resources/REST/Snapshots/ContentTreeRoot.json +++ b/tests/integration/Resources/REST/Snapshots/ContentTreeRoot.json @@ -5,10 +5,10 @@ { "_media-type": "application\/vnd.ibexa.api.ContentTreeNode+json", "locationId": 2, + "mainLanguageCode": "eng-GB", "contentId": 57, "versionNo": 1, "translations": "eng-GB", - "previewableTranslations": "eng-GB", "name": "Home", "pathString": "/1/2/", "contentTypeIdentifier": "landing_page", @@ -22,10 +22,10 @@ { "_media-type": "application\/vnd.ibexa.api.ContentTreeNode+json", "locationId": 60, + "mainLanguageCode": "eng-GB", "contentId": 58, "versionNo": 1, "translations": "eng-GB", - "previewableTranslations": "eng-GB", "name": "Contact Us", "pathString": "/1/2/60/", "contentTypeIdentifier": "feedback_form",