From 0612c664da2f0660e820dd8051c21d38d4f76d17 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Wed, 27 Feb 2013 09:49:26 +0000
Subject: [PATCH] =?UTF-8?q?Issue=20#1763640=20by=20beejeebus,=20alexpott,?=
 =?UTF-8?q?=20Jose=20Reyero,=20c31ck,=20das-peter,=20YesCT,=20heyrocker,?=
 =?UTF-8?q?=20G=C3=A1bor=20Hojtsy:=20Introduce=20config=20context=20to=20m?=
 =?UTF-8?q?ake=20original=20config=20and=20different=20overrides=20accessi?=
 =?UTF-8?q?ble.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 core/includes/config.inc                      |  80 ++++++++-
 core/includes/install.core.inc                |   9 +-
 core/lib/Drupal/Core/Config/Config.php        |  20 +--
 core/lib/Drupal/Core/Config/ConfigEvent.php   |  30 +++-
 core/lib/Drupal/Core/Config/ConfigFactory.php | 131 ++++++++++++---
 .../Core/Config/Context/ConfigContext.php     | 123 ++++++++++++++
 .../Config/Context/ConfigContextFactory.php   |  63 +++++++
 .../Core/Config/Context/ContextInterface.php  |  88 ++++++++++
 .../Config/Context/GlobalConfigContext.php    |  29 ++++
 core/lib/Drupal/Core/CoreBundle.php           |  20 ++-
 .../ConfigGlobalOverrideSubscriber.php        |  39 -----
 .../ConfigOverrideSubscriber.php              |  41 +++++
 .../Drupal/config/Tests/ConfigCRUDTest.php    |  12 +-
 .../config/Tests/ConfigLocaleOverride.php     | 157 ++++++++++++++++++
 .../config/Tests/ConfigOverrideTest.php       |  66 +++++++-
 .../config/Tests/LocaleConfigOverride.php     |  38 -----
 .../locale.config.de.config_test.system.yml   |   1 +
 .../locale.config.fr.config_test.system.yml   |   1 +
 .../locale/lib/Drupal/locale/LocaleBundle.php |   1 +
 .../Drupal/locale/LocaleConfigSubscriber.php  |  70 ++++++--
 core/modules/system/system.admin.inc          |  12 ++
 .../lib/Drupal/user/UserConfigContext.php     |  51 ++++++
 core/modules/user/user.admin.inc              |   2 +
 core/modules/user/user.module                 |  41 ++---
 24 files changed, 951 insertions(+), 174 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Config/Context/ConfigContext.php
 create mode 100644 core/lib/Drupal/Core/Config/Context/ConfigContextFactory.php
 create mode 100644 core/lib/Drupal/Core/Config/Context/ContextInterface.php
 create mode 100644 core/lib/Drupal/Core/Config/Context/GlobalConfigContext.php
 delete mode 100644 core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php
 create mode 100644 core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php
 create mode 100644 core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php
 delete mode 100644 core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php
 create mode 100644 core/modules/config/tests/config_test/config/locale.config.de.config_test.system.yml
 create mode 100644 core/modules/config/tests/config_test/config/locale.config.fr.config_test.system.yml
 create mode 100644 core/modules/user/lib/Drupal/user/UserConfigContext.php

diff --git a/core/includes/config.inc b/core/includes/config.inc
index 0b83f60636ba..7dff3b31c79d 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -1,8 +1,8 @@
 <?php
 
 use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigException;
 use Drupal\Core\Config\FileStorage;
-use Drupal\Core\Config\NullStorage;
 use Drupal\Core\Config\StorageInterface;
 
 /**
@@ -24,6 +24,10 @@
  *   The name of the module or theme to install default configuration for.
  */
 function config_install_default_config($type, $name) {
+  // Use the override free context for config importing so that any overrides do
+  // not change the data on import.
+  config_context_enter('config.context.free');
+
   // If this module defines any ConfigEntity types then create an empty
   // manifest file for each of them.
   foreach (config_get_module_config_entities($name) as $entity_info) {
@@ -47,6 +51,8 @@ function config_install_default_config($type, $name) {
     $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
     config_sync_changes($remaining_changes, $source_storage, $target_storage);
   }
+  // Exit the override free context.
+  config_context_leave();
 }
 
 /**
@@ -87,7 +93,7 @@ function config_get_storage_names_with_prefix($prefix = '') {
  * @code config('book.admin') @endcode will return a configuration object in
  * which the book module can store its administrative settings.
  *
- * @param $name
+ * @param string $name
  *   The name of the configuration object to retrieve. The name corresponds to
  *   a configuration file. For @code config('book.admin') @endcode, the config
  *   object returned will contain the contents of book.admin configuration file.
@@ -99,6 +105,54 @@ function config($name) {
   return drupal_container()->get('config.factory')->get($name);
 }
 
+/*
+ * Sets the config context on the config factory.
+ *
+ * This allows configuration objects to be created using special configuration
+ * contexts eg. global override free or locale using a user preferred language.
+ * Calling this function affects all subsequent calls to config() until
+ * config_context_leave() is called.
+ *
+ * @see config_context_leave()
+ * @see \Drupal\Core\Config\ConfigFactory
+ *
+ * @param string $context_name
+ *   The name of the config context service on the container or a fully
+ *   qualified class implementing \Drupal\Core\Config\Context\ContextInterface.
+ *
+ * @return \Drupal\Core\Config\Context\ContextInterface
+ *   The configuration context object.
+ */
+function config_context_enter($context_name) {
+  if (drupal_container()->has($context_name)) {
+    $context = drupal_container()->get($context_name);
+  }
+  elseif (class_exists($context_name) && in_array("Drupal\\Core\\Config\\Context\\ContextInterface", class_implements($context_name))) {
+    $context = drupal_container()
+      ->get('config.context.factory')
+      ->get($context_name);
+  }
+  else {
+    throw new ConfigException(sprintf('Unknown config context service or class: %s', $context_name));
+  }
+  drupal_container()
+    ->get('config.factory')
+    ->enterContext($context);
+  return $context;
+}
+
+/*
+ * Leaves the current config context returning to the previous context.
+ *
+ * @see config_context_enter()
+ * @see \Drupal\Core\Config\ConfigFactory
+ */
+function config_context_leave() {
+  drupal_container()
+    ->get('config.factory')
+    ->leaveContext();
+}
+
 /**
  * Returns a list of differences between configuration storages.
  *
@@ -184,10 +238,11 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf
  *   The storage to synchronize configuration to.
  */
 function config_sync_changes(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
+  $target_context = drupal_container()->get('config.context.free');
   $factory = drupal_container()->get('config.factory');
   foreach (array('delete', 'create', 'change') as $op) {
     foreach ($config_changes[$op] as $name) {
-      $config = new Config($name, $target_storage);
+      $config = new Config($name, $target_storage, $target_context);
       if ($op == 'delete') {
         $config->delete();
       }
@@ -230,9 +285,16 @@ function config_import() {
 
   $success = TRUE;
   try {
+    // Use the override free context for config importing so that any overrides do
+    // not change the data on import.
+    config_context_enter('config.context.free');
+
     $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
     config_sync_changes($remaining_changes, $source_storage, $target_storage);
     config_import_create_snapshot($target_storage, $snapshot_storage);
+
+    // Exit the override free context.
+    config_context_leave();
   }
   catch (ConfigException $e) {
     watchdog_exception('config_import', $e);
@@ -271,6 +333,10 @@ function config_import_create_snapshot(StorageInterface $source_storage, Storage
  * @todo Add support for other extension types; e.g., themes etc.
  */
 function config_import_invoke_owner(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
+  $factory = drupal_container()->get('config.factory');
+  // Use the admin context for config importing so that any overrides do not
+  // change the data on import.
+  $free_context = drupal_container()->get('config.context.free');
   // Allow modules to take over configuration change operations for
   // higher-level configuration data.
   // First pass deleted, then new, and lastly changed configuration, in order to
@@ -284,11 +350,11 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
       // Validate the configuration object name before importing it.
       Config::validateName($name);
       if ($entity_type = config_get_entity_type_by_name($name)) {
-        $old_config = new Config($name, $target_storage);
+        $old_config = new Config($name, $target_storage, $free_context);
         $old_config->load();
 
         $data = $source_storage->read($name);
-        $new_config = new Config($name, $target_storage);
+        $new_config = new Config($name, $source_storage, $free_context);
         if ($data !== FALSE) {
           $new_config->setData($data);
         }
@@ -297,6 +363,10 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
         $handled_by_module = $manager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
       }
       if (!empty($handled_by_module)) {
+        $factory->reset($name);
+        // Reset the manifest config object for the config entity.
+        $entity_info = drupal_container()->get('plugin.manager.entity')->getDefinition($entity_type);
+        $factory->reset('manifest.' . $entity_info['config_prefix']);
         unset($config_changes[$op][$key]);
       }
     }
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index c4de9eec6bd4..34a994008e80 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -337,9 +337,16 @@ function install_begin_request(&$install_state) {
     $container->register('event_dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher');
 
     $container->register('config.storage', 'Drupal\Core\Config\InstallStorage');
+    $container->register('config.context.factory', 'Drupal\Core\Config\Context\ConfigContextFactory')
+      ->addArgument(new Reference('event_dispatcher'));
+
+    $container->register('config.context', 'Drupal\Core\Config\Context\ContextInterface')
+      ->setFactoryService(new Reference('config.context.factory'))
+      ->setFactoryMethod('get');
+
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
-      ->addArgument(new Reference('event_dispatcher'));
+      ->addArgument(new Reference('config.context'));
 
     // Register the 'language_manager' service.
     $container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index c1f0632eea47..0337d2873619 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -9,7 +9,7 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Config\ConfigNameException;
-use Symfony\Component\EventDispatcher\EventDispatcher;
+use Drupal\Core\Config\Context\ContextInterface;
 
 /**
  * Defines the default configuration object.
@@ -71,11 +71,11 @@ class Config {
   protected $storage;
 
   /**
-   * The event dispatcher used to notify subscribers.
+   * The configuration context used for this configuration object.
    *
-   * @var Symfony\Component\EventDispatcher\EventDispatcher
+   * @var \Drupal\Core\Config\Context\ContextInterface
    */
-  protected $eventDispatcher;
+  protected $context;
 
   /**
    * Whether the config object has already been loaded.
@@ -89,16 +89,16 @@ class Config {
    *
    * @param string $name
    *   The name of the configuration object being constructed.
-   * @param Drupal\Core\Config\StorageInterface $storage
+   * @param \Drupal\Core\Config\StorageInterface $storage
    *   A storage controller object to use for reading and writing the
    *   configuration data.
-   * @param Symfony\Component\EventDispatcher\EventDispatcher $event_dispatcher
-   *   The event dispatcher used to notify subscribers.
+   * @param \Drupal\Core\Config\Context\ContextInterface $context
+   *   The configuration context used for this configuration object.
    */
-  public function __construct($name, StorageInterface $storage, EventDispatcher $event_dispatcher = NULL) {
+  public function __construct($name, StorageInterface $storage, ContextInterface $context) {
     $this->name = $name;
     $this->storage = $storage;
-    $this->eventDispatcher = $event_dispatcher ? $event_dispatcher : drupal_container()->get('event_dispatcher');
+    $this->context = $context;
   }
 
   /**
@@ -491,7 +491,7 @@ public function getStorage() {
    * Dispatch a config event.
    */
   protected function notify($config_event_name) {
-    $this->eventDispatcher->dispatch('config.' . $config_event_name, new ConfigEvent($this));
+    $this->context->notify($config_event_name, $this);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/ConfigEvent.php b/core/lib/Drupal/Core/Config/ConfigEvent.php
index aabd1d820567..c1df902b7900 100644
--- a/core/lib/Drupal/Core/Config/ConfigEvent.php
+++ b/core/lib/Drupal/Core/Config/ConfigEvent.php
@@ -2,10 +2,11 @@
 
 namespace Drupal\Core\Config;
 
+use Drupal\Core\Config\Context\ContextInterface;
 use Symfony\Component\EventDispatcher\Event;
-use Drupal\Core\Config\Config;
 
 class ConfigEvent extends Event {
+
   /**
    * Configuration object.
    *
@@ -14,10 +15,23 @@ class ConfigEvent extends Event {
   protected $config;
 
   /**
-   * Constructor.
+   * Configuration context object.
+   *
+   * @var \Drupal\Core\Config\Context\ContextInterface
+   */
+  protected $context;
+
+  /**
+   * Constructs a configuration event object.
+   *
+   * @param \Drupal\Core\Config\Context\ContextInterface
+   *   Configuration context object.
+   * @param \Drupal\Core\Config\Config
+   *   (optional) Configuration object.
    */
-  public function __construct(Config $config) {
+  public function __construct(ContextInterface $context, Config $config = NULL) {
     $this->config = $config;
+    $this->context = $context;
   }
 
   /**
@@ -26,4 +40,14 @@ public function __construct(Config $config) {
   public function getConfig() {
     return $this->config;
   }
+
+  /**
+   * Get configuration context object.
+   *
+   * @return \Drupal\Core\Config\Context\ContextInterface
+   *   Configuration context.
+   */
+  public function getContext() {
+    return $this->context;
+  }
 }
diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php
index 00d97736000f..4edbeada924c 100644
--- a/core/lib/Drupal/Core/Config/ConfigFactory.php
+++ b/core/lib/Drupal/Core/Config/ConfigFactory.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Config;
 
-use Symfony\Component\EventDispatcher\EventDispatcher;
+use Drupal\Core\Config\Context\ContextInterface;
 
 /**
  * Defines the configuration object factory.
@@ -21,22 +21,28 @@
  * is used for reading and writing the configuration data.
  *
  * @see Drupal\Core\Config\StorageInterface
+ *
+ * A configuration context is an object containing parameters that will be
+ * available to the configuration plug-ins for them to customize the
+ * configuration data in different ways.
+ *
+ * @see Drupal\Core\Config\Context\ContextInterface
  */
 class ConfigFactory {
 
   /**
    * A storage controller instance for reading and writing configuration data.
    *
-   * @var Drupal\Core\Config\StorageInterface
+   * @var \Drupal\Core\Config\StorageInterface
    */
   protected $storage;
 
   /**
-   * An event dispatcher instance to use for configuration events.
+   * A stack of configuration contexts the last being the context in use.
    *
-   * @var Symfony\Component\EventDispatcher\EventDispatcher
+   * @var array
    */
-  protected $eventDispatcher;
+  protected $contextStack = array();
 
   /**
    * Cached configuration objects.
@@ -48,33 +54,31 @@ class ConfigFactory {
   /**
    * Constructs the Config factory.
    *
-   * @param Drupal\Core\Config\StorageInterface $storage
-   *   The storage controller object to use for reading and writing
-   *   configuration data.
-   * @param Symfony\Component\EventDispatcher\EventDispatcher
-   *   An event dispatcher instance to use for configuration events.
+   * @param \Drupal\Core\Config\StorageInterface
+   *   The configuration storage engine.
+   * @param \Drupal\Core\Config\Context\ContextInterface
+   *   Configuration context object.
    */
-  public function __construct(StorageInterface $storage, EventDispatcher $event_dispatcher) {
+  public function __construct(StorageInterface $storage, ContextInterface $context) {
     $this->storage = $storage;
-    $this->eventDispatcher = $event_dispatcher;
+    $this->enterContext($context);
   }
 
   /**
-   * Returns a configuration object for a given name.
+   * Returns a configuration object for a given name and context.
    *
    * @param string $name
    *   The name of the configuration object to construct.
-   *
-   * @return Drupal\Core\Config\Config
-   *   A configuration object with the given $name.
    */
   public function get($name) {
-    if (isset($this->cache[$name])) {
-      return $this->cache[$name];
+    $context = $this->getContext();
+    $cache_key = $this->getCacheKey($name, $context);
+    if (isset($this->cache[$cache_key])) {
+      return $this->cache[$cache_key];
     }
 
-    $this->cache[$name] = new Config($name, $this->storage, $this->eventDispatcher);
-    return $this->cache[$name]->init();
+    $this->cache[$cache_key] = new Config($name, $this->storage, $context);
+    return $this->cache[$cache_key]->init();
   }
 
   /**
@@ -83,11 +87,15 @@ public function get($name) {
    * @param string $name
    *   (optional) The name of the configuration object to reset. If omitted, all
    *   configuration objects are reset.
+   *
+   * @return \Drupal\Core\Config\ConfigFactory
+   *   The config factory object.
    */
   public function reset($name = NULL) {
     if ($name) {
-      if (isset($this->cache[$name])) {
-        $this->cache[$name]->init();
+      // Reinitialise the configuration object in all contexts.
+      foreach ($this->getCacheKeys($name) as $cache_key) {
+        $this->cache[$cache_key]->init();
       }
     }
     else {
@@ -95,6 +103,7 @@ public function reset($name = NULL) {
         $config->init();
       }
     }
+    return $this;
   }
 
   /**
@@ -108,14 +117,84 @@ public function reset($name = NULL) {
    * @todo D8: Remove after http://drupal.org/node/1865206.
    */
   public function rename($old_name, $new_name) {
-    if (isset($this->cache[$old_name])) {
-      $config = $this->cache[$old_name];
+    $old_cache_key = $this->getCacheKey($old_name, $this->getContext());
+    $new_cache_key = $this->getCacheKey($new_name, $this->getContext());
+    if (isset($this->cache[$old_cache_key])) {
+      $config = $this->cache[$old_cache_key];
       // Clone the object into the existing slot.
-      $this->cache[$old_name] = clone $config;
+      $this->cache[$old_cache_key] = clone $config;
 
       // Change the object's name and re-initialize it.
       $config->setName($new_name)->init();
-      $this->cache[$new_name] = $config;
+      $this->cache[$new_cache_key] = $config;
     }
   }
+
+  /**
+   * Sets the config context by adding it to the context stack.
+   *
+   * @param \Drupal\Core\Config\Context\ContextInterface $context
+   *   The configuration context to add.
+   *
+   * @return \Drupal\Core\Config\ConfigFactory
+   *   The config factory object.
+   */
+  public function enterContext(ContextInterface $context) {
+    $this->contextStack[] = $context;
+    return $this;
+  }
+
+  /**
+   * Gets the current config context.
+   *
+   * @return \Drupal\Core\Config\Context\ContextInterface $context
+   *   The current configuration context.
+   */
+  public function getContext() {
+    return end($this->contextStack);
+  }
+
+  /**
+   * Leaves the current context by removing it from the context stack.
+   *
+   * @return \Drupal\Core\Config\ConfigFactory
+   *   The config factory object.
+   */
+  public function leaveContext() {
+    if (count($this->contextStack) > 1) {
+      array_pop($this->contextStack);
+    }
+    return $this;
+  }
+
+  /*
+   * Gets the cache key for a given config name in a particular context.
+   *
+   * @param string $name
+   *   The name of the configuration object.
+   * @param \Drupal\Core\Config\Context\ContextInterface $context
+   *   The configuration context.
+   *
+   * @return string
+   *   The cache key.
+   */
+  public function getCacheKey($name, ContextInterface $context) {
+    return $name . ':' . $context->getUuid();
+  }
+
+  /**
+   * Gets all the cache keys that match the provided config name.
+   *
+   * @param string $name
+   *   The name of the configuration object.
+   *
+   * @return array
+   *   An array of cache keys that match the provided config name.
+   */
+  public function getCacheKeys($name) {
+    $cache_keys = array_keys($this->cache);
+    return array_filter($cache_keys, function($key) use ($name) {
+      return ( strpos($key, $name) !== false );
+    });
+  }
 }
diff --git a/core/lib/Drupal/Core/Config/Context/ConfigContext.php b/core/lib/Drupal/Core/Config/Context/ConfigContext.php
new file mode 100644
index 000000000000..9be0f7188a5a
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Context/ConfigContext.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\Context\ConfigContext.
+ */
+
+namespace Drupal\Core\Config\Context;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigEvent;
+use Drupal\Component\Uuid\Uuid;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Defines the base configuration context object.
+ *
+ * A configuration context object provides a data array that can be used:
+ *   - as a parameter to get customized configuration objects.
+ *   - as a store of config data used to override values.
+ */
+class ConfigContext implements ContextInterface {
+
+  /**
+   * Predefined key, values to override specific configuration objects.
+   */
+  const OVERRIDE = 'config.override';
+
+  /**
+   * The actual storage of key-value pairs.
+   *
+   * @var array
+   */
+  protected $data = array();
+
+  /**
+   * An event dispatcher instance to use for configuration events.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcher
+   */
+  protected $eventDispatcher;
+
+  /**
+   * A unique identifier for the context.
+   *
+   * @var string
+   */
+  protected $uuid;
+
+  /**
+   * Constructs the configuration context.
+   *
+   * @param \Symfony\Component\EventDispatcher\EventDispatcher $event_dispatcher
+   *   An event dispatcher instance to use for configuration events.
+   */
+  public function __construct(EventDispatcher $event_dispatcher) {
+    $this->eventDispatcher = $event_dispatcher;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Context\ContextInterface::init().
+   */
+  public function init($context_key, $data) {
+    if ($data) {
+      $this->set($context_key, $data);
+    }
+    $this->setUuid();
+    // Notify event listeners that a configuration context has been created.
+    $this->notify('context', NULL);
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Context\ContextInterface::get().
+   */
+  public function get($key) {
+    return array_key_exists($key, $this->data) ? $this->data[$key] : NULL;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Context\ContextInterface::set().
+   */
+  public function set($key, $value) {
+    $this->data[$key] = $value;
+  }
+
+  /**
+   * Sets override data.
+   *
+   * @param mixed $data
+   *   Override data to store.
+   *
+   * @return \Drupal\Core\Config\Context\ConfigContext
+   *   The config context object.
+   */
+  public function setOverride($data) {
+    $this->init(self::OVERRIDE, $data);
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Context\ContextInterface::setUuid().
+   */
+  public function setUuid() {
+    $uuid = new Uuid();
+    $this->uuid = $uuid->generate();
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Context\ContextInterface::getUuid().
+   */
+  public function getUuid() {
+    return $this->uuid;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Context\ContextInterface::notify().
+   */
+  public function notify($config_event_name, Config $config = NULL) {
+    $this->eventDispatcher->dispatch('config.' . $config_event_name, new ConfigEvent($this, $config));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Context/ConfigContextFactory.php b/core/lib/Drupal/Core/Config/Context/ConfigContextFactory.php
new file mode 100644
index 000000000000..868c0ef9dacc
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Context/ConfigContextFactory.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\Context\ConfigContextFactory.
+ */
+
+namespace Drupal\Core\Config\Context;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigException;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Defines configuration context factory.
+ *
+ * The configuration context factory creates configuration context objects.
+ *
+ * @see \Drupal\Core\Config\Context\ContextInterface
+ */
+class ConfigContextFactory {
+
+  /**
+   * An event dispatcher instance to use for configuration events.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcher
+   */
+  protected $eventDispatcher;
+
+  /**
+   * Constructs the configuration context.
+   *
+   * @param \Symfony\Component\EventDispatcher\EventDispatcher $event_dispatcher
+   *   An event dispatcher instance to use for configuration events.
+   */
+  public function __construct(EventDispatcher $event_dispatcher) {
+    $this->eventDispatcher = $event_dispatcher;
+  }
+
+  /**
+   * Returns a configuration context object.
+   *
+   * @param string $class
+   *   (Optional) The name of the configuration class to use. Defaults to
+   *   Drupal\Core\Config\Context\ConfigContext
+   *
+   * @return \Drupal\Core\Config\Context\ContextInterface $context
+   *   (Optional) The configuration context to use.
+   */
+  public function get($class = NULL) {
+    if (!$class) {
+      $class = "Drupal\\Core\\Config\\Context\\ConfigContext";
+    }
+    if (class_exists($class)) {
+      $context = new $class($this->eventDispatcher);
+    }
+    else {
+      throw new ConfigException(sprintf('Unknown config context class: %s', $class));
+    }
+    return $context;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Context/ContextInterface.php b/core/lib/Drupal/Core/Config/Context/ContextInterface.php
new file mode 100644
index 000000000000..b3107a7750b1
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Context/ContextInterface.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\Context\ContextInterface.
+ */
+
+namespace Drupal\Core\Config\Context;
+
+use Drupal\Core\Config\Config;
+
+/**
+ * Defines the configuration context interface.
+ *
+ * The configuration context object will contain predefined parameters used
+ * by the configuration object for storage operations and notifications
+ * and contextual data to be used by configuration event listeners.
+ *
+ * @see Drupal\Core\Config\Config
+ * @see Drupal\Core\Config\ConfigFactory
+ * @see config()
+ */
+interface ContextInterface {
+
+  /*
+   * Initialises a config context for use.
+   *
+   * Creates a unique context identifier, adds data and notifies system about
+   * the new context.
+   *
+   * @param string $context_key
+   *   The key that is used to set context data.
+   * @param mixed $data
+   *   The context config data.
+   *
+   * @return \Drupal\Core\Config\Context\ConfigContext
+   *   The config context object.
+   */
+  public function init($context_key, $data);
+
+  /**
+   * Returns the stored value for a given key.
+   *
+   * @param string $key
+   *   The key of the data to retrieve.
+   *
+   * @return mixed
+   *   The stored value, or NULL if no value exists.
+   */
+  public function get($key);
+
+  /**
+   * Saves a value for a given key.
+   *
+   * @param string $key
+   *   The key of the data to store.
+   * @param mixed $value
+   *   The data to store.
+   */
+  public function set($key, $value);
+
+  /**
+   * Sets the uuid for the context.
+   *
+   * @return string
+   *   The context's uuid.
+   */
+  public function setUuid();
+
+  /**
+   * Gets the uuid for the context.
+   *
+   * @return string
+   *   The context's uuid.
+   */
+  public function getUuid();
+
+  /**
+   * Dispatches a config event.
+   *
+   * @param string $config_event_name
+   *   Event name.
+   * @param \Drupal\Core\Config\Config $config
+   *   (optional) Configuration object.
+   */
+  public function notify($config_event_name, Config $config = NULL);
+
+}
diff --git a/core/lib/Drupal/Core/Config/Context/GlobalConfigContext.php b/core/lib/Drupal/Core/Config/Context/GlobalConfigContext.php
new file mode 100644
index 000000000000..9e8fa1e1fae7
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Context/GlobalConfigContext.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\Context\GlobalConfigContext.
+ */
+
+namespace Drupal\Core\Config\Context;
+
+/**
+ * Defines the global configuration context object.
+ *
+ * The global configuration context allows config object data to be overridden
+ * with values from the $conf global.
+ */
+class GlobalConfigContext extends ConfigContext {
+
+  /**
+   * Sets global override data.
+   *
+   * @return \Drupal\Core\Config\Context\ConfigContext
+   *   The config context object.
+   */
+  public function setGlobalOverride() {
+    global $conf;
+    $this->init(self::OVERRIDE, $conf);
+    return $this;
+  }
+}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index cc4e4aaba7c8..6f7ff31adfc4 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -52,9 +52,25 @@ public function build(ContainerBuilder $container) {
       ->addArgument(new Reference('config.cachedstorage.storage'))
       ->addArgument(new Reference('cache.config'));
 
+    $container->register('config.context.factory', 'Drupal\Core\Config\Context\ConfigContextFactory')
+      ->addArgument(new Reference('event_dispatcher'));
+
+    $container->register('config.context', 'Drupal\Core\Config\Context\ContextInterface')
+      ->setFactoryService(new Reference('config.context.factory'))
+      ->setFactoryMethod('get')
+      ->addArgument('Drupal\Core\Config\Context\GlobalConfigContext')
+      ->addTag('persist')
+      ->addMethodCall('setGlobalOverride');
+
+    // Register a config context with no overrides for use in administration
+    // forms, enabling modules and importing configuration.
+    $container->register('config.context.free', 'Drupal\Core\Config\Context\ContextInterface')
+      ->setFactoryService(new Reference('config.context.factory'))
+      ->setFactoryMethod('get');
+
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
-      ->addArgument(new Reference('event_dispatcher'))
+      ->addArgument(new Reference('config.context'))
       ->addTag('persist');
 
     // Register staging configuration storage.
@@ -262,7 +278,7 @@ public function build(ContainerBuilder $container) {
     $container->register('request_close_subscriber', 'Drupal\Core\EventSubscriber\RequestCloseSubscriber')
       ->addArgument(new Reference('module_handler'))
       ->addTag('event_subscriber');
-    $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber')
+    $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigOverrideSubscriber')
       ->addTag('event_subscriber');
     $container->register('language_request_subscriber', 'Drupal\Core\EventSubscriber\LanguageRequestSubscriber')
       ->addArgument(new Reference('language_manager'))
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php
deleted file mode 100644
index 6ee6a3dda765..000000000000
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigGlobalOverrideSubscriber.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-/**
- * @file
- * Definition of Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber.
- */
-
-namespace Drupal\Core\EventSubscriber;
-
-use Drupal\Core\Config\Config;
-use Drupal\Core\Config\ConfigEvent;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-/**
- * Override configuration values with values in global $conf variable.
- */
-class ConfigGlobalOverrideSubscriber implements EventSubscriberInterface {
-  /**
-   * Override configuration values with global $conf.
-   *
-   * @param Drupal\Core\Config\ConfigEvent $event
-   *   The Event to process.
-   */
-  public function configInit(ConfigEvent $event) {
-    global $conf;
-
-    $config = $event->getConfig();
-    if (isset($conf[$config->getName()])) {
-      $config->setOverride($conf[$config->getName()]);
-    }
-  }
-
-  /**
-   * Implements EventSubscriberInterface::getSubscribedEvents().
-   */
-  static function getSubscribedEvents() {
-    $events['config.init'][] = array('configInit', 30);
-    return $events;
-  }
-}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php
new file mode 100644
index 000000000000..376da1085124
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigOverrideSubscriber.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\ConfigOverrideSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigEvent;
+use Drupal\Core\Config\Context\ConfigContext;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Override configuration values with predefined values in context.
+ */
+class ConfigOverrideSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Overrides configuration values.
+   *
+   * @param \Drupal\Core\Config\ConfigEvent $event
+   *   The Event to process.
+   */
+  public function configInit(ConfigEvent $event) {
+    if ($override = $event->getContext()->get(ConfigContext::OVERRIDE)) {
+      $config = $event->getConfig();
+      if (isset($override[$config->getName()])) {
+        $config->setOverride($override[$config->getName()]);
+      }
+    }
+  }
+
+  /**
+   * Implements EventSubscriberInterface::getSubscribedEvents().
+   */
+  public static function getSubscribedEvents() {
+    $events['config.init'][] = array('configInit', 30);
+    return $events;
+  }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
index e363577b5a50..b6bded364bee 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
@@ -172,21 +172,13 @@ function testNameValidation() {
 
     // Write configuration with an invalid name (missing a namespace) to
     // staging.
-    $storage = $this->container->get('config.storage');
     $staging = $this->container->get('config.storage.staging');
     $manifest_data = config('manifest.invalid_object_name')->get();
     $manifest_data['new']['name'] = 'invalid';
     $staging->write('manifest.invalid_object_name', $manifest_data);
 
-    // Verify that an exception is thrown when synchronizing.
-    $message = 'Expected ConfigNameException was thrown when attempting to sync invalid configuration.';
-    try {
-      config_import();
-      $this->fail($message);
-    }
-    catch (ConfigNameException $e) {
-      $this->pass($message);
-    }
+    // Assert that config_import returns false indicating a failure.
+    $this->assertFalse(config_import(), "Config import failed when trying to importing an object with an invalid name");
   }
 
 }
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php b/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php
new file mode 100644
index 000000000000..78253d3c67ad
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigLocaleOverride.php
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\config\Tests\ConfigLocaleOverride.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\ConfigException;
+use Drupal\Core\Language\Language;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests locale config override.
+ */
+class ConfigLocaleOverride extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('locale', 'config_test', 'user', 'language', 'system');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Locale override',
+      'description' => 'Confirm that locale overrides work',
+      'group' => 'Configuration',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    config_install_default_config('module', 'config_test');
+  }
+
+  /*
+   * Tests basic locale override.
+   */
+  function testConfigLocaleOverride() {
+    $name = 'config_test.system';
+    // The default language is en so the config key should be localised.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'en bar');
+
+    // Ensure that we get the expected value when we use system_config.
+    config_context_enter('config.context.free');
+    $config_admin = config('config_test.system');
+    $this->assertIdentical($config_admin->get('foo'), 'bar');
+
+    // Leave the non override context.
+    config_context_leave();
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'en bar');
+  }
+
+  /*
+   * Tests locale override based on user's preferred language.
+   */
+  function testConfigLocaleUserOverride() {
+    $this->installSchema('system', 'variable');
+    $this->installSchema('language', 'language');
+    language_save(new Language(array(
+      'name' => 'French',
+      'langcode' => 'fr',
+    )));
+    language_save(new Language(array(
+      'name' => 'English',
+      'langcode' => 'en',
+    )));
+    language_save(new Language(array(
+      'name' => 'German',
+      'langcode' => 'de',
+    )));
+
+    $this->installSchema('user', 'users');
+    $account = entity_create('user', array(
+      'name' => 'French user',
+      'mail' => 'test@example.com',
+      'created' => REQUEST_TIME,
+      'status' => 1,
+      'preferred_langcode' => 'fr',
+    ));
+    $config_factory = drupal_container()->get('config.factory');
+
+    $user_config_context = config_context_enter("Drupal\\user\\UserConfigContext");
+    $user_config_context->setAccount($account);
+    $config = config('config_test.system');
+    $this->assertIdentical($config->get('foo'), 'fr bar');
+
+    // Ensure that we get the expected value when we leave the user context.
+    $config_factory->leaveContext();
+    $config = config('config_test.system');
+    $this->assertIdentical($config->get('foo'), 'en bar');
+
+    $account = entity_create('user', array(
+      'name' => 'German user',
+      'mail' => 'test@example.com',
+      'created' => REQUEST_TIME,
+      'status' => 1,
+      'preferred_langcode' => 'de',
+    ));
+
+    $config_factory->enterContext($user_config_context->setAccount($account));
+    // Should not have to re-initialise config object to get new overrides as
+    // the new context will have a different uuid.
+    $config = config('config_test.system');
+    $this->assertIdentical($config->get('foo'), 'de bar');
+
+    // Enter an english context on top of the german context.
+    $account = entity_create('user', array(
+      'name' => 'English user',
+      'mail' => 'test@example.com',
+      'created' => REQUEST_TIME,
+      'status' => 1,
+      'preferred_langcode' => 'en',
+    ));
+    // Create a new user config context to stack on top of the existign one.
+    $en_user_config_context = config_context_enter("Drupal\\user\\UserConfigContext");
+    $en_user_config_context->setAccount($account);
+    $config = config('config_test.system');
+    $this->assertIdentical($config->get('foo'), 'en bar');
+
+    // Ensure that we get the expected value when we leave the english user
+    // context.
+    $config_factory->leaveContext();
+    $config = config('config_test.system');
+    $this->assertIdentical($config->get('foo'), 'de bar');
+
+    // Ensure that we get the expected value when we leave the german user
+    // context.
+    $config_factory->leaveContext();
+    $config = config('config_test.system');
+    $this->assertIdentical($config->get('foo'), 'en bar');
+
+    // Ensure that we cannot leave the default context.
+    $config_factory->leaveContext();
+    $config = config('config_test.system');
+    $this->assertIdentical($config->get('foo'), 'en bar');
+  }
+
+  /*
+   * Tests config_context_enter() invalid context name handling.
+   */
+  function testInvalidContextName() {
+    $message = 'Expected ConfigException was thrown for an invalid context_name argument.';
+    try {
+      config_context_enter('invalid.config.context');
+      $this->fail($message);
+    }
+    catch (ConfigException $e) {
+      $this->pass($message);
+    }
+  }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php
index e2ddb5959928..2fdf1222ef5e 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\config\Tests;
 
 use Drupal\simpletest\DrupalUnitTestBase;
+use Drupal\Core\Config\Context\ConfigContext;
 
 /**
  * Tests configuration overrides via $conf in settings.php.
@@ -19,7 +20,7 @@ class ConfigOverrideTest extends DrupalUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('config_test');
+  public static $modules = array('system', 'config_test');
 
   public static function getInfo() {
     return array(
@@ -29,10 +30,9 @@ public static function getInfo() {
     );
   }
 
-  function setUp() {
+  public function setUp() {
     parent::setUp();
-
-    config_install_default_config('module', 'config_test');
+    $this->installSchema('system', 'config_snapshot');
   }
 
   /**
@@ -46,16 +46,39 @@ function testConfOverride() {
       '404' => 'herp',
     );
 
+    // Set globals before installing to prove that the installed file does not
+    // contain these values.
+    $conf['config_test.system']['foo'] = 'overridden';
+    $conf['config_test.system']['baz'] = 'injected';
+    $conf['config_test.system']['404'] = 'derp';
+    drupal_container()->get('config.context')->setGlobalOverride();
+
+    config_install_default_config('module', 'config_test');
+
+    // Verify that the original configuration data exists. Have to read storage
+    // directly otherwise overrides will apply.
+    $active = $this->container->get('config.storage');
+    $data = $active->read('config_test.system');
+    $this->assertIdentical($data['foo'], $expected_original_data['foo']);
+    $this->assertFalse(isset($data['baz']));
+    $this->assertIdentical($data['404'], $expected_original_data['404']);
+
+    // Remove the $conf overrides and reset value in config.context service.
+    unset($conf['config_test.system']);
+    drupal_container()->get('config.context')->setGlobalOverride();
+
     // Verify that the original configuration data exists.
     $config = config('config_test.system');
     $this->assertIdentical($config->get('foo'), $expected_original_data['foo']);
     $this->assertIdentical($config->get('baz'), $expected_original_data['baz']);
     $this->assertIdentical($config->get('404'), $expected_original_data['404']);
 
-    // Apply the overridden data.
+    // Apply the overridden data, that needs to be set into the config.context
+    // service.
     $conf['config_test.system']['foo'] = 'overridden';
     $conf['config_test.system']['baz'] = 'injected';
     $conf['config_test.system']['404'] = 'derp';
+    drupal_container()->get('config.context')->setGlobalOverride();
 
     // Verify that the in-memory configuration object still contains the
     // original data.
@@ -93,14 +116,45 @@ function testConfOverride() {
     $this->assertIdentical($config->get('baz'), $conf['config_test.system']['baz']);
     $this->assertIdentical($config->get('404'), $conf['config_test.system']['404']);
 
-    // Remove the $conf overrides.
+    // Remove the $conf overrides and reset value in config.context service.
     unset($conf['config_test.system']);
+    drupal_container()->get('config.context')->setGlobalOverride();
 
     // Reload it and verify that it still contains the original data.
     $config->init();
     $this->assertIdentical($config->get('foo'), $expected_original_data['foo']);
     $this->assertIdentical($config->get('baz'), $expected_original_data['baz']);
     $this->assertIdentical($config->get('404'), $expected_original_data['404']);
+
+    // Set globals before importing to prove that the imported file does not
+    // contain these values.
+    $conf['config_test.system']['foo'] = 'overridden';
+    $conf['config_test.system']['baz'] = 'injected';
+    $conf['config_test.system']['404'] = 'derp';
+
+    // Write file to staging
+    drupal_container()->get('config.context')->setGlobalOverride();
+    $staging = $this->container->get('config.storage.staging');
+    $expected_new_data = array(
+      'foo' => 'barbar',
+      '404' => 'herpderp',
+    );
+    $staging->write('config_test.system', $expected_new_data);
+
+    // Import changed data from staging to active.
+    config_import();
+    $data = $active->read('config_test.system');
+
+    // Verify that the new configuration data exists. Have to read storage
+    // directly otherwise overrides will apply.
+    $this->assertIdentical($data['foo'], $expected_new_data['foo']);
+    $this->assertFalse(isset($data['baz']));
+    $this->assertIdentical($data['404'], $expected_new_data['404']);
+
+    // Verifiy the overrides are still working.
+    $this->assertIdentical($config->get('foo'), $conf['config_test.system']['foo']);
+    $this->assertIdentical($config->get('baz'), $conf['config_test.system']['baz']);
+    $this->assertIdentical($config->get('404'), $conf['config_test.system']['404']);
   }
 
 }
diff --git a/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php b/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php
deleted file mode 100644
index 20f835deaab9..000000000000
--- a/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\config\Tests\LocaleConfigOverride.
- */
-
-namespace Drupal\config\Tests;
-
-use Drupal\simpletest\WebTestBase;
-
-/**
- * Tests locale config override.
- */
-class LocaleConfigOverride extends WebTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('locale', 'config_test');
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Locale override',
-      'description' => 'Confirm that locale overrides work',
-      'group' => 'Configuration',
-    );
-  }
-
-  function testLocaleConfigOverride() {
-    $name = 'config_test.system';
-    // Verify the default configuration values exist.
-    $config = config($name);
-    $this->assertIdentical($config->get('foo'), 'en bar');
-  }
-}
diff --git a/core/modules/config/tests/config_test/config/locale.config.de.config_test.system.yml b/core/modules/config/tests/config_test/config/locale.config.de.config_test.system.yml
new file mode 100644
index 000000000000..301e5a596f2c
--- /dev/null
+++ b/core/modules/config/tests/config_test/config/locale.config.de.config_test.system.yml
@@ -0,0 +1 @@
+foo: de bar
diff --git a/core/modules/config/tests/config_test/config/locale.config.fr.config_test.system.yml b/core/modules/config/tests/config_test/config/locale.config.fr.config_test.system.yml
new file mode 100644
index 000000000000..c36dbf783cf9
--- /dev/null
+++ b/core/modules/config/tests/config_test/config/locale.config.fr.config_test.system.yml
@@ -0,0 +1 @@
+foo: fr bar
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleBundle.php b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php
index 4bd8dc07bce5..7c2ca6ec9a29 100644
--- a/core/modules/locale/lib/Drupal/locale/LocaleBundle.php
+++ b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php
@@ -22,6 +22,7 @@ class LocaleBundle extends Bundle {
   public function build(ContainerBuilder $container) {
     $container->register('locale_config_subscriber', 'Drupal\locale\LocaleConfigSubscriber')
       ->addArgument(new Reference('language_manager'))
+      ->addArgument(new Reference('config.context'))
       ->addTag('event_subscriber');
   }
 
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php
index 54d91e76fcac..b7b281b5c061 100644
--- a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php
+++ b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php
@@ -1,15 +1,21 @@
 <?php
 /**
  * @file
- * Definition of Drupal\locale\LocaleConfigSubscriber.
+ * Definition of \Drupal\locale\LocaleConfigSubscriber.
  */
 
 namespace Drupal\locale;
 
 use Drupal\Core\Config\Config;
+use Drupal\Core\Config\Context\ConfigContext;
+use Drupal\Core\Config\Context\ContextInterface;
 use Drupal\Core\Config\ConfigEvent;
 use Drupal\Core\Config\StorageDispatcher;
+use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 
@@ -19,36 +25,68 @@
  * $config is always a DrupalConfig object.
  */
 class LocaleConfigSubscriber implements EventSubscriberInterface {
-
   /**
-   * The language manager for retrieving the interface language.
+   * The language manager.
    *
    * @var \Drupal\Core\Language\LanguageManager
    */
   protected $languageManager;
 
+  protected $defaultConfigContext;
+
   /**
    * Constructs a LocaleConfigSubscriber object.
    *
+   * @param \Drupal\Core\Config\Context\ConfigContext $config_context
+   *   The config context service.
    * @param \Drupal\Core\Language\LanguageManager $language_manager
    *   The language manager service.
    */
-  public function __construct(LanguageManager $language_manager) {
+  public function __construct(LanguageManager $language_manager, ContextInterface $config_context) {
     $this->languageManager = $language_manager;
+    $this->defaultConfigContext = $config_context;
+  }
+
+  /**
+   * Initialize configuration context with language.
+   *
+   * @param \Drupal\Core\Config\ConfigEvent $event
+   *   The Event to process.
+   */
+  public function configContext(ConfigEvent $event) {
+    $context = $event->getContext();
+
+    // Add user's language for user context.
+    if ($account = $context->get('user.account')) {
+      $context->set('locale.language', language_load(user_preferred_langcode($account)));
+    }
+    elseif ($language = $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE)) {
+      $context->set('locale.language', $language);
+    }
   }
 
   /**
    * Override configuration values with localized data.
    *
-   * @param Drupal\Core\Config\ConfigEvent $event
+   * @param \Drupal\Core\Config\ConfigEvent $event
    *   The Event to process.
    */
   public function configLoad(ConfigEvent $event) {
-    $config = $event->getConfig();
-    $language = $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE);
-    $locale_name = $this->getLocaleConfigName($config->getName(), $language);
-    if ($override = $config->getStorage()->read($locale_name)) {
-      $config->setOverride($override);
+    $context = $event->getContext();
+    if ($language = $context->get('locale.language')) {
+      $config = $event->getConfig();
+      $locale_name = $this->getLocaleConfigName($config->getName(), $language);
+      // Check to see if the config storage has an appropriately named file
+      // containing override data.
+      if ($override = $event->getConfig()->getStorage()->read($locale_name)) {
+        $config->setOverride($override);
+      }
+    }
+  }
+
+  public function onKernelRequestSetDefaultConfigContextLocale(GetResponseEvent $event) {
+    if ($language = $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE)) {
+      $this->defaultConfigContext->set('locale.language', $language);
     }
   }
 
@@ -57,8 +95,16 @@ public function configLoad(ConfigEvent $event) {
    *
    * It will be the same name with a prefix depending on language code:
    * locale.config.LANGCODE.NAME
+   *
+   * @param string $name
+   *   The name of the config object.
+   * @param \Drupal\Core\Language\Language $language
+   *   The language object.
+   *
+   * @return string
+   *   The localised config name.
    */
-  public function getLocaleConfigName($name, $language) {
+  public function getLocaleConfigName($name, Language $language) {
     return 'locale.config.' . $language->langcode . '.' . $name;
   }
 
@@ -66,7 +112,9 @@ public function getLocaleConfigName($name, $language) {
    * Implements EventSubscriberInterface::getSubscribedEvents().
    */
   static function getSubscribedEvents() {
+    $events['config.context'][] = array('configContext', 20);
     $events['config.load'][] = array('configLoad', 20);
+    $events[KernelEvents::REQUEST][] = array('onKernelRequestSetDefaultConfigContextLocale', 20);
     return $events;
   }
 }
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index b483e7e2adaa..1b7b8753b6f8 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -1403,6 +1403,7 @@ function system_modules_uninstall_submit($form, &$form_state) {
  * @see system_settings_form()
  */
 function system_site_information_settings($form, &$form_state) {
+  config_context_enter('config.context.free');
   $site_config = config('system.site');
   $site_mail = $site_config->get('mail');
   if (empty($site_mail)) {
@@ -1509,6 +1510,7 @@ function system_site_information_settings_validate($form, &$form_state) {
  * Form submission handler for system_site_information_settings().
  */
 function system_site_information_settings_submit($form, &$form_state) {
+  config_context_enter('config.context.free');
   config('system.site')
     ->set('name', $form_state['values']['site_name'])
     ->set('mail', $form_state['values']['site_mail'])
@@ -1525,6 +1527,7 @@ function system_site_information_settings_submit($form, &$form_state) {
  * @ingroup forms
  */
 function system_cron_settings($form, &$form_state) {
+  config_context_enter('config.context.free');
   $form['description'] = array(
     '#markup' => '<p>' . t('Cron takes care of running periodic tasks like checking for updates and indexing content for search.') . '</p>',
   );
@@ -1562,6 +1565,7 @@ function system_cron_settings($form, &$form_state) {
  * @ingroup forms
  */
 function system_cron_settings_submit($form, &$form_state) {
+  config_context_enter('config.context.free');
   config('system.cron')
     ->set('threshold.autorun', $form_state['values']['cron_safe_threshold'])
     ->save();
@@ -1591,6 +1595,7 @@ function system_run_cron_submit($form, &$form_state) {
  * @see system_logging_settings_submit()
  */
 function system_logging_settings($form, &$form_state) {
+  config_context_enter('config.context.free');
   $form['error_level'] = array(
     '#type' => 'radios',
     '#title' => t('Error messages to display'),
@@ -1613,6 +1618,7 @@ function system_logging_settings($form, &$form_state) {
  * @ingroup forms
  */
 function system_logging_settings_submit($form, &$form_state) {
+  config_context_enter('config.context.free');
   config('system.logging')
     ->set('error_level', $form_state['values']['error_level'])
     ->save();
@@ -1626,6 +1632,7 @@ function system_logging_settings_submit($form, &$form_state) {
  */
 function system_performance_settings($form, &$form_state) {
   drupal_add_library('system', 'drupal.system');
+  config_context_enter('config.context.free');
   $config = config('system.performance');
 
   $form['clear_cache'] = array(
@@ -1715,6 +1722,7 @@ function system_performance_settings($form, &$form_state) {
  * @ingroup forms
  */
 function system_performance_settings_submit($form, &$form_state) {
+  config_context_enter('config.context.free');
   $config = config('system.performance');
   $config->set('cache.page.enabled', $form_state['values']['cache']);
   $config->set('cache.page.max_age', $form_state['values']['page_cache_maximum_age']);
@@ -1860,6 +1868,7 @@ function system_image_toolkit_settings() {
  * @ingroup forms
  */
 function system_rss_feeds_settings($form, &$form_state) {
+  config_context_enter('config.context.free');
   $rss_config = config('system.rss');
   $form['feed_description'] = array(
     '#type' => 'textarea',
@@ -1895,6 +1904,7 @@ function system_rss_feeds_settings($form, &$form_state) {
  * @ingroup forms
  */
 function system_rss_feeds_settings_submit($form, &$form_state) {
+  config_context_enter('config.context.free');
   config('system.rss')
     ->set('channel.description', $form_state['values']['feed_description'])
     ->set('items.limit', $form_state['values']['feed_default_items'])
@@ -2015,6 +2025,7 @@ function system_regional_settings_submit($form, &$form_state) {
  * @see system_site_maintenance_mode_submit()
  */
 function system_site_maintenance_mode($form, &$form_state) {
+  config_context_enter('config.context.free');
   $config = config('system.maintenance');
   $form['maintenance_mode'] = array(
     '#type' => 'checkbox',
@@ -2037,6 +2048,7 @@ function system_site_maintenance_mode($form, &$form_state) {
  * @ingroup forms
  */
 function system_site_maintenance_mode_submit($form, &$form_state) {
+  config_context_enter('config.context.free');
   config('system.maintenance')
     ->set('enabled', $form_state['values']['maintenance_mode'])
     ->set('message', $form_state['values']['maintenance_mode_message'])
diff --git a/core/modules/user/lib/Drupal/user/UserConfigContext.php b/core/modules/user/lib/Drupal/user/UserConfigContext.php
new file mode 100644
index 000000000000..d6e411449762
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/UserConfigContext.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\UserConfigContext
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\Config\Context\ConfigContext;
+use Drupal\user\Plugin\Core\Entity\User;
+
+
+/**
+ * Defines a configuration context object for a user account.
+ *
+ * This should be used when configuration objects need a context for a user
+ * other than the current user.
+ *
+ * @see user_mail()
+ */
+class UserConfigContext extends ConfigContext {
+
+  /**
+   * Predefined key for account object.
+   */
+  const USER_KEY = 'user.account';
+
+  /**
+   * Implements \Drupal\Core\Config\Context\ContextInterface::setUuid().
+   */
+  public function setUuid() {
+    // Use the user's uuid to identify the config context.
+    $this->uuid = $this->get(self::USER_KEY)->uuid();
+  }
+
+  /*
+   * Helper function to create config context for user accounts.
+   *
+   * @param \Drupal\user\Plugin\Core\Entity\User $account
+   *   The account to add to the config context.
+   *
+   * @return \Drupal\user\UserConfigContext
+   *   The user config context object.
+   */
+  public function setAccount(User $account) {
+    $this->init(self::USER_KEY, $account);
+    return $this;
+  }
+
+}
diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc
index f04c97d82719..31c6beab9714 100644
--- a/core/modules/user/user.admin.inc
+++ b/core/modules/user/user.admin.inc
@@ -292,6 +292,7 @@ function user_admin_account_validate($form, &$form_state) {
  * @see user_admin_settings_submit()
  */
 function user_admin_settings($form, &$form_state) {
+  config_context_enter('config.context.free');
   $config = config('user.settings');
   $mail_config = config('user.mail');
 
@@ -641,6 +642,7 @@ function user_admin_settings($form, &$form_state) {
  * Form submission handler for user_admin_settings().
  */
 function user_admin_settings_submit($form, &$form_state) {
+  config_context_enter('config.context.free');
   config('user.settings')
     ->set('anonymous', $form_state['values']['anonymous'])
     ->set('admin_role', $form_state['values']['user_admin_role'])
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 5635e1664966..12504233f8d5 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1756,35 +1756,30 @@ function user_view_multiple($accounts, $view_mode = 'full', $langcode = NULL) {
 function user_mail($key, &$message, $params) {
   $langcode = $message['langcode'];
   $variables = array('user' => $params['account']);
-  $message['subject'] .= _user_mail_text($key . '.subject', $langcode, $variables);
-  $message['body'][] = _user_mail_text($key . '.body', $langcode, $variables);
-}
 
-/**
- * Returns a mail string for a variable name.
- *
- * @param string $key
- *   The config key that provides the mail text.
- * @param string $langcode
- *   (optional) A language code to use to generate the e-mail text.
- * @param array $variables
- *   (optional) An array of token keys and values.
- *
- * @return
- *   A string value containing the text for the user.mail config key.
- */
-function _user_mail_text($key, $langcode = NULL, $variables = array()) {
-  // We do not sanitize the token replacement, since the output of this
-  // replacement is intended for an e-mail message, not a web browser.
-  return token_replace(config('user.mail')->get($key), $variables, array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+  // Get configuration objects customized for the user specified in $params as
+  // this user is not necessarily the same as the one triggering the mail. This
+  // allows the configuration objects to be localized for the user's language if
+  // the locale module is enabled.
+  $user_config_context = config_context_enter("Drupal\\user\\UserConfigContext");
+  $user_config_context->setAccount($params['account']);
+  $mail_config = config('user.mail');
+
+   // We do not sanitize the token replacement, since the output of this
+   // replacement is intended for an e-mail message, not a web browser.
+  $token_options = array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE);
+  $message['subject'] .= token_replace($mail_config->get($key . '.subject'), $variables, $token_options);
+  $message['body'][] = token_replace($mail_config->get($key . '.body'), $variables, $token_options);
+
+  // Return the previous config context.
+  config_context_leave();
 }
 
 /**
  * Token callback to add unsafe tokens for user mails.
  *
- * This function is used by the token_replace() call at the end of
- * _user_mail_text() to set up some additional tokens that can be
- * used in email messages generated by user_mail().
+ * This function is used by the token_replace() to set up some additional
+ * tokens that can be used in email messages generated by user_mail().
  *
  * @param $replacements
  *   An associative array variable containing mappings from token names to
-- 
GitLab