Issue #3075695 by kim.pepper, andypost, jhodgdon, larowlan, pwolanin, Sam152: Move search_index() and related functions to a service

(cherry picked from commit 7dc65c5c5d)
merge-requests/55/head
Lee Rowlands 2019-10-11 15:03:59 +10:00
parent 61590bffa7
commit fbd5b07c1b
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
31 changed files with 665 additions and 319 deletions

View File

@ -18,6 +18,7 @@ use Drupal\help\HelpSectionManager;
use Drupal\help_topics\SearchableHelpInterface;
use Drupal\search\Plugin\SearchIndexingInterface;
use Drupal\search\Plugin\SearchPluginBase;
use Drupal\search\SearchIndexInterface;
use Drupal\search\SearchQuery;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -91,6 +92,13 @@ class HelpSearch extends SearchPluginBase implements AccessibleInterface, Search
*/
protected $helpSectionManager;
/**
* The search index.
*
* @var \Drupal\search\SearchIndexInterface
*/
protected $searchIndex;
/**
* {@inheritdoc}
*/
@ -105,7 +113,8 @@ class HelpSearch extends SearchPluginBase implements AccessibleInterface, Search
$container->get('messenger'),
$container->get('current_user'),
$container->get('state'),
$container->get('plugin.manager.help_section')
$container->get('plugin.manager.help_section'),
$container->get('search.index')
);
}
@ -132,8 +141,10 @@ class HelpSearch extends SearchPluginBase implements AccessibleInterface, Search
* The state object.
* @param \Drupal\help\HelpSectionManager $help_section_manager
* The help section manager.
* @param \Drupal\search\SearchIndexInterface $search_index
* The search index.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, Config $search_settings, LanguageManagerInterface $language_manager, MessengerInterface $messenger, AccountInterface $account, StateInterface $state, HelpSectionManager $help_section_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, Config $search_settings, LanguageManagerInterface $language_manager, MessengerInterface $messenger, AccountInterface $account, StateInterface $state, HelpSectionManager $help_section_manager, SearchIndexInterface $search_index) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->database = $database;
$this->searchSettings = $search_settings;
@ -142,6 +153,7 @@ class HelpSearch extends SearchPluginBase implements AccessibleInterface, Search
$this->account = $account;
$this->state = $state;
$this->helpSectionManager = $help_section_manager;
$this->searchIndex = $search_index;
}
/**
@ -327,35 +339,41 @@ class HelpSearch extends SearchPluginBase implements AccessibleInterface, Search
$language_list = $this->languageManager->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
$section_plugins = [];
foreach ($items as $item) {
$section_plugin_id = $item->section_plugin_id;
if (!isset($section_plugins[$section_plugin_id])) {
$section_plugins[$section_plugin_id] = $this->getSectionPlugin($section_plugin_id);
}
$words = [];
try {
foreach ($items as $item) {
$section_plugin_id = $item->section_plugin_id;
if (!isset($section_plugins[$section_plugin_id])) {
$section_plugins[$section_plugin_id] = $this->getSectionPlugin($section_plugin_id);
}
if (!$section_plugins[$section_plugin_id]) {
$this->removeItemsFromIndex($item->sid);
continue;
}
if (!$section_plugins[$section_plugin_id]) {
$this->removeItemsFromIndex($item->sid);
continue;
}
$section_plugin = $section_plugins[$section_plugin_id];
search_index_clear($this->getType(), $item->sid);
foreach ($language_list as $langcode => $language) {
$topic = $section_plugin->renderTopicForSearch($item->topic_id, $language);
if ($topic) {
// Index the title plus body text.
$text = '<h1>' . $topic['title'] . '</h1>' . "\n" . $topic['text'];
search_index($this->getType(), $item->sid, $langcode, $text);
$section_plugin = $section_plugins[$section_plugin_id];
$this->searchIndex->clear($this->getType(), $item->sid);
foreach ($language_list as $langcode => $language) {
$topic = $section_plugin->renderTopicForSearch($item->topic_id, $language);
if ($topic) {
// Index the title plus body text.
$text = '<h1>' . $topic['title'] . '</h1>' . "\n" . $topic['text'];
$words += $this->searchIndex->index($this->getType(), $item->sid, $langcode, $text);
}
}
}
}
finally {
$this->searchIndex->updateWordWeights($words);
}
}
/**
* {@inheritdoc}
*/
public function indexClear() {
search_index_clear($this->getType());
$this->searchIndex->clear($this->getType());
}
/**
@ -419,7 +437,7 @@ class HelpSearch extends SearchPluginBase implements AccessibleInterface, Search
*/
public function markForReindex() {
$this->updateTopicList();
search_mark_for_reindex($this->getType());
$this->searchIndex->markForReindex($this->getType());
}
/**
@ -466,7 +484,7 @@ class HelpSearch extends SearchPluginBase implements AccessibleInterface, Search
// Remove items from the search tables individually, as there is no bulk
// function to delete items from the search index.
foreach ($sids as $sid) {
search_index_clear($this->getType(), $sid);
$this->searchIndex->clear($this->getType(), $sid);
}
}

View File

@ -1421,7 +1421,7 @@ function node_configurable_language_delete(ConfigurableLanguageInterface $langua
function node_reindex_node_search($nid) {
if (\Drupal::moduleHandler()->moduleExists('search')) {
// Reindex node context indexed by the node module search plugin.
search_mark_for_reindex('node_search', $nid);
\Drupal::service('search.index')->markForReindex('node_search', $nid);
}
}

View File

@ -163,9 +163,11 @@ class Node extends EditorialContentEntityBase implements NodeInterface {
parent::preDelete($storage, $entities);
// Ensure that all nodes deleted are removed from the search index.
if (\Drupal::moduleHandler()->moduleExists('search')) {
if (\Drupal::hasService('search.index')) {
/** @var \Drupal\search\SearchIndexInterface $search_index */
$search_index = \Drupal::service('search.index');
foreach ($entities as $entity) {
search_index_clear('node_search', $entity->nid->value);
$search_index->clear('node_search', $entity->nid->value);
}
}
}

View File

@ -2,10 +2,12 @@
namespace Drupal\node\Plugin\Search;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\Config;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\Query\SelectExtender;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
@ -15,14 +17,13 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Render\RendererInterface;
use Drupal\node\NodeInterface;
use Drupal\search\Plugin\ConfigurableSearchPluginBase;
use Drupal\search\Plugin\SearchIndexingInterface;
use Drupal\search\SearchIndexInterface;
use Drupal\Search\SearchQuery;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -98,6 +99,13 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
*/
protected $renderer;
/**
* The search index.
*
* @var \Drupal\search\SearchIndexInterface
*/
protected $searchIndex;
/**
* An array of additional rankings from hook_ranking().
*
@ -153,7 +161,8 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
$container->get('renderer'),
$container->get('messenger'),
$container->get('current_user'),
$container->get('database.replica')
$container->get('database.replica'),
$container->get('search.index')
);
}
@ -184,8 +193,10 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
* The $account object to use for checking for access to advanced search.
* @param \Drupal\Core\Database\Connection|null $database_replica
* (Optional) the replica database connection.
* @param \Drupal\search\SearchIndexInterface $search_index
* The search index.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, Config $search_settings, LanguageManagerInterface $language_manager, RendererInterface $renderer, MessengerInterface $messenger, AccountInterface $account = NULL, Connection $database_replica = NULL) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, Config $search_settings, LanguageManagerInterface $language_manager, RendererInterface $renderer, MessengerInterface $messenger, AccountInterface $account = NULL, Connection $database_replica = NULL, SearchIndexInterface $search_index = NULL) {
$this->database = $database;
$this->databaseReplica = $database_replica ?: $database;
$this->entityTypeManager = $entity_type_manager;
@ -198,6 +209,11 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->addCacheTags(['node_list']);
if (!$search_index) {
@trigger_error('Calling NodeSearch::__construct() without the $search_index argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/3075696', E_USER_DEPRECATED);
$search_index = \Drupal::service('search.index');
}
$this->searchIndex = $search_index;
}
/**
@ -478,8 +494,14 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
}
$node_storage = $this->entityTypeManager->getStorage('node');
foreach ($node_storage->loadMultiple($nids) as $node) {
$this->indexNode($node);
$words = [];
try {
foreach ($node_storage->loadMultiple($nids) as $node) {
$this->indexNode($node, $words);
}
}
finally {
$this->searchIndex->updateWordWeights($words);
}
}
@ -488,8 +510,10 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
*
* @param \Drupal\node\NodeInterface $node
* The node to index.
* @param array $words
* Words that need updating after the index run.
*/
protected function indexNode(NodeInterface $node) {
protected function indexNode(NodeInterface $node, array &$words) {
$languages = $node->getTranslationLanguages();
$node_render = $this->entityTypeManager->getViewBuilder('node');
@ -516,7 +540,7 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
}
// Update index, using search index "type" equal to the plugin ID.
search_index($this->getPluginId(), $node->id(), $language->getId(), $text);
$words += $this->searchIndex->index($this->getPluginId(), $node->id(), $language->getId(), $text);
}
}
@ -526,7 +550,7 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
public function indexClear() {
// All NodeSearch pages share a common search index "type" equal to
// the plugin ID.
search_index_clear($this->getPluginId());
$this->searchIndex->clear($this->getPluginId());
}
/**
@ -535,7 +559,7 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
public function markForReindex() {
// All NodeSearch pages share a common search index "type" equal to
// the plugin ID.
search_mark_for_reindex($this->getPluginId());
$this->searchIndex->markForReindex($this->getPluginId());
}
/**

View File

@ -5,13 +5,11 @@
* Enables site-wide keyword searching.
*/
use Drupal\Core\Url;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
/**
* Matches all 'N' Unicode character classes (numbers)
@ -140,35 +138,15 @@ function search_preprocess_block(&$variables) {
* @param string|null $langcode
* (optional) Language code of the item to remove from the search index. If
* omitted, all items matching $sid and $type are cleared.
*
* @deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use
* \Drupal\search\SearchIndex::clear() instead.
*
* @see https://www.drupal.org/node/3075696
*/
function search_index_clear($type = NULL, $sid = NULL, $langcode = NULL) {
$connection = \Drupal::database();
$query_index = $connection->delete('search_index');
$query_dataset = $connection->delete('search_dataset');
if ($type) {
$query_index->condition('type', $type);
$query_dataset->condition('type', $type);
if ($sid) {
$query_index->condition('sid', $sid);
$query_dataset->condition('sid', $sid);
if ($langcode) {
$query_index->condition('langcode', $langcode);
$query_dataset->condition('langcode', $langcode);
}
}
}
$query_index->execute();
$query_dataset->execute();
if ($type) {
// Invalidate all render cache items that contain data from this index.
Cache::invalidateTags(['search_index:' . $type]);
}
else {
// Invalidate all render cache items that contain data from any index.
Cache::invalidateTags(['search_index']);
}
@trigger_error("search_index_clear() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::clear() instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED);
\Drupal::service('search.index')->clear($type, $sid, $langcode);
}
/**
@ -176,14 +154,17 @@ function search_index_clear($type = NULL, $sid = NULL, $langcode = NULL) {
*
* This is used during indexing (cron). Words that are dirty have outdated
* total counts in the search_total table, and need to be recounted.
*
* @deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom
* implementation of \Drupal\search\SearchIndexInterface instead.
*
* @see https://www.drupal.org/node/3075696
*/
function search_dirty($word = NULL) {
$dirty = &drupal_static(__FUNCTION__, []);
if ($word !== NULL) {
$dirty[$word] = TRUE;
}
else {
return $dirty;
@trigger_error("search_dirty() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED);
// Keep return result type for backward compatibility.
if ($word === NULL) {
return [];
}
}
@ -192,14 +173,8 @@ function search_dirty($word = NULL) {
*
* Fires updateIndex() in the plugins for all indexable active search pages,
* and cleans up dirty words.
*
* @see search_dirty()
*/
function search_cron() {
// We register a shutdown function to ensure that search_total is always up
// to date.
drupal_register_shutdown_function('search_update_totals');
/** @var $search_page_repository \Drupal\search\SearchPageRepositoryInterface */
$search_page_repository = \Drupal::service('search.search_page_repository');
foreach ($search_page_repository->getIndexableSearchPages() as $entity) {
@ -212,34 +187,14 @@ function search_cron() {
*
* This function is called on shutdown to ensure that {search_total} is always
* up to date (even if cron times out or otherwise fails).
*
* @deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom
* implementation of \Drupal\search\SearchIndexInterface instead.
*
* @see https://www.drupal.org/node/3075696
*/
function search_update_totals() {
$connection = \Drupal::database();
$replica = \Drupal::service('database.replica');
// Update word IDF (Inverse Document Frequency) counts for new/changed words.
foreach (search_dirty() as $word => $dummy) {
// Get total count
$total = $replica->query("SELECT SUM(score) FROM {search_index} WHERE word = :word", [':word' => $word])->fetchField();
// Apply Zipf's law to equalize the probability distribution.
$total = log10(1 + 1 / (max(1, $total)));
$connection->merge('search_total')
->key('word', $word)
->fields(['count' => $total])
->execute();
}
// Find words that were deleted from search_index, but are still in
// search_total. We use a LEFT JOIN between the two tables and keep only the
// rows which fail to join.
$result = $replica->query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL");
$or = new Condition('OR');
foreach ($result as $word) {
$or->condition('word', $word->realword);
}
if (count($or) > 0) {
$connection->delete('search_total')
->condition($or)
->execute();
}
@trigger_error("search_update_totals() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED);
}
/**
@ -439,137 +394,15 @@ function search_invoke_preprocess(&$text, $langcode = NULL) {
* The content of this item. Must be a piece of HTML or plain text.
*
* @ingroup search
*
* @deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use
* \Drupal\search\SearchIndex::index() instead.
*
* @see https://www.drupal.org/node/3075696
*/
function search_index($type, $sid, $langcode, $text) {
$minimum_word_size = \Drupal::config('search.settings')->get('index.minimum_word_size');
// Multipliers for scores of words inside certain HTML tags. The weights are
// stored in config so that modules can overwrite the default weights.
// Note: 'a' must be included for link ranking to work.
$tags = \Drupal::config('search.settings')->get('index.tag_weights');
// Strip off all ignored tags to speed up processing, but insert space before
// and after them to keep word boundaries.
$text = str_replace(['<', '>'], [' <', '> '], $text);
$text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>');
// Split HTML tags from plain text.
$split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
// Note: PHP ensures the array consists of alternating delimiters and literals
// and begins and ends with a literal (inserting $null as required).
// Odd/even counter. Tag or no tag.
$tag = FALSE;
// Starting score per word.
$score = 1;
// Accumulator for cleaned up data.
$accum = ' ';
// Stack with open tags.
$tagstack = [];
// Counter for consecutive words.
$tagwords = 0;
// Focus state.
$focus = 1;
// Accumulator for words for index.
$scored_words = [];
foreach ($split as $value) {
if ($tag) {
// Increase or decrease score per word based on tag
list($tagname) = explode(' ', $value, 2);
$tagname = mb_strtolower($tagname);
// Closing or opening tag?
if ($tagname[0] == '/') {
$tagname = substr($tagname, 1);
// If we encounter unexpected tags, reset score to avoid incorrect boosting.
if (!count($tagstack) || $tagstack[0] != $tagname) {
$tagstack = [];
$score = 1;
}
else {
// Remove from tag stack and decrement score
$score = max(1, $score - $tags[array_shift($tagstack)]);
}
}
else {
if (isset($tagstack[0]) && $tagstack[0] == $tagname) {
// None of the tags we look for make sense when nested identically.
// If they are, it's probably broken HTML.
$tagstack = [];
$score = 1;
}
else {
// Add to open tag stack and increment score
array_unshift($tagstack, $tagname);
$score += $tags[$tagname];
}
}
// A tag change occurred, reset counter.
$tagwords = 0;
}
else {
// Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty values
if ($value != '') {
$words = search_index_split($value, $langcode);
foreach ($words as $word) {
// Add word to accumulator
$accum .= $word . ' ';
// Check word length.
if (is_numeric($word) || mb_strlen($word) >= $minimum_word_size) {
if (!isset($scored_words[$word])) {
$scored_words[$word] = 0;
}
$scored_words[$word] += $score * $focus;
// Focus is a decaying value in terms of the amount of unique words up to this point.
// From 100 words and more, it decays, to e.g. 0.5 at 500 words and 0.3 at 1000 words.
$focus = min(1, .01 + 3.5 / (2 + count($scored_words) * .015));
}
$tagwords++;
// Too many words inside a single tag probably mean a tag was accidentally left open.
if (count($tagstack) && $tagwords >= 15) {
$tagstack = [];
$score = 1;
}
}
}
}
$tag = !$tag;
}
// Remove the item $sid from the search index, and invalidate the relevant
// cache tags.
search_index_clear($type, $sid, $langcode);
$connection = \Drupal::database();
// Insert cleaned up data into dataset
$connection->insert('search_dataset')
->fields([
'sid' => $sid,
'langcode' => $langcode,
'type' => $type,
'data' => $accum,
'reindex' => 0,
])
->execute();
// Insert results into search index
foreach ($scored_words as $word => $score) {
// If a word already exists in the database, its score gets increased
// appropriately. If not, we create a new record with the appropriate
// starting score.
$connection->merge('search_index')
->keys([
'word' => $word,
'sid' => $sid,
'langcode' => $langcode,
'type' => $type,
])
->fields(['score' => $score])
->expression('score', 'score + :score', [':score' => $score])
->execute();
search_dirty($word);
}
@trigger_error("search_index() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::index() instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED);
\Drupal::service('search.index')->index($type, $sid, $langcode, $text);
}
/**
@ -589,25 +422,15 @@ function search_index($type, $sid, $langcode, $text) {
* @param string $langcode
* (optional) The language code to clear. If omitted, everything matching
* $type and $sid is marked.
*
* @deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use
* \Drupal\search\SearchIndex::markForReindex() instead.
*
* @see https://www.drupal.org/node/3075696
*/
function search_mark_for_reindex($type = NULL, $sid = NULL, $langcode = NULL) {
$query = \Drupal::database()->update('search_dataset')
->fields(['reindex' => REQUEST_TIME])
// Only mark items that were not previously marked for reindex, so that
// marked items maintain their priority by request time.
->condition('reindex', 0);
if ($type) {
$query->condition('type', $type);
if ($sid) {
$query->condition('sid', $sid);
if ($langcode) {
$query->condition('langcode', $langcode);
}
}
}
$query->execute();
@trigger_error("search_mark_for_reindex() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::markForReindex() instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED);
\Drupal::service('search.index')->markForReindex($type, $sid, $langcode);
}
/**

View File

@ -6,3 +6,7 @@ services:
search.search_page_repository:
class: Drupal\search\SearchPageRepository
arguments: ['@config.factory', '@entity_type.manager']
search.index:
class: Drupal\search\SearchIndex
arguments: ['@config.factory', '@database','@database.replica', '@cache_tags.invalidator']

View File

@ -0,0 +1,8 @@
<?php
namespace Drupal\search\Exception;
/**
* Exception thrown for search index errors.
*/
class SearchIndexException extends \RuntimeException {}

View File

@ -30,8 +30,8 @@ interface SearchIndexingInterface {
* This method is called every cron run if the plugin has been set as
* an active search module on the Search settings page
* (admin/config/search/pages). It allows your module to add items to the
* built-in search index using search_index(), or to add them to your module's
* own indexing mechanism.
* built-in search index by calling the index() method on the search.index
* service class, or to add them to your module's own indexing mechanism.
*
* When implementing this method, your module should index content items that
* were modified or added since the last run. There is a time limit for cron,
@ -49,10 +49,10 @@ interface SearchIndexingInterface {
*
* When a request is made to clear all items from the search index related to
* this plugin, this method will be called. If this plugin uses the default
* search index, this method can call search_index_clear($type) to remove
* indexed items from the search database.
* search index, this method can call clear($type) method on the search.index
* service class to remove indexed items from the search database.
*
* @see search_index_clear()
* @see \Drupal\search\SearchIndexInterface::clear()
*/
public function indexClear();
@ -61,11 +61,11 @@ interface SearchIndexingInterface {
*
* When a request is made to mark all items from the search index related to
* this plugin for reindexing, this method will be called. If this plugin uses
* the default search index, this method can call
* search_mark_for_reindex($type) to mark the items in the search database for
* reindexing.
* the default search index, this method can call markForReindex($type) method
* on the search.index service class to mark the items in the search database
* for reindexing.
*
* @see search_mark_for_reindex()
* @see \Drupal\search\SearchIndexInterface::markForReindex()
*/
public function markForReindex();

View File

@ -66,8 +66,8 @@ interface SearchInterface extends PluginInspectionInterface {
* The type used by this search plugin in the search index, or NULL if this
* plugin does not use the search index.
*
* @see search_index()
* @see search_index_clear()
* @see \Drupal\search\SearchIndexInterface::index()
* @see \Drupal\search\SearchIndexInterface::clear()
*/
public function getType();

View File

@ -0,0 +1,317 @@
<?php
namespace Drupal\search;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Condition;
use Drupal\search\Exception\SearchIndexException;
/**
* Provides search index management functions.
*/
class SearchIndex implements SearchIndexInterface {
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The database replica connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $replica;
/**
* The cache tags invalidator.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
*/
protected $cacheTagsInvalidator;
/**
* SearchIndex constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
* @param \Drupal\Core\Database\Connection $replica
* The database replica connection.
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
* The cache tags invalidator.
*/
public function __construct(ConfigFactoryInterface $config_factory, Connection $connection, Connection $replica, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
$this->configFactory = $config_factory;
$this->connection = $connection;
$this->replica = $replica;
$this->cacheTagsInvalidator = $cache_tags_invalidator;
}
/**
* {@inheritdoc}
*/
public function index($type, $sid, $langcode, $text, $update_weights = TRUE) {
$settings = $this->configFactory->get('search.settings');
$minimum_word_size = $settings->get('index.minimum_word_size');
// Keep track of the words that need to have their weights updated.
$current_words = [];
// Multipliers for scores of words inside certain HTML tags. The weights are
// stored in config so that modules can overwrite the default weights.
// Note: 'a' must be included for link ranking to work.
$tags = $settings->get('index.tag_weights');
// Strip off all ignored tags to speed up processing, but insert space
// before and after them to keep word boundaries.
$text = str_replace(['<', '>'], [' <', '> '], $text);
$text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>');
// Split HTML tags from plain text.
$split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
// Note: PHP ensures the array consists of alternating delimiters and
// literals and begins and ends with a literal (inserting $null as
// required).
// Odd/even counter. Tag or no tag.
$tag = FALSE;
// Starting score per word.
$score = 1;
// Accumulator for cleaned up data.
$accum = ' ';
// Stack with open tags.
$tagstack = [];
// Counter for consecutive words.
$tagwords = 0;
// Focus state.
$focus = 1;
// Accumulator for words for index.
$scored_words = [];
foreach ($split as $value) {
if ($tag) {
// Increase or decrease score per word based on tag.
list($tagname) = explode(' ', $value, 2);
$tagname = mb_strtolower($tagname);
// Closing or opening tag?
if ($tagname[0] == '/') {
$tagname = substr($tagname, 1);
// If we encounter unexpected tags, reset score to avoid incorrect
// boosting.
if (!count($tagstack) || $tagstack[0] != $tagname) {
$tagstack = [];
$score = 1;
}
else {
// Remove from tag stack and decrement score.
$score = max(1, $score - $tags[array_shift($tagstack)]);
}
}
else {
if (isset($tagstack[0]) && $tagstack[0] == $tagname) {
// None of the tags we look for make sense when nested identically.
// If they are, it's probably broken HTML.
$tagstack = [];
$score = 1;
}
else {
// Add to open tag stack and increment score.
array_unshift($tagstack, $tagname);
$score += $tags[$tagname];
}
}
// A tag change occurred, reset counter.
$tagwords = 0;
}
else {
// Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty
// values.
if ($value != '') {
$words = search_index_split($value, $langcode);
foreach ($words as $word) {
// Add word to accumulator.
$accum .= $word . ' ';
// Check word length.
if (is_numeric($word) || mb_strlen($word) >= $minimum_word_size) {
if (!isset($scored_words[$word])) {
$scored_words[$word] = 0;
}
$scored_words[$word] += $score * $focus;
// Focus is a decaying value in terms of the amount of unique
// words up to this point. From 100 words and more, it decays, to
// e.g. 0.5 at 500 words and 0.3 at 1000 words.
$focus = min(1, .01 + 3.5 / (2 + count($scored_words) * .015));
}
$tagwords++;
// Too many words inside a single tag probably mean a tag was
// accidentally left open.
if (count($tagstack) && $tagwords >= 15) {
$tagstack = [];
$score = 1;
}
}
}
}
$tag = !$tag;
}
// Remove the item $sid from the search index, and invalidate the relevant
// cache tags.
$this->clear($type, $sid, $langcode);
try {
// Insert cleaned up data into dataset.
$this->connection->insert('search_dataset')
->fields([
'sid' => $sid,
'langcode' => $langcode,
'type' => $type,
'data' => $accum,
'reindex' => 0,
])
->execute();
// Insert results into search index.
foreach ($scored_words as $word => $score) {
// If a word already exists in the database, its score gets increased
// appropriately. If not, we create a new record with the appropriate
// starting score.
$this->connection->merge('search_index')
->keys([
'word' => $word,
'sid' => $sid,
'langcode' => $langcode,
'type' => $type,
])
->fields(['score' => $score])
->expression('score', 'score + :score', [':score' => $score])
->execute();
$current_words[$word] = TRUE;
}
}
catch (\Exception $e) {
throw new SearchIndexException("Failed to insert dataset in index for type '$type', sid '$sid' and langcode '$langcode'", 0, $e);
}
finally {
if ($update_weights) {
$this->updateWordWeights($current_words);
}
}
return $current_words;
}
/**
* {@inheritdoc}
*/
public function clear($type = NULL, $sid = NULL, $langcode = NULL) {
try {
$query_index = $this->connection->delete('search_index');
$query_dataset = $this->connection->delete('search_dataset');
if ($type) {
$query_index->condition('type', $type);
$query_dataset->condition('type', $type);
if ($sid) {
$query_index->condition('sid', $sid);
$query_dataset->condition('sid', $sid);
if ($langcode) {
$query_index->condition('langcode', $langcode);
$query_dataset->condition('langcode', $langcode);
}
}
}
$query_index->execute();
$query_dataset->execute();
}
catch (\Exception $e) {
throw new SearchIndexException("Failed to clear index for type '$type', sid '$sid' and langcode '$langcode'", 0, $e);
}
if ($type) {
// Invalidate all render cache items that contain data from this index.
$this->cacheTagsInvalidator->invalidateTags(['search_index:' . $type]);
}
else {
// Invalidate all render cache items that contain data from any index.
$this->cacheTagsInvalidator->invalidateTags(['search_index']);
}
}
/**
* {@inheritdoc}
*/
public function markForReindex($type = NULL, $sid = NULL, $langcode = NULL) {
try {
$query = $this->connection->update('search_dataset')
->fields(['reindex' => REQUEST_TIME])
// Only mark items that were not previously marked for reindex, so that
// marked items maintain their priority by request time.
->condition('reindex', 0);
if ($type) {
$query->condition('type', $type);
if ($sid) {
$query->condition('sid', $sid);
if ($langcode) {
$query->condition('langcode', $langcode);
}
}
}
$query->execute();
}
catch (\Exception $e) {
throw new SearchIndexException("Failed to mark index for re-indexing for type '$type', sid '$sid' and langcode '$langcode'", 0, $e);
}
}
/**
* {@inheritdoc}
*/
public function updateWordWeights(array $words) {
try {
// Update word IDF (Inverse Document Frequency) counts for new/changed
// words.
$words = array_keys($words);
foreach ($words as $word) {
// Get total count.
$total = $this->replica->query("SELECT SUM(score) FROM {search_index} WHERE word = :word", [':word' => $word])
->fetchField();
// Apply Zipf's law to equalize the probability distribution.
$total = log10(1 + 1 / (max(1, $total)));
$this->connection->merge('search_total')
->key('word', $word)
->fields(['count' => $total])
->execute();
}
// Find words that were deleted from search_index, but are still in
// search_total. We use a LEFT JOIN between the two tables and keep only
// the rows which fail to join.
$result = $this->replica->query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL");
$or = new Condition('OR');
foreach ($result as $word) {
$or->condition('word', $word->realword);
}
if (count($or) > 0) {
$this->connection->delete('search_total')
->condition($or)
->execute();
}
}
catch (\Exception $e) {
throw new SearchIndexException("Failed to update totals for index words.", 0, $e);
}
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Drupal\search;
/**
* Provides search index management functions.
*
* @ingroup search
*/
interface SearchIndexInterface {
/**
* Updates the full-text search index for a particular item.
*
* @param string $type
* The plugin ID or other machine-readable type of this item,
* which should be less than 64 bytes.
* @param int $sid
* An ID number identifying this particular item (e.g., node ID).
* @param string $langcode
* Language code for the language of the text being indexed.
* @param string $text
* The content of this item. Must be a piece of HTML or plain text.
* @param bool $update_weights
* (optional) TRUE if word weights should be updated. FALSE otherwise.
*
* @return string[]
* The words to be updated.
*
* @throws \Drupal\search\Exception\SearchIndexException
* If there is an error indexing the text.
*/
public function index($type, $sid, $langcode, $text, $update_weights = TRUE);
/**
* Clears either a part of, or the entire search index.
*
* This function is meant for use by search page plugins, or for building a
* user interface that lets users clear all or parts of the search index.
*
* @param string|null $type
* (optional) The plugin ID or other machine-readable type for the items to
* remove from the search index. If omitted, $sid and $langcode are ignored
* and the entire search index is cleared.
* @param int|array|null $sid
* (optional) The ID or array of IDs of the items to remove from the search
* index. If omitted, all items matching $type are cleared, and $langcode
* is ignored.
* @param string|null $langcode
* (optional) Language code of the item to remove from the search index. If
* omitted, all items matching $sid and $type are cleared.
*
* @throws \Drupal\search\Exception\SearchIndexException
* If there is an error clearing the index.
*/
public function clear($type = NULL, $sid = NULL, $langcode = NULL);
/**
* Changes the timestamp on indexed items to 'now' to force reindexing.
*
* This function is meant for use by search page plugins, or for building a
* user interface that lets users mark all or parts of the search index for
* reindexing.
*
* @param string $type
* (optional) The plugin ID or other machine-readable type of this item. If
* omitted, the entire search index is marked for reindexing, and $sid and
* $langcode are ignored.
* @param int $sid
* (optional) An ID number identifying this particular item (e.g., node ID).
* If omitted, everything matching $type is marked, and $langcode is
* ignored.
* @param string $langcode
* (optional) The language code to mark. If omitted, everything matching
* $type and $sid is marked.
*
* @throws \Drupal\search\Exception\SearchIndexException
* If there is an error marking the index for re-indexing.
*/
public function markForReindex($type = NULL, $sid = NULL, $langcode = NULL);
/**
* Updates the {search_total} database table.
*
* @param array $words
* An array whose keys are words from self::index() whose total weights
* need to be updated.
*
* @throws \Drupal\search\Exception\SearchIndexException
* If there is an error updating the totals.
*/
public function updateWordWeights(array $words);
}

View File

@ -43,6 +43,13 @@ class SearchPageListBuilder extends DraggableListBuilder implements FormInterfac
*/
protected $searchManager;
/**
* The search index.
*
* @var \Drupal\search\SearchIndexInterface
*/
protected $searchIndex;
/**
* The messenger.
*
@ -63,12 +70,19 @@ class SearchPageListBuilder extends DraggableListBuilder implements FormInterfac
* The factory for configuration objects.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
* @param \Drupal\search\SearchIndexInterface $search_index
* The search index.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, SearchPluginManager $search_manager, ConfigFactoryInterface $config_factory, MessengerInterface $messenger) {
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, SearchPluginManager $search_manager, ConfigFactoryInterface $config_factory, MessengerInterface $messenger, SearchIndexInterface $search_index = NULL) {
parent::__construct($entity_type, $storage);
$this->configFactory = $config_factory;
$this->searchManager = $search_manager;
$this->messenger = $messenger;
if (!$search_index) {
@trigger_error('Calling SearchPageListBuilder::__construct() without the $search_index argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/3075696', E_USER_DEPRECATED);
$search_index = \Drupal::service('search.index');
}
$this->searchIndex = $search_index;
}
/**
@ -80,7 +94,8 @@ class SearchPageListBuilder extends DraggableListBuilder implements FormInterfac
$container->get('entity_type.manager')->getStorage($entity_type->id()),
$container->get('plugin.manager.search'),
$container->get('config.factory'),
$container->get('messenger')
$container->get('messenger'),
$container->get('search.index')
);
}
@ -344,9 +359,9 @@ class SearchPageListBuilder extends DraggableListBuilder implements FormInterfac
$search_settings->set('index.minimum_word_size', $form_state->getValue('minimum_word_size'));
$search_settings->set('index.overlap_cjk', $form_state->getValue('overlap_cjk'));
// Specifically mark items in the default index for reindexing, since
// these settings are used in the search_index() function.
// these settings are used in the SearchIndex::index() function.
$this->messenger->addStatus($this->t('The default search index will be rebuilt.'));
search_mark_for_reindex();
$this->searchIndex->markForReindex();
}
$search_settings

View File

@ -38,11 +38,6 @@ class SearchAdvancedSearchFormTest extends BrowserTestBase {
// First update the index. This does the initial processing.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
// Then, run the shutdown function. Testing is a unique case where indexing
// and searching has to happen in the same request, so running the shutdown
// function manually is needed to finish the indexing process.
search_update_totals();
}
/**

View File

@ -70,11 +70,6 @@ class SearchCommentCountToggleTest extends BrowserTestBase {
// First update the index. This does the initial processing.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
// Then, run the shutdown function. Testing is a unique case where indexing
// and searching has to happen in the same request, so running the shutdown
// function manually is needed to finish the indexing process.
search_update_totals();
}
/**

View File

@ -294,7 +294,7 @@ class SearchCommentTest extends BrowserTestBase {
*/
public function assertCommentAccess($assume_access, $message) {
// Invoke search index update.
search_mark_for_reindex('node_search', $this->node->id());
\Drupal::service('search.index')->markForReindex('node_search', $this->node->id());
$this->cronRun();
// Search for the comment subject.

View File

@ -53,7 +53,6 @@ class SearchConfigSettingsFormTest extends BrowserTestBase {
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Enable the search block.
$this->drupalPlaceBlock('search_form_block');

View File

@ -56,7 +56,6 @@ class SearchDateIntervalTest extends BrowserTestBase {
// Update the index.
$plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
$plugin->updateIndex();
search_update_totals();
}
/**

View File

@ -42,7 +42,6 @@ class SearchEmbedFormTest extends BrowserTestBase {
$this->node = $this->drupalCreateNode();
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Set up a dummy initial count of times the form has been submitted.
$this->submitCount = \Drupal::state()->get('search_embedded_form.submit_count');

View File

@ -46,7 +46,6 @@ class SearchExactTest extends BrowserTestBase {
// Update the search index.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Refresh variables after the treatment.
$this->refreshVariables();

View File

@ -85,7 +85,6 @@ class SearchLanguageTest extends BrowserTestBase {
// Update the index and then run the shutdown method.
$plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
$plugin->updateIndex();
search_update_totals();
}
public function testLanguages() {

View File

@ -5,6 +5,7 @@ namespace Drupal\Tests\search\Functional;
use Drupal\Core\Database\Database;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\search\SearchIndexInterface;
use Drupal\Tests\BrowserTestBase;
/**
@ -128,7 +129,8 @@ class SearchMultilingualEntityTest extends BrowserTestBase {
// Run the shutdown function. Testing is a unique case where indexing
// and searching has to happen in the same request, so running the shutdown
// function manually is needed to finish the indexing process.
search_update_totals();
$search_index = \Drupal::service('search.index');
assert($search_index instanceof SearchIndexInterface);
$this->assertIndexCounts(6, 8, 'after updating partially');
$this->assertDatabaseCounts(2, 0, 'after updating partially');
@ -140,7 +142,6 @@ class SearchMultilingualEntityTest extends BrowserTestBase {
$this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
$this->plugin->updateIndex();
search_update_totals();
$this->assertIndexCounts(0, 8, 'after updating fully');
$this->assertDatabaseCounts(8, 0, 'after updating fully');
@ -150,7 +151,6 @@ class SearchMultilingualEntityTest extends BrowserTestBase {
$this->assertIndexCounts(8, 8, 'after reindex');
$this->assertDatabaseCounts(8, 0, 'after reindex');
$this->plugin->updateIndex();
search_update_totals();
// Test search results.
@ -190,13 +190,12 @@ class SearchMultilingualEntityTest extends BrowserTestBase {
// Mark one of the nodes for reindexing, using the API function, and
// verify indexing status.
search_mark_for_reindex('node_search', $this->searchableNodes[0]->id());
$search_index->markForReindex('node_search', $this->searchableNodes[0]->id());
$this->assertIndexCounts(1, 8, 'after marking one node to reindex via API function');
// Update the index and verify the totals again.
$this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
$this->plugin->updateIndex();
search_update_totals();
$this->assertIndexCounts(0, 8, 'after indexing again');
// Mark one node for reindexing by saving it, and verify indexing status.
@ -227,32 +226,32 @@ class SearchMultilingualEntityTest extends BrowserTestBase {
// Add a bogus entry to the search index table using a different search
// type. This will not appear in the index status, because it is not
// managed by a plugin.
search_index('foo', $this->searchableNodes[0]->id(), 'en', 'some text');
$search_index->index('foo', $this->searchableNodes[0]->id(), 'en', 'some text');
$this->assertIndexCounts(1, 8, 'after adding a different index item');
// Mark just this "foo" index for reindexing.
search_mark_for_reindex('foo');
$search_index->markForReindex('foo');
$this->assertIndexCounts(1, 8, 'after reindexing the other search type');
// Mark everything for reindexing.
search_mark_for_reindex();
$search_index->markForReindex();
$this->assertIndexCounts(8, 8, 'after reindexing everything');
// Clear one item from the index, but with wrong language.
$this->assertDatabaseCounts(8, 1, 'before clear');
search_index_clear('node_search', $this->searchableNodes[0]->id(), 'hu');
$search_index->clear('node_search', $this->searchableNodes[0]->id(), 'hu');
$this->assertDatabaseCounts(8, 1, 'after clear with wrong language');
// Clear using correct language.
search_index_clear('node_search', $this->searchableNodes[0]->id(), 'en');
$search_index->clear('node_search', $this->searchableNodes[0]->id(), 'en');
$this->assertDatabaseCounts(7, 1, 'after clear with right language');
// Don't specify language.
search_index_clear('node_search', $this->searchableNodes[1]->id());
$search_index->clear('node_search', $this->searchableNodes[1]->id());
$this->assertDatabaseCounts(6, 1, 'unspecified language clear');
// Clear everything in 'foo'.
search_index_clear('foo');
$search_index->clear('foo');
$this->assertDatabaseCounts(6, 0, 'other index clear');
// Clear everything.
search_index_clear();
$search_index->clear();
$this->assertDatabaseCounts(0, 0, 'complete clear');
}

View File

@ -45,7 +45,6 @@ class SearchNodeDiacriticsTest extends BrowserTestBase {
// Update the search index.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Refresh variables after the treatment.
$this->refreshVariables();

View File

@ -43,7 +43,6 @@ class SearchNodePunctuationTest extends BrowserTestBase {
// Update the search index.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Refresh variables after the treatment.
$this->refreshVariables();

View File

@ -3,6 +3,7 @@
namespace Drupal\Tests\search\Functional;
use Drupal\Core\Database\Database;
use Drupal\search\SearchIndexInterface;
use Drupal\Tests\BrowserTestBase;
/**
@ -48,7 +49,8 @@ class SearchNodeUpdateAndDeletionTest extends BrowserTestBase {
$node_search_plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
// Update the search index.
$node_search_plugin->updateIndex();
search_update_totals();
$search_index = \Drupal::service('search.index');
assert($search_index instanceof SearchIndexInterface);
// Search the node to verify it appears in search results
$edit = ['keys' => 'knights'];
@ -61,7 +63,6 @@ class SearchNodeUpdateAndDeletionTest extends BrowserTestBase {
// Run indexer again
$node_search_plugin->updateIndex();
search_update_totals();
// Search again to verify the new text appears in test results.
$edit = ['keys' => 'shrubbery'];
@ -83,7 +84,6 @@ class SearchNodeUpdateAndDeletionTest extends BrowserTestBase {
$node_search_plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
// Update the search index.
$node_search_plugin->updateIndex();
search_update_totals();
// Search the node to verify it appears in search results
$edit = ['keys' => 'dragons'];

View File

@ -55,7 +55,6 @@ class SearchPageCacheTagsTest extends BrowserTestBase {
$this->node->setOwner($this->searchingUser);
$this->node->save();
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
}
/**
@ -174,7 +173,6 @@ class SearchPageCacheTagsTest extends BrowserTestBase {
// Refresh the search index.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Log in with searching user again.
$this->drupalLogin($this->searchingUser);

View File

@ -47,11 +47,6 @@ class SearchPreprocessLangcodeTest extends BrowserTestBase {
// First update the index. This does the initial processing.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
// Then, run the shutdown function. Testing is a unique case where indexing
// and searching has to happen in the same request, so running the shutdown
// function manually is needed to finish the indexing process.
search_update_totals();
// Search for the additional text that is added by the preprocess
// function. If you search for text that is in the node, preprocess is
// not invoked on the node during the search excerpt generation.
@ -76,11 +71,6 @@ class SearchPreprocessLangcodeTest extends BrowserTestBase {
// First update the index. This does the initial processing.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
// Then, run the shutdown function. Testing is a unique case where indexing
// and searching has to happen in the same request, so running the shutdown
// function manually is needed to finish the indexing process.
search_update_totals();
// Search for the title of the node with a POST query.
$edit = ['or' => 'testing'];
$this->drupalPostForm('search/node', $edit, 'edit-submit--2');

View File

@ -41,7 +41,6 @@ class SearchQueryAlterTest extends BrowserTestBase {
// Update the search index.
$this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Search for the body keyword 'pizza'.
$this->drupalPostForm('search/node', ['keys' => 'pizza'], t('Search'));

View File

@ -9,6 +9,7 @@ use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\filter\Entity\FilterFormat;
use Drupal\search\Entity\SearchPage;
use Drupal\search\SearchIndexInterface;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\Traits\Core\CronRunTrait;
@ -239,7 +240,8 @@ class SearchRankingTest extends BrowserTestBase {
// Update the search index.
$this->nodeSearch->getPlugin()->updateIndex();
search_update_totals();
$search_index = \Drupal::service('search.index');
assert($search_index instanceof SearchIndexInterface);
$this->nodeSearch->getPlugin()->setSearch('rocks', [], []);
// Do the search and assert the results.
@ -264,7 +266,6 @@ class SearchRankingTest extends BrowserTestBase {
// Update the search index.
$this->nodeSearch->getPlugin()->updateIndex();
search_update_totals();
$this->nodeSearch->getPlugin()->setSearch('rocks', [], []);
// Do the search and assert the results.

View File

@ -34,7 +34,6 @@ class SearchSetLocaleTest extends BrowserTestBase {
$this->drupalCreateNode(['body' => [['value' => 'tapir']]]);
// Update the search index.
$this->nodeSearchPlugin->updateIndex();
search_update_totals();
}
/**

View File

@ -0,0 +1,70 @@
<?php
namespace Drupal\Tests\search\Kernel;
use Drupal\Core\Language\LanguageInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests deprecated search methods.
*
* @group legacy
* @group search
*/
class SearchDeprecationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['search'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('search', [
'search_index',
'search_dataset',
'search_total',
]);
$this->installConfig(['search']);
}
/**
* @expectedDeprecation search_index() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::index() instead. See https://www.drupal.org/node/3075696
*/
public function testIndex() {
$this->assertNull(search_index('_test_', 1, LanguageInterface::LANGCODE_NOT_SPECIFIED, "foo"));
}
/**
* @expectedDeprecation search_index_clear() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::clear() instead. See https://www.drupal.org/node/3075696
*/
public function testClear() {
$this->assertNull(search_index_clear());
}
/**
* @expectedDeprecation search_dirty() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696
*/
public function testDirty() {
$this->assertNull(search_dirty("foo"));
$this->assertEqual([], search_dirty());
}
/**
* @expectedDeprecation search_update_totals() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696
*/
public function testUpdateTotals() {
$this->assertNull(search_update_totals());
}
/**
* @expectedDeprecation search_mark_for_reindex() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::markForReindex() instead. See https://www.drupal.org/node/3075696
*/
public function testMarkForReindex() {
$this->assertNull(search_mark_for_reindex('_test_', 1, LanguageInterface::LANGCODE_NOT_SPECIFIED));
}
}

View File

@ -5,6 +5,7 @@ namespace Drupal\Tests\search\Kernel;
use Drupal\Core\Database\Database;
use Drupal\Core\Language\LanguageInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\search\SearchIndexInterface;
/**
* Indexes content and queries it.
@ -49,11 +50,13 @@ class SearchMatchTest extends KernelTestBase {
public function _setup() {
$this->config('search.settings')->set('index.minimum_word_size', 3)->save();
$search_index = \Drupal::service('search.index');
assert($search_index instanceof SearchIndexInterface);
for ($i = 1; $i <= 7; ++$i) {
search_index(static::SEARCH_TYPE, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText($i));
$search_index->index(static::SEARCH_TYPE, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText($i));
}
for ($i = 1; $i <= 5; ++$i) {
search_index(static::SEARCH_TYPE_2, $i + 7, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText2($i));
$search_index->index(static::SEARCH_TYPE_2, $i + 7, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText2($i));
}
// No getText builder function for Japanese text; just a simple array.
foreach ([
@ -61,9 +64,8 @@ class SearchMatchTest extends KernelTestBase {
14 => 'ドルーパルが大好きよ!',
15 => 'コーヒーとケーキ',
] as $i => $jpn) {
search_index(static::SEARCH_TYPE_JPN, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $jpn);
$search_index->index(static::SEARCH_TYPE_JPN, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $jpn);
}
search_update_totals();
}
/**