search_api_meilisearch-1.0.x-dev/src/Plugin/search_api/processor/Synonyms.php

src/Plugin/search_api/processor/Synonyms.php
<?php

namespace Drupal\search_api_meilisearch\Plugin\search_api\processor;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Processor\FieldsProcessorPluginBase;
use Drupal\search_api\SearchApiException;
use Drupal\search_api_meilisearch\Api\MeilisearchApiException;
use Drupal\search_api_meilisearch\Api\MeilisearchApiServiceInterface;
use Drupal\search_api_meilisearch\Plugin\search_api\backend\SearchApiMeilisearchBackend;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Add synonyms to the Meilisearch index.
 *
 * @SearchApiProcessor(
 *   id = "search_api_meilisearch_synonyms",
 *   label = @Translation("Synonyms"),
 *   description = @Translation("If multiples words have an equivalent meaning in your dataset, you can decide to create a synonym list for these words. "),
 *   stages = {
 *     "preprocess_index" = 0,
 *   },
 *   locked = false,
 *   hidden = false,
 * )
 */
final class Synonyms extends FieldsProcessorPluginBase implements PluginFormInterface {

  /**
   * Delimiter that separates synonyms in a group.
   *
   * @var string
   */
  const SYNONYM_DELIMITER = ',';

  /**
   * Delimiter that determines the synonym relationship.
   *
   * @var string
   */
  const GROUP_DELIMITER = ':';

  /**
   * The logger to use for logging messages.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * The config factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected ConfigFactoryInterface $configFactory;

  /**
   * Meilisearch service for making API calls.
   *
   * @var \Drupal\search_api_meilisearch\Api\MeilisearchApiServiceInterface
   */
  protected MeilisearchApiServiceInterface $meiliService;

  /**
   * The construct method for the Meilisearch back-end plugin.
   *
   * @param array $configuration
   *   Configuration array.
   * @param string $plugin_id
   *   The plugin id.
   * @param mixed $plugin_definition
   *   A plugin definition.
   * @param \Drupal\search_api_meilisearch\Api\MeilisearchApiServiceInterface $meiliService
   *   A MeilisearchApiService instance.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   A ConfigFactory instance.
   * @param \Psr\Log\LoggerInterface $logger
   *   A LoggerInterface instance.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MeilisearchApiServiceInterface $meiliService, ConfigFactoryInterface $configFactory, LoggerInterface $logger) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->meiliService = $meiliService;
    $this->configFactory = $configFactory;
    $this->logger = $logger;
    $this->setConnection();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
    return new self(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('search_api_meilisearch.api'),
      $container->get('config.factory'),
      $container->get('logger.channel.search_api_meilisearch')
    );
  }

  /**
   * Sets the connection information in the service.
   */
  protected function setConnection() {
    try {
      $server = $this->getIndex()->getServerInstance();
      if (isset($server)) {
        $config = $server->getBackendConfig();
        if (isset($config['meilisearch_host_address']) && $config['meilisearch_host_port']) {
          $connectionUrl = $config['meilisearch_host_address'] . ':' . $config['meilisearch_host_port'];
          $this->meiliService->setUrl($connectionUrl);
          if (isset($config['meilisearch_master_key'])) {
            $masterKey = $config['meilisearch_master_key'];
            $this->meiliService->setMasterKey($masterKey);
          }
        }
      }
    }
    catch (SearchApiException $e) {
      return;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    $configuration = parent::defaultConfiguration();
    $configuration['synonyms'] = [];
    unset($configuration['all_fields']);
    unset($configuration['fields']);

    return $configuration;
  }

  /**
   * {@inheritdoc}
   */
  public static function supportsIndex(IndexInterface $index): bool {
    try {
      $server = $index->getServerInstance();
      return $server && $server->getBackend() instanceof SearchApiMeilisearchBackend;
    }
    catch (SearchApiException $e) {
      return FALSE;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
    $synonymsConfig = $this->configuration['synonyms'];
    $value = '';

    /**
     * @var string $phrase
     * @var string[] $synonyms
     */
    foreach ($synonymsConfig as $phrase => $synonyms) {
      if (empty(trim($phrase)) || empty($synonyms)) {
        continue;
      }

      $value .= $phrase . self::GROUP_DELIMITER . ' ' . implode(self::SYNONYM_DELIMITER . ' ', $synonyms) . "\n";
    }

    $form['synonyms'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Synonyms'),
      '#description' => $this->t('List all the synonyms for the word after the colon and separate them with a comma. <br>Example: <br>beautiful: attractive, pretty <br>attractive: beautiful, pretty<br>united states: us'),
      '#default_value' => $value,
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    try {
      $this->meiliService->connection();
    }
    catch (MeilisearchApiException $e) {
      $this->logger->error($e->getMessage());
      switch ($e->getCode()) {
        case(403):
          $form_state->setErrorByName('server', $this->t('The master key is not correct.'));
          break;

        case(0):
          $form_state->setErrorByName('server', $this->t('The connection to the server could not be established.'));
          break;
      }
    }

    $separateSynonyms = $this->cleanSynonyms($form_state->getValue('synonyms'));
    foreach ($separateSynonyms as $synonym) {
      $match = [];
      $matches = preg_match('/[^.\s\W](?=\S)[a-zA-Z0-9 ]*[^.\s\W]\s?:\s?[a-zA-Z0-9 ,]*[^\W]/', $synonym, $match);
      if (empty($match) || $matches === 0) {
        $form_state->setErrorByName($synonym, $this->t('The format of "@synonym" synonym is not valid.', ['@synonym' => $synonym]));
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $synonyms = $this->prepareSynonyms($form_state->getValue('synonyms'));
    $indexName = $this->getIndex()->id();
    try {
      if (empty($synonyms)) {
        $resetSynonyms = $this->meiliService->resetSynonyms($indexName);
        $task = $this->meiliService->waitForUpdate($resetSynonyms['taskUid']);
        if ($task['status'] === 'failed') {
          throw new MeilisearchApiException($task['error']['message']);
        }
        $this->messenger()->addStatus($this->t('Synonyms were successfully deleted.'));
      }
      else {
        $updateSynonyms = $this->meiliService->updateSynonyms($indexName, $synonyms);
        $task = $this->meiliService->waitForUpdate($updateSynonyms['taskUid']);
        if ($task['status'] === 'failed') {
          throw new MeilisearchApiException($task['error']['message']);
        }
        $this->messenger()->addStatus($this->t('Synonyms were successfully updated.'));
      }

      $form_state->setValue('synonyms', $synonyms);
      parent::submitConfigurationForm($form, $form_state);
    }
    catch (MeilisearchApiException $e) {
      $this->logger->warning($e->getMessage());
      $this->messenger()->addWarning($this->t('@message', ['@message' => $e->getMessage()]));
    }
  }

  /**
   * Processes the synonyms list and prepares it for the Meilisearch.
   *
   * Synonyms need to be transformed from a string of format e.g.
   * beautiful:attractive, pretty
   * hardworking:diligent
   * to an associate array of unique words as keys and synonyms as values.
   *
   * @param string $synonymsString
   *   User's input of synonyms.
   *
   * @return array
   *   Processed synonyms from the users input.
   */
  private function prepareSynonyms(string $synonymsString): array {
    $synonyms = [];

    $separateSynonyms = $this->cleanSynonyms($synonymsString);
    foreach ($separateSynonyms as $synonym) {
      [$synonymGroupName, $synonymGroupValues] = explode(self::GROUP_DELIMITER, $synonym);
      $synonymValues = explode(self::SYNONYM_DELIMITER, $synonymGroupValues);
      $synonymValues = array_map('trim', $synonymValues);
      $synonyms[strtolower($synonymGroupName)] = array_map('strtolower', $synonymValues);
    }

    return $synonyms;
  }

  /**
   * Removes empty lines and whitespaces from the synonyms string.
   *
   * @param string $synonymsString
   *   User's input of synonyms.
   *
   * @return array
   *   Cleaned synonyms array.
   */
  private function cleanSynonyms(string $synonymsString): array {
    $separateSynonyms = preg_split('/\r\n|\r|\n/', $synonymsString);
    $separateSynonyms = array_map('trim', $separateSynonyms);

    return array_filter($separateSynonyms, 'strlen');
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc