=== added file 'includes/graph.inc' --- includes/graph.inc 1970-01-01 00:00:00 +0000 +++ includes/graph.inc 2008-09-29 19:54:15 +0000 @@ -0,0 +1,60 @@ + $data) { + $roots = array_diff_key($roots, $data['edges']); + } + } + foreach ($roots as $start) { + if (!empty($graph[$start]['edges'])) { + _drupal_depth_first_search($graph, $start); + } + } +} + +function _drupal_depth_first_search(&$graph, $start, $reverse_path = array()) { + $reverse_path[$start] = TRUE; + $graph[$start]['paths'] = array(); + foreach ($graph[$start]['edges'] as $end => $v) { + $graph[$start]['paths'][$end] = TRUE; + if (!isset($graph[$end]['reverse_paths'])) { + $graph[$end]['reverse_paths'] = array(); + } + $graph[$end]['reverse_paths'] += $reverse_path; + if (isset($graph[$end]['reverse_paths'][$end])) { + echo 'cycle'; + exit; + } + $graph[$start]['paths'] += _drupal_depth_first_search($graph, $end, $reverse_path); + } + return $graph[$start]['paths']; +} === modified file 'includes/module.inc' --- includes/module.inc 2008-10-12 04:30:05 +0000 +++ includes/module.inc 2008-10-13 00:26:05 +0000 @@ -138,69 +138,34 @@ function module_rebuild_cache() { } /** - * Find dependencies any level deep and fill in dependents information too. - * - * If module A depends on B which in turn depends on C then this function will - * add C to the list of modules A depends on. This will be repeated until - * module A has a list of all modules it depends on. If it depends on itself, - * called a circular dependency, that's marked by adding a nonexistent module, - * called -circular- to this list of modules. Because this does not exist, - * it'll be impossible to switch module A on. - * - * Also we fill in a dependents array in $file->info. Using the names above, - * the dependents array of module B lists A. + * Find dependencies any level deep and fill in required by information too. * * @param $files * The array of filesystem objects used to rebuild the cache. * @return - * The same array with dependencies and dependents added where applicable. + * The same array with the new keys for each module: + * depends_on is an array with the keys being the modules that this module + * depends on. + * required_by is an array with the keys being the modules that will not work + * without this module. */ function _module_build_dependencies($files) { - do { - $new_dependency = FALSE; - foreach ($files as $filename => $file) { - // We will modify this object (module A, see doxygen for module A, B, C). - $file = &$files[$filename]; - if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) { - foreach ($file->info['dependencies'] as $dependency_name) { - // This is a nonexistent module. - if ($dependency_name == '-circular-' || !isset($files[$dependency_name])) { - continue; - } - // $dependency_name is module B (again, see doxygen). - $files[$dependency_name]->info['dependents'][$filename] = $filename; - $dependency = $files[$dependency_name]; - if (isset($dependency->info['dependencies']) && is_array($dependency->info['dependencies'])) { - // Let's find possible C modules. - foreach ($dependency->info['dependencies'] as $candidate) { - if (array_search($candidate, $file->info['dependencies']) === FALSE) { - // Is this a circular dependency? - if ($candidate == $filename) { - // As a module name can not contain dashes, this makes - // impossible to switch on the module. - $candidate = '-circular-'; - // Do not display the message or add -circular- more than once. - if (array_search($candidate, $file->info['dependencies']) !== FALSE) { - continue; - } - drupal_set_message(t('%module is part of a circular dependency. This is not supported and you will not be able to switch it on.', array('%module' => $file->info['name'])), 'error'); - } - else { - // We added a new dependency to module A. The next loop will - // be able to use this as "B module" thus finding even - // deeper dependencies. - $new_dependency = TRUE; - } - $file->info['dependencies'][] = $candidate; - } - } - } - } + include_once DRUPAL_ROOT .'/includes/graph.inc'; + $roots = $files; + foreach ($files as $filename => $file) { + $graph[$file->name]['edges'] = array(); + if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) { + foreach ($file->info['dependencies'] as $dependency_name) { + $graph[$file->name]['edges'][$dependency_name] = TRUE; + unset($roots[$dependency_name]); } - // Don't forget to break the reference. - unset($file); } - } while ($new_dependency); + } + drupal_depth_first_search($graph, array_keys($roots)); + foreach ($graph as $module => $data) { + $files[$module]->required_by= isset($data['reverse_paths']) ? $data['reverse_paths'] : array(); + $files[$module]->depends_on = isset($data['paths']) ? $data['paths'] : array(); + } return $files; } === modified file 'modules/simpletest/drupal_web_test_case.php' --- modules/simpletest/drupal_web_test_case.php 2008-10-12 08:30:05 +0000 +++ modules/simpletest/drupal_web_test_case.php 2008-10-13 02:51:27 +0000 @@ -1703,4 +1703,36 @@ class DrupalWebTestCase { $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code; return $this->assertTrue($match, $message ? $message : t('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser')); } + + /** + * Assert that the given XPath expression can be found within the document. + * + * @param $expression + * The XPath expression to test for. + * @param $message + * Message to display. + * @param $group + * The group this message belongs to. + * @return + * TRUE on pass, FALSE on fail. + */ + function assertXPath($expression, $message = '', $group = 'Other') { + return $this->_assert((bool)$this->xpath($expression), $message, $group); + } + + /** + * Assert that the given XPath expression can't be found within the document. + * + * @param $expression + * The XPath expression to test for. + * @param $message + * Message to display. + * @param $group + * The group this message belongs to. + * @return + * TRUE on pass, FALSE on fail. + */ + function assertNoXpath($expression, $message = '', $group = 'Other') { + return $this->_assert((bool)!$this->xpath($expression), $message, $group); + } } === modified file 'modules/system/system.admin.inc' --- modules/system/system.admin.inc 2008-10-12 04:30:05 +0000 +++ modules/system/system.admin.inc 2008-10-13 01:54:56 +0000 @@ -592,9 +592,10 @@ function _system_is_incompatible(&$incom * description and dependencies. * @see drupal_parse_info_file for information on module.info descriptors. * - * Dependency checking is performed to ensure that a module cannot be enabled if the module has - * disabled dependencies and also to ensure that the module cannot be disabled if the module has - * enabled dependents. + * Dependency checking is performed to ensure that: + * a module can not be enabled if there are disabled modules it requires. + * a module can not be disabled if there are enabled modules which depend on + * it. * * @param $form_state * An associative array containing the current state of the form. @@ -628,11 +629,10 @@ function system_modules($form_state = ar // and if there are unfilled dependencies, then form_state['storage'] is // filled, triggering a rebuild. In this case we need to show a confirm // form. - return system_modules_confirm_form($files, $form_state['storage']); + return system_modules_confirm_form($form_state['storage']); } $dependencies = array(); $modules = array(); - $form['modules'] = array('#tree' => TRUE); // Used when checking if module implements a help page. $help_arg = module_exists('help') ? drupal_help_arg() : FALSE; @@ -641,19 +641,18 @@ function system_modules($form_state = ar foreach ($files as $filename => $module) { $extra = array(); $extra['enabled'] = (bool) $module->status; - // If this module has dependencies, add them to the array. - if (is_array($module->info['dependencies'])) { - foreach ($module->info['dependencies'] as $dependency) { - if (!isset($files[$dependency])) { - $extra['dependencies'][$dependency] = drupal_ucfirst($dependency) . t(' (missing)'); - $extra['disabled'] = TRUE; - } - elseif (!$files[$dependency]->status) { - $extra['dependencies'][$dependency] = $files[$dependency]->info['name'] . t(' (disabled)'); - } - else { - $extra['dependencies'][$dependency] = $files[$dependency]->info['name'] . t(' (enabled)'); - } + // If this module depends on other modules, then list those along with + // their status (missing, disabled, enabled). + foreach ($module->depends_on as $dependency => $v) { + if (!isset($files[$dependency])) { + $extra['depends_on'][$dependency] = drupal_ucfirst($dependency) . t(' (missing)'); + $extra['disabled'] = TRUE; + } + elseif (!$files[$dependency]->status) { + $extra['depends_on'][$dependency] = $files[$dependency]->info['name'] . t(' (disabled)'); + } + else { + $extra['depends_on'][$dependency] = $files[$dependency]->info['name'] . t(' (enabled)'); } } // Generate link for module's help page, if there is one. @@ -663,18 +662,19 @@ function system_modules($form_state = ar $extra['help'] = theme('more_help_link', url("admin/help/$filename")); } } - // Mark dependents disabled so user can not remove modules being depended on. + // If this module is required by others then list those and make it + // impossible to disable this one. $dependents = array(); - foreach ($module->info['dependents'] as $dependent) { - if ($files[$dependent]->status == 1) { - $extra['dependents'][] = $files[$dependent]->info['name'] . t(' (enabled)'); + foreach ($module->required_by as $required_by => $v) { + if ($files[$required_by]->status == 1) { + $extra['required_by'][] = $files[$required_by]->info['name'] . t(' (enabled)'); $extra['disabled'] = TRUE; } else { - $extra['dependents'][] = $files[$dependent]->info['name'] . t(' (disabled)'); + $extra['required_by'][] = $files[$required_by]->info['name'] . t(' (disabled)'); } } - $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra); + $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->name, $module->info, $extra); } // Add basic information to the fieldsets. foreach (element_children($form['modules']) as $package) { @@ -711,14 +711,14 @@ function system_sort_modules_by_info_nam /** * Build a table row for the system modules page. */ -function _system_modules_build_row($info, $extra) { +function _system_modules_build_row($module_name, $info, $extra) { // Add in the defaults. $extra += array( - 'dependencies' => array(), - 'dependents' => array(), 'disabled' => FALSE, 'enabled' => FALSE, 'help' => '', + 'depends_on' => array(), + 'required_by' => array(), ); $form = array( '#tree' => TRUE, @@ -733,8 +733,8 @@ function _system_modules_build_row($info $form['version'] = array( '#markup' => $info['version'], ); - $form['#dependencies'] = $extra['dependencies']; - $form['#dependents'] = $extra['dependents']; + $form['#depends_on'] = $extra['depends_on']; + $form['#required_by'] = $extra['required_by']; // Check the compatibilities. $compatible = TRUE; @@ -765,6 +765,7 @@ function _system_modules_build_row($info '#type' => 'checkbox', '#title' => t('Enable'), '#default_value' => $extra['enabled'], + '#parents' => array('modules', $module_name), ); if ($extra['disabled']) { $form['enable']['#disabled'] = TRUE; @@ -783,10 +784,28 @@ function _system_modules_build_row($info '#markup' => $extra['help'], ); } + return $form; } /** + * This function returns which action is necessary for a module to be enabled. + * + * @param $module + * The name of the module. + * @return + * Either 'install' or 'enable' or nothing if it's already enabled. + */ +function system_modules_action($module) { + if (drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED && drupal_check_module($module)) { + return 'install'; + } + elseif (!module_exists($module)) { + return 'enable'; + } +} + +/** * Display confirmation form for dependencies. * * @param $modules @@ -797,33 +816,15 @@ function _system_modules_build_row($info * form field values from the previous screen. * @ingroup forms */ -function system_modules_confirm_form($modules, $storage) { - $form = array(); - $items = array(); - - $form['validation_modules'] = array('#type' => 'value', '#value' => $modules); - $form['status']['#tree'] = TRUE; - - foreach ($storage['dependencies'] as $info) { - $t_argument = array( - '@module' => $info['name'], - '@dependencies' => implode(', ', $info['dependencies']), - ); - $items[] = format_plural(count($info['dependencies']), 'You must enable the @dependencies module to install @module.', 'You must enable the @dependencies modules to install @module.', $t_argument); - } - $form['text'] = array('#markup' => theme('item_list', $items)); - - if ($form) { - // Set some default form values - $form = confirm_form( - $form, - t('Some required modules must be enabled'), - 'admin/build/modules', - t('Would you like to continue with enabling the above?'), - t('Continue'), - t('Cancel')); - return $form; - } +function system_modules_confirm_form($storage) { + return confirm_form( + array('text' => array('#markup' => $storage['text'])), + t('Some required modules must be enabled'), + 'admin/build/modules', + t('Would you like to continue with enabling the above?'), + t('Continue'), + t('Cancel')); + return $form; } /** @@ -832,116 +833,88 @@ function system_modules_confirm_form($mo function system_modules_submit($form, &$form_state) { include_once DRUPAL_ROOT . '/includes/install.inc'; $modules = array(); - // If we're not coming from the confirmation form, build the list of modules. - if (!isset($form_state['storage'])) { - foreach ($form_state['values']['modules'] as $group_name => $group) { - foreach ($group as $module => $enabled) { - $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']); - } - } - } - else { - // If we are coming from the confirmation form, fetch - // the modules out of $form_state. - $modules = $form_state['storage']['modules']; - } + // If we're not coming from the confirmation form, get the list of modules + // submitted in the form. + // If we are coming from the confirmation form, fetch the modules out of + // $form_state. + $confirmation_done = !empty($form_state['storage']); + $modules = $form_state[$confirmation_done ? 'storage' : 'values']['modules']; // Get a list of all modules, for building dependencies with. $files = module_rebuild_cache(); - // The modules to be enabled. - $modules_to_be_enabled = array(); + // The modules to be enabled or installed. + $new_modules = array(); // The modules to be disabled. $disable_modules = array(); - // The modules to be installed. - $new_modules = array(); - // The un-met dependencies. - $dependencies = array(); - // Go through each module, finding out - // if we should enable, install, or disable it, - // and if it has any un-met dependencies. - 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 dependencies. Otherwise, the user will have already - // approved of the depdent modules being enabled. - if (empty($form_state['storage'])) { - foreach ($form['modules'][$module['group']][$name]['#dependencies'] as $dependency => $string) { - if (!$modules[$dependency]['enabled']) { - if (!isset($dependencies[$name])) { - $dependencies[$name]['name'] = $files[$name]->info['name']; - } - $dependencies[$name]['dependencies'][$dependency] = $files[$dependency]->info['name']; - } - $modules[$dependency] = array('group' => $files[$dependency]->info['package'], 'enabled' => TRUE); + // If more modules are needed than what the user requested. + $more_modules = array(); + // Build up a list of items to confirm. + $confirm_items = array(); + // The modules checked in the form. + $modules_checked = array_filter($modules); + // Go through each module marked as enabled, finding out if we should enable + // or install it and if we need to enable the modules it depends on. + foreach ($modules_checked as $name => $v) { + if ($action = system_modules_action($name)) { + $new_modules[$action][$name] = $name; + // If we're not coming from a confirmation form, check whether we need + // enable more modules. + if (!$confirmation_done && $files[$name]->depends_on) { + $depends_on = array(); + foreach (array_diff_key($files[$name]->depends_on, $modules_checked) as $more_module => $v) { + // We need to postpone this module as it depends on a module that's + // not yet enabled. + unset($new_modules[$action][$name]); + // Store it for later. + $form_state['storage']['modules'][$name] = TRUE; + $form_state['storage']['modules'][$more_module] = TRUE; + $depends_on[] = $files[$more_module]->name; + } + if ($depends_on) { + $t_argument = array( + '@module' => $files[$name]->name, + '@depends_on' => implode(', ', $depends_on), + ); + $confirm_items[] = format_plural(count($depends_on), 'You must enable the @depends_on module to install @module.', 'You must enable the @depends_on modules to install @module.', $t_argument); } } } } - // 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; - } + if ($confirm_items) { + $form_state['storage']['text'] = theme('item_list', $confirm_items); } - if ($dependencies) { - // If there where un-met dependencies 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('dependencies' => $dependencies, 'modules' => $modules); - return; - } - // Otherwise, install or enable the modules. - else { - $dependencies = $form_storage['dependencies']; - foreach ($dependencies as $info) { - foreach ($info['dependencies'] as $dependency => $name) { - if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { - $new_modules[$name] = $name; - } - else { - $modules_to_be_enabled[$name] = $name; - } - } - } + else { + // If we have no dependencies, or the dependencies are confirmed + // to be installed, we don't need the temporary storage anymore. + unset($form_state['storage']); + } + foreach ($modules as $name => $enabled) { + if (!$enabled && module_exists($name)) { + $new_modules['disable'][$name] = $name; } } - // If we have no dependencies, or the dependencies are confirmed - // to be installed, we don't need the temporary storage anymore. - unset($form_state['storage']); - - $old_module_list = module_list(); + $saved = FALSE; // Enable the modules needing enabling. - if (!empty($modules_to_be_enabled)) { - module_enable($modules_to_be_enabled); + if (!empty($new_modules['enable'])) { + module_enable($new_modules['enable']); + $saved = TRUE; } // Disable the modules that need disabling. - if (!empty($disable_modules)) { - module_disable($disable_modules); + if (!empty($new_modules['disable'])) { + module_disable($new_modules['disable']); + $saved = TRUE; } - // Install new modules. - if (!empty($new_modules)) { - foreach ($new_modules as $key => $module) { - if (!drupal_check_module($module)) { - unset($new_modules[$key]); - } - } - drupal_install_modules($new_modules); + if (!empty($new_modules['install'])) { + drupal_install_modules($new_modules['install']); + $saved = TRUE; } - $current_module_list = module_list(TRUE, FALSE); - if ($old_module_list != $current_module_list) { + // Reset module list. + module_list(TRUE, FALSE); + if ($saved) { drupal_set_message(t('The configuration options have been saved.')); } @@ -2096,11 +2069,11 @@ function theme_system_modules_fieldset($ } // Add the description, along with any dependencies. $description .= drupal_render($module['description']); - if ($module['#dependencies']) { - $description .= '