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..5c73892 --- /dev/null +++ b/core/modules/node/config/search.search.node_search.yml @@ -0,0 +1,7 @@ +id: node_search +label: Node +uuid: 25687eeb-4bb5-469c-ad05-5eb24cd7012c +status: '1' +langcode: en +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 6c194d4..04dbdfb 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Search/NodeSearch.php @@ -14,9 +14,8 @@ use Drupal\Core\Entity\EntityManager; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\KeyValueStore\KeyValueStoreInterface; -use Drupal\search\Plugin\SearchPluginBase; +use Drupal\search\Plugin\ConfigurableSearchPluginBase; use Drupal\search\Annotation\SearchPlugin; - use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -28,7 +27,7 @@ * path = "node" * ) */ -class NodeSearch extends SearchPluginBase { +class NodeSearch extends ConfigurableSearchPluginBase { /** * A database connection object. @@ -298,7 +297,7 @@ public function indexStatus() { /** * {@inheritdoc} */ - public function addToAdminForm(array &$form, array &$form_state) { + public function form(array $form, array &$form_state) { // Output form for defining rank factor weights. $form['content_ranking'] = array( '#type' => 'details', @@ -319,12 +318,13 @@ public function addToAdminForm(array &$form, array &$form_state) { '#default_value' => variable_get('node_rank_' . $var, 0), ); } + return $form; } /** * {@inheritdoc} */ - public function submitAdminForm(array &$form, array &$form_state) { + 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. diff --git a/core/modules/search/config/search.settings.yml b/core/modules/search/config/search.settings.yml index a1a88b4..ea419e9 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_plugin: node_search index: cron_limit: '100' overlap_cjk: '1' diff --git a/core/modules/search/lib/Drupal/search/Controller/SearchController.php b/core/modules/search/lib/Drupal/search/Controller/SearchController.php new file mode 100644 index 0000000..eab40a9 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Controller/SearchController.php @@ -0,0 +1,72 @@ +searchManager = $search_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.search') + ); + } + + /** + * @return array + */ + public function addSearchConfig() { + 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, + ); + } + + public function performOperation($search, $op) { + // Perform the operation. + $search->$op()->save(); + \Drupal::state()->set('menu_rebuild_needed', TRUE); + return new RedirectResponse(url('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..6e11b77 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchAddForm.php @@ -0,0 +1,33 @@ +entity->setPlugin($search_plugin_id); + $definition = $this->entity->getPluginDefinition(); + $this->entity->set('label', $definition['title']); + return parent::buildForm($form, $form_state); + } + +} 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..f310210 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Form/SearchFormBase.php @@ -0,0 +1,167 @@ +storageController = $storage_controller; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { + return new static( + $container->get('module_handler'), + $container->get('plugin.manager.entity')->getStorageController($entity_type) + ); + } + + /** + * {@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'), + '#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['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) { + $search = $this->storageController->load($id); + return !empty($search); + } + + /** + * {@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); + + 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 b/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php deleted file mode 100644 index 4d52e69..0000000 --- a/core/modules/search/lib/Drupal/search/Form/SearchSettingsForm.php +++ /dev/null @@ -1,266 +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('plugin.manager.search'), - $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() { - $options = array(); - $names = system_get_module_info('name'); - foreach ($this->searchPluginManager->getDefinitions() as $search_info) { - if (isset($names[$search_info['module']])) { - $options[$search_info['module']] = $names[$search_info['module']]; - } - } - asort($options, SORT_STRING); - return $options; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, array &$form_state) { - // Collect some stats. - $remaining = 0; - $total = 0; - $active_plugins = $this->searchPluginManager->getActivePlugins(); - foreach ($active_plugins as $plugin) { - if ($status = $plugin->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' => $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 plugin settings - foreach ($active_plugins as $plugin) { - $plugin->addToAdminForm($form, $form_state); - } - // 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']); - - // Handle per-plugin submission logic. - foreach ($this->searchPluginManager->getActivePlugins() as $plugin) { - $plugin->submitAdminForm($form, $form_state); - } - - // 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'; - } - -} 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..3018f3b --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Plugin/Core/Entity/Search.php @@ -0,0 +1,180 @@ +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 uri() { + 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( + 'plugin', + 'configuration', + ); + foreach ($names as $name) { + $properties[$name] = $this->get($name); + } + return $properties; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $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 static function sort($a, $b) { + if ($a->status != $b->status) { + return ($a->status > $b->status) ? -1 : 1; + } + $a_weight = isset($a->weight) ? $a->weight : 0; + $b_weight = isset($b->weight) ? $b->weight : 0; + if ($a_weight == $b_weight) { + $a_label = $a->label(); + $b_label = $b->label(); + return strnatcasecmp($a_label, $b_label); + } + return ($a_weight < $b_weight) ? -1 : 1; + } + +} diff --git a/core/modules/search/lib/Drupal/search/Plugin/Menu/LocalAction/SearchAddLocalAction.php b/core/modules/search/lib/Drupal/search/Plugin/Menu/LocalAction/SearchAddLocalAction.php new file mode 100644 index 0000000..d5ee2d8 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Plugin/Menu/LocalAction/SearchAddLocalAction.php @@ -0,0 +1,24 @@ + 0, 'total' => 0); } - /** - * {@inheritdoc} - */ - public function addToAdminForm(array &$form, array &$form_state) { - // Empty default implementation. - } - - /** - * {@inheritdoc} - */ - public function submitAdminForm(array &$form, array &$form_state) { - // Empty default implementation. - } - } 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..c4b948a --- /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'] = t('Label'); + $header['plugin'] = t('Plugin'); + $header['status'] = t('Status'); + $header['operations'] = 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; + $active_plugins = search_get_active_search_plugins(); + foreach ($active_plugins as $plugin) { + if ($status = $plugin->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_plugins'] = array( + '#type' => 'details', + '#title' => t('Search plugins'), + ); + $form['search_plugins']['inline_actions'] = array( + '#prefix' => '