diff --git a/core/includes/config.inc b/core/includes/config.inc index fa804ac..0e489da 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -3,11 +3,10 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigException; -use Drupal\Core\Config\ConfigInstaller; use Drupal\Core\Config\ExtensionInstallStorage; +use Drupal\Core\Config\Context\FreeConfigContext; use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\StorageInterface; -use Drupal\Core\Config\ExtensionInstallStorageComparer; use Symfony\Component\Yaml\Dumper; /** @@ -37,8 +36,6 @@ * The name of the module or theme to install default configuration for. * * @see \Drupal\Core\Config\ExtensionInstallStorage - * @see \Drupal\Core\Config\ExtensionInstallStorageComparer - * @see \Drupal\Core\Config\ConfigInstaller */ function config_install_default_config($type, $name) { // Get all default configuration owned by this extension. @@ -66,20 +63,34 @@ function ($value) use ($name) { $config_to_install = array_merge($config_to_install, $other_module_config); } if (!empty($config_to_install)) { - $storage_comparer = new ExtensionInstallStorageComparer($source_storage, \Drupal::service('config.storage')); - $storage_comparer->setSourceNames($config_to_install); - // Only import new config. Changed config is from previous enables and - // should not be overwritten. - $storage_comparer->addChangelistCreate(); - $installer = new ConfigInstaller( - $storage_comparer, - \Drupal::service('event_dispatcher'), - \Drupal::service('config.factory'), - \Drupal::entityManager(), - \Drupal::lock(), - \Drupal::service('uuid') - ); - $installer->import(); + $entity_manager = Drupal::service('entity.manager'); + $config_factory = Drupal::service('config.factory'); + $context = new FreeConfigContext(Drupal::service('event_dispatcher'), Drupal::service('uuid')); + $target_storage = Drupal::service('config.storage'); + foreach ($config_to_install as $name) { + // Only import new config. + if ($target_storage->exists($name)) { + continue; + } + + $new_config = new Config($name, $target_storage, $context); + $data = $source_storage->read($name); + if ($data !== FALSE) { + $new_config->setData($data); + } + if ($entity_type = config_get_entity_type_by_name($name)) { + $entity_manager + ->getStorageController($entity_type) + ->create($new_config->get()) + ->save(); + } + else { + $new_config->save(); + } + + // Reset static cache on the config factory. + $config_factory->reset($name); + } } } diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index ba2ece6..5e241f6 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -103,6 +103,13 @@ class ConfigImporter { protected $uuidService; /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** * Constructs a configuration import object. * * @param \Drupal\Core\Config\StorageComparerInterface $storage_comparer @@ -118,14 +125,17 @@ class ConfigImporter { * The lock backend to ensure multiple imports do not occur at the same time. * @param \Drupal\Component\Uuid\UuidInterface $uuid_service * The UUID service. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. */ - public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManager $entity_manager, LockBackendInterface $lock, UuidInterface $uuid_service) { + public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManager $entity_manager, LockBackendInterface $lock, UuidInterface $uuid_service, ModuleHandlerInterface $module_handler) { $this->storageComparer = $storage_comparer; $this->eventDispatcher = $event_dispatcher; $this->configFactory = $config_factory; $this->entityManager = $entity_manager; $this->lock = $lock; $this->uuidService = $uuid_service; + $this->moduleHandler = $module_handler; $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 @@ -221,6 +231,9 @@ public function getUnprocessed($op) { */ public function import() { if ($this->hasUnprocessedChanges()) { + + $this->importModules(); + // Ensure that the changes have been validated. $this->validate(); @@ -346,4 +359,80 @@ public function getId() { return static::ID; } + /** + * Import module list changes. + * + * @throws \Drupal\Core\Config\ConfigException + */ + public function importModules() { + // If there are no changes to 'system.module', there's nothing to do. + $module_config_name = 'system.module'; + if (!in_array($module_config_name, $this->getUnprocessed('update'))) { + return; + } + + $all_the_module_data = system_rebuild_module_data(); + $installed_modules = array_keys($this->moduleHandler->getModuleList()); + + $source_modules = $this->storageComparer + ->getSourceStorage() + ->read($module_config_name); + $source_modules = array_keys($source_modules['enabled']); + $installed_modules = array_keys($this->moduleHandler->getModuleList()); + + $modules_to_install = array_diff($source_modules, $installed_modules); + $modules_to_uninstall = array_diff($installed_modules, $source_modules); + + // Calculate our target list of modules upfront, so we can check that + // dependencies are met, both for to-be-installed and to-be-uninstalled + // modules. + $all_enabled_after_import = array_diff(array_merge($installed_modules, $modules_to_install), $modules_to_uninstall); + + if ($modules_to_install) { + // Blow up if we're being asked to install modules we can't find. + $lost_modules = array_diff($modules_to_install, array_keys($all_the_module_data)); + if ($lost_modules) { + throw new ConfigImporterException("Can't find any of these modules: " . implode(',', $lost_modules)); + } + + // Blow up if we're being asked to install any modules that have + // dependencies on modules that will remain uninstalled. + $all_dependencies = array(); + foreach ($all_enabled_after_import as $module) { + $all_dependencies += array_keys($all_the_module_data[$module]->requires); + } + if ($missing_dependencies = array_diff(array_unique($all_dependencies), $all_enabled_after_import)) { + throw new ConfigImporterException("Missing dependencies: " . implode(',', $missing_dependencies)); + } + } + + if ($modules_to_uninstall) { + // Blow up if we're being asked to uninstall modules we can't find. + $lost_modules = array_diff($modules_to_uninstall, array_keys($all_the_module_data)); + if ($lost_modules) { + throw new ConfigImporterException("Can't find any of these modules: " . implode(',', $lost_modules)); + } + + // Blow up if we're being asked to uninstall any modules that are + // dependencies of modules that will remain installed. + $all_dependents = array(); + foreach ($modules_to_uninstall as $module) { + $all_dependents += array_keys($all_the_module_data[$module]->required_by); + } + if ($orphaned_modules = array_intersect($all_dependents, $all_enabled_after_import)) { + throw new ConfigImporterException("Import would orphan these modules: " . implode(',', $orphaned_modules)); + } + } + + foreach ($modules_to_uninstall as $module) { + $this->moduleHandler->uninstall(array($module), FALSE); + } + + foreach ($modules_to_install as $module) { + $this->moduleHandler->install(array($module), FALSE); + } + + $this->setProcessed('update', $module_config_name); + } } + diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php deleted file mode 100644 index 11622b2..0000000 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ /dev/null @@ -1,33 +0,0 @@ -folders; } - - /** - * {@inheritdoc} - * - * @throws \Drupal\Core\Config\StorageException - */ - public function write($name, array $data) { - throw new StorageException('Write operation is not allowed for config extension install storage.'); - } - - /** - * {@inheritdoc} - * - * @throws \Drupal\Core\Config\StorageException - */ - public function delete($name) { - throw new StorageException('Delete operation is not allowed for config extension install storage.'); - } - - /** - * {@inheritdoc} - * - * @throws \Drupal\Core\Config\StorageException - */ - public function rename($name, $new_name) { - throw new StorageException('Rename operation is not allowed for config extension install storage.'); - } - } + diff --git a/core/lib/Drupal/Core/Config/ExtensionInstallStorageComparer.php b/core/lib/Drupal/Core/Config/ExtensionInstallStorageComparer.php deleted file mode 100644 index 0503315..0000000 --- a/core/lib/Drupal/Core/Config/ExtensionInstallStorageComparer.php +++ /dev/null @@ -1,38 +0,0 @@ -sourceNames = $source_names; - return $this; - } - - /** - * Gets all the configuration names in the source storage. - * - * @return array - * List of all the configuration names in the source storage. - * - * @see self::setSourceNames() - */ - protected function getSourceNames() { - return $this->sourceNames; - } - -} diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index da65a09..ff4e0d6 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -16,6 +16,7 @@ use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\ConfigException; use Drupal\Core\Config\ConfigFactory; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Routing\UrlGeneratorInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -26,6 +27,13 @@ class ConfigSync extends FormBase { /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** * The database lock object. * * @var \Drupal\Core\Lock\LockBackendInterface @@ -90,9 +98,11 @@ class ConfigSync extends FormBase { * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * The url generator service. * @param \Drupal\Component\Uuid\UuidInterface $uuid_service - * The UUID Service. + * The UUID Service. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. */ - public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManager $entity_manager, UrlGeneratorInterface $url_generator, UuidInterface $uuid_service) { + public function __construct(StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManager $entity_manager, UrlGeneratorInterface $url_generator, UuidInterface $uuid_service, ModuleHandlerInterface $module_handler) { $this->sourceStorage = $sourceStorage; $this->targetStorage = $targetStorage; $this->lock = $lock; @@ -101,6 +111,7 @@ public function __construct(StorageInterface $sourceStorage, StorageInterface $t $this->entity_manager = $entity_manager; $this->urlGenerator = $url_generator; $this->uuidService = $uuid_service; + $this->moduleHandler = $module_handler; } /** @@ -115,7 +126,8 @@ public static function create(ContainerInterface $container) { $container->get('config.factory'), $container->get('entity.manager'), $container->get('url_generator'), - $container->get('uuid') + $container->get('uuid'), + $container->get('module_handler') ); } @@ -219,7 +231,8 @@ public function submitForm(array &$form, array &$form_state) { $this->configFactory, $this->entity_manager, $this->lock, - $this->uuidService + $this->uuidService, + $this->moduleHandler ); if ($config_importer->alreadyImporting()) { drupal_set_message($this->t('Another request may be synchronizing configuration already.')); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php index 99fbaea..d49ef97 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php @@ -61,7 +61,8 @@ function setUp() { $this->container->get('config.factory'), $this->container->get('entity.manager'), $this->container->get('lock'), - $this->container->get('uuid') + $this->container->get('uuid'), + $this->container->get('module_handler') ); $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php index f9c79e3..1062ea5 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php @@ -52,10 +52,6 @@ function testModuleInstallation() { $config = \Drupal::config($default_configuration_entity); $this->assertIdentical($config->isNew(), FALSE); - // Verify that configuration import callback was invoked for the dynamic - // configuration entity. - $this->assertTrue($GLOBALS['hook_config_import']); - // Verify that config_test API hooks were invoked for the dynamic default // configuration entity. $this->assertFalse(isset($GLOBALS['hook_config_test']['load'])); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 7eb76fd..8503203 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -1358,7 +1358,8 @@ public function configImporter() { $this->container->get('config.factory'), $this->container->get('entity.manager'), $this->container->get('lock'), - $this->container->get('uuid') + $this->container->get('uuid'), + $this->container->get('module_handler') ); } // Always recalculate the changelist when called.