diff --git a/lib/Alchemy/Phrasea/Model/Entities/ElasticsearchRecord.php b/lib/Alchemy/Phrasea/Model/Entities/ElasticsearchRecord.php index 817a1ddc22..0b5f3c4617 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ElasticsearchRecord.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ElasticsearchRecord.php @@ -44,6 +44,8 @@ class ElasticsearchRecord implements RecordInterface, MutableRecordInterface private $subdefs; /** @var ArrayCollection */ private $flags; + /** @var ArrayCollection */ + private $highlight; /** {@inheritdoc} */ public function getId() @@ -319,4 +321,20 @@ class ElasticsearchRecord implements RecordInterface, MutableRecordInterface { $this->position = $position; } + + /** + * @return ArrayCollection + */ + public function getHighlight() + { + return $this->highlight; + } + + /** + * @param ArrayCollection $highlight + */ + public function setHighlight(ArrayCollection $highlight) + { + $this->highlight = $highlight; + } } diff --git a/lib/Alchemy/Phrasea/Model/RecordInterface.php b/lib/Alchemy/Phrasea/Model/RecordInterface.php index ddc4542632..5907d60d34 100644 --- a/lib/Alchemy/Phrasea/Model/RecordInterface.php +++ b/lib/Alchemy/Phrasea/Model/RecordInterface.php @@ -60,4 +60,7 @@ interface RecordInterface /** @return ArrayCollection */ public function getExif(); + + /** @return ArrayCollection */ + public function getCaption(); } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php index 18b6c22b07..64abb667e7 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php @@ -268,6 +268,12 @@ class ElasticSearchEngine implements SearchEngineInterface $params['body']['from'] = $offset; $params['body']['size'] = $perPage; + $params['body']['highlight'] = [ + 'pre_tags' => ['[[em]]'], + 'post_tags' => ['[[/em]]'], + 'order' => 'score', + 'fields' => ['caption.*' => new \stdClass()] + ]; if ($aggs = $this->getAggregationQueryParams($options)) { $params['body']['aggs'] = $aggs; @@ -280,7 +286,7 @@ class ElasticSearchEngine implements SearchEngineInterface $n = 0; foreach ($res['hits']['hits'] as $hit) { - $results[] = ElasticsearchRecordHydrator::hydrate($hit['_source'], $n++); + $results[] = ElasticsearchRecordHydrator::hydrate($hit, $n++); } $facets = $this->facetsResponseFactory->__invoke($res); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php index f3174e0672..7f553439bf 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchRecordHydrator.php @@ -17,8 +17,11 @@ use igorw; class ElasticsearchRecordHydrator { - public static function hydrate(array $data, $position) + public static function hydrate(array $hit, $position) { + $data = $hit['_source']; + $highlight = isset($hit['highlight']) ? $hit['highlight'] : []; + $record = new ElasticsearchRecord(); $record->setPosition($position); @@ -43,6 +46,7 @@ class ElasticsearchRecordHydrator $record->setExif(new ArrayCollection((array) igorw\get_in($data, ['exif'], []))); $record->setSubdefs(new ArrayCollection((array) igorw\get_in($data, ['subdefs'], []))); $record->setFlags(new ArrayCollection((array) igorw\get_in($data, ['flags'], []))); + $record->setHighlight(new ArrayCollection($highlight)); return $record; } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php index 8d4d03a54b..4fd601810d 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Indexer/RecordIndexer.php @@ -232,6 +232,7 @@ class RecordIndexer } else { $m->addRawVersion(); $m->addAnalyzedVersion($this->locales); + $m->highlight(); } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping.php index d54905e979..c28a74b6e2 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping.php @@ -210,6 +210,15 @@ class Mapping return $this; } + public function highlight() + { + $field = &$this->currentField(); + + $field['term_vector'] = 'with_positions_offsets'; + + return $this; + } + public function has($name) { return isset($this->fields[$name]); diff --git a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php index 7634e4b720..8c2fbacbf6 100644 --- a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php +++ b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php @@ -40,9 +40,34 @@ class PhraseanetExtension extends \Twig_Extension )), new \Twig_SimpleFunction('record_flags', array($this, 'getRecordFlags')), new \Twig_SimpleFunction('border_checker_from_fqcn', array($this, 'getCheckerFromFQCN')), + new \Twig_SimpleFunction('caption_field', array($this, 'getCaptionField')), ); } + public function getCaptionField(RecordInterface $record, $field, $value) + { + if ($record instanceof ElasticsearchRecord) { + $highlightKey = sprintf('caption.%s', $field); + + if (false === $record->getHighlight()->containsKey($highlightKey)) { + return implode('; ', (array) $value); + } + + $highlightValue = $record->getHighlight()->get($highlightKey); + + // if field is multivalued, merge highlighted values with captions ones + if (is_array($value)) { + $highlightValue = array_merge($highlightValue, array_diff($value, array_map(function($value) { + return str_replace(array('[[em]]', '[[/em]]'), array('', ''), $value); + }, $highlightValue))); + } + + return implode('; ', (array) $highlightValue); + } + + return implode('; ', (array) $value); + } + public function getRecordFlags(RecordInterface $record) { $recordStatuses = []; diff --git a/lib/classes/record/adapter.php b/lib/classes/record/adapter.php index 537a803a81..cbd592b924 100644 --- a/lib/classes/record/adapter.php +++ b/lib/classes/record/adapter.php @@ -763,6 +763,21 @@ class record_adapter implements RecordInterface, cache_cacheableInterface return new caption_record($this->app, $this, $this->get_databox()); } + public function getCaption() + { + $collection = new ArrayCollection(); + + foreach ($this->get_caption()->get_fields() as $field) { + $values = array_map(function($fieldValue) { + return $fieldValue->getValue(); + }, $field->get_values()); + + $collection->set($field->get_name(), $values); + } + + return $collection; + } + /** * * @return string diff --git a/templates/web/common/caption.html.twig b/templates/web/common/caption.html.twig index 6905a32325..4e06972df6 100644 --- a/templates/web/common/caption.html.twig +++ b/templates/web/common/caption.html.twig @@ -1,20 +1,21 @@ {% import 'common/macros.html.twig' as macro %} -{% set business = false %} -{% if app['authentication'].getUser() is not none %} - {% set business = app['acl'].get(app['authentication'].getUser()).has_right_on_base(record.get_base_id(), 'canmodifrecord') %} -{% endif %} +{% set business = granted_on_collection(record.baseId, 'canmodifrecord') %} +{% set display_exif = true %} {% if view == 'answer' %} - {{ macro.format_caption(record, highlight|default(''), searchEngine|default(null), business, false, true) }} + {{ macro.caption(record, business, display_exif) }} {% elseif view == 'lazaret' %} - {{ macro.format_caption(record, highlight|default(''), searchEngine|default(null), business, true, true) }} + {{ macro.caption(record, business, display_exif) }} {% elseif view == 'preview' %} - {{ macro.format_caption(record, highlight|default(''), searchEngine|default(null), business, true, false) }} + {% set display_exif = false %} + {{ macro.caption(record, business, display_exif) }} {% elseif view == 'basket' %} - {{ macro.format_caption(record, highlight|default(''), searchEngine|default(null), business, true, false) }} + {% set display_exif = false %} + {{ macro.caption(record, business, display_exif) }} {% elseif view == 'overview' %} - {{ macro.format_caption(record, highlight|default(''), searchEngine|default(null), business, false, false) }} + {% set display_exif = false %} + {{ macro.caption(record, business, display_exif) }} {% elseif view == 'publi' %} - {{ macro.format_caption(record, '', null, business, true, true) }} + {{ macro.caption(record, business, display_exif) }} {% endif %} diff --git a/templates/web/common/macros.html.twig b/templates/web/common/macros.html.twig index 2f7eb55fd4..ee712955a1 100644 --- a/templates/web/common/macros.html.twig +++ b/templates/web/common/macros.html.twig @@ -116,19 +116,15 @@ {% endif %} {% endmacro %} -{% macro caption(record, business, technical) %} +{% macro caption(record, can_see_business, display_exif) %} {# @todo handle business fields #} {% for name, value in record.caption %} -