system.admin.inc 111 KB
Newer Older
1 2 3
<?php

/**
4 5 6 7
 * @file
 * Admin page callbacks for the system module.
 */

8 9 10 11 12 13 14 15 16 17 18 19 20 21
/**
 * 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
22
      WHERE ml.link_path <> 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
23 24 25 26 27
    foreach ($result as $item) {
      _menu_link_translate($item);
      if (!$item['access']) {
        continue;
      }
28 29
      // The link description, either derived from 'description' in hook_menu()
      // or customized via menu module is used as title attribute.
30 31
      if (!empty($item['localized_options']['attributes']['title'])) {
        $item['description'] = $item['localized_options']['attributes']['title'];
32
        unset($item['localized_options']['attributes']['title']);
33 34 35
      }
      $block = $item;
      $block['content'] = '';
36
      $block['content'] .= theme('admin_block_content', array('content' => system_admin_menu_block($item)));
37 38 39 40
      if (!empty($block['content'])) {
        $block['show'] = TRUE;
      }

41 42 43 44 45 46 47
      // 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);
48
    return theme('admin_page', array('blocks' => $blocks));
49 50 51 52 53
  }
  else {
    return t('You do not have any administrative items.');
  }
}
54 55 56

/**
 * Provide a single block from the administration menu as a page.
57
 *
58
 * This function is often a destination for these blocks.
59
 * For example, 'admin/structure/types' needs to have a destination to be valid
60 61
 * in the Drupal menu system, but too much information there might be
 * hidden, so we supply the contents of the block.
62 63 64
 *
 * @return
 *   The output HTML.
65 66 67
 */
function system_admin_menu_block_page() {
  $item = menu_get_item();
68
  if ($content = system_admin_menu_block($item)) {
69
    $output = theme('admin_block_content', array('content' => $content));
70 71 72 73
  }
  else {
    $output = t('You do not have any administrative items.');
  }
74 75
  return $output;
}
76 77

/**
78
 * Menu callback; prints a listing of admin tasks, organized by module.
79
 */
80
function system_admin_index() {
81
  $module_info = system_get_info('module');
82
  foreach ($module_info as $module => $info) {
83
    $module_info[$module] = new stdClass();
84 85 86
    $module_info[$module]->info = $info;
  }
  uasort($module_info, 'system_sort_modules_by_info_name');
87 88
  $menu_items = array();

89
  foreach ($module_info as $module => $info) {
90
    // Only display a section if there are any available tasks.
91 92
    if ($admin_tasks = system_get_module_admin_tasks($module, $info->info)) {
      // Sort links by title.
93
      uasort($admin_tasks, 'drupal_sort_title');
94 95 96 97 98 99
      // Move 'Configure permissions' links to the bottom of each section.
      $permission_key = "admin/people/permissions#module-$module";
      if (isset($admin_tasks[$permission_key])) {
        $permission_task = $admin_tasks[$permission_key];
        unset($admin_tasks[$permission_key]);
        $admin_tasks[$permission_key] = $permission_task;
100 101
      }

102
      $menu_items[$info->info['name']] = array($info->info['description'], $admin_tasks);
103 104
    }
  }
105
  return theme('system_admin_index', array('menu_items' => $menu_items));
106 107 108 109 110 111 112 113 114 115 116
}

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

117
  $item = menu_get_item('admin/config');
118 119
  $content = system_admin_menu_block($item);

120
  $output = theme('admin_block_content', array('content' => $content));
121 122 123 124

  return $output;
}

125 126 127 128 129
/**
 * Menu callback; displays a listing of all themes.
 */
function system_themes_page() {
  // Get current list of themes.
130
  $themes = system_rebuild_theme_data();
131
  uasort($themes, 'system_sort_modules_by_info_name');
132

133
  $theme_default = variable_get('theme_default', 'bartik');
134 135 136
  $theme_groups  = array();

  foreach ($themes as &$theme) {
137 138 139
    if (!empty($theme->info['hidden'])) {
      continue;
    }
140 141
    $admin_theme_options[$theme->name] = $theme->info['name'];
    $theme->is_default = ($theme->name == $theme_default);
142

143 144
    // Identify theme screenshot.
    $theme->screenshot = NULL;
145 146
    // Create a list which includes the current theme and all its base themes.
    if (isset($themes[$theme->name]->base_themes)) {
147 148
      $theme_keys = array_keys($themes[$theme->name]->base_themes);
      $theme_keys[] = $theme->name;
149 150 151 152 153 154 155
    }
    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'])) {
156 157 158 159 160 161
        $theme->screenshot = array(
          'path' => $themes[$theme_key]->info['screenshot'],
          'alt' => t('Screenshot for !theme theme', array('!theme' => $theme->info['name'])),
          'title' => t('Screenshot for !theme theme', array('!theme' => $theme->info['name'])),
          'attributes' => array('class' => array('screenshot')),
        );
162 163 164
        break;
      }
    }
165

166 167 168 169 170 171
    if (empty($theme->status)) {
     // Ensure this theme is compatible with this version of core.
     // Require the 'content' region to make sure the main page
     // content has a common place in all themes.
      $theme->incompatible_core = !isset($theme->info['core']) || ($theme->info['core'] != DRUPAL_CORE_COMPATIBILITY) || (!isset($theme->info['regions']['content']));
      $theme->incompatible_php = version_compare(phpversion(), $theme->info['php']) < 0;
172
    }
173 174 175 176 177 178 179 180 181 182 183
    $query['token'] = drupal_get_token('system-theme-operation-link');
    $theme->operations = array();
    if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php) {
      // Create the operations links.
      $query['theme'] = $theme->name;
      if (drupal_theme_access($theme)) {
        $theme->operations[] = array(
          'title' => t('Settings'),
          'href' => 'admin/appearance/settings/' . $theme->name,
          'attributes' => array('title' => t('Settings for !theme theme', array('!theme' => $theme->info['name']))),
        );
184
      }
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
      if (!empty($theme->status)) {
        if (!$theme->is_default) {
          $theme->operations[] = array(
            'title' => t('Disable'),
            'href' => 'admin/appearance/disable',
            'query' => $query,
            'attributes' => array('title' => t('Disable !theme theme', array('!theme' => $theme->info['name']))),
          );
          $theme->operations[] = array(
            'title' => t('Set default'),
            'href' => 'admin/appearance/default',
            'query' => $query,
            'attributes' => array('title' => t('Set !theme as default theme', array('!theme' => $theme->info['name']))),
          );
        }
      }
      else {
        $theme->operations[] = array(
          'title' => t('Enable'),
          'href' => 'admin/appearance/enable',
          'query' => $query,
          'attributes' => array('title' => t('Enable !theme theme', array('!theme' => $theme->info['name']))),
        );
208 209 210 211 212 213
        $theme->operations[] = array(
          'title' => t('Enable and set default'),
          'href' => 'admin/appearance/default',
          'query' => $query,
          'attributes' => array('title' => t('Enable !theme as default theme', array('!theme' => $theme->info['name']))),
        );
214 215
      }
    }
216 217 218 219 220 221 222 223 224 225 226

    // Add notes to default and administration theme.
    $theme->notes = array();
    $theme->classes = array();
    if ($theme->is_default) {
      $theme->classes[] = 'theme-default';
      $theme->notes[] = t('default theme');
    }

    // Sort enabled and disabled themes into their own groups.
    $theme_groups[$theme->status ? 'enabled' : 'disabled'][] = $theme;
227 228
  }

229 230 231
  // There are two possible theme groups.
  $theme_group_titles = array(
    'enabled' => format_plural(count($theme_groups['enabled']), 'Enabled theme', 'Enabled themes'),
232
  );
233 234
  if (!empty($theme_groups['disabled'])) {
    $theme_group_titles['disabled'] = format_plural(count($theme_groups['disabled']), 'Disabled theme', 'Disabled themes');
235
  }
236

237 238 239 240 241 242 243 244 245 246 247 248 249 250
  uasort($theme_groups['enabled'], 'system_sort_themes');
  drupal_alter('system_themes_page', $theme_groups);

  $admin_form = drupal_get_form('system_themes_admin_form', $admin_theme_options);
  return theme('system_themes_page', array('theme_groups' => $theme_groups, 'theme_group_titles' => $theme_group_titles)) . drupal_render($admin_form);
}

/**
 * Form to select the administration theme.
 *
 * @ingroup forms
 * @see system_themes_admin_form_submit()
 */
function system_themes_admin_form($form, &$form_state, $theme_options) {
251 252 253 254 255 256 257
  // Administration theme settings.
  $form['admin_theme'] = array(
    '#type' => 'fieldset',
    '#title' => t('Administration theme'),
  );
  $form['admin_theme']['admin_theme'] = array(
    '#type' => 'select',
258
    '#options' => array(0 => t('Default theme')) + $theme_options,
259
    '#title' => t('Administration theme'),
260
    '#description' => t('Choose "Default theme" to always use the same theme as the rest of the site.'),
261 262 263 264
    '#default_value' => variable_get('admin_theme', 0),
  );
  $form['admin_theme']['node_admin_theme'] = array(
    '#type' => 'checkbox',
265
    '#title' => t('Use the administration theme when editing or creating content'),
266 267
    '#default_value' => variable_get('node_admin_theme', '0'),
  );
268
  $form['admin_theme']['actions'] = array('#type' => 'actions');
269
  $form['admin_theme']['actions']['submit'] = array(
270 271 272 273 274 275
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  return $form;
}

276
/**
277
 * Process system_themes_admin_form form submissions.
278
 */
279 280 281 282 283
function system_themes_admin_form_submit($form, &$form_state) {
  drupal_set_message(t('The configuration options have been saved.'));
  variable_set('admin_theme', $form_state['values']['admin_theme']);
  variable_set('node_admin_theme', $form_state['values']['node_admin_theme']);
}
284

285 286 287 288 289 290 291
/**
 * Menu callback; Enables a theme.
 */
function system_theme_enable() {
  if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) {
    $theme = $_REQUEST['theme'];
    // Get current list of themes.
292
    $themes = list_themes();
293 294 295 296 297 298 299 300

    // Check if the specified theme is one recognized by the system.
    if (!empty($themes[$theme])) {
      theme_enable(array($theme));
      drupal_set_message(t('The %theme theme has been enabled.', array('%theme' => $themes[$theme]->info['name'])));
    }
    else {
      drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
301
    }
302
    drupal_goto('admin/appearance');
303
  }
304 305
  return drupal_access_denied();
}
306

307 308 309 310 311 312 313
/**
 * Menu callback; Disables a theme.
 */
function system_theme_disable() {
  if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) {
    $theme = $_REQUEST['theme'];
    // Get current list of themes.
314
    $themes = list_themes();
315 316 317

    // Check if the specified theme is one recognized by the system.
    if (!empty($themes[$theme])) {
318
      if ($theme == variable_get('theme_default', 'bartik')) {
319 320 321 322 323 324
        // Don't disable the default theme.
        drupal_set_message(t('%theme is the default theme and cannot be disabled.', array('%theme' => $themes[$theme]->info['name'])), 'error');
      }
      else {
        theme_disable(array($theme));
        drupal_set_message(t('The %theme theme has been disabled.', array('%theme' => $themes[$theme]->info['name'])));
325 326
      }
    }
327 328
    else {
      drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
329
    }
330
    drupal_goto('admin/appearance');
331
  }
332 333
  return drupal_access_denied();
}
334

335 336 337 338 339 340 341
/**
 * Menu callback; Set the default theme.
 */
function system_theme_default() {
  if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) {
    $theme = $_REQUEST['theme'];
    // Get current list of themes.
342
    $themes = list_themes();
343 344 345 346 347 348 349 350 351

    // Check if the specified theme is one recognized by the system.
    if (!empty($themes[$theme])) {
      // Enable the theme if it is currently disabled.
      if (empty($themes[$theme]->status)) {
       theme_enable(array($theme));
      }
      // Set the default theme.
      variable_set('theme_default', $theme);
352 353 354 355 356 357 358 359

      // Rebuild the menu. This duplicates the menu_rebuild() in theme_enable().
      // However, modules must know the current default theme in order to use
      // this information in hook_menu() or hook_menu_alter() implementations,
      // and doing the variable_set() before the theme_enable() could result
      // in a race condition where the theme is default but not enabled.
      menu_rebuild();

360 361
      // The status message depends on whether an admin theme is currently in use:
      // a value of 0 means the admin theme is set to be the default theme.
362
      $admin_theme = variable_get('admin_theme', 0);
363
      if ($admin_theme != 0 && $admin_theme != $theme) {
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
        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(
          '%admin_theme' => $themes[$admin_theme]->info['name'],
          '%selected_theme' => $themes[$theme]->info['name'],
        )));
      }
      else {
        drupal_set_message(t('%theme is now the default theme.', array('%theme' => $themes[$theme]->info['name'])));
      }
    }
    else {
      drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
    }
    drupal_goto('admin/appearance');
  }
  return drupal_access_denied();
379 380 381 382
}

/**
 * Form builder; display theme configuration for entire site and individual themes.
383
 *
384 385 386 387
 * @param $key
 *   A theme name.
 * @return
 *   The form structure.
388
 * @ingroup forms
389
 * @see system_theme_settings_submit()
390
 */
391
function system_theme_settings($form, &$form_state, $key = '') {
392
  // Default settings are defined in theme_get_setting() in includes/theme.inc
393
  if ($key) {
394
    $var = 'theme_' . $key . '_settings';
395
    $themes = system_rebuild_theme_data();
396 397 398 399 400 401 402 403 404 405
    $features = $themes[$key]->info['features'];
  }
  else {
    $var = 'theme_settings';
  }

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

  // Toggle settings
  $toggles = array(
406 407 408 409 410 411 412 413 414
    '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'),
    'favicon'                   => t('Shortcut icon'),
    'main_menu'                 => t('Main menu'),
    'secondary_menu'            => t('Secondary menu'),
415 416 417 418 419 420 421 422
  );

  // 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;
  }
423 424 425 426
  if (!module_exists('comment')) {
    $disabled['toggle_comment_user_picture'] = TRUE;
    $disabled['toggle_comment_user_verification'] = TRUE;
  }
427 428 429 430 431 432 433 434

  $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)) {
435
      $form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('toggle_' . $name, $key));
436
      // Disable checkboxes for features not supported in the current configuration.
437 438
      if (isset($disabled['toggle_' . $name])) {
        $form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE;
439 440 441 442
      }
    }
  }

443
  if (!element_children($form['theme_settings'])) {
444 445 446 447
    // 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;
  }
448 449 450 451 452 453 454

  // 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.'),
455
      '#attributes' => array('class' => array('theme-settings-bottom')),
456
    );
457
    $form['logo']['default_logo'] = array(
458 459
      '#type' => 'checkbox',
      '#title' => t('Use the default logo'),
460
      '#default_value' => theme_get_setting('default_logo', $key),
461 462 463
      '#tree' => FALSE,
      '#description' => t('Check here if you want the theme to use the logo supplied with it.')
    );
464 465 466 467 468 469 470 471 472
    $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),
        ),
      ),
    );
473 474 475 476 477 478
    $logo_path = theme_get_setting('logo_path', $key);
    // If $logo_path is a public:// URI, display the path relative to the files
    // directory; stream wrappers are not end-user friendly.
    if (file_uri_scheme($logo_path) == 'public') {
      $logo_path = file_uri_target($logo_path);
    }
479
    $form['logo']['settings']['logo_path'] = array(
480 481
      '#type' => 'textfield',
      '#title' => t('Path to custom logo'),
482
      '#default_value' => $logo_path,
483 484 485
      '#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(
486 487 488 489 490 491 492 493 494 495 496
      '#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'),
497
      '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
498 499 500 501
    );
    $form['favicon']['default_favicon'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use the default shortcut icon.'),
502
      '#default_value' => theme_get_setting('default_favicon', $key),
503 504
      '#description' => t('Check here if you want the theme to use the default shortcut icon.')
    );
505 506 507 508 509 510 511 512 513
    $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),
        ),
      ),
    );
514 515 516 517 518 519
    $favicon_path = theme_get_setting('favicon_path', $key);
    // If $favicon_path is a public:// URI, display the path relative to the
    // files directory; stream wrappers are not end-user friendly.
    if (file_uri_scheme($favicon_path) == 'public') {
      $favicon_path = file_uri_target($favicon_path);
    }
520
    $form['favicon']['settings']['favicon_path'] = array(
521 522
      '#type' => 'textfield',
      '#title' => t('Path to custom icon'),
523
      '#default_value' => $favicon_path,
524 525
      '#description' => t('The path to the image file you would like to use as your custom shortcut icon.')
    );
526
    $form['favicon']['settings']['favicon_upload'] = array(
527 528 529 530 531 532 533
      '#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) {
534
    // Call engine-specific settings.
535
    $function = $themes[$key]->prefix . '_engine_settings';
536
    if (function_exists($function)) {
537 538 539 540 541 542
      $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);
543
    }
544 545 546 547 548

    // 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;
549
    }
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
    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);
572 573
      }
    }
574 575

    // Restore the original current theme.
576
    if (isset($default_theme)) {
577 578 579 580 581
      $GLOBALS['theme_key'] = $default_theme;
    }
    else {
      unset($GLOBALS['theme_key']);
    }
582 583
  }

584
  $form = system_settings_form($form);
585
  // We don't want to call system_settings_form_submit(), so change #submit.
586 587
  array_pop($form['#submit']);
  $form['#submit'][] = 'system_theme_settings_submit';
588
  $form['#validate'][] = 'system_theme_settings_validate';
589 590 591
  return $form;
}

592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
/**
 * Validator for the system_theme_settings() form.
 */
function system_theme_settings_validate($form, &$form_state) {
  // Handle file uploads.
  $validators = array('file_validate_is_image' => array());

  // Check for a new uploaded logo.
  $file = file_save_upload('logo_upload', $validators);
  if (isset($file)) {
    // File upload was attempted.
    if ($file) {
      // Put the temporary file in form_values so we can save it on submit.
      $form_state['values']['logo_upload'] = $file;
    }
    else {
      // File upload failed.
      form_set_error('logo_upload', t('The logo could not be uploaded.'));
    }
  }

613 614
  $validators = array('file_validate_extensions' => array('ico png gif jpg jpeg apng svg'));

615
  // Check for a new uploaded favicon.
616
  $file = file_save_upload('favicon_upload', $validators);
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
  if (isset($file)) {
    // File upload was attempted.
    if ($file) {
      // Put the temporary file in form_values so we can save it on submit.
      $form_state['values']['favicon_upload'] = $file;
    }
    else {
      // File upload failed.
      form_set_error('logo_upload', t('The favicon could not be uploaded.'));
    }
  }

  // If the user provided a path for a logo or favicon file, make sure a file
  // exists at that path.
  if ($form_state['values']['logo_path']) {
    $path = _system_theme_settings_validate_path($form_state['values']['logo_path']);
    if (!$path) {
      form_set_error('logo_path', t('The custom logo path is invalid.'));
    }
  }
  if ($form_state['values']['favicon_path']) {
    $path = _system_theme_settings_validate_path($form_state['values']['favicon_path']);
    if (!$path) {
      form_set_error('favicon_path', t('The custom favicon path is invalid.'));
    }
  }
}

/**
 * Helper function for the system_theme_settings form.
 *
 * Attempts to validate normal system paths, paths relative to the public files
 * directory, or stream wrapper URIs. If the given path is any of the above,
 * returns a valid path or URI that the theme system can display.
 *
 * @param $path
 *   A path relative to the Drupal root or to the public files directory, or
 *   a stream wrapper URI.
 * @return mixed
 *   A valid path that can be displayed through the theme system, or FALSE if
 *   the path could not be validated.
 */
function _system_theme_settings_validate_path($path) {
  if (drupal_realpath($path)) {
    // The path is relative to the Drupal root, or is a valid URI.
    return $path;
  }
  $uri = 'public://' . $path;
  if (file_exists($uri)) {
    return $uri;
  }
  return FALSE;
}

671 672 673
/**
 * Process system_theme_settings form submissions.
 */
674
function system_theme_settings_submit($form, &$form_state) {
675
  $values = $form_state['values'];
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702

  // If the user uploaded a new logo or favicon, save it to a permanent location
  // and use it in place of the default theme-provided file.
  if ($file = $values['logo_upload']) {
    unset($values['logo_upload']);
    $filename = file_unmanaged_copy($file->uri);
    $values['default_logo'] = 0;
    $values['logo_path'] = $filename;
    $values['toggle_logo'] = 1;
  }
  if ($file = $values['favicon_upload']) {
    unset($values['favicon_upload']);
    $filename = file_unmanaged_copy($file->uri);
    $values['default_favicon'] = 0;
    $values['favicon_path'] = $filename;
    $values['toggle_favicon'] = 1;
  }

  // If the user entered a path relative to the system files directory for
  // a logo or favicon, store a public:// URI so the theme system can handle it.
  if (!empty($values['logo_path'])) {
    $values['logo_path'] = _system_theme_settings_validate_path($values['logo_path']);
  }
  if (!empty($values['favicon_path'])) {
    $values['favicon_path'] = _system_theme_settings_validate_path($values['favicon_path']);
  }

703
  if (empty($values['default_favicon']) && !empty($values['favicon_path'])) {
704
    $values['favicon_mimetype'] = file_get_mimetype($values['favicon_path']);
705
  }
706
  $key = $values['var'];
707

708 709 710 711
  // 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.'));
712 713 714 715

  cache_clear_all();
}

716
/**
717 718 719
 * Recursively check compatibility.
 *
 * @param $incompatible
720 721
 *   An associative array which at the end of the check contains all
 *   incompatible files as the keys, their values being TRUE.
722 723 724 725 726
 * @param $files
 *   The set of files that will be tested.
 * @param $file
 *   The file at which the check starts.
 * @return
727 728
 *   Returns TRUE if an incompatible file is found, NULL (no return value)
 *   otherwise.
729 730 731 732 733
 */
function _system_is_incompatible(&$incompatible, $files, $file) {
  if (isset($incompatible[$file->name])) {
    return TRUE;
  }
734 735 736
  // Recursively traverse required modules, looking for incompatible modules.
  foreach ($file->requires as $requires) {
    if (isset($files[$requires]) && _system_is_incompatible($incompatible, $files, $files[$requires])) {
737 738 739 740 741 742
      $incompatible[$file->name] = TRUE;
      return TRUE;
    }
  }
}

743 744 745
/**
 * Menu callback; provides module enable/disable interface.
 *
746 747 748
 * The list of modules gets populated by module.info files, which contain each
 * module's name, description, and information about which modules it requires.
 * See drupal_parse_info_file() for information on module.info descriptors.
749
 *
750 751 752
 * 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.
753
 *
754 755
 * @param $form_state
 *   An associative array containing the current state of the form.
756 757 758 759
 *
 * @return
 *   The form array.
 *
760
 * @ingroup forms
761 762
 * @see theme_system_modules()
 * @see system_modules_submit()
763
 */
764
function system_modules($form, $form_state = array()) {
765
  // Get current list of modules.
766
  $files = system_rebuild_module_data();
767

768
  // Remove hidden modules from display list.
769 770
  $visible_files = $files;
  foreach ($visible_files as $filename => $file) {
771
    if (!empty($file->info['hidden'])) {
772
      unset($visible_files[$filename]);
773 774 775
    }
  }

776
  uasort($visible_files, 'system_sort_modules_by_info_name');
777

778
  // If the modules form was submitted, then system_modules_submit() runs first
779
  // and if there are unfilled required modules, then $form_state['storage'] is
780 781
  // filled, triggering a rebuild. In this case we need to display a
  // confirmation form.
782
  if (!empty($form_state['storage'])) {
783
    return system_modules_confirm_form($visible_files, $form_state['storage']);
784
  }
785

786 787
  $modules = array();
  $form['modules'] = array('#tree' => TRUE);
788

789 790 791
  // Used when checking if module implements a help page.
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;

792 793 794 795
  // Used when displaying modules that are required by the install profile.
  require_once DRUPAL_ROOT . '/includes/install.inc';
  $distribution_name = check_plain(drupal_install_profile_distribution_name());

796
  // Iterate through each of the modules.
797
  foreach ($visible_files as $filename => $module) {
798 799
    $extra = array();
    $extra['enabled'] = (bool) $module->status;
800 801
    if (!empty($module->info['required'] )) {
      $extra['disabled'] = TRUE;
802
      $extra['required_by'][] = $distribution_name . (!empty($module->info['explanation']) ? ' ('. $module->info['explanation'] .')' : '');
803 804
    }

805 806 807 808
    // 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)));
809 810
        $extra['disabled'] = TRUE;
      }
811 812
      // Only display visible modules.
      elseif (isset($visible_files[$requires])) {
813
        $requires_name = $files[$requires]->info['name'];
814 815 816 817 818 819
        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;
820
        }
821 822 823 824 825
        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));
826 827
        }
      }
828 829
    }
    // Generate link for module's help page, if there is one.
830
    if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
831
      if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
832 833 834 835 836 837
        $extra['links']['help'] = array(
          '#type' => 'link',
          '#title' => t('Help'),
          '#href' => "admin/help/$filename",
          '#options' => array('attributes' => array('class' =>  array('module-link', 'module-link-help'), 'title' => t('Help'))),
        );
838 839
      }
    }
840 841 842 843 844
    // Generate link for module's permission, if the user has access to it.
    if ($module->status && user_access('administer permissions') && in_array($filename, module_implements('permission'))) {
      $extra['links']['permissions'] = array(
        '#type' => 'link',
        '#title' => t('Permissions'),
845
        '#href' => 'admin/people/permissions',
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
        '#options' => array('fragment' => 'module-' . $filename, 'attributes' => array('class' => array('module-link', 'module-link-permissions'), 'title' => t('Configure permissions'))),
      );
    }
    // Generate link for module's configuration page, if the module provides
    // one.
    if ($module->status && isset($module->info['configure'])) {
      $configure_link = menu_get_item($module->info['configure']);
      if ($configure_link['access']) {
        $extra['links']['configure'] = array(
          '#type' => 'link',
          '#title' => t('Configure'),
          '#href' => $configure_link['href'],
          '#options' => array('attributes' => array('class' => array('module-link', 'module-link-configure'), 'title' => $configure_link['description'])),
        );
      }
    }

863 864 865
    // 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) {
866
      // Hidden modules are unset already.
867
      if (isset($visible_files[$required_by])) {
868
        if ($files[$required_by]->status == 1 && $module->status == 1) {
869
          $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
870 871 872
          $extra['disabled'] = TRUE;
        }
        else {
873
          $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
874
        }
875 876
      }
    }
877
    $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
878
  }
879

880 881 882 883 884 885 886 887
  // 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(
888
        array('data' => t('Enabled'), 'class' => array('checkbox')),
889 890 891
        t('Name'),
        t('Version'),
        t('Description'),
892
        array('data' => t('Operations'), 'colspan' => 3),
893
      ),
894 895
      // Ensure that the "Core" package fieldset comes first.
      '#weight' => $package == 'Core' ? -10 : NULL,
896
    );
897 898
  }

899 900 901
  // Lastly, sort all fieldsets by title.
  uasort($form['modules'], 'element_sort_by_title');

902
  $form['actions'] = array('#type' => 'actions');
903
  $form['actions']['submit'] = array(
904 905 906
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
907
  $form['#action'] = url('admin/modules/list/confirm');
908 909 910 911

  return $form;
}

912
/**
913
 * Array sorting callback; sorts modules or themes by their name.
914
 */
915 916 917 918
function system_sort_modules_by_info_name($a, $b) {
  return strcasecmp($a->info['name'], $b->info['name']);
}

919 920 921 922 923 924 925 926 927 928 929 930 931
/**
 * Array sorting callback; sorts modules or themes by their name.
 */
function system_sort_themes($a, $b) {
  if ($a->is_default) {
    return -1;
  }
  if ($b->is_default) {
    return 1;
  }
  return strcasecmp($a->info['name'], $b->info['name']);
}

932
/**
933
 * Build a table row for the system modules page.
934
 */
935 936 937
function _system_modules_build_row($info, $extra) {
  // Add in the defaults.
  $extra += array(
938 939
    'requires' => array(),
    'required_by' => array(),
940 941
    'disabled' => FALSE,
    'enabled' => FALSE,
942
    'links' => array(),
943 944 945 946 947 948
  );
  $form = array(
    '#tree' => TRUE,
  );
  // Set the basic properties.
  $form['name'] = array(
949
    '#markup' => $info['name'],
950 951 952 953 954 955 956
  );
  $form['description'] = array(
    '#markup' => t($info['description']),
  );
  $form['version'] = array(
    '#markup' => $info['version'],
  );
957 958
  $form['#requires'] = $extra['requires'];
  $form['#required_by'] = $extra['required_by'];
959 960 961 962 963 964 965

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

  // Check the core compatibility.
966
  if (!isset($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY) {
967 968
    $compatible = FALSE;
    $status_short .= t('Incompatible with this version of Drupal core. ');
969
    $status_long .= t('This version is not compatible with Drupal !core_version and should be replaced.', array('!core_version' => DRUPAL_CORE_COMPATIBILITY));
970 971 972 973 974 975
  }

  // 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');
976
    $php_required = $info['php'];
977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
    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(
997
      '#markup' =>  theme('image', array('path' => 'misc/watchdog-error.png', 'alt' => $status_short, 'title' => $status_short)),
998
    );
999
    $form['description']['#markup'] .= theme('system_modules_incompatible', array('message' => $status_long));
1000 1001
  }

1002 1003 1004
  // Build operation links.
  foreach (array('help', 'permissions', 'configure') as $key) {
    $form['links'][$key] = (isset($extra['links'][$key]) ? $extra['links'][$key] : array());
1005
  }
1006

1007 1008 1009 1010
  return $form;
}

/**
1011
 * Display confirmation form for required modules.
1012 1013
 *
 * @param $modules
1014
 *   Array of module file objects as returned from system_rebuild_module_data().
1015 1016
 * @param $storage
 *   The contents of $form_state['storage']; an array with two
1017
 *   elements: the list of required modules and the list of status
1018 1019 1020 1021 1022 1023 1024 1025
 *   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;
1026

1027
  foreach ($storage['more_required'] as $info) {
1028
    $t_argument = array(
1029
      '@module' => $info['name'],
1030
      '@required' => implode(', ', $info['requires']),
1031
    );
1032
    $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);
1033
  }
1034 1035 1036 1037 1038 1039

  foreach ($storage['missing_modules'] as $name => $info) {
    $t_argument = array(
      '@module' => $name,
      '@depends' => implode(', ', $info['depends']),
    );
1040
    $items[] = format_plural(count($info['depends']), 'The @module module is missing, so the following module will be disabled: @depends.', 'The @module module is missing, so the following modules will be disabled: @depends.', $t_argument);
1041 1042
  }

1043
  $form['text'] = array('#markup' => theme('item_list', array('items' => $items)));
1044 1045 1046 1047 1048 1049

  if ($form) {
    // Set some default form values
    $form = confirm_form(
      $form,
      t('Some required modules must be enabled'),
1050
      'admin/modules',
1051
      t('Would you like to continue with the above?'),
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
      t('Continue'),
      t('Cancel'));
    return $form;
  }
}

/**
 * Submit callback; handles modules form submission.
 */
function system_modules_submit($form, &$form_state) {
1062
  include_once DRUPAL_ROOT . '/includes/install.inc';
1063 1064

  // Builds list of modules.
1065 1066
  $modules = array();
  // If we're not coming from the confirmation form, build the list of modules.
1067
  if (empty($form_state['storage'])) {
1068
    // If we're not coming from the confirmation form, build the module list.
1069 1070 1071
    foreach ($form_state['values']['modules'] as $group_name => $group) {
      foreach ($group as $module => $enabled) {
        $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']);
1072 1073 1074 1075
      }
    }
  }
  else {
1076 1077 1078
    // If we are coming from the confirmation form, fetch
    // the modules out of $form_state.
    $modules = $form_state['storage']['modules'];
1079 1080
  }

1081
  // Collect data for all modules to be able to determine dependencies.
1082
  $files = system_rebuild_module_data();