system.admin.inc 99.1 KB
Newer Older
1
<?php
2
// $Id$
3 4

/**
5 6 7 8 9 10
 * @file
 * Admin page callbacks for the system module.
 */

/**
 * Menu callback; Provide the administration overview page.
11 12 13 14 15 16 17 18 19 20
 */
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.
21
  if (system_status(TRUE) && user_access('administer site configuration')) {
22
    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');
23
  }
24
  $blocks = array();
25
  if ($admin = db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin' AND module = 'system'")->fetchAssoc()) {
26 27 28 29
    $result = db_query("
      SELECT m.*, ml.*
      FROM {menu_links} ml
      INNER JOIN {menu_router} m ON ml.router_path = m.path
30 31
      WHERE ml.link_path != 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
    foreach ($result as $item) {
32 33 34 35 36 37
      _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.
38 39
      if (!empty($item['localized_options']['attributes']['title'])) {
        $item['description'] = $item['localized_options']['attributes']['title'];
40 41 42
      }
      $block = $item;
      $block['content'] = '';
43
      $block['show'] = FALSE;
44 45 46 47
      if ($item['block_callback'] && function_exists($item['block_callback'])) {
        $function = $item['block_callback'];
        $block['content'] .= $function();
      }
48 49 50 51 52 53 54
      $content = system_admin_menu_block($item);
      if ((isset($item['page_callback']) && !in_array($item['page_callback'], array('system_admin_menu_block_page', 'system_admin_config_page', 'system_settings_overview'))) || count($content)) {
        // Only show blocks for items which are not containers, or those which
        // are containers and do have items we can show.
        $block['show'] = TRUE;
        if (empty($content)) {
          // If no items found below, but access checks did not fail, show.
55
          $block['title'] = l($item['title'], $item['href'], $item['localized_options']);
56 57 58
        }
        else {
          // Theme items below.
59
          $block['content'] .= theme('admin_block_content', array('content' => $content));
60 61
        }
      }
62 63
      // Prepare for sorting as in function _menu_tree_check_access().
      // The weight is offset so it is always positive, with a uniform 5-digits.
64
      $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block;
65 66
    }
  }
67 68
  if ($blocks) {
    ksort($blocks);
69
    return theme('admin_page', array('blocks' => $blocks));
70 71 72 73
  }
  else {
    return t('You do not have any administrative items.');
  }
74 75
}

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
/**
 * Menu callback; Provide the administration overview page.
 */
function system_admin_config_page() {
  // 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');
  }
  $blocks = array();
  if ($admin = db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin/config' AND module = 'system'")->fetchAssoc()) {
    $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 = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
    foreach ($result as $item) {
      _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'] = '';
103
      $block['show'] = TRUE;
104 105 106 107
      if ($item['block_callback'] && function_exists($item['block_callback'])) {
        $function = $item['block_callback'];
        $block['content'] .= $function();
      }
108
      $block['content'] .= theme('admin_block_content', array('content' => system_admin_menu_block($item)));
109 110 111 112 113 114 115
      // 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);
116
    return theme('admin_page', array('blocks' => $blocks));
117 118 119 120 121
  }
  else {
    return t('You do not have any administrative items.');
  }
}
122 123 124 125

/**
 * Provide a single block from the administration menu as a page.
 * This function is often a destination for these blocks.
126
 * For example, 'admin/structure/types' needs to have a destination to be valid
127 128
 * in the Drupal menu system, but too much information there might be
 * hidden, so we supply the contents of the block.
129 130 131
 *
 * @return
 *   The output HTML.
132 133 134
 */
function system_admin_menu_block_page() {
  $item = menu_get_item();
135
  if ($content = system_admin_menu_block($item)) {
136
    $output = theme('admin_block_content', array('content' => $content));
137 138 139 140
  }
  else {
    $output = t('You do not have any administrative items.');
  }
141 142
  return $output;
}
143 144 145 146 147 148

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

149
  $module_info = system_get_info('module');
150
  $menu_items = array();
151
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;
152

153
  foreach ($module_info as $module => $info) {
154 155 156 157 158 159 160 161 162 163
    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.
164
      if ($help_arg && module_invoke($module, 'help', "admin/help#$module", $help_arg)) {
165 166 167 168 169 170
        $admin_tasks[100] = l(t('Get help'), "admin/help/$module");
      }

      // Sort.
      ksort($admin_tasks);

171
      $menu_items[$info['name']] = array($info['description'], $admin_tasks);
172 173
    }
  }
174
  return theme('system_admin_by_module', array('menu_items' => $menu_items));
175 176 177 178 179 180 181 182 183 184 185
}

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

186
  $item = menu_get_item('admin/config');
187 188
  $content = system_admin_menu_block($item);

189
  $output = theme('admin_block_content', array('content' => $content));
190 191 192 193 194 195

  return $output;
}

/**
 * Menu callback; displays a listing of all themes.
196
 *
197
 * @ingroup forms
198
 * @see system_themes_form_submit()
199 200
 */
function system_themes_form() {
201
  // Get current list of themes.
202
  $themes = system_rebuild_theme_data();
203 204 205 206 207 208 209 210

  // Remove hidden themes from the display list.
  foreach ($themes as $theme_key => $theme) {
    if (!empty($theme->info['hidden'])) {
      unset($themes[$theme_key]);
    }
  }

211 212
  uasort($themes, 'system_sort_modules_by_info_name');

213 214 215 216 217 218
  $status = array();
  $incompatible_core = array();
  $incompatible_php = array();

  foreach ($themes as $theme) {
    $screenshot = NULL;
219 220
    // Create a list which includes the current theme and all its base themes.
    if (isset($themes[$theme->name]->base_themes)) {
221 222
      $theme_keys = array_keys($themes[$theme->name]->base_themes);
      $theme_keys[] = $theme->name;
223 224 225 226 227 228 229
    }
    else {
      $theme_keys = array($theme->name);
    }
    // Look for a screenshot in the current theme or in its closest ancestor.
    foreach (array_reverse($theme_keys) as $theme_key) {
      if (isset($themes[$theme_key]) && file_exists($themes[$theme_key]->info['screenshot'])) {
230 231 232 233
        $screenshot = $themes[$theme_key]->info['screenshot'];
        break;
      }
    }
234
    $screenshot = $screenshot ? theme('image', array('path' => $screenshot, 'alt' => t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), 'title' => '', 'attributes' => array('class' => array('screenshot')), 'getsize' => FALSE)) : t('no screenshot');
235

236
    $form[$theme->name]['screenshot'] = array('#markup' => $screenshot);
237 238 239 240
    $form[$theme->name]['info'] = array(
      '#type' => 'value',
      '#value' => $theme->info,
    );
241
    $options[$theme->name] = $theme->info['name'];
242

243
    if (drupal_theme_access($theme)) {
244
      $form[$theme->name]['operations'] = array('#markup' => l(t('configure'), 'admin/appearance/settings/' . $theme->name) );
245 246 247 248
    }
    else {
      // Dummy element for drupal_render. Cleaner than adding a check in the theme function.
      $form[$theme->name]['operations'] = array();
249 250 251 252 253
    }
    if (!empty($theme->status)) {
      $status[] = $theme->name;
    }
    else {
254
      // Ensure this theme is compatible with this version of core.
255
      // Require the 'content' region to make sure the main page
256 257
      // content has a common place in all themes.
      if (!isset($theme->info['core']) || ($theme->info['core'] != DRUPAL_CORE_COMPATIBILITY) || (!isset($theme->info['regions']['content']))) {
258 259 260 261 262 263 264 265 266 267
        $incompatible_core[] = $theme->name;
      }
      if (version_compare(phpversion(), $theme->info['php']) < 0) {
        $incompatible_php[$theme->name] = $theme->info['php'];
      }
    }
  }

  $form['status'] = array(
    '#type' => 'checkboxes',
268
    '#options' => array_fill_keys(array_keys($options), ''),
269 270 271 272 273 274
    '#default_value' => $status,
    '#incompatible_themes_core' => drupal_map_assoc($incompatible_core),
    '#incompatible_themes_php' => $incompatible_php,
  );
  $form['theme_default'] = array(
    '#type' => 'radios',
275
    '#options' => array_fill_keys(array_keys($options), ''),
276 277
    '#default_value' => variable_get('theme_default', 'garland'),
  );
278 279 280 281 282 283 284 285 286 287 288 289

  // Administration theme settings.
  $form['admin_theme'] = array(
    '#type' => 'fieldset',
    '#title' => t('Administration theme'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['admin_theme']['admin_theme'] = array(
    '#type' => 'select',
    '#options' => array(0 => t('Default theme')) + $options,
    '#title' => t('Administration theme'),
290
    '#description' => t('Choose "Default theme" to always use the same theme as the rest of the site.'),
291 292 293 294
    '#default_value' => variable_get('admin_theme', 0),
  );
  $form['admin_theme']['node_admin_theme'] = array(
    '#type' => 'checkbox',
295
    '#title' => t('Use the administration theme when editing or creating content'),
296 297 298
    '#default_value' => variable_get('node_admin_theme', '0'),
  );

299 300 301 302
  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
303

304 305 306
  return $form;
}

307 308 309
/**
 * Process system_themes_form form submissions.
 */
310
function system_themes_form_submit($form, &$form_state) {
311
  drupal_clear_css_cache();
312 313 314 315 316 317 318 319

  // 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;
    }
  }
320 321 322 323
  db_update('system')
    ->fields(array('status' => 0))
    ->condition('type', 'theme')
    ->execute();
324 325 326 327 328 329 330

  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) {
          $new_theme_list[] = $key;
331 332 333 334 335
          db_update('system')
            ->fields(array('status' => 1))
            ->condition('type', 'theme')
            ->condition('name', $key)
            ->execute();
336 337 338
        }
      }
    }
339
    if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] != $form_state['values']['theme_default']) {
340
      drupal_set_message(t('Please note that the administration theme 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(
341
        '%admin_theme' => $form_state['values']['admin_theme'],
342 343 344
        '%selected_theme' => $form_state['values']['theme_default'],
      )));
    }
345 346

    // Save the variables.
347
    variable_set('theme_default', $form_state['values']['theme_default']);
348 349
    variable_set('admin_theme', $form_state['values']['admin_theme']);
    variable_set('node_admin_theme', $form_state['values']['node_admin_theme']);
350 351 352 353
  }
  else {
    // Revert to defaults: only Garland is enabled.
    variable_del('theme_default');
354 355
    variable_del('admin_theme');
    variable_del('node_admin_theme');
356 357 358 359 360
    db_update('system')
      ->fields(array('status' => 1))
      ->condition('type', 'theme')
      ->condition('name', 'garland')
      ->execute();
361 362 363 364 365
    $new_theme_list = array('garland');
  }

  list_themes(TRUE);
  menu_rebuild();
366
  drupal_theme_rebuild();
367
  drupal_set_message(t('The configuration options have been saved.'));
368
  $form_state['redirect'] = 'admin/appearance';
369 370 371 372 373 374 375 376 377 378 379

  // 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.
380
 *
381 382 383 384
 * @param $key
 *   A theme name.
 * @return
 *   The form structure.
385
 * @ingroup forms
386
 * @see system_theme_settings_submit()
387
 */
388
function system_theme_settings($form, &$form_state, $key = '') {
389
  $directory_path = file_directory_path();
390 391 392
  if (!file_prepare_directory($directory_path, FILE_CREATE_DIRECTORY)) {
    drupal_set_message(t('The directory %directory does not exist or is not writable.', array('%directory' => $directory_path)), 'warning');
  }
393

394
  // Default settings are defined in theme_get_setting() in includes/theme.inc
395
  if ($key) {
396
    $var = 'theme_' . $key . '_settings';
397
    $themes = system_rebuild_theme_data();
398 399 400 401 402 403 404 405 406 407 408
    $features = $themes[$key]->info['features'];
  }
  else {
    $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);
409
    $filename = ($key) ? $key . '_logo.' . $parts['extension'] : 'logo.' . $parts['extension'];
410 411 412 413

    // 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.
414
    if ($filepath = file_unmanaged_copy($file->uri, $filename, FILE_EXISTS_REPLACE)) {
415
      $_POST['default_logo'] = 0;
416
      $_POST['logo_path'] = $filepath;
417 418 419 420 421 422 423
      $_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);
424
    $filename = ($key) ? $key . '_favicon.' . $parts['extension'] : 'favicon.' . $parts['extension'];
425 426 427 428

    // 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.
429
    if ($filepath = file_unmanaged_copy($file->uri, $filename, FILE_EXISTS_REPLACE)) {
430
      $_POST['default_favicon'] = 0;
431
      $_POST['favicon_path'] = $filepath;
432 433 434 435 436 437
      $_POST['toggle_favicon'] = 1;
    }
  }

  // Toggle settings
  $toggles = array(
438 439 440 441 442 443 444 445 446 447
    'logo'                      => t('Logo'),
    'name'                      => t('Site name'),
    'slogan'                    => t('Site slogan'),
    'node_user_picture'         => t('User pictures in posts'),
    'comment_user_picture'      => t('User pictures in comments'),
    'comment_user_verification' => t('User verification status in comments'),
    'search'                    => t('Search box'),
    'favicon'                   => t('Shortcut icon'),
    'main_menu'                 => t('Main menu'),
    'secondary_menu'            => t('Secondary menu'),
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
  );

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

  $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)) {
464
      $form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('toggle_' . $name, $key));
465
      // Disable checkboxes for features not supported in the current configuration.
466 467
      if (isset($disabled['toggle_' . $name])) {
        $form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE;
468 469 470 471
      }
    }
  }

472
  if (!element_children($form['theme_settings'])) {
473 474 475 476
    // 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;
  }
477 478 479 480 481 482 483

  // 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.'),
484
      '#attributes' => array('class' => array('theme-settings-bottom')),
485
    );
486
    $form['logo']['default_logo'] = array(
487 488
      '#type' => 'checkbox',
      '#title' => t('Use the default logo'),
489
      '#default_value' => theme_get_setting('default_logo', $key),
490 491 492
      '#tree' => FALSE,
      '#description' => t('Check here if you want the theme to use the logo supplied with it.')
    );
493 494 495 496 497 498 499 500 501 502
    $form['logo']['settings'] = array(
      '#type' => 'container',
      '#states' => array(
        // Hide the logo settings when using the default logo.
        'invisible' => array(
          'input[name="default_logo"]' => array('checked' => TRUE),
        ),
      ),
    );
    $form['logo']['settings']['logo_path'] = array(
503 504
      '#type' => 'textfield',
      '#title' => t('Path to custom logo'),
505
      '#default_value' => theme_get_setting('logo_path', $key),
506 507 508
      '#description' => t('The path to the file you would like to use as your logo file instead of the default logo.'),
    );
    $form['logo']['settings']['logo_upload'] = array(
509 510 511 512 513 514 515 516 517 518 519
      '#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'),
520
      '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
521 522 523 524
    );
    $form['favicon']['default_favicon'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use the default shortcut icon.'),
525
      '#default_value' => theme_get_setting('default_favicon', $key),
526 527
      '#description' => t('Check here if you want the theme to use the default shortcut icon.')
    );
528 529 530 531 532 533 534 535 536 537
    $form['favicon']['settings'] = array(
      '#type' => 'container',
      '#states' => array(
        // Hide the favicon settings when using the default favicon.
        'invisible' => array(
          'input[name="default_favicon"]' => array('checked' => TRUE),
        ),
      ),
    );
    $form['favicon']['settings']['favicon_path'] = array(
538 539
      '#type' => 'textfield',
      '#title' => t('Path to custom icon'),
540
      '#default_value' => theme_get_setting('favicon_path', $key),
541 542
      '#description' => t('The path to the image file you would like to use as your custom shortcut icon.')
    );
543
    $form['favicon']['settings']['favicon_upload'] = array(
544 545 546 547 548 549 550
      '#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) {
551
    // Call engine-specific settings.
552
    $function = $themes[$key]->prefix . '_engine_settings';
553
    if (function_exists($function)) {
554 555 556 557 558 559
      $form['engine_specific'] = array(
        '#type' => 'fieldset',
        '#title' => t('Theme-engine-specific settings'),
        '#description' => t('These settings only exist for the themes based on the %engine theme engine.', array('%engine' => $themes[$key]->prefix)),
      );
      $function($form, $form_state);
560
    }
561 562 563 564 565

    // Create a list which includes the current theme and all its base themes.
    if (isset($themes[$key]->base_themes)) {
      $theme_keys = array_keys($themes[$key]->base_themes);
      $theme_keys[] = $key;
566
    }
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
    else {
      $theme_keys = array($key);
    }

    // Save the name of the current theme (if any), so that we can temporarily
    // override the current theme and allow theme_get_setting() to work
    // without having to pass the theme name to it.
    $default_theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : NULL;
    $GLOBALS['theme_key'] = $key;

    // Process the theme and all its base themes.
    foreach ($theme_keys as $theme) {
      // Include the theme-settings.php file.
      $filename = DRUPAL_ROOT . '/' . str_replace("/$theme.info", '', $themes[$theme]->filename) . '/theme-settings.php';
      if (file_exists($filename)) {
        require_once $filename;
      }

      // Call theme-specific settings.
      $function = $theme . '_form_system_theme_settings_alter';
      if (function_exists($function)) {
        $function($form, $form_state);
589 590
      }
    }
591 592 593 594 595 596 597 598

    // Restore the original current theme.
    if (!is_null($default_theme)) {
      $GLOBALS['theme_key'] = $default_theme;
    }
    else {
      unset($GLOBALS['theme_key']);
    }
599 600
  }

601
  $form = system_settings_form($form, FALSE);
602
  // We don't want to call system_settings_form_submit(), so change #submit.
603 604
  array_pop($form['#submit']);
  $form['#submit'][] = 'system_theme_settings_submit';
605 606 607
  return $form;
}

608 609 610
/**
 * Process system_theme_settings form submissions.
 */
611
function system_theme_settings_submit($form, &$form_state) {
612
  $values = $form_state['values'];
613
  if (empty($values['default_favicon']) && !empty($values['favicon_path'])) {
614
    $values['favicon_mimetype'] = file_get_mimetype($values['favicon_path']);
615
  }
616
  $key = $values['var'];
617

618 619 620 621
  // 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.'));
622 623 624 625

  cache_clear_all();
}

626
/**
627 628 629
 * Recursively check compatibility.
 *
 * @param $incompatible
630 631
 *   An associative array which at the end of the check contains all
 *   incompatible files as the keys, their values being TRUE.
632 633 634 635 636
 * @param $files
 *   The set of files that will be tested.
 * @param $file
 *   The file at which the check starts.
 * @return
637 638
 *   Returns TRUE if an incompatible file is found, NULL (no return value)
 *   otherwise.
639 640 641 642 643
 */
function _system_is_incompatible(&$incompatible, $files, $file) {
  if (isset($incompatible[$file->name])) {
    return TRUE;
  }
644 645 646
  // Recursively traverse required modules, looking for incompatible modules.
  foreach ($file->requires as $requires) {
    if (isset($files[$requires]) && _system_is_incompatible($incompatible, $files, $files[$requires])) {
647 648 649 650 651 652
      $incompatible[$file->name] = TRUE;
      return TRUE;
    }
  }
}

653 654 655 656
/**
 * Menu callback; provides module enable/disable interface.
 *
 * The list of modules gets populated by module.info files, which contain each module's name,
657
 * description and information about which modules it requires.
658 659
 * @see drupal_parse_info_file for information on module.info descriptors.
 *
660 661 662
 * Dependency checking is performed to ensure that a module:
 * - can not be enabled if there are disabled modules it requires.
 * - can not be disabled if there are enabled modules which depend on it.
663
 *
664 665
 * @param $form_state
 *   An associative array containing the current state of the form.
666
 * @ingroup forms
667 668
 * @see theme_system_modules()
 * @see system_modules_submit()
669 670 671
 * @return
 *   The form array.
 */
672
function system_modules($form, $form_state = array()) {
673
  // Get current list of modules.
674
  $files = system_rebuild_module_data();
675

676 677
  // Remove hidden modules from display list.
  foreach ($files as $filename => $file) {
678
    if (!empty($file->info['hidden']) || !empty($file->info['required'])) {
679 680 681 682
      unset($files[$filename]);
    }
  }

683 684
  uasort($files, 'system_sort_modules_by_info_name');

685 686 687 688
  // If the modules form was submitted, then system_modules_submit() runs first
  // and if there are unfilled required modules, then form_state['storage'] is
  // filled, triggering a rebuild. In this case we need to display a
  // confirmation form.
689 690 691
  if (!empty($form_state['storage'])) {
    return system_modules_confirm_form($files, $form_state['storage']);
  }
692

693 694
  $modules = array();
  $form['modules'] = array('#tree' => TRUE);
695

696 697 698
  // Used when checking if module implements a help page.
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;

699 700 701 702
  // Iterate through each of the modules.
  foreach ($files as $filename => $module) {
    $extra = array();
    $extra['enabled'] = (bool) $module->status;
703 704 705 706
    // If this module requires other modules, add them to the array.
    foreach ($module->requires as $requires => $v) {
      if (!isset($files[$requires])) {
        $extra['requires'][$requires] = t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst($requires)));
707 708 709 710
        $extra['disabled'] = TRUE;
      }
      else {
        $requires_name = $files[$requires]->info['name'];
711 712 713 714 715 716
        if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']))) {
          $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> version @version)', array(
            '@module' => $requires_name . $incompatible_version,
            '@version' => $files[$requires]->info['version'],
          ));
          $extra['disabled'] = TRUE;
717
        }
718 719 720 721 722
        elseif ($files[$requires]->status) {
          $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $requires_name));
        }
        else {
          $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $requires_name));
723 724
        }
      }
725 726
    }
    // Generate link for module's help page, if there is one.
727
    if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
728 729
      if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
        // Module has a help page.
730
        $extra['help'] = theme('more_help_link', array('url' => url("admin/help/$filename")));
731 732
      }
    }
733
    // Mark dependents disabled so the user cannot remove required modules.
734
    $dependents = array();
735 736 737
    // If this module is required by other modules, list those, and then make it
    // impossible to disable this one.
    foreach ($module->required_by as $required_by => $v) {
738
      // Hidden modules are unset already.
739 740 741
      if (isset($files[$required_by])) {
        if ($files[$required_by]->status == 1) {
          $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
742 743 744
          $extra['disabled'] = TRUE;
        }
        else {
745
          $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
746
        }
747 748
      }
    }
749
    $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
750
  }
751 752 753 754 755 756 757 758
  // Add basic information to the fieldsets.
  foreach (element_children($form['modules']) as $package) {
    $form['modules'][$package] += array(
      '#type' => 'fieldset',
      '#title' => t($package),
      '#collapsible' => TRUE,
      '#theme' => 'system_modules_fieldset',
      '#header' => array(
759
        array('data' => t('Enabled'), 'class' => array('checkbox')),
760 761 762 763 764
        t('Name'),
        t('Version'),
        t('Description'),
      ),
    );
765 766
  }

767
  $form['submit'] = array(
768 769 770
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
771
  $form['#action'] = url('admin/config/modules/list/confirm');
772 773 774 775

  return $form;
}

776
/**
777
 * Array sorting callback; sorts modules or themes by their name.
778
 */
779 780 781 782
function system_sort_modules_by_info_name($a, $b) {
  return strcasecmp($a->info['name'], $b->info['name']);
}

783
/**
784
 * Build a table row for the system modules page.
785
 */
786 787 788
function _system_modules_build_row($info, $extra) {
  // Add in the defaults.
  $extra += array(
789 790
    'requires' => array(),
    'required_by' => array(),
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
    'disabled' => FALSE,
    'enabled' => FALSE,
    'help' => '',
  );
  $form = array(
    '#tree' => TRUE,
  );
  // Set the basic properties.
  $form['name'] = array(
    '#markup' => t($info['name']),
  );
  $form['description'] = array(
    '#markup' => t($info['description']),
  );
  $form['version'] = array(
    '#markup' => $info['version'],
  );
808 809
  $form['#requires'] = $extra['requires'];
  $form['#required_by'] = $extra['required_by'];
810 811 812 813 814 815 816

  // Check the compatibilities.
  $compatible = TRUE;
  $status_short = '';
  $status_long = '';

  // Check the core compatibility.
817
  if (!isset($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY || empty($info['files'])) {
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
    $compatible = FALSE;
    $status_short .= t('Incompatible with this version of Drupal core. ');
    $status_long .= t('This version is incompatible with the !core_version version of Drupal core. ', array('!core_version' => VERSION));
  }

  // Ensure this module is compatible with the currently installed version of PHP.
  if (version_compare(phpversion(), $info['php']) < 0) {
    $compatible = FALSE;
    $status_short .= t('Incompatible with this version of PHP');
    if (substr_count($info['php'], '.') < 2) {
      $php_required .= '.*';
    }
    $status_long .= t('This module requires PHP version @php_required and is incompatible with PHP version !php_version.', array('@php_required' => $php_required, '!php_version' => phpversion()));
  }

  // If this module is compatible, present a checkbox indicating
  // this module may be installed. Otherwise, show a big red X.
  if ($compatible) {
    $form['enable'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable'),
      '#default_value' => $extra['enabled'],
    );
    if ($extra['disabled']) {
      $form['enable']['#disabled'] = TRUE;
    }
  }
  else {
    $form['enable'] = array(
847
      '#markup' =>  theme('image', array('path' => 'misc/watchdog-error.png', 'alt' => t('incompatible'), 'title' => $status_short)),
848
    );
849
    $form['description']['#markup'] .= theme('system_modules_incompatible', array('message' => $status_long));
850 851 852 853 854 855 856
  }

  // Show a "more help" link for modules that have them.
  if ($extra['help']) {
    $form['help'] = array(
      '#markup' => $extra['help'],
    );
857 858 859 860 861
  }
  return $form;
}

/**
862
 * Display confirmation form for required modules.
863 864
 *
 * @param $modules
865
 *   Array of module file objects as returned from system_rebuild_module_data().
866 867
 * @param $storage
 *   The contents of $form_state['storage']; an array with two
868
 *   elements: the list of required modules and the list of status
869 870 871 872 873 874 875 876
 *   form field values from the previous screen.
 * @ingroup forms
 */
function system_modules_confirm_form($modules, $storage) {
  $items = array();

  $form['validation_modules'] = array('#type' => 'value', '#value' => $modules);
  $form['status']['#tree'] = TRUE;
877

878
  foreach ($storage['more_modules'] as $info) {
879
    $t_argument = array(
880
      '@module' => $info['name'],
881
      '@required' => implode(', ', $info['requires']),
882
    );
883
    $items[] = format_plural(count($info['requires']), 'You must enable the @required module to install @module.', 'You must enable the @required modules to install @module.', $t_argument);
884
  }
885
  $form['text'] = array('#markup' => theme('item_list', array('items' => $items)));
886 887 888 889 890 891

  if ($form) {
    // Set some default form values
    $form = confirm_form(
      $form,
      t('Some required modules must be enabled'),
892
      'admin/config/modules',
893 894 895 896 897 898 899 900 901 902 903
      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) {
904
  include_once DRUPAL_ROOT . '/includes/install.inc';
905 906
  $modules = array();
  // If we're not coming from the confirmation form, build the list of modules.
907
  if (!isset($form_state['storage'])) {
908 909 910
    foreach ($form_state['values']['modules'] as $group_name => $group) {
      foreach ($group as $module => $enabled) {
        $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']);
911 912 913 914
      }
    }
  }
  else {
915 916 917
    // If we are coming from the confirmation form, fetch
    // the modules out of $form_state.
    $modules = $form_state['storage']['modules'];
918 919
  }

920 921
  // Get a list of all modules, it will be used to find which module requires
  // which.
922
  $files = system_rebuild_module_data();
923 924

  // The modules to be enabled.
925
  $modules_to_be_enabled = array();
926 927 928 929
  // The modules to be disabled.
  $disable_modules = array();
  // The modules to be installed.
  $new_modules = array();
930 931 932 933 934
  // 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.
935 936 937 938 939 940 941
  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;
      }
942 943
      elseif (!module_exists($name)) {
        $modules_to_be_enabled[$name] = $name;
944
      }
945 946 947
      // 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.
948
      if (empty($form_state['storage'])) {
949 950 951 952
        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'];
953
            }
954
            $more_modules[$name]['requires'][$requires] = $files[$requires]->info['name'];
955
          }
956
          $modules[$requires] = array('group' => $files[$requires]->info['package'], 'enabled' => TRUE);
957 958 959
        }
      }
    }
960 961 962 963 964
  }
  // 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']) {
965 966 967
      $disable_modules[$name] = $name;
    }
  }
968 969 970 971
  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.
972
    if (!isset($form_state['values']['confirm'])) {
973
      $form_state['storage'] = array('more_modules' => $more_modules, 'modules' => $modules);
974 975
      return;
    }
976
    // Otherwise, install or enable the modules.
977
    else {
978 979
      foreach ($form_state['storage']['more_modules'] as $info) {
        foreach ($info['requires'] as $requires => $name) {
980 981 982 983
          if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
            $new_modules[$name] = $name;
          }
          else {
984
            $modules_to_be_enabled[$name] = $name;
985 986 987
          }
        }
      }
988 989
    }
  }
990 991 992
  // 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.
993 994 995 996
  unset($form_state['storage']);

  $old_module_list = module_list();

997
  // Enable the modules needing enabling.
998
  if (!empty($modules_to_be_enabled)) {
999 1000 1001 1002 1003
    $sort = array();
    foreach ($modules_to_be_enabled as $module) {
      $sort[$module] = $files[$module]->sort;
    }
    array_multisort($sort, $modules_to_be_enabled);
1004
    module_enable($modules_to_be_enabled);
1005
  }
1006
  // Disable the modules that need disabling.
1007
  if (!empty($disable_modules)) {
1008 1009 1010 1011 1012
    $sort = array();
    foreach ($disable_modules as $module) {
      $sort[$module] = $files[$module]->sort;
    }
    array_multisort($sort, $disable_modules);
1013 1014 1015 1016
    module_disable($disable_modules);
  }

  // Install new modules.
1017
  if (!empty($new_modules)) {
1018
    $sort = array();
1019 1020 1021 1022
    foreach ($new_modules as $key => $module) {
      if (!drupal_check_module($module)) {
        unset($new_modules[$key]);
      }
1023
      $sort[$module] = $files[$module]->sort;
1024
    }
1025
    array_multisort($sort, $new_modules);
1026
    drupal_install_modules($new_modules);
1027 1028
  }

1029
  $current_module_list = module_list(TRUE);
1030 1031 1032 1033
  if ($old_module_list != $current_module_list) {
    drupal_set_message(t('The configuration options have been saved.'));
  }

1034 1035 1036 1037
  // Clear all caches.
  registry_rebuild();
  drupal_theme_rebuild();
  node_types_rebuild();
1038
  menu_rebuild();
1039
  cache_clear_all('schema', 'cache');
1040
  cache_clear_all('entity_info', 'cache');
1041 1042 1043
  drupal_clear_css_cache();
  drupal_clear_js_cache();

1044
  $form_state['redirect'] = 'admin/config/modules';
1045 1046 1047 1048 1049 1050

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

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

1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
  return;
}

/**
 * Uninstall functions
 */

/**
 * Builds a form of currently disabled modules.
 *
 * @ingroup forms
1065 1066
 * @see system_modules_uninstall_validate()
 * @see system_modules_uninstall_submit()
1067 1068
 * @param $form_state['values']
 *   Submitted form values.
1069 1070 1071
 * @return
 *   A form array representing the currently disabled modules.
 */
1072
function system_modules_uninstall($form, $form_state = NULL) {
1073
  // Make sure the install API is available.
1074
  include_once DRUPAL_ROOT . '/includes/install.inc';
1075 1076 1077 1078 1079 1080 1081

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

  // Pull all disabled modules from the system table.
1082 1083
  $disabled_modules = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' AND status = 0 AND schema_version > :schema ORDER BY name", array(':schema' => SCHEMA_UNINSTALLED));
  foreach ($disabled_modules as $module) {
1084 1085 1086
    // Grab the module info
    $info = unserialize($module->info);

1087
    // Load the .install file, and check for an uninstall or schema hook.
1088 1089
    // If the hook exists, the module can be uninstalled.
    module_load_install($module->name);
1090
    if (module_hook($module->name, 'uninstall') || module_hook($module->name, 'schema')) {
1091 1092
      $form['modules'][$module->name]['name'] = array('#markup' => $info['name'] ? $info['name'] : $module->name);
      $form['modules'][$module->name]['description'] = array('#markup' => t($info['description']));
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
      $options[$module->name] = '';
    }
  }

  // Only build the rest of the form if there are any modules available to uninstall.
  if (!empty($options)) {
    $form['uninstall'] = array(
      '#type' => 'checkboxes',
      '#options' => $options,
    );
    $form['buttons']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Uninstall'),
    );
1107
    $form['#action'] = url('admin/config/modules/uninstall/confirm');
1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
  }
  else {
    $form['modules'] = array();
  }

  return $form;
}

/**
 * Confirm uninstall of selected modules.
 *
 * @ingroup forms
1120 1121
 * @param $storage
 *   An associative array of modules selected to be uninstalled.
1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
 * @return
 *   A form array representing modules to confirm.
 */
function system_modules_uninstall_confirm_form($storage) {
  // Nothing to build.
  if (!isset($storage)) {
    return;
  }

  // Construct the hidden form elements and list items.
  foreach (array_filter($storage['uninstall']) as $module => $value) {
1133
    $info = drupal_parse_info_file(dirname(drupal_get_filename('module', $module)) . '/' . $module . '.info');
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143
    $uninstall[] = $info['name'];
    $form['uninstall'][$module] = array('#type' => 'hidden',
      '#value' => 1,
    );
  }

  // Display a confirm form if modules have been selected.
  if (isset($uninstall)) {
    $form['#confirmed'] = TRUE;
    $form['uninstall']['#tree'] = TRUE;
1144
    $form['modules'] = array('#markup' => '<p>' . t('The following modules will be completely uninstalled from your site, and <em>all data from these modules will be lost</em>!') . '</p>' . theme('item_list', array('items' => $uninstall)));
1145 1146 1147
    $form = confirm_form(
      $form,
      t('Confirm uninstall'),
1148
      'admin/config/modules/uninstall',
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
      t('Would you like to continue with uninstalling the above?'),
      t('Uninstall'),
      t('Cancel'));
    return $form;
  }
}

/**
 * Validates the submitted uninstall form.
 */
function system_modules_uninstall_validate($form, &$form_state) {
  // Form submitted, but no modules selected.
  if (!count(array_filter($form_state['values']['uninstall']))) {
    drupal_set_message(t('No modules selected.'), 'error');
1163
    drupal_goto('admin/config/modules/uninstall');
1164 1165 1166 1167 1168 1169 1170 1171
  }
}

/**
 * Processes the submitted uninstall form.
 */
function system_modules_uninstall_submit($form, &$form_state) {
  // Make sure the install API is available.
1172
  include_once DRUPAL_ROOT . '/includes/install.inc';
1173 1174 1175

  if (!empty($form['#confirmed'])) {
    // Call the uninstall routine for each selected module.
1176 1177
    $modules = array_keys($form_state['values']['uninstall']);
    drupal_uninstall_modules($modules);
1178 1179 1180
    drupal_set_message(t('The selected modules have been uninstalled.'));

    unset($form_state['storage']);
1181
    $form_state['redirect'] = 'admin/config/modules/uninstall';
1182 1183 1184 1185 1186 1187
  }
  else {
    $form_state['storage'] = $form_state['values'];
  }
}

1188 1189 1190 1191 1192 1193 1194 1195
/**
 * Menu callback. Display blocked IP addresses.
 */
function system_ip_blocking() {
  $output = '';
  $rows = array();
  $header = array(t('IP address'), t('Operations'));
  $result = db_query('SELECT * FROM {blocked_ips}');
1196
  foreach ($result as $ip) {
1197 1198
    $rows[] = array(
      $ip->ip,
1199
      l(t('delete'), "admin/config/people/ip-blocking/delete/$ip->iid"),
1200 1201 1202
    );
  }

1203
  $build['system_ip_blocking_form'] = drupal_get_form('system_ip_blocking_form');
1204

1205
  $build['system_ip_blocking_table'] = array(
1206 1207
    '#theme' => 'table',
    '#header' => $header,
Dries's avatar