/** * Sets the keys to search for. * * If this method is not called on the query before execution, this will be a * filter-only query. * * @param string|array|null $keys * A string with the search keys, in one of the formats specified by * getKeys(). A passed string will be parsed according to the set parse * mode. Use NULL to not use any search keys. * * @return $this * * @see \Drupal\search_api\Query\QueryInterface::keys() */ public function keys($keys = NULL) { if (!$this->shouldAbort()) { $this->query->keys($keys); } return $this; }
/** * Implements SearchApiAutocompleteInterface::getAutocompleteSuggestions(). */ public function getAutocompleteSuggestions(QueryInterface $query, SearchApiAutocompleteSearch $search, $incomplete_key, $user_input) { $settings = isset($this->configuration['autocomplete']) ? $this->configuration['autocomplete'] : array(); $settings += array( 'suggest_suffix' => TRUE, 'suggest_words' => TRUE, ); // If none of these options is checked, the user apparently chose a very // roundabout way of telling us he doesn't want autocompletion. if (!array_filter($settings)) { return array(); } $index = $query->getIndex(); $db_info = $this->getIndexDbInfo($index); if (empty($db_info['field_tables'])) { throw new SearchApiException(new FormattableMarkup('Unknown index @id.', array('@id' => $index->id()))); } $fields = $this->getFieldInfo($index); $suggestions = array(); $passes = array(); $incomplete_like = NULL; // Make the input lowercase as the indexed data is (usually) also all // lowercase. $incomplete_key = Unicode::strtolower($incomplete_key); $user_input = Unicode::strtolower($user_input); // Decide which methods we want to use. if ($incomplete_key && $settings['suggest_suffix']) { $passes[] = 1; $incomplete_like = $this->database->escapeLike($incomplete_key) . '%'; } if ($settings['suggest_words'] && (!$incomplete_key || strlen($incomplete_key) >= $this->configuration['min_chars'])) { $passes[] = 2; } if (!$passes) { return array(); } // We want about half of the suggestions from each enabled method. $limit = $query->getOption('limit', 10); $limit /= count($passes); $limit = ceil($limit); // Also collect all keywords already contained in the query so we don't // suggest them. $keys = preg_split('/[^\p{L}\p{N}]+/u', $user_input, -1, PREG_SPLIT_NO_EMPTY); $keys = array_combine($keys, $keys); if ($incomplete_key) { $keys[$incomplete_key] = $incomplete_key; } foreach ($passes as $pass) { if ($pass == 2 && $incomplete_key) { $query->keys($user_input); } // To avoid suggesting incomplete words, we have to temporarily disable // the "partial_matches" option. (There should be no way we'll save the // server during the createDbQuery() call, so this should be safe.) $options = $this->options; $this->options['partial_matches'] = FALSE; $db_query = $this->createDbQuery($query, $fields); $this->options = $options; // We need a list of all current results to match the suggestions against. // However, since MySQL doesn't allow using a temporary table multiple // times in one query, we regrettably have to do it this way. $fulltext_fields = $this->getQueryFulltextFields($query); if (count($fulltext_fields) > 1) { $all_results = $db_query->execute()->fetchCol(); // Compute the total number of results so we can later sort out matches // that occur too often. $total = count($all_results); } else { $table = $this->getTemporaryResultsTable($db_query); if (!$table) { return array(); } $all_results = $this->database->select($table, 't') ->fields('t', array('item_id')); $total = $this->database->query("SELECT COUNT(item_id) FROM {{$table}}")->fetchField(); } $max_occurrences = $this->getConfigFactory()->get('search_api_db.settings')->get('autocomplete_max_occurrences'); $max_occurrences = max(1, floor($total * $max_occurrences)); if (!$total) { if ($pass == 1) { return NULL; } continue; } /** @var \Drupal\Core\Database\Query\SelectInterface|null $word_query */ $word_query = NULL; foreach ($fulltext_fields as $field) { if (!isset($fields[$field]) || !Utility::isTextType($fields[$field]['type'])) { continue; } $field_query = $this->database->select($fields[$field]['table'], 't'); $field_query->fields('t', array('word', 'item_id')) ->condition('item_id', $all_results, 'IN'); if ($pass == 1) { $field_query->condition('word', $incomplete_like, 'LIKE') ->condition('word', $keys, 'NOT IN'); } if (!isset($word_query)) { $word_query = $field_query; } else { $word_query->union($field_query); } } if (!$word_query) { return array(); } $db_query = $this->database->select($word_query, 't'); $db_query->addExpression('COUNT(DISTINCT item_id)', 'results'); $db_query->fields('t', array('word')) ->groupBy('word') ->having('results <= :max', array(':max' => $max_occurrences)) ->orderBy('results', 'DESC') ->range(0, $limit); $incomp_len = strlen($incomplete_key); foreach ($db_query->execute() as $row) { $suffix = ($pass == 1) ? substr($row->word, $incomp_len) : ' ' . $row->word; $suggestions[] = array( 'suggestion_suffix' => $suffix, 'results' => $row->results, ); } } return $suggestions; }