diff --git c/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php w/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php new file mode 100644 index 0000000..d607fd7 --- /dev/null +++ w/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php @@ -0,0 +1,51 @@ +searchManager = $search_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public function applies(Route $route) { + return array_key_exists('search_content', $route->getRequirements()); + } + + /** + * {@inheritdoc} + */ + public function access(Route $route, Request $request) { + return $this->searchManager->getActiveDefinitions() ? static::ALLOW : static::DENY; + } + +} diff --git c/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php w/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php new file mode 100644 index 0000000..f82e52e --- /dev/null +++ w/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php @@ -0,0 +1,28 @@ +attributes->get('_account'); + $requirements = $route->getRequirements(); + $plugin_id = $requirements['_search_plugin_view_access']; + $access = !empty($account) && $account->hasPermission('search content'); + return $access && $this->searchManager->pluginAccess($plugin_id, $account); + } +} diff --git c/core/modules/search/lib/Drupal/search/Controller/SearchController.php w/core/modules/search/lib/Drupal/search/Controller/SearchController.php new file mode 100644 index 0000000..8a6b0fd --- /dev/null +++ w/core/modules/search/lib/Drupal/search/Controller/SearchController.php @@ -0,0 +1,113 @@ +searchManager = $search_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('plugin.manager.search')); + } + + /** + * @param Symfony\Component\HttpFoundation\Request $request + * The request object. + * @param string $plugin_id + * The id of a plugin, i.e. the data type. + * @param string $keys + * search keywords. + * + * @return array + * The search form and search results. + * + */ + public function view(Request $request, $plugin_id = NULL, $keys = NULL) { + $info = FALSE; + $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 && $request->query->has('keys')) { + $keys = trim($request->query->get('keys')); + } + + if (!empty($plugin_id)) { + $active_plugin_info = $this->searchManager->getActiveDefinitions(); + if (isset($active_plugin_info[$plugin_id])) { + $info = $active_plugin_info[$plugin_id]; + } + } + + if (empty($plugin_id) || empty($info)) { + // No path or invalid path: find the default plugin. Note that if there + // are no enabled search plugins, this function should never be called, + // since hook_menu() would not have defined any search paths. + $info = search_get_default_plugin_info(); + // Redirect from bare /search or an invalid path to the default search + // path. + $path = 'search/' . $info['path']; + if ($keys) { + $path .= '/' . $keys; + } + return $this->redirect($this->getUrlGenerator()->generateFromPath($path, array('absolute' => TRUE))); + } + $plugin = $this->searchManager->createInstance($plugin_id); + $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, + // search_form_submit() will cause a redirect to search/[path]/[keys], + // which will get us back to this page callback. In other words, the search + // form submits with POST but redirects to GET. This way we can keep + // the search query URL clean as a whistle. + if ($request->request->has('form_id') || $request->request->get('form_id') != 'search_form') { + // Only search if there are keywords or non-empty conditions. + if ($plugin->isSearchExecutable()) { + // Log the search keys. + watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); + + // Collect the search results. + $results = $plugin->buildResults(); + } + } + // The form may be altered based on whether the search was run. + $build['search_form'] = drupal_get_form(SearchForm::create($this->container), $plugin_id); + $build['search_results'] = $results; + return $build; + } + +} diff --git c/core/modules/search/lib/Drupal/search/Form/SearchForm.php w/core/modules/search/lib/Drupal/search/Form/SearchForm.php new file mode 100644 index 0000000..090e044 --- /dev/null +++ w/core/modules/search/lib/Drupal/search/Form/SearchForm.php @@ -0,0 +1,120 @@ +get('plugin.manager.search') + ); + } + + /** + * Constructs a search form. + * + * @param Drupal\search\SearchPluginManager + * The search plugin manager. + * + */ + public function __construct(SearchInterface $search_plugin) { + $this->searchManager = $search_plugin; + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'search_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state, $plugin_id = NULL) { + $plugin = $this->searchManager->createInstance($plugin_id); + $plugin_info = $plugin->getPluginDefinition(); + + if (!$action) { + $action = 'search/' . $plugin_info['path']; + } + if (!isset($prompt)) { + $prompt = $this->t('Enter your keywords'); + } + + $form['#action'] = $this->getUrlGenerator()->generateFromPath($action); + // Record the $action for later use in redirecting. + $form_state['action'] = $action; + $form['plugin_id'] = array( + '#type' => 'value', + '#value' => $plugin->getPluginId(), + ); + $form['basic'] = array( + '#type' => 'container', + '#attributes' => array( + 'class' => array('container-inline'), + ), + ); + $form['basic']['keys'] = array( + '#type' => 'search', + '#title' => $prompt, + '#default_value' => $plugin->getKeywords(), + '#size' => $prompt ? 40 : 20, + '#maxlength' => 255, + ); + // processed_keys is used to coordinate keyword passing between other forms + // that hook into the basic search form. + $form['basic']['processed_keys'] = array( + '#type' => 'value', + '#value' => '', + ); + $form['basic']['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Search'), + ); + // Allow the plugin to add to or alter the search form. + $plugin->searchFormAlter($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array $form, array &$form_state) { + form_set_value($form['basic']['processed_keys'], trim($form_state['values']['keys']), $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array $form, array &$form_state) { + $keys = $form_state['values']['processed_keys']; + if ($keys == '') { + form_set_error('keys', t('Please enter some keywords.')); + // Fall through to the form redirect. + } + + $form_state['redirect'] = $form_state['action'] . '/' . $keys; + } +} diff --git c/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php w/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php new file mode 100644 index 0000000..ada6075 --- /dev/null +++ w/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php @@ -0,0 +1,71 @@ +searchPluginManager = $search_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[RoutingEvents::DYNAMIC] = 'routes'; + return $events; + } + + /** + * Adds routes for search. + * + * @param \Drupal\Core\Routing\RouteBuildEvent $event + * The route building event. + */ + public function routes(RouteBuildEvent $event) { + $collection = $event->getRouteCollection(); + + $default_info = search_get_default_plugin_info(); + if ($default_info) { + foreach ($this->searchPluginManager->getActiveDefinitions() as $plugin_id => $search_info) { + $route = new Route( + 'search/' . $search_info['path'], + array( + '_content' => 'Drupal\search\Controller\SearchController::searchView', + 'plugin_id' => $plugin_id, + 'keys' => '', + ), + array('_search_plugin_view_access' => $plugin_id) + ); + $collection->add('search_view.' . $plugin_id, $route); + } + } + } +} diff --git c/core/modules/search/search.module w/core/modules/search/search.module index a4830ad..5b7e6de 100644 --- c/core/modules/search/search.module +++ w/core/modules/search/search.module @@ -152,11 +152,8 @@ function search_preprocess_block(&$variables) { function search_menu() { $items['search'] = array( 'title' => 'Search', - 'page callback' => 'search_view', - 'page arguments' => array(NULL, '', ''), - 'access callback' => 'search_is_active', 'type' => MENU_SUGGESTED_ITEM, - 'file' => 'search.pages.inc', + 'route_name' => 'search_view', ); $items['admin/config/search/settings'] = array( 'title' => 'Search settings', @@ -183,10 +180,7 @@ function search_menu() { $path = 'search/' . $search_info['path']; $items[$path] = array( 'title' => $search_info['title'], - 'page callback' => 'search_view', - 'page arguments' => array($plugin_id, ''), - 'access callback' => '_search_menu_access', - 'access arguments' => array($plugin_id), + 'route_name' => 'search_view', 'type' => MENU_LOCAL_TASK, 'file' => 'search.pages.inc', 'weight' => $plugin_id == $default_info['id'] ? -10 : 0, @@ -214,15 +208,6 @@ function search_menu() { } /** - * Determines access for the 'search' path. - */ -function search_is_active() { - // This path cannot be accessed if there are no active plugins. - $account = Drupal::request()->attributes->get('_account'); - return !empty($account) && $account->hasPermission('search content') && Drupal::service('plugin.manager.search')->getActiveDefinitions(); -} - -/** * Returns information about the default search plugin. * * @return array @@ -240,26 +225,6 @@ function search_get_default_plugin_info() { } /** - * Access callback: Determines access for a search page. - * - * @param string $plugin_id - * The name of a search plugin (e.g., 'node_search'). - * - * @return bool - * TRUE if a user has access to the search page; FALSE otherwise. - * - * @see search_menu() - */ -function _search_menu_access($plugin_id) { - $account = Drupal::request()->attributes->get('_account'); - // @todo - remove the empty() check once we are more confident - // that the account will be populated, especially during tests. - // @see https://drupal.org/node/2032553 - $access = !empty($account) && $account->hasPermission('search content'); - return $access && Drupal::service('plugin.manager.search')->pluginAccess($plugin_id, $account); -} - -/** * Clears either a part of, or the entire search index. * * @param $sid @@ -693,60 +658,6 @@ function search_mark_for_reindex($type, $sid) { */ /** - * Form constructor for the search form. - * - * @param \Drupal\search\Plugin\SearchInterface $plugin - * A search plugin instance to render the form for. - * @param $action - * Form action. Defaults to "search/$path", where $path is the search path - * associated with the plugin in its definition. This will be run through - * url(). - * @param $prompt - * Label for the keywords field. Defaults to t('Enter your keywords') if - * NULL. Supply '' to omit. - * - * @see search_form_validate() - * @see search_form_submit() - * - * @ingroup forms - */ -function search_form($form, &$form_state, SearchInterface $plugin, $action = '', $prompt = NULL) { - - $plugin_info = $plugin->getPluginDefinition(); - - if (!$action) { - $action = 'search/' . $plugin_info['path']; - } - if (!isset($prompt)) { - $prompt = t('Enter your keywords'); - } - - $form['#action'] = url($action); - // Record the $action for later use in redirecting. - $form_state['action'] = $action; - $form['plugin_id'] = array('#type' => 'value', '#value' => $plugin->getPluginId()); - $form['basic'] = array('#type' => 'container', '#attributes' => array('class' => array('container-inline'))); - $form['basic']['keys'] = array( - '#type' => 'search', - '#title' => $prompt, - '#default_value' => $plugin->getKeywords(), - '#size' => $prompt ? 40 : 20, - '#maxlength' => 255, - ); - // processed_keys is used to coordinate keyword passing between other forms - // that hook into the basic search form. - $form['basic']['processed_keys'] = array('#type' => 'value', '#value' => ''); - $form['basic']['submit'] = array('#type' => 'submit', '#value' => t('Search')); - // Make sure the default validate and submit handlers are added. - $form['#validate'][] = 'search_form_validate'; - $form['#submit'][] = 'search_form_submit'; - // Allow the plugin to add to or alter the search form. - $plugin->searchFormAlter($form, $form_state); - - return $form; -} - -/** * Form constructor for the search block's search box. * * @param $form_id diff --git c/core/modules/search/search.pages.inc w/core/modules/search/search.pages.inc index 804e4be..50df3cb 100644 --- c/core/modules/search/search.pages.inc +++ w/core/modules/search/search.pages.inc @@ -9,70 +9,6 @@ use Symfony\Component\HttpFoundation\RedirectResponse; /** - * Page callback: Presents the search form and/or search results. - * - * @param $plugin_id - * Search plugin_id to use for the search. - * @param $keys - * Keywords to use for the search. - */ -function search_view($plugin_id = NULL, $keys = '') { - $info = FALSE; - $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']); - } - - $manager = Drupal::service('plugin.manager.search'); - if (!empty($plugin_id)) { - $active_plugin_info = $manager->getActiveDefinitions(); - if (isset($active_plugin_info[$plugin_id])) { - $info = $active_plugin_info[$plugin_id]; - } - } - - if (empty($plugin_id) || empty($info)) { - // No path or invalid path: find the default plugin. Note that if there - // are no enabled search plugins, this function should never be called, - // since hook_menu() would not have defined any search paths. - $info = search_get_default_plugin_info(); - // Redirect from bare /search or an invalid path to the default search path. - $path = 'search/' . $info['path']; - if ($keys) { - $path .= '/' . $keys; - } - return new RedirectResponse(url($path, array('absolute' => TRUE))); - } - $plugin = $manager->createInstance($plugin_id); - $request = Drupal::request(); - $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, - // search_form_submit() will cause a redirect to search/[path]/[keys], - // which will get us back to this page callback. In other words, the search - // 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') { - // Only search if there are keywords or non-empty conditions. - if ($plugin->isSearchExecutable()) { - // Log the search keys. - watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); - - // Collect the search results. - $results = $plugin->buildResults(); - } - } - // The form may be altered based on whether the search was run. - $build['search_form'] = drupal_get_form('search_form', $plugin); - $build['search_results'] = $results; - - return $build; -} - -/** * Prepares variables for search results templates. * * Default template: search-results.html.twig. @@ -149,31 +85,3 @@ function template_preprocess_search_result(&$variables) { $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['plugin_id']; } -/** - * Form validation handler for search_form(). - * - * As the search form collates keys from other modules hooked in via - * hook_form_alter, the validation takes place in search_form_submit(). - * search_form_validate() is used solely to set the 'processed_keys' form - * value for the basic search form. - * - * @see search_form_submit() - */ -function search_form_validate($form, &$form_state) { - form_set_value($form['basic']['processed_keys'], trim($form_state['values']['keys']), $form_state); -} - -/** - * Form submission handler for search_form(). - * - * @see search_form_validate() - */ -function search_form_submit($form, &$form_state) { - $keys = $form_state['values']['processed_keys']; - if ($keys == '') { - form_set_error('keys', t('Please enter some keywords.')); - // Fall through to the form redirect. - } - - $form_state['redirect'] = $form_state['action'] . '/' . $keys; -} diff --git c/core/modules/search/search.routing.yml w/core/modules/search/search.routing.yml index 8acf650..3e76916 100644 --- c/core/modules/search/search.routing.yml +++ w/core/modules/search/search.routing.yml @@ -1,12 +1,23 @@ search_settings: pattern: '/admin/config/search/settings' defaults: - _form: 'Drupal\search\Form\SearchSettingsForm' + _form: '\Drupal\search\Form\SearchSettingsForm' requirements: _permission: 'administer search' + search_reindex_confirm: pattern: '/admin/config/search/settings/reindex' defaults: - _form: 'Drupal\search\Form\ReindexConfirm' + _form: '\Drupal\search\Form\ReindexConfirm' requirements: _permission: 'administer search' + +search_view: + pattern: '/search/{plugin_id}/{keys}' + defaults: + _content: '\Drupal\search\Controller\SearchController::view' + plugin_id: NULL + keys: '' + requirements: + _permission: 'search content' + _access_mode: 'ALL' diff --git c/core/modules/search/search.services.yml w/core/modules/search/search.services.yml index 22dc7f2..8af9864 100644 --- c/core/modules/search/search.services.yml +++ w/core/modules/search/search.services.yml @@ -2,3 +2,21 @@ services: plugin.manager.search: class: Drupal\search\SearchPluginManager arguments: ['@container.namespaces', '@config.factory'] + + access_check.search: + class: Drupal\search\Access\SearchAccessCheck + arguments: ['@plugin.manager.search'] + tags: + - { name: access_check } + + access_check.search_plugin: + class: Drupal\search\Access\SearchPluginAccessCheck + arguments: ['@plugin.manager.search'] + tags: + - { name: access_check } + + route_subscriber.search: + class: Drupal\search\Routing\SearchRouteSubscriber + arguments: ['@plugin.manager.search'] + tags: + - { name: event_subscriber }