system.admin.inc 110 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
/**
 * Menu callback; displays a listing of all themes.
 */
function system_themes_page() {
  // Get current list of themes.
113
  $themes = system_rebuild_theme_data();
114
  uasort($themes, 'system_sort_modules_by_info_name');
115

116
  $theme_default = variable_get('theme_default', 'stark');
117 118 119
  $theme_groups  = array();

  foreach ($themes as &$theme) {
120 121 122
    if (!empty($theme->info['hidden'])) {
      continue;
    }
123 124
    $admin_theme_options[$theme->name] = $theme->info['name'];
    $theme->is_default = ($theme->name == $theme_default);
125

126 127
    // Identify theme screenshot.
    $theme->screenshot = NULL;
128 129
    // Create a list which includes the current theme and all its base themes.
    if (isset($themes[$theme->name]->base_themes)) {
130 131
      $theme_keys = array_keys($themes[$theme->name]->base_themes);
      $theme_keys[] = $theme->name;
132 133 134 135 136 137 138
    }
    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'])) {
139
        $theme->screenshot = array(
140
          'uri' => $themes[$theme_key]->info['screenshot'],
141 142 143 144
          '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')),
        );
145 146 147
        break;
      }
    }
148

149 150 151 152 153 154
    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;
155
    }
156 157 158 159 160 161 162 163 164 165 166
    $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']))),
        );
167
      }
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
      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']))),
        );
191 192 193 194 195 196
        $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']))),
        );
197 198
      }
    }
199 200 201 202 203 204 205 206 207 208 209

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

212 213 214
  // There are two possible theme groups.
  $theme_group_titles = array(
    'enabled' => format_plural(count($theme_groups['enabled']), 'Enabled theme', 'Enabled themes'),
215
  );
216 217
  if (!empty($theme_groups['disabled'])) {
    $theme_group_titles['disabled'] = format_plural(count($theme_groups['disabled']), 'Disabled theme', 'Disabled themes');
218
  }
219

220 221 222 223 224 225 226 227 228 229 230 231 232 233
  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) {
234 235 236 237 238 239 240
  // Administration theme settings.
  $form['admin_theme'] = array(
    '#type' => 'fieldset',
    '#title' => t('Administration theme'),
  );
  $form['admin_theme']['admin_theme'] = array(
    '#type' => 'select',
241
    '#options' => array(0 => t('Default theme')) + $theme_options,
242
    '#title' => t('Administration theme'),
243
    '#description' => t('Choose "Default theme" to always use the same theme as the rest of the site.'),
244 245
    '#default_value' => variable_get('admin_theme', 0),
  );
246
  $form['admin_theme']['actions'] = array('#type' => 'actions');
247
  $form['admin_theme']['actions']['submit'] = array(
248 249 250 251 252 253
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  return $form;
}

254
/**
255
 * Process system_themes_admin_form form submissions.
256
 */
257 258 259 260
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']);
}
261

262 263 264 265 266 267 268
/**
 * 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.
269
    $themes = list_themes();
270 271 272 273 274 275 276 277

    // 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');
278
    }
279
    drupal_goto('admin/appearance');
280
  }
281 282
  return drupal_access_denied();
}
283

284 285 286 287 288 289 290
/**
 * 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.
291
    $themes = list_themes();
292 293 294

    // Check if the specified theme is one recognized by the system.
    if (!empty($themes[$theme])) {
295
      if ($theme == variable_get('theme_default', 'stark')) {
296 297 298 299 300 301
        // 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'])));
302 303
      }
    }
304 305
    else {
      drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
306
    }
307
    drupal_goto('admin/appearance');
308
  }
309 310
  return drupal_access_denied();
}
311

312 313 314 315 316 317 318
/**
 * 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.
319
    $themes = list_themes();
320 321 322 323 324 325 326 327 328

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

330 331 332 333 334 335 336
      // Rebuild the menu. This duplicates the menu_router_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_router_rebuild();
337

338 339
      // 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.
340
      $admin_theme = variable_get('admin_theme', 0);
341
      if ($admin_theme != 0 && $admin_theme != $theme) {
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
        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();
357 358 359 360
}

/**
 * Form builder; display theme configuration for entire site and individual themes.
361
 *
362 363 364 365
 * @param $key
 *   A theme name.
 * @return
 *   The form structure.
366
 * @ingroup forms
367
 * @see system_theme_settings_submit()
368
 */
369
function system_theme_settings($form, &$form_state, $key = '') {
370
  // Default settings are defined in theme_get_setting() in includes/theme.inc
371
  if ($key) {
372
    $var = 'theme_' . $key . '_settings';
373
    $themes = list_themes();
374 375 376 377 378 379 380 381 382 383
    $features = $themes[$key]->info['features'];
  }
  else {
    $var = 'theme_settings';
  }

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

  // Toggle settings
  $toggles = array(
384 385 386 387 388 389 390 391 392
    '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'),
393 394 395 396 397 398 399 400
  );

  // 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;
  }
401 402 403 404
  if (!module_exists('comment')) {
    $disabled['toggle_comment_user_picture'] = TRUE;
    $disabled['toggle_comment_user_verification'] = TRUE;
  }
405 406 407 408 409 410 411 412

  $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)) {
413
      $form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('toggle_' . $name, $key));
414
      // Disable checkboxes for features not supported in the current configuration.
415 416
      if (isset($disabled['toggle_' . $name])) {
        $form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE;
417 418 419 420
      }
    }
  }

421
  if (!element_children($form['theme_settings'])) {
422 423 424 425
    // 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;
  }
426 427 428 429 430 431

  // Logo settings
  if ((!$key) || in_array('logo', $features)) {
    $form['logo'] = array(
      '#type' => 'fieldset',
      '#title' => t('Logo image settings'),
432
      '#attributes' => array('class' => array('theme-settings-bottom')),
433
    );
434
    $form['logo']['default_logo'] = array(
435
      '#type' => 'checkbox',
436
      '#title' => t('Use the default logo supplied by the theme'),
437
      '#default_value' => theme_get_setting('default_logo', $key),
438 439
      '#tree' => FALSE,
    );
440 441 442 443 444 445 446 447 448 449
    $form['logo']['settings'] = array(
      '#type' => 'container',
      '#states' => array(
        // Hide the logo settings when using the default logo.
        'invisible' => array(
          'input[name="default_logo"]' => array('checked' => TRUE),
        ),
      ),
    );
    $form['logo']['settings']['logo_path'] = array(
450 451
      '#type' => 'textfield',
      '#title' => t('Path to custom logo'),
452
      '#default_value' => theme_get_setting('logo_path', $key),
453 454
    );
    $form['logo']['settings']['logo_upload'] = array(
455 456 457 458 459 460 461 462 463 464 465
      '#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'),
466
      '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
467 468 469
    );
    $form['favicon']['default_favicon'] = array(
      '#type' => 'checkbox',
470
      '#title' => t('Use the default shortcut icon supplied by the theme'),
471
      '#default_value' => theme_get_setting('default_favicon', $key),
472
    );
473 474 475 476 477 478 479 480 481 482
    $form['favicon']['settings'] = array(
      '#type' => 'container',
      '#states' => array(
        // Hide the favicon settings when using the default favicon.
        'invisible' => array(
          'input[name="default_favicon"]' => array('checked' => TRUE),
        ),
      ),
    );
    $form['favicon']['settings']['favicon_path'] = array(
483 484
      '#type' => 'textfield',
      '#title' => t('Path to custom icon'),
485
      '#default_value' => theme_get_setting('favicon_path', $key),
486
    );
487
    $form['favicon']['settings']['favicon_upload'] = array(
488 489 490 491 492 493
      '#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.")
    );
  }

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
  // Inject human-friendly values and form element descriptions for logo and
  // favicon.
  foreach (array('logo' => 'logo.png', 'favicon' => 'favicon.ico') as $type => $default) {
    if (isset($form[$type]['settings'][$type . '_path'])) {
      $element = &$form[$type]['settings'][$type . '_path'];

      // If path is a public:// URI, display the path relative to the files
      // directory; stream wrappers are not end-user friendly.
      $original_path = $element['#default_value'];
      $friendly_path = NULL;
      if (file_uri_scheme($original_path) == 'public') {
        $friendly_path = file_uri_target($original_path);
        $element['#default_value'] = $friendly_path;
      }

      // Prepare local file path for description.
      if ($original_path && isset($friendly_path)) {
        $local_file = strtr($original_path, array('public:/' => variable_get('file_public_path', conf_path() . '/files')));
      }
      elseif ($key) {
        $local_file = drupal_get_path('theme', $key) . '/' . $default;
      }
      else {
        $local_file = path_to_theme() . '/' . $default;
      }

      $element['#description'] = t('Examples: <code>@implicit-public-file</code> (for a file in the public filesystem), <code>@explicit-file</code>, or <code>@local-file</code>.', array(
        '@implicit-public-file' => isset($friendly_path) ? $friendly_path : $default,
        '@explicit-file' => file_uri_scheme($original_path) !== FALSE ? $original_path : 'public://' . $default,
        '@local-file' => $local_file,
      ));
    }
  }

528
  if ($key) {
529
    // Call engine-specific settings.
530
    $function = $themes[$key]->prefix . '_engine_settings';
531
    if (function_exists($function)) {
532 533 534 535 536 537
      $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);
538
    }
539 540 541 542 543

    // 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;
544
    }
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
    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);
567 568
      }
    }
569 570

    // Restore the original current theme.
571
    if (isset($default_theme)) {
572 573 574 575 576
      $GLOBALS['theme_key'] = $default_theme;
    }
    else {
      unset($GLOBALS['theme_key']);
    }
577 578
  }

579
  $form = system_settings_form($form);
580
  // We don't want to call system_settings_form_submit(), so change #submit.
581 582
  array_pop($form['#submit']);
  $form['#submit'][] = 'system_theme_settings_submit';
583
  $form['#validate'][] = 'system_theme_settings_validate';
584 585 586
  return $form;
}

587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
/**
 * 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.'));
    }
  }

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

610
  // Check for a new uploaded favicon.
611
  $file = file_save_upload('favicon_upload', $validators);
612 613 614 615 616 617 618 619
  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.
620
      form_set_error('favicon_upload', t('The favicon could not be uploaded.'));
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
    }
  }

  // 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) {
655 656 657 658 659 660
  // Absolute local file paths are invalid.
  if (drupal_realpath($path) == $path) {
    return FALSE;
  }
  // A path relative to the Drupal root or a fully qualified URI is valid.
  if (is_file($path)) {
661 662
    return $path;
  }
663 664 665 666 667 668
  // Prepend 'public://' for relative file paths within public filesystem.
  if (file_uri_scheme($path) === FALSE) {
    $path = 'public://' . $path;
  }
  if (is_file($path)) {
    return $path;
669 670 671 672
  }
  return FALSE;
}

673 674 675
/**
 * Process system_theme_settings form submissions.
 */
676
function system_theme_settings_submit($form, &$form_state) {
677 678 679 680 681
  // Exclude unnecessary elements before saving.
  form_state_values_clean($form_state);
  $key = $form_state['values']['var'];
  unset($form_state['values']['var']);

682
  $values = $form_state['values'];
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709

  // 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']);
  }

710
  if (empty($values['default_favicon']) && !empty($values['favicon_path'])) {
711
    $values['favicon_mimetype'] = file_get_mimetype($values['favicon_path']);
712
  }
713

714 715
  variable_set($key, $values);
  drupal_set_message(t('The configuration options have been saved.'));
716 717 718 719

  cache_clear_all();
}

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

747 748 749
/**
 * Menu callback; provides module enable/disable interface.
 *
750 751 752
 * 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.
753
 *
754 755 756
 * 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.
757
 *
758 759
 * @param $form_state
 *   An associative array containing the current state of the form.
760 761 762 763
 *
 * @return
 *   The form array.
 *
764
 * @ingroup forms
765 766
 * @see theme_system_modules()
 * @see system_modules_submit()
767
 */
768
function system_modules($form, $form_state = array()) {
769
  // Get current list of modules.
770
  $files = system_rebuild_module_data();
771

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

780
  uasort($visible_files, 'system_sort_modules_by_info_name');
781

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

790 791
  $modules = array();
  $form['modules'] = array('#tree' => TRUE);
792

793 794 795
  // Used when checking if module implements a help page.
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;

796
  // Used when displaying modules that are required by the install profile.
797
  require_once DRUPAL_ROOT . '/core/includes/install.inc';
798 799
  $distribution_name = check_plain(drupal_install_profile_distribution_name());

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

809 810 811 812
    // 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)));
813 814
        $extra['disabled'] = TRUE;
      }
815 816
      // Only display visible modules.
      elseif (isset($visible_files[$requires])) {
817
        $requires_name = $files[$requires]->info['name'];
818
        // Disable this module if it is incompatible with the dependency's version.
819 820 821 822 823 824
        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;
825
        }
826 827 828 829 830 831 832 833
        // 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;
        }
834 835 836 837 838
        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));
839 840
        }
      }
841 842
    }
    // Generate link for module's help page, if there is one.
843
    if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
844
      if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
845 846 847 848 849 850
        $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'))),
        );
851 852
      }
    }
853 854 855 856 857
    // 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'),
858
        '#href' => 'admin/people/permissions',
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
        '#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'])),
        );
      }
    }

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

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

912 913 914
  // Lastly, sort all fieldsets by title.
  uasort($form['modules'], 'element_sort_by_title');

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

  return $form;
}

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

932 933 934 935 936 937 938 939 940 941 942 943 944
/**
 * 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']);
}

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

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

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

  // 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');
989
    $php_required = $info['php'];
990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
    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(