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..fb68949
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearchExecute.php
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Plugin\Search\NodeSearchExecute.
+ */
+
+namespace Drupal\node\Plugin\Search;
+
+use Drupal\Component\Plugin\ContextAwarePluginBase;
+use Drupal\Core\Database\Query\SelectExtender;
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\search\SearchExecuteInterface;
+use Drupal\search\Annotation\SearchExecutePlugin;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Executes a keyword search aginst the search index.
+ *
+ * @SearchExecutePlugin(
+ *   id = "node_search_execute",
+ *   title = "Content",
+ *   path = "node",
+ *   module = "node",
+ *   context = {
+ *     "plugin.manager.entity" = {
+ *       "class" = "\Drupal\Core\Entity\EntityManager"
+ *     },
+ *     "database" = {
+ *       "class" = "\Drupal\Core\Database\Connection"
+ *     },
+ *     "module_handler" = {
+ *       "class" = "\Drupal\Core\Extension\ModuleHandlerInterface"
+ *     }
+ *   }
+ * )
+ */
+class NodeSearchExecute extends ContextAwarePluginBase implements SearchExecuteInterface {
+
+  static public function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    if (empty($configuration['context'])) {
+      $configuration['context'] = array();
+    }
+    if (empty($configuration['context']['plugin.manager.entity'])) {
+      $configuration['context']['plugin.manager.entity'] = $container->get('plugin.manager.entity');
+    }
+    if (empty($configuration['context']['database'])) {
+      $configuration['context']['database'] = $container->get('database');
+    }
+    if (empty($configuration['context']['module_handler'])) {
+      $configuration['context']['module_handler'] = $container->get('module_handler');
+    }
+    return new static($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSearchExecutable() {
+    return !empty($this->configuration['keywords']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $results = array();
+    if (!$this->isSearchExecutable()) {
+      return $results;
+    }
+    $keys = $this->configuration['keywords'];
+    // Build matching conditions
+    $query = $this->getContextValue('database')
+      ->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.
+    $this->addNodeRankings($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();
+
+    $entity_manger = $this->getContextValue('plugin.manager.entity');
+    $node_storage = $entity_manger->getStorageController('node');
+    $node_render = $entity_manger->getRenderController('node');
+    $module_handler = $this->getContextValue('module_handler');
+
+    foreach ($find as $item) {
+      // Render the node.
+      $entities = $node_storage->load(array($item->sid));
+      $node = $entities[$item->sid];
+      $build = $node_render->view($node, 'search_result', $item->langcode);
+      unset($build['#theme']);
+      $node->rendered = drupal_render($build);
+
+      // Fetch comments for snippet.
+      $node->rendered .= ' ' . $module_handler->invoke('comment', 'node_update_index', array($node, $item->langcode));
+
+      $extra = $module_handler->invokeAll('node_search_result', array($node, $item->langcode));
+
+      $language = $module_handler->invoke('language', 'load', array($item->langcode));
+      $uri = $node->uri();
+      $results[] = array(
+        'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))),
+        'type' => check_plain($module_handler->invoke('node', 'get_type_label', array($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;
+  }
+
+  /**
+   * Gathers the rankings from the the hook_ranking() implementations.
+   *
+   * @param $query
+   *   A query object that has been extended with the Search DB Extender.
+   */
+  protected function addNodeRankings(SelectExtender $query) {
+    if ($ranking = $this->getContextValue('module_handler')->invokeAll('ranking')) {
+      $tables = &$query->getTables();
+      foreach ($ranking as $rank => $values) {
+        if ($node_rank = variable_get('node_rank_' . $rank, 0)) {
+          // If the table defined in the ranking isn't already joined, then add it.
+          if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
+            $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
+          }
+          $arguments = isset($values['arguments']) ? $values['arguments'] : array();
+          $query->addScore($values['score'], $arguments, $node_rank);
+        }
+      }
+    }
+  }
+
+}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index d41d960..7ae0edb 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1236,38 +1236,6 @@ function node_permission() {
 }
 
 /**
- * Gathers the rankings from the the hook_ranking() implementations.
- *
- * @param $query
- *   A query object that has been extended with the Search DB Extender.
- */
-function _node_rankings(SelectExtender $query) {
-  if ($ranking = module_invoke_all('ranking')) {
-    $tables = &$query->getTables();
-    foreach ($ranking as $rank => $values) {
-      if ($node_rank = variable_get('node_rank_' . $rank, 0)) {
-        // If the table defined in the ranking isn't already joined, then add it.
-        if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
-          $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
-        }
-        $arguments = isset($values['arguments']) ? $values['arguments'] : array();
-        $query->addScore($values['score'], $arguments, $node_rank);
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_search_info().
- */
-function node_search_info() {
-  return array(
-    'title' => 'Content',
-    'path' => 'node',
-  );
-}
-
-/**
  * Implements hook_search_access().
  */
 function node_search_access() {
@@ -1321,72 +1289,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 +2146,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/SearchExecutePlugin.php b/core/modules/search/lib/Drupal/search/Annotation/SearchExecutePlugin.php
new file mode 100644
index 0000000..cbad1c7
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Annotation/SearchExecutePlugin.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\Annotation\SearchExecutePlugin.
+ */
+
+namespace Drupal\search\Annotation;
+
+/**
+ * Defines an SearchPagePlugin type annotation object.
+ *
+ * @Annotation
+ */
+class SearchExecutePlugin extends \Drupal\Component\Annotation\Plugin {
+
+  /**
+   * The ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The name of the module providing the plugin.
+   *
+   * @var string
+   */
+  public $module;
+
+  /**
+   * The path fragment to be added to search/ for the search page.
+   *
+   * @var string
+   */
+  public $path;
+
+  /**
+   * The title for the search page tab.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var string
+   */
+  public $title;
+}
diff --git a/core/modules/search/lib/Drupal/search/SearchExecuteInterface.php b/core/modules/search/lib/Drupal/search/SearchExecuteInterface.php
new file mode 100644
index 0000000..e6fb558
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/SearchExecuteInterface.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Modules\Search\SearchExecuteInterface.
+ */
+
+namespace Drupal\search;
+
+/**
+ * Defines a common interface for all SearchExecute objects.
+ */
+interface SearchExecuteInterface {
+  /**
+   * 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();
+
+  /**
+   * Execute the search
+   *
+   * @return array $results
+   *   A structured list of search results
+   */
+  public function execute();
+}
diff --git a/core/modules/search/lib/Drupal/search/SearchExecutePluginManager.php b/core/modules/search/lib/Drupal/search/SearchExecutePluginManager.php
new file mode 100644
index 0000000..66b0562
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/SearchExecutePluginManager.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search\SearchExecutePluginManager.
+ */
+
+namespace Drupal\search;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Plugin\Discovery\AlterDecorator;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\CacheDecorator;
+use Drupal\Core\Plugin\Factory\ContainerFactory;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * SearchExecute plugin manager.
+ */
+class SearchExecutePluginManager extends PluginManagerBase {
+
+  /**
+   * Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param ContainerInterface $container A ContainerInterface instance.
+   */
+  public function __construct(\Traversable $namespaces, ContainerInterface $container) {
+    $annotation_namespaces = array('Drupal\search\Annotation' => $namespaces['Drupal\search']);
+    $this->discovery = new AnnotatedClassDiscovery('Search', $namespaces, $annotation_namespaces, 'Drupal\search\Annotation\SearchExecutePlugin');
+    $this->discovery = new AlterDecorator($this->discovery, 'search_info');
+    $this->discovery = new CacheDecorator($this->discovery, 'search');
+
+    // By using ContainerFactory, we call a static create() method on each
+    // plugin.
+    $this->factory = new ContainerFactory($this->discovery);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createInstance($plugin_id, array $configuration = array()) {
+    // Normalize the data
+    $configuration += array(
+      'keywords' => '',
+      'query_parameters' => array(),
+      'request_attributes' => array(),
+    );
+    return $this->factory->createInstance($plugin_id, $configuration);
+  }
+}
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..f17de43
--- /dev/null
+++ b/core/modules/search/search.services.yml
@@ -0,0 +1,4 @@
+services:
+  plugin.manager.search.page:
+    class: Drupal\search\SearchExecutePluginManager
+    arguments: ['@container.namespaces', '@service_container']
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..4eab711
--- /dev/null
+++ b/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraSearchExecute.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\search_extra_type\Plugin\Search\SearchExtraSearchExecute.
+ */
+
+namespace Drupal\search_extra_type\Plugin\Search;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\search\SearchExecuteInterface;
+use Drupal\search\Annotation\SearchExecutePlugin;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Executes a keyword search aginst the search index.
+ *
+ * @SearchExecutePlugin(
+ *   id = "search_extra_type_search_execute",
+ *   title = "Dummy search type",
+ *   path = "dummy_path",
+ *   module = "search_extra_type"
+ * )
+ */
+class SearchExtraSearchExecute extends PluginBase implements SearchExecuteInterface {
+
+  /**
+   * The keywords to search for.
+   *
+   * @var string
+   */
+  protected $keywords;
+  protected $conditions = array();
+
+  /**
+   * Constructs a new SearchExecute object.
+   *
+   * @param string $keywords
+   *   The keywords to search for.
+   *
+   * @param array $query_parameters
+   *   Optional query parameters for the given search. Could be used to refine
+   *   the scope of the search.
+   *
+   * @param array $request_attributes
+   *   All the attributes that belong to executed request
+   *
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+    $this->configuration = $configuration;
+    $this->pluginId = $plugin_id;
+    $this->pluginDefinition = $plugin_definition;
+    if (!empty($this->configuration['query_parameters']['search_conditions'])) {
+      $this->conditions['search_conditions'] = $this->configuration['query_parameters']['search_conditions'];
+    }
+    $this->keywords = (string) $this->configuration['keywords'];
+  }
+
+  static public function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * 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..fa9e9e7
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearchExecute.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Search\UserSearchExecute.
+ */
+
+namespace Drupal\user\Plugin\Search;
+
+use Drupal\Component\Plugin\ContextAwarePluginBase;
+use Drupal\search\SearchExecuteInterface;
+use Drupal\search\Annotation\SearchExecutePlugin;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Executes a keyword search aginst the search index.
+ *
+ * @SearchExecutePlugin(
+ *   id = "user_search_execute",
+ *   title = "Users",
+ *   path = "user",
+ *   module = "user",
+ *   context = {
+ *     "plugin.manager.entity" = {
+ *       "class" = "\Drupal\Core\Entity\EntityManager"
+ *     },
+ *     "database" = {
+ *       "class" = "\Drupal\Core\Database\Connection"
+ *     },
+ *     "module_handler" = {
+ *       "class" = "\Drupal\Core\Extension\ModuleHandlerInterface"
+ *     }
+ *   }
+ * )
+ */
+class UserSearchExecute extends ContextAwarePluginBase implements SearchExecuteInterface {
+
+
+  static public function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    if (empty($configuration['context'])) {
+      $configuration['context'] = array();
+    }
+    if (empty($configuration['context']['plugin.manager.entity'])) {
+      $configuration['context']['plugin.manager.entity'] = $container->get('plugin.manager.entity');
+    }
+    if (empty($configuration['context']['database'])) {
+      $configuration['context']['database'] = $container->get('database');
+    }
+    if (empty($configuration['context']['module_handler'])) {
+      $configuration['context']['module_handler'] = $container->get('module_handler');
+    }
+    return new static($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSearchExecutable() {
+    return !empty($this->configuration['keywords']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $results = array();
+    if (!$this->isSearchExecutable()) {
+      return $results;
+    }
+    $keys = $this->configuration['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) {
