diff --git a/eZ/Publish/API/Repository/LocationService.php b/eZ/Publish/API/Repository/LocationService.php index 83696337c0..4bc04347ec 100644 --- a/eZ/Publish/API/Repository/LocationService.php +++ b/eZ/Publish/API/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/eZ/Publish/API/Repository/Tests/LocationServiceTest.php b/eZ/Publish/API/Repository/Tests/LocationServiceTest.php index 90f52a66b6..fa9ec93785 100644 --- a/eZ/Publish/API/Repository/Tests/LocationServiceTest.php +++ b/eZ/Publish/API/Repository/Tests/LocationServiceTest.php @@ -3471,6 +3471,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/eZ/Publish/API/Repository/Values/Content/Location.php b/eZ/Publish/API/Repository/Values/Content/Location.php index ee2888f530..b0510d15e6 100644 --- a/eZ/Publish/API/Repository/Values/Content/Location.php +++ b/eZ/Publish/API/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,4 +239,13 @@ 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; + } } diff --git a/eZ/Publish/API/Repository/Values/ContentType/ContentType.php b/eZ/Publish/API/Repository/Values/ContentType/ContentType.php index fe005ff40b..865f0aa0ea 100644 --- a/eZ/Publish/API/Repository/Values/ContentType/ContentType.php +++ b/eZ/Publish/API/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,4 +228,9 @@ public function getFirstFieldDefinitionOfType(string $fieldTypeIdentifier): ?Fie return null; } + + public function isContainer(): bool + { + return $this->isContainer; + } } diff --git a/eZ/Publish/Core/Persistence/Cache/LocationHandler.php b/eZ/Publish/Core/Persistence/Cache/LocationHandler.php index 2a1b337933..c4d56e9a4e 100644 --- a/eZ/Publish/Core/Persistence/Cache/LocationHandler.php +++ b/eZ/Publish/Core/Persistence/Cache/LocationHandler.php @@ -259,6 +259,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/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway.php b/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway.php index a815e720d6..d49a1f0286 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway.php +++ b/eZ/Publish/Core/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/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php b/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php index 969e09f70a..495947b5fa 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php +++ b/eZ/Publish/Core/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/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php b/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php index 66779e42c0..ed1acf56e2 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php +++ b/eZ/Publish/Core/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/eZ/Publish/Core/Persistence/Legacy/Content/Location/Handler.php b/eZ/Publish/Core/Persistence/Legacy/Content/Location/Handler.php index a3eb10bf25..4a444fa41a 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/Location/Handler.php +++ b/eZ/Publish/Core/Persistence/Legacy/Content/Location/Handler.php @@ -346,6 +346,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/eZ/Publish/Core/Repository/LocationService.php b/eZ/Publish/Core/Repository/LocationService.php index 5fd4ec20ea..33e0675566 100644 --- a/eZ/Publish/Core/Repository/LocationService.php +++ b/eZ/Publish/Core/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/eZ/Publish/Core/Repository/SiteAccessAware/LocationService.php b/eZ/Publish/Core/Repository/SiteAccessAware/LocationService.php index 3e5256c732..1a5700d2f1 100644 --- a/eZ/Publish/Core/Repository/SiteAccessAware/LocationService.php +++ b/eZ/Publish/Core/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/eZ/Publish/Core/Repository/SiteAccessAware/Tests/LocationServiceTest.php b/eZ/Publish/Core/Repository/SiteAccessAware/Tests/LocationServiceTest.php index befe709c37..1d2863e589 100644 --- a/eZ/Publish/Core/Repository/SiteAccessAware/Tests/LocationServiceTest.php +++ b/eZ/Publish/Core/Repository/SiteAccessAware/Tests/LocationServiceTest.php @@ -42,6 +42,8 @@ public function providerForPassTroughMethods(): array ['getLocationChildCount', [$location], 100], + ['getSubtreeSize', [$location], 100], + ['createLocation', [$contentInfo, $locationCreateStruct], $location], ['updateLocation', [$location, $locationUpdateStruct], $location], diff --git a/eZ/Publish/SPI/Persistence/Content/Location/Handler.php b/eZ/Publish/SPI/Persistence/Content/Location/Handler.php index b039c6af69..72459b0d16 100644 --- a/eZ/Publish/SPI/Persistence/Content/Location/Handler.php +++ b/eZ/Publish/SPI/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/eZ/Publish/SPI/Repository/Decorator/LocationServiceDecorator.php b/eZ/Publish/SPI/Repository/Decorator/LocationServiceDecorator.php index 0cf8c946dc..31885e27b9 100644 --- a/eZ/Publish/SPI/Repository/Decorator/LocationServiceDecorator.php +++ b/eZ/Publish/SPI/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