diff --git a/core/core.services.yml b/core/core.services.yml index 06981a0..438297f 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -140,7 +140,7 @@ services: arguments: ['@config.storage', '@event_dispatcher', '@config.typed'] config.installer: class: Drupal\Core\Config\ConfigInstaller - arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher'] + arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher', '@logger.channel.default'] config.storage: class: Drupal\Core\Config\CachedStorage arguments: ['@config.storage.active', '@cache.config'] diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 2161aed..27ad724 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -742,6 +742,8 @@ function install_tasks($install_state) { ), 'install_profile_themes' => array( ), + 'install_install_profile' => array( + ), 'install_import_translations' => array( 'display_name' => t('Set up translations'), 'display' => $needs_translations, @@ -1023,10 +1025,6 @@ function install_base_system(&$install_state) { // State can be set to the database now that system.module is installed. $modules = $install_state['profile_info']['dependencies']; - // The installation profile is also a module, which needs to be installed - // after all the dependencies have been installed. - $modules[] = drupal_get_profile(); - \Drupal::state()->set('install_profile_modules', array_diff($modules, array('system'))); $install_state['base_system_verified'] = TRUE; } @@ -1572,10 +1570,7 @@ function install_profile_modules(&$install_state) { // the modules. $required = array(); $non_required = array(); - // Although the profile module is marked as required, it needs to go after - // every dependency, including non-required ones. So clear its required - // flag for now to allow it to install late. - $files[$install_state['parameters']['profile']]->info['required'] = FALSE; + // Add modules that other modules depend on. foreach ($modules as $module) { if ($files[$module]->requires) { @@ -1635,6 +1630,16 @@ function install_profile_themes(&$install_state) { } /** + * Installs the install profile. + * + * @param $install_state + * An array of information about the current installation state. + */ +function install_install_profile(&$install_state) { + \Drupal::service('module_installer')->install(array(drupal_get_profile()), FALSE); +} + +/** * Prepares the system for import and downloads additional translations. * * @param $install_state diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index 1c7993b..7bd813d 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Entity\ConfigDependencyManager; use Drupal\Core\Site\Settings; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class ConfigInstaller implements ConfigInstallerInterface { @@ -57,6 +58,13 @@ class ConfigInstaller implements ConfigInstallerInterface { protected $sourceStorage; /** + * The logger. + * + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** * Is configuration being created as part of a configuration sync. * * @var bool @@ -76,13 +84,16 @@ class ConfigInstaller implements ConfigInstallerInterface { * The configuration manager. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. + * @param \Psr\Log\LoggerInterface $logger + * The logger. */ - public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher) { + public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, LoggerInterface $logger) { $this->configFactory = $config_factory; $this->activeStorages[$active_storage->getCollectionName()] = $active_storage; $this->typedConfig = $typed_config; $this->configManager = $config_manager; $this->eventDispatcher = $event_dispatcher; + $this->logger = $logger; } /** @@ -99,26 +110,11 @@ public function installDefaultConfig($type, $name) { // Gather information about all the supported collections. $collection_info = $this->configManager->getConfigCollectionInfo(); - // Read enabled extensions directly from configuration to avoid circular - // dependencies with ModuleHandler and ThemeHandler. - $extension_config = $this->configFactory->get('core.extension'); - $modules = (array) $extension_config->get('module'); - // Unless we are installing the profile, remove it from the list. - if ($install_profile = Settings::get('install_profile')) { - if ($name !== $install_profile) { - unset($modules[$install_profile]); - } - } - $enabled_extensions = array_keys($modules); - $enabled_extensions += array_keys((array) $extension_config->get('theme')); - - // Core can provide configuration. - $enabled_extensions[] = 'core'; - + $enabled_extensions = $this->getEnabledExtensions(); foreach ($collection_info->getCollectionNames(TRUE) as $collection) { $config_to_install = $this->listDefaultConfigToInstall($type, $name, $collection, $enabled_extensions); if (!empty($config_to_install)) { - $this->createConfiguration($collection, $config_to_install); + $this->createConfiguration($collection, $config_to_install, $enabled_extensions); } } @@ -146,30 +142,47 @@ public function installDefaultConfig($type, $name) { * The list of configuration objects to create. */ protected function listDefaultConfigToInstall($type, $name, $collection, array $enabled_extensions) { - // Get all default configuration owned by this extension. + // Get all default configuration owned by this extension that is provided + // by other enabled modules and merge it with all the default configuration + // that is provided by the extension that is installable. $source_storage = $this->getSourceStorage($collection); - $config_to_install = $source_storage->listAll($name . '.'); + return array_merge( + $source_storage->listAll($name . '.'), + $this->listExtensionProvidedConfig($type, $name, $collection, $enabled_extensions) + ); + } + /** + * Lists the configuration in the extension's config directory. + * + * @param string $type + * The extension type; e.g., 'module' or 'theme'. + * @param string $name + * The name of the module or theme to install default configuration for. + * @param string $collection + * The configuration collection to install. + * @param array $enabled_extensions + * A list of all the currently enabled modules and themes. + * + * @return array + * The list of configuration in the extension's config directory that can be + * installed. + */ + protected function listExtensionProvidedConfig($type, $name, $collection, array $enabled_extensions) { + $extension_provided_config = []; // If not installing the core base system default configuration, work out if // this extension provides default configuration for any other enabled // extensions. - $extension_path = drupal_get_path($type, $name); - if ($type !== 'core' && is_dir($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY)) { - $default_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection); - $extension_provided_config = array_filter($default_storage->listAll(), function ($config_name) use ($config_to_install, $enabled_extensions) { - // Ensure that we have not already discovered the config to install. - if (in_array($config_name, $config_to_install)) { - return FALSE; - } + $extension_path = $this->getDefaultConfigDirectory($type, $name); + if ($type !== 'core') { + $default_storage = new FileStorage($extension_path, $collection); + $extension_provided_config = array_filter($default_storage->listAll(), function ($config_name) use ($enabled_extensions) { // Ensure the configuration is provided by an enabled module. $provider = Unicode::substr($config_name, 0, strpos($config_name, '.')); return in_array($provider, $enabled_extensions); }); - - $config_to_install = array_merge($config_to_install, $extension_provided_config); } - - return $config_to_install; + return $extension_provided_config; } /** @@ -180,7 +193,7 @@ protected function listDefaultConfigToInstall($type, $name, $collection, array $ * @param array $config_to_install * A list of configuration object names to create. */ - protected function createConfiguration($collection, array $config_to_install) { + protected function createConfiguration($collection, array $config_to_install, array $enabled_extensions) { // Order the configuration to install in the order of dependencies. $data = $this->getSourceStorage($collection)->readMultiple($config_to_install); $config_entity_support = $this->configManager->supportsConfigurationEntities($collection); @@ -191,8 +204,14 @@ protected function createConfiguration($collection, array $config_to_install) { ->sortAll(); } - // Remove configuration that already exists in the active storage. - $config_to_install = array_diff($config_to_install, $this->getActiveStorages($collection)->listAll()); + if ($config_entity_support) { + // Get a list of all configuration that is active and will be installed + // for dependency checking. + $all_config = array_merge($config_to_install, $this->getActiveStorages($collection)->listAll()); + } + else { + $all_config = []; + } foreach ($config_to_install as $name) { // Allow config factory overriders to use a custom configuration object if @@ -208,7 +227,6 @@ protected function createConfiguration($collection, array $config_to_install) { $new_config->setData($data[$name]); } if ($config_entity_support && $entity_type = $this->configManager->getEntityTypeIdByName($name)) { - // If we are syncing do not create configuration entities. Pluggable // configuration entities can have dependencies on modules that are // not yet enabled. This approach means that any code that expects @@ -218,6 +236,14 @@ protected function createConfiguration($collection, array $config_to_install) { if ($this->isSyncing) { continue; } + // All the configuration that is provided by the extension will have + // been dependency checked already. But configuration provided by other + // extensions won't have. If this fails just ignore the configuration + // and continue. + if (!$this->validateDependencies($name, $data[$name], $enabled_extensions, $all_config)) { + $this->logger->info('Unable to create %config_name configuration because it has unmet dependencies.', array('%config_name' => $name)); + continue; + } $entity_storage = $this->configManager ->getEntityManager() ->getStorage($entity_type); @@ -244,15 +270,13 @@ protected function createConfiguration($collection, array $config_to_install) { */ public function installCollectionDefaultConfig($collection) { $config_to_install = $this->getSourceStorage($collection)->listAll(); - $extension_config = $this->configFactory->get('core.extension'); - $enabled_extensions = array_keys((array) $extension_config->get('module')); - $enabled_extensions += array_keys((array) $extension_config->get('theme')); + $enabled_extensions = $this->getEnabledExtensions(); $config_to_install = array_filter($config_to_install, function ($config_name) use ($enabled_extensions) { $provider = Unicode::substr($config_name, 0, strpos($config_name, '.')); return in_array($provider, $enabled_extensions); }); if (!empty($config_to_install)) { - $this->createConfiguration($collection, $config_to_install); + $this->createConfiguration($collection, $config_to_install, $enabled_extensions); // Reset all the static caches and list caches. $this->configFactory->reset(); } @@ -330,20 +354,30 @@ public function isSyncing() { } /** - * {@inheritdoc} + * Finds pre-existing configuration objects for the provided extension. + * + * Extensions can not be installed if configuration objects exist in the + * active storage with the same names. This can happen in a number of ways, + * commonly: + * - if a user has created configuration with the same name as that provided + * by the extension. + * - if the extension provides default configuration that does not depend on + * it and the extension has been uninstalled and is about to the + * reinstalled. + * + * @param string $type + * Type of extension to install. + * @param string $name + * Name of extension to install. + * + * @return array + * Array of configuration objects that already exist keyed by collection. */ - public function findPreExistingConfiguration($type, $name) { + protected function findPreExistingConfiguration($type, $name, array $enabled_extensions) { $existing_configuration = array(); // Gather information about all the supported collections. $collection_info = $this->configManager->getConfigCollectionInfo(); - // Read enabled extensions directly from configuration to avoid circular - // dependencies on ModuleHandler and ThemeHandler. - $extension_config = $this->configFactory->get('core.extension'); - $enabled_extensions = array_keys((array) $extension_config->get('module')); - $enabled_extensions += array_keys((array) $extension_config->get('theme')); - // Add the extension that will be enabled to the list of enabled extensions. - $enabled_extensions[] = $name; foreach ($collection_info->getCollectionNames(TRUE) as $collection) { $config_to_install = $this->listDefaultConfigToInstall($type, $name, $collection, $enabled_extensions); $active_storage = $this->getActiveStorages($collection); @@ -355,4 +389,173 @@ public function findPreExistingConfiguration($type, $name) { } return $existing_configuration; } + + /** + * {@inheritdoc} + */ + public function checkConfigurationToInstall($type, $name) { + if ($this->isSyncing()) { + // Configuration is assumed to already be checked by the config importer + // validation events + return; + } + $enabled_extensions = $this->getEnabledExtensions(); + // Add the extension that will be enabled to the list of enabled extensions. + $enabled_extensions[] = $name; + + // Check the dependencies of configuration provided by the module. + $invalid_default_config = $this->findDefaultConfigWithUnmetDependencies($type, $name, $enabled_extensions); + if (!empty($invalid_default_config)) { + throw UnmetDependenciesException::create($name, $invalid_default_config); + } + + // Install profiles can not have config clashes. Configuration that + // has the same name as a module's configuration will be used instead. + if ($name != $this->drupalGetProfile()) { + // Throw an exception if the module being installed contains configuration + // that already exits. Additionally, can not continue installing more + // modules because those may depend on the current module being installed. + $existing_configuration = $this->findPreExistingConfiguration($type, $name, $enabled_extensions); + if (!empty($existing_configuration)) { + throw PreExistingConfigException::create($name, $existing_configuration); + } + } + } + + /** + * Finds default configuration with unmet dependencies. + * + * @param string $type + * Type of extension to install. + * @param string $name + * Name of extension to install. + * @param array $enabled_extensions + * A list of all the currently enabled modules and themes. + * + * @return array + * List of configuration that has unmet dependencies + */ + protected function findDefaultConfigWithUnmetDependencies($type, $name, array $enabled_extensions) { + $default_storage = new FileStorage($this->getDefaultConfigDirectory($type, $name)); + $list = $this->listExtensionProvidedConfig($type, $name, StorageInterface::DEFAULT_COLLECTION, $enabled_extensions); + $all_config = array_merge($this->configFactory->listAll(), $list); + return array_filter($list, function($config_name) use ($default_storage, $enabled_extensions, $all_config) { + return !$this->validateDependencies($config_name, $default_storage->read($config_name), $enabled_extensions, $all_config); + }); + } + + /** + * Validates an array of config data that contains dependency information. + * + * @param string $config_name + * The name of the configuration object that is being validated. + * @param array $data + * Configuration data. + * @param array $enabled_extensions + * A list of all the currently enabled modules and themes. + * @param array $all_config + * A list of all the active configuration names. + * + * @return bool + * TRUE if the dependencies are met, FALSE if not. + */ + protected function validateDependencies($config_name, array $data, array $enabled_extensions, array $all_config) { + // All the migrate tests will fail if we check since they install the + // migrate_drupal module but only set up the dependencies for the single + // migration they are testing. + if (strpos($config_name, 'migrate.migration.') === 0) { + return TRUE; + } + if (isset($data['dependencies'])) { + $all_dependencies = $data['dependencies']; + + // Ensure enforced dependencies are included. + if (isset($all_dependencies['enforced'])) { + $all_dependencies = array_merge($all_dependencies, $data['dependencies']['enforced']); + } + + foreach ($all_dependencies as $type => $dependencies) { + $list_to_check = []; + switch ($type) { + case 'module': + case 'theme': + $list_to_check = $enabled_extensions; + break; + case 'config': + $list_to_check = $all_config; + break; + } + if (!empty($list_to_check)) { + $missing = array_diff($dependencies, $list_to_check); + if (!empty($missing)) { + return FALSE; + } + } + } + } + return TRUE; + } + + /** + * Gets the list of enabled extensions including both modules and themes. + * + * @return array + * A list of enabled extensions which includes both modules and themes. + */ + protected function getEnabledExtensions() { + // Read enabled extensions directly from configuration to avoid circular + // dependencies on ModuleHandler and ThemeHandler. + $extension_config = $this->configFactory->get('core.extension'); + $enabled_extensions = (array) $extension_config->get('module'); + $enabled_extensions += (array) $extension_config->get('theme'); + // Core can provide configuration. + $enabled_extensions['core'] = 'core'; + return array_keys($enabled_extensions); + } + + /** + * Gets an extension's default configuration directory. + * + * @param string $type + * Type of extension to install. + * @param string $name + * Name of extension to install. + * + * @return string + * The extension's default configuration directory. + */ + protected function getDefaultConfigDirectory($type, $name) { + return $this->drupalGetPath($type, $name) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY; + } + + /** + * Wrapper for drupal_get_path(). + * + * @param $type + * The type of the item; one of 'core', 'profile', 'module', 'theme', or + * 'theme_engine'. + * @param $name + * The name of the item for which the path is requested. Ignored for + * $type 'core'. + * + * @return string + * The path to the requested item or an empty string if the item is not + * found. + */ + protected function drupalGetPath($type, $name) { + return drupal_get_path($type, $name); + } + + /** + * Wrapper for drupal_get_profile(). + * + * @return string|null $profile + * The name of the installation profile or NULL if no installation profile is + * currently active. This is the case for example during the first steps of + * the installer or during unit tests. + */ + protected function drupalGetProfile() { + return drupal_get_profile(); + } + } diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php index 1d1e7e0..5a94428 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php @@ -85,25 +85,16 @@ public function setSyncing($status); public function isSyncing(); /** - * Finds pre-existing configuration objects for the provided extension. - * - * Extensions can not be installed if configuration objects exist in the - * active storage with the same names. This can happen in a number of ways, - * commonly: - * - if a user has created configuration with the same name as that provided - * by the extension. - * - if the extension provides default configuration that does not depend on - * it and the extension has been uninstalled and is about to the - * reinstalled. + * Checks the configuration that will be installed for an extension. * * @param string $type * Type of extension to install. * @param string $name * Name of extension to install. * - * @return array - * Array of configuration objects that already exist keyed by collection. + * @throws \Drupal\Core\Config\UnmetDependenciesException + * @throws \Drupal\Core\Config\PreExistingConfigException */ - public function findPreExistingConfiguration($type, $name); + public function checkConfigurationToInstall($type, $name); } diff --git a/core/lib/Drupal/Core/Config/UnmetDependenciesException.php b/core/lib/Drupal/Core/Config/UnmetDependenciesException.php new file mode 100644 index 0000000..db7ad9c --- /dev/null +++ b/core/lib/Drupal/Core/Config/UnmetDependenciesException.php @@ -0,0 +1,96 @@ +configObjects; + } + + /** + * Gets the name of the extension that is being installed. + * + * @return string + * The name of the extension that is being installed. + */ + public function getExtension() { + return $this->extension; + } + + /** + * Gets a translated message from the exception. + * + * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation + * The string translation service. + * + * @return string + */ + public function getTranslatedMessage(TranslationInterface $string_translation, $extension) { + return $string_translation->formatPlural( + count($this->getConfigObjects()), + 'Unable to install @extension, %config_names has unmet dependencies.', + 'Unable to install @extension, %config_names have unmet dependencies.', + [ + '%config_names' => implode(', ', $this->getConfigObjects()), + '@extension' => $extension, + ] + ); + } + + /** + * Creates an exception for an extension and a list of configuration objects. + * + * @param $extension + * The name of the extension that is being installed. + * @param array $config_objects + * A list of configuration objects that already exist in active + * configuration, keyed by config collection. + * + * @return \Drupal\Core\Config\PreExistingConfigException + */ + public static function create($extension, array $config_objects) { + $message = String::format('Configuration objects (@config_names) provided by @extension have unmet dependencies', + array( + '@config_names' => implode(', ', $config_objects), + '@extension' => $extension + ) + ); + $e = new static($message); + $e->configObjects = $config_objects; + $e->extension = $extension; + return $e; + } + +} diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index dba7a94..af2ab64 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -151,17 +151,9 @@ public function install(array $module_list, $enable_dependencies = TRUE) { ))); } - // Install profiles can not have config clashes. Configuration that - // has the same name as a module's configuration will be used instead. - if ($module != drupal_get_profile()) { - // Validate default configuration of this module. Bail if unable to - // install. Should not continue installing more modules because those - // may depend on this one. - $existing_configuration = $config_installer->findPreExistingConfiguration('module', $module); - if (!empty($existing_configuration)) { - throw PreExistingConfigException::create($module, $existing_configuration); - } - } + // Check the validity of the default configuration. This will throw + // exceptions if the configuration is not valid. + $config_installer->checkConfigurationToInstall('module', $module); $extension_config ->set("module.$module", 0) diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index 9529c8a..79ca84c 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -258,10 +258,7 @@ public function install(array $theme_list, $install_dependencies = TRUE) { // Validate default configuration of the theme. If there is existing // configuration then stop installing. - $existing_configuration = $this->configInstaller->findPreExistingConfiguration('theme', $key); - if (!empty($existing_configuration)) { - throw PreExistingConfigException::create($key, $existing_configuration); - } + $this->configInstaller->checkConfigurationToInstall('theme', $key); // The value is not used; the weight is ignored for themes currently. $extension_config diff --git a/core/modules/anode/anode.info.yml b/core/modules/anode/anode.info.yml new file mode 100644 index 0000000..609127d --- /dev/null +++ b/core/modules/anode/anode.info.yml @@ -0,0 +1,6 @@ +name: A-Node +type: module +description: 'The module at the beginning of the alphabet.' +package: Core +version: VERSION +core: 8.x diff --git a/core/modules/anode/anode.module b/core/modules/anode/anode.module new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/core/modules/anode/anode.module @@ -0,0 +1 @@ +install(['classy']); $entities = $this->controller->loadMultiple(); $this->assertTrue(empty($entities), 'There are no blocks initially.'); diff --git a/core/modules/block_content/src/Tests/BlockContentPageViewTest.php b/core/modules/block_content/src/Tests/BlockContentPageViewTest.php index 90adab7..0564c60 100644 --- a/core/modules/block_content/src/Tests/BlockContentPageViewTest.php +++ b/core/modules/block_content/src/Tests/BlockContentPageViewTest.php @@ -19,7 +19,7 @@ class BlockContentPageViewTest extends BlockContentTestBase { * * @var array */ - public static $modules = array('block', 'block_content', 'block_content_test'); + public static $modules = array('block_content_test'); /** * Checks block edit and fallback functionality. diff --git a/core/modules/block_content/tests/modules/block_content_test/block_content_test.info.yml b/core/modules/block_content/tests/modules/block_content_test/block_content_test.info.yml index 747efa4..16e4bc7 100644 --- a/core/modules/block_content/tests/modules/block_content_test/block_content_test.info.yml +++ b/core/modules/block_content/tests/modules/block_content_test/block_content_test.info.yml @@ -4,3 +4,5 @@ description: "Support module for custom block related testing." package: Testing version: VERSION core: 8.x +dependencies: + - block_content diff --git a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml index ed83291..4cc2360 100644 --- a/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml +++ b/core/modules/block_content/tests/modules/block_content_test/config/install/block.block.foobargorilla.yml @@ -4,7 +4,7 @@ dependencies: module: - block_content theme: - - stark + - classy id: foobargorilla theme: classy region: content diff --git a/core/modules/book/config/install/core.entity_view_mode.node.print.yml b/core/modules/book/config/install/core.entity_view_mode.node.print.yml index 512d581..de47b0c 100644 --- a/core/modules/book/config/install/core.entity_view_mode.node.print.yml +++ b/core/modules/book/config/install/core.entity_view_mode.node.print.yml @@ -5,4 +5,8 @@ cache: true targetEntityType: node dependencies: module: + - book - node + enforced: + module: + - book diff --git a/core/modules/config/src/Tests/ConfigInstallTest.php b/core/modules/config/src/Tests/ConfigInstallTest.php index 2da9e53..4cbc984 100644 --- a/core/modules/config/src/Tests/ConfigInstallTest.php +++ b/core/modules/config/src/Tests/ConfigInstallTest.php @@ -48,11 +48,8 @@ function testModuleInstallation() { $this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.schema_in_install'), 'Configuration schema for config_schema_test.schema_in_install does not exist.'); // Install the test module. - $this->enableModules(array('config_test', 'config_schema_test')); - $this->installConfig(array('config_test', 'config_schema_test')); - - // After module installation the new schema should exist. - $this->assertTrue(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.schema_in_install'), 'Configuration schema for config_schema_test.schema_in_install exists.'); + $this->enableModules(array('config_test')); + $this->installConfig(array('config_test')); // Verify that default module config exists. \Drupal::configFactory()->reset($default_config); @@ -71,6 +68,13 @@ function testModuleInstallation() { $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete'])); $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); + // Install the schema test module. + $this->enableModules(array('config_schema_test')); + $this->installConfig(array('config_schema_test')); + + // After module installation the new schema should exist. + $this->assertTrue(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.schema_in_install'), 'Configuration schema for config_schema_test.schema_in_install exists.'); + // Ensure that data type casting is applied during config installation. $config = $this->config('config_schema_test.schema_in_install'); $this->assertIdentical($config->get('integer'), 1); diff --git a/core/modules/config/src/Tests/ConfigInstallWebTest.php b/core/modules/config/src/Tests/ConfigInstallWebTest.php index b04ce39..5748d51 100644 --- a/core/modules/config/src/Tests/ConfigInstallWebTest.php +++ b/core/modules/config/src/Tests/ConfigInstallWebTest.php @@ -214,4 +214,21 @@ public function testPreExistingConfigInstall() { $this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default) provided by config_clash_test_theme already exist in active configuration'); } } + + /** + * Tests unmet dependencies detection. + */ + public function testUnmetDependenciesInstall() { + $this->drupalLogin($this->adminUser); + // We need to install separately since config_install_dependency_test does + // not depend on config_test and order is important. + $this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE), t('Save configuration')); + $this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Save configuration')); + $this->assertRaw('Unable to install Config install dependency test, config_test.dynamic.other_module_test_with_dependency has unmet dependencies.'); + + $this->drupalPostForm('admin/modules', array('modules[Testing][config_other_module_config_test][enable]' => TRUE), t('Save configuration')); + $this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Save configuration')); + $this->rebuildContainer(); + $this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); + } } diff --git a/core/modules/config/src/Tests/ConfigOtherModuleTest.php b/core/modules/config/src/Tests/ConfigOtherModuleTest.php index 082e485..886f59e 100644 --- a/core/modules/config/src/Tests/ConfigOtherModuleTest.php +++ b/core/modules/config/src/Tests/ConfigOtherModuleTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\StorageInterface; +use Drupal\Core\Config\UnmetDependenciesException; use Drupal\simpletest\WebTestBase; /** @@ -97,6 +98,29 @@ public function testUninstall() { } /** + * Tests the configuration with unmet dependencies is not installed. + */ + public function testDependencyChecking() { + $this->installModule('config_install_dependency_test'); + $this->installModule('config_test'); + $this->assertFalse(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration is ignored during install.'); + $this->uninstallModule('config_install_dependency_test'); + + try { + $this->installModule('config_install_dependency_test'); + $this->fail('Expected UnmetDependenciesException not thrown.'); + } + catch (UnmetDependenciesException $e) { + $this->assertEqual($e->getExtension(), 'config_install_dependency_test'); + $this->assertEqual($e->getConfigObjects(), ['config_test.dynamic.other_module_test_with_dependency']); + $this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.other_module_test_with_dependency) provided by config_install_dependency_test have unmet dependencies'); + } + $this->installModule('config_other_module_config_test'); + $this->installModule('config_install_dependency_test'); + $this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); + } + + /** * Installs a module. * * @param string $module diff --git a/core/modules/config/tests/config_install_dependency_test/config/install/config_test.dynamic.other_module_test_with_dependency.yml b/core/modules/config/tests/config_install_dependency_test/config/install/config_test.dynamic.other_module_test_with_dependency.yml new file mode 100644 index 0000000..69470f8 --- /dev/null +++ b/core/modules/config/tests/config_install_dependency_test/config/install/config_test.dynamic.other_module_test_with_dependency.yml @@ -0,0 +1,11 @@ +id: other_module_test_with_dependency +label: 'Other module test with dependency' +weight: 0 +style: '' +status: true +langcode: en +protected_property: Default +dependencies: + enforced: + module: + - config_other_module_config_test diff --git a/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.info.yml b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.info.yml new file mode 100644 index 0000000..e9b3c72 --- /dev/null +++ b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.info.yml @@ -0,0 +1,5 @@ +name: 'Config install dependency test' +type: module +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/node/config/install/views.view.content_recent.yml b/core/modules/node/config/install/views.view.content_recent.yml index bc12a13..4cb1dd7 100644 --- a/core/modules/node/config/install/views.view.content_recent.yml +++ b/core/modules/node/config/install/views.view.content_recent.yml @@ -423,3 +423,5 @@ display: id: block_1 display_title: Block position: 1 + display_options: + display_extenders: { } diff --git a/core/modules/system/src/Controller/ThemeController.php b/core/modules/system/src/Controller/ThemeController.php index 7126a7e..d82f908 100644 --- a/core/modules/system/src/Controller/ThemeController.php +++ b/core/modules/system/src/Controller/ThemeController.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\PreExistingConfigException; +use Drupal\Core\Config\UnmetDependenciesException; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Routing\RouteBuilderIndicatorInterface; @@ -144,6 +145,9 @@ public function install(Request $request) { 'error' ); } + catch (UnmetDependenciesException $e) { + drupal_set_message($e->getTranslatedMessage($this->getStringTranslation(), $theme), 'error'); + } return $this->redirect('system.themes_page'); } diff --git a/core/modules/system/src/Form/ModulesListConfirmForm.php b/core/modules/system/src/Form/ModulesListConfirmForm.php index b3c7977..5449b41 100644 --- a/core/modules/system/src/Form/ModulesListConfirmForm.php +++ b/core/modules/system/src/Form/ModulesListConfirmForm.php @@ -8,6 +8,7 @@ namespace Drupal\system\Form; use Drupal\Core\Config\PreExistingConfigException; +use Drupal\Core\Config\UnmetDependenciesException; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\Form\ConfirmFormBase; @@ -176,6 +177,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) { ); return; } + catch (UnmetDependenciesException $e) { + drupal_set_message( + $e->getTranslatedMessage($this->getStringTranslation(), $this->modules['install'][$e->getExtension()]), + 'error' + ); + return; + } } // Gets module list after install process, flushes caches and displays a diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index 2e80482..90a6e9f 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\PreExistingConfigException; +use Drupal\Core\Config\UnmetDependenciesException; use Drupal\Core\Controller\TitleResolverInterface; use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\Entity\EntityManagerInterface; @@ -536,6 +537,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) { ); return; } + catch (UnmetDependenciesException $e) { + drupal_set_message( + $e->getTranslatedMessage($this->getStringTranslation(), $modules['install'][$e->getExtension()]), + 'error' + ); + return; + } } // Gets module list after install process, flushes caches and displays a diff --git a/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml b/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml index 37a0bf8..05d5e27 100644 --- a/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml +++ b/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml @@ -7,3 +7,7 @@ status: true langcode: en locked: false pattern: 'U' +dependencies: + enforced: + theme: + - test_basetheme diff --git a/core/profiles/minimal/minimal.info.yml b/core/profiles/minimal/minimal.info.yml index 628dcd3..d1f83de 100644 --- a/core/profiles/minimal/minimal.info.yml +++ b/core/profiles/minimal/minimal.info.yml @@ -7,3 +7,5 @@ dependencies: - node - block - dblog +themes: + - stark diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml index a1c07b2..18c1052 100644 --- a/core/profiles/standard/standard.info.yml +++ b/core/profiles/standard/standard.info.yml @@ -5,6 +5,7 @@ version: VERSION core: 8.x dependencies: - node + - anode - history - block - breakpoint