diff --git a/core/includes/common.inc b/core/includes/common.inc index f954b20..3d349c1 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -279,6 +279,24 @@ function drupal_get_profile() { return $profile; } +/** + * Returns a list of related installation profiles in descending order of their + * dependencies. + */ +function drupal_get_profiles($profile) { + if (!function_exists('install_get_base_profiles')) { + require_once DRUPAL_ROOT . '/core/includes/install.inc'; + } + + $profiles = array(); + + if (!empty($profile)) { + $profiles = array_reverse(install_get_base_profiles($profile)); + $profiles[] = $profile; + } + + return $profiles; +} /** * Sets the breadcrumb trail for the current page. diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 8e4b66e..8451086 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -195,6 +195,9 @@ function install_state_defaults() { // An array of information about the chosen installation profile. This will // be filled in based on the profile's .info.yml file. 'profile_info' => array(), + // An array of information for each of the base profiles of the chosen + // installation profile. + 'base_profiles_info' => array(), // An array of available installation profiles. 'profiles' => array(), // An array of server variables that will be substituted into the global @@ -796,16 +799,17 @@ function install_tasks($install_state) { if (!empty($install_state['parameters']['profile'])) { // Load the profile install file, because it is not always loaded when // hook_install_tasks() is invoked (e.g. batch processing). - $profile = $install_state['parameters']['profile']; - $profile_install_file = dirname($install_state['profiles'][$profile]->uri) . '/' . $profile . '.install'; - if (file_exists($profile_install_file)) { - include_once $profile_install_file; - } - $function = $install_state['parameters']['profile'] . '_install_tasks'; - if (function_exists($function)) { - $result = $function($install_state); - if (is_array($result)) { - $tasks += $result; + foreach (drupal_get_profiles($install_state['parameters']['profile']) as $profile) { + $profile_install_file = dirname($install_state['profiles'][$profile]->uri) . '/' . $profile . '.install'; + if (file_exists($profile_install_file)) { + include_once $profile_install_file; + } + $function = $profile . '_install_tasks'; + if (function_exists($function)) { + $result = $function($install_state); + if (is_array($result)) { + $tasks += $result; + } } } } @@ -831,13 +835,14 @@ function install_tasks($install_state) { // Allow the installation profile to modify the full list of tasks. if (!empty($install_state['parameters']['profile'])) { - $profile = $install_state['parameters']['profile']; - $profile_file = $install_state['profiles'][$profile]->uri; - if (file_exists($profile_file)) { - include_once $profile_file; - $function = $install_state['parameters']['profile'] . '_install_tasks_alter'; - if (function_exists($function)) { - $function($tasks, $install_state); + foreach (drupal_get_profiles($install_state['parameters']['profile']) as $profile) { + $profile_file = $install_state['profiles'][$profile]->uri; + if (file_exists($profile_file)) { + include_once $profile_file; + $function = $profile . '_install_tasks_alter'; + if (function_exists($function)) { + $function($tasks, $install_state); + } } } } @@ -999,11 +1004,19 @@ function install_base_system(&$install_state) { // Save the list of other modules to install for the upcoming tasks. // variable_set() can be used now that system.module is installed. - $modules = $install_state['profile_info']['dependencies']; + $profiles = drupal_get_profiles($install_state['parameters']['profile']); + $profile_dependencies = array(); + + foreach ($profiles as $profile) { + $info = install_profile_info($profile); + $profile_dependencies = array_unique(array_merge($profile_dependencies, $info['dependencies'])); + } + + $modules = $profile_dependencies; // The installation profile is also a module, which needs to be installed // after all the dependencies have been installed. - $modules[] = drupal_get_profile(); + $modules = array_merge($modules, $profiles); Drupal::state()->set('install_profile_modules', array_diff($modules, array('system'))); $install_state['database_tables_exist'] = TRUE; @@ -1740,14 +1753,21 @@ function install_already_done_error() { * the profile cannot be loaded. */ function install_load_profile(&$install_state) { - $profile = $install_state['parameters']['profile']; - $profile_file = $install_state['profiles'][$profile]->uri; - if (file_exists($profile_file)) { - include_once $profile_file; - $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['langcode']); - } - else { - throw new Exception(t('Sorry, the profile you have chosen cannot be loaded.')); + foreach (drupal_get_profiles($install_state['parameters']['profile']) as $profile) { + $profile_file = $install_state['profiles'][$profile]->uri; + if (file_exists($profile_file)) { + include_once $profile_file; + + if ($profile == $install_state['parameters']['profile']) { + $install_state['profile_info'] = install_profile_info($profile, $install_state['parameters']['langcode']); + } + else { + $install_state['base_profiles_info'][] = install_profile_info($profile, $install_state['parameters']['langcode']); + } + } + else { + throw new Exception(t('Sorry, the profile you have chosen cannot be loaded.')); + } } } @@ -1784,7 +1804,9 @@ function install_profile_modules(&$install_state) { // 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; + foreach (drupal_get_profiles($install_state['parameters']['profile']) as $profile) { + $files[$profile]->info['required'] = FALSE; + } // Add modules that other modules depend on. foreach ($modules as $module) { if ($files[$module]->requires) { diff --git a/core/includes/install.inc b/core/includes/install.inc index c308a2d..06f76cf 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -569,10 +569,23 @@ function drupal_verify_profile($install_state) { include_once __DIR__ . '/file.inc'; include_once __DIR__ . '/common.inc'; - $profile = $install_state['parameters']['profile']; - $profile_file = $install_state['profiles'][$profile]->uri; + $selected_profile = $install_state['parameters']['profile']; + $profiles = drupal_get_profiles($selected_profile); - if (!isset($profile) || !file_exists($profile_file)) { + if (!empty($profiles)) { + $profile_dependencies = array(); + + foreach ($profiles as $profile) { + $profile_file = $install_state['profiles'][$profile]->uri; + + if (!file_exists($profile_file)) { + throw new Exception(install_no_profile_error()); + } + $info = install_profile_info($profile); + $profile_dependencies = array_unique(array_merge($profile_dependencies, $info['dependencies'])); + } + } + else { throw new Exception(install_no_profile_error()); } $info = $install_state['profile_info']; @@ -585,10 +598,10 @@ function drupal_verify_profile($install_state) { // The installation profile is also a module, which needs to be installed // after all the other dependencies have been installed. - $present_modules[] = drupal_get_profile(); + $present_modules = array_merge($present_modules, $profiles); // Verify that all of the profile's required modules are present. - $missing_modules = array_diff($info['dependencies'], $present_modules); + $missing_modules = array_diff($profile_dependencies, $present_modules); $requirements = array(); @@ -942,23 +955,35 @@ function drupal_requirements_url($severity) { function drupal_check_profile($profile, array $install_state) { include_once __DIR__ . '/file.inc'; - $profile_file = $install_state['profiles'][$profile]->uri; + $requirements = array(); + if (isset($profile)) { + $profile_dependencies = array(); - if (!isset($profile) || !file_exists($profile_file)) { - throw new Exception(install_no_profile_error()); - } + // Loop through all active installation profiles to collect dependencies. + foreach (drupal_get_profiles($profile) as $profile) { + $profile_file = $install_state['profiles'][$profile]->uri; - $info = install_profile_info($profile); + if (!file_exists($profile_file)) { + throw new Exception(install_no_profile_error()); + } - // Collect requirement testing results. - $requirements = array(); - foreach ($info['dependencies'] as $module) { - module_load_install($module); - $function = $module . '_requirements'; - if (function_exists($function)) { - $requirements = array_merge($requirements, $function('install')); + $info = install_profile_info($profile); + $profile_dependencies = array_unique(array_merge($profile_dependencies, $info['dependencies'])); + } + + // Collect requirement testing results. + foreach ($profile_dependencies as $module) { + module_load_install($module); + $function = $module . '_requirements'; + if (function_exists($function)) { + $requirements = array_merge($requirements, $function('install')); + } } } + else { + throw new Exception(install_no_profile_error()); + } + return $requirements; } @@ -1086,6 +1111,31 @@ function install_profile_info($profile, $langcode = 'en') { } /** + * Returns a list of base installation profiles associated with the specified + * installation profile. + * + * @return + * An array of profile names. An empty array if installation profile has no + * parent profiles. + */ +function install_get_base_profiles($profile) { + $cache = &drupal_static(__FUNCTION__, array()); + + if (!isset($cache[$profile])) { + $profile_file = drupal_get_path('profile', $profile) . "/$profile.info"; + $info = drupal_parse_info_file($profile_file); + $cache[$profile] = array(); + + while (!empty($info) && isset($info['base'])) { + $cache[$profile][] = $info['base']; + $info = install_profile_info($info['base']); + } + } + + return $cache[$profile]; +} + +/** * Ensures the environment for a Drupal database on a predefined connection. * * This will run tasks that check that Drupal can perform all of the functions diff --git a/core/includes/module.inc b/core/includes/module.inc index 1a32c09..838bbd9 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -300,7 +300,7 @@ function drupal_required_modules() { $required = array(); // An installation profile is required and one must always be loaded. - $required[] = drupal_get_profile(); + $required = array_merge($required, drupal_get_profiles(drupal_get_profile())); foreach ($files as $name => $file) { $info = drupal_parse_info_file($file->uri); diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index b9e5724..61573ba 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -695,7 +695,7 @@ function disable($module_list, $disable_dependents = TRUE) { // Create an associative array with weights as values. $module_list = array_flip(array_values($module_list)); - $profile = drupal_get_profile(); + $profiles = drupal_get_profiles(drupal_get_profile()); while (list($module) = each($module_list)) { if (!isset($module_data[$module]) || !$module_data[$module]->status) { // This module doesn't exist or is already disabled, skip it. @@ -707,7 +707,7 @@ function disable($module_list, $disable_dependents = TRUE) { // Add dependent modules to the list, with a placeholder weight. // The new modules will be processed as the while loop continues. foreach ($module_data[$module]->required_by as $dependent => $dependent_data) { - if (!isset($module_list[$dependent]) && $dependent != $profile) { + if (!isset($module_list[$dependent]) && !in_array($dependent, $profiles)) { $module_list[$dependent] = 0; } } @@ -785,7 +785,7 @@ public function uninstall($module_list = array(), $uninstall_dependents = TRUE) // Create an associative array with weights as values. $module_list = array_flip(array_values($module_list)); - $profile = drupal_get_profile(); + $profiles = drupal_get_profiles(drupal_get_profile()); while (list($module) = each($module_list)) { if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) { // This module doesn't exist or is already uninstalled. Skip it. @@ -799,7 +799,7 @@ public function uninstall($module_list = array(), $uninstall_dependents = TRUE) // them automatically because uninstalling a module is a destructive // operation. foreach (array_keys($module_data[$module]->required_by) as $dependent) { - if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && $dependent != $profile) { + if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && !in_array($dependent, $profiles)) { return FALSE; } } diff --git a/core/modules/system/lib/Drupal/system/Form/ModulesUninstallForm.php b/core/modules/system/lib/Drupal/system/Form/ModulesUninstallForm.php index a6b3dea..286f40a 100644 --- a/core/modules/system/lib/Drupal/system/Form/ModulesUninstallForm.php +++ b/core/modules/system/lib/Drupal/system/Form/ModulesUninstallForm.php @@ -107,7 +107,7 @@ public function buildForm(array $form, array &$form_state, Request $request = NU return $form; } - $profile = drupal_get_profile(); + $profiles = drupal_get_profiles(drupal_get_profile()); // Sort all modules by their name. $this->moduleHandler->loadInclude('system', 'inc', 'system.admin'); @@ -130,7 +130,7 @@ public function buildForm(array $form, array &$form_state, Request $request = NU // we can allow this module to be uninstalled. (The installation profile // is excluded from this list.) foreach (array_keys($module->required_by) as $dependent) { - if ($dependent != $profile && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) { + if (!in_array($dependent, $profiles) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) { $name = isset($modules[$dependent]->info['name']) ? $modules[$dependent]->info['name'] : $dependent; $form['modules'][$module->name]['#dependents'][] = $name; $form['uninstall'][$module->name]['#disabled'] = TRUE; diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 3f0fac0..1fd62d9 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -445,11 +445,11 @@ function system_requirements($phase) { // Display an error if a newly introduced dependency in a module is not resolved. if ($phase == 'update') { - $profile = drupal_get_profile(); + $profiles = drupal_get_profiles(drupal_get_profile()); $files = system_rebuild_module_data(); foreach ($files as $module => $file) { // Ignore disabled modules and installation profiles. - if (!$file->status || $module == $profile) { + if (!$file->status || in_array($module, $profiles)) { continue; } // Check the module's PHP version. diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 6a8478f..9a6cb4c 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2499,12 +2499,17 @@ function _system_rebuild_module_data() { // Find installation profiles. $profiles = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles', 'name', 0); - // Include the installation profile in modules that are loaded. - $profile = drupal_get_profile(); - $modules[$profile] = $profiles[$profile]; + // Include the active installation profile(s) in modules that are loaded. + $profile_weight = 1000; + $base_profiles = drupal_get_profiles(drupal_get_profile()); // Installation profile hooks are always executed last. - $modules[$profile]->weight = 1000; + foreach ($base_profiles as $profile) { + $modules[$profile] = $profiles[$profile]; + + // Installation profile hooks are always executed last. + $modules[$profile]->weight = $profile_weight++; + } // Set defaults for module info. $defaults = array( @@ -2540,7 +2545,7 @@ function _system_rebuild_module_data() { // Installation profiles are hidden by default, unless explicitly specified // otherwise in the .info.yml file. - if ($key == $profile && !isset($modules[$key]->info['hidden'])) { + if (in_array($key, $profiles) && !isset($modules[$key]->info['hidden'])) { $modules[$key]->info['hidden'] = TRUE; } @@ -2550,13 +2555,15 @@ function _system_rebuild_module_data() { drupal_alter('system_info', $modules[$key]->info, $modules[$key], $type); } - if (isset($modules[$profile])) { - // The installation profile is required, if it's a valid module. - $modules[$profile]->info['required'] = TRUE; - // Add a default distribution name if the profile did not provide one. This - // matches the default value used in install_profile_info(). - if (!isset($modules[$profile]->info['distribution_name'])) { - $modules[$profile]->info['distribution_name'] = 'Drupal'; + foreach ($base_profiles as $profile) { + if (isset($modules[$profile])) { + // The installation profile is required, if it's a valid module. + $modules[$profile]->info['required'] = TRUE; + // Add a default distribution name if the profile did not provide one. + // This matches the default value used in install_profile_info(). + if (!isset($modules[$profile]->info['distribution_name'])) { + $modules[$profile]->info['distribution_name'] = 'Drupal'; + } } }