diff --git a/core/core.services.yml b/core/core.services.yml index 276e19d..759920e 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -290,7 +290,7 @@ services: - { name: event_subscriber } 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', '@profile_handler'] lazy: true config.storage: class: Drupal\Core\Config\CachedStorage @@ -498,6 +498,9 @@ services: - { name: module_install.uninstall_validator } arguments: ['@string_translation'] lazy: true + profile_handler: + class: Drupal\Core\Extension\ProfileHandler + arguments: ['@info_parser'] theme_handler: class: Drupal\Core\Extension\ThemeHandler arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser'] diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 1019270..8dda481 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -21,6 +21,7 @@ use Drupal\Core\StringTranslation\Translator\FileTranslation; use Drupal\Core\StackMiddleware\ReverseProxyMiddleware; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\Extension\ProfileHandler; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Url; use Drupal\language\Entity\ConfigurableLanguage; @@ -439,6 +440,10 @@ function install_begin_request($class_loader, &$install_state) { if (isset($install_state['profile_info']['distribution']['install']['theme'])) { $install_state['theme'] = $install_state['profile_info']['distribution']['install']['theme']; } + // Ensure all profile directories are registered. + $profile_handler = \Drupal::service('profile_handler'); + $profile_directories = $profile_handler->getProfileDirectories($profile); + $listing->setProfileDirectories($profile_directories); } // Use the language from the profile configuration, if available, to override @@ -1550,7 +1555,11 @@ function install_profile_themes(&$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); + $profile_handler = \Drupal::service('profile_handler'); + $profiles = $profile_handler->getProfileDependencies(drupal_get_profile()); + + // Install all the profiles. + \Drupal::service('module_installer')->install($profiles, 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 diff --git a/core/includes/install.inc b/core/includes/install.inc index 3a9c2bc..29e6cef 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -11,6 +11,7 @@ use Drupal\Component\Utility\OpCodeCache; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\Extension\ModuleHandler; use Drupal\Core\Site\Settings; /** @@ -1029,6 +1030,14 @@ function drupal_check_module($module) { * - install: Optional parameters to override the installer: * - theme: The machine name of a theme to use in the installer instead of * Drupal's default installer theme. + * - base profile: Existence of this key denotes that the installation profile + * depends on a parent installation profile. + * - name: The shortname of the base installation profile. + * - excluded_dependencies: An array of shortnames of other modules that have + * to be excluded from the base profile requirements. This allows e.g. to + * disable a demo module that would be installed by the base profile. + * If there are no excluded_dependencies, a shortcut of "base profile: name" + * can be used. * * Note that this function does an expensive file system scan to get info file * information for dependencies. If you only need information from the info @@ -1073,6 +1082,27 @@ function install_profile_info($profile, $langcode = 'en') { $locale = !empty($langcode) && $langcode != 'en' ? array('locale') : array(); + // Get the base profile chain. + $base_profile_name = !empty($info['base profile']['name']) ? $info['base profile']['name'] : + (!empty($info['base profile']) ? $info['base profile'] : ''); + if ($base_profile_name) { + $base_profile = install_profile_info($base_profile_name, $langcode); + + // Ensure all dependencies are cleanly merged. + $info['dependencies'] = array_merge($info['dependencies'], $base_profile['dependencies']); + // If there are dependency excludes from the base apply them now. + if (!empty($info['base profile']['excluded_dependencies'])) { + $info['dependencies'] = array_diff($info['dependencies'], $info['base profile']['excluded_dependencies']); + } + // Ensure there's no circular dependency. + $info['dependencies'] = array_diff($info['dependencies'], array($profile)); + } + + // Ensure the same dependency notation as in modules can be used. + array_walk($info['dependencies'], function(&$dependency) { + $dependency = ModuleHandler::parseDependency($dependency)['name']; + }); + $info['dependencies'] = array_unique(array_merge($required, $info['dependencies'], $locale)); $cache[$profile][$langcode] = $info; diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index 97c3688..694b694 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -7,6 +7,7 @@ use Drupal\Core\Config\Entity\ConfigDependencyManager; use Drupal\Core\Config\Entity\ConfigEntityDependency; use Drupal\Core\Site\Settings; +use Drupal\Core\Extension\ProfileHandlerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class ConfigInstaller implements ConfigInstallerInterface { @@ -54,6 +55,13 @@ class ConfigInstaller implements ConfigInstallerInterface { protected $sourceStorage; /** + * ProfileHandler + * + * @var \Drupal\Core\Extension\ProfileHandler + */ + protected $profileHandler; + + /** * Is configuration being created as part of a configuration sync. * * @var bool @@ -73,13 +81,16 @@ class ConfigInstaller implements ConfigInstallerInterface { * The configuration manager. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. + * @param \Drupal\Core\Extension\ProfileHandlerInterface $profile_handler + * The profile handler. */ - 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, ProfileHandlerInterface $profile_handler) { $this->configFactory = $config_factory; $this->activeStorages[$active_storage->getCollectionName()] = $active_storage; $this->typedConfig = $typed_config; $this->configManager = $config_manager; $this->eventDispatcher = $event_dispatcher; + $this->profileHandler = $profile_handler; } /** @@ -462,7 +473,7 @@ public function checkConfigurationToInstall($type, $name) { // 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()) { + if (!$this->profileHandler->isProfile($name)) { // 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. diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php index 0d9283c..d5be261 100644 --- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php +++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php @@ -7,6 +7,7 @@ use Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator; use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\Request; +use Drupal\Core\Extension\ProfileHandler; /** * Discovers available extensions in the filesystem. @@ -248,8 +249,10 @@ public function setProfileDirectoriesFromSettings() { // In case both profile directories contain the same extension, the actual // profile always has precedence. if ($profile) { - $this->profileDirectories[] = drupal_get_path('profile', $profile); + $profile_handler = \Drupal::service('profile_handler'); + $this->profileDirectories = array_merge($profile_handler->getProfileDirectories($profile), $this->profileDirectories); } + return $this; } diff --git a/core/lib/Drupal/Core/Extension/ProfileHandler.php b/core/lib/Drupal/Core/Extension/ProfileHandler.php new file mode 100644 index 0000000..1411cd4 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ProfileHandler.php @@ -0,0 +1,83 @@ +infoParser = $info_parser; + } + + /** + * {@inheritdoc} + */ + public function getProfileDependencies($profile) { + if (!isset(static::$cache[$profile])) { + $profiles = array(); + // Check if a valid profile name was given. + if (!empty($profile)) { + // We can't use install_profile_info() because that could trigger an + // endless loop. So we read this on our own. + $profile_file = drupal_get_path('profile', $profile) . "/$profile.info.yml"; + $profile_info = $this->infoParser->parse($profile_file); + + // Check of the profile has a base profile and if so add it - recursion. + $base_profile_name = !empty($profile_info['base profile']['name']) ? $profile_info['base profile']['name'] : + (!empty($profile_info['base profile']) ? $profile_info['base profile'] : ''); + if ($base_profile_name) { + $profiles += $this->getProfileDependencies($base_profile_name); + } + // Add requested profile as last one. + $profiles[$profile] = $profile; + } + static::$cache[$profile] = $profiles; + } + return static::$cache[$profile]; + } + + /** + * {@inheritdoc} + */ + public function getProfileDirectories($profile) { + $profile_directories = array(); + foreach ($this->getProfileDependencies($profile) as $profile_dependency => $profile_dependency_info) { + $profile_directories[] = drupal_get_path('profile', $profile_dependency); + } + return $profile_directories; + } + + /** + * {@inheritdoc} + */ + public function isProfile($profile) { + $current_profile = drupal_get_profile(); + $base_profiles = $this->getProfileDependencies($current_profile); + return isset($base_profiles[$profile]); + } + +} diff --git a/core/lib/Drupal/Core/Extension/ProfileHandlerInterface.php b/core/lib/Drupal/Core/Extension/ProfileHandlerInterface.php new file mode 100644 index 0000000..3d8197b --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ProfileHandlerInterface.php @@ -0,0 +1,45 @@ +