Skip to content
system.admin.inc 81.6 KiB
Newer Older
 * @file
 * Admin page callbacks for the system module.
 */

/**
 * Menu callback; Provide the administration overview page.
 */
function system_main_admin_page($arg = NULL) {
  // If we received an argument, they probably meant some other page.
  // Let's 404 them since the menu system cannot be told we do not
  // accept arguments.
  if (isset($arg) && substr($arg, 0, 3) != 'by-') {
    return drupal_not_found();
  }

  // Check for status report errors.
  if (system_status(TRUE) && user_access('administer site configuration')) {
    drupal_set_message(t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'error');
  if ($admin = db_fetch_array(db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin' AND module = 'system'"))) {
    $result = db_query("
      SELECT m.*, ml.*
      FROM {menu_links} ml
      INNER JOIN {menu_router} m ON ml.router_path = m.path
      WHERE ml.link_path != 'admin/help' AND menu_name = '%s' AND ml.plid = %d AND hidden = 0", $admin);
    while ($item = db_fetch_array($result)) {
      _menu_link_translate($item);
      if (!$item['access']) {
        continue;
      }
      // The link 'description' either derived from the hook_menu 'description'
      // or entered by the user via menu module is saved as the title attribute.
      if (!empty($item['localized_options']['attributes']['title'])) {
        $item['description'] = $item['localized_options']['attributes']['title'];
      }
      $block = $item;
      $block['content'] = '';
      if ($item['block_callback'] && function_exists($item['block_callback'])) {
        $function = $item['block_callback'];
        $block['content'] .= $function();
      }
      $block['content'] .= theme('admin_block_content', system_admin_menu_block($item));
      // Prepare for sorting as in function _menu_tree_check_access().
      // The weight is offset so it is always positive, with a uniform 5-digits.
      $blocks[(50000 + $item['weight']) .' '. $item['title'] .' '. $item['mlid']] = $block;
  if ($blocks) {
    ksort($blocks);
    return theme('admin_page', $blocks);
  }
  else {
    return t('You do not have any administrative items.');
  }
}


/**
 * Provide a single block from the administration menu as a page.
 * This function is often a destination for these blocks.
 * For example, 'admin/content/types' needs to have a destination to be valid
 * in the Drupal menu system, but too much information there might be
 * hidden, so we supply the contents of the block.
 */
function system_admin_menu_block_page() {
  $item = menu_get_item();
  if ($content = system_admin_menu_block($item)) {
    $output = theme('admin_block_content', $content);
  }
  else {
    $output = t('You do not have any administrative items.');
  }

/**
 * Menu callback; prints a listing of admin tasks for each installed module.
 */
function system_admin_by_module() {

  $modules = module_rebuild_cache();
  $menu_items = array();
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;

  foreach ($modules as $file) {
    $module = $file->name;
    if ($module == 'help') {
      continue;
    }

    $admin_tasks = system_get_module_admin_tasks($module);

    // Only display a section if there are any available tasks.
    if (count($admin_tasks)) {

      // Check for help links.
      if ($help_arg && module_invoke($module, 'help', "admin/help#$module", $help_arg)) {
        $admin_tasks[100] = l(t('Get help'), "admin/help/$module");
      }

      // Sort.
      ksort($admin_tasks);

      $menu_items[$file->info['name']] = array($file->info['description'], $admin_tasks);
    }
  }
  return theme('system_admin_by_module', $menu_items);
}

/**
 * Menu callback; displays a module's settings page.
 */
function system_settings_overview() {
  // Check database setup if necessary
  if (function_exists('db_check_setup') && empty($_POST)) {
    db_check_setup();
  }

  $item = menu_get_item('admin/settings');
  $content = system_admin_menu_block($item);

  $output = theme('admin_block_content', $content);

  return $output;
}

/**
 * Form builder; This function allows selection of the theme to show in administration sections.
 */
function system_admin_theme_settings() {
  $themes = system_theme_data();

  uasort($themes, 'system_sort_modules_by_info_name');

  $options[0] = '<'. t('System default') .'>';
  foreach ($themes as $theme) {
    $options[$theme->name] = $theme->info['name'];
  }

  $form['admin_theme'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#title' => t('Administration theme'),
    '#description' => t('Choose which theme the administration pages should display in. If you choose "System default" the administration pages will use the same theme as the rest of the site.'),
    '#default_value' => variable_get('admin_theme', '0'),
  );

  $form['node_admin_theme'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use administration theme for content editing'),
    '#description' => t('Use the administration theme when editing existing posts or creating new ones.'),
    '#default_value' => variable_get('node_admin_theme', '0'),
  );

  $form['#submit'][] = 'system_admin_theme_submit';
  return system_settings_form($form);
}

/**
 * Menu callback; displays a listing of all themes.
 */
function system_themes_form() {

  drupal_clear_css_cache();
  $themes = system_theme_data();

  uasort($themes, 'system_sort_modules_by_info_name');

  $status = array();
  $incompatible_core = array();
  $incompatible_php = array();

  foreach ($themes as $theme) {
    $screenshot = NULL;
    $theme_key = $theme->name;
    while ($theme_key) {
      if (file_exists($themes[$theme_key]->info['screenshot'])) {
        $screenshot = $themes[$theme_key]->info['screenshot'];
        break;
      }
      $theme_key = isset($themes[$theme_key]->info['base theme']) ? $themes[$theme_key]->info['base theme'] : NULL;
    }
    $screenshot = $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');

    $form[$theme->name]['screenshot'] = array('#value' => $screenshot);
    $form[$theme->name]['info'] = array(
      '#type' => 'value',
      '#value' => $theme->info,
    );
    $options[$theme->name] = '';

    if (!empty($theme->status) || $theme->name == variable_get('admin_theme', '0')) {
      $form[$theme->name]['operations'] = array('#value' => l(t('configure'), 'admin/build/themes/settings/'. $theme->name) );
    }
    else {
      // Dummy element for drupal_render. Cleaner than adding a check in the theme function.
      $form[$theme->name]['operations'] = array();
    }
    if (!empty($theme->status)) {
      $status[] = $theme->name;
    }
    else {
      // Ensure this theme is compatible with this version of core.
      if (!isset($theme->info['core']) || $theme->info['core'] != DRUPAL_CORE_COMPATIBILITY) {
        $incompatible_core[] = $theme->name;
      }
      if (version_compare(phpversion(), $theme->info['php']) < 0) {
        $incompatible_php[$theme->name] = $theme->info['php'];
      }
    }
  }

  $form['status'] = array(
    '#type' => 'checkboxes',
    '#options' => $options,
    '#default_value' => $status,
    '#incompatible_themes_core' => drupal_map_assoc($incompatible_core),
    '#incompatible_themes_php' => $incompatible_php,
  );
  $form['theme_default'] = array(
    '#type' => 'radios',
    '#options' => $options,
    '#default_value' => variable_get('theme_default', 'garland'),
  );
  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  $form['buttons']['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Reset to defaults'),
  );
  return $form;
}

/**
 * Process system_themes_form form submissions.
 */
function system_themes_form_submit($form, &$form_state) {

  // Store list of previously enabled themes and disable all themes
  $old_theme_list = $new_theme_list = array();
  foreach (list_themes() as $theme) {
    if ($theme->status) {
      $old_theme_list[] = $theme->name;
    }
  }
  db_query("UPDATE {system} SET status = 0 WHERE type = 'theme'");

  if ($form_state['values']['op'] == t('Save configuration')) {
    if (is_array($form_state['values']['status'])) {
      foreach ($form_state['values']['status'] as $key => $choice) {
        // Always enable the default theme, despite its status checkbox being checked:
        if ($choice || $form_state['values']['theme_default'] == $key) {
          system_initialize_theme_blocks($key);
          $new_theme_list[] = $key;
          db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' and name = '%s'", $key);
        }
      }
    }
    if (($admin_theme = variable_get('admin_theme', '0')) != '0' && $admin_theme != $form_state['values']['theme_default']) {
      drupal_set_message(t('Please note that the <a href="!admin_theme_page">administration theme</a> is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', array(
        '!admin_theme_page' => url('admin/settings/admin'),
        '%admin_theme' => $admin_theme,
        '%selected_theme' => $form_state['values']['theme_default'],
      )));
    }
    variable_set('theme_default', $form_state['values']['theme_default']);
  }
  else {
    // Revert to defaults: only Garland is enabled.
    variable_del('theme_default');
    db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name = 'garland'");
    $new_theme_list = array('garland');
  }

  list_themes(TRUE);
  menu_rebuild();
  drupal_set_message(t('The configuration options have been saved.'));
  $form_state['redirect'] = 'admin/build/themes';

  // Notify locale module about new themes being enabled, so translations can
  // be imported. This might start a batch, and only return to the redirect
  // path after that.
  module_invoke('locale', 'system_update', array_diff($new_theme_list, $old_theme_list));

  return;
}

/**
 * Form builder; display theme configuration for entire site and individual themes.
 * @param $key
 *   A theme name.
 * @return
 *   The form structure.
 */
function system_theme_settings(&$form_state, $key = '') {
  $directory_path = file_directory_path();
  file_check_directory($directory_path, FILE_CREATE_DIRECTORY, 'file_directory_path');

  // Default settings are defined in theme_get_settings() in includes/theme.inc
  if ($key) {
    $settings = theme_get_settings($key);
    $var = str_replace('/', '_', 'theme_'. $key .'_settings');
    $themes = system_theme_data();
    $features = $themes[$key]->info['features'];
  }
  else {
    $settings = theme_get_settings('');
    $var = 'theme_settings';
  }

  $form['var'] = array('#type' => 'hidden', '#value' => $var);

  // Check for a new uploaded logo, and use that instead.
  if ($file = file_save_upload('logo_upload', array('file_validate_is_image' => array()))) {
    $parts = pathinfo($file->filename);
    $filename = ($key) ? str_replace('/', '_', $key) .'_logo.'. $parts['extension'] : 'logo.'. $parts['extension'];

    // The image was saved using file_save_upload() and was added to the
    // files table as a temporary file. We'll make a copy and let the garbage
    // collector delete the original upload.
    if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
      $_POST['default_logo'] = 0;
      $_POST['logo_path'] = $file->filepath;
      $_POST['toggle_logo'] = 1;
    }
  }

  // Check for a new uploaded favicon, and use that instead.
  if ($file = file_save_upload('favicon_upload')) {
    $parts = pathinfo($file->filename);
    $filename = ($key) ? str_replace('/', '_', $key) .'_favicon.'. $parts['extension'] : 'favicon.'. $parts['extension'];

    // The image was saved using file_save_upload() and was added to the
    // files table as a temporary file. We'll make a copy and let the garbage
    // collector delete the original upload.
    if (file_copy($file, $filename)) {
      $_POST['default_favicon'] = 0;
      $_POST['favicon_path'] = $file->filepath;
      $_POST['toggle_favicon'] = 1;
    }
  }

  // Toggle settings
  $toggles = array(
    'logo'                 => t('Logo'),
    'name'                 => t('Site name'),
    'slogan'               => t('Site slogan'),
    'mission'              => t('Mission statement'),
    'node_user_picture'    => t('User pictures in posts'),
    'comment_user_picture' => t('User pictures in comments'),
    'search'               => t('Search box'),
    'favicon'              => t('Shortcut icon'),
    'primary_links'        => t('Primary links'),
    'secondary_links'      => t('Secondary links'),
  );

  // Some features are not always available
  $disabled = array();
  if (!variable_get('user_pictures', 0)) {
    $disabled['toggle_node_user_picture'] = TRUE;
    $disabled['toggle_comment_user_picture'] = TRUE;
  }
  if (!module_exists('search')) {
    $disabled['toggle_search'] = TRUE;
  }

  $form['theme_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Toggle display'),
    '#description' => t('Enable or disable the display of certain page elements.'),
  );
  foreach ($toggles as $name => $title) {
    if ((!$key) || in_array($name, $features)) {
      $form['theme_settings']['toggle_'. $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => $settings['toggle_'. $name]);
      // Disable checkboxes for features not supported in the current configuration.
      if (isset($disabled['toggle_'. $name])) {
        $form['theme_settings']['toggle_'. $name]['#disabled'] = TRUE;
      }
    }
  }

  // System wide only settings.
  if (!$key) {
    // Create neat 2-column layout for the toggles
    $form['theme_settings'] += array(
      '#prefix' => '<div class="theme-settings-left">',
      '#suffix' => '</div>',
    );

    // Toggle node display.
    $node_types = node_get_types('names');
    if ($node_types) {
      $form['node_info'] = array(
        '#type' => 'fieldset',
        '#title' => t('Display post information on'),
        '#description' => t('Enable or disable the <em>submitted by Username on date</em> text when displaying posts of the following type.'),
        '#prefix' => '<div class="theme-settings-right">',
        '#suffix' => '</div>',
      );
      foreach ($node_types as $type => $name) {
        $form['node_info']["toggle_node_info_$type"] = array('#type' => 'checkbox', '#title' => check_plain($name), '#default_value' => $settings["toggle_node_info_$type"]);
  elseif (!element_children($form['theme_settings'])) {
    // If there is no element in the theme settings fieldset then do not show
    // it -- but keep it in the form if another module wants to alter.
    $form['theme_settings']['#access'] = FALSE;
  }

  // Logo settings
  if ((!$key) || in_array('logo', $features)) {
    $form['logo'] = array(
      '#type' => 'fieldset',
      '#title' => t('Logo image settings'),
      '#description' => t('If toggled on, the following logo will be displayed.'),
      '#attributes' => array('class' => 'theme-settings-bottom'),
    );
    $form['logo']["default_logo"] = array(
      '#type' => 'checkbox',
      '#title' => t('Use the default logo'),
      '#default_value' => $settings['default_logo'],
      '#tree' => FALSE,
      '#description' => t('Check here if you want the theme to use the logo supplied with it.')
    );
    $form['logo']['logo_path'] = array(
      '#type' => 'textfield',
      '#title' => t('Path to custom logo'),
      '#default_value' => $settings['logo_path'],
      '#description' => t('The path to the file you would like to use as your logo file instead of the default logo.'));

    $form['logo']['logo_upload'] = array(
      '#type' => 'file',
      '#title' => t('Upload logo image'),
      '#maxlength' => 40,
      '#description' => t("If you don't have direct file access to the server, use this field to upload your logo.")
    );
  }

  if ((!$key) || in_array('favicon', $features)) {
    $form['favicon'] = array(
      '#type' => 'fieldset',
      '#title' => t('Shortcut icon settings'),
      '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers.")
    );
    $form['favicon']['default_favicon'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use the default shortcut icon.'),
      '#default_value' => $settings['default_favicon'],
      '#description' => t('Check here if you want the theme to use the default shortcut icon.')
    );
    $form['favicon']['favicon_path'] = array(
      '#type' => 'textfield',
      '#title' => t('Path to custom icon'),
      '#default_value' => $settings['favicon_path'],
      '#description' => t('The path to the image file you would like to use as your custom shortcut icon.')
    );

    $form['favicon']['favicon_upload'] = array(
      '#type' => 'file',
      '#title' => t('Upload icon image'),
      '#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon.")
    );
  }

  if ($key) {
    // Include the theme's theme-settings.php file
    $filename = './'. str_replace("/$key.info", '', $themes[$key]->filename) .'/theme-settings.php';
    if (!file_exists($filename) and !empty($themes[$key]->info['base theme'])) {
      // If the theme doesn't have a theme-settings.php file, use the base theme's.
      $base = $themes[$key]->info['base theme'];
      $filename = './'. str_replace("/$base.info", '', $themes[$base]->filename) .'/theme-settings.php';
    }
    if (file_exists($filename)) {
      require_once $filename;
    }

    // Call engine-specific settings.
    $function = $themes[$key]->prefix .'_engine_settings';
    if (function_exists($function)) {
      $group = $function($settings);
      if (!empty($group)) {
        $form['engine_specific'] = array('#type' => 'fieldset', '#title' => t('Theme-engine-specific settings'), '#description' => t('These settings only exist for all the templates and styles based on the %engine theme engine.', array('%engine' => $themes[$key]->prefix)));
        $form['engine_specific'] = array_merge($form['engine_specific'], $group);
    }
    // Call theme-specific settings.
    $function = $key .'_settings';
    if (!function_exists($function)) {
      $function = $themes[$key]->prefix .'_settings';
    }
    if (function_exists($function)) {
      $group = $function($settings);
      if (!empty($group)) {
        $form['theme_specific'] = array('#type' => 'fieldset', '#title' => t('Theme-specific settings'), '#description' => t('These settings only exist for the %theme theme and all the styles based on it.', array('%theme' => $themes[$key]->info['name'])));
        $form['theme_specific'] = array_merge($form['theme_specific'], $group);
      }
    }
  }
  $form['#attributes'] = array('enctype' => 'multipart/form-data');

  $form = system_settings_form($form);
  // We don't want to call system_settings_form_submit(), so change #submit.
  $form['#submit'] = array('system_theme_settings_submit');
  return $form;
}

/**
 * Process system_theme_settings form submissions.
 */
function system_theme_settings_submit($form, &$form_state) {
  $values = $form_state['values'];
  $key = $values['var'];
  if ($values['op'] == t('Reset to defaults')) {
    variable_del($key);
    drupal_set_message(t('The configuration options have been reset to their default values.'));
  }
  else {
    // Exclude unnecessary elements before saving.
    unset($values['var'], $values['submit'], $values['reset'], $values['form_id'], $values['op'], $values['form_build_id'], $values['form_token']);
    variable_set($key, $values);
    drupal_set_message(t('The configuration options have been saved.'));
  }

  cache_clear_all();
}

 * Recursively check compatibility.
 *
 * @param $incompatible
 *   An associative array which at the end of the check contains all incompatible files as the keys, their values being TRUE.
 * @param $files
 *   The set of files that will be tested.
 * @param $file
 *   The file at which the check starts.
 * @return
 *   Returns TRUE if an incompatible file is found, NULL (no return value) otherwise.
 */
function _system_is_incompatible(&$incompatible, $files, $file) {
  static $seen;
  // We need to protect ourselves in case of a circular dependency.
  if (isset($seen[$file->name])) {
    return isset($incompatible[$file->name]);
  }
  $seen[$file->name] = TRUE;
  if (isset($incompatible[$file->name])) {
    return TRUE;
  }
  // The 'dependencies' key in .info files was a string in Drupal 5, but changed
  // to an array in Drupal 6. If it is not an array, the module is not
  // compatible and we can skip the check below which requires an array.
  if (!is_array($file->info['dependencies'])) {
    $file->info['dependencies'] = array();
    $incompatible[$file->name] = TRUE;
    return TRUE;
  }
  // Recursively traverse the dependencies, looking for incompatible modules
  foreach ($file->info['dependencies'] as $dependency) {
    if (isset($files[$dependency]) && _system_is_incompatible($incompatible, $files, $files[$dependency])) {
      $incompatible[$file->name] = TRUE;
      return TRUE;
    }
  }
}

/**
 * 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.
 * @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.
 *
 * @param $form_state
 *   An associative array containing the current state of the form.
 * @see theme_system_modules()
 * @see system_modules_submit()
 * @return
 *   The form array.
 */
function system_modules($form_state = array()) {
  drupal_rebuild_theme_registry();
  node_types_rebuild();
  menu_rebuild();
  cache_clear_all('schema', 'cache');
  // Get current list of modules.
  $files = module_rebuild_cache();

  uasort($files, 'system_sort_modules_by_info_name');

  if (!empty($form_state['storage'])) {
    return system_modules_confirm_form($files, $form_state['storage']);
  }
  $dependencies = array();

  // 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());

  // Traverse the files, checking for compatibility
  $incompatible_core = array();
  $incompatible_php = array();
  foreach ($files as $filename => $file) {
    // Ensure this module is compatible with this version of core.
    if (!isset($file->info['core']) || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY) {
      $incompatible_core[$file->name] = $file->name;
    }
    // Ensure this module is compatible with the currently installed version of PHP.
    if (version_compare(phpversion(), $file->info['php']) < 0) {
      $incompatible_php[$file->name] = $file->info['php'];
    }
  }

  // Array for disabling checkboxes in callback system_module_disable.
  $disabled = array();
  $throttle = array();
  // Traverse the files retrieved and build the form.
  foreach ($files as $filename => $file) {
    $form['name'][$filename] = array('#value' => $file->info['name']);
    $form['version'][$filename] = array('#value' => $file->info['version']);
    $form['description'][$filename] = array('#value' => t($file->info['description']));
    $options[$filename] = '';
    // Ensure this module is compatible with this version of core and php.
    if (_system_is_incompatible($incompatible_core, $files, $file) || _system_is_incompatible($incompatible_php, $files, $file)) {
      $disabled[] = $file->name;
      // Nothing else in this loop matters, so move to the next module.
      continue;
    }
    if ($file->status) {
      $status[] = $file->name;
    }
    if ($file->throttle) {
      $throttle[] = $file->name;
    }

    $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) {
          if (isset($files[$dependency])) {
            $dependencies[] = $files[$dependency]->info['name'] . t(' (<span class="admin-disabled">disabled</span>)');
          }
          else {
            $dependencies[] = drupal_ucfirst($dependency) . t(' (<span class="admin-missing">missing</span>)');
            $disabled[] = $filename;
            $form['disabled_modules']['#value'][$filename] = FALSE;
          }
        }
        else {
          $dependencies[] = $files[$dependency]->info['name'] . t(' (<span class="admin-enabled">enabled</span>)');
        }
      }

      // Add text for dependencies.
      if (!empty($dependencies)) {
        $form['description'][$filename]['dependencies'] = array(
          '#value' => t('Depends on: !dependencies', array('!dependencies' => implode(', ', $dependencies))),
          '#prefix' => '<div class="admin-dependencies">',
          '#suffix' => '</div>',
        );
      }
    }

    // Mark dependents disabled so user can not remove modules being depended on.
    $dependents = array();
    foreach ($file->info['dependents'] as $dependent) {
      if ($files[$dependent]->status == 1) {
        $dependents[] = $files[$dependent]->info['name'] . t(' (<span class="admin-enabled">enabled</span>)');
        $disabled[] = $filename;
        $form['disabled_modules']['#value'][$filename] = TRUE;
      }
      else {
        $dependents[] = $files[$dependent]->info['name'] . t(' (<span class="admin-disabled">disabled</span>)');
      }
    }

    // Add text for enabled dependents.
    if (!empty($dependents)) {
      $form['description'][$filename]['required'] = array(
        '#value' => t('Required by: !required', array('!required' => implode(', ', $dependents))),
        '#prefix' => '<div class="admin-required">',
        '#suffix' => '</div>',
      );
    }
  }

  $modules_required = drupal_required_modules();
  // Merge in required modules.
  foreach ($modules_required as $required) {
    $disabled[] = $required;
    $form['disabled_modules']['#value'][$required] = TRUE;
  }

  // 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',
      'system_modules_disable',
    ),
    '#disabled_modules' => $disabled,
    '#incompatible_modules_core' => $incompatible_core,
    '#incompatible_modules_php' => $incompatible_php,
  );

  // 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,
      '#process' => array(
        'expand_checkboxes',
        'system_modules_disable',
      ),
      '#disabled_modules' => array_merge($modules_required, array('throttle')),
    );
  }

  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  $form['#action'] = url('admin/build/modules/list/confirm');

  return $form;
}

 * Array sorting callback; sorts modules or themes by their name.
function system_sort_modules_by_info_name($a, $b) {
  return strcasecmp($a->info['name'], $b->info['name']);
}

/**
 * Form process callback function to disable check boxes.
 *
 * @param $form
 *   The form structure.
 * @param $edit
 *   Not used.
 * @ingroup forms
 * @return
 *   The form structure.
 */
function system_modules_disable($form, $edit) {
  foreach ($form['#disabled_modules'] as $key) {
    $form[$key]['#attributes']['disabled'] = 'disabled';
  }
  return $form;
}

/**
 * Display confirmation form for dependencies.
 *
 * @param $modules
 *   Array of module file objects as returned from module_rebuild_cache().
 * @param $storage
 *   The contents of $form_state['storage']; an array with two
 *   elements: the list of dependencies and the list of status
 *   form field values from the previous screen.
 * @ingroup forms
 */
function system_modules_confirm_form($modules, $storage) {
  $form = array();
  $items = array();

  list($dependencies, $status) = $storage;
  $form['validation_modules'] = array('#type' => 'value', '#value' => $modules);
  $form['status']['#tree'] = TRUE;
  // Remember list of modules selected on the module listing page already.
  foreach ($status as $key => $choice) {
    $form['status'][$key] = array('#type' => 'value', '#value' => $choice);
  }
  foreach ($dependencies as $name => $missing_dependencies) {
    $form['status'][$name] = array('#type' => 'hidden', '#value' => 1);
    foreach ($missing_dependencies as $k => $dependency) {
      $form['status'][$dependency] = array('#type' => 'hidden', '#value' => 1);
      $info = $modules[$dependency]->info;
      $missing_dependencies[$k] = $info['name'] ? $info['name'] : drupal_ucfirst($dependency);
    }
    $t_argument = array(
      '@module' => $modules[$name]->info['name'],
      '@dependencies' => implode(', ', $missing_dependencies),
    );
    $items[] = format_plural(count($missing_dependencies), 'You must enable the @dependencies module to install @module.', 'You must enable the @dependencies modules to install @module.', $t_argument);
  }
  $form['text'] = array('#value' => 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;
  }
}

/**
 * Submit callback; handles modules form submission.
 */
function system_modules_submit($form, &$form_state) {
  include_once './includes/install.inc';
  $new_modules = array();

  // If we are coming from the confirm form...
  if (!isset($form_state['storage'])) {
    // Merge in disabled active modules since they should be enabled.
    // They don't appear because disabled checkboxes are not submitted
    // by browsers.
    $form_state['values']['status'] = array_merge($form_state['values']['status'], $form_state['values']['disabled_modules']);

    // Check values for dependency that we can't install.
    if ($dependencies = system_module_build_dependencies($form_state['values']['validation_modules'], $form_state['values'])) {
      // These are the modules that depend on existing modules.
      foreach (array_keys($dependencies) as $name) {
        $form_state['values']['status'][$name] = 0;
      }
    }
  }
  else {
    $dependencies = NULL;
  }

  // Update throttle settings, if present
  if (isset($form_state['values']['throttle'])) {
    foreach ($form_state['values']['throttle'] as $key => $choice) {
      db_query("UPDATE {system} SET throttle = %d WHERE type = 'module' and name = '%s'", $choice ? 1 : 0, $key);
    }
  }

  // If there where unmet dependencies and they haven't confirmed don't process
  // the submission yet. Store the form submission data needed later.
  if ($dependencies) {
    if (!isset($form_state['values']['confirm'])) {
      $form_state['storage'] = array($dependencies, $form_state['values']['status']);
      return;
    }
    else {
      $form_state['values']['status'] = array_merge($form_state['values']['status'], $form_storage[1]);
    }
  }
  // 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']);

  $enable_modules = array();
  $disable_modules = array();
  foreach ($form_state['values']['status'] as $key => $choice) {
    if ($choice) {
      if (drupal_get_installed_schema_version($key) == SCHEMA_UNINSTALLED) {
        $new_modules[] = $key;
      }
      else {
        $enable_modules[] = $key;
      }
    }
    else {
      $disable_modules[] = $key;
    }
  }

  $old_module_list = module_list();

  if (!empty($enable_modules)) {
    module_enable($enable_modules);
  }
  if (!empty($disable_modules)) {
    module_disable($disable_modules);
  }

  // Install new modules.
  foreach ($new_modules as $key => $module) {
    if (!drupal_check_module($module)) {
      unset($new_modules[$key]);
    }
  }
  drupal_install_modules($new_modules);

  $current_module_list = module_list(TRUE, FALSE);
  if ($old_module_list != $current_module_list) {
    drupal_set_message(t('The configuration options have been saved.'));
  }

  drupal_clear_css_cache();
  drupal_clear_js_cache();

  $form_state['redirect'] = 'admin/build/modules';

  // 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);

  // Synchronize to catch any actions that were added or removed.
  actions_synchronize();


/**
 * Generate a list of dependencies for modules that are going to be switched on.
 *
 * @param $modules
 *   The list of modules to check.
 * @param $form_values
 *   Submitted form values used to determine what modules have been enabled.
 * @return
 *   An array of dependencies.
 */
function system_module_build_dependencies($modules, $form_values) {
  static $dependencies;

  if (!isset($dependencies) && isset($form_values)) {
    $dependencies = array();
    foreach ($modules as $name => $module) {
      // If the module is disabled, will be switched on and it has dependencies.
      if (!$module->status && $form_values['status'][$name] && isset($module->info['dependencies'])) {
        foreach ($module->info['dependencies'] as $dependency) {
          if (!$form_values['status'][$dependency] && isset($modules[$dependency])) {
            if (!isset($dependencies[$name])) {
              $dependencies[$name] = array();
            }
            $dependencies[$name][] = $dependency;
          }
        }
      }
    }
  }
  return $dependencies;
}

/**
 * Uninstall functions
 */

/**
 * Builds a form of currently disabled modules.
 *
 * @ingroup forms
 * @see system_modules_uninstall_validate()
 * @see system_modules_uninstall_submit()
 * @param $form_state['values']
 *   Submitted form values.
 * @return
 *   A form array representing the currently disabled modules.
 */
function system_modules_uninstall($form_state = NULL) {
  // Make sure the install API is available.
  include_once './includes/install.inc';

  // Display the confirm form if any modules have been submitted.
  if (isset($form_state) && $confirm_form = system_modules_uninstall_confirm_form($form_state['storage'])) {
    return $confirm_form;
  }

  $form = array();

  // Pull all disabled modules from the system table.
  $disabled_modules = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' AND status = 0 AND schema_version > %d ORDER BY name", SCHEMA_UNINSTALLED);