diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php index 64c1c43..7bf78c2 100644 --- a/core/lib/Drupal/Core/Entity/EntityListController.php +++ b/core/lib/Drupal/Core/Entity/EntityListController.php @@ -55,6 +55,13 @@ class EntityListController implements EntityListControllerInterface, EntityContr protected $translationManager; /** + * The URL generator. + * + * @var \Drupal\Core\Routing\UrlGeneratorInterface + */ + protected $urlGenerator; + + /** * {@inheritdoc} */ public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { @@ -261,4 +268,30 @@ protected function getTitle() { return; } + /** + * Generates a URL or path for a specific route based on the given parameters. + * + * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for + * details on the arguments, usage, and possible exceptions. + * + * @return string + * The generated URL for the given route. + */ + public function url($route_name, $route_parameters = array(), $options = array()) { + return $this->urlGenerator()->generateFromRoute($route_name, $route_parameters, $options); + } + + /** + * Gets the URL generator. + * + * @return \Drupal\Core\Routing\UrlGeneratorInterface + * The URL generator. + */ + protected function urlGenerator() { + if (!$this->urlGenerator) { + $this->urlGenerator = \Drupal::urlGenerator(); + } + return $this->urlGenerator; + } + } 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 index 087944e..4d4cf8a 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php @@ -7,7 +7,6 @@ namespace Drupal\node\Plugin\Search; -use Drupal\Core\Annotation\Translation; use Drupal\Core\Config\Config; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\SelectExtender; @@ -16,14 +15,11 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\KeyValueStore\KeyValueStoreInterface; use Drupal\Core\Language\Language; -use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Access\AccessibleInterface; use Drupal\Core\Database\Query\Condition; -use Drupal\search\Annotation\SearchPlugin; -use Drupal\search\Plugin\SearchPluginBase; +use Drupal\search\Plugin\ConfigurableSearchPluginBase; use Drupal\search\Plugin\SearchIndexingInterface; - use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -35,7 +31,7 @@ * path = "node" * ) */ -class NodeSearch extends SearchPluginBase implements AccessibleInterface, SearchIndexingInterface, PluginFormInterface { +class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInterface, SearchIndexingInterface { /** * A database connection object. @@ -80,6 +76,13 @@ class NodeSearch extends SearchPluginBase implements AccessibleInterface, Search protected $account; /** + * @todo. + * + * @var array + */ + protected $rankings; + + /** * The list of options and info for advanced search filters. * * Each entry in the array has the option as the key and and for its value, an @@ -269,11 +272,10 @@ public function execute() { * A query object that has been extended with the Search DB Extender. */ protected function addNodeRankings(SelectExtender $query) { - if ($ranking = $this->moduleHandler->invokeAll('ranking')) { + if ($ranking = $this->getRankings()) { $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 ($node_rank = $this->configuration[$rank]) { // 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']); @@ -516,27 +518,53 @@ public function searchFormSubmit(array &$form, array &$form_state) { } /** + * @todo. + * + * @return array + */ + protected function getRankings() { + if (!$this->rankings) { + $this->rankings = array(); + foreach ($this->moduleHandler->invokeAll('ranking') as $var => $value) { + $this->rankings["node_rank_$var"] = $value; + } + } + return $this->rankings; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + $configuration = array(); + foreach ($this->getRankings() as $var => $value) { + $configuration[$var] = 0; + } + return $configuration; + } + + /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, array &$form_state) { // Output form for defining rank factor weights. $form['content_ranking'] = array( '#type' => 'details', - '#title' => t('Content ranking'), + '#title' => $this->t('Content ranking'), + '#theme' => 'node_search_admin', ); - $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.') . '' + '#value' => '' . $this->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( + foreach ($this->getRankings() as $var => $values) { + $form['content_ranking']['factors'][$var] = array( '#title' => $values['title'], '#type' => 'select', '#options' => $options, - '#default_value' => variable_get('node_rank_' . $var, 0), + '#default_value' => $this->configuration[$var], ); } return $form; @@ -545,18 +573,13 @@ public function buildConfigurationForm(array $form, array &$form_state) { /** * {@inheritdoc} */ - public function validateConfigurationForm(array &$form, array &$form_state) { - } - - /** - * {@inheritdoc} - */ public function submitConfigurationForm(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]); + foreach ($this->getRankings() as $var => $values) { + // @todo Restore theme_node_ranking or whatever. + if (isset($form_state['values'][$var])) { + $this->configuration[$var] = $form_state['values'][$var]; } } } + } diff --git a/core/modules/search/config/search.settings.yml b/core/modules/search/config/search.settings.yml index 78e0767..4c73a0b 100644 --- a/core/modules/search/config/search.settings.yml +++ b/core/modules/search/config/search.settings.yml @@ -1,8 +1,5 @@ -active_plugins: - node_search: node_search - user_search: user_search and_or_limit: 7 -default_plugin: node_search +default_type: node_search index: cron_limit: 100 overlap_cjk: true diff --git a/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php index b36d88e..cfd59f2 100644 --- a/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php +++ b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php @@ -8,8 +8,8 @@ namespace Drupal\search\Access; use Drupal\Core\Access\StaticAccessCheckInterface; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\search\SearchPluginManager; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; @@ -19,20 +19,20 @@ class SearchAccessCheck implements StaticAccessCheckInterface { /** - * The search plugin manager. + * The search storage. * - * @var \Drupal\search\SearchPluginManager + * @var \Drupal\search\SearchStorageInterface */ - protected $searchManager; + protected $searchStorage; /** - * Contructs a new search access check. + * Constructs a new search route subscriber. * - * @param SearchPluginManager $search_plugin_manager - * The search plugin manager. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. */ - public function __construct(SearchPluginManager $search_plugin_manager) { - $this->searchManager = $search_plugin_manager; + public function __construct(EntityManagerInterface $entity_manager) { + $this->searchStorage = $entity_manager->getStorageController('search'); } /** @@ -46,7 +46,7 @@ public function appliesTo() { * {@inheritdoc} */ public function access(Route $route, Request $request, AccountInterface $account) { - return $this->searchManager->getActiveDefinitions() ? static::ALLOW : static::DENY; + return $this->searchStorage->isSearchActive() ? static::ALLOW : static::DENY; } } diff --git a/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php index 9ecda1d..b297c1b 100644 --- a/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php +++ b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php @@ -27,8 +27,8 @@ public function appliesTo() { * {@inheritdoc} */ public function access(Route $route, Request $request, AccountInterface $account) { - $plugin_id = $route->getRequirement('_search_plugin_view_access'); - return $this->searchManager->pluginAccess($plugin_id, $account) ? static::ALLOW : static::DENY; + $entity_id = $route->getRequirement('_search_plugin_view_access'); + return $this->searchStorage->load($entity_id)->access('view', $account) ? static::ALLOW : static::DENY; } } diff --git a/core/modules/search/lib/Drupal/search/Controller/SearchController.php b/core/modules/search/lib/Drupal/search/Controller/SearchController.php index 98dae2b..1fb80f4 100644 --- a/core/modules/search/lib/Drupal/search/Controller/SearchController.php +++ b/core/modules/search/lib/Drupal/search/Controller/SearchController.php @@ -10,6 +10,7 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Form\FormBuilderInterface; +use Drupal\search\SearchInterface; use Drupal\search\SearchPluginManager; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -61,7 +62,7 @@ public static function create(ContainerInterface $container) { * * @param \Symfony\Component\HttpFoundation\Request $request * The request object. - * @param string $plugin_id + * @param string $entity_id * The ID of a search plugin. * @param string $keys * Search keywords. @@ -69,7 +70,7 @@ public static function create(ContainerInterface $container) { * @return array|\Symfony\Component\HttpFoundation\RedirectResponse * The search form and search results or redirect response. */ - public function view(Request $request, $plugin_id = NULL, $keys = NULL) { + public function view(Request $request, $entity_id = NULL, $keys = NULL) { $info = FALSE; $keys = trim($keys); // Also try to pull search keywords from the request to support old GET @@ -79,28 +80,16 @@ public function view(Request $request, $plugin_id = NULL, $keys = NULL) { } $build['#title'] = $this->t('Search'); - 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)) { + if (empty($entity_id)) { // 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; - } + $entity_id = search_get_default_type(); - return $this->redirect('search.view_' . $info['id']); + return $this->redirect('search.view_' . $entity_id); } - $plugin = $this->searchManager->createInstance($plugin_id); + /** @var $plugin \Drupal\search\Plugin\SearchInterface */ + $plugin = entity_load('search', $entity_id)->getPlugin(); $plugin->setSearch($keys, $request->query->all(), $request->attributes->all()); // Default results output is an empty string. $results = array('#markup' => ''); @@ -126,4 +115,54 @@ public function view(Request $request, $plugin_id = NULL, $keys = NULL) { return $build; } + /** + * Route title callback. + * + * @param \Drupal\search\SearchInterface $search + * The search entity. + * + * @return string + * The title for the search edit form. + */ + public function editTitle(SearchInterface $search) { + return $this->t('Edit @label search', array('@label' => $search->label())); + } + + /** + * Provides a list of eligible search plugins for configuring. + * + * @return array + * A list of search plugins that can be configured. + */ + public function addSearchType() { + $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) { + $search->$op()->save(); + return $this->redirect('search.settings'); + } + } diff --git a/core/modules/search/lib/Drupal/search/Entity/Search.php b/core/modules/search/lib/Drupal/search/Entity/Search.php new file mode 100644 index 0000000..9601c60 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Entity/Search.php @@ -0,0 +1,229 @@ +pluginBag = new DefaultSinglePluginBag(\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::config('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); + \Drupal::cache('cache')->deleteTags(array('local_task' => 1)); + } + + /** + * {@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/Form/SearchAddForm.php b/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php new file mode 100644 index 0000000..f4960a8 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php @@ -0,0 +1,27 @@ +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/SearchBlockForm.php b/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php index c943c93..7bb2332 100644 --- a/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php +++ b/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php @@ -62,17 +62,17 @@ public function submitForm(array &$form, array &$form_state) { } $form_id = $form['form_id']['#value']; - $info = search_get_default_plugin_info(); - if ($info) { + if ($entity = search_get_default_type()) { $form_state['redirect_route'] = array( - 'route_name' => 'search.view_' . $info['id'], + 'route_name' => 'search.view_' . $entity, 'route_parameters' => array( 'keys' => trim($form_state['values'][$form_id]), ), ); } else { - $this->setFormError('', $form_state, $this->t('Search is currently disabled.'), 'error'); + $this->setFormError('', $form_state, $this->t('Search is currently disabled.')); } } + } 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..e137f9c --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchDeleteForm.php @@ -0,0 +1,49 @@ +t('Are you sure you want to delete the %label search?', array('%label' => $this->entity->label())); + } + + /** + * {@inheritdoc} + */ + public function getCancelRoute() { + return array( + 'route_name' => 'search.settings', + ); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + $this->entity->delete(); + $form_state['redirect'] = 'admin/config/search/settings'; + drupal_set_message($this->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..a897251 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchEditForm.php @@ -0,0 +1,15 @@ +entityQuery = $entity_query; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $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' => $this->t('Label'), + '#description' => $this->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' => $this->t('Menu title'), + '#description' => $this->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' => $this->t('Path'), + '#field_prefix' => 'search/', + '#default_value' => $this->entity->getPath(), + '#maxlength' => '255', + ); + $form['plugin'] = array( + '#type' => 'value', + '#value' => $this->entity->get('plugin'), + ); + + if ($this->plugin instanceof PluginFormInterface) { + $form += $this->plugin->buildConfigurationForm($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) { + $this->setFormError('path', $this->t('The search path must be unique')); + } + + if ($this->plugin instanceof PluginFormInterface) { + $this->plugin->validateConfigurationForm($form, $form_state); + } + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + parent::submit($form, $form_state); + + if ($this->plugin instanceof PluginFormInterface) { + $this->plugin->submitConfigurationForm($form, $form_state); + } + return $this->entity; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, array &$form_state) { + $this->entity->save(); + drupal_set_message($this->t('The search has been successfully saved.')); + + $form_state['redirect_route']['route_name'] = 'search.settings'; + } + +} diff --git a/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php b/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php deleted file mode 100644 index d7e566f..0000000 --- a/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php +++ /dev/null @@ -1,281 +0,0 @@ -searchSettings = $config_factory->get('search.settings'); - $this->searchPluginManager = $manager; - $this->moduleHandler = $module_handler; - $this->state = $state; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('config.factory'), - $container->get('config.context.free'), - $container->get('plugin.manager.search'), - $container->get('module_handler'), - $container->get('state') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'search_admin_settings'; - } - - /** - * Returns names of available search plugins. - * - * @return array - * An array of the names of available search plugins. - */ - protected function getOptions() { - $options = array(); - foreach ($this->searchPluginManager->getDefinitions() as $plugin_id => $search_info) { - $options[$plugin_id] = $search_info['title'] . ' (' . $plugin_id . ')'; - } - asort($options, SORT_STRING); - return $options; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, array &$form_state) { - - // Collect some stats. - $remaining = 0; - $total = 0; - - foreach ($this->searchPluginManager->getActiveIndexingPlugins() as $plugin) { - if ($status = $plugin->indexStatus()) { - $remaining += $status['remaining']; - $total += $status['total']; - } - } - $active_plugins = $this->searchPluginManager->getActivePlugins(); - $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 = '
' . $this->t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '
'; - $form['status'] = array( - '#type' => 'details', - '#title' => $this->t('Indexing status'), - ); - $form['status']['status'] = array('#markup' => $status); - $form['status']['wipe'] = array( - '#type' => 'submit', - '#value' => $this->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' => $this->t('Indexing throttle') - ); - $form['indexing_throttle']['cron_limit'] = array( - '#type' => 'select', - '#title' => $this->t('Number of items to index per cron run'), - '#default_value' => $this->searchSettings->get('index.cron_limit'), - '#options' => $items, - '#description' => $this->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' => $this->url('system.status'))) - ); - // Indexing settings: - $form['indexing_settings'] = array( - '#type' => 'details', - '#title' => $this->t('Indexing settings') - ); - $form['indexing_settings']['info'] = array( - '#markup' => $this->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' => $this->t('Minimum word length to index'), - '#default_value' => $this->searchSettings->get('index.minimum_word_size'), - '#min' => 1, - '#max' => 1000, - '#description' => $this->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' => $this->t('Simple CJK handling'), - '#default_value' => $this->searchSettings->get('index.overlap_cjk'), - '#description' => $this->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' => $this->t('Active search plugins') - ); - $options = $this->getOptions(); - $form['active']['active_plugins'] = array( - '#type' => 'checkboxes', - '#title' => $this->t('Active plugins'), - '#title_display' => 'invisible', - '#default_value' => $this->searchSettings->get('active_plugins'), - '#options' => $options, - '#description' => $this->t('Choose which search plugins are active from the available plugins.') - ); - $form['active']['default_plugin'] = array( - '#title' => $this->t('Default search plugin'), - '#type' => 'radios', - '#default_value' => $this->searchSettings->get('default_plugin'), - '#options' => $options, - '#description' => $this->t('Choose which search plugin is the default.') - ); - - // Per plugin settings. - foreach ($active_plugins as $plugin) { - if ($plugin instanceof PluginFormInterface) { - $form = $plugin->buildConfigurationForm($form, $form_state); - } - } - // Set #submit so we are sure it's invoked even if one of - // the active search plugins 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'] != $this->t('Reset to defaults')) { - $new_plugins = array_filter($form_state['values']['active_plugins']); - $default = $form_state['values']['default_plugin']; - if (!in_array($default, $new_plugins, TRUE)) { - $this->setFormError('default_plugin', $form_state, $this->t('Your default search plugin is not selected as an active plugin.')); - } - } - // Handle per-plugin validation logic. - foreach ($this->searchPluginManager->getActivePlugins() as $plugin) { - if ($plugin instanceof PluginFormInterface) { - $plugin->validateConfigurationForm($form, $form_state); - } - } - } - - /** - * {@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($this->t('The index will be rebuilt.')); - search_reindex(); - } - $this->searchSettings->set('index.cron_limit', $form_state['values']['cron_limit']); - $this->searchSettings->set('default_plugin', $form_state['values']['default_plugin']); - - // Handle per-plugin submission logic. - foreach ($this->searchPluginManager->getActivePlugins() as $plugin) { - if ($plugin instanceof PluginFormInterface) { - $plugin->submitConfigurationForm($form, $form_state); - } - } - - // Check whether we are resetting the values. - if ($form_state['triggering_element']['#value'] == $this->t('Reset to defaults')) { - $new_plugins = array('node_search', 'user_search'); - } - else { - $new_plugins = array_filter($form_state['values']['active_plugins']); - } - if ($this->searchSettings->get('active_plugins') != $new_plugins) { - $this->searchSettings->set('active_plugins', $new_plugins); - drupal_set_message($this->t('The active search plugins have been changed.')); - $this->state->set('menu_rebuild_needed', TRUE); - Cache::deleteTags(array('local_task' => 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_route']['route_name'] = 'search.reindex_confirm'; - } - -} 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..ef771d9 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Plugin/ConfigurableSearchPluginBase.php @@ -0,0 +1,54 @@ +configuration += $this->defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, array &$form_state) { + } + +} diff --git a/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php b/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php index f18e699..6adf06d 100644 --- a/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php +++ b/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php @@ -20,19 +20,22 @@ class SearchLocalTask extends DerivativeBase { public function getDerivativeDefinitions(array $base_plugin_definition) { $this->derivatives = array(); - $default_info = search_get_default_plugin_info(); - if ($default_info) { - foreach (\Drupal::service('plugin.manager.search')->getActiveDefinitions() as $plugin_id => $search_info) { - $this->derivatives[$plugin_id] = array( - 'title' => $search_info['title'], - 'route_name' => 'search.view_' . $plugin_id, - 'tab_root_id' => 'search.plugins:' . $default_info['id'], + if ($default = \Drupal::config('search.settings')->get('default_type')) { + $search_storage = \Drupal::entityManager()->getStorageController('search'); + foreach ($search_storage->getActiveSearches() as $entity_id => $entity) { + /** @var $entity \Drupal\search\SearchInterface */ + $plugin = $entity->getPluginDefinition(); + // @todo Swap the plugin title and entity label. + $this->derivatives[$entity_id] = array( + 'title' => $plugin['title'], + 'route_name' => 'search.view_' . $entity_id, + 'tab_root_id' => 'search.local_tasks:' . $default, ); - if ($plugin_id == $default_info['id']) { - $this->derivatives[$plugin_id]['weight'] = -10; + if ($entity_id == $default) { + $this->derivatives[$entity_id]['weight'] = -10; } else { - $this->derivatives[$plugin_id]['weight'] = 0; + $this->derivatives[$entity_id]['weight'] = 0; } } } diff --git a/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php b/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php index 7f534d5..ec808ad 100644 --- a/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php +++ b/core/modules/search/lib/Drupal/search/Plugin/SearchPluginBase.php @@ -9,6 +9,7 @@ use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines a base class for plugins wishing to support search. @@ -39,6 +40,13 @@ /** * {@inheritdoc} */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ public function setSearch($keywords, array $parameters, array $attributes) { $this->keywords = (string) $keywords; $this->searchParameters = $parameters; diff --git a/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php b/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php index 258d8db..ee62b82 100644 --- a/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php +++ b/core/modules/search/lib/Drupal/search/Routing/SearchRouteSubscriber.php @@ -7,8 +7,8 @@ namespace Drupal\search\Routing; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Routing\RouteSubscriberBase; -use Drupal\search\SearchPluginManager; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -18,41 +18,41 @@ class SearchRouteSubscriber extends RouteSubscriberBase { /** - * The search plugin manager. + * The search storage. * - * @var \Drupal\search\SearchPluginManager + * @var \Drupal\search\SearchStorageInterface */ - protected $searchManager; + protected $searchStorage; /** * Constructs a new search route subscriber. * - * @param \Drupal\search\SearchPluginManager $search_plugin_manager - * The search plugin manager. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. */ - public function __construct(SearchPluginManager $search_plugin_manager) { - $this->searchManager = $search_plugin_manager; + public function __construct(EntityManagerInterface $entity_manager) { + $this->searchStorage = $entity_manager->getStorageController('search'); } /** * {@inheritdoc} */ protected function routes(RouteCollection $collection) { - foreach ($this->searchManager->getActiveDefinitions() as $plugin_id => $search_info) { - $path = 'search/' . $search_info['path'] . '/{keys}'; + foreach ($this->searchStorage->getActiveSearches() as $entity_id => $entity) { + $path = 'search/' . $entity->getPath() . '/{keys}'; $defaults = array( '_content' => 'Drupal\search\Controller\SearchController::view', - '_title' => $search_info['title'], - 'plugin_id' => $plugin_id, + '_title' => $entity->label(), + 'entity_id' => $entity_id, 'keys' => '', ); $requirements = array( 'keys' => '.+', - '_search_plugin_view_access' => $plugin_id, + '_search_plugin_view_access' => $entity_id, '_permission' => 'search content', ); $route = new Route($path, $defaults, $requirements); - $collection->add('search.view_' . $plugin_id, $route); + $collection->add('search.view_' . $entity_id, $route); } } 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..9f6dd51 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/SearchAccessController.php @@ -0,0 +1,37 @@ +isDefaultSearch()) { + return FALSE; + } + if ($operation == 'view') { + if (!$entity->status()) { + return FALSE; + } + $plugin = $entity->getPlugin(); + if ($plugin instanceof AccessibleInterface) { + return $plugin->access($operation, $account); + } + return TRUE; + } + return parent::checkAccess($entity, $operation, $langcode, $account); + } + +} diff --git a/core/modules/search/lib/Drupal/search/SearchInterface.php b/core/modules/search/lib/Drupal/search/SearchInterface.php new file mode 100644 index 0000000..19618e1 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/SearchInterface.php @@ -0,0 +1,72 @@ +configFactory = $config_factory; + $this->configFactory->enterContext($context); + + $this->formBuilder = $form_builder; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { + return new static( + $entity_type, + $entity_info, + $container->get('entity.manager')->getStorageController($entity_type), + $container->get('module_handler'), + $container->get('config.factory'), + $container->get('config.context.free'), + $container->get('form_builder') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'search_admin_settings'; + } + + /** + * {@inheritdoc} + */ + public function render() { + return $this->formBuilder->getForm($this); + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header['label'] = array( + 'data' => $this->t('Label'), + ); + $header['plugin'] = array( + 'data' => $this->t('Plugin'), + 'class' => array(RESPONSIVE_PRIORITY_LOW), + ); + $header['status'] = array( + 'data' => $this->t('Status'), + 'class' => array(RESPONSIVE_PRIORITY_LOW), + ); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + $row['label'] = $this->getLabel($entity); + $definition = $entity->getPlugin()->getPluginDefinition(); + $row['plugin'] = $definition['title']; + $row['status'] = $entity->status() ? $this->t('Enabled') : $this->t('Disabled'); + return $row + parent::buildRow($entity); + } + + /** + * {@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 instanceof SearchIndexingInterface && $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 = '' . $this->t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '
'; + $form['status'] = array( + '#type' => 'details', + '#title' => $this->t('Indexing status'), + ); + $form['status']['status'] = array('#markup' => $status); + $form['status']['wipe'] = array( + '#type' => 'submit', + '#value' => $this->t('Re-index site'), + '#submit' => array(array($this, 'searchAdminReindexSubmit')), + ); + + $items = MapArray::copyValuesToKeys(array(10, 20, 50, 100, 200, 500)); + + // Indexing throttle: + $form['indexing_throttle'] = array( + '#type' => 'details', + '#title' => $this->t('Indexing throttle') + ); + $form['indexing_throttle']['cron_limit'] = array( + '#type' => 'select', + '#title' => $this->t('Number of items to index per cron run'), + '#default_value' => $search_settings->get('index.cron_limit'), + '#options' => $items, + '#description' => $this->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' => $this->url('system.status'))) + ); + // Indexing settings: + $form['indexing_settings'] = array( + '#type' => 'details', + '#title' => $this->t('Indexing settings') + ); + $form['indexing_settings']['info'] = array( + '#markup' => $this->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' => $this->t('Minimum word length to index'), + '#default_value' => $search_settings->get('index.minimum_word_size'), + '#min' => 1, + '#max' => 1000, + '#description' => $this->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' => $this->t('Simple CJK handling'), + '#default_value' => $search_settings->get('index.overlap_cjk'), + '#description' => $this->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' => $this->t('Search types'), + ); + $form['search_types']['inline_actions'] = array( + '#prefix' => '