Index: includes/module.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/module.inc,v retrieving revision 1.85 diff -u -F^f -r1.85 module.inc --- includes/module.inc 31 Aug 2006 22:12:15 -0000 1.85 +++ includes/module.inc 8 Sep 2006 19:13:41 -0000 @@ -130,7 +130,27 @@ function module_rebuild_cache() { db_query("INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, $file->info['description'], 'module', $file->filename, $file->status, $file->throttle, $bootstrap); } } + $files = _module_build_dependents($files); + return $files; +} +/** + * Find dependents; modules that are dependent on by other modules. + * Adds an array of dependents to the $file->info array. + * + * @return + * The list of files array with dependents added where applicable. + */ +function _module_build_dependents($files) { + foreach($files as $filename => $file) { + if (is_array($file->info['dependencies'])) { + foreach ($file->info['dependencies'] as $dependency) { + if (!empty($files[$dependency]) && is_array($files[$dependency]->info)){ + $files[$dependency]->info['dependents'][] = $filename; + } + } + } + } return $files; } @@ -148,7 +168,17 @@ function module_rebuild_cache() { * and variable_set() for that. * - You may not use double-quotes in a value. * - * @param $filename + * Information stored in the module.info file: + * name - The real name of the module for display purposes. + * description - A brief description of the module. + * dependencies - A space delimited list of the short names (shortname) of other modules this module depends on. + * + * Example of .info file: + * name = Forum + * description = Enables threaded discussions about general topics. + * dependencies = taxonomy comment + * + * @param $filename * The file we are parsing. Accepts file with relative or absolute path. * @return * The info array. @@ -159,6 +189,9 @@ function _module_parse_info_file($filena if (file_exists($filename)) { $info = parse_ini_file($filename); } + if(isset($info['dependencies'])) { + $info['dependencies'] = explode(" ", $info['dependencies']); + } return $info; } Index: modules/forum/forum.info =================================================================== RCS file: /cvs/drupal/drupal/modules/forum/forum.info,v retrieving revision 1.1 diff -u -F^f -r1.1 forum.info --- modules/forum/forum.info 31 Aug 2006 20:22:35 -0000 1.1 +++ modules/forum/forum.info 8 Sep 2006 19:13:41 -0000 @@ -1,4 +1,4 @@ ; $Id: forum.info,v 1.1 2006/08/31 20:22:35 dries Exp $ name = Forum description = Enables threaded discussions about general topics. - +dependencies = taxonomy comment views Index: modules/system/admin.css =================================================================== RCS file: /cvs/drupal/drupal/modules/system/admin.css,v retrieving revision 1.7 diff -u -F^f -r1.7 admin.css --- modules/system/admin.css 6 Sep 2006 08:35:59 -0000 1.7 +++ modules/system/admin.css 8 Sep 2006 19:13:41 -0000 @@ -39,6 +39,11 @@ padding-right: 4px; } +div.admin-missing, div.admin-required { + font-size: 0.9em; + color: #444; +} + /** * Formatting for status report */ @@ -63,3 +68,4 @@ table.system-status-report tr.ok th { background-image: url('../../misc/watchdog-ok.png'); } + Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.367 diff -u -F^f -r1.367 system.module --- modules/system/system.module 7 Sep 2006 08:03:08 -0000 1.367 +++ modules/system/system.module 8 Sep 2006 19:13:42 -0000 @@ -1217,49 +1217,184 @@ function system_themes_submit($form_id, } /** - * Menu callback; displays a listing of all modules. + * Menu callback; provides module enable/disable interface. + * + * Modules can be enabled or disabled and set for throttling if the throttle module is enabled. + * The list of modules gets populated by module.info files, which contain each module's name, + * description and dependencies. + * \sa _module_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. + * + * @return + * The form array. */ + function system_modules() { - // Get current list of modules + // Get current list of modules. $files = module_rebuild_cache(); + + // Store module list for validation callback. + $form['validation_modules'] = array('#type' => 'value', '#value' => $files); + + // Create storage for disabled modules as browser will disable checkboxes. + $form['disabled_modules'] = array('#type' => 'value', '#value' => array()); + // Array for disabling checkboxes in callback system_module_disable. + $disabled = array(); + // Traverse the files retrieved and build the form. foreach ($files as $filename => $file) { - $info = $file->info; - $form['name'][$file->name] = array('#value' => $info['name']); - $form['description'][$file->name] = array('#value' => t($info['description'])); - $options[$file->name] = ''; + $form['name'][$filename] = array('#value' => $file->info['name']); + $form['description'][$filename] = array('#value' => t($file->info['description'])); + $options[$filename] = ''; if ($file->status) { $status[] = $file->name; } if ($file->throttle) { $throttle[] = $file->name; } + + $missing_dependencies = array(); + // Check for missing dependencies. + if (is_array($file->info['dependencies'])) { + foreach ($file->info['dependencies'] as $dependency) { + if (!isset($files[$dependency]) || !$files[$dependency]->status) { + $disabled[] = $filename; + if (isset($files[$dependency])) { + $missing_dependencies[] = $files[$dependency]->info['name']; + } + else { + $missing_dependencies[] = t('Please download @dependency.', array('!modules' => 'http://drupal.org/project/Modules/', '@dependency' => $dependency)); + } + } + } + + // Add text for missing dependencies. + if (!empty($missing_dependencies)) { + $form['description'][$filename]['missing_dependencies'] = array( + '#value' => t('Missing dependencies: !missing_dependencies', array('!missing_dependencies' => implode(', ', $missing_dependencies))), + '#prefix' => '
', + '#suffix' => '
', + ); + } + } + + // Mark dependents disabled so user can not remove modules being depended on. + $dependents = array(); + if(is_array($file->info['dependents'])){ + foreach ($file->info['dependents'] as $dependent) { + if ($files[$dependent]->status == 1) { + $dependents[] = $files[$dependent]->info['name']; + $form['disabled_modules']['#value'][$filename] = $file->status; + if (!in_array($filename, $disabled)) { + $disabled[] = $filename; + } + } + } + } + + // Add text for enabled dependents. + if(!empty($dependents)){ + $form['description'][$filename]['required'] = array( + '#value' => t('Required by: @required', array('@required' => implode(', ', $dependents))), + '#prefix' => '
', + '#suffix' => '
', + ); + } } - // Handle status checkboxes, including overriding the generated - // checkboxes for required modules. - $form['status'] = array('#type' => 'checkboxes', '#default_value' => $status, '#options' => $options); + + // Handle status checkboxes, including overriding + // the generated checkboxes for required modules. + $form['status'] = array( + '#type' => 'checkboxes', + '#default_value' => $status, + '#options' => $options, + '#process' => array( + 'expand_checkboxes' => array(), + 'system_module_disable' => array($disabled), + ), + ); $required = array('block', 'filter', 'node', 'system', 'user', 'watchdog'); foreach ($required as $require) { - $form['status'][$require] = array('#type' => 'hidden', '#value' => 1, '#suffix' => t('required')); + $form['status'][$require] = array( + '#type' => 'hidden', + '#value' => 1, + '#suffix' => t('required'), + ); } - /** - * Handle throttle checkboxes, including overriding the generated checkboxes for required modules. - */ + // Handle throttle checkboxes, including overriding the + // generated checkboxes for required modules. if (module_exists('throttle')) { - $form['throttle'] = array('#type' => 'checkboxes', '#default_value' => $throttle, '#options' => $options); + $form['throttle'] = array( + '#type' => 'checkboxes', + '#default_value' => $throttle, + '#options' => $options, + ); $throttle_required = array_merge($required, array('throttle')); foreach ($throttle_required as $require) { - $form['throttle'][$require] = array('#type' => 'hidden', '#value' => 0, '#suffix' => t('required')); + $form['throttle'][$require] = array( + '#type' => 'hidden', + '#value' => 0, + '#suffix' => t('required'), + ); } } - $form['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} +/** + * Form process callback function to disable check boxes. + */ +function system_module_disable($form, $form_values, $disabled) { + foreach ($disabled as $key) { + $form[$key]['#attributes']['disabled'] = 'disabled'; + } return $form; } +/** + * Validate callback; performs dependency integrity checks to ensure that modules don't get added + * if dependencies fail. + */ +function system_modules_validate($form_id, $form_values, $form) { + // Recheck values for submitted dependency errors. + foreach ($form_values['validation_modules'] as $name => $module) { + $failed_dependencies = array(); + // Is module enabled and does it have dependencies. + if (!$module->status && $form_values['status'][$name] && is_array($module->info['dependencies'])) { + foreach ($module->info['dependencies'] as $dependency) { + // Check if dependendency is enabled on the form. + if (!($form_values['status'][$dependency])) { + // Give appropriate task to enable dependency. + $failed_dependencies['dependencies'][$dependency] = isset($form_values['validation_modules'][$dependency]) ? $form_values['validation_modules'][$dependency]->info['name'] : $dependency; + $failed_dependencies['tasks'][] = t('Please enable the %dependent module to continue.', array('%dependent' => $failed_dependencies['dependencies'][$dependency])); + } + } + } + if (!empty($failed_dependencies)) { + $t_argument = array( + '%module' => $form_values['validation_modules'][$name]->info['name'], + '%dependency' => implode(', ', $failed_dependencies['dependencies']), + '!tasks' => theme('item_list',$failed_dependencies['tasks']), + ); + form_set_error("status][$name", t('Could not enable the %module module, as it depends on %dependency. !tasks', $t_argument)); + } + } +} + +/** + * Theme call back for the modules form. + */ function theme_system_modules($form) { foreach (element_children($form['name']) as $key) { $row = array(); @@ -1285,13 +1420,18 @@ function theme_system_modules($form) { return $output; } - +/** + * Submit callback; handles modules form submission. + */ function system_modules_submit($form_id, $edit) { include_once './includes/install.inc'; $new_modules = array(); - // Enable/disable modules that have already been installed - foreach ($edit['status'] as $key => $choice) { + // Merge in disabled active modules since they should be enabled. + // They don't appear because disabled checkboxes are not submited + // by browsers. + $modules = array_merge($edit['status'], $edit['disabled_modules']); + foreach ($modules as $key => $choice) { if ($choice) { if (drupal_get_installed_schema_version($key) == SCHEMA_UNINSTALLED) { $new_modules[] = $key;