diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php index 2c900c9954..cf263206c4 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php @@ -201,6 +201,11 @@ class ElasticSearchEngine implements SearchEngineInterface $this->notImplemented(); } + private function notImplemented() + { + throw new LogicException('Not implemented'); + } + /** * {@inheritdoc} */ @@ -241,11 +246,6 @@ class ElasticSearchEngine implements SearchEngineInterface $this->notImplemented(); } - private function notImplemented() - { - throw new LogicException('Not implemented'); - } - /** * {@inheritdoc} */ @@ -330,131 +330,6 @@ class ElasticSearchEngine implements SearchEngineInterface ); } - private function buildHighlightRules(QueryContext $context) - { - $highlighted_fields = []; - foreach ($context->getHighlightedFields() as $field) { - switch ($field->getType()) { - case FieldMapping::TYPE_STRING: - $index_field = $field->getIndexField(); - $raw_index_field = $field->getIndexField(true); - $highlighted_fields[$index_field] = [ - // Requires calling Mapping::enableTermVectors() on this field mapping - 'matched_fields' => [$index_field, $raw_index_field], - 'type' => 'fvh' - ]; - break; - case FieldMapping::TYPE_FLOAT: - case FieldMapping::TYPE_DOUBLE: - case FieldMapping::TYPE_INTEGER: - case FieldMapping::TYPE_LONG: - case FieldMapping::TYPE_SHORT: - case FieldMapping::TYPE_BYTE: - continue; - case FieldMapping::TYPE_DATE: - default: - continue; - } - } - - return [ - 'pre_tags' => ['[[em]]'], - 'post_tags' => ['[[/em]]'], - 'order' => 'score', - 'fields' => $highlighted_fields - ]; - } - - /** - * {@inheritdoc} - */ - public function autocomplete($query, SearchEngineOptions $options) - { - $params = $this->createCompletionParams($query, $options); - - $res = $this->client->suggest($params); - - $ret = [ - 'text' => [], - 'byField' => [] - ]; - foreach(array_keys($params['body']) as $fname) { - $t = []; - foreach($res[$fname] as $suggest) { // don't know why there is a sub-array level - foreach($suggest['options'] as $option) { - $text = $option['text']; - if(!in_array($text, $ret['text'])) { - $ret['text'][] = $text; - } - $t[] = [ - 'label' => $text, - 'query' => $fname.':'.$text - ]; - } - } - if(!empty($t)) { - $ret['byField'][$fname] = $t; - } - } - - return $ret; - } - - /** - * {@inheritdoc} - */ - public function resetCache() - { - } - - /** - * {@inheritdoc} - */ - public function clearCache() - { - } - - /** - * {@inheritdoc} - */ - public function clearAllCache(\DateTime $date = null) - { - } - - private function createCompletionParams($query, SearchEngineOptions $options) - { - $body = []; - $context = [ - 'record_type' => $options->getSearchType() === SearchEngineOptions::RECORD_RECORD ? - SearchEngineInterface::GEM_TYPE_RECORD : SearchEngineInterface::GEM_TYPE_STORY - ]; - - $base_ids = $options->getBasesIds(); - if (count($base_ids) > 0) { - $context['base_id'] = $base_ids; - } - - $search_context = $this->context_factory->createContext($options); - $fields = $search_context->getUnrestrictedFields(); - foreach($fields as $field) { - if($field->getType() == FieldMapping::TYPE_STRING) { - $k = '' . $field->getName(); - $body[$k] = [ - 'text' => $query, - 'completion' => [ - 'field' => "caption." . $field->getName() . ".suggest", - 'context' => &$context - ] - ]; - } - } - - return [ - 'index' => $this->indexName, - 'body' => $body - ]; - } - private function createRecordQueryParams($ESQuery, SearchEngineOptions $options, \record_adapter $record = null) { $params = [ @@ -483,66 +358,28 @@ class ElasticSearchEngine implements SearchEngineInterface return $params; } - private function getAggregationQueryParams(SearchEngineOptions $options) + private function createSortQueryParams(SearchEngineOptions $options) { - $aggs = []; - // technical aggregates (enable + optional limit) - foreach (ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) { - $size = $this->options->getAggregableFieldLimit($k); - if ($size !== databox_field::FACET_DISABLED) { - if ($size === databox_field::FACET_NO_LIMIT) { - $size = ESField::FACET_NO_LIMIT; - } - $agg = [ - 'terms' => [ - 'field' => $f['field'], - 'size' => $size - ] - ]; - $aggs[$k] = $agg; - } - } - // fields aggregates - $structure = $this->context_factory->getLimitedStructure($options); - foreach ($structure->getFacetFields() as $name => $field) { - // 2015-05-26 (mdarse) Removed databox filtering. - // It was already done by the ACL filter in the query scope, so no - // document that shouldn't be displayed can go this far. - $agg = [ - 'terms' => [ - 'field' => $field->getIndexField(true), - 'size' => $field->getFacetValuesLimit() - ] - ]; - $aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg); - } - return $aggs; - } + $sort = []; - private function createACLFilters(SearchEngineOptions $options) - { - // No ACLs if no user - if (false === $this->app->getAuthenticator()->isAuthenticated()) { - return []; + if ($options->getSortBy() === null || $options->getSortBy() === SearchEngineOptions::SORT_RELEVANCE) { + $sort['_score'] = $options->getSortOrder(); + } + elseif ($options->getSortBy() === SearchEngineOptions::SORT_CREATED_ON) { + $sort['created_on'] = $options->getSortOrder(); + } + elseif ($options->getSortBy() === 'recordid') { + $sort['record_id'] = $options->getSortOrder(); + } + else { + $sort[sprintf('caption.%s', $options->getSortBy())] = $options->getSortOrder(); } - $acl = $this->app->getAclForUser($this->app->getAuthenticatedUser()); - - $grantedCollections = array_keys($acl->get_granted_base([\ACL::ACTIF])); - - if (count($grantedCollections) === 0) { - return ['bool' => ['must_not' => ['match_all' => new \stdClass()]]]; + if (!array_key_exists('record_id', $sort)) { + $sort['record_id'] = $options->getSortOrder(); } - $appbox = $this->app['phraseanet.appbox']; - - $flagNamesMap = $this->getFlagsKey($appbox); - // Get flags rules - $flagRules = $this->getFlagsRules($appbox, $acl, $grantedCollections); - // Get intersection between collection ACLs and collection chosen by end user - $aclRules = $this->getACLsByCollection($flagRules, $flagNamesMap); - - return $this->buildACLsFilters($aclRules, $options); + return $sort; } private function createQueryFilters(SearchEngineOptions $options) @@ -600,27 +437,6 @@ class ElasticSearchEngine implements SearchEngineInterface return $filters; } - private function createSortQueryParams(SearchEngineOptions $options) - { - $sort = []; - - if ($options->getSortBy() === null || $options->getSortBy() === SearchEngineOptions::SORT_RELEVANCE) { - $sort['_score'] = $options->getSortOrder(); - } elseif ($options->getSortBy() === SearchEngineOptions::SORT_CREATED_ON) { - $sort['created_on'] = $options->getSortOrder(); - } elseif ($options->getSortBy() === 'recordid') { - $sort['record_id'] = $options->getSortOrder(); - } else { - $sort[sprintf('caption.%s', $options->getSortBy())] = $options->getSortOrder(); - } - - if (! array_key_exists('record_id', $sort)) { - $sort['record_id'] = $options->getSortOrder(); - } - - return $sort; - } - private function getFlagsKey(\appbox $appbox) { $flags = []; @@ -635,6 +451,32 @@ class ElasticSearchEngine implements SearchEngineInterface return $flags; } + private function createACLFilters(SearchEngineOptions $options) + { + // No ACLs if no user + if (false === $this->app->getAuthenticator()->isAuthenticated()) { + return []; + } + + $acl = $this->app->getAclForUser($this->app->getAuthenticatedUser()); + + $grantedCollections = array_keys($acl->get_granted_base([\ACL::ACTIF])); + + if (count($grantedCollections) === 0) { + return ['bool' => ['must_not' => ['match_all' => new \stdClass()]]]; + } + + $appbox = $this->app['phraseanet.appbox']; + + $flagNamesMap = $this->getFlagsKey($appbox); + // Get flags rules + $flagRules = $this->getFlagsRules($appbox, $acl, $grantedCollections); + // Get intersection between collection ACLs and collection chosen by end user + $aclRules = $this->getACLsByCollection($flagRules, $flagNamesMap); + + return $this->buildACLsFilters($aclRules, $options); + } + private function getFlagsRules(\appbox $appbox, \ACL $acl, array $collections) { $rules = []; @@ -766,4 +608,166 @@ class ElasticSearchEngine implements SearchEngineInterface return []; } } + + private function buildHighlightRules(QueryContext $context) + { + $highlighted_fields = []; + foreach ($context->getHighlightedFields() as $field) { + switch ($field->getType()) { + case FieldMapping::TYPE_STRING: + $index_field = $field->getIndexField(); + $raw_index_field = $field->getIndexField(true); + $highlighted_fields[$index_field . ".light"] = [ + // Requires calling Mapping::enableTermVectors() on this field mapping +// 'matched_fields' => [$index_field, $raw_index_field], + 'type' => 'fvh', + ]; + break; + case FieldMapping::TYPE_FLOAT: + case FieldMapping::TYPE_DOUBLE: + case FieldMapping::TYPE_INTEGER: + case FieldMapping::TYPE_LONG: + case FieldMapping::TYPE_SHORT: + case FieldMapping::TYPE_BYTE: + continue; + case FieldMapping::TYPE_DATE: + default: + continue; + } + } + + return [ + 'pre_tags' => ['[[em]]'], + 'post_tags' => ['[[/em]]'], + 'order' => 'score', + 'fields' => $highlighted_fields + ]; + } + + private function getAggregationQueryParams(SearchEngineOptions $options) + { + $aggs = []; + // technical aggregates (enable + optional limit) + foreach (ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) { + $size = $this->options->getAggregableFieldLimit($k); + if ($size !== databox_field::FACET_DISABLED) { + if ($size === databox_field::FACET_NO_LIMIT) { + $size = ESField::FACET_NO_LIMIT; + } + $agg = [ + 'terms' => [ + 'field' => $f['field'], + 'size' => $size + ] + ]; + $aggs[$k] = $agg; + } + } + // fields aggregates + $structure = $this->context_factory->getLimitedStructure($options); + foreach ($structure->getFacetFields() as $name => $field) { + // 2015-05-26 (mdarse) Removed databox filtering. + // It was already done by the ACL filter in the query scope, so no + // document that shouldn't be displayed can go this far. + $agg = [ + 'terms' => [ + 'field' => $field->getIndexField(true), + 'size' => $field->getFacetValuesLimit() + ] + ]; + $aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg); + } + + return $aggs; + } + + /** + * {@inheritdoc} + */ + public function autocomplete($query, SearchEngineOptions $options) + { + $params = $this->createCompletionParams($query, $options); + + $res = $this->client->suggest($params); + + $ret = [ + 'text' => [], + 'byField' => [] + ]; + foreach (array_keys($params['body']) as $fname) { + $t = []; + foreach ($res[$fname] as $suggest) { // don't know why there is a sub-array level + foreach ($suggest['options'] as $option) { + $text = $option['text']; + if (!in_array($text, $ret['text'])) { + $ret['text'][] = $text; + } + $t[] = [ + 'label' => $text, + 'query' => $fname . ':' . $text + ]; + } + } + if (!empty($t)) { + $ret['byField'][$fname] = $t; + } + } + + return $ret; + } + + private function createCompletionParams($query, SearchEngineOptions $options) + { + $body = []; + $context = [ + 'record_type' => $options->getSearchType() === SearchEngineOptions::RECORD_RECORD ? + SearchEngineInterface::GEM_TYPE_RECORD : SearchEngineInterface::GEM_TYPE_STORY + ]; + + $base_ids = $options->getBasesIds(); + if (count($base_ids) > 0) { + $context['base_id'] = $base_ids; + } + + $search_context = $this->context_factory->createContext($options); + $fields = $search_context->getUnrestrictedFields(); + foreach ($fields as $field) { + if ($field->getType() == FieldMapping::TYPE_STRING) { + $k = '' . $field->getName(); + $body[$k] = [ + 'text' => $query, + 'completion' => [ + 'field' => "caption." . $field->getName() . ".suggest", + 'context' => &$context + ] + ]; + } + } + + return [ + 'index' => $this->indexName, + 'body' => $body + ]; + } + + /** + * {@inheritdoc} + */ + public function resetCache() + { + } + + /** + * {@inheritdoc} + */ + public function clearCache() + { + } + + /** + * {@inheritdoc} + */ + public function clearAllCache(\DateTime $date = null) + { + } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php index 5a49303b9f..55640dcf22 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php @@ -32,7 +32,10 @@ class ElasticsearchRecordHydrator if (substr($key, 0, strlen($prefix)) == $prefix) { $key = substr($key, strlen($prefix)); } - $highlight[$key] = $value; + if (substr($key, -6) == '.light') { + $key = substr($key, 0, strlen($key) - 6); + $highlight[$key] = $value; + } } $record = new ElasticsearchRecord(); diff --git a/templates/web/common/macros.html.twig b/templates/web/common/macros.html.twig index 9d5ed046bc..28a0a31f81 100644 --- a/templates/web/common/macros.html.twig +++ b/templates/web/common/macros.html.twig @@ -128,7 +128,7 @@
{% for name, value in record.getCaption(caption_field_order(record, can_see_business)) %}
{{ caption_field_label(record, name) }}
-
{{ caption_field(record, name, value)|e|highlight|linkify }}
+
{{ caption_field(record, name, value)|e|highlight|linkify|parseColor }}
{% endfor %}
{% if display_exif|default(true) and app.getAuthenticator().user is not none and user_setting('technical_display') == 'group' %} diff --git a/templates/web/prod/results/record.html.twig b/templates/web/prod/results/record.html.twig index 6b99d304c7..bccf31775e 100644 --- a/templates/web/prod/results/record.html.twig +++ b/templates/web/prod/results/record.html.twig @@ -24,8 +24,7 @@ {% set can_see_business = granted_on_collection(record.baseId, [constant('\\ACL::CANMODIFRECORD')]) %}