Skip to content

Commit 89c0c31

Browse files
author
roadiz-ci
committed
Merge branch develop of github.com:roadiz/core-bundle-dev-app into develop
1 parent 81aee95 commit 89c0c31

File tree

3 files changed

+153
-2
lines changed

3 files changed

+153
-2
lines changed

src/Api/Filter/TagGroupFilter.php

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RZ\Roadiz\CoreBundle\Api\Filter;
6+
7+
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
8+
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
9+
use ApiPlatform\Metadata\Operation;
10+
use Doctrine\ORM\Query\Expr\Join;
11+
use Doctrine\ORM\QueryBuilder;
12+
use Doctrine\Persistence\ManagerRegistry;
13+
use Psr\Log\LoggerInterface;
14+
use RZ\Roadiz\CoreBundle\Entity\Node;
15+
use Symfony\Component\PropertyInfo\Type;
16+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
17+
18+
final class TagGroupFilter extends AbstractFilter
19+
{
20+
public const PROPERTY = 'tagGroup';
21+
22+
public function __construct(
23+
ManagerRegistry $managerRegistry,
24+
?LoggerInterface $logger = null,
25+
?array $properties = null,
26+
?NameConverterInterface $nameConverter = null,
27+
) {
28+
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
29+
}
30+
31+
protected function filterProperty(
32+
string $property,
33+
mixed $value,
34+
QueryBuilder $queryBuilder,
35+
QueryNameGeneratorInterface $queryNameGenerator,
36+
string $resourceClass,
37+
?Operation $operation = null,
38+
array $context = [],
39+
): void {
40+
if (self::PROPERTY !== $property) {
41+
return;
42+
}
43+
if (!\is_array($value)) {
44+
return;
45+
}
46+
47+
/*
48+
* Convert comma separated tag identifiers to sub-arrays
49+
*/
50+
$normalizedValue = [];
51+
52+
foreach ($value as $group) {
53+
if (!\is_array($group)) {
54+
$group = explode(',', $group);
55+
}
56+
$normalizedValue[] = array_filter(array_map('trim', $group));
57+
}
58+
59+
if (Node::class !== $resourceClass) {
60+
/*
61+
* If entity is node source, we need to join node
62+
*/
63+
if (null === $nodeJoin = $this->joinExists($queryBuilder)) {
64+
$nodeJoin = 'node';
65+
$queryBuilder->innerJoin('o.node', $nodeJoin);
66+
}
67+
68+
$subQueryBuilder = $this->managerRegistry->getRepository(Node::class)->createQueryBuilder('n');
69+
$subQueryBuilder->select('n.id');
70+
$this->filterByTagNames($subQueryBuilder, $normalizedValue, 'n');
71+
$queryBuilder->andWhere($queryBuilder->expr()->in($nodeJoin.'.id', $subQueryBuilder->getDQL()));
72+
} else {
73+
/*
74+
* For each tag group we create an inner-join
75+
*/
76+
$this->filterByTagNames($queryBuilder, $normalizedValue, 'o');
77+
}
78+
79+
// Apply the tag names filter on root query builder
80+
$this->setTagNamesParameters($queryBuilder, $normalizedValue);
81+
}
82+
83+
public function getDescription(string $resourceClass): array
84+
{
85+
$carry = [];
86+
$carry[self::PROPERTY.'[]'] = [
87+
'property' => self::PROPERTY.'[]',
88+
'type' => Type::BUILTIN_TYPE_ARRAY,
89+
'required' => false,
90+
'description' => 'Filter entities by tag name groups (comma separated). Inside groups filter use OR, between each groups filter use AND.',
91+
'openapi' => [
92+
'description' => 'Filter entities by tag name groups (comma separated). Inside groups filter use OR, between each groups filter use AND.',
93+
'example' => 'tag-1,tag-2&'.self::PROPERTY.'[]=tag-3,tag-4',
94+
'allowEmptyValue' => false,
95+
'explode' => true,
96+
],
97+
];
98+
99+
return $carry;
100+
}
101+
102+
private function filterByTagNames(QueryBuilder $queryBuilder, array $groups, string $nodeAlias): void
103+
{
104+
/*
105+
* For each tag group we create an inner-join
106+
*/
107+
foreach ($groups as $i => $tagGroup) {
108+
$nodesTagsJoin = self::PROPERTY.'_nodesTags_'.$i;
109+
$joinAlias = self::PROPERTY.'_tag_'.$i;
110+
$parameterName = 'p_'.$joinAlias;
111+
$queryBuilder
112+
->innerJoin($nodeAlias.'.nodesTags', $nodesTagsJoin)
113+
->innerJoin($nodesTagsJoin.'.tag', $joinAlias)
114+
->andWhere($queryBuilder->expr()->in(
115+
$joinAlias.'.tagName',
116+
':'.$parameterName
117+
))
118+
;
119+
}
120+
}
121+
122+
private function setTagNamesParameters(QueryBuilder $queryBuilder, array $groups): void
123+
{
124+
/*
125+
* For each tag group we create an inner-join
126+
*/
127+
foreach ($groups as $i => $tagGroup) {
128+
$joinAlias = self::PROPERTY.'_tag_'.$i;
129+
$parameterName = 'p_'.$joinAlias;
130+
$queryBuilder->setParameter($parameterName, $tagGroup);
131+
}
132+
}
133+
134+
private function joinExists(QueryBuilder $queryBuilder): ?string
135+
{
136+
/** @var Join[][] $joinDqlParts */
137+
$joinDqlParts = $queryBuilder->getDQLParts()['join'];
138+
foreach ($joinDqlParts as $joins) {
139+
foreach ($joins as $join) {
140+
if ('o.node' === $join->getJoin()) {
141+
return $join->getAlias();
142+
}
143+
}
144+
}
145+
146+
return null;
147+
}
148+
}

src/Entity/Node.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use RZ\Roadiz\CoreBundle\Api\Filter as RoadizFilter;
2525
use RZ\Roadiz\CoreBundle\Api\Filter\NodeTypePublishableFilter;
2626
use RZ\Roadiz\CoreBundle\Api\Filter\NodeTypeReachableFilter;
27+
use RZ\Roadiz\CoreBundle\Api\Filter\TagGroupFilter;
2728
use RZ\Roadiz\CoreBundle\Enum\NodeStatus;
2829
use RZ\Roadiz\CoreBundle\Model\AttributableInterface;
2930
use RZ\Roadiz\CoreBundle\Model\AttributableTrait;
@@ -72,7 +73,8 @@
7273
),
7374
ApiFilter(NodeTypeReachableFilter::class),
7475
ApiFilter(NodeTypePublishableFilter::class),
75-
ApiFilter(PropertyFilter::class)
76+
ApiFilter(PropertyFilter::class),
77+
ApiFilter(TagGroupFilter::class)
7678
]
7779
class Node extends AbstractDateTimedPositioned implements LeafInterface, AttributableInterface, Loggable, NodeInterface
7880
{

src/Entity/NodesSources.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
ApiFilter(PropertyFilter::class),
5757
ApiFilter(NodeTypeReachableFilter::class),
5858
ApiFilter(NodeTypePublishableFilter::class),
59-
ApiFilter(RoadizFilter\LocaleFilter::class)
59+
ApiFilter(RoadizFilter\LocaleFilter::class),
60+
ApiFilter(RoadizFilter\TagGroupFilter::class),
6061
]
6162
class NodesSources extends AbstractEntity implements Loggable
6263
{

0 commit comments

Comments
 (0)