Skip to content

Commit

Permalink
FEATURE: Add Node-Type Filter
Browse files Browse the repository at this point in the history
Create a method which allows to create a nodetype filter
to include and exclude specific nodetypes
  • Loading branch information
erkenes committed Feb 16, 2023
1 parent 2a7de69 commit c750f23
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 17 deletions.
98 changes: 98 additions & 0 deletions Classes/Eel/ElasticSearchQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Flowpack\ElasticSearch\ContentRepositoryAdaptor\ElasticSearchClient;
use Flowpack\ElasticSearch\ContentRepositoryAdaptor\Exception;
use Flowpack\ElasticSearch\ContentRepositoryAdaptor\Exception\QueryBuildingException;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
use Neos\Flow\Log\ThrowableStorageInterface;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\Flow\Persistence\Exception\IllegalObjectTypeException;
Expand Down Expand Up @@ -61,6 +62,12 @@ class ElasticSearchQueryBuilder implements QueryBuilderInterface, ProtectedConte
*/
protected $throwableStorage;

/**
* @Flow\Inject
* @var NodeTypeManager
*/
protected $nodeTypeManager;

/**
* @var boolean
*/
Expand Down Expand Up @@ -131,6 +138,97 @@ public function nodeType(string $nodeType): QueryBuilderInterface
return $this->queryFilter('term', ['neos_type_and_supertypes' => $nodeType]);
}

/**
* Filter multiple node types
*
* @param string|array $nodeTypeFilter
* @return ElasticSearchQueryBuilder
* @throws QueryBuildingException
* @api
*/
public function nodeTypeFilter($nodeTypeFilter): QueryBuilderInterface
{
$nodeTypeFilterConstraints = $this->getNodeTypeFilterConstraints($nodeTypeFilter);

$excludeShould = [];
foreach ($nodeTypeFilterConstraints['excludeNodeTypes'] as $nodeType) {
$excludeShould[] = [
'term' => [
'neos_type_and_supertypes' => $nodeType
]
];
}
if (!empty($excludeShould)) {
$this->request->queryFilter(
'bool',
[
'should' => $excludeShould
],
'must_not'
);
}

foreach ($nodeTypeFilterConstraints['includeNodeTypes'] as $nodeType) {
$includeShould[] = [
'term' => [
'neos_type_and_supertypes' => $nodeType
]
];
}
if (!empty($includeShould)) {
$this->request->queryFilter(
'bool',
[
'should' => $includeShould
],
'must'
);
}

return $this;
}

/**
* Generates a two-dimensional array with the filters. First level is:
* 'excludeNodeTypes'
* 'includeNodeTypes'
*
* Both are numeric arrays with the respective node types that are included or excluded.
*
* @param string|array $nodeTypeFilter
* @return array
*/
protected function getNodeTypeFilterConstraints($nodeTypeFilter): array
{
$constraints = [
'excludeNodeTypes' => [],
'includeNodeTypes' => []
];

$nodeTypeFilterParts = $nodeTypeFilter;

if (is_string($nodeTypeFilter)) {
$nodeTypeFilterParts = implode(',', $nodeTypeFilterParts);
}

foreach ($nodeTypeFilterParts as $nodeTypeFilterPart) {
$nodeTypeFilterPart = trim($nodeTypeFilterPart);
$constraintType = 'includeNodeTypes';

if (strpos($nodeTypeFilterPart, '!') === 0) {
$nodeTypeFilterPart = substr($nodeTypeFilterPart, 1);
$constraintType = 'excludeNodeTypes';
}

$nodeTypeFilterPartSubTypes = array_merge([$nodeTypeFilterPart], $this->nodeTypeManager->getSubNodeTypes($nodeTypeFilterPart));
foreach ($nodeTypeFilterPartSubTypes as $nodeTypeFilterPartSubType) {
$constraints[$constraintType][(string)$nodeTypeFilterPartSubType] = (string)$nodeTypeFilterPartSubType;
}
}

return $constraints;
}

/**
* Sort descending by $propertyName
*
Expand Down
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,23 +427,24 @@ Furthermore, the following operators are supported:

As **value**, the following methods accept a simple type, a node object or a DateTime object.

| Query Operator | Description |
|----------------|-------------|
|`nodeType('Your.Node:Type')` |Filters on the given NodeType|
|`exactMatch('propertyName', value)` |Supports simple types: `exactMatch('tag', 'foo')`, or node references: `exactMatch('author', authorNode)`|
|`exclude('propertyName', value)` |Excludes results by property - the negation of exactMatch.
|`greaterThan('propertyName', value, [clauseType])` |Range filter with property values greater than the given value|
|`greaterThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values greater than or equal to the given value|
|`lessThan('propertyName', value, [clauseType])` |Range filter with property values less than the given value|
|`lessThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values less than or equal to the given value|
|`sortAsc('propertyName')` / `sortDesc('propertyName')`|Can also be used multiple times, e.g. `sortAsc('tag').sortDesc('date')` will first sort by tag ascending, and then by date descending.|
|`limit(5)` |Only return five results. If not specified, the default limit by Elasticsearch applies (which is at 10 by default)|
|`from(5)` |Return the results starting from the 6th one|
|`prefix('propertyName', 'prefix', [clauseType])` |Adds a prefix filter on the given field with the given prefix|
|`geoDistance(propertyName, geoPoint, distance, [clauseType])`. |Filters documents that include only hits that exists within a specific distance from a geo point.|
|`fulltext('searchWord', options)` |Does a query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-query-string-query.html) to the query_string. Recommendation: **use simpleQueryStringFulltext instead, as it yields better results and is more tolerant to user input**.|
|`simpleQueryStringFulltext('searchWord', options)` |Does a simple_query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/8.3/query-dsl-simple-query-string-query.html) to the simple_query_string. Supports phrase matching like `"firstname lastname"` and tolerates broken input without exceptions (in contrast to `fulltext()`)|
|`highlight(fragmentSize, fragmentCount, noMatchSize, field)` |Configure result highlighting for every fulltext field individually|
| Query Operator | Description |
|----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `nodeType('Your.Node:Type')` | Filters on the given NodeType |
| `nodeTypeFilter('Your.Node:Type,!Your.ExcludedNode:Type')` | Filters multiple NodeTypes |
| `exactMatch('propertyName', value)` | Supports simple types: `exactMatch('tag', 'foo')`, or node references: `exactMatch('author', authorNode)` |
| `exclude('propertyName', value)` | Excludes results by property - the negation of exactMatch. |
| `greaterThan('propertyName', value, [clauseType])` | Range filter with property values greater than the given value |
| `greaterThanOrEqual('propertyName', value, [clauseType])` | Range filter with property values greater than or equal to the given value |
| `lessThan('propertyName', value, [clauseType])` | Range filter with property values less than the given value |
| `lessThanOrEqual('propertyName', value, [clauseType])` | Range filter with property values less than or equal to the given value |
| `sortAsc('propertyName')` / `sortDesc('propertyName')` | Can also be used multiple times, e.g. `sortAsc('tag').sortDesc('date')` will first sort by tag ascending, and then by date descending. |
| `limit(5)` | Only return five results. If not specified, the default limit by Elasticsearch applies (which is at 10 by default) |
| `from(5)` | Return the results starting from the 6th one |
| `prefix('propertyName', 'prefix', [clauseType])` | Adds a prefix filter on the given field with the given prefix |
| `geoDistance(propertyName, geoPoint, distance, [clauseType])`. | Filters documents that include only hits that exists within a specific distance from a geo point. |
| `fulltext('searchWord', options)` | Does a query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-query-string-query.html) to the query_string. Recommendation: **use simpleQueryStringFulltext instead, as it yields better results and is more tolerant to user input**. |
| `simpleQueryStringFulltext('searchWord', options)` | Does a simple_query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/8.3/query-dsl-simple-query-string-query.html) to the simple_query_string. Supports phrase matching like `"firstname lastname"` and tolerates broken input without exceptions (in contrast to `fulltext()`) |
| `highlight(fragmentSize, fragmentCount, noMatchSize, field)` | Configure result highlighting for every fulltext field individually |

## Search Result Highlighting

Expand Down

0 comments on commit c750f23

Please sign in to comment.