diff --git a/src/bundle/Controller/LocationController.php b/src/bundle/Controller/LocationController.php index 1ad53bd9f1..7ca3faf241 100644 --- a/src/bundle/Controller/LocationController.php +++ b/src/bundle/Controller/LocationController.php @@ -302,7 +302,7 @@ public function swapAction(Request $request): Response $currentLocation = $data->getCurrentLocation(); $newLocation = $data->getNewLocation(); - $childCount = $this->locationService->getLocationChildCount($currentLocation); + $childCount = $this->locationService->getLocationChildCount($currentLocation, 1); $contentType = $newLocation->getContent()->getContentType(); if (!$contentType->isContainer && $childCount) { diff --git a/src/bundle/DependencyInjection/Configuration/Parser/SubtreeOperations.php b/src/bundle/DependencyInjection/Configuration/Parser/SubtreeOperations.php index 066539d54d..33597d6a4d 100644 --- a/src/bundle/DependencyInjection/Configuration/Parser/SubtreeOperations.php +++ b/src/bundle/DependencyInjection/Configuration/Parser/SubtreeOperations.php @@ -54,6 +54,14 @@ public function addSemanticConfig(NodeBuilder $nodeBuilder): void ->end() ->end() ->end() + ->arrayNode('query_subtree') + ->children() + ->integerNode('limit') + ->info('Limit the total count of items queried for when calculating the the number of direct children a node has. -1 for no limit. Default is 500 for performance reasons.') + ->defaultValue(500) + ->isRequired() + ->end() + ->end() ->end() ->end(); } diff --git a/src/bundle/Resources/config/ezplatform_default_settings.yaml b/src/bundle/Resources/config/ezplatform_default_settings.yaml index 7c6cbb3174..1c634d0acb 100644 --- a/src/bundle/Resources/config/ezplatform_default_settings.yaml +++ b/src/bundle/Resources/config/ezplatform_default_settings.yaml @@ -42,6 +42,7 @@ parameters: # Subtree Operations ibexa.site_access.config.admin_group.subtree_operations.copy_subtree.limit: 100 + ibexa.site_access.config.admin_group.subtree_operations.query_subtree.limit: 500 # Notifications ibexa.site_access.config.admin_group.notification_count.interval: 30000 diff --git a/src/bundle/Resources/views/themes/admin/content/tab/locations/tab.html.twig b/src/bundle/Resources/views/themes/admin/content/tab/locations/tab.html.twig index 76f815b075..efa580390a 100644 --- a/src/bundle/Resources/views/themes/admin/content/tab/locations/tab.html.twig +++ b/src/bundle/Resources/views/themes/admin/content/tab/locations/tab.html.twig @@ -82,7 +82,7 @@ }]) %} {% set body_row_cols = body_row_cols|merge([ - { content: location.childCount }, + { content: (location.childCount > sub_item_query_limit) ? (location.childCount -1) ~ '+' : location.childCount }, ]) %} {% set body_rows = body_rows|merge([{ cols: body_row_cols }]) %} diff --git a/src/lib/Form/TrashLocationOptionProvider/HasChildren.php b/src/lib/Form/TrashLocationOptionProvider/HasChildren.php index fee9fe66d2..ab8f7cbd4d 100644 --- a/src/lib/Form/TrashLocationOptionProvider/HasChildren.php +++ b/src/lib/Form/TrashLocationOptionProvider/HasChildren.php @@ -11,6 +11,7 @@ use Ibexa\AdminUi\Specification\Location\HasChildren as HasChildrenSpec; use Ibexa\Contracts\Core\Repository\LocationService; use Ibexa\Contracts\Core\Repository\Values\Content\Location; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -23,10 +24,14 @@ final class HasChildren implements TrashLocationOptionProvider /** @var \Symfony\Contracts\Translation\TranslatorInterface */ private $translator; - public function __construct(LocationService $locationService, TranslatorInterface $translator) + /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ + private $configResolver; + + public function __construct(LocationService $locationService, TranslatorInterface $translator, ConfigResolverInterface $configResolver) { $this->locationService = $locationService; $this->translator = $translator; + $this->configResolver = $configResolver; } public function supports(Location $location): bool @@ -36,10 +41,15 @@ public function supports(Location $location): bool public function addOptions(FormInterface $form, Location $location): void { - $childCount = $this->locationService->getLocationChildCount($location); + $limit = $this->configResolver->getParameter('subtree_operations.query_subtree.limit'); + + $useLimit = $limit > 0; + $childCount = $this->locationService->getLocationChildCount($location, $useLimit ? $limit + 1 : null); $translatorParameters = [ - '%children_count%' => $childCount, + '%children_count%' => ($useLimit && $childCount >= $limit) ? + sprintf('%d+', $limit) : + $childCount, '%content%' => $location->getContent()->getName(), ]; diff --git a/src/lib/Specification/Location/HasChildren.php b/src/lib/Specification/Location/HasChildren.php index 227a583e3b..e23bbc614b 100644 --- a/src/lib/Specification/Location/HasChildren.php +++ b/src/lib/Specification/Location/HasChildren.php @@ -31,7 +31,7 @@ public function __construct(LocationService $locationService) */ public function isSatisfiedBy($item): bool { - $childCount = $this->locationService->getLocationChildCount($item); + $childCount = $this->locationService->getLocationChildCount($item, 1); return 0 < $childCount; } diff --git a/src/lib/Specification/Location/IsWithinCopySubtreeLimit.php b/src/lib/Specification/Location/IsWithinCopySubtreeLimit.php index 2a90c52142..7b4b546207 100644 --- a/src/lib/Specification/Location/IsWithinCopySubtreeLimit.php +++ b/src/lib/Specification/Location/IsWithinCopySubtreeLimit.php @@ -44,7 +44,7 @@ public function isSatisfiedBy($item): bool return false; } - return $this->copyLimit >= $this->locationService->getSubtreeSize($item); + return $this->copyLimit >= $this->locationService->getSubtreeSize($item, $this->copyLimit + 1); } private function isContainer(Location $location): bool diff --git a/src/lib/Tab/LocationView/LocationsTab.php b/src/lib/Tab/LocationView/LocationsTab.php index 43f53aeb1e..48e2e02fc7 100644 --- a/src/lib/Tab/LocationView/LocationsTab.php +++ b/src/lib/Tab/LocationView/LocationsTab.php @@ -206,12 +206,18 @@ public function getTemplateParameters(array $contextParameters = []): array ); } + $subItemQueryLimit = $this->configResolver->getParameter('subtree_operations.query_subtree.limit'); + if ($subItemQueryLimit <= 0) { + $subItemQueryLimit = null; + } + $viewParameters = [ 'pager' => $pagination, 'pager_options' => [ 'pageParameter' => sprintf('[%s]', self::PAGINATION_PARAM_NAME), ], 'locations' => $locations, + 'sub_item_query_limit' => $subItemQueryLimit, 'form_content_location_add' => $formLocationAdd->createView(), 'form_content_location_remove' => $formLocationRemove->createView(), 'form_content_location_swap' => $formLocationSwap->createView(), diff --git a/src/lib/UI/Module/Subitems/ContentViewParameterSupplier.php b/src/lib/UI/Module/Subitems/ContentViewParameterSupplier.php index f57b8819da..a48d4285b4 100644 --- a/src/lib/UI/Module/Subitems/ContentViewParameterSupplier.php +++ b/src/lib/UI/Module/Subitems/ContentViewParameterSupplier.php @@ -20,6 +20,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Contracts\Rest\Output\Visitor; use Ibexa\Core\MVC\Symfony\View\ContentView; use Ibexa\Core\Query\QueryFactoryInterface; @@ -69,6 +70,8 @@ class ContentViewParameterSupplier private SearchService $searchService; + private ConfigResolverInterface $configResolver; + public function __construct( Visitor $outputVisitor, JsonOutputGenerator $outputGenerator, @@ -81,7 +84,8 @@ public function __construct( ContentTypeMappings $contentTypeMappings, UserSettingService $userSettingService, QueryFactoryInterface $queryFactory, - SearchService $searchService + SearchService $searchService, + ConfigResolverInterface $configResolver ) { $this->outputVisitor = $outputVisitor; $this->outputGenerator = $outputGenerator; @@ -95,6 +99,7 @@ public function __construct( $this->userSettingService = $userSettingService; $this->queryFactory = $queryFactory; $this->searchService = $searchService; + $this->configResolver = $configResolver; } /** @@ -185,7 +190,12 @@ private function createRestLocation(Location $location): RestLocation { return new RestLocation( $location, - $this->locationService->getLocationChildCount($location) + $this->locationService->getLocationChildCount( + $location, + // For the sub items module we only ever use the count to determine if there are children (0 or 1+), + // hence setting a limit of 1 is sufficient here. + 1 + ) ); } diff --git a/src/lib/UI/Value/ValueFactory.php b/src/lib/UI/Value/ValueFactory.php index 995f889b4c..59026d1133 100644 --- a/src/lib/UI/Value/ValueFactory.php +++ b/src/lib/UI/Value/ValueFactory.php @@ -36,6 +36,7 @@ use Ibexa\Contracts\Core\Repository\Values\ObjectState\ObjectStateGroup; use Ibexa\Contracts\Core\Repository\Values\User\Policy; use Ibexa\Contracts\Core\Repository\Values\User\RoleAssignment; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\MVC\Symfony\Locale\UserLanguagePreferenceProviderInterface; use Ibexa\Core\Repository\LocationResolver\LocationResolver; @@ -74,6 +75,9 @@ class ValueFactory /** @var \Ibexa\Core\Repository\LocationResolver\LocationResolver */ protected $locationResolver; + /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ + protected $configResolver; + /** * @param \Ibexa\Contracts\Core\Repository\UserService $userService * @param \Ibexa\Contracts\Core\Repository\LanguageService $languageService @@ -86,6 +90,7 @@ class ValueFactory * @param \Ibexa\AdminUi\UI\Dataset\DatasetFactory $datasetFactory * @param \Ibexa\Core\MVC\Symfony\Locale\UserLanguagePreferenceProviderInterface $userLanguagePreferenceProvider * @param \Ibexa\Core\Repository\LocationResolver\LocationResolver $locationResolver + * @param \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface $configResolver */ public function __construct( UserService $userService, @@ -98,7 +103,8 @@ public function __construct( PathService $pathService, DatasetFactory $datasetFactory, UserLanguagePreferenceProviderInterface $userLanguagePreferenceProvider, - LocationResolver $locationResolver + LocationResolver $locationResolver, + ConfigResolverInterface $configResolver ) { $this->userService = $userService; $this->languageService = $languageService; @@ -111,6 +117,7 @@ public function __construct( $this->datasetFactory = $datasetFactory; $this->userLanguagePreferenceProvider = $userLanguagePreferenceProvider; $this->locationResolver = $locationResolver; + $this->configResolver = $configResolver; } /** @@ -237,9 +244,11 @@ public function createLocation(Location $location): UIValue\Content\Location { $translations = $location->getContent()->getVersionInfo()->languageCodes; $target = (new Target\Version())->deleteTranslations($translations); + $limit = $this->configResolver->getParameter('subtree_operations.query_subtree.limit'); + $useLimit = $limit > 0; return new UIValue\Content\Location($location, [ - 'childCount' => $this->locationService->getLocationChildCount($location), + 'childCount' => $this->locationService->getLocationChildCount($location, $useLimit ? $limit + 1 : null), 'pathLocations' => $this->pathService->loadPathLocations($location), 'userCanManage' => $this->permissionResolver->canUser( 'content',