Skip to content

Commit

Permalink
BUGFIX: Ignore empty search term in subgraph api when filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
mhsdesign committed Aug 15, 2024
1 parent 5bce349 commit 0190e16
Show file tree
Hide file tree
Showing 7 changed files with 38 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ public function addNodeTypeCriteria(QueryBuilder $queryBuilder, ExpandedNodeType

public function addSearchTermConstraints(QueryBuilder $queryBuilder, SearchTerm $searchTerm, string $nodeTableAlias = 'n'): void
{
if ($searchTerm->term === '') {
return;
}
$queryBuilder->andWhere('JSON_SEARCH(' . $nodeTableAlias . '.properties, "one", :searchTermPattern, NULL, "$.*.value") IS NOT NULL')->setParameter('searchTermPattern', '%' . $searchTerm->term . '%');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ Feature: Find and count nodes using the findChildNodes and countChildNodes queri
| a2a | Neos.ContentRepository.Testing:SpecialPage | a2 | {"text": "a2a"} | {} |
| a2a1 | Neos.ContentRepository.Testing:Page | a2a | {"text": "a2a1", "stringProperty": "the brown fox likes Äpfel", "booleanProperty": true, "integerProperty": 33, "floatProperty": 12.345, "dateProperty": {"__type": "DateTimeImmutable", "value": "1980-12-13"}} | {} |
| a2a2 | Neos.ContentRepository.Testing:Page | a2a | {"text": "a2a2", "stringProperty": "the red fox", "booleanProperty": false, "integerProperty": 22, "floatProperty": 12.34, "dateProperty": {"__type": "DateTimeImmutable", "value": "1980-12-14"}} | {} |
# Note that this node is disabled! See below.
| a2a3 | Neos.ContentRepository.Testing:Page | a2a | {"text": "a2a3", "stringProperty": "the red Bear", "integerProperty": 19, "dateProperty": {"__type": "DateTimeImmutable", "value": "1980-12-12"}} | {} |
# Note that the node a2a3 is disabled! See below.
| a2a3-disabled | Neos.ContentRepository.Testing:Page | a2a | {"text": "a2a3", "stringProperty": "the red Bear", "integerProperty": 19, "dateProperty": {"__type": "DateTimeImmutable", "value": "1980-12-12"}} | {} |
| a2a4 | Neos.ContentRepository.Testing:Page | a2a | {"text": "a2a4", "stringProperty": "the brown Bear", "integerProperty": 19, "dateProperty": {"__type": "DateTimeImmutable", "value": "1980-12-12"}} | {} |
| a2a5 | Neos.ContentRepository.Testing:Page | a2a | {"text": "a2a5", "stringProperty": "the brown bear", "integerProperty": 19, "dateProperty": {"__type": "DateTimeImmutable", "value": "1980-12-13"}} | {} |
# Note that the node a2a6 must not contain any properties!
| a2a6-empty | Neos.ContentRepository.Testing:Page | a2a | {} | {} |
| b | Neos.ContentRepository.Testing:Page | home | {"text": "b"} | {} |
| b1 | Neos.ContentRepository.Testing:Page | b | {"text": "b1"} | {} |
And the current date and time is "2023-03-16T13:00:00+01:00"
Expand All @@ -90,16 +92,19 @@ Feature: Find and count nodes using the findChildNodes and countChildNodes queri
| nodeAggregateId | "a2a5" |
| propertyValues | {"integerProperty": 20} |
And the command DisableNodeAggregate is executed with payload:
| Key | Value |
| nodeAggregateId | "a2a3" |
| nodeVariantSelectionStrategy | "allVariants" |
| Key | Value |
| nodeAggregateId | "a2a3-disabled" |
| nodeVariantSelectionStrategy | "allVariants" |

Scenario:
# Child nodes without filter
When I execute the findChildNodes query for parent node aggregate id "home" I expect the nodes "terms,contact,a,b" to be returned
When I execute the findChildNodes query for parent node aggregate id "a" I expect the nodes "a1,a2" to be returned
When I execute the findChildNodes query for parent node aggregate id "a1" I expect no nodes to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" I expect the nodes "a2a1,a2a2,a2a4,a2a5" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" I expect the nodes "a2a1,a2a2,a2a4,a2a5,a2a6-empty" to be returned
# Child nodes with empty filter
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"searchTerm": ""}' I expect the nodes "a2a1,a2a2,a2a4,a2a5,a2a6-empty" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"nodeTypes": ""}' I expect the nodes "a2a1,a2a2,a2a4,a2a5,a2a6-empty" to be returned

# Child nodes filtered by node type
When I execute the findChildNodes query for parent node aggregate id "home" and filter '{"nodeTypes": "Neos.ContentRepository.Testing:AbstractPage"}' I expect the nodes "terms,contact,a,b" to be returned
Expand Down Expand Up @@ -149,10 +154,10 @@ Feature: Find and count nodes using the findChildNodes and countChildNodes queri
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"propertyValue": "stringProperty $=~ \"Bear\""}' I expect the nodes "a2a4,a2a5" to be returned

# Child nodes with custom ordering
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "text", "direction": "ASCENDING"}]}' I expect the nodes "a2a1,a2a2,a2a4,a2a5" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "text", "direction": "DESCENDING"}]}' I expect the nodes "a2a5,a2a4,a2a2,a2a1" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "non_existing", "direction": "ASCENDING"}]}' I expect the nodes "a2a1,a2a2,a2a4,a2a5" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "booleanProperty", "direction": "ASCENDING"}, {"type": "propertyName", "field": "dateProperty", "direction": "ASCENDING"}]}' I expect the nodes "a2a4,a2a5,a2a2,a2a1" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "timestampField", "field": "CREATED", "direction": "ASCENDING"}]}' I expect the nodes "a2a1,a2a2,a2a4,a2a5" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "timestampField", "field": "LAST_MODIFIED", "direction": "DESCENDING"}]}' I expect the nodes "a2a5,a2a1,a2a2,a2a4" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "text", "direction": "ASCENDING"}]}' I expect the nodes "a2a6-empty,a2a1,a2a2,a2a4,a2a5" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "text", "direction": "DESCENDING"}]}' I expect the nodes "a2a5,a2a4,a2a2,a2a1,a2a6-empty" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "non_existing", "direction": "ASCENDING"}]}' I expect the nodes "a2a1,a2a2,a2a4,a2a5,a2a6-empty" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "propertyName", "field": "booleanProperty", "direction": "ASCENDING"}, {"type": "propertyName", "field": "dateProperty", "direction": "ASCENDING"}]}' I expect the nodes "a2a6-empty,a2a4,a2a5,a2a2,a2a1" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "timestampField", "field": "CREATED", "direction": "ASCENDING"}]}' I expect the nodes "a2a1,a2a2,a2a4,a2a5,a2a6-empty" to be returned
When I execute the findChildNodes query for parent node aggregate id "a2a" and filter '{"ordering": [{"type": "timestampField", "field": "LAST_MODIFIED", "direction": "DESCENDING"}]}' I expect the nodes "a2a5,a2a1,a2a2,a2a4,a2a6-empty" to be returned

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
/**
* A search term for use in Filters for the {@see ContentSubgraphInterface} API.
*
* The search is defined the following:
* - test all properties if one contains the term
* - the term is checked case-insensitive
* - an empty term will lead to no filtering
* - FIXME: define the search behaviour across non-string-typed properties
*
* @api DTO for {@see ContentSubgraphInterface}
*/
final readonly class SearchTerm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public static function matchesNode(Node $node, SearchTerm $searchTerm): bool

public static function matchesSerializedPropertyValues(SerializedPropertyValues $serializedPropertyValues, SearchTerm $searchTerm): bool
{
if ($searchTerm->term === '') {
return true;
}
foreach ($serializedPropertyValues as $serializedPropertyValue) {
if (self::matchesValue($serializedPropertyValue->value, $searchTerm)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ public static function matchingArrayComparisonExamples(): iterable
}
}

public function emptySearchTermAlwaysMatches(): iterable
{
yield '1 property' => ['', self::value('foo')];
yield '1 empty property' => ['', self::value('foo')];
yield '0 properties' => ['', SerializedPropertyValues::fromArray([])];
}

public function notMatchingExamples(): iterable
{
Expand All @@ -125,6 +131,7 @@ public function notMatchingExamples(): iterable
* @dataProvider matchingNumberLikeComparisonExamples
* @dataProvider matchingBooleanLikeComparisonExamples
* @dataProvider matchingArrayComparisonExamples
* @dataProvider emptySearchTermAlwaysMatches
*/
public function searchTermMatchesProperties(
string $searchTerm,
Expand Down
4 changes: 2 additions & 2 deletions Neos.Neos/Classes/Controller/Service/NodesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public function indexAction(
string $contextNode = null,
array|string $nodeIdentifiers = []
): void {
$searchTerm = $searchTerm === '' ? null : SearchTerm::fulltext($searchTerm);
$searchTerm = SearchTerm::fulltext($searchTerm);
$nodeTypeCriteria = NodeTypeCriteria::create(
NodeTypeNames::fromStringArray($nodeTypes),
NodeTypeNames::createEmpty()
Expand Down Expand Up @@ -176,7 +176,7 @@ public function indexAction(
}
}
} else {
if (!empty($searchTerm)) {
if ($searchTerm->term !== '') {
throw new \RuntimeException('Combination of $nodeIdentifiers and $searchTerm not supported');
}

Expand Down
5 changes: 0 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,6 @@ parameters:
count: 1
path: Neos.Neos/Classes/Controller/Module/User/UserSettingsController.php

-
message: "#^Parameter \\#2 \\$searchTerm of static method Neos\\\\ContentRepository\\\\Core\\\\Projection\\\\ContentGraph\\\\Filter\\\\SearchTerm\\\\SearchTermMatcher\\:\\:matchesNode\\(\\) expects Neos\\\\ContentRepository\\\\Core\\\\Projection\\\\ContentGraph\\\\Filter\\\\SearchTerm\\\\SearchTerm, Neos\\\\ContentRepository\\\\Core\\\\Projection\\\\ContentGraph\\\\Filter\\\\SearchTerm\\\\SearchTerm\\|null given\\.$#"
count: 1
path: Neos.Neos/Classes/Controller/Service/NodesController.php

-
message: "#^The internal method \"Neos\\\\ContentRepository\\\\Core\\\\Projection\\\\ContentGraph\\\\Filter\\\\NodeType\\\\ExpandedNodeTypeCriteria\\:\\:matches\" is called\\.$#"
count: 1
Expand Down

0 comments on commit 0190e16

Please sign in to comment.