diff --git a/lib/Alchemy/Phrasea/Controller/Admin/SearchEngineController.php b/lib/Alchemy/Phrasea/Controller/Admin/SearchEngineController.php index 8cf04e7839..8073027302 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/SearchEngineController.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/SearchEngineController.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Controller\Admin; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchSettingsFormType; use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions; +use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -85,7 +86,10 @@ class SearchEngineController extends Controller */ private function getConfigurationForm(ElasticsearchOptions $options) { - return $this->app->form(new ElasticsearchSettingsFormType(), $options, [ + /** @var GlobalStructure $g */ + $g = $this->app['search_engine.structure']; + + return $this->app->form(new ElasticsearchSettingsFormType($g, $options), $options, [ 'action' => $this->app->url('admin_searchengine_form'), ]); } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php index 0532f85c30..b9b3d5a054 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php @@ -9,6 +9,9 @@ */ namespace Alchemy\Phrasea\SearchEngine\Elastic; +use igorw; + + class ElasticsearchOptions { const POPULATE_ORDER_RID = "RECORD_ID"; @@ -58,13 +61,8 @@ class ElasticsearchOptions 'populate_direction' => self::POPULATE_DIRECTION_DESC, 'activeTab' => null, ]; - - foreach(self::getAggregableTechnicalFields() as $k => $f) { - $defaultOptions[$k.'_limit'] = 0; - } $options = array_replace($defaultOptions, $options); - $self = new self(); $self->setHost($options['host']); $self->setPort($options['port']); @@ -76,11 +74,10 @@ class ElasticsearchOptions $self->setPopulateOrder($options['populate_order']); $self->setPopulateDirection($options['populate_direction']); $self->setActiveTab($options['activeTab']); - foreach(self::getAggregableTechnicalFields() as $k => $f) { - $self->setAggregableFieldLimit($k, $options[$k.'_limit']); + foreach($options['aggregates'] as $fieldname=>$attributes) { + $self->setAggregableField($fieldname, $attributes); } - return $self; } @@ -99,10 +96,11 @@ class ElasticsearchOptions 'highlight' => $this->highlight, 'populate_order' => $this->populateOrder, 'populate_direction' => $this->populateDirection, - 'activeTab' => $this->activeTab + 'activeTab' => $this->activeTab, + 'aggregates' => [] ]; - foreach(self::getAggregableTechnicalFields() as $k => $f) { - $ret[$k.'_limit'] = $this->getAggregableFieldLimit($k); + foreach($this->_customValues['aggregates'] as $fieldname=>$attributes) { + $ret['aggregates'][$fieldname] = $attributes; } return $ret; @@ -220,14 +218,19 @@ class ElasticsearchOptions $this->highlight = $highlight; } - public function setAggregableFieldLimit($key, $value) + public function setAggregableField($key, $attributes) { - $this->_customValues[$key.'_limit'] = $value; + $this->_customValues['aggregates'][$key] = $attributes; } - public function getAggregableFieldLimit($key) + public function getAggregableField($key) { - return $this->_customValues[$key.'_limit']; + return $this->_customValues['aggregates'][$key]; + } + + public function getAggregableFields() + { + return $this->_customValues['aggregates']; } public function getActiveTab() @@ -241,56 +244,56 @@ class ElasticsearchOptions public function __get($key) { - if(!array_key_exists($key, $this->_customValues)) { - $this->_customValues[$key] = 0; - } - return $this->_customValues[$key]; + $keys = explode(':', $key); + + return igorw\get_in($this->_customValues, $keys); } public function __set($key, $value) { - $this->_customValues[$key] = $value; + $keys = explode(':', $key); + $this->_customValues = igorw\assoc_in($this->_customValues, $keys, $value); } public static function getAggregableTechnicalFields() { return [ - 'base_aggregate' => [ + 'base' => [ 'type' => 'string', 'label' => 'prod::facet:base_label', 'field' => "database", 'esfield' => 'databox_name', 'query' => 'database:%s', ], - 'collection_aggregate' => [ + 'collection' => [ 'type' => 'string', 'label' => 'prod::facet:collection_label', 'field' => "collection", 'esfield' => 'collection_name', 'query' => 'collection:%s', ], - 'doctype_aggregate' => [ + 'doctype' => [ 'type' => 'string', 'label' => 'prod::facet:doctype_label', 'field' => "type", 'esfield' => 'type', 'query' => 'type:%s', ], - 'camera_model_aggregate' => [ + 'camera_model' => [ 'type' => 'string', 'label' => 'Camera Model', 'field' => "meta.CameraModel", 'esfield' => 'metadata_tags.CameraModel', 'query' => 'meta.CameraModel:%s', ], - 'iso_aggregate' => [ + 'iso' => [ 'type' => 'number', 'label' => 'ISO', 'field' => "meta.ISO", 'esfield' => 'metadata_tags.ISO', 'query' => 'meta.ISO=%s', ], - 'aperture_aggregate' => [ + 'aperture' => [ 'type' => 'number', 'label' => 'Aperture', 'field' => "meta.Aperture", @@ -300,7 +303,7 @@ class ElasticsearchOptions return round($value, 1); }, ], - 'shutterspeed_aggregate' => [ + 'shutterspeed' => [ 'type' => 'number', 'label' => 'Shutter speed', 'field' => "meta.ShutterSpeed", @@ -313,7 +316,7 @@ class ElasticsearchOptions return $value . ' s.'; }, ], - 'flashfired_aggregate' => [ + 'flashfired' => [ 'type' => 'boolean', 'label' => 'FlashFired', 'field' => "meta.FlashFired", @@ -327,49 +330,49 @@ class ElasticsearchOptions return array_key_exists($value, $map) ? $map[$value] : $value; }, ], - 'framerate_aggregate' => [ + 'framerate' => [ 'type' => 'number', 'label' => 'FrameRate', 'field' => "meta.FrameRate", 'esfield' => 'metadata_tags.FrameRate', 'query' => 'meta.FrameRate=%s', ], - 'audiosamplerate_aggregate' => [ + 'audiosamplerate' => [ 'type' => 'number', 'label' => 'Audio Samplerate', 'field' => "meta.AudioSamplerate", 'esfield' => 'metadata_tags.AudioSamplerate', 'query' => 'meta.AudioSamplerate=%s', ], - 'videocodec_aggregate' => [ + 'videocodec' => [ 'type' => 'string', 'label' => 'Video codec', 'field' => "meta.VideoCodec", 'esfield' => 'metadata_tags.VideoCodec', 'query' => 'meta.VideoCodec:%s', ], - 'audiocodec_aggregate' => [ + 'audiocodec' => [ 'type' => 'string', 'label' => 'Audio codec', 'field' => "meta.AudioCodec", 'esfield' => 'metadata_tags.AudioCodec', 'query' => 'meta.AudioCodec:%s', ], - 'orientation_aggregate' => [ + 'orientation' => [ 'type' => 'string', 'label' => 'Orientation', 'field' => "meta.Orientation", 'esfield' => 'metadata_tags.Orientation', 'query' => 'meta.Orientation=%s', ], - 'colorspace_aggregate' => [ + 'colorspace' => [ 'type' => 'string', 'label' => 'Colorspace', 'field' => "meta.ColorSpace", 'esfield' => 'metadata_tags.ColorSpace', 'query' => 'meta.ColorSpace:%s', ], - 'mimetype_aggregate' => [ + 'mimetype' => [ 'type' => 'string', 'label' => 'MimeType', 'field' => "meta.MimeType", diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchSettingsFormType.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchSettingsFormType.php index e1aaa3e378..985bda7f0e 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchSettingsFormType.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchSettingsFormType.php @@ -9,6 +9,7 @@ */ namespace Alchemy\Phrasea\SearchEngine\Elastic; +use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; @@ -17,6 +18,18 @@ use Symfony\Component\Validator\Constraints\Range; class ElasticsearchSettingsFormType extends AbstractType { + /** @var GlobalStructure */ + private $globalStructure; + + /** @var ElasticsearchOptions */ + private $esSettings; + + public function __construct(GlobalStructure $g, ElasticsearchOptions $settings) + { + $this->globalStructure = $g; + $this->esSettings = $settings; + } + public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -56,59 +69,89 @@ class ElasticsearchSettingsFormType extends AbstractType ->add('minScore', 'integer', [ 'label' => 'Thesaurus Min score', 'constraints' => new Range(['min' => 0]), - ]); + ]) + ->add('highlight', 'checkbox', [ + 'label' => 'Activate highlight', + 'required' => false + ]) + // ->add('save', 'submit', [ + // 'attr' => ['class' => 'btn btn-primary'] + // ]) + ->add('esSettingFromIndex', 'button', [ + 'label' => 'Get setting form index', + 'attr' => [ + 'onClick' => 'esSettingFromIndex()', + 'class' => 'btn' + ] + ]) + ->add('dumpField', 'textarea', [ + 'label' => false, + 'required' => false, + 'mapped' => false, + 'attr' => ['class' => 'dumpfield hide'] + ]) + ->add('activeTab', 'hidden'); - foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) { - if(array_key_exists('choices', $f)) { - // choices[] : choice_key => choice_value - $choices = $f['choices']; - } - else { - $choices = [ - "10 values" => 10, - "20 values" => 20, - "50 values" => 50, - "100 values" => 100, - "all values" => -1 - ]; - } - // array_unshift($choices, "not aggregated"); // always as first choice - $choices = array_merge(["not aggregated" => 0], $choices); - $builder - ->add($k.'_limit', ChoiceType::class, [ - // 'label' => $f['label'],// . ' ' . 'aggregate limit', - 'choices_as_values' => true, - 'choices' => $choices, - 'attr' => [ - 'class' => 'aggregate' - ] - ]); + // keep aggregates in configuration order with this intermediate array + $aggs = []; + + // helper fct to add aggregate to a tmp list + $addAgg = function($k, $label, $help, $disabled=false, $choices=null) use (&$aggs) { + if(!$choices) { + $choices = [ + "10 values" => 10, + "50 values" => 50, + "100 values" => 100, + "all values" => -1 + ]; } + $choices = array_merge(["not aggregated" => 0], $choices); // add this option always as first choice + $aggs[$k] = [ // default value will be replaced by hardcoded tech fields & all databoxes fields + 'label' => $label, + 'choices_as_values' => true, + 'choices' => $choices, + 'attr' => [ + 'class' => 'aggregate' + ], + 'disabled' => $disabled, + 'help_message' => $help // todo : not displayed ? + ]; + }; - $builder - ->add('highlight', 'checkbox', [ - 'label' => 'Activate highlight', - 'required' => false - ]) -// ->add('save', 'submit', [ -// 'attr' => ['class' => 'btn btn-primary'] -// ]) - ->add('esSettingFromIndex', 'button', [ - 'label' => 'Get setting form index', - 'attr' => [ - 'onClick' => 'esSettingFromIndex()', - 'class' => 'btn' - ] - ]) - ->add('dumpField', 'textarea', [ - 'label' => false, - 'required' => false, - 'mapped' => false, - 'attr' => ['class' => 'dumpfield hide'] - ]) - ->add('activeTab', 'hidden'); + // all fields fron conf + foreach($this->esSettings->getAggregableFields() as $k=>$f) { + // default value will be replaced by hardcoded tech fields & all databoxes fields + $addAgg($k, "/?\\ " . $k, "This field does not exists in current databoxes.", true); + } + + // add or replace hardcoded tech fields + foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) { + $choices = array_key_exists('choices', $f) ? $f['choices'] : null; // a tech-field can publish it's own choices + $help = null; + $label = '#' . $k; + if(!array_key_exists('_'.$k, $aggs)) { + $label = "/!\\ " . $label; + $help = "New field, please confirm setting."; + } + $addAgg('_'.$k, $label, $help, false, $choices); + } + + // add or replace all databoxes fields (nb: new db field - unknown in conf - will be a the end) + foreach($this->globalStructure->getAllFields() as $field) { + $k = $label = $field->getName(); + $help = null; + if(!array_key_exists($field->getName(), $aggs)) { + $label = "/!\\ " . $label; + $help = "New field, please confirm setting."; + } + $addAgg($k, $label, $help); // default choices + } + + // populate aggs to form + foreach($aggs as $k=>$agg) { + $builder->add('aggregates:' . $k . ':limit', ChoiceType::class, $agg); + } - ; } public function getName()