diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index d28d1e3..2cf982e 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2404,20 +2404,24 @@ function drupal_get_bootstrap_phase() {
*
* @see Drupal\Core\DrupalKernel
*
- * @param $reset
- * A new container instance to reset the Drupal container to.
+ * @param Symfony\Component\DependencyInjection\Container $new_container
+ * A new container instance to replace the current.
+ * @param bool $reset
+ * (optional) Internal use only. Whether to enforce a reset of the statically
+ * cached container. Pass NULL for $new_container to create a fresh Container
+ * in a subsequent call to this function. Used by tests.
*
* @return Symfony\Component\DependencyInjection\Container
* The instance of the Container used to set up and maintain object
* instances.
*/
-function drupal_container(Container $reset = NULL) {
+function drupal_container(Container $new_container = NULL, $reset = FALSE) {
// We do not use drupal_static() here because we do not have a mechanism by
// which to reinitialize the stored objects, so a drupal_static_reset() call
// would leave Drupal in a nonfunctional state.
static $container = NULL;
- if (isset($reset)) {
- $container = $reset;
+ if (isset($new_container) || $reset) {
+ $container = $new_container;
}
elseif (!isset($container)) {
// Return a ContainerBuilder instance with the bare essentials needed for any
@@ -2426,25 +2430,38 @@ function drupal_container(Container $reset = NULL) {
// requests.
$container = new ContainerBuilder();
- // Register configuration storage class and options.
+ // Register active configuration storage.
// @todo The active store and its options need to be configurable.
// Use either global $conf (recursion warning) or global $config, or a
// bootstrap configuration *file* to allow to set/override this very
// lowest of low level configuration.
- $container->setParameter('config.storage.options', array(
- 'connection' => 'default',
- 'target' => 'default',
+ $container->setParameter('config.storage.active.options', array(
+ 'Drupal\Core\Config\FileStorage' => array(
+ 'directory' => config_get_config_directory(CONFIG_ACTIVE_DIRECTORY),
+ ),
+ 'Drupal\Core\Config\CacheStorage' => array(
+ 'backend' => 'Drupal\Core\Cache\DatabaseBackend',
+ 'bin' => 'config',
+ ),
));
- $container->register('config.storage', 'Drupal\Core\Config\DatabaseStorage')
- ->addArgument('%config.storage.options%');
+ $container->register('config.storage.active', 'Drupal\Core\Config\CachedFileStorage')
+ ->addArgument('%config.storage.active.options%');
$container->register('config.subscriber.globalconf', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber');
$container->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
->addMethodCall('addSubscriber', array(new Reference('config.subscriber.globalconf')));
+
// Register configuration object factory.
$container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
- ->addArgument(new Reference('config.storage'))
+ ->addArgument(new Reference('config.storage.active'))
->addArgument(new Reference('dispatcher'));
+
+ // Register staging configuration storage.
+ $container->setParameter('config.storage.staging.options', array(
+ 'directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY),
+ ));
+ $container->register('config.storage.staging', 'Drupal\Core\Config\FileStorage')
+ ->addArgument('%config.storage.staging.options%');
}
return $container;
}
diff --git a/core/includes/config.inc b/core/includes/config.inc
index 369a0d1..2bc77c0 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -17,15 +17,12 @@
* The extension type; e.g., 'module' or 'theme'.
* @param string $name
* The name of the module or theme to install default configuration for.
- *
- * @todo Make this acknowledge other storage engines rather than having
- * SQL be hardcoded.
*/
function config_install_default_config($type, $name) {
$config_dir = drupal_get_path($type, $name) . '/config';
if (is_dir($config_dir)) {
$source_storage = new FileStorage(array('directory' => $config_dir));
- $target_storage = drupal_container()->get('config.storage');
+ $target_storage = drupal_container()->get('config.storage.active');
$null_storage = new NullStorage();
// Upon installation, only new config objects need to be created.
@@ -43,10 +40,12 @@ function config_install_default_config($type, $name) {
}
/**
- * @todo Modules need a way to access the active store, whatever it is.
+ * Gets configuration object names starting with a given prefix.
+ *
+ * @see Drupal\Core\Config\StorageInterface::listAll()
*/
function config_get_storage_names_with_prefix($prefix = '') {
- return drupal_container()->get('config.storage')->listAll($prefix);
+ return drupal_container()->get('config.storage.active')->listAll($prefix);
}
/**
@@ -137,8 +136,8 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto
*/
function config_import() {
// Retrieve a list of differences between staging and the active store.
- $source_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY)));
- $target_storage = drupal_container()->get('config.storage');
+ $source_storage = drupal_container()->get('config.storage.staging');
+ $target_storage = drupal_container()->get('config.storage.active');
$config_changes = config_sync_get_changes($source_storage, $target_storage);
if (empty($config_changes)) {
@@ -218,8 +217,8 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
*/
function config_export() {
// Retrieve a list of differences between the active store and staging.
- $source_storage = drupal_container()->get('config.storage');
- $target_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY)));
+ $source_storage = drupal_container()->get('config.storage.active');
+ $target_storage = drupal_container()->get('config.storage.staging');
$config_changes = config_sync_get_changes($source_storage, $target_storage);
if (empty($config_changes)) {
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index c15107c..039f007 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -5,6 +5,8 @@
use Drupal\Core\Database\Install\TaskException;
use Drupal\Core\Language\Language;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -272,6 +274,42 @@ function install_begin_request(&$install_state) {
include_once DRUPAL_ROOT . '/core/includes/module.inc';
include_once DRUPAL_ROOT . '/core/includes/session.inc';
+ // Determine whether the configuration system is ready to operate.
+ $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY);
+
+ // If it is not, replace the configuration storage with the InstallStorage
+ // implementation, for the following reasons:
+ // - The first call into drupal_container() will try to set up the regular
+ // runtime configuration storage, using the CachedFileStorage. The storage
+ // controller options require to pass in the config directory name, but that
+ // directory does not exist yet.
+ // - The installer outputs maintenance theme pages and performs many other
+ // operations, which try to load configuration. Since there is no active
+ // configuration yet, and because the configuration system does not have a
+ // notion of default values at runtime, data is missing in many places. The
+ // lack of data does not trigger errors, but results in a broken user
+ // interface (e.g., missing page title, etc).
+ // - The actual configuration data to read during installation is essentially
+ // the default configuration provided by the installation profile and
+ // modules (most notably System module). The InstallStorage therefore reads
+ // from the default configuration directories of extensions.
+ // This override is reverted as soon as the config directory has been set up
+ // successfully.
+ // @see drupal_install_config_directory()
+ // @todo Move this into a proper Drupal\Core\DependencyInjection\InstallContainerBuilder.
+ if (!$install_state['config_verified']) {
+ $container = new ContainerBuilder();
+
+ $container->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher');
+
+ $container->register('config.storage.active', 'Drupal\Core\Config\InstallStorage');
+ $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
+ ->addArgument(new Reference('config.storage.active'))
+ ->addArgument(new Reference('dispatcher'));
+
+ drupal_container($container);
+ }
+
// Set up $language, so t() caller functions will still work.
drupal_language_initialize();
@@ -316,7 +354,6 @@ function install_begin_request(&$install_state) {
// Check existing settings.php.
$install_state['database_verified'] = install_verify_database_settings();
- $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY);
$install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified'];
if ($install_state['database_verified']) {
@@ -1054,6 +1091,11 @@ function install_settings_form_submit($form, &$form_state) {
// Add the config directories to settings.php.
drupal_install_config_directories();
+ // We have valid configuration directories in settings.php.
+ // Reset the service container, so the config.storage.active service will use
+ // the actual active storage for installing configuration.
+ drupal_container(NULL, TRUE);
+
// Indicate that the settings file has been verified, and check the database
// for the last completed task, now that we have a valid connection. This
// last step is important since we want to trigger an error if the new
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 2c95bf3..3ae0144 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -7,6 +7,7 @@
use Drupal\Core\Database\Database;
use Drupal\locale\Gettext;
+use Exception;
/**
* Indicates that a module has not been installed yet.
@@ -275,7 +276,7 @@ function drupal_install_config_directories() {
drupal_rewrite_settings($settings);
}
- // Ensure that the config directory exists or can be created, and is writable.
+ // Ensure the config directories exist or can be created, and are writable.
foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $config_type) {
// This should never fail, since if the config directory was specified in
// settings.php it will have already been created and verified earlier, and
@@ -296,7 +297,7 @@ function drupal_install_config_directories() {
* Checks whether a config directory name is defined, and if so, whether it
* exists and is writable.
*
- * This partically duplicates install_ensure_config_directory(), but is required
+ * This partially duplicates install_ensure_config_directory(), but is required
* since the installer would create the config directory too early in the
* installation process otherwise (e.g., when only visiting install.php when
* there is a settings.php already, but not actually executing the installation).
@@ -313,9 +314,16 @@ function install_verify_config_directory($type) {
if (!isset($config_directories[$type])) {
return FALSE;
}
- $config_directory = config_get_config_directory($type);
- if (is_dir($config_directory) && is_writable($config_directory)) {
- return TRUE;
+ // config_get_config_directory() throws an exception when there is a prepared
+ // settings.php that defines $config_directories already and the directories
+ // do not exist yet.
+ try {
+ $config_directory = config_get_config_directory($type);
+ if (is_dir($config_directory) && is_writable($config_directory)) {
+ return TRUE;
+ }
+ }
+ catch (Exception $e) {
}
return FALSE;
}
diff --git a/core/includes/module.inc b/core/includes/module.inc
index d9eab10..c3399c0 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -6,7 +6,7 @@
*/
use Drupal\Component\Graph\Graph;
-use Drupal\Core\Config\DatabaseStorage;
+use Drupal\Core\Config\NullStorage;
/**
* Loads all the modules that have been enabled in the system table.
@@ -617,19 +617,26 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE)
$module_list = array_keys($module_list);
}
- $storage = new DatabaseStorage();
+ $source_storage = new NullStorage();
+ $target_storage = drupal_container()->get('config.storage.active');
foreach ($module_list as $module) {
+ // Remove all configuration belonging to the module.
+ $config_changes = $target_storage->listAll($module . '.');
+ if (!empty($config_changes)) {
+ $config_changes = array(
+ 'delete' => $config_changes,
+ 'change' => array(),
+ 'create' => array(),
+ );
+ $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
+ config_sync_changes($remaining_changes, $source_storage, $target_storage);
+ }
+
// Uninstall the module.
module_load_install($module);
module_invoke($module, 'uninstall');
drupal_uninstall_schema($module);
- // Remove all configuration belonging to the module.
- $config_names = $storage->listAll($module . '.');
- foreach ($config_names as $config_name) {
- config($config_name)->delete();
- }
-
watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
}
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 12de2a7..4b3e80c 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -122,7 +122,7 @@ function theme_task_list($variables) {
}
else {
$class = $done ? 'done' : '';
- $status = $done ? '(' . t('done') . ')' : '';
+ $status = $done ? '(' . $t('done') . ')' : '';
}
$output .= '
';
diff --git a/core/lib/Drupal/Core/Config/CacheStorage.php b/core/lib/Drupal/Core/Config/CacheStorage.php
new file mode 100644
index 0000000..a2effc8
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/CacheStorage.php
@@ -0,0 +1,143 @@
+ 'Drupal\Core\Cache\DatabaseBackend',
+ 'bin' => 'config',
+ );
+ $this->options = $options;
+ }
+
+ /**
+ * Returns the instantiated Cache backend to use.
+ */
+ protected function getBackend() {
+ if (!isset($this->storage)) {
+ $this->storage = new $this->options['backend']($this->options['bin']);
+ }
+ return $this->storage;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::exists().
+ */
+ public function exists($name) {
+ return (bool) $this->getBackend()->get($name);
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::read().
+ */
+ public function read($name) {
+ if ($cache = $this->getBackend()->get($name)) {
+ // The cache backend supports primitive data types, but only an array
+ // represents valid config object data.
+ if (is_array($cache->data)) {
+ return $cache->data;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::write().
+ */
+ public function write($name, array $data) {
+ $this->getBackend()->set($name, $data, CacheBackendInterface::CACHE_PERMANENT, array('config' => array($name)));
+ return TRUE;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::delete().
+ */
+ public function delete($name) {
+ $this->getBackend()->delete($name);
+ return TRUE;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::rename().
+ */
+ public function rename($name, $new_name) {
+ $this->getBackend()->delete($name);
+ $this->getBackend()->delete($new_name);
+ return TRUE;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::encode().
+ */
+ public static function encode($data) {
+ return serialize($data);
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::decode().
+ *
+ * @throws ErrorException
+ * unserialize() triggers E_NOTICE if the string cannot be unserialized.
+ */
+ public static function decode($raw) {
+ $data = @unserialize($raw);
+ return is_array($data) ? $data : FALSE;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::listAll().
+ *
+ * Not supported by CacheBackendInterface.
+ */
+ public function listAll($prefix = '') {
+ return array();
+ }
+}
diff --git a/core/lib/Drupal/Core/Config/CachedFileStorage.php b/core/lib/Drupal/Core/Config/CachedFileStorage.php
new file mode 100644
index 0000000..0fc0cfe
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/CachedFileStorage.php
@@ -0,0 +1,134 @@
+options = $options;
+
+ $this->storages['file'] = new FileStorage($options['Drupal\Core\Config\FileStorage']);
+
+ unset($options['Drupal\Core\Config\FileStorage']);
+ list($cache_class, $cache_options) = each($options);
+ $this->storages['cache'] = new $cache_class($cache_options);
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::exists().
+ */
+ public function exists($name) {
+ // A single filestat is faster than a complex cache lookup and possibly
+ // subsequent filestat.
+ return $this->storages['file']->exists($name);
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::read().
+ */
+ public function read($name) {
+ // Check the cache.
+ $data = $this->storages['cache']->read($name);
+ // If the cache returns no result, check the file storage.
+ if ($data === FALSE) {
+ $data = $this->storages['file']->read($name);
+ // @todo Should the config object be cached if it does not exist?
+ if ($data !== FALSE) {
+ $this->storages['cache']->write($name, $data);
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::write().
+ */
+ public function write($name, array $data) {
+ $success = $this->storages['file']->write($name, $data);
+ $this->storages['cache']->delete($name);
+ return $success;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::delete().
+ */
+ public function delete($name) {
+ $success = TRUE;
+ foreach ($this->storages as $storage) {
+ if (!$storage->delete($name)) {
+ $success = FALSE;
+ }
+ }
+ return $success;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::rename().
+ */
+ public function rename($name, $new_name) {
+ $success = $this->storages['file']->rename($name, $new_name);
+ $this->storages['cache']->rename($name, $new_name);
+ return $success;
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::encode().
+ *
+ * @todo Remove encode() from StorageInterface.
+ */
+ public static function encode($data) {
+ return $this->storages['file']->encode($data);
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::decode().
+ *
+ * @todo Remove decode() from StorageInterface.
+ */
+ public static function decode($raw) {
+ return $this->storages['file']->decode($raw);
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::listAll().
+ */
+ public function listAll($prefix = '') {
+ return $this->storages['file']->listAll($prefix);
+ }
+}
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index ad8c155..e4a1934 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -44,6 +44,15 @@ protected function getConnection() {
}
/**
+ * Implements Drupal\Core\Config\StorageInterface::exists().
+ */
+ public function exists($name) {
+ return (bool) $this->getConnection()->queryRange('SELECT 1 FROM {config} WHERE name = :name', 0, 1, array(
+ ':name' => $name,
+ ), $this->options)->fetchField();
+ }
+
+ /**
* Implements Drupal\Core\Config\StorageInterface::read().
*
* @throws PDOException
@@ -56,8 +65,6 @@ public function read($name) {
// read without actually having the database available. In this case,
// catch the exception and just return an empty array so the caller can
// handle it if need be.
- // @todo Remove this and use appropriate config.storage service definition
- // in the installer instead.
try {
$raw = $this->getConnection()->query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name), $this->options)->fetchField();
if ($raw !== FALSE) {
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index d96af7e..cbad07f 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -28,7 +28,7 @@ class FileStorage implements StorageInterface {
*/
public function __construct(array $options = array()) {
if (!isset($options['directory'])) {
- $options['directory'] = config_get_config_directory();
+ $options['directory'] = config_get_config_directory(CONFIG_ACTIVE_DIRECTORY);
}
$this->options = $options;
}
@@ -54,10 +54,7 @@ public static function getFileExtension() {
}
/**
- * Returns whether the configuration file exists.
- *
- * @return bool
- * TRUE if the configuration file exists, FALSE otherwise.
+ * Implements Drupal\Core\Config\StorageInterface::exists().
*/
public function exists($name) {
return file_exists($this->getFilePath($name));
diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php
new file mode 100644
index 0000000..b2ee894
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/InstallStorage.php
@@ -0,0 +1,105 @@
+options = $options;
+ }
+
+ /**
+ * Overrides Drupal\Core\Config\FileStorage::getFilePath().
+ *
+ * Returns the path to the configuration file.
+ *
+ * Determines the owner and path to the default configuration file of a
+ * requested config object name located in the installation profile, a module,
+ * or a theme (in this order).
+ *
+ * @return string
+ * The path to the configuration file.
+ *
+ * @todo Improve this when figuring out how we want to handle configuration in
+ * installation profiles. E.g., a config object actually has to be searched
+ * in the profile first (whereas the profile is never the owner), only
+ * afterwards check for a corresponding module or theme.
+ */
+ public function getFilePath($name) {
+ // Extract the owner.
+ $owner = strtok($name, '.');
+ // Determine the path to the owner.
+ $path = FALSE;
+ foreach (array('profile', 'module', 'theme') as $type) {
+ if ($path = drupal_get_path($type, $owner)) {
+ $file = $path . '/config/' . $name . '.' . self::getFileExtension();
+ if (file_exists($file)) {
+ return $file;
+ }
+ }
+ }
+ // If any code in the early installer requests a configuration object that
+ // does not exist anywhere as default config, then that must be mistake.
+ throw new StorageException(format_string('Missing configuration file: @name', array(
+ '@name' => $name,
+ )));
+ }
+
+ /**
+ * Overrides Drupal\Core\Config\FileStorage::write().
+ *
+ * @throws Drupal\Core\Config\StorageException
+ */
+ public function write($name, array $data) {
+ throw new StorageException('Write operations are not allowed.');
+ }
+
+ /**
+ * Overrides Drupal\Core\Config\FileStorage::delete().
+ *
+ * @throws Drupal\Core\Config\StorageException
+ */
+ public function delete($name) {
+ throw new StorageException('Write operations are not allowed.');
+ }
+
+ /**
+ * Overrides Drupal\Core\Config\FileStorage::rename().
+ *
+ * @throws Drupal\Core\Config\StorageException
+ */
+ public function rename($name, $new_name) {
+ throw new StorageException('Write operations are not allowed.');
+ }
+
+ /**
+ * Implements Drupal\Core\Config\StorageInterface::listAll().
+ *
+ * @throws Drupal\Core\Config\StorageException
+ */
+ public function listAll($prefix = '') {
+ throw new StorageException('List operation is not supported.');
+ }
+}
diff --git a/core/lib/Drupal/Core/Config/NullStorage.php b/core/lib/Drupal/Core/Config/NullStorage.php
index ea21be1..aa81b73 100644
--- a/core/lib/Drupal/Core/Config/NullStorage.php
+++ b/core/lib/Drupal/Core/Config/NullStorage.php
@@ -29,6 +29,13 @@ public function __construct(array $options = array()) {
}
/**
+ * Implements Drupal\Core\Config\StorageInterface::exists().
+ */
+ public function exists($name) {
+ return FALSE;
+ }
+
+ /**
* Implements Drupal\Core\Config\StorageInterface::read().
*/
public function read($name) {
diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php
index a466538..688ae46 100644
--- a/core/lib/Drupal/Core/Config/StorageInterface.php
+++ b/core/lib/Drupal/Core/Config/StorageInterface.php
@@ -25,6 +25,17 @@
public function __construct(array $options = array());
/**
+ * Returns whether a configuration object exists.
+ *
+ * @param string $name
+ * The name of a configuration object to test.
+ *
+ * @return bool
+ * TRUE if the configuration object exists, FALSE otherwise.
+ */
+ public function exists($name);
+
+ /**
* Reads configuration data from the storage.
*
* @param string $name
diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
new file mode 100644
index 0000000..e41dcb8
--- /dev/null
+++ b/core/modules/config/config.admin.inc
@@ -0,0 +1,132 @@
+ t('There are no configuration changes.'),
+ );
+ return $form;
+ }
+
+ foreach ($config_changes as $config_change_type => $config_files) {
+ if (empty($config_files)) {
+ continue;
+ }
+ $form[$config_change_type] = array(
+ '#type' => 'fieldset',
+ '#title' => $config_change_type . ' (' . count($config_files) . ')',
+ '#collapsible' => TRUE,
+ );
+ $form[$config_change_type]['config_files'] = array(
+ '#theme' => 'table',
+ '#header' => array('Name'),
+ );
+ foreach ($config_files as $config_file) {
+ $form[$config_change_type]['config_files']['#rows'][] = array($config_file);
+ }
+ }
+}
+
+/**
+ * Form constructor for configuration import form.
+ *
+ * @see config_admin_import_form_submit()
+ * @see config_import()
+ */
+function config_admin_import_form($form, &$form_state) {
+ // Retrieve a list of differences between last known state and active store.
+ $source_storage = drupal_container()->get('config.storage.staging');
+ $target_storage = drupal_container()->get('config.storage.active');
+
+ // Prevent users from deleting all configuration.
+ // If the source storage is empty, that signals the unique condition of not
+ // having exported anything at all, and thus no valid storage to compare the
+ // active storage against.
+ // @todo StorageInterface::listAll() can easily yield hundreds or even
+ // thousands of entries; consider to add a dedicated isEmpty() method for
+ // storage controllers.
+ $all = $source_storage->listAll();
+ if (empty($all)) {
+ form_set_error('', t('There is no base configuration. Export it first.', array(
+ '@export-url' => url('admin/config/development/sync/export'),
+ )));
+ return $form;
+ }
+
+ config_admin_sync_form($form, $form_state, $source_storage, $target_storage);
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import'),
+ );
+ return $form;
+}
+
+/**
+ * Form submission handler for config_admin_import_form().
+ */
+function config_admin_import_form_submit($form, &$form_state) {
+ if (config_import()) {
+ drupal_set_message(t('The configuration was imported successfully.'));
+ }
+ else {
+ // Another request may be synchronizing configuration already. Wait for it
+ // to complete before returning the error, so already synchronized changes
+ // do not appear again.
+ lock_wait(__FUNCTION__);
+ drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
+ }
+}
+
+/**
+ * Form constructor for configuration export form.
+ *
+ * @see config_admin_export_form_submit()
+ * @see config_export()
+ *
+ * @todo "export" is a misnomer with config.storage.staging.
+ */
+function config_admin_export_form($form, &$form_state) {
+ // Retrieve a list of differences between active store and last known state.
+ $source_storage = drupal_container()->get('config.storage.active');
+ $target_storage = drupal_container()->get('config.storage.staging');
+
+ config_admin_sync_form($form, $form_state, $source_storage, $target_storage);
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Export'),
+ );
+ return $form;
+}
+
+/**
+ * Form submission handler for config_admin_export_form().
+ */
+function config_admin_export_form_submit($form, &$form_state) {
+ config_export();
+ drupal_set_message(t('The configuration was exported successfully.'));
+}
+
diff --git a/core/modules/config/config.info b/core/modules/config/config.info
index 380f17e..efab7a1 100644
--- a/core/modules/config/config.info
+++ b/core/modules/config/config.info
@@ -3,3 +3,4 @@ description = Allows administrators to manage configuration changes.
package = Core
version = VERSION
core = 8.x
+configure = admin/config/development/sync
diff --git a/core/modules/config/config.module b/core/modules/config/config.module
index b3d9bbc..fd38fd2 100644
--- a/core/modules/config/config.module
+++ b/core/modules/config/config.module
@@ -1 +1,46 @@
t('Synchronize configuration'),
+ 'restrict access' => TRUE,
+ );
+ return $permissions;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function config_menu() {
+ $items['admin/config/development/sync'] = array(
+ 'title' => 'Synchronize configuration',
+ 'description' => 'Synchronize configuration changes.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('config_admin_import_form'),
+ 'access arguments' => array('synchronize configuration'),
+ 'file' => 'config.admin.inc',
+ );
+ $items['admin/config/development/sync/import'] = array(
+ 'title' => 'Import',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/config/development/sync/export'] = array(
+ 'title' => 'Export',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('config_admin_export_form'),
+ 'access arguments' => array('synchronize configuration'),
+ 'file' => 'config.admin.inc',
+ 'type' => MENU_LOCAL_TASK,
+ );
+ return $items;
+}
+
diff --git a/core/modules/config/lib/Drupal/config/ConfigStorageController.php b/core/modules/config/lib/Drupal/config/ConfigStorageController.php
index 12d62b4..840c8fb 100644
--- a/core/modules/config/lib/Drupal/config/ConfigStorageController.php
+++ b/core/modules/config/lib/Drupal/config/ConfigStorageController.php
@@ -151,7 +151,7 @@ protected function buildQuery($ids, $revision_id = FALSE) {
// Load all of the configurables.
if ($ids === NULL) {
- $names = drupal_container()->get('config.storage')->listAll($prefix);
+ $names = drupal_container()->get('config.storage.active')->listAll($prefix);
$result = array();
foreach ($names as $name) {
$config = config($name);
@@ -273,7 +273,8 @@ public function save(StorableInterface $entity) {
// Configuration objects do not have a schema. Extract all key names from
// class properties.
$class_info = new \ReflectionClass($entity);
- foreach ($class_info->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
+ $properties = $class_info->getProperties(\ReflectionProperty::IS_PUBLIC);
+ foreach ($properties as $property) {
$name = $property->getName();
$config->set($name, $entity->$name);
}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
index a5d36a9..3a66d57 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
@@ -7,7 +7,6 @@
namespace Drupal\config\Tests;
-use Drupal\Core\Config\DatabaseStorage;
use Drupal\simpletest\WebTestBase;
/**
@@ -26,7 +25,7 @@ public static function getInfo() {
* Tests CRUD operations.
*/
function testCRUD() {
- $storage = new DatabaseStorage();
+ $storage = $this->container->get('config.storage.active');
$name = 'config_test.crud';
$config = config($name);
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php
index abbd2ae..4a82d0b 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php
@@ -7,7 +7,6 @@
namespace Drupal\config\Tests;
-use Drupal\Core\Config\DatabaseStorage;
use Drupal\Core\Config\FileStorage;
use Drupal\simpletest\WebTestBase;
@@ -23,15 +22,11 @@ public static function getInfo() {
);
}
- function setUp() {
- parent::setUp();
- }
-
/**
* Tests setting, writing, and reading of a configuration setting.
*/
function testReadWriteConfig() {
- $database_storage = new DatabaseStorage();
+ $storage = $this->container->get('config.storage.active');
$name = 'foo.bar';
$key = 'foo';
@@ -67,7 +62,7 @@ function testReadWriteConfig() {
$this->assertEqual($config->get(), array(), t('New config object is empty.'));
// Verify nothing was saved.
- $db_data = $database_storage->read($name);
+ $db_data = $storage->read($name);
$this->assertIdentical($db_data, FALSE);
// Add a top level value
@@ -94,7 +89,7 @@ function testReadWriteConfig() {
$config->save();
// Verify the database entry exists.
- $db_data = $database_storage->read($name);
+ $db_data = $storage->read($name);
$this->assertTrue($db_data);
// Read top level value
@@ -152,27 +147,27 @@ function testReadWriteConfig() {
$config->set($key, $value)->save();
// Verify the database entry exists from a chained save.
- $db_data = $database_storage->read($chained_name);
+ $db_data = $storage->read($chained_name);
$this->assertEqual($db_data, $config->get());
// Get file listing for all files starting with 'foo'. Should return
// two elements.
- $files = $database_storage->listAll('foo');
+ $files = $storage->listAll('foo');
$this->assertEqual(count($files), 2, 'Two files listed with the prefix \'foo\'.');
// Get file listing for all files starting with 'biff'. Should return
// one element.
- $files = $database_storage->listAll('biff');
+ $files = $storage->listAll('biff');
$this->assertEqual(count($files), 1, 'One file listed with the prefix \'biff\'.');
// Get file listing for all files starting with 'foo.bar'. Should return
// one element.
- $files = $database_storage->listAll('foo.bar');
+ $files = $storage->listAll('foo.bar');
$this->assertEqual(count($files), 1, 'One file listed with the prefix \'foo.bar\'.');
// Get file listing for all files starting with 'bar'. Should return
// an empty array.
- $files = $database_storage->listAll('bar');
+ $files = $storage->listAll('bar');
$this->assertEqual($files, array(), 'No files listed with the prefix \'bar\'.');
// Delete the configuration.
@@ -180,7 +175,7 @@ function testReadWriteConfig() {
$config->delete();
// Verify the database entry no longer exists.
- $db_data = $database_storage->read($name);
+ $db_data = $storage->read($name);
$this->assertIdentical($db_data, FALSE);
}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
index 0c1fe68..8ad26f0 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
@@ -7,8 +7,6 @@
namespace Drupal\config\Tests;
-use Drupal\Core\Config\DatabaseStorage;
-use Drupal\Core\Config\FileStorage;
use Drupal\simpletest\WebTestBase;
/**
@@ -64,8 +62,8 @@ function testNoImport() {
function testDeleted() {
$name = 'config_test.system';
$dynamic_name = 'config_test.dynamic.default';
- $active_storage = new DatabaseStorage();
- $staging_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY)));
+ $storage = $this->container->get('config.storage.active');
+ $staging = $this->container->get('config.storage.staging');
// Verify the default configuration values exist.
$config = config($name);
@@ -77,15 +75,15 @@ function testDeleted() {
config_export();
// Delete the configuration objects from the staging directory.
- $staging_storage->delete($name);
- $staging_storage->delete($dynamic_name);
+ $staging->delete($name);
+ $staging->delete($dynamic_name);
// Import.
config_import();
// Verify the values have disappeared.
- $this->assertIdentical($active_storage->read($name), FALSE);
- $this->assertIdentical($active_storage->read($dynamic_name), FALSE);
+ $this->assertIdentical($storage->read($name), FALSE);
+ $this->assertIdentical($storage->read($dynamic_name), FALSE);
$config = config($name);
$this->assertIdentical($config->get('foo'), NULL);
@@ -99,6 +97,9 @@ function testDeleted() {
$this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
$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(config_sync_get_changes($staging, $storage));
}
/**
@@ -107,27 +108,34 @@ function testDeleted() {
function testNew() {
$name = 'config_test.new';
$dynamic_name = 'config_test.dynamic.new';
- $staging_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY)));
+ $storage = $this->container->get('config.storage.active');
+ $staging = $this->container->get('config.storage.staging');
// Export.
config_export();
// Verify the configuration to create does not exist yet.
- $this->assertIdentical($staging_storage->exists($name), FALSE, $name . ' not found.');
- $this->assertIdentical($staging_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
+ $this->assertIdentical($storage->exists($name), FALSE, $name . ' not found.');
+ $this->assertIdentical($storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
+
+ $this->assertIdentical($staging->exists($name), FALSE, $name . ' not found.');
+ $this->assertIdentical($staging->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
// Create new configuration objects in the staging directory.
$original_name_data = array(
'add_me' => 'new value',
);
- $staging_storage->write($name, $original_name_data);
+ $staging->write($name, $original_name_data);
$original_dynamic_data = array(
'id' => 'new',
'label' => 'New',
+ 'langcode' => 'und',
+ 'style' => '',
+ 'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651',
);
- $staging_storage->write($dynamic_name, $original_dynamic_data);
- $this->assertIdentical($staging_storage->exists($name), TRUE, $name . ' found.');
- $this->assertIdentical($staging_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+ $staging->write($dynamic_name, $original_dynamic_data);
+ $this->assertIdentical($staging->exists($name), TRUE, $name . ' found.');
+ $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
// Import.
config_import();
@@ -145,6 +153,9 @@ function testNew() {
$this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
+
+ // Verify that there is nothing more to import.
+ $this->assertFalse(config_sync_get_changes($staging, $storage));
}
/**
@@ -153,26 +164,28 @@ function testNew() {
function testUpdated() {
$name = 'config_test.system';
$dynamic_name = 'config_test.dynamic.default';
- $staging_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY)));
+ $storage = $this->container->get('config.storage.active');
+ $staging = $this->container->get('config.storage.staging');
// Export.
config_export();
// Verify that the configuration objects to import exist.
- $this->assertIdentical($staging_storage->exists($name), TRUE, $name . ' found.');
- $this->assertIdentical($staging_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+ $this->assertIdentical($storage->exists($name), TRUE, $name . ' found.');
+ $this->assertIdentical($storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+
+ $this->assertIdentical($staging->exists($name), TRUE, $name . ' found.');
+ $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
// Replace the file content of the existing configuration objects in the
// staging directory.
$original_name_data = array(
'foo' => 'beer',
);
- $staging_storage->write($name, $original_name_data);
- $original_dynamic_data = array(
- 'id' => 'default',
- 'label' => 'Updated',
- );
- $staging_storage->write($dynamic_name, $original_dynamic_data);
+ $staging->write($name, $original_name_data);
+ $original_dynamic_data = $staging->read($dynamic_name);
+ $original_dynamic_data['label'] = 'Updated';
+ $staging->write($dynamic_name, $original_dynamic_data);
// Verify the active store still returns the default values.
$config = config($name);
@@ -190,8 +203,8 @@ function testUpdated() {
$this->assertIdentical($config->get('label'), 'Updated');
// Verify that the original file content is still the same.
- $this->assertIdentical($staging_storage->read($name), $original_name_data);
- $this->assertIdentical($staging_storage->read($dynamic_name), $original_dynamic_data);
+ $this->assertIdentical($staging->read($name), $original_name_data);
+ $this->assertIdentical($staging->read($dynamic_name), $original_dynamic_data);
// Verify that appropriate module API hooks have been invoked.
$this->assertTrue(isset($GLOBALS['hook_config_test']['load']));
@@ -200,6 +213,9 @@ function testUpdated() {
$this->assertTrue(isset($GLOBALS['hook_config_test']['update']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
+
+ // Verify that there is nothing more to import.
+ $this->assertFalse(config_sync_get_changes($staging, $storage));
}
}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
new file mode 100644
index 0000000..8779734
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
@@ -0,0 +1,153 @@
+ 'Import/Export UI',
+ 'description' => 'Tests the user interface for importing/exporting configuration.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->web_user = $this->drupalCreateUser(array('synchronize configuration'));
+ $this->drupalLogin($this->web_user);
+ }
+
+ /**
+ * Tests exporting configuration.
+ */
+ function testExport() {
+ $name = 'config_test.system';
+ $dynamic_name = 'config_test.dynamic.default';
+
+ // Verify the default configuration values exist.
+ $config = config($name);
+ $this->assertIdentical($config->get('foo'), 'bar');
+ $config = config($dynamic_name);
+ $this->assertIdentical($config->get('id'), 'default');
+
+ // Verify that both appear as deleted by default.
+ $this->drupalGet('admin/config/development/sync/export');
+ $this->assertText($name);
+ $this->assertText($dynamic_name);
+
+ // Export and verify that both do not appear anymore.
+ $this->drupalPost(NULL, array(), t('Export'));
+ $this->assertUrl('admin/config/development/sync/export');
+ $this->assertNoText($name);
+ $this->assertNoText($dynamic_name);
+
+ // Verify that there are no further changes to export.
+ $this->assertText(t('There are no configuration changes.'));
+
+ // Verify that the import screen shows no changes either.
+ $this->drupalGet('admin/config/development/sync');
+ $this->assertText(t('There are no configuration changes.'));
+ }
+
+ /**
+ * Tests importing configuration.
+ */
+ function testImport() {
+ $name = 'config_test.new';
+ $dynamic_name = 'config_test.dynamic.new';
+ $storage = $this->container->get('config.storage.active');
+ $staging = $this->container->get('config.storage.staging');
+
+ // Verify the configuration to create does not exist yet.
+ $this->assertIdentical($storage->exists($name), FALSE, $name . ' not found.');
+ $this->assertIdentical($storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
+ $this->assertIdentical($staging->exists($name), FALSE, $name . ' not found.');
+ $this->assertIdentical($staging->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
+
+ // Verify that the import UI does not allow to import without exported
+ // configuration.
+ $this->drupalGet('admin/config/development/sync');
+ $this->assertText('There is no base configuration.');
+
+ // Verify that the Export link yields to the export UI page, and export.
+ $this->clickLink('Export');
+ $this->drupalPost(NULL, array(), t('Export'));
+
+ // Create new configuration objects.
+ $original_name_data = array(
+ 'add_me' => 'new value',
+ );
+ $staging->write($name, $original_name_data);
+ $original_dynamic_data = array(
+ 'id' => 'new',
+ 'label' => 'New',
+ 'langcode' => 'und',
+ 'style' => '',
+ 'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651',
+ );
+ $staging->write($dynamic_name, $original_dynamic_data);
+ $this->assertIdentical($staging->exists($name), TRUE, $name . ' found.');
+ $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+
+ // Verify that both appear as new.
+ $this->drupalGet('admin/config/development/sync');
+ $this->assertText($name);
+ $this->assertText($dynamic_name);
+
+ // Import and verify that both do not appear anymore.
+ $this->drupalPost(NULL, array(), t('Import'));
+ $this->assertUrl('admin/config/development/sync');
+ $this->assertNoText($name);
+ $this->assertNoText($dynamic_name);
+
+ // Verify that there are no further changes to import.
+ $this->assertText(t('There are no configuration changes.'));
+
+ // Verify that the export screen shows no changes either.
+ $this->drupalGet('admin/config/development/sync/export');
+ $this->assertText(t('There are no configuration changes.'));
+ }
+
+ /**
+ * Tests concurrent importing of configuration.
+ */
+ function testImportLock() {
+ $name = 'config_test.new';
+ $staging = $this->container->get('config.storage.staging');
+
+ // Write a configuration object to import.
+ $staging->write($name, array(
+ 'add_me' => 'new value',
+ ));
+
+ // Verify that there are configuration differences to import.
+ $this->drupalGet('admin/config/development/sync');
+ $this->assertNoText(t('There are no configuration changes.'));
+
+ // Acquire a fake-lock on the import mechanism.
+ $lock_name = 'config_import';
+ lock_acquire($lock_name);
+
+ // Attempt to import configuration and verify that an error message appears.
+ $this->drupalPost(NULL, array(), t('Import'));
+ $this->assertUrl('admin/config/development/sync');
+ $this->assertText(t('The import failed due to an error. Any errors have been logged.'));
+
+ // Release the lock, just to keep testing sane.
+ lock_release($lock_name);
+ }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
index d07b0b7..6b7a74f 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
@@ -32,6 +32,9 @@
function testCRUD() {
$name = 'config_test.storage';
+ // Checking whether a non-existing name exists returns FALSE.
+ $this->assertIdentical($this->storage->exists($name), FALSE);
+
// Reading a non-existing name returns FALSE.
$data = $this->storage->read($name);
$this->assertIdentical($data, FALSE);
@@ -51,9 +54,13 @@ function testCRUD() {
$data = array('foo' => 'bar');
$result = $this->storage->write($name, $data);
$this->assertIdentical($result, TRUE);
+
$raw_data = $this->read($name);
$this->assertIdentical($raw_data, $data);
+ // Checking whether an existing name exists returns TRUE.
+ $this->assertIdentical($this->storage->exists($name), TRUE);
+
// Writing the identical data again still returns TRUE.
$result = $this->storage->write($name, $data);
$this->assertIdentical($result, TRUE);
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
index 2e1599f..185921f 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
@@ -23,8 +23,34 @@ public static function getInfo() {
function setUp() {
parent::setUp();
+
+ $schema['config'] = array(
+ 'description' => 'Default active store for the configuration system.',
+ 'fields' => array(
+ 'name' => array(
+ 'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'data' => array(
+ 'description' => 'The raw data for this configuration entry.',
+ 'type' => 'blob',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ 'translatable' => TRUE,
+ ),
+ ),
+ 'primary key' => array('name'),
+ );
+ db_create_table('config', $schema['config']);
+
$this->storage = new DatabaseStorage();
$this->invalidStorage = new DatabaseStorage(array('connection' => 'invalid'));
+
+ // ::listAll() verifications require other configuration data to exist.
+ $this->storage->write('system.performance', array());
}
protected function read($name) {
diff --git a/core/modules/entity/lib/Drupal/entity/StorableBase.php b/core/modules/entity/lib/Drupal/entity/StorableBase.php
index 57bf352..c5d9867 100644
--- a/core/modules/entity/lib/Drupal/entity/StorableBase.php
+++ b/core/modules/entity/lib/Drupal/entity/StorableBase.php
@@ -45,7 +45,7 @@
*
* @var bool
*/
- public $isCurrentRevision = TRUE;
+ protected $isCurrentRevision = TRUE;
/**
* Constructs a new entity object.
@@ -280,7 +280,11 @@ public function getRevisionId() {
/**
* Implements Drupal\entity\StorableInterface::isCurrentRevision().
*/
- public function isCurrentRevision() {
- return $this->isCurrentRevision;
+ public function isCurrentRevision($new_value = NULL) {
+ $return = $this->isCurrentRevision;
+ if (isset($new_value)) {
+ $this->isCurrentRevision = (bool) $new_value;
+ }
+ return $return;
}
}
diff --git a/core/modules/entity/lib/Drupal/entity/StorableInterface.php b/core/modules/entity/lib/Drupal/entity/StorableInterface.php
index 23bc1ea..b784260 100644
--- a/core/modules/entity/lib/Drupal/entity/StorableInterface.php
+++ b/core/modules/entity/lib/Drupal/entity/StorableInterface.php
@@ -211,8 +211,13 @@ public function getRevisionId();
/**
* Checks if this entity is the current revision.
*
+ * @param bool $new_value
+ * (optional) A Boolean to (re)set the isCurrentRevision flag.
+ *
* @return bool
- * TRUE if the entity is the current revision, FALSE otherwise.
+ * TRUE if the entity is the current revision, FALSE otherwise. If
+ * $new_value was passed, the previous value is returned.
*/
- public function isCurrentRevision();
+ public function isCurrentRevision($new_value = NULL);
+
}
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php
index b96049e..99fd422 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php
@@ -7,12 +7,14 @@
namespace Drupal\search\Tests;
-use Drupal\simpletest\UnitTestBase;
+use Drupal\simpletest\WebTestBase;
/**
* Tests the search_excerpt() function.
*/
-class SearchExcerptTest extends UnitTestBase {
+class SearchExcerptTest extends WebTestBase {
+ public static $modules = array('search');
+
public static function getInfo() {
return array(
'name' => 'Search excerpt extraction',
@@ -21,11 +23,6 @@ public static function getInfo() {
);
}
- function setUp() {
- drupal_load('module', 'search');
- parent::setUp();
- }
-
/**
* Tests search_excerpt() with several simulated search keywords.
*
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 9654f9c..253d4df 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -726,6 +726,10 @@ protected function prepareEnvironment() {
$GLOBALS['config_directories'][$type] = 'simpletest/' . substr($this->databasePrefix, 10) . '/config_' . $type;
}
+ // Reset and create a new service container.
+ drupal_container(NULL, TRUE);
+ $this->container = drupal_container();
+
$this->configDirectories = array();
include_once DRUPAL_ROOT . '/core/includes/install.inc';
foreach ($GLOBALS['config_directories'] as $type => $path) {
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 486afba..dc8821f 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -724,6 +724,8 @@ function system_schema() {
);
$schema['cache_bootstrap'] = $schema['cache'];
$schema['cache_bootstrap']['description'] = 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.';
+ $schema['cache_config'] = $schema['cache'];
+ $schema['cache_config']['description'] = 'Cache table for configuration data.';
$schema['cache_form'] = $schema['cache'];
$schema['cache_form']['description'] = 'Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.';
$schema['cache_page'] = $schema['cache'];
@@ -733,28 +735,6 @@ function system_schema() {
$schema['cache_path'] = $schema['cache'];
$schema['cache_path']['description'] = 'Cache table for path alias lookup.';
- $schema['config'] = array(
- 'description' => 'Default active store for the configuration system.',
- 'fields' => array(
- 'name' => array(
- 'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).',
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => TRUE,
- 'default' => '',
- ),
- 'data' => array(
- 'description' => 'The raw data for this configuration entry.',
- 'type' => 'blob',
- 'not null' => TRUE,
- 'size' => 'big',
- 'translatable' => TRUE,
- ),
- ),
- 'primary key' => array('name'),
- );
-
-
$schema['date_format_type'] = array(
'description' => 'Stores configured date format types.',
'fields' => array(
@@ -1567,33 +1547,63 @@ function system_update_8002() {
}
/**
- * Adds {config} table for new Configuration system.
+ * Creates {cache_config} cache table for the new configuration system.
*/
function system_update_8003() {
- // @todo Temporary.
- if (db_table_exists('config')) {
- db_drop_table('config');
- }
- db_create_table('config', array(
- 'description' => 'Default active store for the configuration system.',
+ $schema['cache'] = array(
+ 'description' => 'Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.',
'fields' => array(
- 'name' => array(
- 'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).',
+ 'cid' => array(
+ 'description' => 'Primary Key: Unique cache ID.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'data' => array(
- 'description' => 'The raw data for this configuration entry.',
+ 'description' => 'A collection of data to cache.',
'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ ),
+ 'expire' => array(
+ 'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'created' => array(
+ 'description' => 'A Unix timestamp indicating when the cache entry was created.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'serialized' => array(
+ 'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
+ 'type' => 'int',
+ 'size' => 'small',
'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'tags' => array(
+ 'description' => 'Space-separated list of cache tags for this entry.',
+ 'type' => 'text',
'size' => 'big',
- 'translatable' => TRUE,
+ 'not null' => FALSE,
+ ),
+ 'checksum' => array(
+ 'description' => 'The tag invalidation sum when this entry was saved.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
),
),
- 'primary key' => array('name'),
- ));
+ 'indexes' => array(
+ 'expire' => array('expire'),
+ ),
+ 'primary key' => array('cid'),
+ );
+ db_create_table('cache_config', $schema['cache']);
}
/**
@@ -1673,7 +1683,7 @@ function system_update_8007() {
foreach ($tables as $table) {
// Assume we have a valid cache table if there is both 'cid' and 'data'
// columns.
- if (db_field_exists($table, 'cid') && db_field_exists($table, 'data')) {
+ if (db_field_exists($table, 'cid') && db_field_exists($table, 'data') && !db_field_exists($table, 'tags')) {
db_add_field($table, 'tags', array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index f78f123..3efffd0 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3506,7 +3506,7 @@ function system_cron() {
*/
function system_cache_flush() {
// Do NOT flush the 'form' cache bin to retain in-progress form submissions.
- return array('bootstrap', 'cache', 'page', 'path');
+ return array('bootstrap', 'config', 'cache', 'page', 'path');
}
/**
diff --git a/core/profiles/standard/standard.info b/core/profiles/standard/standard.info
index 8b8a33b..c5240dd 100644
--- a/core/profiles/standard/standard.info
+++ b/core/profiles/standard/standard.info
@@ -6,6 +6,7 @@ dependencies[] = node
dependencies[] = block
dependencies[] = color
dependencies[] = comment
+dependencies[] = config
dependencies[] = contextual
dependencies[] = help
dependencies[] = image