diff --git a/src/contracts/Persistence/Content/Location/Handler.php b/src/contracts/Persistence/Content/Location/Handler.php index c38accb6ee..b166f2fa68 100644 --- a/src/contracts/Persistence/Content/Location/Handler.php +++ b/src/contracts/Persistence/Content/Location/Handler.php @@ -110,6 +110,8 @@ public function loadParentLocationsForDraftContent($contentId); */ public function copySubtree($sourceId, $destinationParentId); + public function getSubtreeSize(string $path): int; + /** * Moves location identified by $sourceId into new parent identified by $destinationParentId. * diff --git a/src/contracts/Repository/Decorator/LocationServiceDecorator.php b/src/contracts/Repository/Decorator/LocationServiceDecorator.php index f744b04bea..5f11c4cab3 100644 --- a/src/contracts/Repository/Decorator/LocationServiceDecorator.php +++ b/src/contracts/Repository/Decorator/LocationServiceDecorator.php @@ -87,6 +87,11 @@ public function getLocationChildCount(Location $location): int return $this->innerService->getLocationChildCount($location); } + public function getSubtreeSize(Location $location): int + { + return $this->innerService->getSubtreeSize($location); + } + public function createLocation( ContentInfo $contentInfo, LocationCreateStruct $locationCreateStruct diff --git a/src/contracts/Repository/LocationService.php b/src/contracts/Repository/LocationService.php index 4df59abd2f..05231de425 100644 --- a/src/contracts/Repository/LocationService.php +++ b/src/contracts/Repository/LocationService.php @@ -125,6 +125,13 @@ public function loadParentLocationsForDraftContent(VersionInfo $versionInfo, ?ar */ public function getLocationChildCount(Location $location): int; + /** + * Return the subtree size of a given location. + * + * Warning! This method is not permission aware by design. + */ + public function getSubtreeSize(Location $location): int; + /** * Creates the new $location in the content repository for the given content. * diff --git a/src/contracts/Repository/Values/Content/Location.php b/src/contracts/Repository/Values/Content/Location.php index 022d2e6525..72c5348002 100644 --- a/src/contracts/Repository/Values/Content/Location.php +++ b/src/contracts/Repository/Values/Content/Location.php @@ -24,7 +24,7 @@ * @property-read bool $explicitlyHidden Indicates that the Location entity has been explicitly marked as hidden. * @property-read string $remoteId a global unique id of the content object * @property-read int $parentLocationId the id of the parent location - * @property-read string $pathString the path to this location e.g. /1/2/4/23 where 23 is current id. + * @property-read string $pathString @deprecated use {@see Location::getPathString()} instead. * @property-read array $path Same as $pathString but as array, e.g. [ 1, 2, 4, 23 ] * @property-read int $depth Depth location has in the location tree * @property-read int $sortField Specifies which property the child locations should be sorted on. Valid values are found at {@link Location::SORT_FIELD_*} @@ -239,6 +239,15 @@ public function getSortClauses(): array return [$sortClause]; } + + /** + * The path to the Location represented by the current instance, + * e.g. /1/2/4/23 where 23 is current id. + */ + public function getPathString(): string + { + return $this->pathString; + } } class_alias(Location::class, 'eZ\Publish\API\Repository\Values\Content\Location'); diff --git a/src/contracts/Repository/Values/ContentType/ContentType.php b/src/contracts/Repository/Values/ContentType/ContentType.php index 12cefa4462..3fdf59c188 100644 --- a/src/contracts/Repository/Values/ContentType/ContentType.php +++ b/src/contracts/Repository/Values/ContentType/ContentType.php @@ -27,7 +27,7 @@ * @property-read string $remoteId a global unique id of the content object * @property-read string $urlAliasSchema URL alias schema. If nothing is provided, $nameSchema will be used instead. * @property-read string $nameSchema The name schema. - * @property-read bool $isContainer This flag hints to UIs if type may have children or not. + * @property-read bool $isContainer @deprecated use strict getter {@see ContentType::isContainer} instead. * @property-read string $mainLanguageCode the main language of the content type names and description used for fallback. * @property-read bool $defaultAlwaysAvailable if an instance of a content type is created the always available flag is set by default this this value. * @property-read string[] $languageCodes array of language codes used by content type translations. @@ -228,6 +228,11 @@ public function getFirstFieldDefinitionOfType(string $fieldTypeIdentifier): ?Fie return null; } + + public function isContainer(): bool + { + return $this->isContainer; + } } class_alias(ContentType::class, 'eZ\Publish\API\Repository\Values\ContentType\ContentType'); diff --git a/src/lib/Persistence/Cache/LocationHandler.php b/src/lib/Persistence/Cache/LocationHandler.php index 1fdc9f1f40..c7edf4d235 100644 --- a/src/lib/Persistence/Cache/LocationHandler.php +++ b/src/lib/Persistence/Cache/LocationHandler.php @@ -256,6 +256,15 @@ public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null) return $this->persistenceHandler->locationHandler()->copySubtree($sourceId, $destinationParentId, $newOwnerId); } + public function getSubtreeSize(string $path): int + { + $this->logger->logCall(__METHOD__, [ + 'path' => $path, + ]); + + return $this->persistenceHandler->locationHandler()->getSubtreeSize($path); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Persistence/Legacy/Content/Location/Gateway.php b/src/lib/Persistence/Legacy/Content/Location/Gateway.php index c8b10276a2..57e7838eeb 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Gateway.php +++ b/src/lib/Persistence/Legacy/Content/Location/Gateway.php @@ -111,6 +111,8 @@ abstract public function loadParentLocationsDataForDraftContent(int $contentId): */ abstract public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array; + abstract public function getSubtreeSize(string $path): int; + /** * Returns data for the first level children of the location identified by given $locationId. */ diff --git a/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php index e4da2dc636..8341e96196 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php @@ -237,6 +237,21 @@ public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array : $results; } + public function getSubtreeSize(string $path): int + { + $query = $this->createNodeQueryBuilder([$this->dbPlatform->getCountExpression('node_id')]); + $query->andWhere( + $query->expr()->like( + 't.path_string', + $query->createPositionalParameter( + $path . '%', + ) + ) + ); + + return (int) $query->execute()->fetchOne(); + } + /** * Return constraint which limits the given $query to the subtree starting at $rootLocationId. */ diff --git a/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php b/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php index aa6a38c836..70fe4b717c 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php +++ b/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php @@ -106,6 +106,15 @@ public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array } } + public function getSubtreeSize(string $path): int + { + try { + return $this->innerGateway->getSubtreeSize($path); + } catch (DBALException | PDOException $e) { + throw DatabaseException::wrap($e); + } + } + public function getChildren(int $locationId): array { try { diff --git a/src/lib/Persistence/Legacy/Content/Location/Handler.php b/src/lib/Persistence/Legacy/Content/Location/Handler.php index c5a20412f8..999bb97833 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Location/Handler.php @@ -332,6 +332,11 @@ public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null) return $copiedSubtreeRootLocation; } + public function getSubtreeSize(string $path): int + { + return $this->locationGateway->getSubtreeSize($path); + } + /** * Retrieves section ID of the location's content. * diff --git a/src/lib/Repository/LocationService.php b/src/lib/Repository/LocationService.php index 2072d4fdf3..1d0c18dccf 100644 --- a/src/lib/Repository/LocationService.php +++ b/src/lib/Repository/LocationService.php @@ -379,6 +379,13 @@ public function getLocationChildCount(APILocation $location): int return $this->count($filter); } + public function getSubtreeSize(APILocation $location): int + { + return $this->persistenceHandler->locationHandler()->getSubtreeSize( + $location->getPathString() + ); + } + protected function buildLocationChildrenFilter(APILocation $location): Filter { $filter = new Filter(); diff --git a/src/lib/Repository/SiteAccessAware/LocationService.php b/src/lib/Repository/SiteAccessAware/LocationService.php index 9cf0835f42..43e6feb7c9 100644 --- a/src/lib/Repository/SiteAccessAware/LocationService.php +++ b/src/lib/Repository/SiteAccessAware/LocationService.php @@ -109,6 +109,11 @@ public function getLocationChildCount(Location $location): int return $this->service->getLocationChildCount($location); } + public function getSubtreeSize(Location $location): int + { + return $this->service->getSubtreeSize($location); + } + public function createLocation(ContentInfo $contentInfo, LocationCreateStruct $locationCreateStruct): Location { return $this->service->createLocation($contentInfo, $locationCreateStruct); diff --git a/tests/integration/Core/Repository/LocationServiceTest.php b/tests/integration/Core/Repository/LocationServiceTest.php index 4c5d0d93e8..4f92239f6d 100644 --- a/tests/integration/Core/Repository/LocationServiceTest.php +++ b/tests/integration/Core/Repository/LocationServiceTest.php @@ -3469,6 +3469,23 @@ public function testMoveSubtreeKeepsContentHiddenOnChildren(): void } } + public function testGetSubtreeSize(): Location + { + $repository = $this->getRepository(); + $locationService = $repository->getLocationService(); + + $folder = $this->createFolder(['eng-GB' => 'Parent Folder'], 2); + $location = $folder->getVersionInfo()->getContentInfo()->getMainLocation(); + self::assertSame(1, $locationService->getSubtreeSize($location)); + + $this->createFolder(['eng-GB' => 'Child 1'], $location->id); + $this->createFolder(['eng-GB' => 'Child 2'], $location->id); + + self::assertSame(3, $locationService->getSubtreeSize($location)); + + return $location; + } + /** * Loads properties from all locations in the $location's subtree. * diff --git a/tests/lib/Repository/SiteAccessAware/LocationServiceTest.php b/tests/lib/Repository/SiteAccessAware/LocationServiceTest.php index 10f1be7004..532cd536f8 100644 --- a/tests/lib/Repository/SiteAccessAware/LocationServiceTest.php +++ b/tests/lib/Repository/SiteAccessAware/LocationServiceTest.php @@ -42,6 +42,8 @@ public function providerForPassTroughMethods(): array ['getLocationChildCount', [$location], 100], + ['getSubtreeSize', [$location], 100], + ['createLocation', [$contentInfo, $locationCreateStruct], $location], ['updateLocation', [$location, $locationUpdateStruct], $location],