diff --git a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearchExecute.php b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearchExecute.php new file mode 100644 index 0000000..811d89d --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearchExecute.php @@ -0,0 +1,116 @@ +keywords = $keywords; + } + + /** + * {@inheritdoc} + */ + public function isSearchExecutable() { + return (bool) $this->keywords; + } + + /** + * {@inheritdoc} + */ + public function execute() { + $results = array(); + if (!$this->isSearchExecutable()) { + return $results; + } + $keys = $this->keywords; + // Build matching conditions + $query = db_select('search_index', 'i', array('target' => 'slave')) + ->extend('Drupal\search\SearchQuery') + ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); + $query->join('node_field_data', 'n', 'n.nid = i.sid'); + $query + ->condition('n.status', 1) + ->addTag('node_access') + ->searchExpression($keys, 'node'); + + // Insert special keywords. + $query->setOption('type', 'n.type'); + $query->setOption('langcode', 'n.langcode'); + if ($query->setOption('term', 'ti.tid')) { + $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); + } + // Only continue if the first pass query matches. + if (!$query->executeFirstPass()) { + return array(); + } + + // Add the ranking expressions. + _node_rankings($query); + + // 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(); + + foreach ($find as $item) { + // Render the node. + $node = node_load($item->sid); + $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, $item->langcode); + + $extra = module_invoke_all('node_search_result', $node, $item->langcode); + + $language = language_load($item->langcode); + $uri = $node->uri(); + $results[] = array( + 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), + 'type' => check_plain(node_get_type_label($node)), + 'title' => $node->label($item->langcode), + 'user' => theme('username', array('account' => $node)), + 'date' => $node->changed, + 'node' => $node, + 'extra' => $extra, + 'score' => $item->calculated_score, + 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), + 'langcode' => $node->langcode, + ); + } + return $results; + } +} diff --git a/core/modules/node/node.module b/core/modules/node/node.module index d41d960..4f44ea0 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1258,16 +1258,6 @@ function _node_rankings(SelectExtender $query) { } /** - * Implements hook_search_info(). - */ -function node_search_info() { - return array( - 'title' => 'Content', - 'path' => 'node', - ); -} - -/** * Implements hook_search_access(). */ function node_search_access() { @@ -1321,72 +1311,6 @@ function node_search_admin() { } /** - * Implements hook_search_execute(). - */ -function node_search_execute($keys = NULL, $conditions = NULL) { - // Build matching conditions - $query = db_select('search_index', 'i', array('target' => 'slave')) - ->extend('Drupal\search\SearchQuery') - ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); - $query->join('node_field_data', 'n', 'n.nid = i.sid'); - $query - ->condition('n.status', 1) - ->addTag('node_access') - ->searchExpression($keys, 'node'); - - // Insert special keywords. - $query->setOption('type', 'n.type'); - $query->setOption('langcode', 'n.langcode'); - if ($query->setOption('term', 'ti.tid')) { - $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); - } - // Only continue if the first pass query matches. - if (!$query->executeFirstPass()) { - return array(); - } - - // Add the ranking expressions. - _node_rankings($query); - - // 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', $item->langcode); - unset($build['#theme']); - $node->rendered = drupal_render($build); - - // Fetch comments for snippet. - $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node, $item->langcode); - - $extra = module_invoke_all('node_search_result', $node, $item->langcode); - - $language = language_load($item->langcode); - $uri = $node->uri(); - $results[] = array( - 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), - 'type' => check_plain(node_get_type_label($node)), - 'title' => $node->label($item->langcode), - 'user' => theme('username', array('account' => $node)), - 'date' => $node->changed, - 'node' => $node, - 'extra' => $extra, - 'score' => $item->calculated_score, - 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), - 'langcode' => $node->langcode, - ); - } - return $results; -} - -/** * Implements hook_ranking(). */ function node_ranking() { @@ -2244,6 +2168,7 @@ function _node_index_node(EntityInterface $node) { * @see node_search_validate() */ function node_form_search_form_alter(&$form, $form_state) { + if (isset($form['module']) && $form['module']['#value'] == 'node' && user_access('use advanced search')) { // Keyword boxes: $form['advanced'] = array( diff --git a/core/modules/search/lib/Drupal/search/Annotation/SearchPagePlugin.php b/core/modules/search/lib/Drupal/search/Annotation/SearchPagePlugin.php new file mode 100644 index 0000000..f9f35b5 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Annotation/SearchPagePlugin.php @@ -0,0 +1,44 @@ + $namespaces['Drupal\search']); + $this->discovery = new AnnotatedClassDiscovery('Search', $namespaces, $annotation_namespaces, 'Drupal\search\Annotation\SearchPagePlugin'); + $this->discovery = new AlterDecorator($this->discovery, 'search_info'); + $this->discovery = new CacheDecorator($this->discovery, 'search'); + + $this->factory = new DefaultFactory($this->discovery); + } + + /** + * Overrides \Drupal\Component\Plugin\PluginManagerBase::createInstance(). + */ + public function createInstance($plugin_id, array $configuration = array()) { + $plugin_definition = $this->discovery->getDefinition($plugin_id); + $plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition); + + // Normalize the data + $configuration += array( + 'keywords' => '', + 'query_parameters' => array(), + 'request_attributes' => array(), + ); + return new $plugin_class($configuration['keywords'], $configuration['query_parameters'], $configuration['request_attributes']); + } + +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php index e9526d8..6ac55eb 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php @@ -121,7 +121,12 @@ function testSearchingMultilingualFieldValues() { // 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']); + $config = array( + 'keywords' => $body_language_variant[0]['value'], + ); + $plugin = \Drupal::service('plugin.manager.search.page')->createInstance('node_search_execute', $config); + // Do the search and assert the results. + $search_result = $plugin->execute(); // See whether we get the same node as a result. $sts = $this->assertTrue(!empty($search_result[0]['node']->nid) && $search_result[0]['node']->nid == $node->nid, diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php index f6c8646..dfee542 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php @@ -97,9 +97,12 @@ function testRankings() { foreach ($node_ranks as $var) { variable_set('node_rank_' . $var, $var == $node_rank ? 10 : 0); } - + $config = array( + 'keywords' => 'rocks', + ); + $plugin = \Drupal::service('plugin.manager.search.page')->createInstance('node_search_execute', $config); // Do the search and assert the results. - $set = node_search_execute('rocks'); + $set = $plugin->execute(); $this->assertEqual($set[0]['node']->nid, $nodes[$node_rank][1]->nid, 'Search ranking "' . $node_rank . '" order.'); } } diff --git a/core/modules/search/search.module b/core/modules/search/search.module index 9de229b..a4f8f43 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Component\Utility\Unicode; +use Drupal\search\SearchExecuteInterface; /** * Matches all 'N' Unicode character classes (numbers) @@ -151,6 +152,7 @@ function search_menu() { $items['search'] = array( 'title' => 'Search', 'page callback' => 'search_view', + 'page arguments' => array(NULL, '', ''), 'access callback' => 'search_is_active', 'type' => MENU_SUGGESTED_ITEM, 'file' => 'search.pages.inc', @@ -185,20 +187,20 @@ function search_menu() { $items[$path] = array( 'title' => $search_info['title'], 'page callback' => 'search_view', - 'page arguments' => array($module, ''), + 'page arguments' => array($search_info['id'], $search_info['module'], ''), 'access callback' => '_search_menu_access', - 'access arguments' => array($module), + 'access arguments' => array($search_info['module']), 'type' => MENU_LOCAL_TASK, 'file' => 'search.pages.inc', - 'weight' => $module == $default_info['module'] ? -10 : 0, + 'weight' => $search_info['module'] == $default_info['module'] ? -10 : 0, ); $items["$path/%menu_tail"] = array( 'title' => $search_info['title'], 'load arguments' => array('%map', '%index'), 'page callback' => 'search_view', - 'page arguments' => array($module, 2), + 'page arguments' => array($search_info['id'], $search_info['module'], 2), 'access callback' => '_search_menu_access', - 'access arguments' => array($module), + 'access arguments' => array($search_info['module']), // The default local task points to its parent, but this item points to // where it should so it should not be changed. 'type' => MENU_LOCAL_TASK, @@ -231,30 +233,31 @@ function search_is_active() { * have been set to active on the search settings page will be returned. * * @return - * Array of hook_search_info() return values, keyed by module name. The + * Array of plugin.manager.search.page definitions, keyed by module name. The * 'title' and 'path' array elements will be set to defaults for each module * if not supplied by hook_search_info(), and an additional array element of * 'module' will be added (set to the module name). */ function search_get_info($all = FALSE) { - $search_hooks = &drupal_static(__FUNCTION__); - - if (!isset($search_hooks)) { - foreach (module_implements('search_info') as $module) { - $search_hooks[$module] = call_user_func($module . '_search_info'); + $search_info = &drupal_static(__FUNCTION__); + + if (!isset($search_plugins)) { + $search_info = array(); + $search_definitions = Drupal::service('plugin.manager.search.page')->getDefinitions(); + foreach ($search_definitions as $plugin_id => $plugin) { + $module = $plugin['module']; + $search_info[$module] = $plugin; // Use module name as the default value. - $search_hooks[$module] += array('title' => $module, 'path' => $module); - // Include the module name itself in the array. - $search_hooks[$module]['module'] = $module; + $search_info[$module] += array('title' => $module, 'path' => $module, 'module' => $module); } } if ($all) { - return $search_hooks; + return $search_info; } // Return only modules that are set to active in search settings. - return array_intersect_key($search_hooks, array_flip(config('search.settings')->get('active_modules'))); + return array_intersect_key($search_info, array_flip(config('search.settings')->get('active_modules'))); } /** @@ -1070,9 +1073,8 @@ function search_box_form_submit($form, &$form_state) { * Renderable array of search results. No return value if $keys are not * supplied or if the given search module is not active. */ -function search_data($keys, $module, $conditions = NULL) { - if (module_hook($module, 'search_execute')) { - $results = module_invoke($module, 'search_execute', $keys, $conditions); +function search_data(SearchExecuteInterface $plugin, $module) { + $results = $plugin->execute(); if (module_hook($module, 'search_page')) { return module_invoke($module, 'search_page', $results); } @@ -1083,7 +1085,6 @@ function search_data($keys, $module, $conditions = NULL) { '#module' => $module, ); } - } } /** diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc index aed41f6..a3c3656 100644 --- a/core/modules/search/search.pages.inc +++ b/core/modules/search/search.pages.inc @@ -15,7 +15,7 @@ * @param $keys * Keywords to use for the search. */ -function search_view($module = NULL, $keys = '') { +function search_view($plugin_id = NULL, $module = NULL, $keys = '') { $info = FALSE; $keys = trim($keys); // Also try to pull search keywords out of the $_REQUEST variable to @@ -31,7 +31,7 @@ function search_view($module = NULL, $keys = '') { } } - if (empty($info)) { + if (empty($plugin_id) || empty($info)) { // No path or invalid path: find the default module. Note that if there // are no enabled search modules, this function should never be called, // since hook_menu() would not have defined any search paths. @@ -43,7 +43,13 @@ function search_view($module = NULL, $keys = '') { } drupal_goto($path); } - + $request = Drupal::request(); + $config = array( + 'keywords' => $keys, + 'query_parameters' => $request->query->all(), + 'request_attributes' => $request->attributes->all(), + ); + $plugin = Drupal::service('plugin.manager.search.page')->createInstance($plugin_id, $config); // Default results output is an empty string. $results = array('#markup' => ''); // Process the search form. Note that if there is $_POST data, @@ -52,18 +58,13 @@ function search_view($module = NULL, $keys = '') { // form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle. if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') { - $conditions = NULL; - if (isset($info['conditions_callback'])) { - // Build an optional array of more search conditions. - $conditions = call_user_func($info['conditions_callback'], $keys); - } // Only search if there are keywords or non-empty conditions. - if ($keys || !empty($conditions)) { + if ($plugin->isSearchExecutable()) { // Log the search keys. watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); // Collect the search results. - $results = search_data($keys, $info['module'], $conditions); + $results = search_data($plugin, $info['module']); } } // The form may be altered based on whether the search was run. diff --git a/core/modules/search/search.services.yml b/core/modules/search/search.services.yml new file mode 100644 index 0000000..975e8d5 --- /dev/null +++ b/core/modules/search/search.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.search.page: + class: Drupal\search\SearchPagePluginManager + arguments: ['@container.namespaces'] diff --git a/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraSearchExecute.php b/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraSearchExecute.php new file mode 100644 index 0000000..7aca287 --- /dev/null +++ b/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraSearchExecute.php @@ -0,0 +1,87 @@ +keywords = (string) $keywords; + if (!empty($query_parameters['search_conditions'])) { + $this->conditions['search_conditions'] = $query_parameters['search_conditions']; + } + } + + /** + * Verifies if the given parameters are valid enough to execute a search for. + * + * @return boolean + * A true or false depending on the implementation. + */ + public function isSearchExecutable() { + return (bool) ($this->keywords || $this->conditions); + } + + /** + * Execute the search + * + * This is a dummy search, so when search "executes", we just return a dummy + * result containing the keywords and a list of conditions. + * + * @return array $results + * A structured list of search results + */ + public function execute() { + $results = array(); + if (!$this->isSearchExecutable()) { + return $results; + } + return array( + array( + 'link' => url('node'), + 'type' => 'Dummy result type', + 'title' => 'Dummy title', + 'snippet' => "Dummy search snippet to display. Keywords: {$this->keywords}\n\nConditions: " . print_r($this->conditions, TRUE), + ), + ); + } +} diff --git a/core/modules/search/tests/modules/search_extra_type/search_extra_type.module b/core/modules/search/tests/modules/search_extra_type/search_extra_type.module index 1a31d26..52e2af6 100644 --- a/core/modules/search/tests/modules/search_extra_type/search_extra_type.module +++ b/core/modules/search/tests/modules/search_extra_type/search_extra_type.module @@ -6,51 +6,6 @@ */ /** - * Implements hook_search_info(). - */ -function search_extra_type_search_info() { - return array( - 'title' => 'Dummy search type', - 'path' => 'dummy_path', - 'conditions_callback' => 'search_extra_type_conditions', - ); -} - -/** - * Implements callback_search_conditions(). - * - * Tests the conditions callback for hook_search_info(). - */ -function search_extra_type_conditions() { - $conditions = array(); - - if (!empty($_REQUEST['search_conditions'])) { - $conditions['search_conditions'] = $_REQUEST['search_conditions']; - } - return $conditions; -} - -/** - * Implements hook_search_execute(). - * - * This is a dummy search, so when search "executes", we just return a dummy - * result containing the keywords and a list of conditions. - */ -function search_extra_type_search_execute($keys = NULL, $conditions = NULL) { - if (!$keys) { - $keys = ''; - } - return array( - array( - 'link' => url('node'), - 'type' => 'Dummy result type', - 'title' => 'Dummy title', - 'snippet' => "Dummy search snippet to display. Keywords: {$keys}\n\nConditions: " . print_r($conditions, TRUE), - ), - ); -} - -/** * Implements hook_search_page(). * * Adds some text to the search page so we can verify that it runs. diff --git a/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearchExecute.php b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearchExecute.php new file mode 100644 index 0000000..8d66898 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearchExecute.php @@ -0,0 +1,94 @@ +keywords = $keywords; + } + + /** + * {@inheritdoc} + */ + public function isSearchExecutable() { + return (bool) $this->keywords; + } + + /** + * {@inheritdoc} + */ + public function execute() { + $results = array(); + if (!$this->isSearchExecutable()) { + return $results; + } + $keys = $this->keywords; + $find = array(); + // Replace wildcards with MySQL/PostgreSQL wildcards. + $keys = preg_replace('!\*+!', '%', $keys); + $query = db_select('users') + ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); + $query->fields('users', array('uid')); + if (user_access('administer users')) { + // Administrators can also search in the otherwise private email field, and + // they don't need to be restricted to only active users. + $query->fields('users', array('mail')); + $query->condition(db_or()-> + condition('name', '%' . db_like($keys) . '%', 'LIKE')-> + condition('mail', '%' . db_like($keys) . '%', 'LIKE')); + } + else { + // Regular users can only search via usernames, and we do not show them + // blocked accounts. + $query->condition('name', '%' . db_like($keys) . '%', 'LIKE') + ->condition('status', 1); + } + $uids = $query + ->limit(15) + ->execute() + ->fetchCol(); + $accounts = user_load_multiple($uids); + + foreach ($accounts as $account) { + $result = array( + 'title' => user_format_name($account), + 'link' => url('user/' . $account->uid, array('absolute' => TRUE)), + ); + if (user_access('administer users')) { + $result['title'] .= ' (' . $account->mail . ')'; + } + $results[] = $result; + } + + return $results; + } +} \ No newline at end of file diff --git a/core/modules/user/user.module b/core/modules/user/user.module index abfe84b..5ba4401 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -538,15 +538,6 @@ function user_permission() { } /** - * Implements hook_search_info(). - */ -function user_search_info() { - return array( - 'title' => 'Users', - ); -} - -/** * Implements hook_search_access(). */ function user_search_access() { @@ -554,51 +545,6 @@ function user_search_access() { } /** - * Implements hook_search_execute(). - */ -function user_search_execute($keys = NULL, $conditions = NULL) { - $find = array(); - // Replace wildcards with MySQL/PostgreSQL wildcards. - $keys = preg_replace('!\*+!', '%', $keys); - $query = db_select('users') - ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); - $query->fields('users', array('uid')); - if (user_access('administer users')) { - // Administrators can also search in the otherwise private email field, and - // they don't need to be restricted to only active users. - $query->fields('users', array('mail')); - $query->condition(db_or()-> - condition('name', '%' . db_like($keys) . '%', 'LIKE')-> - condition('mail', '%' . db_like($keys) . '%', 'LIKE')); - } - else { - // Regular users can only search via usernames, and we do not show them - // blocked accounts. - $query->condition('name', '%' . db_like($keys) . '%', 'LIKE') - ->condition('status', 1); - } - $uids = $query - ->limit(15) - ->execute() - ->fetchCol(); - $accounts = user_load_multiple($uids); - - $results = array(); - foreach ($accounts as $account) { - $result = array( - 'title' => user_format_name($account), - 'link' => url('user/' . $account->uid, array('absolute' => TRUE)), - ); - if (user_access('administer users')) { - $result['title'] .= ' (' . $account->mail . ')'; - } - $results[] = $result; - } - - return $results; -} - -/** * Implements hook_user_view(). */ function user_user_view(User $account, EntityDisplay $display) {