Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
Fulltext
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
8 / 8
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 operators
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 operatorOptions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 valueForm
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 query
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 op_contains
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 op_word
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Plugin\views\filter;
6
7use Drupal\Core\Database\Connection;
8use Drupal\Core\Form\FormStateInterface;
9use Drupal\views\Attribute\ViewsFilter;
10use Drupal\views\Plugin\views\filter\FilterPluginBase;
11use Symfony\Component\DependencyInjection\ContainerInterface;
12
13/**
14 * Filter by fulltext search.
15 *
16 * @ingroup views_filter_handlers
17 */
18#[ViewsFilter("name_fulltext")]
19class Fulltext extends FilterPluginBase {
20
21  /**
22   * The database connection.
23   *
24   * @var \Drupal\Core\Database\Connection
25   */
26  protected $connection;
27
28  /**
29   * Constructs a new Fulltext object.
30   *
31   * @param array $configuration
32   *   A configuration array containing information about the plugin instance.
33   * @param string $plugin_id
34   *   The plugin_id for the plugin instance.
35   * @param mixed $plugin_definition
36   *   The plugin implementation definition.
37   * @param \Drupal\Core\Database\Connection $connection
38   *   The database connection.
39   */
40  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $connection) {
41    parent::__construct($configuration, $plugin_id, $plugin_definition);
42    $this->connection = $connection;
43  }
44
45  /**
46   * {@inheritdoc}
47   */
48  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
49    return new static(
50      $configuration,
51      $plugin_id,
52      $plugin_definition,
53      $container->get('database')
54    );
55  }
56
57  /**
58   * Supported operations.
59   */
60  protected function operators() {
61    return [
62      'contains' => [
63        'title' => $this->t('Contains'),
64        'short' => $this->t('contains'),
65        'method' => 'op_contains',
66        'values' => 1,
67      ],
68      'word' => [
69        'title' => $this->t('Contains any word'),
70        'short' => $this->t('has word'),
71        'method' => 'op_word',
72        'values' => 1,
73      ],
74      'allwords' => [
75        'title' => $this->t('Contains all words'),
76        'short' => $this->t('has all'),
77        'method' => 'op_word',
78        'values' => 1,
79      ],
80    ];
81  }
82
83  /**
84   * Build strings from the operators() for 'select' options.
85   */
86  public function operatorOptions($which = 'title') {
87    $options = [];
88    foreach ($this->operators() as $id => $info) {
89      $options[$id] = $info[$which];
90    }
91
92    return $options;
93  }
94
95  /**
96   * {@inheritdoc}
97   */
98  protected function valueForm(&$form, FormStateInterface $form_state) {
99    $form['value'] = [
100      '#type' => 'textfield',
101      '#size' => 15,
102      '#default_value' => $this->value,
103      '#attributes' => ['title' => $this->t('Enter the name you wish to search for.')],
104      '#title' => $this->isExposed() ? '' : $this->t('Value'),
105    ];
106  }
107
108  /**
109   * Add this filter to the query.
110   *
111   * Due to the nature of fapi, the value and the operator have an unintended
112   * level of indirection. You will find them in $this->operator
113   * and $this->value respectively.
114   */
115  public function query() {
116    $this->ensureMyTable();
117    // Don't filter on empty strings.
118    if (empty($this->value[0])) {
119      return;
120    }
121    $field = "$this->tableAlias.$this->realField";
122    $fulltext_field = "LOWER(CONCAT(' ', COALESCE({$field}_title, ''), ' ', COALESCE({$field}_given, ''), ' ', COALESCE({$field}_middle, ''), ' ', COALESCE({$field}_family, ''), ' ', COALESCE({$field}_generational, ''), ' ', COALESCE({$field}_credentials, '')))";
123
124    $info = $this->operators();
125    if (!empty($info[$this->operator]['method'])) {
126      $this->{$info[$this->operator]['method']}($fulltext_field);
127    }
128  }
129
130  /**
131   * Contains operation.
132   *
133   * @param string $fulltext_field
134   *   The db field.
135   */
136  public function op_contains($fulltext_field) {// phpcs:ignore Drupal.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
137    $value = mb_strtolower($this->value[0]);
138
139    // Escape LIKE wildcards first.
140    $escaped_value = $this->connection->escapeLike($value);
141    if (is_string($escaped_value)) {
142      $value = $escaped_value;
143    }
144
145    $value = str_replace(' ', '%', $value);
146    $placeholder = $this->placeholder();
147    $this->query->addWhereExpression($this->options['group'], "$fulltext_field LIKE $placeholder", [$placeholder => '% ' . $value . '%']);
148  }
149
150  /**
151   * The word operation.
152   *
153   * @param string $fulltext_field
154   *   The db field.
155   */
156  public function op_word($fulltext_field) {// phpcs:ignore Drupal.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
157    $where = $this->connection->condition($this->operator == 'word' ? 'OR' : 'AND');
158    $value = mb_strtolower($this->value[0]);
159
160    $words = preg_split('/ /', $value, -1, PREG_SPLIT_NO_EMPTY);
161    foreach ($words as $word) {
162      $placeholder = $this->placeholder();
163      $where->where("$fulltext_field LIKE $placeholder", [$placeholder => '% ' . $this->connection->escapeLike($word) . '%']);
164    }
165
166    $this->query->addWhere($this->options['group'], $where);
167  }
168
169}