diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 39faf90..56c1e74 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -1357,7 +1357,7 @@ function comment_node_predelete(Node $node) { /** * Implements hook_node_update_index(). */ -function comment_node_update_index(Node $node) { +function comment_node_update_index(Node $node, $langcode) { $index_comments = &drupal_static(__FUNCTION__); if ($index_comments === NULL) { @@ -1385,7 +1385,7 @@ function comment_node_update_index(Node $node) { if ($node->comment && $cids = comment_get_thread($node, $mode, $comments_per_page)) { $comments = comment_load_multiple($cids); comment_prepare_thread($comments); - $build = comment_view_multiple($comments, $node); + $build = comment_view_multiple($comments, $node, $langcode); return drupal_render($build); } } diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index f4fb5f5..4ac7f0e 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -658,6 +658,8 @@ function hook_node_prepare(Drupal\node\Node $node) { * * @param Drupal\node\Node $node * The node being displayed in a search result. + * @param $langcode + * Language code of result being displayed. * * @return array * Extra information to be displayed with search result. This information @@ -670,7 +672,7 @@ function hook_node_prepare(Drupal\node\Node $node) { * * @ingroup node_api_hooks */ -function hook_node_search_result(Drupal\node\Node $node) { +function hook_node_search_result(Drupal\node\Node $node, $langcode) { $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid))->fetchField(); return array('comment' => format_plural($comments, '1 comment', '@count comments')); } @@ -722,13 +724,15 @@ function hook_node_update(Drupal\node\Node $node) { * * @param Drupal\node\Node $node * The node being indexed. + * @param $langcode + * Language code of the variant of the node being indexed. * * @return string * Additional node information to be indexed. * * @ingroup node_api_hooks */ -function hook_node_update_index(Drupal\node\Node $node) { +function hook_node_update_index(Drupal\node\Node $node, $langcode) { $text = ''; $comments = db_query('SELECT subject, comment, format FROM {comment} WHERE nid = :nid AND status = :status', array(':nid' => $node->nid, ':status' => COMMENT_PUBLISHED)); foreach ($comments as $comment) { diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 2c94427..eac89a9 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1620,28 +1620,32 @@ function node_search_execute($keys = NULL, $conditions = NULL) { // Load results. $find = $query + // Add the language code of the indexed item to the result of the query, + // since the node will be rendered using the respective language. + ->fields('i', array('langcode')) ->limit(10) ->execute(); $results = array(); foreach ($find as $item) { // Render the node. $node = node_load($item->sid); - $build = node_view($node, 'search_result'); + $build = node_view($node, 'search_result', $item->langcode); unset($build['#theme']); $node->rendered = drupal_render($build); // Fetch comments for snippet. - $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node); + $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node, $item->langcode); - $extra = module_invoke_all('node_search_result', $node); + $extra = module_invoke_all('node_search_result', $node, $item->langcode); + $language = language_load($item->langcode); $uri = entity_uri('node', $node); $results[] = array( - 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))), + 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), 'type' => check_plain(node_type_get_name($node)), - 'title' => $node->label(), + 'title' => $node->label($item->langcode), 'user' => theme('username', array('account' => $node)), - 'date' => $node->changed, + 'date' => $node->get('changed', $item->langcode), 'node' => $node, 'extra' => $extra, 'score' => $item->calculated_score, @@ -2654,7 +2658,15 @@ function node_update_index() { return; } + // The indexing throttle should be aware of the number of language variants + // of a node. + $counter = 0; foreach (node_load_multiple($nids) as $node) { + // Determine when the maximum number of indexable items is reached. + $counter += 1 + count($node->translations()); + if ($counter > $limit) { + break; + } _node_index_node($node); } } @@ -2671,21 +2683,26 @@ function _node_index_node(Node $node) { // results half-life calculation. variable_set('node_cron_last', $node->changed); - // Render the node. - $build = node_view($node, 'search_index'); - unset($build['#theme']); - $node->rendered = drupal_render($build); + $languages = array_merge(array(language_load($node->langcode)), $node->translations()); - $text = '

' . check_plain($node->label()) . '

' . $node->rendered; + foreach ($languages as $language) { + // Render the node. + $build = node_view($node, 'search_index', $language->langcode); - // Fetch extra data normally not visible - $extra = module_invoke_all('node_update_index', $node); - foreach ($extra as $t) { - $text .= $t; - } + unset($build['#theme']); + $node->rendered = drupal_render($build); + + $text = '

' . check_plain($node->label($language->langcode)) . '

' . $node->rendered; - // Update index - search_index($node->nid, 'node', $text); + // Fetch extra data normally not visible. + $extra = module_invoke_all('node_update_index', $node, $language->langcode); + foreach ($extra as $t) { + $text .= $t; + } + + // Update index. + search_index($node->nid, 'node', $text, $language->langcode); + } } /** diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchMatchTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchMatchTest.php index 1253cb3..118cbf7 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchMatchTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchMatchTest.php @@ -37,10 +37,10 @@ class SearchMatchTest extends SearchTestBase { config('search.settings')->set('index.minimum_word_size', 3)->save(); for ($i = 1; $i <= 7; ++$i) { - search_index($i, SEARCH_TYPE, $this->getText($i)); + search_index($i, SEARCH_TYPE, $this->getText($i), LANGUAGE_NOT_SPECIFIED); } for ($i = 1; $i <= 5; ++$i) { - search_index($i + 7, SEARCH_TYPE_2, $this->getText2($i)); + search_index($i + 7, SEARCH_TYPE_2, $this->getText2($i), LANGUAGE_NOT_SPECIFIED); } // No getText builder function for Japanese text; just a simple array. foreach (array( @@ -48,7 +48,7 @@ class SearchMatchTest extends SearchTestBase { 14 => 'ドルーパルが大好きよ!', 15 => 'コーヒーとケーキ', ) as $i => $jpn) { - search_index($i, SEARCH_TYPE_JPN, $jpn); + search_index($i, SEARCH_TYPE_JPN, $jpn, LANGUAGE_NOT_SPECIFIED); } search_update_totals(); } diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php new file mode 100644 index 0000000..b3b555b --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php @@ -0,0 +1,125 @@ + 'Multilingual entities', + 'description' => 'Tests entities with multilingual fields.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(); + + // Add two new languages. + $language = (object) array( + 'langcode' => 'hu', + 'name' => 'Hungarian', + ); + language_save($language); + $language = (object) array( + 'langcode' => 'sv', + 'name' => 'Swedish', + ); + language_save($language); + + // Make the body field translatable. + // The parent class has already created the article and page content types. + $field = field_info_field('body'); + $field['translatable'] = TRUE; + field_update_field($field); + + // Create a few page nodes with multilingual body values. + $default_format = filter_default_format(); + $nodes = array( + array( + 'type' => 'page', + 'body' => array( + 'en' => array(array('value' => $this->randomName(32), 'format' => $default_format)), + ), + 'langcode' => 'en', + ), + array( + 'type' => 'page', + 'body' => array( + 'en' => array(array('value' => $this->randomName(32), 'format' => $default_format)), + 'hu' => array(array('value' => $this->randomName(32), 'format' => $default_format)), + ), + 'langcode' => 'en', + ), + array( + 'type' => 'page', + 'body' => array( + 'en' => array(array('value' => $this->randomName(32), 'format' => $default_format)), + 'hu' => array(array('value' => $this->randomName(32), 'format' => $default_format)), + 'sv' => array(array('value' => $this->randomName(32), 'format' => $default_format)), + ), + 'langcode' => 'en', + ), + ); + $this->searchable_nodes = array(); + foreach ($nodes as $node) { + $this->searchable_nodes[] = $this->drupalCreateNode($node); + } + } + + /** + * Tests for indexing throttle with nodes in multiple languages. + */ + function testIndexingThrottle() { + // Index only 4 items per cron run. + config('search.settings')->set('index.cron_limit', 4)->save(); + // Update the index. This does the initial processing. + node_update_index(); + // 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(); + // Then check how many nodes have been indexed. We have created three nodes, + // the first has one, the second has two and the third has three language + // variants. Indexing the third would exceed the throttle limit, so we + // expect that only the first two will be indexed. + $status = module_invoke('node', 'search_status'); + $this->assertEqual($status['remaining'], 1, 'Remaining items after updating the search index is 1.'); + } + + /** + * Tests searching nodes with multiple languages. + */ + function testSearchingMultilingualFieldValues() { + // Update the index and then run the shutdown method. + // See testIndexingThrottle() for further explanation. + node_update_index(); + search_update_totals(); + foreach ($this->searchable_nodes as $node) { + // Each searchable node that we created contains values in the body field + // in one or more languages. Let's pick the last language variant from the + // body array and execute a search using that as a search keyword. + $body_language_variant = end($node->body); + $search_result = node_search_execute($body_language_variant[0]['value']); + // See whether we get the same node as a result. + $this->assertEqual($search_result[0]['node']->nid, $node->nid, 'The search has resulted the correct node.'); + } + } +} diff --git a/core/modules/search/search.api.php b/core/modules/search/search.api.php index 01b3d21..40ac0d5 100644 --- a/core/modules/search/search.api.php +++ b/core/modules/search/search.api.php @@ -206,7 +206,7 @@ function hook_search_execute($keys = NULL, $conditions = NULL) { // Insert special keywords. $query->setOption('type', 'n.type'); - $query->setOption('language', 'n.language'); + $query->setOption('langcode', 'n.langcode'); if ($query->setOption('term', 'ti.tid')) { $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); } @@ -224,28 +224,30 @@ function hook_search_execute($keys = NULL, $conditions = NULL) { ->execute(); $results = array(); foreach ($find as $item) { - // Build the node body. + // Render the node. $node = node_load($item->sid); - node_build_content($node, 'search_result'); - $node->body = drupal_render($node->content); + $build = node_view($node, 'search_result', $item->langcode); + unset($build['#theme']); + $node->rendered = drupal_render($build); // Fetch comments for snippet. - $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node); - // Fetch terms for snippet. - $node->rendered .= ' ' . module_invoke('taxonomy', 'node_update_index', $node); + $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node, $item->langcode); - $extra = module_invoke_all('node_search_result', $node); + $extra = module_invoke_all('node_search_result', $node, $item->langcode); + $language = language_load($item->langcode); + $uri = entity_uri('node', $node); $results[] = array( - 'link' => url('node/' . $item->sid, array('absolute' => TRUE)), + 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), 'type' => check_plain(node_type_get_name($node)), - 'title' => $node->label(), + 'title' => $node->label($item->langcode), 'user' => theme('username', array('account' => $node)), - 'date' => $node->changed, + 'date' => $node->get('changed', $item->langcode), 'node' => $node, 'extra' => $extra, 'score' => $item->calculated_score, - 'snippet' => search_excerpt($keys, $node->body), + 'snippet' => search_excerpt($keys, $node->rendered), + 'langcode' => $node->langcode, ); } return $results; diff --git a/core/modules/search/search.install b/core/modules/search/search.install index a561509..cc17523 100644 --- a/core/modules/search/search.install +++ b/core/modules/search/search.install @@ -19,6 +19,12 @@ function search_schema() { 'default' => 0, 'description' => 'Search item ID, e.g. node ID for nodes.', ), + 'langcode' => array( + 'type' => 'varchar', + 'length' => '12', + 'not null' => TRUE, + 'description' => 'The {languages}.langcode of the item variant.', + ), 'type' => array( 'type' => 'varchar', 'length' => 16, @@ -39,7 +45,7 @@ function search_schema() { 'description' => 'Set to force node reindexing.', ), ), - 'primary key' => array('sid', 'type'), + 'primary key' => array('sid', 'langcode', 'type'), ); $schema['search_index'] = array( @@ -59,6 +65,12 @@ function search_schema() { 'default' => 0, 'description' => 'The {search_dataset}.sid of the searchable item to which the word belongs.', ), + 'langcode' => array( + 'type' => 'varchar', + 'length' => '12', + 'not null' => TRUE, + 'description' => 'The {languages}.langcode of the item variant.', + ), 'type' => array( 'type' => 'varchar', 'length' => 16, @@ -72,7 +84,7 @@ function search_schema() { ), ), 'indexes' => array( - 'sid_type' => array('sid', 'type'), + 'sid_type' => array('sid', 'langcode', 'type'), ), 'foreign keys' => array( 'search_dataset' => array( @@ -83,7 +95,7 @@ function search_schema() { ), ), ), - 'primary key' => array('word', 'sid', 'type'), + 'primary key' => array('word', 'sid', 'langcode', 'type'), ); $schema['search_total'] = array( diff --git a/core/modules/search/search.module b/core/modules/search/search.module index e4f162c..7613465 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -315,20 +315,33 @@ function _search_menu_access($name) { * @param $module * (optional) The machine-readable name of the module for the item to remove * from the search index. + * @param $reindex + * (optional) Boolean to specify whether reindexing happens. + * @param $langcode + * (optional) Language code for the operation. If not provided, all + * index records for the $sid will be deleted. */ -function search_reindex($sid = NULL, $module = NULL, $reindex = FALSE) { +function search_reindex($sid = NULL, $module = NULL, $reindex = FALSE, $langcode = NULL) { if ($module == NULL && $sid == NULL) { module_invoke_all('search_reset'); } else { - db_delete('search_dataset') + $query = db_delete('search_dataset') ->condition('sid', $sid) - ->condition('type', $module) - ->execute(); - db_delete('search_index') + ->condition('type', $module); + if (!empty($langcode)) { + $query->condition('langcode', $langcode); + } + $query->execute(); + + $query = db_delete('search_index') ->condition('sid', $sid) - ->condition('type', $module) - ->execute(); + ->condition('type', $module); + if (!empty($langcode)) { + $query->condition('langcode', $langcode); + } + $query->execute(); + // Don't remove links if re-indexing. if (!$reindex) { db_delete('search_node_links') @@ -557,10 +570,12 @@ function search_invoke_preprocess(&$text) { * that implements hook_search_info()). * @param $text * The content of this item. Must be a piece of HTML or plain text. + * @param $langcode + * Language code for text being indexed. * * @ingroup search */ -function search_index($sid, $module, $text) { +function search_index($sid, $module, $text, $langcode) { $minimum_word_size = config('search.settings')->get('index.minimum_word_size'); // Link matching @@ -690,12 +705,13 @@ function search_index($sid, $module, $text) { $tag = !$tag; } - search_reindex($sid, $module, TRUE); + search_reindex($sid, $module, TRUE, $langcode); // Insert cleaned up data into dataset db_insert('search_dataset') ->fields(array( 'sid' => $sid, + 'langcode' => $langcode, 'type' => $module, 'data' => $accum, 'reindex' => 0, @@ -711,6 +727,7 @@ function search_index($sid, $module, $text) { ->key(array( 'word' => $word, 'sid' => $sid, + 'langcode' => $langcode, 'type' => $module, )) ->fields(array('score' => $score))