search_api-8.x-1.15/src/Plugin/views/filter/SearchApiFulltext.php

src/Plugin/views/filter/SearchApiFulltext.php
<?php

namespace Drupal\search_api\Plugin\views\filter;

use Drupal\Core\Form\FormStateInterface;
use Drupal\search_api\Entity\Index;
use Drupal\search_api\ParseMode\ParseModePluginManager;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Defines a filter for adding a fulltext search to the view.
 *
 * @ingroup views_filter_handlers
 *
 * @ViewsFilter("search_api_fulltext")
 */
class SearchApiFulltext extends FilterPluginBase {

  use SearchApiFilterTrait;

  /**
   * The parse mode manager.
   *
   * @var \Drupal\search_api\ParseMode\ParseModePluginManager|null
   */
  protected $parseModeManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    /** @var static $plugin */
    $plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);

    $plugin->setParseModeManager($container->get('plugin.manager.search_api.parse_mode'));

    return $plugin;
  }

  /**
   * Retrieves the parse mode manager.
   *
   * @return \Drupal\search_api\ParseMode\ParseModePluginManager
   *   The parse mode manager.
   */
  public function getParseModeManager() {
    return $this->parseModeManager ?: \Drupal::service('plugin.manager.search_api.parse_mode');
  }

  /**
   * Sets the parse mode manager.
   *
   * @param \Drupal\search_api\ParseMode\ParseModePluginManager $parse_mode_manager
   *   The new parse mode manager.
   *
   * @return $this
   */
  public function setParseModeManager(ParseModePluginManager $parse_mode_manager) {
    $this->parseModeManager = $parse_mode_manager;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function showOperatorForm(&$form, FormStateInterface $form_state) {
    parent::showOperatorForm($form, $form_state);

    if (!empty($form['operator'])) {
      $form['operator']['#description'] = $this->t('Depending on the parse mode set, some of these options might not work as expected. Please either use "@multiple_words" as the parse mode or make sure that the filter behaves as expected for multiple words.', ['@multiple_words' => $this->t('Multiple words')]);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function operatorOptions($which = 'title') {
    $options = [];
    foreach ($this->operators() as $id => $info) {
      $options[$id] = $info[$which];
    }

    return $options;
  }

  /**
   * Returns information about the available operators for this filter.
   *
   * @return array[]
   *   An associative array mapping operator identifiers to their information.
   *   The operator information itself is an associative array with the
   *   following keys:
   *   - title: The translated title for the operator.
   *   - short: The translated short title for the operator.
   *   - values: The number of values the operator requires as input.
   */
  public function operators() {
    return [
      'and' => [
        'title' => $this->t('Contains all of these words'),
        'short' => $this->t('and'),
        'values' => 1,
      ],
      'or' => [
        'title' => $this->t('Contains any of these words'),
        'short' => $this->t('or'),
        'values' => 1,
      ],
      'not' => [
        'title' => $this->t('Contains none of these words'),
        'short' => $this->t('not'),
        'values' => 1,
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function defineOptions() {
    $options = parent::defineOptions();

    $options['operator']['default'] = 'and';
    $options['parse_mode'] = ['default' => 'terms'];
    $options['min_length'] = ['default' => ''];
    $options['fields'] = ['default' => []];
    $options['expose']['contains']['placeholder'] = ['default' => ''];

    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    parent::buildOptionsForm($form, $form_state);

    $form['parse_mode'] = [
      '#type' => 'select',
      '#title' => $this->t('Parse mode'),
      '#description' => $this->t('Choose how the search keys will be parsed.'),
      '#options' => [],
      '#default_value' => $this->options['parse_mode'],
    ];
    foreach ($this->getParseModeManager()->getInstances() as $key => $mode) {
      if ($mode->isHidden()) {
        continue;
      }
      $form['parse_mode']['#options'][$key] = $mode->label();
      if ($mode->getDescription()) {
        $states['visible'][':input[name="options[parse_mode]"]']['value'] = $key;
        $form["parse_mode_{$key}_description"] = [
          '#type' => 'item',
          '#title' => $mode->label(),
          '#description' => $mode->getDescription(),
          '#states' => $states,
        ];
      }
    }

    $fields = $this->getFulltextFields();
    if (!empty($fields)) {
      $form['fields'] = [
        '#type' => 'select',
        '#title' => $this->t('Searched fields'),
        '#description' => $this->t('Select the fields that will be searched. If no fields are selected, all available fulltext fields will be searched.'),
        '#options' => $fields,
        '#size' => min(4, count($fields)),
        '#multiple' => TRUE,
        '#default_value' => $this->options['fields'],
      ];
    }
    else {
      $form['fields'] = [
        '#type' => 'value',
        '#value' => [],
      ];
    }
    if (isset($form['expose'])) {
      $form['expose']['#weight'] = -5;
    }

    $form['min_length'] = [
      '#title' => $this->t('Minimum keyword length'),
      '#description' => $this->t('Minimum length of each word in the search keys. Leave empty to allow all words.'),
      '#type' => 'number',
      '#min' => 1,
      '#default_value' => $this->options['min_length'],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildExposeForm(&$form, FormStateInterface $form_state) {
    parent::buildExposeForm($form, $form_state);

    $form['expose']['placeholder'] = [
      '#type' => 'textfield',
      '#default_value' => $this->options['expose']['placeholder'],
      '#title' => $this->t('Placeholder'),
      '#size' => 40,
      '#description' => $this->t('Hint text that appears inside the field when empty.'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function valueForm(&$form, FormStateInterface $form_state) {
    parent::valueForm($form, $form_state);

    $form['value'] = [
      '#type' => 'textfield',
      '#title' => !$form_state->get('exposed') ? $this->t('Value') : '',
      '#size' => 30,
      '#default_value' => $this->value,
    ];
    if (!empty($this->options['expose']['placeholder'])) {
      $form['value']['#attributes']['placeholder'] = $this->options['expose']['placeholder'];
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function exposedTranslate(&$form, $type) {
    parent::exposedTranslate($form, $type);

    // We use custom validation for "required", so don't want the Form API to
    // interfere.
    // @see ::validateExposed()
    $form['#required'] = FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function validateExposed(&$form, FormStateInterface $form_state) {
    // Only validate exposed input.
    if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
      return;
    }

    $identifier = $this->options['expose']['identifier'];
    $input = &$form_state->getValue($identifier, '');

    if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
      $this->operator = $this->options['group_info']['group_items'][$input]['operator'];
      $input = &$this->options['group_info']['group_items'][$input]['value'];
    }

    // Under some circumstances, input will be an array containing the string
    // value. Not sure why, but just deal with that.
    while (is_array($input)) {
      $input = $input ? reset($input) : '';
    }
    if (trim($input) === '') {
      // No input was given by the user. If the filter was set to "required" and
      // there is a query (not the case when an exposed filter block is
      // displayed stand-alone), abort it.
      if (!empty($this->options['expose']['required']) && $this->getQuery()) {
        $this->getQuery()->abort();
      }
      // If the input is empty, there is nothing to validate: return early.
      return;
    }

    // Only continue if there is a minimum word length set.
    if ($this->options['min_length'] < 2) {
      return;
    }

    $words = preg_split('/\s+/', $input);
    foreach ($words as $i => $word) {
      if (mb_strlen($word) < $this->options['min_length']) {
        unset($words[$i]);
      }
    }
    if (!$words) {
      $vars['@count'] = $this->options['min_length'];
      $msg = $this->t('You must include at least one positive keyword with @count characters or more.', $vars);
      $form_state->setError($form[$identifier], $msg);
    }
    $input = implode(' ', $words);
  }

  /**
   * {@inheritdoc}
   */
  public function query() {
    while (is_array($this->value)) {
      $this->value = $this->value ? reset($this->value) : '';
    }
    // Catch empty strings entered by the user, but not "0".
    if ($this->value === '') {
      return;
    }
    $fields = $this->options['fields'];
    $fields = $fields ?: array_keys($this->getFulltextFields());
    $query = $this->getQuery();

    // Save any keywords that were already set.
    $old = $query->getKeys();
    $old_original = $query->getOriginalKeys();

    if ($this->options['parse_mode']) {
      /** @var \Drupal\search_api\ParseMode\ParseModeInterface $parse_mode */
      $parse_mode = $this->getParseModeManager()
        ->createInstance($this->options['parse_mode']);
      $query->setParseMode($parse_mode);
    }

    // If something already specifically set different fields, we silently fall
    // back to mere filtering.
    $old_fields = $query->getFulltextFields();
    $use_conditions = $old_fields
      && (array_diff($old_fields, $fields) || array_diff($fields, $old_fields));

    if ($use_conditions) {
      $conditions = $query->createConditionGroup('OR');
      $op = $this->operator === 'not' ? '<>' : '=';
      foreach ($fields as $field) {
        $conditions->addCondition($field, $this->value, $op);
      }
      $query->addConditionGroup($conditions);
      return;
    }

    // If the operator was set to OR or NOT, set OR as the conjunction. It is
    // also set for NOT since otherwise it would be "not all of these words".
    if ($this->operator != 'and') {
      $query->getParseMode()->setConjunction('OR');
    }

    $query->setFulltextFields($fields);
    $query->keys($this->value);
    if ($this->operator == 'not') {
      $keys = &$query->getKeys();
      if (is_array($keys)) {
        $keys['#negation'] = TRUE;
      }
      else {
        // We can't know how negation is expressed in the server's syntax.
      }
      unset($keys);
    }

    // If there were fulltext keys set, we take care to combine them in a
    // meaningful way (especially with negated keys).
    if ($old) {
      $keys = &$query->getKeys();
      // Array-valued keys are combined.
      if (is_array($keys)) {
        // If the old keys weren't parsed into an array, we instead have to
        // combine the original keys.
        if (is_scalar($old)) {
          $keys = "($old) ({$this->value})";
        }
        else {
          // If the conjunction or negation settings aren't the same, we have to
          // nest both old and new keys array.
          if (empty($keys['#negation']) !== empty($old['#negation'])
              || $keys['#conjunction'] !== $old['#conjunction']) {
            $keys = [
              '#conjunction' => 'AND',
              $old,
              $keys,
            ];
          }
          // Otherwise, just add all individual words from the old keys to the
          // new ones.
          else {
            foreach ($old as $key => $value) {
              if (substr($key, 0, 1) === '#') {
                continue;
              }
              $keys[] = $value;
            }
          }
        }
      }
      // If the parse mode was "direct" for both old and new keys, we
      // concatenate them and set them both via method and reference (to also
      // update the originalKeys property.
      elseif (is_scalar($old_original)) {
        $combined_keys = "($old_original) ($keys)";
        $query->keys($combined_keys);
        $keys = $combined_keys;
      }
      unset($keys);
    }
  }

  /**
   * Retrieves a list of all available fulltext fields.
   *
   * @return string[]
   *   An options list of fulltext field identifiers mapped to their prefixed
   *   labels.
   */
  protected function getFulltextFields() {
    $fields = [];
    /** @var \Drupal\search_api\IndexInterface $index */
    $index = Index::load(substr($this->table, 17));

    $fields_info = $index->getFields();
    foreach ($index->getFulltextFields() as $field_id) {
      $fields[$field_id] = $fields_info[$field_id]->getPrefixedLabel();
    }

    return $fields;
  }

}

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

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