From 821962df237108d0ef5fd930e7a454d2c1c20a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 21 Oct 2014 21:16:13 +0200 Subject: [PATCH 01/13] EZP-23465: document CustomFieldInterface as applicable on both criteria and sort clauses --- .../Values/Content/Query/CustomFieldInterface.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eZ/Publish/API/Repository/Values/Content/Query/CustomFieldInterface.php b/eZ/Publish/API/Repository/Values/Content/Query/CustomFieldInterface.php index 6d140780d6c..2c1e3a5d840 100644 --- a/eZ/Publish/API/Repository/Values/Content/Query/CustomFieldInterface.php +++ b/eZ/Publish/API/Repository/Values/Content/Query/CustomFieldInterface.php @@ -10,19 +10,19 @@ namespace eZ\Publish\API\Repository\Values\Content\Query; /** - * Interface for criteria, which define a custom field mapping + * Interface for criteria and sort clauses, which defines a custom field mapping * - * Allows to map the field in a certain type to a custom colum / field / index + * Allows to map the field in a certain type to a custom column / field / index * in the search backend and retrieve it back from the criterion. The SPI - * implementation may or may not handle this information for criteria - * implementing this interface. + * implementation may or may not handle this information for criteria and + * sort clauses implementing this interface. */ interface CustomFieldInterface { /** - * Set a custom field to query + * Set a custom field to query or sort on * - * Set a custom field to query for a defined field in a defined type. + * Set a custom field to query or sort on for a defined field in a defined type. * * @param string $type * @param string $field @@ -32,7 +32,7 @@ interface CustomFieldInterface public function setCustomField( $type, $field, $customField ); /** - * Return the custom field to query if set + * Return the custom field to query or sort on if set * * @param string $type * @param string $field From 0a1cb7f28907b05e3422731d433e3a5267479970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 21 Oct 2014 21:17:12 +0200 Subject: [PATCH 02/13] EZP-23465: implement CustomFieldInterface on Field sort clause --- .../Values/Content/Query/SortClause/Field.php | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/eZ/Publish/API/Repository/Values/Content/Query/SortClause/Field.php b/eZ/Publish/API/Repository/Values/Content/Query/SortClause/Field.php index f5c37e2e51c..8b9a93bfaa2 100644 --- a/eZ/Publish/API/Repository/Values/Content/Query/SortClause/Field.php +++ b/eZ/Publish/API/Repository/Values/Content/Query/SortClause/Field.php @@ -13,12 +13,20 @@ use eZ\Publish\API\Repository\Values\Content\Query; use eZ\Publish\API\Repository\Values\Content\Query\SortClause; +use eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface; /** * Sets sort direction on a field value for a content query */ -class Field extends SortClause +class Field extends SortClause implements CustomFieldInterface { + /** + * Custom fields to sort by instead of the default field + * + * @var array + */ + protected $customFields = array(); + /** * Constructs a new Field SortClause on Type $typeIdentifier and Field $fieldIdentifier * @@ -35,4 +43,40 @@ public function __construct( $typeIdentifier, $fieldIdentifier, $sortDirection = new FieldTarget( $typeIdentifier, $fieldIdentifier, $languageCode ) ); } + + /** + * Set a custom field to sort by + * + * Set a custom field to sort by for a defined field in a defined type. + * + * @param string $type + * @param string $field + * @param string $customField + * + * @return void + */ + public function setCustomField( $type, $field, $customField ) + { + $this->customFields[$type][$field] = $customField; + } + + /** + * Return custom field + * + * If no custom field is set, return null + * + * @param string $type + * @param string $field + * + * @return mixed + */ + public function getCustomField( $type, $field ) + { + if ( !isset( $this->customFields[$type][$field] ) ) + { + return null; + } + + return $this->customFields[$type][$field]; + } } From 11507dad5073942ad53af9299258a36e8994dcd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 21 Oct 2014 21:18:19 +0200 Subject: [PATCH 03/13] EZP-23465: add getDefaultField() on Indexable and implement it --- eZ/Publish/Core/FieldType/Country/SearchField.php | 15 +++++++++++++++ eZ/Publish/Core/FieldType/Integer/SearchField.php | 15 +++++++++++++++ .../Core/FieldType/MapLocation/SearchField.php | 15 +++++++++++++++ eZ/Publish/Core/FieldType/Price/SearchField.php | 15 +++++++++++++++ .../Core/FieldType/TextLine/SearchField.php | 15 +++++++++++++++ eZ/Publish/Core/FieldType/Unindexed.php | 15 +++++++++++++++ eZ/Publish/SPI/FieldType/Indexable.php | 12 ++++++++++++ 7 files changed, 102 insertions(+) diff --git a/eZ/Publish/Core/FieldType/Country/SearchField.php b/eZ/Publish/Core/FieldType/Country/SearchField.php index d141225653e..13c6444ce0f 100644 --- a/eZ/Publish/Core/FieldType/Country/SearchField.php +++ b/eZ/Publish/Core/FieldType/Country/SearchField.php @@ -47,4 +47,19 @@ public function getIndexDefinition() 'value' => new Search\FieldType\MultipleStringField(), ); } + + /** + * Get name of the default field to be used for query and sort. + * + * As field types can index multiple fields (see MapLocation field type's + * implementation of this interface), this method is used to define default + * field for query and sort. Default field is typically used by Field + * criterion and sort clause. + * + * @return string + */ + public function getDefaultField() + { + return "value"; + } } diff --git a/eZ/Publish/Core/FieldType/Integer/SearchField.php b/eZ/Publish/Core/FieldType/Integer/SearchField.php index 4c6bde0b1f4..2a36fc43ff8 100644 --- a/eZ/Publish/Core/FieldType/Integer/SearchField.php +++ b/eZ/Publish/Core/FieldType/Integer/SearchField.php @@ -47,4 +47,19 @@ public function getIndexDefinition() 'value' => new Search\FieldType\IntegerField(), ); } + + /** + * Get name of the default field to be used for query and sort. + * + * As field types can index multiple fields (see MapLocation field type's + * implementation of this interface), this method is used to define default + * field for query and sort. Default field is typically used by Field + * criterion and sort clause. + * + * @return string + */ + public function getDefaultField() + { + return "value"; + } } diff --git a/eZ/Publish/Core/FieldType/MapLocation/SearchField.php b/eZ/Publish/Core/FieldType/MapLocation/SearchField.php index 5543bc5f26f..257b896bfa7 100644 --- a/eZ/Publish/Core/FieldType/MapLocation/SearchField.php +++ b/eZ/Publish/Core/FieldType/MapLocation/SearchField.php @@ -56,4 +56,19 @@ public function getIndexDefinition() 'value_location' => new Search\FieldType\GeoLocationField() ); } + + /** + * Get name of the default field to be used for query and sort. + * + * As field types can index multiple fields (see MapLocation field type's + * implementation of this interface), this method is used to define default + * field for query and sort. Default field is typically used by Field + * criterion and sort clause. + * + * @return string + */ + public function getDefaultField() + { + return "value_address"; + } } diff --git a/eZ/Publish/Core/FieldType/Price/SearchField.php b/eZ/Publish/Core/FieldType/Price/SearchField.php index 1b43728b0b5..fe876ec960c 100644 --- a/eZ/Publish/Core/FieldType/Price/SearchField.php +++ b/eZ/Publish/Core/FieldType/Price/SearchField.php @@ -49,4 +49,19 @@ public function getIndexDefinition() 'value' => new Search\FieldType\PriceField(), ); } + + /** + * Get name of the default field to be used for query and sort. + * + * As field types can index multiple fields (see MapLocation field type's + * implementation of this interface), this method is used to define default + * field for query and sort. Default field is typically used by Field + * criterion and sort clause. + * + * @return string + */ + public function getDefaultField() + { + return "value"; + } } diff --git a/eZ/Publish/Core/FieldType/TextLine/SearchField.php b/eZ/Publish/Core/FieldType/TextLine/SearchField.php index 481ff817541..ea026411b8d 100644 --- a/eZ/Publish/Core/FieldType/TextLine/SearchField.php +++ b/eZ/Publish/Core/FieldType/TextLine/SearchField.php @@ -47,4 +47,19 @@ public function getIndexDefinition() 'value' => new Search\FieldType\MultipleStringField(), ); } + + /** + * Get name of the default field to be used for query and sort. + * + * As field types can index multiple fields (see MapLocation field type's + * implementation of this interface), this method is used to define default + * field for query and sort. Default field is typically used by Field + * criterion and sort clause. + * + * @return string + */ + public function getDefaultField() + { + return "value"; + } } diff --git a/eZ/Publish/Core/FieldType/Unindexed.php b/eZ/Publish/Core/FieldType/Unindexed.php index 78720b5cb3a..aa44f39df30 100644 --- a/eZ/Publish/Core/FieldType/Unindexed.php +++ b/eZ/Publish/Core/FieldType/Unindexed.php @@ -38,5 +38,20 @@ public function getIndexDefinition() { return array(); } + + /** + * Get name of the default field to be used for query and sort. + * + * As field types can index multiple fields (see MapLocation field type's + * implementation of this interface), this method is used to define default + * field for query and sort. Default field is typically used by Field + * criterion and sort clause. + * + * @return string + */ + public function getDefaultField() + { + return null; + } } diff --git a/eZ/Publish/SPI/FieldType/Indexable.php b/eZ/Publish/SPI/FieldType/Indexable.php index c5b45f71d8f..0c9e46fa806 100644 --- a/eZ/Publish/SPI/FieldType/Indexable.php +++ b/eZ/Publish/SPI/FieldType/Indexable.php @@ -34,5 +34,17 @@ public function getIndexData( Field $field ); * @return \eZ\Publish\SPI\Search\FieldType[] */ public function getIndexDefinition(); + + /** + * Get name of the default field to be used for query and sort. + * + * As field types can index multiple fields (see MapLocation field type's + * implementation of this interface), this method is used to define default + * field for query and sort. Default field is typically used by Field + * criterion and sort clause. + * + * @return string + */ + public function getDefaultField(); } From a1e1b451e41035cae3fb5dae264789cd38762b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 21 Oct 2014 21:19:13 +0200 Subject: [PATCH 04/13] EZP-23465: refactor FieldMap to support custom/multiple fields and field map extraction --- .../Elasticsearch/Content/Search/FieldMap.php | 219 +++++++++++++----- 1 file changed, 156 insertions(+), 63 deletions(-) diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php index 603df16f67a..79d9e8f5cb1 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php @@ -12,9 +12,13 @@ use eZ\Publish\SPI\Persistence\Content\Type\Handler as ContentTypeHandler; use eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface; use eZ\Publish\Core\Persistence\Solr\Content\Search\FieldRegistry; +use eZ\Publish\API\Repository\Values\Content\Query\SortClause; +use eZ\Publish\API\Repository\Values\Content\Query\Criterion; +use RuntimeException; /** - * Provides field mapping information + * Provides field mapping information for criteria and sort clauses + * targeting Content fields. */ class FieldMap { @@ -65,25 +69,23 @@ public function __construct( } /** - * Get field type information for criterion + * Get field type information * * Returns an array in the form: * * * array( - * "field-identifier" => array( - * "elasticsearch_field_name", + * "content-type-identifier" => array( + * "field-definition-identifier" => "field-type-identifier", * … * ), * … * ) * * - * @param \eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface $criterion - * * @return array */ - public function getFieldTypes( CustomFieldInterface $criterion ) + protected function getFieldMap() { // @TODO: temp fixed by disabling caching, see https://jira.ez.no/browse/EZP-22834 $this->fieldTypes = array(); @@ -99,21 +101,8 @@ public function getFieldTypes( CustomFieldInterface $criterion ) continue; } - if ( $customField = $criterion->getCustomField( $contentType->identifier, $fieldDefinition->identifier ) ) - { - $this->fieldTypes[$fieldDefinition->identifier]["custom"][] = $customField; - continue; - } - - $fieldType = $this->fieldRegistry->getType( $fieldDefinition->fieldType ); - foreach ( $fieldType->getIndexDefinition() as $name => $type ) - { - $this->fieldTypes[$fieldDefinition->identifier][$type->type][] = - $this->nameGenerator->getTypedName( - $this->nameGenerator->getName( $name, $fieldDefinition->identifier, $contentType->identifier ), - $type - ); - } + $this->fieldTypes[$contentType->identifier][$fieldDefinition->identifier] = + $fieldDefinition->fieldType; } } } @@ -122,61 +111,165 @@ public function getFieldTypes( CustomFieldInterface $criterion ) } /** - * Get field type information for sort clause + * For the given parameters returns a set of index storage field names to search on. * - * TODO: handle custom field - * TODO: caching (see above) + * The method will check for custom fields if given $criterion implements + * CustomFieldInterface. With optional parameters $fieldTypeIdentifier and + * $name specific field type and field from its Indexable implementation + * can be targeted. * - * @param string $contentTypeIdentifier + * @see \eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface + * @see \eZ\Publish\SPI\FieldType\Indexable + * + * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $criterion * @param string $fieldDefinitionIdentifier - * @param string $languageCode + * @param string $fieldTypeIdentifier + * @param string $name * * @return array */ - public function getSortFieldTypes( $contentTypeIdentifier, $fieldDefinitionIdentifier, $languageCode ) + public function getFieldNames( + Criterion $criterion, + $fieldDefinitionIdentifier, + $fieldTypeIdentifier = null, + $name = null + ) { - $types = array(); + $fieldMap = $this->getFieldMap(); + $fieldNames = array(); - foreach ( $this->contentTypeHandler->loadAllGroups() as $group ) + foreach ( $fieldMap as $contentTypeIdentifier => $fieldIdentifierMap ) { - foreach ( $this->contentTypeHandler->loadContentTypes( $group->id ) as $contentType ) + // First check if field exists in the current ContentType, there is nothing to do if it doesn't + if ( !isset( $fieldIdentifierMap[$fieldDefinitionIdentifier] ) ) { - if ( $contentType->identifier !== $contentTypeIdentifier ) - { - continue; - } + continue; + } - foreach ( $contentType->fieldDefinitions as $fieldDefinition ) - { - if ( $fieldDefinition->identifier !== $fieldDefinitionIdentifier ) - { - continue; - } + // If $fieldTypeIdentifier is given it must match current field definition + if ( + $fieldTypeIdentifier !== null && + $fieldTypeIdentifier !== $fieldIdentifierMap[$fieldDefinitionIdentifier] + ) + { + continue; + } - // TODO: find a better way to handle non-translatable fields? - if ( $languageCode === null || $fieldDefinition->isTranslatable ) - { - $fieldType = $this->fieldRegistry->getType( $fieldDefinition->fieldType ); - - foreach ( $fieldType->getIndexDefinition() as $name => $type ) - { - $types[$type->type] = - $this->nameGenerator->getTypedName( - $this->nameGenerator->getName( - $name, - $fieldDefinition->identifier, - $contentType->identifier - ), - $type - ); - } - } + $fieldNames[] = $this->getIndexFieldName( + $criterion, + $contentTypeIdentifier, + $fieldDefinitionIdentifier, + $fieldIdentifierMap[$fieldDefinitionIdentifier], + $name + ); + } - break 3; - } - } + return $fieldNames; + } + + /** + * For the given parameters returns index storage field name to sort on or + * null if the field could not be found. + * + * The method will check for custom fields if given $sortClause implements + * CustomFieldInterface. With optional parameter $name specific field from + * field type's Indexable implementation can be targeted. + * + * Will return null if no sortable field is found. + * + * @see \eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface + * @see \eZ\Publish\SPI\FieldType\Indexable + * + * @param \eZ\Publish\API\Repository\Values\Content\Query\SortClause $sortClause + * @param string $contentTypeIdentifier + * @param string $fieldDefinitionIdentifier + * @param string $name + * + * @return null|string + */ + public function getSortFieldName( + SortClause $sortClause, + $contentTypeIdentifier, + $fieldDefinitionIdentifier, + $name = null + ) + { + $fieldMap = $this->getFieldMap(); + + // First check if field exists in type, there is nothing to do if it doesn't + if ( !isset( $fieldMap[$contentTypeIdentifier][$fieldDefinitionIdentifier] ) ) + { + return null; + } + + return $this->getIndexFieldName( + $sortClause, + $contentTypeIdentifier, + $fieldDefinitionIdentifier, + $fieldMap[$contentTypeIdentifier][$fieldDefinitionIdentifier], + $name + ); + } + + /** + * Returns index field name for the given parameters. + * + * @param object $criterionOrSortClause + * @param string $contentTypeIdentifier + * @param string $fieldDefinitionIdentifier + * @param string $fieldTypeIdentifier + * @param string $name + * + * @return mixed|string + */ + protected function getIndexFieldName( + $criterionOrSortClause, + $contentTypeIdentifier, + $fieldDefinitionIdentifier, + $fieldTypeIdentifier, + $name + ) + { + // If criterion or sort clause implements CustomFieldInterface and custom field is set for + // ContentType/FieldDefinition, return it + if ( + $criterionOrSortClause instanceof CustomFieldInterface && + $customFieldName = $criterionOrSortClause->getCustomField( + $contentTypeIdentifier, + $fieldDefinitionIdentifier + ) + ) + { + return $customFieldName; + } + + // Else, generate field name from field type's index definition + + $indexFieldType = $this->fieldRegistry->getType( $fieldTypeIdentifier ); + + // If $name is not given use default search field name + if ( $name === null ) + { + $name = $indexFieldType->getDefaultField(); + } + + $indexDefinition = $indexFieldType->getIndexDefinition(); + + // Should only happen by mistake, so let's throw if it does + if ( !isset( $indexDefinition[$name] ) ) + { + throw new RuntimeException( + "Could not find '{$name}' field in '{$fieldTypeIdentifier}' field type's index definition" + ); } - return $types; + return $this->nameGenerator->getTypedName( + $this->nameGenerator->getName( + $name, + $fieldDefinitionIdentifier, + $contentTypeIdentifier + ), + $indexDefinition[$name] + ); } } From a70cf0419b7bb372ab6cf35bad20cc00da9e3b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 21 Oct 2014 21:20:10 +0200 Subject: [PATCH 05/13] EZP-23465: adapt criteria and sort clause visitors --- .../Content/Search/CriterionVisitor/Field.php | 24 ++++++++++--- .../Search/CriterionVisitor/Field/FieldIn.php | 36 ++++++++----------- .../CriterionVisitor/Field/FieldRange.php | 20 +++++------ .../Field/MapLocationDistanceRange.php | 32 +++++++++-------- .../Search/SortClauseVisitor/Field/Field.php | 17 ++++----- .../Field/MapLocationDistance.php | 11 +++--- .../Search/SortClauseVisitor/FieldBase.php | 18 ++++++---- 7 files changed, 84 insertions(+), 74 deletions(-) diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field.php index 1b09de6e59e..cfa2d5fd5ef 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field.php @@ -11,7 +11,7 @@ use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\CriterionVisitor; use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap; -use eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface; +use eZ\Publish\API\Repository\Values\Content\Query\Criterion; /** * Base class for Field criterion visitors @@ -36,13 +36,27 @@ public function __construct( FieldMap $fieldMap ) } /** - * Get field type information + * Get field names + * + * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $criterion + * @param string $fieldDefinitionIdentifier + * @param string $fieldTypeIdentifier + * @param string $name * - * @param \eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface $criterion * @return array */ - protected function getFieldTypes( CustomFieldInterface $criterion ) + protected function getFieldNames( + Criterion $criterion, + $fieldDefinitionIdentifier, + $fieldTypeIdentifier = null, + $name = null + ) { - return $this->fieldMap->getFieldTypes( $criterion ); + return $this->fieldMap->getFieldNames( + $criterion, + $fieldDefinitionIdentifier, + $fieldTypeIdentifier, + $name + ); } } diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldIn.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldIn.php index 22b0d748067..bc1c9eccacd 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldIn.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldIn.php @@ -50,12 +50,11 @@ public function canVisit( Criterion $criterion ) */ protected function getCondition( Criterion $criterion ) { - /** @var \eZ\Publish\API\Repository\Values\Content\Query\Criterion\Field $criterion */ - $fieldTypes = $this->getFieldTypes( $criterion ); + $fieldNames = $this->getFieldNames( $criterion, $criterion->target ); $values = (array)$criterion->value; - if ( !isset( $fieldTypes[$criterion->target] ) ) + if ( empty( $fieldNames ) ) { throw new InvalidArgumentException( "\$criterion->target", @@ -63,26 +62,21 @@ protected function getCondition( Criterion $criterion ) ); } + $fields = array(); + foreach ( $fieldNames as $name ) + { + $fields[] = "fields_doc." . $name; + } + $terms = array(); - foreach ( $fieldTypes[$criterion->target] as $type => $names ) + foreach ( $values as $value ) { - // TODO possibly we'll need to dispatch by $type, need more tests - $fields = array(); - - foreach ( $names as $name ) - { - $fields[] = "fields_doc." . $name; - } - - foreach ( $values as $value ) - { - $terms[] = array( - "multi_match" => array( - "query" => $value, - "fields" => $fields, - ), - ); - } + $terms[] = array( + "multi_match" => array( + "query" => $value, + "fields" => $fields, + ), + ); } return array( diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldRange.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldRange.php index ef0336fd943..f5763c22763 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldRange.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/FieldRange.php @@ -53,11 +53,10 @@ public function canVisit( Criterion $criterion ) */ protected function getCondition( Criterion $criterion ) { - /** @var \eZ\Publish\API\Repository\Values\Content\Query\Criterion\Field $criterion */ - $fieldTypes = $this->getFieldTypes( $criterion ); + $fieldNames = $this->getFieldNames( $criterion, $criterion->target ); $criterion->value = (array)$criterion->value; - if ( !isset( $fieldTypes[$criterion->target] ) ) + if ( empty( $fieldNames ) ) { throw new InvalidArgumentException( "\$criterion->target", @@ -70,16 +69,13 @@ protected function getCondition( Criterion $criterion ) $range = $this->getRange( $criterion->operator, $start, $end ); $ranges = array(); - foreach ( $fieldTypes[$criterion->target] as $names ) + foreach ( $fieldNames as $name ) { - foreach ( $names as $name ) - { - $ranges[] = array( - "range" => array( - "fields_doc." . $name => $range, - ), - ); - } + $ranges[] = array( + "range" => array( + "fields_doc." . $name => $range, + ), + ); } return $ranges; diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php index 4761c3aeed1..d20931efd7e 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php @@ -22,11 +22,18 @@ class MapLocationDistanceRange extends Field { /** - * Name of the field type that criterion can handle + * Identifier of the field type that criterion can handle * * @var string */ - protected $typeName = "ez_geolocation"; + protected $fieldTypeName = "ezgmaplocation"; + + /** + * Name of the field type's indexed field that criterion can handle + * + * @var string + */ + protected $fieldName = "value_location"; /** * Check if visitor is applicable to current criterion @@ -68,10 +75,14 @@ protected function getCondition( Criterion $criterion ) $start *= 1000; $end *= 1000; - $fieldTypes = $this->getFieldTypes( $criterion ); + $fieldNames = $this->getFieldNames( + $criterion, + $criterion->target, + $this->fieldTypeName, + $this->fieldName + ); - if ( !isset( $fieldTypes[$criterion->target][$this->typeName] ) && - !isset( $fieldTypes[$criterion->target]["custom"] ) ) + if ( empty( $fieldNames ) ) { throw new InvalidArgumentException( "\$criterion->target", @@ -83,17 +94,8 @@ protected function getCondition( Criterion $criterion ) $location = $criterion->valueData; $range = $this->getRange( $criterion->operator, $start, $end ); - if ( isset( $fieldTypes[$criterion->target]["custom"] ) ) - { - $names = $fieldTypes[$criterion->target]["custom"]; - } - else - { - $names = $fieldTypes[$criterion->target][$this->typeName]; - } - $filters = array(); - foreach ( $names as $name ) + foreach ( $fieldNames as $name ) { $filter = $range; $filter["fields_doc.{$name}"] = array( diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php index b4fbcc0996c..07b530ba80f 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php @@ -41,24 +41,19 @@ public function visit( SortClause $sortClause ) { /** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\FieldTarget $target */ $target = $sortClause->targetData; - $types = $this->getFieldTypes( + $fieldName = $this->getSortFieldName( + $sortClause, $target->typeIdentifier, - $target->fieldIdentifier, - $target->languageCode + $target->fieldIdentifier ); - if ( empty( $types ) ) + if ( $fieldName === null ) { throw new RuntimeException( "No sortable fields found for '{$target->fieldIdentifier}' on '{$target->typeIdentifier}'" ); } - // TODO: should we somehow define/control what is to be used for sorting in this case? - if ( count( $types ) > 1 ) - { - throw new RuntimeException( "Multiple sortable fields found" ); - } - - $fieldName = reset( $types ); + /** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\FieldTarget $target */ + $target = $sortClause->targetData; return array( "fields_doc.{$fieldName}" => array( diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php index 983c9579b62..3c2e0b5c9cd 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php @@ -13,6 +13,7 @@ use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\SortClauseVisitor; use eZ\Publish\API\Repository\Values\Content\Query\SortClause; use RuntimeException; +use eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface; /** * Visits the MapLocationDistance sort clause @@ -51,18 +52,20 @@ public function visit( SortClause $sortClause ) { /** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\MapLocationTarget $target */ $target = $sortClause->targetData; - $types = $this->getFieldTypes( + $fieldName = $this->getSortFieldName( + $sortClause, $target->typeIdentifier, $target->fieldIdentifier, - $target->languageCode + "value_location" ); - if ( empty( $types ) || !isset( $types["ez_geolocation"] ) ) + if ( $fieldName === null ) { throw new RuntimeException( "No sortable fields found" ); } - $fieldName = $types["ez_geolocation"]; + /** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\MapLocationTarget $target */ + $target = $sortClause->targetData; return array( "_geo_distance" => array( diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/FieldBase.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/FieldBase.php index 53aa5699cdb..218f5f0a903 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/FieldBase.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/FieldBase.php @@ -11,7 +11,6 @@ use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\SortClauseVisitor; use eZ\Publish\API\Repository\Values\Content\Query\SortClause; -use eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\FieldTarget; use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap; /** @@ -35,20 +34,27 @@ public function __construct( FieldMap $fieldMap ) } /** - * Get field type information + * Get sort field name * + * @param \eZ\Publish\API\Repository\Values\Content\Query\SortClause $sortClause * @param string $contentTypeIdentifier * @param string $fieldDefinitionIdentifier - * @param string $languageCode + * @param string $name * * @return array */ - protected function getFieldTypes( $contentTypeIdentifier, $fieldDefinitionIdentifier, $languageCode ) + protected function getSortFieldName( + SortClause $sortClause, + $contentTypeIdentifier, + $fieldDefinitionIdentifier, + $name = null + ) { - return $this->fieldMap->getSortFieldTypes( + return $this->fieldMap->getSortFieldName( + $sortClause, $contentTypeIdentifier, $fieldDefinitionIdentifier, - $languageCode + $name ); } From 6f72ceb06cc517b130d7742594f1d90e6e0018c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 21 Oct 2014 21:20:43 +0200 Subject: [PATCH 06/13] EZP-23465: test custom sort field --- .../Repository/Tests/SearchServiceTest.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/eZ/Publish/API/Repository/Tests/SearchServiceTest.php b/eZ/Publish/API/Repository/Tests/SearchServiceTest.php index 62cd7f832eb..00d6f01ba05 100644 --- a/eZ/Publish/API/Repository/Tests/SearchServiceTest.php +++ b/eZ/Publish/API/Repository/Tests/SearchServiceTest.php @@ -2411,6 +2411,46 @@ public function testQueryModifiedField() ); } + /** + * Test for the findContent() method. + * + * This tests first explicitly creates sort clause on the 'short_name' which is empty + * for all Content instances of 'folder' ContentType. Custom sort field is then set + * to the index storage name of folder's 'name' field, in order to show the custom + * sort field working. + * + * @see \eZ\Publish\API\Repository\SearchService::findContent() + * @depends eZ\Publish\API\Repository\Tests\RepositoryTest::testGetSearchService + */ + public function testSortModifiedField() + { + $setupFactory = $this->getSetupFactory(); + if ( !$setupFactory instanceof LegacyElasticsearch ) + { + $this->markTestIncomplete( "Field sort clause is not yet implemented for Solr Storage Engine" ); + } + + $sortClause = new SortClause\Field( "folder", "short_name", Query::SORT_ASC, "eng-US" ); + $sortClause->setCustomField( "folder", "short_name", "folder_name_value_ms" ); + + $query = new Query( + array( + "filter" => new Criterion\ContentTypeId( 1 ), + "offset" => 0, + "limit" => null, + "sortClauses" => array( + $sortClause, + new SortClause\ContentId(), + ) + ) + ); + + $this->assertQueryFixture( + $query, + $this->getFixtureDir() . "/SortFolderName.php" + ); + } + /** * @return \eZ\Publish\API\Repository\Values\ContentType\ContentType */ From d5cafa44bc74c5976a75fb42d14a22744070f925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 28 Oct 2014 12:13:37 +0100 Subject: [PATCH 07/13] EZP-23465: update FullText visitor --- .../Search/CriterionVisitor/FullText.php | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/FullText.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/FullText.php index 13f10e3731e..f8240833017 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/FullText.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/FullText.php @@ -40,12 +40,14 @@ public function __construct( FieldMap $fieldMap ) /** * Get field type information * - * @param \eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface $criterion + * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $criterion + * @param string $fieldDefinitionIdentifier + * * @return array */ - protected function getFieldTypes( CustomFieldInterface $criterion ) + protected function getFieldNames( Criterion $criterion, $fieldDefinitionIdentifier ) { - return $this->fieldMap->getFieldTypes( $criterion ); + return $this->fieldMap->getFieldNames( $criterion, $fieldDefinitionIdentifier ); } /** @@ -71,28 +73,20 @@ public function canVisit( Criterion $criterion ) */ protected function getCondition( Criterion $criterion ) { - /** @var \eZ\Publish\API\Repository\Values\Content\Query\Criterion\FullText $criterion */ - $fields = $this->getFieldTypes( $criterion ); - // Add field document custom _all field $queryFields = array( "fields_doc.meta_all_*", ); // Add boosted fields if any + /** @var \eZ\Publish\API\Repository\Values\Content\Query\Criterion\FullText $criterion */ foreach ( $criterion->boost as $field => $boost ) { - if ( !isset( $fields[$field] ) ) - { - continue; - } + $fieldNames = $this->getFieldNames( $criterion, $field ); - foreach ( $fields[$field] as $fieldNames ) + foreach ( $fieldNames as $fieldName ) { - foreach ( $fieldNames as $fieldName ) - { - $queryFields[] = sprintf( "fields_doc.{$fieldName}^%.1f", $boost ); - } + $queryFields[] = sprintf( "fields_doc.{$fieldName}^%.1f", $boost ); } } From 8130d0632d8fa7a7e3523dab8a6baf4846f4435e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 28 Oct 2014 15:48:12 +0100 Subject: [PATCH 08/13] EZP-23465: expose method to make it testable --- .../Core/Persistence/Elasticsearch/Content/Search/FieldMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php index 79d9e8f5cb1..e636b9cd046 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/FieldMap.php @@ -222,7 +222,7 @@ public function getSortFieldName( * * @return mixed|string */ - protected function getIndexFieldName( + public function getIndexFieldName( $criterionOrSortClause, $contentTypeIdentifier, $fieldDefinitionIdentifier, From 2c016f27a12ed54a2d022e6a0a80800b9cf5b020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 28 Oct 2014 15:48:48 +0100 Subject: [PATCH 09/13] EZP-23465: added unit tests --- .../Tests/Content/Search/FieldMapTest.php | 724 ++++++++++++++++++ .../Elasticsearch/Tests/TestCase.php | 19 + 2 files changed, 743 insertions(+) create mode 100644 eZ/Publish/Core/Persistence/Elasticsearch/Tests/Content/Search/FieldMapTest.php create mode 100644 eZ/Publish/Core/Persistence/Elasticsearch/Tests/TestCase.php diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Tests/Content/Search/FieldMapTest.php b/eZ/Publish/Core/Persistence/Elasticsearch/Tests/Content/Search/FieldMapTest.php new file mode 100644 index 00000000000..b55d9ddabd2 --- /dev/null +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Tests/Content/Search/FieldMapTest.php @@ -0,0 +1,724 @@ +getMockedFieldMap( array( "getFieldMap", "getIndexFieldName" ) ); + $criterionMock = $this->getCriterionMock(); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getFieldMap" ) + ->will( + $this->returnValue( + array( + "content_type_identifier_1" => array( + "field_definition_identifier_1" => "field_type_identifier_1", + ), + "content_type_identifier_2" => array( + "field_definition_identifier_2" => "field_type_identifier_2", + ), + ) + ) + ); + + $fieldNames = $mockedFieldMap->getFieldNames( + $criterionMock, + "field_definition_identifier_1", + "field_type_identifier_2", + "field_name" + ); + + $this->assertInternalType( "array", $fieldNames ); + $this->assertEmpty( $fieldNames ); + } + + public function testGetFieldNames() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap", "getIndexFieldName" ) ); + $criterionMock = $this->getCriterionMock(); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getFieldMap" ) + ->will( + $this->returnValue( + array( + "content_type_identifier_1" => array( + "field_definition_identifier_1" => "field_type_identifier_1", + ), + "content_type_identifier_2" => array( + "field_definition_identifier_1" => "field_type_identifier_2", + "field_definition_identifier_2" => "field_type_identifier_3", + ), + ) + ) + ); + + $mockedFieldMap + ->expects( $this->at( 1 ) ) + ->method( "getIndexFieldName" ) + ->with( + $this->isInstanceOf( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\Criterion" + ), + "content_type_identifier_1", + "field_definition_identifier_1", + "field_type_identifier_1", + null + ) + ->will( $this->returnValue( "index_field_name_1" ) ); + + $mockedFieldMap + ->expects( $this->at( 2 ) ) + ->method( "getIndexFieldName" ) + ->with( + $this->isInstanceOf( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\Criterion" + ), + "content_type_identifier_2", + "field_definition_identifier_1", + "field_type_identifier_2", + null + ) + ->will( $this->returnValue( "index_field_name_2" ) ); + + $fieldNames = $mockedFieldMap->getFieldNames( + $criterionMock, + "field_definition_identifier_1" + ); + + $this->assertInternalType( "array", $fieldNames ); + $this->assertEquals( + array( + "index_field_name_1", + "index_field_name_2", + ), + $fieldNames + ); + } + + public function testGetFieldNamesWithNamedField() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap", "getIndexFieldName" ) ); + $criterionMock = $this->getCriterionMock(); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getFieldMap" ) + ->will( + $this->returnValue( + array( + "content_type_identifier_1" => array( + "field_definition_identifier_1" => "field_type_identifier_1", + ), + "content_type_identifier_2" => array( + "field_definition_identifier_1" => "field_type_identifier_2", + "field_definition_identifier_2" => "field_type_identifier_3", + ), + ) + ) + ); + + $mockedFieldMap + ->expects( $this->at( 1 ) ) + ->method( "getIndexFieldName" ) + ->with( + $this->isInstanceOf( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\Criterion" + ), + "content_type_identifier_1", + "field_definition_identifier_1", + "field_type_identifier_1", + "field_name" + ) + ->will( $this->returnValue( "index_field_name_1" ) ); + + $mockedFieldMap + ->expects( $this->at( 2 ) ) + ->method( "getIndexFieldName" ) + ->with( + $this->isInstanceOf( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\Criterion" + ), + "content_type_identifier_2", + "field_definition_identifier_1", + "field_type_identifier_2", + "field_name" + ) + ->will( $this->returnValue( "index_field_name_2" ) ); + + $fieldNames = $mockedFieldMap->getFieldNames( + $criterionMock, + "field_definition_identifier_1", + null, + "field_name" + ); + + $this->assertInternalType( "array", $fieldNames ); + $this->assertEquals( + array( + "index_field_name_1", + "index_field_name_2", + ), + $fieldNames + ); + } + + public function testGetFieldNamesWithTypedField() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap", "getIndexFieldName" ) ); + $criterionMock = $this->getCriterionMock(); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getFieldMap" ) + ->will( + $this->returnValue( + array( + "content_type_identifier_1" => array( + "field_definition_identifier_1" => "field_type_identifier_1", + ), + "content_type_identifier_2" => array( + "field_definition_identifier_1" => "field_type_identifier_2", + "field_definition_identifier_2" => "field_type_identifier_3", + ), + ) + ) + ); + + $mockedFieldMap + ->expects( $this->at( 1 ) ) + ->method( "getIndexFieldName" ) + ->with( + $this->isInstanceOf( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\Criterion" + ), + "content_type_identifier_2", + "field_definition_identifier_1", + "field_type_identifier_2", + null + ) + ->will( $this->returnValue( "index_field_name_1" ) ); + + $fieldNames = $mockedFieldMap->getFieldNames( + $criterionMock, + "field_definition_identifier_1", + "field_type_identifier_2", + null + ); + + $this->assertInternalType( "array", $fieldNames ); + $this->assertEquals( + array( + "index_field_name_1", + ), + $fieldNames + ); + } + + public function testGetFieldNamesWithTypedAndNamedField() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap", "getIndexFieldName" ) ); + $criterionMock = $this->getCriterionMock(); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getFieldMap" ) + ->will( + $this->returnValue( + array( + "content_type_identifier_1" => array( + "field_definition_identifier_1" => "field_type_identifier_1", + ), + "content_type_identifier_2" => array( + "field_definition_identifier_1" => "field_type_identifier_2", + "field_definition_identifier_2" => "field_type_identifier_3", + ), + ) + ) + ); + + $mockedFieldMap + ->expects( $this->at( 1 ) ) + ->method( "getIndexFieldName" ) + ->with( + $this->isInstanceOf( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\Criterion" + ), + "content_type_identifier_2", + "field_definition_identifier_1", + "field_type_identifier_2", + "field_name" + ) + ->will( $this->returnValue( "index_field_name_1" ) ); + + $fieldNames = $mockedFieldMap->getFieldNames( + $criterionMock, + "field_definition_identifier_1", + "field_type_identifier_2", + "field_name" + ); + + $this->assertInternalType( "array", $fieldNames ); + $this->assertEquals( + array( + "index_field_name_1", + ), + $fieldNames + ); + } + + public function testGetSortFieldName() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap", "getIndexFieldName" ) ); + $sortClauseMock = $this->getSortClauseMock(); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getFieldMap" ) + ->will( + $this->returnValue( + array( + "content_type_identifier" => array( + "field_definition_identifier" => "field_type_identifier", + ), + ) + ) + ); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getIndexFieldName" ) + ->with( + $this->isInstanceOf( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\SortClause" + ), + "content_type_identifier", + "field_definition_identifier", + "field_type_identifier", + "field_name" + ) + ->will( $this->returnValue( "index_field_name" ) ); + + $fieldName = $mockedFieldMap->getSortFieldName( + $sortClauseMock, + "content_type_identifier", + "field_definition_identifier", + "field_name" + ); + + $this->assertEquals( "index_field_name", $fieldName ); + } + + public function testGetSortFieldNameReturnsNull() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap", "getIndexFieldName" ) ); + $sortClauseMock = $this->getSortClauseMock(); + + $mockedFieldMap + ->expects( $this->once() ) + ->method( "getFieldMap" ) + ->will( + $this->returnValue( + array( + "content_type_identifier" => array( + "field_definition_identifier" => "field_type_identifier", + ), + ) + ) + ); + + $fieldName = $mockedFieldMap->getSortFieldName( + $sortClauseMock, + "non_existent_content_type_identifier", + "non_existent_field_definition_identifier", + "field_name" + ); + + $this->assertNull( $fieldName ); + } + + public function testGetIndexFieldNameCustomField() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap" ) ); + + $customFieldMock = $this->getMock( + "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\CustomFieldInterface" + ); + $customFieldMock + ->expects( $this->once() ) + ->method( "getCustomField" ) + ->with( + "content_type_identifier", + "field_definition_identifier" + ) + ->will( + $this->returnValue( "custom_field_name" ) + ); + + $customFieldName = $mockedFieldMap->getIndexFieldName( + $customFieldMock, + "content_type_identifier", + "field_definition_identifier", + "dummy", + "dummy" + ); + + $this->assertEquals( "custom_field_name", $customFieldName ); + } + + public function testGetIndexFieldNameNamedField() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap" ) ); + $indexFieldType = $this->getIndexFieldTypeMock(); + $searchFieldTypeMock = $this->getSearchFieldTypeMock(); + + $this->fieldRegistryMock + ->expects( $this->once() ) + ->method( "getType" ) + ->with( "field_type_identifier" ) + ->will( + $this->returnValue( $indexFieldType ) + ); + + $indexFieldType + ->expects( $this->once() ) + ->method( "getIndexDefinition" ) + ->will( + $this->returnValue( + array( + "field_name" => $searchFieldTypeMock, + ) + ) + ); + + $indexFieldType->expects( $this->never() )->method( "getDefaultField" ); + + $this->fieldNameGeneratorMock + ->expects( $this->once() ) + ->method( "getName" ) + ->with( + "field_name", + "field_definition_identifier", + "content_type_identifier" + ) + ->will( + $this->returnValue( "generated_field_name" ) + ); + + $this->fieldNameGeneratorMock + ->expects( $this->once() ) + ->method( "getTypedName" ) + ->with( + "generated_field_name", + $this->isInstanceOf( "eZ\\Publish\\SPI\\Search\\FieldType" ) + ) + ->will( + $this->returnValue( "generated_typed_field_name" ) + ); + + $fieldName = $mockedFieldMap->getIndexFieldName( + new ArrayObject(), + "content_type_identifier", + "field_definition_identifier", + "field_type_identifier", + "field_name" + ); + + $this->assertEquals( "generated_typed_field_name", $fieldName ); + } + + public function testGetIndexFieldNameDefaultField() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap" ) ); + $indexFieldType = $this->getIndexFieldTypeMock(); + $searchFieldTypeMock = $this->getSearchFieldTypeMock(); + + $this->fieldRegistryMock + ->expects( $this->once() ) + ->method( "getType" ) + ->with( "field_type_identifier" ) + ->will( + $this->returnValue( $indexFieldType ) + ); + + $indexFieldType + ->expects( $this->once() ) + ->method( "getDefaultField" ) + ->will( + $this->returnValue( "field_name" ) + ); + + $indexFieldType + ->expects( $this->once() ) + ->method( "getIndexDefinition" ) + ->will( + $this->returnValue( + array( + "field_name" => $searchFieldTypeMock, + ) + ) + ); + + $this->fieldNameGeneratorMock + ->expects( $this->once() ) + ->method( "getName" ) + ->with( + "field_name", + "field_definition_identifier", + "content_type_identifier" + ) + ->will( + $this->returnValue( "generated_field_name" ) + ); + + $this->fieldNameGeneratorMock + ->expects( $this->once() ) + ->method( "getTypedName" ) + ->with( + "generated_field_name", + $this->isInstanceOf( "eZ\\Publish\\SPI\\Search\\FieldType" ) + ) + ->will( + $this->returnValue( "generated_typed_field_name" ) + ); + + $fieldName = $mockedFieldMap->getIndexFieldName( + new ArrayObject(), + "content_type_identifier", + "field_definition_identifier", + "field_type_identifier", + null + ); + + $this->assertEquals( "generated_typed_field_name", $fieldName ); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetIndexFieldNameDefaultFieldThrowsRuntimeException() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap" ) ); + $indexFieldType = $this->getIndexFieldTypeMock(); + $searchFieldTypeMock = $this->getSearchFieldTypeMock(); + + $this->fieldRegistryMock + ->expects( $this->once() ) + ->method( "getType" ) + ->with( "field_type_identifier" ) + ->will( + $this->returnValue( $indexFieldType ) + ); + + $indexFieldType + ->expects( $this->once() ) + ->method( "getDefaultField" ) + ->will( + $this->returnValue( "non_existent_field_name" ) + ); + + $indexFieldType + ->expects( $this->once() ) + ->method( "getIndexDefinition" ) + ->will( + $this->returnValue( + array( + "field_name" => $searchFieldTypeMock, + ) + ) + ); + + $mockedFieldMap->getIndexFieldName( + new ArrayObject(), + "content_type_identifier", + "field_definition_identifier", + "field_type_identifier", + null + ); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetIndexFieldNameNamedFieldThrowsRuntimeException() + { + $mockedFieldMap = $this->getMockedFieldMap( array( "getFieldMap" ) ); + $indexFieldType = $this->getIndexFieldTypeMock(); + $searchFieldTypeMock = $this->getSearchFieldTypeMock(); + + $this->fieldRegistryMock + ->expects( $this->once() ) + ->method( "getType" ) + ->with( "field_type_identifier" ) + ->will( + $this->returnValue( $indexFieldType ) + ); + + $indexFieldType->expects( $this->never() )->method( "getDefaultField" ); + + $indexFieldType + ->expects( $this->once() ) + ->method( "getIndexDefinition" ) + ->will( + $this->returnValue( + array( + "field_name" => $searchFieldTypeMock, + ) + ) + ); + + $mockedFieldMap->getIndexFieldName( + new ArrayObject(), + "content_type_identifier", + "field_definition_identifier", + "field_type_identifier", + "non_existent_field_name" + ); + } + + /** + * @param array $methods + * + * @return \eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getMockedFieldMap( array $methods = array() ) + { + $fieldMap = $this + ->getMockBuilder( + "eZ\\Publish\\Core\\Persistence\\Elasticsearch\\Content\\Search\\FieldMap" + ) + ->setConstructorArgs( + array( + $this->getFieldRegistryMock(), + $this->getContentTypeHandlerMock(), + $this->getFieldNameGeneratorMock(), + ) + ) + ->setMethods( $methods ) + ->getMock(); + + return $fieldMap; + } + + /** + * @var \eZ\Publish\Core\Persistence\Solr\Content\Search\FieldRegistry|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fieldRegistryMock; + + /** + * @return \eZ\Publish\Core\Persistence\Solr\Content\Search\FieldRegistry|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getFieldRegistryMock() + { + if ( !isset( $this->fieldRegistryMock ) ) + { + $this->fieldRegistryMock = $this->getMock( + "eZ\\Publish\\Core\\Persistence\\Solr\\Content\\Search\\FieldRegistry" + ); + } + + return $this->fieldRegistryMock; + } + + /** + * @return \eZ\Publish\SPI\FieldType\Indexable|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getIndexFieldTypeMock() + { + return $this->getMock( + "eZ\\Publish\\SPI\\FieldType\\Indexable" + ); + } + + /** + * @return \eZ\Publish\SPI\Search\FieldType|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getSearchFieldTypeMock() + { + return $this->getMock( + "eZ\\Publish\\SPI\\Search\\FieldType" + ); + } + + /** + * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler|\PHPUnit_Framework_MockObject_MockObject + */ + protected $contentTypeHandlerMock; + + /** + * @return \eZ\Publish\SPI\Persistence\Content\Type\Handler|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getContentTypeHandlerMock() + { + if ( !isset( $this->contentTypeHandlerMock ) ) + { + $this->contentTypeHandlerMock = $this->getMock( + "eZ\\Publish\\SPI\\Persistence\\Content\\Type\\Handler" + ); + } + + return $this->contentTypeHandlerMock; + } + + /** + * @var \eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldNameGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fieldNameGeneratorMock; + + /** + * @return \eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldNameGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getFieldNameGeneratorMock() + { + if ( !isset( $this->fieldNameGeneratorMock ) ) + { + $this->fieldNameGeneratorMock = $this->getMock( + "eZ\\Publish\\Core\\Persistence\\Elasticsearch\\Content\\Search\\FieldNameGenerator" + ); + } + + return $this->fieldNameGeneratorMock; + } + + /** + * @return \eZ\Publish\API\Repository\Values\Content\Query\Criterion|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getCriterionMock() + { + return $this + ->getMockBuilder( "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\Criterion" ) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return \eZ\Publish\API\Repository\Values\Content\Query\SortClause|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getSortClauseMock() + { + return $this + ->getMockBuilder( "eZ\\Publish\\API\\Repository\\Values\\Content\\Query\\SortClause" ) + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Tests/TestCase.php b/eZ/Publish/Core/Persistence/Elasticsearch/Tests/TestCase.php new file mode 100644 index 00000000000..81429ee7047 --- /dev/null +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Tests/TestCase.php @@ -0,0 +1,19 @@ + Date: Fri, 30 Jan 2015 15:31:25 +0100 Subject: [PATCH 10/13] EZP-23465: fixed: throw InvalidArgumentException on invalid target --- .../Content/Search/SortClauseVisitor/Field/Field.php | 10 ++++++++-- .../SortClauseVisitor/Field/MapLocationDistance.php | 8 ++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php index 07b530ba80f..1a92e15df38 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/Field.php @@ -11,7 +11,7 @@ use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\SortClauseVisitor\FieldBase; use eZ\Publish\API\Repository\Values\Content\Query\SortClause; -use RuntimeException; +use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; /** * Visits the Field sort clause @@ -33,6 +33,8 @@ public function canVisit( SortClause $sortClause ) /** * Map field value to a proper Elasticsearch representation * + * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException If no sortable fields are found for the given sort clause target. + * * @param \eZ\Publish\API\Repository\Values\Content\Query\SortClause $sortClause * * @return mixed @@ -49,7 +51,11 @@ public function visit( SortClause $sortClause ) if ( $fieldName === null ) { - throw new RuntimeException( "No sortable fields found for '{$target->fieldIdentifier}' on '{$target->typeIdentifier}'" ); + throw new InvalidArgumentException( + "\$sortClause->target", + "No searchable fields found for the given sort clause target ". + "'{$target->fieldIdentifier}' on '{$target->typeIdentifier}'." + ); } /** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\FieldTarget $target */ diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php index 3c2e0b5c9cd..145812d5ed7 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php @@ -12,8 +12,8 @@ use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\SortClauseVisitor\FieldBase; use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\SortClauseVisitor; use eZ\Publish\API\Repository\Values\Content\Query\SortClause; -use RuntimeException; use eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface; +use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; /** * Visits the MapLocationDistance sort clause @@ -61,7 +61,11 @@ public function visit( SortClause $sortClause ) if ( $fieldName === null ) { - throw new RuntimeException( "No sortable fields found" ); + throw new InvalidArgumentException( + "\$sortClause->target", + "No searchable fields found for the given sort clause target ". + "'{$target->fieldIdentifier}' on '{$target->typeIdentifier}'." + ); } /** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\MapLocationTarget $target */ From 611dbcb5b7830a05f2cd27cec36f391a4db0a6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Fri, 30 Jan 2015 16:29:07 +0100 Subject: [PATCH 11/13] EZP-23465: updated: inject field type/name into field visitors --- .../Field/MapLocationDistanceRange.php | 22 ++++++++++++++++--- .../Field/MapLocationDistance.php | 18 ++++++++++++--- .../criterion_visitors_content.yml | 2 ++ .../sort_clause_visitors_content.yml | 2 ++ 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php index d20931efd7e..86c7b715c09 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/CriterionVisitor/Field/MapLocationDistanceRange.php @@ -15,6 +15,7 @@ use eZ\Publish\API\Repository\Values\Content\Query\Criterion; use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Operator; use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; +use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap; /** * Visits the MapLocationDistance criterion @@ -26,14 +27,29 @@ class MapLocationDistanceRange extends Field * * @var string */ - protected $fieldTypeName = "ezgmaplocation"; + protected $fieldTypeIdentifier; /** * Name of the field type's indexed field that criterion can handle * * @var string */ - protected $fieldName = "value_location"; + protected $fieldName; + + /** + * Create from FieldMap, FieldType identifier and field name. + * + * @param \eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap $fieldMap + * @param string $fieldTypeIdentifier + * @param string $fieldName + */ + public function __construct( FieldMap $fieldMap, $fieldTypeIdentifier, $fieldName ) + { + $this->fieldTypeIdentifier = $fieldTypeIdentifier; + $this->fieldName = $fieldName; + + parent::__construct( $fieldMap ); + } /** * Check if visitor is applicable to current criterion @@ -78,7 +94,7 @@ protected function getCondition( Criterion $criterion ) $fieldNames = $this->getFieldNames( $criterion, $criterion->target, - $this->fieldTypeName, + $this->fieldTypeIdentifier, $this->fieldName ); diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php index 145812d5ed7..2254bf700ad 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php @@ -14,6 +14,7 @@ use eZ\Publish\API\Repository\Values\Content\Query\SortClause; use eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface; use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; +use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap; /** * Visits the MapLocationDistance sort clause @@ -21,11 +22,22 @@ class MapLocationDistance extends FieldBase { /** - * Name of the field type that sort clause can handle + * Name of the field type's indexed field that criterion can handle. * * @var string */ - protected $typeName = "ez_geolocation"; + protected $fieldName; + + /** + * @param \eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap $fieldMap + * @param string $fieldName + */ + public function __construct( FieldMap $fieldMap, $fieldName ) + { + $this->fieldName = $fieldName; + + parent::__construct( $fieldMap ); + } /** * Check if visitor is applicable to current sortClause @@ -56,7 +68,7 @@ public function visit( SortClause $sortClause ) $sortClause, $target->typeIdentifier, $target->fieldIdentifier, - "value_location" + $this->fieldName ); if ( $fieldName === null ) diff --git a/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml b/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml index badd2cd145e..b608318a6d8 100644 --- a/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml +++ b/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml @@ -137,6 +137,8 @@ services: class: %ezpublish.persistence.elasticsearch.search.content.criterion_visitor.map_location_distance_range.class% arguments: - @ezpublish.persistence.elasticsearch.search.content.field_map + - 'ezgmaplocation' + - 'value_location' tags: - {name: ezpublish.persistence.elasticsearch.search.content.criterion_visitor} diff --git a/eZ/Publish/Core/settings/storage_engines/elasticsearch/sort_clause_visitors_content.yml b/eZ/Publish/Core/settings/storage_engines/elasticsearch/sort_clause_visitors_content.yml index fa671418aa9..d7b9eef07e1 100644 --- a/eZ/Publish/Core/settings/storage_engines/elasticsearch/sort_clause_visitors_content.yml +++ b/eZ/Publish/Core/settings/storage_engines/elasticsearch/sort_clause_visitors_content.yml @@ -67,6 +67,8 @@ services: ezpublish.persistence.elasticsearch.search.content.sort_clause_visitor.map_location_distance: parent: ezpublish.persistence.elasticsearch.search.content.sort_clause_visitor.field_base class: %ezpublish.persistence.elasticsearch.search.content.sort_clause_visitor.map_location_distance.class% + arguments: + - 'value_location' tags: - {name: ezpublish.persistence.elasticsearch.search.content.sort_clause_visitor} From 0248739350a6ff0473c5e8ebddd8572f952bbee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Fri, 30 Jan 2015 16:31:29 +0100 Subject: [PATCH 12/13] EZP-23465: fixed: remove unused import --- .../Search/SortClauseVisitor/Field/MapLocationDistance.php | 1 - 1 file changed, 1 deletion(-) diff --git a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php index 2254bf700ad..29ff2a3c461 100644 --- a/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php +++ b/eZ/Publish/Core/Persistence/Elasticsearch/Content/Search/SortClauseVisitor/Field/MapLocationDistance.php @@ -12,7 +12,6 @@ use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\SortClauseVisitor\FieldBase; use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\SortClauseVisitor; use eZ\Publish\API\Repository\Values\Content\Query\SortClause; -use eZ\Publish\API\Repository\Values\Content\Query\CustomFieldInterface; use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; use eZ\Publish\Core\Persistence\Elasticsearch\Content\Search\FieldMap; From 7fe478beb570f4e72e3889e4b7d404d789c4dfab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Fri, 30 Jan 2015 16:51:50 +0100 Subject: [PATCH 13/13] EZP-23465: use abstract parent service definition for field criteria visitors --- .../elasticsearch/criterion_visitors_content.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml b/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml index b608318a6d8..f422af479e8 100644 --- a/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml +++ b/eZ/Publish/Core/settings/storage_engines/elasticsearch/criterion_visitors_content.yml @@ -67,24 +67,26 @@ services: tags: - {name: ezpublish.persistence.elasticsearch.search.content.criterion_visitor} - ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_in: - class: %ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_in.class% + ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_base: + abstract: true arguments: - @ezpublish.persistence.elasticsearch.search.content.field_map + + ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_in: + parent: ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_base + class: %ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_in.class% tags: - {name: ezpublish.persistence.elasticsearch.search.content.criterion_visitor} ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_range: + parent: ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_base class: %ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_range.class% - arguments: - - @ezpublish.persistence.elasticsearch.search.content.field_map tags: - {name: ezpublish.persistence.elasticsearch.search.content.criterion_visitor} ezpublish.persistence.elasticsearch.search.content.criterion_visitor.fulltext: + parent: ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_base class: %ezpublish.persistence.elasticsearch.search.content.criterion_visitor.fulltext.class% - arguments: - - @ezpublish.persistence.elasticsearch.search.content.field_map tags: - {name: ezpublish.persistence.elasticsearch.search.content.criterion_visitor} @@ -134,9 +136,9 @@ services: - {name: ezpublish.persistence.elasticsearch.search.content.criterion_visitor} ezpublish.persistence.elasticsearch.search.content.criterion_visitor.map_location_distance_range: + parent: ezpublish.persistence.elasticsearch.search.content.criterion_visitor.field_base class: %ezpublish.persistence.elasticsearch.search.content.criterion_visitor.map_location_distance_range.class% arguments: - - @ezpublish.persistence.elasticsearch.search.content.field_map - 'ezgmaplocation' - 'value_location' tags: