diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index fa1335b..4c28ced 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -288,8 +288,6 @@ const REGISTRY_WRITE_LOOKUP_CACHE = 2;
*/
const DRUPAL_PHP_FUNCTION_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
-require_once DRUPAL_ROOT . '/core/includes/config.inc';
-
/**
* Starts the timer with the specified name.
*
@@ -480,6 +478,25 @@ function find_conf_path($http_host, $script_name, $require_settings = TRUE) {
}
/**
+ * Returns the path of the configuration directory.
+ *
+ * @return string
+ * The configuration directory path.
+ */
+function config_get_config_directory() {
+ global $config_directory_name;
+
+ if ($test_prefix = drupal_valid_test_ua()) {
+ // @see Drupal\simpletest\WebTestBase::setUp()
+ $path = conf_path() . '/files/simpletest/' . substr($test_prefix, 10) . '/config';
+ }
+ else {
+ $path = conf_path() . '/files/' . $config_directory_name;
+ }
+ return $path;
+}
+
+/**
* Sets appropriate server variables needed for command line scripts to work.
*
* This function can be called by command line scripts before bootstrapping
@@ -2265,6 +2282,9 @@ function _drupal_bootstrap_configuration() {
// Activate the class loader.
drupal_classloader();
+
+ // Load the procedural configuration system helper functions.
+ require_once DRUPAL_ROOT . '/core/includes/config.inc';
}
/**
diff --git a/core/includes/config.inc b/core/includes/config.inc
index 8d0eea1..d1e7183 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -1,7 +1,10 @@
$file) {
- // Load config data into the active store and write it out to the
- // file system in the drupal config directory. Note the config name
- // needs to be the same as the file name WITHOUT the extension.
- $config_name = basename($file, '.' . FileStorage::getFileExtension());
-
- $database_storage = new DatabaseStorage($config_name);
- $file_storage = new FileStorage($config_name);
- $file_storage->setPath($module_config_dir);
- $database_storage->write($file_storage->read());
+ if (is_dir($module_config_dir)) {
+ $database_storage = new DatabaseStorage();
+ $module_file_storage = new FileStorage(array('directory' => $module_config_dir));
+
+ foreach ($module_file_storage->listAll() as $config_name) {
+ $data = $module_file_storage->read($config_name);
+ $database_storage->write($config_name, $data);
}
}
}
/**
- * @todo http://drupal.org/node/1552396 renames this into config_load_all().
+ * @todo Modules need a way to access the active store, whatever it is.
*/
function config_get_storage_names_with_prefix($prefix = '') {
- return DatabaseStorage::getNamesWithPrefix($prefix);
+ $storage = new DatabaseStorage();
+ return $storage->listAll($prefix);
}
/**
@@ -73,16 +52,210 @@ function config_get_storage_names_with_prefix($prefix = '') {
* 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.
- * @param $class
- * The class name of the config object to be returned. Defaults to
- * DrupalConfig.
*
- * @return
- * An instance of the class specified in the $class parameter.
+ * @return Drupal\Core\Config\Config
+ * A configuration object.
+ */
+function config($name) {
+ return drupal_container()->get('config.factory')->get($name)->load();
+}
+
+/**
+ * Returns a list of differences between configuration storages.
+ *
+ * @param Drupal\Core\Config\StorageInterface $source_storage
+ * The storage to synchronize configuration from.
+ * @param Drupal\Core\Config\StorageInterface $target_storage
+ * The storage to synchronize configuration to.
+ *
+ * @return array|bool
+ * An assocative array containing the differences between source and target
+ * storage, or FALSE if there are no differences.
+ */
+function config_sync_get_changes(StorageInterface $source_storage, StorageInterface $target_storage) {
+ $source_names = $source_storage->listAll();
+ $target_names = $target_storage->listAll();
+ $config_changes = array(
+ 'create' => array_diff($source_names, $target_names),
+ 'change' => array(),
+ 'delete' => array_diff($target_names, $source_names),
+ );
+ foreach (array_intersect($source_names, $target_names) as $name) {
+ $source_config_data = $source_storage->read($name);
+ $target_config_data = $target_storage->read($name);
+ if ($source_config_data != $target_config_data) {
+ $config_changes['change'][] = $name;
+ }
+ }
+
+ // Do not trigger subsequent synchronization operations if there are no
+ // changes in either category.
+ if (empty($config_changes['create']) && empty($config_changes['change']) && empty($config_changes['delete'])) {
+ return FALSE;
+ }
+ return $config_changes;
+}
+
+/**
+ * Imports configuration from FileStorage to DatabaseStorage.
+ */
+function config_import() {
+ $config_changes = config_import_get_changes();
+ if (empty($config_changes)) {
+ return;
+ }
+
+ if (!lock_acquire(__FUNCTION__)) {
+ // Another request is synchronizing configuration.
+ // Return a negative result for UI purposes. We do not make a difference
+ // between an actual synchronization error and a failed lock, because a
+ // concurrent request synchronizing configuration is an edge-case in the
+ // first place and would mean that more than one developer or site builder
+ // attempts to do it without coordinating with others.
+ return FALSE;
+ }
+
+ try {
+ $remaining_changes = config_import_invoke_sync_hooks($config_changes);
+ config_import_save_changes($remaining_changes);
+ // Flush all caches and reset static variables after a successful import.
+ drupal_flush_all_caches();
+ }
+ catch (ConfigException $e) {
+ watchdog_exception('config_import', $e);
+ lock_release(__FUNCTION__);
+ return FALSE;
+ }
+ lock_release(__FUNCTION__);
+ return TRUE;
+}
+
+/**
+ * Returns a list of differences between FileStorage and DatabaseStorage.
+ *
+ * @see config_sync_get_changes()
+ */
+function config_import_get_changes() {
+ // @todo Leverage DI + config.storage.info.
+ $source_storage = new FileStorage();
+ $target_storage = new DatabaseStorage();
+
+ return config_sync_get_changes($source_storage, $target_storage);
+}
+
+/**
+ * Writes an array of config file changes to the active store.
+ *
+ * @param array $config_changes
+ * An array of changes to be written.
+ */
+function config_import_save_changes(array $config_changes) {
+ // @todo Leverage DI + config.storage.info.
+ $source_storage = new FileStorage();
+ $target_storage = new DatabaseStorage();
+ foreach (array('delete', 'create', 'change') as $op) {
+ foreach ($config_changes[$op] as $name) {
+ if ($op == 'delete') {
+ $target_storage->delete($name);
+ }
+ else {
+ $data = $source_storage->read($name);
+ $target_storage->write($name, $data);
+ }
+ }
+ }
+}
+
+/**
+ * Invokes hook_config_import() implementations for configuration changes.
*
- * @todo Replace this with an appropriate factory / ability to inject in
- * alternate storage engines..
+ * @param array $config_changes
+ * An array of changes to be loaded.
*/
-function config($name, $class = 'Drupal\Core\Config\DrupalConfig') {
- return new $class(new DatabaseStorage($name));
+function config_import_invoke_sync_hooks(array $config_changes) {
+ // @todo Leverage DI + config.storage.info.
+ $source_storage = new FileStorage();
+ $target_storage = new DatabaseStorage();
+ $storage_dispatcher = drupal_container()->get('config.storage.dispatcher');
+
+ // 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
+ // handle dependencies correctly.
+ foreach (array('delete', 'create', 'change') as $op) {
+ foreach ($config_changes[$op] as $key => $name) {
+ // Extract owner from configuration object name.
+ $module = strtok($name, '.');
+ // Check whether the module implements hook_config_import() and ask it to
+ // handle the configuration change.
+ $handled_by_module = FALSE;
+ if (module_hook($module, 'config_import')) {
+ $old_config = new Config($storage_dispatcher);
+ $old_config
+ ->setName($name)
+ ->load();
+
+ $data = $source_storage->read($name);
+ $new_config = new Config($storage_dispatcher);
+ $new_config
+ ->setName($name)
+ ->setData($data);
+
+ $handled_by_module = module_invoke($module, 'config_import', $op, $name, $new_config, $old_config);
+ }
+ if (!empty($handled_by_module)) {
+ unset($config_changes[$op][$key]);
+ }
+ }
+ }
+ return $config_changes;
}
+
+/**
+ * Exports configuration from DatabaseStorage to FileStorage.
+ */
+function config_export() {
+ $config_changes = config_export_get_changes();
+ if (empty($config_changes)) {
+ return;
+ }
+ config_export_save_changes($config_changes);
+ return TRUE;
+}
+
+/**
+ * Returns a list of differences between DatabaseStorage and FileStorage.
+ *
+ * @see config_sync_get_changes()
+ */
+function config_export_get_changes() {
+ // @todo Leverage DI + config.storage.info.
+ $source_storage = new DatabaseStorage();
+ $target_storage = new FileStorage();
+
+ return config_sync_get_changes($source_storage, $target_storage);
+}
+
+/**
+ * Writes an array of configuration changes to FileStorage.
+ *
+ * @param array $config_changes
+ * An array of changes to be written.
+ */
+function config_export_save_changes(array $config_changes) {
+ // @todo Leverage DI + config.storage.info.
+ $source_storage = new DatabaseStorage();
+ $target_storage = new FileStorage();
+ foreach (array('delete', 'create', 'change') as $op) {
+ foreach ($config_changes[$op] as $name) {
+ if ($op == 'delete') {
+ $target_storage->delete($name);
+ }
+ else {
+ $data = $source_storage->read($name);
+ $target_storage->write($name, $data);
+ }
+ }
+ }
+}
+
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 96e84b3..cd67257 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -1,7 +1,7 @@
delete();
- }
+ // 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);
diff --git a/core/includes/module.inc b/core/includes/module.inc
index a71cb18..e15cd95 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -487,7 +487,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
$versions = drupal_get_schema_versions($module);
$version = $versions ? max($versions) : SCHEMA_INSTALLED;
- // Copy any default configuration data to the system config directory/
+ // Install default configuration of the module.
config_install_default_config($module);
// If the module has no current updates, but has some that were
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 49df15b..ab373cd 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -10,6 +10,7 @@
use Drupal\Component\Graph\Graph;
use Drupal\Core\Config\FileStorage;
+use Drupal\Core\Config\ConfigException;
/**
* Minimum schema version of Drupal 7 required for upgrade to Drupal 8.
@@ -942,11 +943,11 @@ function update_variables_to_config($config_name, array $variable_map) {
$module = strtok($config_name, '.');
// Load and set default configuration values.
- // Throws a FileStorageReadException if there is no default configuration
- // file, which is required to exist.
- $file = new FileStorage($config_name);
- $file->setPath(drupal_get_path('module', $module) . '/config');
- $default_data = $file->read();
+ $file = new FileStorage(array('directory' => drupal_get_path('module', $module) . '/config'));
+ if (!$file->exists($config_name)) {
+ throw new ConfigException("Default configuration file $config_name for $module extension not found but is required to exist.");
+ }
+ $default_data = $file->read($config_name);
// Merge any possibly existing original data into default values.
// Only relevant when being called repetitively on the same config object.
diff --git a/core/lib/Drupal/Core/Config/DrupalConfig.php b/core/lib/Drupal/Core/Config/Config.php
similarity index 67%
rename from core/lib/Drupal/Core/Config/DrupalConfig.php
rename to core/lib/Drupal/Core/Config/Config.php
index f5a9220..0748b76 100644
--- a/core/lib/Drupal/Core/Config/DrupalConfig.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -1,21 +1,23 @@
storage = $storage;
- $this->read();
+ public function __construct(StorageDispatcher $storageDispatcher) {
+ $this->storageDispatcher = $storageDispatcher;
}
/**
- * Reads config data from the active store into our object.
+ * Returns the name of this configuration object.
*/
- public function read() {
- $data = $this->storage->read();
- $this->setData($data !== FALSE ? $data : array());
- return $this;
+ public function getName() {
+ return $this->name;
}
/**
- * Checks whether a particular value is overridden.
- *
- * @param $key
- * @todo
- *
- * @return
- * @todo
+ * Sets the name of this configuration object.
*/
- public function isOverridden($key) {
- return isset($this->_overrides[$key]);
+ public function setName($name) {
+ $this->name = $name;
+ return $this;
}
/**
@@ -89,7 +89,7 @@ class DrupalConfig {
public function get($key = '') {
global $conf;
- $name = $this->storage->getName();
+ $name = $this->getName();
if (isset($conf[$name])) {
$merged_data = drupal_array_merge_deep($this->data, $conf[$name]);
}
@@ -201,13 +201,48 @@ class DrupalConfig {
else {
drupal_array_unset_nested_value($this->data, $parts);
}
+ return $this;
+ }
+
+ /**
+ * Loads configuration data into this object.
+ */
+ public function load() {
+ $this->setData(array());
+ $data = $this->storageDispatcher->selectStorage('read', $this->name)->read($this->name);
+ if ($data !== FALSE) {
+ $this->setData($data);
+ }
+ return $this;
}
/**
* Saves the configuration object.
*/
public function save() {
- $this->storage->write($this->data);
+ $this->sortByKey($this->data);
+ $this->storageDispatcher->selectStorage('write', $this->name)->write($this->name, $this->data);
+ return $this;
+ }
+
+ /**
+ * Sorts all keys in configuration data.
+ *
+ * Ensures that re-inserted keys appear in the same location as before, in
+ * order to ensure an identical order regardless of storage controller.
+ * A consistent order is important for any storage that allows any kind of
+ * diff operation.
+ *
+ * @param array $data
+ * An associative array to sort recursively by key name.
+ */
+ public function sortByKey(array &$data) {
+ ksort($data);
+ foreach ($data as &$value) {
+ if (is_array($value)) {
+ $this->sortByKey($value);
+ }
+ }
}
/**
@@ -215,6 +250,7 @@ class DrupalConfig {
*/
public function delete() {
$this->data = array();
- $this->storage->delete();
+ $this->storageDispatcher->selectStorage('write', $this->name)->delete($this->name);
+ return $this;
}
}
diff --git a/core/lib/Drupal/Core/Config/ConfigException.php b/core/lib/Drupal/Core/Config/ConfigException.php
index c60a449..5e7daed 100644
--- a/core/lib/Drupal/Core/Config/ConfigException.php
+++ b/core/lib/Drupal/Core/Config/ConfigException.php
@@ -1,8 +1,15 @@
configClass = $config_class;
+ $this->storageDispatcher = $storage_dispatcher;
+ }
+
+ /**
+ * Returns a configuration object for a given name.
+ *
+ * @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) {
+ // @todo Caching the instantiated objects per name might cut off a fair
+ // amount of CPU time and memory. Only the data within the configuration
+ // object changes, so the additional cost of instantiating duplicate
+ // objects could possibly be avoided. It is not uncommon for a
+ // configuration object to be retrieved many times during a single
+ // request; e.g., 'system.performance' alone is retrieved around 10-20
+ // times within a single page request. Sub-requests via HttpKernel will
+ // most likely only increase these counts.
+ // @todo Benchmarks were performed with a script that essentially retained
+ // all instantiated configuration objects in memory until script execution
+ // ended. A variant of that script called config() within a helper
+ // function only, which inherently meant that PHP destroyed all
+ // configuration objects after leaving the function. Consequently,
+ // benchmark results looked entirely different. Profiling should probably
+ // redone under more realistic conditions; e.g., actual HTTP requests.
+ // @todo The decrease of CPU time is interesting, since that means that
+ // ContainerBuilder involves plenty of function calls (which are known to
+ // be slow in PHP).
+ $config = new $this->configClass($this->storageDispatcher);
+ return $config->setName($name);
+ }
+}
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index c736245..1dd8507 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -1,26 +1,65 @@
'default',
+ 'target' => 'default',
+ );
+ $this->options = $options;
+ }
+
+ /**
+ * Returns the database connection to use.
+ */
+ protected function getConnection() {
+ return Database::getConnection($this->options['target'], $this->options['connection']);
+ }
/**
- * Implements StorageInterface::read().
+ * Implements Drupal\Core\Config\StorageInterface::read().
+ *
+ * @throws PDOException
+ * @throws Drupal\Core\Database\DatabaseExceptionWrapper
+ * Only thrown in case $this->options['throw_exception'] is TRUE.
*/
- public function read() {
+ public function read($name) {
+ $data = array();
// There are situations, like in the installer, where we may attempt a
// 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.
- $data = array();
+ // @todo Remove this and use appropriate StorageDispatcher configuration in
+ // the installer instead.
try {
- $raw = db_query('SELECT data FROM {config} WHERE name = :name', array(':name' => $this->name))->fetchField();
+ $raw = $this->getConnection()->query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name), $this->options)->fetchField();
if ($raw !== FALSE) {
$data = $this->decode($raw);
}
@@ -31,43 +70,63 @@ class DatabaseStorage extends StorageBase {
}
/**
- * Implements StorageInterface::writeToActive().
+ * Implements Drupal\Core\Config\StorageInterface::write().
+ *
+ * @throws PDOException
+ *
+ * @todo Ignore slave targets for data manipulation operations.
*/
- public function writeToActive($data) {
+ public function write($name, array $data) {
$data = $this->encode($data);
- return db_merge('config')
- ->key(array('name' => $this->name))
+ $options = array('return' => Database::RETURN_AFFECTED) + $this->options;
+ return (bool) $this->getConnection()->merge('config', $options)
+ ->key(array('name' => $name))
->fields(array('data' => $data))
->execute();
}
/**
- * @todo
+ * Implements Drupal\Core\Config\StorageInterface::delete().
+ *
+ * @throws PDOException
+ *
+ * @todo Ignore slave targets for data manipulation operations.
*/
- public function deleteFromActive() {
- db_delete('config')
- ->condition('name', $this->name)
+ public function delete($name) {
+ $options = array('return' => Database::RETURN_AFFECTED) + $this->options;
+ return (bool) $this->getConnection()->delete('config', $options)
+ ->condition('name', $name)
->execute();
}
/**
- * Implements StorageInterface::encode().
+ * Implements Drupal\Core\Config\StorageInterface::encode().
*/
public static function encode($data) {
return serialize($data);
}
/**
- * Implements StorageInterface::decode().
+ * Implements Drupal\Core\Config\StorageInterface::decode().
+ *
+ * @throws ErrorException
+ * unserialize() triggers E_NOTICE if the string cannot be unserialized.
*/
public static function decode($raw) {
- return unserialize($raw);
+ $data = @unserialize($raw);
+ return $data !== FALSE ? $data : array();
}
/**
- * Implements StorageInterface::getNamesWithPrefix().
+ * Implements Drupal\Core\Config\StorageInterface::listAll().
+ *
+ * @throws PDOException
+ * @throws Drupal\Core\Database\DatabaseExceptionWrapper
+ * Only thrown in case $this->options['throw_exception'] is TRUE.
*/
- static public function getNamesWithPrefix($prefix = '') {
- return db_query('SELECT name FROM {config} WHERE name LIKE :name', array(':name' => db_like($prefix) . '%'))->fetchCol();
+ public function listAll($prefix = '') {
+ return $this->getConnection()->query('SELECT name FROM {config} WHERE name LIKE :name', array(
+ ':name' => db_like($prefix) . '%',
+ ), $this->options)->fetchCol();
}
}
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 2a6d448..7827dc0 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -1,59 +1,36 @@
name = $name;
- }
-
- /**
- * Returns the path containing the configuration file.
- *
- * @return string
- * The relative path to the configuration object.
+ * Implements Drupal\Core\Config\StorageInterface::__construct().
*/
- public function getPath() {
- // If the path has not been set yet, retrieve and assign the default path
- // for configuration files.
- if (!isset($this->path)) {
- $this->setPath(config_get_config_directory());
+ public function __construct(array $options = array()) {
+ if (!isset($options['directory'])) {
+ $options['directory'] = config_get_config_directory();
}
- return $this->path;
- }
-
- /**
- * Sets the path containing the configuration file.
- */
- public function setPath($directory) {
- $this->path = $directory;
- return $this;
+ $this->options = $options;
}
/**
@@ -62,8 +39,8 @@ class FileStorage {
* @return string
* The path to the configuration file.
*/
- public function getFilePath() {
- return $this->getPath() . '/' . $this->getName() . '.' . self::getFileExtension();
+ public function getFilePath($name) {
+ return $this->options['directory'] . '/' . $name . '.' . self::getFileExtension();
}
/**
@@ -82,50 +59,65 @@ class FileStorage {
* @return bool
* TRUE if the configuration file exists, FALSE otherwise.
*/
- protected function exists() {
- return file_exists($this->getFilePath());
+ public function exists($name) {
+ return file_exists($this->getFilePath($name));
}
/**
- * Implements StorageInterface::write().
+ * Implements Drupal\Core\Config\StorageInterface::read().
*
- * @throws FileStorageException
+ * @throws Symfony\Component\Yaml\Exception\ParseException
*/
- public function write($data) {
- $data = $this->encode($data);
- if (!file_put_contents($this->getFilePath(), $data)) {
- throw new FileStorageException('Failed to write configuration file: ' . $this->getFilePath());
+ public function read($name) {
+ if (!$this->exists($name)) {
+ return array();
+ }
+ $data = file_get_contents($this->getFilePath($name));
+ // @todo Yaml throws a ParseException on invalid data. Is it expected to be
+ // caught or not?
+ $data = $this->decode($data);
+ if ($data === FALSE) {
+ return array();
}
+ // A simple string is valid YAML for any reason.
+ if (!is_array($data)) {
+ return array();
+ }
+ return $data;
}
/**
- * Implements StorageInterface::read().
+ * Implements Drupal\Core\Config\StorageInterface::write().
*
- * @throws FileStorageReadException
+ * @throws Symfony\Component\Yaml\Exception\DumpException
+ * @throws Drupal\Core\Config\StorageException
*/
- public function read() {
- if (!$this->exists()) {
- throw new FileStorageReadException("Configuration file '$this->name' does not exist.");
- }
-
- $data = file_get_contents($this->getFilePath());
- $data = $this->decode($data);
- if ($data === FALSE) {
- throw new FileStorageReadException("Failed to decode configuration file '$this->name'.");
+ public function write($name, array $data) {
+ $data = $this->encode($data);
+ $status = @file_put_contents($this->getFilePath($name), $data);
+ if ($status === FALSE) {
+ throw new StorageException('Failed to write configuration file: ' . $this->getFilePath($name));
}
- return $data;
+ return TRUE;
}
/**
- * Deletes a configuration file.
+ * Implements Drupal\Core\Config\StorageInterface::delete().
*/
- public function delete() {
- // Needs error handling and etc.
- @drupal_unlink($this->getFilePath());
+ public function delete($name) {
+ if (!$this->exists($name)) {
+ if (!file_exists($this->options['directory'])) {
+ throw new StorageException($this->options['directory'] . '/ not found.');
+ }
+ return FALSE;
+ }
+ return drupal_unlink($this->getFilePath($name));
}
/**
- * Implements StorageInterface::encode().
+ * Implements Drupal\Core\Config\StorageInterface::encode().
+ *
+ * @throws Symfony\Component\Yaml\Exception\DumpException
*/
public static function encode($data) {
// The level where you switch to inline YAML is set to PHP_INT_MAX to ensure
@@ -134,7 +126,9 @@ class FileStorage {
}
/**
- * Implements StorageInterface::decode().
+ * Implements Drupal\Core\Config\StorageInterface::decode().
+ *
+ * @throws Symfony\Component\Yaml\Exception\ParseException
*/
public static function decode($raw) {
if (empty($raw)) {
@@ -144,28 +138,18 @@ class FileStorage {
}
/**
- * Implements StorageInterface::getName().
- */
- public function getName() {
- return $this->name;
- }
-
- /**
- * Implements StorageInterface::setName().
+ * Implements Drupal\Core\Config\StorageInterface::listAll().
*/
- public function setName($name) {
- $this->name = $name;
- }
-
- /**
- * Implements StorageInterface::getNamesWithPrefix().
- */
- public static function getNamesWithPrefix($prefix = '') {
- // @todo Use $this->getPath() to allow for contextual search of files in
- // custom paths.
- $files = glob(config_get_config_directory() . '/' . $prefix . '*.' . FileStorage::getFileExtension());
- $clean_name = function ($value) {
- return basename($value, '.' . FileStorage::getFileExtension());
+ public function listAll($prefix = '') {
+ // glob() silently ignores the error of a non-existing search directory,
+ // even with the GLOB_ERR flag.
+ if (!file_exists($this->options['directory'])) {
+ throw new StorageException($this->options['directory'] . '/ not found.');
+ }
+ $extension = '.' . self::getFileExtension();
+ $files = glob($this->options['directory'] . '/' . $prefix . '*' . $extension);
+ $clean_name = function ($value) use ($extension) {
+ return basename($value, $extension);
};
return array_map($clean_name, $files);
}
diff --git a/core/lib/Drupal/Core/Config/FileStorageException.php b/core/lib/Drupal/Core/Config/FileStorageException.php
deleted file mode 100644
index bf3ae5f..0000000
--- a/core/lib/Drupal/Core/Config/FileStorageException.php
+++ /dev/null
@@ -1,10 +0,0 @@
-name = $name;
- }
-
- /**
- * Instantiates a new file storage object or returns the existing one.
- *
- * @return Drupal\Core\Config\FileStorage
- * The file object for this configuration object.
- */
- protected function fileStorage() {
- if (!isset($this->fileStorage)) {
- $this->fileStorage = new FileStorage($this->name);
- }
- return $this->fileStorage;
- }
-
- /**
- * Implements StorageInterface::copyToFile().
- */
- public function copyToFile() {
- return $this->writeToFile($this->read());
- }
-
- /**
- * Implements StorageInterface::deleteFile().
- */
- public function deleteFile() {
- return $this->fileStorage()->delete();
- }
-
- /**
- * Implements StorageInterface::copyFromFile().
- */
- public function copyFromFile() {
- return $this->writeToActive($this->readFromFile());
- }
-
- /**
- * @todo
- *
- * @return
- * @todo
- */
- public function readFromFile() {
- return $this->fileStorage()->read($this->name);
- }
-
- /**
- * Implements StorageInterface::isOutOfSync().
- */
- public function isOutOfSync() {
- return $this->read() !== $this->readFromFile();
- }
-
- /**
- * Implements StorageInterface::write().
- */
- public function write($data) {
- $this->writeToActive($data);
- $this->writeToFile($data);
- }
-
- /**
- * Implements StorageInterface::writeToFile().
- */
- public function writeToFile($data) {
- return $this->fileStorage()->write($data);
- }
-
- /**
- * Implements StorageInterface::delete().
- */
- public function delete() {
- $this->deleteFromActive();
- $this->deleteFile();
- }
-
- /**
- * Implements StorageInterface::getName().
- */
- public function getName() {
- return $this->name;
- }
-
- /**
- * Implements StorageInterface::setName().
- */
- public function setName($name) {
- $this->name = $name;
- }
-}
diff --git a/core/lib/Drupal/Core/Config/StorageDispatcher.php b/core/lib/Drupal/Core/Config/StorageDispatcher.php
new file mode 100644
index 0000000..ebf7049
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/StorageDispatcher.php
@@ -0,0 +1,105 @@
+ array(
+ * 'target' => 'default',
+ * 'read' => TRUE,
+ * 'write' => TRUE,
+ * ),
+ * 'Drupal\Core\Config\FileStorage' => array(
+ * 'directory' => 'sites/default/files/config',
+ * 'read' => TRUE,
+ * 'write' => FALSE,
+ * ),
+ * )
+ * @endcode
+ */
+ public function __construct(array $storage_info) {
+ $this->storageInfo = $storage_info;
+ }
+
+ /**
+ * Returns a storage controller to use for a given operation.
+ *
+ * Handles the core functionality of the storage dispatcher by determining
+ * which storage can handle a particular storage access operation and
+ * configuration object.
+ *
+ * @param string $access_operation
+ * The operation access level; either 'read' or 'write'. Use 'write' both
+ * for saving and deleting configuration.
+ * @param string $name
+ * The name of the configuration object that is operated on.
+ *
+ * @return Drupal\Core\Config\StorageInterface
+ * The storage controller instance that can handle the requested operation.
+ *
+ * @throws Drupal\Core\Config\ConfigException
+ *
+ * @todo Allow write operations to write to multiple storages.
+ */
+ public function selectStorage($access_operation, $name) {
+ // Determine the appropriate storage controller to use.
+ // Take the first defined storage that allows $op.
+ foreach ($this->storageInfo as $class => $storage_config) {
+ if (!empty($storage_config[$access_operation])) {
+ $storage_class = $class;
+ break;
+ }
+ }
+ if (!isset($storage_class)) {
+ throw new ConfigException("Failed to find storage controller that allows $access_operation access for $name.");
+ }
+
+ // Instantiate a new storage controller object, if there is none yet.
+ if (!isset($this->storageInstances[$storage_class])) {
+ $this->storageInstances[$storage_class] = new $storage_class($this->storageInfo[$storage_class]);
+ }
+ return $this->storageInstances[$storage_class];
+ }
+}
diff --git a/core/lib/Drupal/Core/Config/StorageException.php b/core/lib/Drupal/Core/Config/StorageException.php
new file mode 100644
index 0000000..b1e99a1
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/StorageException.php
@@ -0,0 +1,13 @@
+register(LANGUAGE_TYPE_CONTENT, 'Drupal\\Core\\Language\\Language');
+
+ // Register configuration storage dispatcher.
+ $this->setParameter('config.storage.info', array(
+ 'Drupal\Core\Config\DatabaseStorage' => array(
+ 'connection' => 'default',
+ 'target' => 'default',
+ 'read' => TRUE,
+ 'write' => TRUE,
+ ),
+ 'Drupal\Core\Config\FileStorage' => array(
+ 'directory' => config_get_config_directory(),
+ 'read' => TRUE,
+ 'write' => FALSE,
+ ),
+ ));
+ $this->register('config.storage.dispatcher', 'Drupal\Core\Config\StorageDispatcher')
+ ->addArgument('%config.storage.info%');
+
+ // Register configuration object factory.
+ $this->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
+ ->addArgument('Drupal\Core\Config\Config')
+ ->addArgument(new Reference('config.storage.dispatcher'));
}
}
diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
new file mode 100644
index 0000000..6f77bf2
--- /dev/null
+++ b/core/modules/config/config.admin.inc
@@ -0,0 +1,62 @@
+ 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['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');
+ }
+}
+
diff --git a/core/modules/config/config.api.php b/core/modules/config/config.api.php
new file mode 100644
index 0000000..8f95d32
--- /dev/null
+++ b/core/modules/config/config.api.php
@@ -0,0 +1,56 @@
+get();
+ return image_style_delete($style);
+ }
+ if ($op == 'create') {
+ $style = $new_config->get();
+ return image_style_save($style);
+ }
+ if ($op == 'change') {
+ $style = $new_config->get();
+ return image_style_save($style);
+ }
+}
+
diff --git a/core/modules/config/config.module b/core/modules/config/config.module
index b3d9bbc..3d4fcfe 100644
--- a/core/modules/config/config.module
+++ b/core/modules/config/config.module
@@ -1 +1,33 @@
t('Import configuration'),
+ 'restrict access' => TRUE,
+ );
+ return $permissions;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function config_menu() {
+ $items['admin/config/development/import'] = array(
+ 'title' => 'Import configuration',
+ 'description' => 'Import and synchronize configuration changes.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('config_admin_import_form'),
+ 'access arguments' => array('import configuration'),
+ 'file' => 'config.admin.inc',
+ );
+ return $items;
+}
+
diff --git a/core/modules/config/config_test/config/config_test.delete.yml b/core/modules/config/config_test/config/config_test.delete.yml
new file mode 100644
index 0000000..b8ccb67
--- /dev/null
+++ b/core/modules/config/config_test/config/config_test.delete.yml
@@ -0,0 +1 @@
+delete_me: bar
diff --git a/core/modules/config/config_test/config/config_test.dynamic.default.yml b/core/modules/config/config_test/config/config_test.dynamic.default.yml
new file mode 100644
index 0000000..d285439
--- /dev/null
+++ b/core/modules/config/config_test/config/config_test.dynamic.default.yml
@@ -0,0 +1,2 @@
+id: default
+name: Default
diff --git a/core/modules/config/config_test/config/config_test.system.yml b/core/modules/config/config_test/config/config_test.system.yml
new file mode 100644
index 0000000..20e9ff3
--- /dev/null
+++ b/core/modules/config/config_test/config/config_test.system.yml
@@ -0,0 +1 @@
+foo: bar
diff --git a/core/modules/config/config_test/config_test.info b/core/modules/config/config_test/config_test.info
new file mode 100644
index 0000000..8735450
--- /dev/null
+++ b/core/modules/config/config_test/config_test.info
@@ -0,0 +1,6 @@
+name = Configuration test module
+package = Core
+version = VERSION
+core = 8.x
+dependencies[] = config
+hidden = TRUE
diff --git a/core/modules/config/config_test/config_test.module b/core/modules/config/config_test/config_test.module
new file mode 100644
index 0000000..7572224
--- /dev/null
+++ b/core/modules/config/config_test/config_test.module
@@ -0,0 +1,240 @@
+delete();
+ }
+ if ($op == 'create') {
+ $config_test = new ConfigTest($new_config);
+ $config_test->save();
+ }
+ if ($op == 'change') {
+ $config_test = new ConfigTest($new_config);
+ $config_test->setOriginal($old_config);
+ $config_test->save();
+ }
+ return TRUE;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function config_test_menu() {
+ $items['admin/structure/config_test'] = array(
+ 'title' => 'Test configuration',
+ 'page callback' => 'config_test_list_page',
+ 'access callback' => TRUE,
+ );
+ $items['admin/structure/config_test/add'] = array(
+ 'title' => 'Add test configuration',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('config_test_form'),
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_ACTION,
+ );
+ $items['admin/structure/config_test/manage/%config_test'] = array(
+ 'title' => 'Edit test configuration',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('config_test_form', 4),
+ 'access callback' => TRUE,
+ );
+ $items['admin/structure/config_test/manage/%config_test/edit'] = array(
+ 'title' => 'Edit',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/structure/config_test/manage/%config_test/delete'] = array(
+ 'title' => 'Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('config_test_delete_form', 4),
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_TASK,
+ );
+ return $items;
+}
+
+/**
+ * Loads a ConfigTest object.
+ *
+ * @param string $id
+ * The ID of the ConfigTest object to load.
+ */
+function config_test_load($id) {
+ $config = config('config_test.dynamic.' . $id);
+ // @todo Requires a more reliable + generic method to check for whether the
+ // configuration object exists.
+ if ($config->get('id') === NULL) {
+ return FALSE;
+ }
+ return new ConfigTest($config);
+}
+
+/**
+ * Saves a ConfigTest object.
+ *
+ * @param Drupal\config_test\ConfigTest $config_test
+ * The ConfigTest object to save.
+ */
+function config_test_save(ConfigTest $config_test) {
+ return $config_test->save();
+}
+
+/**
+ * Deletes a ConfigTest object.
+ *
+ * @param string $id
+ * The ID of the ConfigTest object to delete.
+ */
+function config_test_delete($id) {
+ $config = config('config_test.dynamic.' . $id);
+ $config_test = new ConfigTest($config);
+ return $config_test->delete();
+}
+
+/**
+ * Page callback; Lists available ConfigTest objects.
+ */
+function config_test_list_page() {
+ $config_names = config_get_storage_names_with_prefix('config_test.dynamic.');
+ $rows = array();
+ foreach ($config_names as $config_name) {
+ $config_test = new ConfigTest(config($config_name));
+ $row = array();
+ $row['name']['data'] = array(
+ '#type' => 'link',
+ '#title' => $config_test->getLabel(),
+ '#href' => $config_test->getUri(),
+ );
+ $row['delete']['data'] = array(
+ '#type' => 'link',
+ '#title' => t('Delete'),
+ '#href' => $config_test->getUri() . '/delete',
+ );
+ $rows[] = $row;
+ }
+ $build = array(
+ '#theme' => 'table',
+ '#header' => array('Name', 'Operations'),
+ '#rows' => $rows,
+ '#empty' => format_string('No test configuration defined. Add some', array(
+ '@add-url' => url('admin/structure/config_test/add'),
+ )),
+ );
+ return $build;
+}
+
+/**
+ * Form constructor to add or edit a ConfigTest object.
+ *
+ * @param Drupal\config_test\ConfigTest $config_test
+ * (optional) An existing ConfigTest object to edit. If omitted, the form
+ * creates a new ConfigTest.
+ */
+function config_test_form($form, &$form_state, ConfigTest $config_test = NULL) {
+ if (!isset($config_test)) {
+ $config_test = new ConfigTest(config(NULL));
+ }
+ $form_state['config_test'] = $config_test;
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => 'Label',
+ '#default_value' => $config_test->getLabel(),
+ '#required' => TRUE,
+ );
+ $form['id'] = array(
+ '#type' => 'machine_name',
+ '#default_value' => $config_test->getId(),
+ '#required' => TRUE,
+ '#machine_name' => array(
+ 'exists' => 'config_test_load',
+ ),
+ );
+ $form['style'] = array(
+ '#type' => 'select',
+ '#title' => 'Image style',
+ '#options' => array(),
+ '#default_value' => $config_test->get('style'),
+ '#access' => FALSE,
+ );
+ if (module_exists('image')) {
+ $form['style']['#access'] = TRUE;
+ $form['style']['#options'] = image_style_options();
+ }
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Save');
+
+ return $form;
+}
+
+/**
+ * Form submission handler for config_test_form().
+ */
+function config_test_form_submit($form, &$form_state) {
+ form_state_values_clean($form_state);
+
+ $config_test = $form_state['config_test'];
+
+ foreach ($form_state['values'] as $key => $value) {
+ $config_test->set($key, $value);
+ }
+ $config_test->save();
+
+ if (!empty($config_test->original)) {
+ drupal_set_message(format_string('%label configuration has been updated.', array('%label' => $config_test->getLabel())));
+ }
+ else {
+ drupal_set_message(format_string('%label configuration has been created.', array('%label' => $config_test->getLabel())));
+ }
+
+ $form_state['redirect'] = 'admin/structure/config_test';
+}
+
+/**
+ * Form constructor to delete a ConfigTest object.
+ *
+ * @param Drupal\config_test\ConfigTest $config_test
+ * The ConfigTest object to delete.
+ */
+function config_test_delete_form($form, &$form_state, ConfigTest $config_test) {
+ $form_state['config_test'] = $config_test;
+
+ $form['id'] = array('#type' => 'value', '#value' => $config_test->getId());
+ return confirm_form($form,
+ format_string('Are you sure you want to delete %label', array('%label' => $config_test->getLabel())),
+ 'admin/structure/config_test',
+ NULL,
+ 'Delete'
+ );
+}
+
+/**
+ * Form submission handler for config_test_delete_form().
+ */
+function config_test_delete_form_submit($form, &$form_state) {
+ $form_state['config_test']->delete();
+ $form_state['redirect'] = 'admin/structure/config_test';
+}
diff --git a/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php b/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php
new file mode 100644
index 0000000..2bb25c7
--- /dev/null
+++ b/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php
@@ -0,0 +1,178 @@
+load($config);
+ }
+
+ /**
+ * Implements Drupal\Core\Config\ConfigObjectInterface::getConfigPrefix().
+ */
+ public function getConfigPrefix() {
+ return 'config_test.dynamic';
+ }
+
+ /**
+ * Implements ConfigObjectInterface::getName().
+ */
+ public function getConfigName() {
+ return $this->getConfigPrefix() . '.' . $this->getId;
+ }
+
+ /**
+ * Implements ConfigObjectInterface::getId().
+ */
+ public function getId() {
+ return $this->config->get($this->idKey);
+ }
+
+ /**
+ * Implements ConfigObjectInterface::isNew().
+ */
+ public function isNew() {
+ return isset($this->originalId);
+ }
+
+ /**
+ * Implements ConfigObjectInterface::getLabel().
+ */
+ public function getLabel() {
+ return $this->config->get($this->labelKey);
+ }
+
+ /**
+ * Implements ConfigObjectInterface::getUri().
+ */
+ public function getUri() {
+ // @todo Check whether this makes sense.
+ return 'admin/structure/config_test/manage/' . $this->getId();
+ }
+
+ /**
+ * Implements ConfigObjectInterface::get().
+ */
+ public function get($property_name, $langcode = NULL) {
+ return $this->config->get($property_name);
+ }
+
+ /**
+ * Implements ConfigObjectInterface::set().
+ */
+ public function set($property_name, $value, $langcode = NULL) {
+ return $this->config->set($property_name, $value);
+ }
+
+ /**
+ * Implements ConfigObjectInterface::setOriginal().
+ */
+ public function setOriginal($config) {
+ $this->original = $config;
+ $this->originalId = $config->get($this->idKey);
+ return $this;
+ }
+
+ /**
+ * Implements ConfigObjectInterface::load().
+ */
+ public function load($config) {
+ $this->config = $config;
+ $this->originalId = $config->get($this->idKey);
+ return $this;
+ }
+
+ /**
+ * Implements ConfigObjectInterface::save().
+ */
+ public function save() {
+ // Provide the original configuration in $config->original, if any.
+ if (isset($this->originalId)) {
+ $original_config = config($this->getConfigPrefix() . '.' . $this->originalId);
+ $this->setOriginal(new $this($original_config));
+
+ // Delete the original configuration, if it was renamed.
+ if ($this->originalId !== $this->getId()) {
+ // Configuration data is emptied out upon delete, so back it up and
+ // re-inject it. Delete the old configuration data directly; hooks will
+ // get and will be able to react to the data in $this->original.
+ $original_data = $original_config->get();
+ $original_config->delete();
+ $original_config->setData($original_data);
+ }
+ }
+
+ // Save the new configuration.
+ $this->config->setName($this->getConfigPrefix() . '.' . $this->getId());
+ $this->config->save();
+
+ return $this;
+ }
+
+ /**
+ * Implements ConfigObjectInterface::delete().
+ */
+ public function delete() {
+ $this->config->delete();
+ }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
new file mode 100644
index 0000000..aeca024
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php
@@ -0,0 +1,106 @@
+ 'CRUD operations',
+ 'description' => 'Tests CRUD operations on configuration objects.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ /**
+ * Tests CRUD operations.
+ */
+ function testCRUD() {
+ $storage = new DatabaseStorage();
+ $name = 'config_test.crud';
+
+ // Create a new configuration object.
+ $config = config($name);
+ $config->set('value', 'initial');
+ $config->save();
+
+ // Verify the active store contains the saved value.
+ $actual_data = $storage->read($name);
+ $this->assertIdentical($actual_data, array('value' => 'initial'));
+
+ // Update the configuration object instance.
+ $config->set('value', 'instance-update');
+ $config->save();
+
+ // Verify the active store contains the updated value.
+ $actual_data = $storage->read($name);
+ $this->assertIdentical($actual_data, array('value' => 'instance-update'));
+
+ // Verify a call to config() immediately returns the updated value.
+ $new_config = config($name);
+ $this->assertIdentical($new_config->get(), $config->get());
+
+ // Delete the configuration object.
+ $config->delete();
+
+ // Verify the configuration object is empty.
+ $this->assertIdentical($config->get(), array());
+
+ // Verify the active store contains no value.
+ $actual_data = $storage->read($name);
+ $this->assertIdentical($actual_data, array());
+
+ // Verify config() returns no data.
+ $new_config = config($name);
+ $this->assertIdentical($new_config->get(), $config->get());
+
+ // Re-create the configuration object.
+ $config->set('value', 're-created');
+ $config->save();
+
+ // Verify the active store contains the updated value.
+ $actual_data = $storage->read($name);
+ $this->assertIdentical($actual_data, array('value' => 're-created'));
+
+ // Verify a call to config() immediately returns the updated value.
+ $new_config = config($name);
+ $this->assertIdentical($new_config->get(), $config->get());
+ }
+
+ /**
+ * Tests Drupal\Core\Config\Config::sortByKey().
+ */
+ function testDataKeySort() {
+ $config = config('config_test.keysort');
+ $config->set('new', 'Value to be replaced');
+ $config->set('static', 'static');
+ $config->save();
+ // Clone this Config, so this test does not rely on any particular
+ // architecture.
+ $config = clone $config;
+
+ // Load the configuration data into a new object.
+ $new_config = config('config_test.keysort');
+ // Clear the 'new' key that came first.
+ $new_config->clear('new');
+ // Add a new 'new' key and save.
+ $new_config->set('new', 'Value to be replaced');
+ $new_config->save();
+
+ // Verify that the data of both objects is in the identical order.
+ // assertIdentical() is the required essence of this test; it performs a
+ // strict comparison, which means that keys and values must be identical and
+ // their order must be identical.
+ $this->assertIdentical($new_config->get(), $config->get());
+ }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php
new file mode 100644
index 0000000..68df5f1
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php
@@ -0,0 +1,80 @@
+ 'Configurable configuration',
+ 'description' => 'Tests configurable configuration.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('config_test'));
+ }
+
+ /**
+ * Tests basic CRUD operations through the UI.
+ */
+ function testCRUD() {
+ // Create a thingie.
+ $id = 'thingie';
+ $edit = array(
+ 'id' => $id,
+ 'name' => 'Thingie',
+ );
+ $this->drupalPost('admin/structure/config_test/add', $edit, 'Save');
+ $this->assertResponse(200);
+ $this->assertText('Thingie');
+
+ // Update the thingie.
+ $this->assertLinkByHref('admin/structure/config_test/manage/' . $id);
+ $edit = array(
+ 'name' => 'Thongie',
+ );
+ $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save');
+ $this->assertResponse(200);
+ $this->assertNoText('Thingie');
+ $this->assertText('Thongie');
+
+ // Delete the thingie.
+ $this->assertLinkByHref('admin/structure/config_test/manage/' . $id . '/delete');
+ $this->drupalPost('admin/structure/config_test/manage/' . $id . '/delete', array(), 'Delete');
+ $this->assertResponse(200);
+ $this->assertNoText('Thingie');
+ $this->assertNoText('Thongie');
+
+ // Re-create a thingie.
+ $edit = array(
+ 'id' => $id,
+ 'name' => 'Thingie',
+ );
+ $this->drupalPost('admin/structure/config_test/add', $edit, 'Save');
+ $this->assertResponse(200);
+ $this->assertText('Thingie');
+
+ // Rename the thingie's ID/machine name.
+ $this->assertLinkByHref('admin/structure/config_test/manage/' . $id);
+ $new_id = 'zingie';
+ $edit = array(
+ 'id' => $new_id,
+ 'name' => 'Zingie',
+ );
+ $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save');
+ $this->assertResponse(200);
+ $this->assertNoText('Thingie');
+ $this->assertText('Zingie');
+ }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php
index bfd27ac..3add6d8 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php
@@ -7,6 +7,7 @@
namespace Drupal\config\Tests;
+use Drupal\Core\Config\DatabaseStorage;
use Drupal\Core\Config\FileStorage;
use Drupal\simpletest\WebTestBase;
@@ -14,8 +15,6 @@ use Drupal\simpletest\WebTestBase;
* Tests reading and writing file contents.
*/
class ConfigFileContentTest extends WebTestBase {
- protected $fileExtension;
-
public static function getInfo() {
return array(
'name' => 'File content',
@@ -26,15 +25,14 @@ class ConfigFileContentTest extends WebTestBase {
function setUp() {
parent::setUp();
-
- $this->fileExtension = FileStorage::getFileExtension();
}
/**
* Tests setting, writing, and reading of a configuration setting.
*/
function testReadWriteConfig() {
- $config_dir = config_get_config_directory();
+ $database_storage = new DatabaseStorage();
+
$name = 'foo.bar';
$key = 'foo';
$value = 'bar';
@@ -62,16 +60,15 @@ class ConfigFileContentTest extends WebTestBase {
$config = config($name);
// Verify an configuration object is returned.
-// $this->assertEqual($config->name, $name);
+ $this->assertEqual($config->getName(), $name);
$this->assertTrue($config, t('Config object created.'));
// Verify the configuration object is empty.
$this->assertEqual($config->get(), array(), t('New config object is empty.'));
// Verify nothing was saved.
- $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $name))->fetch();
- $this->assertIdentical($db_config, FALSE, t('Active store does not have a record for %name', array('%name' => $name)));
- $this->assertFalse(file_exists($config_dir . '/' . $name . '.' . $this->fileExtension), 'Configuration file does not exist.');
+ $db_data = $database_storage->read($name);
+ $this->assertIdentical($db_data, array());
// Add a top level value
$config = config($name);
@@ -97,15 +94,12 @@ class ConfigFileContentTest extends WebTestBase {
$config->save();
// Verify the database entry exists.
- $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $name))->fetch();
- $this->assertEqual($db_config->name, $name, t('After saving configuration, active store has a record for %name', array('%name' => $name)));
-
- // Verify the file exists.
- $this->assertTrue(file_exists($config_dir . '/' . $name . '.' . $this->fileExtension), t('After saving configuration, config file exists.'));
+ $db_data = $database_storage->read($name);
+ $this->assertTrue($db_data);
// Read top level value
$config = config($name);
-// $this->assertEqual($config->name, $name);
+ $this->assertEqual($config->getName(), $name);
$this->assertTrue($config, 'Config object created.');
$this->assertEqual($config->get($key), 'bar', t('Top level configuration value found.'));
@@ -158,30 +152,27 @@ class ConfigFileContentTest extends WebTestBase {
$config->set($key, $value)->save();
// Verify the database entry exists from a chained save.
- $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $chained_name))->fetch();
- $this->assertEqual($db_config->name, $chained_name, t('After saving configuration by chaining through set(), active store has a record for %name', array('%name' => $chained_name)));
-
- // Verify the file exists from a chained save.
- $this->assertTrue(file_exists($config_dir . '/' . $chained_name . '.' . $this->fileExtension), t('After saving configuration by chaining through set(), config file exists.'));
+ $db_data = $database_storage->read($chained_name);
+ $this->assertEqual($db_data, $config->get());
// Get file listing for all files starting with 'foo'. Should return
// two elements.
- $files = FileStorage::getNamesWithPrefix('foo');
+ $files = $database_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 = FileStorage::getNamesWithPrefix('biff');
+ $files = $database_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 = FileStorage::getNamesWithPrefix('foo.bar');
+ $files = $database_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 = FileStorage::getNamesWithPrefix('bar');
+ $files = $database_storage->listAll('bar');
$this->assertEqual($files, array(), 'No files listed with the prefix \'bar\'.');
// Delete the configuration.
@@ -189,17 +180,14 @@ class ConfigFileContentTest extends WebTestBase {
$config->delete();
// Verify the database entry no longer exists.
- $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $name))->fetch();
- $this->assertIdentical($db_config, FALSE);
- $this->assertFalse(file_exists($config_dir . '/' . $name . $this->fileExtension));
-
- // Attempt to delete non-existing configuration.
+ $db_data = $database_storage->read($name);
+ $this->assertIdentical($db_data, array());
}
/**
* Tests serialization of configuration to file.
*/
- function testConfigSerialization() {
+ function testSerialization() {
$name = $this->randomName(10) . '.' . $this->randomName(10);
$config_data = array(
// Indexed arrays; the order of elements is essential.
@@ -216,17 +204,10 @@ class ConfigFileContentTest extends WebTestBase {
'invalid xml' => ' & < > " \' ',
);
- // Attempt to read non-existing configuration.
- $config = config($name);
-
- foreach ($config_data as $key => $value) {
- $config->set($key, $value);
- }
-
- $config->save();
-
- $config_filestorage = new FileStorage($name);
- $config_parsed = $config_filestorage->read();
+ // Encode and write, and reload and decode the configuration data.
+ $filestorage = new FileStorage();
+ $filestorage->write($name, $config_data);
+ $config_parsed = $filestorage->read($name);
$key = 'numeric keys';
$this->assertIdentical($config_data[$key], $config_parsed[$key]);
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigFileSecurityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileSecurityTest.php
deleted file mode 100644
index 5f9ec07..0000000
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigFileSecurityTest.php
+++ /dev/null
@@ -1,51 +0,0 @@
- 'Good morning, Denver!');
-
- public static function getInfo() {
- return array(
- 'name' => 'File security',
- 'description' => 'Tests security of saved configuration files.',
- 'group' => 'Configuration',
- );
- }
-
- /**
- * Tests that a file written by this system can be successfully read back.
- */
- function testFilePersist() {
- $file = new FileStorage($this->filename);
- $file->write($this->testContent);
-
- unset($file);
-
- // Reading should throw an exception in case of bad validation.
- // Note that if any other exception is thrown, we let the test system
- // handle catching and reporting it.
- try {
- $file = new FileStorage($this->filename);
- $saved_content = $file->read();
-
- $this->assertEqual($saved_content, $this->testContent);
- }
- catch (Exception $e) {
- $this->fail('File failed verification when being read.');
- }
- }
-}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
new file mode 100644
index 0000000..09baded
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
@@ -0,0 +1,162 @@
+ 'Import configuration',
+ 'description' => 'Tests importing configuration from files into active store.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('config_test');
+ }
+
+ /**
+ * Tests deletion of configuration during import.
+ */
+ function testDeleted() {
+ $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');
+
+ // Export.
+ config_export();
+
+ // Delete the configuration objects.
+ $file_storage = new FileStorage();
+ $file_storage->delete($name);
+ $file_storage->delete($dynamic_name);
+
+ // Import.
+ config_import();
+
+ // Verify the values have disappeared.
+ $database_storage = new DatabaseStorage();
+ $this->assertIdentical($database_storage->read($name), array());
+ $this->assertIdentical($database_storage->read($dynamic_name), array());
+
+ $config = config($name);
+ $this->assertIdentical($config->get('foo'), NULL);
+ $config = config($dynamic_name);
+ $this->assertIdentical($config->get('id'), NULL);
+ }
+
+ /**
+ * Tests creation of configuration during import.
+ */
+ function testNew() {
+ $name = 'config_test.new';
+ $dynamic_name = 'config_test.dynamic.new';
+
+ // Verify the configuration to create does not exist yet.
+ $file_storage = new FileStorage();
+ $this->assertIdentical($file_storage->exists($name), FALSE, $name . ' not found.');
+ $this->assertIdentical($file_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
+
+ // Export.
+ config_export();
+
+ // Create new configuration objects.
+ $file_storage->write($name, array(
+ 'add_me' => 'new value',
+ ));
+ $file_storage->write($dynamic_name, array(
+ 'id' => 'new',
+ 'name' => 'New',
+ ));
+ $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
+ $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+
+ // Import.
+ config_import();
+
+ // Verify the values appeared.
+ $config = config($name);
+ $this->assertIdentical($config->get('add_me'), 'new value');
+ $config = config($dynamic_name);
+ $this->assertIdentical($config->get('name'), 'New');
+ }
+
+ /**
+ * Tests updating of configuration during import.
+ */
+ function testUpdated() {
+ $name = 'config_test.system';
+ $dynamic_name = 'config_test.dynamic.default';
+
+ // Export.
+ config_export();
+
+ // Replace the file content of the existing configuration objects.
+ $file_storage = new FileStorage();
+ $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
+ $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+ $file_storage->write($name, array(
+ 'foo' => 'beer',
+ ));
+ $file_storage->write($dynamic_name, array(
+ 'id' => 'default',
+ 'name' => 'Updated',
+ ));
+
+ // Verify the active store still returns the default values.
+ $config = config($name);
+ $this->assertIdentical($config->get('foo'), 'bar');
+ $config = config($dynamic_name);
+ $this->assertIdentical($config->get('name'), 'Default');
+
+ // Import.
+ config_import();
+
+ // Verify the values were updated.
+ $config = config($name);
+ $this->assertIdentical($config->get('foo'), 'beer');
+ $config = config($dynamic_name);
+ $this->assertIdentical($config->get('name'), 'Updated');
+ }
+
+ /**
+ * Tests config_import() hook invocations.
+ */
+ function testSyncHooks() {
+ $name = 'config_test.system';
+ $dynamic_name = 'config_test.dynamic.default';
+
+ // Export.
+ config_export();
+
+ // Delete a file so that hook_config_import() hooks are run.
+ $file_storage = new FileStorage();
+ $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
+ $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+ $file_storage->delete($name);
+ $file_storage->delete($dynamic_name);
+
+ // Import.
+ config_import();
+
+ // Verify hook_config_import() was invoked.
+ $this->assertIdentical($GLOBALS['hook_config_import'], 'config_test_config_import');
+ }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigUpgradeTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigUpgradeTest.php
index b2286da..0b43de4 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigUpgradeTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigUpgradeTest.php
@@ -7,6 +7,7 @@
namespace Drupal\config\Tests;
+use Drupal\Core\Config\ConfigException;
use Drupal\simpletest\WebTestBase;
/**
@@ -80,5 +81,14 @@ class ConfigUpgradeTest extends WebTestBase {
// Verify that variables have been deleted.
$variables = db_query('SELECT name FROM {variable} WHERE name IN (:names)', array(':names' => array('config_upgrade_additional')))->fetchCol();
$this->assertFalse($variables);
+
+ // Verify that a default module configuration file is required to exist.
+ try {
+ update_variables_to_config('config_upgrade.missing.default.config', array());
+ $this->fail('Exception was not thrown on missing default module configuration file.');
+ }
+ catch (ConfigException $e) {
+ $this->pass('Exception was thrown on missing default module configuration file.');
+ }
}
}
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
new file mode 100644
index 0000000..3e6b254
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
@@ -0,0 +1,121 @@
+storage->read($name);
+ $this->assertIdentical($data, array());
+
+ // Reading a name containing non-decodeable data returns an empty array.
+ $this->insert($name, '');
+ $data = $this->storage->read($name);
+ $this->assertIdentical($data, array());
+
+ $this->update($name, 'foo');
+ $data = $this->storage->read($name);
+ $this->assertIdentical($data, array());
+
+ $this->delete($name);
+
+ // Writing data returns TRUE and the data has been written.
+ $data = array('foo' => 'bar');
+ $result = $this->storage->write($name, $data);
+ $this->assertIdentical($result, TRUE);
+ $raw_data = $this->read($name);
+ $this->assertIdentical($raw_data, $data);
+
+ // Writing the identical data again still returns TRUE.
+ $result = $this->storage->write($name, $data);
+ $this->assertIdentical($result, TRUE);
+
+ // Listing all names returns all.
+ $names = $this->storage->listAll();
+ $this->assertTrue(in_array('system.performance', $names));
+ $this->assertTrue(in_array($name, $names));
+
+ // Listing all names with prefix returns names with that prefix only.
+ $names = $this->storage->listAll('config_test.');
+ $this->assertFalse(in_array('system.performance', $names));
+ $this->assertTrue(in_array($name, $names));
+
+ // Deleting an existing name returns TRUE.
+ $result = $this->storage->delete($name);
+ $this->assertIdentical($result, TRUE);
+
+ // Deleting a non-existing name returns FALSE.
+ $result = $this->storage->delete($name);
+ $this->assertIdentical($result, FALSE);
+
+ // Reading from a non-existing storage bin returns an empty data array.
+ $data = $this->invalidStorage->read($name);
+ $this->assertIdentical($data, array());
+
+ // Writing to a non-existing storage bin throws an exception.
+ try {
+ $this->invalidStorage->write($name, array('foo' => 'bar'));
+ $this->fail('Exception not thrown upon writing to a non-existing storage bin.');
+ }
+ catch (\Exception $e) {
+ $class = get_class($e);
+ $this->pass($class . ' thrown upon writing to a non-existing storage bin.');
+ }
+
+ // Deleting from a non-existing storage bin throws an exception.
+ try {
+ $this->invalidStorage->delete($name);
+ $this->fail('Exception not thrown upon deleting from a non-existing storage bin.');
+ }
+ catch (\Exception $e) {
+ $class = get_class($e);
+ $this->pass($class . ' thrown upon deleting from a non-existing storage bin.');
+ }
+
+ // Listing on a non-existing storage bin throws an exception.
+ try {
+ $this->invalidStorage->listAll();
+ $this->fail('Exception not thrown upon listing from a non-existing storage bin.');
+ }
+ catch (\Exception $e) {
+ $class = get_class($e);
+ $this->pass($class . ' thrown upon listing from a non-existing storage bin.');
+ }
+ }
+
+ abstract protected function read($name);
+
+ abstract protected function insert($name, $data);
+
+ abstract protected function update($name, $data);
+
+ abstract protected function delete($name);
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
new file mode 100644
index 0000000..2e1599f
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
@@ -0,0 +1,46 @@
+ 'DatabaseStorage controller operations',
+ 'description' => 'Tests DatabaseStorage controller operations.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->storage = new DatabaseStorage();
+ $this->invalidStorage = new DatabaseStorage(array('connection' => 'invalid'));
+ }
+
+ protected function read($name) {
+ $data = db_query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name))->fetchField();
+ return unserialize($data);
+ }
+
+ protected function insert($name, $data) {
+ db_insert('config')->fields(array('name' => $name, 'data' => $data))->execute();
+ }
+
+ protected function update($name, $data) {
+ db_update('config')->fields(array('data' => $data))->condition('name', $name)->execute();
+ }
+
+ protected function delete($name) {
+ db_delete('config')->condition('name', $name)->execute();
+ }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php
new file mode 100644
index 0000000..092687f
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php
@@ -0,0 +1,50 @@
+ 'FileStorage controller operations',
+ 'description' => 'Tests FileStorage controller operations.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->storage = new FileStorage();
+ $this->invalidStorage = new FileStorage(array('directory' => $this->configFileDirectory . '/nonexisting'));
+
+ // FileStorage::listAll() requires other configuration data to exist.
+ $this->storage->write('system.performance', config('system.performance')->get());
+ }
+
+ protected function read($name) {
+ $data = file_get_contents($this->storage->getFilePath($name));
+ return Yaml::parse($data);
+ }
+
+ protected function insert($name, $data) {
+ file_put_contents($this->storage->getFilePath($name), $data);
+ }
+
+ protected function update($name, $data) {
+ file_put_contents($this->storage->getFilePath($name), $data);
+ }
+
+ protected function delete($name) {
+ unlink($this->storage->getFilePath($name));
+ }
+}
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index f3bf83d..c95af19 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -500,6 +500,36 @@ function image_path_flush($path) {
}
/**
+ * Implements hook_config_import().
+ */
+function image_config_import($op, $name, $new_config, $old_config) {
+ // Only image styles require custom handling. Any other module settings can be
+ // synchronized directly.
+ if (strpos($name, 'image.style.') !== 0) {
+ return FALSE;
+ }
+
+ if ($op == 'delete') {
+ // @todo image_style_delete() supports the notion of a "replacement style"
+ // to be used by other modules instead of the deleted style. Good idea.
+ // But squeezing that into a "delete" operation is the worst idea ever.
+ // Regardless of Image module insanity, add a 'replaced' stack to
+ // config_import()? And how can that work? If an 'old_ID' key would be a
+ // standard, wouldn't this belong into 'changed' instead?
+ $style = $old_config->get();
+ return image_style_delete($style);
+ }
+ if ($op == 'create') {
+ $style = $new_config->get();
+ return image_style_save($style);
+ }
+ if ($op == 'change') {
+ $style = $new_config->get();
+ return image_style_save($style);
+ }
+}
+
+/**
* Get an array of all styles and their settings.
*
* @return
diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
index 6319572..8afe33b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
@@ -106,7 +106,7 @@ class EnableDisableTest extends ModuleTestBase {
$this->assertText(t('hook_modules_enabled fired for @module', array('@module' => $module_to_enable)));
$this->assertModules(array($module_to_enable), TRUE);
$this->assertModuleTablesExist($module_to_enable);
- $this->assertModuleConfigFilesExist($module_to_enable);
+ $this->assertModuleConfig($module_to_enable);
$this->assertLogMessage('system', "%module module installed.", array('%module' => $module_to_enable), WATCHDOG_INFO);
$this->assertLogMessage('system', "%module module enabled.", array('%module' => $module_to_enable), WATCHDOG_INFO);
}
@@ -187,7 +187,7 @@ class EnableDisableTest extends ModuleTestBase {
// Check that the module's database tables still exist.
$this->assertModuleTablesExist($module);
// Check that the module's config files still exist.
- $this->assertModuleConfigFilesExist($module);
+ $this->assertModuleConfig($module);
// Uninstall the module.
$edit = array();
@@ -209,6 +209,6 @@ class EnableDisableTest extends ModuleTestBase {
// Check that the module's database tables no longer exist.
$this->assertModuleTablesDoNotExist($module);
// Check that the module's config files no longer exist.
- $this->assertModuleConfigFilesDoNotExist($module);
+ $this->assertNoModuleConfig($module);
}
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php
index 3b2ed5c..8f372ca 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php
@@ -8,6 +8,7 @@
namespace Drupal\system\Tests\Module;
use Drupal\Core\Database\Database;
+use Drupal\Core\Config\DatabaseStorage;
use Drupal\Core\Config\FileStorage;
use Drupal\simpletest\WebTestBase;
@@ -77,65 +78,49 @@ class ModuleTestBase extends WebTestBase {
}
/**
- * Assert that a module's config files have been loaded.
+ * Asserts that the default configuration of a module has been installed.
*
* @param string $module
* The name of the module.
*
* @return bool
- * TRUE if the module's config files exist, FALSE otherwise.
+ * TRUE if configuration has been installed, FALSE otherwise.
*/
- function assertModuleConfigFilesExist($module) {
- // Define test variable.
- $files_exist = TRUE;
- // Get the path to the module's config dir.
+ function assertModuleConfig($module) {
$module_config_dir = drupal_get_path('module', $module) . '/config';
- if (is_dir($module_config_dir)) {
- $files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension());
- $this->assertTrue($files);
- $config_dir = config_get_config_directory();
- // Get the filename of each config file.
- foreach ($files as $file) {
- $parts = explode('/', $file);
- $filename = array_pop($parts);
- if (!file_exists($config_dir . '/' . $filename)) {
- $files_exist = FALSE;
- }
- }
+ if (!is_dir($module_config_dir)) {
+ return;
}
+ $module_file_storage = new FileStorage(array('directory' => $module_config_dir));
+ $names = $module_file_storage->listAll();
+
+ // Verify that the config directory is not empty.
+ $this->assertTrue($names);
- return $this->assertTrue($files_exist, t('All config files defined by the @module module have been copied to the live config directory.', array('@module' => $module)));
+ // Look up each default configuration object name in the active store, and
+ // if it exists, remove it from the stack.
+ foreach ($names as $key => $name) {
+ if (config($name)->get()) {
+ unset($names[$key]);
+ }
+ }
+ // Verify that all configuration has been installed (which means that $names
+ // is empty).
+ return $this->assertFalse($names, format_string('All default configuration of @module module found.', array('@module' => $module)));
}
/**
- * Assert that none of a module's default config files are loaded.
+ * Asserts that no configuration exists for a given module.
*
* @param string $module
* The name of the module.
*
* @return bool
- * TRUE if the module's config files do not exist, FALSE otherwise.
+ * TRUE if no configuration was found, FALSE otherwise.
*/
- function assertModuleConfigFilesDoNotExist($module) {
- // Define test variable.
- $files_exist = FALSE;
- // Get the path to the module's config dir.
- $module_config_dir = drupal_get_path('module', $module) . '/config';
- if (is_dir($module_config_dir)) {
- $files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension());
- $this->assertTrue($files);
- $config_dir = config_get_config_directory();
- // Get the filename of each config file.
- foreach ($files as $file) {
- $parts = explode('/', $file);
- $filename = array_pop($parts);
- if (file_exists($config_dir . '/' . $filename)) {
- $files_exist = TRUE;
- }
- }
- }
-
- return $this->assertFalse($files_exist, t('All config files defined by the @module module have been deleted from the live config directory.', array('@module' => $module)));
+ function assertNoModuleConfig($module) {
+ $names = config_get_storage_names_with_prefix($module . '.');
+ return $this->assertFalse($names, format_string('No configuration found for @module module.', array('@module' => $module)));
}
/**
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index b9558e5..32cfb75 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -379,7 +379,8 @@ function system_element_info() {
$types['email'] = array(
'#input' => TRUE,
'#size' => 60,
- '#maxlength' => EMAIL_MAX_LENGTH,
+ // user.module is not loaded in case of early bootstrap errors.
+ '#maxlength' => defined('EMAIL_MAX_LENGTH') ? EMAIL_MAX_LENGTH : 255,
'#autocomplete_path' => FALSE,
'#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern'),
'#element_validate' => array('form_validate_email'),