system.admin.inc 112 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', 'stark');
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
        $theme->screenshot = array(
157
          'uri' => $themes[$theme_key]->info['screenshot'],
158 159 160 161
          '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', 'stark')) {
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
  // Used when displaying modules that are required by the install profile.
793
  require_once DRUPAL_ROOT . '/core/includes/install.inc';
794 795
  $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
        // Disable this module if it is incompatible with the dependency's version.
815 816 817 818 819 820
        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;
821
        }
822 823 824 825 826 827 828 829
        // Disable this module if the dependency is incompatible with this
        // version of Drupal core.
        elseif ($files[$requires]->info['core'] != DRUPAL_CORE_COMPATIBILITY) {
          $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> this version of Drupal core)', array(
            '@module' => $requires_name,
          ));
          $extra['disabled'] = TRUE;
        }
830 831 832 833 834
        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));
835 836
        }
      }
837 838
    }
    // Generate link for module's help page, if there is one.
839
    if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
840
      if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
841 842 843 844 845 846
        $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'))),
        );
847 848
      }
    }
849 850 851 852 853
    // 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'),
854
        '#href' => 'admin/people/permissions',
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871
        '#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'])),
        );
      }
    }

872 873 874
    // 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) {
875
      // Hidden modules are unset already.
876
      if (isset($visible_files[$required_by])) {
877
        if ($files[$required_by]->status == 1 && $module->status == 1) {
878
          $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
879 880 881
          $extra['disabled'] = TRUE;
        }
        else {
882
          $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
883
        }
884 885
      }
    }
886
    $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
887
  }
888

889 890 891 892 893 894 895 896
  // 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(
897
        array('data' => t('Enabled'), 'class' => array('checkbox')),
898 899 900
        t('Name'),
        t('Version'),
        t('Description'),
901
        array('data' => t('Operations'), 'colspan' => 3),
902
      ),
903 904
      // Ensure that the "Core" package fieldset comes first.
      '#weight' => $package == 'Core' ? -10 : NULL,
905
    );
906 907
  }

908 909 910
  // Lastly, sort all fieldsets by title.
  uasort($form['modules'], 'element_sort_by_title');

911
  $form['actions'] = array('#type' => 'actions');
912
  $form['actions']['submit'] = array(
913 914 915
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
916
  $form['#action'] = url('admin/modules/list/confirm');
917 918 919 920

  return $form;
}

921
/**
922
 * Array sorting callback; sorts modules or themes by their name.
923
 */
924 925 926 927
function system_sort_modules_by_info_name($a, $b) {
  return strcasecmp($a->info['name'], $b->info['name']);
}

928 929 930 931 932 933 934 935 936 937 938 939 940
/**
 * 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']);
}

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

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

  // Check the core compatibility.
975
  if (!isset($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY) {
976
    $compatible = FALSE;
977
    $status_short .= t('Incompatible with this version of Drupal core.');
978
    $status_long .= t('This version is not compatible with Drupal !core_version and should be replaced.', array('!core_version' => DRUPAL_CORE_COMPATIBILITY));
979 980 981 982 983 984
  }

  // 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');
985
    $php_required = $info['php'];
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
    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(
1006
      '#markup' =>  theme('image', array('uri' => 'core/misc/watchdog-error.png', 'alt' => $status_short, 'title' => $status_short)),
1007
    );
1008
    $form['description']['#markup'] .= theme('system_modules_incompatible', array('message' => $status_long));
1009 1010
  }

1011 1012 1013
  // Build operation links.
  foreach (array('help', 'permissions', 'configure') as $key) {
    $form['links'][$key] = (isset($extra['links'][$key]) ? $extra['links'][$key] : array());
1014
  }
1015

1016 1017 1018 1019
  return $form;
}

/**
1020
 * Display confirmation form for required modules.
1021 1022
 *
 * @param $modules
1023
 *   Array of module file objects as returned from system_rebuild_module_data().
1024 1025
 * @param $storage
 *   The contents of $form_state['storage']; an array with two
1026
 *   elements: the list of required modules and the list of status
1027 1028 1029 1030 1031 1032 1033 1034
 *   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;
1035

1036
  foreach ($storage['more_required'] as $info) {
1037
    $t_argument = array(
1038
      '@module' => $info['name'],
1039
      '@required' => implode(', ', $info['requires']),
1040
    );
1041
    $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);
1042
  }
1043 1044 1045 1046 1047 1048

  foreach ($storage['missing_modules'] as $name => $info) {
    $t_argument = array(
      '@module' => $name,
      '@depends' => implode(', ', $info['depends']),
    );
1049
    $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);
1050 1051
  }

1052
  $form['text'] = array('#markup' => theme('item_list', array('items' => $items)));
1053 1054 1055 1056 1057 1058

  if ($form) {
    // Set some default form values
    $form = confirm_form(
      $form,
      t('Some required modules must be enabled'),
1059
      'admin/modules',
1060
      t('Would you like to continue with the above?'),
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
      t('Continue'),
      t('Cancel'));
    return $form;
  }
}

/**
 * Submit callback; handles modules form submission.
 */
function system_modules_submit($form, &$form_state) {
1071
  include_once DRUPAL_ROOT . '/core/includes/install.inc';
1072 1073

  // Builds list of modules.
1074 1075
  $modules = array();
  // If we're not coming from the confirmation form, build the list of modules.
1076
  if (empty($form_state['storage'])) {
1077
    // If we're not coming from the confirmation form, build the module list.
1078 1079 1080
    foreach ($form_state['values']['modules'] as $group_name => $group) {
      foreach ($group as $module => $enabled) {
        $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']);
1081 1082 1083 1084
      }
    }
  }
  else {
1085 1086 1087
    // If we are coming from the confirmation form, fetch
    // the modules out of $form_state.
    $modules = $form_state['storage']['modules'];
1088 1089
  }

1090
  // Collect data for all modules to be able to determine dependencies.
1091
  $files = system_rebuild_module_data();
1092

1093 1094 1095 1096 1097 1098
  // Sorts modules by weight.
  $sort = array();
  foreach (array_keys($modules) as $module) {
    $sort[$module] = $files[$module]->sort;
  }
  array_multisort($sort, $modules);
1099

1100 1101 1102
  // Makes sure all required modules are set to be enabled.
  $more_required = array();
  $missing_modules = array();
1103 1104
  foreach ($modules as $name => $module) {
    if ($module['enabled']) {
1105 1106 1107 1108 1109 1110 1111 1112 1113
      // Checks that all dependencies are set to be enabled.  Stores the ones
      // that are not in $dependencies variable so that the user can be alerted
      // in the confirmation form that more modules need to be enabled.
      $dependencies = array();
      foreach (array_keys($files[$name]->requires) as $required) {
        if (empty($modules[$required]['enabled'])) {
          if (isset($files[$required])) {
            $dependencies[] = $files[$required]->info['name'];
            $modules[$required]['enabled'] = TRUE;
1114 1115
          }
          else {
1116 1117
            $missing_modules[$required]['depends'][] = $name;
            $modules[$name]['enabled'] = FALSE;
1118 1119 1120
          }
        }
      }
1121 1122 1123 1124 1125 1126 1127 1128

      // Stores additional modules that need to be enabled in $more_required.
      if (!empty($dependencies)) {
        $more_required[$name] = array(
          'name' => $files[$name]->info['name'],
          'requires' => $dependencies,
        );
      }
1129 1130
    }
  }
1131

1132 1133
  // Redirects to confirmation form if more modules need to be enabled.
  if ((!empty($more_required) || !empty($missing_modules)) && !isset($form_state['values']['confirm'])) {
1134
    $form_state['storage'] = array(
1135 1136
      'more_required' => $more_required,
      'modules' => $modules,
1137 1138 1139 1140
      'missing_modules' => $missing_modules,
    );
    $form_state['rebuild'] = TRUE;
    return;
1141 1142
  }

1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
  // Invokes hook_requirements('install').  If failures are detected, makes sure
  // the dependent modules aren't installed either.
  foreach ($modules as $name => $module) {
    // Only invoke hook_requirements() on modules that are going to be installed.
    if ($module['enabled'] && drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
      if (!drupal_check_module($name)) {
        $modules[$name]['enabled'] = FALSE;
        foreach (array_keys($files[$name]->required_by) as $required_by) {
          $modules[$required_by]['enabled'] = FALSE;
        }
      }
1154
    }
1155 1156
  }

1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
  // Initializes array of actions.
  $actions = array(
    'enable' => array(),
    'disable' => array(),
    'install' => array(),
  );

  // Builds arrays of modules that need to be enabled, disabled, and installed.
  foreach ($modules as $name => $module) {
    if ($module['enabled']) {
      if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
        $actions['install'][] = $name;
1169
        $actions['enable'][] = $name;
1170 1171 1172
      }
      elseif (!module_exists($name)) {
        $actions['enable'][] = $name;
1173
      }
1174
    }
1175 1176 1177
    elseif (module_exists($name)) {
      $actions['disable'][] = $name;
    }
1178 1179
  }

1180 1181 1182 1183 1184
  // Gets list of modules prior to install process, unsets $form_state['storage']
  // so we don't get redirected back to the confirmation form.
  $pre_install_list = module_list();
  unset($form_state['storage']);

1185
  // Reverse the 'enable' list, to order dependencies before dependents.
1186
  krsort($actions['enable']);
1187

1188
  // Installs, enables, and disables modules.
1189 1190
  module_enable($actions['enable'], FALSE);
  module_disable($actions['disable'], FALSE);
1191

1192 1193
  // Gets module list after install process, flushes caches and displays a
  // message if there are changes.
1194 1195
  $post_install_list = module_list(TRUE);
  if ($pre_install_list != $post_install_list) {
1196
    drupal_flush_all_caches();
1197 1198 1199
    drupal_set_message(t('The configuration options have been saved.'));
  }

1200
  $form_state['redirect'] = 'admin/modules';
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210
}

/**
 * Uninstall functions
 */

/**
 * Builds a form of currently disabled modules.
 *
 * @ingroup forms
1211 1212
 * @see system_modules_uninstall_validate()
 * @see system_modules_uninstall_submit()
1213 1214
 * @param $form_state['values']
 *   Submitted form values.