You are here

IndexProcessorsForm.php in Search API 8

File

src/Form/IndexProcessorsForm.php
View source
<?php

namespace Drupal\search_api\Form;

use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Form\SubformState;
use Drupal\search_api\Processor\ProcessorInterface;
use Drupal\search_api\Processor\ProcessorPluginManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a form for configuring the processors of a search index.
 */
class IndexProcessorsForm extends EntityForm {

  /**
   * The messenger.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * The index being configured.
   *
   * @var \Drupal\search_api\IndexInterface
   */
  protected $entity;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The datasource manager.
   *
   * @var \Drupal\search_api\Processor\ProcessorPluginManager
   */
  protected $processorPluginManager;

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

  /**
   * Constructs an IndexProcessorsForm object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\search_api\Processor\ProcessorPluginManager $processor_plugin_manager
   *   The processor plugin manager.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, ProcessorPluginManager $processor_plugin_manager, LoggerInterface $logger, MessengerInterface $messenger) {
    $this->entityTypeManager = $entity_type_manager;
    $this->processorPluginManager = $processor_plugin_manager;
    $this->logger = $logger;
    $this->messenger = $messenger;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $entity_type_manager = $container
      ->get('entity_type.manager');
    $processor_plugin_manager = $container
      ->get('plugin.manager.search_api.processor');
    $logger = $container
      ->get('logger.channel.search_api');
    $messenger = $container
      ->get('messenger');
    return new static($entity_type_manager, $processor_plugin_manager, $logger, $messenger);
  }

  /**
   * {@inheritdoc}
   */
  public function getBaseFormId() {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css';

    // Retrieve lists of all processors, and the stages and weights they have.
    if (!$form_state
      ->has('processors')) {
      $all_processors = $this
        ->getAllProcessors();
      $sort_processors = function (ProcessorInterface $a, ProcessorInterface $b) {
        return strnatcasecmp($a
          ->label(), $b
          ->label());
      };
      uasort($all_processors, $sort_processors);
      $form_state
        ->set('processors', $all_processors);
    }
    else {
      $all_processors = $form_state
        ->get('processors');
    }
    $stages = $this->processorPluginManager
      ->getProcessingStages();

    /** @var \Drupal\search_api\Processor\ProcessorInterface[][] $processors_by_stage */
    $processors_by_stage = [];
    foreach ($all_processors as $processor_id => $processor) {
      foreach ($stages as $stage => $definition) {
        if ($processor
          ->supportsStage($stage)) {
          $processors_by_stage[$stage][$processor_id] = $processor;
        }
      }
    }
    $enabled_processors = $this->entity
      ->getProcessors();
    $discouraged_processors = [];
    $discouraged_warning = '';
    if ($this->entity
      ->getServerInstance()) {
      $discouraged_processors = $this->entity
        ->getServerInstance()
        ->getDiscouragedProcessors();
      $discouraged_processors = array_flip($discouraged_processors);
      $discouraged_warning = '<br /><strong>' . $this
        ->t('It is recommended not to use this processor with the selected server.') . '</strong>';
    }
    $form['#tree'] = TRUE;
    $form['#attached']['library'][] = 'search_api/drupal.search_api.processors';
    $form['#title'] = $this
      ->t('Manage processors for search index %label', [
      '%label' => $this->entity
        ->label(),
    ]);
    $form['description']['#markup'] = '<p>' . $this
      ->t('Configure processors which will pre- and post-process data at index and search time. Find more information on the <a href=":url" target="_blank">processors documentation page</a>.', [
      ':url' => 'https://www.drupal.org/docs/8/modules/search-api/getting-started/processors',
    ]) . '</p>';

    // Add the list of processors with checkboxes to enable/disable them.
    $form['status'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('Enabled'),
      '#attributes' => [
        'class' => [
          'search-api-status-wrapper',
        ],
      ],
    ];
    foreach ($all_processors as $processor_id => $processor) {
      $clean_css_id = Html::cleanCssIdentifier($processor_id);
      $is_enabled = !empty($enabled_processors[$processor_id]);
      $is_locked = $processor
        ->isLocked();
      $is_discouraged = isset($discouraged_processors[$processor_id]);
      $form['status'][$processor_id] = [
        '#type' => 'checkbox',
        '#title' => $processor
          ->label(),
        '#default_value' => $is_locked || $is_enabled,
        '#description' => $processor
          ->getDescription(),
        '#attributes' => [
          'class' => [
            'search-api-processor-status-' . $clean_css_id,
          ],
          'data-id' => $clean_css_id,
        ],
        '#disabled' => $is_locked || !$is_enabled && $is_discouraged,
        '#access' => !$processor
          ->isHidden(),
      ];
      if ($is_discouraged) {
        $form['status'][$processor_id]['#description'] .= $discouraged_warning;
      }
    }
    $form['weights'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('Processor order'),
    ];

    // Order enabled processors per stage.
    foreach ($stages as $stage => $description) {
      $form['weights'][$stage] = [
        '#type' => 'fieldset',
        '#title' => $description['label'],
        '#attributes' => [
          'class' => [
            'search-api-stage-wrapper',
            'search-api-stage-wrapper-' . Html::cleanCssIdentifier($stage),
          ],
        ],
      ];
      $form['weights'][$stage]['order'] = [
        '#type' => 'table',
      ];
      $form['weights'][$stage]['order']['#tabledrag'][] = [
        'action' => 'order',
        'relationship' => 'sibling',
        'group' => 'search-api-processor-weight-' . Html::cleanCssIdentifier($stage),
      ];
    }
    foreach ($processors_by_stage as $stage => $processors) {

      // Sort the processors by weight for this stage.
      $processor_weights = [];
      foreach ($processors as $processor_id => $processor) {
        if (!isset($discouraged_processors[$processor_id])) {
          $processor_weights[$processor_id] = $processor
            ->getWeight($stage);
        }
      }
      asort($processor_weights);
      foreach ($processor_weights as $processor_id => $weight) {
        $processor = $processors[$processor_id];
        if ($processor
          ->isHidden()) {
          $form['processors'][$processor_id]['weights'][$stage] = [
            '#type' => 'value',
            '#value' => $weight,
          ];
          continue;
        }
        $form['weights'][$stage]['order'][$processor_id]['#attributes']['class'][] = 'draggable';
        $form['weights'][$stage]['order'][$processor_id]['#attributes']['class'][] = 'search-api-processor-weight--' . Html::cleanCssIdentifier($processor_id);
        $form['weights'][$stage]['order'][$processor_id]['#weight'] = $weight;
        $form['weights'][$stage]['order'][$processor_id]['label']['#plain_text'] = $processor
          ->label();
        $form['weights'][$stage]['order'][$processor_id]['weight'] = [
          '#type' => 'weight',
          '#title' => $this
            ->t('Weight for processor %title', [
            '%title' => $processor
              ->label(),
          ]),
          '#title_display' => 'invisible',
          '#delta' => 50,
          '#default_value' => $weight,
          '#parents' => [
            'processors',
            $processor_id,
            'weights',
            $stage,
          ],
          '#attributes' => [
            'class' => [
              'search-api-processor-weight-' . Html::cleanCssIdentifier($stage),
            ],
          ],
        ];
      }
    }

    // Add vertical tabs containing the settings for the processors. Tabs for
    // disabled processors are hidden with JS magic, but need to be included in
    // case the processor is enabled.
    $form['processor_settings'] = [
      '#title' => $this
        ->t('Processor settings'),
      '#type' => 'vertical_tabs',
    ];
    foreach ($all_processors as $processor_id => $processor) {
      if ($processor instanceof PluginFormInterface) {
        $form['settings'][$processor_id] = [
          '#type' => 'details',
          '#title' => $processor
            ->label(),
          '#group' => 'processor_settings',
          '#parents' => [
            'processors',
            $processor_id,
            'settings',
          ],
          '#attributes' => [
            'class' => [
              'search-api-processor-settings-' . Html::cleanCssIdentifier($processor_id),
            ],
          ],
        ];
        $processor_form_state = SubformState::createForSubform($form['settings'][$processor_id], $form, $form_state);
        $form['settings'][$processor_id] += $processor
          ->buildConfigurationForm($form['settings'][$processor_id], $processor_form_state);
      }
      else {
        unset($form['settings'][$processor_id]);
      }
    }
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
    $values = $form_state
      ->getValues();
    $processors = $this
      ->getAllProcessors();

    // Iterate over all processors that have a form and are enabled.
    foreach (array_keys(array_filter($values['status'])) as $processor_id) {
      $processor = $processors[$processor_id];
      if ($processor instanceof PluginFormInterface) {
        $processor_form_state = SubformState::createForSubform($form['settings'][$processor_id], $form, $form_state);
        $processor
          ->validateConfigurationForm($form['settings'][$processor_id], $processor_form_state);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state
      ->getValues();
    $old_processors = $this->entity
      ->getProcessors();

    // Store processor settings.
    $processors = $this
      ->getAllProcessors();
    foreach ($processors as $processor_id => $processor) {
      if (empty($values['status'][$processor_id])) {
        if (isset($old_processors[$processor_id])) {
          $this->entity
            ->removeProcessor($processor_id);
          $form_state
            ->set('processors_changed', TRUE);
        }
        continue;
      }
      $old_configuration = $processor
        ->getConfiguration();
      if ($processor instanceof PluginFormInterface) {
        $processor_form_state = SubformState::createForSubform($form['settings'][$processor_id], $form, $form_state);
        $processor
          ->submitConfigurationForm($form['settings'][$processor_id], $processor_form_state);
      }
      if (!empty($values['processors'][$processor_id]['weights'])) {
        foreach ($values['processors'][$processor_id]['weights'] as $stage => $weight) {
          $processor
            ->setWeight($stage, (int) $weight);
        }
      }
      if (!isset($old_processors[$processor_id])) {
        $this->entity
          ->addProcessor($processor);
        $form_state
          ->set('processors_changed', TRUE);
      }
      elseif ($old_configuration != $processor
        ->getConfiguration()) {
        $form_state
          ->set('processors_changed', TRUE);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state) {
    if ($form_state
      ->get('processors_changed')) {
      $save_status = parent::save($form, $form_state);
      $this->messenger
        ->addStatus($this
        ->t('The indexing workflow was successfully edited.'));
      if ($this->entity
        ->isReindexing()) {
        $url = $this->entity
          ->toUrl('canonical');
        $this->messenger
          ->addStatus($this
          ->t('All content was scheduled for <a href=":url">reindexing</a> so the new settings can take effect.', [
          ':url' => $url
            ->toString(),
        ]));
      }
    }
    else {
      $this->messenger
        ->addStatus($this
        ->t('No values were changed.'));
      $save_status = SAVED_UPDATED;
    }
    return $save_status;
  }

  /**
   * {@inheritdoc}
   */
  protected function actions(array $form, FormStateInterface $form_state) {
    $actions = parent::actions($form, $form_state);

    // We don't have a "delete" action here.
    unset($actions['delete']);
    return $actions;
  }

  /**
   * Retrieves all available processors.
   */
  protected function getAllProcessors() {
    $processors = $this->entity
      ->getProcessors();
    $settings['#index'] = $this->entity;
    foreach ($this->processorPluginManager
      ->getDefinitions() as $name => $processor_definition) {
      if (isset($processors[$name])) {
        continue;
      }
      elseif (class_exists($processor_definition['class'])) {
        if (call_user_func([
          $processor_definition['class'],
          'supportsIndex',
        ], $this->entity)) {

          /** @var \Drupal\search_api\Processor\ProcessorInterface $processor */
          $processor = $this->processorPluginManager
            ->createInstance($name, $settings);
          $processors[$name] = $processor;
        }
      }
      else {
        $this->logger
          ->warning('Processor %id specifies a non-existing class %class.', [
          '%id' => $name,
          '%class' => $processor_definition['class'],
        ]);
      }
    }
    return $processors;
  }

}

Classes

Namesort descending Description
IndexProcessorsForm Provides a form for configuring the processors of a search index.