Issue #1890784 by alexpott, YesCT, heyrocker, tim.plunkett, EllaTheHarpy, beejeebus: Refactor configuration import and sync functions.
parent
3a8e8c6082
commit
4eaa775b10
|
@ -356,6 +356,15 @@ services:
|
||||||
class: Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber
|
class: Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber
|
||||||
tags:
|
tags:
|
||||||
- { name: event_subscriber }
|
- { name: event_subscriber }
|
||||||
|
config_import_subscriber:
|
||||||
|
class: Drupal\Core\EventSubscriber\ConfigImportSubscriber
|
||||||
|
tags:
|
||||||
|
- { name: event_subscriber }
|
||||||
|
config_snapshot_subscriber:
|
||||||
|
class: Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber
|
||||||
|
tags:
|
||||||
|
- { name: event_subscriber }
|
||||||
|
arguments: ['@config.storage', '@config.storage.snapshot']
|
||||||
language_request_subscriber:
|
language_request_subscriber:
|
||||||
class: Drupal\Core\EventSubscriber\LanguageRequestSubscriber
|
class: Drupal\Core\EventSubscriber\LanguageRequestSubscriber
|
||||||
tags:
|
tags:
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
use Drupal\Core\Config\Config;
|
use Drupal\Core\Config\Config;
|
||||||
use Drupal\Core\Config\ConfigException;
|
use Drupal\Core\Config\ConfigException;
|
||||||
|
use Drupal\Core\Config\ConfigInstaller;
|
||||||
use Drupal\Core\Config\FileStorage;
|
use Drupal\Core\Config\FileStorage;
|
||||||
use Drupal\Core\Config\StorageInterface;
|
use Drupal\Core\Config\StorageInterface;
|
||||||
|
use Drupal\Core\Config\StorageComparer;
|
||||||
use Symfony\Component\Yaml\Dumper;
|
use Symfony\Component\Yaml\Dumper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,11 +13,6 @@ use Symfony\Component\Yaml\Dumper;
|
||||||
* This is the API for configuration storage.
|
* This is the API for configuration storage.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Config import lock name used to prevent concurrent synchronizations.
|
|
||||||
*/
|
|
||||||
const CONFIG_IMPORT_LOCK = 'config_import';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs the default configuration of a given extension.
|
* Installs the default configuration of a given extension.
|
||||||
*
|
*
|
||||||
|
@ -25,10 +22,6 @@ const CONFIG_IMPORT_LOCK = 'config_import';
|
||||||
* The name of the module or theme to install default configuration for.
|
* The name of the module or theme to install default configuration for.
|
||||||
*/
|
*/
|
||||||
function config_install_default_config($type, $name) {
|
function config_install_default_config($type, $name) {
|
||||||
// Use the override free context for config importing so that any overrides do
|
|
||||||
// not change the data on import.
|
|
||||||
config_context_enter('config.context.free');
|
|
||||||
|
|
||||||
// If this module defines any ConfigEntity types then create an empty
|
// If this module defines any ConfigEntity types then create an empty
|
||||||
// manifest file for each of them.
|
// manifest file for each of them.
|
||||||
foreach (config_get_module_config_entities($name) as $entity_info) {
|
foreach (config_get_module_config_entities($name) as $entity_info) {
|
||||||
|
@ -38,22 +31,19 @@ function config_install_default_config($type, $name) {
|
||||||
$config_dir = drupal_get_path($type, $name) . '/config';
|
$config_dir = drupal_get_path($type, $name) . '/config';
|
||||||
if (is_dir($config_dir)) {
|
if (is_dir($config_dir)) {
|
||||||
$source_storage = new FileStorage($config_dir);
|
$source_storage = new FileStorage($config_dir);
|
||||||
$target_storage = drupal_container()->get('config.storage');
|
$storage_comparer = new StorageComparer($source_storage, Drupal::service('config.storage'));
|
||||||
|
// Only import new config. Changed config is from previous enables and
|
||||||
// Ignore manifest files.
|
// should not be overwritten.
|
||||||
$config_changes = config_sync_get_changes($source_storage, $target_storage, FALSE);
|
$storage_comparer->addChangelistCreate();
|
||||||
if (empty($config_changes['create'])) {
|
$installer = new ConfigInstaller(
|
||||||
return;
|
$storage_comparer,
|
||||||
}
|
Drupal::service('event_dispatcher'),
|
||||||
|
Drupal::service('config.factory'),
|
||||||
// Do not overwrite or delete pre-existing configuration.
|
Drupal::entityManager(),
|
||||||
$config_changes['change'] = array();
|
Drupal::lock()
|
||||||
$config_changes['delete'] = array();
|
);
|
||||||
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
$installer->import();
|
||||||
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
|
||||||
}
|
}
|
||||||
// Exit the override free context.
|
|
||||||
config_context_leave();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,227 +144,6 @@ function config_context_leave() {
|
||||||
->leaveContext();
|
->leaveContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @param bool $use_manifest
|
|
||||||
* (optional) Whether to determine changes based on manifest files. Defaults
|
|
||||||
* to TRUE.
|
|
||||||
*
|
|
||||||
* @return array|bool
|
|
||||||
* An associative 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, $use_manifest = TRUE) {
|
|
||||||
$config_changes = array(
|
|
||||||
'create' => array(),
|
|
||||||
'change' => array(),
|
|
||||||
'delete' => array(),
|
|
||||||
);
|
|
||||||
$all_source_names = $source_storage->listAll();
|
|
||||||
$all_target_names = $target_storage->listAll();
|
|
||||||
|
|
||||||
// Config entities maintain 'manifest' files that list the objects they
|
|
||||||
// are currently handling. Each file is a simple indexed array of config
|
|
||||||
// object names. In order to generate a list of objects that have been
|
|
||||||
// created or deleted we need to open these files in both the source and
|
|
||||||
// target storage, generate an array of the objects, and compare them.
|
|
||||||
if ($use_manifest) {
|
|
||||||
$source_config_data = array();
|
|
||||||
$target_config_data = array();
|
|
||||||
foreach ($source_storage->listAll('manifest') as $name) {
|
|
||||||
if ($source_manifest_data = $source_storage->read($name)) {
|
|
||||||
$source_config_data = array_merge($source_config_data, $source_manifest_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($target_manifest_data = $target_storage->read($name)) {
|
|
||||||
$target_config_data = array_merge($target_config_data, $target_manifest_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (array_diff_key($target_config_data, $source_config_data) as $name => $value) {
|
|
||||||
$config_changes['delete'][] = $value['name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (array_diff_key($source_config_data, $target_config_data) as $name => $value) {
|
|
||||||
$config_changes['create'][] = $value['name'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$config_changes['delete'] = array_diff($all_target_names, $all_source_names);
|
|
||||||
$config_changes['create'] = array_diff($all_source_names, $all_target_names);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (array_intersect($all_source_names, $all_target_names) as $name) {
|
|
||||||
// Ignore manifest files
|
|
||||||
if (substr($name, 0, 9) != 'manifest.') {
|
|
||||||
$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) {
|
|
||||||
$target_context = drupal_container()->get('config.context.free');
|
|
||||||
$factory = drupal_container()->get('config.factory');
|
|
||||||
foreach (array('delete', 'create', 'change') as $op) {
|
|
||||||
foreach ($config_changes[$op] as $name) {
|
|
||||||
$config = new Config($name, $target_storage, $target_context);
|
|
||||||
if ($op == 'delete') {
|
|
||||||
$config->delete();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$data = $source_storage->read($name);
|
|
||||||
$config->setData($data ? $data : array());
|
|
||||||
$config->save();
|
|
||||||
}
|
|
||||||
$factory->reset($name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports configuration into the active configuration.
|
|
||||||
*
|
|
||||||
* @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 configuration.
|
|
||||||
$source_storage = drupal_container()->get('config.storage.staging');
|
|
||||||
$snapshot_storage = drupal_container()->get('config.storage.snapshot');
|
|
||||||
$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(CONFIG_IMPORT_LOCK)) {
|
|
||||||
// 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 {
|
|
||||||
// Use the override free context for config importing so that any overrides do
|
|
||||||
// not change the data on import.
|
|
||||||
config_context_enter('config.context.free');
|
|
||||||
|
|
||||||
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
|
||||||
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
|
||||||
config_import_create_snapshot($target_storage, $snapshot_storage);
|
|
||||||
|
|
||||||
// Exit the override free context.
|
|
||||||
config_context_leave();
|
|
||||||
}
|
|
||||||
catch (ConfigException $e) {
|
|
||||||
watchdog_exception('config_import', $e);
|
|
||||||
$success = FALSE;
|
|
||||||
}
|
|
||||||
lock()->release(CONFIG_IMPORT_LOCK);
|
|
||||||
|
|
||||||
return $success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a configuration snapshot following a successful import.
|
|
||||||
*
|
|
||||||
* @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_import_create_snapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
|
|
||||||
$snapshot_storage->deleteAll();
|
|
||||||
foreach ($source_storage->listAll() as $name) {
|
|
||||||
$snapshot_storage->write($name, $source_storage->read($name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
$factory = drupal_container()->get('config.factory');
|
|
||||||
// Use the admin context for config importing so that any overrides do not
|
|
||||||
// change the data on import.
|
|
||||||
$free_context = drupal_container()->get('config.context.free');
|
|
||||||
// Allow modules to take over configuration change operations for
|
|
||||||
// higher-level configuration data.
|
|
||||||
// First pass deleted, then new, and lastly changed configuration, in order to
|
|
||||||
// handle dependencies correctly.
|
|
||||||
$manager = Drupal::entityManager();
|
|
||||||
foreach (array('delete', 'create', 'change') as $op) {
|
|
||||||
foreach ($config_changes[$op] as $key => $name) {
|
|
||||||
// Call to the configuration entity's storage controller to handle the
|
|
||||||
// configuration change.
|
|
||||||
$handled_by_module = FALSE;
|
|
||||||
// Validate the configuration object name before importing it.
|
|
||||||
Config::validateName($name);
|
|
||||||
if ($entity_type = config_get_entity_type_by_name($name)) {
|
|
||||||
$old_config = new Config($name, $target_storage, $free_context);
|
|
||||||
$old_config->load();
|
|
||||||
|
|
||||||
$data = $source_storage->read($name);
|
|
||||||
$new_config = new Config($name, $source_storage, $free_context);
|
|
||||||
if ($data !== FALSE) {
|
|
||||||
$new_config->setData($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$method = 'import' . ucfirst($op);
|
|
||||||
$handled_by_module = $manager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
|
|
||||||
}
|
|
||||||
if (!empty($handled_by_module)) {
|
|
||||||
$factory->reset($name);
|
|
||||||
// Reset the manifest config object for the config entity.
|
|
||||||
$entity_info = Drupal::entityManager()->getDefinition($entity_type);
|
|
||||||
$factory->reset('manifest.' . $entity_info['config_prefix']);
|
|
||||||
unset($config_changes[$op][$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $config_changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of all config entity types provided by a module.
|
* Return a list of all config entity types provided by a module.
|
||||||
*
|
*
|
||||||
|
@ -424,6 +193,21 @@ function config_typed() {
|
||||||
return drupal_container()->get('config.typed');
|
return drupal_container()->get('config.typed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a configuration snapshot following a successful import.
|
||||||
|
*
|
||||||
|
* @param Drupal\Core\Config\StorageInterface $source_storage
|
||||||
|
* The storage to synchronize configuration from.
|
||||||
|
* @param Drupal\Core\Config\StorageInterface $snapshot_storage
|
||||||
|
* The storage to synchronize configuration to.
|
||||||
|
*/
|
||||||
|
function config_import_create_snapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
|
||||||
|
$snapshot_storage->deleteAll();
|
||||||
|
foreach ($source_storage->listAll() as $name) {
|
||||||
|
$snapshot_storage->write($name, $source_storage->read($name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a formatted diff of a named config between two storages.
|
* Return a formatted diff of a named config between two storages.
|
||||||
*
|
*
|
||||||
|
|
|
@ -96,15 +96,13 @@ class ConfigFactory {
|
||||||
*/
|
*/
|
||||||
public function reset($name = NULL) {
|
public function reset($name = NULL) {
|
||||||
if ($name) {
|
if ($name) {
|
||||||
// Reinitialize the configuration object in all contexts.
|
// Clear the cached configuration object in all contexts.
|
||||||
foreach ($this->getCacheKeys($name) as $cache_key) {
|
foreach ($this->getCacheKeys($name) as $cache_key) {
|
||||||
$this->cache[$cache_key]->init();
|
unset($this->cache[$cache_key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
foreach ($this->cache as $config) {
|
$this->cache = array();
|
||||||
$config->init();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,338 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Config\ConfigImporter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\Context\FreeConfigContext;
|
||||||
|
use Drupal\Core\Entity\EntityManager;
|
||||||
|
use Drupal\Core\Lock\LockBackendInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a configuration importer.
|
||||||
|
*
|
||||||
|
* A config importer imports the changes into the configuration system. To
|
||||||
|
* determine which changes to import a StorageComparer in used.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
*
|
||||||
|
* The ConfigImporter has a identifier which is used to construct event names.
|
||||||
|
* The events fired during an import are:
|
||||||
|
* - 'config.importer.validate': Events listening can throw a
|
||||||
|
* \Drupal\Core\Config\ConfigImporterException to prevent an import from
|
||||||
|
* occurring.
|
||||||
|
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
|
||||||
|
* - 'config.importer.import': Events listening can react to a successful import.
|
||||||
|
* @see \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\ConfigImporterEvent
|
||||||
|
*/
|
||||||
|
class ConfigImporter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name used to identify events and the lock.
|
||||||
|
*/
|
||||||
|
const ID = 'config.importer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The storage comparer used to discover configuration changes.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
*/
|
||||||
|
protected $storageComparer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event dispatcher used to notify subscribers.
|
||||||
|
*
|
||||||
|
* @var \Symfony\Component\EventDispatcher\EventDispatcher
|
||||||
|
*/
|
||||||
|
protected $eventDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration context.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\Context\ContextInterface
|
||||||
|
*/
|
||||||
|
protected $context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration factory.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\ConfigFactory
|
||||||
|
*/
|
||||||
|
protected $configFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The plugin manager for entities.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Entity\EntityManager
|
||||||
|
*/
|
||||||
|
protected $entityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The used lock backend instance.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||||
|
*/
|
||||||
|
protected $lock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of changes processed by the import().
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $processed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates changes to import have been validated.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $validated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a configuration import object.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\StorageComparerInterface $storage_comparer
|
||||||
|
* A storage comparer object used to determin configuration changes and
|
||||||
|
* access the source and target storage objects.
|
||||||
|
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||||
|
* The event dispatcher used to notify subscribers of config import events.
|
||||||
|
* @param \Drupal\Core\Config\ConfigFactory $config_factory
|
||||||
|
* The config factory that statically caches config objects.
|
||||||
|
* @param \Drupal\Core\Entity\EntityManager $entity_manager
|
||||||
|
* The entity manager used to import config entities.
|
||||||
|
* @param \Drupal\Core\Lock\LockBackendInterface
|
||||||
|
* The lock backend to ensure multiple imports do not occur at the same time.
|
||||||
|
*/
|
||||||
|
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManager $entity_manager, LockBackendInterface $lock) {
|
||||||
|
$this->storageComparer = $storage_comparer;
|
||||||
|
$this->eventDispatcher = $event_dispatcher;
|
||||||
|
$this->configFactory = $config_factory;
|
||||||
|
$this->entityManager = $entity_manager;
|
||||||
|
$this->lock = $lock;
|
||||||
|
$this->processed = $this->storageComparer->getEmptyChangelist();
|
||||||
|
// Use an override free context for importing so that overrides to do not
|
||||||
|
// pollute the imported data. The context is hard coded to ensure this is
|
||||||
|
// the case.
|
||||||
|
$this->context = new FreeConfigContext($this->eventDispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration storage comparer.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
* Storage comparer object used to calculate configuration changes.
|
||||||
|
*/
|
||||||
|
public function getStorageComparer() {
|
||||||
|
return $this->storageComparer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the storage comparer and processed list.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\ConfigImporter
|
||||||
|
* The ConfigImporter instance.
|
||||||
|
*/
|
||||||
|
public function reset() {
|
||||||
|
$this->storageComparer->reset();
|
||||||
|
$this->processed = $this->storageComparer->getEmptyChangelist();
|
||||||
|
$this->validated = FALSE;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are any unprocessed changes.
|
||||||
|
*
|
||||||
|
* @param array $ops
|
||||||
|
* The operations to check for changes. Defaults to all operations, i.e.
|
||||||
|
* array('delete', 'create', 'update').
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if there are changes to process and FALSE if not.
|
||||||
|
*/
|
||||||
|
public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')) {
|
||||||
|
foreach ($ops as $op) {
|
||||||
|
if (count($this->getUnprocessed($op))) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets list of processed changes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array containing a list of processed changes.
|
||||||
|
*/
|
||||||
|
public function getProcessed() {
|
||||||
|
return $this->processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a change as processed.
|
||||||
|
*
|
||||||
|
* @param string $op
|
||||||
|
* The change operation performed, either delete, create or update.
|
||||||
|
* @param string $name
|
||||||
|
* The name of the configuration processed.
|
||||||
|
*/
|
||||||
|
protected function setProcessed($op, $name) {
|
||||||
|
$this->processed[$op][] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of unprocessed changes for a given operation.
|
||||||
|
*
|
||||||
|
* @param string $op
|
||||||
|
* The change operation to get the unprocessed list for, either delete,
|
||||||
|
* create or update.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of configuration names.
|
||||||
|
*/
|
||||||
|
public function getUnprocessed($op) {
|
||||||
|
return array_diff($this->storageComparer->getChangelist($op), $this->processed[$op]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports the changelist to the target storage.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\Core\Config\ConfigException
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\ConfigImporter
|
||||||
|
* The ConfigImporter instance.
|
||||||
|
*/
|
||||||
|
public function import() {
|
||||||
|
if ($this->hasUnprocessedChanges()) {
|
||||||
|
// Ensure that the changes have been validated.
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
$this->configFactory->enterContext($this->context);
|
||||||
|
if (!$this->lock->acquire(static::ID)) {
|
||||||
|
// Another process is synchronizing configuration.
|
||||||
|
throw new ConfigImporterException(sprintf('%s is already importing', static::ID));
|
||||||
|
}
|
||||||
|
$this->importInvokeOwner();
|
||||||
|
$this->importConfig();
|
||||||
|
// Allow modules to react to a import.
|
||||||
|
$this->notify('import');
|
||||||
|
|
||||||
|
// The import is now complete.
|
||||||
|
$this->lock->release(static::ID);
|
||||||
|
$this->reset();
|
||||||
|
// Leave the context used during import and clear the ConfigFactory's
|
||||||
|
// static cache.
|
||||||
|
$this->configFactory->leaveContext()->reset();
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches validate event for a ConfigImporter object.
|
||||||
|
*
|
||||||
|
* Events should throw a \Drupal\Core\Config\ConfigImporterException to
|
||||||
|
* prevent an import from occurring.
|
||||||
|
*/
|
||||||
|
public function validate() {
|
||||||
|
if (!$this->validated) {
|
||||||
|
$this->notify('validate');
|
||||||
|
$this->validated = TRUE;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes an array of config changes from the source to the target storage.
|
||||||
|
*/
|
||||||
|
protected function importConfig() {
|
||||||
|
foreach (array('delete', 'create', 'update') as $op) {
|
||||||
|
foreach ($this->getUnprocessed($op) as $name) {
|
||||||
|
$config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
|
||||||
|
if ($op == 'delete') {
|
||||||
|
$config->delete();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$data = $this->storageComparer->getSourceStorage()->read($name);
|
||||||
|
$config->setData($data ? $data : array());
|
||||||
|
$config->save();
|
||||||
|
}
|
||||||
|
$this->setProcessed($op, $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes import* methods on configuration entity storage controllers.
|
||||||
|
*
|
||||||
|
* Allow modules to take over configuration change operations for higher-level
|
||||||
|
* configuration data.
|
||||||
|
*
|
||||||
|
* @todo Add support for other extension types; e.g., themes etc.
|
||||||
|
*/
|
||||||
|
protected function importInvokeOwner() {
|
||||||
|
// First pass deleted, then new, and lastly changed configuration, in order
|
||||||
|
// to handle dependencies correctly.
|
||||||
|
foreach (array('delete', 'create', 'update') as $op) {
|
||||||
|
foreach ($this->getUnprocessed($op) as $name) {
|
||||||
|
// Call to the configuration entity's storage controller to handle the
|
||||||
|
// configuration change.
|
||||||
|
$handled_by_module = FALSE;
|
||||||
|
// Validate the configuration object name before importing it.
|
||||||
|
// Config::validateName($name);
|
||||||
|
if ($entity_type = config_get_entity_type_by_name($name)) {
|
||||||
|
$old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
|
||||||
|
$old_config->load();
|
||||||
|
|
||||||
|
$data = $this->storageComparer->getSourceStorage()->read($name);
|
||||||
|
$new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
|
||||||
|
if ($data !== FALSE) {
|
||||||
|
$new_config->setData($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$method = 'import' . ucfirst($op);
|
||||||
|
$handled_by_module = $this->entityManager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
|
||||||
|
}
|
||||||
|
if (!empty($handled_by_module)) {
|
||||||
|
$this->setProcessed($op, $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a config importer event.
|
||||||
|
*
|
||||||
|
* @param string $event_name
|
||||||
|
* The name of the config importer event to dispatch.
|
||||||
|
*/
|
||||||
|
protected function notify($event_name) {
|
||||||
|
$this->eventDispatcher->dispatch(static::ID . '.' . $event_name, new ConfigImporterEvent($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a import is already running.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if an import is already running, FALSE if not.
|
||||||
|
*/
|
||||||
|
public function alreadyImporting() {
|
||||||
|
return !$this->lock->lockMayBeAvailable(static::ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identifier for events and locks.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The identifier for events and locks.
|
||||||
|
*/
|
||||||
|
public function getId() {
|
||||||
|
return static::ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Config\ConfigImporterEvent.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\Event;
|
||||||
|
|
||||||
|
class ConfigImporterEvent extends Event {
|
||||||
|
/**
|
||||||
|
* Configuration import object.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\ConfigImporter
|
||||||
|
*/
|
||||||
|
protected $configImporter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs ConfigImporterEvent.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigImporter $config_importer
|
||||||
|
* A config import object to notify listeners about.
|
||||||
|
*/
|
||||||
|
public function __construct(ConfigImporter $config_importer) {
|
||||||
|
$this->configImporter = $config_importer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the config import object.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\ConfigImporter
|
||||||
|
* The ConfigImporter object.
|
||||||
|
*/
|
||||||
|
public function getConfigImporter() {
|
||||||
|
return $this->configImporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Config\ConfigImporterException.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a config import fails.
|
||||||
|
*/
|
||||||
|
class ConfigImporterException extends ConfigException {}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Config\ConfigInstaller.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a configuration installer.
|
||||||
|
*
|
||||||
|
* A config installer imports the changes into the configuration system during
|
||||||
|
* module installs.
|
||||||
|
*
|
||||||
|
* The ConfigInstaller has a identifier which is used to construct event names.
|
||||||
|
* The events fired during an import are:
|
||||||
|
* - 'config.installer.validate': Events listening can throw a
|
||||||
|
* \Drupal\Core\Config\ConfigImporterException to prevent an import from
|
||||||
|
* occurring.
|
||||||
|
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
|
||||||
|
* - 'config.installer.import': Events listening can react to a successful import.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\ConfigImporter
|
||||||
|
*/
|
||||||
|
class ConfigInstaller extends ConfigImporter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name used to identify events and the lock.
|
||||||
|
*/
|
||||||
|
const ID = 'config.installer';
|
||||||
|
|
||||||
|
}
|
|
@ -521,7 +521,7 @@ class ConfigStorageController implements EntityStorageControllerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update configuration upon synchronizing configuration changes.
|
* Updates configuration upon synchronizing configuration changes.
|
||||||
*
|
*
|
||||||
* This callback is invoked when configuration is synchronized between storages
|
* This callback is invoked when configuration is synchronized between storages
|
||||||
* and allows a module to take over the synchronization of configuration data.
|
* and allows a module to take over the synchronization of configuration data.
|
||||||
|
@ -533,7 +533,7 @@ class ConfigStorageController implements EntityStorageControllerInterface {
|
||||||
* @param \Drupal\Core\Config\Config $old_config
|
* @param \Drupal\Core\Config\Config $old_config
|
||||||
* A configuration object containing the old configuration data.
|
* A configuration object containing the old configuration data.
|
||||||
*/
|
*/
|
||||||
public function importChange($name, Config $new_config, Config $old_config) {
|
public function importUpdate($name, Config $new_config, Config $old_config) {
|
||||||
$id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']);
|
$id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']);
|
||||||
$entities = $this->load(array($id));
|
$entities = $this->load(array($id));
|
||||||
$entity = $entities[$id];
|
$entity = $entities[$id];
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Config\StorageComparer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a config storage comparer.
|
||||||
|
*/
|
||||||
|
class StorageComparer implements StorageComparerInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source storage used to discover configuration changes.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\StorageInterface
|
||||||
|
*/
|
||||||
|
protected $sourceStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The target storage used to write configuration changes.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\StorageInterface
|
||||||
|
*/
|
||||||
|
protected $targetStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of changes to between the source storage and the target storage.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $changelist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all the configuration object names in the source storage.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\StorageComparer::getSourceNames()
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $sourceNames = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all the configuration object names in the target storage.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\StorageComparer::getTargetNames()
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $targetNames = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the Configuration storage comparer.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||||
|
* Storage controller object used to read configuration.
|
||||||
|
* @param \Drupal\Core\Config\StorageInterface $target_storage
|
||||||
|
* Storage controller object used to write configuration.
|
||||||
|
*/
|
||||||
|
public function __construct(StorageInterface $source_storage, StorageInterface $target_storage) {
|
||||||
|
$this->sourceStorage = $source_storage;
|
||||||
|
$this->targetStorage = $target_storage;
|
||||||
|
$this->changelist = $this->getEmptyChangelist();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getSourceStorage() {
|
||||||
|
return $this->sourceStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getTargetStorage() {
|
||||||
|
return $this->targetStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getEmptyChangelist() {
|
||||||
|
return array(
|
||||||
|
'create' => array(),
|
||||||
|
'update' => array(),
|
||||||
|
'delete' => array(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getChangelist($op = NULL) {
|
||||||
|
if ($op) {
|
||||||
|
return $this->changelist[$op];
|
||||||
|
}
|
||||||
|
return $this->changelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addChangeList($op, array $changes) {
|
||||||
|
// Only add changes that aren't already listed.
|
||||||
|
$changes = array_diff($changes, $this->changelist[$op]);
|
||||||
|
$this->changelist[$op] = array_merge($this->changelist[$op], $changes);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function createChangelist() {
|
||||||
|
return $this
|
||||||
|
->addChangelistCreate()
|
||||||
|
->addChangelistUpdate()
|
||||||
|
->addChangelistDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addChangelistDelete() {
|
||||||
|
return $this->addChangeList('delete', array_diff($this->getTargetNames(), $this->getSourceNames()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addChangelistCreate() {
|
||||||
|
return $this->addChangeList('create', array_diff($this->getSourceNames(), $this->getTargetNames()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addChangelistUpdate() {
|
||||||
|
foreach (array_intersect($this->getSourceNames(), $this->getTargetNames()) as $name) {
|
||||||
|
// Ignore manifest files.
|
||||||
|
if (substr($name, 0, 9) != 'manifest.') {
|
||||||
|
$source_config_data = $this->sourceStorage->read($name);
|
||||||
|
$target_config_data = $this->targetStorage->read($name);
|
||||||
|
if ($source_config_data !== $target_config_data) {
|
||||||
|
$this->addChangeList('update', array($name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function reset() {
|
||||||
|
$this->changelist = $this->getEmptyChangelist();
|
||||||
|
$this->sourceNames = $this->targetNames = array();
|
||||||
|
return $this->createChangelist();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function hasChanges($ops = array('delete', 'create', 'update')) {
|
||||||
|
foreach ($ops as $op) {
|
||||||
|
if (!empty($this->changelist[$op])) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the configuration names in the source storage.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* List of all the configuration names in the source storage.
|
||||||
|
*/
|
||||||
|
protected function getSourceNames() {
|
||||||
|
if (empty($this->sourceNames)) {
|
||||||
|
$this->sourceNames = $this->sourceStorage->listAll();
|
||||||
|
}
|
||||||
|
return $this->sourceNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the configuration names in the target storage.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* List of all the configuration names in the target storage.
|
||||||
|
*/
|
||||||
|
protected function getTargetNames() {
|
||||||
|
if (empty($this->targetNames)) {
|
||||||
|
$this->targetNames = $this->targetStorage->listAll();
|
||||||
|
}
|
||||||
|
return $this->targetNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Config\StorageComparerInterface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an interface for comparison of configuration storage objects.
|
||||||
|
*/
|
||||||
|
interface StorageComparerInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration source storage.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageInterface
|
||||||
|
* Storage controller object used to read configuration.
|
||||||
|
*/
|
||||||
|
public function getSourceStorage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration target storage.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageInterface
|
||||||
|
* Storage controller object used to write configuration.
|
||||||
|
*/
|
||||||
|
public function getTargetStorage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an empty changelist.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An empty changelist array.
|
||||||
|
*/
|
||||||
|
public function getEmptyChangelist();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of differences to import.
|
||||||
|
*
|
||||||
|
* @param string $op
|
||||||
|
* (optional) A change operation. Either delete, create or update. If
|
||||||
|
* supplied the returned list will be limited to this operation.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of config changes that are yet to be imported.
|
||||||
|
*/
|
||||||
|
public function getChangelist($op = NULL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds changes to the changelist.
|
||||||
|
*
|
||||||
|
* @param string $op
|
||||||
|
* The change operation performed. Either delete, create or update.
|
||||||
|
* @param array $changes
|
||||||
|
* Array of changes to add the changelist.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
* An object which implements the StorageComparerInterface.
|
||||||
|
*/
|
||||||
|
public function addChangeList($op, array $changes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add differences between source and target configuration storage to changelist.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
* An object which implements the StorageComparerInterface.
|
||||||
|
*/
|
||||||
|
public function createChangelist();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the delete changelist.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
* An object which implements the StorageComparerInterface.
|
||||||
|
*/
|
||||||
|
public function addChangelistDelete();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the create changelist.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
* An object which implements the StorageComparerInterface.
|
||||||
|
*/
|
||||||
|
public function addChangelistCreate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the update changelist.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
* An object which implements the StorageComparerInterface.
|
||||||
|
*/
|
||||||
|
public function addChangelistUpdate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recalculates the differences.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||||
|
* An object which implements the StorageComparerInterface.
|
||||||
|
*/
|
||||||
|
public function reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are any operations with changes to process.
|
||||||
|
*
|
||||||
|
* Until the changelist has been calculated this will always be FALSE.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\StorageComparerInterface::createChangelist().
|
||||||
|
*
|
||||||
|
* @param array $ops
|
||||||
|
* The operations to check for changes. Defaults to all operations, i.e.
|
||||||
|
* array('delete', 'create', 'update').
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if there are changes to process and FALSE if not.
|
||||||
|
*/
|
||||||
|
public function hasChanges($ops = array('delete', 'create', 'update'));
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Config\StorageComparerManifest.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a config storage comparer that uses config entity manifests.
|
||||||
|
*
|
||||||
|
* Config entities maintain 'manifest' files that list the objects they are
|
||||||
|
* currently handling. Each file is a simple indexed array of config object
|
||||||
|
* names. In order to generate a list of objects that have been created or
|
||||||
|
* deleted we need to open these files in both the source and target storage,
|
||||||
|
* generate an array of the objects, and compare them.
|
||||||
|
*/
|
||||||
|
class StorageComparerManifest extends StorageComparer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of config entities managed by manifests in the source storage.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\StorageComparerManifest::getSourceManifestData()
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $sourceManifestData = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of config entities managed by manifests in the target storage.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\StorageComparerManifest::getTargetManifestData()
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $targetManifestData = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addChangelistDelete() {
|
||||||
|
foreach (array_diff_key($this->getTargetManifestData(), $this->getSourceManifestData()) as $value) {
|
||||||
|
$this->addChangeList('delete', array($value['name']));
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addChangelistCreate() {
|
||||||
|
foreach (array_diff_key($this->getSourceManifestData(), $this->getTargetManifestData()) as $value) {
|
||||||
|
$this->addChangeList('create', array($value['name']));
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of config entities from the source storage's manifest files.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* The list of config entities in the source storage whose entity type has a
|
||||||
|
* manifest in the source storage.
|
||||||
|
*/
|
||||||
|
protected function getSourceManifestData() {
|
||||||
|
if (empty($this->sourceManifestData)) {
|
||||||
|
foreach ($this->getSourceStorage()->listAll('manifest') as $name) {
|
||||||
|
if ($source_manifest_data = $this->getSourceStorage()->read($name)) {
|
||||||
|
$this->sourceManifestData = array_merge($this->sourceManifestData, $source_manifest_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->sourceManifestData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of config entities from the target storage's manifest files.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\ConfigImporter::getSourceManifestData()
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* The list of config entities in the target storage whose entity type has a
|
||||||
|
* manifest in the source storage.
|
||||||
|
*/
|
||||||
|
protected function getTargetManifestData() {
|
||||||
|
if (empty($this->targetManifestData)) {
|
||||||
|
foreach ($this->getSourceStorage()->listAll('manifest') as $name) {
|
||||||
|
if ($target_manifest_data = $this->targetStorage->read($name)) {
|
||||||
|
$this->targetManifestData = array_merge($this->targetManifestData, $target_manifest_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->targetManifestData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function reset() {
|
||||||
|
$this->sourceManifestData = $this->targetManifestData = array();
|
||||||
|
return parent::reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\EventSubscriber\ConfigImportSubscriber.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\EventSubscriber;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\Config;
|
||||||
|
use Drupal\Core\Config\ConfigImporterEvent;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config import subscriber for config import events.
|
||||||
|
*/
|
||||||
|
class ConfigImportSubscriber implements EventSubscriberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the configuration to be imported.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigImporterEvent $event
|
||||||
|
* The Event to process.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\Core\Config\ConfigNameException
|
||||||
|
*/
|
||||||
|
public function onConfigImporterValidate(ConfigImporterEvent $event) {
|
||||||
|
foreach (array('delete', 'create', 'update') as $op) {
|
||||||
|
foreach ($event->getConfigImporter()->getUnprocessed($op) as $name) {
|
||||||
|
Config::validateName($name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the methods in this class that should be listeners.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of event listener definitions.
|
||||||
|
*/
|
||||||
|
static function getSubscribedEvents() {
|
||||||
|
$events['config.importer.validate'][] = array('onConfigImporterValidate', 40);
|
||||||
|
$events['config.installer.validate'][] = array('onConfigImporterValidate', 40);
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\EventSubscriber;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\Config;
|
||||||
|
use Drupal\Core\Config\StorageInterface;
|
||||||
|
use Drupal\Core\Config\ConfigImporterEvent;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a snapshot when config is imported.
|
||||||
|
*/
|
||||||
|
class ConfigSnapshotSubscriber implements EventSubscriberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source storage used to discover configuration changes.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\StorageInterface
|
||||||
|
*/
|
||||||
|
protected $sourceStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The snapshot storage used to write configuration changes.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\StorageInterface
|
||||||
|
*/
|
||||||
|
protected $snapshotStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the ConfigSnapshotSubscriber object.
|
||||||
|
*
|
||||||
|
* @param StorageInterface $source_storage
|
||||||
|
* The source storage used to discover configuration changes.
|
||||||
|
* @param StorageInterface $snapshot_storage
|
||||||
|
* The snapshot storage used to write configuration changes.
|
||||||
|
*/
|
||||||
|
public function __construct(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
|
||||||
|
$this->sourceStorage = $source_storage;
|
||||||
|
$this->snapshotStorage = $snapshot_storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a config snapshot.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigImporterEvent $event
|
||||||
|
* The Event to process.
|
||||||
|
*/
|
||||||
|
public function onConfigImporterImport(ConfigImporterEvent $event) {
|
||||||
|
config_import_create_snapshot($this->sourceStorage, $this->snapshotStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the methods in this class that should be listeners.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array of event listener definitions.
|
||||||
|
*/
|
||||||
|
static function getSubscribedEvents() {
|
||||||
|
$events['config.importer.import'][] = array('onConfigImporterImport', 40);
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,9 +5,12 @@
|
||||||
* Admin page callbacks for the config module.
|
* Admin page callbacks for the config module.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Drupal\Core\Config\StorageInterface;
|
|
||||||
use Drupal\Core\Ajax\AjaxResponse;
|
use Drupal\Core\Ajax\AjaxResponse;
|
||||||
use Drupal\Core\Ajax\OpenModalDialogCommand;
|
use Drupal\Core\Ajax\OpenModalDialogCommand;
|
||||||
|
use Drupal\Core\Config\ConfigException;
|
||||||
|
use Drupal\Core\Config\ConfigImporter;
|
||||||
|
use Drupal\Core\Config\StorageComparerManifest;
|
||||||
|
use Drupal\Core\Config\StorageInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to construct the storage changes in a configuration synchronization form.
|
* Helper function to construct the storage changes in a configuration synchronization form.
|
||||||
|
@ -16,12 +19,13 @@ use Drupal\Core\Ajax\OpenModalDialogCommand;
|
||||||
* The form structure to add to. Passed by reference.
|
* The form structure to add to. Passed by reference.
|
||||||
* @param array $form_state
|
* @param array $form_state
|
||||||
* The current state of the form. Passed by reference.
|
* The current state of the form. Passed by reference.
|
||||||
* @param Drupal\Core\Config\StorageInterface $source_storage
|
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||||
* The source storage to retrieve differences from.
|
* The source storage to retrieve differences from.
|
||||||
* @param Drupal\Core\Config\StorageInterface $target_storage
|
*
|
||||||
* The target storage to compare differences to.
|
* @return array
|
||||||
|
* The form with the configuration storage changes.
|
||||||
*/
|
*/
|
||||||
function config_admin_sync_form(array &$form, array &$form_state, StorageInterface $source_storage, StorageInterface $target_storage) {
|
function config_admin_sync_form(array &$form, array &$form_state, StorageInterface $source_storage) {
|
||||||
$source_list = $source_storage->listAll();
|
$source_list = $source_storage->listAll();
|
||||||
if (empty($source_list)) {
|
if (empty($source_list)) {
|
||||||
$form['no_changes'] = array(
|
$form['no_changes'] = array(
|
||||||
|
@ -31,18 +35,23 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config_changes = config_sync_get_changes($source_storage, $target_storage);
|
$config_comparer = new StorageComparerManifest(Drupal::service('config.storage.staging'), Drupal::service('config.storage'));
|
||||||
if (empty($config_changes)) {
|
if (!$config_comparer->createChangelist()->hasChanges()) {
|
||||||
$form['no_changes'] = array(
|
$form['no_changes'] = array(
|
||||||
'#markup' => t('There are no configuration changes.'),
|
'#markup' => t('There are no configuration changes.'),
|
||||||
);
|
);
|
||||||
|
$form['actions']['#access'] = FALSE;
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Store the comparer for use in the submit.
|
||||||
|
$form_state['storage_comparer'] = $config_comparer;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the AJAX library to the form for dialog support.
|
// Add the AJAX library to the form for dialog support.
|
||||||
$form['#attached']['library'][] = array('system', 'drupal.ajax');
|
$form['#attached']['library'][] = array('system', 'drupal.ajax');
|
||||||
|
|
||||||
foreach ($config_changes as $config_change_type => $config_files) {
|
foreach ($config_comparer->getChangelist() as $config_change_type => $config_files) {
|
||||||
if (empty($config_files)) {
|
if (empty($config_files)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +67,7 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa
|
||||||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new');
|
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'change':
|
case 'update':
|
||||||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed');
|
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -122,23 +131,37 @@ function config_admin_import_form($form, &$form_state) {
|
||||||
* Form submission handler for config_admin_import_form().
|
* Form submission handler for config_admin_import_form().
|
||||||
*/
|
*/
|
||||||
function config_admin_import_form_submit($form, &$form_state) {
|
function config_admin_import_form_submit($form, &$form_state) {
|
||||||
if (!lock()->lockMayBeAvailable(CONFIG_IMPORT_LOCK)) {
|
$config_importer = new ConfigImporter(
|
||||||
|
$form_state['storage_comparer'],
|
||||||
|
Drupal::service('event_dispatcher'),
|
||||||
|
Drupal::service('config.factory'),
|
||||||
|
Drupal::entityManager(),
|
||||||
|
Drupal::lock()
|
||||||
|
);
|
||||||
|
if ($config_importer->alreadyImporting()) {
|
||||||
drupal_set_message(t('Another request may be synchronizing configuration already.'));
|
drupal_set_message(t('Another request may be synchronizing configuration already.'));
|
||||||
}
|
}
|
||||||
else if (config_import()) {
|
else{
|
||||||
// Once a sync completes, we empty the staging directory. This prevents
|
try {
|
||||||
// changes from being accidentally overwritten by stray files getting
|
$config_importer->import();
|
||||||
// imported later.
|
drupal_flush_all_caches();
|
||||||
$source_storage = drupal_container()->get('config.storage.staging');
|
drupal_set_message(t('The configuration was imported successfully.'));
|
||||||
foreach ($source_storage->listAll() as $name) {
|
|
||||||
$source_storage->delete($name);
|
// Once a sync completes, we empty the staging directory. This prevents
|
||||||
|
// changes from being accidentally overwritten by stray files getting
|
||||||
|
// imported later.
|
||||||
|
$source_storage = $config_importer->getStorageComparer()->getSourceStorage();
|
||||||
|
foreach ($source_storage->listAll() as $name) {
|
||||||
|
$source_storage->delete($name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ConfigException $e) {
|
||||||
|
// 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.
|
||||||
|
watchdog_exception('config_import', $e);
|
||||||
|
drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
drupal_flush_all_caches();
|
|
||||||
|
|
||||||
drupal_set_message(t('The configuration was imported successfully.'));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,14 @@ use Drupal\simpletest\DrupalUnitTestBase;
|
||||||
* Tests CRUD operations on configuration objects.
|
* Tests CRUD operations on configuration objects.
|
||||||
*/
|
*/
|
||||||
class ConfigCRUDTest extends DrupalUnitTestBase {
|
class ConfigCRUDTest extends DrupalUnitTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules to enable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $modules = array('system');
|
||||||
|
|
||||||
public static function getInfo() {
|
public static function getInfo() {
|
||||||
return array(
|
return array(
|
||||||
'name' => 'CRUD operations',
|
'name' => 'CRUD operations',
|
||||||
|
@ -193,8 +201,15 @@ class ConfigCRUDTest extends DrupalUnitTestBase {
|
||||||
$manifest_data['new']['name'] = 'invalid';
|
$manifest_data['new']['name'] = 'invalid';
|
||||||
$staging->write('manifest.invalid_object_name', $manifest_data);
|
$staging->write('manifest.invalid_object_name', $manifest_data);
|
||||||
|
|
||||||
// Assert that config_import returns false indicating a failure.
|
// Verify that an exception is thrown when importing.
|
||||||
$this->assertFalse(config_import(), "Config import failed when trying to importing an object with an invalid name");
|
$message = 'Expected ConfigNameException was thrown when attempting to sync invalid configuration.';
|
||||||
|
try {
|
||||||
|
$this->configImporter()->import();
|
||||||
|
$this->fail($message);
|
||||||
|
}
|
||||||
|
catch (ConfigNameException $e) {
|
||||||
|
$this->pass($message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,26 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Definition of Drupal\config\Tests\ConfigImportTest.
|
* Contains \Drupal\config\Tests\ConfigImporterTest.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Drupal\config\Tests;
|
namespace Drupal\config\Tests;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\ConfigImporter;
|
||||||
|
use Drupal\Core\Config\StorageComparerManifest;
|
||||||
use Drupal\simpletest\DrupalUnitTestBase;
|
use Drupal\simpletest\DrupalUnitTestBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests importing configuration from files into active configuration.
|
* Tests importing configuration from files into active configuration.
|
||||||
*/
|
*/
|
||||||
class ConfigImportTest extends DrupalUnitTestBase {
|
class ConfigImporterTest extends DrupalUnitTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config Importer object used for testing.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\ConfigImporter
|
||||||
|
*/
|
||||||
|
protected $configImporter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modules to enable.
|
* Modules to enable.
|
||||||
|
@ -39,6 +48,18 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
||||||
// variable being used for recording hook invocations by this test already,
|
// variable being used for recording hook invocations by this test already,
|
||||||
// so it has to be cleared out manually.
|
// so it has to be cleared out manually.
|
||||||
unset($GLOBALS['hook_config_test']);
|
unset($GLOBALS['hook_config_test']);
|
||||||
|
|
||||||
|
// Set up the ConfigImporter object for testing.
|
||||||
|
$config_comparer = new StorageComparerManifest(
|
||||||
|
$this->container->get('config.storage.staging'),
|
||||||
|
$this->container->get('config.storage'));
|
||||||
|
$this->configImporter = new ConfigImporter(
|
||||||
|
$config_comparer->createChangelist(),
|
||||||
|
$this->container->get('event_dispatcher'),
|
||||||
|
$this->container->get('config.factory'),
|
||||||
|
$this->container->get('plugin.manager.entity'),
|
||||||
|
$this->container->get('lock')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,7 +91,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
||||||
// Create an empty manifest to delete the configuration object.
|
// Create an empty manifest to delete the configuration object.
|
||||||
$staging->write('manifest.config_test.dynamic', array());
|
$staging->write('manifest.config_test.dynamic', array());
|
||||||
// Import.
|
// Import.
|
||||||
config_import();
|
$this->configImporter->reset()->import();
|
||||||
|
|
||||||
// Verify the values have disappeared.
|
// Verify the values have disappeared.
|
||||||
$this->assertIdentical($storage->read($dynamic_name), FALSE);
|
$this->assertIdentical($storage->read($dynamic_name), FALSE);
|
||||||
|
@ -87,7 +108,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
||||||
$this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
|
$this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
|
||||||
|
|
||||||
// Verify that there is nothing more to import.
|
// Verify that there is nothing more to import.
|
||||||
$this->assertFalse(config_sync_get_changes($staging, $storage));
|
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,7 +144,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
||||||
$this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
|
$this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
|
||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
config_import();
|
$this->configImporter->reset()->import();
|
||||||
|
|
||||||
// Verify the values appeared.
|
// Verify the values appeared.
|
||||||
$config = config($dynamic_name);
|
$config = config($dynamic_name);
|
||||||
|
@ -138,7 +159,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
||||||
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
||||||
|
|
||||||
// Verify that there is nothing more to import.
|
// Verify that there is nothing more to import.
|
||||||
$this->assertFalse(config_sync_get_changes($staging, $storage));
|
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,7 +195,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
||||||
$this->assertIdentical($config->get('label'), 'Default');
|
$this->assertIdentical($config->get('label'), 'Default');
|
||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
config_import();
|
$this->configImporter->reset()->import();
|
||||||
|
|
||||||
// Verify the values were updated.
|
// Verify the values were updated.
|
||||||
$config = config($name);
|
$config = config($name);
|
||||||
|
@ -195,7 +216,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
||||||
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
||||||
|
|
||||||
// Verify that there is nothing more to import.
|
// Verify that there is nothing more to import.
|
||||||
$this->assertFalse(config_sync_get_changes($staging, $storage));
|
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,8 @@ class ConfigImportUITest extends WebTestBase {
|
||||||
$this->assertNoText(t('There are no configuration changes.'));
|
$this->assertNoText(t('There are no configuration changes.'));
|
||||||
|
|
||||||
// Acquire a fake-lock on the import mechanism.
|
// Acquire a fake-lock on the import mechanism.
|
||||||
lock()->acquire('config_import');
|
$config_importer_lock = $this->configImporter()->getId();
|
||||||
|
$this->container->get('lock')->acquire($config_importer_lock);
|
||||||
|
|
||||||
// Attempt to import configuration and verify that an error message appears.
|
// Attempt to import configuration and verify that an error message appears.
|
||||||
$this->drupalPost(NULL, array(), t('Import all'));
|
$this->drupalPost(NULL, array(), t('Import all'));
|
||||||
|
@ -120,7 +121,7 @@ class ConfigImportUITest extends WebTestBase {
|
||||||
$this->assertText(t('Another request may be synchronizing configuration already.'));
|
$this->assertText(t('Another request may be synchronizing configuration already.'));
|
||||||
|
|
||||||
// Release the lock, just to keep testing sane.
|
// Release the lock, just to keep testing sane.
|
||||||
lock()->release('config_import');
|
$this->container->get('lock')->release($config_importer_lock);
|
||||||
|
|
||||||
// Verify site name has not changed.
|
// Verify site name has not changed.
|
||||||
$this->assertNotEqual($new_site_name, config('system.site')->get('name'));
|
$this->assertNotEqual($new_site_name, config('system.site')->get('name'));
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
namespace Drupal\config\Tests;
|
namespace Drupal\config\Tests;
|
||||||
|
|
||||||
use Drupal\simpletest\DrupalUnitTestBase;
|
use Drupal\simpletest\DrupalUnitTestBase;
|
||||||
use Drupal\Core\Config\Context\ConfigContext;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests configuration overrides via $conf in settings.php.
|
* Tests configuration overrides via $conf in settings.php.
|
||||||
|
@ -118,7 +117,7 @@ class ConfigOverrideTest extends DrupalUnitTestBase {
|
||||||
$staging->write('config_test.system', $expected_new_data);
|
$staging->write('config_test.system', $expected_new_data);
|
||||||
|
|
||||||
// Import changed data from staging to active.
|
// Import changed data from staging to active.
|
||||||
config_import();
|
$this->configImporter()->import();
|
||||||
$data = $active->read('config_test.system');
|
$data = $active->read('config_test.system');
|
||||||
|
|
||||||
// Verify that the new configuration data exists. Have to read storage
|
// Verify that the new configuration data exists. Have to read storage
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
namespace Drupal\config\Tests;
|
namespace Drupal\config\Tests;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\StorageComparer;
|
||||||
use Drupal\simpletest\DrupalUnitTestBase;
|
use Drupal\simpletest\DrupalUnitTestBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,21 +46,24 @@ class ConfigSnapshotTest extends DrupalUnitTestBase {
|
||||||
$config_key = 'foo';
|
$config_key = 'foo';
|
||||||
$new_data = 'foobar';
|
$new_data = 'foobar';
|
||||||
|
|
||||||
|
$active_snapshot_comparer = new StorageComparer($active, $snapshot);
|
||||||
|
$staging_snapshot_comparer = new StorageComparer($staging, $snapshot);
|
||||||
|
|
||||||
// Verify that we have an initial snapshot that matches the active
|
// Verify that we have an initial snapshot that matches the active
|
||||||
// configuration. This has to be true as no config should be installed.
|
// configuration. This has to be true as no config should be installed.
|
||||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
$this->assertFalse($active_snapshot_comparer->createChangelist()->hasChanges());
|
||||||
|
|
||||||
// Install the default config.
|
// Install the default config.
|
||||||
config_install_default_config('module', 'config_test');
|
config_install_default_config('module', 'config_test');
|
||||||
// Although we have imported config this has not affected the snapshot.
|
// Although we have imported config this has not affected the snapshot.
|
||||||
$this->assertTrue(config_sync_get_changes($snapshot, $active, FALSE));
|
$this->assertTrue($active_snapshot_comparer->reset()->hasChanges());
|
||||||
|
|
||||||
// Update the config snapshot.
|
// Update the config snapshot.
|
||||||
config_import_create_snapshot($active, $snapshot);
|
config_import_create_snapshot($active, $snapshot);
|
||||||
|
|
||||||
// The snapshot and active config should now contain the same config
|
// The snapshot and active config should now contain the same config
|
||||||
// objects.
|
// objects.
|
||||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
|
||||||
|
|
||||||
// Change a configuration value in staging.
|
// Change a configuration value in staging.
|
||||||
$staging_data = config($config_name)->get();
|
$staging_data = config($config_name)->get();
|
||||||
|
@ -67,20 +71,19 @@ class ConfigSnapshotTest extends DrupalUnitTestBase {
|
||||||
$staging->write($config_name, $staging_data);
|
$staging->write($config_name, $staging_data);
|
||||||
|
|
||||||
// Verify that active and snapshot match, and that staging doesn't match
|
// Verify that active and snapshot match, and that staging doesn't match
|
||||||
// either of them.
|
// active.
|
||||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
|
||||||
$this->assertTrue(config_sync_get_changes($snapshot, $staging, FALSE));
|
$this->assertTrue($staging_snapshot_comparer->createChangelist()->hasChanges());
|
||||||
$this->assertTrue(config_sync_get_changes($staging, $active, FALSE));
|
|
||||||
|
|
||||||
// Import changed data from staging to active.
|
// Import changed data from staging to active.
|
||||||
config_import();
|
$this->configImporter()->import();
|
||||||
|
|
||||||
// Verify changed config was properly imported.
|
// Verify changed config was properly imported.
|
||||||
$this->assertIdentical(config($config_name)->get($config_key), $new_data);
|
$this->assertIdentical(config($config_name)->get($config_key), $new_data);
|
||||||
|
|
||||||
// Verify that a new snapshot was created which and that it matches
|
// Verify that a new snapshot was created which and that it matches
|
||||||
// the active config.
|
// the active config.
|
||||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,13 @@ class ConfigTestStorageController extends ConfigStorageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importChange().
|
* Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importUpdate().
|
||||||
*/
|
*/
|
||||||
public function importChange($name, Config $new_config, Config $old_config) {
|
public function importUpdate($name, Config $new_config, Config $old_config) {
|
||||||
// Set a global value we can check in test code.
|
// Set a global value we can check in test code.
|
||||||
$GLOBALS['hook_config_import'] = __METHOD__;
|
$GLOBALS['hook_config_import'] = __METHOD__;
|
||||||
|
|
||||||
return parent::importChange($name, $new_config, $old_config);
|
return parent::importUpdate($name, $new_config, $old_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -54,7 +54,7 @@ class FieldImportChangeTest extends FieldUnitTestBase {
|
||||||
$staging->write($instance_config_name, $instance);
|
$staging->write($instance_config_name, $instance);
|
||||||
|
|
||||||
// Import the content of the staging directory.
|
// Import the content of the staging directory.
|
||||||
config_import();
|
$this->configImporter()->import();
|
||||||
|
|
||||||
// Check that the updated config was correctly imported.
|
// Check that the updated config was correctly imported.
|
||||||
$instance = entity_load('field_instance', $instance_id);
|
$instance = entity_load('field_instance', $instance_id);
|
||||||
|
|
|
@ -69,7 +69,7 @@ class FieldImportCreateTest extends FieldUnitTestBase {
|
||||||
$staging->write($instance_manifest_name, $instance_manifest);
|
$staging->write($instance_manifest_name, $instance_manifest);
|
||||||
|
|
||||||
// Import the content of the staging directory.
|
// Import the content of the staging directory.
|
||||||
config_import();
|
$this->configImporter()->import();
|
||||||
|
|
||||||
// Check that the field and instance were created.
|
// Check that the field and instance were created.
|
||||||
$field = entity_load('field_entity', $field_id);
|
$field = entity_load('field_entity', $field_id);
|
||||||
|
|
|
@ -66,7 +66,7 @@ class FieldImportDeleteTest extends FieldUnitTestBase {
|
||||||
$staging->write($instance_manifest_name, $instance_manifest);
|
$staging->write($instance_manifest_name, $instance_manifest);
|
||||||
|
|
||||||
// Import the content of the staging directory.
|
// Import the content of the staging directory.
|
||||||
config_import();
|
$this->configImporter()->import();
|
||||||
|
|
||||||
// Check that the field and instance are gone.
|
// Check that the field and instance are gone.
|
||||||
$field = entity_load('field_entity', $field_id, TRUE);
|
$field = entity_load('field_entity', $field_id, TRUE);
|
||||||
|
|
|
@ -38,8 +38,8 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
|
||||||
$this->installSchema('entity_test', 'entity_test');
|
$this->installSchema('entity_test', 'entity_test');
|
||||||
$this->installSchema('field_test', array('test_entity', 'test_entity_revision', 'test_entity_bundle'));
|
$this->installSchema('field_test', array('test_entity', 'test_entity_revision', 'test_entity_bundle'));
|
||||||
|
|
||||||
// Set default storage backend.
|
// Set default storage backend and configure the theme system.
|
||||||
$this->installConfig(array('field'));
|
$this->installConfig(array('field', 'system'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -367,7 +367,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
|
||||||
unset($manifest_data[$style_name]);
|
unset($manifest_data[$style_name]);
|
||||||
$staging = $this->container->get('config.storage.staging');
|
$staging = $this->container->get('config.storage.staging');
|
||||||
$staging->write('manifest.image.style', $manifest_data);
|
$staging->write('manifest.image.style', $manifest_data);
|
||||||
config_import();
|
$this->configImporter()->import();
|
||||||
|
|
||||||
$this->assertFalse(entity_load('image_style', $style_name), 'Style deleted after config import.');
|
$this->assertFalse(entity_load('image_style', $style_name), 'Style deleted after config import.');
|
||||||
$this->assertEqual($this->getImageCount($style), 0, 'Image style was flushed after being deleted by config import.');
|
$this->assertEqual($this->getImageCount($style), 0, 'Image style was flushed after being deleted by config import.');
|
||||||
|
|
|
@ -9,6 +9,8 @@ namespace Drupal\simpletest;
|
||||||
|
|
||||||
use Drupal\Core\Database\Database;
|
use Drupal\Core\Database\Database;
|
||||||
use Drupal\Component\Utility\Settings;
|
use Drupal\Component\Utility\Settings;
|
||||||
|
use Drupal\Core\Config\ConfigImporter;
|
||||||
|
use Drupal\Core\Config\StorageComparerManifest;
|
||||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||||
use Drupal\Core\Database\ConnectionNotDefinedException;
|
use Drupal\Core\Database\ConnectionNotDefinedException;
|
||||||
use Drupal\Core\DrupalKernel;
|
use Drupal\Core\DrupalKernel;
|
||||||
|
@ -167,6 +169,13 @@ abstract class TestBase {
|
||||||
*/
|
*/
|
||||||
protected $container;
|
protected $container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The config importer that can used in a test.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Config\ConfigImporter
|
||||||
|
*/
|
||||||
|
protected $configImporter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for Test.
|
* Constructor for Test.
|
||||||
*
|
*
|
||||||
|
@ -1272,4 +1281,28 @@ abstract class TestBase {
|
||||||
public static function filePreDeleteCallback($path) {
|
public static function filePreDeleteCallback($path) {
|
||||||
chmod($path, 0700);
|
chmod($path, 0700);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ConfigImporter object to import test importing of configuration.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Config\ConfigImporter
|
||||||
|
* The ConfigImporter object.
|
||||||
|
*/
|
||||||
|
public function configImporter() {
|
||||||
|
if (!$this->configImporter) {
|
||||||
|
// Set up the ConfigImporter object for testing.
|
||||||
|
$config_comparer = new StorageComparerManifest(
|
||||||
|
$this->container->get('config.storage.staging'),
|
||||||
|
$this->container->get('config.storage'));
|
||||||
|
$this->configImporter = new ConfigImporter(
|
||||||
|
$config_comparer,
|
||||||
|
$this->container->get('event_dispatcher'),
|
||||||
|
$this->container->get('config.factory'),
|
||||||
|
$this->container->get('plugin.manager.entity'),
|
||||||
|
$this->container->get('lock')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Always recalculate the changelist when called.
|
||||||
|
return $this->configImporter->reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,13 @@ namespace Drupal\system\Tests\Database;
|
||||||
*/
|
*/
|
||||||
class RegressionTest extends DatabaseTestBase {
|
class RegressionTest extends DatabaseTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules to enable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $modules = array('node');
|
||||||
|
|
||||||
public static function getInfo() {
|
public static function getInfo() {
|
||||||
return array(
|
return array(
|
||||||
'name' => 'Regression tests',
|
'name' => 'Regression tests',
|
||||||
|
|
|
@ -25,7 +25,7 @@ class TextPlainUnitTest extends DrupalUnitTestBase {
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static $modules = array('entity', 'field', 'field_sql_storage', 'text', 'field_test');
|
public static $modules = array('system', 'entity', 'field', 'field_sql_storage', 'text', 'field_test');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains rendered content.
|
* Contains rendered content.
|
||||||
|
@ -45,7 +45,8 @@ class TextPlainUnitTest extends DrupalUnitTestBase {
|
||||||
function setUp() {
|
function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->installConfig(array('field'));
|
// Configure the theme system.
|
||||||
|
$this->installConfig(array('system', 'field'));
|
||||||
|
|
||||||
// @todo Add helper methods for all of the following.
|
// @todo Add helper methods for all of the following.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\views\Tests\ViewTestConfigInstaller.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\views\Tests;
|
||||||
|
|
||||||
|
use Drupal\Core\Config\ConfigImporter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a configuration installer.
|
||||||
|
*
|
||||||
|
* A config installer imports test views for views testing.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Core\Config\ConfigImporter
|
||||||
|
* @see \Drupal\views\Tests\ViewTestData
|
||||||
|
*/
|
||||||
|
class ViewTestConfigInstaller extends ConfigImporter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name used to identify events and the lock.
|
||||||
|
*/
|
||||||
|
const ID = 'views.test.installer';
|
||||||
|
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
namespace Drupal\views\Tests;
|
namespace Drupal\views\Tests;
|
||||||
|
|
||||||
use Drupal\Core\Config\FileStorage;
|
use Drupal\Core\Config\FileStorage;
|
||||||
|
use Drupal\Core\Config\StorageComparer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides tests view data and the base test schema with sample data records.
|
* Provides tests view data and the base test schema with sample data records.
|
||||||
|
@ -39,13 +40,6 @@ class ViewTestData {
|
||||||
$class = get_parent_class($class);
|
$class = get_parent_class($class);
|
||||||
}
|
}
|
||||||
if (!empty($views)) {
|
if (!empty($views)) {
|
||||||
$target_storage = drupal_container()->get('config.storage');
|
|
||||||
$config_changes = array(
|
|
||||||
'delete' => array(),
|
|
||||||
'create' => array(),
|
|
||||||
'change' => array(),
|
|
||||||
);
|
|
||||||
|
|
||||||
$module_handler = \Drupal::moduleHandler();
|
$module_handler = \Drupal::moduleHandler();
|
||||||
foreach ($modules as $module) {
|
foreach ($modules as $module) {
|
||||||
$config_dir = drupal_get_path('module', $module) . '/test_views';
|
$config_dir = drupal_get_path('module', $module) . '/test_views';
|
||||||
|
@ -54,16 +48,27 @@ class ViewTestData {
|
||||||
}
|
}
|
||||||
|
|
||||||
$source_storage = new FileStorage($config_dir);
|
$source_storage = new FileStorage($config_dir);
|
||||||
|
// Only import views used by test.
|
||||||
|
$views_to_import = array();
|
||||||
foreach ($source_storage->listAll('views.view.') as $config_name) {
|
foreach ($source_storage->listAll('views.view.') as $config_name) {
|
||||||
$id = str_replace('views.view.', '', $config_name);
|
$id = str_replace('views.view.', '', $config_name);
|
||||||
if (in_array($id, $views)) {
|
if (in_array($id, $views)) {
|
||||||
$config_changes['create'][] = $config_name;
|
$views_to_import[] = $config_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
$storage_comparer = new StorageComparer(
|
||||||
if (!empty($config_changes['create'])) {
|
$source_storage,
|
||||||
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
\Drupal::service('config.storage')
|
||||||
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
);
|
||||||
|
$storage_comparer->addChangelist('create', $views_to_import);
|
||||||
|
$installer = new ViewTestConfigInstaller(
|
||||||
|
$storage_comparer,
|
||||||
|
\Drupal::service('event_dispatcher'),
|
||||||
|
\Drupal::service('config.factory'),
|
||||||
|
\Drupal::entityManager(),
|
||||||
|
\Drupal::lock()
|
||||||
|
);
|
||||||
|
$installer->import();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ abstract class ViewUnitTestBase extends DrupalUnitTestBase {
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static $modules = array('views', 'views_test_config', 'views_test_data');
|
public static $modules = array('system', 'views', 'views_test_config', 'views_test_data');
|
||||||
|
|
||||||
protected function setUp() {
|
protected function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
@ -62,6 +62,9 @@ abstract class ViewUnitTestBase extends DrupalUnitTestBase {
|
||||||
}
|
}
|
||||||
$query->execute();
|
$query->execute();
|
||||||
|
|
||||||
|
// Tests implementing ViewUnitTestBase depend on the theme system being
|
||||||
|
// properly configured.
|
||||||
|
$this->installConfig(array('system'));
|
||||||
ViewTestData::importTestViews(get_class($this), array('views_test_config'));
|
ViewTestData::importTestViews(get_class($this), array('views_test_config'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue