diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 06d4f08..7e33986 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -741,6 +741,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, @@ -1022,10 +1024,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; } @@ -1571,10 +1569,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) { @@ -1634,6 +1629,24 @@ 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); + // Install all available optional config. During installation the module order + // is determined by dependencies. If there are no dependencies between modules + // then the order in which they are installed is dependent on random factors + // like PHP version. Optional configuration therefore might or might not be + // created depending on this order. Ensuring that we have installed all of the + // optional configuration whose dependencies can be met at this point removes + // any disparities that this creates. + \Drupal::service('config.installer')->installOptionalConfig(); +} + +/** * Prepares the system for import and downloads additional translations. * * @param $install_state diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 715138f..3f7db46 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -786,8 +786,7 @@ protected function processExtension($type, $op, $name) { $this->setProcessedExtension($type, $op, $name); \Drupal::service('config.installer') - ->setSyncing(FALSE) - ->resetSourceStorage(); + ->setSyncing(FALSE); } /** diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index 8065613..b77103e 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -9,7 +9,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Entity\ConfigDependencyManager; -use Drupal\Core\Site\Settings; +use Drupal\Core\Entity\EntityTypeInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class ConfigInstaller implements ConfigInstallerInterface { @@ -89,36 +89,57 @@ public function __construct(ConfigFactoryInterface $config_factory, StorageInter * {@inheritdoc} */ public function installDefaultConfig($type, $name) { - $extension_path = drupal_get_path($type, $name); + $extension_path = $this->drupalGetPath($type, $name); // Refresh the schema cache if the extension provides configuration schema // or is a theme. if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY) || $type == 'theme') { $this->typedConfig->clearCachedDefinitions(); } - // Gather information about all the supported collections. - $collection_info = $this->configManager->getConfigCollectionInfo(); + $default_install_path = $this->getDefaultConfigDirectory($type, $name); + if (is_dir($default_install_path)) { + if (!$this->isSyncing()) { + $storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION); + $prefix = ''; + } + else { + // The configuration importer sets the source storage on the config + // installer. The configuration importer handles all of the + // configuration entity imports. We only need to ensure that simple + // configuration is created when the extension is installed. + $storage = $this->getSourceStorage(); + $prefix = $name . '.'; + } - // 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]); + // Gather information about all the supported collections. + $collection_info = $this->configManager->getConfigCollectionInfo(); + foreach ($collection_info->getCollectionNames() as $collection) { + $config_to_create = $this->getConfigToCreate($storage, $collection, $prefix); + if (!empty($config_to_create)) { + $this->createConfiguration($collection, $config_to_create); + } } } - $enabled_extensions = array_keys($modules); - $enabled_extensions += array_keys((array) $extension_config->get('theme')); - - // Core can provide configuration. - $enabled_extensions[] = 'core'; - 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); + // During a drupal installation optional configuration is installed at the + // end of the installation process. + // @see install_install_profile() + if (!$this->isSyncing() && !$this->drupalInstallationAttempted()) { + $optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY; + if (is_dir($optional_install_path)) { + // Install any optional config the module provides. + $storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION); + $this->installOptionalConfig($storage, ''); + } + // Install any optional configuration entities whose type this extension + // provides. This searches all the installed modules config/optional + // directories. + $provides_config_entity_type = array_reduce($this->configManager->getEntityManager()->getDefinitions(), function ($return, EntityTypeInterface $entity_type) use ($name) { + return $return ?: $entity_type->getProvider() && $entity_type->getConfigPrefix(); + }, FALSE); + if ($provides_config_entity_type) { + $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE); + $this->installOptionalConfig($storage, $name . '.'); } } @@ -127,49 +148,56 @@ public function installDefaultConfig($type, $name) { } /** - * Lists default configuration for an extension that is available to install. - * - * This looks in the extension's config/install directory and all of the - * currently enabled extensions config/install directories for configuration - * that begins with the extension's name. - * - * @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 objects to create. + * {@inheritdoc} */ - protected function listDefaultConfigToInstall($type, $name, $collection, array $enabled_extensions) { - // Get all default configuration owned by this extension. - $source_storage = $this->getSourceStorage($collection); - $config_to_install = $source_storage->listAll($name . '.'); - - // 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; - } - // Ensure the configuration is provided by an enabled module. - $provider = Unicode::substr($config_name, 0, strpos($config_name, '.')); - return in_array($provider, $enabled_extensions); - }); + public function installOptionalConfig(StorageInterface $storage = NULL, $prefix = '') { + if (!$storage) { + $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, $this->drupalInstallationAttempted()); + } + $collection_info = $this->configManager->getConfigCollectionInfo(); + $enabled_extensions = $this->getEnabledExtensions(); + $existing_config = $this->configFactory->listAll($prefix); + foreach ($collection_info->getCollectionNames() as $collection) { + if (!$this->configManager->supportsConfigurationEntities($collection)) { + continue; + } - $config_to_install = array_merge($config_to_install, $extension_provided_config); + $config_to_create = $this->getConfigToCreate($storage, $collection, $prefix); + $all_config = array_merge($existing_config, array_keys($config_to_create)); + foreach ($config_to_create as $config_name => $data) { + // Exclude configuration that: + // - already exists + // - is a not configuration entity + // - or its dependencies cannot be met. + if (in_array($config_name, $existing_config) || + !$this->configManager->getEntityTypeIdByName($config_name) || + !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config)) { + unset($config_to_create[$config_name]); + } + } + if (!empty($config_to_create)) { + $this->createConfiguration($collection, $config_to_create, TRUE); + } } + } - return $config_to_install; + /** + * Gets configuration data from the provided storage to create. + * + * @param StorageInterface $storage + * The configuration storage to read configuration from. + * @param string $collection + * The configuration collection to use. + * @param string $prefix + * (optional) Limit to configuration starting with the provided string. + * @return array An array of configuration data read from the source storage keyed by the + * An array of configuration data read from the source storage keyed by the + */ + protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '') { + if ($storage->getCollectionName() != $collection) { + $storage = $storage->createCollection($collection); + } + return $storage->readMultiple($storage->listAll($prefix)); } /** @@ -177,24 +205,23 @@ protected function listDefaultConfigToInstall($type, $name, $collection, array $ * * @param string $collection * The configuration collection. - * @param array $config_to_install - * A list of configuration object names to create. + * @param array $config_to_create + * An array of configuration data to create, keyed by name. */ - protected function createConfiguration($collection, array $config_to_install) { + protected function createConfiguration($collection, array $config_to_create) { // 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); if ($config_entity_support) { $dependency_manager = new ConfigDependencyManager(); - $config_to_install = $dependency_manager - ->setData($data) + $config_names = $dependency_manager + ->setData($config_to_create) ->sortAll(); } + else { + $config_names = array_keys($config_to_create); + } - // Remove configuration that already exists in the active storage. - $config_to_install = array_diff($config_to_install, $this->getActiveStorages($collection)->listAll()); - - foreach ($config_to_install as $name) { + foreach ($config_names as $name) { // Allow config factory overriders to use a custom configuration object if // they are responsible for the collection. $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection); @@ -204,18 +231,17 @@ protected function createConfiguration($collection, array $config_to_install) { else { $new_config = new Config($name, $this->getActiveStorages($collection), $this->eventDispatcher, $this->typedConfig); } - if ($data[$name] !== FALSE) { - $new_config->setData($data[$name]); + if ($config_to_create[$name] !== FALSE) { + $new_config->setData($config_to_create[$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 // default configuration entities to exist will be unstable after the // module has been enabled and before the config entity has been // imported. - if ($this->isSyncing) { + if ($this->isSyncing()) { continue; } /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entity_storage */ @@ -246,16 +272,15 @@ protected function createConfiguration($collection, array $config_to_install) { * {@inheritdoc} */ 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')); - $config_to_install = array_filter($config_to_install, function ($config_name) use ($enabled_extensions) { + $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted()); + // Only install configuration for enabled extensions. + $enabled_extensions = $this->getEnabledExtensions(); + $config_to_install = array_filter($storage->listAll(), 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, $storage->readMultiple($config_to_install)); // Reset all the static caches and list caches. $this->configFactory->reset(); } @@ -270,33 +295,13 @@ public function setSourceStorage(StorageInterface $storage) { } /** - * {@inheritdoc} - */ - public function resetSourceStorage() { - $this->sourceStorage = null; - return $this; - } - - /** * Gets the configuration storage that provides the default configuration. * - * @param string $collection - * (optional) The configuration collection. Defaults to the default - * collection. - * - * @return \Drupal\Core\Config\StorageInterface + * @return \Drupal\Core\Config\StorageInterface|null * The configuration storage that provides the default configuration. + * Returns null if the source storage has not been set. */ - public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) { - if (!isset($this->sourceStorage)) { - // Default to using the ExtensionInstallStorage which searches extension's - // config directories for default configuration. Only include the profile - // configuration during Drupal installation. - $this->sourceStorage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, drupal_installation_attempted()); - } - if ($this->sourceStorage->getCollectionName() != $collection) { - $this->sourceStorage = $this->sourceStorage->createCollection($collection); - } + public function getSourceStorage() { return $this->sourceStorage; } @@ -321,6 +326,9 @@ protected function getActiveStorages($collection = StorageInterface::DEFAULT_COL * {@inheritdoc} */ public function setSyncing($status) { + if (!$status) { + $this->sourceStorage = NULL; + } $this->isSyncing = $status; return $this; } @@ -333,24 +341,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. + * + * @return array + * Array of configuration object names that already exist keyed by + * collection. */ - public function findPreExistingConfiguration($type, $name) { + protected function findPreExistingConfiguration(StorageInterface $storage) { $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); + foreach ($collection_info->getCollectionNames() as $collection) { + $config_to_create = array_keys($this->getConfigToCreate($storage, $collection)); $active_storage = $this->getActiveStorages($collection); - foreach ($config_to_install as $config_name) { + foreach ($config_to_create as $config_name) { if ($active_storage->exists($config_name)) { $existing_configuration[$collection][] = $config_name; } @@ -358,4 +372,195 @@ 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; + } + $config_install_path = $this->getDefaultConfigDirectory($type, $name); + if (!is_dir($config_install_path)) { + return; + } + + $storage = new FileStorage($config_install_path, StorageInterface::DEFAULT_COLLECTION); + + $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($storage, $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 exists. Additionally, can not continue installing more + // modules because those may depend on the current module being installed. + $existing_configuration = $this->findPreExistingConfiguration($storage); + if (!empty($existing_configuration)) { + throw PreExistingConfigException::create($name, $existing_configuration); + } + } + } + + /** + * Finds default configuration with unmet dependencies. + * + * @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(StorageInterface $storage, array $enabled_extensions) { + $config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION); + $all_config = array_merge($this->configFactory->listAll(), array_keys($config_to_create)); + return array_filter(array_keys($config_to_create), function($config_name) use ($enabled_extensions, $all_config, $config_to_create) { + return !$this->validateDependencies($config_name, $config_to_create[$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']); + unset($all_dependencies['enforced']); + } + // Ensure the configuration entity type provider is in the list of + // dependencies. + list($provider) = explode('.', $config_name, 2); + if (!isset($all_dependencies['module'])) { + $all_dependencies['module'][] = $provider; + } + elseif (!in_array($provider, $all_dependencies['module'])) { + $all_dependencies['module'][] = $provider; + } + + 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(); + } + + /** + * Wrapper for drupal_installation_attempted(). + * + * @return bool + * TRUE if a Drupal installation is currently being attempted. + */ + protected function drupalInstallationAttempted() { + return drupal_installation_attempted(); + } + } diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php index 1d1e7e0..6d8e489 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php @@ -38,6 +38,24 @@ public function installDefaultConfig($type, $name); /** + * Installs optional configuration. + * + * Optional configuration is only installed if: + * - the configuration does not exist already. + * - it's a configuration entity. + * - its dependencies can be met. + * + * @param \Drupal\Core\Config\StorageInterface + * (optional) The configuration storage to search for optional + * configuration. If not provided, all enabled extension's optional + * configuration directories will be searched. + * @param string $prefix + * (optional) If set, limits the installed configuration to only + * configuration beginning with the provided value. + */ + public function installOptionalConfig(StorageInterface $storage = NULL, $prefix = ''); + + /** * Installs all default configuration in the specified collection. * * The function is useful if the site needs to respond to an event that has @@ -60,13 +78,6 @@ public function installCollectionDefaultConfig($collection); public function setSourceStorage(StorageInterface $storage); /** - * Resets the configuration storage that provides the default configuration. - * - * @return $this - */ - public function resetSourceStorage(); - - /** * Sets the status of the isSyncing flag. * * @param bool $status @@ -85,25 +96,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/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php index d4e7d5c..06618d7 100644 --- a/core/lib/Drupal/Core/Config/InstallStorage.php +++ b/core/lib/Drupal/Core/Config/InstallStorage.php @@ -28,6 +28,11 @@ class InstallStorage extends FileStorage { const CONFIG_INSTALL_DIRECTORY = 'config/install'; /** + * Extension sub-directory containing optional configuration for installation. + */ + const CONFIG_OPTIONAL_DIRECTORY = 'config/optional'; + + /** * Extension sub-directory containing configuration schema. */ const CONFIG_SCHEMA_DIRECTORY = 'config/schema'; 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..378dc3c 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) @@ -250,12 +242,6 @@ public function install(array $module_list, $enable_dependencies = TRUE) { ->setSyncing(TRUE) ->setSourceStorage($source_storage); } - else { - // If we're not in a config synchronization reset the source storage - // so that the extension install storage will pick up the new - // configuration. - $config_installer->resetSourceStorage(); - } \Drupal::service('config.installer')->installDefaultConfig('module', $module); // If the module has no current updates, but has some that were diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index 9529c8a..a493550 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 @@ -288,16 +285,6 @@ public function install(array $theme_list, $install_dependencies = TRUE) { // Only install default configuration if this theme has not been installed // already. if (!isset($installed_themes[$key])) { - // The default config installation storage only knows about the - // currently installed list of themes, so it has to be reset in order to - // pick up the default config of the newly installed theme. However, do - // not reset the source storage when synchronizing configuration, since - // that would needlessly trigger a reload of the whole configuration to - // be imported. - if (!$this->configInstaller->isSyncing()) { - $this->configInstaller->resetSourceStorage(); - } - // Install default configuration of the theme. $this->configInstaller->installDefaultConfig('theme', $key); } diff --git a/core/modules/aggregator/config/install/views.view.aggregator_rss_feed.yml b/core/modules/aggregator/config/optional/views.view.aggregator_rss_feed.yml similarity index 100% rename from core/modules/aggregator/config/install/views.view.aggregator_rss_feed.yml rename to core/modules/aggregator/config/optional/views.view.aggregator_rss_feed.yml diff --git a/core/modules/aggregator/config/install/views.view.aggregator_sources.yml b/core/modules/aggregator/config/optional/views.view.aggregator_sources.yml similarity index 100% rename from core/modules/aggregator/config/install/views.view.aggregator_sources.yml rename to core/modules/aggregator/config/optional/views.view.aggregator_sources.yml diff --git a/core/modules/block/src/Tests/BlockStorageUnitTest.php b/core/modules/block/src/Tests/BlockStorageUnitTest.php index ff4ba29..eb3f8eb 100644 --- a/core/modules/block/src/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/src/Tests/BlockStorageUnitTest.php @@ -149,6 +149,7 @@ protected function deleteTests() { * Tests the installation of default blocks. */ public function testDefaultBlocks() { + \Drupal::service('theme_handler')->install(['classy']); $entities = $this->controller->loadMultiple(); $this->assertTrue(empty($entities), 'There are no blocks initially.'); diff --git a/core/modules/block/tests/modules/block_test/block_test.info.yml b/core/modules/block/tests/modules/block_test/block_test.info.yml index 9a8895f..fceed36 100644 --- a/core/modules/block/tests/modules/block_test/block_test.info.yml +++ b/core/modules/block/tests/modules/block_test/block_test.info.yml @@ -4,3 +4,5 @@ description: 'Provides test blocks.' package: Testing version: VERSION core: 8.x +dependencies: + - block 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/comment/config/install/views.view.comments_recent.yml b/core/modules/comment/config/optional/views.view.comments_recent.yml similarity index 100% rename from core/modules/comment/config/install/views.view.comments_recent.yml rename to core/modules/comment/config/optional/views.view.comments_recent.yml diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php index c04b939..67cd4a7 100644 --- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php @@ -42,7 +42,7 @@ protected function setUp() { // Install tables and config needed to render comments. $this->installSchema('comment', array('comment_entity_statistics')); - $this->installConfig(array('system', 'filter')); + $this->installConfig(array('system', 'filter', 'comment')); // Comment rendering generates links, so build the router. $this->installSchema('system', array('router')); diff --git a/core/modules/comment/src/Tests/CommentFieldAccessTest.php b/core/modules/comment/src/Tests/CommentFieldAccessTest.php index 78e5146..c51b47e 100644 --- a/core/modules/comment/src/Tests/CommentFieldAccessTest.php +++ b/core/modules/comment/src/Tests/CommentFieldAccessTest.php @@ -78,7 +78,7 @@ class CommentFieldAccessTest extends EntityUnitTestBase { */ protected function setUp() { parent::setUp(); - $this->installConfig(array('user')); + $this->installConfig(array('user', 'comment')); $this->installSchema('comment', array('comment_entity_statistics')); } diff --git a/core/modules/config/src/Tests/ConfigImportRecreateTest.php b/core/modules/config/src/Tests/ConfigImportRecreateTest.php index 74aea5f..83ea2ee 100644 --- a/core/modules/config/src/Tests/ConfigImportRecreateTest.php +++ b/core/modules/config/src/Tests/ConfigImportRecreateTest.php @@ -38,7 +38,7 @@ protected function setUp() { parent::setUp(); $this->installEntitySchema('node'); - $this->installConfig(array('field')); + $this->installConfig(array('field', 'node')); $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); @@ -94,8 +94,8 @@ public function testRecreateEntity() { // will be recreated. $creates = $this->configImporter->getUnprocessedConfiguration('create'); $deletes = $this->configImporter->getUnprocessedConfiguration('delete'); - $this->assertEqual(4, count($creates), 'There are 4 configuration items to create.'); - $this->assertEqual(4, count($deletes), 'There are 4 configuration items to delete.'); + $this->assertEqual(5, count($creates), 'There are 5 configuration items to create.'); + $this->assertEqual(5, count($deletes), 'There are 5 configuration items to delete.'); $this->assertEqual(0, count($this->configImporter->getUnprocessedConfiguration('update')), 'There are no configuration items to update.'); $this->assertIdentical($creates, array_reverse($deletes), 'Deletes and creates contain the same configuration names in opposite orders due to dependencies.'); diff --git a/core/modules/config/src/Tests/ConfigInstallTest.php b/core/modules/config/src/Tests/ConfigInstallTest.php index 2da9e53..6ecd266 100644 --- a/core/modules/config/src/Tests/ConfigInstallTest.php +++ b/core/modules/config/src/Tests/ConfigInstallTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\StorageInterface; +use Drupal\Core\Config\UnmetDependenciesException; use Drupal\simpletest\KernelTestBase; /** @@ -48,11 +49,7 @@ 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->installModules(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); @@ -170,8 +174,7 @@ public function testCollectionInstallationCollectionConfigEntity() { ); \Drupal::state()->set('config_collection_install_test.collection_names', $collections); // Install the test module. - $this->enableModules(array('config_test', 'config_collection_install_test')); - $this->installConfig(array('config_test')); + $this->installModules(array('config_test', 'config_collection_install_test')); /** @var \Drupal\Core\Config\StorageInterface $active_storage */ $active_storage = \Drupal::service('config.storage'); $this->assertEqual($collections, $active_storage->getAllCollectionNames()); @@ -189,4 +192,34 @@ public function testCollectionInstallationCollectionConfigEntity() { $this->assertIdentical(array('label' => 'entity'), $data); } + /** + * Tests the configuration with unmet dependencies is not installed. + */ + public function testDependencyChecking() { + $this->installModules(['config_test']); + try { + $this->installModules(['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->installModules(['config_other_module_config_test']); + $this->installModules(['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 array $modules + * The module names. + */ + protected function installModules(array $modules) { + $this->container->get('module_installer')->install($modules); + $this->container = \Drupal::getContainer(); + } + } 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..dea0e49 100644 --- a/core/modules/config/src/Tests/ConfigOtherModuleTest.php +++ b/core/modules/config/src/Tests/ConfigOtherModuleTest.php @@ -9,6 +9,8 @@ use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\StorageInterface; +use Drupal\Core\Config\UnmetDependenciesException; +use Drupal\simpletest\KernelTestBase; use Drupal\simpletest\WebTestBase; /** @@ -16,7 +18,7 @@ * * @group config */ -class ConfigOtherModuleTest extends WebTestBase { +class ConfigOtherModuleTest extends KernelTestBase { /** * Tests enabling the provider of the default configuration first. @@ -59,18 +61,10 @@ public function testInstallOtherModuleFirst() { // Default configuration provided by config_test should still exist. $this->assertTrue(entity_load('config_test', 'dotted.default', TRUE), 'The configuration is not deleted.'); - // Re-enable module to test that pre-existing default configuration throws - // an error. - $msg = "The expected PreExistingConfigException is thrown by reinstalling config_other_module_config_test."; - try { - $this->installModule('config_other_module_config_test'); - $this->fail($msg); - } - catch (PreExistingConfigException $e) { - $this->pass($msg); - $this->assertEqual($e->getExtension(), 'config_other_module_config_test'); - $this->assertEqual($e->getConfigObjects(), [StorageInterface::DEFAULT_COLLECTION => ['config_test.dynamic.other_module_test']]); - } + // Re-enable module to test that pre-existing optional configuration does + // not throw an error. + $this->installModule('config_other_module_config_test'); + $this->assertTrue(\Drupal::moduleHandler()->moduleExists('config_other_module_config_test'), 'The config_other_module_config_test module is installed.'); } /** 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/block/tests/modules/block_test/block_test.info.yml b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.info.yml similarity index 50% copy from core/modules/block/tests/modules/block_test/block_test.info.yml copy to core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.info.yml index 9a8895f..e9b3c72 100644 --- a/core/modules/block/tests/modules/block_test/block_test.info.yml +++ b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.info.yml @@ -1,6 +1,5 @@ -name: 'Block test' +name: 'Config install dependency test' type: module -description: 'Provides test blocks.' package: Testing version: VERSION core: 8.x diff --git a/core/modules/config/tests/config_other_module_config_test/config/install/config_test.dynamic.other_module_test.yml b/core/modules/config/tests/config_other_module_config_test/config/optional/config_test.dynamic.other_module_test.yml similarity index 100% rename from core/modules/config/tests/config_other_module_config_test/config/install/config_test.dynamic.other_module_test.yml rename to core/modules/config/tests/config_other_module_config_test/config/optional/config_test.dynamic.other_module_test.yml diff --git a/core/modules/contact/src/Tests/MessageEntityTest.php b/core/modules/contact/src/Tests/MessageEntityTest.php index 52c78a2..7d9b9b2 100644 --- a/core/modules/contact/src/Tests/MessageEntityTest.php +++ b/core/modules/contact/src/Tests/MessageEntityTest.php @@ -32,7 +32,7 @@ class MessageEntityTest extends EntityUnitTestBase { protected function setUp() { parent::setUp(); - $this->installConfig(array('contact')); + $this->installConfig(array('contact', 'contact_test')); } /** diff --git a/core/modules/editor/src/Tests/EditorFileUsageTest.php b/core/modules/editor/src/Tests/EditorFileUsageTest.php index 3e66cef..7b5210e 100644 --- a/core/modules/editor/src/Tests/EditorFileUsageTest.php +++ b/core/modules/editor/src/Tests/EditorFileUsageTest.php @@ -28,6 +28,7 @@ protected function setUp() { $this->installEntitySchema('file'); $this->installSchema('node', array('node_access')); $this->installSchema('file', array('file_usage')); + $this->installConfig(['node']); // Add text formats. $filtered_html_format = entity_create('filter_format', array( diff --git a/core/modules/entity_reference/tests/modules/entity_reference_test/entity_reference_test.info.yml b/core/modules/entity_reference/tests/modules/entity_reference_test/entity_reference_test.info.yml index 67a5f48..e465519 100644 --- a/core/modules/entity_reference/tests/modules/entity_reference_test/entity_reference_test.info.yml +++ b/core/modules/entity_reference/tests/modules/entity_reference_test/entity_reference_test.info.yml @@ -6,3 +6,6 @@ package: Testing version: VERSION dependencies: - entity_reference + - node + - user + - views diff --git a/core/modules/field/src/Tests/FieldImportChangeTest.php b/core/modules/field/src/Tests/FieldImportChangeTest.php index 8541664..c34ff31 100644 --- a/core/modules/field/src/Tests/FieldImportChangeTest.php +++ b/core/modules/field/src/Tests/FieldImportChangeTest.php @@ -31,6 +31,7 @@ class FieldImportChangeTest extends FieldUnitTestBase { * Tests importing an updated field. */ function testImportChange() { + $this->installConfig(['field_test_config']); $field_storage_id = 'field_test_import'; $field_id = "entity_test.entity_test.$field_storage_id"; $field_config_name = "field.field.$field_id"; diff --git a/core/modules/field/src/Tests/FieldImportDeleteTest.php b/core/modules/field/src/Tests/FieldImportDeleteTest.php index 030adfb..04eaecc 100644 --- a/core/modules/field/src/Tests/FieldImportDeleteTest.php +++ b/core/modules/field/src/Tests/FieldImportDeleteTest.php @@ -33,6 +33,7 @@ class FieldImportDeleteTest extends FieldUnitTestBase { * Tests deleting field storages and fields as part of config import. */ public function testImportDelete() { + $this->installConfig(['field_test_config']); // At this point there are 5 field configuration objects in the active // storage. // - field.storage.entity_test.field_test_import diff --git a/core/modules/field_ui/src/Tests/EntityDisplayTest.php b/core/modules/field_ui/src/Tests/EntityDisplayTest.php index ef0f476..98d2e3c 100644 --- a/core/modules/field_ui/src/Tests/EntityDisplayTest.php +++ b/core/modules/field_ui/src/Tests/EntityDisplayTest.php @@ -27,7 +27,7 @@ class EntityDisplayTest extends KernelTestBase { protected function setUp() { parent::setUp(); $this->installEntitySchema('node'); - $this->installConfig(array('field')); + $this->installConfig(array('field', 'node')); } /** diff --git a/core/modules/file/config/install/views.view.files.yml b/core/modules/file/config/optional/views.view.files.yml similarity index 100% rename from core/modules/file/config/install/views.view.files.yml rename to core/modules/file/config/optional/views.view.files.yml diff --git a/core/modules/filter/src/Tests/FilterAPITest.php b/core/modules/filter/src/Tests/FilterAPITest.php index 6ee11e2..ca36f2e 100644 --- a/core/modules/filter/src/Tests/FilterAPITest.php +++ b/core/modules/filter/src/Tests/FilterAPITest.php @@ -28,7 +28,7 @@ class FilterAPITest extends EntityUnitTestBase { protected function setUp() { parent::setUp(); - $this->installConfig(array('system', 'filter')); + $this->installConfig(array('system', 'filter', 'filter_test')); } /** diff --git a/core/modules/filter/tests/filter_test/filter_test.info.yml b/core/modules/filter/tests/filter_test/filter_test.info.yml index dcefe8e..6e377bc 100644 --- a/core/modules/filter/tests/filter_test/filter_test.info.yml +++ b/core/modules/filter/tests/filter_test/filter_test.info.yml @@ -4,3 +4,5 @@ description: 'Tests filter hooks and functions.' package: Testing version: VERSION core: 8.x +dependencies: + - filter diff --git a/core/modules/forum/config/install/rdf.mapping.node.forum.yml b/core/modules/forum/config/optional/rdf.mapping.node.forum.yml similarity index 100% rename from core/modules/forum/config/install/rdf.mapping.node.forum.yml rename to core/modules/forum/config/optional/rdf.mapping.node.forum.yml diff --git a/core/modules/forum/config/install/rdf.mapping.taxonomy_term.forums.yml b/core/modules/forum/config/optional/rdf.mapping.taxonomy_term.forums.yml similarity index 100% rename from core/modules/forum/config/install/rdf.mapping.taxonomy_term.forums.yml rename to core/modules/forum/config/optional/rdf.mapping.taxonomy_term.forums.yml diff --git a/core/modules/hal/src/Tests/EntityTest.php b/core/modules/hal/src/Tests/EntityTest.php index a1b221d..9fb1014 100644 --- a/core/modules/hal/src/Tests/EntityTest.php +++ b/core/modules/hal/src/Tests/EntityTest.php @@ -35,6 +35,7 @@ protected function setUp() { $this->installSchema('system', array('sequences')); $this->installSchema('comment', array('comment_entity_statistics')); $this->installEntitySchema('taxonomy_term'); + $this->installConfig(['node', 'comment']); } /** diff --git a/core/modules/language/config/install/tour.tour.language-add.yml b/core/modules/language/config/optional/tour.tour.language-add.yml similarity index 100% rename from core/modules/language/config/install/tour.tour.language-add.yml rename to core/modules/language/config/optional/tour.tour.language-add.yml diff --git a/core/modules/language/config/install/tour.tour.language-edit.yml b/core/modules/language/config/optional/tour.tour.language-edit.yml similarity index 100% rename from core/modules/language/config/install/tour.tour.language-edit.yml rename to core/modules/language/config/optional/tour.tour.language-edit.yml diff --git a/core/modules/language/config/install/tour.tour.language.yml b/core/modules/language/config/optional/tour.tour.language.yml similarity index 100% rename from core/modules/language/config/install/tour.tour.language.yml rename to core/modules/language/config/optional/tour.tour.language.yml diff --git a/core/modules/locale/config/install/tour.tour.locale.yml b/core/modules/locale/config/optional/tour.tour.locale.yml similarity index 100% rename from core/modules/locale/config/install/tour.tour.locale.yml rename to core/modules/locale/config/optional/tour.tour.locale.yml diff --git a/core/modules/node/config/install/search.page.node_search.yml b/core/modules/node/config/optional/search.page.node_search.yml similarity index 100% rename from core/modules/node/config/install/search.page.node_search.yml rename to core/modules/node/config/optional/search.page.node_search.yml diff --git a/core/modules/node/config/install/views.view.archive.yml b/core/modules/node/config/optional/views.view.archive.yml similarity index 100% rename from core/modules/node/config/install/views.view.archive.yml rename to core/modules/node/config/optional/views.view.archive.yml diff --git a/core/modules/node/config/install/views.view.content.yml b/core/modules/node/config/optional/views.view.content.yml similarity index 100% rename from core/modules/node/config/install/views.view.content.yml rename to core/modules/node/config/optional/views.view.content.yml diff --git a/core/modules/node/config/install/views.view.content_recent.yml b/core/modules/node/config/optional/views.view.content_recent.yml similarity index 99% rename from core/modules/node/config/install/views.view.content_recent.yml rename to core/modules/node/config/optional/views.view.content_recent.yml index 0fb273d..ce2093c 100644 --- a/core/modules/node/config/install/views.view.content_recent.yml +++ b/core/modules/node/config/optional/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/node/config/install/views.view.frontpage.yml b/core/modules/node/config/optional/views.view.frontpage.yml similarity index 100% rename from core/modules/node/config/install/views.view.frontpage.yml rename to core/modules/node/config/optional/views.view.frontpage.yml diff --git a/core/modules/node/config/install/views.view.glossary.yml b/core/modules/node/config/optional/views.view.glossary.yml similarity index 100% rename from core/modules/node/config/install/views.view.glossary.yml rename to core/modules/node/config/optional/views.view.glossary.yml diff --git a/core/modules/node/src/Tests/NodeBodyFieldStorageTest.php b/core/modules/node/src/Tests/NodeBodyFieldStorageTest.php index bdfa24c..54f83f2 100644 --- a/core/modules/node/src/Tests/NodeBodyFieldStorageTest.php +++ b/core/modules/node/src/Tests/NodeBodyFieldStorageTest.php @@ -36,7 +36,7 @@ protected function setUp() { $this->installSchema('user', 'users_data'); $this->installEntitySchema('user'); $this->installEntitySchema('node'); - $this->installConfig(array('field')); + $this->installConfig(array('field', 'node')); } /** diff --git a/core/modules/node/src/Tests/NodeTokenReplaceTest.php b/core/modules/node/src/Tests/NodeTokenReplaceTest.php index 7c0dfe7..7431f06 100644 --- a/core/modules/node/src/Tests/NodeTokenReplaceTest.php +++ b/core/modules/node/src/Tests/NodeTokenReplaceTest.php @@ -30,7 +30,7 @@ class NodeTokenReplaceTest extends TokenReplaceUnitTestBase { */ protected function setUp() { parent::setUp(); - $this->installConfig(array('filter')); + $this->installConfig(array('filter', 'node')); $node_type = entity_create('node_type', array('type' => 'article', 'name' => 'Article')); $node_type->save(); diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php index 218d0b9..79e4323 100644 --- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php +++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php @@ -209,6 +209,8 @@ function testInstallEntitySchema() { * Tests expected behavior of installConfig(). */ function testInstallConfig() { + // The user module has configuration that depends on system. + $this->enableModules(array('system')); $module = 'user'; // Verify that default config can only be installed for enabled modules. diff --git a/core/modules/system/src/Controller/ThemeController.php b/core/modules/system/src/Controller/ThemeController.php index f1d1b9e..e249774 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/src/Tests/Entity/EntityCrudHookTest.php b/core/modules/system/src/Tests/Entity/EntityCrudHookTest.php index 9386983..deff706 100644 --- a/core/modules/system/src/Tests/Entity/EntityCrudHookTest.php +++ b/core/modules/system/src/Tests/Entity/EntityCrudHookTest.php @@ -52,6 +52,7 @@ protected function setUp() { $this->installSchema('file', array('file_usage')); $this->installSchema('node', array('node_access')); $this->installSchema('comment', array('comment_entity_statistics')); + $this->installConfig(['node', 'comment']); } /** 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/modules/taxonomy/config/install/views.view.taxonomy_term.yml b/core/modules/taxonomy/config/optional/views.view.taxonomy_term.yml similarity index 100% rename from core/modules/taxonomy/config/install/views.view.taxonomy_term.yml rename to core/modules/taxonomy/config/optional/views.view.taxonomy_term.yml diff --git a/core/modules/user/config/install/rdf.mapping.user.user.yml b/core/modules/user/config/optional/rdf.mapping.user.user.yml similarity index 100% rename from core/modules/user/config/install/rdf.mapping.user.user.yml rename to core/modules/user/config/optional/rdf.mapping.user.user.yml diff --git a/core/modules/user/config/install/search.page.user_search.yml b/core/modules/user/config/optional/search.page.user_search.yml similarity index 100% rename from core/modules/user/config/install/search.page.user_search.yml rename to core/modules/user/config/optional/search.page.user_search.yml diff --git a/core/modules/user/config/install/views.view.user_admin_people.yml b/core/modules/user/config/optional/views.view.user_admin_people.yml similarity index 100% rename from core/modules/user/config/install/views.view.user_admin_people.yml rename to core/modules/user/config/optional/views.view.user_admin_people.yml diff --git a/core/modules/user/config/install/views.view.who_s_new.yml b/core/modules/user/config/optional/views.view.who_s_new.yml similarity index 100% rename from core/modules/user/config/install/views.view.who_s_new.yml rename to core/modules/user/config/optional/views.view.who_s_new.yml diff --git a/core/modules/user/config/install/views.view.who_s_online.yml b/core/modules/user/config/optional/views.view.who_s_online.yml similarity index 100% rename from core/modules/user/config/install/views.view.who_s_online.yml rename to core/modules/user/config/optional/views.view.who_s_online.yml diff --git a/core/modules/user/user.info.yml b/core/modules/user/user.info.yml index 45a421a..8aec70e 100644 --- a/core/modules/user/user.info.yml +++ b/core/modules/user/user.info.yml @@ -6,3 +6,5 @@ version: VERSION core: 8.x required: true configure: user.admin_index +dependencies: + - system diff --git a/core/modules/views/src/Tests/ModuleTest.php b/core/modules/views/src/Tests/ModuleTest.php index 1f52a19..34f3dd5 100644 --- a/core/modules/views/src/Tests/ModuleTest.php +++ b/core/modules/views/src/Tests/ModuleTest.php @@ -142,7 +142,7 @@ public function customErrorHandler($error_level, $message, $filename, $line, $co * Tests the load wrapper/helper functions. */ public function testLoadFunctions() { - $this->enableModules(array('node')); + $this->enableModules(array('field', 'text', 'node')); $this->installConfig(array('node')); $storage = $this->container->get('entity.manager')->getStorage('view'); diff --git a/core/modules/views/src/Tests/ViewExecutableTest.php b/core/modules/views/src/Tests/ViewExecutableTest.php index ecbe857..30b470e 100644 --- a/core/modules/views/src/Tests/ViewExecutableTest.php +++ b/core/modules/views/src/Tests/ViewExecutableTest.php @@ -84,7 +84,7 @@ protected function setUpFixtures() { $this->installEntitySchema('node'); $this->installEntitySchema('comment'); $this->installSchema('comment', array('comment_entity_statistics')); - $this->installConfig(array('field')); + $this->installConfig(array('system', 'field', 'node', 'comment')); entity_create('node_type', array( 'type' => 'page', diff --git a/core/modules/views_ui/config/install/tour.tour.views-ui.yml b/core/modules/views_ui/config/optional/tour.tour.views-ui.yml similarity index 100% rename from core/modules/views_ui/config/install/tour.tour.views-ui.yml rename to core/modules/views_ui/config/optional/tour.tour.views-ui.yml 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/testing/config/install/locale.settings.yml b/core/profiles/testing/config/optional/locale.settings.yml similarity index 100% rename from core/profiles/testing/config/install/locale.settings.yml rename to core/profiles/testing/config/optional/locale.settings.yml