Commit fa17da86 authored by catch's avatar catch

Issue #2042807 by tim.plunkett, pwolanin, ianthomas_uk, jhodgdon: Convert...

Issue #2042807 by tim.plunkett, pwolanin, ianthomas_uk, jhodgdon: Convert search plugins to use a ConfigEntity and a PluginBag.
parent bbed679f
......@@ -480,6 +480,7 @@ function menu_get_item($path = NULL, $router_item = NULL) {
if (\Drupal::state()->get('menu_rebuild_needed') || !\Drupal::state()->get('menu.masks')) {
menu_router_rebuild();
\Drupal::service('router.builder')->rebuild();
\Drupal::cache()->deleteTags(array('local_task' => 1));
}
$original_map = arg(NULL, $path);
......
......@@ -39,6 +39,13 @@ abstract class DraggableListController extends ConfigEntityListController implem
*/
protected $weightKey = FALSE;
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* {@inheritdoc}
*/
......@@ -88,7 +95,7 @@ public function buildRow(EntityInterface $entity) {
*/
public function render() {
if (!empty($this->weightKey)) {
return drupal_get_form($this);
return $this->formBuilder()->getForm($this);
}
return parent::render();
}
......@@ -149,4 +156,17 @@ public function submitForm(array &$form, array &$form_state) {
}
}
/**
* Returns the form builder.
*
* @return \Drupal\Core\Form\FormBuilderInterface
* The form builder.
*/
protected function formBuilder() {
if (!$this->formBuilder) {
$this->formBuilder = \Drupal::formBuilder();
}
return $this->formBuilder;
}
}
......@@ -75,3 +75,15 @@ node.settings.node:
submitted:
type: boolean
label: 'Display setting for author and date Submitted by post information'
# Plugin \Drupal\node\Plugin\Search\NodeSearch
search.plugin.node_search:
type: mapping
label: 'Content search'
mapping:
rankings:
type: sequence
label: 'Content ranking'
sequence:
- type: integer
label: 'Influence'
id: node_search
label: Content
uuid: 25687eeb-4bb5-469c-ad05-5eb24cd7012c
status: true
langcode: en
path: node
weight: -10
plugin: node_search
configuration:
rankings: { }
......@@ -15,13 +15,11 @@
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\KeyValueStore\StateInterface;
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\Plugin\SearchPluginBase;
use Drupal\search\Plugin\ConfigurableSearchPluginBase;
use Drupal\search\Plugin\SearchIndexingInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -29,11 +27,10 @@
*
* @SearchPlugin(
* id = "node_search",
* title = @Translation("Content"),
* path = "node"
* title = @Translation("Content")
* )
*/
class NodeSearch extends SearchPluginBase implements AccessibleInterface, SearchIndexingInterface, PluginFormInterface {
class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInterface, SearchIndexingInterface {
/**
* A database connection object.
......@@ -77,6 +74,13 @@ class NodeSearch extends SearchPluginBase implements AccessibleInterface, Search
*/
protected $account;
/**
* An array of additional rankings from hook_ranking().
*
* @var array
*/
protected $rankings;
/**
* The list of options and info for advanced search filters.
*
......@@ -261,17 +265,17 @@ public function execute() {
}
/**
* Gathers the rankings from the the hook_ranking() implementations.
* Adds the configured rankings to the search query.
*
* @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')) {
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 (isset($this->configuration['rankings'][$rank]) && !empty($this->configuration['rankings'][$rank])) {
$node_rank = $this->configuration['rankings'][$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']);
......@@ -404,7 +408,6 @@ public function searchFormAlter(array &$form, array &$form_state) {
);
// Add node types.
$node_types = $this->entityManager->getStorageController('node_type')->loadMultiple();
$types = array_map('check_plain', node_type_get_names());
$form['advanced']['types-fieldset'] = array(
'#type' => 'fieldset',
......@@ -504,13 +507,41 @@ public function searchFormSubmit(array &$form, array &$form_state) {
if (!empty($keys)) {
form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
}
$path = $form_state['action'] . '/' . $keys;
$options = array();
if ($filters) {
$options['query'] = array('f' => $filters);
}
$form_state['redirect'] = array($path, $options);
$form_state['redirect_route'] = array(
'route_name' => 'search.view_' . $form_state['search_page_id'],
'route_parameters' => array(
'keys' => $keys,
),
'options' => $options,
);
}
/**
* Gathers ranking definitions from hook_ranking().
*
* @return array
* An array of ranking definitions.
*/
protected function getRankings() {
if (!$this->rankings) {
$this->rankings = $this->moduleHandler->invokeAll('ranking');
}
return $this->rankings;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$configuration = array(
'rankings' => array(),
);
return $configuration;
}
/**
......@@ -524,37 +555,34 @@ public function buildConfigurationForm(array $form, array &$form_state) {
);
$form['content_ranking']['#theme'] = 'node_search_admin';
$form['content_ranking']['info'] = array(
'#value' => '<em>' . t('Influence is a numeric multiplier used in ordering search results. A higher number means the corresponding factor has more influence on search results; zero means the factor is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
'#value' => '<em>' . $this->t('Influence is a numeric multiplier used in ordering search results. A higher number means the corresponding factor has more influence on search results; zero means the factor is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
);
// 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']["rankings_$var"] = array(
'#title' => $values['title'],
'#type' => 'select',
'#options' => $options,
'#default_value' => variable_get('node_rank_' . $var, 0),
'#default_value' => isset($this->configuration['rankings'][$var]) ? $this->configuration['rankings'][$var] : 0,
);
}
return $form;
}
/**
* {@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) {
if (!empty($form_state['values']["rankings_$var"])) {
$this->configuration['rankings'][$var] = $form_state['values']["rankings_$var"];
}
else {
unset($this->configuration['rankings'][$var]);
}
}
}
}
......@@ -450,12 +450,6 @@ function node_uninstall() {
\Drupal::config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save();
}
// Delete node search ranking variables.
variable_del('node_rank_relevance');
variable_del('node_rank_sticky');
variable_del('node_rank_promote');
variable_del('node_rank_recent');
// Delete remaining general module variables.
\Drupal::state()->delete('node.node_access_needs_rebuild');
......
......@@ -4,18 +4,12 @@ search.settings:
type: mapping
label: 'Search settings'
mapping:
active_plugins:
type: sequence
label: 'Active search plugins'
sequence:
- type: string
label: 'Plugin'
and_or_limit:
type: integer
label: 'AND/OR combination limit'
default_plugin:
default_page:
type: string
label: 'Default search plugin'
label: 'Default search page'
index:
type: mapping
label: 'Indexing settings'
......@@ -69,3 +63,34 @@ search.settings:
a:
type: integer
label: 'Tag a weight'
search.page.*:
type: mapping
label: 'Search page'
mapping:
id:
type: string
label: 'Machine-readable name'
label:
type: label
label: 'Label'
uuid:
type: string
label: 'UUID'
status:
type: boolean
label: 'Enabled status of the configuration entity'
langcode:
type: string
label: 'Default language'
path:
type: string
label: 'Search page path'
weight:
type: integer
label: 'Weight'
plugin:
type: string
label: 'Plugin'
configuration:
type: search.plugin.[%parent.plugin]
active_plugins:
node_search: node_search
user_search: user_search
and_or_limit: 7
default_plugin: node_search
default_page: node_search
index:
cron_limit: 100
overlap_cjk: true
......
/**
* @file
* Styles for administration pages.
*/
/**
* Add search page select/submit.
*/
#search-admin-settings #edit-add-page {
margin-bottom: 1em;
}
#search-admin-settings #edit-add-page label {
display: block;
}
<?php
/**
* @file
* Contains Drupal\search\Access\SearchAccessCheck
*/
namespace Drupal\search\Access;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\search\SearchPluginManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Checks access for viewing search.
*/
class SearchAccessCheck implements AccessInterface {
/**
* The search plugin manager.
*
* @var \Drupal\search\SearchPluginManager
*/
protected $searchManager;
/**
* Contructs a new search access check.
*
* @param SearchPluginManager $search_plugin_manager
* The search plugin manager.
*/
public function __construct(SearchPluginManager $search_plugin_manager) {
$this->searchManager = $search_plugin_manager;
}
/**
* {@inheritdoc}
*/
public function access(Route $route, Request $request, AccountInterface $account) {
return $this->searchManager->getActiveDefinitions() ? static::ALLOW : static::DENY;
}
}
<?php
/**
* @file
* Contains Drupal\search\Access\SearchPluginAccessCheck
*/
namespace Drupal\search\Access;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Route access check for search plugins.
*/
class SearchPluginAccessCheck extends SearchAccessCheck {
/**
* {@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;
}
}
......@@ -13,8 +13,7 @@
* Defines a SearchPlugin type annotation object.
*
* SearchPlugin classes define search types for the core Search module. Each
* active search type is displayed in a tab on the Search page, and each has a
* path suffix after "search/".
* search type can be used to create search pages from the Search settings page.
*
* @see SearchPluginBase
*
......@@ -29,13 +28,6 @@ class SearchPlugin extends Plugin {
*/
public $id;
/**
* The path fragment to be added to search/ for the search page.
*
* @var string
*/
public $path;
/**
* The title for the search page tab.
*
......@@ -47,4 +39,5 @@ class SearchPlugin extends Plugin {
* @var \Drupal\Core\Annotation\Translation
*/
public $title;
}
......@@ -9,8 +9,8 @@
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\search\SearchPluginManager;
use Drupal\search\SearchPageInterface;
use Drupal\search\SearchPageRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
......@@ -20,30 +20,20 @@
class SearchController extends ControllerBase implements ContainerInjectionInterface {
/**
* The search plugin manager.
* The search page repository.
*
* @var \Drupal\search\SearchPluginManager
* @var \Drupal\search\SearchPageRepositoryInterface
*/
protected $searchManager;
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
protected $searchPageRepository;
/**
* Constructs a new search controller.
*
* @param \Drupal\search\SearchPluginManager $search_plugin_manager
* The search plugin manager.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
* @param \Drupal\search\SearchPageRepositoryInterface $search_page_repository
* The search page repository.
*/
public function __construct(SearchPluginManager $search_plugin_manager, FormBuilderInterface $form_builder) {
$this->searchManager = $search_plugin_manager;
$this->formBuilder = $form_builder;
public function __construct(SearchPageRepositoryInterface $search_page_repository) {
$this->searchPageRepository = $search_page_repository;
}
/**
......@@ -51,8 +41,7 @@ public function __construct(SearchPluginManager $search_plugin_manager, FormBuil
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.search'),
$container->get('form_builder')
$container->get('search.search_page_repository')
);
}
......@@ -61,46 +50,24 @@ public static function create(ContainerInterface $container) {
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param string $plugin_id
* The ID of a search plugin.
* @param \Drupal\search\SearchPageInterface $entity
* The search page entity.
* @param string $keys
* Search keywords.
* (optional) Search keywords, defaults to an empty string.
*
* @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) {
$info = FALSE;
$keys = trim($keys);
public function view(Request $request, SearchPageInterface $entity, $keys = '') {
// Also try to pull search keywords from the request to support old GET
// format of searches for existing links.
if (!$keys && $request->query->has('keys')) {
$keys = trim($request->query->get('keys'));
$keys = $request->query->get('keys');
}
$keys = trim($keys);
$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)) {
// 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('search.view_' . $info['id']);
}
$plugin = $this->searchManager->createInstance($plugin_id);
$plugin = $entity->getPlugin();
$plugin->setSearch($keys, $request->query->all(), $request->attributes->all());
// Default results output is an empty string.
$results = array('#markup' => '');
......@@ -114,16 +81,85 @@ public function view(Request $request, $plugin_id = NULL, $keys = NULL) {
// 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));
watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $entity->label()), WATCHDOG_NOTICE, $this->l(t('results'), 'search.view_' . $entity->id(), array('keys' => $keys)));
// Collect the search results.
$results = $plugin->buildResults();
}
}
// The form may be altered based on whether the search was run.
$build['search_form'] = $this->formBuilder->getForm('\Drupal\search\Form\SearchForm', $plugin);
$build['search_form'] = $this->entityManager()->getForm($entity, 'search');
$build['search_results'] = $results;
return $build;
}
/**
* Redirects to a search page.
*
* This is used to redirect from /search to the default search page.
*
* @param \Drupal\search\SearchPageInterface $entity
* The search page entity.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect to the search page.
*/
public function redirectSearchPage(SearchPageInterface $entity) {
return $this->redirect('search.view_' . $entity->id());
}
/**
* Route title callback.
*
* @param \Drupal\search\SearchPageInterface $search_page
* The search page entity.
*
* @return string
* The title for the search page edit form.
*/
public function editTitle(SearchPageInterface $search_page) {
return $this->t('Edit %label search page', array('%label' => $search_page->label()));
}
/**
* Performs an operation on the search page entity.
*
* @param \Drupal\search\SearchPageInterface $search_page
* The search page 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(SearchPageInterface $search_page, $op) {
$search_page->$op()->save();
if ($op == 'enable') {
drupal_set_message($this->t('The %label search page has been enabled.', array('%label' => $search_page->label())));
}
elseif ($op == 'disable') {
drupal_set_message($this->t('The %label search page has been disabled.', array('%label' => $search_page->label())));
}
return $this->redirect('search.settings');
}
/**
* Sets the search page as the default.
*
* @param \Drupal\search\SearchPageInterface $search_page
* The search page entity.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect to the search settings page.
*/
public function setAsDefault(SearchPageInterface $search_page) {
// Set the default page to this search page.
$this->searchPageRepository->setDefaultSearchPage($search_page);
drupal_set_message($this->t('The default search page is now %label. Be sure to check the ordering of your search pages.', array('%label' => $search_page->label())));
return $this->redirect('search.settings');
}
}
<?php
/**
* @file
* Contains \Drupal\search\Entity\SearchPage.
*/
namespace Drupal\search\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\search\Plugin\SearchIndexingInterface;
use Drupal\search\Plugin\SearchPluginBag;
use Drupal\search\SearchPageInterface;
/**
* Defines a configured search page.
*
* @EntityType(
* id = "search_page",
* label = @Translation("Search page"),
* controllers = {
* "access" = "Drupal\search\SearchPageAccessController",
* "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
* "list" = "Drupal\search\SearchPageListController",
* "form" = {
* "add" = "Drupal\search\Form\SearchPageAddForm",
* "edit" = "Drupal\search\Form\SearchPageEditForm",
* "search" = "Drupal\search\Form\SearchPageForm",
* "delete" = "Drupal\search\Form\SearchPageDeleteForm"
* }
* },
* admin_permission = "administer search",
* links = {
* "edit-form" = "search.edit"
* },
* config_prefix = "search.page",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid",
* "weight" = "weight",
* "status" = "status"
* }
* )
*/