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

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

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

  // Check for status report errors.
21
  if (system_status(TRUE) && user_access('administer site configuration')) {
22
    drupal_set_message(t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'error');
23
  }
24
  $blocks = array();
25
  if ($admin = db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin' AND module = 'system'")->fetchAssoc()) {
26
27
28
29
    $result = db_query("
      SELECT m.*, ml.*
      FROM {menu_links} ml
      INNER JOIN {menu_router} m ON ml.router_path = m.path
30
31
      WHERE ml.link_path != 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
    foreach ($result as $item) {
32
33
34
35
36
37
      _menu_link_translate($item);
      if (!$item['access']) {
        continue;
      }
      // The link 'description' either derived from the hook_menu 'description'
      // or entered by the user via menu module is saved as the title attribute.
38
39
      if (!empty($item['localized_options']['attributes']['title'])) {
        $item['description'] = $item['localized_options']['attributes']['title'];
40
41
42
43
44
45
46
47
48
49
      }
      $block = $item;
      $block['content'] = '';
      if ($item['block_callback'] && function_exists($item['block_callback'])) {
        $function = $item['block_callback'];
        $block['content'] .= $function();
      }
      $block['content'] .= theme('admin_block_content', system_admin_menu_block($item));
      // Prepare for sorting as in function _menu_tree_check_access().
      // The weight is offset so it is always positive, with a uniform 5-digits.
50
      $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block;
51
52
    }
  }
53
54
55
56
57
58
59
  if ($blocks) {
    ksort($blocks);
    return theme('admin_page', $blocks);
  }
  else {
    return t('You do not have any administrative items.');
  }
60
61
62
63
64
65
}


/**
 * Provide a single block from the administration menu as a page.
 * This function is often a destination for these blocks.
66
 * For example, 'admin/build/types' needs to have a destination to be valid
67
68
 * in the Drupal menu system, but too much information there might be
 * hidden, so we supply the contents of the block.
69
70
71
 *
 * @return
 *   The output HTML.
72
73
74
 */
function system_admin_menu_block_page() {
  $item = menu_get_item();
75
76
77
78
79
80
  if ($content = system_admin_menu_block($item)) {
    $output = theme('admin_block_content', $content);
  }
  else {
    $output = t('You do not have any administrative items.');
  }
81
82
  return $output;
}
83
84
85
86
87
88

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

89
  $modules = system_get_module_data();
90
  $menu_items = array();
91
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;
92
93
94
95
96
97
98
99
100
101
102
103
104

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

    $admin_tasks = system_get_module_admin_tasks($module);

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

      // Check for help links.
105
      if ($help_arg && module_invoke($module, 'help', "admin/help#$module", $help_arg)) {
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
        $admin_tasks[100] = l(t('Get help'), "admin/help/$module");
      }

      // Sort.
      ksort($admin_tasks);

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

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

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

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

  return $output;
}

/**
 * Menu callback; displays a listing of all themes.
137
 *
138
 * @ingroup forms
139
 * @see system_themes_form_submit()
140
141
 */
function system_themes_form() {
142

143
  $themes = system_get_theme_data();
144
145
  uasort($themes, 'system_sort_modules_by_info_name');

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
  $status = array();
  $incompatible_core = array();
  $incompatible_php = array();

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

162
    $form[$theme->name]['screenshot'] = array('#markup' => $screenshot);
163
164
165
166
    $form[$theme->name]['info'] = array(
      '#type' => 'value',
      '#value' => $theme->info,
    );
167
    $options[$theme->name] = $theme->info['name'];
168

169
    if (!empty($theme->status) || $theme->name == variable_get('admin_theme', 0)) {
170
      $form[$theme->name]['operations'] = array('#markup' => l(t('configure'), 'admin/build/themes/settings/' . $theme->name) );
171
172
173
174
    }
    else {
      // Dummy element for drupal_render. Cleaner than adding a check in the theme function.
      $form[$theme->name]['operations'] = array();
175
176
177
178
179
    }
    if (!empty($theme->status)) {
      $status[] = $theme->name;
    }
    else {
180
      // Ensure this theme is compatible with this version of core.
181
      // Require the 'content' region to make sure the main page
182
183
      // content has a common place in all themes.
      if (!isset($theme->info['core']) || ($theme->info['core'] != DRUPAL_CORE_COMPATIBILITY) || (!isset($theme->info['regions']['content']))) {
184
185
186
187
188
189
190
191
192
193
        $incompatible_core[] = $theme->name;
      }
      if (version_compare(phpversion(), $theme->info['php']) < 0) {
        $incompatible_php[$theme->name] = $theme->info['php'];
      }
    }
  }

  $form['status'] = array(
    '#type' => 'checkboxes',
194
    '#options' => array_fill_keys(array_keys($options), ''),
195
196
197
198
199
200
    '#default_value' => $status,
    '#incompatible_themes_core' => drupal_map_assoc($incompatible_core),
    '#incompatible_themes_php' => $incompatible_php,
  );
  $form['theme_default'] = array(
    '#type' => 'radios',
201
    '#options' => array_fill_keys(array_keys($options), ''),
202
203
    '#default_value' => variable_get('theme_default', 'garland'),
  );
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

  // Administration theme settings.
  $form['admin_theme'] = array(
    '#type' => 'fieldset',
    '#title' => t('Administration theme'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['admin_theme']['admin_theme'] = array(
    '#type' => 'select',
    '#options' => array(0 => t('Default theme')) + $options,
    '#title' => t('Administration theme'),
    '#description' => t('Choose which theme the administration pages should display in. If you choose "Default theme" the administration pages will use the same theme as the rest of the site.'),
    '#default_value' => variable_get('admin_theme', 0),
  );
  $form['admin_theme']['node_admin_theme'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use administration theme for content editing'),
    '#description' => t('Use the administration theme when editing existing posts or creating new ones.'),
    '#default_value' => variable_get('node_admin_theme', '0'),
  );

226
227
228
229
230
231
232
233
  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  $form['buttons']['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Reset to defaults'),
  );
234

235
236
237
  return $form;
}

238
239
240
/**
 * Process system_themes_form form submissions.
 */
241
function system_themes_form_submit($form, &$form_state) {
242
  drupal_clear_css_cache();
243
244
245
246
247
248
249
250

  // Store list of previously enabled themes and disable all themes
  $old_theme_list = $new_theme_list = array();
  foreach (list_themes() as $theme) {
    if ($theme->status) {
      $old_theme_list[] = $theme->name;
    }
  }
251
252
253
254
  db_update('system')
    ->fields(array('status' => 0))
    ->condition('type', 'theme')
    ->execute();
255
256
257
258
259
260
261

  if ($form_state['values']['op'] == t('Save configuration')) {
    if (is_array($form_state['values']['status'])) {
      foreach ($form_state['values']['status'] as $key => $choice) {
        // Always enable the default theme, despite its status checkbox being checked:
        if ($choice || $form_state['values']['theme_default'] == $key) {
          $new_theme_list[] = $key;
262
263
264
265
266
          db_update('system')
            ->fields(array('status' => 1))
            ->condition('type', 'theme')
            ->condition('name', $key)
            ->execute();
267
268
269
        }
      }
    }
270
    if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] != $form_state['values']['theme_default']) {
271
272
      drupal_set_message(t('Please note that the <a href="!admin_theme_page">administration theme</a> is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', array(
        '!admin_theme_page' => url('admin/settings/admin'),
273
        '%admin_theme' => $form_state['values']['admin_theme'],
274
275
276
        '%selected_theme' => $form_state['values']['theme_default'],
      )));
    }
277
278

    // Save the variables.
279
    variable_set('theme_default', $form_state['values']['theme_default']);
280
281
    variable_set('admin_theme', $form_state['values']['admin_theme']);
    variable_set('node_admin_theme', $form_state['values']['node_admin_theme']);
282
283
284
285
  }
  else {
    // Revert to defaults: only Garland is enabled.
    variable_del('theme_default');
286
287
    variable_del('admin_theme');
    variable_del('node_admin_theme');
288
289
290
291
292
    db_update('system')
      ->fields(array('status' => 1))
      ->condition('type', 'theme')
      ->condition('name', 'garland')
      ->execute();
293
294
295
296
297
    $new_theme_list = array('garland');
  }

  list_themes(TRUE);
  menu_rebuild();
298
  drupal_theme_rebuild();
299
300
301
302
303
304
305
306
307
308
309
310
311
  drupal_set_message(t('The configuration options have been saved.'));
  $form_state['redirect'] = 'admin/build/themes';

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

  return;
}

/**
 * Form builder; display theme configuration for entire site and individual themes.
312
 *
313
314
315
316
 * @param $key
 *   A theme name.
 * @return
 *   The form structure.
317
 * @ingroup forms
318
 * @see system_theme_settings_submit()
319
320
321
322
323
324
325
326
 */
function system_theme_settings(&$form_state, $key = '') {
  $directory_path = file_directory_path();
  file_check_directory($directory_path, FILE_CREATE_DIRECTORY, 'file_directory_path');

  // Default settings are defined in theme_get_settings() in includes/theme.inc
  if ($key) {
    $settings = theme_get_settings($key);
327
    $var = str_replace('/', '_', 'theme_' . $key . '_settings');
328
    $themes = system_get_theme_data();
329
330
331
332
333
334
335
336
337
338
339
340
    $features = $themes[$key]->info['features'];
  }
  else {
    $settings = theme_get_settings('');
    $var = 'theme_settings';
  }

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

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

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

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

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

  // Toggle settings
  $toggles = array(
370
371
372
373
374
375
376
377
378
379
    'logo'                      => t('Logo'),
    'name'                      => t('Site name'),
    'slogan'                    => t('Site slogan'),
    'node_user_picture'         => t('User pictures in posts'),
    'comment_user_picture'      => t('User pictures in comments'),
    'comment_user_verification' => t('User verification status in comments'),
    'search'                    => t('Search box'),
    'favicon'                   => t('Shortcut icon'),
    'main_menu'                 => t('Main menu'),
    'secondary_menu'            => t('Secondary menu'),
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
  );

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

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

407
  if (!element_children($form['theme_settings'])) {
408
409
410
411
    // 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;
  }
412
413
414
415
416
417
418
419
420

  // Logo settings
  if ((!$key) || in_array('logo', $features)) {
    $form['logo'] = array(
      '#type' => 'fieldset',
      '#title' => t('Logo image settings'),
      '#description' => t('If toggled on, the following logo will be displayed.'),
      '#attributes' => array('class' => 'theme-settings-bottom'),
    );
421
    $form['logo']['default_logo'] = array(
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
      '#type' => 'checkbox',
      '#title' => t('Use the default logo'),
      '#default_value' => $settings['default_logo'],
      '#tree' => FALSE,
      '#description' => t('Check here if you want the theme to use the logo supplied with it.')
    );
    $form['logo']['logo_path'] = array(
      '#type' => 'textfield',
      '#title' => t('Path to custom logo'),
      '#default_value' => $settings['logo_path'],
      '#description' => t('The path to the file you would like to use as your logo file instead of the default logo.'));

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

  if ((!$key) || in_array('favicon', $features)) {
    $form['favicon'] = array(
      '#type' => 'fieldset',
      '#title' => t('Shortcut icon settings'),
446
      '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
447
448
449
450
451
452
453
454
455
456
    );
    $form['favicon']['default_favicon'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use the default shortcut icon.'),
      '#default_value' => $settings['default_favicon'],
      '#description' => t('Check here if you want the theme to use the default shortcut icon.')
    );
    $form['favicon']['favicon_path'] = array(
      '#type' => 'textfield',
      '#title' => t('Path to custom icon'),
457
      '#default_value' => $settings['favicon_path'],
458
459
460
461
462
463
464
465
466
467
468
      '#description' => t('The path to the image file you would like to use as your custom shortcut icon.')
    );

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

  if ($key) {
469
    // Include the theme's theme-settings.php file
470
    $filename = DRUPAL_ROOT . '/' . str_replace("/$key.info", '', $themes[$key]->filename) . '/theme-settings.php';
471
472
473
    if (!file_exists($filename) and !empty($themes[$key]->info['base theme'])) {
      // If the theme doesn't have a theme-settings.php file, use the base theme's.
      $base = $themes[$key]->info['base theme'];
474
      $filename = DRUPAL_ROOT . '/' . str_replace("/$base.info", '', $themes[$base]->filename) . '/theme-settings.php';
475
476
477
478
479
480
    }
    if (file_exists($filename)) {
      require_once $filename;
    }

    // Call engine-specific settings.
481
    $function = $themes[$key]->prefix . '_engine_settings';
482
    if (function_exists($function)) {
483
      $group = $function($settings, $form);
484
485
486
      if (!empty($group)) {
        $form['engine_specific'] = array('#type' => 'fieldset', '#title' => t('Theme-engine-specific settings'), '#description' => t('These settings only exist for all the templates and styles based on the %engine theme engine.', array('%engine' => $themes[$key]->prefix)));
        $form['engine_specific'] = array_merge($form['engine_specific'], $group);
487
      }
488
489
    }
    // Call theme-specific settings.
490
    $function = $key . '_settings';
491
    if (!function_exists($function)) {
492
      $function = $themes[$key]->prefix . '_settings';
493
494
    }
    if (function_exists($function)) {
495
      $group = $function($settings, $form);
496
497
498
      if (!empty($group)) {
        $form['theme_specific'] = array('#type' => 'fieldset', '#title' => t('Theme-specific settings'), '#description' => t('These settings only exist for the %theme theme and all the styles based on it.', array('%theme' => $themes[$key]->info['name'])));
        $form['theme_specific'] = array_merge($form['theme_specific'], $group);
499
500
501
502
      }
    }
  }

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

509
510
511
/**
 * Process system_theme_settings form submissions.
 */
512
function system_theme_settings_submit($form, &$form_state) {
513
514
  $values = $form_state['values'];
  $key = $values['var'];
515

516
  if ($values['op'] == t('Reset to defaults')) {
517
518
519
520
    variable_del($key);
    drupal_set_message(t('The configuration options have been reset to their default values.'));
  }
  else {
521
522
523
    // 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);
524
525
526
527
528
529
    drupal_set_message(t('The configuration options have been saved.'));
  }

  cache_clear_all();
}

530
/**
531
532
533
 * Recursively check compatibility.
 *
 * @param $incompatible
534
535
 *   An associative array which at the end of the check contains all
 *   incompatible files as the keys, their values being TRUE.
536
537
538
539
540
 * @param $files
 *   The set of files that will be tested.
 * @param $file
 *   The file at which the check starts.
 * @return
541
542
 *   Returns TRUE if an incompatible file is found, NULL (no return value)
 *   otherwise.
543
544
545
546
547
 */
function _system_is_incompatible(&$incompatible, $files, $file) {
  if (isset($incompatible[$file->name])) {
    return TRUE;
  }
548
549
550
  // Recursively traverse required modules, looking for incompatible modules.
  foreach ($file->requires as $requires) {
    if (isset($files[$requires]) && _system_is_incompatible($incompatible, $files, $files[$requires])) {
551
552
553
554
555
556
      $incompatible[$file->name] = TRUE;
      return TRUE;
    }
  }
}

557
558
559
560
/**
 * Menu callback; provides module enable/disable interface.
 *
 * The list of modules gets populated by module.info files, which contain each module's name,
561
 * description and information about which modules it requires.
562
563
 * @see drupal_parse_info_file for information on module.info descriptors.
 *
564
565
566
 * 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.
567
 *
568
569
 * @param $form_state
 *   An associative array containing the current state of the form.
570
 * @ingroup forms
571
572
 * @see theme_system_modules()
 * @see system_modules_submit()
573
574
575
576
 * @return
 *   The form array.
 */
function system_modules($form_state = array()) {
577
  // Clear all caches.
578
  registry_rebuild();
579
  drupal_theme_rebuild();
580
581
  node_types_rebuild();
  cache_clear_all('schema', 'cache');
582
  // Get current list of modules.
583
  $files = system_get_module_data();
584

585
586
  // Remove hidden modules from display list.
  foreach ($files as $filename => $file) {
587
    if (!empty($file->info['hidden']) || !empty($file->info['required'])) {
588
589
590
591
      unset($files[$filename]);
    }
  }

592
593
  uasort($files, 'system_sort_modules_by_info_name');

594
595
596
597
  // If the modules form was submitted, then system_modules_submit() runs first
  // and if there are unfilled required modules, then form_state['storage'] is
  // filled, triggering a rebuild. In this case we need to display a
  // confirmation form.
598
599
600
  if (!empty($form_state['storage'])) {
    return system_modules_confirm_form($files, $form_state['storage']);
  }
601

602
603
  $modules = array();
  $form['modules'] = array('#tree' => TRUE);
604

605
606
607
  // Used when checking if module implements a help page.
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;

608
609
610
611
  // Iterate through each of the modules.
  foreach ($files as $filename => $module) {
    $extra = array();
    $extra['enabled'] = (bool) $module->status;
612
613
614
615
    // 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)));
616
617
          $extra['disabled'] = TRUE;
        }
618
619
      elseif (!$files[$requires]->status) {
        $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$requires]->info['name']));
620
621
        }
        else {
622
        $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$requires]->info['name']));
623
      }
624
625
    }
    // Generate link for module's help page, if there is one.
626
    if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
627
628
629
      if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
        // Module has a help page.
        $extra['help'] = theme('more_help_link', url("admin/help/$filename"));
630
631
      }
    }
632
    // Mark dependents disabled so the user cannot remove required modules.
633
    $dependents = array();
634
635
636
    // 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) {
637
      // Hidden modules are unset already.
638
639
640
      if (isset($files[$required_by])) {
        if ($files[$required_by]->status == 1) {
          $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
641
642
643
          $extra['disabled'] = TRUE;
        }
        else {
644
          $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
645
        }
646
647
      }
    }
648
    $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
649
  }
650
651
652
653
654
655
656
657
658
659
660
661
662
663
  // 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(
        array('data' => t('Enabled'), 'class' => 'checkbox'),
        t('Name'),
        t('Version'),
        t('Description'),
      ),
    );
664
665
  }

666
  $form['submit'] = array(
667
668
669
670
671
672
673
674
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  $form['#action'] = url('admin/build/modules/list/confirm');

  return $form;
}

675
/**
676
 * Array sorting callback; sorts modules or themes by their name.
677
 */
678
679
680
681
function system_sort_modules_by_info_name($a, $b) {
  return strcasecmp($a->info['name'], $b->info['name']);
}

682
/**
683
 * Build a table row for the system modules page.
684
 */
685
686
687
function _system_modules_build_row($info, $extra) {
  // Add in the defaults.
  $extra += array(
688
689
    'requires' => array(),
    'required_by' => array(),
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
    'disabled' => FALSE,
    'enabled' => FALSE,
    'help' => '',
  );
  $form = array(
    '#tree' => TRUE,
  );
  // Set the basic properties.
  $form['name'] = array(
    '#markup' => t($info['name']),
  );
  $form['description'] = array(
    '#markup' => t($info['description']),
  );
  $form['version'] = array(
    '#markup' => $info['version'],
  );
707
708
  $form['#requires'] = $extra['requires'];
  $form['#required_by'] = $extra['required_by'];
709
710
711
712
713
714
715

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

  // Check the core compatibility.
716
  if (!isset($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY || empty($info['files'])) {
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
    $compatible = FALSE;
    $status_short .= t('Incompatible with this version of Drupal core. ');
    $status_long .= t('This version is incompatible with the !core_version version of Drupal core. ', array('!core_version' => VERSION));
  }

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

  // If this module is compatible, present a checkbox indicating
  // this module may be installed. Otherwise, show a big red X.
  if ($compatible) {
    $form['enable'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable'),
      '#default_value' => $extra['enabled'],
    );
    if ($extra['disabled']) {
      $form['enable']['#disabled'] = TRUE;
    }
  }
  else {
    $form['enable'] = array(
      '#markup' =>  theme('image', 'misc/watchdog-error.png', t('incompatible'), $status_short),
    );
748
    $form['description']['#markup'] .= theme('system_modules_incompatible', $status_long);
749
750
751
752
753
754
755
  }

  // Show a "more help" link for modules that have them.
  if ($extra['help']) {
    $form['help'] = array(
      '#markup' => $extra['help'],
    );
756
757
758
759
760
  }
  return $form;
}

/**
761
 * Display confirmation form for required modules.
762
763
 *
 * @param $modules
764
 *   Array of module file objects as returned from system_get_module_data().
765
766
 * @param $storage
 *   The contents of $form_state['storage']; an array with two
767
 *   elements: the list of required modules and the list of status
768
769
770
771
772
773
774
775
776
 *   form field values from the previous screen.
 * @ingroup forms
 */
function system_modules_confirm_form($modules, $storage) {
  $form = array();
  $items = array();

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

778
  foreach ($storage['more_modules'] as $info) {
779
    $t_argument = array(
780
      '@module' => $info['name'],
781
      '@required' => implode(', ', $info['requires']),
782
    );
783
    $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);
784
  }
785
  $form['text'] = array('#markup' => theme('item_list', $items));
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803

  if ($form) {
    // Set some default form values
    $form = confirm_form(
      $form,
      t('Some required modules must be enabled'),
      'admin/build/modules',
      t('Would you like to continue with enabling the above?'),
      t('Continue'),
      t('Cancel'));
    return $form;
  }
}

/**
 * Submit callback; handles modules form submission.
 */
function system_modules_submit($form, &$form_state) {
804
  include_once DRUPAL_ROOT . '/includes/install.inc';
805
806
  $modules = array();
  // If we're not coming from the confirmation form, build the list of modules.
807
  if (!isset($form_state['storage'])) {
808
809
810
    foreach ($form_state['values']['modules'] as $group_name => $group) {
      foreach ($group as $module => $enabled) {
        $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']);
811
812
813
814
      }
    }
  }
  else {
815
816
817
    // If we are coming from the confirmation form, fetch
    // the modules out of $form_state.
    $modules = $form_state['storage']['modules'];
818
819
  }

820
821
  // Get a list of all modules, it will be used to find which module requires
  // which.
822
  $files = system_get_module_data();
823
824

  // The modules to be enabled.
825
  $modules_to_be_enabled = array();
826
827
828
829
  // The modules to be disabled.
  $disable_modules = array();
  // The modules to be installed.
  $new_modules = array();
830
831
832
833
834
  // Modules that need to be switched on because other modules require them.
  $more_modules = array();
  // Go through each module, finding out if we should enable, install, or
  // disable it. Also, we find out if there are modules it requires that are
  // not enabled.
835
836
837
838
839
840
841
  foreach ($modules as $name => $module) {
    // If it's enabled, find out whether to just
    // enable it, or install it.
    if ($module['enabled']) {
      if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
        $new_modules[$name] = $name;
      }
842
843
      elseif (!module_exists($name)) {
        $modules_to_be_enabled[$name] = $name;
844
      }
845
846
847
      // If we're not coming from a confirmation form, search for modules the
      // new ones require and see whether there are any that additionally
      // need to be switched on.
848
      if (empty($form_state['storage'])) {
849
850
851
852
        foreach ($form['modules'][$module['group']][$name]['#requires'] as $requires => $v) {
          if (!$modules[$requires]['enabled']) {
            if (!isset($more_modules[$name])) {
              $more_modules[$name]['name'] = $files[$name]->info['name'];
853
            }
854
            $more_modules[$name]['requires'][$requires] = $files[$requires]->info['name'];
855
          }
856
          $modules[$requires] = array('group' => $files[$requires]->info['package'], 'enabled' => TRUE);
857
858
859
        }
      }
    }
860
861
862
863
864
  }
  // A second loop is necessary, otherwise the modules set to be enabled in the
  // previous loop would not be found.
  foreach ($modules as $name => $module) {
    if (module_exists($name) && !$module['enabled']) {
865
866
867
      $disable_modules[$name] = $name;
    }
  }
868
869
870
871
  if ($more_modules) {
    // If we need to switch on more modules because other modules require
    // them and they haven't confirmed, don't process the submission yet. Store
    // the form submission data needed later.
872
    if (!isset($form_state['values']['confirm'])) {
873
      $form_state['storage'] = array('more_modules' => $more_modules, 'modules' => $modules);
874
875
      return;
    }
876
    // Otherwise, install or enable the modules.
877
    else {
878
879
      foreach ($form_state['storage']['more_modules'] as $info) {
        foreach ($info['requires'] as $requires => $name) {
880
881
882
883
          if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
            $new_modules[$name] = $name;
          }
          else {
884
            $modules_to_be_enabled[$name] = $name;
885
886
887
          }
        }
      }
888
889
    }
  }
890
891
892
  // Now we have installed every module as required (either by the user or
  // because other modules require them) so we don't need the temporary
  // storage anymore.
893
894
895
896
  unset($form_state['storage']);

  $old_module_list = module_list();

897
  // Enable the modules needing enabling.
898
  if (!empty($modules_to_be_enabled)) {
899
900
901
902
903
    $sort = array();
    foreach ($modules_to_be_enabled as $module) {
      $sort[$module] = $files[$module]->sort;
    }
    array_multisort($sort, $modules_to_be_enabled);
904
    module_enable($modules_to_be_enabled);
905
  }
906
  // Disable the modules that need disabling.
907
  if (!empty($disable_modules)) {
908
909
910
911
912
    $sort = array();
    foreach ($disable_modules as $module) {
      $sort[$module] = $files[$module]->sort;
    }
    array_multisort($sort, $disable_modules);
913
914
915
916
    module_disable($disable_modules);
  }

  // Install new modules.
917
  if (!empty($new_modules)) {
918
    $sort = array();
919
920
921
922
    foreach ($new_modules as $key => $module) {
      if (!drupal_check_module($module)) {
        unset($new_modules[$key]);
      }
923
      $sort[$module] = $files[$module]->sort;
924
    }
925
    array_multisort($sort, $new_modules);
926
    drupal_install_modules($new_modules);
927
928
  }

929
  $current_module_list = module_list(TRUE);
930
931
932
933
934
935
936
937
938
939
940
941
942
943
  if ($old_module_list != $current_module_list) {
    drupal_set_message(t('The configuration options have been saved.'));
  }

  drupal_clear_css_cache();
  drupal_clear_js_cache();

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

  // Notify locale module about module changes, so translations can be
  // imported. This might start a batch, and only return to the redirect
  // path after that.
  module_invoke('locale', 'system_update', $new_modules);

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

947
948
949
950
951
952
953
954
955
956
957
  return;
}

/**
 * Uninstall functions
 */

/**
 * Builds a form of currently disabled modules.
 *
 * @ingroup forms
958
959
 * @see system_modules_uninstall_validate()
 * @see system_modules_uninstall_submit()
960
961
 * @param $form_state['values']
 *   Submitted form values.
962
963
964
965
966
 * @return
 *   A form array representing the currently disabled modules.
 */
function system_modules_uninstall($form_state = NULL) {
  // Make sure the install API is available.
967
  include_once DRUPAL_ROOT . '/includes/install.inc';
968
969
970
971
972
973
974
975
976

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

  $form = array();

  // Pull all disabled modules from the system table.
977
978
  $disabled_modules = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' AND status = 0 AND schema_version > :schema ORDER BY name", array(':schema' => SCHEMA_UNINSTALLED));
  foreach ($disabled_modules as $module) {
979
980
981
982
983
984
985
986

    // Grab the module info
    $info = unserialize($module->info);

    // Load the .install file, and check for an uninstall hook.
    // If the hook exists, the module can be uninstalled.
    module_load_install($module->name);
    if (module_hook($module->name, 'uninstall')) {
987
988
      $form['modules'][$module->name]['name'] = array('#markup' => $info['name'] ? $info['name'] : $module->name);
      $form['modules'][$module->name]['description'] = array('#markup' => t($info['description']));
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
      $options[$module->name] = '';
    }
  }

  // Only build the rest of the form if there are any modules available to uninstall.
  if (!empty($options)) {
    $form['uninstall'] = array(
      '#type' => 'checkboxes',
      '#options' => $options,
    );
    $form['buttons']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Uninstall'),
    );
    $form['#action'] = url('admin/build/modules/uninstall/confirm');
  }
  else {
    $form['modules'] = array();
  }

  return $form;
}

/**
 * Confirm uninstall of selected modules.
 *
 * @ingroup forms
1016
1017
 * @param $storage
 *   An associative array of modules selected to be uninstalled.
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
 * @return
 *   A form array representing modules to confirm.
 */
function system_modules_uninstall_confirm_form($storage) {
  // Nothing to build.
  if (!isset($storage)) {
    return;
  }

  // Construct the hidden form elements and list items.
  foreach (array_filter($storage['uninstall']) as $module => $value) {
1029
    $info = drupal_parse_info_file(dirname(drupal_get_filename('module', $module)) . '/' . $module . '.info');
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
    $uninstall[] = $info['name'];
    $form['uninstall'][$module] = array('#type' => 'hidden',
      '#value' => 1,
    );
  }

  // Display a confirm form if modules have been selected.
  if (isset($uninstall)) {
    $form['#confirmed'] = TRUE;
    $form['uninstall']['#tree'] = TRUE;
1040
    $form['modules'] = array('#markup' => '<p>' . t('The following modules will be completely uninstalled from your site, and <em>all data from these modules will be lost</em>!') . '</p>' . theme('item_list', $uninstall));
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
    $form = confirm_form(
      $form,
      t('Confirm uninstall'),
      'admin/build/modules/uninstall',
      t('Would you like to continue with uninstalling the above?'),
      t('Uninstall'),
      t('Cancel'));
    return $form;
  }
}

/**
 * Validates the submitted uninstall form.
 */
function system_modules_uninstall_validate($form, &$form_state) {
  // Form submitted, but no modules selected.
  if (!count(array_filter($form_state['values']['uninstall']))) {
    drupal_set_message(t('No modules selected.'), 'error');
    drupal_goto('admin/build/modules/uninstall');
  }
}

/**
 * Processes the submitted uninstall form.
 */
function system_modules_uninstall_submit($form, &$form_state) {
  // Make sure the install API is available.
1068
  include_once DRUPAL_ROOT . '/includes/install.inc';
1069
1070
1071

  if (!empty($form['#confirmed'])) {
    // Call the uninstall routine for each selected module.
1072
1073
    $modules = array_keys($form_state['values']['uninstall']);
    drupal_uninstall_modules($modules);
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
    drupal_set_message(t('The selected modules have been uninstalled.'));

    unset($form_state['storage']);
    $form_state['redirect'] = 'admin/build/modules/uninstall';
  }
  else {
    $form_state['storage'] = $form_state['values'];
  }
}

1084
1085
1086
1087
1088
1089
1090
1091
/**
 * Menu callback. Display blocked IP addresses.
 */
function system_ip_blocking() {
  $output = '';
  $rows = array();
  $header = array(t('IP address'), t('Operations'));
  $result = db_query('SELECT * FROM {blocked_ips}');
1092
  foreach ($result as $ip) {
1093
1094
1095
1096
1097
1098
    $rows[] = array(
      $ip->ip,
      l(t('delete'), "admin/settings/ip-blocking/delete/$ip->iid"),
    );
  }

1099
  $build['system_ip_blocking_form'] = drupal_get_form('system_ip_blocking_form');
1100

1101
  $build['system_ip_blocking_table'] = array('#markup' => theme('table', $header, $rows));
1102

1103
  return $build;
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
}

/**
 * Define the form for blocking IP addresses.
 *
 * @ingroup forms
 * @see system_ip_blocking_form_validate()
 * @see system_ip_blocking_form_submit()
 */
function system_ip_blocking_form($form_state) {
  $form['ip'] = array(
    '#title' => t('IP address'),
    '#type' => 'textfield',
    '#size' => 64,
    '#maxlength' => 32,
1119
    '#default_value' => arg(3),
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
    '#description' => t('Enter a valid IP address.'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  $form['#submit'][] = 'system_ip_blocking_form_submit';
  $form['#validate'][] = 'system_ip_blocking_form_validate';
  return $form;
}

function system_ip_blocking_form_validate($form, &$form_state) {
1132
  $ip = trim($form_state['values']['ip']);
1133
  if (db_query("SELECT * FROM {blocked_ips} WHERE ip = :ip", array(':ip' => $ip))->fetchField()) {
1134
1135
    form_set_error('ip', t('This IP address is already blocked.'));
  }
1136
  elseif ($ip == ip_address()) {
1137
1138
    form_set_error('ip', t('You may not block your own IP address.'));
  }
1139
  elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) == FALSE) {
1140
1141
1142
1143
1144
    form_set_error('ip', t('Please enter a valid IP address.'));
  }
}

function system_ip_blocking_form_submit($form, &$form_state) {
1145
  $ip = trim($form_state['values']['ip']);
1146
1147
1148
  db_insert('blocked_ips')
    ->fields(array('ip' => $ip))
    ->execute();
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
  drupal_set_message(t('The IP address %ip has been blocked.', array('%ip' => $ip)));
  $form_state['redirect'] = 'admin/settings/ip-blocking';
  return;
}

/**
 * IP deletion confirm page.
 *
 * @see system_ip_blocking_delete_submit()
 */
function system_ip_blocking_delete(&$form_state, $iid) {
  $form['blocked_ip'] = array(
    '#type' => 'value',
    '#value' => $iid,
  );
  return confirm_form($form, t('Are you sure you want to delete %ip?', array('%ip' => $iid['ip'])), 'admin/settings/ip-blocking', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}

/**
 * Process system_ip_blocking_delete form submissions.
 */
function system_ip_blocking_delete_submit($form, &$form_state) {
  $blocked_ip = $form_state['values']['blocked_ip'];
1172
1173
1174
  db_delete('blocked_ips')
    ->condition('iid', $blocked_ip['iid'])
    ->execute();
1175
1176
1177
1178
1179
  watchdog('user', 'Deleted %ip', array('%ip' => $blocked_ip['ip']));
  drupal_set_message(t('The IP address %ip was deleted.', array('%ip' => $blocked_ip['ip'])));
  $form_state['redirect'] = 'admin/settings/ip-blocking';
}

1180
1181
/**
 * Form builder; The general site information form.
1182
 *
1183
 * @ingroup forms
1184
 * @see system_settings_form()
1185
1186
1187
1188
1189
 */
function system_site_information_settings() {
  $form['site_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
1190
    '#default_value' => 'Drupal',
1191
1192
1193
1194
1195
1196
    '#description' => t('The name of this website.'),
    '#required' => TRUE
  );
  $form['site_mail'] = array(
    '#type' => 'textfield',
    '#title' => t('E-mail address'),
1197
    '#default_value' => ini_get('sendmail_from'),
1198
    '#description' => t("The <em>From</em> address in automated e-mails sent during registration and new password requests, and other notifications. (Use an address ending in your site's domain to help prevent this e-mail being flagged as spam.)"),
1199
1200
1201
1202
1203
    '#required' => TRUE,
  );
  $form['site_slogan'] = array(
    '#type' => 'textfield',
    '#title' => t('Slogan'),
1204
    '#default_value' => '',
1205
    '#description' => t("Your site's motto, tag line, or catchphrase (often displayed alongside the title of the site).")
1206
1207
1208
1209
  );
  $form['site_frontpage'] = array(
    '#type' => 'textfield',
    '#title' => t('Default front page'),
1210
    '#default_value' => 'node',
1211
1212
    '#size' => 40,
    '#description' => t('The home page displays content from this relative URL. If unsure, specify "node".'),
1213
1214
    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
    '#required' => TRUE,
1215
  );
1216
1217
1218
1219
1220
1221
  $form['default_nodes_main'] = array(
    '#type' => 'select', '#title' => t('Number of posts on front page'),
    '#default_value' => 10,
    '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
    '#description' => t('The maximum number of posts per page to display on overview pages like the front page above.')
  );
1222
1223
1224
1225
1226
  $form['site_403'] = array(
    '#type' => 'textfield',
    '#title' => t('Default 403 (access denied) page'),
    '#default_value' => '',
    '#size' => 40,
1227
    '#description' => t('This page is displayed when the requested document is denied to the current user. Leave blank to display a generic "access denied" page.'),
1228
1229
1230
1231
1232
1233
1234
    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
  );
  $form['site_404'] = array(
    '#type' => 'textfield',
    '#title' => t('Default 404 (not found) page'),
    '#default_value' => '',
    '#size' => 40,
1235
    '#description' => t('This page is displayed when no other content matches the requested document. Leave blank to display a generic "page not found" page.'),
1236
1237
    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
  );