c95fd96ec24eafc32e56c90066ffc8243ae3db6d
[yaffs-website] / web / core / modules / search / src / Plugin / views / filter / Search.php
1 <?php
2
3 namespace Drupal\search\Plugin\views\filter;
4
5 use Drupal\Core\Form\FormStateInterface;
6 use Drupal\views\Plugin\views\filter\FilterPluginBase;
7 use Drupal\views\Plugin\views\display\DisplayPluginBase;
8 use Drupal\views\ViewExecutable;
9 use Drupal\views\Views;
10
11 /**
12  * Filter handler for search keywords.
13  *
14  * @ingroup views_filter_handlers
15  *
16  * @ViewsFilter("search_keywords")
17  */
18 class Search extends FilterPluginBase {
19
20   /**
21    * This filter is always considered multiple-valued.
22    *
23    * @var bool
24    */
25   protected $alwaysMultiple = TRUE;
26
27   /**
28    * A search query to use for parsing search keywords.
29     *
30     * @var \Drupal\search\ViewsSearchQuery
31     */
32   protected $searchQuery = NULL;
33
34   /**
35    * TRUE if the search query has been parsed.
36    */
37   protected $parsed = FALSE;
38
39   /**
40    * The search type name (value of {search_index}.type in the database).
41    *
42    * @var string
43    */
44   protected $searchType;
45
46   /**
47    * {@inheritdoc}
48    */
49   public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
50     parent::init($view, $display, $options);
51
52     $this->searchType = $this->definition['search_type'];
53   }
54
55   /**
56    * {@inheritdoc}
57    */
58   protected function defineOptions() {
59     $options = parent::defineOptions();
60
61     $options['operator']['default'] = 'optional';
62
63     return $options;
64   }
65
66   /**
67    * {@inheritdoc}
68    */
69   protected function operatorForm(&$form, FormStateInterface $form_state) {
70     $form['operator'] = [
71       '#type' => 'radios',
72       '#title' => $this->t('On empty input'),
73       '#default_value' => $this->operator,
74       '#options' => [
75         'optional' => $this->t('Show All'),
76         'required' => $this->t('Show None'),
77       ],
78     ];
79   }
80
81   /**
82    * {@inheritdoc}
83    */
84   protected function valueForm(&$form, FormStateInterface $form_state) {
85     $form['value'] = [
86       '#type' => 'textfield',
87       '#size' => 15,
88       '#default_value' => $this->value,
89       '#attributes' => ['title' => $this->t('Search keywords')],
90       '#title' => !$form_state->get('exposed') ? $this->t('Keywords') : '',
91     ];
92   }
93
94   /**
95    * {@inheritdoc}
96    */
97   public function validateExposed(&$form, FormStateInterface $form_state) {
98     if (!isset($this->options['expose']['identifier'])) {
99       return;
100     }
101
102     $key = $this->options['expose']['identifier'];
103     if (!$form_state->isValueEmpty($key)) {
104       $this->queryParseSearchExpression($form_state->getValue($key));
105       if (count($this->searchQuery->words()) == 0) {
106         $form_state->setErrorByName($key, $this->formatPlural(\Drupal::config('search.settings')->get('index.minimum_word_size'), 'You must include at least one keyword to match in the content, and punctuation is ignored.', 'You must include at least one keyword to match in the content. Keywords must be at least @count characters, and punctuation is ignored.'));
107       }
108     }
109   }
110
111   /**
112    * Sets up and parses the search query.
113    *
114    * @param string $input
115    *   The search keywords entered by the user.
116    */
117   protected function queryParseSearchExpression($input) {
118     if (!isset($this->searchQuery)) {
119       $this->parsed = TRUE;
120       $this->searchQuery = db_select('search_index', 'i', ['target' => 'replica'])->extend('Drupal\search\ViewsSearchQuery');
121       $this->searchQuery->searchExpression($input, $this->searchType);
122       $this->searchQuery->publicParseSearchExpression();
123     }
124   }
125
126   /**
127    * {@inheritdoc}
128    */
129   public function query() {
130     // Since attachment views don't validate the exposed input, parse the search
131     // expression if required.
132     if (!$this->parsed) {
133       $this->queryParseSearchExpression($this->value);
134     }
135     $required = FALSE;
136     if (!isset($this->searchQuery)) {
137       $required = TRUE;
138     }
139     else {
140       $words = $this->searchQuery->words();
141       if (empty($words)) {
142         $required = TRUE;
143       }
144     }
145     if ($required) {
146       if ($this->operator == 'required') {
147         $this->query->addWhere($this->options['group'], 'FALSE');
148       }
149     }
150     else {
151       $search_index = $this->ensureMyTable();
152
153       $search_condition = db_and();
154
155       // Create a new join to relate the 'search_total' table to our current
156       // 'search_index' table.
157       $definition = [
158         'table' => 'search_total',
159         'field' => 'word',
160         'left_table' => $search_index,
161         'left_field' => 'word',
162       ];
163       $join = Views::pluginManager('join')->createInstance('standard', $definition);
164       $search_total = $this->query->addRelationship('search_total', $join, $search_index);
165
166       // Add the search score field to the query.
167       $this->search_score = $this->query->addField('', "$search_index.score * $search_total.count", 'score', ['function' => 'sum']);
168
169       // Add the conditions set up by the search query to the views query.
170       $search_condition->condition("$search_index.type", $this->searchType);
171       $search_dataset = $this->query->addTable('node_search_dataset');
172       $conditions = $this->searchQuery->conditions();
173       $condition_conditions =& $conditions->conditions();
174       foreach ($condition_conditions  as $key => &$condition) {
175         // Make sure we just look at real conditions.
176         if (is_numeric($key)) {
177           // Replace the conditions with the table alias of views.
178           $this->searchQuery->conditionReplaceString('d.', "$search_dataset.", $condition);
179         }
180       }
181       $search_conditions =& $search_condition->conditions();
182       $search_conditions = array_merge($search_conditions, $condition_conditions);
183
184       // Add the keyword conditions, as is done in
185       // SearchQuery::prepareAndNormalize(), but simplified because we are
186       // only concerned with relevance ranking so we do not need to normalize.
187       $or = db_or();
188       foreach ($words as $word) {
189         $or->condition("$search_index.word", $word);
190       }
191       $search_condition->condition($or);
192
193       $this->query->addWhere($this->options['group'], $search_condition);
194
195       // Add the GROUP BY and HAVING expressions to the query.
196       $this->query->addGroupBy("$search_index.sid");
197       $matches = $this->searchQuery->matches();
198       $placeholder = $this->placeholder();
199       $this->query->addHavingExpression($this->options['group'], "COUNT(*) >= $placeholder", [$placeholder => $matches]);
200     }
201     // Set to NULL to prevent PDO exception when views object is cached.
202     $this->searchQuery = NULL;
203   }
204
205 }