/[drupal]/drupal/modules/system/system.admin.inc
ViewVC logotype

Contents of /drupal/modules/system/system.admin.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.219 - (show annotations) (download) (as text)
Tue Nov 3 05:27:18 2009 UTC (3 weeks, 3 days ago) by webchick
Branch: MAIN
Changes since 1.218: +3 -8 lines
File MIME type: text/x-php
#602522 by effulgentsia, sun, and moshe weitzman: Make links in renderable arrays and forms (e.g. 'Operations') alterable.
1 <?php
2 // $Id: system.admin.inc,v 1.218 2009/11/02 23:22:23 webchick Exp $
3
4 /**
5 * @file
6 * Admin page callbacks for the system module.
7 */
8
9 /**
10 * Menu callback; Provide the administration overview page.
11 */
12 function system_main_admin_page($arg = NULL) {
13 // If we received an argument, they probably meant some other page.
14 // Let's 404 them since the menu system cannot be told we do not
15 // accept arguments.
16 if (isset($arg) && substr($arg, 0, 3) != 'by-') {
17 return drupal_not_found();
18 }
19
20 // 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 $result = db_query("
27 SELECT m.*, ml.*
28 FROM {menu_links} ml
29 INNER JOIN {menu_router} m ON ml.router_path = m.path
30 WHERE ml.link_path != 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
31 foreach ($result as $item) {
32 _menu_link_translate($item);
33 if (!$item['access']) {
34 continue;
35 }
36 // The link 'description' either derived from the hook_menu 'description'
37 // or entered by the user via menu module is saved as the title attribute.
38 if (!empty($item['localized_options']['attributes']['title'])) {
39 $item['description'] = $item['localized_options']['attributes']['title'];
40 }
41 $block = $item;
42 $block['content'] = '';
43 $block['show'] = FALSE;
44 if ($item['block_callback'] && function_exists($item['block_callback'])) {
45 $function = $item['block_callback'];
46 $block['content'] .= $function();
47 }
48 $content = system_admin_menu_block($item);
49 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)) {
50 // Only show blocks for items which are not containers, or those which
51 // are containers and do have items we can show.
52 $block['show'] = TRUE;
53 if (empty($content)) {
54 // If no items found below, but access checks did not fail, show.
55 $block['title'] = l($item['title'], $item['href'], $item['localized_options']);
56 }
57 else {
58 // Theme items below.
59 $block['content'] .= theme('admin_block_content', array('content' => $content));
60 }
61 }
62 // Prepare for sorting as in function _menu_tree_check_access().
63 // 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 if ($blocks) {
68 ksort($blocks);
69 return theme('admin_page', array('blocks' => $blocks));
70 }
71 else {
72 return t('You do not have any administrative items.');
73 }
74 }
75
76 /**
77 * Menu callback; Provide the administration overview page.
78 */
79 function system_admin_config_page() {
80 // Check for status report errors.
81 if (system_status(TRUE) && user_access('administer site configuration')) {
82 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');
83 }
84 $blocks = array();
85 if ($admin = db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin/config' AND module = 'system'")->fetchAssoc()) {
86 $result = db_query("
87 SELECT m.*, ml.*
88 FROM {menu_links} ml
89 INNER JOIN {menu_router} m ON ml.router_path = m.path
90 WHERE ml.link_path != 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
91 foreach ($result as $item) {
92 _menu_link_translate($item);
93 if (!$item['access']) {
94 continue;
95 }
96 // The link 'description' either derived from the hook_menu 'description'
97 // or entered by the user via menu module is saved as the title attribute.
98 if (!empty($item['localized_options']['attributes']['title'])) {
99 $item['description'] = $item['localized_options']['attributes']['title'];
100 }
101 $block = $item;
102 $block['content'] = '';
103 $block['show'] = TRUE;
104 if ($item['block_callback'] && function_exists($item['block_callback'])) {
105 $function = $item['block_callback'];
106 $block['content'] .= $function();
107 }
108 $block['content'] .= theme('admin_block_content', array('content' => system_admin_menu_block($item)));
109 // Prepare for sorting as in function _menu_tree_check_access().
110 // The weight is offset so it is always positive, with a uniform 5-digits.
111 $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block;
112 }
113 }
114 if ($blocks) {
115 ksort($blocks);
116 return theme('admin_page', array('blocks' => $blocks));
117 }
118 else {
119 return t('You do not have any administrative items.');
120 }
121 }
122
123 /**
124 * Provide a single block from the administration menu as a page.
125 * This function is often a destination for these blocks.
126 * For example, 'admin/structure/types' needs to have a destination to be valid
127 * in the Drupal menu system, but too much information there might be
128 * hidden, so we supply the contents of the block.
129 *
130 * @return
131 * The output HTML.
132 */
133 function system_admin_menu_block_page() {
134 $item = menu_get_item();
135 if ($content = system_admin_menu_block($item)) {
136 $output = theme('admin_block_content', array('content' => $content));
137 }
138 else {
139 $output = t('You do not have any administrative items.');
140 }
141 return $output;
142 }
143
144 /**
145 * Menu callback; prints a listing of admin tasks for each installed module.
146 */
147 function system_admin_by_module() {
148
149 $module_info = system_get_info('module');
150 $menu_items = array();
151 $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;
152
153 foreach ($module_info as $module => $info) {
154 if ($module == 'help') {
155 continue;
156 }
157
158 $admin_tasks = system_get_module_admin_tasks($module);
159
160 // Only display a section if there are any available tasks.
161 if (count($admin_tasks)) {
162
163 // Check for help links.
164 if ($help_arg && module_invoke($module, 'help', "admin/help#$module", $help_arg)) {
165 $admin_tasks[100] = l(t('Get help'), "admin/help/$module");
166 }
167
168 // Sort.
169 ksort($admin_tasks);
170
171 $menu_items[$info['name']] = array($info['description'], $admin_tasks);
172 }
173 }
174 return theme('system_admin_by_module', array('menu_items' => $menu_items));
175 }
176
177 /**
178 * Menu callback; displays a module's settings page.
179 */
180 function system_settings_overview() {
181 // Check database setup if necessary
182 if (function_exists('db_check_setup') && empty($_POST)) {
183 db_check_setup();
184 }
185
186 $item = menu_get_item('admin/config');
187 $content = system_admin_menu_block($item);
188
189 $output = theme('admin_block_content', array('content' => $content));
190
191 return $output;
192 }
193
194 /**
195 * Menu callback; displays a listing of all themes.
196 *
197 * @ingroup forms
198 * @see system_themes_form_submit()
199 */
200 function system_themes_form() {
201 // Get current list of themes.
202 $themes = system_rebuild_theme_data();
203
204 // Remove hidden themes from the display list.
205 foreach ($themes as $theme_key => $theme) {
206 if (!empty($theme->info['hidden'])) {
207 unset($themes[$theme_key]);
208 }
209 }
210
211 uasort($themes, 'system_sort_modules_by_info_name');
212
213 $status = array();
214 $incompatible_core = array();
215 $incompatible_php = array();
216
217 foreach ($themes as $theme) {
218 $screenshot = NULL;
219 // Create a list which includes the current theme and all its base themes.
220 if (isset($themes[$theme->name]->base_themes)) {
221 $theme_keys = array_keys($themes[$theme->name]->base_themes);
222 $theme_keys[] = $theme->name;
223 }
224 else {
225 $theme_keys = array($theme->name);
226 }
227 // Look for a screenshot in the current theme or in its closest ancestor.
228 foreach (array_reverse($theme_keys) as $theme_key) {
229 if (isset($themes[$theme_key]) && file_exists($themes[$theme_key]->info['screenshot'])) {
230 $screenshot = $themes[$theme_key]->info['screenshot'];
231 break;
232 }
233 }
234 $screenshot = $screenshot ? theme('image', array('path' => $screenshot, 'alt' => t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), 'title' => '', 'attributes' => array('class' => array('screenshot')), 'getsize' => FALSE)) : t('no screenshot');
235
236 $form[$theme->name]['screenshot'] = array('#markup' => $screenshot);
237 $form[$theme->name]['info'] = array(
238 '#type' => 'value',
239 '#value' => $theme->info,
240 );
241 $options[$theme->name] = $theme->info['name'];
242
243 $form[$theme->name]['operations'] = drupal_theme_access($theme) ? array('#type' => 'link', '#title' => t('configure'), '#href' => 'admin/appearance/settings/' . $theme->name) : array();
244
245 if (!empty($theme->status)) {
246 $status[] = $theme->name;
247 }
248 else {
249 // Ensure this theme is compatible with this version of core.
250 // Require the 'content' region to make sure the main page
251 // content has a common place in all themes.
252 if (!isset($theme->info['core']) || ($theme->info['core'] != DRUPAL_CORE_COMPATIBILITY) || (!isset($theme->info['regions']['content']))) {
253 $incompatible_core[] = $theme->name;
254 }
255 if (version_compare(phpversion(), $theme->info['php']) < 0) {
256 $incompatible_php[$theme->name] = $theme->info['php'];
257 }
258 }
259 }
260
261 $form['status'] = array(
262 '#type' => 'checkboxes',
263 '#options' => array_fill_keys(array_keys($options), ''),
264 '#default_value' => $status,
265 '#incompatible_themes_core' => drupal_map_assoc($incompatible_core),
266 '#incompatible_themes_php' => $incompatible_php,
267 );
268 $form['theme_default'] = array(
269 '#type' => 'radios',
270 '#options' => array_fill_keys(array_keys($options), ''),
271 '#default_value' => variable_get('theme_default', 'garland'),
272 );
273
274 // Administration theme settings.
275 $form['admin_theme'] = array(
276 '#type' => 'fieldset',
277 '#title' => t('Administration theme'),
278 '#collapsible' => TRUE,
279 '#collapsed' => TRUE,
280 );
281 $form['admin_theme']['admin_theme'] = array(
282 '#type' => 'select',
283 '#options' => array(0 => t('Default theme')) + $options,
284 '#title' => t('Administration theme'),
285 '#description' => t('Choose "Default theme" to always use the same theme as the rest of the site.'),
286 '#default_value' => variable_get('admin_theme', 0),
287 );
288 $form['admin_theme']['node_admin_theme'] = array(
289 '#type' => 'checkbox',
290 '#title' => t('Use the administration theme when editing or creating content'),
291 '#default_value' => variable_get('node_admin_theme', '0'),
292 );
293
294 $form['buttons']['submit'] = array(
295 '#type' => 'submit',
296 '#value' => t('Save configuration'),
297 );
298
299 return $form;
300 }
301
302 /**
303 * Process system_themes_form form submissions.
304 */
305 function system_themes_form_submit($form, &$form_state) {
306 drupal_clear_css_cache();
307
308 // Store list of previously enabled themes and disable all themes
309 $old_theme_list = $new_theme_list = array();
310 foreach (list_themes() as $theme) {
311 if ($theme->status) {
312 $old_theme_list[] = $theme->name;
313 }
314 }
315 db_update('system')
316 ->fields(array('status' => 0))
317 ->condition('type', 'theme')
318 ->execute();
319
320 if ($form_state['values']['op'] == t('Save configuration')) {
321 if (is_array($form_state['values']['status'])) {
322 foreach ($form_state['values']['status'] as $key => $choice) {
323 // Always enable the default theme, despite its status checkbox being checked:
324 if ($choice || $form_state['values']['theme_default'] == $key) {
325 $new_theme_list[] = $key;
326 db_update('system')
327 ->fields(array('status' => 1))
328 ->condition('type', 'theme')
329 ->condition('name', $key)
330 ->execute();
331 }
332 }
333 }
334 if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] != $form_state['values']['theme_default']) {
335 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(
336 '%admin_theme' => $form_state['values']['admin_theme'],
337 '%selected_theme' => $form_state['values']['theme_default'],
338 )));
339 }
340
341 // Save the variables.
342 variable_set('theme_default', $form_state['values']['theme_default']);
343 variable_set('admin_theme', $form_state['values']['admin_theme']);
344 variable_set('node_admin_theme', $form_state['values']['node_admin_theme']);
345 }
346 else {
347 // Revert to defaults: only Garland is enabled.
348 variable_del('theme_default');
349 variable_del('admin_theme');
350 variable_del('node_admin_theme');
351 db_update('system')
352 ->fields(array('status' => 1))
353 ->condition('type', 'theme')
354 ->condition('name', 'garland')
355 ->execute();
356 $new_theme_list = array('garland');
357 }
358
359 list_themes(TRUE);
360 menu_rebuild();
361 drupal_theme_rebuild();
362 drupal_set_message(t('The configuration options have been saved.'));
363 $form_state['redirect'] = 'admin/appearance';
364
365 // Notify locale module about new themes being enabled, so translations can
366 // be imported. This might start a batch, and only return to the redirect
367 // path after that.
368 module_invoke('locale', 'system_update', array_diff($new_theme_list, $old_theme_list));
369
370 return;
371 }
372
373 /**
374 * Form builder; display theme configuration for entire site and individual themes.
375 *
376 * @param $key
377 * A theme name.
378 * @return
379 * The form structure.
380 * @ingroup forms
381 * @see system_theme_settings_submit()
382 */
383 function system_theme_settings($form, &$form_state, $key = '') {
384 $directory_path = file_directory_path();
385 if (!file_prepare_directory($directory_path, FILE_CREATE_DIRECTORY)) {
386 drupal_set_message(t('The directory %directory does not exist or is not writable.', array('%directory' => $directory_path)), 'warning');
387 }
388
389 // Default settings are defined in theme_get_setting() in includes/theme.inc
390 if ($key) {
391 $var = 'theme_' . $key . '_settings';
392 $themes = system_rebuild_theme_data();
393 $features = $themes[$key]->info['features'];
394 }
395 else {
396 $var = 'theme_settings';
397 }
398
399 $form['var'] = array('#type' => 'hidden', '#value' => $var);
400
401 // Check for a new uploaded logo, and use that instead.
402 if ($file = file_save_upload('logo_upload', array('file_validate_is_image' => array()))) {
403 $parts = pathinfo($file->filename);
404 $filename = ($key) ? $key . '_logo.' . $parts['extension'] : 'logo.' . $parts['extension'];
405
406 // The image was saved using file_save_upload() and was added to the
407 // files table as a temporary file. We'll make a copy and let the garbage
408 // collector delete the original upload.
409 if ($filepath = file_unmanaged_copy($file->uri, $filename, FILE_EXISTS_REPLACE)) {
410 $_POST['default_logo'] = 0;
411 $_POST['logo_path'] = $filepath;
412 $_POST['toggle_logo'] = 1;
413 }
414 }
415
416 // Check for a new uploaded favicon, and use that instead.
417 if ($file = file_save_upload('favicon_upload')) {
418 $parts = pathinfo($file->filename);
419 $filename = ($key) ? $key . '_favicon.' . $parts['extension'] : 'favicon.' . $parts['extension'];
420
421 // The image was saved using file_save_upload() and was added to the
422 // files table as a temporary file. We'll make a copy and let the garbage
423 // collector delete the original upload.
424 if ($filepath = file_unmanaged_copy($file->uri, $filename, FILE_EXISTS_REPLACE)) {
425 $_POST['default_favicon'] = 0;
426 $_POST['favicon_path'] = $filepath;
427 $_POST['toggle_favicon'] = 1;
428 }
429 }
430
431 // Toggle settings
432 $toggles = array(
433 'logo' => t('Logo'),
434 'name' => t('Site name'),
435 'slogan' => t('Site slogan'),
436 'node_user_picture' => t('User pictures in posts'),
437 'comment_user_picture' => t('User pictures in comments'),
438 'comment_user_verification' => t('User verification status in comments'),
439 'search' => t('Search box'),
440 'favicon' => t('Shortcut icon'),
441 'main_menu' => t('Main menu'),
442 'secondary_menu' => t('Secondary menu'),
443 );
444
445 // Some features are not always available
446 $disabled = array();
447 if (!variable_get('user_pictures', 0)) {
448 $disabled['toggle_node_user_picture'] = TRUE;
449 $disabled['toggle_comment_user_picture'] = TRUE;
450 }
451
452 $form['theme_settings'] = array(
453 '#type' => 'fieldset',
454 '#title' => t('Toggle display'),
455 '#description' => t('Enable or disable the display of certain page elements.'),
456 );
457 foreach ($toggles as $name => $title) {
458 if ((!$key) || in_array($name, $features)) {
459 $form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('toggle_' . $name, $key));
460 // Disable checkboxes for features not supported in the current configuration.
461 if (isset($disabled['toggle_' . $name])) {
462 $form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE;
463 }
464 }
465 }
466
467 if (!element_children($form['theme_settings'])) {
468 // If there is no element in the theme settings fieldset then do not show
469 // it -- but keep it in the form if another module wants to alter.
470 $form['theme_settings']['#access'] = FALSE;
471 }
472
473 // Logo settings
474 if ((!$key) || in_array('logo', $features)) {
475 $form['logo'] = array(
476 '#type' => 'fieldset',
477 '#title' => t('Logo image settings'),
478 '#description' => t('If toggled on, the following logo will be displayed.'),
479 '#attributes' => array('class' => array('theme-settings-bottom')),
480 );
481 $form['logo']['default_logo'] = array(
482 '#type' => 'checkbox',
483 '#title' => t('Use the default logo'),
484 '#default_value' => theme_get_setting('default_logo', $key),
485 '#tree' => FALSE,
486 '#description' => t('Check here if you want the theme to use the logo supplied with it.')
487 );
488 $form['logo']['settings'] = array(
489 '#type' => 'container',
490 '#states' => array(
491 // Hide the logo settings when using the default logo.
492 'invisible' => array(
493 'input[name="default_logo"]' => array('checked' => TRUE),
494 ),
495 ),
496 );
497 $form['logo']['settings']['logo_path'] = array(
498 '#type' => 'textfield',
499 '#title' => t('Path to custom logo'),
500 '#default_value' => theme_get_setting('logo_path', $key),
501 '#description' => t('The path to the file you would like to use as your logo file instead of the default logo.'),
502 );
503 $form['logo']['settings']['logo_upload'] = array(
504 '#type' => 'file',
505 '#title' => t('Upload logo image'),
506 '#maxlength' => 40,
507 '#description' => t("If you don't have direct file access to the server, use this field to upload your logo.")
508 );
509 }
510
511 if ((!$key) || in_array('favicon', $features)) {
512 $form['favicon'] = array(
513 '#type' => 'fieldset',
514 '#title' => t('Shortcut icon settings'),
515 '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
516 );
517 $form['favicon']['default_favicon'] = array(
518 '#type' => 'checkbox',
519 '#title' => t('Use the default shortcut icon.'),
520 '#default_value' => theme_get_setting('default_favicon', $key),
521 '#description' => t('Check here if you want the theme to use the default shortcut icon.')
522 );
523 $form['favicon']['settings'] = array(
524 '#type' => 'container',
525 '#states' => array(
526 // Hide the favicon settings when using the default favicon.
527 'invisible' => array(
528 'input[name="default_favicon"]' => array('checked' => TRUE),
529 ),
530 ),
531 );
532 $form['favicon']['settings']['favicon_path'] = array(
533 '#type' => 'textfield',
534 '#title' => t('Path to custom icon'),
535 '#default_value' => theme_get_setting('favicon_path', $key),
536 '#description' => t('The path to the image file you would like to use as your custom shortcut icon.')
537 );
538 $form['favicon']['settings']['favicon_upload'] = array(
539 '#type' => 'file',
540 '#title' => t('Upload icon image'),
541 '#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon.")
542 );
543 }
544
545 if ($key) {
546 // Call engine-specific settings.
547 $function = $themes[$key]->prefix . '_engine_settings';
548 if (function_exists($function)) {
549 $form['engine_specific'] = array(
550 '#type' => 'fieldset',
551 '#title' => t('Theme-engine-specific settings'),
552 '#description' => t('These settings only exist for the themes based on the %engine theme engine.', array('%engine' => $themes[$key]->prefix)),
553 );
554 $function($form, $form_state);
555 }
556
557 // Create a list which includes the current theme and all its base themes.
558 if (isset($themes[$key]->base_themes)) {
559 $theme_keys = array_keys($themes[$key]->base_themes);
560 $theme_keys[] = $key;
561 }
562 else {
563 $theme_keys = array($key);
564 }
565
566 // Save the name of the current theme (if any), so that we can temporarily
567 // override the current theme and allow theme_get_setting() to work
568 // without having to pass the theme name to it.
569 $default_theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : NULL;
570 $GLOBALS['theme_key'] = $key;
571
572 // Process the theme and all its base themes.
573 foreach ($theme_keys as $theme) {
574 // Include the theme-settings.php file.
575 $filename = DRUPAL_ROOT . '/' . str_replace("/$theme.info", '', $themes[$theme]->filename) . '/theme-settings.php';
576 if (file_exists($filename)) {
577 require_once $filename;
578 }
579
580 // Call theme-specific settings.
581 $function = $theme . '_form_system_theme_settings_alter';
582 if (function_exists($function)) {
583 $function($form, $form_state);
584 }
585 }
586
587 // Restore the original current theme.
588 if (!is_null($default_theme)) {
589 $GLOBALS['theme_key'] = $default_theme;
590 }
591 else {
592 unset($GLOBALS['theme_key']);
593 }
594 }
595
596 $form = system_settings_form($form, FALSE);
597 // We don't want to call system_settings_form_submit(), so change #submit.
598 array_pop($form['#submit']);
599 $form['#submit'][] = 'system_theme_settings_submit';
600 return $form;
601 }
602
603 /**
604 * Process system_theme_settings form submissions.
605 */
606 function system_theme_settings_submit($form, &$form_state) {
607 $values = $form_state['values'];
608 if (empty($values['default_favicon']) && !empty($values['favicon_path'])) {
609 $values['favicon_mimetype'] = file_get_mimetype($values['favicon_path']);
610 }
611 $key = $values['var'];
612
613 // Exclude unnecessary elements before saving.
614 unset($values['var'], $values['submit'], $values['reset'], $values['form_id'], $values['op'], $values['form_build_id'], $values['form_token']);
615 variable_set($key, $values);
616 drupal_set_message(t('The configuration options have been saved.'));
617
618 cache_clear_all();
619 }
620
621 /**
622 * Recursively check compatibility.
623 *
624 * @param $incompatible
625 * An associative array which at the end of the check contains all
626 * incompatible files as the keys, their values being TRUE.
627 * @param $files
628 * The set of files that will be tested.
629 * @param $file
630 * The file at which the check starts.
631 * @return
632 * Returns TRUE if an incompatible file is found, NULL (no return value)
633 * otherwise.
634 */
635 function _system_is_incompatible(&$incompatible, $files, $file) {
636 if (isset($incompatible[$file->name])) {
637 return TRUE;
638 }
639 // Recursively traverse required modules, looking for incompatible modules.
640 foreach ($file->requires as $requires) {
641 if (isset($files[$requires]) && _system_is_incompatible($incompatible, $files, $files[$requires])) {
642 $incompatible[$file->name] = TRUE;
643 return TRUE;
644 }
645 }
646 }
647
648 /**
649 * Menu callback; provides module enable/disable interface.
650 *
651 * The list of modules gets populated by module.info files, which contain each module's name,
652 * description and information about which modules it requires.
653 * @see drupal_parse_info_file for information on module.info descriptors.
654 *
655 * Dependency checking is performed to ensure that a module:
656 * - can not be enabled if there are disabled modules it requires.
657 * - can not be disabled if there are enabled modules which depend on it.
658 *
659 * @param $form_state
660 * An associative array containing the current state of the form.
661 * @ingroup forms
662 * @see theme_system_modules()
663 * @see system_modules_submit()
664 * @return
665 * The form array.
666 */
667 function system_modules($form, $form_state = array()) {
668 // Get current list of modules.
669 $files = system_rebuild_module_data();
670
671 // Remove hidden modules from display list.
672 foreach ($files as $filename => $file) {
673 if (!empty($file->info['hidden']) || !empty($file->info['required'])) {
674 unset($files[$filename]);
675 }
676 }
677
678 uasort($files, 'system_sort_modules_by_info_name');
679
680 // If the modules form was submitted, then system_modules_submit() runs first
681 // and if there are unfilled required modules, then form_state['storage'] is
682 // filled, triggering a rebuild. In this case we need to display a
683 // confirmation form.
684 if (!empty($form_state['storage'])) {
685 return system_modules_confirm_form($files, $form_state['storage']);
686 }
687
688 $modules = array();
689 $form['modules'] = array('#tree' => TRUE);
690
691 // Used when checking if module implements a help page.
692 $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;
693
694 // Iterate through each of the modules.
695 foreach ($files as $filename => $module) {
696 $extra = array();
697 $extra['enabled'] = (bool) $module->status;
698 // If this module requires other modules, add them to the array.
699 foreach ($module->requires as $requires => $v) {
700 if (!isset($files[$requires])) {
701 $extra['requires'][$requires] = t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst($requires)));
702 $extra['disabled'] = TRUE;
703 }
704 else {
705 $requires_name = $files[$requires]->info['name'];
706 if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']))) {
707 $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> version @version)', array(
708 '@module' => $requires_name . $incompatible_version,
709 '@version' => $files[$requires]->info['version'],
710 ));
711 $extra['disabled'] = TRUE;
712 }
713 elseif ($files[$requires]->status) {
714 $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $requires_name));
715 }
716 else {
717 $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $requires_name));
718 }
719 }
720 }
721 // Generate link for module's help page, if there is one.
722 if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
723 if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
724 // Module has a help page.
725 $extra['help'] = theme('more_help_link', array('url' => url("admin/help/$filename")));
726 }
727 }
728 // Mark dependents disabled so the user cannot remove required modules.
729 $dependents = array();
730 // If this module is required by other modules, list those, and then make it
731 // impossible to disable this one.
732 foreach ($module->required_by as $required_by => $v) {
733 // Hidden modules are unset already.
734 if (isset($files[$required_by])) {
735 if ($files[$required_by]->status == 1) {
736 $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
737 $extra['disabled'] = TRUE;
738 }
739 else {
740 $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
741 }
742 }
743 }
744 $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
745 }
746 // Add basic information to the fieldsets.
747 foreach (element_children($form['modules']) as $package) {
748 $form['modules'][$package] += array(
749 '#type' => 'fieldset',
750 '#title' => t($package),
751 '#collapsible' => TRUE,
752 '#theme' => 'system_modules_fieldset',
753 '#header' => array(
754 array('data' => t('Enabled'), 'class' => array('checkbox')),
755 t('Name'),
756 t('Version'),
757 t('Description'),
758 ),
759 );
760 }
761
762 $form['submit'] = array(
763 '#type' => 'submit',
764 '#value' => t('Save configuration'),
765 );
766 $form['#action'] = url('admin/config/modules/list/confirm');
767
768 return $form;
769 }
770
771 /**
772 * Array sorting callback; sorts modules or themes by their name.
773 */
774 function system_sort_modules_by_info_name($a, $b) {
775 return strcasecmp($a->info['name'], $b->info['name']);
776 }
777
778 /**
779 * Build a table row for the system modules page.
780 */
781 function _system_modules_build_row($info, $extra) {
782 // Add in the defaults.
783 $extra += array(
784 'requires' => array(),
785 'required_by' => array(),
786 'disabled' => FALSE,
787 'enabled' => FALSE,
788 'help' => '',
789 );
790 $form = array(
791 '#tree' => TRUE,
792 );
793 // Set the basic properties.
794 $form['name'] = array(
795 '#markup' => t($info['name']),
796 );
797 $form['description'] = array(
798 '#markup' => t($info['description']),
799 );
800 $form['version'] = array(
801 '#markup' => $info['version'],
802 );
803 $form['#requires'] = $extra['requires'];
804 $form['#required_by'] = $extra['required_by'];
805
806 // Check the compatibilities.
807 $compatible = TRUE;
808 $status_short = '';
809 $status_long = '';
810
811 // Check the core compatibility.
812 if (!isset($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY || empty($info['files'])) {
813 $compatible = FALSE;
814 $status_short .= t('Incompatible with this version of Drupal core. ');
815 $status_long .= t('This version is incompatible with the !core_version version of Drupal core. ', array('!core_version' => VERSION));
816 }
817
818 // Ensure this module is compatible with the currently installed version of PHP.
819 if (version_compare(phpversion(), $info['php']) < 0) {
820 $compatible = FALSE;
821 $status_short .= t('Incompatible with this version of PHP');
822 if (substr_count($info['php'], '.') < 2) {
823 $php_required .= '.*';
824 }
825 $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()));
826 }
827
828 // If this module is compatible, present a checkbox indicating
829 // this module may be installed. Otherwise, show a big red X.
830 if ($compatible) {
831 $form['enable'] = array(
832 '#type' => 'checkbox',
833 '#title' => t('Enable'),
834 '#default_value' => $extra['enabled'],
835 );
836 if ($extra['disabled']) {
837 $form['enable']['#disabled'] = TRUE;
838 }
839 }
840 else {
841 $form['enable'] = array(
842 '#markup' => theme('image', array('path' => 'misc/watchdog-error.png', 'alt' => t('incompatible'), 'title' => $status_short)),
843 );
844 $form['description']['#markup'] .= theme('system_modules_incompatible', array('message' => $status_long));
845 }
846
847 // Show a "more help" link for modules that have them.
848 if ($extra['help']) {
849 $form['help'] = array(
850 '#markup' => $extra['help'],
851 );
852 }
853 return $form;
854 }
855
856 /**
857 * Display confirmation form for required modules.
858 *
859 * @param $modules
860 * Array of module file objects as returned from system_rebuild_module_data().
861 * @param $storage
862 * The contents of $form_state['storage']; an array with two
863 * elements: the list of required modules and the list of status
864 * form field values from the previous screen.
865 * @ingroup forms
866 */
867 function system_modules_confirm_form($modules, $storage) {
868 $items = array();
869
870 $form['validation_modules'] = array('#type' => 'value', '#value' => $modules);
871 $form['status']['#tree'] = TRUE;
872
873 foreach ($storage['more_modules'] as $info) {
874 $t_argument = array(
875 '@module' => $info['name'],
876 '@required' => implode(', ', $info['requires']),
877 );
878 $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);
879 }
880 $form['text'] = array('#markup' => theme('item_list', array('items' => $items)));
881
882 if ($form) {
883 // Set some default form values
884 $form = confirm_form(
885 $form,
886 t('Some required modules must be enabled'),
887 'admin/config/modules',
888 t('Would you like to continue with enabling the above?'),
889 t('Continue'),
890 t('Cancel'));
891 return $form;
892 }
893 }
894
895 /**
896 * Submit callback; handles modules form submission.
897 */
898 function system_modules_submit($form, &$form_state) {
899 include_once DRUPAL_ROOT . '/includes/install.inc';
900 $modules = array();
901 // If we're not coming from the confirmation form, build the list of modules.
902 if (!isset($form_state['storage'])) {
903 foreach ($form_state['values']['modules'] as $group_name => $group) {
904 foreach ($group as $module => $enabled) {
905 $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']);
906 }
907 }
908 }
909 else {
910 // If we are coming from the confirmation form, fetch
911 // the modules out of $form_state.
912 $modules = $form_state['storage']['modules'];
913 }
914
915 // Get a list of all modules, it will be used to find which module requires
916 // which.
917 $files = system_rebuild_module_data();
918
919 // The modules to be enabled.
920 $modules_to_be_enabled = array();
921 // The modules to be disabled.
922 $disable_modules = array();
923 // The modules to be installed.
924 $new_modules = array();
925 // Modules that need to be switched on because other modules require them.
926 $more_modules = array();
927 // Go through each module, finding out if we should enable, install, or
928 // disable it. Also, we find out if there are modules it requires that are
929 // not enabled.
930 foreach ($modules as $name => $module) {
931 // If it's enabled, find out whether to just
932 // enable it, or install it.
933 if ($module['enabled']) {
934 if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
935 $new_modules[$name] = $name;
936 }
937 elseif (!module_exists($name)) {
938 $modules_to_be_enabled[$name] = $name;
939 }
940 // If we're not coming from a confirmation form, search for modules the
941 // new ones require and see whether there are any that additionally
942 // need to be switched on.
943 if (empty($form_state['storage'])) {
944 foreach ($form['modules'][$module['group']][$name]['#requires'] as $requires => $v) {
945 if (!$modules[$requires]['enabled']) {
946 if (!isset($more_modules[$name])) {
947 $more_modules[$name]['name'] = $files[$name]->info['name'];
948 }
949 $more_modules[$name]['requires'][$requires] = $files[$requires]->info['name'];
950 }
951 $modules[$requires] = array('group' => $files[$requires]->info['package'], 'enabled' => TRUE);
952 }
953 }
954 }
955 }
956 // A second loop is necessary, otherwise the modules set to be enabled in the
957 // previous loop would not be found.
958 foreach ($modules as $name => $module) {
959 if (module_exists($name) && !$module['enabled']) {
960 $disable_modules[$name] = $name;
961 }
962 }
963 if ($more_modules) {
964 // If we need to switch on more modules because other modules require
965 // them and they haven't confirmed, don't process the submission yet. Store
966 // the form submission data needed later.
967 if (!isset($form_state['values']['confirm'])) {
968 $form_state['storage'] = array('more_modules' => $more_modules, 'modules' => $modules);
969 return;
970 }
971 // Otherwise, install or enable the modules.
972 else {
973 foreach ($form_state['storage']['more_modules'] as $info) {
974 foreach ($info['requires'] as $requires => $name) {
975 if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
976 $new_modules[$name] = $name;
977 }
978 else {
979 $modules_to_be_enabled[$name] = $name;
980 }
981 }
982 }
983 }
984 }
985 // Now we have installed every module as required (either by the user or
986 // because other modules require them) so we don't need the temporary
987 // storage anymore.
988 unset($form_state['storage']);
989
990 $old_module_list = module_list();
991
992 // Enable the modules needing enabling.
993 if (!empty($modules_to_be_enabled)) {
994 $sort = array();
995 foreach ($modules_to_be_enabled as $module) {
996 $sort[$module] = $files[$module]->sort;
997 }
998 array_multisort($sort, $modules_to_be_enabled);
999 module_enable($modules_to_be_enabled);
1000 }
1001 // Disable the modules that need disabling.
1002 if (!empty($disable_modules)) {
1003 $sort = array();
1004 foreach ($disable_modules as $module) {
1005 $sort[$module] = $files[$module]->sort;
1006 }
1007 array_multisort($sort, $disable_modules);
1008 module_disable($disable_modules);
1009 }
1010
1011 // Install new modules.
1012 if (!empty($new_modules)) {
1013 $sort = array();
1014 foreach ($new_modules as $key => $module) {
1015 if (!drupal_check_module($module)) {
1016 unset($new_modules[$key]);
1017 }
1018 $sort[$module] = $files[$module]->sort;
1019 }
1020 array_multisort($sort, $new_modules);
1021 drupal_install_modules($new_modules);
1022 }
1023
1024 $current_module_list = module_list(TRUE);
1025 if ($old_module_list != $current_module_list) {
1026 drupal_set_message(t('The configuration options have been saved.'));
1027 }
1028
1029 // Clear all caches.
1030 registry_rebuild();
1031 drupal_theme_rebuild();
1032 node_types_rebuild();
1033 menu_rebuild();
1034 cache_clear_all('schema', 'cache');
1035 cache_clear_all('entity_info', 'cache');
1036 drupal_clear_css_cache();
1037 drupal_clear_js_cache();
1038
1039 $form_state['redirect'] = 'admin/config/modules';
1040
1041 // Notify locale module about module changes, so translations can be
1042 // imported. This might start a batch, and only return to the redirect
1043 // path after that.
1044 module_invoke('locale', 'system_update', $new_modules);
1045
1046 // Synchronize to catch any actions that were added or removed.
1047 actions_synchronize();
1048
1049 return;
1050 }
1051
1052 /**
1053 * Uninstall functions
1054 */
1055
1056 /**
1057 * Builds a form of currently disabled modules.
1058 *
1059 * @ingroup forms
1060 * @see system_modules_uninstall_validate()
1061 * @see system_modules_uninstall_submit()
1062 * @param $form_state['values']
1063 * Submitted form values.
1064 * @return
1065 * A form array representing the currently disabled modules.
1066 */
1067 function system_modules_uninstall($form, $form_state = NULL) {
1068 // Make sure the install API is available.
1069 include_once DRUPAL_ROOT . '/includes/install.inc';
1070
1071 // Display the confirm form if any modules have been submitted.
1072 if (isset($form_state) && $confirm_form = system_modules_uninstall_confirm_form($form_state['storage'])) {
1073 return $confirm_form;
1074 }
1075
1076 // Pull all disabled modules from the system table.
1077 $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));
1078 foreach ($disabled_modules as $module) {
1079 // Grab the module info
1080 $info = unserialize($module->info);
1081
1082 // Load the .install file, and check for an uninstall or schema hook.
1083 // If the hook exists, the module can be uninstalled.
1084 module_load_install($module->name);
1085 if (module_hook($module->name, 'uninstall') || module_hook($module->name, 'schema')) {
1086 $form['modules'][$module->name]['name'] = array('#markup' => $info['name'] ? $info['name'] : $module->name);
1087 $form['modules'][$module->name]['description'] = array('#markup' => t($info['description']));
1088 $options[$module->name] = '';
1089 }
1090 }
1091
1092 // Only build the rest of the form if there are any modules available to uninstall.
1093 if (!empty($options)) {
1094 $form['uninstall'] = array(
1095 '#type' => 'checkboxes',
1096 '#options' => $options,
1097 );
1098 $form['buttons']['submit'] = array(
1099 '#type' => 'submit',
1100 '#value' => t('Uninstall'),
1101 );
1102 $form['#action'] = url('admin/config/modules/uninstall/confirm');
1103 }
1104 else {
1105 $form['modules'] = array();
1106 }
1107
1108 return $form;
1109