/** * Tests whether overriding of testType() works correctly. */ public function testTestTypeOverride() { $override = function ($type) { return Utility::isTextType($type, array('string', 'integer')); }; $this->processor->setMethodOverride('testType', $override); $items = $this->getTestItem(); $this->processor->preprocessIndexItems($items); $this->assertFieldsProcessed($items, array('string_field', 'integer_field')); }
/** * {@inheritdoc} */ protected function testType($type) { return Utility::isTextType($type, array('text', 'tokenized_text')); }
/** * {@inheritdoc} */ protected function processFieldValue(&$value, &$type) { // Remove invisible content. $text = preg_replace('@<(applet|audio|canvas|command|embed|iframe|map|menu|noembed|noframes|noscript|script|style|svg|video)[^>]*>.*</\1>@siU', ' ', $value); // Let removed tags still delimit words. $is_text_type = Utility::isTextType($type, array('text', 'tokenized_text')); if ($is_text_type) { $text = str_replace(array('<', '>'), array(' <', '> '), $text); if ($this->configuration['title']) { $text = preg_replace('/(<[-a-z_]+[^>]*["\s])title\s*=\s*("([^"]+)"|\'([^\']+)\')([^>]*>)/i', '$1 $5 $3$4 ', $text); } if ($this->configuration['alt']) { $text = preg_replace('/<[-a-z_]+[^>]*["\s]alt\s*=\s*("([^"]+)"|\'([^\']+)\')[^>]*>/i', ' <img>$2$3</img> ', $text); } } if ($this->configuration['tags'] && $is_text_type) { $text = strip_tags($text, '<' . implode('><', array_keys($this->configuration['tags'])) . '>'); $value = $this->parseHtml($text); $type = 'tokenized_text'; } else { $text = strip_tags($text); $value = $this->normalizeText(trim($text)); } }
/** * Retrieves the fulltext fields of the given result items. * * @param \Drupal\search_api\Item\ItemInterface[] $result_items * The results for which fulltext data should be extracted, keyed by item * ID. * @param bool $load * (optional) If FALSE, only field values already present will be returned. * Otherwise, fields will be loaded if necessary. * * @return \Drupal\search_api\Item\FieldInterface[][] * An two-dimensional array of fulltext fields, keyed first by item ID and * then field ID. */ protected function getFulltextFields(array $result_items, $load = TRUE) { // @todo Add some caching, since this will sometimes be called twice for the // same result set. $items = array(); // All the index's fulltext fields, grouped by datasource. $fulltext_fields = array(); foreach ($this->index->getFields() as $field_id => $field) { if (Utility::isTextType($field->getType())) { $fulltext_fields[$field->getDatasourceId()][$field_id] = $field; } } $needs_extraction = array(); foreach ($result_items as $item_id => $result_item) { $datasource_id = $result_item->getDatasourceId(); // Make sure this datasource even has any indexed fulltext fields. if (empty($fulltext_fields[$datasource_id])) { continue; } /** @var \Drupal\search_api\Item\FieldInterface $field */ foreach ($fulltext_fields[$datasource_id] as $field_id => $field) { if ($result_item->getField($field_id, FALSE)) { $items[$item_id][$field_id] = $result_item->getField($field_id, FALSE); } elseif ($load) { $needs_extraction[$item_id][$field->getPropertyPath()] = clone $field; } } } $needs_load = array(); foreach ($needs_extraction as $item_id => $fields) { if (!$result_items[$item_id]->getOriginalObject(FALSE)) { $needs_load[$item_id] = $item_id; } } if ($needs_load) { foreach ($this->index->loadItemsMultiple($needs_load) as $item_id => $object) { $result_items[$item_id]->setOriginalObject($object); unset($needs_load[$item_id]); } } // Remove the fields for all items that couldn't be loaded. $needs_extraction = array_diff_key($needs_extraction, $needs_load); foreach ($needs_extraction as $item_id => $fields) { try { Utility::extractFields($result_items[$item_id]->getOriginalObject(), $fields); foreach ($fields as $field) { $field_id = $field->getFieldIdentifier(); $result_items[$item_id]->setField($field_id, $field); $items[$item_id][$field_id] = $field; } } catch (SearchApiException $e) { // Missing highlighting will be the least problem in this case – just // ignore it. } } return $items; }
/** * Implements the magic __toString() method to simplify debugging. */ public function __toString() { $out = $this->getLabel() . ' [' . $this->getFieldIdentifier() . ']: '; if ($this->isIndexed()) { $out .= 'indexed as type ' . $this->getType(); if (Utility::isTextType($this->getType())) { $out .= ' (boost ' . $this->getBoost() . ')'; } } else { $out .= 'not indexed'; } if ($this->getValues()) { $out .= "\nValues:"; foreach ($this->getValues() as $value) { $value = str_replace("\n", "\n ", "{$value}"); $out .= "\n- " . $value; } } return $out; }
/** * {@inheritdoc} */ public function getFulltextFields() { $fulltext_fields = array(); foreach ($this->getFields() as $key => $field) { if (Utility::isTextType($field->getType())) { $fulltext_fields[] = $key; } } return $fulltext_fields; }
/** * {@inheritdoc} */ public function getFulltextFields($only_indexed = TRUE) { $i = $only_indexed ? 1 : 0; if (!isset($this->fulltextFields[$i])) { $this->fulltextFields[$i] = array(); if ($only_indexed) { if (isset($this->options['fields'])) { foreach ($this->options['fields'] as $key => $field) { if (Utility::isTextType($field['type'])) { $this->fulltextFields[$i][] = $key; } } } } else { foreach ($this->getFields(FALSE) as $key => $field) { if (Utility::isTextType($field->getType())) { $this->fulltextFields[$i][] = $key; } } } } return $this->fulltextFields[$i]; }
/** * 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; }
public function setGrouping(Query $solarium_query, QueryInterface $query, $grouping_options = array(), $index_fields = array(), $field_names = array()) { $group_params['group'] = 'true'; // We always want the number of groups returned so that we get pagers done // right. $group_params['group.ngroups'] = 'true'; if (!empty($grouping_options['truncate'])) { $group_params['group.truncate'] = 'true'; } if (!empty($grouping_options['group_facet'])) { $group_params['group.facet'] = 'true'; } foreach ($grouping_options['fields'] as $collapse_field) { $type = $index_fields[$collapse_field]['type']; // Only single-valued fields are supported. if ($this->getSolrVersion() < 4) { // For Solr 3.x, only string and boolean fields are supported. if (!SearchApiUtility::isTextType($type, array('string', 'boolean', 'uri'))) { $warnings[] = $this->t('Grouping is not supported for field @field. ' . 'Only single-valued fields of type "String", "Boolean" or "URI" are supported.', array('@field' => $index_fields[$collapse_field]['name'])); continue; } } else { if (SearchApiUtility::isTextType($type)) { $warnings[] = $this->t('Grouping is not supported for field @field. ' . 'Only single-valued fields not indexed as "Fulltext" are supported.', array('@field' => $index_fields[$collapse_field]['name'])); continue; } } $group_params['group.field'][] = $field_names[$collapse_field]; } if (empty($group_params['group.field'])) { unset($group_params); } else { if (!empty($grouping_options['group_sort'])) { foreach ($grouping_options['group_sort'] as $group_sort_field => $order) { if (isset($fields[$group_sort_field])) { $f = $fields[$group_sort_field]; if (substr($f, 0, 3) == 'ss_') { $f = 'sort_' . substr($f, 3); } $order = strtolower($order); $group_params['group.sort'][] = $f . ' ' . $order; } } if (!empty($group_params['group.sort'])) { $group_params['group.sort'] = implode(', ', $group_params['group.sort']); } } if (!empty($grouping_options['group_limit']) && $grouping_options['group_limit'] != 1) { $group_params['group.limit'] = $grouping_options['group_limit']; } } foreach ($group_params as $param_id => $param_value) { $solarium_query->addParam($param_id, $param_value); } }
/** * Implements the magic __toString() method to simplify debugging. */ public function __toString() { $label = $this->getLabel(); $field_id = $this->getFieldIdentifier(); $type = $this->getType(); $out = "{$label} [{$field_id}]: indexed as type {$type}"; if (Utility::isTextType($type)) { $out .= ' (boost ' . $this->getBoost() . ')'; } if ($this->getValues()) { $out .= "\nValues:"; foreach ($this->getValues() as $value) { $value = str_replace("\n", "\n ", "{$value}"); $out .= "\n- " . $value; } } return $out; }