From 74ac668ce28e0d2389d570169a44bc801b3fde2a Mon Sep 17 00:00:00 2001 From: Abel B Date: Wed, 12 Nov 2025 15:48:50 +0100 Subject: [PATCH 1/7] Add quick mapping action and button --- config/module.config.php | 4 +- src/Controller/Admin/MappingController.php | 61 +++++++++++++ src/Form/Admin/SolrQuickMappingForm.php | 89 +++++++++++++++++++ src/Schema.php | 5 ++ .../Form/SolrQuickMappingFormFactory.php | 40 +++++++++ view/solr/admin/mapping/browse-resource.phtml | 6 ++ view/solr/admin/mapping/quick-add.phtml | 47 ++++++++++ 7 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 src/Form/Admin/SolrQuickMappingForm.php create mode 100644 src/Service/Form/SolrQuickMappingFormFactory.php create mode 100644 view/solr/admin/mapping/quick-add.phtml diff --git a/config/module.config.php b/config/module.config.php index 3181ab7..e752c31 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -204,11 +204,11 @@ 'Solr\Form\Admin\SolrNodeForm' => Service\Form\SolrNodeFormFactory::class, 'Solr\Form\Admin\SolrMappingForm' => Service\Form\SolrMappingFormFactory::class, 'Solr\Form\Admin\SolrSearchFieldForm' => Service\Form\SolrSearchFieldFormFactory::class, + 'Solr\Form\Admin\SolrQuickMappingForm' => Service\Form\SolrQuickMappingFormFactory::class, ], 'invokables' => [ 'Solr\Form\Admin\SolrMappingImportForm' => Form\Admin\SolrMappingImportForm::class, - 'Solr\Form\Element\Transformations' => Form\Element\Transformations::class, - ], + 'Solr\Form\Element\Transformations' => Form\Element\Transformations::class, ], ], 'router' => [ 'routes' => $routes, diff --git a/src/Controller/Admin/MappingController.php b/src/Controller/Admin/MappingController.php index 2ce2ccc..7d9a139 100644 --- a/src/Controller/Admin/MappingController.php +++ b/src/Controller/Admin/MappingController.php @@ -36,6 +36,7 @@ use Solr\Api\Representation\SolrNodeRepresentation; use Solr\Form\Admin\SolrMappingForm; use Solr\Form\Admin\SolrMappingImportForm; +use Solr\Form\Admin\SolrQuickMappingForm; use Solr\ValueExtractor\Manager as ValueExtractorManager; class MappingController extends AbstractActionController @@ -131,6 +132,55 @@ public function addAction() return $view; } + public function quickAddAction() + { + $solrNodeId = $this->params('nodeId'); + $resourceName = $this->params('resourceName'); + + /*$form = $this->getForm(SolrQuickMappingForm::class, [ + 'solr_node_id' => $solrNodeId, + 'resource_name' => $resourceName, + ]);*/ + $form = $this->getForm(SolrQuickMappingForm::class, ['solr_node_id' => $solrNodeId]); + + $view = new ViewModel; + $view->setVariable('form', $form); + + if ($this->getRequest()->isPost()) { + $form->setData($this->params()->fromPost()); + if ($form->isValid()) { + $data = $form->getData(); + + $this->logger()->debug('[Solr] data received:'); + $this->logger()->debug(json_encode($data)); + + foreach ($data["o:source"] as $source) { + foreach ($data["o:field_name"] as $field_name) { + $this->api()->create('solr_mappings', + [ + 'o:solr_node' => ['o:id' => $solrNodeId], + 'o:resource_name' => $resourceName, + 'o:field_name' => str_replace('*', str_replace(':', '_', $source), $field_name), + 'o:source' => $source, + 'o:settings' => $data['o:settings'], + ]); + } + } + + $this->messenger()->addSuccess('Solr mapping modified.'); + + return $this->redirect()->toRoute('admin/solr/node-id-mapping-resource', [ + 'nodeId' => $solrNodeId, + 'resourceName' => $resourceName, + ]); + } else { + $this->messenger()->addError('There was an error during validation'); + } + } + + return $view; + } + public function editAction() { $solrNodeId = $this->params('nodeId'); @@ -269,6 +319,17 @@ protected function getSolrSchema($solrNodeId) } } + protected function getSolrSchemaClass($solrNodeId) + { + try { + $solrNode = $this->api()->read('solr_nodes', $solrNodeId)->getContent(); + $schema = $solrNode->schema(); + return $schema; + } catch (\Exception $e) { + return null; + } + } + protected function deleteMappings(SolrNodeRepresentation $solrNode) { $ids = $this->api()->search('solr_mappings', ['solr_node_id' => $solrNode->id()], ['returnScalar' => 'id'])->getContent(); diff --git a/src/Form/Admin/SolrQuickMappingForm.php b/src/Form/Admin/SolrQuickMappingForm.php new file mode 100644 index 0000000..1e8c0f7 --- /dev/null +++ b/src/Form/Admin/SolrQuickMappingForm.php @@ -0,0 +1,89 @@ +getTranslator(); + + $this->add([ + 'name' => 'o:source', + 'type' => Element\Select::class, + 'options' => [ + 'label' => $translator->translate('Sources'), + 'value_options' => array_combine( + $this->options['terms'], + $this->options['terms'] + ), + ], + 'attributes' => [ + 'multiple' => true, + 'required' => true, + ], + ]); + + $this->add([ + 'name' => 'o:field_name', + 'type' => Element\Select::class, + 'options' => [ + 'label' => $translator->translate('Solr fields'), + 'value_options' => array_combine( + $this->options['dynamic_fields'], + $this->options['dynamic_fields'] + ), + ], + 'attributes' => [ + 'multiple' => true, + 'required' => true, + ], + ]); + + $settingsFieldset = new Fieldset('o:settings'); + + $transformationNames = $this->transformationManager->getRegisteredNames($sortAlpha = true); + $transformationValueOptions = []; + foreach ($transformationNames as $name) { + $transformation = $this->transformationManager->get($name); + $transformationValueOptions[] = [ + 'value' => $name, + 'label' => $transformation->getLabel(), + 'empty_option' => '', + ]; + } + + $settingsFieldset->add([ + 'name' => 'transformations', + 'type' => Transformations::class, + 'options' => [ + 'label' => 'Transformations', // @translate + 'value_options' => $transformationValueOptions, + 'empty_option' => '', + ], + ]); + + $this->add($settingsFieldset); + } + + public function setTransformationManager(\Solr\Transformation\Manager $transformationManager) + { + $this->transformationManager = $transformationManager; + } + + public function getTransformationManager(): \Solr\Transformation\Manager + { + return $this->transformationManager; + } +} diff --git a/src/Schema.php b/src/Schema.php index 67f929e..82f0597 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -141,6 +141,11 @@ public function getDynamicFieldsMap() return $this->dynamicFieldsMap; } + public function getDynamicFields() + { + return $this->getSchema()['dynamicFields']; + } + public function getType($name) { $typesByName = $this->getTypesByName(); diff --git a/src/Service/Form/SolrQuickMappingFormFactory.php b/src/Service/Form/SolrQuickMappingFormFactory.php new file mode 100644 index 0000000..3dc0874 --- /dev/null +++ b/src/Service/Form/SolrQuickMappingFormFactory.php @@ -0,0 +1,40 @@ +setTranslator($services->get('MvcTranslator')); + + $solrNode = $services->get('Omeka\ApiManager')->read('solr_nodes', $options['solr_node_id'])->getContent(); + $dynamicFieldsAux = $solrNode->schema()->getDynamicFields(); + + $services->get('Omeka\Logger')->debug('[Solr] Dynamic fields:'); + $services->get('Omeka\Logger')->debug(json_encode($dynamicFieldsAux)); + + $dynamicFields = []; + foreach ($dynamicFieldsAux as $dynamicField) { + $dynamicFields[] = $dynamicField['name']; + } + + $properties = $services->get('Omeka\ApiManager')->search('properties')->getContent(); + $terms = []; + foreach ($properties as $property) { + $terms[] = $property->term(); + } + + $form->setOption('terms', $terms); + $form->setOption('dynamic_fields', $dynamicFields); + + $transformationManager = $services->get('Solr\TransformationManager'); + $form->setTransformationManager($transformationManager); + + return $form; + } +} diff --git a/view/solr/admin/mapping/browse-resource.phtml b/view/solr/admin/mapping/browse-resource.phtml index 066f324..0f0a8d0 100644 --- a/view/solr/admin/mapping/browse-resource.phtml +++ b/view/solr/admin/mapping/browse-resource.phtml @@ -36,6 +36,12 @@ ?>
+ url('admin/solr/node-id-mapping-resource', [ + 'nodeId' => $solrNode->id(), + 'resourceName' => $resourceName, + 'action' => 'quickAdd', + ]); ?> + translate('Quick add')?> url('admin/solr/node-id-mapping-resource', [ 'nodeId' => $solrNode->id(), 'resourceName' => $resourceName, diff --git a/view/solr/admin/mapping/quick-add.phtml b/view/solr/admin/mapping/quick-add.phtml new file mode 100644 index 0000000..9ceb898 --- /dev/null +++ b/view/solr/admin/mapping/quick-add.phtml @@ -0,0 +1,47 @@ + + +pageTitle($this->translate('Quickly add Solr mappings')); ?> + +partial('solr/admin/mapping/form'); ?> + + From c74c17f647e9a86a8994b7817ce2c3148f948c33 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Mon, 9 Mar 2026 11:58:13 +0100 Subject: [PATCH 2/7] Fix code style --- config/module.config.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/module.config.php b/config/module.config.php index e752c31..1bee2dc 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -208,7 +208,8 @@ ], 'invokables' => [ 'Solr\Form\Admin\SolrMappingImportForm' => Form\Admin\SolrMappingImportForm::class, - 'Solr\Form\Element\Transformations' => Form\Element\Transformations::class, ], + 'Solr\Form\Element\Transformations' => Form\Element\Transformations::class, + ], ], 'router' => [ 'routes' => $routes, From 25c9bfd6b105fa5fd8fe0b1b1a87203fead673a3 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Mon, 9 Mar 2026 11:58:38 +0100 Subject: [PATCH 3/7] Remove commented code --- src/Controller/Admin/MappingController.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Controller/Admin/MappingController.php b/src/Controller/Admin/MappingController.php index 7d9a139..08f604f 100644 --- a/src/Controller/Admin/MappingController.php +++ b/src/Controller/Admin/MappingController.php @@ -137,10 +137,6 @@ public function quickAddAction() $solrNodeId = $this->params('nodeId'); $resourceName = $this->params('resourceName'); - /*$form = $this->getForm(SolrQuickMappingForm::class, [ - 'solr_node_id' => $solrNodeId, - 'resource_name' => $resourceName, - ]);*/ $form = $this->getForm(SolrQuickMappingForm::class, ['solr_node_id' => $solrNodeId]); $view = new ViewModel; From a6b235065484e9a1454c078ddeed8d1367bbef42 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Mon, 9 Mar 2026 12:00:31 +0100 Subject: [PATCH 4/7] Remove debug logs --- src/Controller/Admin/MappingController.php | 3 --- src/Service/Form/SolrQuickMappingFormFactory.php | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/Controller/Admin/MappingController.php b/src/Controller/Admin/MappingController.php index 08f604f..4b40ba8 100644 --- a/src/Controller/Admin/MappingController.php +++ b/src/Controller/Admin/MappingController.php @@ -147,9 +147,6 @@ public function quickAddAction() if ($form->isValid()) { $data = $form->getData(); - $this->logger()->debug('[Solr] data received:'); - $this->logger()->debug(json_encode($data)); - foreach ($data["o:source"] as $source) { foreach ($data["o:field_name"] as $field_name) { $this->api()->create('solr_mappings', diff --git a/src/Service/Form/SolrQuickMappingFormFactory.php b/src/Service/Form/SolrQuickMappingFormFactory.php index 3dc0874..1f0ae10 100644 --- a/src/Service/Form/SolrQuickMappingFormFactory.php +++ b/src/Service/Form/SolrQuickMappingFormFactory.php @@ -15,9 +15,6 @@ public function __invoke(ContainerInterface $services, $requestedName, array $op $solrNode = $services->get('Omeka\ApiManager')->read('solr_nodes', $options['solr_node_id'])->getContent(); $dynamicFieldsAux = $solrNode->schema()->getDynamicFields(); - $services->get('Omeka\Logger')->debug('[Solr] Dynamic fields:'); - $services->get('Omeka\Logger')->debug(json_encode($dynamicFieldsAux)); - $dynamicFields = []; foreach ($dynamicFieldsAux as $dynamicField) { $dynamicFields[] = $dynamicField['name']; From 94742e0109de2ce751a48a8a4f3eddb601432276 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Mon, 9 Mar 2026 12:01:42 +0100 Subject: [PATCH 5/7] Create all mappings at once with batchCreate and warn if we find a potential duplicate --- src/Controller/Admin/MappingController.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Controller/Admin/MappingController.php b/src/Controller/Admin/MappingController.php index 4b40ba8..544bcbc 100644 --- a/src/Controller/Admin/MappingController.php +++ b/src/Controller/Admin/MappingController.php @@ -147,19 +147,33 @@ public function quickAddAction() if ($form->isValid()) { $data = $form->getData(); + $new_mappings = []; foreach ($data["o:source"] as $source) { foreach ($data["o:field_name"] as $field_name) { - $this->api()->create('solr_mappings', - [ + $field_name = str_replace('*', preg_replace('/[^a-zA-Z0-9]/', '_', $source), $field_name); + $solr_mapping = $this->api()->searchOne('solr_mappings', [ + 'solr_node_id' => $solrNodeId, + 'resource_name' => $resourceName, + 'field_name' => $field_name, + 'source' => $source, + ])->getContent(); + + if ($solr_mapping) { + // Do not prevent creation, the new mapping may have different settings + $this->messenger()->addWarning(sprintf('A mapping with the name "%s" and the source "%s" already exists. This may be a duplicate.', $field_name, $source)); + } + + $new_mappings[] = [ 'o:solr_node' => ['o:id' => $solrNodeId], 'o:resource_name' => $resourceName, - 'o:field_name' => str_replace('*', str_replace(':', '_', $source), $field_name), + 'o:field_name' => $field_name, 'o:source' => $source, 'o:settings' => $data['o:settings'], - ]); + ]; } } + $this->api()->batchCreate('solr_mappings', $new_mappings); $this->messenger()->addSuccess('Solr mapping modified.'); return $this->redirect()->toRoute('admin/solr/node-id-mapping-resource', [ From bf253b7e08935155dd319aa91c533d0caa6237f1 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Mon, 9 Mar 2026 12:03:49 +0100 Subject: [PATCH 6/7] Apply chosen only to source and field-name select --- src/Form/Admin/SolrQuickMappingForm.php | 2 ++ view/solr/admin/mapping/quick-add.phtml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Form/Admin/SolrQuickMappingForm.php b/src/Form/Admin/SolrQuickMappingForm.php index 1e8c0f7..aaaf995 100644 --- a/src/Form/Admin/SolrQuickMappingForm.php +++ b/src/Form/Admin/SolrQuickMappingForm.php @@ -30,6 +30,7 @@ public function init() ), ], 'attributes' => [ + 'id' => 'source', 'multiple' => true, 'required' => true, ], @@ -46,6 +47,7 @@ public function init() ), ], 'attributes' => [ + 'id' => 'field-name', 'multiple' => true, 'required' => true, ], diff --git a/view/solr/admin/mapping/quick-add.phtml b/view/solr/admin/mapping/quick-add.phtml index 9ceb898..fb2be8e 100644 --- a/view/solr/admin/mapping/quick-add.phtml +++ b/view/solr/admin/mapping/quick-add.phtml @@ -35,7 +35,7 @@ (function ($) { $(document).ready(function () { // Activate Chosen - const $select = $('select').chosen({ + $('select#source, select#field-name').chosen({ allow_single_deselect: true, width: '100%', search_contains: true, From 71fa06bc038372901d954d566546fe4fca1e36b2 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Mon, 9 Mar 2026 12:04:59 +0100 Subject: [PATCH 7/7] Escape translation in JS code to avoid XSS vulnerability --- view/solr/admin/mapping/quick-add.phtml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/view/solr/admin/mapping/quick-add.phtml b/view/solr/admin/mapping/quick-add.phtml index fb2be8e..a18f00a 100644 --- a/view/solr/admin/mapping/quick-add.phtml +++ b/view/solr/admin/mapping/quick-add.phtml @@ -34,13 +34,14 @@