Skip to content

Commit

Permalink
IBX-7653: allow filtering for loadSubtreeAction
Browse files Browse the repository at this point in the history
  • Loading branch information
tischsoic committed Feb 12, 2024
1 parent b114ef7 commit 89a58ae
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 41 deletions.
3 changes: 2 additions & 1 deletion src/bundle/Controller/Content/ContentTreeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ public function loadSubtreeAction(Request $request): Root
true,
0,
$sortClause,
$sortOrder
$sortOrder,
$loadSubtreeRequest->filter,
);
}

Expand Down
10 changes: 6 additions & 4 deletions src/lib/REST/Input/Parser/ContentTree/LoadSubtreeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@

class LoadSubtreeRequest extends BaseParser
{
/**
* {@inheritdoc}
*/
public function parse(array $data, ParsingDispatcher $parsingDispatcher): LoadSubtreeRequestValue
{
if (!array_key_exists('nodes', $data) || !is_array($data['nodes'])) {
Expand All @@ -31,7 +28,12 @@ public function parse(array $data, ParsingDispatcher $parsingDispatcher): LoadSu
$nodes[] = $parsingDispatcher->parse($node, $node['_media-type']);
}

return new LoadSubtreeRequestValue($nodes);
$filter = null;
if (isset($data['filter'])) {
$filter = $parsingDispatcher->parse($data['filter'], 'application/vnd.ibexa.api.internal.Filter');
}

return new LoadSubtreeRequestValue($nodes, $filter);
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/lib/REST/Value/ContentTree/LoadSubtreeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@

namespace Ibexa\AdminUi\REST\Value\ContentTree;

use Ibexa\Contracts\Core\Repository\Values\Filter\Filter;
use Ibexa\Rest\Value as RestValue;

class LoadSubtreeRequest extends RestValue
{
/** @var \Ibexa\AdminUi\REST\Value\ContentTree\LoadSubtreeRequestNode[] */
public $nodes;

public ?Filter $filter;

/**
* @param array $nodes
*/
public function __construct(array $nodes = [])
public function __construct(array $nodes = [], Filter $filter = null)
{
$this->nodes = $nodes;
$this->filter = $filter;
}
}

Expand Down
114 changes: 79 additions & 35 deletions src/lib/UI/Module/ContentTree/NodeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,35 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Content;
use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo;
use Ibexa\Contracts\Core\Repository\Values\Content\Location;
use Ibexa\Contracts\Core\Repository\Values\Content\LocationList;
use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery;
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\AggregationResult\TermAggregationResult;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Repository\Values\Filter\Filter;
use Ibexa\Contracts\Core\Repository\Values\Filter\FilteringSortClause;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
use Ibexa\Core\Helper\TranslationHelper;
use Ibexa\Core\Repository\Repository;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
* @internal
*/
final class NodeFactory
final class NodeFactory implements LoggerAwareInterface
{
use LoggerAwareTrait;

private const TOP_NODE_CONTENT_ID = 0;

/**
* @var array<string, class-string<\Ibexa\Contracts\Core\Repository\Values\Filter\FilteringSortClause>>
*/
private const SORT_CLAUSE_MAP = [
'DatePublished' => SortClause\DatePublished::class,
'ContentName' => SortClause\ContentName::class,
Expand Down Expand Up @@ -73,7 +85,8 @@ public function __construct(
PermissionResolver $permissionResolver,
Repository $repository,
SiteaccessResolverInterface $siteaccessResolver,
int $maxLocationIdsInSingleAggregation
int $maxLocationIdsInSingleAggregation,
?LoggerInterface $logger = null
) {
$this->bookmarkService = $bookmarkService;
$this->contentService = $contentService;
Expand All @@ -84,6 +97,7 @@ public function __construct(
$this->repository = $repository;
$this->siteaccessResolver = $siteaccessResolver;
$this->maxLocationIdsInSingleAggregation = $maxLocationIdsInSingleAggregation;
$this->logger = $logger ?? new NullLogger();
}

/**
Expand All @@ -97,7 +111,8 @@ public function createNode(
bool $loadChildren = false,
int $depth = 0,
?string $sortClause = null,
string $sortOrder = Query::SORT_ASC
string $sortOrder = Query::SORT_ASC,
Filter $filter = null
): Node {
$uninitializedContentInfoList = [];
$containerLocations = [];
Expand All @@ -109,7 +124,9 @@ public function createNode(
$loadChildren,
$depth,
$sortClause,
$sortOrder
$sortOrder,
[],
$filter
);
$versionInfoById = $this->contentService->loadVersionInfoListByContentInfo($uninitializedContentInfoList);

Expand All @@ -119,7 +136,7 @@ public function createNode(
}

$this->supplyTranslatedContentName($node, $versionInfoById);
$this->supplyChildrenCount($node, $aggregatedChildrenCount);
$this->supplyChildrenCount($node, $aggregatedChildrenCount, $filter);

return $node;
}
Expand All @@ -144,24 +161,32 @@ private function findSubitems(
int $limit = 10,
int $offset = 0,
?string $sortClause = null,
string $sortOrder = Query::SORT_ASC
): SearchResult {
$searchQuery = $this->getSearchQuery($parentLocation->id);
string $sortOrder = Query::SORT_ASC,
Filter $filter = null
): LocationList {
$searchQuery = $this->getSearchQuery($parentLocation->id, $filter);

$searchQuery->withLimit($limit);
$searchQuery->withOffset($offset);
foreach ($this->getSortClauses($sortClause, $sortOrder, $parentLocation) as $sortClause) {
$searchQuery->withSortClause($sortClause);
}

$searchQuery->limit = $limit;
$searchQuery->offset = $offset;
$searchQuery->sortClauses = $this->getSortClauses($sortClause, $sortOrder, $parentLocation);
$locationService = $this->repository->getLocationService();

return $this->searchService->findLocations($searchQuery);
return $locationService->find($searchQuery);
}

/**
* @param \Ibexa\Contracts\Core\Repository\Values\Content\Location $parentLocation
*/
private function getSearchQuery(int $parentLocationId): LocationQuery
private function getSearchQuery(int $parentLocationId, Filter $requestFilter = null): Filter
{
$searchQuery = new LocationQuery();
$searchQuery->filter = new Criterion\ParentLocationId($parentLocationId);
$filter = empty($requestFilter)
? new Filter()
: new Filter($requestFilter->getCriterion(), $requestFilter->getSortClauses());

$filter->andWithCriterion(new Criterion\ParentLocationId($parentLocationId));

$contentTypeCriterion = null;

Expand All @@ -176,10 +201,10 @@ private function getSearchQuery(int $parentLocationId): LocationQuery
}

if (null !== $contentTypeCriterion) {
$searchQuery->filter = new Criterion\LogicalAnd([$searchQuery->filter, $contentTypeCriterion]);
$filter->andWithCriterion($contentTypeCriterion);
}

return $searchQuery;
return $filter;
}

private function findChild(int $locationId, LoadSubtreeRequestNode $loadSubtreeRequestNode): ?LoadSubtreeRequestNode
Expand All @@ -196,15 +221,13 @@ private function findChild(int $locationId, LoadSubtreeRequestNode $loadSubtreeR
/**
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
*/
private function countSubitems(int $parentLocationId): int
private function countSubitems(int $parentLocationId, Filter $filter = null): int
{
$searchQuery = $this->getSearchQuery($parentLocationId);
$searchQuery = $this->getSearchQuery($parentLocationId, $filter);

$searchQuery->limit = 0;
$searchQuery->offset = 0;
$searchQuery->performCount = true;
$locationService = $this->repository->getLocationService();

return $this->searchService->findLocations($searchQuery)->totalCount;
return $locationService->count($searchQuery);
}

/**
Expand Down Expand Up @@ -259,6 +282,9 @@ private function aggregationResultToArray(TermAggregationResult $aggregationResu
return $resultsAsArray;
}

/**
* @return mixed
*/
private function getSetting(string $name)
{
return $this->configResolver->getParameter("content_tree_module.$name");
Expand All @@ -267,23 +293,22 @@ private function getSetting(string $name)
/**
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
*/
private function buildSortClause(string $sortClause, string $sortOrder): SortClause
private function buildSortClause(string $sortClause, string $sortOrder): FilteringSortClause
{
if (!isset(static::SORT_CLAUSE_MAP[$sortClause])) {
throw new InvalidArgumentException('$sortClause', 'Invalid sort clause');
}

$map = static::SORT_CLAUSE_MAP;

/** @var \Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause $sortClauseInstance */
$sortClauseInstance = new $map[$sortClause]();
$sortClauseInstance->direction = $sortOrder;

return $sortClauseInstance;
}

/**
* @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause[]
* @return \Ibexa\Contracts\Core\Repository\Values\Filter\FilteringSortClause[]
*
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
*/
Expand All @@ -297,7 +322,24 @@ private function getSortClauses(
}

try {
return $parentLocation->getSortClauses();
$sortClauses = $parentLocation->getSortClauses();
/** @var \Ibexa\Contracts\Core\Repository\Values\Filter\FilteringSortClause[] $filteringSortClauses */
$filteringSortClauses = array_filter($sortClauses, function (SortClause $sortClause): bool {
if (!$sortClause instanceof FilteringSortClause) {
$this->logger->warning(
sprintf(
'SortClause %s cannot be used',
$sortClause::class,
)
);

return false;
}

return true;
});

return $filteringSortClauses;
} catch (NotImplementedException $e) {
return []; // rely on storage engine default sorting
}
Expand All @@ -320,7 +362,8 @@ private function buildNode(
int $depth = 0,
?string $sortClause = null,
string $sortOrder = Query::SORT_ASC,
array $bookmarkLocations = []
array $bookmarkLocations = [],
Filter $filter = null
): Node {
$contentInfo = $location->getContentInfo();
$contentId = $location->contentId;
Expand Down Expand Up @@ -348,11 +391,11 @@ private function buildNode(
$totalChildrenCount = 0;
$children = [];
if ($loadChildren && $depth < $this->getSetting('tree_max_depth')) {
$searchResult = $this->findSubitems($location, $limit, $offset, $sortClause, $sortOrder);
$searchResult = $this->findSubitems($location, $limit, $offset, $sortClause, $sortOrder, $filter);
$totalChildrenCount = (int) $searchResult->totalCount;

/** @var \Ibexa\Contracts\Core\Repository\Values\Content\Location $childLocation */
foreach (array_column($searchResult->searchHits, 'valueObject') as $childLocation) {
foreach ($searchResult as $childLocation) {
$childLoadSubtreeRequestNode = null !== $loadSubtreeRequestNode
? $this->findChild($childLocation->id, $loadSubtreeRequestNode)
: null;
Expand All @@ -366,7 +409,8 @@ private function buildNode(
$depth + 1,
null,
Query::SORT_ASC,
$bookmarkLocations
$bookmarkLocations,
$filter
);
}
}
Expand Down Expand Up @@ -423,20 +467,20 @@ private function supplyTranslatedContentName(Node $node, array $versionInfoById)
/**
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
*/
private function supplyChildrenCount(Node $node, ?array $aggregationResult = null): void
private function supplyChildrenCount(Node $node, ?array $aggregationResult = null, Filter $filter = null): void
{
if ($node->isContainer) {
if ($aggregationResult !== null) {
$totalCount = $aggregationResult[$node->locationId] ?? 0;
} else {
$totalCount = $this->countSubitems($node->locationId);
$totalCount = $this->countSubitems($node->locationId, $filter);
}

$node->totalChildrenCount = $totalCount;
}

foreach ($node->children as $child) {
$this->supplyChildrenCount($child, $aggregationResult);
$this->supplyChildrenCount($child, $aggregationResult, $filter);
}
}

Expand Down

0 comments on commit 89a58ae

Please sign in to comment.