View source
<?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;
class SearchApiFulltext extends FilterPluginBase {
use SearchApiFilterTrait;
public $searchedFields = [];
protected $parseModeManager;
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$plugin
->setParseModeManager($container
->get('plugin.manager.search_api.parse_mode'));
return $plugin;
}
public function getParseModeManager() {
return $this->parseModeManager ?: \Drupal::service('plugin.manager.search_api.parse_mode');
}
public function setParseModeManager(ParseModePluginManager $parse_mode_manager) {
$this->parseModeManager = $parse_mode_manager;
return $this;
}
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'),
]);
}
}
public function operatorOptions($which = 'title') {
$options = [];
foreach ($this
->operators() as $id => $info) {
$options[$id] = $info[$which];
}
return $options;
}
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,
],
];
}
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' => '',
];
$options['expose']['contains']['expose_fields'] = [
'default' => FALSE,
];
$options['expose']['contains']['searched_fields_id'] = [
'default' => '',
];
return $options;
}
public function defaultExposeOptions() {
parent::defaultExposeOptions();
$this->options['expose']['searched_fields_id'] = $this->options['id'] . '_searched_fields';
}
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'],
];
}
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.'),
];
$form['expose']['expose_fields'] = [
'#type' => 'checkbox',
'#default_value' => $this->options['expose']['expose_fields'],
'#title' => $this
->t('Expose searched fields'),
'#description' => $this
->t('Expose the list of searched fields. This allows users to narrow the search to the desired fields.'),
];
$form['expose']['searched_fields_id'] = [
'#type' => 'textfield',
'#default_value' => $this->options['expose']['searched_fields_id'],
'#title' => $this
->t('Searched fields identifier'),
'#size' => 40,
'#description' => $this
->t('This will appear in the URL after the ? to identify this searched fields form element.'),
'#states' => [
'visible' => [
':input[name="options[expose][expose_fields]"]' => [
'checked' => TRUE,
],
],
],
];
}
public function buildExposedForm(&$form, FormStateInterface $form_state) {
parent::buildExposedForm($form, $form_state);
if (empty($this->options['exposed'])) {
return;
}
if ($this->options['expose']['expose_fields']) {
$fields = $this
->getFulltextFields();
$configured_fields = $this->options['fields'];
if (!empty($configured_fields)) {
$configured_fields = array_flip($configured_fields);
$fields = array_intersect_key($fields, $configured_fields);
}
$searched_fields_identifier = $this->options['id'] . '_searched_fields';
if (!empty($this->options['expose']['searched_fields_id'])) {
$searched_fields_identifier = $this->options['expose']['searched_fields_id'];
}
$form[$searched_fields_identifier] = [
'#type' => 'select',
'#title' => $this
->t('Search fields'),
'#options' => $fields,
'#multiple' => TRUE,
'#size' => min(count($fields), 5),
];
}
}
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'];
}
}
protected function exposedTranslate(&$form, $type) {
parent::exposedTranslate($form, $type);
$form['#required'] = FALSE;
}
public function validateExposed(&$form, FormStateInterface $form_state) {
if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
return;
}
$searched_fields_identifier = $this->options['id'] . '_searched_fields';
if (!empty($this->options['expose']['searched_fields_id'])) {
$searched_fields_identifier = $this->options['expose']['searched_fields_id'];
}
$this->searchedFields = $form_state
->getValue($searched_fields_identifier, []);
$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'];
}
while (is_array($input)) {
$input = $input ? reset($input) : '';
}
if (trim($input) === '') {
if (!empty($this->options['expose']['required']) && $this
->getQuery()) {
$this
->getQuery()
->abort();
}
return;
}
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
->setErrorByName($identifier, $msg);
}
$input = implode(' ', $words);
}
public function query() {
while (is_array($this->value)) {
$this->value = $this->value ? reset($this->value) : '';
}
if ($this->value === '') {
return;
}
$fields = $this->options['fields'];
$fields = $fields ?: array_keys($this
->getFulltextFields());
if (!empty($this->searchedFields)) {
$fields = array_intersect($fields, $this->searchedFields);
}
$query = $this
->getQuery();
$old = $query
->getKeys();
$old_original = $query
->getOriginalKeys();
if ($this->options['parse_mode']) {
$parse_mode = $this
->getParseModeManager()
->createInstance($this->options['parse_mode']);
$query
->setParseMode($parse_mode);
}
$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 ($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 {
}
unset($keys);
}
if ($old) {
$keys =& $query
->getKeys();
if (is_array($keys)) {
if (is_scalar($old)) {
$keys = "({$old}) ({$this->value})";
}
else {
if (empty($keys['#negation']) !== empty($old['#negation']) || $keys['#conjunction'] !== $old['#conjunction']) {
$keys = [
'#conjunction' => 'AND',
$old,
$keys,
];
}
else {
foreach ($old as $key => $value) {
if (substr($key, 0, 1) === '#') {
continue;
}
$keys[] = $value;
}
}
}
}
elseif (is_scalar($old_original)) {
$combined_keys = "({$old_original}) ({$keys})";
$query
->keys($combined_keys);
$keys = $combined_keys;
}
unset($keys);
}
}
protected function getFulltextFields() {
$fields = [];
$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;
}
}