diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php index ec1707f..64d0992 100644 --- a/core/modules/comment/comment.api.php +++ b/core/modules/comment/comment.api.php @@ -34,7 +34,7 @@ function hook_comment_presave(Drupal\comment\Comment $comment) { */ function hook_comment_insert(Drupal\comment\Comment $comment) { // Reindex the node when comments are added. - search_touch_node($comment->nid->target_id); + search_mark_for_reindex('node_search', $comment->nid->target_id); } /** @@ -45,7 +45,7 @@ function hook_comment_insert(Drupal\comment\Comment $comment) { */ function hook_comment_update(Drupal\comment\Comment $comment) { // Reindex the node when comments are updated. - search_touch_node($comment->nid->target_id); + search_mark_for_reindex('node_search', $comment->nid->target_id); } /** diff --git a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php index 9e9059a..e9c5ae3 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php @@ -162,7 +162,7 @@ public function execute() { $query->join('node_field_data', 'n', 'n.nid = i.sid'); $query->condition('n.status', 1) ->addTag('node_access') - ->searchExpression($keys, 'node'); + ->searchExpression($keys, $this->getPluginId()); $parameters = $this->getParameters(); // Combine the f query strings into one string to simplify searching. @@ -272,7 +272,7 @@ protected function addNodeRankings(SelectExtender $query) { public function updateIndex() { $limit = (int) $this->searchSettings->get('index.cron_limit'); - $result = $this->database->queryRange("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(), array('target' => 'slave')); + $result = $this->database->queryRange("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = :type AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit, array(':type' => $this->getPluginId()), array('target' => 'slave')); $nids = $result->fetchCol(); if (!$nids) { return; @@ -324,7 +324,7 @@ protected function indexNode(EntityInterface $node) { } // Update index. - $this->moduleHandler->invoke('search', 'index', array($node->id(), 'node', $text, $language->id)); + $this->moduleHandler->invoke('search', 'index', array($node->id(), $this->getPluginId(), $text, $language->id)); } } @@ -334,7 +334,7 @@ protected function indexNode(EntityInterface $node) { public function resetIndex() { $this->database->update('search_dataset') ->fields(array('reindex' => REQUEST_TIME)) - ->condition('type', 'node') + ->condition('type', $this->getPluginId()) ->execute(); } @@ -343,7 +343,7 @@ public function resetIndex() { */ public function indexStatus() { $total = $this->database->query('SELECT COUNT(*) FROM {node}')->fetchField(); - $remaining = $this->database->query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField(); + $remaining = $this->database->query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = :type AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0", array(':type' => $this->getPluginId()))->fetchField(); return array('remaining' => $remaining, 'total' => $total); } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 6dd3787..a4f1a6d 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -2261,3 +2261,65 @@ function node_system_info_alter(&$info, $file, $type) { $info['hidden'] = !module_exists('translation') && config('system.module.disabled')->get('translation') === NULL; } } + +/** + * Mark a node to be re-indexed by the node_search plugin. + * + * @param int $nid + * The Node ID. + */ +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); + } +} + +/** + * Implements hook_node_update(). + */ +function node_node_update(EntityInterface $node) { + // Reindex the node when it is updated. The node is automatically indexed + // when it is added, simply by being added to the node table. + node_reindex_node_search($node->id()); +} + +/** + * Implements hook_comment_insert(). + */ +function node_comment_insert($comment) { + // Reindex the node when comments are added. + node_reindex_node_search($comment->nid->target_id); +} + +/** + * Implements hook_comment_update(). + */ +function node_comment_update($comment) { + // Reindex the node when comments are changed. + node_reindex_node_search($comment->nid->target_id); +} + +/** + * Implements hook_comment_delete(). + */ +function node_comment_delete($comment) { + // Reindex the node when comments are deleted. + node_reindex_node_search($comment->nid->target_id); +} + +/** + * Implements hook_comment_publish(). + */ +function node_comment_publish($comment) { + // Reindex the node when comments are published. + node_reindex_node_search($comment->nid->target_id); +} + +/** + * Implements hook_comment_unpublish(). + */ +function node_comment_unpublish($comment) { + // Reindex the node when comments are unpublished. + node_reindex_node_search($comment->nid->target_id); +} diff --git a/core/modules/search/lib/Drupal/search/SearchExpression.php b/core/modules/search/lib/Drupal/search/SearchExpression.php index cd5e5dc..5662389 100644 --- a/core/modules/search/lib/Drupal/search/SearchExpression.php +++ b/core/modules/search/lib/Drupal/search/SearchExpression.php @@ -39,7 +39,7 @@ public function getExpression() { } /** - * Extracts a module-specific search option from a search expression. + * Extracts a type-specific search option from a search expression. * * Search options are added using SearchExpression::insert() and retrieved * using SearchExpression::extract(). They take the form option:value, and @@ -59,7 +59,7 @@ public function extract($option) { } /** - * Adds a module-specific search option to a search expression. + * Adds a type-specific search option to a search expression. * * Search options are added using SearchExpression::insert() and retrieved * using SearchExpression::extract(). They take the form option:value, and diff --git a/core/modules/search/lib/Drupal/search/SearchPluginManager.php b/core/modules/search/lib/Drupal/search/SearchPluginManager.php index 207023b..c8d5ef3 100644 --- a/core/modules/search/lib/Drupal/search/SearchPluginManager.php +++ b/core/modules/search/lib/Drupal/search/SearchPluginManager.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\Core\Session\AccountInterface; /** * SearchExecute plugin manager. @@ -92,4 +93,27 @@ public function getActiveDefinitions() { } return $active_definitions; } + + /** + * Check whether access is allowed to search results from a given plugin. + * + * @param string $plugin_id + * The id of the plugin being checked. + * @param \Drupal\Core\Session\AccountInterface $account + * The account being checked for access + * + * @return bool + * TRUE if access is allowed, FALSE otherwise. + */ + public function pluginAccess($plugin_id, AccountInterface $account) { + $definition = $this->getDefinition($plugin_id); + if (empty($definition['class'])) { + return FALSE; + } + // Plugins that implement AccessibleInterface can deny access. + if (is_subclass_of($definition['class'], '\Drupal\Core\TypedData\AccessibleInterface')) { + return $this->createInstance($plugin_id)->access('view', $account); + } + return TRUE; + } } diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php index e2d0d9f..d5c339b 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php @@ -194,7 +194,7 @@ function setRolePermissions($rid, $access_comments = FALSE, $search_content = TR */ function assertCommentAccess($assume_access, $message) { // Invoke search index update. - search_touch_node($this->node->id()); + search_mark_for_reindex('node_search', $this->node->id()); $this->cronRun(); // Search for the comment subject. diff --git a/core/modules/search/search.install b/core/modules/search/search.install index 6c3e652..0cb52d3 100644 --- a/core/modules/search/search.install +++ b/core/modules/search/search.install @@ -120,43 +120,6 @@ function search_schema() { 'primary key' => array('word'), ); - $schema['search_node_links'] = array( - 'description' => 'Stores items (like nodes) that link to other nodes, used to improve search scores for nodes that are frequently linked to.', - 'fields' => array( - 'sid' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - 'description' => 'The {search_dataset}.sid of the searchable item containing the link to the node.', - ), - 'type' => array( - 'type' => 'varchar', - 'length' => 16, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The {search_dataset}.type of the searchable item containing the link to the node.', - ), - 'nid' => array( - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - 'description' => 'The {node}.nid that this item links to.', - ), - 'caption' => array( - 'type' => 'text', - 'size' => 'big', - 'not null' => FALSE, - 'description' => 'The text used to link to the {node}.nid.', - ), - ), - 'primary key' => array('sid', 'type', 'nid'), - 'indexes' => array( - 'nid' => array('nid'), - ), - ); - return $schema; } @@ -210,7 +173,8 @@ function search_update_8001() { // need to recreate search data through running cron. db_truncate('search_dataset'); db_truncate('search_index'); - db_truncate('search_node_links'); + // This table is no longer used. + db_drop_table('search_node_links'); // Add the fields and indexes. db_drop_primary_key('search_dataset'); diff --git a/core/modules/search/search.module b/core/modules/search/search.module index f6231ec..0aa10a6 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -256,15 +256,7 @@ function _search_menu_access($plugin_id) { // that the account will be populated, especially during tests. // @see https://drupal.org/node/2032553 $access = !empty($account) && $account->hasPermission('search content'); - if ($access) { - $manager = Drupal::service('plugin.manager.search'); - $definition = $manager->getDefinition($plugin_id); - // Plugins that implement AccessibleInterface can deny access. - if (is_subclass_of($definition['class'], '\Drupal\Core\TypedData\AccessibleInterface')) { - $access = $access && $manager->createInstance($plugin_id)->access('view', $account); - } - } - return $access; + return $access && Drupal::service('plugin.manager.search')->pluginAccess($plugin_id, $account); } /** @@ -272,10 +264,10 @@ function _search_menu_access($plugin_id) { * * @param $sid * (optional) The ID of the item to remove from the search index. If - * specified, $plugin_id must also be given. Omit both $sid and $plugin_id to clear + * specified, $type must also be given. Omit both $sid and $type to clear * the entire search index. * @param $type - * (optional) The entity type of other machine-readable type for the item to + * (optional) The plugin ID other machine-readable type for the item to * remove from the search index. * @param $reindex * (optional) Boolean to specify whether reindexing happens. @@ -305,14 +297,6 @@ function search_reindex($sid = NULL, $type = NULL, $reindex = FALSE, $langcode = $query->condition('langcode', $langcode); } $query->execute(); - - // Don't remove links if re-indexing. - if (!$reindex) { - db_delete('search_node_links') - ->condition('sid', $sid) - ->condition('type', $type) - ->execute(); - } } } @@ -529,7 +513,7 @@ function search_invoke_preprocess(&$text, $langcode = NULL) { * @param $sid * An ID number identifying this particular item (e.g., node ID). * @param $type - * The entity type or other machine-readable type of this item. + * The plugin ID or other machine-readable type of this item. * @param $text * The content of this item. Must be a piece of HTML or plain text. * @param $langcode @@ -540,17 +524,13 @@ function search_invoke_preprocess(&$text, $langcode = NULL) { function search_index($sid, $type, $text, $langcode) { $minimum_word_size = config('search.settings')->get('index.minimum_word_size'); - // Link matching - global $base_url; - $node_regexp = '@href=[\'"]?(?:' . preg_quote($base_url, '@') . '/|' . preg_quote(base_path(), '@') . ')(?:\?q=)?/?((?![a-z]+:)[^\'">]+)[\'">]@i'; - - // Multipliers for scores of words inside certain HTML tags. The weights are stored - // in a variable so that modules can overwrite the default weights. + // 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 = config('search.settings')->get('index.tag_weights'); - // Strip off all ignored tags to speed up processing, but insert space before/after - // them to keep word boundaries. + // Strip off all ignored tags to speed up processing, but insert space before + // and after them to keep word boundaries. $text = str_replace(array('<', '>'), array(' <', '> '), $text); $text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>'); @@ -560,14 +540,13 @@ function search_index($sid, $type, $text, $langcode) { // and begins and ends with a literal (inserting $null as required). $tag = FALSE; // Odd/even counter. Tag or no tag. - $link = FALSE; // State variable for link analyzer $score = 1; // Starting score per word $accum = ' '; // Accumulator for cleaned up data $tagstack = array(); // Stack with open tags $tagwords = 0; // Counter for consecutive words $focus = 1; // Focus state - $results = array(0 => array()); // Accumulator for words for index + $scored_words = array(); // Accumulator for words for index foreach ($split as $value) { if ($tag) { @@ -586,9 +565,6 @@ function search_index($sid, $type, $text, $langcode) { // Remove from tag stack and decrement score $score = max(1, $score - $tags[array_shift($tagstack)]); } - if ($tagname == 'a') { - $link = FALSE; - } } else { if (isset($tagstack[0]) && $tagstack[0] == $tagname) { @@ -602,20 +578,6 @@ function search_index($sid, $type, $text, $langcode) { array_unshift($tagstack, $tagname); $score += $tags[$tagname]; } - if ($tagname == 'a') { - // Check if link points to a node on this site - if (preg_match($node_regexp, $value, $match)) { - $path = Drupal::service('path.alias_manager')->getSystemPath($match[1]); - if (preg_match('!(?:node|book)/(?:view/)?([0-9]+)!i', $path, $match)) { - $linknid = $match[1]; - if ($linknid > 0) { - $link = TRUE; - $node = node_load($linknid); - $linktitle = $node->label(); - } - } - } - } } // A tag change occurred, reset counter. $tagwords = 0; @@ -623,36 +585,19 @@ function search_index($sid, $type, $text, $langcode) { else { // Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty values if ($value != '') { - if ($link) { - // Check to see if the node link text is its URL. If so, we use the target node title instead. - if (preg_match('!^https?://!i', $value)) { - $value = $linktitle; - } - } $words = search_index_split($value, $langcode); foreach ($words as $word) { // Add word to accumulator $accum .= $word . ' '; // Check wordlength if (is_numeric($word) || drupal_strlen($word) >= $minimum_word_size) { - // Links score mainly for the target. - if ($link) { - if (!isset($results[$linknid])) { - $results[$linknid] = array(); - } - $results[$linknid][] = $word; - // Reduce score of the link caption in the source. - $focus *= 0.2; + if (!isset($scored_words[$word])) { + $scored_words[$word] = 0; } - // Fall-through - if (!isset($results[0][$word])) { - $results[0][$word] = 0; - } - $results[0][$word] += $score * $focus; - + $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($results[0]) * .015)); + $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. @@ -680,7 +625,7 @@ function search_index($sid, $type, $text, $langcode) { ->execute(); // Insert results into search index - foreach ($results[0] as $word => $score) { + 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. @@ -696,139 +641,25 @@ function search_index($sid, $type, $text, $langcode) { ->execute(); search_dirty($word); } - unset($results[0]); - - // Get all previous links from this item. - $result = db_query("SELECT nid, caption FROM {search_node_links} WHERE sid = :sid AND type = :type", array( - ':sid' => $sid, - ':type' => $type, - ), array('target' => 'slave')); - $links = array(); - foreach ($result as $link) { - $links[$link->nid] = $link->caption; - } - - // Now store links to nodes. - foreach ($results as $nid => $words) { - $caption = implode(' ', $words); - if (isset($links[$nid])) { - if ($links[$nid] != $caption) { - // Update the existing link and mark the node for reindexing. - db_update('search_node_links') - ->fields(array('caption' => $caption)) - ->condition('sid', $sid) - ->condition('type', $type) - ->condition('nid', $nid) - ->execute(); - search_touch_node($nid); - } - // Unset the link to mark it as processed. - unset($links[$nid]); - } - elseif ($sid != $nid || $type != 'node') { - // Insert the existing link and mark the node for reindexing, but don't - // reindex if this is a link in a node pointing to itself. - db_insert('search_node_links') - ->fields(array( - 'caption' => $caption, - 'sid' => $sid, - 'type' => $type, - 'nid' => $nid, - )) - ->execute(); - search_touch_node($nid); - } - } - // Any left-over links in $links no longer exist. Delete them and mark the nodes for reindexing. - foreach ($links as $nid => $caption) { - db_delete('search_node_links') - ->condition('sid', $sid) - ->condition('type', $type) - ->condition('nid', $nid) - ->execute(); - search_touch_node($nid); - } } /** - * Changes a node's changed timestamp to 'now' to force reindexing. + * Changes the timestamp on an indexed item to 'now' to force reindexing. * - * @param $nid - * The node ID of the node that needs reindexing. + * @param $type + * The plugin ID or other machine-readable type of this item. + * * @param $sid + * An ID number identifying this particular item (e.g., node ID). */ -function search_touch_node($nid) { +function search_mark_for_reindex($type, $sid) { db_update('search_dataset') ->fields(array('reindex' => REQUEST_TIME)) - ->condition('type', 'node') - ->condition('sid', $nid) + ->condition('type', $type) + ->condition('sid', $sid) ->execute(); } /** - * Implements hook_node_update_index(). - */ -function search_node_update_index(EntityInterface $node) { - // Transplant links to a node into the target node. - $result = db_query("SELECT caption FROM {search_node_links} WHERE nid = :nid", array(':nid' => $node->id()), array('target' => 'slave')); - $output = array(); - foreach ($result as $link) { - $output[] = $link->caption; - } - if (count($output)) { - return '(' . implode(', ', $output) . ')'; - } -} - -/** - * Implements hook_node_update(). - */ -function search_node_update(EntityInterface $node) { - // Reindex the node when it is updated. The node is automatically indexed - // when it is added, simply by being added to the node table. - search_touch_node($node->id()); -} - -/** - * Implements hook_comment_insert(). - */ -function search_comment_insert($comment) { - // Reindex the node when comments are added. - search_touch_node($comment->nid->target_id); -} - -/** - * Implements hook_comment_update(). - */ -function search_comment_update($comment) { - // Reindex the node when comments are changed. - search_touch_node($comment->nid->target_id); -} - -/** - * Implements hook_comment_delete(). - */ -function search_comment_delete($comment) { - // Reindex the node when comments are deleted. - search_touch_node($comment->nid->target_id); -} - -/** - * Implements hook_comment_publish(). - */ -function search_comment_publish($comment) { - // Reindex the node when comments are published. - search_touch_node($comment->nid->target_id); -} - -/** - * Implements hook_comment_unpublish(). - */ -function search_comment_unpublish($comment) { - // Reindex the node when comments are unpublished. - search_touch_node($comment->nid->target_id); -} - -/** * Extracts a type-specific search option from a search expression. * * Search options are added using search_expression_insert(), and retrieved