diff --git a/includes/common.inc b/includes/common.inc index 34fa9b9..2836c49 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -232,6 +232,30 @@ function drupal_get_profile() { return $profile; } +/** + * Returns an array of the installation profile hierarchy. + * + * @return $profiles + * An array of installation profiles. + */ +function drupal_get_profiles() { + global $install_state; + + if (!empty($install_state['profiles'])) { + $profiles = $install_state['profiles']; + } + else { + $profiles = variable_get('install_profiles', array(drupal_get_profile())); + } + while(list($key, $profile) = each($profiles)) { + $path = drupal_get_path('profile', $profile) . '/' . $profile . '.info'; + $info = drupal_parse_info_file($path); + if (!empty($info['base profile'])) { + $profiles[] = $info['base profile']; + } + } + return $profiles; +} /** * Sets the breadcrumb trail for the current page. @@ -5480,25 +5504,26 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) $searchdir = array($directory); $files = array(); - + $installed_profiles = array_reverse(drupal_get_profiles()); // The 'profiles' directory contains pristine collections of modules and // themes as organized by a distribution. It is pristine in the same way // that /modules is pristine for core; users should avoid changing anything // there in favor of sites/all or sites/ directories. $profiles = array(); - $profile = drupal_get_profile(); // For SimpleTest to be able to test modules packaged together with a // distribution we need to include the profile of the parent site (in which // test runs are triggered). if (drupal_valid_test_ua()) { $testing_profile = variable_get('simpletest_parent_profile', FALSE); - if ($testing_profile && $testing_profile != $profile) { + if ($testing_profile && !in_array($testing_profile, $installed_profiles)) { $profiles[] = $testing_profile; } } // In case both profile directories contain the same extension, the actual // profile always has precedence. - $profiles[] = $profile; + + $profiles = array_merge($profiles, $installed_profiles); + foreach ($profiles as $profile) { if (file_exists("profiles/$profile/$directory")) { $searchdir[] = "profiles/$profile/$directory"; diff --git a/includes/install.core.inc b/includes/install.core.inc index ad43b42..1b80cf6 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -162,8 +162,10 @@ function install_state_defaults() { // An array of information about the chosen installation profile. This will // be filled in based on the profile's .info file. 'profile_info' => array(), - // An array of available installation profiles. + // An array of the profile hiearchy. 'profiles' => array(), + // An array of available installation profiles. + 'profiles_available' => array(), // An array of server variables that will be substituted into the global // $_SERVER array via drupal_override_server_variables(). Used by // non-interactive installations only. @@ -531,14 +533,14 @@ function install_tasks($install_state) { $tasks = array( 'install_select_profile' => array( 'display_name' => st('Choose profile'), - 'display' => count($install_state['profiles']) != 1, + 'display' => count($install_state['profiles_available']) != 1, 'run' => INSTALL_TASK_RUN_IF_REACHED, ), - 'install_select_locale' => array( - 'display_name' => st('Choose language'), + 'install_load_profile' => array( 'run' => INSTALL_TASK_RUN_IF_REACHED, ), - 'install_load_profile' => array( + 'install_select_locale' => array( + 'display_name' => st('Choose language'), 'run' => INSTALL_TASK_RUN_IF_REACHED, ), 'install_verify_requirements' => array( @@ -555,7 +557,7 @@ function install_tasks($install_state) { 'run' => INSTALL_TASK_RUN_IF_REACHED, ), 'install_profile_modules' => array( - 'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Install profile'), + 'display_name' => count($install_state['profiles_available']) == 1 ? st('Install site') : st('Install profile'), 'type' => 'batch', ), 'install_import_locales' => array( @@ -570,19 +572,21 @@ function install_tasks($install_state) { ), ); - // Now add any tasks defined by the installation profile. - 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_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['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; + // Now add any tasks defined by the installation profiles. + if (!empty($install_state['profiles'])) { + $profiles = array_reverse($install_state['profiles']); + foreach ($profiles as $profile) { + $profile_install_file = DRUPAL_ROOT . '/profiles/' . $profile . '/' . $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; + } } } } @@ -600,14 +604,17 @@ function install_tasks($install_state) { ), ); - // Allow the installation profile to modify the full list of tasks. - if (!empty($install_state['parameters']['profile'])) { - $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; - 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); + // Allow for the installation profiles to modify the full list of tasks. + if (!empty($install_state['profiles'])) { + $profiles = array_reverse($install_state['profiles']); + foreach ($profiles as $profile) { + $profile_file = DRUPAL_ROOT . '/profiles/' . $profile . '/' . $profile . '.profile'; + if (file_exists($profile_file)) { + include_once $profile_file; + $function = $profile . '_install_tasks_alter'; + if (function_exists($function)) { + $function($tasks, $install_state); + } } } } @@ -801,11 +808,26 @@ function install_system_module(&$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']; + $modules = array(); + $profiles = array($install_state['parameters']['profile']); + $profile_info = $install_state['profile_info']; + do { + $modules = array_merge($modules, $profile_info['dependencies']); + if (!empty($profile_info['base profile']) && isset($profile_info[$profile_info['base profile']])) { + $profiles[] = $profile_info['base profile']; + $profile_info = $profile_info[$profile_info['base profile']]; + } + else { + $profile_info = NULL; + } + } + while ($profile_info); // The installation profile is also a module, which needs to be installed // after all the dependencies have been installed. - $modules[] = drupal_get_profile(); + foreach ($profiles as $profile) { + $modules[] = $profile; + } variable_set('install_profile_modules', array_diff($modules, array('system'))); $install_state['database_tables_exist'] = TRUE; @@ -1043,10 +1065,10 @@ function install_find_profiles() { * thrown if a profile cannot be chosen automatically. */ function install_select_profile(&$install_state) { - $install_state['profiles'] += install_find_profiles(); + $install_state['profiles_available'] += install_find_profiles(); if (empty($install_state['parameters']['profile'])) { // Try to find a profile. - $profile = _install_select_profile($install_state['profiles']); + $profile = _install_select_profile($install_state['profiles_available']); if (empty($profile)) { // We still don't have a profile, so display a form for selecting one. // Only do this in the case of interactive installations, since this is @@ -1056,7 +1078,7 @@ function install_select_profile(&$install_state) { if ($install_state['interactive']) { include_once DRUPAL_ROOT . '/includes/form.inc'; drupal_set_title(st('Select an installation profile')); - $form = drupal_get_form('install_select_profile_form', $install_state['profiles']); + $form = drupal_get_form('install_select_profile_form', $install_state['profiles_available']); return drupal_render($form); } else { @@ -1223,9 +1245,14 @@ function install_find_locales($profilename) { * locale cannot be chosen automatically. */ function install_select_locale(&$install_state) { + $locales = array(); + $profiles = array_reverse($install_state['profiles']); // Find all available locales. - $profilename = $install_state['parameters']['profile']; - $locales = install_find_locales($profilename); + foreach ($profiles as $profile) { + $profilelocales = install_find_locales($profile); + $locales = array_merge($locales, $profilelocales); + } + $locales = array_unique($locales, SORT_REGULAR); $install_state['locales'] += $locales; if (!empty($_POST['locale'])) { @@ -1237,6 +1264,7 @@ function install_select_locale(&$install_state) { } } + $profilename = $install_state['parameters']['profile']; if (empty($install_state['parameters']['locale'])) { // If only the built-in (English) language is available, and we are // performing an interactive installation, inform the user that the @@ -1276,14 +1304,16 @@ function install_select_locale(&$install_state) { } else { // Allow profile to pre-select the language, skipping the selection. - $function = $profilename . '_profile_details'; - if (function_exists($function)) { - $details = $function(); - if (isset($details['language'])) { - foreach ($locales as $locale) { - if ($details['language'] == $locale->name) { - $install_state['parameters']['locale'] = $locale->name; - return; + foreach ($profiles as $profile) { + $function = $profile . '_profile_details'; + if (function_exists($function)) { + $details = $function(); + if (isset($details['language'])) { + foreach ($locales as $locale) { + if ($details['language'] == $locale->name) { + $install_state['parameters']['locale'] = $locale->name; + return; + } } } } @@ -1371,10 +1401,25 @@ function install_load_profile(&$install_state) { $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; if (file_exists($profile_file)) { include_once $profile_file; - $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['locale']); + $install_state['profiles'][] = $install_state['parameters']['profile']; + $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile']); + $profile_info = &$install_state['profile_info']; + while (isset($profile_info['base profile']) && $profile_info['base profile']) { + $profile = $profile_info['base profile']; + $profile_file = DRUPAL_ROOT . '/profiles/' . $profile . '/' . $profile . '.profile'; + if (file_exists($profile_file)) { + include_once $profile_file; + $install_state['profiles'][] = $profile; + $profile_info[$profile] = install_profile_info($profile); + $profile_info = &$profile_info[$profile]; + } + else { + throw new Exception(install_no_profile_error()); + } + } } else { - throw new Exception(st('Sorry, the profile you have chosen cannot be loaded.')); + throw new Exception(install_no_profile_error()); } } @@ -1409,7 +1454,10 @@ 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 ($install_state['profiles'] as $profile) { + $files[$profile]->info['required'] = FALSE; + } + // Add modules that other modules depend on. foreach ($modules as $module) { if ($files[$module]->requires) { @@ -1570,13 +1618,19 @@ function install_finished(&$install_state) { // Remember the profile which was used. variable_set('install_profile', drupal_get_profile()); + variable_set('install_profiles', drupal_get_profiles()); // Installation profiles are always loaded last - db_update('system') - ->fields(array('weight' => 1000)) - ->condition('type', 'module') - ->condition('name', drupal_get_profile()) - ->execute(); + $profiles = drupal_get_profiles(); + $weight = 1000 + count($profiles); + foreach ($profiles as $profile) { + db_update('system') + ->fields(array('weight' => $weight)) + ->condition('type', 'module') + ->condition('name', $profile) + ->execute(); + $weight = $weight - 1; + } // Cache a fully-built schema. drupal_get_schema(NULL, TRUE); @@ -1619,10 +1673,12 @@ function _install_profile_modules_finished($success, $results, $operations) { * Checks installation requirements and reports any errors. */ function install_check_requirements($install_state) { - $profile = $install_state['parameters']['profile']; + $requirements = array(); // Check the profile requirements. - $requirements = drupal_check_profile($profile); + foreach ($install_state['profiles'] as $profile) { + $requirements += drupal_check_profile($profile); + } // If Drupal is not set up already, we need to create a settings file. if (!$install_state['settings_verified']) { diff --git a/includes/install.inc b/includes/install.inc index 2b55589..f0d4336 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -676,31 +676,62 @@ function drupal_rewrite_settings($settings = array(), $prefix = '') { * The list of modules to install. */ function drupal_verify_profile($install_state) { - $profile = $install_state['parameters']['profile']; + $profiles = $install_state['profiles']; $locale = $install_state['parameters']['locale']; include_once DRUPAL_ROOT . '/includes/file.inc'; include_once DRUPAL_ROOT . '/includes/common.inc'; - $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile"; - - if (!isset($profile) || !file_exists($profile_file)) { + if (!empty($profiles)) { + foreach ($profiles as $profile) { + $profile_file = DRUPAL_ROOT . '/profiles/' . $profile . '/' . $profile . '.profile'; + if (!file_exists($profile_file)) { + throw new Exception(install_no_profile_error()); + } + } + } + else { throw new Exception(install_no_profile_error()); } + $info = $install_state['profile_info']; + $profile_dependencies = $info['dependencies']; + + while (isset($info['base profile']) && $info[$info['base profile']]) { + $info = $info[$info['base profile']]; + $profile_dependencies += $info['dependencies']; + } // Get a list of modules that exist in Drupal's assorted subdirectories. $present_modules = array(); foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0) as $present_module) { - $present_modules[] = $present_module->name; + $present_module_path = drupal_get_path('module', $present_module->name); + $present_modules[$present_module->name] = drupal_parse_info_file($present_module_path . '/' . $present_module->name . '.info'); } - // 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(); + $dependencies = array(); + // Recursively seek all dependencies; not just those of the profiles. + foreach ($profile_dependencies as $profile_dependency) { + _drupal_verify_profile_dependencies($dependencies, $profile_dependency); + } // Verify that all of the profile's required modules are present. - $missing_modules = array_diff($info['dependencies'], $present_modules); + $missing_modules = array(); + foreach ($dependencies as $dependency) { + // Check early if the module even exists, continue early on. + if (!empty($present_modules[$dependency['name']])) { + // Check for version match if a versioned module + if (isset($present_modules[$dependency['name']]['version'])) { + $current_version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $present_modules[$dependency['name']]['version']); + if ($incompatible = drupal_check_incompatibility($dependency, $current_version)) { + $missing_modules[] = $dependency['name'] . $incompatible; + } + } + } + else { + $missing_modules[] = $dependency['name']; + } + } $requirements = array(); @@ -720,6 +751,55 @@ function drupal_verify_profile($install_state) { } /** + * Recursively load all profile dependencies. + * + * This helper function recursively loads and searches all of the + * $module's dependencies and appends them to the $dependencies. + * + * @param array $dependencies + * An array passed by reference in which dependencies are appended. + * @param $module + * The module name to load dependencies for. + */ +function _drupal_verify_profile_dependencies(array &$dependencies, $module) { + if (isset($dependencies[$module])) { + // Exit early if $module already has been checked. + return; + } + + $module_info_file = drupal_get_path('module', $module) . '/' . $module . '.info'; + if (file_exists($module_info_file)) { + $module_info = drupal_parse_info_file($module_info_file); + if (!empty($module_info['dependencies'])) { + foreach ($module_info['dependencies'] as $dependency) { + $dependency = drupal_parse_dependency($dependency); + if (empty($dependency['versions'])) { + // Create default versions array in case no versions + // were found while parsing dependency. + $dependency['versions'] = array(); + } + + if (isset($dependencies[$dependency['name']])) { + $dependencies[$dependency['name']]['versions'] = array_merge($dependencies[$dependency['name']]['versions'], $dependency['versions']); + } + else { + $dependencies[$dependency['name']] = $dependency; + } + + // Recursively call this function one self. + _drupal_verify_profile_dependencies($dependencies, $dependency['name']); + } + } + } + else { + $dependencies[$module] = array( + 'name' => $module, + 'versions' => array(), + ); + } +} + +/** * Installs the system module. * * Separated from the installation of other modules so core system @@ -1293,6 +1373,7 @@ function install_profile_info($profile, $locale = 'en') { 'dependencies' => array(), 'description' => '', 'distribution_name' => 'Drupal', + 'base profile' => NULL, 'version' => NULL, 'hidden' => FALSE, 'php' => DRUPAL_MINIMUM_PHP, diff --git a/modules/system/system.module b/modules/system/system.module index 39de758..d665c2f 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -2369,14 +2369,20 @@ function _system_rebuild_module_data() { $modules = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0); // Include the installation profile in modules that are loaded. - $profile = drupal_get_profile(); - $modules[$profile] = new stdClass(); - $modules[$profile]->name = $profile; - $modules[$profile]->uri = 'profiles/' . $profile . '/' . $profile . '.profile'; - $modules[$profile]->filename = $profile . '.profile'; + $profiles = drupal_get_profiles(); + $weight = 1000 + count($profiles); + + foreach ($profiles as $profile) { + $modules[$profile] = new stdClass(); + $modules[$profile]->name = $profile; + $modules[$profile]->uri = 'profiles/' . $profile . '/' . $profile . '.profile'; + $modules[$profile]->filename = $profile . '.profile'; + + // Installation profile hooks are always executed last. + $modules[$profile]->weight = $weight; + $weight = $weight - 1; + } - // Installation profile hooks are always executed last. - $modules[$profile]->weight = 1000; // Set defaults for module info. $defaults = array( @@ -2436,7 +2442,11 @@ function _system_rebuild_module_data() { drupal_alter('system_info', $modules[$key]->info, $modules[$key], $type); } - if (isset($modules[$profile])) { + foreach ($profiles as $profile) { + if (!isset($modules[$profile])) { + continue; + } + // 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