mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-18 15:33:15 +00:00
Add PHRAS-1539
This commit is contained in:
@@ -201,6 +201,11 @@ class ElasticSearchEngine implements SearchEngineInterface
|
|||||||
$this->notImplemented();
|
$this->notImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function notImplemented()
|
||||||
|
{
|
||||||
|
throw new LogicException('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@@ -241,11 +246,6 @@ class ElasticSearchEngine implements SearchEngineInterface
|
|||||||
$this->notImplemented();
|
$this->notImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function notImplemented()
|
|
||||||
{
|
|
||||||
throw new LogicException('Not implemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@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)
|
private function createRecordQueryParams($ESQuery, SearchEngineOptions $options, \record_adapter $record = null)
|
||||||
{
|
{
|
||||||
$params = [
|
$params = [
|
||||||
@@ -483,66 +358,28 @@ class ElasticSearchEngine implements SearchEngineInterface
|
|||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAggregationQueryParams(SearchEngineOptions $options)
|
private function createSortQueryParams(SearchEngineOptions $options)
|
||||||
{
|
{
|
||||||
$aggs = [];
|
$sort = [];
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createACLFilters(SearchEngineOptions $options)
|
if ($options->getSortBy() === null || $options->getSortBy() === SearchEngineOptions::SORT_RELEVANCE) {
|
||||||
{
|
$sort['_score'] = $options->getSortOrder();
|
||||||
// No ACLs if no user
|
}
|
||||||
if (false === $this->app->getAuthenticator()->isAuthenticated()) {
|
elseif ($options->getSortBy() === SearchEngineOptions::SORT_CREATED_ON) {
|
||||||
return [];
|
$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());
|
if (!array_key_exists('record_id', $sort)) {
|
||||||
|
$sort['record_id'] = $options->getSortOrder();
|
||||||
$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'];
|
return $sort;
|
||||||
|
|
||||||
$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 createQueryFilters(SearchEngineOptions $options)
|
private function createQueryFilters(SearchEngineOptions $options)
|
||||||
@@ -600,27 +437,6 @@ class ElasticSearchEngine implements SearchEngineInterface
|
|||||||
return $filters;
|
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)
|
private function getFlagsKey(\appbox $appbox)
|
||||||
{
|
{
|
||||||
$flags = [];
|
$flags = [];
|
||||||
@@ -635,6 +451,32 @@ class ElasticSearchEngine implements SearchEngineInterface
|
|||||||
return $flags;
|
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)
|
private function getFlagsRules(\appbox $appbox, \ACL $acl, array $collections)
|
||||||
{
|
{
|
||||||
$rules = [];
|
$rules = [];
|
||||||
@@ -766,4 +608,166 @@ class ElasticSearchEngine implements SearchEngineInterface
|
|||||||
return [];
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,10 @@ class ElasticsearchRecordHydrator
|
|||||||
if (substr($key, 0, strlen($prefix)) == $prefix) {
|
if (substr($key, 0, strlen($prefix)) == $prefix) {
|
||||||
$key = substr($key, strlen($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();
|
$record = new ElasticsearchRecord();
|
||||||
|
@@ -128,7 +128,7 @@
|
|||||||
<dl class="{% if limitedWidth %}{% else %}dl-horizontal{% endif %}">
|
<dl class="{% if limitedWidth %}{% else %}dl-horizontal{% endif %}">
|
||||||
{% for name, value in record.getCaption(caption_field_order(record, can_see_business)) %}
|
{% for name, value in record.getCaption(caption_field_order(record, can_see_business)) %}
|
||||||
<dt>{{ caption_field_label(record, name) }}</dt>
|
<dt>{{ caption_field_label(record, name) }}</dt>
|
||||||
<dd>{{ caption_field(record, name, value)|e|highlight|linkify }}</dd>
|
<dd>{{ caption_field(record, name, value)|e|highlight|linkify|parseColor }}</dd>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dl>
|
</dl>
|
||||||
{% if display_exif|default(true) and app.getAuthenticator().user is not none and user_setting('technical_display') == 'group' %}
|
{% if display_exif|default(true) and app.getAuthenticator().user is not none and user_setting('technical_display') == 'group' %}
|
||||||
|
@@ -24,8 +24,7 @@
|
|||||||
{% set can_see_business = granted_on_collection(record.baseId, [constant('\\ACL::CANMODIFRECORD')]) %}
|
{% set can_see_business = granted_on_collection(record.baseId, [constant('\\ACL::CANMODIFRECORD')]) %}
|
||||||
|
|
||||||
<div class="thumb captionTips"
|
<div class="thumb captionTips"
|
||||||
{% if settings.rollover_thumbnail == 'caption' %}title="{{ macro.caption(record, can_see_business, false) | e }}"
|
{% if settings.rollover_thumbnail == 'caption' %}title="{{ macro.caption(record, can_see_business, false) | e }}"{% endif %}
|
||||||
"{% endif %}
|
|
||||||
{% if settings.rollover_thumbnail == 'preview' %}tooltipsrc="{{ path('prod_tooltip_preview', { 'sbas_id' : record.databoxId, 'record_id' : record.recordId }) }}"{% endif %}
|
{% if settings.rollover_thumbnail == 'preview' %}tooltipsrc="{{ path('prod_tooltip_preview', { 'sbas_id' : record.databoxId, 'record_id' : record.recordId }) }}"{% endif %}
|
||||||
style="height:{{ settings.images_size }}px; z-index:90;">
|
style="height:{{ settings.images_size }}px; z-index:90;">
|
||||||
<div class="doc_infos">
|
<div class="doc_infos">
|
||||||
|
Reference in New Issue
Block a user