diff --git a/core/includes/common.inc b/core/includes/common.inc index f2a2b4c..b726bf5 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -3368,6 +3368,8 @@ function drupal_pre_render_dropbutton($element) { * the page template (required). * - #show_messages: Suppress drupal_get_message() items. Used by Batch * API (optional). + * - #show_local_tasks: Suppress tabs and actions. Used by Batch API + * (optional). * * @return array * The processed render array for the page. @@ -3439,6 +3441,8 @@ function drupal_prepare_page($page) { * the page template (required). * - #show_messages: Suppress drupal_get_message() items. Used by Batch * API (optional). + * - #show_local_tasks: Suppress tabs and actions. Used by Batch API + * (optional). * * @return string * Returns the rendered string. diff --git a/core/includes/theme.inc b/core/includes/theme.inc index c63f679..3e5573f 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2079,10 +2079,10 @@ function template_preprocess_page(&$variables) { $variables['logo'] = theme_get_setting('logo.url'); $variables['main_menu'] = theme_get_setting('features.main_menu') ? menu_main_menu() : array(); $variables['secondary_menu'] = theme_get_setting('features.secondary_menu') ? menu_secondary_menu() : array(); - $variables['action_links'] = menu_get_local_actions(); + $variables['action_links'] = $variables['page']['#show_local_tasks'] ? menu_get_local_actions() : array(); $variables['site_name'] = (theme_get_setting('features.name') ? String::checkPlain($site_config->get('name')) : ''); $variables['site_slogan'] = (theme_get_setting('features.slogan') ? filter_xss_admin($site_config->get('slogan')) : ''); - $variables['tabs'] = menu_local_tabs(); + $variables['tabs'] = $variables['page']['#show_local_tasks'] ? menu_local_tabs() : array(); // Pass the main menu and secondary menu to the template as render arrays. if (!empty($variables['main_menu'])) { diff --git a/core/modules/config/config.services.yml b/core/modules/config/config.services.yml new file mode 100644 index 0000000..5ce2696 --- /dev/null +++ b/core/modules/config/config.services.yml @@ -0,0 +1,5 @@ +services: + config.config_subscriber: + class: Drupal\config\ConfigSubscriber + tags: + - { name: event_subscriber } diff --git a/core/lib/Drupal/Core/Config/BatchConfigImporter.php b/core/modules/config/lib/Drupal/config/BatchConfigImporter.php similarity index 96% rename from core/lib/Drupal/Core/Config/BatchConfigImporter.php rename to core/modules/config/lib/Drupal/config/BatchConfigImporter.php index 67aee13..f9dffcb 100644 --- a/core/lib/Drupal/Core/Config/BatchConfigImporter.php +++ b/core/modules/config/lib/Drupal/config/BatchConfigImporter.php @@ -2,10 +2,15 @@ /** * @file - * Contains \Drupal\Core\Config\BatchConfigImporter. + * Contains \Drupal\config\BatchConfigImporter. */ -namespace Drupal\Core\Config; +namespace Drupal\config; + +use Drupal\Core\Config\ConfigEvents; +use Drupal\Core\Config\ConfigImporter; +use Drupal\Core\Config\ConfigImporterEvent; +use Drupal\Core\Config\ConfigImporterException; /** * Defines a batch configuration importer. diff --git a/core/modules/config/lib/Drupal/config/ConfigSubscriber.php b/core/modules/config/lib/Drupal/config/ConfigSubscriber.php new file mode 100644 index 0000000..80e69f6 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/ConfigSubscriber.php @@ -0,0 +1,47 @@ +getConfigImporter(); + if ($importer instanceof BatchConfigImporter) { + $core_extension = $importer->getStorageComparer()->getSourceStorage()->read('core.extension'); + if (!isset($core_extension['module']['config'])) { + throw new ConfigImporterException(t('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.')); + } + } + } + + /** + * {@inheritdoc} + */ + static function getSubscribedEvents() { + $events[ConfigEvents::IMPORT_VALIDATE][] = array('onConfigImporterValidate', 20); + return $events; + } +} diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index c48a776..17daf6d 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -8,6 +8,8 @@ namespace Drupal\config\Form; use Drupal\Component\Uuid\UuidInterface; +use Drupal\config\BatchConfigImporter; +use Drupal\Core\Config\ConfigImporterException; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; @@ -15,7 +17,6 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Lock\LockBackendInterface; -use Drupal\Core\Config\BatchConfigImporter; use Drupal\Core\Config\StorageComparer; use Drupal\Core\Config\TypedConfigManager; use Drupal\Core\Routing\UrlGeneratorInterface; @@ -256,21 +257,27 @@ public function submitForm(array &$form, array &$form_state) { drupal_set_message($this->t('Another request may be synchronizing configuration already.')); } else{ - $operations = $config_importer->initialize(); - $batch = array( - 'operations' => array(), - 'finished' => array(get_class($this), 'finishBatch'), - 'title' => t('Synchronizing configuration'), - 'init_message' => t('Starting configuration synchronization.'), - 'progress_message' => t('Completed @current step of @total.'), - 'error_message' => t('Configuration synchronization has encountered an error.'), - 'file' => drupal_get_path('module', 'config') . '/config.admin.inc', - ); - foreach ($operations as $operation) { - $batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $operation)); + try { + $operations = $config_importer->initialize(); + $batch = array( + 'operations' => array(), + 'finished' => array(get_class($this), 'finishBatch'), + 'title' => t('Synchronizing configuration'), + 'init_message' => t('Starting configuration synchronization.'), + 'progress_message' => t('Completed @current step of @total.'), + 'error_message' => t('Configuration synchronization has encountered an error.'), + 'file' => drupal_get_path('module', 'config') . '/config.admin.inc', + ); + foreach ($operations as $operation) { + $batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $operation)); + } + + batch_set($batch); + } + catch (ConfigImporterException $e) { + drupal_set_message($e->getMessage(), 'error'); } - batch_set($batch); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php index 038e318..ade406b 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php @@ -29,6 +29,13 @@ public static function getInfo() { ); } + public function setUp() { + parent::setUp(); + + $this->web_user = $this->drupalCreateUser(array('synchronize configuration')); + $this->drupalLogin($this->web_user); + } + /** * Tests that a fixed set of modules can be installed and uninstalled. */ @@ -81,6 +88,9 @@ public function testInstallUninstall() { return TRUE; }); + // Can not uninstall config and use admin/config/development/configuration! + unset($modules_to_uninstall['config']); + $this->assertTrue(isset($modules_to_uninstall['comment']), 'The comment module will be disabled'); // Uninstall all modules that can be uninstalled. @@ -93,7 +103,7 @@ public function testInstallUninstall() { } // Import the configuration thereby re-installing all the modules. - $this->configImporter()->import(); + $this->drupalPostForm('admin/config/development/configuration', array(), t('Import all')); // Check that all modules that were uninstalled are now reinstalled. $this->assertModules(array_keys($modules_to_uninstall), TRUE); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php index 084be15..68c3264 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php @@ -305,6 +305,21 @@ function testImportDiff() { $this->drupalGet('admin/config/development/configuration/sync/diff/' . $config_name); } + public function testConfigUninstallConfigException() { + $staging = $this->container->get('config.storage.staging'); + + $core_extension = \Drupal::config('core.extension')->get(); + unset($core_extension['module']['config']); + $staging->write('core.extension', $core_extension); + + $this->drupalGet('admin/config/development/configuration'); + $this->assertText('core.extension'); + + // Import and verify that both do not appear anymore. + $this->drupalPostForm(NULL, array(), t('Import all')); + $this->assertText('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.'); + } + function prepareSiteNameUpdate($new_site_name) { $staging = $this->container->get('config.storage.staging'); // Create updated configuration object. diff --git a/core/modules/system/lib/Drupal/system/Controller/BatchController.php b/core/modules/system/lib/Drupal/system/Controller/BatchController.php index 574872e..cbf3fba 100644 --- a/core/modules/system/lib/Drupal/system/Controller/BatchController.php +++ b/core/modules/system/lib/Drupal/system/Controller/BatchController.php @@ -87,6 +87,8 @@ public function batchPage(Request $request) { drupal_set_page_content($output); $page = element_info('page'); $page['#show_messages'] = FALSE; + // @todo Remove once local tasks and actions are blocks. + $page['#show_local_tasks'] = FALSE; $page = $this->render($page); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index a870cb3..b23a26a 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -290,6 +290,7 @@ function system_element_info() { ); $types['page'] = array( '#show_messages' => TRUE, + '#show_local_tasks' => TRUE, '#theme' => 'page', '#title' => '', );