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

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

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

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

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
 * Menu callback; Provide the administration overview page.
 */
function system_admin_config_page() {
  // Check for status report errors.
  if (system_status(TRUE) && user_access('administer site configuration')) {
    drupal_set_message(t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'error');
  }
  $blocks = array();
  if ($admin = db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin/config' AND module = 'system'")->fetchAssoc()) {
    $result = db_query("
      SELECT m.*, ml.*
      FROM {menu_links} ml
      INNER JOIN {menu_router} m ON ml.router_path = m.path
      WHERE ml.link_path != 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
    foreach ($result as $item) {
      _menu_link_translate($item);
      if (!$item['access']) {
        continue;
      }
      // The link 'description' either derived from the hook_menu 'description'
      // or entered by the user via menu module is saved as the title attribute.
      if (!empty($item['localized_options']['attributes']['title'])) {
        $item['description'] = $item['localized_options']['attributes']['title'];
      }
      $block = $item;
      $block['content'] = '';
103
      $block['show'] = TRUE;
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
      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.
      $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block;
    }
  }
  if ($blocks) {
    ksort($blocks);
    return theme('admin_page', $blocks);
  }
  else {
    return t('You do not have any administrative items.');
  }
}
122
123
124
125

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

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

149
  $modules = system_get_module_data();
150
  $menu_items = array();
151
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;
152
153
154
155
156
157
158
159
160
161
162
163
164

  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.
165
      if ($help_arg && module_invoke($module, 'help', "admin/help#$module", $help_arg)) {
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
        $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.
197
 *
198
 * @ingroup forms
199
 * @see system_themes_form_submit()
200
201
 */
function system_themes_form() {
202

203
  $themes = system_get_theme_data();
204
205
  uasort($themes, 'system_sort_modules_by_info_name');

206
207
208
209
210
211
  $status = array();
  $incompatible_core = array();
  $incompatible_php = array();

  foreach ($themes as $theme) {
    $screenshot = NULL;
212
213
214
215
216
217
218
219
220
221
    // Create a list which includes the current theme and all its base themes.
    if (isset($themes[$theme->name]->base_themes)) {
      $theme_keys = array_keys($themes[$theme->name]->base_themes) + array($theme->name);
    }
    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'])) {
222
223
224
225
        $screenshot = $themes[$theme_key]->info['screenshot'];
        break;
      }
    }
226
    $screenshot = $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => array('screenshot')), FALSE) : t('no screenshot');
227

228
    $form[$theme->name]['screenshot'] = array('#markup' => $screenshot);
229
230
231
232
    $form[$theme->name]['info'] = array(
      '#type' => 'value',
      '#value' => $theme->info,
    );
233
    $options[$theme->name] = $theme->info['name'];
234

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

  $form['status'] = array(
    '#type' => 'checkboxes',
260
    '#options' => array_fill_keys(array_keys($options), ''),
261
262
263
264
265
266
    '#default_value' => $status,
    '#incompatible_themes_core' => drupal_map_assoc($incompatible_core),
    '#incompatible_themes_php' => $incompatible_php,
  );
  $form['theme_default'] = array(
    '#type' => 'radios',
267
    '#options' => array_fill_keys(array_keys($options), ''),
268
269
    '#default_value' => variable_get('theme_default', 'garland'),
  );
270
271
272
273
274
275
276
277
278
279
280
281

  // 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'),
282
    '#description' => t('Choose "Default theme" to always use the same theme as the rest of the site.'),
283
284
285
286
    '#default_value' => variable_get('admin_theme', 0),
  );
  $form['admin_theme']['node_admin_theme'] = array(
    '#type' => 'checkbox',
287
    '#title' => t('Use the administration theme when editing or creating content'),
288
289
290
    '#default_value' => variable_get('node_admin_theme', '0'),
  );

291
292
293
294
  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
295

296
297
298
  return $form;
}

299
300
301
/**
 * Process system_themes_form form submissions.
 */
302
function system_themes_form_submit($form, &$form_state) {
303
  drupal_clear_css_cache();
304
305
306
307
308
309
310
311

  // 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;
    }
  }
312
313
314
315
  db_update('system')
    ->fields(array('status' => 0))
    ->condition('type', 'theme')
    ->execute();
316
317
318
319
320
321
322

  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;
323
324
325
326
327
          db_update('system')
            ->fields(array('status' => 1))
            ->condition('type', 'theme')
            ->condition('name', $key)
            ->execute();
328
329
330
        }
      }
    }
331
    if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] != $form_state['values']['theme_default']) {
332
      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(
333
        '%admin_theme' => $form_state['values']['admin_theme'],
334
335
336
        '%selected_theme' => $form_state['values']['theme_default'],
      )));
    }
337
338

    // Save the variables.
339
    variable_set('theme_default', $form_state['values']['theme_default']);
340
341
    variable_set('admin_theme', $form_state['values']['admin_theme']);
    variable_set('node_admin_theme', $form_state['values']['node_admin_theme']);
342
343
344
345
  }
  else {
    // Revert to defaults: only Garland is enabled.
    variable_del('theme_default');
346
347
    variable_del('admin_theme');
    variable_del('node_admin_theme');
348
349
350
351
352
    db_update('system')
      ->fields(array('status' => 1))
      ->condition('type', 'theme')
      ->condition('name', 'garland')
      ->execute();
353
354
355
356
357
    $new_theme_list = array('garland');
  }

  list_themes(TRUE);
  menu_rebuild();
358
  drupal_theme_rebuild();
359
  drupal_set_message(t('The configuration options have been saved.'));
360
  $form_state['redirect'] = 'admin/appearance';
361
362
363
364
365
366
367
368
369
370
371

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

  // Default settings are defined in theme_get_settings() in includes/theme.inc
  if ($key) {
    $settings = theme_get_settings($key);
389
    $var = str_replace('/', '_', 'theme_' . $key . '_settings');
390
    $themes = system_get_theme_data();
391
392
393
394
395
396
397
398
399
400
401
402
    $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);
403
    $filename = ($key) ? str_replace('/', '_', $key) . '_logo.' . $parts['extension'] : 'logo.' . $parts['extension'];
404
405
406
407

    // 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.
408
    if ($filepath = file_unmanaged_copy($file->uri, $filename, FILE_EXISTS_REPLACE)) {
409
      $_POST['default_logo'] = 0;
410
      $_POST['logo_path'] = $filepath;
411
412
413
414
415
416
417
      $_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);
418
    $filename = ($key) ? str_replace('/', '_', $key) . '_favicon.' . $parts['extension'] : 'favicon.' . $parts['extension'];
419
420
421
422

    // 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.
423
    if ($filepath = file_unmanaged_copy($file->uri, $filename, FILE_EXISTS_REPLACE)) {
424
      $_POST['default_favicon'] = 0;
425
      $_POST['favicon_path'] = $filepath;
426
427
428
429
430
431
      $_POST['toggle_favicon'] = 1;
    }
  }

  // Toggle settings
  $toggles = array(
432
433
434
435
436
437
438
439
440
441
    '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'),
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
  );

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

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

  // 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.'),
481
      '#attributes' => array('class' => array('theme-settings-bottom')),
482
    );
483
    $form['logo']['default_logo'] = array(
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
      '#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'),
508
      '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
509
510
511
512
513
514
515
516
517
518
    );
    $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'),
519
      '#default_value' => $settings['favicon_path'],
520
521
522
523
524
525
526
527
528
529
530
      '#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) {
531
    // Include the theme's theme-settings.php file
532
    $filename = DRUPAL_ROOT . '/' . str_replace("/$key.info", '', $themes[$key]->filename) . '/theme-settings.php';
533
534
535
    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'];
536
      $filename = DRUPAL_ROOT . '/' . str_replace("/$base.info", '', $themes[$base]->filename) . '/theme-settings.php';
537
538
539
540
541
542
    }
    if (file_exists($filename)) {
      require_once $filename;
    }

    // Call engine-specific settings.
543
    $function = $themes[$key]->prefix . '_engine_settings';
544
    if (function_exists($function)) {
545
      $group = $function($settings, $form);
546
547
548
      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);
549
      }
550
551
    }
    // Call theme-specific settings.
552
    $function = $key . '_settings';
553
    if (!function_exists($function)) {
554
      $function = $themes[$key]->prefix . '_settings';
555
556
    }
    if (function_exists($function)) {
557
      $group = $function($settings, $form);
558
559
560
      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);
561
562
563
564
      }
    }
  }

565
  $form = system_settings_form($form, FALSE);
566
567
568
569
570
  // We don't want to call system_settings_form_submit(), so change #submit.
  $form['#submit'] = array('system_theme_settings_submit');
  return $form;
}

571
572
573
/**
 * Process system_theme_settings form submissions.
 */
574
function system_theme_settings_submit($form, &$form_state) {
575
  $values = $form_state['values'];
576
  if (empty($values['default_favicon']) && !empty($values['favicon_path'])) {
577
    $values['favicon_mimetype'] = file_get_mimetype($values['favicon_path']);
578
  }
579
  $key = $values['var'];
580

581
582
583
584
  // 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.'));
585
586
587
588

  cache_clear_all();
}

589
/**
590
591
592
 * Recursively check compatibility.
 *
 * @param $incompatible
593
594
 *   An associative array which at the end of the check contains all
 *   incompatible files as the keys, their values being TRUE.
595
596
597
598
599
 * @param $files
 *   The set of files that will be tested.
 * @param $file
 *   The file at which the check starts.
 * @return
600
601
 *   Returns TRUE if an incompatible file is found, NULL (no return value)
 *   otherwise.
602
603
604
605
606
 */
function _system_is_incompatible(&$incompatible, $files, $file) {
  if (isset($incompatible[$file->name])) {
    return TRUE;
  }
607
608
609
  // Recursively traverse required modules, looking for incompatible modules.
  foreach ($file->requires as $requires) {
    if (isset($files[$requires]) && _system_is_incompatible($incompatible, $files, $files[$requires])) {
610
611
612
613
614
615
      $incompatible[$file->name] = TRUE;
      return TRUE;
    }
  }
}

616
617
618
619
/**
 * Menu callback; provides module enable/disable interface.
 *
 * The list of modules gets populated by module.info files, which contain each module's name,
620
 * description and information about which modules it requires.
621
622
 * @see drupal_parse_info_file for information on module.info descriptors.
 *
623
624
625
 * 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.
626
 *
627
628
 * @param $form_state
 *   An associative array containing the current state of the form.
629
 * @ingroup forms
630
631
 * @see theme_system_modules()
 * @see system_modules_submit()
632
633
634
635
636
 * @return
 *   The form array.
 */
function system_modules($form_state = array()) {
  // Get current list of modules.
637
  $files = system_get_module_data();
638

639
640
  // Remove hidden modules from display list.
  foreach ($files as $filename => $file) {
641
    if (!empty($file->info['hidden']) || !empty($file->info['required'])) {
642
643
644
645
      unset($files[$filename]);
    }
  }

646
647
  uasort($files, 'system_sort_modules_by_info_name');

648
649
650
651
  // 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.
652
653
654
  if (!empty($form_state['storage'])) {
    return system_modules_confirm_form($files, $form_state['storage']);
  }
655

656
657
  $modules = array();
  $form['modules'] = array('#tree' => TRUE);
658

659
660
661
  // Used when checking if module implements a help page.
  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;

662
663
664
665
  // Iterate through each of the modules.
  foreach ($files as $filename => $module) {
    $extra = array();
    $extra['enabled'] = (bool) $module->status;
666
667
668
669
    // 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)));
670
671
672
673
        $extra['disabled'] = TRUE;
      }
      else {
        $requires_name = $files[$requires]->info['name'];
674
675
676
677
678
679
        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;
680
        }
681
682
683
684
685
        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));
686
687
        }
      }
688
689
    }
    // Generate link for module's help page, if there is one.
690
    if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
691
692
693
      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"));
694
695
      }
    }
696
    // Mark dependents disabled so the user cannot remove required modules.
697
    $dependents = array();
698
699
700
    // 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) {
701
      // Hidden modules are unset already.
702
703
704
      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']));
705
706
707
          $extra['disabled'] = TRUE;
        }
        else {
708
          $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
709
        }
710
711
      }
    }
712
    $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
713
  }
714
715
716
717
718
719
720
721
  // 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(
722
        array('data' => t('Enabled'), 'class' => array('checkbox')),
723
724
725
726
727
        t('Name'),
        t('Version'),
        t('Description'),
      ),
    );
728
729
  }

730
  $form['submit'] = array(
731
732
733
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
734
  $form['#action'] = url('admin/config/modules/list/confirm');
735
736
737
738

  return $form;
}

739
/**
740
 * Array sorting callback; sorts modules or themes by their name.
741
 */
742
743
744
745
function system_sort_modules_by_info_name($a, $b) {
  return strcasecmp($a->info['name'], $b->info['name']);
}

746
/**
747
 * Build a table row for the system modules page.
748
 */
749
750
751
function _system_modules_build_row($info, $extra) {
  // Add in the defaults.
  $extra += array(
752
753
    'requires' => array(),
    'required_by' => array(),
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
    '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'],
  );
771
772
  $form['#requires'] = $extra['requires'];
  $form['#required_by'] = $extra['required_by'];
773
774
775
776
777
778
779

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

  // Check the core compatibility.
780
  if (!isset($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY || empty($info['files'])) {
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
    $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),
    );
812
    $form['description']['#markup'] .= theme('system_modules_incompatible', $status_long);
813
814
815
816
817
818
819
  }

  // Show a "more help" link for modules that have them.
  if ($extra['help']) {
    $form['help'] = array(
      '#markup' => $extra['help'],
    );
820
821
822
823
824
  }
  return $form;
}

/**
825
 * Display confirmation form for required modules.
826
827
 *
 * @param $modules
828
 *   Array of module file objects as returned from system_get_module_data().
829
830
 * @param $storage
 *   The contents of $form_state['storage']; an array with two
831
 *   elements: the list of required modules and the list of status
832
833
834
835
836
837
838
839
840
 *   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;
841

842
  foreach ($storage['more_modules'] as $info) {
843
    $t_argument = array(
844
      '@module' => $info['name'],
845
      '@required' => implode(', ', $info['requires']),
846
    );
847
    $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);
848
  }
849
  $form['text'] = array('#markup' => theme('item_list', $items));
850
851
852
853
854
855

  if ($form) {
    // Set some default form values
    $form = confirm_form(
      $form,
      t('Some required modules must be enabled'),
856
      'admin/config/modules',
857
858
859
860
861
862
863
864
865
866
867
      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) {
868
  include_once DRUPAL_ROOT . '/includes/install.inc';
869
870
  $modules = array();
  // If we're not coming from the confirmation form, build the list of modules.
871
  if (!isset($form_state['storage'])) {
872
873
874
    foreach ($form_state['values']['modules'] as $group_name => $group) {
      foreach ($group as $module => $enabled) {
        $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']);
875
876
877
878
      }
    }
  }
  else {
879
880
881
    // If we are coming from the confirmation form, fetch
    // the modules out of $form_state.
    $modules = $form_state['storage']['modules'];
882
883
  }

884
885
  // Get a list of all modules, it will be used to find which module requires
  // which.
886
  $files = system_get_module_data();
887
888

  // The modules to be enabled.
889
  $modules_to_be_enabled = array();
890
891
892
893
  // The modules to be disabled.
  $disable_modules = array();
  // The modules to be installed.
  $new_modules = array();
894
895
896
897
898
  // 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.
899
900
901
902
903
904
905
  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;
      }
906
907
      elseif (!module_exists($name)) {
        $modules_to_be_enabled[$name] = $name;
908
      }
909
910
911
      // 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.
912
      if (empty($form_state['storage'])) {
913
914
915
916
        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'];
917
            }
918
            $more_modules[$name]['requires'][$requires] = $files[$requires]->info['name'];
919
          }
920
          $modules[$requires] = array('group' => $files[$requires]->info['package'], 'enabled' => TRUE);
921
922
923
        }
      }
    }
924
925
926
927
928
  }
  // 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']) {
929
930
931
      $disable_modules[$name] = $name;
    }
  }
932
933
934
935
  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.
936
    if (!isset($form_state['values']['confirm'])) {
937
      $form_state['storage'] = array('more_modules' => $more_modules, 'modules' => $modules);
938
939
      return;
    }
940
    // Otherwise, install or enable the modules.
941
    else {
942
943
      foreach ($form_state['storage']['more_modules'] as $info) {
        foreach ($info['requires'] as $requires => $name) {
944
945
946
947
          if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
            $new_modules[$name] = $name;
          }
          else {
948
            $modules_to_be_enabled[$name] = $name;
949
950
951
          }
        }
      }
952
953
    }
  }
954
955
956
  // 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.
957
958
959
960
  unset($form_state['storage']);

  $old_module_list = module_list();

961
  // Enable the modules needing enabling.
962
  if (!empty($modules_to_be_enabled)) {
963
964
965
966
967
    $sort = array();
    foreach ($modules_to_be_enabled as $module) {
      $sort[$module] = $files[$module]->sort;
    }
    array_multisort($sort, $modules_to_be_enabled);
968
    module_enable($modules_to_be_enabled);
969
  }
970
  // Disable the modules that need disabling.
971
  if (!empty($disable_modules)) {
972
973
974
975
976
    $sort = array();
    foreach ($disable_modules as $module) {
      $sort[$module] = $files[$module]->sort;
    }
    array_multisort($sort, $disable_modules);
977
978
979
980
    module_disable($disable_modules);
  }

  // Install new modules.
981
  if (!empty($new_modules)) {
982
    $sort = array();
983
984
985
986
    foreach ($new_modules as $key => $module) {
      if (!drupal_check_module($module)) {
        unset($new_modules[$key]);
      }
987
      $sort[$module] = $files[$module]->sort;
988
    }
989
    array_multisort($sort, $new_modules);
990
    drupal_install_modules($new_modules);
991
992
  }

993
  $current_module_list = module_list(TRUE);
994
995
996
997
  if ($old_module_list != $current_module_list) {
    drupal_set_message(t('The configuration options have been saved.'));
  }

998
999
1000
  // Clear all caches.
  registry_rebuild();
  drupal_theme_rebuild();