From ae702dc56d6abdfaeccd705c55fba916c04a3d33 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Fri, 4 Apr 2014 14:49:13 +0100
Subject: [PATCH] Issue #1808248 by alexpott, beejeebus, tayzlor, Nitesh
 Sethia: Add a separate module install/uninstall step to the config import
 process.

---
 core/lib/Drupal.php                           |  10 +
 .../Core/Config/BatchConfigImporter.php       | 152 ++++++--
 .../lib/Drupal/Core/Config/ConfigImporter.php | 364 ++++++++++++++++--
 .../Drupal/Core/Config/ConfigInstaller.php    |  71 +++-
 .../Core/Config/ConfigInstallerInterface.php  |  34 ++
 core/lib/Drupal/Core/Config/ConfigManager.php |   7 +
 .../Core/Config/ConfigManagerInterface.php    |   8 +
 .../Drupal/Core/Config/StorageComparer.php    |  21 +-
 .../ConfigImportSubscriber.php                |   2 +-
 .../Drupal/Core/Extension/ModuleHandler.php   |  20 +-
 .../Drupal/Core/Extension/ThemeHandler.php    |   9 +
 core/modules/block/block.module               |  11 +-
 .../lib/Drupal/comment/CommentManager.php     |  17 +
 .../lib/Drupal/config/Form/ConfigSync.php     |  49 ++-
 .../config/Tests/ConfigImportAllTest.php      | 112 ++++++
 .../config/Tests/ConfigImportRecreateTest.php |  17 +-
 .../config/Tests/ConfigImportUITest.php       | 130 ++++++-
 .../config/Tests/ConfigImporterTest.php       |  14 +-
 .../config/Tests/ConfigOverrideTest.php       |   1 +
 .../config/Tests/ConfigSnapshotTest.php       |   1 +
 .../config_import_test.info.yml               |   6 +
 .../config_import_test.module                 |   6 +
 .../config_import_test.services.yml           |   6 +
 .../config_import_test/EventSubscriber.php    | 107 +++++
 core/modules/contact/contact.install          |   8 +-
 .../content_translation.install               |  31 ++
 .../field/Entity/FieldInstanceConfig.php      |   7 +
 .../field/FieldInstanceConfigInterface.php    |   8 +
 ...d.field.taxonomy_term.forum_container.yml} |   0
 ...e.taxonomy_term.forums.forum_container.yml |   2 +-
 core/modules/forum/forum.install              | 129 ++++---
 core/modules/node/node.install                |   4 +-
 .../Drupal/search/SearchPageRepository.php    |   2 +-
 .../Drupal/simpletest/DrupalUnitTestBase.php  |   2 +-
 .../lib/Drupal/simpletest/TestBase.php        |   4 +-
 core/modules/simpletest/simpletest.install    |   2 +-
 ...tity.view_display.node.article.default.yml |  14 +-
 ...ntity.view_display.node.article.teaser.yml |  14 +-
 38 files changed, 1233 insertions(+), 169 deletions(-)
 create mode 100644 core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php
 create mode 100644 core/modules/config/tests/config_import_test/config_import_test.info.yml
 create mode 100644 core/modules/config/tests/config_import_test/config_import_test.module
 create mode 100644 core/modules/config/tests/config_import_test/config_import_test.services.yml
 create mode 100644 core/modules/config/tests/config_import_test/lib/Drupal/config_import_test/EventSubscriber.php
 rename core/modules/forum/config/{field.field.forum.forum_container.yml => field.field.taxonomy_term.forum_container.yml} (100%)

diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index cc8e9623e3a7..c4622197b8d5 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -612,4 +612,14 @@ public static function formBuilder() {
     return static::$container->get('form_builder');
   }
 
+  /**
+   * Gets the syncing state.
+   *
+   * @return bool
+   *   Returns TRUE is syncing flag set.
+   */
+  public function isConfigSyncing() {
+    return static::$container->get('config.installer')->isSyncing();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Config/BatchConfigImporter.php b/core/lib/Drupal/Core/Config/BatchConfigImporter.php
index 0ab6cc153446..67aee132f988 100644
--- a/core/lib/Drupal/Core/Config/BatchConfigImporter.php
+++ b/core/lib/Drupal/Core/Config/BatchConfigImporter.php
@@ -14,10 +14,34 @@
  */
 class BatchConfigImporter extends ConfigImporter {
 
+  /**
+   * The total number of extensions to process.
+   *
+   * @var int
+   */
+  protected $totalExtensionsToProcess = 0;
+
+  /**
+   * The total number of configuration objects to process.
+   *
+   * @var int
+   */
+  protected $totalConfigurationToProcess = 0;
+
   /**
    * Initializes the config importer in preparation for processing a batch.
+   *
+   * @return array
+   *   An array of method names that to be called by the batch. If there are
+   *   modules or themes to process then an extra step is added.
+   *
+   * @throws ConfigImporterException
+   *   If the configuration is already importing.
    */
   public function initialize() {
+    $batch_operations = array();
+    $this->createExtensionChangelist();
+
     // Ensure that the changes have been validated.
     $this->validate();
 
@@ -25,61 +49,137 @@ public function initialize() {
       // Another process is synchronizing configuration.
       throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
     }
-    $this->totalToProcess = 0;
-    foreach(array('create', 'delete', 'update') as $op) {
-      $this->totalToProcess += count($this->getUnprocessed($op));
+
+    $modules = $this->getUnprocessedExtensions('module');
+    foreach (array('install', 'uninstall') as $op) {
+      $this->totalExtensionsToProcess += count($modules[$op]);
+    }
+    $themes = $this->getUnprocessedExtensions('theme');
+    foreach (array('enable', 'disable') as $op) {
+      $this->totalExtensionsToProcess += count($themes[$op]);
+    }
+
+    // We have extensions to process.
+    if ($this->totalExtensionsToProcess > 0) {
+      $batch_operations[] = 'processExtensionBatch';
     }
+
+    $batch_operations[] = 'processConfigurationBatch';
+    $batch_operations[] = 'finishBatch';
+    return $batch_operations;
   }
 
   /**
-   * Processes batch.
+   * Processes extensions as a batch operation.
    *
    * @param array $context.
    *   The batch context.
    */
-  public function processBatch(array &$context) {
-    $operation = $this->getNextOperation();
+  public function processExtensionBatch(array &$context) {
+    $operation = $this->getNextExtensionOperation();
     if (!empty($operation)) {
-      $this->process($operation['op'], $operation['name']);
-      $context['message'] = t('Synchronizing @name.', array('@name' => $operation['name']));
-      $context['finished'] = $this->batchProgress();
+      $this->processExtension($operation['type'], $operation['op'], $operation['name']);
+      $context['message'] = t('Synchronising extensions: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
+      $processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']);
+      $processed_count += count($this->processedExtensions['theme']['disable']) + count($this->processedExtensions['theme']['enable']);
+      $context['finished'] = $processed_count / $this->totalExtensionsToProcess;
     }
     else {
       $context['finished'] = 1;
     }
-    if ($context['finished'] >= 1) {
-      $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
-      // The import is now complete.
-      $this->lock->release(static::LOCK_ID);
-      $this->reset();
+  }
+
+  /**
+   * Processes configuration as a batch operation.
+   *
+   * @param array $context.
+   *   The batch context.
+   */
+  public function processConfigurationBatch(array &$context) {
+    // The first time this is called we need to calculate the total to process.
+    // This involves recalculating the changelist which will ensure that if
+    // extensions have been processed any configuration affected will be taken
+    // into account.
+    if ($this->totalConfigurationToProcess == 0) {
+      $this->storageComparer->reset();
+      foreach (array('delete', 'create', 'update') as $op) {
+        $this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op));
+      }
+    }
+    $operation = $this->getNextConfigurationOperation();
+    if (!empty($operation)) {
+      $this->processConfiguration($operation['op'], $operation['name']);
+      $context['message'] = t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
+      $processed_count = count($this->processedConfiguration['create']) + count($this->processedConfiguration['delete']) + count($this->processedConfiguration['update']);
+      $context['finished'] = $processed_count / $this->totalConfigurationToProcess;
+    }
+    else {
+      $context['finished'] = 1;
     }
   }
 
   /**
-   * Gets percentage of progress made.
+   * Finishes the batch.
+   *
+   * @param array $context.
+   *   The batch context.
+   */
+  public function finishBatch(array &$context) {
+    $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
+    // The import is now complete.
+    $this->lock->release(static::LOCK_ID);
+    $this->reset();
+    $context['message'] = t('Finalising configuration synchronisation.');
+    $context['finished'] = 1;
+  }
+
+  /**
+   * Gets the next extension operation to perform.
    *
-   * @return float
-   *   The percentage of progress made expressed as a float between 0 and 1.
+   * @return array|bool
+   *   An array containing the next operation and extension name to perform it
+   *   on. If there is nothing left to do returns FALSE;
    */
-  protected  function batchProgress() {
-    $processed_count = count($this->processed['create']) + count($this->processed['delete']) + count($this->processed['update']);
-    return $processed_count / $this->totalToProcess;
+  protected function getNextExtensionOperation() {
+    foreach (array('install', 'uninstall') as $op) {
+      $modules = $this->getUnprocessedExtensions('module');
+      if (!empty($modules[$op])) {
+        return array(
+          'op' => $op,
+          'type' => 'module',
+          'name' => array_shift($modules[$op]),
+        );
+      }
+    }
+    foreach (array('enable', 'disable') as $op) {
+      $themes = $this->getUnprocessedExtensions('theme');
+      if (!empty($themes[$op])) {
+        return array(
+          'op' => $op,
+          'type' => 'theme',
+          'name' => array_shift($themes[$op]),
+        );
+      }
+    }
+    return FALSE;
   }
 
   /**
-   * Gets the next operation to perform.
+   * Gets the next configuration operation to perform.
    *
    * @return array|bool
    *   An array containing the next operation and configuration name to perform
    *   it on. If there is nothing left to do returns FALSE;
    */
-  protected  function getNextOperation() {
-    foreach(array('create', 'delete', 'update') as $op) {
-      $names = $this->getUnprocessed($op);
-      if (!empty($names)) {
+  protected function getNextConfigurationOperation() {
+    // The order configuration operations is processed is important. Deletes
+    // have to come first so that recreates can work.
+    foreach (array('delete', 'create', 'update') as $op) {
+      $config_names = $this->getUnprocessedConfiguration($op);
+      if (!empty($config_names)) {
         return array(
           'op' => $op,
-          'name' => array_shift($names),
+          'name' => array_shift($config_names),
         );
       }
     }
diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php
index 5e11697ca585..1303b7f5ef6c 100644
--- a/core/lib/Drupal/Core/Config/ConfigImporter.php
+++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Config;
 
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
 use Drupal\Core\DependencyInjection\DependencySerialization;
@@ -71,16 +73,30 @@ class ConfigImporter extends DependencySerialization {
   /**
    * The typed config manager.
    *
-   * @var \Drupal\Core\Config\TypedConfigManager
+   * @var \Drupal\Core\Config\TypedConfigManagerInterface
    */
   protected $typedConfigManager;
 
   /**
-   * List of changes processed by the import().
+   * List of configuration file changes processed by the import().
    *
    * @var array
    */
-  protected $processed;
+  protected $processedConfiguration;
+
+  /**
+   * List of extension changes processed by the import().
+   *
+   * @var array
+   */
+  protected $processedExtensions;
+
+  /**
+   * List of extension changes to be processed by the import().
+   *
+   * @var array
+   */
+  protected $extensionChangelist;
 
   /**
    * Indicates changes to import have been validated.
@@ -89,6 +105,27 @@ class ConfigImporter extends DependencySerialization {
    */
   protected $validated;
 
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The theme handler.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
+  /**
+   * Flag set to import system.theme during processing theme enable and disables.
+   *
+   * @var bool
+   */
+  protected $processedSystemTheme = FALSE;
+
   /**
    * Constructs a configuration import object.
    *
@@ -103,14 +140,21 @@ class ConfigImporter extends DependencySerialization {
    *   The lock backend to ensure multiple imports do not occur at the same time.
    * @param \Drupal\Core\Config\TypedConfigManager $typed_config
    *   The typed configuration manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler
+   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+   *   The theme handler
    */
-  public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManager $typed_config) {
+  public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
     $this->storageComparer = $storage_comparer;
     $this->eventDispatcher = $event_dispatcher;
     $this->configManager = $config_manager;
     $this->lock = $lock;
     $this->typedConfigManager = $typed_config;
-    $this->processed = $this->storageComparer->getEmptyChangelist();
+    $this->moduleHandler = $module_handler;
+    $this->themeHandler = $theme_handler;
+    $this->processedConfiguration = $this->storageComparer->getEmptyChangelist();
+    $this->processedExtensions = $this->getEmptyExtensionsProcessedList();
   }
 
   /**
@@ -131,13 +175,35 @@ public function getStorageComparer() {
    */
   public function reset() {
     $this->storageComparer->reset();
-    $this->processed = $this->storageComparer->getEmptyChangelist();
+    $this->processedConfiguration = $this->storageComparer->getEmptyChangelist();
+    $this->processedExtensions = $this->getEmptyExtensionsProcessedList();
+    $this->createExtensionChangelist();
     $this->validated = FALSE;
+    $this->processedSystemTheme = FALSE;
     return $this;
   }
 
   /**
-   * Checks if there are any unprocessed changes.
+   * Gets an empty list of extensions to process.
+   *
+   * @return array
+   *   An empty list of extensions to process.
+   */
+  protected function getEmptyExtensionsProcessedList() {
+    return array(
+      'module' => array(
+        'install' => array(),
+        'uninstall' => array(),
+      ),
+      'theme' => array(
+        'enable' => array(),
+        'disable' => array(),
+      ),
+    );
+  }
+
+  /**
+   * Checks if there are any unprocessed configuration changes.
    *
    * @param array $ops
    *   The operations to check for changes. Defaults to all operations, i.e.
@@ -146,9 +212,9 @@ public function reset() {
    * @return bool
    *   TRUE if there are changes to process and FALSE if not.
    */
-  public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')) {
+  public function hasUnprocessedConfigurationChanges($ops = array('delete', 'create', 'update')) {
     foreach ($ops as $op) {
-      if (count($this->getUnprocessed($op))) {
+      if (count($this->getUnprocessedConfiguration($op))) {
         return TRUE;
       }
     }
@@ -161,8 +227,8 @@ public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')
    * @return array
    *   An array containing a list of processed changes.
    */
-  public function getProcessed() {
-    return $this->processed;
+  public function getProcessedConfiguration() {
+    return $this->processedConfiguration;
   }
 
   /**
@@ -173,8 +239,8 @@ public function getProcessed() {
    * @param string $name
    *   The name of the configuration processed.
    */
-  protected function setProcessed($op, $name) {
-    $this->processed[$op][] = $name;
+  protected function setProcessedConfiguration($op, $name) {
+    $this->processedConfiguration[$op][] = $name;
   }
 
   /**
@@ -187,8 +253,139 @@ protected function setProcessed($op, $name) {
    * @return array
    *   An array of configuration names.
    */
-  public function getUnprocessed($op) {
-    return array_diff($this->storageComparer->getChangelist($op), $this->processed[$op]);
+  public function getUnprocessedConfiguration($op) {
+    return array_diff($this->storageComparer->getChangelist($op), $this->processedConfiguration[$op]);
+  }
+
+  /**
+   * Gets list of processed extension changes.
+   *
+   * @return array
+   *   An array containing a list of processed extension changes.
+   */
+  public function getProcessedExtensions() {
+    return $this->processedExtensions;
+  }
+
+  /**
+   * Determines if the current import has processed extensions.
+   *
+   * @return bool
+   *   TRUE if the ConfigImporter has processed extensions.
+   */
+  protected function hasProcessedExtensions() {
+    $compare = array_diff($this->processedExtensions, getEmptyExtensionsProcessedList());
+    return !empty($compare);
+  }
+
+  /**
+   * Sets an extension change as processed.
+   *
+   * @param string $type
+   *   The type of extension, either 'theme' or 'module'.
+   * @param string $op
+   *   The change operation performed, either install or uninstall.
+   * @param string $name
+   *   The name of the extension processed.
+   */
+  protected function setProcessedExtension($type, $op, $name) {
+    $this->processedExtensions[$type][$op][] = $name;
+  }
+
+  /**
+   * Populates the extension change list.
+   */
+  protected function createExtensionChangelist() {
+    // Read the extensions information to determine changes.
+    $current_extensions = $this->storageComparer->getTargetStorage()->read('core.extension');
+    $new_extensions = $this->storageComparer->getSourceStorage()->read('core.extension');
+
+    // If there is no extension information in staging then exit. This is
+    // probably due to an empty staging directory.
+    if (!$new_extensions) {
+      return;
+    }
+
+    // Get a list of modules with dependency weights as values.
+    $module_data = system_rebuild_module_data();
+    // Set the actual module weights.
+    $module_list = array_combine(array_keys($module_data), array_keys($module_data));
+    $module_list = array_map(function ($module) use ($module_data) {
+      return $module_data[$module]->sort;
+    }, $module_list);
+
+    // Work out what modules to install and uninstall.
+    $uninstall = array_diff(array_keys($current_extensions['module']), array_keys($new_extensions['module']));
+    $install = array_diff(array_keys($new_extensions['module']), array_keys($current_extensions['module']));
+    // Sort the module list by their weights. So that dependencies
+    // are uninstalled last.
+    asort($module_list);
+    $uninstall = array_intersect(array_keys($module_list), $uninstall);
+    // Sort the module list by their weights (reverse). So that dependencies
+    // are installed first.
+    arsort($module_list);
+    $install = array_intersect(array_keys($module_list), $install);
+
+    // Work out what themes to enable and to disable.
+    $enable = array_diff(array_keys($new_extensions['theme']), array_keys($current_extensions['theme']));
+    $disable = array_diff(array_keys($current_extensions['theme']), array_keys($new_extensions['theme']));
+
+    $this->extensionChangelist = array(
+      'module' => array(
+        'uninstall' => $uninstall,
+        'install' => $install,
+      ),
+      'theme' => array(
+        'enable' => $enable,
+        'disable' => $disable,
+      ),
+    );
+  }
+
+  /**
+   * Gets a list changes for extensions.
+   *
+   * @param string $type
+   *   The type of extension, either 'theme' or 'module'.
+   * @param string $op
+   *   The change operation to get the unprocessed list for, either install
+   *   or uninstall.
+   *
+   * @return array
+   *   An array of extension names.
+   */
+  protected function getExtensionChangelist($type, $op = NULL) {
+    if ($op) {
+      return $this->extensionChangelist[$type][$op];
+    }
+    return $this->extensionChangelist[$type];
+  }
+
+  /**
+   * Gets a list of unprocessed changes for extensions.
+   *
+   * @param string $type
+   *   The type of extension, either 'theme' or 'module'.
+   *
+   * @return array
+   *   An array of extension names.
+   */
+  public function getUnprocessedExtensions($type) {
+    $changelist = $this->getExtensionChangelist($type);
+
+    if ($type == 'theme') {
+      $unprocessed = array(
+        'enable' => array_diff($changelist['enable'], $this->processedExtensions[$type]['enable']),
+        'disable' => array_diff($changelist['disable'], $this->processedExtensions[$type]['disable']),
+      );
+    }
+    else {
+      $unprocessed = array(
+        'install' => array_diff($changelist['install'], $this->processedExtensions[$type]['install']),
+        'uninstall' => array_diff($changelist['uninstall'], $this->processedExtensions[$type]['uninstall']),
+      );
+    }
+    return $unprocessed;
   }
 
   /**
@@ -200,7 +397,9 @@ public function getUnprocessed($op) {
    *   The ConfigImporter instance.
    */
   public function import() {
-    if ($this->hasUnprocessedChanges()) {
+    if ($this->hasUnprocessedConfigurationChanges()) {
+      $this->createExtensionChangelist();
+
       // Ensure that the changes have been validated.
       $this->validate();
 
@@ -208,19 +407,20 @@ public function import() {
         // Another process is synchronizing configuration.
         throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
       }
+
+      // Process any extension changes before importing configuration.
+      $this->handleExtensions();
+
       // First pass deleted, then new, and lastly changed configuration, in order
       // to handle dependencies correctly.
-      // @todo Implement proper dependency ordering using
-      //   https://drupal.org/node/2080823
       foreach (array('delete', 'create', 'update') as $op) {
-        foreach ($this->getUnprocessed($op) as $name) {
-          $this->process($op, $name);
+        foreach ($this->getUnprocessedConfiguration($op) as $name) {
+          $this->processConfiguration($op, $name);
         }
       }
       // Allow modules to react to a import.
       $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
 
-
       // The import is now complete.
       $this->lock->release(static::LOCK_ID);
       $this->reset();
@@ -253,12 +453,58 @@ public function validate() {
    * @param string $name
    *   The name of the configuration to process.
    */
-  protected function process($op, $name) {
+  protected function processConfiguration($op, $name) {
     if (!$this->importInvokeOwner($op, $name)) {
       $this->importConfig($op, $name);
     }
   }
 
+  /**
+   * Processes an extension change.
+   *
+   * @param string $type
+   *   The type of extension, either 'module' or 'theme'.
+   * @param string $op
+   *   The change operation.
+   * @param string $name
+   *   The name of the extension to process.
+   */
+  protected function processExtension($type, $op, $name) {
+    // Set the config installer to use the staging directory instead of the
+    // extensions own default config directories.
+    \Drupal::service('config.installer')
+      ->setSyncing(TRUE)
+      ->setSourceStorage($this->storageComparer->getSourceStorage());
+    if ($type == 'module') {
+      $this->moduleHandler->$op(array($name), FALSE);
+      // Installing a module can cause a kernel boot therefore reinject all the
+      // services.
+      $this->reInjectMe();
+      // During a module install or uninstall the container is rebuilt and the
+      // module handler is called from drupal_get_complete_schema(). This causes
+      // the container's instance of the module handler not to have loaded all
+      // the enabled modules.
+      $this->moduleHandler->loadAll();
+    }
+    if ($type == 'theme') {
+      // Theme disables possible remove default or admin themes therefore we
+      // need to import this before doing any. If there are no disables and
+      // the default or admin theme is change this will be picked up whilst
+      // processing configuration.
+      if ($op == 'disable' && $this->processedSystemTheme === FALSE) {
+        $this->importConfig('update', 'system.theme');
+        $this->configManager->getConfigFactory()->reset('system.theme');
+        $this->processedSystemTheme = TRUE;
+      }
+      $this->themeHandler->$op(array($name));
+    }
+
+    $this->setProcessedExtension($type, $op, $name);
+    \Drupal::service('config.installer')
+      ->setSyncing(FALSE)
+      ->resetSourceStorage();
+  }
+
   /**
    * Writes a configuration change from the source to the target storage.
    *
@@ -277,7 +523,7 @@ protected function importConfig($op, $name) {
       $config->setData($data ? $data : array());
       $config->save();
     }
-    $this->setProcessed($op, $name);
+    $this->setProcessedConfiguration($op, $name);
   }
 
   /**
@@ -325,7 +571,7 @@ protected function importInvokeOwner($op, $name) {
         throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type)));
       }
       $entity_storage->$method($name, $new_config, $old_config);
-      $this->setProcessed($op, $name);
+      $this->setProcessedConfiguration($op, $name);
       return TRUE;
     }
     return FALSE;
@@ -341,4 +587,74 @@ public function alreadyImporting() {
     return !$this->lock->lockMayBeAvailable(static::LOCK_ID);
   }
 
+  /**
+   * Returns the identifier for events and locks.
+   *
+   * @return string
+   *   The identifier for events and locks.
+   */
+  public function getId() {
+    return static::LOCK_ID;
+  }
+
+  /**
+   * Checks if a configuration object will be updated by the import.
+   *
+   * @param string $config_name
+   *   The configuration object name.
+   *
+   * @return bool
+   *   TRUE if the configuration object will be updated.
+   */
+  protected function hasUpdate($config_name) {
+    return in_array($config_name, $this->getUnprocessedConfiguration('update'));
+  }
+
+  /**
+   * Handle changes to installed modules and themes.
+   */
+  protected function handleExtensions() {
+    $processed_extension = FALSE;
+    foreach (array('install', 'uninstall') as $op) {
+      $modules = $this->getUnprocessedExtensions('module');
+      foreach($modules[$op] as $module) {
+        $processed_extension = TRUE;
+        $this->processExtension('module', $op, $module);
+      }
+    }
+    foreach (array('enable', 'disable') as $op) {
+      $themes = $this->getUnprocessedExtensions('theme');
+      foreach($themes[$op] as $theme) {
+        $processed_extension = TRUE;
+        $this->processExtension('theme', $op, $theme);
+      }
+    }
+
+    if ($processed_extension) {
+      // Recalculate differences as default config could have been imported.
+      $this->storageComparer->reset();
+      $this->processed = $this->storageComparer->getEmptyChangelist();
+      // Modules have been updated. Services etc might have changed.
+      // We don't reinject storage comparer because swapping out the active
+      // store during config import is a complete nonsense.
+      $this->recalculateChangelist = TRUE;
+    }
+  }
+
+  /**
+   * Gets all the service dependencies from \Drupal.
+   *
+   * Since the ConfigImporter handles module installation the kernel and the
+   * container can be rebuilt and altered during processing. It is necessary to
+   * keep the services used by the importer in sync.
+   */
+  protected function reInjectMe() {
+    $this->eventDispatcher = \Drupal::service('event_dispatcher');
+    $this->configFactory = \Drupal::configFactory();
+    $this->entityManager = \Drupal::entityManager();
+    $this->lock = \Drupal::lock();
+    $this->typedConfigManager = \Drupal::service('config.typed');
+    $this->moduleHandler = \Drupal::moduleHandler();
+    $this->themeHandler = \Drupal::service('theme_handler');
+  }
 }
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index 038dbd63b84e..7ee1b56f3174 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -48,6 +48,20 @@ class ConfigInstaller implements ConfigInstallerInterface {
    */
   protected $eventDispatcher;
 
+  /**
+   * The configuration storage that provides the default configuration.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $sourceStorage;
+
+  /**
+   * Is configuration being created as part of a configuration sync.
+   *
+   * @var bool
+   */
+  protected $isSyncing = FALSE;
+
   /**
    * Constructs the configuration installer.
    *
@@ -75,7 +89,7 @@ public function __construct(ConfigFactoryInterface $config_factory, StorageInter
    */
   public function installDefaultConfig($type, $name) {
     // Get all default configuration owned by this extension.
-    $source_storage = new ExtensionInstallStorage($this->activeStorage);
+    $source_storage = $this->getSourceStorage();
     $config_to_install = $source_storage->listAll($name . '.');
 
     // Work out if this extension provides default configuration for any other
@@ -130,6 +144,16 @@ public function installDefaultConfig($type, $name) {
           $new_config->setData($data[$name]);
         }
         if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) {
+
+          // If we are syncing do not create configuration entities. Pluggable
+          // configuration entities can have dependencies on modules that are
+          // not yet enabled. This approach means that any code that expects
+          // default configuration entities to exist will be unstable after the
+          // module has been enabled and before the config entity has been
+          // imported.
+          if ($this->isSyncing) {
+            continue;
+          }
           $entity_storage = $this->configManager
             ->getEntityManager()
             ->getStorage($entity_type);
@@ -159,4 +183,49 @@ public function installDefaultConfig($type, $name) {
     $this->configFactory->reset();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setSourceStorage(StorageInterface $storage) {
+    $this->sourceStorage = $storage;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetSourceStorage() {
+    $this->sourceStorage = null;
+    return $this;
+  }
+
+  /**
+   * Gets the configuration storage that provides the default configuration.
+   *
+   * @return \Drupal\Core\Config\StorageInterface
+   *   The configuration storage that provides the default configuration.
+   */
+  public function getSourceStorage() {
+    if (!isset($this->sourceStorage)) {
+      // Default to using the ExtensionInstallStorage which searches extension's
+      // config directories for default configuration.
+      $this->sourceStorage = new ExtensionInstallStorage($this->activeStorage);
+    }
+    return $this->sourceStorage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSyncing($status) {
+    $this->isSyncing = $status;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSyncing() {
+    return $this->isSyncing;
+  }
 }
diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
index 927c6105422e..6e7c20d0269f 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
@@ -37,4 +37,38 @@ interface ConfigInstallerInterface {
    */
   public function installDefaultConfig($type, $name);
 
+  /**
+   * Sets the configuration storage that provides the default configuration.
+   *
+   * @param \Drupal\Core\Config\StorageInterface $storage
+   *
+   * @return self
+   *   The configuration installer.
+   */
+  public function setSourceStorage(StorageInterface $storage);
+
+  /**
+   * Resets the configuration storage that provides the default configuration.
+   *
+   * @return self
+   *   The configuration installer.
+   */
+  public function resetSourceStorage();
+
+  /**
+   * Sets the status of the isSyncing flag.
+   *
+   * @param bool $status
+   *   The status of the sync flag.
+   */
+  public function setSyncing($status);
+
+  /**
+   * Gets the syncing state.
+   *
+   * @return bool
+   *   Returns TRUE is syncing flag set.
+   */
+  public function isSyncing();
+
 }
diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php
index d1048ea966b2..6fd55c948e67 100644
--- a/core/lib/Drupal/Core/Config/ConfigManager.php
+++ b/core/lib/Drupal/Core/Config/ConfigManager.php
@@ -90,6 +90,13 @@ public function getEntityManager() {
     return $this->entityManager;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigFactory() {
+    return $this->configFactory;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php
index b5084fe7af56..4da9474d845a 100644
--- a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php
+++ b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php
@@ -31,6 +31,14 @@ public function getEntityTypeIdByName($name);
    */
   public function getEntityManager();
 
+  /**
+   * Gets the config factory.
+   *
+   * @return \Drupal\Core\Config\ConfigFactoryInterface
+   *   The entity manager.
+   */
+  public function getConfigFactory();
+
   /**
    * Return a formatted diff of a named config between two storages.
    *
diff --git a/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php
index ac895373cf17..ee467b6be812 100644
--- a/core/lib/Drupal/Core/Config/StorageComparer.php
+++ b/core/lib/Drupal/Core/Config/StorageComparer.php
@@ -6,6 +6,8 @@
  */
 
 namespace Drupal\Core\Config;
+
+use Drupal\Component\Utility\String;
 use Drupal\Core\Config\Entity\ConfigDependencyManager;
 
 /**
@@ -118,11 +120,23 @@ public function getChangelist($op = NULL) {
    *   The change operation performed. Either delete, create or update.
    * @param array $changes
    *   Array of changes to add to the changelist.
+   * @param array $sort_order
+   *   Array to sort that can be used to sort the changelist. This array must
+   *   contain all the items that are in the change list.
    */
-  protected function addChangeList($op, array $changes) {
+  protected function addChangeList($op, array $changes, array $sort_order = NULL) {
     // Only add changes that aren't already listed.
     $changes = array_diff($changes, $this->changelist[$op]);
     $this->changelist[$op] = array_merge($this->changelist[$op], $changes);
+    if (isset($sort_order)) {
+      $count = count($this->changelist[$op]);
+      // Sort the changlist in the same order as the $sort_order array and
+      // ensure the array is keyed from 0.
+      $this->changelist[$op] = array_values(array_intersect($sort_order, $this->changelist[$op]));
+      if ($count != count($this->changelist[$op])) {
+        throw new \InvalidArgumentException(String::format('Sorting the @op changelist should not change its length.', array('@op' => $op)));
+      }
+    }
   }
 
   /**
@@ -188,8 +202,9 @@ protected function addChangelistUpdate() {
     if (!empty($recreates)) {
       // Recreates should become deletes and creates. Deletes should be ordered
       // so that dependencies are deleted first.
-      $this->addChangeList('create', $recreates);
-      $this->addChangeList('delete', array_reverse($recreates));
+      $this->addChangeList('create', $recreates, $this->sourceNames);
+      $this->addChangeList('delete', $recreates, array_reverse($this->targetNames));
+
     }
   }
 
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
index 75f06d4d675b..72e2232b9945 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
@@ -28,7 +28,7 @@ class ConfigImportSubscriber implements EventSubscriberInterface {
    */
   public function onConfigImporterValidate(ConfigImporterEvent $event) {
     foreach (array('delete', 'create', 'update') as $op) {
-      foreach ($event->getConfigImporter()->getUnprocessed($op) as $name) {
+      foreach ($event->getConfigImporter()->getUnprocessedConfiguration($op) as $name) {
         Config::validateName($name);
       }
     }
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 17b4239b0cb4..ac48c1758830 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -582,6 +582,12 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
     // Required for module installation checks.
     include_once DRUPAL_ROOT . '/core/includes/install.inc';
 
+    /** @var \Drupal\Core\Config\ConfigInstaller $config_installer */
+    $config_installer = \Drupal::service('config.installer');
+    $sync_status = $config_installer->isSyncing();
+    if ($sync_status) {
+      $source_storage = $config_installer->getSourceStorage();
+    }
     $modules_installed = array();
     foreach ($module_list as $module) {
       $enabled = $extension_config->get("module.$module") !== NULL;
@@ -671,6 +677,18 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         }
 
         // Install default configuration of the module.
+        $config_installer = \Drupal::service('config.installer');
+        if ($sync_status) {
+          $config_installer
+            ->setSyncing(TRUE)
+            ->setSourceStorage($source_storage);
+        }
+        else {
+          // If we're not in a config synchronisation reset the source storage
+          // so that the extension install storage will pick up the new
+          // configuration.
+          $config_installer->resetSourceStorage();
+        }
         \Drupal::service('config.installer')->installDefaultConfig('module', $module);
 
         // If the module has no current updates, but has some that were
@@ -732,7 +750,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
 
           // Skip already uninstalled modules.
           if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) {
-            $module_list[$dependent] = TRUE;
+            $module_list[$dependent] = $dependent;
           }
         }
       }
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index 1474102fc018..b43d3e47bea7 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -146,6 +146,15 @@ public function enable(array $theme_list) {
       // Refresh the theme list as installation of default configuration needs
       // an updated list to work.
       $this->reset();
+
+      // The default config installation storage only knows about the currently
+      // enabled list of themes, so it has to be reset in order to pick up the
+      // default config of the newly installed theme. However, do not reset the
+      // source storage when synchronizing configuration, since that would
+      // needlessly trigger a reload of the whole configuration to be imported.
+      if (!$this->configInstaller->isSyncing()) {
+        $this->configInstaller->resetSourceStorage();
+      }
       // Install default configuration of the theme.
       $this->configInstaller->installDefaultConfig('theme', $key);
     }
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 2d4b811a2674..183462e111c0 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -8,6 +8,7 @@
 use Drupal\block\BlockInterface;
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\language\Entity\Language;
+use Drupal\system\Entity\Menu;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
 /**
@@ -422,10 +423,12 @@ function block_user_role_delete($role) {
 /**
  * Implements hook_menu_delete().
  */
-function block_menu_delete($menu) {
-  foreach (entity_load_multiple('block') as $block) {
-    if ($block->get('plugin') == 'system_menu_block:' . $menu->id()) {
-      $block->delete();
+function block_menu_delete(Menu $menu) {
+  if (!$menu->isSyncing()) {
+    foreach (entity_load_multiple('block') as $block) {
+      if ($block->get('plugin') == 'system_menu_block:' . $menu->id()) {
+        $block->delete();
+      }
     }
   }
 }
diff --git a/core/modules/comment/lib/Drupal/comment/CommentManager.php b/core/modules/comment/lib/Drupal/comment/CommentManager.php
index 5b1820cdc28f..c522fd34c93c 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentManager.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentManager.php
@@ -184,6 +184,14 @@ public function addDefaultField($entity_type, $bundle, $field_name = 'comment',
         ))
         ->save();
 
+      // The comment field should be hidden in all other form displays.
+      foreach ($this->entityManager->getFormModes($entity_type) as $id => $form_mode) {
+        $display = entity_get_form_display($entity_type, $bundle, $id);
+        // Only update existing displays.
+        if ($display && !$display->isNew()) {
+          $display->removeComponent($field_name)->save();
+        }
+      }
       // Set default to display comment list.
       entity_get_display($entity_type, $bundle, 'default')
         ->setComponent($field_name, array(
@@ -192,6 +200,15 @@ public function addDefaultField($entity_type, $bundle, $field_name = 'comment',
           'weight' => 20,
         ))
         ->save();
+        // The comment field should be hidden in all other view displays.
+      foreach ($this->entityManager->getViewModes($entity_type) as $id => $view_mode) {
+        $display = entity_get_display($entity_type, $bundle, $id);
+        // Only update existing displays.
+        if ($display && !$display->isNew()) {
+          $display->removeComponent($field_name)->save();
+        }
+      }
+
     }
     $this->addBodyField($entity_type, $field_name);
   }
diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
index c6000016c1e7..c48a77649fc3 100644
--- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
+++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
@@ -7,6 +7,10 @@
 
 namespace Drupal\config\Form;
 
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Config\ConfigManagerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Config\StorageInterface;
@@ -72,6 +76,20 @@ class ConfigSync extends FormBase {
    */
   protected $typedConfigManager;
 
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The theme handler.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
   /**
    * Constructs the object.
    *
@@ -89,8 +107,12 @@ class ConfigSync extends FormBase {
    *   The url generator service.
    * @param \Drupal\Core\Config\TypedConfigManager $typed_config
    *   The typed configuration manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler
+   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+   *   The theme handler
    */
-  public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config) {
+  public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, UrlGeneratorInterface $url_generator, TypedConfigManager $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
     $this->sourceStorage = $sourceStorage;
     $this->targetStorage = $targetStorage;
     $this->lock = $lock;
@@ -98,6 +120,8 @@ public function __construct(StorageInterface $sourceStorage, StorageInterface $t
     $this->configManager = $config_manager;
     $this->urlGenerator = $url_generator;
     $this->typedConfigManager = $typed_config;
+    $this->moduleHandler = $module_handler;
+    $this->themeHandler = $theme_handler;
   }
 
   /**
@@ -111,7 +135,9 @@ public static function create(ContainerInterface $container) {
       $container->get('event_dispatcher'),
       $container->get('config.manager'),
       $container->get('url_generator'),
-      $container->get('config.typed')
+      $container->get('config.typed'),
+      $container->get('module_handler'),
+      $container->get('theme_handler')
     );
   }
 
@@ -222,24 +248,27 @@ public function submitForm(array &$form, array &$form_state) {
       $this->eventDispatcher,
       $this->configManager,
       $this->lock,
-      $this->typedConfigManager
+      $this->typedConfigManager,
+      $this->moduleHandler,
+      $this->themeHandler
     );
     if ($config_importer->alreadyImporting()) {
       drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
     }
     else{
-      $config_importer->initialize();
+      $operations = $config_importer->initialize();
       $batch = array(
-        'operations' => array(
-          array(array(get_class($this), 'processBatch'), array($config_importer)),
-        ),
+        'operations' => array(),
         'finished' => array(get_class($this), 'finishBatch'),
         'title' => t('Synchronizing configuration'),
         'init_message' => t('Starting configuration synchronization.'),
-        'progress_message' => t('Synchronized @current configuration files out of @total.'),
+        '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);
     }
@@ -253,13 +282,13 @@ public function submitForm(array &$form, array &$form_state) {
    * @param $context
    *   The batch context.
    */
-  public static function processBatch(BatchConfigImporter $config_importer, &$context) {
+  public static function processBatch(BatchConfigImporter $config_importer, $operation, &$context) {
     if (!isset($context['sandbox']['config_importer'])) {
       $context['sandbox']['config_importer'] = $config_importer;
     }
 
     $config_importer = $context['sandbox']['config_importer'];
-    $config_importer->processBatch($context);
+    $config_importer->$operation($context);
   }
 
   /**
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php
new file mode 100644
index 000000000000..038e318ee5ab
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportAllTest.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config\Tests\ConfigImportAllTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\StorageComparer;
+use Drupal\system\Tests\Module\ModuleTestBase;
+
+class ConfigImportAllTest extends ModuleTestBase {
+
+  /**
+   * The profile to install as a basis for testing.
+   *
+   * Using the standard profile as this has a lot of additional configuration.
+   *
+   * @var string
+   */
+  protected $profile = 'standard';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Import configuration from all modules and the standard profile',
+      'description' => 'Tests the largest configuration import possible with the modules and profiles provided by core.',
+      'group' => 'Configuration',
+    );
+  }
+
+  /**
+   * Tests that a fixed set of modules can be installed and uninstalled.
+   */
+  public function testInstallUninstall() {
+
+    // Get a list of modules to enable.
+    $all_modules = system_rebuild_module_data();
+    $all_modules = array_filter($all_modules, function ($module) {
+      // Filter hidden, already enabled modules and modules in the Testing
+      // package.
+      if (!empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing') {
+        return FALSE;
+      }
+      return TRUE;
+    });
+
+    // Install every module possible.
+    \Drupal::moduleHandler()->install(array_keys($all_modules));
+
+    $this->assertModules(array_keys($all_modules), TRUE);
+    foreach($all_modules as $module => $info) {
+      $this->assertModuleConfig($module);
+      $this->assertModuleTablesExist($module);
+    }
+
+    // Export active config to staging
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
+
+    system_list_reset();
+    $this->resetAll();
+
+    // Delete every field on the site so all modules can be uninstalled. For
+    // example, if a comment field exists then module becomes required and can
+    // not be uninstalled.
+    $fields = \Drupal::service('field.info')->getFields();
+    foreach ($fields as $field) {
+      entity_invoke_bundle_hook('delete', $field->entity_type, $field->entity_type . '__' . $field->name);
+      $field->delete();
+    }
+    // Purge the data.
+    field_purge_batch(1000);
+
+    system_list_reset();
+    $all_modules = system_rebuild_module_data();
+    $modules_to_uninstall = array_filter($all_modules, function ($module) {
+      // Filter required and not enabled modules.
+      if (!empty($module->info['required']) || $module->status == FALSE) {
+        return FALSE;
+      }
+      return TRUE;
+    });
+
+    $this->assertTrue(isset($modules_to_uninstall['comment']), 'The comment module will be disabled');
+
+    // Uninstall all modules that can be uninstalled.
+    \Drupal::moduleHandler()->uninstall(array_keys($modules_to_uninstall));
+
+    $this->assertModules(array_keys($modules_to_uninstall), FALSE);
+    foreach($modules_to_uninstall as $module => $info) {
+      $this->assertNoModuleConfig($module);
+      $this->assertModuleTablesDoNotExist($module);
+    }
+
+    // Import the configuration thereby re-installing all the modules.
+    $this->configImporter()->import();
+
+    // Check that all modules that were uninstalled are now reinstalled.
+    $this->assertModules(array_keys($modules_to_uninstall), TRUE);
+    foreach($modules_to_uninstall as $module => $info) {
+      $this->assertModuleConfig($module);
+      $this->assertModuleTablesExist($module);
+    }
+
+    // Ensure that we have no configuration changes to import.
+    $storage_comparer = new StorageComparer(
+      $this->container->get('config.storage.staging'),
+      $this->container->get('config.storage')
+    );
+    $this->assertIdentical($storage_comparer->createChangelist()->getChangelist(), $storage_comparer->getEmptyChangelist());
+  }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php
index 9f5ed9099723..3f86c9b273f2 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php
@@ -45,6 +45,8 @@ public function setUp() {
     $this->installSchema('system', 'config_snapshot');
     $this->installSchema('node', 'node');
 
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
+
     // Set up the ConfigImporter object for testing.
     $storage_comparer = new StorageComparer(
       $this->container->get('config.storage.staging'),
@@ -55,9 +57,10 @@ public function setUp() {
       $this->container->get('event_dispatcher'),
       $this->container->get('config.manager'),
       $this->container->get('lock'),
-      $this->container->get('config.typed')
+      $this->container->get('config.typed'),
+      $this->container->get('module_handler'),
+      $this->container->get('theme_handler')
     );
-    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
   }
 
   public function testRecreateEntity() {
@@ -89,21 +92,19 @@ public function testRecreateEntity() {
     $this->configImporter->reset();
     // A node type, a field, a field instance an entity view display and an
     // entity form display will be recreated.
-    $creates = $this->configImporter->getUnprocessed('create');
-    $deletes = $this->configImporter->getUnprocessed('delete');
+    $creates = $this->configImporter->getUnprocessedConfiguration('create');
+    $deletes = $this->configImporter->getUnprocessedConfiguration('delete');
     $this->assertEqual(5, count($creates), 'There are 5 configuration items to create.');
     $this->assertEqual(5, count($deletes), 'There are 5 configuration items to delete.');
-    $this->assertEqual(0, count($this->configImporter->getUnprocessed('update')), 'There are no configuration items to update.');
+    $this->assertEqual(0, count($this->configImporter->getUnprocessedConfiguration('update')), 'There are no configuration items to update.');
     $this->assertIdentical($creates, array_reverse($deletes), 'Deletes and creates contain the same configuration names in opposite orders due to dependencies.');
 
     $this->configImporter->import();
 
     // Verify that there is nothing more to import.
-    $this->assertFalse($this->configImporter->reset()->hasUnprocessedChanges());
+    $this->assertFalse($this->configImporter->reset()->hasUnprocessedConfigurationChanges());
     $content_type = entity_load('node_type', $type_name);
     $this->assertEqual('Node type one', $content_type->label());
   }
 
-
 }
-
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
index 4a38a4df3eba..084be152d3ec 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\config\Tests;
 
+use Drupal\Core\Config\InstallStorage;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -14,7 +15,9 @@
  */
 class ConfigImportUITest extends WebTestBase {
 
-  public static $modules = array('config', 'config_test');
+  // Enable the Options and Text modules to ensure dependencies are handled
+  // correctly.
+  public static $modules = array('config', 'config_test', 'config_import_test', 'text', 'options');
 
   public static function getInfo() {
     return array(
@@ -38,6 +41,7 @@ function setUp() {
   function testImport() {
     $name = 'system.site';
     $dynamic_name = 'config_test.dynamic.new';
+    /** @var \Drupal\Core\Config\StorageInterface $staging */
     $staging = $this->container->get('config.storage.staging');
 
     $this->drupalGet('admin/config/development/configuration');
@@ -65,16 +69,61 @@ function testImport() {
     $staging->write($dynamic_name, $original_dynamic_data);
     $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
 
+    // Enable the Action and Ban modules during import. The Ban
+    // module is used because it creates a table during the install. The Action
+    // module is used because it creates a single simple configuration file
+    // during the install.
+    $core_extension = \Drupal::config('core.extension')->get();
+    $core_extension['module']['action'] = 0;
+    $core_extension['module']['ban'] = 0;
+    $core_extension['module'] = module_config_sort($core_extension['module']);
+    $core_extension['theme']['bartik'] = 0;
+    $staging->write('core.extension', $core_extension);
+
+    // Use the install storage so that we can read configuration from modules
+    // and themes that are not installed.
+    $install_storage = new InstallStorage();
+
+    // Set the Bartik theme as default.
+    $system_theme = \Drupal::config('system.theme')->get();
+    $system_theme['default'] = 'bartik';
+    $staging->write('system.theme', $system_theme);
+    $staging->write('bartik.settings', $install_storage->read('bartik.settings'));
+
+    // Read the action config from module default config folder.
+    $action_settings = $install_storage->read('action.settings');
+    $action_settings['recursion_limit'] = 50;
+    $staging->write('action.settings', $action_settings);
+
+    // Uninstall the Options and Text modules to ensure that dependencies are
+    // handled correctly. Options depends on Text so Text should be installed
+    // first. Since they were enabled during the test setup the core.extension
+    // file in staging will already contain them.
+    \Drupal::moduleHandler()->uninstall(array('text', 'options'));
+
+    // Set the state system to record installations and uninstallations.
+    \Drupal::state()->set('ConfigImportUITest.core.extension.modules_installed', array());
+    \Drupal::state()->set('ConfigImportUITest.core.extension.modules_uninstalled', array());
+
     // Verify that both appear as ready to import.
     $this->drupalGet('admin/config/development/configuration');
     $this->assertText($name);
     $this->assertText($dynamic_name);
+    $this->assertText('core.extension');
+    $this->assertText('system.theme');
+    $this->assertText('action.settings');
+    $this->assertText('bartik.settings');
     $this->assertFieldById('edit-submit', t('Import all'));
 
     // Import and verify that both do not appear anymore.
     $this->drupalPostForm(NULL, array(), t('Import all'));
     $this->assertNoText($name);
     $this->assertNoText($dynamic_name);
+    $this->assertNoText('core.extension');
+    $this->assertNoText('system.theme');
+    $this->assertNoText('action.settings');
+    $this->assertNoText('bartik.settings');
+
     $this->assertNoFieldById('edit-submit', t('Import all'));
 
     // Verify that there are no further changes to import.
@@ -88,6 +137,85 @@ function testImport() {
 
     // Verify the cache got cleared.
     $this->assertTrue(isset($GLOBALS['hook_cache_flush']));
+
+    $this->rebuildContainer();
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('ban'), 'Ban module installed during import.');
+    $this->assertTrue(\Drupal::database()->schema()->tableExists('ban_ip'), 'The database table ban_ip exists.');
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('action'), 'Action module installed during import.');
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('options'), 'Options module installed during import.');
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('text'), 'Text module installed during import.');
+
+    $theme_info = \Drupal::service('theme_handler')->listInfo();
+    $this->assertTrue($theme_info['bartik']->status, 'Bartik theme enabled during import.');
+
+    // Ensure installations and uninstallation occur as expected.
+    $installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
+    $uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
+    $expected = array('ban', 'action', 'text', 'options');
+    $this->assertIdentical($expected, $installed, 'Ban, Action, Text and Options modules installed in the correct order.');
+    $this->assertTrue(empty($uninstalled), 'No modules uninstalled during import');
+
+    // Verify that the action.settings configuration object was only written
+    // once during the import process and only with the value set in the staged
+    // configuration. This verifies that the module's default configuration is
+    // used during configuration import and, additionally, that after installing
+    // a module, that configuration is not synced twice.
+    $recursion_limit_values = \Drupal::state()->get('ConfigImportUITest.action.settings.recursion_limit', array());
+    $this->assertIdentical($recursion_limit_values, array(50));
+
+    $core_extension = \Drupal::config('core.extension')->get();
+    unset($core_extension['module']['action']);
+    unset($core_extension['module']['ban']);
+    unset($core_extension['module']['options']);
+    unset($core_extension['module']['text']);
+    unset($core_extension['theme']['bartik']);
+    $core_extension['disabled']['theme']['bartik'] = 0;
+    $staging->write('core.extension', $core_extension);
+    $staging->delete('action.settings');
+    $staging->delete('text.settings');
+
+    $system_theme = \Drupal::config('system.theme')->get();
+    $system_theme['default'] = 'stark';
+    $system_theme['admin'] = 'stark';
+    $staging->write('system.theme', $system_theme);
+
+    // Set the state system to record installations and uninstallations.
+    \Drupal::state()->set('ConfigImportUITest.core.extension.modules_installed', array());
+    \Drupal::state()->set('ConfigImportUITest.core.extension.modules_uninstalled', array());
+
+    // Verify that both appear as ready to import.
+    $this->drupalGet('admin/config/development/configuration');
+    $this->assertText('core.extension');
+    $this->assertText('system.theme');
+    $this->assertText('action.settings');
+
+    // Import and verify that both do not appear anymore.
+    $this->drupalPostForm(NULL, array(), t('Import all'));
+    $this->assertNoText('core.extension');
+    $this->assertNoText('system.theme');
+    $this->assertNoText('action.settings');
+
+    $this->rebuildContainer();
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('ban'), 'Ban module uninstalled during import.');
+    $this->assertFalse(\Drupal::database()->schema()->tableExists('ban_ip'), 'The database table ban_ip does not exist.');
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('action'), 'Action module uninstalled during import.');
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('options'), 'Options module uninstalled during import.');
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('text'), 'Text module uninstalled during import.');
+
+    // Ensure installations and uninstallation occur as expected.
+    $installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
+    $uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
+    $expected = array('options', 'text', 'ban', 'action');
+    $this->assertIdentical($expected, $uninstalled, 'Options, Text, Action and Ban modules uninstalled in the correct order.');
+    $this->assertTrue(empty($installed), 'No modules installed during import');
+
+    $theme_info = \Drupal::service('theme_handler')->listInfo();
+    $this->assertTrue(isset($theme_info['bartik']) && !$theme_info['bartik']->status, 'Bartik theme disabled during import.');
+
+    // Verify that the action.settings configuration object was only deleted
+    // once during the import process.
+    $delete_called = \Drupal::state()->get('ConfigImportUITest.action.settings.delete', 0);
+    $this->assertIdentical($delete_called, 1, "The action.settings configuration was deleted once during configuration import.");
   }
 
   /**
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
index 8e99296012ac..ff154b78956e 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
@@ -50,6 +50,8 @@ function setUp() {
     // so it has to be cleared out manually.
     unset($GLOBALS['hook_config_test']);
 
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
+
     // Set up the ConfigImporter object for testing.
     $storage_comparer = new StorageComparer(
       $this->container->get('config.storage.staging'),
@@ -60,9 +62,10 @@ function setUp() {
       $this->container->get('event_dispatcher'),
       $this->container->get('config.manager'),
       $this->container->get('lock'),
-      $this->container->get('config.typed')
+      $this->container->get('config.typed'),
+      $this->container->get('module_handler'),
+      $this->container->get('theme_handler')
     );
-    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
   }
 
   /**
@@ -145,8 +148,7 @@ function testDeleted() {
     $this->assertTrue(isset($GLOBALS['hook_config_test']['predelete']));
     $this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
 
-    // Verify that there is nothing more to import.
-    $this->assertFalse($this->configImporter->hasUnprocessedChanges());
+    $this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
   }
 
   /**
@@ -193,7 +195,7 @@ function testNew() {
     $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
 
     // Verify that there is nothing more to import.
-    $this->assertFalse($this->configImporter->hasUnprocessedChanges());
+    $this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
   }
 
   /**
@@ -248,7 +250,7 @@ function testUpdated() {
     $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
 
     // Verify that there is nothing more to import.
-    $this->assertFalse($this->configImporter->hasUnprocessedChanges());
+    $this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
   }
 }
 
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php
index 137635d1df89..7c6c1cbed4c7 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php
@@ -32,6 +32,7 @@ public static function getInfo() {
   public function setUp() {
     parent::setUp();
     $this->installSchema('system', 'config_snapshot');
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
   }
 
   /**
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php
index d8e7187de927..295ff74d1356 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php
@@ -36,6 +36,7 @@ public function setUp() {
     // Update the config snapshot. This allows the parent::setUp() to write
     // configuration files.
     \Drupal::service('config.manager')->createSnapshot(\Drupal::service('config.storage'), \Drupal::service('config.storage.snapshot'));
+    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
   }
 
   /**
diff --git a/core/modules/config/tests/config_import_test/config_import_test.info.yml b/core/modules/config/tests/config_import_test/config_import_test.info.yml
new file mode 100644
index 000000000000..87cdd025cc53
--- /dev/null
+++ b/core/modules/config/tests/config_import_test/config_import_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Configuration import test'
+type: module
+package: Testing
+version: VERSION
+core: 8.x
+hidden: true
diff --git a/core/modules/config/tests/config_import_test/config_import_test.module b/core/modules/config/tests/config_import_test/config_import_test.module
new file mode 100644
index 000000000000..936b72b74b13
--- /dev/null
+++ b/core/modules/config/tests/config_import_test/config_import_test.module
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Provides configuration import test helpers.
+ */
diff --git a/core/modules/config/tests/config_import_test/config_import_test.services.yml b/core/modules/config/tests/config_import_test/config_import_test.services.yml
new file mode 100644
index 000000000000..da4fa1e90314
--- /dev/null
+++ b/core/modules/config/tests/config_import_test/config_import_test.services.yml
@@ -0,0 +1,6 @@
+services:
+  config_import_test.event_subscriber:
+    class: Drupal\config_import_test\EventSubscriber
+    tags:
+      - { name: event_subscriber }
+    arguments: ['@state']
diff --git a/core/modules/config/tests/config_import_test/lib/Drupal/config_import_test/EventSubscriber.php b/core/modules/config/tests/config_import_test/lib/Drupal/config_import_test/EventSubscriber.php
new file mode 100644
index 000000000000..41ff79151ab5
--- /dev/null
+++ b/core/modules/config/tests/config_import_test/lib/Drupal/config_import_test/EventSubscriber.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_import_test\EventSubscriber.
+ */
+
+namespace Drupal\config_import_test;
+
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
+use Drupal\Core\Config\ConfigImporterEvent;
+use Drupal\Core\KeyValueStore\StateInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Config import subscriber for config import events.
+ */
+class EventSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\StateInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs the event subscriber.
+   *
+   * @param \Drupal\Core\KeyValueStore\StateInterface $state
+   *   The key value store.
+   */
+  public function __construct(StateInterface $state) {
+    $this->state = $state;
+  }
+
+  /**
+   * Validates the configuration to be imported.
+   *
+   * @param \Drupal\Core\Config\ConfigImporterEvent $event
+   *   The Event to process.
+   *
+   * @throws \Drupal\Core\Config\ConfigNameException
+   */
+  public function onConfigImporterValidate(ConfigImporterEvent $event) {
+
+  }
+
+  /**
+   * Reacts to a config save and records information in state for testing.
+   *
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   */
+  public function onConfigSave(ConfigCrudEvent $event) {
+    $config = $event->getConfig();
+    if ($config->getName() == 'action.settings') {
+      $values = $this->state->get('ConfigImportUITest.action.settings.recursion_limit', array());
+      $values[] = $config->get('recursion_limit');
+      $this->state->set('ConfigImportUITest.action.settings.recursion_limit', $values);
+    }
+
+    if ($config->getName() == 'core.extension') {
+      $installed = $this->state->get('ConfigImportUITest.core.extension.modules_installed', array());
+      $uninstalled = $this->state->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
+      $original = $config->getOriginal('module');
+      $data = $config->get('module');
+      $install = array_diff_key($data, $original);
+      if (!empty($install)) {
+        $installed[] = key($install);
+      }
+      $uninstall = array_diff_key($original, $data);
+      if (!empty($uninstall)) {
+        $uninstalled[] = key($uninstall);
+      }
+
+      $this->state->set('ConfigImportUITest.core.extension.modules_installed', $installed);
+      $this->state->set('ConfigImportUITest.core.extension.modules_uninstalled', $uninstalled);
+    }
+  }
+
+  /**
+   * Reacts to a config delete and records information in state for testing.
+   *
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   */
+  public function onConfigDelete(ConfigCrudEvent $event) {
+    $config = $event->getConfig();
+    if ($config->getName() == 'action.settings') {
+      $value = $this->state->get('ConfigImportUITest.action.settings.delete', 0);
+      $this->state->set('ConfigImportUITest.action.settings.delete', $value + 1);
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[ConfigEvents::SAVE][] = array('onConfigSave', 40);
+    $events[ConfigEvents::DELETE][] = array('onConfigDelete', 40);
+    return $events;
+  }
+
+}
diff --git a/core/modules/contact/contact.install b/core/modules/contact/contact.install
index a67134ec6865..477f3596a747 100644
--- a/core/modules/contact/contact.install
+++ b/core/modules/contact/contact.install
@@ -15,5 +15,11 @@ function contact_install() {
   if (empty($site_mail)) {
     $site_mail = ini_get('sendmail_from');
   }
-  \Drupal::config('contact.category.feedback')->set('recipients', array($site_mail))->save();
+  $config = \Drupal::config('contact.category.feedback');
+  // Update the recipients if the default configuration entity has been created.
+  // We should never rely on default config entities as during enabling a module
+  // during config sync they will not exist.
+  if (!$config->isNew()) {
+    \Drupal::config('contact.category.feedback')->set('recipients', array($site_mail))->save();
+  }
 }
diff --git a/core/modules/content_translation/content_translation.install b/core/modules/content_translation/content_translation.install
index 8ff4c7ee03d6..b814f2dc428b 100644
--- a/core/modules/content_translation/content_translation.install
+++ b/core/modules/content_translation/content_translation.install
@@ -88,6 +88,19 @@ function content_translation_install() {
   // hook_module_implements_alter() is run among the last ones.
   module_set_weight('content_translation', 10);
   \Drupal::service('language_negotiator')->saveConfiguration(Language::TYPE_CONTENT, array(LanguageNegotiationUrl::METHOD_ID => 0));
+
+  $config_names = \Drupal::configFactory()->listAll('field.field.');
+  foreach ($config_names as $name) {
+    \Drupal::config($name)
+      ->set('settings.translation_sync', FALSE)
+      ->save();
+  }
+  $config_names = \Drupal::configFactory()->listAll('field.instance.');
+  foreach ($config_names as $name) {
+    \Drupal::config($name)
+      ->set('settings.translation_sync', FALSE)
+      ->save();
+  }
 }
 
 /**
@@ -105,3 +118,21 @@ function content_translation_enable() {
   $message = t('<a href="!settings_url">Enable translation</a> for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em>, or any other element you wish to translate.', $t_args);
   drupal_set_message($message, 'warning');
 }
+
+/**
+ * Implements hook_uninstall().
+ */
+function content_translation_uninstall() {
+  $config_names = \Drupal::configFactory()->listAll('field.field.');
+  foreach ($config_names as $name) {
+    \Drupal::config($name)
+      ->clear('settings.translation_sync')
+      ->save();
+  }
+  $config_names = \Drupal::configFactory()->listAll('field.instance.');
+  foreach ($config_names as $name) {
+    \Drupal::config($name)
+      ->clear('settings.translation_sync')
+      ->save();
+  }
+}
diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php b/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php
index 3eea9cb40cfa..854d80e886bf 100644
--- a/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php
+++ b/core/modules/field/lib/Drupal/field/Entity/FieldInstanceConfig.php
@@ -794,4 +794,11 @@ public function hasCustomStorage() {
     return $this->field->hasCustomStorage();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isDeleted() {
+    return $this->deleted;
+  }
+
 }
diff --git a/core/modules/field/lib/Drupal/field/FieldInstanceConfigInterface.php b/core/modules/field/lib/Drupal/field/FieldInstanceConfigInterface.php
index e796caa1c41d..6c842825b0f4 100644
--- a/core/modules/field/lib/Drupal/field/FieldInstanceConfigInterface.php
+++ b/core/modules/field/lib/Drupal/field/FieldInstanceConfigInterface.php
@@ -40,4 +40,12 @@ public function allowBundleRename();
    */
   public function targetBundle();
 
+  /**
+   * Gets the deleted flag of the field instance.
+   *
+   * @return bool
+   *   Returns TRUE if the instance is deleted.
+   */
+  public function isDeleted();
+
 }
diff --git a/core/modules/forum/config/field.field.forum.forum_container.yml b/core/modules/forum/config/field.field.taxonomy_term.forum_container.yml
similarity index 100%
rename from core/modules/forum/config/field.field.forum.forum_container.yml
rename to core/modules/forum/config/field.field.taxonomy_term.forum_container.yml
diff --git a/core/modules/forum/config/field.instance.taxonomy_term.forums.forum_container.yml b/core/modules/forum/config/field.instance.taxonomy_term.forums.forum_container.yml
index ed16957b16a1..710b8bdd7bce 100644
--- a/core/modules/forum/config/field.instance.taxonomy_term.forums.forum_container.yml
+++ b/core/modules/forum/config/field.instance.taxonomy_term.forums.forum_container.yml
@@ -15,5 +15,5 @@ settings: {  }
 field_type: list_boolean
 dependencies:
   entity:
-    - field.field.forum.forum_container
+    - field.field.taxonomy_term.forum_container
     - taxonomy.vocabulary.forums
diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install
index b5f0e0a2ccc8..7f975e929800 100644
--- a/core/modules/forum/forum.install
+++ b/core/modules/forum/forum.install
@@ -16,72 +16,75 @@ function forum_install() {
   $locked['forum'] = 'forum';
   \Drupal::state()->set('node.type.locked', $locked);
 
-  // Create the 'taxonomy_forums' field if it doesn't already exist. If forum
-  // is being enabled at the same time as taxonomy after both modules have been
-  // enabled, the field might exist but still be marked inactive.
-  if (!field_info_field('node', 'taxonomy_forums')) {
-    entity_create('field_config', array(
-      'name' => 'taxonomy_forums',
-      'entity_type' => 'node',
-      'type' => 'taxonomy_term_reference',
-      'settings' => array(
-        'allowed_values' => array(
-          array(
-            'vocabulary' => 'forums',
-            'parent' => 0,
+  if (!\Drupal::service('config.installer')->isSyncing()) {
+    // Create the 'taxonomy_forums' field if it doesn't already exist. If forum
+    // is being enabled at the same time as taxonomy after both modules have been
+    // enabled, the field might exist but still be marked inactive.
+    if (!field_info_field('node', 'taxonomy_forums')) {
+      entity_create('field_config', array(
+        'name' => 'taxonomy_forums',
+        'entity_type' => 'node',
+        'type' => 'taxonomy_term_reference',
+        'settings' => array(
+          'allowed_values' => array(
+            array(
+              'vocabulary' => 'forums',
+              'parent' => 0,
+            ),
           ),
         ),
-      ),
-    ))->save();
-
-    // Create a default forum so forum posts can be created.
-    $term = entity_create('taxonomy_term', array(
-      'name' => t('General discussion'),
-      'description' => '',
-      'parent' => array(0),
-      'vid' => 'forums',
-      'forum_container' => 0,
+      ))->save();
+
+      // Create a default forum so forum posts can be created.
+      $term = entity_create('taxonomy_term', array(
+        'name' => t('General discussion'),
+        'description' => '',
+        'parent' => array(0),
+        'vid' => 'forums',
+        'forum_container' => 0,
+      ));
+      $term->save();
+
+      // Create the instance on the bundle.
+      entity_create('field_instance_config', array(
+        'field_name' => 'taxonomy_forums',
+        'entity_type' => 'node',
+        'label' => 'Forums',
+        'bundle' => 'forum',
+        'required' => TRUE,
+      ))->save();
+
+      // Assign form display settings for the 'default' form mode.
+      entity_get_form_display('node', 'forum', 'default')
+        ->setComponent('taxonomy_forums', array(
+          'type' => 'options_select',
+        ))
+        ->save();
+
+      // Assign display settings for the 'default' and 'teaser' view modes.
+      entity_get_display('node', 'forum', 'default')
+        ->setComponent('taxonomy_forums', array(
+          'type' => 'taxonomy_term_reference_link',
+          'weight' => 10,
+        ))
+        ->save();
+
+      entity_get_display('node', 'forum', 'teaser')
+        ->setComponent('taxonomy_forums', array(
+          'type' => 'taxonomy_term_reference_link',
+          'weight' => 10,
+        ))
+        ->save();
+    }
+    // Add the comment field to the forum node type.
+    $fields = entity_load_multiple_by_properties('field_config', array(
+      'type' => 'comment',
+      'name' => 'comment_forum',
+      'include_deleted' => FALSE,
     ));
-    $term->save();
-
-    // Create the instance on the bundle.
-    entity_create('field_instance_config', array(
-      'field_name' => 'taxonomy_forums',
-      'entity_type' => 'node',
-      'label' => 'Forums',
-      'bundle' => 'forum',
-      'required' => TRUE,
-    ))->save();
-
-    // Assign form display settings for the 'default' form mode.
-    entity_get_form_display('node', 'forum', 'default')
-      ->setComponent('taxonomy_forums', array(
-        'type' => 'options_select',
-      ))
-      ->save();
-
-    // Assign display settings for the 'default' and 'teaser' view modes.
-    entity_get_display('node', 'forum', 'default')
-      ->setComponent('taxonomy_forums', array(
-        'type' => 'taxonomy_term_reference_link',
-        'weight' => 10,
-      ))
-      ->save();
-    entity_get_display('node', 'forum', 'teaser')
-      ->setComponent('taxonomy_forums', array(
-        'type' => 'taxonomy_term_reference_link',
-        'weight' => 10,
-      ))
-      ->save();
-  }
-  // Add the comment field to the forum node type.
-  $fields = entity_load_multiple_by_properties('field_config', array(
-    'type' => 'comment',
-    'name' => 'comment_forum',
-    'include_deleted' => FALSE,
-  ));
-  if (empty($fields)) {
-    Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'comment_forum');
+    if (empty($fields)) {
+      Drupal::service('comment.manager')->addDefaultField('node', 'forum', 'comment_forum');
+    }
   }
 }
 
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index a6af612ea476..36f83454e3c4 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -459,7 +459,9 @@ function node_uninstall() {
   $types = \Drupal::configFactory()->listAll('node.type.');
   foreach ($types as $config_name) {
     $type = \Drupal::config($config_name)->get('type');
-    \Drupal::config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save();
+    if (\Drupal::moduleHandler()->moduleExists('language')) {
+      \Drupal::config('language.settings')->clear('node. ' . $type . '.language.default_configuration')->save();
+    }
   }
 
   // Delete remaining general module variables.
diff --git a/core/modules/search/lib/Drupal/search/SearchPageRepository.php b/core/modules/search/lib/Drupal/search/SearchPageRepository.php
index 55c27dc8eecb..3dfdfb237b2b 100644
--- a/core/modules/search/lib/Drupal/search/SearchPageRepository.php
+++ b/core/modules/search/lib/Drupal/search/SearchPageRepository.php
@@ -87,7 +87,7 @@ public function getDefaultSearchPage() {
     }
 
     // Otherwise, use the first active search page.
-    return reset($search_pages);
+    return is_array($search_pages) ? reset($search_pages) : FALSE;
   }
 
   /**
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index a50a2d485ca1..36adb8befa4f 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -148,7 +148,7 @@ protected function setUp() {
     // \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work.
     // Write directly to active storage to avoid early instantiation of
     // the event dispatcher which can prevent modules from registering events.
-    \Drupal::service('config.storage')->write('core.extension', array('module' => array()));
+    \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array()));
 
     // Collect and set a fixed module list.
     $class = get_class($this);
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 6558f209200b..e19169f7b18f 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -1526,7 +1526,9 @@ public function configImporter() {
         $this->container->get('event_dispatcher'),
         $this->container->get('config.manager'),
         $this->container->get('lock'),
-        $this->container->get('config.typed')
+        $this->container->get('config.typed'),
+        $this->container->get('module_handler'),
+        $this->container->get('theme_handler')
       );
     }
     // Always recalculate the changelist when called.
diff --git a/core/modules/simpletest/simpletest.install b/core/modules/simpletest/simpletest.install
index 917a938cda8d..82e1ebbb2ad5 100644
--- a/core/modules/simpletest/simpletest.install
+++ b/core/modules/simpletest/simpletest.install
@@ -182,7 +182,7 @@ function simpletest_uninstall() {
   // Do not clean the environment in case the Simpletest module is uninstalled
   // in a (recursive) test for itself, since simpletest_clean_environment()
   // would also delete the test site of the parent test process.
-  if (!DRUPAL_TEST_IN_CHILD_SITE) {
+  if (!drupal_valid_test_ua()) {
     simpletest_clean_environment();
   }
   // Delete verbose test output and any other testing framework files.
diff --git a/core/profiles/standard/config/entity.view_display.node.article.default.yml b/core/profiles/standard/config/entity.view_display.node.article.default.yml
index c4f2c85baf20..81f0545957e8 100644
--- a/core/profiles/standard/config/entity.view_display.node.article.default.yml
+++ b/core/profiles/standard/config/entity.view_display.node.article.default.yml
@@ -4,6 +4,13 @@ bundle: article
 mode: default
 status: true
 content:
+  field_image:
+    label: hidden
+    type: image
+    settings:
+      image_style: large
+      image_link: ''
+    weight: -1
   body:
     label: hidden
     type: text_default
@@ -14,13 +21,6 @@ content:
     weight: 10
     label: above
     settings: {  }
-  field_image:
-    label: hidden
-    type: image
-    settings:
-      image_style: large
-      image_link: ''
-    weight: -1
 dependencies:
   entity:
     - field.instance.node.article.body
diff --git a/core/profiles/standard/config/entity.view_display.node.article.teaser.yml b/core/profiles/standard/config/entity.view_display.node.article.teaser.yml
index a88fc068d672..e21e3cffdcac 100644
--- a/core/profiles/standard/config/entity.view_display.node.article.teaser.yml
+++ b/core/profiles/standard/config/entity.view_display.node.article.teaser.yml
@@ -4,6 +4,13 @@ bundle: article
 mode: teaser
 status: true
 content:
+  field_image:
+    label: hidden
+    type: image
+    settings:
+      image_style: medium
+      image_link: content
+    weight: -1
   body:
     label: hidden
     type: text_summary_or_trimmed
@@ -15,13 +22,6 @@ content:
     weight: 10
     label: above
     settings: {  }
-  field_image:
-    label: hidden
-    type: image
-    settings:
-      image_style: medium
-      image_link: content
-    weight: -1
 dependencies:
   entity:
     - entity.view_mode.node.teaser
-- 
GitLab