Index: modules/simpletest/tests/requirements1_test.info =================================================================== RCS file: modules/simpletest/tests/requirements1_test.info diff -N modules/simpletest/tests/requirements1_test.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/requirements1_test.info 9 Oct 2009 19:40:06 -0000 @@ -0,0 +1,9 @@ +; $Id$ +name = Requirements 1 Test +description = "Tests that a module is not installed when it fails hook_requirements('install')." +package = Core +version = VERSION +core = 7.x +files[] = requirements1_test.install +files[] = requirements1_test.module +hidden = TRUE \ No newline at end of file Index: modules/simpletest/tests/requirements1_test.install =================================================================== RCS file: modules/simpletest/tests/requirements1_test.install diff -N modules/simpletest/tests/requirements1_test.install --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/requirements1_test.install 9 Oct 2009 19:40:06 -0000 @@ -0,0 +1,34 @@ + $t('Requirements 1 Test'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Requirements 1 Test failed requirements.'), + ); + } + + return $requirements; +} \ No newline at end of file Index: modules/simpletest/tests/requirements1_test.module =================================================================== RCS file: modules/simpletest/tests/requirements1_test.module diff -N modules/simpletest/tests/requirements1_test.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/requirements1_test.module 9 Oct 2009 19:40:06 -0000 @@ -0,0 +1,8 @@ +name == 'requirements1_test' || $file->name == 'requirements2_test') { + $info['hidden'] = FALSE; + } } /** Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.209 diff -u -p -r1.209 system.admin.inc --- modules/system/system.admin.inc 9 Oct 2009 01:00:05 -0000 1.209 +++ modules/system/system.admin.inc 9 Oct 2009 19:40:06 -0000 @@ -847,7 +847,7 @@ function system_modules_confirm_form($mo $form['validation_modules'] = array('#type' => 'value', '#value' => $modules); $form['status']['#tree'] = TRUE; - foreach ($storage['more_modules'] as $info) { + foreach ($storage['more_required'] as $info) { $t_argument = array( '@module' => $info['name'], '@required' => implode(', ', $info['requires']), @@ -874,9 +874,11 @@ function system_modules_confirm_form($mo */ function system_modules_submit($form, &$form_state) { include_once DRUPAL_ROOT . '/includes/install.inc'; + + // Builds list of modules. $modules = array(); - // If we're not coming from the confirmation form, build the list of modules. if (!isset($form_state['storage'])) { + // If we're not coming from the confirmation form, build the module list. foreach ($form_state['values']['modules'] as $group_name => $group) { foreach ($group as $module => $enabled) { $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']); @@ -884,122 +886,101 @@ function system_modules_submit($form, &$ } } else { - // If we are coming from the confirmation form, fetch - // the modules out of $form_state. + // If we coming from the confirmation form, fetch modules from $form_state. $modules = $form_state['storage']['modules']; } - // Get a list of all modules, it will be used to find which module requires - // which. + // Collect data for all modules to be able to determine dependencies. $files = system_get_module_data(); - // The modules to be enabled. - $modules_to_be_enabled = array(); - // The modules to be disabled. - $disable_modules = array(); - // The modules to be installed. - $new_modules = array(); - // Modules that need to be switched on because other modules require them. - $more_modules = array(); - // Go through each module, finding out if we should enable, install, or - // disable it. Also, we find out if there are modules it requires that are - // not enabled. + // Sorts modules by weight. + $sort = array(); + foreach (array_keys($modules) as $module) { + $sort[$module] = $files[$module]->sort; + } + array_multisort($sort, $modules); + + // Makes sure all required modules are set to be enabled. + $more_required = array(); foreach ($modules as $name => $module) { - // If it's enabled, find out whether to just - // enable it, or install it. if ($module['enabled']) { - if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { - $new_modules[$name] = $name; - } - elseif (!module_exists($name)) { - $modules_to_be_enabled[$name] = $name; - } - // If we're not coming from a confirmation form, search for modules the - // new ones require and see whether there are any that additionally - // need to be switched on. - if (empty($form_state['storage'])) { - foreach ($form['modules'][$module['group']][$name]['#requires'] as $requires => $v) { - if (!$modules[$requires]['enabled']) { - if (!isset($more_modules[$name])) { - $more_modules[$name]['name'] = $files[$name]->info['name']; - } - $more_modules[$name]['requires'][$requires] = $files[$requires]->info['name']; - } - $modules[$requires] = array('group' => $files[$requires]->info['package'], 'enabled' => TRUE); + + // Checks that all dependencies are set to be enabled. Stores the ones + // that are not in $dependencies variable so that the user can be alerted + // in the confirmation form that more modules need to be enabled. + $dependencies = array(); + foreach (array_keys($files[$name]->requires) as $required) { + if (!$modules[$required]['enabled']) { + $dependencies[] = $files[$required]->info['name']; + $modules[$required]['enabled'] = 1; } } + + // Stores additional modules that need to be enabled in $more_required. + if (!empty($dependencies)) { + $more_required[$name] = array( + 'name' => $files[$name]->info['name'], + 'requires' => $dependencies, + ); + } } } - // A second loop is necessary, otherwise the modules set to be enabled in the - // previous loop would not be found. - foreach ($modules as $name => $module) { - if (module_exists($name) && !$module['enabled']) { - $disable_modules[$name] = $name; - } + + // Redirects to confirmation form if more modules need to be enabled. + if (!empty($more_required) && !isset($form_state['values']['confirm'])) { + $form_state['storage'] = array('more_required' => $more_required, 'modules' => $modules); + return; } - if ($more_modules) { - // If we need to switch on more modules because other modules require - // them and they haven't confirmed, don't process the submission yet. Store - // the form submission data needed later. - if (!isset($form_state['values']['confirm'])) { - $form_state['storage'] = array('more_modules' => $more_modules, 'modules' => $modules); - return; - } - // Otherwise, install or enable the modules. - else { - foreach ($form_state['storage']['more_modules'] as $info) { - foreach ($info['requires'] as $requires => $name) { - if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { - $new_modules[$name] = $name; - } - else { - $modules_to_be_enabled[$name] = $name; - } + + // Invokes hook_requirements('install'). If failures are detected, makes sure + // the dependent modules aren't installed either. + foreach ($modules as $name => $module) { + // Only invoke hook_requirements() on modules that are going to be installed. + if ($module['enabled'] && drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { + if (!drupal_check_module($name)) { + $modules[$name]['enabled'] = 0; + foreach (array_keys($files[$name]->required_by) as $required_by) { + $modules[$required_by] = 0; } } } } - // Now we have installed every module as required (either by the user or - // because other modules require them) so we don't need the temporary - // storage anymore. - unset($form_state['storage']); - $old_module_list = module_list(); + // Initializes array of actions. + $actions = array( + 'enable' => array(), + 'disable' => array(), + 'install' => array(), + ); - // Enable the modules needing enabling. - if (!empty($modules_to_be_enabled)) { - $sort = array(); - foreach ($modules_to_be_enabled as $module) { - $sort[$module] = $files[$module]->sort; - } - array_multisort($sort, $modules_to_be_enabled); - module_enable($modules_to_be_enabled); - } - // Disable the modules that need disabling. - if (!empty($disable_modules)) { - $sort = array(); - foreach ($disable_modules as $module) { - $sort[$module] = $files[$module]->sort; - } - array_multisort($sort, $disable_modules); - module_disable($disable_modules); - } - - // Install new modules. - if (!empty($new_modules)) { - $sort = array(); - foreach ($new_modules as $key => $module) { - if (!drupal_check_module($module)) { - unset($new_modules[$key]); + // Builds arrays of modules that need to be enabled, disabled, and installed. + foreach ($modules as $name => $module) { + if ($module['enabled']) { + if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { + $actions['install'][] = $name; + } + elseif (!module_exists($name)) { + $actions['enable'][] = $name; } - $sort[$module] = $files[$module]->sort; } - array_multisort($sort, $new_modules); - drupal_install_modules($new_modules); + elseif (module_exists($name)) { + $actions['disable'][] = $name; + } } - $current_module_list = module_list(TRUE); - if ($old_module_list != $current_module_list) { + // Gets list of modules prior to install process, unsets $form_state['storage'] + // so we don't get redirected back to the confirmation form. + $pre_install_list = module_list(); + unset($form_state['storage']); + + // Installs, enables, and disables modules. + drupal_install_modules($actions['install']); + module_enable($actions['enable']); + module_disable($actions['disable']); + + // Gets module list after install process, displays message if there are changes. + $post_install_list = module_list(TRUE); + if ($pre_install_list != $post_install_list) { drupal_set_message(t('The configuration options have been saved.')); } @@ -1018,7 +999,7 @@ function system_modules_submit($form, &$ // Notify locale module about module changes, so translations can be // imported. This might start a batch, and only return to the redirect // path after that. - module_invoke('locale', 'system_update', $new_modules); + module_invoke('locale', 'system_update', $actions['install']); // Synchronize to catch any actions that were added or removed. actions_synchronize(); @@ -1932,7 +1913,7 @@ function system_php() { function system_batch_page() { require_once DRUPAL_ROOT . '/includes/batch.inc'; $output = _batch_page(); - + // Use the same theme that the page that started the batch. $batch = &batch_get(); $GLOBALS['custom_theme'] = $batch['theme']; Index: modules/system/system.test =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.test,v retrieving revision 1.82 diff -u -p -r1.82 system.test --- modules/system/system.test 9 Oct 2009 07:48:07 -0000 1.82 +++ modules/system/system.test 9 Oct 2009 19:40:08 -0000 @@ -159,6 +159,35 @@ class EnableDisableTestCase extends Modu } /** + * Tests failure of hook_requirements('install'). + */ +class HookRequirementsTestCase extends ModuleTestCase { + public static function getInfo() { + return array( + 'name' => 'Requirements hook failure', + 'description' => "Attempts enabling a module that fails hook_requirements('install').", + 'group' => 'Module', + ); + } + + /** + * Assert that a module cannot be installed if it fails hook_requirements(). + */ + function testHookRequirementsFailure() { + $this->assertModules(array('requirements1_test'), FALSE); + + // Attempt to install the requirements1_test module. + $edit = array(); + $edit['modules[Core][requirements1_test][enable]'] = 'requirements1_test'; + $this->drupalPost('admin/config/modules', $edit, t('Save configuration')); + + // Makes sure the module was NOT installed. + $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.')); + $this->assertModules(array('requirements1_test'), FALSE); + } +} + +/** * Test module dependency functionality. */ class ModuleDependencyTestCase extends ModuleTestCase { @@ -195,6 +224,25 @@ class ModuleDependencyTestCase extends M $this->assertTableCount('languages', TRUE); $this->assertTableCount('locale', TRUE); } + + /** + * Tests enabling a module that depends on a module which fails hook_requirements(). + */ + function testEnableRequirementsFailureDependency() { + $this->assertModules(array('requirements1_test'), FALSE); + $this->assertModules(array('requirements2_test'), FALSE); + + // Attempt to install both modules at the same time. + $edit = array(); + $edit['modules[Core][requirements1_test][enable]'] = 'requirements1_test'; + $edit['modules[Core][requirements2_test][enable]'] = 'requirements2_test'; + $this->drupalPost('admin/config/modules', $edit, t('Save configuration')); + + // Makes sure the modules were NOT installed. + $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.')); + $this->assertModules(array('requirements1_test'), FALSE); + $this->assertModules(array('requirements2_test'), FALSE); + } } /**