Commit 7c7b91ef authored by webchick's avatar webchick

Issue #1741804 by beejeebus, sun, chx, alexpott, matason: Implement a config...

Issue #1741804 by beejeebus, sun, chx, alexpott, matason: Implement a config import/staging directory.
parent c34d8048
......@@ -295,6 +295,20 @@
*/
const DRUPAL_PHP_FUNCTION_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
/**
* $config_directories key for active directory.
*
* @see config_get_config_directory
*/
const CONFIG_ACTIVE_DIRECTORY = 'active';
/**
* $config_directories key for staging directory.
*
* @see config_get_config_directory
*/
const CONFIG_STAGING_DIRECTORY = 'staging';
/**
* Starts the timer with the specified name.
*
......@@ -469,20 +483,27 @@ function find_conf_path($http_host, $script_name, $require_settings = TRUE) {
}
/**
* Returns the path of the configuration directory.
* Returns the path of a configuration directory.
*
* @param string $type
* (optional) The type of config directory to return. Drupal core provides
* 'active' and 'staging'. Defaults to CONFIG_ACTIVE_DIRECTORY.
*
* @return string
* The configuration directory path.
*/
function config_get_config_directory() {
global $config_directory_name;
function config_get_config_directory($type = CONFIG_ACTIVE_DIRECTORY) {
global $config_directories;
if ($test_prefix = drupal_valid_test_ua()) {
// @see Drupal\simpletest\WebTestBase::setUp()
$path = conf_path() . '/files/simpletest/' . substr($test_prefix, 10) . '/config';
$path = conf_path() . '/files/simpletest/' . substr($test_prefix, 10) . '/config_' . $type;
}
elseif (!empty($config_directories[$type])) {
$path = conf_path() . '/files/' . $config_directories[$type];
}
else {
$path = conf_path() . '/files/' . $config_directory_name;
throw new Exception(format_string('The configuration directory type %type does not exist.', array('%type' => $type)));
}
return $path;
}
......@@ -675,7 +696,7 @@ function drupal_settings_initialize() {
global $base_url, $base_path, $base_root, $script_path;
// Export these settings.php variables to the global namespace.
global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url, $config_directory_name;
global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url, $config_directories;
$conf = array();
// Make conf_path() available as local variable in settings.php.
......
......@@ -129,15 +129,15 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto
}
/**
* Imports configuration from FileStorage to the active store.
* Imports configuration into the active store.
*
* @return bool|null
* TRUE if configuration was imported successfully, FALSE in case of a
* synchronization error, or NULL if there are no changes to synchronize.
*/
function config_import() {
// Retrieve a list of differences between FileStorage and the active store.
$source_storage = new FileStorage();
// 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');
$config_changes = config_sync_get_changes($source_storage, $target_storage);
......@@ -214,12 +214,12 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
}
/**
* Exports configuration from the active store to FileStorage.
* Exports configuration from the active store to staging.
*/
function config_export() {
// Retrieve a list of differences between the active store and FileStorage.
// Retrieve a list of differences between the active store and staging.
$source_storage = drupal_container()->get('config.storage');
$target_storage = new FileStorage();
$target_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY)));
$config_changes = config_sync_get_changes($source_storage, $target_storage);
if (empty($config_changes)) {
......
......@@ -320,7 +320,7 @@ function install_begin_request(&$install_state) {
// Check existing settings.php.
$install_state['database_verified'] = install_verify_database_settings();
$install_state['config_verified'] = install_ensure_config_directory();
$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']) {
......@@ -1053,8 +1053,8 @@ function install_settings_form_submit($form, &$form_state) {
drupal_rewrite_settings($settings);
// Add the config directory to settings.php.
drupal_install_config_directory();
// Add the config directories to settings.php.
drupal_install_config_directories();
// 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
......
......@@ -254,14 +254,17 @@ function drupal_rewrite_settings($settings = array()) {
* @see install_settings_form_submit()
* @see update_prepare_d8_bootstrap()
*/
function drupal_install_config_directory() {
global $config_directory_name;
function drupal_install_config_directories() {
global $config_directories;
// Add a randomized config directory name to settings.php, unless it was
// manually defined in the existing already.
if (!$config_directory_name) {
$settings['config_directory_name'] = array(
'value' => 'config_' . drupal_hash_base64(drupal_random_bytes(55)),
if (empty($config_directories)) {
$settings['config_directories'] = array(
'value' => array(
CONFIG_ACTIVE_DIRECTORY => 'config/active_' . drupal_hash_base64(drupal_random_bytes(55)),
CONFIG_STAGING_DIRECTORY => 'config/staging_' . drupal_hash_base64(drupal_random_bytes(55)),
),
'required' => TRUE,
);
// Rewrite settings.php, which also sets the value as global variable.
......@@ -269,33 +272,70 @@ function drupal_install_config_directory() {
}
// Ensure that the config directory exists or can be created, and is writable.
if (!install_ensure_config_directory()) {
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
// if it wasn't specified in settings.php, it is created here inside the
// public files directory, which has already been verified to be writable
// itself. But if it somehow fails anyway, the installation cannot proceed.
// Bail out using a similar error message as in system_requirements().
throw new Exception(st('The directory %directory could not be created or could not be made writable. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see the <a href="@handbook_url">online handbook</a>.', array(
'%directory' => config_get_config_directory(),
'@handbook_url' => 'http://drupal.org/server-permissions',
)));
if (!install_ensure_config_directory($config_type)) {
throw new Exception(st('The directory %directory could not be created or could not be made writable. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see the <a href="@handbook_url">online handbook</a>.', array(
'%directory' => config_get_config_directory($config_type),
'@handbook_url' => 'http://drupal.org/server-permissions',
)));
}
}
}
/**
* 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
* 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).
*
* @param string $type
* Type of config directory to return. Drupal core provides 'active' and
* 'staging'.
*
* @return bool
* TRUE if the config directory exists and is writable.
*/
function install_verify_config_directory($type) {
global $config_directories;
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;
}
return FALSE;
}
/**
* Ensures that the config directory exists and is writable, or can be made so.
*
* @param string $type
* Type of config directory to return. Drupal core provides 'active' and
* 'staging'.
*
* @return bool
* TRUE if the config directory exists and is writable.
*/
function install_ensure_config_directory() {
function install_ensure_config_directory($type) {
// The config directory must be defined in settings.php.
global $config_directory_name;
if (empty($config_directory_name)) {
global $config_directories;
if (!isset($config_directories[$type])) {
return FALSE;
}
// The logic here is similar to that used by system_requirements() for other
// directories that the installer creates.
else {
$config_directory = config_get_config_directory();
$config_directory = config_get_config_directory($type);
return file_prepare_directory($config_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
}
}
......
......@@ -89,7 +89,7 @@ function update_prepare_d8_bootstrap() {
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
// Check whether settings.php needs to be rewritten.
$settings_exist = !empty($GLOBALS['config_directory_name']);
$settings_exist = !empty($GLOBALS['config_directories']);
// If any of the required settings needs to be written, then settings.php
// needs to be writable.
......@@ -139,11 +139,11 @@ function update_prepare_d8_bootstrap() {
// Update the environment for the language bootstrap if needed.
update_prepare_d8_language();
// Ensure the configuration directory exists and is writable or create it.
// If no $config_directory_name has been specified in settings.php and
// created manually already, and the directory cannot be created by the
// Ensure the configuration directories exist and are writable, or create
// them. If the directories have not been specified in settings.php and
// created manually already, and either directory cannot be created by the
// web server, an exception will be thrown, halting the update.
drupal_install_config_directory();
drupal_install_config_directories();
// Change language column to langcode in url_alias.
if (db_table_exists('url_alias') && db_field_exists('url_alias', 'language')) {
......
......@@ -64,6 +64,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)));
// Verify the default configuration values exist.
$config = config($name);
......@@ -74,18 +76,16 @@ function testDeleted() {
// Export.
config_export();
// Delete the configuration objects.
$file_storage = new FileStorage();
$file_storage->delete($name);
$file_storage->delete($dynamic_name);
// Delete the configuration objects from the staging directory.
$staging_storage->delete($name);
$staging_storage->delete($dynamic_name);
// Import.
config_import();
// Verify the values have disappeared.
$database_storage = new DatabaseStorage();
$this->assertIdentical($database_storage->read($name), FALSE);
$this->assertIdentical($database_storage->read($dynamic_name), FALSE);
$this->assertIdentical($active_storage->read($name), FALSE);
$this->assertIdentical($active_storage->read($dynamic_name), FALSE);
$config = config($name);
$this->assertIdentical($config->get('foo'), NULL);
......@@ -107,34 +107,36 @@ function testDeleted() {
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.');
$staging_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY)));
// Export.
config_export();
// Create new configuration objects.
$file_storage->write($name, array(
// 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.');
// Create new configuration objects in the staging directory.
$original_name_data = array(
'add_me' => 'new value',
));
$file_storage->write($dynamic_name, array(
);
$staging_storage->write($name, $original_name_data);
$original_dynamic_data = array(
'id' => 'new',
'label' => 'New',
));
$this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
$this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
);
$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.');
// Import.
config_import();
// Verify the values appeared.
$config = config($name);
$this->assertIdentical($config->get('add_me'), 'new value');
$this->assertIdentical($config->get('add_me'), $original_name_data['add_me']);
$config = config($dynamic_name);
$this->assertIdentical($config->get('label'), 'New');
$this->assertIdentical($config->get('label'), $original_dynamic_data['label']);
// Verify that appropriate module API hooks have been invoked.
$this->assertFalse(isset($GLOBALS['hook_config_test']['load']));
......@@ -151,21 +153,26 @@ 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)));
// 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(
// 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.');
// Replace the file content of the existing configuration objects in the
// staging directory.
$original_name_data = array(
'foo' => 'beer',
));
$file_storage->write($dynamic_name, array(
);
$staging_storage->write($name, $original_name_data);
$original_dynamic_data = array(
'id' => 'default',
'label' => 'Updated',
));
);
$staging_storage->write($dynamic_name, $original_dynamic_data);
// Verify the active store still returns the default values.
$config = config($name);
......@@ -182,6 +189,10 @@ function testUpdated() {
$config = config($dynamic_name);
$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);
// Verify that appropriate module API hooks have been invoked.
$this->assertTrue(isset($GLOBALS['hook_config_test']['load']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['presave']));
......
......@@ -25,7 +25,7 @@ public static function getInfo() {
function setUp() {
parent::setUp();
$this->storage = new FileStorage();
$this->invalidStorage = new FileStorage(array('directory' => $this->configFileDirectory . '/nonexisting'));
$this->invalidStorage = new FileStorage(array('directory' => $this->configDirectories[CONFIG_ACTIVE_DIRECTORY] . '/nonexisting'));
// FileStorage::listAll() requires other configuration data to exist.
$this->storage->write('system.performance', config('system.performance')->get());
......
......@@ -689,7 +689,7 @@ protected function prepareEnvironment() {
// Backup statics and globals.
$this->originalContainer = clone drupal_container();
$this->originalLanguage = $language_interface;
$this->originalConfigDirectory = $GLOBALS['config_directory_name'];
$this->originalConfigDirectories = $GLOBALS['config_directories'];
// Save further contextual information.
$this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
......@@ -717,13 +717,23 @@ protected function prepareEnvironment() {
file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY);
$this->generatedTestFiles = FALSE;
// Create and set a new configuration directory and signature key.
// The child site automatically adjusts the global $config_directory_name to
// Create and set new configuration directories. The child site
// uses drupal_valid_test_ua() to adjust the config directory paths to
// a test-prefix-specific directory within the public files directory.
// @see config_get_config_directory()
$GLOBALS['config_directory_name'] = 'simpletest/' . substr($this->databasePrefix, 10) . '/config';
$this->configFileDirectory = $this->originalFileDirectory . '/' . $GLOBALS['config_directory_name'];
file_prepare_directory($this->configFileDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$GLOBALS['config_directories'] = array();
foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
$GLOBALS['config_directories'][$type] = 'simpletest/' . substr($this->databasePrefix, 10) . '/config_' . $type;
}
$this->configDirectories = array();
include_once DRUPAL_ROOT . '/core/includes/install.inc';
foreach ($GLOBALS['config_directories'] as $type => $path) {
if (!install_ensure_config_directory($type)) {
return FALSE;
}
$this->configDirectories[$type] = $this->originalFileDirectory . '/' . $path;
}
// Log fatal errors.
ini_set('log_errors', 1);
......@@ -781,7 +791,7 @@ protected function tearDown() {
// Restore original statics and globals.
drupal_container($this->originalContainer);
$language_interface = $this->originalLanguage;
$GLOBALS['config_directory_name'] = $this->originalConfigDirectory;
$GLOBALS['config_directories'] = $this->originalConfigDirectories;
// Restore original shutdown callbacks.
$callbacks = &drupal_register_shutdown_function();
......
......@@ -330,14 +330,15 @@ function system_requirements($phase) {
// Check the config directory if it is defined in settings.php. If it isn't
// defined, the installer will create a valid config directory later, but
// during runtime we must always display an error.
if (!empty($GLOBALS['config_directory_name'])) {
$directories[] = config_get_config_directory();
if (!empty($GLOBALS['config_directories'])) {
$directories[] = config_get_config_directory(CONFIG_ACTIVE_DIRECTORY);
$directories[] = config_get_config_directory(CONFIG_STAGING_DIRECTORY);
}
elseif ($phase != 'install') {
$requirements['config directory'] = array(
'title' => $t('Configuration directory'),
$requirements['config directories'] = array(
'title' => $t('Configuration directories'),
'value' => $t('Not present'),
'description' => $t('Your %file file must define the $config_directory_name variable as the name of a directory in which configuration files can be written.', array('%file' => conf_path() . '/settings.php')),
'description' => $t('Your %file file must define the $config_directories variable as an array containing the name of a directories in which configuration files can be written.', array('%file' => conf_path() . '/settings.php')),
'severity' => REQUIREMENT_ERROR,
);
}
......
......@@ -257,9 +257,14 @@
* @todo Flesh this out, provide more details, etc.
*
* Example:
* $config_directory_name = '/some/directory/outside/webroot';
* @code
* $config_directories = array(
* CONFIG_ACTIVE_DIRECTORY => '/some/directory/outside/webroot',
* CONFIG_STAGING_DIRECTORY => '/another/directory/outside/webroot',
* );
* @endcode
*/
$config_directory_name = '';
$config_directories = array();
/**
* Base URL (optional).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment