230 lines
8.1 KiB
PHP
230 lines
8.1 KiB
PHP
<?php
|
|
|
|
use Drupal\Core\Config\Config;
|
|
use Drupal\Core\Config\FileStorage;
|
|
use Drupal\Core\Config\NullStorage;
|
|
use Drupal\Core\Config\StorageInterface;
|
|
|
|
/**
|
|
* @file
|
|
* This is the API for configuration storage.
|
|
*/
|
|
|
|
/**
|
|
* Installs the default configuration of a given extension.
|
|
*
|
|
* @param string $type
|
|
* The extension type; e.g., 'module' or 'theme'.
|
|
* @param string $name
|
|
* The name of the module or theme to install default configuration for.
|
|
*/
|
|
function config_install_default_config($type, $name) {
|
|
$config_dir = drupal_get_path($type, $name) . '/config';
|
|
if (is_dir($config_dir)) {
|
|
$source_storage = new FileStorage($config_dir);
|
|
$target_storage = drupal_container()->get('config.storage');
|
|
$null_storage = new NullStorage();
|
|
|
|
// Upon installation, only new config objects need to be created.
|
|
// config_sync_get_changes() would potentially perform a diff of hundreds or
|
|
// even thousands of config objects that happen to be contained in the
|
|
// active store. We leverage the NullStorage to avoid that needless
|
|
// computation of differences.
|
|
$config_changes = config_sync_get_changes($source_storage, $null_storage);
|
|
if (empty($config_changes)) {
|
|
return;
|
|
}
|
|
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
|
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets configuration object names starting with a given prefix.
|
|
*
|
|
* @see Drupal\Core\Config\StorageInterface::listAll()
|
|
*/
|
|
function config_get_storage_names_with_prefix($prefix = '') {
|
|
return drupal_container()->get('config.storage')->listAll($prefix);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a configuration object.
|
|
*
|
|
* This is the main entry point to the configuration API. Calling
|
|
* @code config(book.admin) @endcode will return a configuration object in which
|
|
* the book module can store its administrative settings.
|
|
*
|
|
* @param $name
|
|
* The name of the configuration object to retrieve. The name corresponds to
|
|
* a configuration file. For @code config(book.admin) @endcode, the config
|
|
* object returned will contain the contents of book.admin configuration file.
|
|
*
|
|
* @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 any category.
|
|
if (empty($config_changes['create']) && empty($config_changes['change']) && empty($config_changes['delete'])) {
|
|
return FALSE;
|
|
}
|
|
return $config_changes;
|
|
}
|
|
|
|
/**
|
|
* Writes an array of config file changes from a source storage to a target storage.
|
|
*
|
|
* @param array $config_changes
|
|
* An array of changes to be written.
|
|
* @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.
|
|
*/
|
|
function config_sync_changes(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 staging and the active store.
|
|
$source_storage = drupal_container()->get('config.storage.staging');
|
|
$target_storage = drupal_container()->get('config.storage');
|
|
|
|
$config_changes = config_sync_get_changes($source_storage, $target_storage);
|
|
if (empty($config_changes)) {
|
|
return;
|
|
}
|
|
|
|
if (!lock()->acquire(__FUNCTION__)) {
|
|
// Another request is synchronizing configuration.
|
|
// Return a negative result for UI purposes. We do not differentiate between
|
|
// an actual synchronization error and a failed lock, because concurrent
|
|
// synchronizations are an edge-case happening only when multiple developers
|
|
// or site builders attempt to do it without coordinating.
|
|
return FALSE;
|
|
}
|
|
|
|
$success = TRUE;
|
|
try {
|
|
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
|
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
|
// Flush all caches and reset static variables after a successful import.
|
|
drupal_flush_all_caches();
|
|
}
|
|
catch (ConfigException $e) {
|
|
watchdog_exception('config_import', $e);
|
|
$success = FALSE;
|
|
}
|
|
lock()->release(__FUNCTION__);
|
|
return $success;
|
|
}
|
|
|
|
/**
|
|
* Invokes MODULE_config_import() callbacks for configuration changes.
|
|
*
|
|
* @param array $config_changes
|
|
* An array of changes to be loaded.
|
|
* @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.
|
|
*
|
|
* @todo Add support for other extension types; e.g., themes etc.
|
|
*/
|
|
function config_import_invoke_owner(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
|
|
// 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_' . $op)) {
|
|
$old_config = new Config($name, $target_storage);
|
|
$old_config->load();
|
|
|
|
$data = $source_storage->read($name);
|
|
$new_config = new Config($name, $target_storage);
|
|
if ($data !== FALSE) {
|
|
$new_config->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 the active store to staging.
|
|
*/
|
|
function config_export() {
|
|
// Retrieve a list of differences between the active store and staging.
|
|
$source_storage = drupal_container()->get('config.storage');
|
|
$target_storage = drupal_container()->get('config.storage.staging');
|
|
|
|
$config_changes = config_sync_get_changes($source_storage, $target_storage);
|
|
if (empty($config_changes)) {
|
|
return;
|
|
}
|
|
config_sync_changes($config_changes, $source_storage, $target_storage);
|
|
return TRUE;
|
|
}
|