diff --git a/core/includes/config.inc b/core/includes/config.inc index 0b83f60..42ed211 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -2,7 +2,6 @@ use Drupal\Core\Config\Config; use Drupal\Core\Config\FileStorage; -use Drupal\Core\Config\NullStorage; use Drupal\Core\Config\StorageInterface; /** @@ -24,28 +23,23 @@ * The name of the module or theme to install default configuration for. */ function config_install_default_config($type, $name) { - // If this module defines any ConfigEntity types then create an empty - // manifest file for each of them. - foreach (config_get_module_config_entities($name) as $entity_info) { + // If this module defines any ConfigEntity types, then create a manifest file + // for each of them with a listing of the objects it maintains. + foreach (config_get_module_config_entities($name) as $entity_type => $entity_info) { config('manifest.' . $entity_info['config_prefix'])->save(); } $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'); - - // Ignore manifest files. - $config_changes = config_sync_get_changes($source_storage, $target_storage, FALSE); - if (empty($config_changes['create'])) { - return; - } - - // Do not overwrite or delete pre-existing configuration. - $config_changes['change'] = array(); - $config_changes['delete'] = array(); - $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); - config_sync_changes($remaining_changes, $source_storage, $target_storage); + drupal_container()->get('config.installer')->setSourceStorage($source_storage) + // Ignore manifest files when creating changelist to import. + ->defaultChangelist(FALSE) + // Only import new config. Changed config is from previous enables and + // should not be overwritten. + ->removeChangelist('delete') + ->removeChangelist('change') + ->import(); } } @@ -100,108 +94,6 @@ function config($name) { } /** - * 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) { - $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); - 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 @@ -209,13 +101,10 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto * 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_importer = drupal_container()->get('config.importer'); - $config_changes = config_sync_get_changes($source_storage, $target_storage); - if (empty($config_changes)) { + // Retrieve a list of differences between staging and the active configuration. + if (!$config_importer->hasChanges()) { return; } @@ -230,9 +119,7 @@ function config_import() { $success = TRUE; try { - $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); + $config_importer->import(); } catch (ConfigException $e) { watchdog_exception('config_import', $e); @@ -244,67 +131,6 @@ function config_import() { } /** - * 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) { - // 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_container()->get('plugin.manager.entity'); - 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); - $old_config->load(); - - $data = $source_storage->read($name); - $new_config = new Config($name, $target_storage); - 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)) { - unset($config_changes[$op][$key]); - } - } - } - return $config_changes; -} - -/** * Return a list of all config entity types provided by a module. * * @param string $module @@ -352,3 +178,18 @@ function config_get_entity_type_by_name($name) { function 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 $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)); + } +} diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php new file mode 100644 index 0000000..be43654 --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -0,0 +1,414 @@ +serviceName = $service_name; + $this->targetStorage = $target_storage; + $this->eventDispatcher = $event_dispatcher; + } + + /* + * Sets the configuration source storage. + * + * @param \Drupal\Core\Config\StorageInterface $sourceStorage + * Storage controller object used to read configuration changes from. + * + * @return \Drupal\Core\Config\ConfigImporter + * The ConfigImporter instance. + */ + public function setSourceStorage(StorageInterface $sourceStorage) { + $this->sourceStorage = $sourceStorage; + $this->resetLists(); + return $this; + } + + /* + * Gets the configuration source storage. + * + * @return \Drupal\Core\Config\StorageInterface + * Storage controller object used to read configuration changes from. + */ + public function getSourceStorage() { + // @todo throw error if not set. + return $this->sourceStorage; + } + /** + * Gets an empty changelist. + * + * @return array + * An empty changelist array. + */ + protected function getEmptyChangelist() { + return array( + 'create' => array(), + 'change' => array(), + 'delete' => array(), + ); + } + + /** + * Gets the list of differences to import. + * + * @return array + * An array of config changes that are yet to be imported. + */ + public function getChangelist() { + return $this->changelist; + } + + /** + * Sets the list of differences to import. + * + * @param array $changelist + * An array of change operations to perform. + * + * @return \Drupal\Core\Config\ConfigImporter + * The ConfigImporter instance. + */ + public function setChangelist(array $changelist) { + $this->changelist = $changelist; + $this->validated = FALSE; + return $this; + } + + /* + * Add changes to the changelist. + * + * @param string $op + * The change operation performed. Either create, change or delete. + * @param array $changes + * Array of changes to add the changelist. + * + * @return \Drupal\Core\Config\ConfigImporter + * The ConfigImporter instance. + */ + 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); + $this->validated = FALSE; + return $this; + } + + /* + * Removes changes from the changelist. + * + * @param string $op + * The change operation to be reset. + * + * @return \Drupal\Core\Config\ConfigImporter + * The ConfigImporter instance. + */ + public function removeChangeList($op) { + $this->changelist[$op] = array(); + return $this; + } + + /* + * Resets the changelist and processed list. + * + * @return \Drupal\Core\Config\ConfigImporter + * The ConfigImporter instance. + */ + public function resetLists() { + $this->processed = $this->changelist = $this->getEmptyChangelist(); + $this->validated = FALSE; + return $this; + } + + /** + * Add differences between source and target configuration storage to changelist. + * + * @param bool $use_manifest + * Use manifest to compare storages. Defaults to TRUE. + * + * @return \Drupal\Core\Config\ConfigImporter + * The ConfigImporter instance. + */ + public function defaultChangelist($use_manifest = TRUE) { + + // Only calculate the changelist if there are no changes in the list. + if (!$this->hasChanges()) { + $all_source_names = $this->getSourceStorage()->listAll(); + $all_target_names = $this->targetStorage->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 ($this->getSourceStorage()->listAll('manifest') as $name) { + if ($source_manifest_data = $this->getSourceStorage()->read($name)) { + $source_config_data = array_merge($source_config_data, $source_manifest_data); + } + + if ($target_manifest_data = $this->targetStorage->read($name)) { + $target_config_data = array_merge($target_config_data, $target_manifest_data); + } + } + + foreach (array_diff_key($target_config_data, $source_config_data) as $value) { + $this->addChangeList('delete', array($value['name'])); + } + + foreach (array_diff_key($source_config_data, $target_config_data) as $value) { + $this->addChangeList('create', array($value['name'])); + } + } + else { + $this->addChangeList('delete', array_diff($all_target_names, $all_source_names)); + $this->addChangeList('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 = $this->getSourceStorage()->read($name); + $target_config_data = $this->targetStorage->read($name); + if ($source_config_data !== $target_config_data) { + $this->addChangeList('change', array($name)); + } + } + } + } + return $this; + } + + /** + * Checks if there is a changelist with changes to process. + * + * @return bool + * TRUE if there are changes to process and FALSE if not. + */ + public function hasChanges() { + foreach (array('delete', 'create', 'change') 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 create, change or delete. + * @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 $op + * The change operation to get the unprocessed list for. Either create, + * change or delete. + * + * @return array + * An array of configuration names. + */ + public function getUnprocessed($op) { + return array_diff($this->changelist[$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->hasChanges()) { + // Ensure that the changes have been validated. + $this->validate(); + + $this->importInvokeOwner(); + $this->importConfig(); + + // Allow modules to react to a import. + $this->notify('import'); + + // We're done. + $this->resetLists(); + } + 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. + * + * The changelist is modified as each change is processed. + */ + protected function importConfig() { + $factory = drupal_container()->get('config.factory'); + foreach (array('delete', 'create', 'change') as $op) { + foreach ($this->getUnprocessed($op) as $name) { + $config = new Config($name, $this->targetStorage); + if ($op == 'delete') { + $config->delete(); + } + else { + $data = $this->getSourceStorage()->read($name); + $config->setData($data ? $data : array()); + $config->save(); + } + $factory->reset($name); + $this->setProcessed($op, $name); + } + } + } + + /** + * Invokes import* methods on configuration entity storage controllers. + * + * The changelist is modified as each change is processed. + * + * @todo Add support for other extension types; e.g., themes etc. + */ + protected function importInvokeOwner() { + // 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_container()->get('plugin.manager.entity'); + foreach (array('delete', 'create', 'change') 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->targetStorage); + $old_config->load(); + + $data = $this->getSourceStorage()->read($name); + $new_config = new Config($name, $this->targetStorage); + 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)) { + $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($this->serviceName . '.' . $event_name, new ConfigImporterEvent($this)); + } +} diff --git a/core/lib/Drupal/Core/Config/ConfigImporterEvent.php b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php new file mode 100644 index 0000000..37e343f --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php @@ -0,0 +1,39 @@ +configImporter = $config_importer; + } + + /** + * Gets the config import object. + * + * @return \Drupal\Core\Config\ConfigImporter + * The ConfigImporter object. + */ + public function getConfigImporter() { + return $this->configImporter; + } +} diff --git a/core/lib/Drupal/Core/Config/ConfigImporterException.php b/core/lib/Drupal/Core/Config/ConfigImporterException.php new file mode 100644 index 0000000..fd18c4d --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigImporterException.php @@ -0,0 +1,13 @@ +addArgument(new Reference('config.storage')) ->addArgument(new Reference('config.storage.schema')); + // Register configuration importer and installer. + $container + ->register('config.importer', 'Drupal\Core\Config\ConfigImporter') + ->addArgument('config.importer') + ->addArgument(new Reference('config.storage')) + ->addArgument(new Reference('event_dispatcher')) + ->addMethodCall('setSourceStorage' ,array(new Reference('config.storage.staging'))) + ->addMethodCall('defaultChangelist', array()) ; + + $container + ->register('config.installer', 'Drupal\Core\Config\ConfigImporter') + ->addArgument('config.installer') + ->addArgument(new Reference('config.storage')) + ->addArgument(new Reference('event_dispatcher')); + // Register the service for the default database connection. $container->register('database', 'Drupal\Core\Database\Connection') ->setFactoryClass('Drupal\Core\Database\Database') @@ -264,6 +279,10 @@ public function build(ContainerBuilder $container) { ->addTag('event_subscriber'); $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber') ->addTag('event_subscriber'); + $container->register('config_snapshot_subscriber', 'Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber') + ->addArgument(new Reference('config.storage')) + ->addArgument(new Reference('config.storage.snapshot')) + ->addTag('event_subscriber'); $container->register('language_request_subscriber', 'Drupal\Core\EventSubscriber\LanguageRequestSubscriber') ->addArgument(new Reference('language_manager')) ->addTag('event_subscriber'); diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigSnapshotSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigSnapshotSubscriber.php new file mode 100644 index 0000000..ca06246 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ConfigSnapshotSubscriber.php @@ -0,0 +1,59 @@ +sourceStorage = $source_storage; + $this->snapshotStorage = $snapshot_storage; + } + + /** + * Create 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; + } + +} diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc index 3813885..36df209 100644 --- a/core/modules/config/config.admin.inc +++ b/core/modules/config/config.admin.inc @@ -19,7 +19,7 @@ * @param Drupal\Core\Config\StorageInterface $target_storage * The target storage to compare differences to. */ -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(); if (empty($source_list)) { $form['no_changes'] = array( @@ -29,15 +29,16 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa return $form; } - $config_changes = config_sync_get_changes($source_storage, $target_storage); - if (empty($config_changes)) { + + $config_import = drupal_container()->get('config.importer'); + if (!$config_import->hasChanges()) { $form['no_changes'] = array( '#markup' => t('There are no configuration changes.'), ); return $form; } - foreach ($config_changes as $config_change_type => $config_files) { + foreach ($config_import->getChangelist() as $config_change_type => $config_files) { if (empty($config_files)) { continue; } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php index e363577..73d2b6d 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -7,6 +7,7 @@ namespace Drupal\config\Tests; +use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\ConfigNameException; use Drupal\simpletest\DrupalUnitTestBase; @@ -14,6 +15,14 @@ * Tests CRUD operations on configuration objects. */ class ConfigCRUDTest extends DrupalUnitTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('system'); + public static function getInfo() { return array( 'name' => 'CRUD operations', @@ -178,11 +187,14 @@ function testNameValidation() { $manifest_data['new']['name'] = 'invalid'; $staging->write('manifest.invalid_object_name', $manifest_data); - // Verify that an exception is thrown when synchronizing. + // Verify that an exception is thrown when importing. $message = 'Expected ConfigNameException was thrown when attempting to sync invalid configuration.'; + $config_importer = new ConfigImporter('config.importer', $storage, $this->container->get('event_dispatcher')); try { - config_import(); - $this->fail($message); + $config_importer + ->setSourceStorage($staging) + ->defaultChangelist() + ->import(); } catch (ConfigNameException $e) { $this->pass($message); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php similarity index 87% rename from core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php rename to core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php index 9278269..8ad9750 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php @@ -2,17 +2,25 @@ /** * @file - * Definition of Drupal\config\Tests\ConfigImportTest. + * Definition of Drupal\config\Tests\ConfigImporterTest. */ namespace Drupal\config\Tests; +use Drupal\Core\Config\ConfigImporter; use Drupal\simpletest\DrupalUnitTestBase; /** * 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 $config_importer; /** * Modules to enable. @@ -39,6 +47,9 @@ function setUp() { // variable being used for recording hook invocations by this test already, // so it has to be cleared out manually. unset($GLOBALS['hook_config_test']); + + // Set up the ConfigImporter object for testing. + $this->config_importer = new ConfigImporter('config.importer', $this->container->get('config.storage'), $this->container->get('event_dispatcher')); } /** @@ -70,7 +81,10 @@ function testDeleted() { // Create an empty manifest to delete the configuration object. $staging->write('manifest.config_test.dynamic', array()); // Import. - config_import(); + $this->config_importer + ->setSourceStorage($staging) + ->defaultChangelist() + ->import(); // Verify the values have disappeared. $this->assertIdentical($storage->read($dynamic_name), FALSE); @@ -87,7 +101,7 @@ function testDeleted() { $this->assertTrue(isset($GLOBALS['hook_config_test']['delete'])); // Verify that there is nothing more to import. - $this->assertFalse(config_sync_get_changes($staging, $storage)); + $this->assertFalse($this->config_importer->hasChanges()); } /** @@ -123,7 +137,10 @@ function testNew() { $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); // Import. - config_import(); + $this->config_importer + ->setSourceStorage($staging) + ->defaultChangelist() + ->import(); // Verify the values appeared. $config = config($dynamic_name); @@ -138,7 +155,7 @@ function testNew() { $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); // Verify that there is nothing more to import. - $this->assertFalse(config_sync_get_changes($staging, $storage)); + $this->assertFalse($this->config_importer->hasChanges()); } /** @@ -174,7 +191,10 @@ function testUpdated() { $this->assertIdentical($config->get('label'), 'Default'); // Import. - config_import(); + $this->config_importer + ->setSourceStorage($staging) + ->defaultChangelist() + ->import(); // Verify the values were updated. $config = config($name); @@ -195,7 +215,7 @@ function testUpdated() { $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); // Verify that there is nothing more to import. - $this->assertFalse(config_sync_get_changes($staging, $storage)); + $this->assertFalse($this->config_importer->hasChanges()); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php index 439f191..48332d3 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php @@ -7,6 +7,7 @@ namespace Drupal\config\Tests; +use Drupal\Core\Config\ConfigImporter; use Drupal\simpletest\DrupalUnitTestBase; /** @@ -45,21 +46,24 @@ function testSnapshot() { $config_key = 'foo'; $new_data = 'foobar'; + $config_importer = new ConfigImporter('snapshot.comparer', $snapshot, drupal_container()->get('event_dispatcher')); + $config_importer->setSourceStorage($active); + // Verify that we have an initial snapshot that matches the active // configuration. This has to be true as no config should be installed. - $this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE)); + $this->assertFalse($config_importer->defaultChangelist(FALSE)->hasChanges()); // Install the default config. config_install_default_config('module', 'config_test'); // Although we have imported config this has not affected the snapshot. - $this->assertTrue(config_sync_get_changes($snapshot, $active, FALSE)); + $this->assertTrue($config_importer->resetLists()->defaultChangelist(FALSE)->hasChanges()); // Update the config snapshot. config_import_create_snapshot($active, $snapshot); // The snapshot and active config should now contain the same config // objects. - $this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE)); + $this->assertFalse($config_importer->resetLists()->defaultChangelist(FALSE)->hasChanges()); // Change a configuration value in staging. $staging_data = config($config_name)->get(); @@ -67,10 +71,9 @@ function testSnapshot() { $staging->write($config_name, $staging_data); // Verify that active and snapshot match, and that staging doesn't match - // either of them. - $this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE)); - $this->assertTrue(config_sync_get_changes($snapshot, $staging, FALSE)); - $this->assertTrue(config_sync_get_changes($staging, $active, FALSE)); + // active. + $this->assertFalse($config_importer->resetLists()->defaultChangelist(FALSE)->hasChanges()); + $this->assertTrue($config_importer->setSourceStorage($staging)->defaultChangelist(FALSE)->hasChanges()); // Import changed data from staging to active. config_import(); @@ -80,7 +83,7 @@ function testSnapshot() { // Verify that a new snapshot was created which and that it matches // the active config. - $this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE)); + $this->assertFalse($config_importer->setSourceStorage($active)->defaultChangelist(FALSE)->hasChanges()); } } diff --git a/core/modules/system/lib/Drupal/system/EventSubscriber/SystemSubscriber.php b/core/modules/system/lib/Drupal/system/EventSubscriber/SystemSubscriber.php new file mode 100644 index 0000000..74dd7a9 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/EventSubscriber/SystemSubscriber.php @@ -0,0 +1,46 @@ +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; + } + +} diff --git a/core/modules/system/lib/Drupal/system/SystemBundle.php b/core/modules/system/lib/Drupal/system/SystemBundle.php index 8662169..10147d0 100644 --- a/core/modules/system/lib/Drupal/system/SystemBundle.php +++ b/core/modules/system/lib/Drupal/system/SystemBundle.php @@ -25,5 +25,8 @@ public function build(ContainerBuilder $container) { // Register the various system plugin manager classes with the dependency // injection container. $container->register('plugin.manager.system.plugin_ui', 'Drupal\system\Plugin\Type\PluginUIManager'); + + $container->register('system.subscriber', 'Drupal\system\EventSubscriber\SystemSubscriber') + ->addTag('event_subscriber'); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php index 4ad4417..f38b49f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php @@ -12,6 +12,13 @@ */ class RegressionTest extends DatabaseTestBase { + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('node'); + public static function getInfo() { return array( 'name' => 'Regression tests', diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php b/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php index 232a847..eeef23b 100644 --- a/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php +++ b/core/modules/views/lib/Drupal/views/Tests/ViewTestData.php @@ -39,12 +39,6 @@ public static function importTestViews($class, $modules = array()) { $class = get_parent_class($class); } if (!empty($views)) { - $target_storage = drupal_container()->get('config.storage'); - $config_changes = array( - 'delete' => array(), - 'create' => array(), - 'change' => array(), - ); foreach ($modules as $module) { $config_dir = drupal_get_path('module', $module) . '/test_views'; if (!is_dir($config_dir) || !module_exists($module)) { @@ -52,16 +46,16 @@ public static function importTestViews($class, $modules = array()) { } $source_storage = new FileStorage($config_dir); + $config_importer = drupal_container()->get('config.installer')->setSourceStorage($source_storage); + // Only import views used by test. + $views_to_import = array(); foreach ($source_storage->listAll('views.view.') as $config_name) { $id = str_replace('views.view.', '', $config_name); if (in_array($id, $views)) { - $config_changes['create'][] = $config_name; + $views_to_import[] = $config_name; } } - } - if (!empty($config_changes['create'])) { - $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); - config_sync_changes($remaining_changes, $source_storage, $target_storage); + $config_importer->addChangelist('create', $views_to_import)->import(); } } }