From 0ae027d4a5d30436289c3c541c87d9d85c182457 Mon Sep 17 00:00:00 2001
From: ajstanley <astanley@upei.ca>
Date: Fri, 28 Feb 2025 14:40:32 +0000
Subject: [PATCH] working

---
 config/schema/roblib_alter_solr.schema.yml    |   8 +
 css/styles.css                                |  34 ++++
 js/help_modal.js                              |  43 ++++
 js/roblib_alter_solr.js                       |  31 +++
 roblib_alter_solr.libraries.yml               |  13 ++
 roblib_alter_solr.links.menu.yml              |   6 +
 roblib_alter_solr.routing.yml                 |   7 +
 roblib_alter_solr.services.yml                |   6 +
 .../RoblibAlterSolrSubscriber.php             |  79 ++++++++
 src/Form/RoblibAlterSolrSettingsForm.php      | 183 ++++++++++++++++++
 10 files changed, 410 insertions(+)
 create mode 100644 config/schema/roblib_alter_solr.schema.yml
 create mode 100644 css/styles.css
 create mode 100644 js/help_modal.js
 create mode 100644 js/roblib_alter_solr.js
 create mode 100644 roblib_alter_solr.libraries.yml
 create mode 100644 roblib_alter_solr.links.menu.yml
 create mode 100644 roblib_alter_solr.routing.yml
 create mode 100644 roblib_alter_solr.services.yml
 create mode 100644 src/EventSubscriber/RoblibAlterSolrSubscriber.php
 create mode 100644 src/Form/RoblibAlterSolrSettingsForm.php

diff --git a/config/schema/roblib_alter_solr.schema.yml b/config/schema/roblib_alter_solr.schema.yml
new file mode 100644
index 0000000..1b11736
--- /dev/null
+++ b/config/schema/roblib_alter_solr.schema.yml
@@ -0,0 +1,8 @@
+# Schema for the configuration files of the Roblib Alter Solr module.
+roblib_alter_solr.settings:
+  type: config_object
+  label: 'Roblib Alter Solr settings'
+  mapping:
+    example:
+      type: string
+      label: 'Example'
diff --git a/css/styles.css b/css/styles.css
new file mode 100644
index 0000000..fa6a270
--- /dev/null
+++ b/css/styles.css
@@ -0,0 +1,34 @@
+/* Modal styles */
+.modal {
+  display: none;
+  position: fixed;
+  z-index: 1;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  background-color: rgba(0, 0, 0, 0.4);
+}
+
+.modal-content {
+  background-color: #fff;
+  margin: 15% auto;
+  padding: 20px;
+  border: 1px solid #888;
+  width: 80%;
+}
+
+.close-button {
+  color: #aaa;
+  font-size: 28px;
+  font-weight: bold;
+  float: right;
+}
+
+.close-button:hover,
+.close-button:focus {
+  color: black;
+  text-decoration: none;
+  cursor: pointer;
+}
diff --git a/js/help_modal.js b/js/help_modal.js
new file mode 100644
index 0000000..ecb6259
--- /dev/null
+++ b/js/help_modal.js
@@ -0,0 +1,43 @@
+(function ($, Drupal) {
+  Drupal.behaviors.robib_solr_alterlBehavior = {
+    attach: function (context, settings) {
+      // Attach event to the modal link without using once()
+      $('#open-modal-link', context).click(function (e) {
+        e.preventDefault();  
+        // Create the modal content
+        var modalContent = '<div id="help-modal" class="modal">' +
+                            '<div class="modal-content">' +
+                              '<span class="close-button">&times;</span>' +
+                              '<h2>Roblib Solr Alter</h2>' +
+                              '<p>The form allows you two or more existing Solr fields to a new multivalues field.</p>' +
+                              '<p>Select existing fields from the dropdowns on the left, and pair them with a new field</ br> '+
+                              'in the column on the right. Your new field name comply with your Solr installion\'s schema.</p>' +
+                            '</div>' +
+                          '</div>';
+        
+        // Append the modal to the body
+        $('body').append(modalContent);
+    
+        
+        // Show the modal
+        $('#help-modal').fadeIn();
+        
+        // Close the modal when clicking the close button
+        $('#help-modal .close-button').click(function () {
+          $('#help-modal').fadeOut(function () {
+            $('#help-modal').remove();  // Remove modal from DOM after closing
+          });
+        });
+
+        // Close the modal when clicking outside the modal content
+        $(window).click(function(event) {
+          if ($(event.target).is('#help-modal')) {
+            $('#help-modal').fadeOut(function () {
+              $('#help-modal').remove();  // Remove modal from DOM
+            });
+          }
+        });
+      });
+    }
+  };
+})(jQuery, Drupal);
diff --git a/js/roblib_alter_solr.js b/js/roblib_alter_solr.js
new file mode 100644
index 0000000..d4e6073
--- /dev/null
+++ b/js/roblib_alter_solr.js
@@ -0,0 +1,31 @@
+(function ($, Drupal, once) {
+  Drupal.behaviors.addRowVisibility = {
+    attach: function (context, settings) {
+      function toggleButton() {
+        var sourceField = $('.source-field', context);
+        var destinationField = $('.destination-field', context);
+
+        // Ensure source and destination fields exist before checking values
+        if (sourceField.length > 0 && destinationField.length > 0) {
+          var sourceFilled = sourceField.find('option:selected').length > 0;
+          var destinationFilled = destinationField.val().trim().length > 0;
+          // Show/hide button based on whether both fields are filled
+          if (sourceFilled && destinationFilled) {
+            $('.form-submit[value="Add Row"]', context).show();
+          } else {
+            $('.form-submit[value="Add Row"]', context).hide();
+          }
+        } else {
+          console.error('Source or destination field is missing');
+        }
+      }
+
+      once('toggleButton', '.source-field, .destination-field', context).forEach(function (element) {
+        $(element).on('change keyup', toggleButton);
+      });
+
+      toggleButton(); // Run initially to set the correct state
+      
+    }
+  };
+})(jQuery, Drupal, once);
diff --git a/roblib_alter_solr.libraries.yml b/roblib_alter_solr.libraries.yml
new file mode 100644
index 0000000..00d0952
--- /dev/null
+++ b/roblib_alter_solr.libraries.yml
@@ -0,0 +1,13 @@
+roblib_alter_solr:
+  version: 1.x
+  js:
+    js/roblib_alter_solr.js: {}
+    js/help_modal.js: {}
+  dependencies:
+    - core/drupal
+    - core/jquery
+    - core/drupal.once 
+    - core/drupalSettings
+  css:
+   theme:
+      css/styles.css: {}
\ No newline at end of file
diff --git a/roblib_alter_solr.links.menu.yml b/roblib_alter_solr.links.menu.yml
new file mode 100644
index 0000000..88e1db4
--- /dev/null
+++ b/roblib_alter_solr.links.menu.yml
@@ -0,0 +1,6 @@
+roblib_alter_solr.roblib_alter_solr_settings:
+  title: Roblib alter solr settings
+  description: Configure Solr overrides
+  parent: system.admin_config_system
+  route_name: roblib_alter_solr.roblib_alter_solr_settings
+  weight: 10
diff --git a/roblib_alter_solr.routing.yml b/roblib_alter_solr.routing.yml
new file mode 100644
index 0000000..3ec34c7
--- /dev/null
+++ b/roblib_alter_solr.routing.yml
@@ -0,0 +1,7 @@
+roblib_alter_solr.roblib_alter_solr_settings:
+  path: '/admin/config/system/roblib-alter-solr-settings'
+  defaults:
+    _title: 'Roblib alter solr settings'
+    _form: 'Drupal\roblib_alter_solr\Form\RoblibAlterSolrSettingsForm'
+  requirements:
+    _permission: 'administer site configuration'
diff --git a/roblib_alter_solr.services.yml b/roblib_alter_solr.services.yml
new file mode 100644
index 0000000..c4cafdd
--- /dev/null
+++ b/roblib_alter_solr.services.yml
@@ -0,0 +1,6 @@
+services:
+  roblib_alter_solr.event_subscriber:
+    class: Drupal\roblib_alter_solr\EventSubscriber\RoblibAlterSolrSubscriber
+    arguments: ['@logger.factory', '@config.factory']
+    tags:
+      - { name: event_subscriber }
diff --git a/src/EventSubscriber/RoblibAlterSolrSubscriber.php b/src/EventSubscriber/RoblibAlterSolrSubscriber.php
new file mode 100644
index 0000000..94af3e1
--- /dev/null
+++ b/src/EventSubscriber/RoblibAlterSolrSubscriber.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\roblib_alter_solr\EventSubscriber;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\search_api_solr\Event\SearchApiSolrEvents;
+use Drupal\search_api_solr\Event\PostCreateIndexDocumentEvent;
+use Drupal\Core\Config\ConfigFactory;
+
+/**
+ * Allows users to combine Solr fields dynamically.
+ */
+final class RoblibAlterSolrSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The logger service.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
+   */
+  protected $logger;
+
+  /**
+   * The Config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $config;
+
+  /**
+   * Constructs a new RoblibAlterSolrSubscriber.
+   *
+   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
+   *   The logger factory service.
+   * @param \Drupal\Core\Config\ConfigFactory $config
+   *   The config factory service.
+   */
+  public function __construct(LoggerChannelFactoryInterface $logger_factory, ConfigFactory $config) {
+    $this->logger = $logger_factory->get('roblib_alter_solr');
+    $this->config = $config->get('roblib_alter_solr.settings');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      SearchApiSolrEvents::POST_CREATE_INDEX_DOCUMENT => 'onAlterSolrDocument',
+    ];
+  }
+
+  /**
+   * Alters the Solr document before indexing.
+   *
+   * @param \Drupal\search_api_solr\Event\PostCreateIndexDocumentEvent $event
+   *   The Solr document alter event.
+   */
+  public function onAlterSolrDocument(PostCreateIndexDocumentEvent $event) {
+    $solrDocument = $event->getSolariumDocument();
+    $item = $event->getSearchApiItem();
+    $pairs = $this->config->get('pairs');
+    foreach ($pairs as $index => $values) {
+      $fields = array_values($values['source']);
+      $consolidated_values = [];
+      foreach ($fields as $field) {
+        $field_value = $item->getField($field);
+        if ($field_value && $field_value->getValues()) {
+          $all_values = $field_value->getValues();
+          $consolidated_values[] = $all_values[0];
+        }
+      }
+      if ($consolidated_values) {
+        $field_name = "sm_{$values['destination']}";
+        $solrDocument->setField($field_name, $consolidated_values);
+      }
+    }
+  }
+
+}
diff --git a/src/Form/RoblibAlterSolrSettingsForm.php b/src/Form/RoblibAlterSolrSettingsForm.php
new file mode 100644
index 0000000..4f458de
--- /dev/null
+++ b/src/Form/RoblibAlterSolrSettingsForm.php
@@ -0,0 +1,183 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\roblib_alter_solr\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Configure Roblib Alter Solr settings for this site.
+ */
+final class RoblibAlterSolrSettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId(): string {
+    return 'roblib_alter_solr_roblib_alter_solr_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames(): array {
+    return ['roblib_alter_solr.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('roblib_alter_solr.settings');
+    $solr_config = $this->config('search_api.index.default_solr_index');
+    $solr_fields = $solr_config->get('field_settings');
+    $fields_options = [];
+    foreach ($solr_fields as $name => $field) {
+      $fields_options[$name] = $field['label'];
+    }
+    // Use getStorage() to persist state across AJAX calls.
+    $pairs = $form_state->getStorage()['pairs'] ?? $form_state->get('pairs') ?? $config->get('pairs') ?? [];
+    $form_state->setStorage(['pairs' => $pairs]);
+    $form['open_modal'] = [
+      '#type' => 'markup',
+      '#markup' => '<a href="#" id="open-modal-link">' . $this->t('How to use this form') . '</a>',
+      '#prefix' => '<div id="open_link-wrapper">',
+      '#suffix' => '</div>',
+    ];
+
+    $form['pairs'] = [
+      '#type' => 'table',
+      '#header' => [
+        $this->t('Source'),
+        $this->t('Destination'),
+        $this->t('Operations'),
+      ],
+      '#prefix' => '<div id="pairs-wrapper">',
+      '#suffix' => '</div>',
+    ];
+    $added = FALSE;
+
+    foreach ($pairs as $index => $pair) {
+      $form['pairs'][$index]['source'] = [
+        '#type' => 'select',
+        '#multiple' => TRUE,
+        '#options' => $fields_options,
+        '#default_value' => $pair['source'] ?? '',
+      ];
+
+      $form['pairs'][$index]['destination'] = [
+        '#type' => 'textfield',
+        '#default_value' => $pair['destination'] ?? '',
+      ];
+      if ($pair['source'] && $pair['destination']) {
+        $form['pairs'][$index]['remove'] = [
+          '#type' => 'submit',
+          '#value' => $this->t('Remove'),
+          '#submit' => ['::removeCallback'],
+          '#ajax' => [
+            'callback' => '::ajaxCallback',
+            'wrapper' => 'pairs-wrapper',
+          ],
+          '#name' => 'remove_' . $index,
+          '#limit_validation_errors' => [],
+        ];
+      }
+      else {
+        $form['pairs'][$index]['add'] = [
+          '#type' => 'submit',
+          '#value' => $this->t('Add Row'),
+          '#submit' => ['::addCallback'],
+          '#ajax' => [
+            'callback' => '::ajaxCallback',
+            'wrapper' => 'pairs-wrapper',
+          ],
+        ];
+        $form['pairs'][$index]['source']['#attributes'] = ['class' => ['source-field']];
+        $form['pairs'][$index]['destination']['#attributes'] = ['class' => ['destination-field']];
+        $added = TRUE;
+      }
+
+    }
+    if (!$added) {
+      $unique_id = uniqid();
+      $pairs[$unique_id] = ['source' => '', 'destination' => ''];
+      $form['pairs'][$unique_id]['source'] = [
+        '#type' => 'select',
+        '#multiple' => TRUE,
+        '#options' => $fields_options,
+        '#default_value' => '',
+        '#attributes' => ['class' => ['source-field']],
+      ];
+
+      $form['pairs'][$unique_id]['destination'] = [
+        '#type' => 'textfield',
+        '#default_value' => '',
+        '#attributes' => ['class' => ['destination-field']],
+      ];
+      $form['add'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Add Row'),
+        '#submit' => ['::addCallback'],
+        '#ajax' => [
+          'callback' => '::ajaxCallback',
+          'wrapper' => 'pairs-wrapper',
+        ],
+      ];
+
+    }
+    $form['#attached']['library'][] = 'roblib_alter_solr/roblib_alter_solr';
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * Callback function to add a new row.
+   */
+  public function addCallback(array &$form, FormStateInterface $form_state) {
+    $user_input = $form_state->getUserInput();
+    $unique_id = uniqid();
+    $pairs = $user_input['pairs'] ?? [];
+    $pairs[$unique_id] = ['source' => '', 'destination' => ''];
+    $form_state->set('pairs', $pairs);
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Callback function to remove a row.
+   */
+  public function removeCallback(array &$form, FormStateInterface $form_state) {
+    $trigger = $form_state->getTriggeringElement();
+    $index = str_replace('remove_', '', $trigger['#name']);
+    $pairs = $form_state->get('pairs') ?? [];
+
+    if (isset($pairs[$index])) {
+      unset($pairs[$index]);
+      $form_state->set('pairs', $pairs);
+    }
+
+    $form_state->setRebuild();
+  }
+
+  /**
+   * AJAX callback to update the form.
+   */
+  public function ajaxCallback(array &$form, FormStateInterface $form_state) {
+    return $form['pairs'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $pairs = $form_state->getValue('pairs');
+    $config = $this->config('roblib_alter_solr.settings');
+    $config->set('pairs', $pairs)->save();
+
+    $this->messenger()->addStatus($this->t('Configuration saved successfully.'));
+  }
+
+  
+
+}