diff --git a/core/core.services.yml b/core/core.services.yml index 24fa98e..84b1fa3 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -146,7 +146,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 0358b39..632ea59 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,19 @@ 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. + // @todo discuss whether this is the desired approach? + \Drupal::service('config.installer')->installAllOptionalConfig(); +} + +/** * 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 8065613..d702b26 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,12 +58,21 @@ 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 */ protected $isSyncing = FALSE; + protected $isInstalling = FALSE; + /** * Constructs the configuration installer. * @@ -76,19 +86,23 @@ 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; } /** * {@inheritdoc} */ public function installDefaultConfig($type, $name) { + $this->isInstalling = TRUE; $extension_path = drupal_get_path($type, $name); // Refresh the schema cache if the extension provides configuration schema // or is a theme. @@ -96,34 +110,104 @@ public function installDefaultConfig($type, $name) { $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()) { + // Set the source storage to the configuration provided by the extension. + $this->setSourceStorage(new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION)); + $prefix = NULL; + } + 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. + $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(TRUE) as $collection) { + $config_to_install = $this->getDefaultConfigToInstall($collection, $prefix); + if (!empty($config_to_install)) { + $this->createConfiguration($collection, $config_to_install); + } } } - $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); + if (!$this->isSyncing() && !drupal_installation_attempted()) { + $optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY; + if (is_dir($optional_install_path)) { + // Install any optional config the module provides. + $this->setSourceStorage(new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION)); + $this->installOptionalConfig(); } + // Install any optional configuration entities whose type this extension + // provides. This searches all the installed modules config/optional + // directories. + // @todo consider optimising this by checking if the extension provides + // any configuration entities. + $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE); + $this->setSourceStorage($storage); + $this->installOptionalConfig($name); } // Reset all the static caches and list caches. + $this->isInstalling = FALSE; $this->configFactory->reset(); + $this->resetSourceStorage(); + } + + /** + * {@inheritdoc} + */ + public function installAllOptionalConfig() { + $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, drupal_installation_attempted()); + $this->setSourceStorage($storage); + $this->installOptionalConfig(); + $this->resetSourceStorage(); + } + + /** + * 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 string $name + * If set, limits the installed configuration to only configuration + * beginning with the provided value. + */ + protected function installOptionalConfig($name = NULL) { + if ($this->isSyncing()) { + return; + } + $collection_info = $this->configManager->getConfigCollectionInfo(); + $enabled_extensions = $this->getEnabledExtensions(); + $existing_config = $this->configFactory->listAll(); + foreach ($collection_info->getCollectionNames(TRUE) as $collection) { + $config_entity_support = $this->configManager->supportsConfigurationEntities($collection); + if (!$config_entity_support) { + continue; + } + + $config_to_install = $this->getDefaultConfigToInstall($collection, $name); + $all_config = array_merge($existing_config, array_keys($config_to_install)); + foreach ($config_to_install as $config_name => $data) { + // Ensure that the configuration does not exist already, that it is a + // configuration entity, and that its dependencies can 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_install[$config_name]); + } + } + if (!empty($config_to_install)) { + $this->createConfiguration($collection, $config_to_install, TRUE); + } + } } /** @@ -133,43 +217,23 @@ public function installDefaultConfig($type, $name) { * 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. + * @param string $name + * The name that configuration must start with. + * * @return array * The list of configuration objects to create. */ - 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); - }); - - $config_to_install = array_merge($config_to_install, $extension_provided_config); + protected function getDefaultConfigToInstall($collection, $name = NULL) { + $prefix = ''; + if ($name) { + $prefix = $name . '.'; } - - return $config_to_install; + $storage = $this->getSourceStorage($collection); + $list = $storage->listAll($prefix); + return $storage->readMultiple($list); } /** @@ -177,12 +241,11 @@ 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 $data + * An array of configuration data to create, keyed by name. */ - protected function createConfiguration($collection, array $config_to_install) { + protected function createConfiguration($collection, array $data) { // 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(); @@ -190,9 +253,9 @@ protected function createConfiguration($collection, array $config_to_install) { ->setData($data) ->sortAll(); } - - // Remove configuration that already exists in the active storage. - $config_to_install = array_diff($config_to_install, $this->getActiveStorages($collection)->listAll()); + else { + $config_to_install = array_keys($data); + } foreach ($config_to_install as $name) { // Allow config factory overriders to use a custom configuration object if @@ -208,14 +271,13 @@ 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 // 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,19 +308,26 @@ protected function createConfiguration($collection, array $config_to_install) { * {@inheritdoc} */ public function installCollectionDefaultConfig($collection) { + // We're in the middle of a config install any collections will be picked up + // later? + // @todo is this really correct. + if ($this->isInstalling) { + return; + } + $this->setSourceStorage(new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, drupal_installation_attempted())); $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')); + // @todo is this right? + $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, $this->getSourceStorage($collection)->readMultiple($config_to_install)); // Reset all the static caches and list caches. $this->configFactory->reset(); } + $this->resetSourceStorage(); } /** @@ -289,10 +358,11 @@ public function resetSourceStorage() { */ public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) { if (!isset($this->sourceStorage)) { + throw new ConfigException('This should not happen'); // 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()); + // $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); @@ -333,22 +403,27 @@ 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 objects that already exist keyed by collection. */ - public function findPreExistingConfiguration($type, $name) { + protected function findPreExistingConfiguration() { $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); + $config_to_install = array_keys($this->getDefaultConfigToInstall($collection)); $active_storage = $this->getActiveStorages($collection); foreach ($config_to_install as $config_name) { if ($active_storage->exists($config_name)) { @@ -358,4 +433,184 @@ 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; + } + + $this->setSourceStorage(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($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(); + 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(array $enabled_extensions) { + $config_data = $this->getDefaultConfigToInstall(StorageInterface::DEFAULT_COLLECTION); + $all_config = array_merge($this->configFactory->listAll(), array_keys($config_data)); + return array_filter(array_keys($config_data), function($config_name) use ($enabled_extensions, $all_config, $config_data) { + return !$this->validateDependencies($config_name, $config_data[$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']); + } + // 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(); + } + } diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php index 1d1e7e0..e49dfe7 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php @@ -38,6 +38,16 @@ public function installDefaultConfig($type, $name); /** + * Installs all optional configuration provided by any extension. + * + * Optional configuration is only installed if: + * - the configuration does not exist already. + * - it's a configuration entity. + * - its dependencies can be met. + */ + public function installAllOptionalConfig(); + + /** * Installs all default configuration in the specified collection. * * The function is useful if the site needs to respond to an event that has @@ -85,25 +95,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..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/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 763d106..3df2879 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 e1a607a..1c0166d 100644 --- a/core/modules/config/src/Tests/ConfigImportRecreateTest.php +++ b/core/modules/config/src/Tests/ConfigImportRecreateTest.php @@ -37,7 +37,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')); @@ -93,8 +93,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 4 configuration items to create.'); + $this->assertEqual(5, count($deletes), 'There are 4 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..00be940 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); @@ -171,7 +175,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->installConfig(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()); 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..748d125 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; /** @@ -59,18 +60,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.'); } /** @@ -97,6 +90,25 @@ public function testUninstall() { } /** + * Tests the configuration with unmet dependencies is not installed. + */ + public function testDependencyChecking() { + 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_test'); + $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/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 f888345..69ad3e8 100644 --- a/core/modules/contact/src/Tests/MessageEntityTest.php +++ b/core/modules/contact/src/Tests/MessageEntityTest.php @@ -32,7 +32,7 @@ class MessageEntityTest extends KernelTestBase { 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 50210b5..61c7766 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/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 bc12a13..4cb1dd7 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 4d09490..4a0cbc4 100644 --- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php +++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php @@ -191,6 +191,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 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/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 27c4e55..6affac2 100644 --- a/core/modules/views/src/Tests/ViewExecutableTest.php +++ b/core/modules/views/src/Tests/ViewExecutableTest.php @@ -20,6 +20,7 @@ use Drupal\views\Plugin\views\query\Sql; use Drupal\views\Plugin\views\pager\PagerPluginBase; use Drupal\views\Plugin\views\query\QueryPluginBase; +use Drupal\views\ViewsDataHelper; use Drupal\views_test_data\Plugin\views\display\DisplayTest; use Symfony\Component\HttpFoundation\Response; @@ -83,7 +84,9 @@ 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')); + // Clear the views data cache so it updated with the new entity information. + \Drupal::service('views.views_data')->clear(); 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