{"id":13546316,"url":"https://github.com/Flowpack/Flowpack.ElasticSearch.ContentRepositoryAdaptor","last_synced_at":"2025-04-02T18:30:34.899Z","repository":{"id":11181910,"uuid":"13559632","full_name":"Flowpack/Flowpack.ElasticSearch.ContentRepositoryAdaptor","owner":"Flowpack","description":"Flowpack.ElasticSearch adapter to support the Neos Content Repository ","archived":false,"fork":false,"pushed_at":"2025-03-15T07:32:48.000Z","size":1447,"stargazers_count":43,"open_issues_count":29,"forks_count":70,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-03-27T04:13:00.251Z","etag":null,"topics":["elasticsearch","hacktoberfest","neoscms"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Flowpack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-10-14T11:39:47.000Z","updated_at":"2024-10-21T19:01:57.000Z","dependencies_parsed_at":"2023-01-13T16:22:19.010Z","dependency_job_id":"53db751c-4e0a-484c-ab01-3cdaa61cdf9c","html_url":"https://github.com/Flowpack/Flowpack.ElasticSearch.ContentRepositoryAdaptor","commit_stats":{"total_commits":828,"total_committers":62,"mean_commits":13.35483870967742,"dds":0.5978260869565217,"last_synced_commit":"d9c1b777b529605725dd993220b843dcad4251e1"},"previous_names":[],"tags_count":72,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowpack%2FFlowpack.ElasticSearch.ContentRepositoryAdaptor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowpack%2FFlowpack.ElasticSearch.ContentRepositoryAdaptor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowpack%2FFlowpack.ElasticSearch.ContentRepositoryAdaptor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowpack%2FFlowpack.ElasticSearch.ContentRepositoryAdaptor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Flowpack","download_url":"https://codeload.github.com/Flowpack/Flowpack.ElasticSearch.ContentRepositoryAdaptor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246869597,"owners_count":20847156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elasticsearch","hacktoberfest","neoscms"],"created_at":"2024-08-01T12:00:35.476Z","updated_at":"2025-04-02T18:30:34.890Z","avatar_url":"https://github.com/Flowpack.png","language":"PHP","funding_links":[],"categories":["Community Packages"],"sub_categories":["Videos"],"readme":"[![Build Status](https://travis-ci.com/Flowpack/Flowpack.ElasticSearch.ContentRepositoryAdaptor.svg?branch=master)](https://travis-ci.com/Flowpack/Flowpack.ElasticSearch.ContentRepositoryAdaptor) [![Latest Stable Version](https://poser.pugx.org/flowpack/elasticsearch-contentrepositoryadaptor/v/stable)](https://packagist.org/packages/flowpack/elasticsearch-contentrepositoryadaptor) [![Total Downloads](https://poser.pugx.org/flowpack/elasticsearch-contentrepositoryadaptor/downloads)](https://packagist.org/packages/flowpack/elasticsearch-contentrepositoryadaptor)\n\n# Neos Elasticsearch Adapter\n\nThis project connects the Neos Content Repository to Elasticsearch; enabling two\nmain functionalities:\n\n* finding Nodes in Fusion / Eel by arbitrary queries\n* Full-Text Indexing of Pages and other Documents (of course including the full content)\n\nThis documentation is structured in the following parts:\n\n* [Installation](#installation): Which packages are needed and how they are installed.\n* [Commands](#commands): This section describes the available ./flow CLI commands\n* [Configuration](#configuration): Configuration of indices, fields, ...\n* [Query Data](#query-data): Available Eel operations to query data\n* [Examples](#examples): Some more advanced examples\n\n# Installation\n\n```\ncomposer require 'flowpack/elasticsearch-contentrepositoryadaptor'\n// Not required, but can be used to learn how to integration the flowpack/elasticsearch-contentrepositoryadaptor in your project\ncomposer require 'flowpack/searchplugin'\n```\nEnsure to update `\u003cyour-elasticsearch\u003e/config/elasticsearch.yml` as explained below; then start Elasticsearch.\n\nFinally, run `./flow nodeindex:build`, and add the search plugin to your page. It should \"just work\".\n\n## Relevant Packages\n\n* [Neos.ContentRepository.Search](https://www.neos.io/download-and-extend/packages/neos/neos-content-repository-search.html): provides common functionality for searching Neos Content Repository nodes. Does not contain a search backend.\n* [Flowpack.ElasticSearch](https://www.neos.io/download-and-extend/packages/flowpack/flowpack-elasticsearch.html): provides common code for working with Elasticsearch\n* [Flowpack.ElasticSearch.ContentRepositoryAdaptor](https://www.neos.io/download-and-extend/packages/flowpack/flowpack-elasticsearch-contentrepositoryadaptor.html): this package\n* [Flowpack.SimpleSearch.ContentRepositoryAdaptor](https://www.neos.io/download-and-extend/packages/flowpack/flowpack-simplesearch-contentrepositoryadaptor.html): an alternative search backend (to be used instead of this package); storing the search index in SQLite\n* [Flowpack.SearchPlugin](https://www.neos.io/download-and-extend/packages/flowpack/flowpack-searchplugin.html): search plugin for Neos\n\n## Elasticsearch and Neos compatibility\n\nThis following matrix shows the compatibility of this package to Elasticsearch and Neos versions:\n\n| CR Adaptor | Neos       | Elasticsearch | Status                                                                        |\n|------------|------------|---------------|-------------------------------------------------------------------------------|\n| 4          | 3.x, 4.x   | 1.x, 2,x 5.x  | Unmaintained, probably broken                                                 |\n| 5          | \u003e 3.3, 4.x | 5.x           | Unmaintained                                                                   | \n| 6          | 5.x        | 5.x           | Unmaintained                                                                   |\n| 7          | 5.x        | 6.x, 7.x      | Unmaintained                                                           |\n| 8          | 7.x, 8.x   | 6.x, 7.x, 8.x | Bugfix and Features                                                           |\n| 9          | 9.x        | 6.x, 7.x, 8.x | Bugfix and Features ([Upgrade Instructions](Documentation/Upgrade-8-to-9.md)) |\n\n_Currently the Driver interfaces are not marked as API, and can be changed to adapt to future needs._\n\n### Elasticsearch Configuration file elasticsearch.yml\n\nThe following general configuration advice can make your life easier:\n\n```yaml\n# the following settings secure your cluster\ncluster.name: [PUT_YOUR_CUSTOM_NAME_HERE]\nnode.name: [PUT_YOUR_CUSTOM_NAME_HERE]\nnetwork.host: _local_\n```\n\nThere may be a need, to add specific configuration to your Elasticsearch Configuration File\n`\u003cyour-elasticsearch\u003e/config/elasticsearch.yml`, depending on your version of Elasticsearch.\n\n- [Elasticsearch 5.x](Documentation/ElasticConfiguration-5.x.md)\n\n# Commands\n\nAll commands are operating on a single content repository. If you don't specify the content repository identifer by `--contentRepository` the `default` content repository is used.\n\n### Building up the index\n\nThe node index is updated on the fly, but during development you need to update it frequently.\n\nIn case of a mapping update, you need to reindex all nodes. Don't worry to do that in production;\nthe system transparently creates a new index, fills it completely, and when everything worked,\nchanges the index alias.\n\n\t./flow nodeindex:build\n\nif during development, you only want to index a few nodes, you can use \"limit\"\n\n\t./flow nodeindex:build --limit 20\n\n### Cleanup old indices\n\n In order to remove old, non-used indices, you should use this command from time to time:\n\n\t./flow nodeindex:cleanup\n\n### Debug commands\n\nThe following commands are meant to be used for debugging while configuring and developing your search:\n\n\t./flow nodeindexmapping:indices\n\nShows the mapping between the projects dimensions presets and the resulting index name.\n\n\t./flow nodeindexmapping:mapping\n\nShows the mapping created for the NodeTypes.\n\n\t./flow nodetype:showIndexableConfiguration\n\nShows a list of NodeTypes and if they are configured to be indexable\n\n\t./flow search:viewnode \u003cnodeIdentifier\u003e [\u003cdimensionCombinationAsJson\u003e] [\u003cfield\u003e]\n\nShows all contents that are indexed fo a given node.\n\n\t./flow search:fulltext\n\nPerforms a fulltext search and displays the results.\n\n\n# Configuration\n\n## Index Settings\n\nIf you want to fine-tune the indexing and mapping on a more detailed level, you can do so in the following way.\n\n### Configure the index name\n\nIf you need to run serveral (different) neos instances on the same elasticsearch server you will need to change the Configuration/Settings.yaml indexName for each of your project.\n\nSo `./flow nodeindex:build` or `./flow nodeindex:cleanup` won't overwrite your other sites index.\n\n```yaml\nNeos:\n  ContentRepository:\n    Search:\n      elasticSearch:\n        indexName: useMoreSpecificIndexName\n```\n\nIf you use multiple client configurations, please change the *default* key just below the *indexes*.\n\n### Configure per index\n\nYou can set one default configuration for all indices with your index prefix.\n\n```yaml\nFlowpack:\n  ElasticSearch:\n    indexes:\n      default: # Configuration bundle name\n        neoscontentrepository: # The index prefix name, must be the same as in the Neos.ContentRepository.Search.elasticSearch.indexName setting\n          settings:\n            index:\n              number_of_shards: 1\n              number_of_replicas: 0\n```\n\n### Configure per dimension\n\nAs an index is created for every dimension combination of the Neos content repository, you can configure the index behavior for every dimension combination separately.\n\n**Caution: Default configuration and per dimension combination configuration is not merged. If a configuration for a dimension-combination is found, this configuration is used.**\n\n```yaml\nFlowpack:\n  ElasticSearch:\n    indexes:\n      default: \n        'neoscontentrepository-0359ed5c416567b8bc2e5ade0f277b36': # The hash specifies the dimension combination\n          settings: \n            index:\n              number_of_shards: 1\n              number_of_replicas: 0\n            analysis:\n              filter:\n                elision:\n                  type: 'elision'\n                  articles: [ 'l', 'm', 't', 'qu', 'n', 's', 'j', 'd' ]\n              analyzer:\n                custom_french_analyzer:\n                  tokenizer: 'letter'\n                  filter: [ 'asciifolding', 'lowercase', 'french_stem', 'elision', 'stop' ]\n                tag_analyzer:\n                  tokenizer: 'keyword'\n                  filter: [ 'asciifolding', 'lowercase' ]\n```\n\nWhich dimension combinations are available in your system and which hashes they are identified with can be shown with the CLI command: \n\n\t./flow nodeindexmapping:indices\n\n### Configurations per property (index field)\n\nThen, you can change the analyzers on a per-field level; or e.g. reconfigure the _all field with the following snippet\nin the NodeTypes.yaml. Generally this works by defining the global mapping at `[nodeType].search.elasticSearchMapping`:\n\n```yaml\n'Neos.Neos:Node':\n  search:\n    elasticSearchMapping:\n      myProperty:\n        analyzer: custom_french_analyzer\n```\n\n## Exclude NodeTypes from indexing\n\nBy default the indexing processes all NodeTypes, but you can change this in your *Settings.yaml*:\n\n```yaml\nNeos:\n  ContentRepository:\n    Search:\n      defaultConfigurationPerNodeType:\n        '*':\n          indexed: true\n        'Neos.Neos:FallbackNode':\n          indexed: false\n        'Neos.Neos:Shortcut':\n          indexed: false\n        'Neos.Neos:ContentCollection':\n          indexed: false\n```\n\nYou need to explicitly configure the individual NodeTypes (this feature does not check the Super Type configuration).\nBut you  can use a special notation to configure a full namespace, `Acme.AcmeCom:*` will be applied for all node\ntypes in the `Acme.AcmeCom` namespace. The most specific configuration is used in this order: \n\n- NodeType name (`Neos.Neos:Shortcut`)\n- Full namespace notation (`Neos.Neos:*`)\n- Catch all (`*`)\n\n## Advanced Indexing configuration\n\n### Indexing configuration per data type\n\n**The default configuration supports most use cases and often may not need to be touched, as this package comes\nwith sane defaults for all Neos data types.**\n\nIndexing of properties is configured at two places. The defaults per-data-type are configured\ninside `Neos.ContentRepository.Search.defaultConfigurationPerType` of `Settings.yaml`.\nFurthermore, this can be overridden using the `properties.[....].search` path inside\n`NodeTypes.yaml`.\n\nThis configuration contains two parts:\n\n* Underneath `elasticSearchMapping`, the Elasticsearch property mapping can be defined.\n* Underneath `indexing`, an Eel expression which processes the value before indexing has to be\n  specified. It has access to the current `value` and the current `node`.\n\nExample (from the default configuration):\n```yaml\n # Settings.yaml\nNeos:\n  ContentRepository:\n    Search:\n      defaultConfigurationPerType:\n\n        # strings should just be indexed with their simple value.\n        string:\n          elasticSearchMapping:\n            type: string\n          indexing: '${value}'\n```\n\n### Indexing **configuration** per property\n\n```yaml\n # NodeTypes.yaml\n'My.Package:NodeType':\n  properties:\n    'dateTime':\n      search:\n\n        # A date should be mapped differently, and in this case we want to use a date format which\n        # Elasticsearch understands\n        elasticSearchMapping:\n          type: DateTime\n          format: 'date_time_no_millis'\n        indexing: '${(q(node).property(\"dateTime\") ? Date.format(q(node).property(\"dateTime\"), \"Y-m-d\\TH:i:sP\") : null)}'\n```\n\nIf your nodetypes schema defines custom properties of type DateTime, you have got to provide similar configuration for\nthem as well in your `NodeTypes.yaml`, or else they will not be indexed correctly.\n\nThere are a few indexing helpers inside the `Indexing` namespace which are usable inside the\n`indexing` expression. In most cases, you don't need to touch this, but they were needed to build up\nthe standard indexing configuration:\n\n* `Indexing.buildAllPathPrefixes`: for a path such as `foo/bar/baz`, builds up a list of path\n  prefixes, e.g. `['foo', 'foo/bar', 'foo/bar/baz']`.\n* `Indexing.extractNodeTypeNamesAndSupertypes(Node)`: extracts a list of node type names for\n  the passed node type and all of its supertypes\n* `Indexing.convertArrayOfNodesToArrayOfNodeIdentifiers(array $nodes)`: convert the given nodes to\n  their node identifiers.\n\n#### Skip indexing and mapping of a property\n\nIf you don't want a property to be indexed, set `indexing: false`. In this case no mapping is configured for this field. \nThis can be used to also solve a type conflict of two node properties with same name and different type. \n\n### Fulltext Indexing\n\nIn order to enable fulltext indexing, every `Document` node must be configured as *fulltext root*. Thus,\nthe following is configured in the default configuration:\n\n```yaml\n'Neos.Neos:Document':\n  search:\n    fulltext:\n      isRoot: true\n```\n\nA *fulltext root* contains all the *content* of its non-document children, such that when one searches\ninside these texts, the document itself is returned as result.\n\nIn order to specify how the fulltext of a property in a node should be extracted, this is configured\nin `NodeTypes.yaml` at `properties.[propertyName].search.fulltextExtractor`.\n\nAn example:\n\n```yaml\n'Neos.Neos.NodeTypes:Text':\n  properties:\n    'text':\n      search:\n        fulltextExtractor: '${Indexing.extractHtmlTags(value)}'\n\n'My.Blog:Post':\n  properties:\n    title:\n      search:\n        fulltextExtractor: '${Indexing.extractInto(\"h1\", value)}'\n```\n\n\n### Working with Dates\n\nAs a default, Elasticsearch indexes dates in the UTC Timezone. In order to have it index using the timezone\ncurrently configured in PHP, the configuration for any property in a node which represents a date should look like this:\n\n```yaml\n'My.Blog:Post':\n  properties:\n    date:\n      search:\n        elasticSearchMapping:\n          type: 'date'\n          format: 'date_time_no_millis'\n        indexing: '${(value ? Date.format(value, \"Y-m-d\\TH:i:sP\") : null)}'\n```\n\nThis is important so that Date- and Time-based searches work as expected, both when using formatted DateTime strings and\nwhen using relative DateTime calculations (eg.: `now`, `now+1d`).\n\nIf you want to filter items by date, e.g. to show items with date later than today, you can create a query like this:\n\n```\n${...greaterThan('date', Date.format(Date.Now(), \"Y-m-d\\TH:i:sP\"))...}\n```\n\nFor more information on Elasticsearch's Date Formats,\n[click here](http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html).\n\n\n### Working with Assets / Attachments\n\nIf you want to index attachments, you need to install the [Elasticsearch Ingest-Attachment Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/master/ingest-attachment.html).\nThen, you can add the following to your `Settings.yaml`:\n\n```yaml\nNeos:\n  ContentRepository:\n    Search:\n      defaultConfigurationPerType:\n        'Neos\\Media\\Domain\\Model\\Asset':\n          elasticSearchMapping:\n            type: text\n          indexing: ${Indexing.extractAssetContent(value)}\n```\n\nor add the attachments content to a fulletxt field in your NodeType configuration:\n\n```yaml\n  properties:\n    file:\n      type: 'Neos\\Media\\Domain\\Model\\Asset'\n      ui:\n      search:\n        fulltextExtractor: ${Indexing.extractInto('text', Indexing.extractAssetContent(value))}\n```\n\nBy default `Indexing.extractAssetContent(value)` returns the asset content. You can use the second parameter to return asset meta data. The field parameter can be set to one of the following: `content, title, name, author, keywords, date, content_type, content_length, language`. \n\nWith that, you can for example add the keywords of a file to a higher boosted field: \n\n```yaml\n  properties:\n    file:\n      type: 'Neos\\Media\\Domain\\Model\\Asset'\n      ui:\n      search:\n        fulltextExtractor: ${Indexing.extractInto('h2', Indexing.extractAssetContent(value, 'keywords'))}\n```\n\n\n# Query Data\n\nWe'll first show how to do arbitrary Elasticsearch Queries in Fusion. This is a more powerful\nalternative to FlowQuery. In the long run, we might be able to integrate this API back into FlowQuery,\nbut for now it works well as-is.\n\nGenerally, Elasticsearch queries are done using the `Search` Eel helper. In case you want\nto retrieve a *list of nodes*, you'll generally do:\n```\nnodes = ${Search.query(site)....execute()}\n```\n\nIn case you just want to retrieve a *single node*, the form of a query is as follows:\n```\nnodes = ${q(Search.query(site)....execute()).get(0)}\n```\n\nTo fetch the total number of hits a query returns, the form of a query is as follows:\n```\nnodes = ${Search.query(site)....count()}\n```\n\nAll queries search underneath a certain subnode. In case you want to search \"globally\", you will\nsearch underneath the current site node (like in the example above).\n\nFurthermore, the following operators are supported:\n\nAs **value**, the following methods accept a simple type, a node object or a DateTime object.\n\n| Query Operator | Description |\n|----------------|-------------|\n|`nodeType('Your.Node:Type')`                          |Filters on the given NodeType|\n|`exactMatch('propertyName', value)`                   |Supports simple types: `exactMatch('tag', 'foo')`, or node references: `exactMatch('author', authorNode)`|\n|`exclude('propertyName', value)`                      |Excludes results by property - the negation of exactMatch.\n|`greaterThan('propertyName', value, [clauseType])`    |Range filter with property values greater than the given value|\n|`greaterThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values greater than or equal to the given value|\n|`lessThan('propertyName', value, [clauseType])`       |Range filter with property values less than the given value|\n|`lessThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values less than or equal to the given value|\n|`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.|\n|`limit(5)`                                            |Only return five results. If not specified, the default limit by Elasticsearch applies (which is at 10 by default)|\n|`from(5)`                                                        |Return the results starting from the 6th one|\n|`prefix('propertyName', 'prefix', [clauseType])`                 |Adds a prefix filter on the given field with the given prefix|\n|`geoDistance(propertyName, geoPoint, distance, [clauseType])`.   |Filters documents that include only hits that exists within a specific distance from a geo point.|\n|`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**.|\n|`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()`)|\n|`highlight(fragmentSize, fragmentCount, noMatchSize, field)`     |Configure result highlighting for every fulltext field individually|\n\n## Search Result Highlighting\n\nWhen using the `.fulltext()` or `.simpleQueryStringFulltext()` operator to do a fulltext, **highlight snippets** ([elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/highlighting.html#highlighting)) are automatically queried. By default snippets with 150 characters are queried for all available fulltext fields with a 150 character fallback text. \n\nTo adjust this behavior you can first deactivate the default and then configure highlighting for every field individually:\n\n\tSearch.highlight(false).highlight(150, 2, 150, 'neos_fulltext.text').highlight(100, 1, 0, 'neos_fulltext.h2')\n\nThis deactivates the default highlighting and then queries 2 snipets of 150 characters each from hits in `neos_fulltext.text`, with a fallback to 150 characters of the beginning of the text if no match was found and additional 100 characters from `neos_fulltext.h2` without a fallback.\n\nThe highlight snippets can be accessed by\n\n\thighlight = ${Search(...).execute().searchHitForNode(node).highlight}\n\n\n## moreLikeThis(like, fields, options)\n\nThe More Like This Query (MLT Query) finds documents that are \"like\" a given text or a given set of documents.\n\n* `like` Single value or an array of strings or nodes.\n* `fields` An array of fields which are used to compare other docs with the given \"like\" definition.\n* `options` Additional options for the `more_like_this` query. See the [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-mlt-query.html) for what is possible.\n\n## Low-level operations\n\nFurthermore, there is a more low-level operator which can be used to add arbitrary Elasticsearch filters:\n\n* `queryFilter(\"filterType\", {option1: \"value1\"}, [clauseType])`\n\nThe optional argument `clauseType` defaults to \"must\" and can be used to specify the boolean operator of the [bool query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html). It has to be one of `must`, `should`, `must_not` or `filter`.\t\n\nAt lowest level, there is the `request` operator which allows to modify the request in arbitrary manner. Note that the existing request is merged with the passed-in type in case it is an array:\n\n* `request('query.filtered.query.bool.minimum_should_match', 1)`\n* `request('query.filtered.query.bool', {\"minimum_should_match\": 1})`\n\nIn order to debug the query more easily, the following operation is helpful:\n\n* `log()` log the full query on execution into the Elasticsearch log (i.e. in `Data/Logs/ElasticSearch.log`)\n\n## Example Queries\n\n### Finding all pages which are tagged in a special way and rendering them in an overview\n\nUse Case: On a \"Tag Overview\" page, you want to show all pages being tagged in a certain way\n\nSetup: You have two node types in a blog called `Acme.Blog:Post` and `Acme.Blog:Tag`, both\ninheriting from `Neos.Neos:Document`. The `Post` node type has a property `tags` which is\nof type `references`, pointing to `Tag` documents.\n\nFusion setup:\n\n```\n # for \"Tag\" documents, replace the main content area.\nprototype(Neos.Neos:PrimaryContent).acmeBlogTag {\n    condition = ${q(node).is('[instanceof Acme.Blog:Tag]')}\n    type = 'Acme.Blog:TagPage'\n}\n\n # The \"TagPage\"\nprototype(Acme.Blog:TagPage) \u003c prototype(Neos.Fusion:Collection) {\n    collection = ${Search.query(site).nodeType('Acme.Blog:Post').exactMatch('tags', node).sortDesc('creationDate').execute()}\n    itemName = 'node'\n    itemRenderer = Acme.Blog:SingleTag\n}\nprototype(Acme.Blog:SingleTag) \u003c prototype(Neos.Neos:Template) {\n    ...\n}\n```\n\n### Making OR queries\n\nThere's no OR operator provided in this package, so you need to use a custom Elasticsearch query filter for that:\n\n```\n....queryFilter('bool', {should: [\n    {term: {tags: tagNode.identifier}},\n    {term: {places: tagNode.identifier}},\n    {term: {projects: tagNode.identifier}}\n]})\n```\n\n## Aggregations\n\nAggregation is an easy way to aggregate your node data in different ways. Elasticsearch provides a couple of different types of\naggregations. Check `https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html` for more\ninfo about aggregations. You can use them to get some simple aggregations like min, max or average values for\nyour node data. Aggregations also allows you to build a complex filter for e.g. a product search or statistics.\n\n**Aggregation methods**\nRight now there are two methods implemented. One generic `aggregation` function that allows you to add any kind of\naggregation definition and a pre-configured `fieldBasedAggregation`. Both methods can be added to your TS search query.\nYou can nest aggregations by providing a parent name.\n\n* `aggregation($name, array $aggregationDefinition, $parentPath = NULL)` -- generic method to add a $aggregationDefinition under a path $parentPath with the name $name.\n* `fieldBasedAggregation($name, $field, $type = 'terms', $parentPath = '', $size = 10)` -- adds a simple filed based Aggregation of type $type with name $name under path $parentPath. Used for simple aggregations like sum, avg, min, max or terms. By default 10 buckets are returned.\n\n\n# Examples\n\n## Aggregations\n\n### Add a average aggregation\n\nTo add an average aggregation you can use the fieldBasedAggregation. This snippet would add an average aggregation for\na property price:\n```\nnodes = ${Search.query(site)...fieldBasedAggregation(\"avgprice\", \"price\", \"avg\").execute()}\n```\nNow you can access your aggregations inside your fluid template with\n```\n{nodes.aggregations}\n```\n\n### Create a nested aggregation\n\nIn this scenario you could have a node that represents a product with the properties price and color. If you would like\nto know the average price for all your colors you just nest an aggregation in your Fusion:\n```\nnodes = ${Search.query(site)...fieldBasedAggregation(\"colors\", \"color\").fieldBasedAggregation(\"avgprice\", \"price\", \"avg\", \"colors\").execute()}\n```\nThe first `fieldBasedAggregation` will add a simple terms aggregation (https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html)\nwith the name colors. So all different colors of your nodetype will be listed here.\nThe second `fieldBasedAggregation` will add another sub-aggregation named avgprice below your colors-aggregation.\n\nYou can nest even more aggregations like this:\n```\nfieldBasedAggregation(\"anotherAggregation\", \"field\", \"avg\", \"colors.avgprice\")\n```\n\n### Add a custom aggregation\n\nTo add a custom aggregation you can use the `aggregation()` method. All you have to do is to provide an array with your\naggregation definition. This example would do the same as the fieldBasedAggregation would do for you:\n```\naggregationDefinition = Neos.Fusion:DataStructure {\n    terms = Neos.Fusion:DataStructure {\n        field = \"color\"\n    }\n}\nnodes = ${Search.query(site)...aggregation(\"color\", this.aggregationDefinition).execute()}\n```\n\n#### Product filter\n\nThis is a more complex scenario. With this snippet we will create a full product filter based on your selected Nodes. Imagine\nan NodeTye ProductList with an property `products`. This property contains a comma separated list of sku's. This could also\nbe a reference on other products.\n\n```\nprototype(Vendor.Name:FilteredProductList) \u003c prototype(Neos.Neos:Content)\nprototype(Vendor.Name:FilteredProductList) {\n\n    // Create SearchFilter for products\n    searchFilter = Neos.Fusion:DataStructure {\n        sku = ${String.split(q(node).property(\"products\"), \",\")}\n    }\n\n    # Search for all products that matches your queryFilter and add aggregations\n    filter = ${Search.query(site).nodeType(\"Vendor.Name:Product\").queryFilterMultiple(this.searchFilter, \"must\").fieldBasedAggregation(\"color\", \"color\").fieldBasedAggregation(\"size\", \"size\").execute()}\n\n    # Add more filter if get/post params are set\n    searchFilter.color = ${request.arguments.color}\n    searchFilter.color.@if.onlyRenderWhenFilterColorIsSet = ${request.arguments.color != \"\"}\n    searchFilter.size = ${request.arguments.size}\n    searchFilter.size.@if.onlyRenderWhenFilterSizeIsSet = ${request.arguments.size != \"\"}\n\n    # filter your products\n    products = ${Search.query(site).nodeType(\"Vendor.Name:Product\").queryFilterMultiple(this.searchFilter, \"must\").execute()}\n\n    # don't cache this element\n    @cache {\n        mode = 'uncached'\n        context {\n            1 = 'node'\n            2 = 'site'\n        }\n    }\n```\n\nIn the first lines we will add a new searchFilter variable and add your selected sku's as a filter. Based on this selection\nwe will add two aggregations of type terms. You can access the filter in your template with `{filter.aggregations}`. With\nthis information it is easy to create a form with some select fields with all available options. If you submit the form\njust call the same page and add the get parameter color and/or size.\nThe next lines will parse those parameters and add them to the searchFilter. Based on your selection all products will\nbe fetched and passed to your template.\n\n\n**Important notice**\n\nIf you do use the terms filter be aware of Elasticsearchs analyze functionality for strings. You might want to disable this\nfor all your filterable properties, or else filtering won't work on them properly:\n```yaml\n'Vendor.Name:Product'\n  properties:\n    color:\n      type: string\n      defaultValue: ''\n      search:\n        elasticSearchMapping:\n          type: keyword\n```\n\n## Sorting\n\nThis package adapts Elasticsearchs sorting capabilities. You can add multiple sort operations to your query.\nRight now there are three methods you can use:\n\n* `sortAsc('propertyName')`\n* `sortDesc('propertyName')`\n* `sort('configuration')`\n\nJust append those method to your query like this:\n```\n# Sort ascending by property title\n\nnodes = ${q(Search.query(site).....sortAsc(\"title\").execute())}\n\n# Sort for multiple properties\n\nnodes = ${q(Search.query(site).....sortAsc(\"title\").sortDesc(\"name\").execute())}\n\n# Custom sort operation\n\ngeoSorting = Neos.Fusion:DataStructure {\n    _geo_distance = Neos.Fusion:DataStructure {\n        latlng = Neos.Fusion:DataStructure {\n            lat = 51.512711\n            lon = 7.453084\n        }\n        order = \"plane\"\n        unit = \"km\"\n        distance_type = \"sloppy_arc\"\n    }\n}\nnodes = ${Search.query(site).....sort(this.geoSorting).execute()}\n\n```\nCheck https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html for more configuration\noptions.\n\n### Example with pagination and sort by distance\n\nThis is how a more complex example could look like. Imagine you a want to render a list of nodes and in addition to each\nnode you want to display the distance to a specific point.\n\nFirst of all you have to define a property in your NodeTypes.yaml for your node where to store lat/lon information's:\n```yaml\n'Vendor.Name:Retailer':\n  properties:\n    'latlng':\n      type: string\n      search:\n        elasticSearchMapping:\n          type: \"geo_point\"\n```\n\nQuery your nodes in your Fusion:\n```\n    geoSorting = Neos.Fusion:DataStructure {\n\t_geo_distance {\n\t    geopoint { \n\t\tlat = 51.512711\n\t        lon = 7.453084\n\t    }\n\t    order = \"asc\"\n\t    unit = \"km\"\n\t    ignore_unmapped = true\n\t}\n    }\n\nnodes = ${Search.query(site).nodeType('Vendor.Name:Retailer').sort(this.geoSorting)}\n```\n\nNow you can paginate that nodes in your template. To get your actually distance for each node use\nthe `GetHitArrayForNodeViewHelper`:\n```\n{namespace cr=Neos\\ContentRepository\\Search\\ViewHelpers}\n{namespace es=Flowpack\\ElasticSearch\\ContentRepositoryAdaptor\\ViewHelpers}\n\n\u003ccr:widget.paginate query=\"{nodes}\" as=\"paginatedNodes\"\u003e\n    \u003cf:for each=\"{paginatedNodes}\" as=\"singleNode\"\u003e\n        {singleNode.name} - \u003ces:getHitArrayForNode queryResultObject=\"{nodes}\" node=\"{singleNode}\" path=\"sort.0\" /\u003e\n    \u003c/f:for\u003e\n\u003c/cr:widget.paginate\u003e\n\n```\n\nThe ViewHelper will use \\Neos\\Utility\\Arrays::getValueByPath() to return a specified path. So you can make use\nof an array or a string. Check the documentation \\Neos\\Utility\\Arrays::getValueByPath() for more information.\n\n**Important notice**\n\nThe ViewHelper GetHitArrayForNode will return the raw hit result array. The path property allows you to access some\nspecific data like the the sort data. If there is only one value for your path the value will be returned.\nIf there is more data the full array will be returned by GetHitArrayForNode-VH. So you might have to use the\nForViewHelper to access your sort values.\n\n\n## Fulltext Search / Indexing\n\nWhen searching in a fulltext index, we want to show Pages, or, generally speaking, everything\nwhich is a `Document` node. However, the main content of a certain `Document` is often not stored\nin the node itself, but inside its (`Content`) child nodes.\n\nThis is why we need some special functionality for indexing, which *adds the content of the inner\nnodes* to the `Document` nodes where they belong to, to a field called `neos_fulltext` and\n`neos_fulltext_parts`.\n\nFurthermore, we want that a fulltext match e.g. inside a headline is seen as *more important* than\na match inside the normal body text. That's why the `Document` node not only contains one field with\nall the texts, but multiple \"buckets\" where text is added to: One field which contains everything\ndeemed as \"very important\" (`neos_fulltext.h1`), one which is \"less important\" (`neos_fulltext.h2`),\nand finally one for the plain text (`neos_fulltext.text`). All of these fields are configured with different `boost` values.\n\n**For a search user interface, checkout the Flowpack.SearchPlugin package**\n\n## Suggestions\n\nElasticsearch offers an easy way to get query suggestions based on your query. Check\n`https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html` for more information about how\nyou can build and use suggestion in your search.\n\n**Suggestion methods implemented**\n\nThere are two methods implemented. `suggestions` is a generic one that allows to build the suggestion query of your\ndreams. The other method is `termSuggestions` and is meant for basic term suggestions. They can be added to your totaly\nawesome TS search query.\n\n* `suggestions($name, array $suggestionDefinition)` -- generic method to be filled with your own suggestionQuery\n* `termSuggestions($term, $field = '_all', $name = 'suggestions'` -- simple term suggestion query on all fields\n\n### Examples\n\n#### Add a simple suggestion to a query\n\nSimple suggestion that returns a suggestion based on the sent term\n\n```\nsuggestions = ${Search.query(site)...termSuggestions('someTerm')}\n```\nYou can access your suggestions inside your fluid template with\n```\n{nodes.suggestions}\n```\n\n### Add a custom suggestion\n\nPhrase query that returns query suggestions\n\n```\nsuggestionsQueryDefinition = Neos.Fusion:DataStructure {\n    text = 'some Text'\n    simple_phrase = Neos.Fusion:DataStructure {\n        phrase = Neos.Fusion:DataStructure {\n            analyzer = 'body'\n            field = 'bigram'\n            size = 1\n            real_world_error_likelihood = 0.95\n            ...\n        }\n    }\n}\nsuggestions = ${Search.query(site)...suggestions('my_suggestions', this.suggestionsQueryDefinition)}\n```\n\n## Debugging\n\nIn order to understand what's going on, the following might be helpful:\n\n* use `./flow nodeindex:showMapping` to show the currently defined Elasticsearch Mapping\n* use the `.log()` statement inside queries to dump them to the Elasticsearch Log\n* the logfile `Data/Logs/ElasticSearch.log` contains loads of helpful information.\n\n**Settings.yaml**\n\n1. Change the base namespace for configuration from `Flowpack.ElasticSearch.ContentRepositoryAdaptor`\n   to `Neos.ContentRepository.Search`. All further adjustments are made underneath this namespace:\n2. (If it exists in your configuration:) Move `indexName` to `elasticSearch.indexName`\n3. (If it exists in your configuration:) Move `log` to `elasticSearch.log`\n4. search for `mapping` (inside `defaultConfigurationPerType.\u003ctypeName\u003e`) and replace it by\n   `elasticSearchMapping`.\n5. Inside the `indexing` expressions (at `defaultConfigurationPerType.\u003ctypeName\u003e`), replace\n   `ElasticSearch.` by `Indexing.`.\n\n**NodeTypes.yaml**\n\n1. Replace `elasticSearch` by `search`. This replaces both `\u003cYourNodeType\u003e.elasticSearch`\n   and `\u003cYourNodeType\u003e.properties.\u003cpropertyName\u003e.elasticSearch`.\n2. search for `mapping` (inside `\u003cYourNodeType\u003e.properties.\u003cpropertyName\u003e.search`) and replace it by\n   `elasticSearchMapping`.\n3. Replace `ElasticSeach.fulltext` by `Indexing`\n4. Search for `ElasticSearch.` (inside the `indexing` expressions) and replace them by `Indexing.`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFlowpack%2FFlowpack.ElasticSearch.ContentRepositoryAdaptor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFlowpack%2FFlowpack.ElasticSearch.ContentRepositoryAdaptor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFlowpack%2FFlowpack.ElasticSearch.ContentRepositoryAdaptor/lists"}