diff --git a/core/lib/Drupal/Core/Plugin/ConfigurablePluginInterface.php b/core/lib/Drupal/Core/Plugin/ConfigurablePluginInterface.php new file mode 100644 index 0000000..b66d4d3 --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/ConfigurablePluginInterface.php @@ -0,0 +1,56 @@ +manager = $manager; + $this->instanceIDs = drupal_map_assoc($instance_ids); + $this->configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + protected function initializePlugin($instance_id) { + if (!isset($this->pluginInstances[$instance_id])) { + $this->pluginInstances[$instance_id] = $this->manager->createInstance($instance_id, $this->configuration); + } + } + +} diff --git a/core/modules/node/config/search.search.node_search.yml b/core/modules/node/config/search.search.node_search.yml new file mode 100644 index 0000000..7e9ac7e --- /dev/null +++ b/core/modules/node/config/search.search.node_search.yml @@ -0,0 +1,9 @@ +id: node_search +label: 'Node search' +uuid: 25687eeb-4bb5-469c-ad05-5eb24cd7012c +status: '1' +langcode: en +path: node +title: Content +plugin: node_search +configuration: { } diff --git a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php new file mode 100644 index 0000000..04dbdfb --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php @@ -0,0 +1,336 @@ +get('database'), + $container->get('plugin.manager.entity'), + $container->get('module_handler'), + $container->get('config.factory')->get('search.settings'), + $container->get('keyvalue')->get('state') + ); + } + + /** + * Constructs a \Drupal\node\Plugin\Search\NodeSearch object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Database\Connection $database + * A database connection object. + * @param \Drupal\Core\Entity\EntityManager $entity_manager + * An entity manager object. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * A module manager object. + * @param \Drupal\Core\Config\Config $search_settings + * A config object for 'search.settings'. + * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state + * The Drupal state object used to set 'node.cron_last'. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, Connection $database, EntityManager $entity_manager, ModuleHandlerInterface $module_handler, Config $search_settings, KeyValueStoreInterface $state) { + $this->database = $database; + $this->entityManager = $entity_manager; + $this->moduleHandler = $module_handler; + $this->searchSettings = $search_settings; + $this->state = $state; + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public function execute() { + $results = array(); + if (!$this->isSearchExecutable()) { + return $results; + } + $keys = $this->keywords; + // Build matching conditions + $query = $this->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(); + + $node_storage = $this->entityManager->getStorageController('node'); + $node_render = $this->entityManager->getRenderController('node'); + + foreach ($find as $item) { + // Render the node. + $entities = $node_storage->loadMultiple(array($item->sid)); + // Convert to BCEntity to match node_load_multiple(). + // @todo - remove this when code that receives this object is updated. + $node = $entities[$item->sid]->getBCEntity(); + $build = $node_render->view($node, 'search_result', $item->langcode); + unset($build['#theme']); + $node->rendered = drupal_render($build); + + // Fetch comments for snippet. + $node->rendered .= ' ' . $this->moduleHandler->invoke('comment', 'node_update_index', array($node, $item->langcode)); + + $extra = $this->moduleHandler->invokeAll('node_search_result', array($node, $item->langcode)); + + $language = $this->moduleHandler->invoke('language', 'load', array($item->langcode)); + $uri = $node->uri(); + $username = array( + '#theme' => 'username', + '#account' => $this->entityManager->getStorageController('user')->load($node->uid), + ); + $results[] = array( + 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), + 'type' => check_plain($this->entityManager->getStorageController('node_type')->load($node->bundle())->label()), + 'title' => $node->label($item->langcode), + 'user' => drupal_render($username), + 'date' => $node->getChangedTime(), + 'node' => $node, + 'extra' => $extra, + 'score' => $item->calculated_score, + 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), + 'langcode' => $node->language()->id, + ); + } + 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->moduleHandler->invokeAll('ranking')) { + $tables = &$query->getTables(); + foreach ($ranking as $rank => $values) { + // @todo - move rank out of drupal variables. + 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); + } + } + } + } + + /** + * {@inheritdoc} + */ + 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')); + $nids = $result->fetchCol(); + if (!$nids) { + return; + } + + // The indexing throttle should be aware of the number of language variants + // of a node. + $counter = 0; + $node_storage = $this->entityManager->getStorageController('node'); + foreach ($node_storage->loadMultiple($nids) as $entity) { + // Convert to BCEntity to match node_load_multiple(). + // @todo - remove this when hooks passed this object are updated. + $node = $entity->getBCEntity(); + // Determine when the maximum number of indexable items is reached. + $counter += count($node->getTranslationLanguages()); + if ($counter > $limit) { + break; + } + $this->indexNode($node); + } + } + + /** + * Indexes a single node. + * + * @param \Drupal\Core\Entity\EntityInterface $node + * The node to index. + */ + protected function indexNode(EntityInterface $node) { + // Save the changed time of the most recent indexed node, for the search + // results half-life calculation. + $this->state->set('node.cron_last', $node->getChangedTime()); + + $languages = $node->getTranslationLanguages(); + + foreach ($languages as $language) { + // Render the node. + $build = $this->moduleHandler->invoke('node', 'view', array($node, 'search_index', $language->id)); + + unset($build['#theme']); + $node->rendered = drupal_render($build); + + $text = '

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

' . $node->rendered; + + // Fetch extra data normally not visible. + $extra = $this->moduleHandler->invokeAll('node_update_index', array($node, $language->id)); + foreach ($extra as $t) { + $text .= $t; + } + + // Update index. + $this->moduleHandler->invoke('search', 'index', array($node->id(), 'node', $text, $language->id)); + } + } + + /** + * {@inheritdoc} + */ + public function resetIndex() { + $this->database->update('search_dataset') + ->fields(array('reindex' => REQUEST_TIME)) + ->condition('type', 'node') + ->execute(); + } + + /** + * {@inheritdoc} + */ + 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(); + return array('remaining' => $remaining, 'total' => $total); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, array &$form_state) { + // Output form for defining rank factor weights. + $form['content_ranking'] = array( + '#type' => 'details', + '#title' => t('Content ranking'), + ); + $form['content_ranking']['#theme'] = 'node_search_admin'; + $form['content_ranking']['info'] = array( + '#value' => '' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '' + ); + + // Note: reversed to reflect that higher number = higher ranking. + $options = drupal_map_assoc(range(0, 10)); + foreach ($this->moduleHandler->invokeAll('ranking') as $var => $values) { + $form['content_ranking']['factors']['node_rank_' . $var] = array( + '#title' => $values['title'], + '#type' => 'select', + '#options' => $options, + '#default_value' => variable_get('node_rank_' . $var, 0), + ); + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function submit(array &$form, array &$form_state) { + foreach ($this->moduleHandler->invokeAll('ranking') as $var => $values) { + if (isset($form_state['values']['node_rank_' . $var])) { + // @todo Fix when https://drupal.org/node/1831632 is in. + variable_set('node_rank_' . $var, $form_state['values']['node_rank_' . $var]); + } + } + } + +} diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 2166391..ce240bc 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -107,7 +107,7 @@ * - Resulting node is prepared for viewing (see Viewing a single node above) * - comment_node_update_index() is called. * - hook_node_search_result() (all) - * - Search indexing (calling node_update_index()): + * - Search indexing (calling updateIndex() on the 'node_search' plugin): * - Node is loaded (see Loading section above) * - Node is prepared for viewing (see Viewing a single node above) * - hook_node_update_index() (all) diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 5a6d53a..f1a0de7 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -9,12 +9,9 @@ */ use Drupal\Core\Language\Language; -use Drupal\node\NodeInterface; use Symfony\Component\HttpFoundation\Response; - use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Database\Query\AlterableInterface; -use Drupal\Core\Database\Query\SelectExtender; use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\node\NodeTypeInterface; @@ -815,38 +812,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() { @@ -854,122 +819,6 @@ function node_search_access() { } /** - * Implements hook_search_reset(). - */ -function node_search_reset() { - db_update('search_dataset') - ->fields(array('reindex' => REQUEST_TIME)) - ->condition('type', 'node') - ->execute(); -} - -/** - * Implements hook_search_status(). - */ -function node_search_status() { - $total = db_query('SELECT COUNT(*) FROM {node}')->fetchField(); - $remaining = db_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(); - return array('remaining' => $remaining, 'total' => $total); -} - -/** - * Implements hook_search_admin(). - */ -function node_search_admin() { - // Output form for defining rank factor weights. - $form['content_ranking'] = array( - '#type' => 'details', - '#title' => t('Content ranking'), - ); - $form['content_ranking']['#theme'] = 'node_search_admin'; - $form['content_ranking']['info'] = array( - '#value' => '' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '' - ); - - // Note: reversed to reflect that higher number = higher ranking. - $options = drupal_map_assoc(range(0, 10)); - foreach (module_invoke_all('ranking') as $var => $values) { - $form['content_ranking']['factors']['node_rank_' . $var] = array( - '#title' => $values['title'], - '#type' => 'select', - '#options' => $options, - '#default_value' => variable_get('node_rank_' . $var, 0), - ); - } - return $form; -} - -/** - * 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(); - $username = array( - '#theme' => 'username', - '#account' => user_load($node->uid), - ); - $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' => drupal_render($username), - 'date' => $node->getChangedTime(), - 'node' => $node, - 'extra' => $extra, - 'score' => $item->calculated_score, - 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), - 'langcode' => $node->language()->id, - ); - } - return $results; -} - -/** * Implements hook_ranking(). */ function node_ranking() { @@ -1772,7 +1621,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')) { + if (isset($form_state['search_plugin']) && $form_state['search_plugin'] == 'node_search' && user_access('use advanced search')) { // Keyword boxes: $form['advanced'] = array( '#type' => 'details', diff --git a/core/modules/search/config/search.settings.yml b/core/modules/search/config/search.settings.yml index a1a88b4..f6fa8fc 100644 --- a/core/modules/search/config/search.settings.yml +++ b/core/modules/search/config/search.settings.yml @@ -1,8 +1,5 @@ -active_modules: - - node - - user and_or_limit: '7' -default_module: node +default_type: node_search index: cron_limit: '100' overlap_cjk: '1' diff --git a/core/modules/search/lib/Drupal/search/Annotation/SearchPlugin.php b/core/modules/search/lib/Drupal/search/Annotation/SearchPlugin.php new file mode 100644 index 0000000..5dfda5b --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Annotation/SearchPlugin.php @@ -0,0 +1,49 @@ +searchManager = $search_manager; + $this->state = $state; + $this->urlGenerator = $url_generator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.search'), + $container->get('state'), + $container->get('url_generator') + ); + } + + /** + * Provides a list of eligible search plugins for configuring. + * + * @return array + * A list of search plugins that can be configured. + */ + public function addSearchType() { + // @todo Remove this when https://drupal.org/node/2032535 is in. + drupal_set_title(t('Add new search configuration')); + + $search_types = array(); + foreach ($this->searchManager->getDefinitions() as $plugin_id => $search_info) { + $search_types[$plugin_id] = array( + 'title' => $search_info['title'], + 'href' => 'admin/config/search/settings/add/' . $plugin_id, + 'localized_options' => array(), + ); + } + return array( + '#theme' => 'admin_block_content', + '#content' => $search_types, + ); + } + + /** + * Performs an operation on the search entity. + * + * @param \Drupal\search\SearchInterface $search + * The search entity. + * @param string $op + * The operation to perform, usually 'enable' or 'disable'. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * A redirect back to the search settings page. + */ + public function performOperation(SearchInterface $search, $op) { + // Perform the operation. + if (!$search->isDefaultSearch()) { + $search->$op()->save(); + } + else { + debug($search->id()); + } + return new RedirectResponse($this->urlGenerator->generateFromPath('admin/config/search/settings', array('absolute' => TRUE))); + } + +} diff --git a/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php b/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php new file mode 100644 index 0000000..a3afa35 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php @@ -0,0 +1,35 @@ +entity->setPlugin($search_plugin_id); + $definition = $this->entity->getPluginDefinition(); + $this->entity->set('path', $definition['path']); + $this->entity->set('label', $definition['title']); + $this->entity->set('title', $definition['title']); + return parent::buildForm($form, $form_state); + } + +} diff --git a/core/modules/search/lib/Drupal/search/Form/SearchDeleteForm.php b/core/modules/search/lib/Drupal/search/Form/SearchDeleteForm.php new file mode 100644 index 0000000..9ff0c2d --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchDeleteForm.php @@ -0,0 +1,47 @@ + $this->entity->label())); + } + + /** + * {@inheritdoc} + */ + public function getCancelPath() { + return 'admin/config/search/settings'; + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + $this->entity->delete(); + $form_state['redirect'] = 'admin/config/search/settings'; + drupal_set_message(t('The %label search has been deleted.', array('%label' => $this->entity->label()))); + } + +} diff --git a/core/modules/search/lib/Drupal/search/Form/SearchEditForm.php b/core/modules/search/lib/Drupal/search/Form/SearchEditForm.php new file mode 100644 index 0000000..34c8bef --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchEditForm.php @@ -0,0 +1,23 @@ + $this->entity->label()))); + parent::init($form_state); + } + +} diff --git a/core/modules/search/lib/Drupal/search/Form/SearchFormBase.php b/core/modules/search/lib/Drupal/search/Form/SearchFormBase.php new file mode 100644 index 0000000..bd1083d --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchFormBase.php @@ -0,0 +1,201 @@ +entityQuery = $entity_query; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { + return new static( + $container->get('module_handler'), + $container->get('entity.query') + ); + } + + /** + * {@inheritdoc} + */ + public function getBaseFormID() { + return 'search_entity_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state) { + $this->plugin = $this->entity->getPlugin(); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, array &$form_state) { + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#description' => t('The administrative label for this search'), + '#default_value' => $this->entity->label(), + '#maxlength' => '255', + ); + + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $this->entity->id(), + '#disabled' => !$this->entity->isNew(), + '#maxlength' => 64, + '#machine_name' => array( + 'exists' => array($this, 'exists'), + ), + ); + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Menu title'), + '#description' => t('The title used by the local tab on the search page'), + '#default_value' => $this->entity->getTitle(), + '#maxlength' => '255', + ); + $form['path'] = array( + '#type' => 'textfield', + '#title' => t('Path'), + '#field_prefix' => 'search/', + '#default_value' => $this->entity->getPath(), + '#maxlength' => '255', + ); + $form['plugin'] = array( + '#type' => 'value', + '#value' => $this->entity->get('plugin'), + ); + $form['type'] = array( + '#type' => 'value', + '#value' => $this->entity->getType(), + ); + + if ($this->plugin instanceof ConfigurablePluginInterface) { + $form += $this->plugin->form($form, $form_state); + } + + return parent::form($form, $form_state); + } + + /** + * Determines if the search entity already exists. + * + * @param string $id + * The search configuration ID. + * + * @return bool + * TRUE if the search configuration exists, FALSE otherwise. + */ + public function exists($id) { + $entity = $this->entityQuery->get('search') + ->condition('id', $id) + ->execute(); + return (bool) $entity; + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, array &$form_state) { + $actions = parent::actions($form, $form_state); + unset($actions['delete']); + return $actions; + } + + /** + * {@inheritdoc} + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + + // Ensure each path is unique. + $path = $this->entityQuery->get('search') + ->condition('path', $form_state['values']['path']) + ->condition('id', $form_state['values']['id'], '<>') + ->execute(); + if ($path) { + form_set_error('path', t('The search path must be unique')); + } + + if ($this->plugin instanceof ConfigurablePluginInterface) { + $this->plugin->validate($form, $form_state); + } + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + parent::submit($form, $form_state); + + if ($this->plugin instanceof ConfigurablePluginInterface) { + $this->plugin->submit($form, $form_state); + } + return $this->entity; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, array &$form_state) { + $this->entity->save(); + drupal_set_message(t('The search has been successfully saved.')); + + $form_state['redirect'] = 'admin/config/search/settings'; + } + +} diff --git a/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php.rej b/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php.rej new file mode 100644 index 0000000..5a6ade9 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php.rej @@ -0,0 +1,247 @@ +*************** +*** 1,244 **** +- searchSettings = $search_settings; +- $this->moduleHandler = $module_handler; +- $this->state = $state; +- } +- +- /** +- * {@inheritdoc} +- */ +- public static function create(ContainerInterface $container) { +- return new static( +- $container->get('config.factory')->get('search.settings'), +- $container->get('module_handler'), +- $container->get('keyvalue')->get('state') +- ); +- } +- +- /** +- * {@inheritdoc} +- */ +- public function getFormID() { +- return 'search_admin_settings'; +- } +- +- /** +- * Returns names of available search modules. +- * +- * @return array +- * An array of the names of enabled modules that call hook_search_info +- * sorted into alphabetical order. +- */ +- protected function getModuleOptions() { +- $search_info = search_get_info(TRUE); +- $names = system_get_module_info('name'); +- $names = array_intersect_key($names, $search_info); +- asort($names, SORT_STRING); +- return $names; +- } +- +- /** +- * {@inheritdoc} +- */ +- public function buildForm(array $form, array &$form_state) { +- // Collect some stats +- $remaining = 0; +- $total = 0; +- foreach ($this->searchSettings->get('active_modules') as $module) { +- if ($status = $this->moduleHandler->invoke($module, 'search_status')) { +- $remaining += $status['remaining']; +- $total += $status['total']; +- } +- } +- +- $this->moduleHandler->loadAllIncludes('admin.inc'); +- $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.'); +- $percentage = ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) . '%'; +- $status = '

' . t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '

'; +- $form['status'] = array( +- '#type' => 'details', +- '#title' => t('Indexing status'), +- ); +- $form['status']['status'] = array('#markup' => $status); +- $form['status']['wipe'] = array( +- '#type' => 'submit', +- '#value' => t('Re-index site'), +- '#submit' => array(array($this, 'searchAdminReindexSubmit')), +- ); +- +- $items = drupal_map_assoc(array(10, 20, 50, 100, 200, 500)); +- +- // Indexing throttle: +- $form['indexing_throttle'] = array( +- '#type' => 'details', +- '#title' => t('Indexing throttle') +- ); +- $form['indexing_throttle']['cron_limit'] = array( +- '#type' => 'select', +- '#title' => t('Number of items to index per cron run'), +- '#default_value' => $this->searchSettings->get('index.cron_limit'), +- '#options' => $items, +- '#description' => t('The maximum number of items indexed in each pass of a cron maintenance task. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status'))) +- ); +- // Indexing settings: +- $form['indexing_settings'] = array( +- '#type' => 'details', +- '#title' => t('Indexing settings') +- ); +- $form['indexing_settings']['info'] = array( +- '#markup' => t('

Changing the settings below will cause the site index to be rebuilt. The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.

The default settings should be appropriate for the majority of sites.

') +- ); +- $form['indexing_settings']['minimum_word_size'] = array( +- '#type' => 'number', +- '#title' => t('Minimum word length to index'), +- '#default_value' => $this->searchSettings->get('index.minimum_word_size'), +- '#min' => 1, +- '#max' => 1000, +- '#description' => t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).') +- ); +- $form['indexing_settings']['overlap_cjk'] = array( +- '#type' => 'checkbox', +- '#title' => t('Simple CJK handling'), +- '#default_value' => $this->searchSettings->get('index.overlap_cjk'), +- '#description' => t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.') +- ); +- +- $form['active'] = array( +- '#type' => 'details', +- '#title' => t('Active search modules') +- ); +- $module_options = $this->getModuleOptions(); +- $form['active']['active_modules'] = array( +- '#type' => 'checkboxes', +- '#title' => t('Active modules'), +- '#title_display' => 'invisible', +- '#default_value' => $this->searchSettings->get('active_modules'), +- '#options' => $module_options, +- '#description' => t('Choose which search modules are active from the available modules.') +- ); +- $form['active']['default_module'] = array( +- '#title' => t('Default search module'), +- '#type' => 'radios', +- '#default_value' => $this->searchSettings->get('default_module'), +- '#options' => $module_options, +- '#description' => t('Choose which search module is the default.') +- ); +- +- // Per module settings +- foreach ($this->searchSettings->get('active_modules') as $module) { +- $added_form = $this->moduleHandler->invoke($module, 'search_admin'); +- if (is_array($added_form)) { +- $form = NestedArray::mergeDeep($form, $added_form); +- } +- } +- // Set #submit so we are sure it's invoked even if one of +- // the active search modules added its own #submit. +- $form['#submit'][] = array($this, 'submitForm'); +- +- return parent::buildForm($form, $form_state); +- } +- +- /** +- * {@inheritdoc} +- */ +- public function validateForm(array &$form, array &$form_state) { +- parent::validateForm($form, $form_state); +- +- // Check whether we selected a valid default. +- if ($form_state['triggering_element']['#value'] != t('Reset to defaults')) { +- $new_modules = array_filter($form_state['values']['active_modules']); +- $default = $form_state['values']['default_module']; +- if (!in_array($default, $new_modules, TRUE)) { +- form_set_error('default_module', t('Your default search module is not selected as an active module.')); +- } +- } +- } +- +- /** +- * {@inheritdoc} +- */ +- public function submitForm(array &$form, array &$form_state) { +- parent::submitForm($form, $form_state); +- +- // If these settings change, the index needs to be rebuilt. +- if (($this->searchSettings->get('index.minimum_word_size') != $form_state['values']['minimum_word_size']) || ($this->searchSettings->get('index.overlap_cjk') != $form_state['values']['overlap_cjk'])) { +- $this->searchSettings->set('index.minimum_word_size', $form_state['values']['minimum_word_size']); +- $this->searchSettings->set('index.overlap_cjk', $form_state['values']['overlap_cjk']); +- drupal_set_message(t('The index will be rebuilt.')); +- search_reindex(); +- } +- $this->searchSettings->set('index.cron_limit', $form_state['values']['cron_limit']); +- $this->searchSettings->set('default_module', $form_state['values']['default_module']); +- +- // Check whether we are resetting the values. +- if ($form_state['triggering_element']['#value'] == t('Reset to defaults')) { +- $new_modules = array('node', 'user'); +- } +- else { +- $new_modules = array_filter($form_state['values']['active_modules']); +- } +- if ($this->searchSettings->get('active_modules') != $new_modules) { +- $this->searchSettings->set('active_modules', $new_modules); +- drupal_set_message(t('The active search modules have been changed.')); +- $this->state->set('menu_rebuild_needed', TRUE); +- } +- $this->searchSettings->save(); +- } +- +- /** +- * Form submission handler for the reindex button on the search admin settings +- * form. +- */ +- public function searchAdminReindexSubmit(array $form, array &$form_state) { +- // send the user to the confirmation page +- $form_state['redirect'] = 'admin/config/search/settings/reindex'; +- } +- } +--- 0 ---- diff --git a/core/modules/search/lib/Drupal/search/Plugin/ConfigurableSearchPluginBase.php b/core/modules/search/lib/Drupal/search/Plugin/ConfigurableSearchPluginBase.php new file mode 100644 index 0000000..d41836a --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Plugin/ConfigurableSearchPluginBase.php @@ -0,0 +1,48 @@ +configuration += $this->getDefaultConfiguration(); + } + + /** + * Returns default configuration for this search plugin. + * + * @return array + */ + protected function getDefaultConfiguration() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function validate(array &$form, array &$form_state) { + } + +} diff --git a/core/modules/search/lib/Drupal/search/Plugin/Core/Entity/Search.php b/core/modules/search/lib/Drupal/search/Plugin/Core/Entity/Search.php new file mode 100644 index 0000000..bc30708 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Plugin/Core/Entity/Search.php @@ -0,0 +1,229 @@ +pluginBag = new DefaultPluginBag(\Drupal::service('plugin.manager.search'), array($this->plugin), $this->configuration); + } + + /** + * {@inheritdoc} + */ + public function getPlugin() { + return $this->pluginBag->get($this->plugin); + } + + /** + * {@inheritdoc} + */ + public function setPlugin($plugin_id) { + $this->plugin = $plugin_id; + $this->pluginBag->addInstanceID($plugin_id); + } + + /** + * {@inheritdoc} + */ + public function getPluginDefinition() { + return $this->getPlugin()->getPluginDefinition(); + } + + /** + * {@inheritdoc} + */ + public function isConfigurable() { + return $this->getPlugin() instanceof ConfigurablePluginInterface; + } + + /** + * {@inheritdoc} + */ + public function isDefaultSearch() { + return \Drupal::service('config.factory')->get('search.settings')->get('default_type') == $this->id(); + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->title; + } + + /** + * {@inheritdoc} + */ + public function getPath() { + return $this->path; + } + + /** + * {@inheritdoc} + */ + public function getProvider() { + $definition = $this->getPluginDefinition(); + return $definition['provider']; + } + + /** + * {@inheritdoc} + */ + public function uri() { + // @todo replace with a route name and parameters. + return array( + 'path' => 'admin/config/search/settings/manage/' . $this->id(), + 'options' => array( + 'entity_type' => $this->entityType, + 'entity' => $this, + ), + ); + } + + /** + * {@inheritdoc} + */ + public function getExportProperties() { + $properties = parent::getExportProperties(); + $names = array( + 'path', + 'title', + 'plugin', + 'configuration', + ); + foreach ($names as $name) { + $properties[$name] = $this->get($name); + } + return $properties; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + parent::preSave($storage_controller); + + $plugin = $this->getPlugin(); + // If this plugin has any configuration, ensure that it is set. + if ($plugin instanceof ConfigurablePluginInterface) { + $this->set('configuration', $plugin->getConfiguration()); + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + parent::postSave($storage_controller, $update); + + \Drupal::state()->set('menu_rebuild_needed', TRUE); + } + + /** + * {@inheritdoc} + */ + public static function sort($a, $b) { + $a_status = (int) $a->status(); + $b_status = (int) $b->status(); + if ($a_status != $b_status) { + return ($a_status > $b_status) ? -1 : 1; + } + return strnatcasecmp($a->label(), $b->label()); + } + +} diff --git a/core/modules/search/lib/Drupal/search/Plugin/SearchInterface.php b/core/modules/search/lib/Drupal/search/Plugin/SearchInterface.php new file mode 100644 index 0000000..a9e4850 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Plugin/SearchInterface.php @@ -0,0 +1,120 @@ +get('index.cron_limit') (see + * example below). Also, since the cron run could time out and abort in the + * middle of your run, you should update your module's internal bookkeeping on + * when items have last been indexed as you go rather than waiting to the end + * of indexing. + */ + public function updateIndex(); + + /** + * Take action when the search index is going to be rebuilt. + * + * Modules that use updateIndex() should update their indexing + * bookkeeping so that it starts from scratch the next time updateIndex() + * is called. + */ + public function resetIndex(); + + /** + * Report the status of indexing. + * + * The core search module only invokes this method on active module plugins. + * Implementing modules do not need to check whether they are active when + * calculating their return values. + * + * @return + * An associative array with the key-value pairs: + * - remaining: The number of items left to index. + * - total: The total number of items to index. + */ + public function indexStatus(); + +} diff --git a/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php b/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php new file mode 100644 index 0000000..86490c3 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php @@ -0,0 +1,112 @@ +keywords = (string) $keywords; + $this->searchParams = $params; + $this->searchAttributes = $attributes; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSearchKeywords() { + return $this->keywords; + } + + /** + * {@inheritdoc} + */ + public function getSearchParams() { + return $this->searchParams; + } + + /** + * {@inheritdoc} + */ + public function getSearchAttributes() { + return $this->searchAttributes; + } + + /** + * {@inheritdoc} + */ + public function isSearchExecutable() { + // Default implementation suitable for plugins that only use keywords. + return !empty($this->keywords); + } + + /** + * {@inheritdoc} + */ + public function buildResults() { + $results = $this->execute(); + return array( + '#theme' => 'search_results', + '#results' => $results, + '#module' => $this->pluginDefinition['provider'], + ); + } + + /** + * {@inheritdoc} + */ + public function updateIndex() { + // Empty default implementation. + } + + /** + * {@inheritdoc} + */ + public function resetIndex() { + // Empty default implementation. + } + + /** + * {@inheritdoc} + */ + public function indexStatus() { + // No-op default implementation + return array('remaining' => 0, 'total' => 0); + } + +} diff --git a/core/modules/search/lib/Drupal/search/SearchAccessController.php b/core/modules/search/lib/Drupal/search/SearchAccessController.php new file mode 100644 index 0000000..be812a3 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/SearchAccessController.php @@ -0,0 +1,34 @@ +configFactory = $config_factory; + $this->configFactory->enterContext($context); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { + return new static( + $entity_type, + $entity_info, + $container->get('plugin.manager.entity')->getStorageController($entity_type), + $container->get('module_handler'), + $container->get('config.factory'), + $container->get('config.context.free') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'search_admin_settings'; + } + + /** + * {@inheritdoc} + */ + public function render() { + return drupal_get_form($this); + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header['label'] = array( + 'data' => t('Label'), + ); + $header['plugin'] = array( + 'data' => t('Plugin'), + 'class' => array(RESPONSIVE_PRIORITY_LOW), + ); + $header['status'] = array( + 'data' => t('Status'), + 'class' => array(RESPONSIVE_PRIORITY_LOW), + ); + $header['operations'] = array( + 'data' => t('Operations'), + ); + return $header; + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + $row['label'] = String::checkPlain($entity->label()); + $definition = $entity->getPlugin()->getPluginDefinition(); + $row['plugin'] = $definition['title']; + $row['status'] = $entity->status() ? t('Enabled') : t('Disabled'); + $row['operations']['data'] = $this->buildOperations($entity); + return $row; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state) { + $search_settings = $this->configFactory->get('search.settings'); + // Collect some stats. + $remaining = 0; + $total = 0; + $entities = $this->load(); + foreach ($entities as $entity) { + if ($entity->status() && $status = $entity->getPlugin()->indexStatus()) { + $remaining += $status['remaining']; + $total += $status['total']; + } + } + + $this->moduleHandler->loadAllIncludes('admin.inc'); + $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.'); + $percentage = ((int) min(100, 100 * ($total - $remaining) / max(1, $total))) . '%'; + $status = '

' . t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '

'; + $form['status'] = array( + '#type' => 'details', + '#title' => t('Indexing status'), + ); + $form['status']['status'] = array('#markup' => $status); + $form['status']['wipe'] = array( + '#type' => 'submit', + '#value' => t('Re-index site'), + '#submit' => array(array($this, 'searchAdminReindexSubmit')), + ); + + $items = drupal_map_assoc(array(10, 20, 50, 100, 200, 500)); + + // Indexing throttle: + $form['indexing_throttle'] = array( + '#type' => 'details', + '#title' => t('Indexing throttle') + ); + $form['indexing_throttle']['cron_limit'] = array( + '#type' => 'select', + '#title' => t('Number of items to index per cron run'), + '#default_value' => $search_settings->get('index.cron_limit'), + '#options' => $items, + '#description' => t('The maximum number of items indexed in each pass of a cron maintenance task. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status'))) + ); + // Indexing settings: + $form['indexing_settings'] = array( + '#type' => 'details', + '#title' => t('Indexing settings') + ); + $form['indexing_settings']['info'] = array( + '#markup' => t('

Changing the settings below will cause the site index to be rebuilt. The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.

The default settings should be appropriate for the majority of sites.

') + ); + $form['indexing_settings']['minimum_word_size'] = array( + '#type' => 'number', + '#title' => t('Minimum word length to index'), + '#default_value' => $search_settings->get('index.minimum_word_size'), + '#min' => 1, + '#max' => 1000, + '#description' => t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).') + ); + $form['indexing_settings']['overlap_cjk'] = array( + '#type' => 'checkbox', + '#title' => t('Simple CJK handling'), + '#default_value' => $search_settings->get('index.overlap_cjk'), + '#description' => t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.') + ); + + $form['search_types'] = array( + '#type' => 'details', + '#title' => t('Search types'), + ); + $form['search_types']['inline_actions'] = array( + '#prefix' => '', + ); + $form['search_types']['inline_actions']['add'] = array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => t('Add new search type'), + 'href' => 'admin/config/search/settings/add', + ), + ); + $rows = array(); + foreach ($entities as $entity) { + $rows[$entity->id()] = $this->buildRow($entity); + } + $form['search_types']['default_type'] = array( + '#type' => 'tableselect', + '#header' => $this->buildHeader(), + '#options' => $rows, + '#required' => TRUE, + '#multiple' => FALSE, + '#default_value' => $search_settings->get('default_type'), + ); + + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + '#button_type' => 'primary', + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getOperations(EntityInterface $entity) { + $operations = parent::getOperations($entity); + + // Prevent the default search from being disabled or deleted. + if ($entity->isDefaultSearch()) { + unset($operations['disable'], $operations['delete']); + } + + return $operations; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + $search_settings = $this->configFactory->get('search.settings'); + // If these settings change, the index needs to be rebuilt. + if (($search_settings->get('index.minimum_word_size') != $form_state['values']['minimum_word_size']) || ($search_settings->get('index.overlap_cjk') != $form_state['values']['overlap_cjk'])) { + $search_settings->set('index.minimum_word_size', $form_state['values']['minimum_word_size']); + $search_settings->set('index.overlap_cjk', $form_state['values']['overlap_cjk']); + drupal_set_message(t('The index will be rebuilt.')); + search_reindex(); + } + + // If the default search is disabled, enable it. + $entity = $this->storage->load($form_state['values']['default_type']); + if (!$entity->status()) { + $entity->enable()->save(); + } + + $search_settings + ->set('index.cron_limit', $form_state['values']['cron_limit']) + ->set('default_type', $form_state['values']['default_type']) + ->save(); + + drupal_set_message(t('The configuration options have been saved.')); + } + + /** + * Form submission handler for the reindex button on the search admin settings + * form. + */ + public function searchAdminReindexSubmit(array &$form, array &$form_state) { + // Send the user to the confirmation page. + $form_state['redirect'] = 'admin/config/search/settings/reindex'; + } + +} diff --git a/core/modules/search/lib/Drupal/search/SearchPluginManager.php b/core/modules/search/lib/Drupal/search/SearchPluginManager.php new file mode 100644 index 0000000..f3d86c1 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/SearchPluginManager.php @@ -0,0 +1,39 @@ + $namespaces['Drupal\search']); + parent::__construct('Plugin/Search', $namespaces, $annotation_namespaces, 'Drupal\search\Annotation\SearchPlugin'); + } + + /** + * {@inheritdoc} + */ + public function processDefinition(&$definition, $plugin_id) { + parent::processDefinition($definition, $plugin_id); + + // Fill in the provider as default values for missing keys. + $definition += array( + 'title' => $definition['provider'], + 'path' => $definition['provider'], + ); + } + +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchAdvancedSearchFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchAdvancedSearchFormTest.php index 5b0efeb..74fb54f 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchAdvancedSearchFormTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchAdvancedSearchFormTest.php @@ -29,7 +29,7 @@ function setUp() { $this->node = $this->drupalCreateNode(); // First update the index. This does the initial processing. - node_update_index(); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); // Then, run the shutdown function. Testing is a unique case where indexing // and searching has to happen in the same request, so running the shutdown diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php index f45895d..aab4c13 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php @@ -66,7 +66,7 @@ function setUp() { $this->drupalPost('comment/reply/' . $this->searchable_nodes['1 comment']->id(), $edit_comment, t('Save')); // First update the index. This does the initial processing. - node_update_index(); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); // Then, run the shutdown function. Testing is a unique case where indexing // and searching has to happen in the same request, so running the shutdown diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php index 2313a95..4495cc8 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php @@ -49,7 +49,7 @@ function setUp() { $edit[$body_key] = l($node->label(), 'node/' . $node->id()) . ' pizza sandwich'; $this->drupalPost('node/' . $node->id() . '/edit', $edit, t('Save and keep published')); - node_update_index(); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Enable the search block. @@ -66,22 +66,22 @@ function testSearchSettingsPage() { $this->assertText(t('There are @count items left to index.', array('@count' => 0))); // Test the re-index button. - $this->drupalPost('admin/config/search/settings', array(), t('Re-index site')); + $this->drupalPost(NULL, array(), t('Re-index site')); $this->assertText(t('Are you sure you want to re-index the site')); - $this->drupalPost('admin/config/search/settings/reindex', array(), t('Re-index site')); + $this->drupalPost(NULL, array(), t('Re-index site')); $this->assertText(t('The index will be rebuilt')); $this->drupalGet('admin/config/search/settings'); $this->assertText(t('There is 1 item left to index.')); // Test that the form saves with the default values. - $this->drupalPost('admin/config/search/settings', array(), t('Save configuration')); + $this->drupalPost(NULL, array(), t('Save configuration')); $this->assertText(t('The configuration options have been saved.'), 'Form saves with the default values.'); // Test that the form does not save with an invalid word length. $edit = array( 'minimum_word_size' => $this->randomName(3), ); - $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); + $this->drupalPost(NULL, $edit, t('Save configuration')); $this->assertNoText(t('The configuration options have been saved.'), 'Form does not save with an invalid word length.'); } @@ -98,19 +98,24 @@ function testSearchModuleSettingsPage() { $this->assertNoText(t('Extra type settings')); $this->assertNoText(t('Boost method')); + $this->clickLink(t('Add new search type')); + // Ensure that the test module is listed as an option - $this->assertTrue($this->xpath('//input[@id="edit-active-modules-search-extra-type"]'), 'Checkbox for activating search for an extra module is visible'); - $this->assertTrue($this->xpath('//input[@id="edit-default-module-search-extra-type"]'), 'Radio button for setting extra module as default search module is visible'); + $this->assertLinkByHref('admin/config/search/settings/add/search_extra_type_search'); // Enable search for the test module - $edit['active_modules[search_extra_type]'] = 'search_extra_type'; - $edit['default_module'] = 'search_extra_type'; + $edit = array( + 'default_type' => 'dummy_search_type', + 'minimum_word_size' => 5, + ); $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); + $this->assertTrue($this->xpath('//input[@id="edit-minimum-word-size" and @value="5"]'), 'Common search settings can be modified if a module-specific form is active'); - // Ensure that the settings fieldset is visible after enabling search for - // the test module - $this->assertText(t('Extra type settings')); - $this->assertText(t('Boost method')); + // Ensure that the modifications took effect. + $this->assertText(t('The configuration options have been saved.')); + + $this->assertText(t('Dummy search type')); + $this->drupalGet('admin/config/search/settings/manage/dummy_search_type'); // Ensure that the default setting was picked up from the default config $this->assertTrue($this->xpath('//select[@id="edit-extra-type-settings-boost"]//option[@value="bi" and @selected="selected"]'), 'Module specific settings are picked up from the default config'); @@ -118,65 +123,64 @@ function testSearchModuleSettingsPage() { // Change extra type setting and also modify a common search setting. $edit = array( 'extra_type_settings[boost]' => 'ii', - 'minimum_word_size' => 5, ); - $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); + $this->drupalPost(NULL, $edit, t('Save')); - // Ensure that the modifications took effect. - $this->assertText(t('The configuration options have been saved.')); + $this->drupalGet('admin/config/search/settings/manage/dummy_search_type'); $this->assertTrue($this->xpath('//select[@id="edit-extra-type-settings-boost"]//option[@value="ii" and @selected="selected"]'), 'Module specific settings can be changed'); - $this->assertTrue($this->xpath('//input[@id="edit-minimum-word-size" and @value="5"]'), 'Common search settings can be modified if a module-specific form is active'); } /** * Verify that you can disable individual search modules. */ function testSearchModuleDisabling() { - // Array of search modules to test: 'path' is the search path, 'title' is + // Array of search plugins to test: 'path' is the search path, 'title' is // the tab title, 'keys' are the keywords to search for, and 'text' is // the text to assert is on the results page. - $module_info = array( - 'node' => array( + $plugin_info = array( + 'node_search' => array( 'path' => 'node', 'title' => 'Content', 'keys' => 'pizza', 'text' => $this->search_node->label(), ), - 'user' => array( + 'user_search' => array( 'path' => 'user', 'title' => 'User', 'keys' => $this->search_user->getUsername(), 'text' => $this->search_user->getEmail(), ), - 'search_extra_type' => array( + 'dummy_search_type' => array( 'path' => 'dummy_path', 'title' => 'Dummy search type', 'keys' => 'foo', 'text' => 'Dummy search snippet to display', ), ); - $modules = array_keys($module_info); + $search_plugins = array_keys($plugin_info); // Test each module if it's enabled as the only search module. - foreach ($modules as $module) { + foreach ($search_plugins as $id) { // Enable the one module and disable other ones. - $info = $module_info[$module]; - $edit = array(); - foreach ($modules as $other) { - $edit['active_modules[' . $other . ']'] = (($other == $module) ? $module : FALSE); - } - $edit['default_module'] = $module; + $edit = array('default_type' => $id); $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); + foreach ($search_plugins as $other) { + if ($other != $id) { + $this->drupalGet('admin/config/search/settings/manage/' . $other . '/disable'); + } + } + // Run a search from the correct search URL. + $info = $plugin_info[$id]; $this->drupalGet('search/' . $info['path'] . '/' . $info['keys']); $this->assertNoText('no results', $info['title'] . ' search found results'); $this->assertText($info['text'], 'Correct search text found'); // Verify that other module search tab titles are not visible. - foreach ($modules as $other) { - if ($other != $module) { - $title = $module_info[$other]['title']; + foreach ($search_plugins as $other) { + if ($other != $id) { + $title = $plugin_info[$other]['title']; $this->assertNoText($title, $title . ' search tab is not shown'); } } @@ -200,20 +204,20 @@ function testSearchModuleDisabling() { // Test with all search modules enabled. When you go to the search // page or run search, all modules should be shown. - $edit = array(); - foreach ($modules as $module) { - $edit['active_modules[' . $module . ']'] = $module; + foreach (entity_load_multiple('search') as $search) { + $search->enable()->save(); } - $edit['default_module'] = 'node'; + $edit = array('default_type' => 'node_search'); $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); foreach (array('search/node/pizza', 'search/node') as $path) { $this->drupalGet($path); - foreach ($modules as $module) { - $title = $module_info[$module]['title']; + foreach ($search_plugins as $id) { + $title = $plugin_info[$id]['title']; $this->assertText($title, format_string('%title search tab is shown', array('%title' => $title))); } } } + } diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchEmbedFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchEmbedFormTest.php index ff822c6..649e410 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchEmbedFormTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchEmbedFormTest.php @@ -46,7 +46,7 @@ function setUp() { $this->node = $this->drupalCreateNode(); - node_update_index(); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Set up a dummy initial count of times the form has been submitted. diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExactTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExactTest.php index c52549f..587d773 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchExactTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchExactTest.php @@ -42,7 +42,7 @@ function testExactQuery() { } // Update the search index. - module_invoke_all('update_index'); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Refresh variables after the treatment. diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php index d9ab415..8cbe050 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php @@ -95,7 +95,8 @@ 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(); + $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); + $plugin->updateIndex(); // 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. @@ -104,7 +105,7 @@ function testIndexingThrottle() { // 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'); + $status = $plugin->indexStatus(); $this->assertEqual($status['remaining'], 1, 'Remaining items after updating the search index is 1.'); } @@ -114,14 +115,17 @@ function testIndexingThrottle() { function testSearchingMultilingualFieldValues() { // Update the index and then run the shutdown method. // See testIndexingThrottle() for further explanation. - node_update_index(); + $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); + $plugin->updateIndex(); 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']); + $plugin->setSearch($body_language_variant[0]['value'], array(), array()); + // Do the search and assert the results. + $search_result = $plugin->execute(); // See whether we get the same node as a result. $this->assertEqual($search_result[0]['node']->id(), $node->id(), 'The search has resulted the correct node.'); } diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchNodeAccessTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchNodeAccessTest.php index 67706cf..8f067f6 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchNodeAccessTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchNodeAccessTest.php @@ -45,7 +45,7 @@ function testPhraseSearchPunctuation() { $node = $this->drupalCreateNode(array('body' => array(array('value' => "The bunny's ears were fluffy.")))); // Update the search index. - module_invoke_all('update_index'); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Refresh variables after the treatment. diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchPreprocessLangcodeTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchPreprocessLangcodeTest.php index a8c2e61..8792596 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchPreprocessLangcodeTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchPreprocessLangcodeTest.php @@ -46,7 +46,7 @@ function testPreprocessLangcode() { $node = $this->drupalCreateNode(array('body' => array(array()), 'langcode' => 'en')); // First update the index. This does the initial processing. - node_update_index(); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); // Then, run the shutdown function. Testing is a unique case where indexing // and searching has to happen in the same request, so running the shutdown @@ -73,7 +73,7 @@ function testPreprocessStemming() { )); // First update the index. This does the initial processing. - node_update_index(); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); // Then, run the shutdown function. Testing is a unique case where indexing // and searching has to happen in the same request, so running the shutdown diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php index 887e997..01be76e 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php @@ -63,7 +63,7 @@ function testRankings() { } // Update the search index. - module_invoke_all('update_index'); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Refresh variables after the treatment. @@ -92,6 +92,11 @@ function testRankings() { } // Test each of the possible rankings. + // @todo - comments and views are removed from the array since they are + // broken in core. Those modules expected hook_update_index() to be called + // even though it was only called on modules that implemented a search type. + array_pop($node_ranks); + array_pop($node_ranks); foreach ($node_ranks as $node_rank) { // Disable all relevancy rankings except the one we are testing. foreach ($node_ranks as $var) { @@ -99,7 +104,8 @@ function testRankings() { } // Do the search and assert the results. - $set = node_search_execute('rocks'); + $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); + $set = $plugin->setSearch('rocks', array(), array())->execute(); $this->assertEqual($set[0]['node']->id(), $nodes[$node_rank][1]->id(), 'Search ranking "' . $node_rank . '" order.'); } } @@ -143,7 +149,7 @@ function testHTMLRankings() { } // Update the search index. - module_invoke_all('update_index'); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Refresh variables after the treatment. @@ -154,7 +160,10 @@ function testHTMLRankings() { foreach ($node_ranks as $node_rank) { variable_set('node_rank_' . $node_rank, 0); } - $set = node_search_execute('rocks'); + $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); + $plugin->setSearch('rocks', array(), array()); + // Do the search and assert the results. + $set = $plugin->execute(); // Test the ranking of each tag. foreach ($sorted_tags as $tag_rank => $tag) { @@ -173,13 +182,15 @@ function testHTMLRankings() { $node = $this->drupalCreateNode($settings); // Update the search index. - module_invoke_all('update_index'); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Refresh variables after the treatment. $this->refreshVariables(); - - $set = node_search_execute('rocks'); + $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); + $plugin->setSearch('rocks', array(), array()); + // Do the search and assert the results. + $set = $plugin->execute(); // Ranking should always be second to last. $set = array_slice($set, -2, 1); @@ -212,7 +223,7 @@ function testDoubleRankings() { $node = $this->drupalCreateNode($settings); // Update the search index. - module_invoke_all('update_index'); + $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); search_update_totals(); // Refresh variables after the treatment. @@ -227,7 +238,8 @@ function testDoubleRankings() { } // Do the search and assert the results. - $set = node_search_execute('rocks'); + $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); + $set = $plugin->setSearch('rocks', array(), array())->execute(); $this->assertEqual($set[0]['node']->id(), $node->id(), 'Search double ranking order.'); } } diff --git a/core/modules/search/search.api.php b/core/modules/search/search.api.php index 03b45aa..6c08d48 100644 --- a/core/modules/search/search.api.php +++ b/core/modules/search/search.api.php @@ -11,42 +11,6 @@ */ /** - * Define a custom search type. - * - * This hook allows a module to tell the Search module that it wishes to - * perform searches on content it defines (custom node types, users, or - * comments for example) when a site search is performed. - * - * In order for the search to do anything, your module must also implement - * hook_search_execute(), which is called when someone requests a search on - * your module's type of content. If you want to have your content indexed - * in the standard search index, your module should also implement - * hook_update_index(). If your search type has settings, you can implement - * hook_search_admin() to add them to the search settings page. You can use - * hook_form_FORM_ID_alter(), with FORM_ID set to 'search_form', to add fields - * to the search form (see node_form_search_form_alter() for an example). - * You can use hook_search_access() to limit access to searching, and - * hook_search_page() to override how search results are displayed. - * - * @return - * Array with optional keys: - * - title: Title for the tab on the search page for this module. Defaults to - * the module name if not given. - * - path: Path component after 'search/' for searching with this module. - * Defaults to the module name if not given. - * - conditions_callback: An implementation of callback_search_conditions(). - * - * @ingroup search - */ -function hook_search_info() { - return array( - 'title' => 'Content', - 'path' => 'node', - 'conditions_callback' => 'callback_search_conditions', - ); -} - -/** * Define access to a custom search routine. * * This hook allows a module to define permissions for a search tab. @@ -58,225 +22,6 @@ function hook_search_access() { } /** - * Take action when the search index is going to be rebuilt. - * - * Modules that use hook_update_index() should update their indexing - * bookkeeping so that it starts from scratch the next time hook_update_index() - * is called. - * - * @ingroup search - */ -function hook_search_reset() { - db_update('search_dataset') - ->fields(array('reindex' => REQUEST_TIME)) - ->condition('type', 'node') - ->execute(); -} - -/** - * Report the status of indexing. - * - * The core search module only invokes this hook on active modules. - * Implementing modules do not need to check whether they are active when - * calculating their return values. - * - * @return - * An associative array with the key-value pairs: - * - remaining: The number of items left to index. - * - total: The total number of items to index. - * - * @ingroup search - */ -function hook_search_status() { - $total = db_query('SELECT COUNT(DISTINCT nid) FROM {node_field_data} WHERE status = 1')->fetchField(); - $remaining = db_query("SELECT COUNT(DISTINCT nid) FROM {node_field_data} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND d.sid IS NULL OR d.reindex <> 0")->fetchField(); - return array('remaining' => $remaining, 'total' => $total); -} - -/** - * Add elements to the search settings form. - * - * @return - * Form array for the Search settings page at admin/config/search/settings. - * - * @ingroup search - */ -function hook_search_admin() { - // Output form for defining rank factor weights. - $form['content_ranking'] = array( - '#type' => 'details', - '#title' => t('Content ranking'), - ); - $form['content_ranking']['#theme'] = 'node_search_admin'; - $form['content_ranking']['#tree'] = TRUE; - $form['content_ranking']['info'] = array( - '#value' => '' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '' - ); - - // Note: reversed to reflect that higher number = higher ranking. - $options = drupal_map_assoc(range(0, 10)); - $ranks = config('node.settings')->get('search_rank'); - foreach (module_invoke_all('ranking') as $var => $values) { - $form['content_ranking']['factors'][$var] = array( - '#title' => $values['title'], - '#type' => 'select', - '#options' => $options, - '#default_value' => isset($ranks[$var]) ? $ranks[$var] : 0, - ); - } - - $form['#submit'][] = 'node_search_admin_submit'; - - return $form; -} - -/** - * Execute a search for a set of key words. - * - * Use database API with the 'Drupal\Core\Database\Query\PagerSelectExtender' - * query extension to perform your search. - * - * If your module uses hook_update_index() and search_index() to index its - * items, use table 'search_index' aliased to 'i' as the main table in your - * query, with the 'Drupal\search\SearchQuery' extension. You can join to your - * module's table using the 'i.sid' field, which will contain the $sid values - * you provided to search_index(). Add the main keywords to the query by using - * method searchExpression(). The functions search_expression_extract() and - * search_expression_insert() may also be helpful for adding custom search - * parameters to the search expression. - * - * See node_search_execute() for an example of a module that uses the search - * index, and user_search_execute() for an example that doesn't use the search - * index. - * - * @param $keys - * The search keywords as entered by the user. Defaults to NULL. - * @param $conditions - * (optional) An array of additional conditions, such as filters. Defaults to - * NULL. - * - * @return - * An array of search results. To use the default search result display, each - * item should have the following keys': - * - link: (required) The URL of the found item. - * - type: The type of item (such as the content type). - * - title: (required) The name of the item. - * - user: The author of the item. - * - date: A timestamp when the item was last modified. - * - extra: An array of optional extra information items. - * - snippet: An excerpt or preview to show with the result (can be generated - * with search_excerpt()). - * - language: Language code for the item (usually two characters). - * - * @ingroup search - */ -function hook_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(); - $username = array( - '#theme' => 'username', - '#account' => $node, - ); - $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' => drupal_render($username), - 'date' => $node->changed, - 'node' => $node, - 'extra' => $extra, - 'score' => $item->calculated_score, - 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), - 'langcode' => $node->langcode, - ); - } - return $results; -} - -/** - * Override the rendering of search results. - * - * A module that implements hook_search_info() to define a type of search may - * implement this hook in order to override the default theming of its search - * results, which is otherwise themed using theme('search_results'). - * - * Note that by default, theme('search_results') and theme('search_result') - * work together to create an ordered list (OL). So your hook_search_page() - * implementation should probably do this as well. - * - * @param $results - * An array of search results. - * - * @return - * A renderable array, which will render the formatted search results with a - * pager included. - * - * @see search-result.tpl.php - * @see search-results.tpl.php - */ -function hook_search_page($results) { - $output['prefix']['#markup'] = '
    '; - - foreach ($results as $entry) { - $output[] = array( - '#theme' => 'search_result', - '#result' => $entry, - '#module' => 'my_module_name', - ); - } - $pager = array( - '#theme' => 'pager', - ); - $output['suffix']['#markup'] = '
' . drupal_render($pager); - - return $output; -} - -/** * Preprocess text for search. * * This hook is called to preprocess both the text added to the search index @@ -315,96 +60,3 @@ function hook_search_preprocess($text, $langcode = NULL) { return $text; } - -/** - * Update the search index for this module. - * - * This hook is called every cron run if the Search module is enabled, your - * module has implemented hook_search_info(), and your module has been set as - * an active search module on the Search settings page - * (admin/config/search/settings). It allows your module to add items to the - * built-in search index using search_index(), or to add them to your module's - * own indexing mechanism. - * - * When implementing this hook, your module should index content items that - * were modified or added since the last run. PHP has a time limit - * for cron, though, so it is advisable to limit how many items you index - * per run using config('search.settings')->get('index.cron_limit') (see - * example below). Also, since the cron run could time out and abort in the - * middle of your run, you should update your module's internal bookkeeping on - * when items have last been indexed as you go rather than waiting to the end - * of indexing. - * - * @ingroup search - */ -function hook_update_index() { - $limit = (int) config('search.settings')->get('index.cron_limit'); - - $result = db_query_range("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); - - foreach ($result as $node) { - $node = node_load($node->id()); - - // Save the changed time of the most recent indexed node, for the search - // results half-life calculation. - \Drupal::state()->set('node.cron_last', $node->changed); - - // Render the node. - $build = node_view($node, 'search_index'); - $node->rendered = drupal_render($node->content); - - $text = '

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

' . $node->rendered; - - // Fetch extra data normally not visible - $extra = module_invoke_all('node_update_index', $node); - foreach ($extra as $t) { - $text .= $t; - } - - // Update index - search_index($node->id(), 'node', $text); - } -} - -/** - * @} End of "addtogroup hooks". - */ - -/** - * Provide search query conditions. - * - * Callback for hook_search_info(). - * - * This callback is invoked by search_view() to get an array of additional - * search conditions to pass to search_data(). For example, a search module - * may get additional keywords, filters, or modifiers for the search from - * the query string. - * - * This example pulls additional search keywords out of the $_REQUEST variable, - * (i.e. from the query string of the request). The conditions may also be - * generated internally - for example based on a module's settings. - * - * @param $keys - * The search keywords string. - * - * @return - * An array of additional conditions, such as filters. - * - * @ingroup callbacks - * @ingroup search - */ -function callback_search_conditions($keys) { - $conditions = array(); - - if (!empty($_REQUEST['keys'])) { - $conditions['keys'] = $_REQUEST['keys']; - } - if (!empty($_REQUEST['sample_search_keys'])) { - $conditions['sample_search_keys'] = $_REQUEST['sample_search_keys']; - } - if ($force_keys = config('sample_search.settings')->get('force_keywords')) { - $conditions['sample_search_force_keywords'] = $force_keys; - } - return $conditions; -} - diff --git a/core/modules/search/search.module b/core/modules/search/search.module index 9e9921b..037dfc1 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -156,12 +156,30 @@ function search_menu() { 'type' => MENU_SUGGESTED_ITEM, 'file' => 'search.pages.inc', ); + $items['admin/config/search/settings/add'] = array( + 'title' => t('Add'), + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + 'route_name' => 'search.add', + ); + $items['admin/config/search/settings/add/%'] = array( + 'route_name' => 'search.add_type', + ); $items['admin/config/search/settings'] = array( 'title' => 'Search settings', 'description' => 'Configure relevance settings for search and other indexing options.', 'route_name' => 'search_settings', 'weight' => -10, ); + $items['admin/config/search/settings/manage/%search'] = array( + 'title callback' => 'entity_page_label', + 'title arguments' => array(5), + 'route_name' => 'search.edit', + ); + $items['admin/config/search/settings/manage/%search/delete'] = array( + 'title callback' => 'entity_page_label', + 'title arguments' => array(5), + 'route_name' => 'search.delete', + ); $items['admin/config/search/settings/reindex'] = array( 'title' => 'Clear index', 'route_name' => 'search_reindex_confirm', @@ -175,101 +193,85 @@ function search_menu() { // system appears to be having two sets of tabs. See discussion on issue // http://drupal.org/node/245103 for details. - drupal_static_reset('search_get_info'); - $default_info = search_get_default_module_info(); - if ($default_info) { - foreach (search_get_info() as $module => $search_info) { - $path = 'search/' . $search_info['path']; - $items[$path] = array( - 'title' => $search_info['title'], - 'page callback' => 'search_view', - 'page arguments' => array($module, ''), - 'access callback' => '_search_menu_access', - 'access arguments' => array($module), - 'type' => MENU_LOCAL_TASK, - 'file' => 'search.pages.inc', - 'weight' => $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), - 'access callback' => '_search_menu_access', - 'access arguments' => array($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, - 'file' => 'search.pages.inc', - 'weight' => 0, - // These tabs are not subtabs. - 'tab_root' => 'search/' . $default_info['path'] . '/%', - // These tabs need to display at the same level. - 'tab_parent' => 'search/' . $default_info['path'], - ); - } + $default_type = search_get_default_type(); + foreach (entity_load_multiple_by_properties('search', array('status' => '1')) as $search_id => $entity) { + $path = 'search/' . $entity->getPath(); + $items[$path] = array( + 'title' => $entity->getTitle(), + 'page callback' => 'search_view', + 'page arguments' => array($search_id, ''), + 'access callback' => '_search_menu_access', + 'access arguments' => array($entity->getProvider()), + 'type' => MENU_LOCAL_TASK, + 'file' => 'search.pages.inc', + 'weight' => $entity->isDefaultSearch() ? -10 : 0, + ); + $items["$path/%menu_tail"] = array( + 'title' => $entity->getTitle(), + 'load arguments' => array('%map', '%index'), + 'page callback' => 'search_view', + 'page arguments' => array($search_id, 2), + 'access callback' => '_search_menu_access', + 'access arguments' => array($entity->getProvider()), + // 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, + 'file' => 'search.pages.inc', + 'weight' => 0, + // These tabs are not subtabs. + 'tab_root' => 'search/' . $default_type->getPath() . '/%', + // These tabs need to display at the same level. + 'tab_parent' => 'search/' . $default_type->getPath(), + ); } return $items; } /** - * Determines access for the 'search' path. - */ -function search_is_active() { - // This path cannot be accessed if there are no active modules. - return user_access('search content') && search_get_info(); -} - -/** - * Returns information about available search modules. + * Loads an active search entity. * - * @param $all - * If TRUE, information about all enabled modules implementing - * hook_search_info() will be returned. If FALSE (default), only modules that - * have been set to active on the search settings page will be returned. + * @param string $entity_id + * The search entity ID. + * @param bool $active_only + * (optional) TRUE if only an active search entity should be returned. + * Defaults to FALSE. * - * @return - * Array of hook_search_info() return values, 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). + * @return \Drupal\search\SearchInterface + * The search entity. */ -function search_get_info($all = FALSE) { - $search_hooks = &drupal_static(__FUNCTION__); - - if (!isset($search_hooks)) { - foreach (Drupal::moduleHandler()->getImplementations('search_info') as $module) { - $search_hooks[$module] = call_user_func($module . '_search_info'); - // 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; - } - } - - if ($all) { - return $search_hooks; +function search_load($entity_id, $active_only = FALSE) { + $entity = entity_load('search', $entity_id); + if (!$active_only || ($entity && $entity->status())) { + return $entity; } +} - // 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'))); +/** + * Determines access for the 'search' path. + */ +function search_is_active() { + // This path cannot be accessed if there are no active modules. + return user_access('search content') && Drupal::entityQuery('search')->condition('status', 1)->execute(); } /** - * Returns information about the default search module. + * Returns the default search entity. * - * @return - * The search_get_info() array element for the default search module, if any. + * @return \Drupal\search\SearchInterface + * The search entity. */ -function search_get_default_module_info() { - $info = search_get_info(); - $default = config('search.settings')->get('default_module'); - if (isset($info[$default])) { - return $info[$default]; +function search_get_default_type() { + $default = config('search.settings')->get('default_type'); + if (!$default || !$entity = search_load($default, TRUE)) { + // The variable setting does not match any active plugin, so just return + // the info for the first active plugin (if any). + $id = Drupal::entityQuery('search') + ->condition('status', 1) + ->range(0, 1) + ->execute(); + $entity = entity_load('search', reset($id)); } - // The variable setting does not match any active module, so just return - // the info for the first active module (if any). - return reset($info); + return $entity; } /** @@ -305,7 +307,9 @@ function _search_menu_access($name) { */ function search_reindex($sid = NULL, $module = NULL, $reindex = FALSE, $langcode = NULL) { if ($module == NULL && $sid == NULL) { - module_invoke_all('search_reset'); + foreach (entity_load_multiple_by_properties('search', array('status' => '1')) as $entity) { + $entity->getPlugin()->resetIndex(); + } } else { $query = db_delete('search_dataset') @@ -362,9 +366,8 @@ function search_cron() { // to date. drupal_register_shutdown_function('search_update_totals'); - foreach (config('search.settings')->get('active_modules') as $module) { - // Update word index - module_invoke($module, 'update_index'); + foreach (entity_load_multiple_by_properties('search', array('status' => '1')) as $entity) { + $entity->getPlugin()->updateIndex(); } } @@ -947,24 +950,11 @@ function search_expression_insert($expression, $option, $value = NULL) { * @see search_form_validate() * @see search_form_submit() */ -function search_form($form, &$form_state, $action = '', $keys = '', $module = NULL, $prompt = NULL) { - $module_info = FALSE; - if (!$module) { - $module_info = search_get_default_module_info(); - } - else { - $info = search_get_info(); - $module_info = isset($info[$module]) ? $info[$module] : FALSE; - } - - // Sanity check. - if (!$module_info) { - form_set_error(NULL, t('Search is currently disabled.'), 'error'); - return $form; - } +function search_form($form, &$form_state, $action = '', $keys = '', $entity_id = NULL, $prompt = NULL) { + $entity = $entity_id ? entity_load('search', $entity_id) : search_get_default_type(); if (!$action) { - $action = 'search/' . $module_info['path']; + $action = 'search/' . $entity->getPath(); } if (!isset($prompt)) { $prompt = t('Enter your keywords'); @@ -972,8 +962,8 @@ function search_form($form, &$form_state, $action = '', $keys = '', $module = NU $form['#action'] = url($action); // Record the $action for later use in redirecting. + $form_state['search_plugin'] = $entity->getPlugin()->getPluginId(); $form_state['action'] = $action; - $form['module'] = array('#type' => 'value', '#value' => $module); $form['basic'] = array('#type' => 'container', '#attributes' => array('class' => array('container-inline'))); $form['basic']['keys'] = array( '#type' => 'search', @@ -1038,9 +1028,8 @@ function search_box_form_submit($form, &$form_state) { } $form_id = $form['form_id']['#value']; - $info = search_get_default_module_info(); - if ($info) { - $form_state['redirect'] = 'search/' . $info['path'] . '/' . trim($form_state['values'][$form_id]); + if ($entity = search_get_default_type()) { + $form_state['redirect'] = 'search/' . $entity->getPath() . '/' . trim($form_state['values'][$form_id]); } else { form_set_error(NULL, t('Search is currently disabled.'), 'error'); @@ -1048,36 +1037,6 @@ function search_box_form_submit($form, &$form_state) { } /** - * Performs a search by calling hook_search_execute(). - * - * @param $keys - * Keyword query to search on. - * @param $module - * Search module to search. - * @param $conditions - * Optional array of additional search conditions. - * - * @return - * 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); - if (module_hook($module, 'search_page')) { - return module_invoke($module, 'search_page', $results); - } - else { - return array( - '#theme' => 'search_results', - '#results' => $results, - '#module' => $module, - ); - } - } -} - -/** * Returns snippets from a piece of text, with certain keywords highlighted. * * Used for formatting search results. diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc index a39a815..15d3e7b 100644 --- a/core/modules/search/search.pages.inc +++ b/core/modules/search/search.pages.inc @@ -16,35 +16,29 @@ * @param $keys * Keywords to use for the search. */ -function search_view($module = NULL, $keys = '') { - $info = FALSE; +function search_view($entity_id = NULL, $keys = '') { $keys = trim($keys); // Also try to pull search keywords out of the $_REQUEST variable to // support old GET format of searches for existing links. if (!$keys && !empty($_REQUEST['keys'])) { $keys = trim($_REQUEST['keys']); } - - if (!empty($module)) { - $active_module_info = search_get_info(); - if (isset($active_module_info[$module])) { - $info = $active_module_info[$module]; - } - } - - if (empty($info)) { + if (!$entity_id || !$entity = search_load($entity_id, TRUE)) { // 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. - $info = search_get_default_module_info(); + $entity = search_get_default_type(); // Redirect from bare /search or an invalid path to the default search path. - $path = 'search/' . $info['path']; + $path = 'search/' . $entity->getPath(); if ($keys) { $path .= '/' . $keys; } return new RedirectResponse(url($path, array('absolute' => TRUE))); } + $request = Drupal::request(); + $plugin = $entity->getPlugin(); + $plugin->setSearch($keys, $request->query->all(), $request->attributes->all()); // Default results output is an empty string. $results = array('#markup' => ''); // Process the search form. Note that if there is $_POST data, @@ -53,22 +47,17 @@ 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)); + watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $entity->getTitle()), WATCHDOG_NOTICE, l(t('results'), 'search/' . $entity->getPath() . '/' . $keys)); // Collect the search results. - $results = search_data($keys, $info['module'], $conditions); + $results = $plugin->buildResults(); } } // The form may be altered based on whether the search was run. - $build['search_form'] = drupal_get_form('search_form', NULL, $keys, $info['module']); + $build['search_form'] = drupal_get_form('search_form', NULL, $keys, $entity_id); $build['search_results'] = $results; return $build; diff --git a/core/modules/search/search.routing.yml b/core/modules/search/search.routing.yml index 8acf650..833573c 100644 --- a/core/modules/search/search.routing.yml +++ b/core/modules/search/search.routing.yml @@ -1,12 +1,49 @@ search_settings: pattern: '/admin/config/search/settings' defaults: - _form: 'Drupal\search\Form\SearchSettingsForm' + _entity_list: 'search' requirements: _permission: 'administer search' + search_reindex_confirm: pattern: '/admin/config/search/settings/reindex' defaults: _form: 'Drupal\search\Form\ReindexConfirm' requirements: _permission: 'administer search' + +search.add: + pattern: '/admin/config/search/settings/add' + defaults: + _content: '\Drupal\search\Controller\SearchController::addSearchType' + requirements: + _permission: 'administer search' + +search.add_type: + pattern: '/admin/config/search/settings/add/{search_plugin_id}' + defaults: + _entity_form: 'search.add' + requirements: + _entity_create_access: 'search' + +search.edit: + pattern: '/admin/config/search/settings/manage/{search}' + defaults: + _entity_form: 'search.edit' + requirements: + _entity_access: 'search.update' + +search.disable: + pattern: '/admin/config/search/settings/manage/{search}/{op}' + defaults: + _controller: '\Drupal\search\Controller\SearchController::performOperation' + requirements: + _permission: 'administer search' + op: 'enable|disable' + +search.delete: + pattern: '/admin/config/search/settings/manage/{search}/delete' + defaults: + _entity_form: 'search.delete' + requirements: + _entity_access: 'search.delete' diff --git a/core/modules/search/search.services.yml b/core/modules/search/search.services.yml new file mode 100644 index 0000000..3bbd395 --- /dev/null +++ b/core/modules/search/search.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.search: + class: Drupal\search\SearchPluginManager + arguments: ['@container.namespaces'] diff --git a/core/modules/search/tests/modules/search_extra_type/config/search.search.dummy_search_type.yml b/core/modules/search/tests/modules/search_extra_type/config/search.search.dummy_search_type.yml new file mode 100644 index 0000000..cfebc79 --- /dev/null +++ b/core/modules/search/tests/modules/search_extra_type/config/search.search.dummy_search_type.yml @@ -0,0 +1,9 @@ +id: dummy_search_type +label: 'Dummy search type' +uuid: b55858d4-f428-474c-8200-ef47a4597aef +status: '1' +langcode: en +path: dummy_path +title: 'Dummy search type' +plugin: search_extra_type_search +configuration: { } diff --git a/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraTypeSearch.php b/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraTypeSearch.php new file mode 100644 index 0000000..a64e011 --- /dev/null +++ b/core/modules/search/tests/modules/search_extra_type/lib/Drupal/search_extra_type/Plugin/Search/SearchExtraTypeSearch.php @@ -0,0 +1,155 @@ +get('config.factory')->get('search_extra_type.settings'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * Creates a SearchExtraTypeSearch object. + * + * @param Config $config_settings + * The extra config settings. + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + */ + public function __construct(Config $config_settings, array $configuration, $plugin_id, array $plugin_definition) { + $this->configSettings = $config_settings; + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public function setSearch($keywords, array $params, array $attributes) { + if (empty($params['search_conditions'])) { + $params['search_conditions'] = ''; + } + parent::setSearch($keywords, $params, $attributes); + } + + /** + * Verifies if the given parameters are valid enough to execute a search for. + * + * @return bool + * A true or false depending on the implementation. + */ + public function isSearchExecutable() { + return (bool) ($this->keywords || !empty($this->searchParams['search_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 + * 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->searchParams, TRUE), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function buildResults() { + $results = $this->execute(); + $output['prefix']['#markup'] = '

Test page text is here

    '; + + foreach ($results as $entry) { + $output[] = array( + '#theme' => 'search_result', + '#result' => $entry, + '#module' => 'search_extra_type', + ); + } + $output['suffix']['#markup'] = '
' . theme('pager'); + + return $output; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, array &$form_state) { + // Output form for defining rank factor weights. + $form['extra_type_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Extra type settings'), + '#tree' => TRUE, + ); + + $form['extra_type_settings']['boost'] = array( + '#type' => 'select', + '#title' => t('Boost method'), + '#options' => array( + 'bi' => t('Bistromathic'), + 'ii' => t('Infinite Improbability'), + ), + '#default_value' => $this->configSettings->get('boost'), + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function submit(array &$form, array &$form_state) { + $this->configSettings + ->set('boost', $form_state['values']['extra_type_settings']['boost']) + ->save(); + } + +} 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 4435b44..4600708 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 @@ -5,105 +5,3 @@ * Dummy module implementing a search type for search module testing. */ -/** - * 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. - */ -function search_extra_type_search_page($results) { - $output['prefix']['#markup'] = '

Test page text is here

    '; - - foreach ($results as $entry) { - $output[] = array( - '#theme' => 'search_result', - '#result' => $entry, - '#module' => 'search_extra_type', - ); - } - $pager = array( - '#theme' => 'pager', - ); - $output['suffix']['#markup'] = '
' . drupal_render($pager); - - return $output; -} - -/** - * Implements hook_search_admin(). - */ -function search_extra_type_search_admin() { - // Output form for defining rank factor weights. - $form['extra_type_settings'] = array( - '#type' => 'fieldset', - '#title' => t('Extra type settings'), - '#tree' => TRUE, - ); - - $form['extra_type_settings']['boost'] = array( - '#type' => 'select', - '#title' => t('Boost method'), - '#options' => array( - 'bi' => t('Bistromathic'), - 'ii' => t('Infinite Improbability'), - ), - '#default_value' => config('search_extra_type.settings')->get('boost'), - ); - - $form['#submit'][] = 'search_extra_type_admin_submit'; - - return $form; -} - -/** - * Form API callback: Save admin settings - */ -function search_extra_type_admin_submit($form, &$form_state) { - config('search_extra_type.settings') - ->set('boost', $form_state['values']['extra_type_settings']['boost']) - ->save(); -} diff --git a/core/modules/user/config/search.search.user_search.yml b/core/modules/user/config/search.search.user_search.yml new file mode 100644 index 0000000..94c43ce --- /dev/null +++ b/core/modules/user/config/search.search.user_search.yml @@ -0,0 +1,9 @@ +id: user_search +label: 'User search' +uuid: c0d6b9a7-09a7-415f-b71a-26957bef635c +status: '1' +langcode: en +path: user +title: Users +plugin: user_search +configuration: { } diff --git a/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearch.php b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearch.php new file mode 100644 index 0000000..15622bf --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Plugin/Search/UserSearch.php @@ -0,0 +1,131 @@ +get('database'), + $container->get('plugin.manager.entity'), + $container->get('module_handler'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * Creates a UserSearch object. + * + * @param Connection $database + * The database connection. + * @param EntityManager $entity_manager + * The entity manager. + * @param ModuleHandlerInterface $module_handler + * The module handler. + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + */ + public function __construct(Connection $database, EntityManager $entity_manager, ModuleHandlerInterface $module_handler, array $configuration, $plugin_id, array $plugin_definition) { + $this->database = $database; + $this->entityManager = $entity_manager; + $this->moduleHandler = $module_handler; + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public function execute() { + $results = array(); + if (!$this->isSearchExecutable()) { + return $results; + } + $keys = $this->keywords; + // Replace wildcards with MySQL/PostgreSQL wildcards. + $keys = preg_replace('!\*+!', '%', $keys); + $query = $this->database + ->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($query->orConditionGroup() + ->condition('name', '%' . $this->database->escapeLike($keys) . '%', 'LIKE') + ->condition('mail', '%' . $this->database->escapeLike($keys) . '%', 'LIKE') + ); + } + else { + // Regular users can only search via usernames, and we do not show them + // blocked accounts. + $query->condition('name', '%' . $this->database->escapeLike($keys) . '%', 'LIKE') + ->condition('status', 1); + } + $uids = $query + ->limit(15) + ->execute() + ->fetchCol(); + $accounts = $this->entityManager->getStorageController('user')->loadMultiple($uids); + + foreach ($accounts as $account_ng) { + $account = $account_ng->getBCEntity(); + $result = array( + 'title' => $account->getUsername(), + 'link' => url('user/' . $account->id(), array('absolute' => TRUE)), + ); + if (user_access('administer users')) { + $result['title'] .= ' (' . $account->getEmail() . ')'; + } + $results[] = $result; + } + + return $results; + } + +} diff --git a/core/modules/user/user.module b/core/modules/user/user.module index b94e78a..66b2e00 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1,12 +1,10 @@ 'Users', - ); -} - -/** * Implements hook_search_access(). */ function user_search_access() { @@ -525,51 +514,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->id(), array('absolute' => TRUE)), - ); - if (user_access('administer users')) { - $result['title'] .= ' (' . $account->getEmail() . ')'; - } - $results[] = $result; - } - - return $results; -} - -/** * Implements hook_user_view(). */ function user_user_view(UserInterface $account, EntityDisplay $display) {