diff --git a/core/includes/menu.inc b/core/includes/menu.inc index b83c323..7b644ce 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -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); diff --git a/core/modules/search/lib/Drupal/search/Entity/SearchPage.php b/core/modules/search/lib/Drupal/search/Entity/SearchPage.php index d0b87ec..bba66e0 100644 --- a/core/modules/search/lib/Drupal/search/Entity/SearchPage.php +++ b/core/modules/search/lib/Drupal/search/Entity/SearchPage.php @@ -40,6 +40,7 @@ * "id" = "id", * "label" = "label", * "uuid" = "uuid", + * "weight" = "weight", * "status" = "status" * } * ) @@ -98,6 +99,13 @@ class SearchPage extends ConfigEntityBase implements SearchPageInterface { protected $title; /** + * The weight of the search page. + * + * @var int + */ + protected $weight; + + /** * The plugin bag that stores search plugins. * * @var \Drupal\search\Plugin\SearchPluginBag @@ -159,11 +167,19 @@ public function getPath() { /** * {@inheritdoc} */ + public function getWeight() { + return $this->weight; + } + + /** + * {@inheritdoc} + */ public function getExportProperties() { $properties = parent::getExportProperties(); $names = array( 'path', 'title', + 'weight', 'plugin', 'configuration', ); @@ -176,6 +192,19 @@ public function getExportProperties() { /** * {@inheritdoc} */ + public function postCreate(EntityStorageControllerInterface $storage_controller) { + parent::postCreate($storage_controller); + + // @todo Use self::applyDefaultValue() once https://drupal.org/node/2004756 + // is in. + if (!isset($this->weight)) { + $this->weight = $this->isDefaultSearch() ? -10 : 0; + } + } + + /** + * {@inheritdoc} + */ public function preSave(EntityStorageControllerInterface $storage_controller) { parent::preSave($storage_controller); @@ -193,14 +222,14 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $ parent::postSave($storage_controller, $update); \Drupal::state()->set('menu_rebuild_needed', TRUE); - // @todo Local tasks shouldn't have to be manually cleared on menu rebuild. - \Drupal::cache('cache')->deleteTags(array('local_task' => 1)); } /** * {@inheritdoc} */ public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + parent::postDelete($storage_controller, $entities); + /** @var $storage_controller \Drupal\search\SearchPageStorageInterface */ if (!$storage_controller->isSearchActive()) { \Drupal::config('search.settings')->clear('default_page')->save(); @@ -218,7 +247,7 @@ public static function sort($a, $b) { if ($a_status != $b_status) { return ($a_status > $b_status) ? -1 : 1; } - return strnatcasecmp($a->label(), $b->label()); + return parent::sort($a, $b); } } diff --git a/core/modules/search/lib/Drupal/search/Form/SearchPageFormBase.php b/core/modules/search/lib/Drupal/search/Form/SearchPageFormBase.php index d189b52..da97e4a 100644 --- a/core/modules/search/lib/Drupal/search/Form/SearchPageFormBase.php +++ b/core/modules/search/lib/Drupal/search/Form/SearchPageFormBase.php @@ -79,7 +79,7 @@ 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 page'), + '#description' => $this->t('The administrative label for this search page.'), '#default_value' => $this->entity->label(), '#maxlength' => '255', ); @@ -95,8 +95,8 @@ public function form(array $form, array &$form_state) { ); $form['title'] = array( '#type' => 'textfield', - '#title' => $this->t('Menu title'), - '#description' => $this->t('The title used by the local tab on the search page'), + '#title' => $this->t('Tab title'), + '#description' => $this->t('The title presented to the user, usually as a tab on the search page.'), '#default_value' => $this->entity->getTitle(), '#maxlength' => '255', ); 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 716226c..ea1f50f 100644 --- a/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php +++ b/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php @@ -67,6 +67,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) { 'title' => $entity->getTitle(), 'route_name' => 'search.view_' . $entity_id, 'tab_root_id' => 'search.plugins:' . $default, + 'weight' => $entity->getWeight(), ); } } diff --git a/core/modules/search/lib/Drupal/search/SearchPageInterface.php b/core/modules/search/lib/Drupal/search/SearchPageInterface.php index a7db839..1ebfa7f 100644 --- a/core/modules/search/lib/Drupal/search/SearchPageInterface.php +++ b/core/modules/search/lib/Drupal/search/SearchPageInterface.php @@ -56,4 +56,9 @@ public function getPath(); */ public function getTitle(); + /** + * @return int + */ + public function getWeight(); + } diff --git a/core/modules/search/lib/Drupal/search/SearchPageListController.php b/core/modules/search/lib/Drupal/search/SearchPageListController.php index 7bb1093..9593519 100644 --- a/core/modules/search/lib/Drupal/search/SearchPageListController.php +++ b/core/modules/search/lib/Drupal/search/SearchPageListController.php @@ -10,18 +10,33 @@ use Drupal\Component\Utility\MapArray; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Config\Context\ContextInterface; -use Drupal\Core\Config\Entity\ConfigEntityListController; +use Drupal\Core\Config\Entity\DraggableListController; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormInterface; +use Drupal\Core\KeyValueStore\StateInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a listing of search entities. */ -class SearchPageListController extends ConfigEntityListController implements FormInterface { +class SearchPageListController extends DraggableListController implements FormInterface { + + /** + * The entity storage controller class. + * + * @var \Drupal\search\SearchPageStorageInterface + */ + protected $storage; + + /** + * The entities being listed. + * + * @var \Drupal\search\SearchPageInterface[] + */ + protected $entities = array(); /** * Stores the configuration factory. @@ -45,6 +60,13 @@ class SearchPageListController extends ConfigEntityListController implements For protected $searchManager; /** + * The state service. + * + * @var \Drupal\Core\KeyValueStore\StateInterface + */ + protected $state; + + /** * Constructs a new SearchPageListController object. * * @param string $entity_type @@ -63,8 +85,10 @@ class SearchPageListController extends ConfigEntityListController implements For * The configuration context to use. * @param \Drupal\Core\Form\FormBuilderInterface $form_builder * The form builder. + * @param \Drupal\Core\KeyValueStore\StateInterface $state + * The state service. */ - public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, SearchPluginManager $search_manager, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, ContextInterface $context, FormBuilderInterface $form_builder) { + public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, SearchPluginManager $search_manager, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, ContextInterface $context, FormBuilderInterface $form_builder, StateInterface $state) { parent::__construct($entity_type, $entity_info, $storage, $module_handler); $this->configFactory = $config_factory; @@ -72,6 +96,7 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr $this->formBuilder = $form_builder; $this->searchManager = $search_manager; + $this->state = $state; } /** @@ -86,7 +111,8 @@ public static function createInstance(ContainerInterface $container, $entity_typ $container->get('module_handler'), $container->get('config.factory'), $container->get('config.context.free'), - $container->get('form_builder') + $container->get('form_builder'), + $container->get('state') ); } @@ -132,20 +158,20 @@ public function buildHeader() { public function buildRow(EntityInterface $entity) { /** @var $entity \Drupal\search\SearchPageInterface */ $row['label'] = $this->getLabel($entity); - $definition = $entity->getPlugin()->getPluginDefinition(); - $row['url'] = 'search/' . $entity->getPath(); + $row['url']['#markup'] = 'search/' . $entity->getPath(); // If the search page is active, link to it. if ($entity->status()) { $row['url'] = array( - 'data' => array( - '#type' => 'link', - '#title' => $row['url'], - '#route_name' => 'search.view_' . $entity->id(), - ), + '#type' => 'link', + '#title' => $row['url'], + '#route_name' => 'search.view_' . $entity->id(), ); } - $row['plugin'] = $definition['title']; - $row['status'] = $entity->status() ? $this->t('Enabled') : $this->t('Disabled'); + + $definition = $entity->getPlugin()->getPluginDefinition(); + $row['plugin']['#markup'] = $definition['title']; + + $row['status']['#markup'] = $entity->status() ? $this->t('Enabled') : $this->t('Disabled'); return $row + parent::buildRow($entity); } @@ -153,12 +179,14 @@ public function buildRow(EntityInterface $entity) { * {@inheritdoc} */ public function buildForm(array $form, array &$form_state) { + $form = parent::buildForm($form, $form_state); $search_settings = $this->configFactory->get('search.settings'); // Collect some stats. $remaining = 0; $total = 0; - $entities = $this->load(); - foreach ($entities as $entity) { + $options = array(); + foreach ($this->entities as $entity_id => $entity) { + $options[$entity_id] = $this->getLabel($entity); if ($entity->isIndexable() && $status = $entity->getPlugin()->indexStatus()) { $remaining += $status['remaining']; $total += $status['total']; @@ -221,6 +249,12 @@ public function buildForm(array $form, array &$form_state) { '#type' => 'details', '#title' => $this->t('Search pages'), ); + $form['search_pages']['default_page'] = array( + '#type' => 'select', + '#title' => $this->t('Default search page'), + '#options' => $options, + '#default_value' => $search_settings->get('default_page'), + ); $form['search_pages']['add_page'] = array( '#type' => 'container', '#attributes' => array( @@ -249,19 +283,11 @@ public function buildForm(array $form, array &$form_state) { '#submit' => array(array($this, 'submitAddSearchPage')), '#limit_validation_errors' => array(array('search_type')), ); - $rows = array(); - foreach ($entities as $entity) { - $rows[$entity->id()] = $this->buildRow($entity); - } - $form['search_pages']['default_page'] = array( - '#type' => 'tableselect', - '#header' => $this->buildHeader(), - '#options' => $rows, - '#required' => TRUE, - '#multiple' => FALSE, - '#default_value' => $search_settings->get('default_page'), - '#empty' => $this->t('No search pages have been configured.'), - ); + + // Move the listing into the search_pages element. + $form['search_pages'][$this->entitiesKey] = $form[$this->entitiesKey]; + $form['search_pages'][$this->entitiesKey]['#empty'] = $this->t('No search pages have been configured.'); + unset($form[$this->entitiesKey]); $form['actions']['#type'] = 'actions'; $form['actions']['submit'] = array( @@ -298,6 +324,8 @@ public function validateForm(array &$form, array &$form_state) { * {@inheritdoc} */ public function submitForm(array &$form, array &$form_state) { + parent::submitForm($form, $form_state); + $search_settings = $this->configFactory->get('search.settings'); // If these settings change, the index needs to be rebuilt. if (($search_settings->get('index.minimum_word_size') != $form_state['values']['minimum_word_size']) || ($search_settings->get('index.overlap_cjk') != $form_state['values']['overlap_cjk'])) { @@ -314,6 +342,11 @@ public function submitForm(array &$form, array &$form_state) { $default_search_page->enable()->save(); } + // If the default page is changing, rebuild the menu router. + if ($search_settings->get('default_page') != $form_state['values']['default_page']) { + $this->state->set('menu_rebuild_needed', TRUE); + } + $search_settings ->set('index.cron_limit', $form_state['values']['cron_limit']) ->set('default_page', $form_state['values']['default_page']) diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php index 066888b..f87dcbb 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php @@ -263,6 +263,17 @@ public function testMultipleSearchPages() { $this->assertIdentical((string) $elements[0]['href'], url('search/' . $first['path'])); $this->assertIdentical((string) $elements[1]['href'], url('search/' . $second['path'])); + // Switch the weight of the search pages and check the order of the tabs. + $edit = array( + 'entities[' . $first_id . '][weight]' => 10, + 'entities[' . $second_id . '][weight]' => -10, + ); + $this->drupalPostForm('admin/config/search/settings', $edit, t('Save configuration')); + $this->drupalGet('search'); + $elements = $this->xpath('//*[contains(@class, :class)]//a', array(':class' => 'tabs primary')); + $this->assertIdentical((string) $elements[0]['href'], url('search/' . $second['path'])); + $this->assertIdentical((string) $elements[1]['href'], url('search/' . $first['path'])); + // Check the initial state of the search pages. $this->drupalGet('admin/config/search/settings'); $this->assertSearchPageOperations($first_id, TRUE, FALSE, FALSE, FALSE);