summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/system
diff options
context:
space:
mode:
Diffstat (limited to 'core/modules/system')
-rw-r--r--core/modules/system/html.tpl.php58
-rw-r--r--core/modules/system/image.gd.inc367
-rw-r--r--core/modules/system/maintenance-page.tpl.php93
-rw-r--r--core/modules/system/page.tpl.php152
-rw-r--r--core/modules/system/region.tpl.php33
-rw-r--r--core/modules/system/system.admin-rtl.css86
-rw-r--r--core/modules/system/system.admin.css270
-rw-r--r--core/modules/system/system.admin.inc3173
-rw-r--r--core/modules/system/system.api.php4130
-rw-r--r--core/modules/system/system.archiver.inc139
-rw-r--r--core/modules/system/system.base-rtl.css54
-rw-r--r--core/modules/system/system.base.css281
-rw-r--r--core/modules/system/system.cron.js19
-rw-r--r--core/modules/system/system.info13
-rw-r--r--core/modules/system/system.install1647
-rw-r--r--core/modules/system/system.js137
-rw-r--r--core/modules/system/system.mail.inc112
-rw-r--r--core/modules/system/system.maintenance.css55
-rw-r--r--core/modules/system/system.menus-rtl.css37
-rw-r--r--core/modules/system/system.menus.css116
-rw-r--r--core/modules/system/system.messages-rtl.css13
-rw-r--r--core/modules/system/system.messages.css63
-rw-r--r--core/modules/system/system.module3988
-rw-r--r--core/modules/system/system.queue.inc371
-rw-r--r--core/modules/system/system.tar.inc1892
-rw-r--r--core/modules/system/system.test2564
-rw-r--r--core/modules/system/system.theme-rtl.css53
-rw-r--r--core/modules/system/system.theme.css239
-rw-r--r--core/modules/system/system.tokens.inc269
-rw-r--r--core/modules/system/system.updater.inc146
-rw-r--r--core/modules/system/theme.api.php230
31 files changed, 20800 insertions, 0 deletions
diff --git a/core/modules/system/html.tpl.php b/core/modules/system/html.tpl.php
new file mode 100644
index 00000000000..d889b23e384
--- /dev/null
+++ b/core/modules/system/html.tpl.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display the basic html structure of a single
+ * Drupal page.
+ *
+ * Variables:
+ * - $css: An array of CSS files for the current page.
+ * - $language: (object) The language the site is being displayed in.
+ * $language->language contains its textual representation.
+ * $language->dir contains the language direction.
+ * It will either be 'ltr' or 'rtl'.
+ * - $head_title: A modified version of the page title, for use in the TITLE
+ * tag.
+ * - $head_title_array: (array) An associative array containing the string parts
+ * that were used to generate the $head_title variable, already prepared to be
+ * output as TITLE tag. The key/value pairs may contain one or more of the
+ * following, depending on conditions:
+ * - title: The title of the current page, if any.
+ * - name: The name of the site.
+ * - slogan: The slogan of the site, if any, and if there is no title.
+ * - $head: Markup for the HEAD section (including meta tags, keyword tags, and
+ * so on).
+ * - $styles: Style tags necessary to import all CSS files for the page.
+ * - $scripts: Script tags necessary to load the JavaScript files and settings
+ * for the page.
+ * - $page_top: Initial markup from any modules that have altered the
+ * page. This variable should always be output first, before all other dynamic
+ * content.
+ * - $page: The rendered page content.
+ * - $page_bottom: Final closing markup from any modules that have altered the
+ * page. This variable should always be output last, after all other dynamic
+ * content.
+ * - $classes String of classes that can be used to style contextually through
+ * CSS.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_html()
+ * @see template_process()
+ */
+?><!DOCTYPE html>
+<html<?php print $html_attributes; ?>>
+ <head>
+ <?php print $head; ?>
+ <title><?php print $head_title; ?></title>
+ <?php print $styles; ?>
+ <?php print $scripts; ?>
+ </head>
+ <body class="<?php print $classes; ?>" <?php print $body_attributes;?>>
+ <div id="skip-link">
+ <a href="#main-content" class="element-invisible element-focusable"><?php print t('Skip to main content'); ?></a>
+ </div>
+ <?php print $page_top; ?>
+ <?php print $page; ?>
+ <?php print $page_bottom; ?>
+ </body>
+</html>
diff --git a/core/modules/system/image.gd.inc b/core/modules/system/image.gd.inc
new file mode 100644
index 00000000000..39f86dc30e6
--- /dev/null
+++ b/core/modules/system/image.gd.inc
@@ -0,0 +1,367 @@
+<?php
+
+/**
+ * @file
+ * GD2 toolkit for image manipulation within Drupal.
+ */
+
+/**
+ * @ingroup image
+ * @{
+ */
+
+/**
+ * Retrieve settings for the GD2 toolkit.
+ */
+function image_gd_settings() {
+ if (image_gd_check_settings()) {
+ $form['status'] = array(
+ '#markup' => t('The GD toolkit is installed and working properly.')
+ );
+
+ $form['image_jpeg_quality'] = array(
+ '#type' => 'textfield',
+ '#title' => t('JPEG quality'),
+ '#description' => t('Define the image quality for JPEG manipulations. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
+ '#size' => 10,
+ '#maxlength' => 3,
+ '#default_value' => variable_get('image_jpeg_quality', 75),
+ '#field_suffix' => t('%'),
+ );
+ $form['#element_validate'] = array('image_gd_settings_validate');
+
+ return $form;
+ }
+ else {
+ form_set_error('image_toolkit', t('The GD image toolkit requires that the GD module for PHP be installed and configured properly. For more information see <a href="@url">PHP\'s image documentation</a>.', array('@url' => 'http://php.net/image')));
+ return FALSE;
+ }
+}
+
+/**
+ * Validate the submitted GD settings.
+ */
+function image_gd_settings_validate($form, &$form_state) {
+ // Validate image quality range.
+ $value = $form_state['values']['image_jpeg_quality'];
+ if (!is_numeric($value) || $value < 0 || $value > 100) {
+ form_set_error('image_jpeg_quality', t('JPEG quality must be a number between 0 and 100.'));
+ }
+}
+
+/**
+ * Verify GD2 settings (that the right version is actually installed).
+ *
+ * @return
+ * A boolean indicating if the GD toolkit is available on this machine.
+ */
+function image_gd_check_settings() {
+ if ($check = get_extension_funcs('gd')) {
+ if (in_array('imagegd2', $check)) {
+ // GD2 support is available.
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Scale an image to the specified size using GD.
+ *
+ * @param $image
+ * An image object. The $image->resource, $image->info['width'], and
+ * $image->info['height'] values will be modified by this call.
+ * @param $width
+ * The new width of the resized image, in pixels.
+ * @param $height
+ * The new height of the resized image, in pixels.
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_resize()
+ */
+function image_gd_resize(stdClass $image, $width, $height) {
+ $res = image_gd_create_tmp($image, $width, $height);
+
+ if (!imagecopyresampled($res, $image->resource, 0, 0, 0, 0, $width, $height, $image->info['width'], $image->info['height'])) {
+ return FALSE;
+ }
+
+ imagedestroy($image->resource);
+ // Update image object.
+ $image->resource = $res;
+ $image->info['width'] = $width;
+ $image->info['height'] = $height;
+ return TRUE;
+}
+
+/**
+ * Rotate an image the given number of degrees.
+ *
+ * @param $image
+ * An image object. The $image->resource, $image->info['width'], and
+ * $image->info['height'] values will be modified by this call.
+ * @param $degrees
+ * The number of (clockwise) degrees to rotate the image.
+ * @param $background
+ * An hexadecimal integer specifying the background color to use for the
+ * uncovered area of the image after the rotation. E.g. 0x000000 for black,
+ * 0xff00ff for magenta, and 0xffffff for white. For images that support
+ * transparency, this will default to transparent. Otherwise it will
+ * be white.
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_rotate()
+ */
+function image_gd_rotate(stdClass $image, $degrees, $background = NULL) {
+ // PHP installations using non-bundled GD do not have imagerotate.
+ if (!function_exists('imagerotate')) {
+ watchdog('image', 'The image %file could not be rotated because the imagerotate() function is not available in this PHP installation.', array('%file' => $image->source));
+ return FALSE;
+ }
+
+ $width = $image->info['width'];
+ $height = $image->info['height'];
+
+ // Convert the hexadecimal background value to a color index value.
+ if (isset($background)) {
+ $rgb = array();
+ for ($i = 16; $i >= 0; $i -= 8) {
+ $rgb[] = (($background >> $i) & 0xFF);
+ }
+ $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0);
+ }
+ // Set the background color as transparent if $background is NULL.
+ else {
+ // Get the current transparent color.
+ $background = imagecolortransparent($image->resource);
+
+ // If no transparent colors, use white.
+ if ($background == 0) {
+ $background = imagecolorallocatealpha($image->resource, 255, 255, 255, 0);
+ }
+ }
+
+ // Images are assigned a new color palette when rotating, removing any
+ // transparency flags. For GIF images, keep a record of the transparent color.
+ if ($image->info['extension'] == 'gif') {
+ $transparent_index = imagecolortransparent($image->resource);
+ if ($transparent_index != 0) {
+ $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index);
+ }
+ }
+
+ $image->resource = imagerotate($image->resource, 360 - $degrees, $background);
+
+ // GIFs need to reassign the transparent color after performing the rotate.
+ if (isset($transparent_gif_color)) {
+ $background = imagecolorexactalpha($image->resource, $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']);
+ imagecolortransparent($image->resource, $background);
+ }
+
+ $image->info['width'] = imagesx($image->resource);
+ $image->info['height'] = imagesy($image->resource);
+ return TRUE;
+}
+
+/**
+ * Crop an image using the GD toolkit.
+ *
+ * @param $image
+ * An image object. The $image->resource, $image->info['width'], and
+ * $image->info['height'] values will be modified by this call.
+ * @param $x
+ * The starting x offset at which to start the crop, in pixels.
+ * @param $y
+ * The starting y offset at which to start the crop, in pixels.
+ * @param $width
+ * The width of the cropped area, in pixels.
+ * @param $height
+ * The height of the cropped area, in pixels.
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_crop()
+ */
+function image_gd_crop(stdClass $image, $x, $y, $width, $height) {
+ $res = image_gd_create_tmp($image, $width, $height);
+
+ if (!imagecopyresampled($res, $image->resource, 0, 0, $x, $y, $width, $height, $width, $height)) {
+ return FALSE;
+ }
+
+ // Destroy the original image and return the modified image.
+ imagedestroy($image->resource);
+ $image->resource = $res;
+ $image->info['width'] = $width;
+ $image->info['height'] = $height;
+ return TRUE;
+}
+
+/**
+ * Convert an image resource to grayscale.
+ *
+ * Note that transparent GIFs loose transparency when desaturated.
+ *
+ * @param $image
+ * An image object. The $image->resource value will be modified by this call.
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_desaturate()
+ */
+function image_gd_desaturate(stdClass $image) {
+ // PHP installations using non-bundled GD do not have imagefilter.
+ if (!function_exists('imagefilter')) {
+ watchdog('image', 'The image %file could not be desaturated because the imagefilter() function is not available in this PHP installation.', array('%file' => $image->source));
+ return FALSE;
+ }
+
+ return imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
+}
+
+/**
+ * GD helper function to create an image resource from a file.
+ *
+ * @param $image
+ * An image object. The $image->resource value will populated by this call.
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_load()
+ */
+function image_gd_load(stdClass $image) {
+ $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
+ $function = 'imagecreatefrom' . $extension;
+ return (function_exists($function) && $image->resource = $function($image->source));
+}
+
+/**
+ * GD helper to write an image resource to a destination file.
+ *
+ * @param $image
+ * An image object.
+ * @param $destination
+ * A string file URI or path where the image should be saved.
+ * @return
+ * TRUE or FALSE, based on success.
+ *
+ * @see image_save()
+ */
+function image_gd_save(stdClass $image, $destination) {
+ $scheme = file_uri_scheme($destination);
+ // Work around lack of stream wrapper support in imagejpeg() and imagepng().
+ if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
+ // If destination is not local, save image to temporary local file.
+ $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
+ if (!isset($local_wrappers[$scheme])) {
+ $permanent_destination = $destination;
+ $destination = drupal_tempnam('temporary://', 'gd_');
+ }
+ // Convert stream wrapper URI to normal path.
+ $destination = drupal_realpath($destination);
+ }
+
+ $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
+ $function = 'image' . $extension;
+ if (!function_exists($function)) {
+ return FALSE;
+ }
+ if ($extension == 'jpeg') {
+ $success = $function($image->resource, $destination, variable_get('image_jpeg_quality', 75));
+ }
+ else {
+ // Always save PNG images with full transparency.
+ if ($extension == 'png') {
+ imagealphablending($image->resource, FALSE);
+ imagesavealpha($image->resource, TRUE);
+ }
+ $success = $function($image->resource, $destination);
+ }
+ // Move temporary local file to remote destination.
+ if (isset($permanent_destination) && $success) {
+ return (bool) file_unmanaged_move($destination, $permanent_destination, FILE_EXISTS_REPLACE);
+ }
+ return $success;
+}
+
+/**
+ * Create a truecolor image preserving transparency from a provided image.
+ *
+ * @param $image
+ * An image object.
+ * @param $width
+ * The new width of the new image, in pixels.
+ * @param $height
+ * The new height of the new image, in pixels.
+ * @return
+ * A GD image handle.
+ */
+function image_gd_create_tmp(stdClass $image, $width, $height) {
+ $res = imagecreatetruecolor($width, $height);
+
+ if ($image->info['extension'] == 'gif') {
+ // Grab transparent color index from image resource.
+ $transparent = imagecolortransparent($image->resource);
+
+ if ($transparent >= 0) {
+ // The original must have a transparent color, allocate to the new image.
+ $transparent_color = imagecolorsforindex($image->resource, $transparent);
+ $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
+
+ // Flood with our new transparent color.
+ imagefill($res, 0, 0, $transparent);
+ imagecolortransparent($res, $transparent);
+ }
+ }
+ elseif ($image->info['extension'] == 'png') {
+ imagealphablending($res, FALSE);
+ $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
+ imagefill($res, 0, 0, $transparency);
+ imagealphablending($res, TRUE);
+ imagesavealpha($res, TRUE);
+ }
+ else {
+ imagefill($res, 0, 0, imagecolorallocate($res, 255, 255, 255));
+ }
+
+ return $res;
+}
+
+/**
+ * Get details about an image.
+ *
+ * @param $image
+ * An image object.
+ * @return
+ * FALSE, if the file could not be found or is not an image. Otherwise, a
+ * keyed array containing information about the image:
+ * - "width": Width, in pixels.
+ * - "height": Height, in pixels.
+ * - "extension": Commonly used file extension for the image.
+ * - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
+ *
+ * @see image_get_info()
+ */
+function image_gd_get_info(stdClass $image) {
+ $details = FALSE;
+ $data = getimagesize($image->source);
+
+ if (isset($data) && is_array($data)) {
+ $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
+ $extension = isset($extensions[$data[2]]) ? $extensions[$data[2]] : '';
+ $details = array(
+ 'width' => $data[0],
+ 'height' => $data[1],
+ 'extension' => $extension,
+ 'mime_type' => $data['mime'],
+ );
+ }
+
+ return $details;
+}
+
+/**
+ * @} End of "ingroup image".
+ */
diff --git a/core/modules/system/maintenance-page.tpl.php b/core/modules/system/maintenance-page.tpl.php
new file mode 100644
index 00000000000..31de3bb2385
--- /dev/null
+++ b/core/modules/system/maintenance-page.tpl.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display a single Drupal page while offline.
+ *
+ * All the available variables are mirrored in html.tpl.php and page.tpl.php.
+ * Some may be blank but they are provided for consistency.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_maintenance_page()
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+
+<head>
+ <title><?php print $head_title; ?></title>
+ <?php print $head; ?>
+ <?php print $styles; ?>
+ <?php print $scripts; ?>
+</head>
+<body class="<?php print $classes; ?>">
+ <div id="page">
+ <div id="header">
+ <div id="logo-title">
+
+ <?php if (!empty($logo)): ?>
+ <a href="<?php print $base_path; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo">
+ <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" />
+ </a>
+ <?php endif; ?>
+
+ <div id="name-and-slogan">
+ <?php if (!empty($site_name)): ?>
+ <h1 id="site-name">
+ <a href="<?php print $base_path ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>
+ </h1>
+ <?php endif; ?>
+
+ <?php if (!empty($site_slogan)): ?>
+ <div id="site-slogan"><?php print $site_slogan; ?></div>
+ <?php endif; ?>
+ </div> <!-- /name-and-slogan -->
+ </div> <!-- /logo-title -->
+
+ <?php if (!empty($header)): ?>
+ <div id="header-region">
+ <?php print $header; ?>
+ </div>
+ <?php endif; ?>
+
+ </div> <!-- /header -->
+
+ <div id="container" class="clearfix">
+
+ <?php if (!empty($sidebar_first)): ?>
+ <div id="sidebar-first" class="column sidebar">
+ <?php print $sidebar_first; ?>
+ </div> <!-- /sidebar-first -->
+ <?php endif; ?>
+
+ <div id="main" class="column"><div id="main-squeeze">
+
+ <div id="content">
+ <?php if (!empty($title)): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
+ <?php if (!empty($messages)): print $messages; endif; ?>
+ <div id="content-content" class="clearfix">
+ <?php print $content; ?>
+ </div> <!-- /content-content -->
+ </div> <!-- /content -->
+
+ </div></div> <!-- /main-squeeze /main -->
+
+ <?php if (!empty($sidebar_second)): ?>
+ <div id="sidebar-second" class="column sidebar">
+ <?php print $sidebar_second; ?>
+ </div> <!-- /sidebar-second -->
+ <?php endif; ?>
+
+ </div> <!-- /container -->
+
+ <div id="footer-wrapper">
+ <div id="footer">
+ <?php if (!empty($footer)): print $footer; endif; ?>
+ </div> <!-- /footer -->
+ </div> <!-- /footer-wrapper -->
+
+ </div> <!-- /page -->
+
+</body>
+</html>
diff --git a/core/modules/system/page.tpl.php b/core/modules/system/page.tpl.php
new file mode 100644
index 00000000000..ca9273cd663
--- /dev/null
+++ b/core/modules/system/page.tpl.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display a single Drupal page.
+ *
+ * Available variables:
+ *
+ * General utility variables:
+ * - $base_path: The base URL path of the Drupal installation. At the very
+ * least, this will always default to /.
+ * - $directory: The directory the template is located in, e.g. modules/system
+ * or themes/bartik.
+ * - $is_front: TRUE if the current page is the front page.
+ * - $logged_in: TRUE if the user is registered and signed in.
+ * - $is_admin: TRUE if the user has permission to access administration pages.
+ *
+ * Site identity:
+ * - $front_page: The URL of the front page. Use this instead of $base_path,
+ * when linking to the front page. This includes the language domain or
+ * prefix.
+ * - $logo: The path to the logo image, as defined in theme configuration.
+ * - $site_name: The name of the site, empty when display has been disabled
+ * in theme settings.
+ * - $site_slogan: The slogan of the site, empty when display has been disabled
+ * in theme settings.
+ *
+ * Navigation:
+ * - $main_menu (array): An array containing the Main menu links for the
+ * site, if they have been configured.
+ * - $secondary_menu (array): An array containing the Secondary menu links for
+ * the site, if they have been configured.
+ * - $breadcrumb: The breadcrumb trail for the current page.
+ *
+ * Page content (in order of occurrence in the default page.tpl.php):
+ * - $title_prefix (array): An array containing additional output populated by
+ * modules, intended to be displayed in front of the main title tag that
+ * appears in the template.
+ * - $title: The page title, for use in the actual HTML content.
+ * - $title_suffix (array): An array containing additional output populated by
+ * modules, intended to be displayed after the main title tag that appears in
+ * the template.
+ * - $messages: HTML for status and error messages. Should be displayed
+ * prominently.
+ * - $tabs (array): Tabs linking to any sub-pages beneath the current page
+ * (e.g., the view and edit tabs when displaying a node).
+ * - $action_links (array): Actions local to the page, such as 'Add menu' on the
+ * menu administration interface.
+ * - $feed_icons: A string of all feed icons for the current page.
+ * - $node: The node object, if there is an automatically-loaded node
+ * associated with the page, and the node ID is the second argument
+ * in the page's path (e.g. node/12345 and node/12345/revisions, but not
+ * comment/reply/12345).
+ *
+ * Regions:
+ * - $page['help']: Dynamic help text, mostly for admin pages.
+ * - $page['highlighted']: Items for the highlighted content region.
+ * - $page['content']: The main content of the current page.
+ * - $page['sidebar_first']: Items for the first sidebar.
+ * - $page['sidebar_second']: Items for the second sidebar.
+ * - $page['header']: Items for the header region.
+ * - $page['footer']: Items for the footer region.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_page()
+ * @see template_process()
+ */
+?>
+
+ <div id="page-wrapper"><div id="page">
+
+ <div id="header"><div class="section clearfix">
+
+ <?php if ($logo): ?>
+ <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo">
+ <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" />
+ </a>
+ <?php endif; ?>
+
+ <?php if ($site_name || $site_slogan): ?>
+ <div id="name-and-slogan">
+ <?php if ($site_name): ?>
+ <?php if ($title): ?>
+ <div id="site-name"><strong>
+ <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>
+ </strong></div>
+ <?php else: /* Use h1 when the content title is empty */ ?>
+ <h1 id="site-name">
+ <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>
+ </h1>
+ <?php endif; ?>
+ <?php endif; ?>
+
+ <?php if ($site_slogan): ?>
+ <div id="site-slogan"><?php print $site_slogan; ?></div>
+ <?php endif; ?>
+ </div> <!-- /#name-and-slogan -->
+ <?php endif; ?>
+
+ <?php print render($page['header']); ?>
+
+ </div></div> <!-- /.section, /#header -->
+
+ <?php if ($main_menu || $secondary_menu): ?>
+ <div id="navigation"><div class="section">
+ <?php print theme('links__system_main_menu', array('links' => $main_menu, 'attributes' => array('id' => 'main-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Main menu'))); ?>
+ <?php print theme('links__system_secondary_menu', array('links' => $secondary_menu, 'attributes' => array('id' => 'secondary-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Secondary menu'))); ?>
+ </div></div> <!-- /.section, /#navigation -->
+ <?php endif; ?>
+
+ <?php if ($breadcrumb): ?>
+ <div id="breadcrumb"><?php print $breadcrumb; ?></div>
+ <?php endif; ?>
+
+ <?php if ($messages): ?>
+ <div id="messages"><?php print $messages; ?></div>
+ <?php endif; ?>
+
+ <div id="main-wrapper"><div id="main" class="clearfix">
+
+ <div id="content" class="column"><div class="section">
+ <?php if ($page['highlighted']): ?><div id="highlighted"><?php print render($page['highlighted']); ?></div><?php endif; ?>
+ <a id="main-content"></a>
+ <?php print render($title_prefix); ?>
+ <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
+ <?php print render($title_suffix); ?>
+ <?php if ($tabs): ?><div class="tabs"><?php print render($tabs); ?></div><?php endif; ?>
+ <?php print render($page['help']); ?>
+ <?php if ($action_links): ?><ul class="action-links"><?php print render($action_links); ?></ul><?php endif; ?>
+ <?php print render($page['content']); ?>
+ <?php print $feed_icons; ?>
+ </div></div> <!-- /.section, /#content -->
+
+ <?php if ($page['sidebar_first']): ?>
+ <div id="sidebar-first" class="column sidebar"><div class="section">
+ <?php print render($page['sidebar_first']); ?>
+ </div></div> <!-- /.section, /#sidebar-first -->
+ <?php endif; ?>
+
+ <?php if ($page['sidebar_second']): ?>
+ <div id="sidebar-second" class="column sidebar"><div class="section">
+ <?php print render($page['sidebar_second']); ?>
+ </div></div> <!-- /.section, /#sidebar-second -->
+ <?php endif; ?>
+
+ </div></div> <!-- /#main, /#main-wrapper -->
+
+ <div id="footer"><div class="section">
+ <?php print render($page['footer']); ?>
+ </div></div> <!-- /.section, /#footer -->
+
+ </div></div> <!-- /#page, /#page-wrapper -->
diff --git a/core/modules/system/region.tpl.php b/core/modules/system/region.tpl.php
new file mode 100644
index 00000000000..b29e24f0178
--- /dev/null
+++ b/core/modules/system/region.tpl.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to display a region.
+ *
+ * Available variables:
+ * - $content: The content for this region, typically blocks.
+ * - $classes: String of classes that can be used to style contextually through
+ * CSS. It can be manipulated through the variable $classes_array from
+ * preprocess functions. The default values can be one or more of the following:
+ * - region: The current template type, i.e., "theming hook".
+ * - region-[name]: The name of the region with underscores replaced with
+ * dashes. For example, the page_top region would have a region-page-top class.
+ * - $region: The name of the region variable as defined in the theme's .info file.
+ *
+ * Helper variables:
+ * - $classes_array: Array of html class attribute values. It is flattened
+ * into a string within the variable $classes.
+ * - $is_admin: Flags true when the current user is an administrator.
+ * - $is_front: Flags true when presented in the front page.
+ * - $logged_in: Flags true when the current user is a logged-in member.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_region()
+ * @see template_process()
+ */
+?>
+<?php if ($content): ?>
+ <div class="<?php print $classes; ?>">
+ <?php print $content; ?>
+ </div>
+<?php endif; ?>
diff --git a/core/modules/system/system.admin-rtl.css b/core/modules/system/system.admin-rtl.css
new file mode 100644
index 00000000000..362a406c59c
--- /dev/null
+++ b/core/modules/system/system.admin-rtl.css
@@ -0,0 +1,86 @@
+
+/**
+ * @file
+ * RTL styles for administration pages.
+ */
+
+/**
+ * Administration blocks.
+ */
+div.admin-panel .body {
+ padding: 0 8px 2px 4px;
+}
+div.admin .left {
+ float: right;
+ margin-left: 0;
+ margin-right: 1em;
+}
+div.admin .right {
+ float: left;
+ margin-left: 1em;
+ margin-right: 0;
+}
+div.admin .expert-link {
+ margin-right: 0;
+ margin-left: 1em;
+ padding-right: 0;
+ padding-left: 4px;
+ text-align: left;
+}
+
+/**
+ * Status report.
+ */
+table.system-status-report td.status-icon {
+ padding-left: 0;
+ padding-right: 6px;
+}
+table.system-status-report tr.merge-up td {
+ padding: 0 28px 8px 6px;
+}
+
+/**
+ * Appearance page.
+ */
+table.screenshot {
+ margin-left: 1em;
+}
+.system-themes-list-enabled .theme-selector .screenshot,
+.system-themes-list-enabled .theme-selector .no-screenshot {
+ float: right;
+ margin: 0 0 0 20px;
+}
+.system-themes-list-disabled .theme-selector {
+ float: right;
+ padding: 20px 0 20px 20px;
+}
+.theme-selector .operations li {
+ border-right: none;
+ border-left: 1px solid #cdcdcd;
+ float: right;
+}
+.theme-selector .operations li.last {
+ border-left: none;
+ padding: 0 0.7em 0 0;
+}
+.theme-selector .operations li.first {
+ padding: 0 0 0 0.7em;
+}
+
+/**
+ * Exposed filters.
+ */
+.exposed-filters .filters {
+ float: right;
+ margin-left: 1em;
+ margin-right: 0;
+}
+.exposed-filters .form-item label {
+ float: right;
+}
+/* Current filters */
+.exposed-filters .additional-filters {
+ float: right;
+ margin-left: 1em;
+ margin-right: 0;
+}
diff --git a/core/modules/system/system.admin.css b/core/modules/system/system.admin.css
new file mode 100644
index 00000000000..7299484c38d
--- /dev/null
+++ b/core/modules/system/system.admin.css
@@ -0,0 +1,270 @@
+
+/**
+ * @file
+ * Styles for administration pages.
+ */
+
+/**
+ * Administration blocks.
+ */
+div.admin-panel {
+ margin: 0;
+ padding: 5px 5px 15px 5px;
+}
+div.admin-panel .description {
+ margin: 0 0 3px;
+ padding: 2px 0 3px 0;
+}
+div.admin-panel .body {
+ padding: 0 4px 2px 8px; /* LTR */
+}
+div.admin {
+ padding-top: 15px;
+}
+div.admin .left {
+ float: left; /* LTR */
+ width: 47%;
+ margin-left: 1em; /* LTR */
+}
+div.admin .right {
+ float: right; /* LTR */
+ width: 47%;
+ margin-right: 1em; /* LTR */
+}
+div.admin .expert-link {
+ text-align: right; /* LTR */
+ margin-right: 1em; /* LTR */
+ padding-right: 4px; /* LTR */
+}
+
+/**
+ * Markup generated by theme_system_compact_link().
+ */
+.compact-link {
+ margin: 0 0 0.5em 0;
+}
+
+/**
+ * Quick inline admin links.
+ */
+small .admin-link:before {
+ content: '[';
+}
+small .admin-link:after {
+ content: ']';
+}
+
+/**
+ * Modules page.
+ */
+#system-modules div.incompatible {
+ font-weight: bold;
+}
+div.admin-requirements,
+div.admin-required {
+ font-size: 0.9em;
+ color: #444;
+}
+span.admin-disabled {
+ color: #800;
+}
+span.admin-enabled {
+ color: #080;
+}
+span.admin-missing {
+ color: #f00;
+}
+a.module-link {
+ display: block;
+ padding: 1px 0 1px 20px; /* LTR */
+ white-space: nowrap;
+}
+a.module-link-help {
+ background: url(../../misc/help.png) 0 50% no-repeat; /* LTR */
+}
+a.module-link-permissions {
+ background: url(../../misc/permissions.png) 0 50% no-repeat; /* LTR */
+}
+a.module-link-configure {
+ background: url(../../misc/configure.png) 0 50% no-repeat; /* LTR */
+}
+.module-help {
+ margin-left: 1em; /* LTR */
+ float: right; /* LTR */
+}
+
+/**
+ * Status report.
+ */
+table.system-status-report td {
+ padding: 6px;
+ vertical-align: middle;
+}
+table.system-status-report tr.merge-up td {
+ padding: 0 6px 8px 28px; /* LTR */
+}
+table.system-status-report td.status-icon {
+ width: 16px;
+ padding-right: 0; /* LTR */
+}
+table.system-status-report td.status-icon div {
+ background-repeat: no-repeat;
+ height: 16px;
+ width: 16px;
+}
+table.system-status-report tr.error td.status-icon div {
+ background-image: url(../../misc/message-16-error.png);
+}
+table.system-status-report tr.warning td.status-icon div {
+ background-image: url(../../misc/message-16-warning.png);
+}
+tr.merge-down,
+tr.merge-down td {
+ border-bottom-width: 0 !important;
+}
+tr.merge-up,
+tr.merge-up td {
+ border-top-width: 0 !important;
+}
+
+/**
+ * Theme settings.
+ */
+.theme-settings-left {
+ float: left;
+ width: 49%;
+}
+.theme-settings-right {
+ float: right;
+ width: 49%;
+}
+.theme-settings-bottom {
+ clear: both;
+}
+
+/**
+ * Appearance page.
+ */
+table.screenshot {
+ margin-right: 1em; /* LTR */
+}
+.theme-info h2 {
+ margin-bottom: 0;
+}
+.theme-info p {
+ margin-top: 0;
+}
+.system-themes-list {
+ margin-bottom: 20px;
+}
+.system-themes-list-disabled {
+ border-top: 1px solid #cdcdcd;
+ padding-top: 20px;
+}
+.system-themes-list h2 {
+ margin: 0;
+}
+.theme-selector {
+ padding-top: 20px;
+}
+.theme-selector .screenshot,
+.theme-selector .no-screenshot {
+ border: 1px solid #e0e0d8;
+ padding: 2px;
+ vertical-align: bottom;
+ width: 294px;
+ height: 219px;
+ line-height: 219px;
+ text-align: center;
+}
+.theme-default .screenshot {
+ border: 1px solid #aaa;
+}
+.system-themes-list-enabled .theme-selector .screenshot,
+.system-themes-list-enabled .theme-selector .no-screenshot {
+ float: left; /* LTR */
+ margin: 0 20px 0 0; /* LTR */
+}
+.system-themes-list-disabled .theme-selector .screenshot,
+.system-themes-list-disabled .theme-selector .no-screenshot {
+ width: 194px;
+ height: 144px;
+ line-height: 144px;
+}
+.theme-selector h3 {
+ font-weight: normal;
+}
+.theme-default h3 {
+ font-weight: bold;
+}
+.system-themes-list-enabled .theme-selector h3 {
+ margin-top: 0;
+}
+.system-themes-list-disabled .theme-selector {
+ width: 300px;
+ float: left; /* LTR */
+ padding: 20px 20px 20px 0; /* LTR */
+}
+.system-themes-list-enabled .theme-info {
+ max-width: 940px;
+}
+.system-themes-list-disabled .theme-info {
+ min-height: 170px;
+}
+.theme-selector .incompatible {
+ margin-top: 10px;
+ font-weight: bold;
+}
+.theme-selector .operations {
+ margin: 10px 0 0 0;
+ padding: 0;
+}
+.theme-selector .operations li {
+ float: left; /* LTR */
+ margin: 0;
+ padding: 0 0.7em;
+ list-style-type: none;
+ border-right: 1px solid #cdcdcd; /* LTR */
+}
+.theme-selector .operations li.last {
+ padding: 0 0 0 0.7em; /* LTR */
+ border-right: none; /* LTR */
+}
+.theme-selector .operations li.first {
+ padding: 0 0.7em 0 0; /* LTR */
+}
+#system-themes-admin-form {
+ clear: left;
+}
+
+/**
+ * Exposed filters.
+ */
+.exposed-filters .filters {
+ float: left; /* LTR */
+ margin-right: 1em; /* LTR */
+}
+.exposed-filters .form-item {
+ margin: 0 0 0.1em 0;
+ padding: 0;
+}
+.exposed-filters .form-item label {
+ float: left; /* LTR */
+ font-weight: normal;
+ width: 10em;
+}
+.exposed-filters .form-select {
+ width: 14em;
+}
+/* Current filters */
+.exposed-filters .current-filters {
+ margin-bottom: 1em;
+}
+.exposed-filters .current-filters .placeholder {
+ font-style: normal;
+ font-weight: bold;
+}
+.exposed-filters .additional-filters {
+ float: left; /* LTR */
+ margin-right: 1em; /* LTR */
+}
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
new file mode 100644
index 00000000000..b08f4180663
--- /dev/null
+++ b/core/modules/system/system.admin.inc
@@ -0,0 +1,3173 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the system module.
+ */
+
+/**
+ * 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 'description' in hook_menu()
+ // or customized via menu module is used as title attribute.
+ if (!empty($item['localized_options']['attributes']['title'])) {
+ $item['description'] = $item['localized_options']['attributes']['title'];
+ unset($item['localized_options']['attributes']['title']);
+ }
+ $block = $item;
+ $block['content'] = '';
+ $block['content'] .= theme('admin_block_content', array('content' => system_admin_menu_block($item)));
+ if (!empty($block['content'])) {
+ $block['show'] = TRUE;
+ }
+
+ // 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', array('blocks' => $blocks));
+ }
+ else {
+ return t('You do not have any administrative items.');
+ }
+}
+
+/**
+ * Provide a single block from the administration menu as a page.
+ *
+ * This function is often a destination for these blocks.
+ * For example, 'admin/structure/types' needs to have a destination to be valid
+ * in the Drupal menu system, but too much information there might be
+ * hidden, so we supply the contents of the block.
+ *
+ * @return
+ * The output HTML.
+ */
+function system_admin_menu_block_page() {
+ $item = menu_get_item();
+ if ($content = system_admin_menu_block($item)) {
+ $output = theme('admin_block_content', array('content' => $content));
+ }
+ else {
+ $output = t('You do not have any administrative items.');
+ }
+ return $output;
+}
+
+/**
+ * Menu callback; prints a listing of admin tasks, organized by module.
+ */
+function system_admin_index() {
+ $module_info = system_get_info('module');
+ foreach ($module_info as $module => $info) {
+ $module_info[$module] = new stdClass();
+ $module_info[$module]->info = $info;
+ }
+ uasort($module_info, 'system_sort_modules_by_info_name');
+ $menu_items = array();
+
+ foreach ($module_info as $module => $info) {
+ // Only display a section if there are any available tasks.
+ if ($admin_tasks = system_get_module_admin_tasks($module, $info->info)) {
+ // Sort links by title.
+ uasort($admin_tasks, 'drupal_sort_title');
+ // Move 'Configure permissions' links to the bottom of each section.
+ $permission_key = "admin/people/permissions#module-$module";
+ if (isset($admin_tasks[$permission_key])) {
+ $permission_task = $admin_tasks[$permission_key];
+ unset($admin_tasks[$permission_key]);
+ $admin_tasks[$permission_key] = $permission_task;
+ }
+
+ $menu_items[$info->info['name']] = array($info->info['description'], $admin_tasks);
+ }
+ }
+ return theme('system_admin_index', array('menu_items' => $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/config');
+ $content = system_admin_menu_block($item);
+
+ $output = theme('admin_block_content', array('content' => $content));
+
+ return $output;
+}
+
+/**
+ * Menu callback; displays a listing of all themes.
+ */
+function system_themes_page() {
+ // Get current list of themes.
+ $themes = system_rebuild_theme_data();
+ uasort($themes, 'system_sort_modules_by_info_name');
+
+ $theme_default = variable_get('theme_default', 'bartik');
+ $theme_groups = array();
+
+ foreach ($themes as &$theme) {
+ if (!empty($theme->info['hidden'])) {
+ continue;
+ }
+ $admin_theme_options[$theme->name] = $theme->info['name'];
+ $theme->is_default = ($theme->name == $theme_default);
+
+ // Identify theme screenshot.
+ $theme->screenshot = NULL;
+ // 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);
+ $theme_keys[] = $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'])) {
+ $theme->screenshot = array(
+ 'path' => $themes[$theme_key]->info['screenshot'],
+ 'alt' => t('Screenshot for !theme theme', array('!theme' => $theme->info['name'])),
+ 'title' => t('Screenshot for !theme theme', array('!theme' => $theme->info['name'])),
+ 'attributes' => array('class' => array('screenshot')),
+ );
+ break;
+ }
+ }
+
+ if (empty($theme->status)) {
+ // Ensure this theme is compatible with this version of core.
+ // Require the 'content' region to make sure the main page
+ // content has a common place in all themes.
+ $theme->incompatible_core = !isset($theme->info['core']) || ($theme->info['core'] != DRUPAL_CORE_COMPATIBILITY) || (!isset($theme->info['regions']['content']));
+ $theme->incompatible_php = version_compare(phpversion(), $theme->info['php']) < 0;
+ }
+ $query['token'] = drupal_get_token('system-theme-operation-link');
+ $theme->operations = array();
+ if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php) {
+ // Create the operations links.
+ $query['theme'] = $theme->name;
+ if (drupal_theme_access($theme)) {
+ $theme->operations[] = array(
+ 'title' => t('Settings'),
+ 'href' => 'admin/appearance/settings/' . $theme->name,
+ 'attributes' => array('title' => t('Settings for !theme theme', array('!theme' => $theme->info['name']))),
+ );
+ }
+ if (!empty($theme->status)) {
+ if (!$theme->is_default) {
+ $theme->operations[] = array(
+ 'title' => t('Disable'),
+ 'href' => 'admin/appearance/disable',
+ 'query' => $query,
+ 'attributes' => array('title' => t('Disable !theme theme', array('!theme' => $theme->info['name']))),
+ );
+ $theme->operations[] = array(
+ 'title' => t('Set default'),
+ 'href' => 'admin/appearance/default',
+ 'query' => $query,
+ 'attributes' => array('title' => t('Set !theme as default theme', array('!theme' => $theme->info['name']))),
+ );
+ }
+ }
+ else {
+ $theme->operations[] = array(
+ 'title' => t('Enable'),
+ 'href' => 'admin/appearance/enable',
+ 'query' => $query,
+ 'attributes' => array('title' => t('Enable !theme theme', array('!theme' => $theme->info['name']))),
+ );
+ $theme->operations[] = array(
+ 'title' => t('Enable and set default'),
+ 'href' => 'admin/appearance/default',
+ 'query' => $query,
+ 'attributes' => array('title' => t('Enable !theme as default theme', array('!theme' => $theme->info['name']))),
+ );
+ }
+ }
+
+ // Add notes to default and administration theme.
+ $theme->notes = array();
+ $theme->classes = array();
+ if ($theme->is_default) {
+ $theme->classes[] = 'theme-default';
+ $theme->notes[] = t('default theme');
+ }
+
+ // Sort enabled and disabled themes into their own groups.
+ $theme_groups[$theme->status ? 'enabled' : 'disabled'][] = $theme;
+ }
+
+ // There are two possible theme groups.
+ $theme_group_titles = array(
+ 'enabled' => format_plural(count($theme_groups['enabled']), 'Enabled theme', 'Enabled themes'),
+ );
+ if (!empty($theme_groups['disabled'])) {
+ $theme_group_titles['disabled'] = format_plural(count($theme_groups['disabled']), 'Disabled theme', 'Disabled themes');
+ }
+
+ uasort($theme_groups['enabled'], 'system_sort_themes');
+ drupal_alter('system_themes_page', $theme_groups);
+
+ $admin_form = drupal_get_form('system_themes_admin_form', $admin_theme_options);
+ return theme('system_themes_page', array('theme_groups' => $theme_groups, 'theme_group_titles' => $theme_group_titles)) . drupal_render($admin_form);
+}
+
+/**
+ * Form to select the administration theme.
+ *
+ * @ingroup forms
+ * @see system_themes_admin_form_submit()
+ */
+function system_themes_admin_form($form, &$form_state, $theme_options) {
+ // Administration theme settings.
+ $form['admin_theme'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Administration theme'),
+ );
+ $form['admin_theme']['admin_theme'] = array(
+ '#type' => 'select',
+ '#options' => array(0 => t('Default theme')) + $theme_options,
+ '#title' => t('Administration theme'),
+ '#description' => t('Choose "Default theme" to always 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 the administration theme when editing or creating content'),
+ '#default_value' => variable_get('node_admin_theme', '0'),
+ );
+ $form['admin_theme']['actions'] = array('#type' => 'actions');
+ $form['admin_theme']['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save configuration'),
+ );
+ return $form;
+}
+
+/**
+ * Process system_themes_admin_form form submissions.
+ */
+function system_themes_admin_form_submit($form, &$form_state) {
+ drupal_set_message(t('The configuration options have been saved.'));
+ variable_set('admin_theme', $form_state['values']['admin_theme']);
+ variable_set('node_admin_theme', $form_state['values']['node_admin_theme']);
+}
+
+/**
+ * Menu callback; Enables a theme.
+ */
+function system_theme_enable() {
+ if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) {
+ $theme = $_REQUEST['theme'];
+ // Get current list of themes.
+ $themes = list_themes();
+
+ // Check if the specified theme is one recognized by the system.
+ if (!empty($themes[$theme])) {
+ theme_enable(array($theme));
+ drupal_set_message(t('The %theme theme has been enabled.', array('%theme' => $themes[$theme]->info['name'])));
+ }
+ else {
+ drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
+ }
+ drupal_goto('admin/appearance');
+ }
+ return drupal_access_denied();
+}
+
+/**
+ * Menu callback; Disables a theme.
+ */
+function system_theme_disable() {
+ if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) {
+ $theme = $_REQUEST['theme'];
+ // Get current list of themes.
+ $themes = list_themes();
+
+ // Check if the specified theme is one recognized by the system.
+ if (!empty($themes[$theme])) {
+ if ($theme == variable_get('theme_default', 'bartik')) {
+ // Don't disable the default theme.
+ drupal_set_message(t('%theme is the default theme and cannot be disabled.', array('%theme' => $themes[$theme]->info['name'])), 'error');
+ }
+ else {
+ theme_disable(array($theme));
+ drupal_set_message(t('The %theme theme has been disabled.', array('%theme' => $themes[$theme]->info['name'])));
+ }
+ }
+ else {
+ drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
+ }
+ drupal_goto('admin/appearance');
+ }
+ return drupal_access_denied();
+}
+
+/**
+ * Menu callback; Set the default theme.
+ */
+function system_theme_default() {
+ if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) {
+ $theme = $_REQUEST['theme'];
+ // Get current list of themes.
+ $themes = list_themes();
+
+ // Check if the specified theme is one recognized by the system.
+ if (!empty($themes[$theme])) {
+ // Enable the theme if it is currently disabled.
+ if (empty($themes[$theme]->status)) {
+ theme_enable(array($theme));
+ }
+ // Set the default theme.
+ variable_set('theme_default', $theme);
+
+ // Rebuild the menu. This duplicates the menu_rebuild() in theme_enable().
+ // However, modules must know the current default theme in order to use
+ // this information in hook_menu() or hook_menu_alter() implementations,
+ // and doing the variable_set() before the theme_enable() could result
+ // in a race condition where the theme is default but not enabled.
+ menu_rebuild();
+
+ // The status message depends on whether an admin theme is currently in use:
+ // a value of 0 means the admin theme is set to be the default theme.
+ $admin_theme = variable_get('admin_theme', 0);
+ if ($admin_theme != 0 && $admin_theme != $theme) {
+ drupal_set_message(t('Please note that the administration theme is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', array(
+ '%admin_theme' => $themes[$admin_theme]->info['name'],
+ '%selected_theme' => $themes[$theme]->info['name'],
+ )));
+ }
+ else {
+ drupal_set_message(t('%theme is now the default theme.', array('%theme' => $themes[$theme]->info['name'])));
+ }
+ }
+ else {
+ drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
+ }
+ drupal_goto('admin/appearance');
+ }
+ return drupal_access_denied();
+}
+
+/**
+ * Form builder; display theme configuration for entire site and individual themes.
+ *
+ * @param $key
+ * A theme name.
+ * @return
+ * The form structure.
+ * @ingroup forms
+ * @see system_theme_settings_submit()
+ */
+function system_theme_settings($form, &$form_state, $key = '') {
+ // Default settings are defined in theme_get_setting() in includes/theme.inc
+ if ($key) {
+ $var = 'theme_' . $key . '_settings';
+ $themes = system_rebuild_theme_data();
+ $features = $themes[$key]->info['features'];
+ }
+ else {
+ $var = 'theme_settings';
+ }
+
+ $form['var'] = array('#type' => 'hidden', '#value' => $var);
+
+ // Toggle settings
+ $toggles = array(
+ 'logo' => t('Logo'),
+ 'name' => t('Site name'),
+ 'slogan' => t('Site slogan'),
+ 'node_user_picture' => t('User pictures in posts'),
+ 'comment_user_picture' => t('User pictures in comments'),
+ 'comment_user_verification' => t('User verification status in comments'),
+ 'favicon' => t('Shortcut icon'),
+ 'main_menu' => t('Main menu'),
+ 'secondary_menu' => t('Secondary menu'),
+ );
+
+ // 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('comment')) {
+ $disabled['toggle_comment_user_picture'] = TRUE;
+ $disabled['toggle_comment_user_verification'] = 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)) {
+ $form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('toggle_' . $name, $key));
+ // Disable checkboxes for features not supported in the current configuration.
+ if (isset($disabled['toggle_' . $name])) {
+ $form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE;
+ }
+ }
+ }
+
+ if (!element_children($form['theme_settings'])) {
+ // 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;
+ }
+
+ // 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' => array('theme-settings-bottom')),
+ );
+ $form['logo']['default_logo'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use the default logo'),
+ '#default_value' => theme_get_setting('default_logo', $key),
+ '#tree' => FALSE,
+ '#description' => t('Check here if you want the theme to use the logo supplied with it.')
+ );
+ $form['logo']['settings'] = array(
+ '#type' => 'container',
+ '#states' => array(
+ // Hide the logo settings when using the default logo.
+ 'invisible' => array(
+ 'input[name="default_logo"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $logo_path = theme_get_setting('logo_path', $key);
+ // If $logo_path is a public:// URI, display the path relative to the files
+ // directory; stream wrappers are not end-user friendly.
+ if (file_uri_scheme($logo_path) == 'public') {
+ $logo_path = file_uri_target($logo_path);
+ }
+ $form['logo']['settings']['logo_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path to custom logo'),
+ '#default_value' => $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']['settings']['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'),
+ '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
+ );
+ $form['favicon']['default_favicon'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Use the default shortcut icon.'),
+ '#default_value' => theme_get_setting('default_favicon', $key),
+ '#description' => t('Check here if you want the theme to use the default shortcut icon.')
+ );
+ $form['favicon']['settings'] = array(
+ '#type' => 'container',
+ '#states' => array(
+ // Hide the favicon settings when using the default favicon.
+ 'invisible' => array(
+ 'input[name="default_favicon"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $favicon_path = theme_get_setting('favicon_path', $key);
+ // If $favicon_path is a public:// URI, display the path relative to the
+ // files directory; stream wrappers are not end-user friendly.
+ if (file_uri_scheme($favicon_path) == 'public') {
+ $favicon_path = file_uri_target($favicon_path);
+ }
+ $form['favicon']['settings']['favicon_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path to custom icon'),
+ '#default_value' => $favicon_path,
+ '#description' => t('The path to the image file you would like to use as your custom shortcut icon.')
+ );
+ $form['favicon']['settings']['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) {
+ // Call engine-specific settings.
+ $function = $themes[$key]->prefix . '_engine_settings';
+ if (function_exists($function)) {
+ $form['engine_specific'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Theme-engine-specific settings'),
+ '#description' => t('These settings only exist for the themes based on the %engine theme engine.', array('%engine' => $themes[$key]->prefix)),
+ );
+ $function($form, $form_state);
+ }
+
+ // Create a list which includes the current theme and all its base themes.
+ if (isset($themes[$key]->base_themes)) {
+ $theme_keys = array_keys($themes[$key]->base_themes);
+ $theme_keys[] = $key;
+ }
+ else {
+ $theme_keys = array($key);
+ }
+
+ // Save the name of the current theme (if any), so that we can temporarily
+ // override the current theme and allow theme_get_setting() to work
+ // without having to pass the theme name to it.
+ $default_theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : NULL;
+ $GLOBALS['theme_key'] = $key;
+
+ // Process the theme and all its base themes.
+ foreach ($theme_keys as $theme) {
+ // Include the theme-settings.php file.
+ $filename = DRUPAL_ROOT . '/' . str_replace("/$theme.info", '', $themes[$theme]->filename) . '/theme-settings.php';
+ if (file_exists($filename)) {
+ require_once $filename;
+ }
+
+ // Call theme-specific settings.
+ $function = $theme . '_form_system_theme_settings_alter';
+ if (function_exists($function)) {
+ $function($form, $form_state);
+ }
+ }
+
+ // Restore the original current theme.
+ if (isset($default_theme)) {
+ $GLOBALS['theme_key'] = $default_theme;
+ }
+ else {
+ unset($GLOBALS['theme_key']);
+ }
+ }
+
+ $form = system_settings_form($form);
+ // We don't want to call system_settings_form_submit(), so change #submit.
+ array_pop($form['#submit']);
+ $form['#submit'][] = 'system_theme_settings_submit';
+ $form['#validate'][] = 'system_theme_settings_validate';
+ return $form;
+}
+
+/**
+ * Validator for the system_theme_settings() form.
+ */
+function system_theme_settings_validate($form, &$form_state) {
+ // Handle file uploads.
+ $validators = array('file_validate_is_image' => array());
+
+ // Check for a new uploaded logo.
+ $file = file_save_upload('logo_upload', $validators);
+ if (isset($file)) {
+ // File upload was attempted.
+ if ($file) {
+ // Put the temporary file in form_values so we can save it on submit.
+ $form_state['values']['logo_upload'] = $file;
+ }
+ else {
+ // File upload failed.
+ form_set_error('logo_upload', t('The logo could not be uploaded.'));
+ }
+ }
+
+ $validators = array('file_validate_extensions' => array('ico png gif jpg jpeg apng svg'));
+
+ // Check for a new uploaded favicon.
+ $file = file_save_upload('favicon_upload', $validators);
+ if (isset($file)) {
+ // File upload was attempted.
+ if ($file) {
+ // Put the temporary file in form_values so we can save it on submit.
+ $form_state['values']['favicon_upload'] = $file;
+ }
+ else {
+ // File upload failed.
+ form_set_error('logo_upload', t('The favicon could not be uploaded.'));
+ }
+ }
+
+ // If the user provided a path for a logo or favicon file, make sure a file
+ // exists at that path.
+ if ($form_state['values']['logo_path']) {
+ $path = _system_theme_settings_validate_path($form_state['values']['logo_path']);
+ if (!$path) {
+ form_set_error('logo_path', t('The custom logo path is invalid.'));
+ }
+ }
+ if ($form_state['values']['favicon_path']) {
+ $path = _system_theme_settings_validate_path($form_state['values']['favicon_path']);
+ if (!$path) {
+ form_set_error('favicon_path', t('The custom favicon path is invalid.'));
+ }
+ }
+}
+
+/**
+ * Helper function for the system_theme_settings form.
+ *
+ * Attempts to validate normal system paths, paths relative to the public files
+ * directory, or stream wrapper URIs. If the given path is any of the above,
+ * returns a valid path or URI that the theme system can display.
+ *
+ * @param $path
+ * A path relative to the Drupal root or to the public files directory, or
+ * a stream wrapper URI.
+ * @return mixed
+ * A valid path that can be displayed through the theme system, or FALSE if
+ * the path could not be validated.
+ */
+function _system_theme_settings_validate_path($path) {
+ if (drupal_realpath($path)) {
+ // The path is relative to the Drupal root, or is a valid URI.
+ return $path;
+ }
+ $uri = 'public://' . $path;
+ if (file_exists($uri)) {
+ return $uri;
+ }
+ return FALSE;
+}
+
+/**
+ * Process system_theme_settings form submissions.
+ */
+function system_theme_settings_submit($form, &$form_state) {
+ $values = $form_state['values'];
+
+ // If the user uploaded a new logo or favicon, save it to a permanent location
+ // and use it in place of the default theme-provided file.
+ if ($file = $values['logo_upload']) {
+ unset($values['logo_upload']);
+ $filename = file_unmanaged_copy($file->uri);
+ $values['default_logo'] = 0;
+ $values['logo_path'] = $filename;
+ $values['toggle_logo'] = 1;
+ }
+ if ($file = $values['favicon_upload']) {
+ unset($values['favicon_upload']);
+ $filename = file_unmanaged_copy($file->uri);
+ $values['default_favicon'] = 0;
+ $values['favicon_path'] = $filename;
+ $values['toggle_favicon'] = 1;
+ }
+
+ // If the user entered a path relative to the system files directory for
+ // a logo or favicon, store a public:// URI so the theme system can handle it.
+ if (!empty($values['logo_path'])) {
+ $values['logo_path'] = _system_theme_settings_validate_path($values['logo_path']);
+ }
+ if (!empty($values['favicon_path'])) {
+ $values['favicon_path'] = _system_theme_settings_validate_path($values['favicon_path']);
+ }
+
+ if (empty($values['default_favicon']) && !empty($values['favicon_path'])) {
+ $values['favicon_mimetype'] = file_get_mimetype($values['favicon_path']);
+ }
+ $key = $values['var'];
+
+ // 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.'));
+
+ cache_clear_all();
+}
+
+/**
+ * Recursively check compatibility.
+ *
+ * @param $incompatible
+ * An associative array which at the end of the check contains all
+ * incompatible files as the keys, their values being TRUE.
+ * @param $files
+ * The set of files that will be tested.
+ * @param $file
+ * The file at which the check starts.
+ * @return
+ * Returns TRUE if an incompatible file is found, NULL (no return value)
+ * otherwise.
+ */
+function _system_is_incompatible(&$incompatible, $files, $file) {
+ if (isset($incompatible[$file->name])) {
+ return TRUE;
+ }
+ // Recursively traverse required modules, looking for incompatible modules.
+ foreach ($file->requires as $requires) {
+ if (isset($files[$requires]) && _system_is_incompatible($incompatible, $files, $files[$requires])) {
+ $incompatible[$file->name] = TRUE;
+ return TRUE;
+ }
+ }
+}
+
+/**
+ * Menu callback; provides module enable/disable interface.
+ *
+ * The list of modules gets populated by module.info files, which contain each
+ * module's name, description, and information about which modules it requires.
+ * See drupal_parse_info_file() for information on module.info descriptors.
+ *
+ * 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.
+ *
+ * @param $form_state
+ * An associative array containing the current state of the form.
+ *
+ * @return
+ * The form array.
+ *
+ * @ingroup forms
+ * @see theme_system_modules()
+ * @see system_modules_submit()
+ */
+function system_modules($form, $form_state = array()) {
+ // Get current list of modules.
+ $files = system_rebuild_module_data();
+
+ // Remove hidden modules from display list.
+ $visible_files = $files;
+ foreach ($visible_files as $filename => $file) {
+ if (!empty($file->info['hidden'])) {
+ unset($visible_files[$filename]);
+ }
+ }
+
+ uasort($visible_files, 'system_sort_modules_by_info_name');
+
+ // 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.
+ if (!empty($form_state['storage'])) {
+ return system_modules_confirm_form($visible_files, $form_state['storage']);
+ }
+
+ $modules = array();
+ $form['modules'] = array('#tree' => TRUE);
+
+ // Used when checking if module implements a help page.
+ $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;
+
+ // Used when displaying modules that are required by the install profile.
+ require_once DRUPAL_ROOT . '/includes/install.inc';
+ $distribution_name = check_plain(drupal_install_profile_distribution_name());
+
+ // Iterate through each of the modules.
+ foreach ($visible_files as $filename => $module) {
+ $extra = array();
+ $extra['enabled'] = (bool) $module->status;
+ if (!empty($module->info['required'] )) {
+ $extra['disabled'] = TRUE;
+ $extra['required_by'][] = $distribution_name . (!empty($module->info['explanation']) ? ' ('. $module->info['explanation'] .')' : '');
+ }
+
+ // 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)));
+ $extra['disabled'] = TRUE;
+ }
+ // Only display visible modules.
+ elseif (isset($visible_files[$requires])) {
+ $requires_name = $files[$requires]->info['name'];
+ 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;
+ }
+ 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));
+ }
+ }
+ }
+ // Generate link for module's help page, if there is one.
+ if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
+ if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
+ $extra['links']['help'] = array(
+ '#type' => 'link',
+ '#title' => t('Help'),
+ '#href' => "admin/help/$filename",
+ '#options' => array('attributes' => array('class' => array('module-link', 'module-link-help'), 'title' => t('Help'))),
+ );
+ }
+ }
+ // Generate link for module's permission, if the user has access to it.
+ if ($module->status && user_access('administer permissions') && in_array($filename, module_implements('permission'))) {
+ $extra['links']['permissions'] = array(
+ '#type' => 'link',
+ '#title' => t('Permissions'),
+ '#href' => 'admin/people/permissions',
+ '#options' => array('fragment' => 'module-' . $filename, 'attributes' => array('class' => array('module-link', 'module-link-permissions'), 'title' => t('Configure permissions'))),
+ );
+ }
+ // Generate link for module's configuration page, if the module provides
+ // one.
+ if ($module->status && isset($module->info['configure'])) {
+ $configure_link = menu_get_item($module->info['configure']);
+ if ($configure_link['access']) {
+ $extra['links']['configure'] = array(
+ '#type' => 'link',
+ '#title' => t('Configure'),
+ '#href' => $configure_link['href'],
+ '#options' => array('attributes' => array('class' => array('module-link', 'module-link-configure'), 'title' => $configure_link['description'])),
+ );
+ }
+ }
+
+ // 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) {
+ // Hidden modules are unset already.
+ if (isset($visible_files[$required_by])) {
+ if ($files[$required_by]->status == 1 && $module->status == 1) {
+ $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
+ $extra['disabled'] = TRUE;
+ }
+ else {
+ $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
+ }
+ }
+ }
+ $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
+ }
+
+ // 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' => array('checkbox')),
+ t('Name'),
+ t('Version'),
+ t('Description'),
+ array('data' => t('Operations'), 'colspan' => 3),
+ ),
+ // Ensure that the "Core" package fieldset comes first.
+ '#weight' => $package == 'Core' ? -10 : NULL,
+ );
+ }
+
+ // Lastly, sort all fieldsets by title.
+ uasort($form['modules'], 'element_sort_by_title');
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save configuration'),
+ );
+ $form['#action'] = url('admin/modules/list/confirm');
+
+ return $form;
+}
+
+/**
+ * Array sorting callback; sorts modules or themes by their name.
+ */
+function system_sort_modules_by_info_name($a, $b) {
+ return strcasecmp($a->info['name'], $b->info['name']);
+}
+
+/**
+ * Array sorting callback; sorts modules or themes by their name.
+ */
+function system_sort_themes($a, $b) {
+ if ($a->is_default) {
+ return -1;
+ }
+ if ($b->is_default) {
+ return 1;
+ }
+ return strcasecmp($a->info['name'], $b->info['name']);
+}
+
+/**
+ * Build a table row for the system modules page.
+ */
+function _system_modules_build_row($info, $extra) {
+ // Add in the defaults.
+ $extra += array(
+ 'requires' => array(),
+ 'required_by' => array(),
+ 'disabled' => FALSE,
+ 'enabled' => FALSE,
+ 'links' => array(),
+ );
+ $form = array(
+ '#tree' => TRUE,
+ );
+ // Set the basic properties.
+ $form['name'] = array(
+ '#markup' => $info['name'],
+ );
+ $form['description'] = array(
+ '#markup' => t($info['description']),
+ );
+ $form['version'] = array(
+ '#markup' => $info['version'],
+ );
+ $form['#requires'] = $extra['requires'];
+ $form['#required_by'] = $extra['required_by'];
+
+ // Check the compatibilities.
+ $compatible = TRUE;
+ $status_short = '';
+ $status_long = '';
+
+ // Check the core compatibility.
+ if (!isset($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY) {
+ $compatible = FALSE;
+ $status_short .= t('Incompatible with this version of Drupal core.');
+ $status_long .= t('This version is not compatible with Drupal !core_version and should be replaced.', array('!core_version' => DRUPAL_CORE_COMPATIBILITY));
+ }
+
+ // 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');
+ $php_required = $info['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', array('path' => 'misc/watchdog-error.png', 'alt' => $status_short, 'title' => $status_short)),
+ );
+ $form['description']['#markup'] .= theme('system_modules_incompatible', array('message' => $status_long));
+ }
+
+ // Build operation links.
+ foreach (array('help', 'permissions', 'configure') as $key) {
+ $form['links'][$key] = (isset($extra['links'][$key]) ? $extra['links'][$key] : array());
+ }
+
+ return $form;
+}
+
+/**
+ * Display confirmation form for required modules.
+ *
+ * @param $modules
+ * Array of module file objects as returned from system_rebuild_module_data().
+ * @param $storage
+ * The contents of $form_state['storage']; an array with two
+ * elements: the list of required modules and the list of status
+ * form field values from the previous screen.
+ * @ingroup forms
+ */
+function system_modules_confirm_form($modules, $storage) {
+ $items = array();
+
+ $form['validation_modules'] = array('#type' => 'value', '#value' => $modules);
+ $form['status']['#tree'] = TRUE;
+
+ foreach ($storage['more_required'] as $info) {
+ $t_argument = array(
+ '@module' => $info['name'],
+ '@required' => implode(', ', $info['requires']),
+ );
+ $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);
+ }
+
+ foreach ($storage['missing_modules'] as $name => $info) {
+ $t_argument = array(
+ '@module' => $name,
+ '@depends' => implode(', ', $info['depends']),
+ );
+ $items[] = format_plural(count($info['depends']), 'The @module module is missing, so the following module will be disabled: @depends.', 'The @module module is missing, so the following modules will be disabled: @depends.', $t_argument);
+ }
+
+ $form['text'] = array('#markup' => theme('item_list', array('items' => $items)));
+
+ if ($form) {
+ // Set some default form values
+ $form = confirm_form(
+ $form,
+ t('Some required modules must be enabled'),
+ 'admin/modules',
+ t('Would you like to continue with the above?'),
+ t('Continue'),
+ t('Cancel'));
+ return $form;
+ }
+}
+
+/**
+ * Submit callback; handles modules form submission.
+ */
+function system_modules_submit($form, &$form_state) {
+ include_once DRUPAL_ROOT . '/includes/install.inc';
+
+ // Builds list of modules.
+ $modules = array();
+ // If we're not coming from the confirmation form, build the list of modules.
+ if (empty($form_state['storage'])) {
+ // If we're not coming from the confirmation form, build the module list.
+ foreach ($form_state['values']['modules'] as $group_name => $group) {
+ foreach ($group as $module => $enabled) {
+ $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']);
+ }
+ }
+ }
+ else {
+ // If we are coming from the confirmation form, fetch
+ // the modules out of $form_state.
+ $modules = $form_state['storage']['modules'];
+ }
+
+ // Collect data for all modules to be able to determine dependencies.
+ $files = system_rebuild_module_data();
+
+ // Sorts modules by weight.
+ $sort = array();
+ foreach (array_keys($modules) as $module) {
+ $sort[$module] = $files[$module]->sort;
+ }
+ array_multisort($sort, $modules);
+
+ // Makes sure all required modules are set to be enabled.
+ $more_required = array();
+ $missing_modules = array();
+ foreach ($modules as $name => $module) {
+ if ($module['enabled']) {
+ // Checks that all dependencies are set to be enabled. Stores the ones
+ // that are not in $dependencies variable so that the user can be alerted
+ // in the confirmation form that more modules need to be enabled.
+ $dependencies = array();
+ foreach (array_keys($files[$name]->requires) as $required) {
+ if (empty($modules[$required]['enabled'])) {
+ if (isset($files[$required])) {
+ $dependencies[] = $files[$required]->info['name'];
+ $modules[$required]['enabled'] = TRUE;
+ }
+ else {
+ $missing_modules[$required]['depends'][] = $name;
+ $modules[$name]['enabled'] = FALSE;
+ }
+ }
+ }
+
+ // Stores additional modules that need to be enabled in $more_required.
+ if (!empty($dependencies)) {
+ $more_required[$name] = array(
+ 'name' => $files[$name]->info['name'],
+ 'requires' => $dependencies,
+ );
+ }
+ }
+ }
+
+ // Redirects to confirmation form if more modules need to be enabled.
+ if ((!empty($more_required) || !empty($missing_modules)) && !isset($form_state['values']['confirm'])) {
+ $form_state['storage'] = array(
+ 'more_required' => $more_required,
+ 'modules' => $modules,
+ 'missing_modules' => $missing_modules,
+ );
+ $form_state['rebuild'] = TRUE;
+ return;
+ }
+
+ // Invokes hook_requirements('install'). If failures are detected, makes sure
+ // the dependent modules aren't installed either.
+ foreach ($modules as $name => $module) {
+ // Only invoke hook_requirements() on modules that are going to be installed.
+ if ($module['enabled'] && drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
+ if (!drupal_check_module($name)) {
+ $modules[$name]['enabled'] = FALSE;
+ foreach (array_keys($files[$name]->required_by) as $required_by) {
+ $modules[$required_by]['enabled'] = FALSE;
+ }
+ }
+ }
+ }
+
+ // Initializes array of actions.
+ $actions = array(
+ 'enable' => array(),
+ 'disable' => array(),
+ 'install' => array(),
+ );
+
+ // Builds arrays of modules that need to be enabled, disabled, and installed.
+ foreach ($modules as $name => $module) {
+ if ($module['enabled']) {
+ if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) {
+ $actions['install'][] = $name;
+ $actions['enable'][] = $name;
+ }
+ elseif (!module_exists($name)) {
+ $actions['enable'][] = $name;
+ }
+ }
+ elseif (module_exists($name)) {
+ $actions['disable'][] = $name;
+ }
+ }
+
+ // Gets list of modules prior to install process, unsets $form_state['storage']
+ // so we don't get redirected back to the confirmation form.
+ $pre_install_list = module_list();
+ unset($form_state['storage']);
+
+ // Reverse the 'enable' list, to order dependencies before dependents.
+ krsort($actions['enable']);
+
+ // Installs, enables, and disables modules.
+ module_enable($actions['enable'], FALSE);
+ module_disable($actions['disable'], FALSE);
+
+ // Gets module list after install process, flushes caches and displays a
+ // message if there are changes.
+ $post_install_list = module_list(TRUE);
+ if ($pre_install_list != $post_install_list) {
+ drupal_flush_all_caches();
+ drupal_set_message(t('The configuration options have been saved.'));
+ }
+
+ $form_state['redirect'] = 'admin/modules';
+}
+
+/**
+ * Uninstall functions
+ */
+
+/**
+ * Builds a form of currently disabled modules.
+ *
+ * @ingroup forms
+ * @see system_modules_uninstall_validate()
+ * @see system_modules_uninstall_submit()
+ * @param $form_state['values']
+ * Submitted form values.
+ * @return
+ * A form array representing the currently disabled modules.
+ */
+function system_modules_uninstall($form, $form_state = NULL) {
+ // Make sure the install API is available.
+ include_once DRUPAL_ROOT . '/includes/install.inc';
+
+ // Display the confirm form if any modules have been submitted.
+ if (!empty($form_state['storage']) && $confirm_form = system_modules_uninstall_confirm_form($form_state['storage'])) {
+ return $confirm_form;
+ }
+
+ // Get a list of disabled, installed modules.
+ $all_modules = system_rebuild_module_data();
+ $disabled_modules = array();
+ foreach ($all_modules as $name => $module) {
+ if (empty($module->status) && $module->schema_version > SCHEMA_UNINSTALLED) {
+ $disabled_modules[$name] = $module;
+ }
+ }
+
+ // Only build the rest of the form if there are any modules available to
+ // uninstall.
+ if (!empty($disabled_modules)) {
+ $profile = drupal_get_profile();
+ uasort($disabled_modules, 'system_sort_modules_by_info_name');
+ $form['uninstall'] = array('#tree' => TRUE);
+ foreach ($disabled_modules as $module) {
+ $module_name = $module->info['name'] ? $module->info['name'] : $module->name;
+ $form['modules'][$module->name]['#module_name'] = $module_name;
+ $form['modules'][$module->name]['name']['#markup'] = $module_name;
+ $form['modules'][$module->name]['description']['#markup'] = t($module->info['description']);
+ $form['uninstall'][$module->name] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Uninstall @module module', array('@module' => $module_name)),
+ '#title_display' => 'invisible',
+ );
+ // All modules which depend on this one must be uninstalled first, before
+ // we can allow this module to be uninstalled. (The install profile is
+ // excluded from this list.)
+ foreach (array_keys($module->required_by) as $dependent) {
+ if ($dependent != $profile && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
+ $dependent_name = isset($all_modules[$dependent]->info['name']) ? $all_modules[$dependent]->info['name'] : $dependent;
+ $form['modules'][$module->name]['#required_by'][] = $dependent_name;
+ $form['uninstall'][$module->name]['#disabled'] = TRUE;
+ }
+ }
+ }
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Uninstall'),
+ );
+ $form['#action'] = url('admin/modules/uninstall/confirm');
+ }
+ else {
+ $form['modules'] = array();
+ }
+
+ return $form;
+}
+
+/**
+ * Confirm uninstall of selected modules.
+ *
+ * @ingroup forms
+ * @param $storage
+ * An associative array of modules selected to be uninstalled.
+ * @return
+ * A form array representing modules to confirm.
+ */
+function system_modules_uninstall_confirm_form($storage) {
+ // Nothing to build.
+ if (empty($storage)) {
+ return;
+ }
+
+ // Construct the hidden form elements and list items.
+ foreach (array_filter($storage['uninstall']) as $module => $value) {
+ $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info');
+ $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;
+ $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', array('items' => $uninstall)));
+ $form = confirm_form(
+ $form,
+ t('Confirm uninstall'),
+ 'admin/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/modules/uninstall');
+ }
+}
+
+/**
+ * Processes the submitted uninstall form.
+ */
+function system_modules_uninstall_submit($form, &$form_state) {
+ // Make sure the install API is available.
+ include_once DRUPAL_ROOT . '/includes/install.inc';
+
+ if (!empty($form['#confirmed'])) {
+ // Call the uninstall routine for each selected module.
+ $modules = array_keys($form_state['values']['uninstall']);
+ drupal_uninstall_modules($modules);
+ drupal_set_message(t('The selected modules have been uninstalled.'));
+
+ $form_state['redirect'] = 'admin/modules/uninstall';
+ }
+ else {
+ $form_state['storage'] = $form_state['values'];
+ $form_state['rebuild'] = TRUE;
+ }
+}
+
+/**
+ * Menu callback. Display blocked IP addresses.
+ *
+ * @param $default_ip
+ * Optional IP address to be passed on to drupal_get_form() for
+ * use as the default value of the IP address form field.
+ */
+function system_ip_blocking($default_ip = '') {
+ $rows = array();
+ $header = array(t('Blocked IP addresses'), t('Operations'));
+ $result = db_query('SELECT * FROM {blocked_ips}');
+ foreach ($result as $ip) {
+ $rows[] = array(
+ $ip->ip,
+ l(t('delete'), "admin/config/people/ip-blocking/delete/$ip->iid"),
+ );
+ }
+
+ $build['system_ip_blocking_form'] = drupal_get_form('system_ip_blocking_form', $default_ip);
+
+ $build['system_ip_blocking_table'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ );
+
+ return $build;
+}
+
+/**
+ * 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, $form_state, $default_ip) {
+ $form['ip'] = array(
+ '#title' => t('IP address'),
+ '#type' => 'textfield',
+ '#size' => 48,
+ '#maxlength' => 40,
+ '#default_value' => $default_ip,
+ '#description' => t('Enter a valid IP address.'),
+ );
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Add'),
+ );
+ $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) {
+ $ip = trim($form_state['values']['ip']);
+ if (db_query("SELECT * FROM {blocked_ips} WHERE ip = :ip", array(':ip' => $ip))->fetchField()) {
+ form_set_error('ip', t('This IP address is already blocked.'));
+ }
+ elseif ($ip == ip_address()) {
+ form_set_error('ip', t('You may not block your own IP address.'));
+ }
+ elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) == FALSE) {
+ form_set_error('ip', t('Enter a valid IP address.'));
+ }
+}
+
+function system_ip_blocking_form_submit($form, &$form_state) {
+ $ip = trim($form_state['values']['ip']);
+ db_insert('blocked_ips')
+ ->fields(array('ip' => $ip))
+ ->execute();
+ drupal_set_message(t('The IP address %ip has been blocked.', array('%ip' => $ip)));
+ $form_state['redirect'] = 'admin/config/people/ip-blocking';
+ return;
+}
+
+/**
+ * IP deletion confirm page.
+ *
+ * @see system_ip_blocking_delete_submit()
+ */
+function system_ip_blocking_delete($form, &$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/config/people/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'];
+ db_delete('blocked_ips')
+ ->condition('iid', $blocked_ip['iid'])
+ ->execute();
+ 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/config/people/ip-blocking';
+}
+
+/**
+ * Form builder; The general site information form.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_site_information_settings() {
+ $form['site_information'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Site details'),
+ );
+ $form['site_information']['site_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Site name'),
+ '#default_value' => variable_get('site_name', 'Drupal'),
+ '#required' => TRUE
+ );
+ $form['site_information']['site_slogan'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Slogan'),
+ '#default_value' => variable_get('site_slogan', ''),
+ '#description' => t("How this is used depends on your site's theme."),
+ );
+ $form['site_information']['site_mail'] = array(
+ '#type' => 'textfield',
+ '#title' => t('E-mail address'),
+ '#default_value' => variable_get('site_mail', ini_get('sendmail_from')),
+ '#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.)"),
+ '#required' => TRUE,
+ );
+ $form['front_page'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Front page'),
+ );
+ $form['front_page']['site_frontpage'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Default front page'),
+ '#default_value' => (variable_get('site_frontpage')!='node'?drupal_get_path_alias(variable_get('site_frontpage', 'node')):''),
+ '#size' => 40,
+ '#description' => t('Optionally, specify a relative URL to display as the front page. Leave blank to display the default content feed.'),
+ '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+ );
+ $form['front_page']['default_nodes_main'] = array(
+ '#type' => 'select', '#title' => t('Number of posts on front page'),
+ '#default_value' => variable_get('default_nodes_main', 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 displayed on overview pages such as the front page.'),
+ '#access' => (variable_get('site_frontpage')=='node'),
+ );
+ $form['error_page'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Error pages'),
+ );
+ $form['error_page']['site_403'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Default 403 (access denied) page'),
+ '#default_value' => variable_get('site_403', ''),
+ '#size' => 40,
+ '#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.'),
+ '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
+ );
+ $form['error_page']['site_404'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Default 404 (not found) page'),
+ '#default_value' => variable_get('site_404', ''),
+ '#size' => 40,
+ '#description' => t('This page is displayed when no other content matches the requested document. Leave blank to display a generic "page not found" page.'),
+ '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
+ );
+
+ $form['#validate'][] = 'system_site_information_settings_validate';
+
+ return system_settings_form($form);
+}
+
+/**
+ * Validates the submitted site-information form.
+ */
+function system_site_information_settings_validate($form, &$form_state) {
+ // Validate the e-mail address.
+ if ($error = user_validate_mail($form_state['values']['site_mail'])) {
+ form_set_error('site_mail', $error);
+ }
+ // Check for empty front page path.
+ if (empty($form_state['values']['site_frontpage'])) {
+ // Set to default "node".
+ form_set_value($form['front_page']['site_frontpage'], 'node', $form_state);
+ }
+ else {
+ // Get the normal path of the front page.
+ form_set_value($form['front_page']['site_frontpage'], drupal_get_normal_path($form_state['values']['site_frontpage']), $form_state);
+ }
+ // Validate front page path.
+ if (!drupal_valid_path($form_state['values']['site_frontpage'])) {
+ form_set_error('site_frontpage', t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state['values']['site_frontpage'])));
+ }
+ // Get the normal paths of both error pages.
+ if (!empty($form_state['values']['site_403'])) {
+ form_set_value($form['error_page']['site_403'], drupal_get_normal_path($form_state['values']['site_403']), $form_state);
+ }
+ if (!empty($form_state['values']['site_404'])) {
+ form_set_value($form['error_page']['site_404'], drupal_get_normal_path($form_state['values']['site_404']), $form_state);
+ }
+ // Validate 403 error path.
+ if (!empty($form_state['values']['site_403']) && !drupal_valid_path($form_state['values']['site_403'])) {
+ form_set_error('site_403', t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state['values']['site_403'])));
+ }
+ // Validate 404 error path.
+ if (!empty($form_state['values']['site_404']) && !drupal_valid_path($form_state['values']['site_404'])) {
+ form_set_error('site_404', t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state['values']['site_404'])));
+ }
+}
+
+/**
+ * Form builder; Cron form.
+ *
+ * @see system_settings_form()
+ * @ingroup forms
+ */
+function system_cron_settings() {
+ $form['description'] = array(
+ '#markup' => '<p>' . t('Cron takes care of running periodic tasks like checking for updates and indexing content for search.') . '</p>',
+ );
+ $form['run'] = array(
+ '#type' => 'submit',
+ '#value' => t('Run cron'),
+ '#submit' => array('system_run_cron_submit'),
+ );
+ $status = '<p>' . t('Last run: %cron-last ago.', array('%cron-last' => format_interval(REQUEST_TIME - variable_get('cron_last')),)) . '</p>';
+ $form['status'] = array(
+ '#markup' => $status,
+ );
+ $form['cron'] = array(
+ '#type' => 'fieldset',
+ );
+ $form['cron']['cron_safe_threshold'] = array(
+ '#type' => 'select',
+ '#title' => t('Run cron every'),
+ '#default_value' => variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD),
+ '#options' => array(0 => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 43200, 86400, 604800), 'format_interval'),
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Submit callback; run cron.
+ *
+ * @ingroup forms
+ */
+function system_run_cron_submit($form, &$form_state) {
+ // Run cron manually from Cron form.
+ if (drupal_cron_run()) {
+ drupal_set_message(t('Cron run successfully.'));
+ }
+ else {
+ drupal_set_message(t('Cron run failed.'), 'error');
+ }
+
+ drupal_goto('admin/config/system/cron');
+}
+
+/**
+ * Form builder; Configure error reporting settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_logging_settings() {
+ $form['error_level'] = array(
+ '#type' => 'radios',
+ '#title' => t('Error messages to display'),
+ '#default_value' => variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL),
+ '#options' => array(
+ ERROR_REPORTING_HIDE => t('None'),
+ ERROR_REPORTING_DISPLAY_SOME => t('Errors and warnings'),
+ ERROR_REPORTING_DISPLAY_ALL => t('All messages'),
+ ),
+ '#description' => t('It is recommended that sites running on production environments do not display any errors.'),
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure site performance settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_performance_settings() {
+ drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
+
+ $form['clear_cache'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Clear cache'),
+ );
+
+ $form['clear_cache']['clear'] = array(
+ '#type' => 'submit',
+ '#value' => t('Clear all caches'),
+ '#submit' => array('system_clear_cache_submit'),
+ );
+
+ $form['caching'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Caching'),
+ );
+
+ $cache = variable_get('cache', 0);
+ $form['caching']['cache'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Cache pages for anonymous users'),
+ '#default_value' => $cache,
+ '#weight' => -2,
+ );
+ $period = drupal_map_assoc(array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400), 'format_interval');
+ $period[0] = '<' . t('none') . '>';
+ $form['caching']['cache_lifetime'] = array(
+ '#type' => 'select',
+ '#title' => t('Minimum cache lifetime'),
+ '#default_value' => variable_get('cache_lifetime', 0),
+ '#options' => $period,
+ '#description' => t('Cached pages will not be re-created until at least this much time has elapsed.')
+ );
+ $form['caching']['page_cache_maximum_age'] = array(
+ '#type' => 'select',
+ '#title' => t('Expiration of cached pages'),
+ '#default_value' => variable_get('page_cache_maximum_age', 0),
+ '#options' => $period,
+ '#description' => t('The maximum time an external cache can use an old version of a page.')
+ );
+
+ $directory = 'public://';
+ $is_writable = is_dir($directory) && is_writable($directory);
+ $disabled = !$is_writable;
+ $disabled_message = '';
+ if (!$is_writable) {
+ $disabled_message = ' ' . t('<strong class="error">Set up the <a href="!file-system">public files directory</a> to make these optimizations available.</strong>', array('!file-system' => url('admin/config/media/file-system')));
+ }
+
+ $form['bandwidth_optimization'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Bandwidth optimization'),
+ '#description' => t('External resources can be optimized automatically, which can reduce both the size and number of requests made to your website.') . $disabled_message,
+ );
+
+ $js_hide = $cache ? '' : ' class="js-hide"';
+ $form['bandwidth_optimization']['page_compression'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Compress cached pages.'),
+ '#default_value' => variable_get('page_compression', TRUE),
+ '#prefix' => '<div id="page-compression-wrapper"' . $js_hide . '>',
+ '#suffix' => '</div>',
+ );
+ $form['bandwidth_optimization']['preprocess_css'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Aggregate and compress CSS files.'),
+ '#default_value' => intval(variable_get('preprocess_css', 0) && $is_writable),
+ '#disabled' => $disabled,
+ );
+ $form['bandwidth_optimization']['preprocess_js'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Aggregate JavaScript files.'),
+ '#default_value' => intval(variable_get('preprocess_js', 0) && $is_writable),
+ '#disabled' => $disabled,
+ );
+
+ $form['#submit'][] = 'drupal_clear_css_cache';
+ $form['#submit'][] = 'drupal_clear_js_cache';
+ // This form allows page compression settings to be changed, which can
+ // invalidate the page cache, so it needs to be cleared on form submit.
+ $form['#submit'][] = 'system_clear_page_cache_submit';
+
+ return system_settings_form($form);
+}
+
+/**
+ * Submit callback; clear system caches.
+ *
+ * @ingroup forms
+ */
+function system_clear_cache_submit($form, &$form_state) {
+ drupal_flush_all_caches();
+ drupal_set_message(t('Caches cleared.'));
+}
+
+/**
+ * Submit callback; clear the page cache.
+ *
+ * @ingroup forms
+ */
+function system_clear_page_cache_submit($form, &$form_state) {
+ cache('page')->flush();
+}
+
+/**
+ * Form builder; Configure the site file handling.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_file_system_settings() {
+ $form['file_public_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Public file system path'),
+ '#default_value' => variable_get('file_public_path', conf_path() . '/files'),
+ '#maxlength' => 255,
+ '#description' => t('A local file system path where public files will be stored. This directory must exist and be writable by Drupal. This directory must be relative to the Drupal installation directory and be accessible over the web.'),
+ '#after_build' => array('system_check_directory'),
+ );
+
+ $form['file_private_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Private file system path'),
+ '#default_value' => variable_get('file_private_path', ''),
+ '#maxlength' => 255,
+ '#description' => t('An existing local file system path for storing private files. It should be writable by Drupal and not accessible over the web. See the online handbook for <a href="@handbook">more information about securing private files</a>.', array('@handbook' => 'http://drupal.org/handbook/modules/file')),
+ '#after_build' => array('system_check_directory'),
+ );
+
+ $form['file_temporary_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Temporary directory'),
+ '#default_value' => variable_get('file_temporary_path', file_directory_temp()),
+ '#maxlength' => 255,
+ '#description' => t('A local file system path where temporary files will be stored. This directory should not be accessible over the web.'),
+ '#after_build' => array('system_check_directory'),
+ );
+ // Any visible, writeable wrapper can potentially be used for the files
+ // directory, including a remote file system that integrates with a CDN.
+ foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
+ $options[$scheme] = check_plain($info['description']);
+ }
+
+ if (!empty($options)) {
+ $form['file_default_scheme'] = array(
+ '#type' => 'radios',
+ '#title' => t('Default download method'),
+ '#default_value' => variable_get('file_default_scheme', isset($options['public']) ? 'public' : key($options)),
+ '#options' => $options,
+ '#description' => t('This setting is used as the preferred download method. The use of public files is more efficient, but does not provide any access control.'),
+ );
+ }
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure site image toolkit usage.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_image_toolkit_settings() {
+ $toolkits_available = image_get_available_toolkits();
+ $current_toolkit = image_get_toolkit();
+
+ if (count($toolkits_available) == 0) {
+ variable_del('image_toolkit');
+ $form['image_toolkit_help'] = array(
+ '#markup' => t("No image toolkits were detected. Drupal includes support for <a href='!gd-link'>PHP's built-in image processing functions</a> but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('gd-link' => url('http://php.net/gd'))),
+ );
+ return $form;
+ }
+
+ if (count($toolkits_available) > 1) {
+ $form['image_toolkit'] = array(
+ '#type' => 'radios',
+ '#title' => t('Select an image processing toolkit'),
+ '#default_value' => variable_get('image_toolkit', $current_toolkit),
+ '#options' => $toolkits_available
+ );
+ }
+ else {
+ variable_set('image_toolkit', key($toolkits_available));
+ }
+
+ // Get the toolkit's settings form.
+ $function = 'image_' . $current_toolkit . '_settings';
+ if (function_exists($function)) {
+ $form['image_toolkit_settings'] = $function();
+ }
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure how the site handles RSS feeds.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_rss_feeds_settings() {
+ $form['feed_description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Feed description'),
+ '#default_value' => variable_get('feed_description', ''),
+ '#description' => t('Description of your site, included in each feed.')
+ );
+ $form['feed_default_items'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of items in each feed'),
+ '#default_value' => variable_get('feed_default_items', 10),
+ '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
+ '#description' => t('Default number of items to include in each feed.')
+ );
+ $form['feed_item_length'] = array(
+ '#type' => 'select',
+ '#title' => t('Feed content'),
+ '#default_value' => variable_get('feed_item_length', 'fulltext'),
+ '#options' => array('title' => t('Titles only'), 'teaser' => t('Titles plus teaser'), 'fulltext' => t('Full text')),
+ '#description' => t('Global setting for the default display of content items in each feed.')
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure the site regional settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ * @see system_regional_settings_submit()
+ */
+function system_regional_settings() {
+ include_once DRUPAL_ROOT . '/includes/locale.inc';
+ $countries = country_get_list();
+
+ // Date settings:
+ $zones = system_time_zones();
+
+ $form['locale'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Locale'),
+ );
+
+ $form['locale']['site_default_country'] = array(
+ '#type' => 'select',
+ '#title' => t('Default country'),
+ '#empty_value' => '',
+ '#default_value' => variable_get('site_default_country', ''),
+ '#options' => $countries,
+ '#attributes' => array('class' => array('country-detect')),
+ );
+
+ $form['locale']['date_first_day'] = array(
+ '#type' => 'select',
+ '#title' => t('First day of week'),
+ '#default_value' => variable_get('date_first_day', 0),
+ '#options' => array(0 => t('Sunday'), 1 => t('Monday'), 2 => t('Tuesday'), 3 => t('Wednesday'), 4 => t('Thursday'), 5 => t('Friday'), 6 => t('Saturday')),
+ );
+
+ $form['timezone'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Time zones'),
+ );
+
+ $form['timezone']['date_default_timezone'] = array(
+ '#type' => 'select',
+ '#title' => t('Default time zone'),
+ '#default_value' => variable_get('date_default_timezone', date_default_timezone_get()),
+ '#options' => $zones,
+ );
+
+ $configurable_timezones = variable_get('configurable_timezones', 1);
+ $form['timezone']['configurable_timezones'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Users may set their own time zone.'),
+ '#default_value' => $configurable_timezones,
+ );
+
+ $form['timezone']['configurable_timezones_wrapper'] = array(
+ '#type' => 'container',
+ '#states' => array(
+ // Hide the user configured timezone settings when users are forced to use
+ // the default setting.
+ 'invisible' => array(
+ 'input[name="configurable_timezones"]' => array('checked' => FALSE),
+ ),
+ ),
+ );
+ $form['timezone']['configurable_timezones_wrapper']['empty_timezone_message'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Remind users at login if their time zone is not set.'),
+ '#default_value' => variable_get('empty_timezone_message', 0),
+ '#description' => t('Only applied if users may set their own time zone.')
+ );
+
+ $form['timezone']['configurable_timezones_wrapper']['user_default_timezone'] = array(
+ '#type' => 'radios',
+ '#title' => t('Time zone for new users'),
+ '#default_value' => variable_get('user_default_timezone', DRUPAL_USER_TIMEZONE_DEFAULT),
+ '#options' => array(
+ DRUPAL_USER_TIMEZONE_DEFAULT => t('Default time zone.'),
+ DRUPAL_USER_TIMEZONE_EMPTY => t('Empty time zone.'),
+ DRUPAL_USER_TIMEZONE_SELECT => t('Users may set their own time zone at registration.'),
+ ),
+ '#description' => t('Only applied if users may set their own time zone.')
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure the site date and time settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_date_time_settings() {
+ // Get list of all available date types.
+ drupal_static_reset('system_get_date_types');
+ $format_types = system_get_date_types();
+
+ // Get list of all available date formats.
+ $all_formats = array();
+ drupal_static_reset('system_get_date_formats');
+ $date_formats = system_get_date_formats(); // Call this to rebuild the list, and to have default list.
+ foreach ($date_formats as $type => $format_info) {
+ $all_formats = array_merge($all_formats, $format_info);
+ }
+ $custom_formats = system_get_date_formats('custom');
+ if (!empty($format_types)) {
+ foreach ($format_types as $type => $type_info) {
+ // If a system type, only show the available formats for that type and
+ // custom ones.
+ if ($type_info['locked'] == 1) {
+ $formats = system_get_date_formats($type);
+ if (empty($formats)) {
+ $formats = $all_formats;
+ }
+ elseif (!empty($custom_formats)) {
+ $formats = array_merge($formats, $custom_formats);
+ }
+ }
+ // If a user configured type, show all available date formats.
+ else {
+ $formats = $all_formats;
+ }
+
+ $choices = array();
+ foreach ($formats as $f => $format) {
+ $choices[$f] = format_date(REQUEST_TIME, 'custom', $f);
+ }
+ reset($formats);
+ $default = variable_get('date_format_' . $type, key($formats));
+
+ // Get date type info for this date type.
+ $type_info = system_get_date_types($type);
+ $form['formats']['#theme'] = 'system_date_time_settings';
+
+ // Show date format select list.
+ $form['formats']['format']['date_format_' . $type] = array(
+ '#type' => 'select',
+ '#title' => check_plain($type_info['title']),
+ '#attributes' => array('class' => array('date-format')),
+ '#default_value' => (isset($choices[$default]) ? $default : 'custom'),
+ '#options' => $choices,
+ );
+
+ // If this isn't a system provided type, allow the user to remove it from
+ // the system.
+ if ($type_info['locked'] == 0) {
+ $form['formats']['delete']['date_format_' . $type . '_delete'] = array(
+ '#type' => 'link',
+ '#title' => t('delete'),
+ '#href' => 'admin/config/regional/date-time/types/' . $type . '/delete',
+ );
+ }
+ }
+ }
+
+ // Display a message if no date types configured.
+ $form['#empty_text'] = t('No date types available. <a href="@link">Add date type</a>.', array('@link' => url('admin/config/regional/date-time/types/add')));
+
+ return system_settings_form($form);
+}
+
+/**
+ * Returns HTML for the date settings form.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_system_date_time_settings($variables) {
+ $form = $variables['form'];
+ $header = array(
+ t('Date type'),
+ t('Format'),
+ t('Operations'),
+ );
+
+ foreach (element_children($form['format']) as $key) {
+ $delete_key = $key . '_delete';
+ $row = array();
+ $row[] = $form['format'][$key]['#title'];
+ $form['format'][$key]['#title_display'] = 'invisible';
+ $row[] = array('data' => drupal_render($form['format'][$key]));
+ $row[] = array('data' => drupal_render($form['delete'][$delete_key]));
+ $rows[] = $row;
+ }
+
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'system-date-types')));
+ $output .= drupal_render_children($form);
+
+ return $output;
+}
+
+
+/**
+ * Add new date type.
+ *
+ * @ingroup forms
+ * @ingroup system_add_date_format_type_form_validate()
+ * @ingroup system_add_date_format_type_form_submit()
+ */
+function system_add_date_format_type_form($form, &$form_state) {
+ $form['date_type'] = array(
+ '#title' => t('Date type'),
+ '#type' => 'textfield',
+ '#required' => TRUE,
+ );
+ $form['machine_name'] = array(
+ '#type' => 'machine_name',
+ '#machine_name' => array(
+ 'exists' => 'system_get_date_types',
+ 'source' => array('date_type'),
+ ),
+ );
+
+ // Get list of all available date formats.
+ $formats = array();
+ drupal_static_reset('system_get_date_formats');
+ $date_formats = system_get_date_formats(); // Call this to rebuild the list, and to have default list.
+ foreach ($date_formats as $type => $format_info) {
+ $formats = array_merge($formats, $format_info);
+ }
+ $custom_formats = system_get_date_formats('custom');
+ if (!empty($custom_formats)) {
+ $formats = array_merge($formats, $custom_formats);
+ }
+ $choices = array();
+ foreach ($formats as $f => $format) {
+ $choices[$f] = format_date(REQUEST_TIME, 'custom', $f);
+ }
+ // Show date format select list.
+ $form['date_format'] = array(
+ '#type' => 'select',
+ '#title' => t('Date format'),
+ '#attributes' => array('class' => array('date-format')),
+ '#options' => $choices,
+ '#required' => TRUE,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Add date type'),
+ );
+
+ $form['#validate'][] = 'system_add_date_format_type_form_validate';
+ $form['#submit'][] = 'system_add_date_format_type_form_submit';
+
+ return $form;
+}
+
+/**
+ * Validate system_add_date_format_type form submissions.
+ */
+function system_add_date_format_type_form_validate($form, &$form_state) {
+ if (!empty($form_state['values']['machine_name']) && !empty($form_state['values']['date_type'])) {
+ if (!preg_match("/^[a-zA-Z0-9_]+$/", trim($form_state['values']['machine_name']))) {
+ form_set_error('machine_name', t('The date type must contain only alphanumeric characters and underscores.'));
+ }
+ $types = system_get_date_types();
+ if (in_array(trim($form_state['values']['machine_name']), array_keys($types))) {
+ form_set_error('machine_name', t('This date type already exists. Enter a unique type.'));
+ }
+ }
+}
+
+/**
+ * Process system_add_date_format_type form submissions.
+ */
+function system_add_date_format_type_form_submit($form, &$form_state) {
+ $machine_name = trim($form_state['values']['machine_name']);
+
+ $format_type = array();
+ $format_type['title'] = trim($form_state['values']['date_type']);
+ $format_type['type'] = $machine_name;
+ $format_type['locked'] = 0;
+ $format_type['is_new'] = 1;
+ system_date_format_type_save($format_type);
+ variable_set('date_format_' . $machine_name, $form_state['values']['date_format']);
+
+ drupal_set_message(t('New date type added successfully.'));
+ $form_state['redirect'] = 'admin/config/regional/date-time';
+}
+
+/**
+ * Return the date for a given format string via Ajax.
+ */
+function system_date_time_lookup() {
+ $result = format_date(REQUEST_TIME, 'custom', $_GET['format']);
+ drupal_json_output($result);
+}
+
+/**
+ * Form builder; Configure the site's maintenance status.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_site_maintenance_mode() {
+ $form['maintenance_mode'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Put site into maintenance mode'),
+ '#default_value' => variable_get('maintenance_mode', 0),
+ '#description' => t('Visitors will only see the maintenance mode message. Only users with the "Access site in maintenance mode" <a href="@permissions-url">permission</a> will be able to access the site. Authorized users can log in directly via the <a href="@user-login">user login</a> page.', array('@permissions-url' => url('admin/config/people/permissions'), '@user-login' => url('user'))),
+ );
+ $form['maintenance_mode_message'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Message to display when in maintenance mode'),
+ '#default_value' => variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))),
+ );
+
+ return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure clean URL settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_clean_url_settings($form, &$form_state) {
+ $available = FALSE;
+ $conflict = FALSE;
+
+ // If the request URI is a clean URL, clean URLs must be available.
+ // Otherwise, run a test.
+ if (strpos(request_uri(), '?q=') === FALSE && strpos(request_uri(), '&q=') === FALSE) {
+ $available = TRUE;
+ }
+ else {
+ $request = drupal_http_request($GLOBALS['base_url'] . '/admin/config/search/clean-urls/check');
+ // If the request returns HTTP 200, clean URLs are available.
+ if (isset($request->code) && $request->code == 200) {
+ $available = TRUE;
+ // If the user started the clean URL test, provide explicit feedback.
+ if (isset($form_state['input']['clean_url_test_execute'])) {
+ drupal_set_message(t('The clean URL test passed.'));
+ }
+ }
+ else {
+ // If the test failed while clean URLs are enabled, make sure clean URLs
+ // can be disabled.
+ if (variable_get('clean_url', 0)) {
+ $conflict = TRUE;
+ // Warn the user of a conflicting situation, unless after processing
+ // a submitted form.
+ if (!isset($form_state['input']['op'])) {
+ drupal_set_message(t('Clean URLs are enabled, but the clean URL test failed. Uncheck the box below to disable clean URLs.'), 'warning');
+ }
+ }
+ // If the user started the clean URL test, provide explicit feedback.
+ elseif (isset($form_state['input']['clean_url_test_execute'])) {
+ drupal_set_message(t('The clean URL test failed.'), 'warning');
+ }
+ }
+ }
+
+ // Show the enable/disable form if clean URLs are available or if the user
+ // must be able to resolve a conflicting setting.
+ if ($available || $conflict) {
+ $form['clean_url'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable clean URLs'),
+ '#default_value' => variable_get('clean_url', 0),
+ '#description' => t('Use URLs like <code>example.com/user</code> instead of <code>example.com/?q=user</code>.'),
+ );
+ $form = system_settings_form($form);
+ if ($conflict) {
+ // $form_state['redirect'] needs to be set to the non-clean URL,
+ // otherwise the setting is not saved.
+ $form_state['redirect'] = url('', array('query' => array('q' => '/admin/config/search/clean-urls')));
+ }
+ }
+ // Show the clean URLs test form.
+ else {
+ drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
+
+ $form_state['redirect'] = url('admin/config/search/clean-urls');
+ $form['clean_url_description'] = array(
+ '#type' => 'markup',
+ '#markup' => '<p>' . t('Use URLs like <code>example.com/user</code> instead of <code>example.com/?q=user</code>.'),
+ );
+ // Explain why the user is seeing this page and what to expect after
+ // clicking the 'Run the clean URL test' button.
+ $form['clean_url_test_result'] = array(
+ '#type' => 'markup',
+ '#markup' => '<p>' . t('Clean URLs cannot be enabled. If you are directed to this page or to a <em>Page not found (404)</em> error after testing for clean URLs, see the <a href="@handbook">online handbook</a>.', array('@handbook' => 'http://drupal.org/node/15365')) . '</p>',
+ );
+ $form['actions'] = array(
+ '#type' => 'actions',
+ 'clean_url_test' => array(
+ '#type' => 'submit',
+ '#value' => t('Run the clean URL test'),
+ ),
+ );
+ $form['clean_url_test_execute'] = array(
+ '#type' => 'hidden',
+ '#value' => 1,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Menu callback: displays the site status report. Can also be used as a pure check.
+ *
+ * @param $check
+ * If true, only returns a boolean whether there are system status errors.
+ */
+function system_status($check = FALSE) {
+ // Load .install files
+ include_once DRUPAL_ROOT . '/includes/install.inc';
+ drupal_load_updates();
+
+ // Check run-time requirements and status information.
+ $requirements = module_invoke_all('requirements', 'runtime');
+ usort($requirements, '_system_sort_requirements');
+
+ if ($check) {
+ return drupal_requirements_severity($requirements) == REQUIREMENT_ERROR;
+ }
+ // MySQL import might have set the uid of the anonymous user to autoincrement
+ // value. Let's try fixing it. See http://drupal.org/node/204411
+ db_update('users')
+ ->expression('uid', 'uid - uid')
+ ->condition('name', '')
+ ->condition('pass', '')
+ ->condition('status', 0)
+ ->execute();
+ return theme('status_report', array('requirements' => $requirements));
+}
+
+/**
+ * Menu callback: run cron manually.
+ */
+function system_run_cron() {
+ // Run cron manually
+ if (drupal_cron_run()) {
+ drupal_set_message(t('Cron ran successfully.'));
+ }
+ else {
+ drupal_set_message(t('Cron run failed.'), 'error');
+ }
+
+ drupal_goto('admin/reports/status');
+}
+
+/**
+ * Menu callback: return information about PHP.
+ */
+function system_php() {
+ phpinfo();
+ drupal_exit();
+}
+
+/**
+ * Default page callback for batches.
+ */
+function system_batch_page() {
+ require_once DRUPAL_ROOT . '/includes/batch.inc';
+ $output = _batch_page();
+
+ if ($output === FALSE) {
+ drupal_access_denied();
+ }
+ elseif (isset($output)) {
+ // Force a page without blocks or messages to
+ // display a list of collected messages later.
+ drupal_set_page_content($output);
+ $page = element_info('page');
+ $page['#show_messages'] = FALSE;
+ return $page;
+ }
+}
+
+/**
+ * Returns HTML for an administrative block for display.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - block: An array containing information about the block:
+ * - show: A Boolean whether to output the block. Defaults to FALSE.
+ * - title: The block's title.
+ * - content: (optional) Formatted content for the block.
+ * - description: (optional) Description of the block. Only output if
+ * 'content' is not set.
+ *
+ * @ingroup themeable
+ */
+function theme_admin_block($variables) {
+ $block = $variables['block'];
+ $output = '';
+
+ // Don't display the block if it has no content to display.
+ if (empty($block['show'])) {
+ return $output;
+ }
+
+ $output .= '<div class="admin-panel">';
+ if (!empty($block['title'])) {
+ $output .= '<h3>' . $block['title'] . '</h3>';
+ }
+ if (!empty($block['content'])) {
+ $output .= '<div class="body">' . $block['content'] . '</div>';
+ }
+ else {
+ $output .= '<div class="description">' . $block['description'] . '</div>';
+ }
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Returns HTML for the content of an administrative block.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - content: An array containing information about the block. Each element
+ * of the array represents an administrative menu item, and must at least
+ * contain the keys 'title', 'href', and 'localized_options', which are
+ * passed to l(). A 'description' key may also be provided.
+ *
+ * @ingroup themeable
+ */
+function theme_admin_block_content($variables) {
+ $content = $variables['content'];
+ $output = '';
+
+ if (!empty($content)) {
+ $class = 'admin-list';
+ if ($compact = system_admin_compact_mode()) {
+ $class .= ' compact';
+ }
+ $output .= '<dl class="' . $class . '">';
+ foreach ($content as $item) {
+ $output .= '<dt>' . l($item['title'], $item['href'], $item['localized_options']) . '</dt>';
+ if (!$compact && isset($item['description'])) {
+ $output .= '<dd>' . filter_xss_admin($item['description']) . '</dd>';
+ }
+ }
+ $output .= '</dl>';
+ }
+ return $output;
+}
+
+/**
+ * Returns HTML for an administrative page.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - blocks: An array of blocks to display. Each array should include a
+ * 'title', a 'description', a formatted 'content' and a 'position' which
+ * will control which container it will be in. This is usually 'left' or
+ * 'right'.
+ *
+ * @ingroup themeable
+ */
+function theme_admin_page($variables) {
+ $blocks = $variables['blocks'];
+
+ $stripe = 0;
+ $container = array();
+
+ foreach ($blocks as $block) {
+ if ($block_output = theme('admin_block', array('block' => $block))) {
+ if (empty($block['position'])) {
+ // perform automatic striping.
+ $block['position'] = ++$stripe % 2 ? 'left' : 'right';
+ }
+ if (!isset($container[$block['position']])) {
+ $container[$block['position']] = '';
+ }
+ $container[$block['position']] .= $block_output;
+ }
+ }
+
+ $output = '<div class="admin clearfix">';
+ $output .= theme('system_compact_link');
+
+ foreach ($container as $id => $data) {
+ $output .= '<div class="' . $id . ' clearfix">';
+ $output .= $data;
+ $output .= '</div>';
+ }
+ $output .= '</div>';
+ return $output;
+}
+
+/**
+ * Returns HTML for the output of the dashboard page.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - menu_items: An array of modules to be displayed.
+ *
+ * @ingroup themeable
+ */
+function theme_system_admin_index($variables) {
+ $menu_items = $variables['menu_items'];
+
+ $stripe = 0;
+ $container = array('left' => '', 'right' => '');
+ $flip = array('left' => 'right', 'right' => 'left');
+ $position = 'left';
+
+ // Iterate over all modules.
+ foreach ($menu_items as $module => $block) {
+ list($description, $items) = $block;
+
+ // Output links.
+ if (count($items)) {
+ $block = array();
+ $block['title'] = $module;
+ $block['content'] = theme('admin_block_content', array('content' => $items));
+ $block['description'] = t($description);
+ $block['show'] = TRUE;
+
+ if ($block_output = theme('admin_block', array('block' => $block))) {
+ if (!isset($block['position'])) {
+ // Perform automatic striping.
+ $block['position'] = $position;
+ $position = $flip[$position];
+ }
+ $container[$block['position']] .= $block_output;
+ }
+ }
+ }
+
+ $output = '<div class="admin clearfix">';
+ $output .= theme('system_compact_link');
+ foreach ($container as $id => $data) {
+ $output .= '<div class="' . $id . ' clearfix">';
+ $output .= $data;
+ $output .= '</div>';
+ }
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Returns HTML for the status report.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - requirements: An array of requirements.
+ *
+ * @ingroup themeable
+ */
+function theme_status_report($variables) {
+ $requirements = $variables['requirements'];
+ $severities = array(
+ REQUIREMENT_INFO => array(
+ 'title' => t('Info'),
+ 'class' => 'info',
+ ),
+ REQUIREMENT_OK => array(
+ 'title' => t('OK'),
+ 'class' => 'ok',
+ ),
+ REQUIREMENT_WARNING => array(
+ 'title' => t('Warning'),
+ 'class' => 'warning',
+ ),
+ REQUIREMENT_ERROR => array(
+ 'title' => t('Error'),
+ 'class' => 'error',
+ ),
+ );
+ $output = '<table class="system-status-report">';
+
+ foreach ($requirements as $requirement) {
+ if (empty($requirement['#type'])) {
+ $severity = $severities[isset($requirement['severity']) ? (int) $requirement['severity'] : 0];
+ $severity['icon'] = '<div title="' . $severity['title'] . '"><span class="element-invisible">' . $severity['title'] . '</span></div>';
+
+ // Output table row(s)
+ if (!empty($requirement['description'])) {
+ $output .= '<tr class="' . $severity['class'] . ' merge-down"><td class="status-icon">' . $severity['icon'] . '</td><td class="status-title">' . $requirement['title'] . '</td><td class="status-value">' . $requirement['value'] . '</td></tr>';
+ $output .= '<tr class="' . $severity['class'] . ' merge-up"><td colspan="3" class="status-description">' . $requirement['description'] . '</td></tr>';
+ }
+ else {
+ $output .= '<tr class="' . $severity['class'] . '"><td class="status-icon">' . $severity['icon'] . '</td><td class="status-title">' . $requirement['title'] . '</td><td class="status-value">' . $requirement['value'] . '</td></tr>';
+ }
+ }
+ }
+
+ $output .= '</table>';
+ return $output;
+}
+
+/**
+ * Returns HTML for the modules form.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_system_modules_fieldset($variables) {
+ $form = $variables['form'];
+
+ // Individual table headers.
+ $rows = array();
+ // Iterate through all the modules, which are
+ // children of this fieldset.
+ foreach (element_children($form) as $key) {
+ // Stick it into $module for easier accessing.
+ $module = $form[$key];
+ $row = array();
+ unset($module['enable']['#title']);
+ $row[] = array('class' => array('checkbox'), 'data' => drupal_render($module['enable']));
+ $label = '<label';
+ if (isset($module['enable']['#id'])) {
+ $label .= ' for="' . $module['enable']['#id'] . '"';
+ }
+ $row[] = $label . '><strong>' . drupal_render($module['name']) . '</strong></label>';
+ $row[] = drupal_render($module['version']);
+ // Add the description, along with any modules it requires.
+ $description = drupal_render($module['description']);
+ if ($module['#requires']) {
+ $description .= '<div class="admin-requirements">' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '</div>';
+ }
+ if ($module['#required_by']) {
+ $description .= '<div class="admin-requirements">' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '</div>';
+ }
+ $row[] = array('data' => $description, 'class' => array('description'));
+ // Display links (such as help or permissions) in their own columns.
+ foreach (array('help', 'permissions', 'configure') as $key) {
+ $row[] = array('data' => drupal_render($module['links'][$key]), 'class' => array($key));
+ }
+ $rows[] = $row;
+ }
+
+ return theme('table', array('header' => $form['#header'], 'rows' => $rows));
+}
+
+/**
+ * Returns HTML for a message about incompatible modules.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - message: The form array representing the currently disabled modules.
+ *
+ * @ingroup themeable
+ */
+function theme_system_modules_incompatible($variables) {
+ return '<div class="incompatible">' . $variables['message'] . '</div>';
+}
+
+/**
+ * Returns HTML for a table of currently disabled modules.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_system_modules_uninstall($variables) {
+ $form = $variables['form'];
+
+ // No theming for the confirm form.
+ if (isset($form['confirm'])) {
+ return drupal_render($form);
+ }
+
+ // Table headers.
+ $header = array(t('Uninstall'),
+ t('Name'),
+ t('Description'),
+ );
+
+ // Display table.
+ $rows = array();
+ foreach (element_children($form['modules']) as $module) {
+ if (!empty($form['modules'][$module]['#required_by'])) {
+ $disabled_message = format_plural(count($form['modules'][$module]['#required_by']),
+ 'To uninstall @module, the following module must be uninstalled first: @required_modules',
+ 'To uninstall @module, the following modules must be uninstalled first: @required_modules',
+ array('@module' => $form['modules'][$module]['#module_name'], '@required_modules' => implode(', ', $form['modules'][$module]['#required_by'])));
+ $disabled_message = '<div class="admin-requirements">' . $disabled_message . '</div>';
+ }
+ else {
+ $disabled_message = '';
+ }
+ $rows[] = array(
+ array('data' => drupal_render($form['uninstall'][$module]), 'align' => 'center'),
+ '<strong><label for="' . $form['uninstall'][$module]['#id'] . '">' . drupal_render($form['modules'][$module]['name']) . '</label></strong>',
+ array('data' => drupal_render($form['modules'][$module]['description']) . $disabled_message, 'class' => array('description')),
+ );
+ }
+
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No modules are available to uninstall.')));
+ $output .= drupal_render_children($form);
+
+ return $output;
+}
+
+/**
+ * Returns HTML for the Appearance page.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - theme_groups: An associative array containing groups of themes.
+ *
+ * @ingroup themeable
+ */
+function theme_system_themes_page($variables) {
+ $theme_groups = $variables['theme_groups'];
+
+ $output = '<div id="system-themes-page">';
+
+ foreach ($variables['theme_group_titles'] as $state => $title) {
+ if (!count($theme_groups[$state])) {
+ // Skip this group of themes if no theme is there.
+ continue;
+ }
+ // Start new theme group.
+ $output .= '<div class="system-themes-list system-themes-list-'. $state .' clearfix"><h2>'. $title .'</h2>';
+
+ foreach ($theme_groups[$state] as $theme) {
+
+ // Theme the screenshot.
+ $screenshot = $theme->screenshot ? theme('image', $theme->screenshot) : '<div class="no-screenshot">' . t('no screenshot') . '</div>';
+
+ // Localize the theme description.
+ $description = t($theme->info['description']);
+
+ // Style theme info
+ $notes = count($theme->notes) ? ' (' . join(', ', $theme->notes) . ')' : '';
+ $theme->classes[] = 'theme-selector';
+ $theme->classes[] = 'clearfix';
+ $output .= '<div class="'. join(' ', $theme->classes) .'">' . $screenshot . '<div class="theme-info"><h3>' . $theme->info['name'] . ' ' . (isset($theme->info['version']) ? $theme->info['version'] : '') . $notes . '</h3><div class="theme-description">' . $description . '</div>';
+
+ // Make sure to provide feedback on compatibility.
+ if (!empty($theme->incompatible_core)) {
+ $output .= '<div class="incompatible">' . t('This version is not compatible with Drupal !core_version and should be replaced.', array('!core_version' => DRUPAL_CORE_COMPATIBILITY)) . '</div>';
+ }
+ elseif (!empty($theme->incompatible_php)) {
+ if (substr_count($theme->info['php'], '.') < 2) {
+ $theme->info['php'] .= '.*';
+ }
+ $output .= '<div class="incompatible">' . t('This theme requires PHP version @php_required and is incompatible with PHP version !php_version.', array('@php_required' => $theme->info['php'], '!php_version' => phpversion())) . '</div>';
+ }
+ else {
+ $output .= theme('links', array('links' => $theme->operations, 'attributes' => array('class' => array('operations', 'clearfix'))));
+ }
+ $output .= '</div></div>';
+ }
+ $output .= '</div>';
+ }
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Menu callback; present a form for deleting a date format.
+ */
+function system_date_delete_format_form($form, &$form_state, $dfid) {
+ $form['dfid'] = array(
+ '#type' => 'value',
+ '#value' => $dfid,
+ );
+ $format = system_get_date_format($dfid);
+
+ $output = confirm_form($form,
+ t('Are you sure you want to remove the format %format?', array('%format' => format_date(REQUEST_TIME, 'custom', $format->format))),
+ 'admin/config/regional/date-time/formats',
+ t('This action cannot be undone.'),
+ t('Remove'), t('Cancel'),
+ 'confirm'
+ );
+
+ return $output;
+}
+
+/**
+ * Delete a configured date format.
+ */
+function system_date_delete_format_form_submit($form, &$form_state) {
+ if ($form_state['values']['confirm']) {
+ $format = system_get_date_format($form_state['values']['dfid']);
+ system_date_format_delete($form_state['values']['dfid']);
+ drupal_set_message(t('Removed date format %format.', array('%format' => format_date(REQUEST_TIME, 'custom', $format->format))));
+ $form_state['redirect'] = 'admin/config/regional/date-time/formats';
+ }
+}
+
+/**
+ * Menu callback; present a form for deleting a date type.
+ */
+function system_delete_date_format_type_form($form, &$form_state, $format_type) {
+ $form['format_type'] = array(
+ '#type' => 'value',
+ '#value' => $format_type,
+ );
+ $type_info = system_get_date_types($format_type);
+
+ $output = confirm_form($form,
+ t('Are you sure you want to remove the date type %type?', array('%type' => $type_info['title'])),
+ 'admin/config/regional/date-time',
+ t('This action cannot be undone.'),
+ t('Remove'), t('Cancel'),
+ 'confirm'
+ );
+
+ return $output;
+}
+
+/**
+ * Delete a configured date type.
+ */
+function system_delete_date_format_type_form_submit($form, &$form_state) {
+ if ($form_state['values']['confirm']) {
+ $type_info = system_get_date_types($form_state['values']['format_type']);
+ system_date_format_type_delete($form_state['values']['format_type']);
+ drupal_set_message(t('Removed date type %type.', array('%type' => $type_info['title'])));
+ $form_state['redirect'] = 'admin/config/regional/date-time';
+ }
+}
+
+
+/**
+ * Displays the date format strings overview page.
+ */
+function system_date_time_formats() {
+ $header = array(t('Format'), array('data' => t('Operations'), 'colspan' => '2'));
+ $rows = array();
+
+ drupal_static_reset('system_get_date_formats');
+ $formats = system_get_date_formats('custom');
+ if (!empty($formats)) {
+ foreach ($formats as $format) {
+ $row = array();
+ $row[] = array('data' => format_date(REQUEST_TIME, 'custom', $format['format']));
+ $row[] = array('data' => l(t('edit'), 'admin/config/regional/date-time/formats/' . $format['dfid'] . '/edit'));
+ $row[] = array('data' => l(t('delete'), 'admin/config/regional/date-time/formats/' . $format['dfid'] . '/delete'));
+ $rows[] = $row;
+ }
+ }
+
+ $build['date_formats_table'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ '#empty' => t('No custom date formats available. <a href="@link">Add date format</a>.', array('@link' => url('admin/config/regional/date-time/formats/add'))),
+ );
+
+ return $build;
+}
+
+/**
+ * Allow users to add additional date formats.
+ */
+function system_configure_date_formats_form($form, &$form_state, $dfid = 0) {
+ $js_settings = array(
+ 'type' => 'setting',
+ 'data' => array(
+ 'dateTime' => array(
+ 'date-format' => array(
+ 'text' => t('Displayed as'),
+ 'lookup' => url('admin/config/regional/date-time/formats/lookup'),
+ ),
+ ),
+ ),
+ );
+
+ if ($dfid) {
+ $form['dfid'] = array(
+ '#type' => 'value',
+ '#value' => $dfid,
+ );
+ $format = system_get_date_format($dfid);
+ }
+
+ $now = ($dfid ? t('Displayed as %date', array('%date' => format_date(REQUEST_TIME, 'custom', $format->format))) : '');
+
+ $form['date_format'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Format string'),
+ '#maxlength' => 100,
+ '#description' => t('A user-defined date format. See the <a href="@url">PHP manual</a> for available options.', array('@url' => 'http://php.net/manual/function.date.php')),
+ '#default_value' => ($dfid ? $format->format : ''),
+ '#field_suffix' => ' <small id="edit-date-format-suffix">' . $now . '</small>',
+ '#attached' => array(
+ 'js' => array(drupal_get_path('module', 'system') . '/system.js', $js_settings),
+ ),
+ '#required' => TRUE,
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['update'] = array(
+ '#type' => 'submit',
+ '#value' => ($dfid ? t('Save format') : t('Add format')),
+ );
+
+ $form['#validate'][] = 'system_add_date_formats_form_validate';
+ $form['#submit'][] = 'system_add_date_formats_form_submit';
+
+ return $form;
+}
+
+/**
+ * Validate new date format string submission.
+ */
+function system_add_date_formats_form_validate($form, &$form_state) {
+ $formats = system_get_date_formats('custom');
+ $format = trim($form_state['values']['date_format']);
+ if (!empty($formats) && in_array($format, array_keys($formats)) && (!isset($form_state['values']['dfid']) || $form_state['values']['dfid'] != $formats[$format]['dfid'])) {
+ form_set_error('date_format', t('This format already exists. Enter a unique format string.'));
+ }
+}
+
+/**
+ * Process new date format string submission.
+ */
+function system_add_date_formats_form_submit($form, &$form_state) {
+ $format = array();
+ $format['format'] = trim($form_state['values']['date_format']);
+ $format['type'] = 'custom';
+ $format['locked'] = 0;
+ if (!empty($form_state['values']['dfid'])) {
+ system_date_format_save($format, $form_state['values']['dfid']);
+ drupal_set_message(t('Custom date format updated.'));
+ }
+ else {
+ $format['is_new'] = 1;
+ system_date_format_save($format);
+ drupal_set_message(t('Custom date format added.'));
+ }
+
+ $form_state['redirect'] = 'admin/config/regional/date-time/formats';
+}
+
+/**
+ * Menu callback; Displays an overview of available and configured actions.
+ */
+function system_actions_manage() {
+ actions_synchronize();
+ $actions = actions_list();
+ $actions_map = actions_actions_map($actions);
+ $options = array();
+ $unconfigurable = array();
+
+ foreach ($actions_map as $key => $array) {
+ if ($array['configurable']) {
+ $options[$key] = $array['label'] . '...';
+ }
+ else {
+ $unconfigurable[] = $array;
+ }
+ }
+
+ $row = array();
+ $instances_present = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchField();
+ $header = array(
+ array('data' => t('Action type'), 'field' => 'type'),
+ array('data' => t('Label'), 'field' => 'label'),
+ array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
+ );
+ $query = db_select('actions')->extend('PagerDefault')->extend('TableSort');
+ $result = $query
+ ->fields('actions')
+ ->limit(50)
+ ->orderByHeader($header)
+ ->execute();
+
+ foreach ($result as $action) {
+ $row[] = array(
+ array('data' => $action->type),
+ array('data' => check_plain($action->label)),
+ array('data' => $action->parameters ? l(t('configure'), "admin/config/system/actions/configure/$action->aid") : ''),
+ array('data' => $action->parameters ? l(t('delete'), "admin/config/system/actions/delete/$action->aid") : '')
+ );
+ }
+
+ if ($row) {
+ $pager = theme('pager');
+ if (!empty($pager)) {
+ $row[] = array(array('data' => $pager, 'colspan' => '3'));
+ }
+ $build['system_actions_header'] = array('#markup' => '<h3>' . t('Available actions:') . '</h3>');
+ $build['system_actions_table'] = array('#markup' => theme('table', array('header' => $header, 'rows' => $row)));
+ }
+
+ if ($actions_map) {
+ $build['system_actions_manage_form'] = drupal_get_form('system_actions_manage_form', $options);
+ }
+
+ return $build;
+}
+
+/**
+ * Define the form for the actions overview page.
+ *
+ * @param $form_state
+ * An associative array containing the current state of the form; not used.
+ * @param $options
+ * An array of configurable actions.
+ * @return
+ * Form definition.
+ *
+ * @ingroup forms
+ * @see system_actions_manage_form_submit()
+ */
+function system_actions_manage_form($form, &$form_state, $options = array()) {
+ $form['parent'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Create an advanced action'),
+ '#attributes' => array('class' => array('container-inline')),
+ );
+ $form['parent']['action'] = array(
+ '#type' => 'select',
+ '#title' => t('Action'),
+ '#title_display' => 'invisible',
+ '#options' => $options,
+ '#empty_option' => t('Choose an advanced action'),
+ );
+ $form['parent']['actions'] = array('#type' => 'actions');
+ $form['parent']['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Create'),
+ );
+ return $form;
+}
+
+/**
+ * Process system_actions_manage form submissions.
+ *
+ * @see system_actions_manage_form()
+ */
+function system_actions_manage_form_submit($form, &$form_state) {
+ if ($form_state['values']['action']) {
+ $form_state['redirect'] = 'admin/config/system/actions/configure/' . $form_state['values']['action'];
+ }
+}
+
+/**
+ * Menu callback; Creates the form for configuration of a single action.
+ *
+ * We provide the "Description" field. The rest of the form is provided by the
+ * action. We then provide the Save button. Because we are combining unknown
+ * form elements with the action configuration form, we use an 'actions_' prefix
+ * on our elements.
+ *
+ * @param $action
+ * Hash of an action ID or an integer. If it is a hash, we are
+ * creating a new instance. If it is an integer, we are editing an existing
+ * instance.
+ * @return
+ * A form definition.
+ *
+ * @see system_actions_configure_validate()
+ * @see system_actions_configure_submit()
+ */
+function system_actions_configure($form, &$form_state, $action = NULL) {
+ if ($action === NULL) {
+ drupal_goto('admin/config/system/actions');
+ }
+
+ $actions_map = actions_actions_map(actions_list());
+ $edit = array();
+
+ // Numeric action denotes saved instance of a configurable action.
+ if (is_numeric($action)) {
+ $aid = $action;
+ // Load stored parameter values from database.
+ $data = db_query("SELECT * FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetch();
+ $edit['actions_label'] = $data->label;
+ $edit['actions_type'] = $data->type;
+ $function = $data->callback;
+ $action = drupal_hash_base64($data->callback);
+ $params = unserialize($data->parameters);
+ if ($params) {
+ foreach ($params as $name => $val) {
+ $edit[$name] = $val;
+ }
+ }
+ }
+ // Otherwise, we are creating a new action instance.
+ else {
+ $function = $actions_map[$action]['callback'];
+ $edit['actions_label'] = $actions_map[$action]['label'];
+ $edit['actions_type'] = $actions_map[$action]['type'];
+ }
+
+ $form['actions_label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#default_value' => $edit['actions_label'],
+ '#maxlength' => '255',
+ '#description' => t('A unique label for this advanced action. This label will be displayed in the interface of modules that integrate with actions, such as Trigger module.'),
+ '#weight' => -10
+ );
+ $action_form = $function . '_form';
+ $form = array_merge($form, $action_form($edit));
+ $form['actions_type'] = array(
+ '#type' => 'value',
+ '#value' => $edit['actions_type'],
+ );
+ $form['actions_action'] = array(
+ '#type' => 'hidden',
+ '#value' => $action,
+ );
+ // $aid is set when configuring an existing action instance.
+ if (isset($aid)) {
+ $form['actions_aid'] = array(
+ '#type' => 'hidden',
+ '#value' => $aid,
+ );
+ }
+ $form['actions_configured'] = array(
+ '#type' => 'hidden',
+ '#value' => '1',
+ );
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#weight' => 13
+ );
+
+ return $form;
+}
+
+/**
+ * Validate system_actions_configure() form submissions.
+ */
+function system_actions_configure_validate($form, &$form_state) {
+ $function = actions_function_lookup($form_state['values']['actions_action']) . '_validate';
+ // Hand off validation to the action.
+ if (function_exists($function)) {
+ $function($form, $form_state);
+ }
+}
+
+/**
+ * Process system_actions_configure() form submissions.
+ */
+function system_actions_configure_submit($form, &$form_state) {
+ $function = actions_function_lookup($form_state['values']['actions_action']);
+ $submit_function = $function . '_submit';
+
+ // Action will return keyed array of values to store.
+ $params = $submit_function($form, $form_state);
+ $aid = isset($form_state['values']['actions_aid']) ? $form_state['values']['actions_aid'] : NULL;
+
+ actions_save($function, $form_state['values']['actions_type'], $params, $form_state['values']['actions_label'], $aid);
+ drupal_set_message(t('The action has been successfully saved.'));
+
+ $form_state['redirect'] = 'admin/config/system/actions/manage';
+}
+
+/**
+ * Create the form for confirmation of deleting an action.
+ *
+ * @see system_actions_delete_form_submit()
+ * @ingroup forms
+ */
+function system_actions_delete_form($form, &$form_state, $action) {
+ $form['aid'] = array(
+ '#type' => 'hidden',
+ '#value' => $action->aid,
+ );
+ return confirm_form($form,
+ t('Are you sure you want to delete the action %action?', array('%action' => $action->label)),
+ 'admin/config/system/actions/manage',
+ t('This cannot be undone.'),
+ t('Delete'),
+ t('Cancel')
+ );
+}
+
+/**
+ * Process system_actions_delete form submissions.
+ *
+ * Post-deletion operations for action deletion.
+ */
+function system_actions_delete_form_submit($form, &$form_state) {
+ $aid = $form_state['values']['aid'];
+ $action = actions_load($aid);
+ actions_delete($aid);
+ watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $action->label));
+ drupal_set_message(t('Action %action was deleted', array('%action' => $action->label)));
+ $form_state['redirect'] = 'admin/config/system/actions/manage';
+}
+
+/**
+ * Post-deletion operations for deleting action orphans.
+ *
+ * @param $orphaned
+ * An array of orphaned actions.
+ */
+function system_action_delete_orphans_post($orphaned) {
+ foreach ($orphaned as $callback) {
+ drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
+ }
+}
+
+/**
+ * Remove actions that are in the database but not supported by any enabled module.
+ */
+function system_actions_remove_orphans() {
+ actions_synchronize(TRUE);
+ drupal_goto('admin/config/system/actions/manage');
+}
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
new file mode 100644
index 00000000000..cc67924e82c
--- /dev/null
+++ b/core/modules/system/system.api.php
@@ -0,0 +1,4130 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by Drupal core and the System module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Defines one or more hooks that are exposed by a module.
+ *
+ * Normally hooks do not need to be explicitly defined. However, by declaring a
+ * hook explicitly, a module may define a "group" for it. Modules that implement
+ * a hook may then place their implementation in either $module.module or in
+ * $module.$group.inc. If the hook is located in $module.$group.inc, then that
+ * file will be automatically loaded when needed.
+ * In general, hooks that are rarely invoked and/or are very large should be
+ * placed in a separate include file, while hooks that are very short or very
+ * frequently called should be left in the main module file so that they are
+ * always available.
+ *
+ * @return
+ * An associative array whose keys are hook names and whose values are an
+ * associative array containing:
+ * - group: A string defining the group to which the hook belongs. The module
+ * system will determine whether a file with the name $module.$group.inc
+ * exists, and automatically load it when required.
+ *
+ * See system_hook_info() for all hook groups defined by Drupal core.
+ *
+ * @see hook_hook_info_alter().
+ */
+function hook_hook_info() {
+ $hooks['token_info'] = array(
+ 'group' => 'tokens',
+ );
+ $hooks['tokens'] = array(
+ 'group' => 'tokens',
+ );
+ return $hooks;
+}
+
+/**
+ * Alter information from hook_hook_info().
+ *
+ * @param $hooks
+ * Information gathered by module_hook_info() from other modules'
+ * implementations of hook_hook_info(). Alter this array directly.
+ * See hook_hook_info() for information on what this may contain.
+ */
+function hook_hook_info_alter(&$hooks) {
+ // Our module wants to completely override the core tokens, so make
+ // sure the core token hooks are not found.
+ $hooks['token_info']['group'] = 'mytokens';
+ $hooks['tokens']['group'] = 'mytokens';
+}
+
+/**
+ * Define administrative paths.
+ *
+ * Modules may specify whether or not the paths they define in hook_menu() are
+ * to be considered administrative. Other modules may use this information to
+ * display those pages differently (e.g. in a modal overlay, or in a different
+ * theme).
+ *
+ * To change the administrative status of menu items defined in another module's
+ * hook_menu(), modules should implement hook_admin_paths_alter().
+ *
+ * @return
+ * An associative array. For each item, the key is the path in question, in
+ * a format acceptable to drupal_match_path(). The value for each item should
+ * be TRUE (for paths considered administrative) or FALSE (for non-
+ * administrative paths).
+ *
+ * @see hook_menu()
+ * @see drupal_match_path()
+ * @see hook_admin_paths_alter()
+ */
+function hook_admin_paths() {
+ $paths = array(
+ 'mymodule/*/add' => TRUE,
+ 'mymodule/*/edit' => TRUE,
+ );
+ return $paths;
+}
+
+/**
+ * Redefine administrative paths defined by other modules.
+ *
+ * @param $paths
+ * An associative array of administrative paths, as defined by implementations
+ * of hook_admin_paths().
+ *
+ * @see hook_admin_paths()
+ */
+function hook_admin_paths_alter(&$paths) {
+ // Treat all user pages as administrative.
+ $paths['user'] = TRUE;
+ $paths['user/*'] = TRUE;
+ // Treat the forum topic node form as a non-administrative page.
+ $paths['node/add/forum'] = FALSE;
+}
+
+/**
+ * Perform periodic actions.
+ *
+ * Modules that require some commands to be executed periodically can
+ * implement hook_cron(). The engine will then call the hook whenever a cron
+ * run happens, as defined by the administrator. Typical tasks managed by
+ * hook_cron() are database maintenance, backups, recalculation of settings
+ * or parameters, automated mailing, and retrieving remote data.
+ *
+ * Short-running or non-resource-intensive tasks can be executed directly in
+ * the hook_cron() implementation.
+ *
+ * Long-running tasks and tasks that could time out, such as retrieving remote
+ * data, sending email, and intensive file tasks, should use the queue API
+ * instead of executing the tasks directly. To do this, first define one or
+ * more queues via hook_cron_queue_info(). Then, add items that need to be
+ * processed to the defined queues.
+ */
+function hook_cron() {
+ // Short-running operation example, not using a queue:
+ // Delete all expired records since the last cron run.
+ $expires = variable_get('mymodule_cron_last_run', REQUEST_TIME);
+ db_delete('mymodule_table')
+ ->condition('expires', $expires, '>=')
+ ->execute();
+ variable_set('mymodule_cron_last_run', REQUEST_TIME);
+
+ // Long-running operation example, leveraging a queue:
+ // Fetch feeds from other sites.
+ $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < :time AND refresh <> :never', array(
+ ':time' => REQUEST_TIME,
+ ':never' => AGGREGATOR_CLEAR_NEVER,
+ ));
+ $queue = DrupalQueue::get('aggregator_feeds');
+ foreach ($result as $feed) {
+ $queue->createItem($feed);
+ }
+}
+
+/**
+ * Declare queues holding items that need to be run periodically.
+ *
+ * While there can be only one hook_cron() process running at the same time,
+ * there can be any number of processes defined here running. Because of
+ * this, long running tasks are much better suited for this API. Items queued
+ * in hook_cron() might be processed in the same cron run if there are not many
+ * items in the queue, otherwise it might take several requests, which can be
+ * run in parallel.
+ *
+ * @return
+ * An associative array where the key is the queue name and the value is
+ * again an associative array. Possible keys are:
+ * - 'worker callback': The name of the function to call. It will be called
+ * with one argument, the item created via DrupalQueue::createItem() in
+ * hook_cron().
+ * - 'time': (optional) How much time Drupal should spend on calling this
+ * worker in seconds. Defaults to 15.
+ *
+ * @see hook_cron()
+ * @see hook_cron_queue_info_alter()
+ */
+function hook_cron_queue_info() {
+ $queues['aggregator_feeds'] = array(
+ 'worker callback' => 'aggregator_refresh',
+ 'time' => 60,
+ );
+ return $queues;
+}
+
+/**
+ * Alter cron queue information before cron runs.
+ *
+ * Called by drupal_cron_run() to allow modules to alter cron queue settings
+ * before any jobs are processesed.
+ *
+ * @param array $queues
+ * An array of cron queue information.
+ *
+ * @see hook_cron_queue_info()
+ * @see drupal_cron_run()
+ */
+function hook_cron_queue_info_alter(&$queues) {
+ // This site has many feeds so let's spend 90 seconds on each cron run
+ // updating feeds instead of the default 60.
+ $queues['aggregator_feeds']['time'] = 90;
+}
+
+/**
+ * Allows modules to declare their own Forms API element types and specify their
+ * default values.
+ *
+ * This hook allows modules to declare their own form element types and to
+ * specify their default values. The values returned by this hook will be
+ * merged with the elements returned by hook_form() implementations and so
+ * can return defaults for any Form APIs keys in addition to those explicitly
+ * mentioned below.
+ *
+ * Each of the form element types defined by this hook is assumed to have
+ * a matching theme function, e.g. theme_elementtype(), which should be
+ * registered with hook_theme() as normal.
+ *
+ * For more information about custom element types see the explanation at
+ * http://drupal.org/node/169815.
+ *
+ * @return
+ * An associative array describing the element types being defined. The array
+ * contains a sub-array for each element type, with the machine-readable type
+ * name as the key. Each sub-array has a number of possible attributes:
+ * - "#input": boolean indicating whether or not this element carries a value
+ * (even if it's hidden).
+ * - "#process": array of callback functions taking $element, $form_state,
+ * and $complete_form.
+ * - "#after_build": array of callback functions taking $element and $form_state.
+ * - "#validate": array of callback functions taking $form and $form_state.
+ * - "#element_validate": array of callback functions taking $element and
+ * $form_state.
+ * - "#pre_render": array of callback functions taking $element and $form_state.
+ * - "#post_render": array of callback functions taking $element and $form_state.
+ * - "#submit": array of callback functions taking $form and $form_state.
+ * - "#title_display": optional string indicating if and how #title should be
+ * displayed, see theme_form_element() and theme_form_element_label().
+ *
+ * @see hook_element_info_alter()
+ * @see system_element_info()
+ */
+function hook_element_info() {
+ $types['filter_format'] = array(
+ '#input' => TRUE,
+ );
+ return $types;
+}
+
+/**
+ * Alter the element type information returned from modules.
+ *
+ * A module may implement this hook in order to alter the element type defaults
+ * defined by a module.
+ *
+ * @param $type
+ * All element type defaults as collected by hook_element_info().
+ *
+ * @see hook_element_info()
+ */
+function hook_element_info_alter(&$type) {
+ // Decrease the default size of textfields.
+ if (isset($type['textfield']['#size'])) {
+ $type['textfield']['#size'] = 40;
+ }
+}
+
+/**
+ * Perform cleanup tasks.
+ *
+ * This hook is run at the end of each page request. It is often used for
+ * page logging and specialized cleanup. This hook MUST NOT print anything.
+ *
+ * Only use this hook if your code must run even for cached page views.
+ * If you have code which must run once on all non-cached pages, use
+ * hook_init() instead. That is the usual case. If you implement this hook
+ * and see an error like 'Call to undefined function', it is likely that
+ * you are depending on the presence of a module which has not been loaded yet.
+ * It is not loaded because Drupal is still in bootstrap mode.
+ *
+ * @param $destination
+ * If this hook is invoked as part of a drupal_goto() call, then this argument
+ * will be a fully-qualified URL that is the destination of the redirect.
+ */
+function hook_exit($destination = NULL) {
+ db_update('counter')
+ ->expression('hits', 'hits + 1')
+ ->condition('type', 1)
+ ->execute();
+}
+
+/**
+ * Perform necessary alterations to the JavaScript before it is presented on
+ * the page.
+ *
+ * @param $javascript
+ * An array of all JavaScript being presented on the page.
+ *
+ * @see drupal_add_js()
+ * @see drupal_get_js()
+ * @see drupal_js_defaults()
+ */
+function hook_js_alter(&$javascript) {
+ // Swap out jQuery to use an updated version of the library.
+ $javascript['misc/jquery.js']['data'] = drupal_get_path('module', 'jquery_update') . '/jquery.js';
+}
+
+/**
+ * Registers JavaScript/CSS libraries associated with a module.
+ *
+ * Modules implementing this return an array of arrays. The key to each
+ * sub-array is the machine readable name of the library. Each library may
+ * contain the following items:
+ *
+ * - 'title': The human readable name of the library.
+ * - 'website': The URL of the library's web site.
+ * - 'version': A string specifying the version of the library; intentionally
+ * not a float because a version like "1.2.3" is not a valid float. Use PHP's
+ * version_compare() to compare different versions.
+ * - 'js': An array of JavaScript elements; each element's key is used as $data
+ * argument, each element's value is used as $options array for
+ * drupal_add_js(). To add library-specific (not module-specific) JavaScript
+ * settings, the key may be skipped, the value must specify
+ * 'type' => 'setting', and the actual settings must be contained in a 'data'
+ * element of the value.
+ * - 'css': Like 'js', an array of CSS elements passed to drupal_add_css().
+ * - 'dependencies': An array of libraries that are required for a library. Each
+ * element is an array listing the module and name of another library. Note
+ * that all dependencies for each dependent library will also be added when
+ * this library is added.
+ *
+ * Registered information for a library should contain re-usable data only.
+ * Module- or implementation-specific data and integration logic should be added
+ * separately.
+ *
+ * @return
+ * An array defining libraries associated with a module.
+ *
+ * @see system_library_info()
+ * @see drupal_add_library()
+ * @see drupal_get_library()
+ */
+function hook_library_info() {
+ // Library One.
+ $libraries['library-1'] = array(
+ 'title' => 'Library One',
+ 'website' => 'http://example.com/library-1',
+ 'version' => '1.2',
+ 'js' => array(
+ drupal_get_path('module', 'my_module') . '/library-1.js' => array(),
+ ),
+ 'css' => array(
+ drupal_get_path('module', 'my_module') . '/library-2.css' => array(
+ 'type' => 'file',
+ 'media' => 'screen',
+ ),
+ ),
+ );
+ // Library Two.
+ $libraries['library-2'] = array(
+ 'title' => 'Library Two',
+ 'website' => 'http://example.com/library-2',
+ 'version' => '3.1-beta1',
+ 'js' => array(
+ // JavaScript settings may use the 'data' key.
+ array(
+ 'type' => 'setting',
+ 'data' => array('library2' => TRUE),
+ ),
+ ),
+ 'dependencies' => array(
+ // Require jQuery UI core by System module.
+ array('system', 'ui'),
+ // Require our other library.
+ array('my_module', 'library-1'),
+ // Require another library.
+ array('other_module', 'library-3'),
+ ),
+ );
+ return $libraries;
+}
+
+/**
+ * Alters the JavaScript/CSS library registry.
+ *
+ * Allows certain, contributed modules to update libraries to newer versions
+ * while ensuring backwards compatibility. In general, such manipulations should
+ * only be done by designated modules, since most modules that integrate with a
+ * certain library also depend on the API of a certain library version.
+ *
+ * @param $libraries
+ * The JavaScript/CSS libraries provided by $module. Keyed by internal library
+ * name and passed by reference.
+ * @param $module
+ * The name of the module that registered the libraries.
+ *
+ * @see hook_library_info()
+ */
+function hook_library_info_alter(&$libraries, $module) {
+ // Update Farbtastic to version 2.0.
+ if ($module == 'system' && isset($libraries['farbtastic'])) {
+ // Verify existing version is older than the one we are updating to.
+ if (version_compare($libraries['farbtastic']['version'], '2.0', '<')) {
+ // Update the existing Farbtastic to version 2.0.
+ $libraries['farbtastic']['version'] = '2.0';
+ $libraries['farbtastic']['js'] = array(
+ drupal_get_path('module', 'farbtastic_update') . '/farbtastic-2.0.js' => array(),
+ );
+ }
+ }
+}
+
+/**
+ * Alter CSS files before they are output on the page.
+ *
+ * @param $css
+ * An array of all CSS items (files and inline CSS) being requested on the page.
+ *
+ * @see drupal_add_css()
+ * @see drupal_get_css()
+ */
+function hook_css_alter(&$css) {
+ // Remove defaults.css file.
+ unset($css[drupal_get_path('module', 'system') . '/defaults.css']);
+}
+
+/**
+ * Alter the commands that are sent to the user through the Ajax framework.
+ *
+ * @param $commands
+ * An array of all commands that will be sent to the user.
+ *
+ * @see ajax_render()
+ */
+function hook_ajax_render_alter($commands) {
+ // Inject any new status messages into the content area.
+ $commands[] = ajax_command_prepend('#block-system-main .content', theme('status_messages'));
+}
+
+/**
+ * Add elements to a page before it is rendered.
+ *
+ * Use this hook when you want to add elements at the page level. For your
+ * additions to be printed, they have to be placed below a top level array key
+ * of the $page array that has the name of a region of the active theme.
+ *
+ * By default, valid region keys are 'page_top', 'header', 'sidebar_first',
+ * 'content', 'sidebar_second' and 'page_bottom'. To get a list of all regions
+ * of the active theme, use system_region_list($theme). Note that $theme is a
+ * global variable.
+ *
+ * If you want to alter the elements added by other modules or if your module
+ * depends on the elements of other modules, use hook_page_alter() instead which
+ * runs after this hook.
+ *
+ * @param $page
+ * Nested array of renderable elements that make up the page.
+ *
+ * @see hook_page_alter()
+ * @see drupal_render_page()
+ */
+function hook_page_build(&$page) {
+ if (menu_get_object('node', 1)) {
+ // We are on a node detail page. Append a standard disclaimer to the
+ // content region.
+ $page['content']['disclaimer'] = array(
+ '#markup' => t('Acme, Inc. is not responsible for the contents of this sample code.'),
+ '#weight' => 25,
+ );
+ }
+}
+
+/**
+ * Alter a menu router item right after it has been retrieved from the database or cache.
+ *
+ * This hook is invoked by menu_get_item() and allows for run-time alteration of router
+ * information (page_callback, title, and so on) before it is translated and checked for
+ * access. The passed-in $router_item is statically cached for the current request, so this
+ * hook is only invoked once for any router item that is retrieved via menu_get_item().
+ *
+ * Usually, modules will only want to inspect the router item and conditionally
+ * perform other actions (such as preparing a state for the current request).
+ * Note that this hook is invoked for any router item that is retrieved by
+ * menu_get_item(), which may or may not be called on the path itself, so implementations
+ * should check the $path parameter if the alteration should fire for the current request
+ * only.
+ *
+ * @param $router_item
+ * The menu router item for $path.
+ * @param $path
+ * The originally passed path, for which $router_item is responsible.
+ * @param $original_map
+ * The path argument map, as contained in $path.
+ *
+ * @see menu_get_item()
+ */
+function hook_menu_get_item_alter(&$router_item, $path, $original_map) {
+ // When retrieving the router item for the current path...
+ if ($path == $_GET['q']) {
+ // ...call a function that prepares something for this request.
+ mymodule_prepare_something();
+ }
+}
+
+/**
+ * Define menu items and page callbacks.
+ *
+ * This hook enables modules to register paths in order to define how URL
+ * requests are handled. Paths may be registered for URL handling only, or they
+ * can register a link to be placed in a menu (usually the Navigation menu). A
+ * path and its associated information is commonly called a "menu router item".
+ * This hook is rarely called (for example, when modules are enabled), and
+ * its results are cached in the database.
+ *
+ * hook_menu() implementations return an associative array whose keys define
+ * paths and whose values are an associative array of properties for each
+ * path. (The complete list of properties is in the return value section below.)
+ *
+ * The definition for each path may include a page callback function, which is
+ * invoked when the registered path is requested. If there is no other
+ * registered path that fits the requested path better, any further path
+ * components are passed to the callback function. For example, your module
+ * could register path 'abc/def':
+ * @code
+ * function mymodule_menu() {
+ * $items['abc/def'] = array(
+ * 'page callback' => 'mymodule_abc_view',
+ * );
+ * return $items;
+ * }
+ *
+ * function mymodule_abc_view($ghi = 0, $jkl = '') {
+ * // ...
+ * }
+ * @endcode
+ * When path 'abc/def' is requested, no further path components are in the
+ * request, and no additional arguments are passed to the callback function (so
+ * $ghi and $jkl would take the default values as defined in the function
+ * signature). When 'abc/def/123/foo' is requested, $ghi will be '123' and
+ * $jkl will be 'foo'. Note that this automatic passing of optional path
+ * arguments applies only to page and theme callback functions.
+ *
+ * In addition to optional path arguments, the page callback and other callback
+ * functions may specify argument lists as arrays. These argument lists may
+ * contain both fixed/hard-coded argument values and integers that correspond
+ * to path components. When integers are used and the callback function is
+ * called, the corresponding path components will be substituted for the
+ * integers. That is, the integer 0 in an argument list will be replaced with
+ * the first path component, integer 1 with the second, and so on (path
+ * components are numbered starting from zero). To pass an integer without it
+ * being replaced with its respective path component, use the string value of
+ * the integer (e.g., '1') as the argument value. This substitution feature
+ * allows you to re-use a callback function for several different paths. For
+ * example:
+ * @code
+ * function mymodule_menu() {
+ * $items['abc/def'] = array(
+ * 'page callback' => 'mymodule_abc_view',
+ * 'page arguments' => array(1, 'foo'),
+ * );
+ * return $items;
+ * }
+ * @endcode
+ * When path 'abc/def' is requested, the page callback function will get 'def'
+ * as the first argument and (always) 'foo' as the second argument.
+ *
+ * If a page callback function uses an argument list array, and its path is
+ * requested with optional path arguments, then the list array's arguments are
+ * passed to the callback function first, followed by the optional path
+ * arguments. Using the above example, when path 'abc/def/bar/baz' is requested,
+ * mymodule_abc_view() will be called with 'def', 'foo', 'bar' and 'baz' as
+ * arguments, in that order.
+ *
+ * Special care should be taken for the page callback drupal_get_form(), because
+ * your specific form callback function will always receive $form and
+ * &$form_state as the first function arguments:
+ * @code
+ * function mymodule_abc_form($form, &$form_state) {
+ * // ...
+ * return $form;
+ * }
+ * @endcode
+ * See @link form_api Form API documentation @endlink for details.
+ *
+ * Wildcards within paths also work with integer substitution. For example,
+ * your module could register path 'my-module/%/edit':
+ * @code
+ * $items['my-module/%/edit'] = array(
+ * 'page callback' => 'mymodule_abc_edit',
+ * 'page arguments' => array(1),
+ * );
+ * @endcode
+ * When path 'my-module/foo/edit' is requested, integer 1 will be replaced
+ * with 'foo' and passed to the callback function.
+ *
+ * Registered paths may also contain special "auto-loader" wildcard components
+ * in the form of '%mymodule_abc', where the '%' part means that this path
+ * component is a wildcard, and the 'mymodule_abc' part defines the prefix for a
+ * load function, which here would be named mymodule_abc_load(). When a matching
+ * path is requested, your load function will receive as its first argument the
+ * path component in the position of the wildcard; load functions may also be
+ * passed additional arguments (see "load arguments" in the return value
+ * section below). For example, your module could register path
+ * 'my-module/%mymodule_abc/edit':
+ * @code
+ * $items['my-module/%mymodule_abc/edit'] = array(
+ * 'page callback' => 'mymodule_abc_edit',
+ * 'page arguments' => array(1),
+ * );
+ * @endcode
+ * When path 'my-module/123/edit' is requested, your load function
+ * mymodule_abc_load() will be invoked with the argument '123', and should
+ * load and return an "abc" object with internal id 123:
+ * @code
+ * function mymodule_abc_load($abc_id) {
+ * return db_query("SELECT * FROM {mymodule_abc} WHERE abc_id = :abc_id", array(':abc_id' => $abc_id))->fetchObject();
+ * }
+ * @endcode
+ * This 'abc' object will then be passed into the callback functions defined
+ * for the menu item, such as the page callback function mymodule_abc_edit()
+ * to replace the integer 1 in the argument array.
+ *
+ * You can also define a %wildcard_to_arg() function (for the example menu
+ * entry above this would be 'mymodule_abc_to_arg()'). The _to_arg() function
+ * is invoked to retrieve a value that is used in the path in place of the
+ * wildcard. A good example is user.module, which defines
+ * user_uid_optional_to_arg() (corresponding to the menu entry
+ * 'user/%user_uid_optional'). This function returns the user ID of the
+ * current user.
+ *
+ * The _to_arg() function will get called with three arguments:
+ * - $arg: A string representing whatever argument may have been supplied by
+ * the caller (this is particularly useful if you want the _to_arg()
+ * function only supply a (default) value if no other value is specified,
+ * as in the case of user_uid_optional_to_arg().
+ * - $map: An array of all path fragments (e.g. array('node','123','edit') for
+ * 'node/123/edit').
+ * - $index: An integer indicating which element of $map corresponds to $arg.
+ *
+ * _load() and _to_arg() functions may seem similar at first glance, but they
+ * have different purposes and are called at different times. _load()
+ * functions are called when the menu system is collecting arguments to pass
+ * to the callback functions defined for the menu item. _to_arg() functions
+ * are called when the menu system is generating links to related paths, such
+ * as the tabs for a set of MENU_LOCAL_TASK items.
+ *
+ * You can also make groups of menu items to be rendered (by default) as tabs
+ * on a page. To do that, first create one menu item of type MENU_NORMAL_ITEM,
+ * with your chosen path, such as 'foo'. Then duplicate that menu item, using a
+ * subdirectory path, such as 'foo/tab1', and changing the type to
+ * MENU_DEFAULT_LOCAL_TASK to make it the default tab for the group. Then add
+ * the additional tab items, with paths such as "foo/tab2" etc., with type
+ * MENU_LOCAL_TASK. Example:
+ * @code
+ * // Make "Foo settings" appear on the admin Config page
+ * $items['admin/config/system/foo'] = array(
+ * 'title' => 'Foo settings',
+ * 'type' => MENU_NORMAL_ITEM,
+ * // Page callback, etc. need to be added here.
+ * );
+ * // Make "Tab 1" the main tab on the "Foo settings" page
+ * $items['admin/config/system/foo/tab1'] = array(
+ * 'title' => 'Tab 1',
+ * 'type' => MENU_DEFAULT_LOCAL_TASK,
+ * // Access callback, page callback, and theme callback will be inherited
+ * // from 'admin/config/system/foo', if not specified here to override.
+ * );
+ * // Make an additional tab called "Tab 2" on "Foo settings"
+ * $items['admin/config/system/foo/tab2'] = array(
+ * 'title' => 'Tab 2',
+ * 'type' => MENU_LOCAL_TASK,
+ * // Page callback and theme callback will be inherited from
+ * // 'admin/config/system/foo', if not specified here to override.
+ * // Need to add access callback or access arguments.
+ * );
+ * @endcode
+ *
+ * @return
+ * An array of menu items. Each menu item has a key corresponding to the
+ * Drupal path being registered. The corresponding array value is an
+ * associative array that may contain the following key-value pairs:
+ * - "title": Required. The untranslated title of the menu item.
+ * - "title callback": Function to generate the title; defaults to t().
+ * If you require only the raw string to be output, set this to FALSE.
+ * - "title arguments": Arguments to send to t() or your custom callback,
+ * with path component substitution as described above.
+ * - "description": The untranslated description of the menu item.
+ * - "page callback": The function to call to display a web page when the user
+ * visits the path. If omitted, the parent menu item's callback will be used
+ * instead.
+ * - "page arguments": An array of arguments to pass to the page callback
+ * function, with path component substitution as described above.
+ * - "delivery callback": The function to call to package the result of the
+ * page callback function and send it to the browser. Defaults to
+ * drupal_deliver_html_page() unless a value is inherited from a parent menu
+ * item. Note that this function is called even if the access checks fail,
+ * so any custom delivery callback function should take that into account.
+ * See drupal_deliver_html_page() for an example.
+ * - "access callback": A function returning TRUE if the user has access
+ * rights to this menu item, and FALSE if not. It can also be a boolean
+ * constant instead of a function, and you can also use numeric values
+ * (will be cast to boolean). Defaults to user_access() unless a value is
+ * inherited from the parent menu item; only MENU_DEFAULT_LOCAL_TASK items
+ * can inherit access callbacks. To use the user_access() default callback,
+ * you must specify the permission to check as 'access arguments' (see
+ * below).
+ * - "access arguments": An array of arguments to pass to the access callback
+ * function, with path component substitution as described above. If the
+ * access callback is inherited (see above), the access arguments will be
+ * inherited with it, unless overridden in the child menu item.
+ * - "theme callback": (optional) A function returning the machine-readable
+ * name of the theme that will be used to render the page. If not provided,
+ * the value will be inherited from a parent menu item. If there is no
+ * theme callback, or if the function does not return the name of a current
+ * active theme on the site, the theme for this page will be determined by
+ * either hook_custom_theme() or the default theme instead. As a general
+ * rule, the use of theme callback functions should be limited to pages
+ * whose functionality is very closely tied to a particular theme, since
+ * they can only be overridden by modules which specifically target those
+ * pages in hook_menu_alter(). Modules implementing more generic theme
+ * switching functionality (for example, a module which allows the theme to
+ * be set dynamically based on the current user's role) should use
+ * hook_custom_theme() instead.
+ * - "theme arguments": An array of arguments to pass to the theme callback
+ * function, with path component substitution as described above.
+ * - "file": A file that will be included before the page callback is called;
+ * this allows page callback functions to be in separate files. The file
+ * should be relative to the implementing module's directory unless
+ * otherwise specified by the "file path" option. Does not apply to other
+ * callbacks (only page callback).
+ * - "file path": The path to the directory containing the file specified in
+ * "file". This defaults to the path to the module implementing the hook.
+ * - "load arguments": An array of arguments to be passed to each of the
+ * wildcard object loaders in the path, after the path argument itself.
+ * For example, if a module registers path node/%node/revisions/%/view
+ * with load arguments set to array(3), the '%node' in the path indicates
+ * that the loader function node_load() will be called with the second
+ * path component as the first argument. The 3 in the load arguments
+ * indicates that the fourth path component will also be passed to
+ * node_load() (numbering of path components starts at zero). So, if path
+ * node/12/revisions/29/view is requested, node_load(12, 29) will be called.
+ * There are also two "magic" values that can be used in load arguments.
+ * "%index" indicates the index of the wildcard path component. "%map"
+ * indicates the path components as an array. For example, if a module
+ * registers for several paths of the form 'user/%user_category/edit/*', all
+ * of them can use the same load function user_category_load(), by setting
+ * the load arguments to array('%map', '%index'). For instance, if the user
+ * is editing category 'foo' by requesting path 'user/32/edit/foo', the load
+ * function user_category_load() will be called with 32 as its first
+ * argument, the array ('user', 32, 'edit', 'foo') as the map argument,
+ * and 1 as the index argument (because %user_category is the second path
+ * component and numbering starts at zero). user_category_load() can then
+ * use these values to extract the information that 'foo' is the category
+ * being requested.
+ * - "weight": An integer that determines the relative position of items in
+ * the menu; higher-weighted items sink. Defaults to 0. Menu items with the
+ * same weight are ordered alphabetically.
+ * - "menu_name": Optional. Set this to a custom menu if you don't want your
+ * item to be placed in Navigation.
+ * - "context": (optional) Defines the context a tab may appear in. By
+ * default, all tabs are only displayed as local tasks when being rendered
+ * in a page context. All tabs that should be accessible as contextual links
+ * in page region containers outside of the parent menu item's primary page
+ * context should be registered using one of the following contexts:
+ * - MENU_CONTEXT_PAGE: (default) The tab is displayed as local task for the
+ * page context only.
+ * - MENU_CONTEXT_INLINE: The tab is displayed as contextual link outside of
+ * the primary page context only.
+ * Contexts can be combined. For example, to display a tab both on a page
+ * and inline, a menu router item may specify:
+ * @code
+ * 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+ * @endcode
+ * - "tab_parent": For local task menu items, the path of the task's parent
+ * item; defaults to the same path without the last component (e.g., the
+ * default parent for 'admin/people/create' is 'admin/people').
+ * - "tab_root": For local task menu items, the path of the closest non-tab
+ * item; same default as "tab_parent".
+ * - "position": Position of the block ('left' or 'right') on the system
+ * administration page for this item.
+ * - "type": A bitmask of flags describing properties of the menu item.
+ * Many shortcut bitmasks are provided as constants in menu.inc:
+ * - MENU_NORMAL_ITEM: Normal menu items show up in the menu tree and can be
+ * moved/hidden by the administrator.
+ * - MENU_CALLBACK: Callbacks simply register a path so that the correct
+ * information is generated when the path is accessed.
+ * - MENU_SUGGESTED_ITEM: Modules may "suggest" menu items that the
+ * administrator may enable.
+ * - MENU_LOCAL_ACTION: Local actions are menu items that describe actions
+ * on the parent item such as adding a new user or block, and are
+ * rendered in the action-links list in your theme.
+ * - MENU_LOCAL_TASK: Local tasks are menu items that describe different
+ * displays of data, and are generally rendered as tabs.
+ * - MENU_DEFAULT_LOCAL_TASK: Every set of local tasks should provide one
+ * "default" task, which should display the same page as the parent item.
+ * If the "type" element is omitted, MENU_NORMAL_ITEM is assumed.
+ * - "options": An array of options to be passed to l() when generating a link
+ * from this menu item.
+ *
+ * For a detailed usage example, see page_example.module.
+ * For comprehensive documentation on the menu system, see
+ * http://drupal.org/node/102338.
+ */
+function hook_menu() {
+ $items['example'] = array(
+ 'title' => 'Example Page',
+ 'page callback' => 'example_page',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_SUGGESTED_ITEM,
+ );
+ $items['example/feed'] = array(
+ 'title' => 'Example RSS feed',
+ 'page callback' => 'example_feed',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Alter the data being saved to the {menu_router} table after hook_menu is invoked.
+ *
+ * This hook is invoked by menu_router_build(). The menu definitions are passed
+ * in by reference. Each element of the $items array is one item returned
+ * by a module from hook_menu. Additional items may be added, or existing items
+ * altered.
+ *
+ * @param $items
+ * Associative array of menu router definitions returned from hook_menu().
+ */
+function hook_menu_alter(&$items) {
+ // Example - disable the page at node/add
+ $items['node/add']['access callback'] = FALSE;
+}
+
+/**
+ * Alter the data being saved to the {menu_links} table by menu_link_save().
+ *
+ * @param $item
+ * Associative array defining a menu link as passed into menu_link_save().
+ *
+ * @see hook_translated_menu_link_alter()
+ */
+function hook_menu_link_alter(&$item) {
+ // Make all new admin links hidden (a.k.a disabled).
+ if (strpos($item['link_path'], 'admin') === 0 && empty($item['mlid'])) {
+ $item['hidden'] = 1;
+ }
+ // Flag a link to be altered by hook_translated_menu_link_alter().
+ if ($item['link_path'] == 'devel/cache/clear') {
+ $item['options']['alter'] = TRUE;
+ }
+ // Flag a link to be altered by hook_translated_menu_link_alter(), but only
+ // if it is derived from a menu router item; i.e., do not alter a custom
+ // menu link pointing to the same path that has been created by a user.
+ if ($item['link_path'] == 'user' && $item['module'] == 'system') {
+ $item['options']['alter'] = TRUE;
+ }
+}
+
+/**
+ * Alter a menu link after it has been translated and before it is rendered.
+ *
+ * This hook is invoked from _menu_link_translate() after a menu link has been
+ * translated; i.e., after dynamic path argument placeholders (%) have been
+ * replaced with actual values, the user access to the link's target page has
+ * been checked, and the link has been localized. It is only invoked if
+ * $item['options']['alter'] has been set to a non-empty value (e.g., TRUE).
+ * This flag should be set using hook_menu_link_alter().
+ *
+ * Implementations of this hook are able to alter any property of the menu link.
+ * For example, this hook may be used to add a page-specific query string to all
+ * menu links, or hide a certain link by setting:
+ * @code
+ * 'hidden' => 1,
+ * @endcode
+ *
+ * @param $item
+ * Associative array defining a menu link after _menu_link_translate()
+ * @param $map
+ * Associative array containing the menu $map (path parts and/or objects).
+ *
+ * @see hook_menu_link_alter()
+ */
+function hook_translated_menu_link_alter(&$item, $map) {
+ if ($item['href'] == 'devel/cache/clear') {
+ $item['localized_options']['query'] = drupal_get_destination();
+ }
+}
+
+/**
+ * Inform modules that a menu link has been created.
+ *
+ * This hook is used to notify modules that menu items have been
+ * created. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param $link
+ * Associative array defining a menu link as passed into menu_link_save().
+ *
+ * @see hook_menu_link_update()
+ * @see hook_menu_link_delete()
+ */
+function hook_menu_link_insert($link) {
+ // In our sample case, we track menu items as editing sections
+ // of the site. These are stored in our table as 'disabled' items.
+ $record['mlid'] = $link['mlid'];
+ $record['menu_name'] = $link['menu_name'];
+ $record['status'] = 0;
+ drupal_write_record('menu_example', $record);
+}
+
+/**
+ * Inform modules that a menu link has been updated.
+ *
+ * This hook is used to notify modules that menu items have been
+ * updated. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param $link
+ * Associative array defining a menu link as passed into menu_link_save().
+ *
+ * @see hook_menu_link_insert()
+ * @see hook_menu_link_delete()
+ */
+function hook_menu_link_update($link) {
+ // If the parent menu has changed, update our record.
+ $menu_name = db_query("SELECT menu_name FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $link['mlid']))->fetchField();
+ if ($menu_name != $link['menu_name']) {
+ db_update('menu_example')
+ ->fields(array('menu_name' => $link['menu_name']))
+ ->condition('mlid', $link['mlid'])
+ ->execute();
+ }
+}
+
+/**
+ * Inform modules that a menu link has been deleted.
+ *
+ * This hook is used to notify modules that menu items have been
+ * deleted. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param $link
+ * Associative array defining a menu link as passed into menu_link_save().
+ *
+ * @see hook_menu_link_insert()
+ * @see hook_menu_link_update()
+ */
+function hook_menu_link_delete($link) {
+ // Delete the record from our table.
+ db_delete('menu_example')
+ ->condition('mlid', $link['mlid'])
+ ->execute();
+}
+
+/**
+ * Alter tabs and actions displayed on the page before they are rendered.
+ *
+ * This hook is invoked by menu_local_tasks(). The system-determined tabs and
+ * actions are passed in by reference. Additional tabs or actions may be added,
+ * or existing items altered.
+ *
+ * Each tab or action is an associative array containing:
+ * - #theme: The theme function to use to render.
+ * - #link: An associative array containing:
+ * - title: The localized title of the link.
+ * - href: The system path to link to.
+ * - localized_options: An array of options to pass to url().
+ * - #active: Whether the link should be marked as 'active'.
+ *
+ * @param $data
+ * An associative array containing:
+ * - actions: An associative array containing:
+ * - count: The amount of actions determined by the menu system, which can
+ * be ignored.
+ * - output: A list of of actions, each one being an associative array
+ * as described above.
+ * - tabs: An indexed array (list) of tab levels (up to 2 levels), each
+ * containing an associative array:
+ * - count: The amount of tabs determined by the menu system. This value
+ * does not need to be altered if there is more than one tab.
+ * - output: A list of of tabs, each one being an associative array as
+ * described above.
+ * @param $router_item
+ * The menu system router item of the page.
+ * @param $root_path
+ * The path to the root item for this set of tabs.
+ */
+function hook_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ // Add an action linking to node/add to all pages.
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_task',
+ '#link' => array(
+ 'title' => t('Add new content'),
+ 'href' => 'node/add',
+ 'localized_options' => array(
+ 'attributes' => array(
+ 'title' => t('Add new content'),
+ ),
+ ),
+ ),
+ );
+
+ // Add a tab linking to node/add to all pages.
+ $data['tabs'][0]['output'][] = array(
+ '#theme' => 'menu_local_task',
+ '#link' => array(
+ 'title' => t('Example tab'),
+ 'href' => 'node/add',
+ 'localized_options' => array(
+ 'attributes' => array(
+ 'title' => t('Add new content'),
+ ),
+ ),
+ ),
+ // Define whether this link is active. This can be omitted for
+ // implementations that add links to pages outside of the current page
+ // context.
+ '#active' => ($router_item['path'] == $root_path),
+ );
+}
+
+/**
+ * Alter links in the active trail before it is rendered as the breadcrumb.
+ *
+ * This hook is invoked by menu_get_active_breadcrumb() and allows alteration
+ * of the breadcrumb links for the current page, which may be preferred instead
+ * of setting a custom breadcrumb via drupal_set_breadcrumb().
+ *
+ * Implementations should take into account that menu_get_active_breadcrumb()
+ * subsequently performs the following adjustments to the active trail *after*
+ * this hook has been invoked:
+ * - The last link in $active_trail is removed, if its 'href' is identical to
+ * the 'href' of $item. This happens, because the breadcrumb normally does
+ * not contain a link to the current page.
+ * - The (second to) last link in $active_trail is removed, if the current $item
+ * is a MENU_DEFAULT_LOCAL_TASK. This happens in order to do not show a link
+ * to the current page, when being on the path for the default local task;
+ * e.g. when being on the path node/%/view, the breadcrumb should not contain
+ * a link to node/%.
+ *
+ * Each link in the active trail must contain:
+ * - title: The localized title of the link.
+ * - href: The system path to link to.
+ * - localized_options: An array of options to pass to url().
+ *
+ * @param $active_trail
+ * An array containing breadcrumb links for the current page.
+ * @param $item
+ * The menu router item of the current page.
+ *
+ * @see drupal_set_breadcrumb()
+ * @see menu_get_active_breadcrumb()
+ * @see menu_get_active_trail()
+ * @see menu_set_active_trail()
+ */
+function hook_menu_breadcrumb_alter(&$active_trail, $item) {
+ // Always display a link to the current page by duplicating the last link in
+ // the active trail. This means that menu_get_active_breadcrumb() will remove
+ // the last link (for the current page), but since it is added once more here,
+ // it will appear.
+ if (!drupal_is_front_page()) {
+ $end = end($active_trail);
+ if ($item['href'] == $end['href']) {
+ $active_trail[] = $end;
+ }
+ }
+}
+
+/**
+ * Alter contextual links before they are rendered.
+ *
+ * This hook is invoked by menu_contextual_links(). The system-determined
+ * contextual links are passed in by reference. Additional links may be added
+ * or existing links can be altered.
+ *
+ * Each contextual link must at least contain:
+ * - title: The localized title of the link.
+ * - href: The system path to link to.
+ * - localized_options: An array of options to pass to url().
+ *
+ * @param $links
+ * An associative array containing contextual links for the given $root_path,
+ * as described above. The array keys are used to build CSS class names for
+ * contextual links and must therefore be unique for each set of contextual
+ * links.
+ * @param $router_item
+ * The menu router item belonging to the $root_path being requested.
+ * @param $root_path
+ * The (parent) path that has been requested to build contextual links for.
+ * This is a normalized path, which means that an originally passed path of
+ * 'node/123' became 'node/%'.
+ *
+ * @see hook_contextual_links_view_alter()
+ * @see menu_contextual_links()
+ * @see hook_menu()
+ * @see contextual_preprocess()
+ */
+function hook_menu_contextual_links_alter(&$links, $router_item, $root_path) {
+ // Add a link to all contextual links for nodes.
+ if ($root_path == 'node/%') {
+ $links['foo'] = array(
+ 'title' => t('Do fu'),
+ 'href' => 'foo/do',
+ 'localized_options' => array(
+ 'query' => array(
+ 'foo' => 'bar',
+ ),
+ ),
+ );
+ }
+}
+
+/**
+ * Perform alterations before a page is rendered.
+ *
+ * Use this hook when you want to remove or alter elements at the page
+ * level, or add elements at the page level that depend on an other module's
+ * elements (this hook runs after hook_page_build().
+ *
+ * If you are making changes to entities such as forms, menus, or user
+ * profiles, use those objects' native alter hooks instead (hook_form_alter(),
+ * for example).
+ *
+ * The $page array contains top level elements for each block region:
+ * @code
+ * $page['page_top']
+ * $page['header']
+ * $page['sidebar_first']
+ * $page['content']
+ * $page['sidebar_second']
+ * $page['page_bottom']
+ * @endcode
+ *
+ * The 'content' element contains the main content of the current page, and its
+ * structure will vary depending on what module is responsible for building the
+ * page. Some legacy modules may not return structured content at all: their
+ * pre-rendered markup will be located in $page['content']['main']['#markup'].
+ *
+ * Pages built by Drupal's core Node module use a standard structure:
+ *
+ * @code
+ * // Node body.
+ * $page['content']['system_main']['nodes'][$nid]['body']
+ * // Array of links attached to the node (add comments, read more).
+ * $page['content']['system_main']['nodes'][$nid]['links']
+ * // The node object itself.
+ * $page['content']['system_main']['nodes'][$nid]['#node']
+ * // The results pager.
+ * $page['content']['system_main']['pager']
+ * @endcode
+ *
+ * Blocks may be referenced by their module/delta pair within a region:
+ * @code
+ * // The login block in the first sidebar region.
+ * $page['sidebar_first']['user_login']['#block'];
+ * @endcode
+ *
+ * @param $page
+ * Nested array of renderable elements that make up the page.
+ *
+ * @see hook_page_build()
+ * @see drupal_render_page()
+ */
+function hook_page_alter(&$page) {
+ // Add help text to the user login block.
+ $page['sidebar_first']['user_login']['help'] = array(
+ '#weight' => -10,
+ '#markup' => t('To post comments or add new content, you first have to log in.'),
+ );
+}
+
+/**
+ * Perform alterations before a form is rendered.
+ *
+ * One popular use of this hook is to add form elements to the node form. When
+ * altering a node form, the node object can be accessed at $form['#node'].
+ *
+ * Note that instead of hook_form_alter(), which is called for all forms, you
+ * can also use hook_form_FORM_ID_alter() to alter a specific form. For each
+ * module (in system weight order) the general form alter hook implementation
+ * is invoked first, then the form ID specific alter implementation is called.
+ * After all module hook implementations are invoked, the hook_form_alter()
+ * implementations from themes are invoked in the same manner.
+ *
+ * @param $form
+ * Nested array of form elements that comprise the form.
+ * @param $form_state
+ * A keyed array containing the current state of the form. The arguments
+ * that drupal_get_form() was originally called with are available in the
+ * array $form_state['build_info']['args'].
+ * @param $form_id
+ * String representing the name of the form itself. Typically this is the
+ * name of the function that generated the form.
+ *
+ * @see hook_form_FORM_ID_alter()
+ */
+function hook_form_alter(&$form, &$form_state, $form_id) {
+ if (isset($form['type']) && $form['type']['#value'] . '_node_settings' == $form_id) {
+ $form['workflow']['upload_' . $form['type']['#value']] = array(
+ '#type' => 'radios',
+ '#title' => t('Attachments'),
+ '#default_value' => variable_get('upload_' . $form['type']['#value'], 1),
+ '#options' => array(t('Disabled'), t('Enabled')),
+ );
+ }
+}
+
+/**
+ * Provide a form-specific alteration instead of the global hook_form_alter().
+ *
+ * Modules can implement hook_form_FORM_ID_alter() to modify a specific form,
+ * rather than implementing hook_form_alter() and checking the form ID, or
+ * using long switch statements to alter multiple forms.
+ *
+ * @param $form
+ * Nested array of form elements that comprise the form.
+ * @param $form_state
+ * A keyed array containing the current state of the form. The arguments
+ * that drupal_get_form() was originally called with are available in the
+ * array $form_state['build_info']['args'].
+ * @param $form_id
+ * String representing the name of the form itself. Typically this is the
+ * name of the function that generated the form.
+ *
+ * @see hook_form_alter()
+ * @see drupal_prepare_form()
+ */
+function hook_form_FORM_ID_alter(&$form, &$form_state, $form_id) {
+ // Modification for the form with the given form ID goes here. For example, if
+ // FORM_ID is "user_register_form" this code would run only on the user
+ // registration form.
+
+ // Add a checkbox to registration form about agreeing to terms of use.
+ $form['terms_of_use'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("I agree with the website's terms and conditions."),
+ '#required' => TRUE,
+ );
+}
+
+/**
+ * Provide a form-specific alteration for shared forms.
+ *
+ * Modules can implement hook_form_BASE_FORM_ID_alter() to modify a specific
+ * form belonging to multiple form_ids, rather than implementing
+ * hook_form_alter() and checking for conditions that would identify the
+ * shared form constructor.
+ *
+ * Examples for such forms are node_form() or comment_form().
+ *
+ * Note that this hook fires after hook_form_FORM_ID_alter() and before
+ * hook_form_alter().
+ *
+ * @param $form
+ * Nested array of form elements that comprise the form.
+ * @param $form_state
+ * A keyed array containing the current state of the form.
+ * @param $form_id
+ * String representing the name of the form itself. Typically this is the
+ * name of the function that generated the form.
+ *
+ * @see hook_form_FORM_ID_alter()
+ * @see drupal_prepare_form()
+ */
+function hook_form_BASE_FORM_ID_alter(&$form, &$form_state, $form_id) {
+ // Modification for the form with the given BASE_FORM_ID goes here. For
+ // example, if BASE_FORM_ID is "node_form", this code would run on every
+ // node form, regardless of node type.
+
+ // Add a checkbox to the node form about agreeing to terms of use.
+ $form['terms_of_use'] = array(
+ '#type' => 'checkbox',
+ '#title' => t("I agree with the website's terms and conditions."),
+ '#required' => TRUE,
+ );
+}
+
+/**
+ * Map form_ids to form builder functions.
+ *
+ * By default, when drupal_get_form() is called, the system will look for a
+ * function with the same name as the form ID, and use that function to build
+ * the form. This hook allows you to override that behavior in two ways.
+ *
+ * First, you can use this hook to tell the form system to use a different
+ * function to build certain forms in your module; this is often used to define
+ * a form "factory" function that is used to build several similar forms. In
+ * this case, your hook implementation will likely ignore all of the input
+ * arguments. See node_forms() for an example of this.
+ *
+ * Second, you could use this hook to define how to build a form with a
+ * dynamically-generated form ID. In this case, you would need to verify that
+ * the $form_id input matched your module's format for dynamically-generated
+ * form IDs, and if so, act appropriately.
+ *
+ * @param $form_id
+ * The unique string identifying the desired form.
+ * @param $args
+ * An array containing the original arguments provided to drupal_get_form()
+ * or drupal_form_submit(). These are always passed to the form builder and
+ * do not have to be specified manually in 'callback arguments'.
+ *
+ * @return
+ * An associative array whose keys define form_ids and whose values are an
+ * associative array defining the following keys:
+ * - callback: The name of the form builder function to invoke.
+ * - callback arguments: (optional) Additional arguments to pass to the
+ * function defined in 'callback', which are prepended to $args.
+ * - wrapper_callback: (optional) The name of a form builder function to
+ * invoke before the form builder defined in 'callback' is invoked. This
+ * wrapper callback may prepopulate the $form array with form elements,
+ * which will then be already contained in the $form that is passed on to
+ * the form builder defined in 'callback'. For example, a wrapper callback
+ * could setup wizard-alike form buttons that are the same for a variety of
+ * forms that belong to the wizard, which all share the same wrapper
+ * callback.
+ */
+function hook_forms($form_id, $args) {
+ // Simply reroute the (non-existing) $form_id 'mymodule_first_form' to
+ // 'mymodule_main_form'.
+ $forms['mymodule_first_form'] = array(
+ 'callback' => 'mymodule_main_form',
+ );
+
+ // Reroute the $form_id and prepend an additional argument that gets passed to
+ // the 'mymodule_main_form' form builder function.
+ $forms['mymodule_second_form'] = array(
+ 'callback' => 'mymodule_main_form',
+ 'callback arguments' => array('some parameter'),
+ );
+
+ // Reroute the $form_id, but invoke the form builder function
+ // 'mymodule_main_form_wrapper' first, so we can prepopulate the $form array
+ // that is passed to the actual form builder 'mymodule_main_form'.
+ $forms['mymodule_wrapped_form'] = array(
+ 'callback' => 'mymodule_main_form',
+ 'wrapper_callback' => 'mymodule_main_form_wrapper',
+ );
+
+ return $forms;
+}
+
+/**
+ * Perform setup tasks for all page requests.
+ *
+ * This hook is run at the beginning of the page request. It is typically
+ * used to set up global parameters that are needed later in the request.
+ *
+ * Only use this hook if your code must run even for cached page views. This
+ * hook is called before modules or most include files are loaded into memory.
+ * It happens while Drupal is still in bootstrap mode.
+ *
+ * @see hook_init()
+ */
+function hook_boot() {
+ // We need user_access() in the shutdown function. Make sure it gets loaded.
+ drupal_load('module', 'user');
+ drupal_register_shutdown_function('devel_shutdown');
+}
+
+/**
+ * Perform setup tasks for non-cached page requests.
+ *
+ * This hook is run at the beginning of the page request. It is typically
+ * used to set up global parameters that are needed later in the request.
+ * When this hook is called, all modules are already loaded in memory.
+ *
+ * This hook is not run on cached pages.
+ *
+ * To add CSS or JS that should be present on all pages, modules should not
+ * implement this hook, but declare these files in their .info file.
+ *
+ * @see hook_boot()
+ */
+function hook_init() {
+ // Since this file should only be loaded on the front page, it cannot be
+ // declared in the info file.
+ if (drupal_is_front_page()) {
+ drupal_add_css(drupal_get_path('module', 'foo') . '/foo.css');
+ }
+}
+
+/**
+ * Define image toolkits provided by this module.
+ *
+ * The file which includes each toolkit's functions must be declared as part of
+ * the files array in the module .info file so that the registry will find and
+ * parse it.
+ *
+ * The toolkit's functions must be named image_toolkitname_operation().
+ * where the operation may be:
+ * - 'load': Required. See image_gd_load() for usage.
+ * - 'save': Required. See image_gd_save() for usage.
+ * - 'settings': Optional. See image_gd_settings() for usage.
+ * - 'resize': Optional. See image_gd_resize() for usage.
+ * - 'rotate': Optional. See image_gd_rotate() for usage.
+ * - 'crop': Optional. See image_gd_crop() for usage.
+ * - 'desaturate': Optional. See image_gd_desaturate() for usage.
+ *
+ * @return
+ * An array with the toolkit name as keys and sub-arrays with these keys:
+ * - 'title': A string with the toolkit's title.
+ * - 'available': A Boolean value to indicate that the toolkit is operating
+ * properly, e.g. all required libraries exist.
+ *
+ * @see system_image_toolkits()
+ */
+function hook_image_toolkits() {
+ return array(
+ 'working' => array(
+ 'title' => t('A toolkit that works.'),
+ 'available' => TRUE,
+ ),
+ 'broken' => array(
+ 'title' => t('A toolkit that is "broken" and will not be listed.'),
+ 'available' => FALSE,
+ ),
+ );
+}
+
+/**
+ * Alter an email message created with the drupal_mail() function.
+ *
+ * hook_mail_alter() allows modification of email messages created and sent
+ * with drupal_mail(). Usage examples include adding and/or changing message
+ * text, message fields, and message headers.
+ *
+ * Email messages sent using functions other than drupal_mail() will not
+ * invoke hook_mail_alter(). For example, a contributed module directly
+ * calling the drupal_mail_system()->mail() or PHP mail() function
+ * will not invoke this hook. All core modules use drupal_mail() for
+ * messaging, it is best practice but not mandatory in contributed modules.
+ *
+ * @param $message
+ * An array containing the message data. Keys in this array include:
+ * - 'id':
+ * The drupal_mail() id of the message. Look at module source code or
+ * drupal_mail() for possible id values.
+ * - 'to':
+ * The address or addresses the message will be sent to. The
+ * formatting of this string must comply with RFC 2822.
+ * - 'from':
+ * The address the message will be marked as being from, which is
+ * either a custom address or the site-wide default email address.
+ * - 'subject':
+ * Subject of the email to be sent. This must not contain any newline
+ * characters, or the email may not be sent properly.
+ * - 'body':
+ * An array of strings containing the message text. The message body is
+ * created by concatenating the individual array strings into a single text
+ * string using "\n\n" as a separator.
+ * - 'headers':
+ * Associative array containing mail headers, such as From, Sender,
+ * MIME-Version, Content-Type, etc.
+ * - 'params':
+ * An array of optional parameters supplied by the caller of drupal_mail()
+ * that is used to build the message before hook_mail_alter() is invoked.
+ * - 'language':
+ * The language object used to build the message before hook_mail_alter()
+ * is invoked.
+ *
+ * @see drupal_mail()
+ */
+function hook_mail_alter(&$message) {
+ if ($message['id'] == 'modulename_messagekey') {
+ $message['body'][] = "--\nMail sent out from " . variable_get('sitename', t('Drupal'));
+ }
+}
+
+/**
+ * Alter the registry of modules implementing a hook.
+ *
+ * This hook is invoked during module_implements(). A module may implement this
+ * hook in order to reorder the implementing modules, which are otherwise
+ * ordered by the module's system weight.
+ *
+ * @param $implementations
+ * An array keyed by the module's name. The value of each item corresponds
+ * to a $group, which is usually FALSE, unless the implementation is in a
+ * file named $module.$group.inc.
+ * @param $hook
+ * The name of the module hook being implemented.
+ */
+function hook_module_implements_alter(&$implementations, $hook) {
+ if ($hook == 'rdf_mapping') {
+ // Move my_module_rdf_mapping() to the end of the list. module_implements()
+ // iterates through $implementations with a foreach loop which PHP iterates
+ // in the order that the items were added, so to move an item to the end of
+ // the array, we remove it and then add it.
+ $group = $implementations['my_module'];
+ unset($implementations['my_module']);
+ $implementations['my_module'] = $group;
+ }
+}
+
+/**
+ * Alter the information parsed from module and theme .info files
+ *
+ * This hook is invoked in _system_rebuild_module_data() and in
+ * _system_rebuild_theme_data(). A module may implement this hook in order to
+ * add to or alter the data generated by reading the .info file with
+ * drupal_parse_info_file().
+ *
+ * @param $info
+ * The .info file contents, passed by reference so that it can be altered.
+ * @param $file
+ * Full information about the module or theme, including $file->name, and
+ * $file->filename
+ * @param $type
+ * Either 'module' or 'theme', depending on the type of .info file that was
+ * passed.
+ */
+function hook_system_info_alter(&$info, $file, $type) {
+ // Only fill this in if the .info file does not define a 'datestamp'.
+ if (empty($info['datestamp'])) {
+ $info['datestamp'] = filemtime($file->filename);
+ }
+}
+
+/**
+ * Define user permissions.
+ *
+ * This hook can supply permissions that the module defines, so that they
+ * can be selected on the user permissions page and used to grant or restrict
+ * access to actions the module performs.
+ *
+ * Permissions are checked using user_access().
+ *
+ * For a detailed usage example, see page_example.module.
+ *
+ * @return
+ * An array whose keys are permission names and whose corresponding values
+ * are arrays containing the following key-value pairs:
+ * - title: The human-readable name of the permission, to be shown on the
+ * permission administration page. This should be wrapped in the t()
+ * function so it can be translated.
+ * - description: (optional) A description of what the permission does. This
+ * should be wrapped in the t() function so it can be translated.
+ * - restrict access: (optional) A boolean which can be set to TRUE to
+ * indicate that site administrators should restrict access to this
+ * permission to trusted users. This should be used for permissions that
+ * have inherent security risks across a variety of potential use cases
+ * (for example, the "administer filters" and "bypass node access"
+ * permissions provided by Drupal core). When set to TRUE, a standard
+ * warning message defined in user_admin_permissions() and output via
+ * theme_user_permission_description() will be associated with the
+ * permission and displayed with it on the permission administration page.
+ * Defaults to FALSE.
+ * - warning: (optional) A translated warning message to display for this
+ * permission on the permission administration page. This warning overrides
+ * the automatic warning generated by 'restrict access' being set to TRUE.
+ * This should rarely be used, since it is important for all permissions to
+ * have a clear, consistent security warning that is the same across the
+ * site. Use the 'description' key instead to provide any information that
+ * is specific to the permission you are defining.
+ *
+ * @see theme_user_permission_description()
+ */
+function hook_permission() {
+ return array(
+ 'administer my module' => array(
+ 'title' => t('Administer my module'),
+ 'description' => t('Perform administration tasks for my module.'),
+ ),
+ );
+}
+
+/**
+ * Register a module (or theme's) theme implementations.
+ *
+ * The implementations declared by this hook have two purposes: either they
+ * specify how a particular render array is to be rendered as HTML (this is
+ * usually the case if the theme function is assigned to the render array's
+ * #theme property), or they return the HTML that should be returned by an
+ * invocation of theme().
+ *
+ * The following parameters are all optional.
+ *
+ * @param array $existing
+ * An array of existing implementations that may be used for override
+ * purposes. This is primarily useful for themes that may wish to examine
+ * existing implementations to extract data (such as arguments) so that
+ * it may properly register its own, higher priority implementations.
+ * @param $type
+ * Whether a theme, module, etc. is being processed. This is primarily useful
+ * so that themes tell if they are the actual theme being called or a parent
+ * theme. May be one of:
+ * - 'module': A module is being checked for theme implementations.
+ * - 'base_theme_engine': A theme engine is being checked for a theme that is
+ * a parent of the actual theme being used.
+ * - 'theme_engine': A theme engine is being checked for the actual theme
+ * being used.
+ * - 'base_theme': A base theme is being checked for theme implementations.
+ * - 'theme': The actual theme in use is being checked.
+ * @param $theme
+ * The actual name of theme, module, etc. that is being being processed.
+ * @param $path
+ * The directory path of the theme or module, so that it doesn't need to be
+ * looked up.
+ *
+ * @return array
+ * An associative array of theme hook information. The keys on the outer
+ * array are the internal names of the hooks, and the values are arrays
+ * containing information about the hook. Each information array must contain
+ * either a 'variables' element or a 'render element' element, but not both.
+ * Use 'render element' if you are theming a single element or element tree
+ * composed of elements, such as a form array, a page array, or a single
+ * checkbox element. Use 'variables' if your theme implementation is
+ * intended to be called directly through theme() and has multiple arguments
+ * for the data and style; in this case, the variables not supplied by the
+ * calling function will be given default values and passed to the template
+ * or theme function. The returned theme information array can contain the
+ * following key/value pairs:
+ * - variables: (see above) Each array key is the name of the variable, and
+ * the value given is used as the default value if the function calling
+ * theme() does not supply it. Template implementations receive each array
+ * key as a variable in the template file (so they must be legal PHP
+ * variable names). Function implementations are passed the variables in a
+ * single $variables function argument.
+ * - render element: (see above) The name of the renderable element or element
+ * tree to pass to the theme function. This name is used as the name of the
+ * variable that holds the renderable element or tree in preprocess and
+ * process functions.
+ * - file: The file the implementation resides in. This file will be included
+ * prior to the theme being rendered, to make sure that the function or
+ * preprocess function (as needed) is actually loaded; this makes it
+ * possible to split theme functions out into separate files quite easily.
+ * - path: Override the path of the file to be used. Ordinarily the module or
+ * theme path will be used, but if the file will not be in the default
+ * path, include it here. This path should be relative to the Drupal root
+ * directory.
+ * - template: If specified, this theme implementation is a template, and
+ * this is the template file without an extension. Do not put .tpl.php on
+ * this file; that extension will be added automatically by the default
+ * rendering engine (which is PHPTemplate). If 'path', above, is specified,
+ * the template should also be in this path.
+ * - function: If specified, this will be the function name to invoke for
+ * this implementation. If neither 'template' nor 'function' is specified,
+ * a default function name will be assumed. For example, if a module
+ * registers the 'node' theme hook, 'theme_node' will be assigned to its
+ * function. If the chameleon theme registers the node hook, it will be
+ * assigned 'chameleon_node' as its function.
+ * - pattern: A regular expression pattern to be used to allow this theme
+ * implementation to have a dynamic name. The convention is to use __ to
+ * differentiate the dynamic portion of the theme. For example, to allow
+ * forums to be themed individually, the pattern might be: 'forum__'. Then,
+ * when the forum is themed, call:
+ * @code
+ * theme(array('forum__' . $tid, 'forum'), $forum)
+ * @endcode
+ * - preprocess functions: A list of functions used to preprocess this data.
+ * Ordinarily this won't be used; it's automatically filled in. By default,
+ * for a module this will be filled in as template_preprocess_HOOK. For
+ * a theme this will be filled in as phptemplate_preprocess and
+ * phptemplate_preprocess_HOOK as well as themename_preprocess and
+ * themename_preprocess_HOOK.
+ * - override preprocess functions: Set to TRUE when a theme does NOT want
+ * the standard preprocess functions to run. This can be used to give a
+ * theme FULL control over how variables are set. For example, if a theme
+ * wants total control over how certain variables in the page.tpl.php are
+ * set, this can be set to true. Please keep in mind that when this is used
+ * by a theme, that theme becomes responsible for making sure necessary
+ * variables are set.
+ * - type: (automatically derived) Where the theme hook is defined:
+ * 'module', 'theme_engine', or 'theme'.
+ * - theme path: (automatically derived) The directory path of the theme or
+ * module, so that it doesn't need to be looked up.
+ */
+function hook_theme($existing, $type, $theme, $path) {
+ return array(
+ 'forum_display' => array(
+ 'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
+ ),
+ 'forum_list' => array(
+ 'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
+ ),
+ 'forum_topic_list' => array(
+ 'variables' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
+ ),
+ 'forum_icon' => array(
+ 'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
+ ),
+ 'status_report' => array(
+ 'render element' => 'requirements',
+ 'file' => 'system.admin.inc',
+ ),
+ 'system_date_time_settings' => array(
+ 'render element' => 'form',
+ 'file' => 'system.admin.inc',
+ ),
+ );
+}
+
+/**
+ * Alter the theme registry information returned from hook_theme().
+ *
+ * The theme registry stores information about all available theme hooks,
+ * including which callback functions those hooks will call when triggered,
+ * what template files are exposed by these hooks, and so on.
+ *
+ * Note that this hook is only executed as the theme cache is re-built.
+ * Changes here will not be visible until the next cache clear.
+ *
+ * The $theme_registry array is keyed by theme hook name, and contains the
+ * information returned from hook_theme(), as well as additional properties
+ * added by _theme_process_registry().
+ *
+ * For example:
+ * @code
+ * $theme_registry['user_profile'] = array(
+ * 'variables' => array(
+ * 'account' => NULL,
+ * ),
+ * 'template' => 'modules/user/user-profile',
+ * 'file' => 'modules/user/user.pages.inc',
+ * 'type' => 'module',
+ * 'theme path' => 'modules/user',
+ * 'preprocess functions' => array(
+ * 0 => 'template_preprocess',
+ * 1 => 'template_preprocess_user_profile',
+ * ),
+ * );
+ * @endcode
+ *
+ * @param $theme_registry
+ * The entire cache of theme registry information, post-processing.
+ *
+ * @see hook_theme()
+ * @see _theme_process_registry()
+ */
+function hook_theme_registry_alter(&$theme_registry) {
+ // Kill the next/previous forum topic navigation links.
+ foreach ($theme_registry['forum_topic_navigation']['preprocess functions'] as $key => $value) {
+ if ($value == 'template_preprocess_forum_topic_navigation') {
+ unset($theme_registry['forum_topic_navigation']['preprocess functions'][$key]);
+ }
+ }
+}
+
+/**
+ * Return the machine-readable name of the theme to use for the current page.
+ *
+ * This hook can be used to dynamically set the theme for the current page
+ * request. It should be used by modules which need to override the theme
+ * based on dynamic conditions (for example, a module which allows the theme to
+ * be set based on the current user's role). The return value of this hook will
+ * be used on all pages except those which have a valid per-page or per-section
+ * theme set via a theme callback function in hook_menu(); the themes on those
+ * pages can only be overridden using hook_menu_alter().
+ *
+ * Since only one theme can be used at a time, the last (i.e., highest
+ * weighted) module which returns a valid theme name from this hook will
+ * prevail.
+ *
+ * @return
+ * The machine-readable name of the theme that should be used for the current
+ * page request. The value returned from this function will only have an
+ * effect if it corresponds to a currently-active theme on the site.
+ */
+function hook_custom_theme() {
+ // Allow the user to request a particular theme via a query parameter.
+ if (isset($_GET['theme'])) {
+ return $_GET['theme'];
+ }
+}
+
+/**
+ * Register XML-RPC callbacks.
+ *
+ * This hook lets a module register callback functions to be called when
+ * particular XML-RPC methods are invoked by a client.
+ *
+ * @return
+ * An array which maps XML-RPC methods to Drupal functions. Each array
+ * element is either a pair of method => function or an array with four
+ * entries:
+ * - The XML-RPC method name (for example, module.function).
+ * - The Drupal callback function (for example, module_function).
+ * - The method signature is an array of XML-RPC types. The first element
+ * of this array is the type of return value and then you should write a
+ * list of the types of the parameters. XML-RPC types are the following
+ * (See the types at http://www.xmlrpc.com/spec):
+ * - "boolean": 0 (false) or 1 (true).
+ * - "double": a floating point number (for example, -12.214).
+ * - "int": a integer number (for example, -12).
+ * - "array": an array without keys (for example, array(1, 2, 3)).
+ * - "struct": an associative array or an object (for example,
+ * array('one' => 1, 'two' => 2)).
+ * - "date": when you return a date, then you may either return a
+ * timestamp (time(), mktime() etc.) or an ISO8601 timestamp. When
+ * date is specified as an input parameter, then you get an object,
+ * which is described in the function xmlrpc_date
+ * - "base64": a string containing binary data, automatically
+ * encoded/decoded automatically.
+ * - "string": anything else, typically a string.
+ * - A descriptive help string, enclosed in a t() function for translation
+ * purposes.
+ * Both forms are shown in the example.
+ */
+function hook_xmlrpc() {
+ return array(
+ 'drupal.login' => 'drupal_login',
+ array(
+ 'drupal.site.ping',
+ 'drupal_directory_ping',
+ array('boolean', 'string', 'string', 'string', 'string', 'string'),
+ t('Handling ping request'))
+ );
+}
+
+/**
+ * Alters the definition of XML-RPC methods before they are called.
+ *
+ * This hook allows modules to modify the callback definition of declared
+ * XML-RPC methods, right before they are invoked by a client. Methods may be
+ * added, or existing methods may be altered.
+ *
+ * Note that hook_xmlrpc() supports two distinct and incompatible formats to
+ * define a callback, so care must be taken when altering other methods.
+ *
+ * @param $methods
+ * An asssociative array of method callback definitions, as returned from
+ * hook_xmlrpc() implementations.
+ *
+ * @see hook_xmlrpc()
+ * @see xmlrpc_server()
+ */
+function hook_xmlrpc_alter(&$methods) {
+ // Directly change a simple method.
+ $methods['drupal.login'] = 'mymodule_login';
+
+ // Alter complex definitions.
+ foreach ($methods as $key => &$method) {
+ // Skip simple method definitions.
+ if (!is_int($key)) {
+ continue;
+ }
+ // Perform the wanted manipulation.
+ if ($method[0] == 'drupal.site.ping') {
+ $method[1] = 'mymodule_directory_ping';
+ }
+ }
+}
+
+/**
+ * Log an event message
+ *
+ * This hook allows modules to route log events to custom destinations, such as
+ * SMS, Email, pager, syslog, ...etc.
+ *
+ * @param $log_entry
+ * An associative array containing the following keys:
+ * - type: The type of message for this entry. For contributed modules, this is
+ * normally the module name. Do not use 'debug', use severity WATCHDOG_DEBUG instead.
+ * - user: The user object for the user who was logged in when the event happened.
+ * - request_uri: The Request URI for the page the event happened in.
+ * - referer: The page that referred the use to the page where the event occurred.
+ * - ip: The IP address where the request for the page came from.
+ * - timestamp: The UNIX timestamp of the date/time the event occurred
+ * - severity: One of the following values as defined in RFC 3164 http://www.faqs.org/rfcs/rfc3164.html
+ * WATCHDOG_EMERGENCY Emergency: system is unusable
+ * WATCHDOG_ALERT Alert: action must be taken immediately
+ * WATCHDOG_CRITICAL Critical: critical conditions
+ * WATCHDOG_ERROR Error: error conditions
+ * WATCHDOG_WARNING Warning: warning conditions
+ * WATCHDOG_NOTICE Notice: normal but significant condition
+ * WATCHDOG_INFO Informational: informational messages
+ * WATCHDOG_DEBUG Debug: debug-level messages
+ * - link: an optional link provided by the module that called the watchdog() function.
+ * - message: The text of the message to be logged.
+ */
+function hook_watchdog(array $log_entry) {
+ global $base_url, $language;
+
+ $severity_list = array(
+ WATCHDOG_EMERGENCY => t('Emergency'),
+ WATCHDOG_ALERT => t('Alert'),
+ WATCHDOG_CRITICALI => t('Critical'),
+ WATCHDOG_ERROR => t('Error'),
+ WATCHDOG_WARNING => t('Warning'),
+ WATCHDOG_NOTICE => t('Notice'),
+ WATCHDOG_INFO => t('Info'),
+ WATCHDOG_DEBUG => t('Debug'),
+ );
+
+ $to = 'someone@example.com';
+ $params = array();
+ $params['subject'] = t('[@site_name] @severity_desc: Alert from your web site', array(
+ '@site_name' => variable_get('site_name', 'Drupal'),
+ '@severity_desc' => $severity_list[$log_entry['severity']],
+ ));
+
+ $params['message'] = "\nSite: @base_url";
+ $params['message'] .= "\nSeverity: (@severity) @severity_desc";
+ $params['message'] .= "\nTimestamp: @timestamp";
+ $params['message'] .= "\nType: @type";
+ $params['message'] .= "\nIP Address: @ip";
+ $params['message'] .= "\nRequest URI: @request_uri";
+ $params['message'] .= "\nReferrer URI: @referer_uri";
+ $params['message'] .= "\nUser: (@uid) @name";
+ $params['message'] .= "\nLink: @link";
+ $params['message'] .= "\nMessage: \n\n@message";
+
+ $params['message'] = t($params['message'], array(
+ '@base_url' => $base_url,
+ '@severity' => $log_entry['severity'],
+ '@severity_desc' => $severity_list[$log_entry['severity']],
+ '@timestamp' => format_date($log_entry['timestamp']),
+ '@type' => $log_entry['type'],
+ '@ip' => $log_entry['ip'],
+ '@request_uri' => $log_entry['request_uri'],
+ '@referer_uri' => $log_entry['referer'],
+ '@uid' => $log_entry['user']->uid,
+ '@name' => $log_entry['user']->name,
+ '@link' => strip_tags($log_entry['link']),
+ '@message' => strip_tags($log_entry['message']),
+ ));
+
+ drupal_mail('emaillog', 'entry', $to, $language, $params);
+}
+
+/**
+ * Prepare a message based on parameters; called from drupal_mail().
+ *
+ * Note that hook_mail(), unlike hook_mail_alter(), is only called on the
+ * $module argument to drupal_mail(), not all modules.
+ *
+ * @param $key
+ * An identifier of the mail.
+ * @param $message
+ * An array to be filled in. Elements in this array include:
+ * - id: An ID to identify the mail sent. Look at module source code
+ * or drupal_mail() for possible id values.
+ * - to: The address or addresses the message will be sent to. The
+ * formatting of this string must comply with RFC 2822.
+ * - subject: Subject of the e-mail to be sent. This must not contain any
+ * newline characters, or the mail may not be sent properly. drupal_mail()
+ * sets this to an empty string when the hook is invoked.
+ * - body: An array of lines containing the message to be sent. Drupal will
+ * format the correct line endings for you. drupal_mail() sets this to an
+ * empty array when the hook is invoked.
+ * - from: The address the message will be marked as being from, which is
+ * set by drupal_mail() to either a custom address or the site-wide
+ * default email address when the hook is invoked.
+ * - headers: Associative array containing mail headers, such as From,
+ * Sender, MIME-Version, Content-Type, etc. drupal_mail() pre-fills
+ * several headers in this array.
+ * @param $params
+ * An array of parameters supplied by the caller of drupal_mail().
+ */
+function hook_mail($key, &$message, $params) {
+ $account = $params['account'];
+ $context = $params['context'];
+ $variables = array(
+ '%site_name' => variable_get('site_name', 'Drupal'),
+ '%username' => format_username($account),
+ );
+ if ($context['hook'] == 'taxonomy') {
+ $entity = $params['entity'];
+ $vocabulary = taxonomy_vocabulary_load($entity->vid);
+ $variables += array(
+ '%term_name' => $entity->name,
+ '%term_description' => $entity->description,
+ '%term_id' => $entity->tid,
+ '%vocabulary_name' => $vocabulary->name,
+ '%vocabulary_description' => $vocabulary->description,
+ '%vocabulary_id' => $vocabulary->vid,
+ );
+ }
+
+ // Node-based variable translation is only available if we have a node.
+ if (isset($params['node'])) {
+ $node = $params['node'];
+ $variables += array(
+ '%uid' => $node->uid,
+ '%node_url' => url('node/' . $node->nid, array('absolute' => TRUE)),
+ '%node_type' => node_type_get_name($node),
+ '%title' => $node->title,
+ '%teaser' => $node->teaser,
+ '%body' => $node->body,
+ );
+ }
+ $subject = strtr($context['subject'], $variables);
+ $body = strtr($context['message'], $variables);
+ $message['subject'] .= str_replace(array("\r", "\n"), '', $subject);
+ $message['body'][] = drupal_html_to_text($body);
+}
+
+/**
+ * Add a list of cache tables to be cleared.
+ *
+ * This hook allows your module to add cache bins to the list of cache bins
+ * that will be cleared by the Clear button on the Performance page or
+ * whenever drupal_flush_all_caches is invoked.
+ *
+ * @return
+ * An array of cache bins.
+ *
+ * @see drupal_flush_all_caches()
+ */
+function hook_flush_caches() {
+ return array('example');
+}
+
+/**
+ * Perform necessary actions before modules are installed.
+ *
+ * This function allows all modules to react prior to a module being installed.
+ *
+ * @param $modules
+ * An array of modules about to be installed.
+ */
+function hook_modules_preinstall($modules) {
+ mymodule_cache_clear();
+}
+
+/**
+ * Perform necessary actions before modules are enabled.
+ *
+ * This function allows all modules to react prior to a module being enabled.
+ *
+ * @param $module
+ * An array of modules about to be enabled.
+ */
+function hook_modules_preenable($modules) {
+ mymodule_cache_clear();
+}
+
+/**
+ * Perform necessary actions after modules are installed.
+ *
+ * This function differs from hook_install() in that it gives all other modules
+ * a chance to perform actions when a module is installed, whereas
+ * hook_install() is only called on the module actually being installed. See
+ * module_enable() for a detailed description of the order in which install and
+ * enable hooks are invoked.
+ *
+ * @param $modules
+ * An array of the modules that were installed.
+ *
+ * @see module_enable()
+ * @see hook_modules_enabled()
+ * @see hook_install()
+ */
+function hook_modules_installed($modules) {
+ if (in_array('lousy_module', $modules)) {
+ variable_set('lousy_module_conflicting_variable', FALSE);
+ }
+}
+
+/**
+ * Perform necessary actions after modules are enabled.
+ *
+ * This function differs from hook_enable() in that it gives all other modules a
+ * chance to perform actions when modules are enabled, whereas hook_enable() is
+ * only called on the module actually being enabled. See module_enable() for a
+ * detailed description of the order in which install and enable hooks are
+ * invoked.
+ *
+ * @param $modules
+ * An array of the modules that were enabled.
+ *
+ * @see hook_enable()
+ * @see hook_modules_installed()
+ * @see module_enable()
+ */
+function hook_modules_enabled($modules) {
+ if (in_array('lousy_module', $modules)) {
+ drupal_set_message(t('mymodule is not compatible with lousy_module'), 'error');
+ mymodule_disable_functionality();
+ }
+}
+
+/**
+ * Perform necessary actions after modules are disabled.
+ *
+ * This function differs from hook_disable() in that it gives all other modules
+ * a chance to perform actions when modules are disabled, whereas hook_disable()
+ * is only called on the module actually being disabled.
+ *
+ * @param $modules
+ * An array of the modules that were disabled.
+ *
+ * @see hook_disable()
+ * @see hook_modules_uninstalled()
+ */
+function hook_modules_disabled($modules) {
+ if (in_array('lousy_module', $modules)) {
+ mymodule_enable_functionality();
+ }
+}
+
+/**
+ * Perform necessary actions after modules are uninstalled.
+ *
+ * This function differs from hook_uninstall() in that it gives all other
+ * modules a chance to perform actions when a module is uninstalled, whereas
+ * hook_uninstall() is only called on the module actually being uninstalled.
+ *
+ * It is recommended that you implement this hook if your module stores
+ * data that may have been set by other modules.
+ *
+ * @param $modules
+ * An array of the modules that were uninstalled.
+ *
+ * @see hook_uninstall()
+ * @see hook_modules_disabled()
+ */
+function hook_modules_uninstalled($modules) {
+ foreach ($modules as $module) {
+ db_delete('mymodule_table')
+ ->condition('module', $module)
+ ->execute();
+ }
+ mymodule_cache_rebuild();
+}
+
+/**
+ * Registers PHP stream wrapper implementations associated with a module.
+ *
+ * Provide a facility for managing and querying user-defined stream wrappers
+ * in PHP. PHP's internal stream_get_wrappers() doesn't return the class
+ * registered to handle a stream, which we need to be able to find the handler
+ * for class instantiation.
+ *
+ * If a module registers a scheme that is already registered with PHP, it will
+ * be unregistered and replaced with the specified class.
+ *
+ * @return
+ * A nested array, keyed first by scheme name ("public" for "public://"),
+ * then keyed by the following values:
+ * - 'name' A short string to name the wrapper.
+ * - 'class' A string specifying the PHP class that implements the
+ * DrupalStreamWrapperInterface interface.
+ * - 'description' A string with a short description of what the wrapper does.
+ * - 'type' (Optional) A bitmask of flags indicating what type of streams this
+ * wrapper will access - local or remote, readable and/or writeable, etc.
+ * Many shortcut constants are defined in stream_wrappers.inc. Defaults to
+ * STREAM_WRAPPERS_NORMAL which includes all of these bit flags:
+ * - STREAM_WRAPPERS_READ
+ * - STREAM_WRAPPERS_WRITE
+ * - STREAM_WRAPPERS_VISIBLE
+ *
+ * @see file_get_stream_wrappers()
+ * @see hook_stream_wrappers_alter()
+ * @see system_stream_wrappers()
+ */
+function hook_stream_wrappers() {
+ return array(
+ 'public' => array(
+ 'name' => t('Public files'),
+ 'class' => 'DrupalPublicStreamWrapper',
+ 'description' => t('Public local files served by the webserver.'),
+ 'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
+ ),
+ 'private' => array(
+ 'name' => t('Private files'),
+ 'class' => 'DrupalPrivateStreamWrapper',
+ 'description' => t('Private local files served by Drupal.'),
+ 'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
+ ),
+ 'temp' => array(
+ 'name' => t('Temporary files'),
+ 'class' => 'DrupalTempStreamWrapper',
+ 'description' => t('Temporary local files for upload and previews.'),
+ 'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
+ ),
+ 'cdn' => array(
+ 'name' => t('Content delivery network files'),
+ 'class' => 'MyModuleCDNStreamWrapper',
+ 'description' => t('Files served by a content delivery network.'),
+ // 'type' can be omitted to use the default of STREAM_WRAPPERS_NORMAL
+ ),
+ 'youtube' => array(
+ 'name' => t('YouTube video'),
+ 'class' => 'MyModuleYouTubeStreamWrapper',
+ 'description' => t('Video streamed from YouTube.'),
+ // A module implementing YouTube integration may decide to support using
+ // the YouTube API for uploading video, but here, we assume that this
+ // particular module only supports playing YouTube video.
+ 'type' => STREAM_WRAPPERS_READ_VISIBLE,
+ ),
+ );
+}
+
+/**
+ * Alters the list of PHP stream wrapper implementations.
+ *
+ * @see file_get_stream_wrappers()
+ * @see hook_stream_wrappers()
+ */
+function hook_stream_wrappers_alter(&$wrappers) {
+ // Change the name of private files to reflect the performance.
+ $wrappers['private']['name'] = t('Slow files');
+}
+
+/**
+ * Load additional information into file objects.
+ *
+ * file_load_multiple() calls this hook to allow modules to load
+ * additional information into each file.
+ *
+ * @param $files
+ * An array of file objects, indexed by fid.
+ *
+ * @see file_load_multiple()
+ * @see upload_file_load()
+ */
+function hook_file_load($files) {
+ // Add the upload specific data into the file object.
+ $result = db_query('SELECT * FROM {upload} u WHERE u.fid IN (:fids)', array(':fids' => array_keys($files)))->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($result as $record) {
+ foreach ($record as $key => $value) {
+ $files[$record['fid']]->$key = $value;
+ }
+ }
+}
+
+/**
+ * Check that files meet a given criteria.
+ *
+ * This hook lets modules perform additional validation on files. They're able
+ * to report a failure by returning one or more error messages.
+ *
+ * @param $file
+ * The file object being validated.
+ * @return
+ * An array of error messages. If there are no problems with the file return
+ * an empty array.
+ *
+ * @see file_validate()
+ */
+function hook_file_validate($file) {
+ $errors = array();
+
+ if (empty($file->filename)) {
+ $errors[] = t("The file's name is empty. Please give a name to the file.");
+ }
+ if (strlen($file->filename) > 255) {
+ $errors[] = t("The file's name exceeds the 255 characters limit. Please rename the file and try again.");
+ }
+
+ return $errors;
+}
+
+/**
+ * Act on a file being inserted or updated.
+ *
+ * This hook is called when a file has been added to the database. The hook
+ * doesn't distinguish between files created as a result of a copy or those
+ * created by an upload.
+ *
+ * @param $file
+ * The file that has just been created.
+ *
+ * @see file_save()
+ */
+function hook_file_presave($file) {
+ // Change the file timestamp to an hour prior.
+ $file->timestamp -= 3600;
+}
+
+/**
+ * Respond to a file being added.
+ *
+ * This hook is called after a file has been added to the database. The hook
+ * doesn't distinguish between files created as a result of a copy or those
+ * created by an upload.
+ *
+ * @param $file
+ * The file that has been added.
+ *
+ * @see file_save()
+ */
+function hook_file_insert($file) {
+ // Add a message to the log, if the file is a jpg
+ $validate = file_validate_extensions($file, 'jpg');
+ if (empty($validate)) {
+ watchdog('file', 'A jpg has been added.');
+ }
+}
+
+/**
+ * Respond to a file being updated.
+ *
+ * This hook is called when file_save() is called on an existing file.
+ *
+ * @param $file
+ * The file that has just been updated.
+ *
+ * @see file_save()
+ */
+function hook_file_update($file) {
+
+}
+
+/**
+ * Respond to a file that has been copied.
+ *
+ * @param $file
+ * The newly copied file object.
+ * @param $source
+ * The original file before the copy.
+ *
+ * @see file_copy()
+ */
+function hook_file_copy($file, $source) {
+
+}
+
+/**
+ * Respond to a file that has been moved.
+ *
+ * @param $file
+ * The updated file object after the move.
+ * @param $source
+ * The original file object before the move.
+ *
+ * @see file_move()
+ */
+function hook_file_move($file, $source) {
+
+}
+
+/**
+ * Respond to a file being deleted.
+ *
+ * @param $file
+ * The file that has just been deleted.
+ *
+ * @see file_delete()
+ * @see upload_file_delete()
+ */
+function hook_file_delete($file) {
+ // Delete all information associated with the file.
+ db_delete('upload')->condition('fid', $file->fid)->execute();
+}
+
+/**
+ * Control access to private file downloads and specify HTTP headers.
+ *
+ * This hook allows modules enforce permissions on file downloads when the
+ * private file download method is selected. Modules can also provide headers
+ * to specify information like the file's name or MIME type.
+ *
+ * @param $uri
+ * The URI of the file.
+ * @return
+ * If the user does not have permission to access the file, return -1. If the
+ * user has permission, return an array with the appropriate headers. If the
+ * file is not controlled by the current module, the return value should be
+ * NULL.
+ *
+ * @see file_download()
+ */
+function hook_file_download($uri) {
+ // Check if the file is controlled by the current module.
+ if (!file_prepare_directory($uri)) {
+ $uri = FALSE;
+ }
+ if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
+ if (!user_access('access user profiles')) {
+ // Access to the file is denied.
+ return -1;
+ }
+ else {
+ $info = image_get_info($uri);
+ return array('Content-Type' => $info['mime_type']);
+ }
+ }
+}
+
+/**
+ * Alter the URL to a file.
+ *
+ * This hook is called from file_create_url(), and is called fairly
+ * frequently (10+ times per page), depending on how many files there are in a
+ * given page.
+ * If CSS and JS aggregation are disabled, this can become very frequently
+ * (50+ times per page) so performance is critical.
+ *
+ * This function should alter the URI, if it wants to rewrite the file URL.
+ *
+ * @param $uri
+ * The URI to a file for which we need an external URL, or the path to a
+ * shipped file.
+ */
+function hook_file_url_alter(&$uri) {
+ global $user;
+
+ // User 1 will always see the local file in this example.
+ if ($user->uid == 1) {
+ return;
+ }
+
+ $cdn1 = 'http://cdn1.example.com';
+ $cdn2 = 'http://cdn2.example.com';
+ $cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png');
+
+ // Most CDNs don't support private file transfers without a lot of hassle,
+ // so don't support this in the common case.
+ $schemes = array('public');
+
+ $scheme = file_uri_scheme($uri);
+
+ // Only serve shipped files and public created files from the CDN.
+ if (!$scheme || in_array($scheme, $schemes)) {
+ // Shipped files.
+ if (!$scheme) {
+ $path = $uri;
+ }
+ // Public created files.
+ else {
+ $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
+ $path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
+ }
+
+ // Clean up Windows paths.
+ $path = str_replace('\\', '/', $path);
+
+ // Serve files with one of the CDN extensions from CDN 1, all others from
+ // CDN 2.
+ $pathinfo = pathinfo($path);
+ if (isset($pathinfo['extension']) && in_array($pathinfo['extension'], $cdn_extensions)) {
+ $uri = $cdn1 . '/' . $path;
+ }
+ else {
+ $uri = $cdn2 . '/' . $path;
+ }
+ }
+}
+
+/**
+ * Check installation requirements and do status reporting.
+ *
+ * This hook has three closely related uses, determined by the $phase argument:
+ * - Checking installation requirements ($phase == 'install').
+ * - Checking update requirements ($phase == 'update').
+ * - Status reporting ($phase == 'runtime').
+ *
+ * Note that this hook, like all others dealing with installation and updates,
+ * must reside in a module_name.install file, or it will not properly abort
+ * the installation of the module if a critical requirement is missing.
+ *
+ * During the 'install' phase, modules can for example assert that
+ * library or server versions are available or sufficient.
+ * Note that the installation of a module can happen during installation of
+ * Drupal itself (by install.php) with an installation profile or later by hand.
+ * As a consequence, install-time requirements must be checked without access
+ * to the full Drupal API, because it is not available during install.php.
+ * For localization you should for example use $t = get_t() to
+ * retrieve the appropriate localization function name (t() or st()).
+ * If a requirement has a severity of REQUIREMENT_ERROR, install.php will abort
+ * or at least the module will not install.
+ * Other severity levels have no effect on the installation.
+ * Module dependencies do not belong to these installation requirements,
+ * but should be defined in the module's .info file.
+ *
+ * The 'runtime' phase is not limited to pure installation requirements
+ * but can also be used for more general status information like maintenance
+ * tasks and security issues.
+ * The returned 'requirements' will be listed on the status report in the
+ * administration section, with indication of the severity level.
+ * Moreover, any requirement with a severity of REQUIREMENT_ERROR severity will
+ * result in a notice on the the administration overview page.
+ *
+ * @param $phase
+ * The phase in which requirements are checked:
+ * - install: The module is being installed.
+ * - update: The module is enabled and update.php is run.
+ * - runtime: The runtime requirements are being checked and shown on the
+ * status report page.
+ *
+ * @return
+ * A keyed array of requirements. Each requirement is itself an array with
+ * the following items:
+ * - title: The name of the requirement.
+ * - value: The current value (e.g., version, time, level, etc). During
+ * install phase, this should only be used for version numbers, do not set
+ * it if not applicable.
+ * - description: The description of the requirement/status.
+ * - severity: The requirement's result/severity level, one of:
+ * - REQUIREMENT_INFO: For info only.
+ * - REQUIREMENT_OK: The requirement is satisfied.
+ * - REQUIREMENT_WARNING: The requirement failed with a warning.
+ * - REQUIREMENT_ERROR: The requirement failed with an error.
+ */
+function hook_requirements($phase) {
+ $requirements = array();
+ // Ensure translations don't break at install time
+ $t = get_t();
+
+ // Report Drupal version
+ if ($phase == 'runtime') {
+ $requirements['drupal'] = array(
+ 'title' => $t('Drupal'),
+ 'value' => VERSION,
+ 'severity' => REQUIREMENT_INFO
+ );
+ }
+
+ // Test PHP version
+ $requirements['php'] = array(
+ 'title' => $t('PHP'),
+ 'value' => ($phase == 'runtime') ? l(phpversion(), 'admin/logs/status/php') : phpversion(),
+ );
+ if (version_compare(phpversion(), DRUPAL_MINIMUM_PHP) < 0) {
+ $requirements['php']['description'] = $t('Your PHP installation is too old. Drupal requires at least PHP %version.', array('%version' => DRUPAL_MINIMUM_PHP));
+ $requirements['php']['severity'] = REQUIREMENT_ERROR;
+ }
+
+ // Report cron status
+ if ($phase == 'runtime') {
+ $cron_last = variable_get('cron_last');
+
+ if (is_numeric($cron_last)) {
+ $requirements['cron']['value'] = $t('Last run !time ago', array('!time' => format_interval(REQUEST_TIME - $cron_last)));
+ }
+ else {
+ $requirements['cron'] = array(
+ 'description' => $t('Cron has not run. It appears cron jobs have not been setup on your system. Check the help pages for <a href="@url">configuring cron jobs</a>.', array('@url' => 'http://drupal.org/cron')),
+ 'severity' => REQUIREMENT_ERROR,
+ 'value' => $t('Never run'),
+ );
+ }
+
+ $requirements['cron']['description'] .= ' ' . $t('You can <a href="@cron">run cron manually</a>.', array('@cron' => url('admin/logs/status/run-cron')));
+
+ $requirements['cron']['title'] = $t('Cron maintenance tasks');
+ }
+
+ return $requirements;
+}
+
+/**
+ * Define the current version of the database schema.
+ *
+ * A Drupal schema definition is an array structure representing one or
+ * more tables and their related keys and indexes. A schema is defined by
+ * hook_schema() which must live in your module's .install file.
+ *
+ * This hook is called at both install and uninstall time, and in the latter
+ * case, it cannot rely on the .module file being loaded or hooks being known.
+ * If the .module file is needed, it may be loaded with drupal_load().
+ *
+ * The tables declared by this hook will be automatically created when
+ * the module is first enabled, and removed when the module is uninstalled.
+ * This happens before hook_install() is invoked, and after hook_uninstall()
+ * is invoked, respectively.
+ *
+ * By declaring the tables used by your module via an implementation of
+ * hook_schema(), these tables will be available on all supported database
+ * engines. You don't have to deal with the different SQL dialects for table
+ * creation and alteration of the supported database engines *
+ * See the Schema API Handbook at http://drupal.org/node/146843 for
+ * details on schema definition structures.
+ *
+ * @return
+ * A schema definition structure array. For each element of the
+ * array, the key is a table name and the value is a table structure
+ * definition.
+ *
+ * @ingroup schemaapi
+ */
+function hook_schema() {
+ $schema['node'] = array(
+ // example (partial) specification for table "node"
+ 'description' => 'The base table for nodes.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The primary identifier for a node.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE),
+ 'vid' => array(
+ 'description' => 'The current {node_revision}.vid version identifier.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0),
+ 'type' => array(
+ 'description' => 'The {node_type} of this node.',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => ''),
+ 'title' => array(
+ 'description' => 'The title of this node, always treated as non-markup plain text.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => ''),
+ ),
+ 'indexes' => array(
+ 'node_changed' => array('changed'),
+ 'node_created' => array('created'),
+ ),
+ 'unique keys' => array(
+ 'nid_vid' => array('nid', 'vid'),
+ 'vid' => array('vid')
+ ),
+ 'foreign keys' => array(
+ 'node_revision' => array(
+ 'table' => 'node_revision',
+ 'columns' => array('vid' => 'vid'),
+ ),
+ 'node_author' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid')
+ ),
+ ),
+ 'primary key' => array('nid'),
+ );
+ return $schema;
+}
+
+/**
+ * Perform alterations to existing database schemas.
+ *
+ * When a module modifies the database structure of another module (by
+ * changing, adding or removing fields, keys or indexes), it should
+ * implement hook_schema_alter() to update the default $schema to take its
+ * changes into account.
+ *
+ * See hook_schema() for details on the schema definition structure.
+ *
+ * @param $schema
+ * Nested array describing the schemas for all modules.
+ */
+function hook_schema_alter(&$schema) {
+ // Add field to existing schema.
+ $schema['users']['fields']['timezone_id'] = array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Per-user timezone configuration.',
+ );
+}
+
+/**
+ * Perform alterations to a structured query.
+ *
+ * Structured (aka dynamic) queries that have tags associated may be altered by any module
+ * before the query is executed.
+ *
+ * @param $query
+ * A Query object describing the composite parts of a SQL query.
+ *
+ * @see hook_query_TAG_alter()
+ * @see node_query_node_access_alter()
+ * @see QueryAlterableInterface
+ * @see SelectQueryInterface
+ */
+function hook_query_alter(QueryAlterableInterface $query) {
+ if ($query->hasTag('micro_limit')) {
+ $query->range(0, 2);
+ }
+}
+
+/**
+ * Perform alterations to a structured query for a given tag.
+ *
+ * @param $query
+ * An Query object describing the composite parts of a SQL query.
+ *
+ * @see hook_query_alter()
+ * @see node_query_node_access_alter()
+ * @see QueryAlterableInterface
+ * @see SelectQueryInterface
+ */
+function hook_query_TAG_alter(QueryAlterableInterface $query) {
+ // Skip the extra expensive alterations if site has no node access control modules.
+ if (!node_access_view_all_nodes()) {
+ // Prevent duplicates records.
+ $query->distinct();
+ // The recognized operations are 'view', 'update', 'delete'.
+ if (!$op = $query->getMetaData('op')) {
+ $op = 'view';
+ }
+ // Skip the extra joins and conditions for node admins.
+ if (!user_access('bypass node access')) {
+ // The node_access table has the access grants for any given node.
+ $access_alias = $query->join('node_access', 'na', '%alias.nid = n.nid');
+ $or = db_or();
+ // If any grant exists for the specified user, then user has access to the node for the specified operation.
+ foreach (node_access_grants($op, $query->getMetaData('account')) as $realm => $gids) {
+ foreach ($gids as $gid) {
+ $or->condition(db_and()
+ ->condition($access_alias . '.gid', $gid)
+ ->condition($access_alias . '.realm', $realm)
+ );
+ }
+ }
+
+ if (count($or->conditions())) {
+ $query->condition($or);
+ }
+
+ $query->condition($access_alias . 'grant_' . $op, 1, '>=');
+ }
+ }
+}
+
+/**
+ * Perform setup tasks when the module is installed.
+ *
+ * If the module implements hook_schema(), the database tables will
+ * be created before this hook is fired.
+ *
+ * Implementations of this hook are by convention declared in the module's
+ * .install file. The implementation can rely on the .module file being loaded.
+ * The hook will only be called the first time a module is enabled or after it
+ * is re-enabled after being uninstalled. The module's schema version will be
+ * set to the module's greatest numbered update hook. Because of this, any time
+ * a hook_update_N() is added to the module, this function needs to be updated
+ * to reflect the current version of the database schema.
+ *
+ * See the Schema API documentation at
+ * @link http://drupal.org/node/146843 http://drupal.org/node/146843 @endlink
+ * for details on hook_schema and how database tables are defined.
+ *
+ * Note that since this function is called from a full bootstrap, all functions
+ * (including those in modules enabled by the current page request) are
+ * available when this hook is called. Use cases could be displaying a user
+ * message, or calling a module function necessary for initial setup, etc.
+ *
+ * Please be sure that anything added or modified in this function that can
+ * be removed during uninstall should be removed with hook_uninstall().
+ *
+ * @see hook_schema()
+ * @see module_enable()
+ * @see hook_enable()
+ * @see hook_disable()
+ * @see hook_uninstall()
+ * @see hook_modules_installed()
+ */
+function hook_install() {
+ // Populate the default {node_access} record.
+ db_insert('node_access')
+ ->fields(array(
+ 'nid' => 0,
+ 'gid' => 0,
+ 'realm' => 'all',
+ 'grant_view' => 1,
+ 'grant_update' => 0,
+ 'grant_delete' => 0,
+ ))
+ ->execute();
+}
+
+/**
+ * Perform a single update.
+ *
+ * For each patch which requires a database change add a new hook_update_N()
+ * which will be called by update.php. The database updates are numbered
+ * sequentially according to the version of Drupal you are compatible with.
+ *
+ * Schema updates should adhere to the Schema API:
+ * @link http://drupal.org/node/150215 http://drupal.org/node/150215 @endlink
+ *
+ * Database updates consist of 3 parts:
+ * - 1 digit for Drupal core compatibility
+ * - 1 digit for your module's major release version (e.g. is this the 5.x-1.* (1) or 5.x-2.* (2) series of your module?)
+ * - 2 digits for sequential counting starting with 00
+ *
+ * The 2nd digit should be 0 for initial porting of your module to a new Drupal
+ * core API.
+ *
+ * Examples:
+ * - mymodule_update_5200()
+ * - This is the first update to get the database ready to run mymodule 5.x-2.*.
+ * - mymodule_update_6000()
+ * - This is the required update for mymodule to run with Drupal core API 6.x.
+ * - mymodule_update_6100()
+ * - This is the first update to get the database ready to run mymodule 6.x-1.*.
+ * - mymodule_update_6200()
+ * - This is the first update to get the database ready to run mymodule 6.x-2.*.
+ * Users can directly update from 5.x-2.* to 6.x-2.* and they get all 60XX
+ * and 62XX updates, but not 61XX updates, because those reside in the
+ * 6.x-1.x branch only.
+ *
+ * A good rule of thumb is to remove updates older than two major releases of
+ * Drupal. See hook_update_last_removed() to notify Drupal about the removals.
+ *
+ * Never renumber update functions.
+ *
+ * Further information about releases and release numbers:
+ * - @link http://drupal.org/handbook/version-info http://drupal.org/handbook/version-info @endlink
+ * - @link http://drupal.org/node/93999 http://drupal.org/node/93999 @endlink (Overview of contributions branches and tags)
+ * - @link http://drupal.org/handbook/cvs/releases http://drupal.org/handbook/cvs/releases @endlink
+ *
+ * Implementations of this hook should be placed in a mymodule.install file in
+ * the same directory as mymodule.module. Drupal core's updates are implemented
+ * using the system module as a name and stored in database/updates.inc.
+ *
+ * If your update task is potentially time-consuming, you'll need to implement a
+ * multipass update to avoid PHP timeouts. Multipass updates use the $sandbox
+ * parameter provided by the batch API (normally, $context['sandbox']) to store
+ * information between successive calls, and the $sandbox['#finished'] value
+ * to provide feedback regarding completion level.
+ *
+ * See the batch operations page for more information on how to use the batch API:
+ * @link http://drupal.org/node/180528 http://drupal.org/node/180528 @endlink
+ *
+ * @param $sandbox
+ * Stores information for multipass updates. See above for more information.
+ *
+ * @throws DrupalUpdateException, PDOException
+ * In case of error, update hooks should throw an instance of DrupalUpdateException
+ * with a meaningful message for the user. If a database query fails for whatever
+ * reason, it will throw a PDOException.
+ *
+ * @return
+ * Optionally update hooks may return a translated string that will be displayed
+ * to the user. If no message is returned, no message will be presented to the
+ * user.
+ */
+function hook_update_N(&$sandbox) {
+ // For non-multipass updates, the signature can simply be;
+ // function hook_update_N() {
+
+ // For most updates, the following is sufficient.
+ db_add_field('mytable1', 'newcol', array('type' => 'int', 'not null' => TRUE, 'description' => 'My new integer column.'));
+
+ // However, for more complex operations that may take a long time,
+ // you may hook into Batch API as in the following example.
+
+ // Update 3 users at a time to have an exclamation point after their names.
+ // (They're really happy that we can do batch API in this hook!)
+ if (!isset($sandbox['progress'])) {
+ $sandbox['progress'] = 0;
+ $sandbox['current_uid'] = 0;
+ // We'll -1 to disregard the uid 0...
+ $sandbox['max'] = db_query('SELECT COUNT(DISTINCT uid) FROM {users}')->fetchField() - 1;
+ }
+
+ $users = db_select('users', 'u')
+ ->fields('u', array('uid', 'name'))
+ ->condition('uid', $sandbox['current_uid'], '>')
+ ->range(0, 3)
+ ->orderBy('uid', 'ASC')
+ ->execute();
+
+ foreach ($users as $user) {
+ $user->name .= '!';
+ db_update('users')
+ ->fields(array('name' => $user->name))
+ ->condition('uid', $user->uid)
+ ->execute();
+
+ $sandbox['progress']++;
+ $sandbox['current_uid'] = $user->uid;
+ }
+
+ $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
+
+ // To display a message to the user when the update is completed, return it.
+ // If you do not want to display a completion message, simply return nothing.
+ return t('The update did what it was supposed to do.');
+
+ // In case of an error, simply throw an exception with an error message.
+ throw new DrupalUpdateException('Something went wrong; here is what you should do.');
+}
+
+/**
+ * Return an array of information about module update dependencies.
+ *
+ * This can be used to indicate update functions from other modules that your
+ * module's update functions depend on, or vice versa. It is used by the update
+ * system to determine the appropriate order in which updates should be run, as
+ * well as to search for missing dependencies.
+ *
+ * Implementations of this hook should be placed in a mymodule.install file in
+ * the same directory as mymodule.module.
+ *
+ * @return
+ * A multidimensional array containing information about the module update
+ * dependencies. The first two levels of keys represent the module and update
+ * number (respectively) for which information is being returned, and the
+ * value is an array of information about that update's dependencies. Within
+ * this array, each key represents a module, and each value represents the
+ * number of an update function within that module. In the event that your
+ * update function depends on more than one update from a particular module,
+ * you should always list the highest numbered one here (since updates within
+ * a given module always run in numerical order).
+ *
+ * @see update_resolve_dependencies()
+ * @see hook_update_N()
+ */
+function hook_update_dependencies() {
+ // Indicate that the mymodule_update_8000() function provided by this module
+ // must run after the another_module_update_8002() function provided by the
+ // 'another_module' module.
+ $dependencies['mymodule'][8000] = array(
+ 'another_module' => 8002,
+ );
+ // Indicate that the mymodule_update_8001() function provided by this module
+ // must run before the yet_another_module_update_8004() function provided by
+ // the 'yet_another_module' module. (Note that declaring dependencies in this
+ // direction should be done only in rare situations, since it can lead to the
+ // following problem: If a site has already run the yet_another_module
+ // module's database updates before it updates its codebase to pick up the
+ // newest mymodule code, then the dependency declared here will be ignored.)
+ $dependencies['yet_another_module'][8004] = array(
+ 'mymodule' => 8001,
+ );
+ return $dependencies;
+}
+
+/**
+ * Return a number which is no longer available as hook_update_N().
+ *
+ * If you remove some update functions from your mymodule.install file, you
+ * should notify Drupal of those missing functions. This way, Drupal can
+ * ensure that no update is accidentally skipped.
+ *
+ * Implementations of this hook should be placed in a mymodule.install file in
+ * the same directory as mymodule.module.
+ *
+ * @return
+ * An integer, corresponding to hook_update_N() which has been removed from
+ * mymodule.install.
+ *
+ * @see hook_update_N()
+ */
+function hook_update_last_removed() {
+ // We've removed the 5.x-1.x version of mymodule, including database updates.
+ // The next update function is mymodule_update_5200().
+ return 5103;
+}
+
+/**
+ * Remove any information that the module sets.
+ *
+ * The information that the module should remove includes:
+ * - variables that the module has set using variable_set() or system_settings_form()
+ * - modifications to existing tables
+ *
+ * The module should not remove its entry from the {system} table. Database
+ * tables defined by hook_schema() will be removed automatically.
+ *
+ * The uninstall hook must be implemented in the module's .install file. It
+ * will fire when the module gets uninstalled but before the module's database
+ * tables are removed, allowing your module to query its own tables during
+ * this routine.
+ *
+ * When hook_uninstall() is called, your module will already be disabled, so
+ * its .module file will not be automatically included. If you need to call API
+ * functions from your .module file in this hook, use drupal_load() to make
+ * them available. (Keep this usage to a minimum, though, especially when
+ * calling API functions that invoke hooks, or API functions from modules
+ * listed as dependencies, since these may not be available or work as expected
+ * when the module is disabled.)
+ *
+ * @see hook_install()
+ * @see hook_schema()
+ * @see hook_disable()
+ * @see hook_modules_uninstalled()
+ */
+function hook_uninstall() {
+ variable_del('upload_file_types');
+}
+
+/**
+ * Perform necessary actions after module is enabled.
+ *
+ * The hook is called every time the module is enabled. It should be
+ * implemented in the module's .install file. The implementation can
+ * rely on the .module file being loaded.
+ *
+ * @see module_enable()
+ * @see hook_install()
+ * @see hook_modules_enabled()
+ */
+function hook_enable() {
+ mymodule_cache_rebuild();
+}
+
+/**
+ * Perform necessary actions before module is disabled.
+ *
+ * The hook is called every time the module is disabled. It should be
+ * implemented in the module's .install file. The implementation can rely
+ * on the .module file being loaded.
+ *
+ * @see hook_uninstall()
+ * @see hook_modules_disabled()
+ */
+function hook_disable() {
+ mymodule_cache_rebuild();
+}
+
+/**
+ * Perform necessary alterations to the list of files parsed by the registry.
+ *
+ * Modules can manually modify the list of files before the registry parses
+ * them. The $modules array provides the .info file information, which includes
+ * the list of files registered to each module. Any files in the list can then
+ * be added to the list of files that the registry will parse, or modify
+ * attributes of a file.
+ *
+ * A necessary alteration made by the core SimpleTest module is to force .test
+ * files provided by disabled modules into the list of files parsed by the
+ * registry.
+ *
+ * @param $files
+ * List of files to be parsed by the registry. The list will contain
+ * files found in each enabled module's info file and the core includes
+ * directory. The array is keyed by the file path and contains an array of
+ * the related module's name and weight as used internally by
+ * _registry_update() and related functions.
+ *
+ * For example:
+ * @code
+ * $files["modules/system/system.module"] = array(
+ * 'module' => 'system',
+ * 'weight' => 0,
+ * );
+ * @endcode
+ * @param $modules
+ * An array containing all module information stored in the {system} table.
+ * Each element of the array also contains the module's .info file
+ * information in the property 'info'. An additional 'dir' property has been
+ * added to the module information which provides the path to the directory
+ * in which the module resides. The example shows how to take advantage of
+ * both properties.
+ *
+ * @see _registry_update()
+ * @see simpletest_test_get_all()
+ */
+function hook_registry_files_alter(&$files, $modules) {
+ foreach ($modules as $module) {
+ // Only add test files for disabled modules, as enabled modules should
+ // already include any test files they provide.
+ if (!$module->status) {
+ $dir = $module->dir;
+ foreach ($module->info['files'] as $file) {
+ if (substr($file, -5) == '.test') {
+ $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Return an array of tasks to be performed by an installation profile.
+ *
+ * Any tasks you define here will be run, in order, after the installer has
+ * finished the site configuration step but before it has moved on to the
+ * final import of languages and the end of the installation. You can have any
+ * number of custom tasks to perform during this phase.
+ *
+ * Each task you define here corresponds to a callback function which you must
+ * separately define and which is called when your task is run. This function
+ * will receive the global installation state variable, $install_state, as
+ * input, and has the opportunity to access or modify any of its settings. See
+ * the install_state_defaults() function in the installer for the list of
+ * $install_state settings used by Drupal core.
+ *
+ * At the end of your task function, you can indicate that you want the
+ * installer to pause and display a page to the user by returning any themed
+ * output that should be displayed on that page (but see below for tasks that
+ * use the form API or batch API; the return values of these task functions are
+ * handled differently). You should also use drupal_set_title() within the task
+ * callback function to set a custom page title. For some tasks, however, you
+ * may want to simply do some processing and pass control to the next task
+ * without ending the page request; to indicate this, simply do not send back
+ * a return value from your task function at all. This can be used, for
+ * example, by installation profiles that need to configure certain site
+ * settings in the database without obtaining any input from the user.
+ *
+ * The task function is treated specially if it defines a form or requires
+ * batch processing; in that case, you should return either the form API
+ * definition or batch API array, as appropriate. See below for more
+ * information on the 'type' key that you must define in the task definition
+ * to inform the installer that your task falls into one of those two
+ * categories. It is important to use these APIs directly, since the installer
+ * may be run non-interactively (for example, via a command line script), all
+ * in one page request; in that case, the installer will automatically take
+ * care of submitting forms and processing batches correctly for both types of
+ * installations. You can inspect the $install_state['interactive'] boolean to
+ * see whether or not the current installation is interactive, if you need
+ * access to this information.
+ *
+ * Remember that a user installing Drupal interactively will be able to reload
+ * an installation page multiple times, so you should use variable_set() and
+ * variable_get() if you are collecting any data that you need to store and
+ * inspect later. It is important to remove any temporary variables using
+ * variable_del() before your last task has completed and control is handed
+ * back to the installer.
+ *
+ * @return
+ * A keyed array of tasks the profile will perform during the final stage of
+ * the installation. Each key represents the name of a function (usually a
+ * function defined by this profile, although that is not strictly required)
+ * that is called when that task is run. The values are associative arrays
+ * containing the following key-value pairs (all of which are optional):
+ * - 'display_name'
+ * The human-readable name of the task. This will be displayed to the
+ * user while the installer is running, along with a list of other tasks
+ * that are being run. Leave this unset to prevent the task from
+ * appearing in the list.
+ * - 'display'
+ * This is a boolean which can be used to provide finer-grained control
+ * over whether or not the task will display. This is mostly useful for
+ * tasks that are intended to display only under certain conditions; for
+ * these tasks, you can set 'display_name' to the name that you want to
+ * display, but then use this boolean to hide the task only when certain
+ * conditions apply.
+ * - 'type'
+ * A string representing the type of task. This parameter has three
+ * possible values:
+ * - 'normal': This indicates that the task will be treated as a regular
+ * callback function, which does its processing and optionally returns
+ * HTML output. This is the default behavior which is used when 'type' is
+ * not set.
+ * - 'batch': This indicates that the task function will return a batch
+ * API definition suitable for batch_set(). The installer will then take
+ * care of automatically running the task via batch processing.
+ * - 'form': This indicates that the task function will return a standard
+ * form API definition (and separately define validation and submit
+ * handlers, as appropriate). The installer will then take care of
+ * automatically directing the user through the form submission process.
+ * - 'run'
+ * A constant representing the manner in which the task will be run. This
+ * parameter has three possible values:
+ * - INSTALL_TASK_RUN_IF_NOT_COMPLETED: This indicates that the task will
+ * run once during the installation of the profile. This is the default
+ * behavior which is used when 'run' is not set.
+ * - INSTALL_TASK_SKIP: This indicates that the task will not run during
+ * the current installation page request. It can be used to skip running
+ * an installation task when certain conditions are met, even though the
+ * task may still show on the list of installation tasks presented to the
+ * user.
+ * - INSTALL_TASK_RUN_IF_REACHED: This indicates that the task will run
+ * on each installation page request that reaches it. This is rarely
+ * necessary for an installation profile to use; it is primarily used by
+ * the Drupal installer for bootstrap-related tasks.
+ * - 'function'
+ * Normally this does not need to be set, but it can be used to force the
+ * installer to call a different function when the task is run (rather
+ * than the function whose name is given by the array key). This could be
+ * used, for example, to allow the same function to be called by two
+ * different tasks.
+ *
+ * @see install_state_defaults()
+ * @see batch_set()
+ */
+function hook_install_tasks() {
+ // Here, we define a variable to allow tasks to indicate that a particular,
+ // processor-intensive batch process needs to be triggered later on in the
+ // installation.
+ $myprofile_needs_batch_processing = variable_get('myprofile_needs_batch_processing', FALSE);
+ $tasks = array(
+ // This is an example of a task that defines a form which the user who is
+ // installing the site will be asked to fill out. To implement this task,
+ // your profile would define a function named myprofile_data_import_form()
+ // as a normal form API callback function, with associated validation and
+ // submit handlers. In the submit handler, in addition to saving whatever
+ // other data you have collected from the user, you might also call
+ // variable_set('myprofile_needs_batch_processing', TRUE) if the user has
+ // entered data which requires that batch processing will need to occur
+ // later on.
+ 'myprofile_data_import_form' => array(
+ 'display_name' => st('Data import options'),
+ 'type' => 'form',
+ ),
+ // Similarly, to implement this task, your profile would define a function
+ // named myprofile_settings_form() with associated validation and submit
+ // handlers. This form might be used to collect and save additional
+ // information from the user that your profile needs. There are no extra
+ // steps required for your profile to act as an "installation wizard"; you
+ // can simply define as many tasks of type 'form' as you wish to execute,
+ // and the forms will be presented to the user, one after another.
+ 'myprofile_settings_form' => array(
+ 'display_name' => st('Additional options'),
+ 'type' => 'form',
+ ),
+ // This is an example of a task that performs batch operations. To
+ // implement this task, your profile would define a function named
+ // myprofile_batch_processing() which returns a batch API array definition
+ // that the installer will use to execute your batch operations. Due to the
+ // 'myprofile_needs_batch_processing' variable used here, this task will be
+ // hidden and skipped unless your profile set it to TRUE in one of the
+ // previous tasks.
+ 'myprofile_batch_processing' => array(
+ 'display_name' => st('Import additional data'),
+ 'display' => $myprofile_needs_batch_processing,
+ 'type' => 'batch',
+ 'run' => $myprofile_needs_batch_processing ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
+ ),
+ // This is an example of a task that will not be displayed in the list that
+ // the user sees. To implement this task, your profile would define a
+ // function named myprofile_final_site_setup(), in which additional,
+ // automated site setup operations would be performed. Since this is the
+ // last task defined by your profile, you should also use this function to
+ // call variable_del('myprofile_needs_batch_processing') and clean up the
+ // variable that was used above. If you want the user to pass to the final
+ // Drupal installation tasks uninterrupted, return no output from this
+ // function. Otherwise, return themed output that the user will see (for
+ // example, a confirmation page explaining that your profile's tasks are
+ // complete, with a link to reload the current page and therefore pass on
+ // to the final Drupal installation tasks when the user is ready to do so).
+ 'myprofile_final_site_setup' => array(
+ ),
+ );
+ return $tasks;
+}
+
+/**
+ * Change the page the user is sent to by drupal_goto().
+ *
+ * @param $path
+ * A Drupal path or a full URL.
+ * @param $options
+ * An associative array of additional URL options to pass to url().
+ * @param $http_response_code
+ * The HTTP status code to use for the redirection. See drupal_goto() for more
+ * information.
+ */
+function hook_drupal_goto_alter(&$path, &$options, &$http_response_code) {
+ // A good addition to misery module.
+ $http_response_code = 500;
+}
+
+/**
+ * Alter XHTML HEAD tags before they are rendered by drupal_get_html_head().
+ *
+ * Elements available to be altered are only those added using
+ * drupal_add_html_head_link() or drupal_add_html_head(). CSS and JS files
+ * are handled using drupal_add_css() and drupal_add_js(), so the head links
+ * for those files will not appear in the $head_elements array.
+ *
+ * @param $head_elements
+ * An array of renderable elements. Generally the values of the #attributes
+ * array will be the most likely target for changes.
+ */
+function hook_html_head_alter(&$head_elements) {
+ foreach ($head_elements as $key => $element) {
+ if (isset($element['#attributes']['rel']) && $element['#attributes']['rel'] == 'canonical') {
+ // I want a custom canonical url.
+ $head_elements[$key]['#attributes']['href'] = mymodule_canonical_url();
+ }
+ }
+}
+
+/**
+ * Alter the full list of installation tasks.
+ *
+ * @param $tasks
+ * An array of all available installation tasks, including those provided by
+ * Drupal core. You can modify this array to change or replace any part of
+ * the Drupal installation process that occurs after the installation profile
+ * is selected.
+ * @param $install_state
+ * An array of information about the current installation state.
+ */
+function hook_install_tasks_alter(&$tasks, $install_state) {
+ // Replace the "Choose language" installation task provided by Drupal core
+ // with a custom callback function defined by this installation profile.
+ $tasks['install_select_locale']['function'] = 'myprofile_locale_selection';
+}
+
+/**
+ * Alter MIME type mappings used to determine MIME type from a file extension.
+ *
+ * This hook is run when file_mimetype_mapping() is called. It is used to
+ * allow modules to add to or modify the default mapping from
+ * file_default_mimetype_mapping().
+ *
+ * @param $mapping
+ * An array of mimetypes correlated to the extensions that relate to them.
+ * The array has 'mimetypes' and 'extensions' elements, each of which is an
+ * array.
+ *
+ * @see file_default_mimetype_mapping()
+ */
+function hook_file_mimetype_mapping_alter(&$mapping) {
+ // Add new MIME type 'drupal/info'.
+ $mapping['mimetypes']['example_info'] = 'drupal/info';
+ // Add new extension '.info' and map it to the 'drupal/info' MIME type.
+ $mapping['extensions']['info'] = 'example_info';
+ // Override existing extension mapping for '.ogg' files.
+ $mapping['extensions']['ogg'] = 189;
+}
+
+/**
+ * Declares information about actions.
+ *
+ * Any module can define actions, and then call actions_do() to make those
+ * actions happen in response to events. The trigger module provides a user
+ * interface for associating actions with module-defined triggers, and it makes
+ * sure the core triggers fire off actions when their events happen.
+ *
+ * An action consists of two or three parts:
+ * - an action definition (returned by this hook)
+ * - a function which performs the action (which by convention is named
+ * MODULE_description-of-function_action)
+ * - an optional form definition function that defines a configuration form
+ * (which has the name of the action function with '_form' appended to it.)
+ *
+ * The action function takes two to four arguments, which come from the input
+ * arguments to actions_do().
+ *
+ * @return
+ * An associative array of action descriptions. The keys of the array
+ * are the names of the action functions, and each corresponding value
+ * is an associative array with the following key-value pairs:
+ * - 'type': The type of object this action acts upon. Core actions have types
+ * 'node', 'user', 'comment', and 'system'.
+ * - 'label': The human-readable name of the action, which should be passed
+ * through the t() function for translation.
+ * - 'configurable': If FALSE, then the action doesn't require any extra
+ * configuration. If TRUE, then your module must define a form function with
+ * the same name as the action function with '_form' appended (e.g., the
+ * form for 'node_assign_owner_action' is 'node_assign_owner_action_form'.)
+ * This function takes $context as its only parameter, and is paired with
+ * the usual _submit function, and possibly a _validate function.
+ * - 'triggers': An array of the events (that is, hooks) that can trigger this
+ * action. For example: array('node_insert', 'user_update'). You can also
+ * declare support for any trigger by returning array('any') for this value.
+ * - 'behavior': (optional) A machine-readable array of behaviors of this
+ * action, used to signal additionally required actions that may need to be
+ * triggered. Currently recognized behaviors by Trigger module:
+ * - 'changes_property': If an action with this behavior is assigned to a
+ * trigger other than a "presave" hook, any save actions also assigned to
+ * this trigger are moved later in the list. If no save action is present,
+ * one will be added.
+ * Modules that are processing actions (like Trigger module) should take
+ * special care for the "presave" hook, in which case a dependent "save"
+ * action should NOT be invoked.
+ *
+ * @ingroup actions
+ */
+function hook_action_info() {
+ return array(
+ 'comment_unpublish_action' => array(
+ 'type' => 'comment',
+ 'label' => t('Unpublish comment'),
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
+ ),
+ 'comment_unpublish_by_keyword_action' => array(
+ 'type' => 'comment',
+ 'label' => t('Unpublish comment containing keyword(s)'),
+ 'configurable' => TRUE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
+ ),
+ 'comment_save_action' => array(
+ 'type' => 'comment',
+ 'label' => t('Save comment'),
+ 'configurable' => FALSE,
+ 'triggers' => array('comment_insert', 'comment_update'),
+ ),
+ );
+}
+
+/**
+ * Executes code after an action is deleted.
+ *
+ * @param $aid
+ * The action ID.
+ */
+function hook_actions_delete($aid) {
+ db_delete('actions_assignments')
+ ->condition('aid', $aid)
+ ->execute();
+}
+
+/**
+ * Alters the actions declared by another module.
+ *
+ * Called by actions_list() to allow modules to alter the return values from
+ * implementations of hook_action_info().
+ *
+ * @see trigger_example_action_info_alter()
+ */
+function hook_action_info_alter(&$actions) {
+ $actions['node_unpublish_action']['label'] = t('Unpublish and remove from public view.');
+}
+
+/**
+ * Declare archivers to the system.
+ *
+ * An archiver is a class that is able to package and unpackage one or more files
+ * into a single possibly compressed file. Common examples of such files are
+ * zip files and tar.gz files. All archiver classes must implement
+ * ArchiverInterface.
+ *
+ * Each entry should be keyed on a unique value, and specify three
+ * additional keys:
+ * - class: The name of the PHP class for this archiver.
+ * - extensions: An array of file extensions that this archiver supports.
+ * - weight: This optional key specifies the weight of this archiver.
+ * When mapping file extensions to archivers, the first archiver by
+ * weight found that supports the requested extension will be used.
+ *
+ * @see hook_archiver_info_alter()
+ */
+function hook_archiver_info() {
+ return array(
+ 'tar' => array(
+ 'class' => 'ArchiverTar',
+ 'extensions' => array('tar', 'tar.gz', 'tar.bz2'),
+ ),
+ );
+}
+
+/**
+ * Alter archiver information declared by other modules.
+ *
+ * See hook_archiver_info() for a description of archivers and the archiver
+ * information structure.
+ *
+ * @param $info
+ * Archiver information to alter (return values from hook_archiver_info()).
+ */
+function hook_archiver_info_alter(&$info) {
+ $info['tar']['extensions'][] = 'tgz';
+}
+
+/**
+ * Define additional date types.
+ *
+ * Next to the 'long', 'medium' and 'short' date types defined in core, any
+ * module can define additional types that can be used when displaying dates,
+ * by implementing this hook. A date type is basically just a name for a date
+ * format.
+ *
+ * Date types are used in the administration interface: a user can assign
+ * date format types defined in hook_date_formats() to date types defined in
+ * this hook. Once a format has been assigned by a user, the machine name of a
+ * type can be used in the format_date() function to format a date using the
+ * chosen formatting.
+ *
+ * To define a date type in a module and make sure a format has been assigned to
+ * it, without requiring a user to visit the administrative interface, use
+ * @code variable_set('date_format_' . $type, $format); @endcode
+ * where $type is the machine-readable name defined here, and $format is a PHP
+ * date format string.
+ *
+ * To avoid namespace collisions with date types defined by other modules, it is
+ * recommended that each date type starts with the module name. A date type
+ * can consist of letters, numbers and underscores.
+ *
+ * @return
+ * An array of date types where the keys are the machine-readable names and
+ * the values are the human-readable labels.
+ *
+ * @see hook_date_formats()
+ * @see format_date()
+ */
+function hook_date_format_types() {
+ // Define the core date format types.
+ return array(
+ 'long' => t('Long'),
+ 'medium' => t('Medium'),
+ 'short' => t('Short'),
+ );
+}
+
+/**
+ * Modify existing date types.
+ *
+ * Allows other modules to modify existing date types like 'long'. Called by
+ * _system_date_format_types_build(). For instance, A module may use this hook
+ * to apply settings across all date types, such as locking all date types so
+ * they appear to be provided by the system.
+ *
+ * @param $types
+ * A list of date types. Each date type is keyed by the machine-readable name
+ * and the values are associative arrays containing:
+ * - is_new: Set to FALSE to override previous settings.
+ * - module: The name of the module that created the date type.
+ * - type: The machine-readable date type name.
+ * - title: The human-readable date type name.
+ * - locked: Specifies that the date type is system-provided.
+ */
+function hook_date_format_types_alter(&$types) {
+ foreach ($types as $name => $type) {
+ $types[$name]['locked'] = 1;
+ }
+}
+
+/**
+ * Define additional date formats.
+ *
+ * This hook is used to define the PHP date format strings that can be assigned
+ * to date types in the administrative interface. A module can provide date
+ * format strings for the core-provided date types ('long', 'medium', and
+ * 'short'), or for date types defined in hook_date_format_types() by itself
+ * or another module.
+ *
+ * Since date formats can be locale-specific, you can specify the locales that
+ * each date format string applies to. There may be more than one locale for a
+ * format. There may also be more than one format for the same locale. For
+ * example d/m/Y and Y/m/d work equally well in some locales. You may wish to
+ * define some additional date formats that aren't specific to any one locale,
+ * for example, "Y m". For these cases, the 'locales' component of the return
+ * value should be omitted.
+ *
+ * Providing a date format here does not normally assign the format to be
+ * used with the associated date type -- a user has to choose a format for each
+ * date type in the administrative interface. There is one exception: locale
+ * initialization chooses a locale-specific format for the three core-provided
+ * types (see locale_get_localized_date_format() for details). If your module
+ * needs to ensure that a date type it defines has a format associated with it,
+ * call @code variable_set('date_format_' . $type, $format); @endcode
+ * where $type is the machine-readable name defined in hook_date_format_types(),
+ * and $format is a PHP date format string.
+ *
+ * @return
+ * A list of date formats to offer as choices in the administrative
+ * interface. Each date format is a keyed array consisting of three elements:
+ * - 'type': The date type name that this format can be used with, as
+ * declared in an implementation of hook_date_format_types().
+ * - 'format': A PHP date format string to use when formatting dates. It
+ * can contain any of the formatting options described at
+ * http://php.net/manual/en/function.date.php
+ * - 'locales': (optional) An array of 2 and 5 character locale codes,
+ * defining which locales this format applies to (for example, 'en',
+ * 'en-us', etc.). If your date format is not language-specific, leave this
+ * array empty.
+ *
+ * @see hook_date_format_types()
+ */
+function hook_date_formats() {
+ return array(
+ array(
+ 'type' => 'mymodule_extra_long',
+ 'format' => 'l jS F Y H:i:s e',
+ 'locales' => array('en-ie'),
+ ),
+ array(
+ 'type' => 'mymodule_extra_long',
+ 'format' => 'l jS F Y h:i:sa',
+ 'locales' => array('en', 'en-us'),
+ ),
+ array(
+ 'type' => 'short',
+ 'format' => 'F Y',
+ 'locales' => array(),
+ ),
+ );
+}
+
+/**
+ * Alter date formats declared by another module.
+ *
+ * Called by _system_date_format_types_build() to allow modules to alter the
+ * return values from implementations of hook_date_formats().
+ */
+function hook_date_formats_alter(&$formats) {
+ foreach ($formats as $id => $format) {
+ $formats[$id]['locales'][] = 'en-ca';
+ }
+}
+
+/**
+ * Alters the delivery callback used to send the result of the page callback to the browser.
+ *
+ * Called by drupal_deliver_page() to allow modules to alter how the
+ * page is delivered to the browser.
+ *
+ * This hook is intended for altering the delivery callback based on
+ * information unrelated to the path of the page accessed. For example,
+ * it can be used to set the delivery callback based on a HTTP request
+ * header (as shown in the code sample). To specify a delivery callback
+ * based on path information, use hook_menu() or hook_menu_alter().
+ *
+ * This hook can also be used as an API function that can be used to explicitly
+ * set the delivery callback from some other function. For example, for a module
+ * named MODULE:
+ * @code
+ * function MODULE_page_delivery_callback_alter(&$callback, $set = FALSE) {
+ * static $stored_callback;
+ * if ($set) {
+ * $stored_callback = $callback;
+ * }
+ * elseif (isset($stored_callback)) {
+ * $callback = $stored_callback;
+ * }
+ * }
+ * function SOMEWHERE_ELSE() {
+ * $desired_delivery_callback = 'foo';
+ * MODULE_page_delivery_callback_alter($desired_delivery_callback, TRUE);
+ * }
+ * @endcode
+ *
+ * @param $callback
+ * The name of a function.
+ *
+ * @see drupal_deliver_page()
+ */
+function hook_page_delivery_callback_alter(&$callback) {
+ // jQuery sets a HTTP_X_REQUESTED_WITH header of 'XMLHttpRequest'.
+ // If a page would normally be delivered as an html page, and it is called
+ // from jQuery, deliver it instead as an Ajax response.
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' && $callback == 'drupal_deliver_html_page') {
+ $callback = 'ajax_deliver';
+ }
+}
+
+/**
+ * Alters theme operation links.
+ *
+ * @param $theme_groups
+ * An associative array containing groups of themes.
+ *
+ * @see system_themes_page()
+ */
+function hook_system_themes_page_alter(&$theme_groups) {
+ foreach ($theme_groups as $state => &$group) {
+ foreach ($theme_groups[$state] as &$theme) {
+ // Add a foo link to each list of theme operations.
+ $theme->operations[] = array(
+ 'title' => t('Foo'),
+ 'href' => 'admin/appearance/foo',
+ 'query' => array('theme' => $theme->name)
+ );
+ }
+ }
+}
+
+/**
+ * Alters inbound URL requests.
+ *
+ * @param $path
+ * The path being constructed, which, if a path alias, has been resolved to a
+ * Drupal path by the database, and which also may have been altered by other
+ * modules before this one.
+ * @param $original_path
+ * The original path, before being checked for path aliases or altered by any
+ * modules.
+ * @param $path_language
+ * The language of the path.
+ *
+ * @see drupal_get_normal_path()
+ */
+function hook_url_inbound_alter(&$path, $original_path, $path_language) {
+ // Create the path user/me/edit, which allows a user to edit their account.
+ if (preg_match('|^user/me/edit(/.*)?|', $path, $matches)) {
+ global $user;
+ $path = 'user/' . $user->uid . '/edit' . $matches[1];
+ }
+}
+
+/**
+ * Alters outbound URLs.
+ *
+ * @param $path
+ * The outbound path to alter, not adjusted for path aliases yet. It won't be
+ * adjusted for path aliases until all modules are finished altering it, thus
+ * being consistent with hook_url_alter_inbound(), which adjusts for all path
+ * aliases before allowing modules to alter it. This may have been altered by
+ * other modules before this one.
+ * @param $options
+ * A set of URL options for the URL so elements such as a fragment or a query
+ * string can be added to the URL.
+ * @param $original_path
+ * The original path, before being altered by any modules.
+ *
+ * @see url()
+ */
+function hook_url_outbound_alter(&$path, &$options, $original_path) {
+ // Use an external RSS feed rather than the Drupal one.
+ if ($path == 'rss.xml') {
+ $path = 'http://example.com/rss.xml';
+ $options['external'] = TRUE;
+ }
+
+ // Instead of pointing to user/[uid]/edit, point to user/me/edit.
+ if (preg_match('|^user/([0-9]*)/edit(/.*)?|', $path, $matches)) {
+ global $user;
+ if ($user->uid == $matches[1]) {
+ $path = 'user/me/edit' . $matches[2];
+ }
+ }
+}
+
+/**
+ * Alter the username that is displayed for a user.
+ *
+ * Called by format_username() to allow modules to alter the username that's
+ * displayed. Can be used to ensure user privacy in situations where
+ * $account->name is too revealing.
+ *
+ * @param $name
+ * The string that format_username() will return.
+ *
+ * @param $account
+ * The account object passed to format_username().
+ *
+ * @see format_username()
+ */
+function hook_username_alter(&$name, $account) {
+ // Display the user's uid instead of name.
+ if (isset($account->uid)) {
+ $name = t('User !uid', array('!uid' => $account->uid));
+ }
+}
+
+/**
+ * Provide replacement values for placeholder tokens.
+ *
+ * This hook is invoked when someone calls token_replace(). That function first
+ * scans the text for [type:token] patterns, and splits the needed tokens into
+ * groups by type. Then hook_tokens() is invoked on each token-type group,
+ * allowing your module to respond by providing replacement text for any of
+ * the tokens in the group that your module knows how to process.
+ *
+ * A module implementing this hook should also implement hook_token_info() in
+ * order to list its available tokens on editing screens.
+ *
+ * @param $type
+ * The machine-readable name of the type (group) of token being replaced, such
+ * as 'node', 'user', or another type defined by a hook_token_info()
+ * implementation.
+ * @param $tokens
+ * An array of tokens to be replaced. The keys are the machine-readable token
+ * names, and the values are the raw [type:token] strings that appeared in the
+ * original text.
+ * @param $data
+ * (optional) An associative array of data objects to be used when generating
+ * replacement values, as supplied in the $data parameter to token_replace().
+ * @param $options
+ * (optional) An associative array of options for token replacement; see
+ * token_replace() for possible values.
+ *
+ * @return
+ * An associative array of replacement values, keyed by the raw [type:token]
+ * strings from the original text.
+ *
+ * @see hook_token_info()
+ * @see hook_tokens_alter()
+ */
+function hook_tokens($type, $tokens, array $data = array(), array $options = array()) {
+ $url_options = array('absolute' => TRUE);
+ if (isset($options['language'])) {
+ $url_options['language'] = $options['language'];
+ $language_code = $options['language']->language;
+ }
+ else {
+ $language_code = NULL;
+ }
+ $sanitize = !empty($options['sanitize']);
+
+ $replacements = array();
+
+ if ($type == 'node' && !empty($data['node'])) {
+ $node = $data['node'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ // Simple key values on the node.
+ case 'nid':
+ $replacements[$original] = $node->nid;
+ break;
+
+ case 'title':
+ $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title;
+ break;
+
+ case 'edit-url':
+ $replacements[$original] = url('node/' . $node->nid . '/edit', $url_options);
+ break;
+
+ // Default values for the chained tokens handled below.
+ case 'author':
+ $name = ($node->uid == 0) ? variable_get('anonymous', t('Anonymous')) : $node->name;
+ $replacements[$original] = $sanitize ? filter_xss($name) : $name;
+ break;
+
+ case 'created':
+ $replacements[$original] = format_date($node->created, 'medium', '', NULL, $language_code);
+ break;
+ }
+ }
+
+ if ($author_tokens = token_find_with_prefix($tokens, 'author')) {
+ $author = user_load($node->uid);
+ $replacements += token_generate('user', $author_tokens, array('user' => $author), $options);
+ }
+
+ if ($created_tokens = token_find_with_prefix($tokens, 'created')) {
+ $replacements += token_generate('date', $created_tokens, array('date' => $node->created), $options);
+ }
+ }
+
+ return $replacements;
+}
+
+/**
+ * Alter replacement values for placeholder tokens.
+ *
+ * @param $replacements
+ * An associative array of replacements returned by hook_tokens().
+ * @param $context
+ * The context in which hook_tokens() was called. An associative array with
+ * the following keys, which have the same meaning as the corresponding
+ * parameters of hook_tokens():
+ * - 'type'
+ * - 'tokens'
+ * - 'data'
+ * - 'options'
+ *
+ * @see hook_tokens()
+ */
+function hook_tokens_alter(array &$replacements, array $context) {
+ $options = $context['options'];
+
+ if (isset($options['language'])) {
+ $url_options['language'] = $options['language'];
+ $language_code = $options['language']->language;
+ }
+ else {
+ $language_code = NULL;
+ }
+ $sanitize = !empty($options['sanitize']);
+
+ if ($context['type'] == 'node' && !empty($context['data']['node'])) {
+ $node = $context['data']['node'];
+
+ // Alter the [node:title] token, and replace it with the rendered content
+ // of a field (field_title).
+ if (isset($context['tokens']['title'])) {
+ $title = field_view_field('node', $node, 'field_title', 'default', $language_code);
+ $replacements[$context['tokens']['title']] = drupal_render($title);
+ }
+ }
+}
+
+/**
+ * Provide information about available placeholder tokens and token types.
+ *
+ * Tokens are placeholders that can be put into text by using the syntax
+ * [type:token], where type is the machine-readable name of a token type, and
+ * token is the machine-readable name of a token within this group. This hook
+ * provides a list of types and tokens to be displayed on text editing screens,
+ * so that people editing text can see what their token options are.
+ *
+ * The actual token replacement is done by token_replace(), which invokes
+ * hook_tokens(). Your module will need to implement that hook in order to
+ * generate token replacements from the tokens defined here.
+ *
+ * @return
+ * An associative array of available tokens and token types. The outer array
+ * has two components:
+ * - types: An associative array of token types (groups). Each token type is
+ * an associative array with the following components:
+ * - name: The translated human-readable short name of the token type.
+ * - description: A translated longer description of the token type.
+ * - needs-data: The type of data that must be provided to token_replace()
+ * in the $data argument (i.e., the key name in $data) in order for tokens
+ * of this type to be used in the $text being processed. For instance, if
+ * the token needs a node object, 'needs-data' should be 'node', and to
+ * use this token in token_replace(), the caller needs to supply a node
+ * object as $data['node']. Some token data can also be supplied
+ * indirectly; for instance, a node object in $data supplies a user object
+ * (the author of the node), allowing user tokens to be used when only
+ * a node data object is supplied.
+ * - tokens: An associative array of tokens. The outer array is keyed by the
+ * group name (the same key as in the types array). Within each group of
+ * tokens, each token item is keyed by the machine name of the token, and
+ * each token item has the following components:
+ * - name: The translated human-readable short name of the token.
+ * - description: A translated longer description of the token.
+ * - type (optional): A 'needs-data' data type supplied by this token, which
+ * should match a 'needs-data' value from another token type. For example,
+ * the node author token provides a user object, which can then be used
+ * for token replacement data in token_replace() without having to supply
+ * a separate user object.
+ *
+ * @see hook_token_info_alter()
+ * @see hook_tokens()
+ */
+function hook_token_info() {
+ $type = array(
+ 'name' => t('Nodes'),
+ 'description' => t('Tokens related to individual nodes.'),
+ 'needs-data' => 'node',
+ );
+
+ // Core tokens for nodes.
+ $node['nid'] = array(
+ 'name' => t("Node ID"),
+ 'description' => t("The unique ID of the node."),
+ );
+ $node['title'] = array(
+ 'name' => t("Title"),
+ 'description' => t("The title of the node."),
+ );
+ $node['edit-url'] = array(
+ 'name' => t("Edit URL"),
+ 'description' => t("The URL of the node's edit page."),
+ );
+
+ // Chained tokens for nodes.
+ $node['created'] = array(
+ 'name' => t("Date created"),
+ 'description' => t("The date the node was posted."),
+ 'type' => 'date',
+ );
+ $node['author'] = array(
+ 'name' => t("Author"),
+ 'description' => t("The author of the node."),
+ 'type' => 'user',
+ );
+
+ return array(
+ 'types' => array('node' => $type),
+ 'tokens' => array('node' => $node),
+ );
+}
+
+/**
+ * Alter the metadata about available placeholder tokens and token types.
+ *
+ * @param $data
+ * The associative array of token definitions from hook_token_info().
+ *
+ * @see hook_token_info()
+ */
+function hook_token_info_alter(&$data) {
+ // Modify description of node tokens for our site.
+ $data['tokens']['node']['nid'] = array(
+ 'name' => t("Node ID"),
+ 'description' => t("The unique ID of the article."),
+ );
+ $data['tokens']['node']['title'] = array(
+ 'name' => t("Title"),
+ 'description' => t("The title of the article."),
+ );
+
+ // Chained tokens for nodes.
+ $data['tokens']['node']['created'] = array(
+ 'name' => t("Date created"),
+ 'description' => t("The date the article was posted."),
+ 'type' => 'date',
+ );
+}
+
+/**
+ * Alter batch information before a batch is processed.
+ *
+ * Called by batch_process() to allow modules to alter a batch before it is
+ * processed.
+ *
+ * @param $batch
+ * The associative array of batch information. See batch_set() for details on
+ * what this could contain.
+ *
+ * @see batch_set()
+ * @see batch_process()
+ *
+ * @ingroup batch
+ */
+function hook_batch_alter(&$batch) {
+ // If the current page request is inside the overlay, add ?render=overlay to
+ // the success callback URL, so that it appears correctly within the overlay.
+ if (overlay_get_mode() == 'child') {
+ if (isset($batch['url_options']['query'])) {
+ $batch['url_options']['query']['render'] = 'overlay';
+ }
+ else {
+ $batch['url_options']['query'] = array('render' => 'overlay');
+ }
+ }
+}
+
+/**
+ * Provide information on Updaters (classes that can update Drupal).
+ *
+ * An Updater is a class that knows how to update various parts of the Drupal
+ * file system, for example to update modules that have newer releases, or to
+ * install a new theme.
+ *
+ * @return
+ * An associative array of information about the updater(s) being provided.
+ * This array is keyed by a unique identifier for each updater, and the
+ * values are subarrays that can contain the following keys:
+ * - class: The name of the PHP class which implements this updater.
+ * - name: Human-readable name of this updater.
+ * - weight: Controls what order the Updater classes are consulted to decide
+ * which one should handle a given task. When an update task is being run,
+ * the system will loop through all the Updater classes defined in this
+ * registry in weight order and let each class respond to the task and
+ * decide if each Updater wants to handle the task. In general, this
+ * doesn't matter, but if you need to override an existing Updater, make
+ * sure your Updater has a lighter weight so that it comes first.
+ *
+ * @see drupal_get_updaters()
+ * @see hook_updater_info_alter()
+ */
+function hook_updater_info() {
+ return array(
+ 'module' => array(
+ 'class' => 'ModuleUpdater',
+ 'name' => t('Update modules'),
+ 'weight' => 0,
+ ),
+ 'theme' => array(
+ 'class' => 'ThemeUpdater',
+ 'name' => t('Update themes'),
+ 'weight' => 0,
+ ),
+ );
+}
+
+/**
+ * Alter the Updater information array.
+ *
+ * An Updater is a class that knows how to update various parts of the Drupal
+ * file system, for example to update modules that have newer releases, or to
+ * install a new theme.
+ *
+ * @param array $updaters
+ * Associative array of updaters as defined through hook_updater_info().
+ * Alter this array directly.
+ *
+ * @see drupal_get_updaters()
+ * @see hook_updater_info()
+ */
+function hook_updater_info_alter(&$updaters) {
+ // Adjust weight so that the theme Updater gets a chance to handle a given
+ // update task before module updaters.
+ $updaters['theme']['weight'] = -1;
+}
+
+/**
+ * Alter the default country list.
+ *
+ * @param $countries
+ * The associative array of countries keyed by ISO 3166-1 country code.
+ *
+ * @see country_get_list()
+ * @see standard_country_list()
+ */
+function hook_countries_alter(&$countries) {
+ // Elbonia is now independent, so add it to the country list.
+ $countries['EB'] = 'Elbonia';
+}
+
+/**
+ * Control site status before menu dispatching.
+ *
+ * The hook is called after checking whether the site is offline but before
+ * the current router item is retrieved and executed by
+ * menu_execute_active_handler(). If the site is in offline mode,
+ * $menu_site_status is set to MENU_SITE_OFFLINE.
+ *
+ * @param $menu_site_status
+ * Supported values are MENU_SITE_OFFLINE, MENU_ACCESS_DENIED,
+ * MENU_NOT_FOUND and MENU_SITE_ONLINE. Any other value than
+ * MENU_SITE_ONLINE will skip the default menu handling system and be passed
+ * for delivery to drupal_deliver_page() with a NULL
+ * $default_delivery_callback.
+ * @param $path
+ * Contains the system path that is going to be loaded. This is read only,
+ * use hook_url_inbound_alter() to change the path.
+ */
+function hook_menu_site_status_alter(&$menu_site_status, $path) {
+ // Allow access to my_module/authentication even if site is in offline mode.
+ if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && $path == 'my_module/authentication') {
+ $menu_site_status = MENU_SITE_ONLINE;
+ }
+}
+
+/**
+ * Register information about FileTransfer classes provided by a module.
+ *
+ * The FileTransfer class allows transferring files over a specific type of
+ * connection. Core provides classes for FTP and SSH. Contributed modules are
+ * free to extend the FileTransfer base class to add other connection types,
+ * and if these classes are registered via hook_filetransfer_info(), those
+ * connection types will be available to site administrators using the Update
+ * manager when they are redirected to the authorize.php script to authorize
+ * the file operations.
+ *
+ * @return array
+ * Nested array of information about FileTransfer classes. Each key is a
+ * FileTransfer type (not human readable, used for form elements and
+ * variable names, etc), and the values are subarrays that define properties
+ * of that type. The keys in each subarray are:
+ * - 'title': Required. The human-readable name of the connection type.
+ * - 'class': Required. The name of the FileTransfer class. The constructor
+ * will always be passed the full path to the root of the site that should
+ * be used to restrict where file transfer operations can occur (the $jail)
+ * and an array of settings values returned by the settings form.
+ * - 'file': Required. The include file containing the FileTransfer class.
+ * This should be a separate .inc file, not just the .module file, so that
+ * the minimum possible code is loaded when authorize.php is running.
+ * - 'file path': Optional. The directory (relative to the Drupal root)
+ * where the include file lives. If not defined, defaults to the base
+ * directory of the module implementing the hook.
+ * - 'weight': Optional. Integer weight used for sorting connection types on
+ * the authorize.php form.
+ *
+ * @see FileTransfer
+ * @see authorize.php
+ * @see hook_filetransfer_info_alter()
+ * @see drupal_get_filetransfer_info()
+ */
+function hook_filetransfer_info() {
+ $info['sftp'] = array(
+ 'title' => t('SFTP (Secure FTP)'),
+ 'file' => 'sftp.filetransfer.inc',
+ 'class' => 'FileTransferSFTP',
+ 'weight' => 10,
+ );
+ return $info;
+}
+
+/**
+ * Alter the FileTransfer class registry.
+ *
+ * @param array $filetransfer_info
+ * Reference to a nested array containing information about the FileTransfer
+ * class registry.
+ *
+ * @see hook_filetransfer_info()
+ */
+function hook_filetransfer_info_alter(&$filetransfer_info) {
+ if (variable_get('paranoia', FALSE)) {
+ // Remove the FTP option entirely.
+ unset($filetransfer_info['ftp']);
+ // Make sure the SSH option is listed first.
+ $filetransfer_info['ssh']['weight'] = -10;
+ }
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/core/modules/system/system.archiver.inc b/core/modules/system/system.archiver.inc
new file mode 100644
index 00000000000..c37f07daa10
--- /dev/null
+++ b/core/modules/system/system.archiver.inc
@@ -0,0 +1,139 @@
+<?php
+
+/**
+ * @file
+ * Archiver implementations provided by the system module.
+ */
+
+/**
+ * Archiver for .tar files.
+ */
+class ArchiverTar implements ArchiverInterface {
+
+ /**
+ * The underlying Archive_Tar instance that does the heavy lifting.
+ *
+ * @var Archive_Tar
+ */
+ protected $tar;
+
+ public function __construct($file_path) {
+ $this->tar = new Archive_Tar($file_path);
+ }
+
+ public function add($file_path) {
+ $this->tar->add($file_path);
+
+ return $this;
+ }
+
+ public function remove($file_path) {
+ // @todo Archive_Tar doesn't have a remove operation
+ // so we'll have to simulate it somehow, probably by
+ // creating a new archive with everything but the removed
+ // file.
+
+ return $this;
+ }
+
+ public function extract($path, Array $files = array()) {
+ if ($files) {
+ $this->tar->extractList($files, $path);
+ }
+ else {
+ $this->tar->extract($path);
+ }
+
+ return $this;
+ }
+
+ public function listContents() {
+ $files = array();
+ foreach ($this->tar->listContent() as $file_data) {
+ $files[] = $file_data['filename'];
+ }
+ return $files;
+ }
+
+ /**
+ * Retrieve the tar engine itself.
+ *
+ * In some cases it may be necessary to directly access the underlying
+ * Archive_Tar object for implementation-specific logic. This is for advanced
+ * use only as it is not shared by other implementations of ArchiveInterface.
+ *
+ * @return
+ * The Archive_Tar object used by this object.
+ */
+ public function getArchive() {
+ return $this->tar;
+ }
+}
+
+/**
+ * Archiver for .zip files.
+ *
+ * @link http://php.net/zip
+ */
+class ArchiverZip implements ArchiverInterface {
+
+ /**
+ * The underlying ZipArchive instance that does the heavy lifting.
+ *
+ * @var ZipArchive
+ */
+ protected $zip;
+
+ public function __construct($file_path) {
+ $this->zip = new ZipArchive();
+ if ($this->zip->open($file_path) !== TRUE) {
+ // @todo: This should be an interface-specific exception some day.
+ throw new Exception(t('Cannot open %file_path', array('%file_path' => $file_path)));
+ }
+ }
+
+ public function add($file_path) {
+ $this->zip->addFile($file_path);
+
+ return $this;
+ }
+
+ public function remove($file_path) {
+ $this->zip->deleteName($file_path);
+
+ return $this;
+ }
+
+ public function extract($path, Array $files = array()) {
+ if ($files) {
+ $this->zip->extractTo($path, $files);
+ }
+ else {
+ $this->zip->extractTo($path);
+ }
+
+ return $this;
+ }
+
+ public function listContents() {
+ $files = array();
+ for ($i=0; $i < $this->zip->numFiles; $i++) {
+ $files[] = $this->zip->getNameIndex($i);
+ }
+ return $files;
+ }
+
+ /**
+ * Retrieve the zip engine itself.
+ *
+ * In some cases it may be necessary to directly access the underlying
+ * ZipArchive object for implementation-specific logic. This is for advanced
+ * use only as it is not shared by other implementations of ArchiveInterface.
+ *
+ * @return
+ * The ZipArchive object used by this object.
+ */
+ public function getArchive() {
+ return $this->zip;
+ }
+}
diff --git a/core/modules/system/system.base-rtl.css b/core/modules/system/system.base-rtl.css
new file mode 100644
index 00000000000..9099c9d72b7
--- /dev/null
+++ b/core/modules/system/system.base-rtl.css
@@ -0,0 +1,54 @@
+
+/**
+ * @file
+ * Generic theme-independent base styles.
+ */
+
+/**
+ * Autocomplete.
+ */
+/* Animated throbber */
+html.js input.form-autocomplete {
+ background-position: 0% 2px;
+}
+html.js input.throbbing {
+ background-position: 0% -18px;
+}
+
+/**
+ * Progress bar.
+ */
+.progress .percentage {
+ float: left;
+}
+.progress-disabled {
+ float: right;
+}
+.ajax-progress {
+ float: right;
+}
+.ajax-progress .throbber {
+ float: right;
+}
+
+/**
+ * TableDrag behavior.
+ */
+.draggable a.tabledrag-handle {
+ float: right;
+ margin: -0.4em -0.5em -0.4em 0;
+ padding: 0.42em 0.5em 0.42em 1.5em;
+}
+div.indentation {
+ float: right;
+ margin: -0.4em -0.4em -0.4em 0.2em;
+ padding: 0.42em 0.6em 0.42em 0;
+}
+div.tree-child,
+div.tree-child-last {
+ background-position: -65px center;
+}
+.tabledrag-toggle-weight-wrapper {
+ text-align: left;
+}
+
diff --git a/core/modules/system/system.base.css b/core/modules/system/system.base.css
new file mode 100644
index 00000000000..e78edca2c7c
--- /dev/null
+++ b/core/modules/system/system.base.css
@@ -0,0 +1,281 @@
+
+/**
+ * @file
+ * Generic theme-independent base styles.
+ */
+
+/**
+ * Autocomplete.
+ *
+ * @see autocomplete.js
+ */
+/* Suggestion list */
+#autocomplete {
+ border: 1px solid;
+ overflow: hidden;
+ position: absolute;
+ z-index: 100;
+}
+#autocomplete ul {
+ list-style: none;
+ list-style-image: none;
+ margin: 0;
+ padding: 0;
+}
+#autocomplete li {
+ background: #fff;
+ color: #000;
+ cursor: default;
+ white-space: pre;
+}
+/* Animated throbber */
+html.js input.form-autocomplete {
+ background-image: url(../../misc/throbber.gif);
+ background-position: 100% 2px; /* LTR */
+ background-repeat: no-repeat;
+}
+html.js input.throbbing {
+ background-position: 100% -18px; /* LTR */
+}
+
+/**
+ * Collapsible fieldsets.
+ *
+ * @see collapse.js
+ */
+html.js fieldset.collapsed {
+ border-bottom-width: 0;
+ border-left-width: 0;
+ border-right-width: 0;
+ height: 1em;
+}
+html.js fieldset.collapsed .fieldset-wrapper {
+ display: none;
+}
+fieldset.collapsible {
+ position: relative;
+}
+fieldset.collapsible .fieldset-legend {
+ display: block;
+}
+
+/**
+ * Resizable textareas.
+ *
+ * @see textarea.js
+ */
+.form-textarea-wrapper textarea {
+ display: block;
+ margin: 0;
+ width: 100%;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.resizable-textarea .grippie {
+ background: #eee url(../../misc/grippie.png) no-repeat center 2px;
+ border: 1px solid #ddd;
+ border-top-width: 0;
+ cursor: s-resize;
+ height: 9px;
+ overflow: hidden;
+}
+
+/**
+ * TableDrag behavior.
+ *
+ * @see tabledrag.js
+ */
+body.drag {
+ cursor: move;
+}
+.draggable a.tabledrag-handle {
+ cursor: move;
+ float: left; /* LTR */
+ height: 1.7em;
+ margin: -0.4em 0 -0.4em -0.5em; /* LTR */
+ padding: 0.42em 1.5em 0.42em 0.5em; /* LTR */
+ text-decoration: none;
+}
+a.tabledrag-handle:hover {
+ text-decoration: none;
+}
+a.tabledrag-handle .handle {
+ background: url(../../misc/draggable.png) no-repeat 0 0;
+ height: 13px;
+ margin-top: 4px;
+ width: 13px;
+}
+a.tabledrag-handle-hover .handle {
+ background-position: 0 -20px;
+}
+div.indentation {
+ float: left; /* LTR */
+ height: 1.7em;
+ margin: -0.4em 0.2em -0.4em -0.4em; /* LTR */
+ padding: 0.42em 0 0.42em 0.6em; /* LTR */
+ width: 20px;
+}
+div.tree-child {
+ background: url(../../misc/tree.png) no-repeat 11px center; /* LTR */
+}
+div.tree-child-last {
+ background: url(../../misc/tree-bottom.png) no-repeat 11px center; /* LTR */
+}
+div.tree-child-horizontal {
+ background: url(../../misc/tree.png) no-repeat -11px center;
+}
+.tabledrag-toggle-weight-wrapper {
+ text-align: right; /* LTR */
+}
+
+/**
+ * TableHeader behavior.
+ *
+ * @see tableheader.js
+ */
+table.sticky-header {
+ background-color: #fff;
+ margin-top: 0;
+}
+
+/**
+ * Progress behavior.
+ *
+ * @see progress.js
+ */
+/* Bar */
+.progress .bar {
+ background-color: #fff;
+ border: 1px solid;
+}
+.progress .filled {
+ background-color: #000;
+ height: 1.5em;
+ width: 5px;
+}
+.progress .percentage {
+ float: right; /* LTR */
+}
+.progress-disabled {
+ float: left; /* LTR */
+}
+/* Throbber */
+.ajax-progress {
+ float: left; /* LTR */
+}
+.ajax-progress .throbber {
+ background: transparent url(../../misc/throbber.gif) no-repeat 0px -18px;
+ float: left; /* LTR */
+ height: 15px;
+ margin: 2px;
+ width: 15px;
+}
+tr .ajax-progress .throbber {
+ margin: 0 2px;
+}
+.ajax-progress-bar {
+ width: 16em;
+}
+
+/**
+ * Inline items.
+ */
+.container-inline div,
+.container-inline label {
+ display: inline;
+}
+/* Fieldset contents always need to be rendered as block. */
+.container-inline .fieldset-wrapper {
+ display: block;
+}
+
+/**
+ * Prevent text wrapping.
+ */
+.nowrap {
+ white-space: nowrap;
+}
+
+/**
+ * For anything you want to hide on page load when JS is enabled, so
+ * that you can use the JS to control visibility and avoid flicker.
+ */
+html.js .js-hide {
+ display: none;
+}
+
+/**
+ * Hide elements from all users.
+ *
+ * Used for elements which should not be immediately displayed to any user. An
+ * example would be a collapsible fieldset that will be expanded with a click
+ * from a user. The effect of this class can be toggled with the jQuery show()
+ * and hide() functions.
+ */
+.element-hidden {
+ display: none;
+}
+
+/**
+ * Hide elements visually, but keep them available for screen-readers.
+ *
+ * Used for information required for screen-reader users to understand and use
+ * the site where visual display is undesirable. Information provided in this
+ * manner should be kept concise, to avoid unnecessary burden on the user.
+ * "!important" is used to prevent unintentional overrides.
+ */
+.element-invisible {
+ position: absolute !important;
+ clip: rect(1px 1px 1px 1px); /* IE7 */
+ clip: rect(1px, 1px, 1px, 1px);
+}
+
+/**
+ * The .element-focusable class extends the .element-invisible class to allow
+ * the element to be focusable when navigated to via the keyboard.
+ */
+.element-invisible.element-focusable:active,
+.element-invisible.element-focusable:focus {
+ position: static !important;
+ clip: auto;
+}
+
+/**
+ * Float clearing.
+ *
+ * Based on the micro clearfix hack by Nicolas Gallagher, with the :before
+ * pseudo selector removed to allow normal top margin collapse.
+ *
+ * @see http://nicolasgallagher.com/micro-clearfix-hack
+ */
+.clearfix:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.clearfix {
+ zoom: 1; /* hasLayout trigger to clear floats in IE */
+}
+
+/**
+ * Block-level HTML5 display definition.
+ *
+ * Provides display values for browsers that don't recognize the new elements
+ * and therefore display them inline by default.
+ */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
diff --git a/core/modules/system/system.cron.js b/core/modules/system/system.cron.js
new file mode 100644
index 00000000000..af17dab5273
--- /dev/null
+++ b/core/modules/system/system.cron.js
@@ -0,0 +1,19 @@
+(function ($) {
+
+/**
+ * Checks to see if the cron should be automatically run.
+ */
+Drupal.behaviors.cronCheck = {
+ attach: function(context, settings) {
+ if (settings.cronCheck || false) {
+ $('body').once('cron-check', function() {
+ // Only execute the cron check if its the right time.
+ if (Math.round(new Date().getTime() / 1000.0) > settings.cronCheck) {
+ $.get(settings.basePath + 'system/run-cron-check');
+ }
+ });
+ }
+ }
+};
+
+})(jQuery);
diff --git a/core/modules/system/system.info b/core/modules/system/system.info
new file mode 100644
index 00000000000..c394849e73b
--- /dev/null
+++ b/core/modules/system/system.info
@@ -0,0 +1,13 @@
+name = System
+description = Handles general site configuration for administrators.
+package = Core
+version = VERSION
+core = 8.x
+files[] = system.archiver.inc
+files[] = system.mail.inc
+files[] = system.queue.inc
+files[] = system.tar.inc
+files[] = system.updater.inc
+files[] = system.test
+required = TRUE
+configure = admin/config/system
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
new file mode 100644
index 00000000000..24933e2d9b6
--- /dev/null
+++ b/core/modules/system/system.install
@@ -0,0 +1,1647 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the system module.
+ */
+
+/**
+ * Test and report Drupal installation requirements.
+ *
+ * @param $phase
+ * The current system installation phase.
+ * @return
+ * An array of system requirements.
+ */
+function system_requirements($phase) {
+ global $base_url;
+ $requirements = array();
+ // Ensure translations don't break at install time
+ $t = get_t();
+
+ // Report Drupal version
+ if ($phase == 'runtime') {
+ $requirements['drupal'] = array(
+ 'title' => $t('Drupal'),
+ 'value' => VERSION,
+ 'severity' => REQUIREMENT_INFO,
+ 'weight' => -10,
+ );
+
+ // Display the currently active install profile, if the site
+ // is not running the default install profile.
+ $profile = drupal_get_profile();
+ if ($profile != 'standard') {
+ $info = system_get_info('module', $profile);
+ $requirements['install_profile'] = array(
+ 'title' => $t('Install profile'),
+ 'value' => $t('%profile_name (%profile-%version)', array(
+ '%profile_name' => $info['name'],
+ '%profile' => $profile,
+ '%version' => $info['version']
+ )),
+ 'severity' => REQUIREMENT_INFO,
+ 'weight' => -9
+ );
+ }
+ }
+
+ // Web server information.
+ $software = $_SERVER['SERVER_SOFTWARE'];
+ $requirements['webserver'] = array(
+ 'title' => $t('Web server'),
+ 'value' => $software,
+ );
+
+ // Test PHP version and show link to phpinfo() if it's available
+ $phpversion = phpversion();
+ if (function_exists('phpinfo')) {
+ $requirements['php'] = array(
+ 'title' => $t('PHP'),
+ 'value' => ($phase == 'runtime') ? $phpversion .' ('. l($t('more information'), 'admin/reports/status/php') .')' : $phpversion,
+ );
+ }
+ else {
+ $requirements['php'] = array(
+ 'title' => $t('PHP'),
+ 'value' => $phpversion,
+ 'description' => $t('The phpinfo() function has been disabled for security reasons. To see your server\'s phpinfo() information, change your PHP settings or contact your server administrator. For more information, <a href="@phpinfo">Enabling and disabling phpinfo()</a> handbook page.', array('@phpinfo' => 'http://drupal.org/node/243993')),
+ 'severity' => REQUIREMENT_INFO,
+ );
+ }
+
+ if (version_compare($phpversion, DRUPAL_MINIMUM_PHP) < 0) {
+ $requirements['php']['description'] = $t('Your PHP installation is too old. Drupal requires at least PHP %version.', array('%version' => DRUPAL_MINIMUM_PHP));
+ $requirements['php']['severity'] = REQUIREMENT_ERROR;
+ // If PHP is old, it's not safe to continue with the requirements check.
+ return $requirements;
+ }
+
+ // Test PHP register_globals setting.
+ $requirements['php_register_globals'] = array(
+ 'title' => $t('PHP register globals'),
+ );
+ $register_globals = trim(ini_get('register_globals'));
+ // Unfortunately, ini_get() may return many different values, and we can't
+ // be certain which values mean 'on', so we instead check for 'not off'
+ // since we never want to tell the user that their site is secure
+ // (register_globals off), when it is in fact on. We can only guarantee
+ // register_globals is off if the value returned is 'off', '', or 0.
+ if (!empty($register_globals) && strtolower($register_globals) != 'off') {
+ $requirements['php_register_globals']['description'] = $t('<em>register_globals</em> is enabled. Drupal requires this configuration directive to be disabled. Your site may not be secure when <em>register_globals</em> is enabled. The PHP manual has instructions for <a href="http://php.net/configuration.changes">how to change configuration settings</a>.');
+ $requirements['php_register_globals']['severity'] = REQUIREMENT_ERROR;
+ $requirements['php_register_globals']['value'] = $t("Enabled ('@value')", array('@value' => $register_globals));
+ }
+ else {
+ $requirements['php_register_globals']['value'] = $t('Disabled');
+ }
+
+ // Test for PHP extensions.
+ $requirements['php_extensions'] = array(
+ 'title' => $t('PHP extensions'),
+ );
+
+ $missing_extensions = array();
+ $required_extensions = array(
+ 'date',
+ 'dom',
+ 'filter',
+ 'gd',
+ 'hash',
+ 'json',
+ 'pcre',
+ 'pdo',
+ 'session',
+ 'SimpleXML',
+ 'SPL',
+ 'xml',
+ );
+ foreach ($required_extensions as $extension) {
+ if (!extension_loaded($extension)) {
+ $missing_extensions[] = $extension;
+ }
+ }
+
+ if (!empty($missing_extensions)) {
+ $description = $t('Drupal requires you to enable the PHP extensions in the following list (see the <a href="@system_requirements">system requirements page</a> for more information):', array(
+ '@system_requirements' => 'http://drupal.org/requirements',
+ ));
+
+ $description .= theme('item_list', array('items' => $missing_extensions));
+
+ $requirements['php_extensions']['value'] = $t('Disabled');
+ $requirements['php_extensions']['severity'] = REQUIREMENT_ERROR;
+ $requirements['php_extensions']['description'] = $description;
+ }
+ else {
+ $requirements['php_extensions']['value'] = $t('Enabled');
+ }
+
+ if ($phase == 'install' || $phase == 'update') {
+ // Test for PDO (database).
+ $requirements['database_extensions'] = array(
+ 'title' => $t('Database support'),
+ );
+
+ // Make sure PDO is available.
+ $database_ok = extension_loaded('pdo');
+ if (!$database_ok) {
+ $pdo_message = $t('Your web server does not appear to support PDO (PHP Data Objects). Ask your hosting provider if they support the native PDO extension. See the <a href="@link">system requirements</a> page for more information.', array(
+ '@link' => 'http://drupal.org/requirements/pdo',
+ ));
+ }
+ else {
+ // Make sure at least one supported database driver exists.
+ $drivers = drupal_detect_database_types();
+ if (empty($drivers)) {
+ $database_ok = FALSE;
+ $pdo_message = $t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that <a href="@drupal-databases">Drupal supports</a>.', array(
+ '@drupal-databases' => 'http://drupal.org/node/270#database',
+ ));
+ }
+ // Make sure the native PDO extension is available, not the older PEAR
+ // version. (See install_verify_pdo() for details.)
+ if (!defined('PDO::ATTR_DEFAULT_FETCH_MODE')) {
+ $database_ok = FALSE;
+ $pdo_message = $t('Your web server seems to have the wrong version of PDO installed. Drupal 7 requires the PDO extension from PHP core. This system has the older PECL version. See the <a href="@link">system requirements</a> page for more information.', array(
+ '@link' => 'http://drupal.org/requirements/pdo#pecl',
+ ));
+ }
+ }
+
+ if (!$database_ok) {
+ $requirements['database_extensions']['value'] = $t('Disabled');
+ $requirements['database_extensions']['severity'] = REQUIREMENT_ERROR;
+ $requirements['database_extensions']['description'] = $pdo_message;
+ }
+ else {
+ $requirements['database_extensions']['value'] = $t('Enabled');
+ }
+ }
+ else {
+ // Database information.
+ $class = 'DatabaseTasks_' . Database::getConnection()->driver();
+ $tasks = new $class();
+ $requirements['database_system'] = array(
+ 'title' => $t('Database system'),
+ 'value' => $tasks->name(),
+ );
+ $requirements['database_system_version'] = array(
+ 'title' => $t('Database system version'),
+ 'value' => Database::getConnection()->version(),
+ );
+ }
+
+ // Test PHP memory_limit
+ $memory_limit = ini_get('memory_limit');
+ $requirements['php_memory_limit'] = array(
+ 'title' => $t('PHP memory limit'),
+ 'value' => $memory_limit == -1 ? t('-1 (Unlimited)') : $memory_limit,
+ );
+
+ if ($memory_limit && $memory_limit != -1 && parse_size($memory_limit) < parse_size(DRUPAL_MINIMUM_PHP_MEMORY_LIMIT)) {
+ $description = '';
+ if ($phase == 'install') {
+ $description = $t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the installation process.', array('%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
+ }
+ elseif ($phase == 'update') {
+ $description = $t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the update process.', array('%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
+ }
+ elseif ($phase == 'runtime') {
+ $description = $t('Depending on your configuration, Drupal can run with a %memory_limit PHP memory limit. However, a %memory_minimum_limit PHP memory limit or above is recommended, especially if your site uses additional custom or contributed modules.', array('%memory_limit' => $memory_limit, '%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
+ }
+
+ if (!empty($description)) {
+ if ($php_ini_path = get_cfg_var('cfg_file_path')) {
+ $description .= ' ' . $t('Increase the memory limit by editing the memory_limit parameter in the file %configuration-file and then restart your web server (or contact your system administrator or hosting provider for assistance).', array('%configuration-file' => $php_ini_path));
+ }
+ else {
+ $description .= ' ' . $t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.');
+ }
+
+ $requirements['php_memory_limit']['description'] = $description . ' ' . $t('See the <a href="@url">Drupal requirements</a> for more information.', array('@url' => 'http://drupal.org/requirements'));
+ $requirements['php_memory_limit']['severity'] = REQUIREMENT_WARNING;
+ }
+ }
+
+ // Test settings.php file writability
+ if ($phase == 'runtime') {
+ $conf_dir = drupal_verify_install_file(conf_path(), FILE_NOT_WRITABLE, 'dir');
+ $conf_file = drupal_verify_install_file(conf_path() . '/settings.php', FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE);
+ if (!$conf_dir || !$conf_file) {
+ $requirements['settings.php'] = array(
+ 'value' => $t('Not protected'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => '',
+ );
+ if (!$conf_dir) {
+ $requirements['settings.php']['description'] .= $t('The directory %file is not protected from modifications and poses a security risk. You must change the directory\'s permissions to be non-writable. ', array('%file' => conf_path()));
+ }
+ if (!$conf_file) {
+ $requirements['settings.php']['description'] .= $t('The file %file is not protected from modifications and poses a security risk. You must change the file\'s permissions to be non-writable.', array('%file' => conf_path() . '/settings.php'));
+ }
+ }
+ else {
+ $requirements['settings.php'] = array(
+ 'value' => $t('Protected'),
+ );
+ }
+ $requirements['settings.php']['title'] = $t('Configuration file');
+ }
+
+ // Report cron status.
+ if ($phase == 'runtime') {
+ // Cron warning threshold defaults to two days.
+ $threshold_warning = variable_get('cron_threshold_warning', 172800);
+ // Cron error threshold defaults to two weeks.
+ $threshold_error = variable_get('cron_threshold_error', 1209600);
+ // Cron configuration help text.
+ $help = $t('For more information, see the online handbook entry for <a href="@cron-handbook">configuring cron jobs</a>.', array('@cron-handbook' => 'http://drupal.org/cron'));
+
+ // Determine when cron last ran.
+ $cron_last = variable_get('cron_last');
+ if (!is_numeric($cron_last)) {
+ $cron_last = variable_get('install_time', 0);
+ }
+
+ // Determine severity based on time since cron last ran.
+ $severity = REQUIREMENT_OK;
+ if (REQUEST_TIME - $cron_last > $threshold_error) {
+ $severity = REQUIREMENT_ERROR;
+ }
+ elseif (REQUEST_TIME - $cron_last > $threshold_warning) {
+ $severity = REQUIREMENT_WARNING;
+ }
+
+ // Set summary and description based on values determined above.
+ $summary = $t('Last run !time ago', array('!time' => format_interval(REQUEST_TIME - $cron_last)));
+ $description = '';
+ if ($severity != REQUIREMENT_OK) {
+ $description = $t('Cron has not run recently.') . ' ' . $help;
+ }
+
+ $description .= ' ' . $t('You can <a href="@cron">run cron manually</a>.', array('@cron' => url('admin/reports/status/run-cron')));
+ $description .= '<br />' . $t('To run cron from outside the site, go to <a href="!cron">!cron</a>', array('!cron' => url($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal'))))));
+
+ $requirements['cron'] = array(
+ 'title' => $t('Cron maintenance tasks'),
+ 'severity' => $severity,
+ 'value' => $summary,
+ 'description' => $description
+ );
+ }
+
+ // Test files directories.
+ $directories = array(
+ variable_get('file_public_path', conf_path() . '/files'),
+ // By default no private files directory is configured. For private files
+ // to be secure the admin needs to provide a path outside the webroot.
+ variable_get('file_private_path', FALSE),
+ );
+
+ // Do not check for the temporary files directory at install time
+ // unless it has been set in settings.php. In this case the user has
+ // no alternative but to fix the directory if it is not writable.
+ if ($phase == 'install') {
+ $directories[] = variable_get('file_temporary_path', FALSE);
+ }
+ else {
+ $directories[] = variable_get('file_temporary_path', file_directory_temp());
+ }
+
+ $requirements['file system'] = array(
+ 'title' => $t('File system'),
+ );
+
+ $error = '';
+ // For installer, create the directories if possible.
+ foreach ($directories as $directory) {
+ if (!$directory) {
+ continue;
+ }
+ if ($phase == 'install') {
+ file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
+ }
+ $is_writable = is_writable($directory);
+ $is_directory = is_dir($directory);
+ if (!$is_writable || !$is_directory) {
+ $description = '';
+ $requirements['file system']['value'] = $t('Not writable');
+ if (!$is_directory) {
+ $error .= $t('The directory %directory does not exist.', array('%directory' => $directory)) . ' ';
+ }
+ else {
+ $error .= $t('The directory %directory is not writable.', array('%directory' => $directory)) . ' ';
+ }
+ // The files directory requirement check is done only during install and runtime.
+ if ($phase == 'runtime') {
+ $description = $error . $t('You may need to set the correct directory at the <a href="@admin-file-system">file system settings page</a> or change the current directory\'s permissions so that it is writable.', array('@admin-file-system' => url('admin/config/media/file-system')));
+ }
+ elseif ($phase == 'install') {
+ // For the installer UI, we need different wording. 'value' will
+ // be treated as version, so provide none there.
+ $description = $error . $t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href="@handbook_url">online handbook</a>.', array('@handbook_url' => 'http://drupal.org/server-permissions'));
+ $requirements['file system']['value'] = '';
+ }
+ if (!empty($description)) {
+ $requirements['file system']['description'] = $description;
+ $requirements['file system']['severity'] = REQUIREMENT_ERROR;
+ }
+ }
+ else {
+ if (file_default_scheme() == 'public') {
+ $requirements['file system']['value'] = $t('Writable (<em>public</em> download method)');
+ }
+ else {
+ $requirements['file system']['value'] = $t('Writable (<em>private</em> download method)');
+ }
+ }
+ }
+
+ // See if updates are available in update.php.
+ if ($phase == 'runtime') {
+ $requirements['update'] = array(
+ 'title' => $t('Database updates'),
+ 'severity' => REQUIREMENT_OK,
+ 'value' => $t('Up to date'),
+ );
+
+ // Check installed modules.
+ foreach (module_list() as $module) {
+ $updates = drupal_get_schema_versions($module);
+ if ($updates !== FALSE) {
+ $default = drupal_get_installed_schema_version($module);
+ if (max($updates) > $default) {
+ $requirements['update']['severity'] = REQUIREMENT_ERROR;
+ $requirements['update']['value'] = $t('Out of date');
+ $requirements['update']['description'] = $t('Some modules have database schema updates to install. You should run the <a href="@update">database update script</a> immediately.', array('@update' => base_path() . 'update.php'));
+ break;
+ }
+ }
+ }
+ }
+
+ // Verify the update.php access setting
+ if ($phase == 'runtime') {
+ if (!empty($GLOBALS['update_free_access'])) {
+ $requirements['update access'] = array(
+ 'value' => $t('Not protected'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => $t('The update.php script is accessible to everyone without authentication check, which is a security risk. You must change the $update_free_access value in your settings.php back to FALSE.'),
+ );
+ }
+ else {
+ $requirements['update access'] = array(
+ 'value' => $t('Protected'),
+ );
+ }
+ $requirements['update access']['title'] = $t('Access to update.php');
+ }
+
+ // Display an error if a newly introduced dependency in a module is not resolved.
+ if ($phase == 'update') {
+ $profile = drupal_get_profile();
+ $files = system_rebuild_module_data();
+ foreach ($files as $module => $file) {
+ // Ignore disabled modules and install profiles.
+ if (!$file->status || $module == $profile) {
+ continue;
+ }
+ // Check the module's PHP version.
+ $name = $file->info['name'];
+ $php = $file->info['php'];
+ if (version_compare($php, PHP_VERSION, '>')) {
+ $requirements['php']['description'] .= $t('@name requires at least PHP @version.', array('@name' => $name, '@version' => $php));
+ $requirements['php']['severity'] = REQUIREMENT_ERROR;
+ }
+ // Check the module's required modules.
+ foreach ($file->requires as $requirement) {
+ $required_module = $requirement['name'];
+ // Check if the module exists.
+ if (!isset($files[$required_module])) {
+ $requirements["$module-$required_module"] = array(
+ 'title' => $t('Unresolved dependency'),
+ 'description' => $t('@name requires this module.', array('@name' => $name)),
+ 'value' => t('@required_name (Missing)', array('@required_name' => $required_module)),
+ 'severity' => REQUIREMENT_ERROR,
+ );
+ continue;
+ }
+ // Check for an incompatible version.
+ $required_file = $files[$required_module];
+ $required_name = $required_file->info['name'];
+ $version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $required_file->info['version']);
+ $compatibility = drupal_check_incompatibility($requirement, $version);
+ if ($compatibility) {
+ $compatibility = rtrim(substr($compatibility, 2), ')');
+ $requirements["$module-$required_module"] = array(
+ 'title' => $t('Unresolved dependency'),
+ 'description' => $t('@name requires this module and version. Currently using @required_name version @version', array('@name' => $name, '@required_name' => $required_name, '@version' => $version)),
+ 'value' => t('@required_name (Version @compatibility required)', array('@required_name' => $required_name, '@compatibility' => $compatibility)),
+ 'severity' => REQUIREMENT_ERROR,
+ );
+ continue;
+ }
+ }
+ }
+ }
+
+ // Test Unicode library
+ include_once DRUPAL_ROOT . '/includes/unicode.inc';
+ $requirements = array_merge($requirements, unicode_requirements());
+
+ if ($phase == 'runtime') {
+ // Check for update status module.
+ if (!module_exists('update')) {
+ $requirements['update status'] = array(
+ 'value' => $t('Not enabled'),
+ 'severity' => REQUIREMENT_WARNING,
+ 'description' => $t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you enable the update status module from the <a href="@module">module administration page</a> in order to stay up-to-date on new releases. For more information, <a href="@update">Update status handbook page</a>.', array('@update' => 'http://drupal.org/handbook/modules/update', '@module' => url('admin/modules'))),
+ );
+ }
+ else {
+ $requirements['update status'] = array(
+ 'value' => $t('Enabled'),
+ );
+ }
+ $requirements['update status']['title'] = $t('Update notifications');
+
+ // Check that Drupal can issue HTTP requests.
+ if (variable_get('drupal_http_request_fails', TRUE) && !system_check_http_request()) {
+ $requirements['http requests'] = array(
+ 'title' => $t('HTTP request status'),
+ 'value' => $t('Fails'),
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => $t('Your system or network configuration does not allow Drupal to access web pages, resulting in reduced functionality. This could be due to your webserver configuration or PHP settings, and should be resolved in order to download information about available updates, fetch aggregator feeds, sign in via OpenID, or use other network-dependent services. If you are certain that Drupal can access web pages but you are still seeing this message, you may add <code>$conf[\'drupal_http_request_fails\'] = FALSE;</code> to the bottom of your settings.php file.'),
+ );
+ }
+ }
+
+ return $requirements;
+}
+
+/**
+ * Implements hook_install().
+ */
+function system_install() {
+ // Create tables.
+ drupal_install_schema('system');
+ $versions = drupal_get_schema_versions('system');
+ $version = $versions ? max($versions) : SCHEMA_INSTALLED;
+ drupal_set_installed_schema_version('system', $version);
+
+ // Clear out module list and hook implementation statics before calling
+ // system_rebuild_theme_data().
+ module_list(TRUE);
+ module_implements_reset();
+
+ // Load system theme data appropriately.
+ system_rebuild_theme_data();
+
+ // Enable the default theme.
+ variable_set('theme_default', 'bartik');
+ db_update('system')
+ ->fields(array('status' => 1))
+ ->condition('type', 'theme')
+ ->condition('name', 'bartik')
+ ->execute();
+
+ // Populate the cron key variable.
+ $cron_key = drupal_hash_base64(drupal_random_bytes(55));
+ variable_set('cron_key', $cron_key);
+}
+
+/**
+ * Implements hook_schema().
+ */
+function system_schema() {
+ // NOTE: {variable} needs to be created before all other tables, as
+ // some database drivers, e.g. Oracle and DB2, will require variable_get()
+ // and variable_set() for overcoming some database specific limitations.
+ $schema['variable'] = array(
+ 'description' => 'Named variable/value pairs created by Drupal core or any other module or theme. All variables are cached in memory at the start of every Drupal request so developers should not be careless about what is stored here.',
+ 'fields' => array(
+ 'name' => array(
+ 'description' => 'The name of the variable.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'value' => array(
+ 'description' => 'The value of the variable.',
+ 'type' => 'blob',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ 'translatable' => TRUE,
+ ),
+ ),
+ 'primary key' => array('name'),
+ );
+
+ $schema['actions'] = array(
+ 'description' => 'Stores action information.',
+ 'fields' => array(
+ 'aid' => array(
+ 'description' => 'Primary Key: Unique actions ID.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '0',
+ ),
+ 'type' => array(
+ 'description' => 'The object that that action acts on (node, user, comment, system or custom types.)',
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'callback' => array(
+ 'description' => 'The callback function that executes when the action runs.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'parameters' => array(
+ 'description' => 'Parameters to be passed to the callback function.',
+ 'type' => 'blob',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ ),
+ 'label' => array(
+ 'description' => 'Label of the action.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '0',
+ ),
+ ),
+ 'primary key' => array('aid'),
+ );
+
+ $schema['batch'] = array(
+ 'description' => 'Stores details about batches (processes that run in multiple HTTP requests).',
+ 'fields' => array(
+ 'bid' => array(
+ 'description' => 'Primary Key: Unique batch ID.',
+ // This is not a serial column, to allow both progressive and
+ // non-progressive batches. See batch_process().
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'token' => array(
+ 'description' => "A string token generated against the current user's session id and the batch id, used to ensure that only the user who submitted the batch can effectively access it.",
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ ),
+ 'timestamp' => array(
+ 'description' => 'A Unix timestamp indicating when this batch was submitted for processing. Stale batches are purged at cron time.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ ),
+ 'batch' => array(
+ 'description' => 'A serialized array containing the processing data for the batch.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ ),
+ ),
+ 'primary key' => array('bid'),
+ 'indexes' => array(
+ 'token' => array('token'),
+ ),
+ );
+
+ $schema['blocked_ips'] = array(
+ 'description' => 'Stores blocked IP addresses.',
+ 'fields' => array(
+ 'iid' => array(
+ 'description' => 'Primary Key: unique ID for IP addresses.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'ip' => array(
+ 'description' => 'IP address',
+ 'type' => 'varchar',
+ 'length' => 40,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ ),
+ 'indexes' => array(
+ 'blocked_ip' => array('ip'),
+ ),
+ 'primary key' => array('iid'),
+ );
+
+ $schema['cache'] = array(
+ 'description' => 'Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.',
+ 'fields' => array(
+ 'cid' => array(
+ 'description' => 'Primary Key: Unique cache ID.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'data' => array(
+ 'description' => 'A collection of data to cache.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ ),
+ 'expire' => array(
+ 'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'created' => array(
+ 'description' => 'A Unix timestamp indicating when the cache entry was created.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'serialized' => array(
+ 'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
+ 'type' => 'int',
+ 'size' => 'small',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'expire' => array('expire'),
+ ),
+ 'primary key' => array('cid'),
+ );
+ $schema['cache_bootstrap'] = $schema['cache'];
+ $schema['cache_bootstrap']['description'] = 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.';
+ $schema['cache_form'] = $schema['cache'];
+ $schema['cache_form']['description'] = 'Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.';
+ $schema['cache_page'] = $schema['cache'];
+ $schema['cache_page']['description'] = 'Cache table used to store compressed pages for anonymous users, if page caching is enabled.';
+ $schema['cache_menu'] = $schema['cache'];
+ $schema['cache_menu']['description'] = 'Cache table for the menu system to store router information as well as generated link trees for various menu/page/user combinations.';
+ $schema['cache_path'] = $schema['cache'];
+ $schema['cache_path']['description'] = 'Cache table for path alias lookup.';
+
+ $schema['date_format_type'] = array(
+ 'description' => 'Stores configured date format types.',
+ 'fields' => array(
+ 'type' => array(
+ 'description' => 'The date format type, e.g. medium.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ ),
+ 'title' => array(
+ 'description' => 'The human readable name of the format type.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ),
+ 'locked' => array(
+ 'description' => 'Whether or not this is a system provided format.',
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'default' => 0,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'primary key' => array('type'),
+ 'indexes' => array(
+ 'title' => array('title'),
+ ),
+ );
+
+ // This table's name is plural as some versions of MySQL can't create a
+ // table named 'date_format'.
+ $schema['date_formats'] = array(
+ 'description' => 'Stores configured date formats.',
+ 'fields' => array(
+ 'dfid' => array(
+ 'description' => 'The date format identifier.',
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'unsigned' => TRUE,
+ ),
+ 'format' => array(
+ 'description' => 'The date format string.',
+ 'type' => 'varchar',
+ 'length' => 100,
+ 'not null' => TRUE,
+ ),
+ 'type' => array(
+ 'description' => 'The date format type, e.g. medium.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ ),
+ 'locked' => array(
+ 'description' => 'Whether or not this format can be modified.',
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'default' => 0,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'primary key' => array('dfid'),
+ 'unique keys' => array('formats' => array('format', 'type')),
+ );
+
+ $schema['date_format_locale'] = array(
+ 'description' => 'Stores configured date formats for each locale.',
+ 'fields' => array(
+ 'format' => array(
+ 'description' => 'The date format string.',
+ 'type' => 'varchar',
+ 'length' => 100,
+ 'not null' => TRUE,
+ ),
+ 'type' => array(
+ 'description' => 'The date format type, e.g. medium.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ ),
+ 'language' => array(
+ 'description' => 'A {languages}.language for this format to be used with.',
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'primary key' => array('type', 'language'),
+ );
+
+ $schema['file_managed'] = array(
+ 'description' => 'Stores information for uploaded files.',
+ 'fields' => array(
+ 'fid' => array(
+ 'description' => 'File ID.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'uid' => array(
+ 'description' => 'The {users}.uid of the user who is associated with the file.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'filename' => array(
+ 'description' => 'Name of the file with no path components. This may differ from the basename of the URI if the file is renamed to avoid overwriting an existing file.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'uri' => array(
+ 'description' => 'The URI to access the file (either local or remote).',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'filemime' => array(
+ 'description' => "The file's MIME type.",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'filesize' => array(
+ 'description' => 'The size of the file in bytes.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'status' => array(
+ 'description' => 'A field indicating the status of the file. Two status are defined in core: temporary (0) and permanent (1). Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during a cron run.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'timestamp' => array(
+ 'description' => 'UNIX timestamp for when the file was added.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'indexes' => array(
+ 'uid' => array('uid'),
+ 'status' => array('status'),
+ 'timestamp' => array('timestamp'),
+ ),
+ 'unique keys' => array(
+ 'uri' => array('uri'),
+ ),
+ 'primary key' => array('fid'),
+ 'foreign keys' => array(
+ 'file_owner' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid'),
+ ),
+ ),
+ );
+
+ $schema['file_usage'] = array(
+ 'description' => 'Track where a file is used.',
+ 'fields' => array(
+ 'fid' => array(
+ 'description' => 'File ID.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'module' => array(
+ 'description' => 'The name of the module that is using the file.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'type' => array(
+ 'description' => 'The name of the object type in which the file is used.',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'id' => array(
+ 'description' => 'The primary key of the object using the file.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'count' => array(
+ 'description' => 'The number of times this file is used by this object.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('fid', 'type', 'id', 'module'),
+ 'indexes' => array(
+ 'type_id' => array('type', 'id'),
+ 'fid_count' => array('fid', 'count'),
+ 'fid_module' => array('fid', 'module'),
+ ),
+ );
+
+ $schema['flood'] = array(
+ 'description' => 'Flood controls the threshold of events, such as the number of contact attempts.',
+ 'fields' => array(
+ 'fid' => array(
+ 'description' => 'Unique flood event ID.',
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ ),
+ 'event' => array(
+ 'description' => 'Name of event (e.g. contact).',
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'identifier' => array(
+ 'description' => 'Identifier of the visitor, such as an IP address or hostname.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'timestamp' => array(
+ 'description' => 'Timestamp of the event.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'expiration' => array(
+ 'description' => 'Expiration timestamp. Expired events are purged on cron run.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('fid'),
+ 'indexes' => array(
+ 'allow' => array('event', 'identifier', 'timestamp'),
+ 'purge' => array('expiration'),
+ ),
+ );
+
+ $schema['menu_router'] = array(
+ 'description' => 'Maps paths to various callbacks (access, page and title)',
+ 'fields' => array(
+ 'path' => array(
+ 'description' => 'Primary Key: the Drupal path this entry describes',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'load_functions' => array(
+ 'description' => 'A serialized array of function names (like node_load) to be called to load an object corresponding to a part of the current path.',
+ 'type' => 'blob',
+ 'not null' => TRUE,
+ ),
+ 'to_arg_functions' => array(
+ 'description' => 'A serialized array of function names (like user_uid_optional_to_arg) to be called to replace a part of the router path with another string.',
+ 'type' => 'blob',
+ 'not null' => TRUE,
+ ),
+ 'access_callback' => array(
+ 'description' => 'The callback which determines the access to this router path. Defaults to user_access.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'access_arguments' => array(
+ 'description' => 'A serialized array of arguments for the access callback.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ ),
+ 'page_callback' => array(
+ 'description' => 'The name of the function that renders the page.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'page_arguments' => array(
+ 'description' => 'A serialized array of arguments for the page callback.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ ),
+ 'delivery_callback' => array(
+ 'description' => 'The name of the function that sends the result of the page_callback function to the browser.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'fit' => array(
+ 'description' => 'A numeric representation of how specific the path is.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'number_parts' => array(
+ 'description' => 'Number of parts in this router path.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'context' => array(
+ 'description' => 'Only for local tasks (tabs) - the context of a local task to control its placement.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'tab_parent' => array(
+ 'description' => 'Only for local tasks (tabs) - the router path of the parent page (which may also be a local task).',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'tab_root' => array(
+ 'description' => 'Router path of the closest non-tab parent page. For pages that are not local tasks, this will be the same as the path.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'title' => array(
+ 'description' => 'The title for the current page, or the title for the tab if this is a local task.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'title_callback' => array(
+ 'description' => 'A function which will alter the title. Defaults to t()',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'title_arguments' => array(
+ 'description' => 'A serialized array of arguments for the title callback. If empty, the title will be used as the sole argument for the title callback.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'theme_callback' => array(
+ 'description' => 'A function which returns the name of the theme that will be used to render this page. If left empty, the default theme will be used.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'theme_arguments' => array(
+ 'description' => 'A serialized array of arguments for the theme callback.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'type' => array(
+ 'description' => 'Numeric representation of the type of the menu item, like MENU_LOCAL_TASK.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'description' => array(
+ 'description' => 'A description of this item.',
+ 'type' => 'text',
+ 'not null' => TRUE,
+ ),
+ 'position' => array(
+ 'description' => 'The position of the block (left or right) on the system administration page for this item.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'weight' => array(
+ 'description' => 'Weight of the element. Lighter weights are higher up, heavier weights go down.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'include_file' => array(
+ 'description' => 'The file to include for this element, usually the page callback function lives in this file.',
+ 'type' => 'text',
+ 'size' => 'medium',
+ ),
+ ),
+ 'indexes' => array(
+ 'fit' => array('fit'),
+ 'tab_parent' => array(array('tab_parent', 64), 'weight', 'title'),
+ 'tab_root_weight_title' => array(array('tab_root', 64), 'weight', 'title'),
+ ),
+ 'primary key' => array('path'),
+ );
+
+ $schema['menu_links'] = array(
+ 'description' => 'Contains the individual links within a menu.',
+ 'fields' => array(
+ 'menu_name' => array(
+ 'description' => "The menu name. All links with the same menu name (such as 'navigation') are part of the same menu.",
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'mlid' => array(
+ 'description' => 'The menu link ID (mlid) is the integer primary key.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'plid' => array(
+ 'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'link_path' => array(
+ 'description' => 'The Drupal path or external path this link points to.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'router_path' => array(
+ 'description' => 'For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'link_title' => array(
+ 'description' => 'The text displayed for the link, which may be modified by a title callback stored in {menu_router}.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'options' => array(
+ 'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'translatable' => TRUE,
+ ),
+ 'module' => array(
+ 'description' => 'The name of the module that generated this link.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => 'system',
+ ),
+ 'hidden' => array(
+ 'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'external' => array(
+ 'description' => 'A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'has_children' => array(
+ 'description' => 'Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'expanded' => array(
+ 'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'weight' => array(
+ 'description' => 'Link weight among links in the same menu at the same depth.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'depth' => array(
+ 'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'customized' => array(
+ 'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'p1' => array(
+ 'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p2' => array(
+ 'description' => 'The second mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p3' => array(
+ 'description' => 'The third mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p4' => array(
+ 'description' => 'The fourth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p5' => array(
+ 'description' => 'The fifth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p6' => array(
+ 'description' => 'The sixth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p7' => array(
+ 'description' => 'The seventh mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p8' => array(
+ 'description' => 'The eighth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p9' => array(
+ 'description' => 'The ninth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'updated' => array(
+ 'description' => 'Flag that indicates that this link was generated during the update from Drupal 5.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ ),
+ 'indexes' => array(
+ 'path_menu' => array(array('link_path', 128), 'menu_name'),
+ 'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
+ 'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+ 'router_path' => array(array('router_path', 128)),
+ ),
+ 'primary key' => array('mlid'),
+ );
+
+ $schema['queue'] = array(
+ 'description' => 'Stores items in queues.',
+ 'fields' => array(
+ 'item_id' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique item ID.',
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The queue name.',
+ ),
+ 'data' => array(
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'serialize' => TRUE,
+ 'description' => 'The arbitrary data for the item.',
+ ),
+ 'expire' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Timestamp when the claim lease expires on the item.',
+ ),
+ 'created' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Timestamp when the item was created.',
+ ),
+ ),
+ 'primary key' => array('item_id'),
+ 'indexes' => array(
+ 'name_created' => array('name', 'created'),
+ 'expire' => array('expire'),
+ ),
+ );
+
+ $schema['registry'] = array(
+ 'description' => "Each record is a function, class, or interface name and the file it is in.",
+ 'fields' => array(
+ 'name' => array(
+ 'description' => 'The name of the function, class, or interface.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'type' => array(
+ 'description' => 'Either function or class or interface.',
+ 'type' => 'varchar',
+ 'length' => 9,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'filename' => array(
+ 'description' => 'Name of the file.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ),
+ 'module' => array(
+ 'description' => 'Name of the module the file belongs to.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => ''
+ ),
+ 'weight' => array(
+ 'description' => "The order in which this module's hooks should be invoked relative to other modules. Equal-weighted modules are ordered by name.",
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('name', 'type'),
+ 'indexes' => array(
+ 'hook' => array('type', 'weight', 'module'),
+ ),
+ );
+
+ $schema['registry_file'] = array(
+ 'description' => "Files parsed to build the registry.",
+ 'fields' => array(
+ 'filename' => array(
+ 'description' => 'Path to the file.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ ),
+ 'hash' => array(
+ 'description' => "sha-256 hash of the file's contents when last parsed.",
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'primary key' => array('filename'),
+ );
+
+ $schema['semaphore'] = array(
+ 'description' => 'Table for holding semaphores, locks, flags, etc. that cannot be stored as Drupal variables since they must not be cached.',
+ 'fields' => array(
+ 'name' => array(
+ 'description' => 'Primary Key: Unique name.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => ''
+ ),
+ 'value' => array(
+ 'description' => 'A value for the semaphore.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => ''
+ ),
+ 'expire' => array(
+ 'description' => 'A Unix timestamp with microseconds indicating when the semaphore should expire.',
+ 'type' => 'float',
+ 'size' => 'big',
+ 'not null' => TRUE
+ ),
+ ),
+ 'indexes' => array(
+ 'value' => array('value'),
+ 'expire' => array('expire'),
+ ),
+ 'primary key' => array('name'),
+ );
+
+ $schema['sequences'] = array(
+ 'description' => 'Stores IDs.',
+ 'fields' => array(
+ 'value' => array(
+ 'description' => 'The value of the sequence.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'primary key' => array('value'),
+ );
+
+ $schema['sessions'] = array(
+ 'description' => "Drupal's session handlers read and write into the sessions table. Each record represents a user session, either anonymous or authenticated.",
+ 'fields' => array(
+ 'uid' => array(
+ 'description' => 'The {users}.uid corresponding to a session, or 0 for anonymous user.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'sid' => array(
+ 'description' => "A session ID. The value is generated by Drupal's session handlers.",
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ ),
+ 'ssid' => array(
+ 'description' => "Secure session ID. The value is generated by Drupal's session handlers.",
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'hostname' => array(
+ 'description' => 'The IP address that last used this session ID (sid).',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'timestamp' => array(
+ 'description' => 'The Unix timestamp when this session last requested a page. Old records are purged by PHP automatically.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'cache' => array(
+ 'description' => "The time of this user's last post. This is used when the site has specified a minimum_cache_lifetime. See cache_get().",
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'session' => array(
+ 'description' => 'The serialized contents of $_SESSION, an array of name/value pairs that persists across page requests by this session ID. Drupal loads $_SESSION from here at the start of each request and saves it at the end.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ ),
+ ),
+ 'primary key' => array(
+ 'sid',
+ 'ssid',
+ ),
+ 'indexes' => array(
+ 'timestamp' => array('timestamp'),
+ 'uid' => array('uid'),
+ 'ssid' => array('ssid'),
+ ),
+ 'foreign keys' => array(
+ 'session_user' => array(
+ 'table' => 'users',
+ 'columns' => array('uid' => 'uid'),
+ ),
+ ),
+ );
+
+ $schema['system'] = array(
+ 'description' => "A list of all modules, themes, and theme engines that are or have been installed in Drupal's file system.",
+ 'fields' => array(
+ 'filename' => array(
+ 'description' => 'The path of the primary file for this item, relative to the Drupal root; e.g. modules/node/node.module.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'name' => array(
+ 'description' => 'The name of the item; e.g. node.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'type' => array(
+ 'description' => 'The type of the item, either module, theme, or theme_engine.',
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'owner' => array(
+ 'description' => "A theme's 'parent' . Can be either a theme or an engine.",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'status' => array(
+ 'description' => 'Boolean indicating whether or not this item is enabled.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'bootstrap' => array(
+ 'description' => "Boolean indicating whether this module is loaded during Drupal's early bootstrapping phase (e.g. even before the page cache is consulted).",
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'schema_version' => array(
+ 'description' => "The module's database schema version number. -1 if the module is not installed (its tables do not exist); 0 or the largest N of the module's hook_update_N() function that has either been run or existed when the module was first installed.",
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => -1,
+ 'size' => 'small',
+ ),
+ 'weight' => array(
+ 'description' => "The order in which this module's hooks should be invoked relative to other modules. Equal-weighted modules are ordered by name.",
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'info' => array(
+ 'description' => "A serialized array containing information from the module's .info file; keys can include name, description, package, version, core, dependencies, and php.",
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ ),
+ ),
+ 'primary key' => array('filename'),
+ 'indexes' => array(
+ 'system_list' => array('status', 'bootstrap', 'type', 'weight', 'name'),
+ 'type_name' => array('type', 'name'),
+ ),
+ );
+
+ $schema['url_alias'] = array(
+ 'description' => 'A list of URL aliases for Drupal paths; a user may visit either the source or destination path.',
+ 'fields' => array(
+ 'pid' => array(
+ 'description' => 'A unique path alias identifier.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'source' => array(
+ 'description' => 'The Drupal path this alias is for; e.g. node/12.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'alias' => array(
+ 'description' => 'The alias for this path; e.g. title-of-the-story.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'language' => array(
+ 'description' => "The language this alias is for; if 'und', the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.",
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ ),
+ 'primary key' => array('pid'),
+ 'indexes' => array(
+ 'alias_language_pid' => array('alias', 'language', 'pid'),
+ 'source_language_pid' => array('source', 'language', 'pid'),
+ ),
+ );
+
+ return $schema;
+}
+
+// Updates for core.
+
+function system_update_last_removed() {
+ return 7069;
+}
+
+/**
+ * @defgroup updates-7.x-to-8.x Updates from 7.x to 8.x
+ * @{
+ * Update functions from 7.x to 8.x.
+ */
+
+/**
+ * Enable entity module.
+ */
+function system_update_8000() {
+ update_module_enable(array('entity'));
+}
+
+/**
+ * Move from the Garland theme.
+ */
+function system_update_8001() {
+ $themes = array('theme_default', 'maintenance_theme', 'admin_theme');
+ foreach ($themes as $theme) {
+ if (variable_get($theme) == 'garland') {
+ variable_set($theme, 'bartik');
+ }
+ }
+}
+
+/**
+ * @} End of "defgroup updates-7.x-to-8.x"
+ * The next series of updates should start at 9000.
+ */
diff --git a/core/modules/system/system.js b/core/modules/system/system.js
new file mode 100644
index 00000000000..5446d28a370
--- /dev/null
+++ b/core/modules/system/system.js
@@ -0,0 +1,137 @@
+(function ($) {
+
+/**
+ * Show/hide the 'Email site administrator when updates are available' checkbox
+ * on the install page.
+ */
+Drupal.hideEmailAdministratorCheckbox = function () {
+ // Make sure the secondary box is shown / hidden as necessary on page load.
+ if ($('#edit-update-status-module-1').is(':checked')) {
+ $('.form-item-update-status-module-2').show();
+ }
+ else {
+ $('.form-item-update-status-module-2').hide();
+ }
+
+ // Toggle the display as necessary when the checkbox is clicked.
+ $('#edit-update-status-module-1').change( function () {
+ $('.form-item-update-status-module-2').toggle();
+ });
+};
+
+/**
+ * Internal function to check using Ajax if clean URLs can be enabled on the
+ * settings page.
+ *
+ * This function is not used to verify whether or not clean URLs
+ * are currently enabled.
+ */
+Drupal.behaviors.cleanURLsSettingsCheck = {
+ attach: function (context, settings) {
+ // This behavior attaches by ID, so is only valid once on a page.
+ // Also skip if we are on an install page, as Drupal.cleanURLsInstallCheck will handle
+ // the processing.
+ if (!($('#edit-clean-url').length) || $('#edit-clean-url.install').once('clean-url').length) {
+ return;
+ }
+ var url = settings.basePath + 'admin/config/search/clean-urls/check';
+ $.ajax({
+ url: location.protocol + '//' + location.host + url,
+ dataType: 'json',
+ success: function () {
+ // Check was successful. Redirect using a "clean URL". This will force the form that allows enabling clean URLs.
+ location = settings.basePath +"admin/config/search/clean-urls";
+ }
+ });
+ }
+};
+
+/**
+ * Internal function to check using Ajax if clean URLs can be enabled on the
+ * install page.
+ *
+ * This function is not used to verify whether or not clean URLs
+ * are currently enabled.
+ */
+Drupal.cleanURLsInstallCheck = function () {
+ var url = location.protocol + '//' + location.host + Drupal.settings.basePath + 'admin/config/search/clean-urls/check';
+ // Submit a synchronous request to avoid database errors associated with
+ // concurrent requests during install.
+ $.ajax({
+ async: false,
+ url: url,
+ dataType: 'json',
+ success: function () {
+ // Check was successful.
+ $('#edit-clean-url').attr('value', 1);
+ }
+ });
+};
+
+/**
+ * When a field is filled out, apply its value to other fields that will likely
+ * use the same value. In the installer this is used to populate the
+ * administrator e-mail address with the same value as the site e-mail address.
+ */
+Drupal.behaviors.copyFieldValue = {
+ attach: function (context, settings) {
+ for (var sourceId in settings.copyFieldValue) {
+ $('#' + sourceId, context).once('copy-field-values').bind('blur', function () {
+ // Get the list of target fields.
+ var targetIds = settings.copyFieldValue[sourceId];
+ // Add the behavior to update target fields on blur of the primary field.
+ for (var delta in targetIds) {
+ var targetField = $('#' + targetIds[delta]);
+ if (targetField.val() == '') {
+ targetField.val(this.value);
+ }
+ }
+ });
+ }
+ }
+};
+
+/**
+ * Show/hide custom format sections on the regional settings page.
+ */
+Drupal.behaviors.dateTime = {
+ attach: function (context, settings) {
+ for (var value in settings.dateTime) {
+ var settings = settings.dateTime[value];
+ var source = '#edit-' + value;
+ var suffix = source + '-suffix';
+
+ // Attach keyup handler to custom format inputs.
+ $('input' + source, context).once('date-time').keyup(function () {
+ var input = $(this);
+ var url = settings.lookup + (settings.lookup.match(/\?q=/) ? '&format=' : '?format=') + encodeURIComponent(input.val());
+ $.getJSON(url, function (data) {
+ $(suffix).empty().append(' ' + settings.text + ': <em>' + data + '</em>');
+ });
+ });
+ }
+ }
+};
+
+ /**
+ * Show/hide settings for page caching depending on whether page caching is
+ * enabled or not.
+ */
+Drupal.behaviors.pageCache = {
+ attach: function (context, settings) {
+ $('#edit-cache-0', context).change(function () {
+ $('#page-compression-wrapper').hide();
+ $('#cache-error').hide();
+ });
+ $('#edit-cache-1', context).change(function () {
+ $('#page-compression-wrapper').show();
+ $('#cache-error').hide();
+ });
+ $('#edit-cache-2', context).change(function () {
+ $('#page-compression-wrapper').show();
+ $('#cache-error').show();
+ });
+ }
+};
+
+})(jQuery);
diff --git a/core/modules/system/system.mail.inc b/core/modules/system/system.mail.inc
new file mode 100644
index 00000000000..ef50642c55a
--- /dev/null
+++ b/core/modules/system/system.mail.inc
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Drupal core implementations of MailSystemInterface.
+ */
+
+/**
+ * The default Drupal mail backend using PHP's mail function.
+ */
+class DefaultMailSystem implements MailSystemInterface {
+ /**
+ * Concatenate and wrap the e-mail body for plain-text mails.
+ *
+ * @param $message
+ * A message array, as described in hook_mail_alter().
+ *
+ * @return
+ * The formatted $message.
+ */
+ public function format(array $message) {
+ // Join the body array into one string.
+ $message['body'] = implode("\n\n", $message['body']);
+ // Convert any HTML to plain-text.
+ $message['body'] = drupal_html_to_text($message['body']);
+ // Wrap the mail body for sending.
+ $message['body'] = drupal_wrap_mail($message['body']);
+ return $message;
+ }
+
+ /**
+ * Send an e-mail message, using Drupal variables and default settings.
+ *
+ * @see http://php.net/manual/en/function.mail.php
+ * @see drupal_mail()
+ *
+ * @param $message
+ * A message array, as described in hook_mail_alter().
+ * @return
+ * TRUE if the mail was successfully accepted, otherwise FALSE.
+ */
+ public function mail(array $message) {
+ // If 'Return-Path' isn't already set in php.ini, we pass it separately
+ // as an additional parameter instead of in the header.
+ // However, if PHP's 'safe_mode' is on, this is not allowed.
+ if (isset($message['headers']['Return-Path']) && !ini_get('safe_mode')) {
+ $return_path_set = strpos(ini_get('sendmail_path'), ' -f');
+ if (!$return_path_set) {
+ $message['Return-Path'] = $message['headers']['Return-Path'];
+ unset($message['headers']['Return-Path']);
+ }
+ }
+ $mimeheaders = array();
+ foreach ($message['headers'] as $name => $value) {
+ $mimeheaders[] = $name . ': ' . mime_header_encode($value);
+ }
+ $line_endings = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
+ // Prepare mail commands.
+ $mail_subject = mime_header_encode($message['subject']);
+ // Note: e-mail uses CRLF for line-endings. PHP's API requires LF
+ // on Unix and CRLF on Windows. Drupal automatically guesses the
+ // line-ending format appropriate for your system. If you need to
+ // override this, adjust $conf['mail_line_endings'] in settings.php.
+ $mail_body = preg_replace('@\r?\n@', $line_endings, $message['body']);
+ // For headers, PHP's API suggests that we use CRLF normally,
+ // but some MTAs incorrectly replace LF with CRLF. See #234403.
+ $mail_headers = join("\n", $mimeheaders);
+ if (isset($message['Return-Path']) && !ini_get('safe_mode')) {
+ $mail_result = mail(
+ $message['to'],
+ $mail_subject,
+ $mail_body,
+ $mail_headers,
+ // Pass the Return-Path via sendmail's -f command.
+ '-f ' . $message['Return-Path']
+ );
+ }
+ else {
+ // The optional $additional_parameters argument to mail() is not allowed
+ // if safe_mode is enabled. Passing any value throws a PHP warning and
+ // makes mail() return FALSE.
+ $mail_result = mail(
+ $message['to'],
+ $mail_subject,
+ $mail_body,
+ $mail_headers
+ );
+ }
+ return $mail_result;
+ }
+}
+
+/**
+ * A mail sending implementation that captures sent messages to a variable.
+ *
+ * This class is for running tests or for development.
+ */
+class TestingMailSystem extends DefaultMailSystem implements MailSystemInterface {
+ /**
+ * Accept an e-mail message and store it in a variable.
+ *
+ * @param $message
+ * An e-mail message.
+ */
+ public function mail(array $message) {
+ $captured_emails = variable_get('drupal_test_email_collector', array());
+ $captured_emails[] = $message;
+ variable_set('drupal_test_email_collector', $captured_emails);
+ return TRUE;
+ }
+}
+
diff --git a/core/modules/system/system.maintenance.css b/core/modules/system/system.maintenance.css
new file mode 100644
index 00000000000..5543c2db816
--- /dev/null
+++ b/core/modules/system/system.maintenance.css
@@ -0,0 +1,55 @@
+
+/**
+ * Update styles
+ */
+#update-results {
+ margin-top: 3em;
+ padding: 0.25em;
+ border: 1px solid #ccc;
+ background: #eee;
+ font-size: smaller;
+}
+#update-results h2 {
+ margin-top: 0.25em;
+}
+#update-results h4 {
+ margin-bottom: 0.25em;
+}
+#update-results li.none {
+ color: #888;
+ font-style: italic;
+}
+#update-results li.failure strong {
+ color: #b63300;
+}
+
+/**
+ * Authorize.php styles
+ */
+.connection-settings-update-filetransfer-default-wrapper {
+ float: left;
+}
+#edit-submit-connection {
+ clear: both;
+}
+.filetransfer {
+ display: none;
+ clear: both;
+}
+#edit-connection-settings-change-connection-type {
+ margin: 2.6em 0.5em 0em 1em;
+}
+
+/**
+ * Installation task list
+ */
+ol.task-list li.active {
+ font-weight: bold;
+}
+
+/**
+ * Installation clean URLs
+ */
+#clean-url.install {
+ display: none;
+}
diff --git a/core/modules/system/system.menus-rtl.css b/core/modules/system/system.menus-rtl.css
new file mode 100644
index 00000000000..be85245b2bf
--- /dev/null
+++ b/core/modules/system/system.menus-rtl.css
@@ -0,0 +1,37 @@
+
+/**
+ * @file
+ * RTL styles for menus and navigation markup.
+ */
+
+ul.menu {
+ text-align:right;
+}
+ul.menu li {
+ margin: 0 0.5em 0 0;
+}
+ul li.collapsed {
+ list-style-image: url(../../misc/menu-collapsed-rtl.png);
+}
+li.expanded,
+li.collapsed,
+li.leaf {
+ padding: 0.2em 0 0 0.5em;
+}
+
+/**
+ * Markup generated by theme_menu_local_tasks().
+ */
+ul.primary {
+ padding: 0 1em 0 0;
+}
+ul.primary li a {
+ margin-right: 5px;
+ margin-left: 0.5em;
+}
+ul.secondary li {
+ border-left: 1px solid #ccc;
+ border-right: none;
+ display: inline;
+ padding: 0 1em;
+}
diff --git a/core/modules/system/system.menus.css b/core/modules/system/system.menus.css
new file mode 100644
index 00000000000..514b029527a
--- /dev/null
+++ b/core/modules/system/system.menus.css
@@ -0,0 +1,116 @@
+
+/**
+ * @file
+ * Styles for menus and navigation markup.
+ */
+
+/**
+ * Markup generated by theme_menu_tree().
+ */
+ul.menu {
+ border: none;
+ list-style: none;
+ text-align: left; /* LTR */
+}
+ul.menu li {
+ margin: 0 0 0 0.5em; /* LTR */
+}
+ul li.expanded {
+ list-style-image: url(../../misc/menu-expanded.png);
+ list-style-type: circle;
+}
+ul li.collapsed {
+ list-style-image: url(../../misc/menu-collapsed.png); /* LTR */
+ list-style-type: disc;
+}
+ul li.leaf {
+ list-style-image: url(../../misc/menu-leaf.png);
+ list-style-type: square;
+}
+li.expanded,
+li.collapsed,
+li.leaf {
+ padding: 0.2em 0.5em 0 0; /* LTR */
+ margin: 0;
+}
+li a.active {
+ color: #000;
+}
+td.menu-disabled {
+ background: #ccc;
+}
+
+/**
+ * Markup generated by theme_links().
+ */
+ul.inline,
+ul.links.inline {
+ display: inline;
+ padding-left: 0;
+}
+ul.inline li {
+ display: inline;
+ list-style-type: none;
+ padding: 0 0.5em;
+}
+
+/**
+ * Markup generated by theme_breadcrumb().
+ */
+.breadcrumb {
+ padding-bottom: 0.5em;
+}
+
+/**
+ * Markup generated by theme_menu_local_tasks().
+ */
+ul.primary {
+ border-bottom: 1px solid #bbb;
+ border-collapse: collapse;
+ height: auto;
+ line-height: normal;
+ list-style: none;
+ margin: 5px;
+ padding: 0 0 0 1em; /* LTR */
+ white-space: nowrap;
+}
+ul.primary li {
+ display: inline;
+}
+ul.primary li a {
+ background-color: #ddd;
+ border-color: #bbb;
+ border-style: solid solid none solid;
+ border-width: 1px;
+ height: auto;
+ margin-right: 0.5em; /* LTR */
+ padding: 0 1em;
+ text-decoration: none;
+}
+ul.primary li.active a {
+ background-color: #fff;
+ border: 1px solid #bbb;
+ border-bottom: 1px solid #fff;
+}
+ul.primary li a:hover {
+ background-color: #eee;
+ border-color: #ccc;
+ border-bottom-color: #eee;
+}
+ul.secondary {
+ border-bottom: 1px solid #bbb;
+ padding: 0.5em 1em;
+ margin: 5px;
+}
+ul.secondary li {
+ border-right: 1px solid #ccc; /* LTR */
+ display: inline;
+ padding: 0 1em;
+}
+ul.secondary a {
+ padding: 0;
+ text-decoration: none;
+}
+ul.secondary a.active {
+ border-bottom: 4px solid #999;
+}
diff --git a/core/modules/system/system.messages-rtl.css b/core/modules/system/system.messages-rtl.css
new file mode 100644
index 00000000000..445417b08c4
--- /dev/null
+++ b/core/modules/system/system.messages-rtl.css
@@ -0,0 +1,13 @@
+
+/**
+ * @file
+ * RTL Styles for system messages.
+ */
+
+div.messages {
+ background-position: 99% 8px;
+ padding: 10px 50px 10px 10px;
+}
+div.messages ul {
+ margin: 0 1em 0 0;
+}
diff --git a/core/modules/system/system.messages.css b/core/modules/system/system.messages.css
new file mode 100644
index 00000000000..ffd4e8efcaa
--- /dev/null
+++ b/core/modules/system/system.messages.css
@@ -0,0 +1,63 @@
+
+/**
+ * @file
+ * Styles for system messages.
+ */
+
+div.messages {
+ background-position: 8px 8px; /* LTR */
+ background-repeat: no-repeat;
+ border: 1px solid;
+ margin: 6px 0;
+ padding: 10px 10px 10px 50px; /* LTR */
+}
+
+div.status {
+ background-image: url(../../misc/message-24-ok.png);
+ border-color: #be7;
+}
+div.status,
+.ok {
+ color: #234600;
+}
+div.status,
+table tr.ok {
+ background-color: #f8fff0;
+}
+
+div.warning {
+ background-image: url(../../misc/message-24-warning.png);
+ border-color: #ed5;
+}
+div.warning,
+.warning {
+ color: #840;
+}
+div.warning,
+table tr.warning {
+ background-color: #fffce5;
+}
+
+div.error {
+ background-image: url(../../misc/message-24-error.png);
+ border-color: #ed541d;
+}
+div.error,
+.error {
+ color: #8c2e0b;
+}
+div.error,
+table tr.error {
+ background-color: #fef5f1;
+}
+div.error p.error {
+ color: #333;
+}
+
+div.messages ul {
+ margin: 0 0 0 1em; /* LTR */
+ padding: 0;
+}
+div.messages ul li {
+ list-style-image: none;
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
new file mode 100644
index 00000000000..0ef688e4a69
--- /dev/null
+++ b/core/modules/system/system.module
@@ -0,0 +1,3988 @@
+<?php
+
+/**
+ * @file
+ * Configuration system that lets administrators modify the workings of the site.
+ */
+
+/**
+ * Maximum age of temporary files in seconds.
+ */
+define('DRUPAL_MAXIMUM_TEMP_FILE_AGE', 21600);
+
+/**
+ * Default interval for automatic cron executions in seconds.
+ */
+define('DRUPAL_CRON_DEFAULT_THRESHOLD', 10800);
+
+/**
+ * New users will be set to the default time zone at registration.
+ */
+define('DRUPAL_USER_TIMEZONE_DEFAULT', 0);
+
+/**
+ * New users will get an empty time zone at registration.
+ */
+define('DRUPAL_USER_TIMEZONE_EMPTY', 1);
+
+/**
+ * New users will select their own timezone at registration.
+ */
+define('DRUPAL_USER_TIMEZONE_SELECT', 2);
+
+ /**
+ * Disabled option on forms and settings
+ */
+define('DRUPAL_DISABLED', 0);
+
+/**
+ * Optional option on forms and settings
+ */
+define('DRUPAL_OPTIONAL', 1);
+
+/**
+ * Required option on forms and settings
+ */
+define('DRUPAL_REQUIRED', 2);
+
+/**
+ * Return only visible regions.
+ *
+ * @see system_region_list()
+ */
+define('REGIONS_VISIBLE', 'visible');
+
+/**
+ * Return all regions.
+ *
+ * @see system_region_list()
+ */
+define('REGIONS_ALL', 'all');
+
+/**
+ * Implements hook_help().
+ */
+function system_help($path, $arg) {
+ global $base_url;
+
+ switch ($path) {
+ case 'admin/help#system':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $output .= '<p>' . t('The System module is integral to the site, and provides basic but extensible functionality for use by other modules and themes. Some integral elements of Drupal are contained in and managed by the System module, including caching, enabling and disabling modules and themes, preparing and displaying the administrative page, and configuring fundamental site settings. A number of key system maintenance operations are also part of the System module. For more information, see the online handbook entry for <a href="@system">System module</a>.', array('@system' => 'http://drupal.org/handbook/modules/system')) . '</p>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Managing modules') . '</dt>';
+ $output .= '<dd>' . t('The System module allows users with the appropriate permissions to enable and disable modules on the <a href="@modules">Modules administration page</a>. Drupal comes with a number of core modules, and each module provides a discrete set of features and may be enabled or disabled depending on the needs of the site. Many additional modules contributed by members of the Drupal community are available for download at the <a href="@drupal-modules">Drupal.org module page</a>.', array('@modules' => url('admin/modules'), '@drupal-modules' => 'http://drupal.org/project/modules')) . '</dd>';
+ $output .= '<dt>' . t('Managing themes') . '</dt>';
+ $output .= '<dd>' . t('The System module allows users with the appropriate permissions to enable and disable themes on the <a href="@themes">Appearance administration page</a>. Themes determine the design and presentation of your site. Drupal comes packaged with several core themes, and additional contributed themes are available at the <a href="@drupal-themes">Drupal.org theme page</a>.', array('@themes' => url('admin/appearance'), '@drupal-themes' => 'http://drupal.org/project/themes')) . '</dd>';
+ $output .= '<dt>' . t('Managing caching') . '</dt>';
+ $output .= '<dd>' . t("The System module allows users with the appropriate permissions to manage caching on the <a href='@cache-settings'>Performance settings page</a>. Drupal has a robust caching system that allows the efficient re-use of previously-constructed web pages and web page components. Pages requested by anonymous users are stored in a compressed format; depending on your site configuration and the amount of your web traffic tied to anonymous visitors, the caching system may significantly increase the speed of your site.", array('@cache-settings' => url('admin/config/development/performance'))) . '</dd>';
+ $output .= '<dt>' . t('Performing system maintenance') . '</dt>';
+ $output .= '<dd>' . t('In order for the site and its modules to continue to operate well, a set of routine administrative operations must run on a regular basis. The System module manages this task by making use of a system cron job. You can verify the status of cron tasks by visiting the <a href="@status">Status report page</a>. For more information, see the online handbook entry for <a href="@handbook">configuring cron jobs</a>. You can set up cron job by visiting <a href="@cron">Cron configuration</a> page', array('@status' => url('admin/reports/status'), '@handbook' => 'http://drupal.org/cron', '@cron' => url('admin/config/system/cron'))) . '</dd>';
+ $output .= '<dt>' . t('Configuring basic site settings') . '</dt>';
+ $output .= '<dd>' . t('The System module also handles basic configuration options for your site, including <a href="@date-time-settings">Date and time settings</a>, <a href="@file-system">File system settings</a>, <a href="@clean-url">Clean URL support</a>, <a href="@site-info">Site name and other information</a>, and a <a href="@maintenance-mode">Maintenance mode</a> for taking your site temporarily offline.', array('@date-time-settings' => url('admin/config/regional/date-time'), '@file-system' => url('admin/config/media/file-system'), '@clean-url' => url('admin/config/search/clean-urls'), '@site-info' => url('admin/config/system/site-information'), '@maintenance-mode' => url('admin/config/development/maintenance'))) . '</dd>';
+ $output .= '<dt>' . t('Configuring actions') . '</dt>';
+ $output .= '<dd>' . t('Actions are individual tasks that the system can do, such as unpublishing a piece of content or banning a user. Modules, such as the <a href="@trigger-help">Trigger module</a>, can fire these actions when certain system events happen; for example, when a new post is added or when a user logs in. Modules may also provide additional actions. Visit the <a href="@actions">Actions page</a> to configure actions.', array('@trigger-help' => url('admin/help/trigger'), '@actions' => url('admin/config/system/actions'))) . '</dd>';
+ $output .= '</dl>';
+ return $output;
+ case 'admin/index':
+ return '<p>' . t('This page shows you all available administration tasks for each module.') . '</p>';
+ case 'admin/appearance':
+ $output = '<p>' . t('Set and configure the default theme for your website. Alternative <a href="@themes">themes</a> are available.', array('@themes' => 'http://drupal.org/project/themes')) . '</p>';
+ return $output;
+ case 'admin/appearance/settings/' . $arg[3]:
+ $theme_list = list_themes();
+ $theme = $theme_list[$arg[3]];
+ return '<p>' . t('These options control the display settings for the %name theme. When your site is displayed using this theme, these settings will be used.', array('%name' => $theme->info['name'])) . '</p>';
+ case 'admin/appearance/settings':
+ return '<p>' . t('These options control the default display settings for your entire site, across all themes. Unless they have been overridden by a specific theme, these settings will be used.') . '</p>';
+ case 'admin/modules':
+ $output = '<p>' . t('Download additional <a href="@modules">contributed modules</a> to extend Drupal\'s functionality.', array('@modules' => 'http://drupal.org/project/modules')) . '</p>';
+ if (module_exists('update')) {
+ if (update_manager_access()) {
+ $output .= '<p>' . t('Regularly review and install <a href="@updates">available updates</a> to maintain a secure and current site. Always run the <a href="@update-php">update script</a> each time a module is updated.', array('@update-php' => $base_url . '/update.php', '@updates' => url('admin/reports/updates'))) . '</p>';
+ }
+ else {
+ $output .= '<p>' . t('Regularly review <a href="@updates">available updates</a> to maintain a secure and current site. Always run the <a href="@update-php">update script</a> each time a module is updated.', array('@update-php' => $base_url . '/update.php', '@updates' => url('admin/reports/updates'))) . '</p>';
+ }
+ }
+ else {
+ $output .= '<p>' . t('Regularly review available updates to maintain a secure and current site. Always run the <a href="@update-php">update script</a> each time a module is updated. Enable the Update manager module to update and install modules and themes.', array('@update-php' => $base_url . '/update.php')) . '</p>';
+ }
+ return $output;
+ case 'admin/modules/uninstall':
+ return '<p>' . t('The uninstall process removes all data related to a module. To uninstall a module, you must first disable it on the main <a href="@modules">Modules page</a>.', array('@modules' => url('admin/modules'))) . '</p>';
+ case 'admin/structure/block/manage':
+ if ($arg[4] == 'system' && $arg[5] == 'powered-by') {
+ return '<p>' . t('The <em>Powered by Drupal</em> block is an optional link to the home page of the Drupal project. While there is absolutely no requirement that sites feature this link, it may be used to show support for Drupal.') . '</p>';
+ }
+ break;
+ case 'admin/config/development/maintenance':
+ global $user;
+ if ($user->uid == 1) {
+ return '<p>' . t('Use maintenance mode when making major updates, particularly if the updates could disrupt visitors or the update process. Examples include upgrading, importing or exporting content, modifying a theme, modifying content types, and making backups.') . '</p>';
+ }
+ break;
+ case 'admin/config/system/actions':
+ case 'admin/config/system/actions/manage':
+ $output = '';
+ $output .= '<p>' . t('There are two types of actions: simple and advanced. Simple actions do not require any additional configuration and are listed here automatically. Advanced actions need to be created and configured before they can be used because they have options that need to be specified; for example, sending an e-mail to a specified address or unpublishing content containing certain words. To create an advanced action, select the action from the drop-down list in the advanced action section below and click the <em>Create</em> button.') . '</p>';
+ if (module_exists('trigger')) {
+ $output .= '<p>' . t('You may proceed to the <a href="@url">Triggers</a> page to assign these actions to system events.', array('@url' => url('admin/structure/trigger'))) . '</p>';
+ }
+ return $output;
+ case 'admin/config/system/actions/configure':
+ return t('An advanced action offers additional configuration options which may be filled out below. Changing the <em>Description</em> field is recommended in order to better identify the precise action taking place. This description will be displayed in modules such as the Trigger module when assigning actions to system events, so it is best if it is as descriptive as possible (for example, "Send e-mail to Moderation Team" rather than simply "Send e-mail").');
+ case 'admin/config/people/ip-blocking':
+ return '<p>' . t('IP addresses listed here are blocked from your site. Blocked addresses are completely forbidden from accessing the site and instead see a brief message explaining the situation.') . '</p>';
+ case 'admin/reports/status':
+ return '<p>' . t("Here you can find a short overview of your site's parameters as well as any problems detected with your installation. It may be useful to copy and paste this information into support requests filed on drupal.org's support forums and project issue queues.") . '</p>';
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function system_theme() {
+ return array_merge(drupal_common_theme(), array(
+ 'system_themes_page' => array(
+ 'variables' => array('theme_groups' => NULL),
+ 'file' => 'system.admin.inc',
+ ),
+ 'system_settings_form' => array(
+ 'render element' => 'form',
+ ),
+ 'confirm_form' => array(
+ 'render element' => 'form',
+ ),
+ 'system_modules_fieldset' => array(
+ 'render element' => 'form',
+ 'file' => 'system.admin.inc',
+ ),
+ 'system_modules_incompatible' => array(
+ 'variables' => array('message' => NULL),
+ 'file' => 'system.admin.inc',
+ ),
+ 'system_modules_uninstall' => array(
+ 'render element' => 'form',
+ 'file' => 'system.admin.inc',
+ ),
+ 'status_report' => array(
+ 'render element' => 'requirements',
+ 'file' => 'system.admin.inc',
+ ),
+ 'admin_page' => array(
+ 'variables' => array('blocks' => NULL),
+ 'file' => 'system.admin.inc',
+ ),
+ 'admin_block' => array(
+ 'variables' => array('block' => NULL),
+ 'file' => 'system.admin.inc',
+ ),
+ 'admin_block_content' => array(
+ 'variables' => array('content' => NULL),
+ 'file' => 'system.admin.inc',
+ ),
+ 'system_admin_index' => array(
+ 'variables' => array('menu_items' => NULL),
+ 'file' => 'system.admin.inc',
+ ),
+ 'system_powered_by' => array(
+ 'variables' => array(),
+ ),
+ 'system_compact_link' => array(
+ 'variables' => array(),
+ ),
+ 'system_date_time_settings' => array(
+ 'render element' => 'form',
+ 'file' => 'system.admin.inc',
+ ),
+ ));
+}
+
+/**
+ * Implements hook_permission().
+ */
+function system_permission() {
+ return array(
+ 'administer modules' => array(
+ 'title' => t('Administer modules'),
+ ),
+ 'administer site configuration' => array(
+ 'title' => t('Administer site configuration'),
+ 'restrict access' => TRUE,
+ ),
+ 'administer themes' => array(
+ 'title' => t('Administer themes'),
+ ),
+ 'administer software updates' => array(
+ 'title' => t('Administer software updates'),
+ 'restrict access' => TRUE,
+ ),
+ 'administer actions' => array(
+ 'title' => t('Administer actions'),
+ ),
+ 'access administration pages' => array(
+ 'title' => t('Use the administration pages and help'),
+ ),
+ 'access site in maintenance mode' => array(
+ 'title' => t('Use the site in maintenance mode'),
+ ),
+ 'view the administration theme' => array(
+ 'title' => t('View the administration theme'),
+ 'description' => variable_get('admin_theme') ? '' : t('This is only used when the site is configured to use a separate administration theme on the <a href="@appearance-url">Appearance</a> page.', array('@appearance-url' => url('admin/appearance'))),
+ ),
+ 'access site reports' => array(
+ 'title' => t('View site reports'),
+ ),
+ 'block IP addresses' => array(
+ 'title' => t('Block IP addresses'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function system_hook_info() {
+ $hooks['token_info'] = array(
+ 'group' => 'tokens',
+ );
+ $hooks['token_info_alter'] = array(
+ 'group' => 'tokens',
+ );
+ $hooks['tokens'] = array(
+ 'group' => 'tokens',
+ );
+ $hooks['tokens_alter'] = array(
+ 'group' => 'tokens',
+ );
+
+ return $hooks;
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function system_entity_info() {
+ return array(
+ 'file' => array(
+ 'label' => t('File'),
+ 'base table' => 'file_managed',
+ 'entity keys' => array(
+ 'id' => 'fid',
+ 'label' => 'filename',
+ ),
+ 'static cache' => FALSE,
+ ),
+ );
+}
+
+/**
+ * Implements hook_element_info().
+ */
+function system_element_info() {
+ // Top level elements.
+ $types['form'] = array(
+ '#method' => 'post',
+ '#action' => request_uri(),
+ '#theme_wrappers' => array('form'),
+ );
+ $types['page'] = array(
+ '#show_messages' => TRUE,
+ '#theme' => 'page',
+ '#theme_wrappers' => array('html'),
+ );
+ // By default, we don't want Ajax commands being rendered in the context of an
+ // HTML page, so we don't provide defaults for #theme or #theme_wrappers.
+ // However, modules can set these properties (for example, to provide an HTML
+ // debugging page that displays rather than executes Ajax commands).
+ $types['ajax'] = array(
+ '#header' => TRUE,
+ '#commands' => array(),
+ '#error' => NULL,
+ );
+ $types['html_tag'] = array(
+ '#theme' => 'html_tag',
+ '#pre_render' => array('drupal_pre_render_conditional_comments'),
+ '#attributes' => array(),
+ '#value' => NULL,
+ );
+ $types['styles'] = array(
+ '#items' => array(),
+ '#pre_render' => array('drupal_pre_render_styles'),
+ '#group_callback' => 'drupal_group_css',
+ '#aggregate_callback' => 'drupal_aggregate_css',
+ );
+
+ // Input elements.
+ $types['submit'] = array(
+ '#input' => TRUE,
+ '#name' => 'op',
+ '#button_type' => 'submit',
+ '#executes_submit_callback' => TRUE,
+ '#limit_validation_errors' => FALSE,
+ '#process' => array('ajax_process_form'),
+ '#theme_wrappers' => array('button'),
+ );
+ $types['button'] = array(
+ '#input' => TRUE,
+ '#name' => 'op',
+ '#button_type' => 'submit',
+ '#executes_submit_callback' => FALSE,
+ '#limit_validation_errors' => FALSE,
+ '#process' => array('ajax_process_form'),
+ '#theme_wrappers' => array('button'),
+ );
+ $types['image_button'] = array(
+ '#input' => TRUE,
+ '#button_type' => 'submit',
+ '#executes_submit_callback' => TRUE,
+ '#limit_validation_errors' => FALSE,
+ '#process' => array('ajax_process_form'),
+ '#return_value' => TRUE,
+ '#has_garbage_value' => TRUE,
+ '#src' => NULL,
+ '#theme_wrappers' => array('image_button'),
+ );
+ $types['textfield'] = array(
+ '#input' => TRUE,
+ '#size' => 60,
+ '#maxlength' => 128,
+ '#autocomplete_path' => FALSE,
+ '#process' => array('ajax_process_form'),
+ '#theme' => 'textfield',
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['machine_name'] = array(
+ '#input' => TRUE,
+ '#default_value' => NULL,
+ '#required' => TRUE,
+ '#maxlength' => 64,
+ '#size' => 60,
+ '#autocomplete_path' => FALSE,
+ '#process' => array('form_process_machine_name', 'ajax_process_form'),
+ '#element_validate' => array('form_validate_machine_name'),
+ '#theme' => 'textfield',
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['password'] = array(
+ '#input' => TRUE,
+ '#size' => 60,
+ '#maxlength' => 128,
+ '#process' => array('ajax_process_form'),
+ '#theme' => 'password',
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['password_confirm'] = array(
+ '#input' => TRUE,
+ '#process' => array('form_process_password_confirm', 'user_form_process_password_confirm'),
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['textarea'] = array(
+ '#input' => TRUE,
+ '#cols' => 60,
+ '#rows' => 5,
+ '#resizable' => TRUE,
+ '#process' => array('ajax_process_form'),
+ '#theme' => 'textarea',
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['radios'] = array(
+ '#input' => TRUE,
+ '#process' => array('form_process_radios'),
+ '#theme_wrappers' => array('radios'),
+ '#pre_render' => array('form_pre_render_conditional_form_element'),
+ );
+ $types['radio'] = array(
+ '#input' => TRUE,
+ '#default_value' => NULL,
+ '#process' => array('ajax_process_form'),
+ '#theme' => 'radio',
+ '#theme_wrappers' => array('form_element'),
+ '#title_display' => 'after',
+ );
+ $types['checkboxes'] = array(
+ '#input' => TRUE,
+ '#process' => array('form_process_checkboxes'),
+ '#theme_wrappers' => array('checkboxes'),
+ '#pre_render' => array('form_pre_render_conditional_form_element'),
+ );
+ $types['checkbox'] = array(
+ '#input' => TRUE,
+ '#return_value' => 1,
+ '#theme' => 'checkbox',
+ '#process' => array('form_process_checkbox', 'ajax_process_form'),
+ '#theme_wrappers' => array('form_element'),
+ '#title_display' => 'after',
+ );
+ $types['select'] = array(
+ '#input' => TRUE,
+ '#multiple' => FALSE,
+ '#process' => array('form_process_select', 'ajax_process_form'),
+ '#theme' => 'select',
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['weight'] = array(
+ '#input' => TRUE,
+ '#delta' => 10,
+ '#default_value' => 0,
+ '#process' => array('form_process_weight', 'ajax_process_form'),
+ );
+ $types['date'] = array(
+ '#input' => TRUE,
+ '#element_validate' => array('date_validate'),
+ '#process' => array('form_process_date'),
+ '#theme' => 'date',
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['file'] = array(
+ '#input' => TRUE,
+ '#size' => 60,
+ '#theme' => 'file',
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['tableselect'] = array(
+ '#input' => TRUE,
+ '#js_select' => TRUE,
+ '#multiple' => TRUE,
+ '#process' => array('form_process_tableselect'),
+ '#options' => array(),
+ '#empty' => '',
+ '#theme' => 'tableselect',
+ );
+
+ // Form structure.
+ $types['item'] = array(
+ '#markup' => '',
+ '#pre_render' => array('drupal_pre_render_markup'),
+ '#theme_wrappers' => array('form_element'),
+ );
+ $types['hidden'] = array(
+ '#input' => TRUE,
+ '#process' => array('ajax_process_form'),
+ '#theme' => 'hidden',
+ );
+ $types['value'] = array(
+ '#input' => TRUE,
+ );
+ $types['markup'] = array(
+ '#markup' => '',
+ '#pre_render' => array('drupal_pre_render_markup'),
+ );
+ $types['link'] = array(
+ '#pre_render' => array('drupal_pre_render_link', 'drupal_pre_render_markup'),
+ );
+ $types['fieldset'] = array(
+ '#collapsible' => FALSE,
+ '#collapsed' => FALSE,
+ '#value' => NULL,
+ '#process' => array('form_process_fieldset', 'ajax_process_form'),
+ '#pre_render' => array('form_pre_render_fieldset'),
+ '#theme_wrappers' => array('fieldset'),
+ );
+ $types['vertical_tabs'] = array(
+ '#theme_wrappers' => array('vertical_tabs'),
+ '#default_tab' => '',
+ '#process' => array('form_process_vertical_tabs'),
+ );
+
+ $types['container'] = array(
+ '#theme_wrappers' => array('container'),
+ '#process' => array('form_process_container'),
+ );
+ $types['actions'] = array(
+ '#theme_wrappers' => array('container'),
+ '#process' => array('form_process_actions', 'form_process_container'),
+ '#weight' => 100,
+ );
+
+ $types['token'] = array(
+ '#input' => TRUE,
+ '#theme' => 'hidden',
+ );
+
+ return $types;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function system_menu() {
+ $items['system/files'] = array(
+ 'title' => 'File download',
+ 'page callback' => 'file_download',
+ 'page arguments' => array('private'),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['system/temporary'] = array(
+ 'title' => 'Temporary files',
+ 'page callback' => 'file_download',
+ 'page arguments' => array('temporary'),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+ $items['system/ajax'] = array(
+ 'title' => 'AHAH callback',
+ 'page callback' => 'ajax_form_callback',
+ 'delivery callback' => 'ajax_deliver',
+ 'access callback' => TRUE,
+ 'theme callback' => 'ajax_base_page_theme',
+ 'type' => MENU_CALLBACK,
+ 'file path' => 'includes',
+ 'file' => 'form.inc',
+ );
+ $items['system/timezone'] = array(
+ 'title' => 'Time zone',
+ 'page callback' => 'system_timezone',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin'] = array(
+ 'title' => 'Administration',
+ 'access arguments' => array('access administration pages'),
+ 'page callback' => 'system_admin_menu_block_page',
+ 'weight' => 9,
+ 'menu_name' => 'management',
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/compact'] = array(
+ 'title' => 'Compact mode',
+ 'page callback' => 'system_admin_compact_page',
+ 'access arguments' => array('access administration pages'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/tasks'] = array(
+ 'title' => 'Tasks',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -20,
+ );
+ $items['admin/index'] = array(
+ 'title' => 'Index',
+ 'page callback' => 'system_admin_index',
+ 'access arguments' => array('access administration pages'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => -18,
+ 'file' => 'system.admin.inc',
+ );
+
+ // Menu items that are basically just menu blocks.
+ $items['admin/structure'] = array(
+ 'title' => 'Structure',
+ 'description' => 'Administer blocks, content types, menus, etc.',
+ 'position' => 'right',
+ 'weight' => -8,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ // Appearance.
+ $items['admin/appearance'] = array(
+ 'title' => 'Appearance',
+ 'description' => 'Select and configure your themes.',
+ 'page callback' => 'system_themes_page',
+ 'access arguments' => array('administer themes'),
+ 'position' => 'left',
+ 'weight' => -6,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/appearance/list'] = array(
+ 'title' => 'List',
+ 'description' => 'Select and configure your theme',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -1,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/appearance/enable'] = array(
+ 'title' => 'Enable theme',
+ 'page callback' => 'system_theme_enable',
+ 'access arguments' => array('administer themes'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/appearance/disable'] = array(
+ 'title' => 'Disable theme',
+ 'page callback' => 'system_theme_disable',
+ 'access arguments' => array('administer themes'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/appearance/default'] = array(
+ 'title' => 'Set default theme',
+ 'page callback' => 'system_theme_default',
+ 'access arguments' => array('administer themes'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/appearance/settings'] = array(
+ 'title' => 'Settings',
+ 'description' => 'Configure default and theme specific settings.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_theme_settings'),
+ 'access arguments' => array('administer themes'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'system.admin.inc',
+ 'weight' => 20,
+ );
+ // Theme configuration subtabs.
+ $items['admin/appearance/settings/global'] = array(
+ 'title' => 'Global settings',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -1,
+ );
+
+ foreach (list_themes() as $key => $theme) {
+ $items['admin/appearance/settings/' . $theme->name] = array(
+ 'title' => $theme->info['name'],
+ 'page arguments' => array('system_theme_settings', $theme->name),
+ 'type' => MENU_LOCAL_TASK,
+ 'access callback' => '_system_themes_access',
+ 'access arguments' => array($key),
+ 'file' => 'system.admin.inc',
+ );
+ }
+
+ // Modules.
+ $items['admin/modules'] = array(
+ 'title' => 'Modules',
+ 'description' => 'Extend site functionality.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_modules'),
+ 'access arguments' => array('administer modules'),
+ 'file' => 'system.admin.inc',
+ 'weight' => -2,
+ );
+ $items['admin/modules/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['admin/modules/list/confirm'] = array(
+ 'title' => 'List',
+ 'access arguments' => array('administer modules'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ );
+ $items['admin/modules/uninstall'] = array(
+ 'title' => 'Uninstall',
+ 'page arguments' => array('system_modules_uninstall'),
+ 'access arguments' => array('administer modules'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'system.admin.inc',
+ 'weight' => 20,
+ );
+ $items['admin/modules/uninstall/confirm'] = array(
+ 'title' => 'Uninstall',
+ 'access arguments' => array('administer modules'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ 'file' => 'system.admin.inc',
+ );
+
+ // Configuration.
+ $items['admin/config'] = array(
+ 'title' => 'Configuration',
+ 'description' => 'Administer settings.',
+ 'page callback' => 'system_admin_config_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+
+ // IP address blocking.
+ $items['admin/config/people/ip-blocking'] = array(
+ 'title' => 'IP address blocking',
+ 'description' => 'Manage blocked IP addresses.',
+ 'page callback' => 'system_ip_blocking',
+ 'access arguments' => array('block IP addresses'),
+ 'file' => 'system.admin.inc',
+ 'weight' => 10,
+ );
+ $items['admin/config/people/ip-blocking/delete/%blocked_ip'] = array(
+ 'title' => 'Delete IP address',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_ip_blocking_delete', 5),
+ 'access arguments' => array('block IP addresses'),
+ 'file' => 'system.admin.inc',
+ );
+
+ // Media settings.
+ $items['admin/config/media'] = array(
+ 'title' => 'Media',
+ 'description' => 'Media tools.',
+ 'position' => 'left',
+ 'weight' => -10,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/media/file-system'] = array(
+ 'title' => 'File system',
+ 'description' => 'Tell Drupal where to store uploaded files and how they are accessed.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_file_system_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'weight' => -10,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/media/image-toolkit'] = array(
+ 'title' => 'Image toolkit',
+ 'description' => 'Choose which image toolkit to use if you have installed optional toolkits.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_image_toolkit_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'weight' => 20,
+ 'file' => 'system.admin.inc',
+ );
+
+ // Service settings.
+ $items['admin/config/services'] = array(
+ 'title' => 'Web services',
+ 'description' => 'Tools related to web services.',
+ 'position' => 'right',
+ 'weight' => 0,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/services/rss-publishing'] = array(
+ 'title' => 'RSS publishing',
+ 'description' => 'Configure the site description, the number of items per feed and whether feeds should be titles/teasers/full-text.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_rss_feeds_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ );
+
+ // Development settings.
+ $items['admin/config/development'] = array(
+ 'title' => 'Development',
+ 'description' => 'Development tools.',
+ 'position' => 'right',
+ 'weight' => -10,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/development/maintenance'] = array(
+ 'title' => 'Maintenance mode',
+ 'description' => 'Take the site offline for maintenance or bring it back online.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_site_maintenance_mode'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ 'weight' => -10,
+ );
+ $items['admin/config/development/performance'] = array(
+ 'title' => 'Performance',
+ 'description' => 'Enable or disable page caching for anonymous users and set CSS and JS bandwidth optimization options.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_performance_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ 'weight' => -20,
+ );
+ $items['admin/config/development/logging'] = array(
+ 'title' => 'Logging and errors',
+ 'description' => "Settings for logging and alerts modules. Various modules can route Drupal's system events to different destinations, such as syslog, database, email, etc.",
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_logging_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ 'weight' => -15,
+ );
+
+ // Regional and date settings.
+ $items['admin/config/regional'] = array(
+ 'title' => 'Regional and language',
+ 'description' => 'Regional settings, localization and translation.',
+ 'position' => 'left',
+ 'weight' => -5,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/settings'] = array(
+ 'title' => 'Regional settings',
+ 'description' => "Settings for the site's default time zone and country.",
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_regional_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'weight' => -20,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time'] = array(
+ 'title' => 'Date and time',
+ 'description' => 'Configure display formats for date and time.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_date_time_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'weight' => -15,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/types'] = array(
+ 'title' => 'Types',
+ 'description' => 'Configure display formats for date and time.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_date_time_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/types/add'] = array(
+ 'title' => 'Add date type',
+ 'description' => 'Add new date type.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_add_date_format_type_form'),
+ 'access arguments' => array('administer site configuration'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'weight' => -10,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/types/%/delete'] = array(
+ 'title' => 'Delete date type',
+ 'description' => 'Allow users to delete a configured date type.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_delete_date_format_type_form', 5),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/formats'] = array(
+ 'title' => 'Formats',
+ 'description' => 'Configure display format strings for date and time.',
+ 'page callback' => 'system_date_time_formats',
+ 'access arguments' => array('administer site configuration'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => -9,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/formats/add'] = array(
+ 'title' => 'Add format',
+ 'description' => 'Allow users to add additional date formats.',
+ 'type' => MENU_LOCAL_ACTION,
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_configure_date_formats_form'),
+ 'access arguments' => array('administer site configuration'),
+ 'weight' => -10,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/formats/%/edit'] = array(
+ 'title' => 'Edit date format',
+ 'description' => 'Allow users to edit a configured date format.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_configure_date_formats_form', 5),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/formats/%/delete'] = array(
+ 'title' => 'Delete date format',
+ 'description' => 'Allow users to delete a configured date format.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_date_delete_format_form', 5),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/regional/date-time/formats/lookup'] = array(
+ 'title' => 'Date and time lookup',
+ 'page callback' => 'system_date_time_lookup',
+ 'access arguments' => array('administer site configuration'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+
+ // Search settings.
+ $items['admin/config/search'] = array(
+ 'title' => 'Search and metadata',
+ 'description' => 'Local site search, metadata and SEO.',
+ 'position' => 'left',
+ 'weight' => -10,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/search/clean-urls'] = array(
+ 'title' => 'Clean URLs',
+ 'description' => 'Enable or disable clean URLs for your site.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_clean_url_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ 'weight' => 5,
+ );
+ $items['admin/config/search/clean-urls/check'] = array(
+ 'title' => 'Clean URL check',
+ 'page callback' => 'drupal_json_output',
+ 'page arguments' => array(array('status' => TRUE)),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+
+ // System settings.
+ $items['admin/config/system'] = array(
+ 'title' => 'System',
+ 'description' => 'General system related configuration.',
+ 'position' => 'right',
+ 'weight' => -20,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/system/actions'] = array(
+ 'title' => 'Actions',
+ 'description' => 'Manage the actions defined for your site.',
+ 'access arguments' => array('administer actions'),
+ 'page callback' => 'system_actions_manage',
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/system/actions/manage'] = array(
+ 'title' => 'Manage actions',
+ 'description' => 'Manage the actions defined for your site.',
+ 'page callback' => 'system_actions_manage',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -2,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/system/actions/configure'] = array(
+ 'title' => 'Configure an advanced action',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_actions_configure'),
+ 'access arguments' => array('administer actions'),
+ 'type' => MENU_VISIBLE_IN_BREADCRUMB,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/system/actions/delete/%actions'] = array(
+ 'title' => 'Delete action',
+ 'description' => 'Delete an action.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_actions_delete_form', 5),
+ 'access arguments' => array('administer actions'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/system/actions/orphan'] = array(
+ 'title' => 'Remove orphans',
+ 'page callback' => 'system_actions_remove_orphans',
+ 'access arguments' => array('administer actions'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/system/site-information'] = array(
+ 'title' => 'Site information',
+ 'description' => t('Change site name, e-mail address, slogan, default front page, and number of posts per page, error pages.'),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_site_information_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ 'weight' => -20,
+ );
+ $items['admin/config/system/cron'] = array(
+ 'title' => t('Cron'),
+ 'description' => t('Manage automatic site maintenance tasks.'),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('system_cron_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ 'weight' => 20,
+ );
+ // Additional categories
+ $items['admin/config/user-interface'] = array(
+ 'title' => 'User interface',
+ 'description' => 'Tools that enhance the user interface.',
+ 'position' => 'right',
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ 'weight' => -15,
+ );
+ $items['admin/config/workflow'] = array(
+ 'title' => 'Workflow',
+ 'description' => 'Content workflow, editorial workflow tools.',
+ 'position' => 'right',
+ 'weight' => 5,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/config/content'] = array(
+ 'title' => 'Content authoring',
+ 'description' => 'Settings related to formatting and authoring content.',
+ 'position' => 'left',
+ 'weight' => -15,
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'file' => 'system.admin.inc',
+ );
+
+ // Reports.
+ $items['admin/reports'] = array(
+ 'title' => 'Reports',
+ 'description' => 'View reports, updates, and errors.',
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access site reports'),
+ 'weight' => 5,
+ 'position' => 'left',
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/reports/status'] = array(
+ 'title' => 'Status report',
+ 'description' => "Get a status report about your site's operation and any detected problems.",
+ 'page callback' => 'system_status',
+ 'weight' => -60,
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/reports/status/run-cron'] = array(
+ 'title' => 'Run cron',
+ 'page callback' => 'system_run_cron',
+ 'access arguments' => array('administer site configuration'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ $items['admin/reports/status/php'] = array(
+ 'title' => 'PHP',
+ 'page callback' => 'system_php',
+ 'access arguments' => array('administer site configuration'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+
+ // Default page for batch operations.
+ $items['batch'] = array(
+ 'page callback' => 'system_batch_page',
+ 'access callback' => TRUE,
+ 'theme callback' => '_system_batch_theme',
+ 'type' => MENU_CALLBACK,
+ 'file' => 'system.admin.inc',
+ );
+ return $items;
+}
+
+/**
+ * Theme callback for the default batch page.
+ */
+function _system_batch_theme() {
+ // Retrieve the current state of the batch.
+ $batch = &batch_get();
+ if (!$batch && isset($_REQUEST['id'])) {
+ require_once DRUPAL_ROOT . '/includes/batch.inc';
+ $batch = batch_load($_REQUEST['id']);
+ }
+ // Use the same theme as the page that started the batch.
+ if (!empty($batch['theme'])) {
+ return $batch['theme'];
+ }
+}
+
+/**
+ * Implements hook_library_info().
+ */
+function system_library_info() {
+ // Drupal's Ajax framework.
+ $libraries['drupal.ajax'] = array(
+ 'title' => 'Drupal AJAX',
+ 'website' => 'http://api.drupal.org/api/drupal/includes--ajax.inc/group/ajax/7',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/ajax.js' => array('group' => JS_LIBRARY, 'weight' => 2),
+ ),
+ 'dependencies' => array(
+ array('system', 'drupal.progress'),
+ ),
+ );
+
+ // Drupal's batch API.
+ $libraries['drupal.batch'] = array(
+ 'title' => 'Drupal batch API',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/batch.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
+ ),
+ 'dependencies' => array(
+ array('system', 'drupal.progress'),
+ ),
+ );
+
+ // Drupal's progress indicator.
+ $libraries['drupal.progress'] = array(
+ 'title' => 'Drupal progress indicator',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/progress.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
+ ),
+ );
+
+ // Drupal's form library.
+ $libraries['drupal.form'] = array(
+ 'title' => 'Drupal form library',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/form.js' => array('group' => JS_LIBRARY, 'weight' => 1),
+ ),
+ );
+
+ // Drupal's states library.
+ $libraries['drupal.states'] = array(
+ 'title' => 'Drupal states',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/states.js' => array('group' => JS_LIBRARY, 'weight' => 1),
+ ),
+ );
+
+ // Drupal's collapsible fieldset.
+ $libraries['drupal.collapse'] = array(
+ 'title' => 'Drupal collapsible fieldset',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/collapse.js' => array('group' => JS_DEFAULT),
+ ),
+ 'dependencies' => array(
+ // collapse.js relies on drupalGetSummary in form.js
+ array('system', 'drupal.form'),
+ ),
+ );
+
+ // Drupal's resizable textarea.
+ $libraries['drupal.textarea'] = array(
+ 'title' => 'Drupal resizable textarea',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/textarea.js' => array('group' => JS_DEFAULT),
+ ),
+ );
+
+ // Drupal's autocomplete widget.
+ $libraries['drupal.autocomplete'] = array(
+ 'title' => 'Drupal autocomplete',
+ 'version' => VERSION,
+ 'js' => array(
+ 'misc/autocomplete.js' => array('group' => JS_DEFAULT),
+ ),
+ );
+
+ // jQuery.
+ $libraries['jquery'] = array(
+ 'title' => 'jQuery',
+ 'website' => 'http://jquery.com',
+ 'version' => '1.4.4',
+ 'js' => array(
+ 'misc/jquery.js' => array('group' => JS_LIBRARY, 'weight' => -20),
+ ),
+ );
+
+ // jQuery Once.
+ $libraries['jquery.once'] = array(
+ 'title' => 'jQuery Once',
+ 'website' => 'http://plugins.jquery.com/project/once',
+ 'version' => '1.2',
+ 'js' => array(
+ 'misc/jquery.once.js' => array('group' => JS_LIBRARY, 'weight' => -19),
+ ),
+ );
+
+ // jQuery Form Plugin.
+ $libraries['jquery.form'] = array(
+ 'title' => 'jQuery Form Plugin',
+ 'website' => 'http://malsup.com/jquery/form/',
+ 'version' => '2.52',
+ 'js' => array(
+ 'misc/jquery.form.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'jquery.cookie'),
+ ),
+ );
+
+ // jQuery BBQ plugin.
+ $libraries['jquery.bbq'] = array(
+ 'title' => 'jQuery BBQ',
+ 'website' => 'http://benalman.com/projects/jquery-bbq-plugin/',
+ 'version' => '1.2.1',
+ 'js' => array(
+ 'misc/jquery.ba-bbq.js' => array(),
+ ),
+ );
+
+ // Vertical Tabs.
+ $libraries['drupal.vertical-tabs'] = array(
+ 'title' => 'Vertical Tabs',
+ 'website' => 'http://drupal.org/node/323112',
+ 'version' => '1.0',
+ 'js' => array(
+ 'misc/vertical-tabs.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/vertical-tabs.css' => array(),
+ ),
+ 'dependencies' => array(
+ // Vertical tabs relies on drupalGetSummary in form.js
+ array('system', 'drupal.form'),
+ ),
+ );
+
+ // Farbtastic.
+ $libraries['farbtastic'] = array(
+ 'title' => 'Farbtastic',
+ 'website' => 'http://code.google.com/p/farbtastic/',
+ 'version' => '1.2',
+ 'js' => array(
+ 'misc/farbtastic/farbtastic.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/farbtastic/farbtastic.css' => array(),
+ ),
+ );
+
+ // Cookie.
+ $libraries['jquery.cookie'] = array(
+ 'title' => 'Cookie',
+ 'website' => 'http://plugins.jquery.com/project/cookie',
+ 'version' => '1.0',
+ 'js' => array(
+ 'misc/jquery.cookie.js' => array(),
+ ),
+ );
+
+ // jQuery UI.
+ $libraries['ui'] = array(
+ 'title' => 'jQuery UI: Core',
+ 'website' => 'http://jqueryui.com',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.core.min.js' => array('group' => JS_LIBRARY, 'weight' => -11),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.core.css' => array(),
+ 'misc/ui/jquery.ui.theme.css' => array(),
+ ),
+ );
+ $libraries['ui.accordion'] = array(
+ 'title' => 'jQuery UI: Accordion',
+ 'website' => 'http://jqueryui.com/demos/accordion/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.accordion.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.accordion.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ ),
+ );
+ $libraries['ui.autocomplete'] = array(
+ 'title' => 'jQuery UI: Autocomplete',
+ 'website' => 'http://jqueryui.com/demos/autocomplete/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.autocomplete.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.autocomplete.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.position'),
+ ),
+ );
+ $libraries['ui.button'] = array(
+ 'title' => 'jQuery UI: Button',
+ 'website' => 'http://jqueryui.com/demos/button/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.button.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.button.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ ),
+ );
+ $libraries['ui.datepicker'] = array(
+ 'title' => 'jQuery UI: Date Picker',
+ 'website' => 'http://jqueryui.com/demos/datepicker/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.datepicker.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.datepicker.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui'),
+ ),
+ );
+ $libraries['ui.dialog'] = array(
+ 'title' => 'jQuery UI: Dialog',
+ 'website' => 'http://jqueryui.com/demos/dialog/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.dialog.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.dialog.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.button'),
+ array('system', 'ui.draggable'),
+ array('system', 'ui.mouse'),
+ array('system', 'ui.position'),
+ array('system', 'ui.resizable'),
+ ),
+ );
+ $libraries['ui.draggable'] = array(
+ 'title' => 'jQuery UI: Draggable',
+ 'website' => 'http://jqueryui.com/demos/draggable/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.draggable.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.mouse'),
+ ),
+ );
+ $libraries['ui.droppable'] = array(
+ 'title' => 'jQuery UI: Droppable',
+ 'website' => 'http://jqueryui.com/demos/droppable/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.droppable.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.mouse'),
+ array('system', 'ui.draggable'),
+ ),
+ );
+ $libraries['ui.mouse'] = array(
+ 'title' => 'jQuery UI: Mouse',
+ 'website' => 'http://docs.jquery.com/UI/Mouse',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.mouse.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ ),
+ );
+ $libraries['ui.position'] = array(
+ 'title' => 'jQuery UI: Position',
+ 'website' => 'http://jqueryui.com/demos/position/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.position.min.js' => array(),
+ ),
+ );
+ $libraries['ui.progressbar'] = array(
+ 'title' => 'jQuery UI: Progress Bar',
+ 'website' => 'http://jqueryui.com/demos/progressbar/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.progressbar.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.progressbar.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ ),
+ );
+ $libraries['ui.resizable'] = array(
+ 'title' => 'jQuery UI: Resizable',
+ 'website' => 'http://jqueryui.com/demos/resizable/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.resizable.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.resizable.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.mouse'),
+ ),
+ );
+ $libraries['ui.selectable'] = array(
+ 'title' => 'jQuery UI: Selectable',
+ 'website' => 'http://jqueryui.com/demos/selectable/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.selectable.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.selectable.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.mouse'),
+ ),
+ );
+ $libraries['ui.slider'] = array(
+ 'title' => 'jQuery UI: Slider',
+ 'website' => 'http://jqueryui.com/demos/slider/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.slider.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.slider.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.mouse'),
+ ),
+ );
+ $libraries['ui.sortable'] = array(
+ 'title' => 'jQuery UI: Sortable',
+ 'website' => 'http://jqueryui.com/demos/sortable/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.sortable.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ array('system', 'ui.mouse'),
+ ),
+ );
+ $libraries['ui.tabs'] = array(
+ 'title' => 'jQuery UI: Tabs',
+ 'website' => 'http://jqueryui.com/demos/tabs/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.tabs.min.js' => array(),
+ ),
+ 'css' => array(
+ 'misc/ui/jquery.ui.tabs.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.widget'),
+ ),
+ );
+ $libraries['ui.widget'] = array(
+ 'title' => 'jQuery UI: Widget',
+ 'website' => 'http://docs.jquery.com/UI/Widget',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.ui.widget.min.js' => array('group' => JS_LIBRARY, 'weight' => -10),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui'),
+ ),
+ );
+ $libraries['effects'] = array(
+ 'title' => 'jQuery UI: Effects',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.core.min.js' => array('group' => JS_LIBRARY, 'weight' => -9),
+ ),
+ );
+ $libraries['effects.blind'] = array(
+ 'title' => 'jQuery UI: Effects Blind',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.blind.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.bounce'] = array(
+ 'title' => 'jQuery UI: Effects Bounce',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.bounce.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.clip'] = array(
+ 'title' => 'jQuery UI: Effects Clip',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.clip.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.drop'] = array(
+ 'title' => 'jQuery UI: Effects Drop',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.drop.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.explode'] = array(
+ 'title' => 'jQuery UI: Effects Explode',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.explode.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.fade'] = array(
+ 'title' => 'jQuery UI: Effects Fade',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.fade.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.fold'] = array(
+ 'title' => 'jQuery UI: Effects Fold',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.fold.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.highlight'] = array(
+ 'title' => 'jQuery UI: Effects Highlight',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.highlight.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.pulsate'] = array(
+ 'title' => 'jQuery UI: Effects Pulsate',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.pulsate.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.scale'] = array(
+ 'title' => 'jQuery UI: Effects Scale',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.scale.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.shake'] = array(
+ 'title' => 'jQuery UI: Effects Shake',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.shake.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.slide'] = array(
+ 'title' => 'jQuery UI: Effects Slide',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.slide.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+ $libraries['effects.transfer'] = array(
+ 'title' => 'jQuery UI: Effects Transfer',
+ 'website' => 'http://jqueryui.com/demos/effect/',
+ 'version' => '1.8.7',
+ 'js' => array(
+ 'misc/ui/jquery.effects.transfer.min.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'effects'),
+ ),
+ );
+
+ // These library names are deprecated. Earlier versions of Drupal 7 didn't
+ // consistently namespace their libraries, so these names are included for
+ // backwards compatibility with those versions.
+ $libraries['once'] = &$libraries['jquery.once'];
+ $libraries['form'] = &$libraries['jquery.form'];
+ $libraries['jquery-bbq'] = &$libraries['jquery.bbq'];
+ $libraries['vertical-tabs'] = &$libraries['drupal.vertical-tabs'];
+ $libraries['cookie'] = &$libraries['jquery.cookie'];
+
+ return $libraries;
+}
+
+/**
+ * Implements hook_stream_wrappers().
+ */
+function system_stream_wrappers() {
+ $wrappers = array(
+ 'public' => array(
+ 'name' => t('Public files'),
+ 'class' => 'DrupalPublicStreamWrapper',
+ 'description' => t('Public local files served by the webserver.'),
+ 'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
+ ),
+ 'temporary' => array(
+ 'name' => t('Temporary files'),
+ 'class' => 'DrupalTemporaryStreamWrapper',
+ 'description' => t('Temporary local files for upload and previews.'),
+ 'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
+ ),
+ );
+
+ // Only register the private file stream wrapper if a file path has been set.
+ if (variable_get('file_private_path', FALSE)) {
+ $wrappers['private'] = array(
+ 'name' => t('Private files'),
+ 'class' => 'DrupalPrivateStreamWrapper',
+ 'description' => t('Private local files served by Drupal.'),
+ 'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
+ );
+ }
+
+ return $wrappers;
+}
+
+/**
+ * Retrieve a blocked IP address from the database.
+ *
+ * @param $iid integer
+ * The ID of the blocked IP address to retrieve.
+ *
+ * @return
+ * The blocked IP address from the database as an array.
+ */
+function blocked_ip_load($iid) {
+ return db_query("SELECT * FROM {blocked_ips} WHERE iid = :iid", array(':iid' => $iid))->fetchAssoc();
+}
+
+/**
+ * Menu item access callback - only admin or enabled themes can be accessed.
+ */
+function _system_themes_access($theme) {
+ return user_access('administer themes') && drupal_theme_access($theme);
+}
+
+/**
+ * @defgroup authorize Authorized operations
+ * @{
+ * Functions to run operations with elevated privileges via authorize.php.
+ *
+ * Because of the Update manager functionality included in Drupal core, there
+ * is a mechanism for running operations with elevated file system privileges,
+ * the top-level authorize.php script. This script runs at a reduced Drupal
+ * bootstrap level so that it is not reliant on the entire site being
+ * functional. The operations use a FileTransfer class to manipulate code
+ * installed on the system as the user that owns the files, not the user that
+ * the httpd is running as.
+ *
+ * The first setup is to define a callback function that should be authorized
+ * to run with the elevated privileges. This callback should take a
+ * FileTransfer as its first argument, although you can define an array of
+ * other arguments it should be invoked with. The callback should be placed in
+ * a separate .inc file that will be included by authorize.php.
+ *
+ * To run the operation, certain data must be saved into the SESSION, and then
+ * the flow of control should be redirected to the authorize.php script. There
+ * are two ways to do this, either to call system_authorized_run() directly,
+ * or to call system_authorized_init() and then redirect to authorize.php,
+ * using the URL from system_authorized_get_url(). Redirecting yourself is
+ * necessary when your authorized operation is being triggered by a form
+ * submit handler, since calling drupal_goto() in a submit handler is a bad
+ * idea, and you should instead set $form_state['redirect'].
+ *
+ * Once the SESSION is setup for the operation and the user is redirected to
+ * authorize.php, they will be prompted for their connection credentials (core
+ * provides FTP and SSH by default, although other connection classes can be
+ * added via contributed modules). With valid credentials, authorize.php will
+ * instantiate the appropriate FileTransfer object, and then invoke the
+ * desired operation passing in that object. The authorize.php script can act
+ * as a Batch API processing page, if the operation requires a batch.
+ *
+ * @see authorize.php
+ * @see FileTransfer
+ * @see hook_filetransfer_info()
+ */
+
+/**
+ * Setup a given callback to run via authorize.php with elevated privileges.
+ *
+ * To use authorize.php, certain variables must be stashed into $_SESSION.
+ * This function sets up all the necessary $_SESSION variables, then returns
+ * the full path to authorize.php so the caller can redirect to authorize.php.
+ * That initiates the workflow that will eventually lead to the callback being
+ * invoked. The callback will be invoked at a low bootstrap level, without all
+ * modules being invoked, so it needs to be careful not to assume any code
+ * exists.
+ *
+ * @param $callback
+ * The name of the function to invoke one the user authorizes the operation.
+ * @param $file
+ * The full path to the file where the callback function is implemented.
+ * @param $arguments
+ * Optional array of arguments to pass into the callback when it is invoked.
+ * Note that the first argument to the callback is always the FileTransfer
+ * object created by authorize.php when the user authorizes the operation.
+ * @param $page_title
+ * Optional string to use as the page title once redirected to authorize.php.
+ * @return
+ * Nothing, this function just initializes variables in the user's session.
+ */
+function system_authorized_init($callback, $file, $arguments = array(), $page_title = NULL) {
+ // First, figure out what file transfer backends the site supports, and put
+ // all of those in the SESSION so that authorize.php has access to all of
+ // them via the class autoloader, even without a full bootstrap.
+ $_SESSION['authorize_filetransfer_info'] = drupal_get_filetransfer_info();
+
+ // Now, define the callback to invoke.
+ $_SESSION['authorize_operation'] = array(
+ 'callback' => $callback,
+ 'file' => $file,
+ 'arguments' => $arguments,
+ );
+
+ if (isset($page_title)) {
+ $_SESSION['authorize_operation']['page_title'] = $page_title;
+ }
+}
+
+/**
+ * Return the URL for the authorize.php script.
+ *
+ * @param array $options
+ * Optional array of options to pass to url().
+ * @return
+ * The full URL to authorize.php, using https if available.
+ */
+function system_authorized_get_url(array $options = array()) {
+ global $base_url;
+ // Force https if available, regardless of what the caller specifies.
+ $options['https'] = TRUE;
+ // We prefix with $base_url so we get a full path even if clean URLs are
+ // disabled.
+ return url($base_url . '/authorize.php', $options);
+}
+
+/**
+ * Returns the URL for the authorize.php script when it is processing a batch.
+ */
+function system_authorized_batch_processing_url() {
+ return system_authorized_get_url(array('query' => array('batch' => '1')));
+}
+
+/**
+ * Setup and invoke an operation using authorize.php.
+ *
+ * @see system_authorized_init()
+ */
+function system_authorized_run($callback, $file, $arguments = array(), $page_title = NULL) {
+ system_authorized_init($callback, $file, $arguments, $page_title);
+ drupal_goto(system_authorized_get_url());
+}
+
+/**
+ * Use authorize.php to run batch_process().
+ *
+ * @see batch_process()
+ */
+function system_authorized_batch_process() {
+ $finish_url = system_authorized_get_url();
+ $process_url = system_authorized_batch_processing_url();
+ batch_process($finish_url, $process_url);
+}
+
+/**
+ * @} End of "defgroup authorize".
+ */
+
+/**
+ * Implements hook_updater_info().
+ */
+function system_updater_info() {
+ return array(
+ 'module' => array(
+ 'class' => 'ModuleUpdater',
+ 'name' => t('Update modules'),
+ 'weight' => 0,
+ ),
+ 'theme' => array(
+ 'class' => 'ThemeUpdater',
+ 'name' => t('Update themes'),
+ 'weight' => 0,
+ ),
+ );
+}
+
+/**
+ * Implements hook_filetransfer_info().
+ */
+function system_filetransfer_info() {
+ $backends = array();
+
+ // This is the default, will be available on most systems.
+ if (function_exists('ftp_connect')) {
+ $backends['ftp'] = array(
+ 'title' => t('FTP'),
+ 'class' => 'FileTransferFTP',
+ 'file' => 'ftp.inc',
+ 'file path' => 'includes/filetransfer',
+ 'weight' => 0,
+ );
+ }
+
+ // SSH2 lib connection is only available if the proper PHP extension is
+ // installed.
+ if (function_exists('ssh2_connect')) {
+ $backends['ssh'] = array(
+ 'title' => t('SSH'),
+ 'class' => 'FileTransferSSH',
+ 'file' => 'ssh.inc',
+ 'file path' => 'includes/filetransfer',
+ 'weight' => 20,
+ );
+ }
+ return $backends;
+}
+
+/**
+ * Implements hook_init().
+ */
+function system_init() {
+ $path = drupal_get_path('module', 'system');
+ // Add the CSS for this module. These aren't in system.info, because they
+ // need to be in the CSS_SYSTEM group rather than the CSS_DEFAULT group.
+ drupal_add_css($path . '/system.base.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
+ if (path_is_admin(current_path())) {
+ drupal_add_css($path . '/system.admin.css', array('group' => CSS_SYSTEM));
+ }
+ drupal_add_css($path . '/system.menus.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
+ drupal_add_css($path . '/system.messages.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
+ drupal_add_css($path . '/system.theme.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
+
+ // Ignore slave database servers for this request.
+ //
+ // In Drupal's distributed database structure, new data is written to the master
+ // and then propagated to the slave servers. This means there is a lag
+ // between when data is written to the master and when it is available on the slave.
+ // At these times, we will want to avoid using a slave server temporarily.
+ // For example, if a user posts a new node then we want to disable the slave
+ // server for that user temporarily to allow the slave server to catch up.
+ // That way, that user will see their changes immediately while for other
+ // users we still get the benefits of having a slave server, just with slightly
+ // stale data. Code that wants to disable the slave server should use the
+ // db_set_ignore_slave() function to set $_SESSION['ignore_slave_server'] to
+ // the timestamp after which the slave can be re-enabled.
+ if (isset($_SESSION['ignore_slave_server'])) {
+ if ($_SESSION['ignore_slave_server'] >= REQUEST_TIME) {
+ Database::ignoreTarget('default', 'slave');
+ }
+ else {
+ unset($_SESSION['ignore_slave_server']);
+ }
+ }
+
+ // Add CSS/JS files from module .info files.
+ system_add_module_assets();
+}
+
+/**
+ * Adds CSS and JavaScript files declared in module .info files.
+ */
+function system_add_module_assets() {
+ foreach (system_get_info('module') as $module => $info) {
+ if (!empty($info['stylesheets'])) {
+ foreach ($info['stylesheets'] as $media => $stylesheets) {
+ foreach ($stylesheets as $stylesheet) {
+ drupal_add_css($stylesheet, array('every_page' => TRUE, 'media' => $media));
+ }
+ }
+ }
+ if (!empty($info['scripts'])) {
+ foreach ($info['scripts'] as $script) {
+ drupal_add_js($script, array('every_page' => TRUE));
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_custom_theme().
+ */
+function system_custom_theme() {
+ if (user_access('view the administration theme') && path_is_admin(current_path())) {
+ return variable_get('admin_theme');
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function system_form_user_profile_form_alter(&$form, &$form_state) {
+ if ($form['#user_category'] == 'account') {
+ if (variable_get('configurable_timezones', 1)) {
+ system_user_timezone($form, $form_state);
+ }
+ return $form;
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function system_form_user_register_form_alter(&$form, &$form_state) {
+ if (variable_get('configurable_timezones', 1)) {
+ if (variable_get('user_default_timezone', DRUPAL_USER_TIMEZONE_DEFAULT) == DRUPAL_USER_TIMEZONE_SELECT) {
+ system_user_timezone($form, $form_state);
+ }
+ else {
+ $form['account']['timezone'] = array(
+ '#type' => 'hidden',
+ '#value' => variable_get('user_default_timezone', DRUPAL_USER_TIMEZONE_DEFAULT) ? '' : variable_get('date_default_timezone', ''),
+ );
+ }
+ return $form;
+ }
+}
+
+/**
+ * Implements hook_user_login().
+ */
+function system_user_login(&$edit, $account) {
+ // If the user has a NULL time zone, notify them to set a time zone.
+ if (!$account->timezone && variable_get('configurable_timezones', 1) && variable_get('empty_timezone_message', 0)) {
+ drupal_set_message(t('Configure your <a href="@user-edit">account time zone setting</a>.', array('@user-edit' => url("user/$account->uid/edit", array('query' => drupal_get_destination(), 'fragment' => 'edit-timezone')))));
+ }
+}
+
+/**
+ * Add the time zone field to the user edit and register forms.
+ */
+function system_user_timezone(&$form, &$form_state) {
+ global $user;
+
+ $account = $form['#user'];
+
+ $form['timezone'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Locale settings'),
+ '#weight' => 6,
+ '#collapsible' => TRUE,
+ );
+ $form['timezone']['timezone'] = array(
+ '#type' => 'select',
+ '#title' => t('Time zone'),
+ '#default_value' => isset($account->timezone) ? $account->timezone : ($account->uid == $user->uid ? variable_get('date_default_timezone', '') : ''),
+ '#options' => system_time_zones($account->uid != $user->uid),
+ '#description' => t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'),
+ );
+ if (!isset($account->timezone) && $account->uid == $user->uid && empty($form_state['input']['timezone'])) {
+ $form['timezone']['#description'] = t('Your time zone setting will be automatically detected if possible. Confirm the selection and click save.');
+ $form['timezone']['timezone']['#attributes'] = array('class' => array('timezone-detect'));
+ drupal_add_js('misc/timezone.js');
+ }
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function system_block_info() {
+ $blocks['main'] = array(
+ 'info' => t('Main page content'),
+ // Cached elsewhere.
+ 'cache' => DRUPAL_NO_CACHE,
+ );
+ $blocks['powered-by'] = array(
+ 'info' => t('Powered by Drupal'),
+ 'weight' => '10',
+ 'cache' => DRUPAL_NO_CACHE,
+ );
+ $blocks['help'] = array(
+ 'info' => t('System help'),
+ 'weight' => '5',
+ 'cache' => DRUPAL_NO_CACHE,
+ );
+ // System-defined menu blocks.
+ foreach (menu_list_system_menus() as $menu_name => $title) {
+ $blocks[$menu_name]['info'] = t($title);
+ // Menu blocks can't be cached because each menu item can have
+ // a custom access callback. menu.inc manages its own caching.
+ $blocks[$menu_name]['cache'] = DRUPAL_NO_CACHE;
+ }
+ return $blocks;
+}
+
+/**
+ * Implements hook_block_view().
+ *
+ * Generate a block with a promotional link to Drupal.org and
+ * all system menu blocks.
+ */
+function system_block_view($delta = '') {
+ $block = array();
+ switch ($delta) {
+ case 'main':
+ $block['subject'] = NULL;
+ $block['content'] = drupal_set_page_content();
+ return $block;
+ case 'powered-by':
+ $block['subject'] = NULL;
+ $block['content'] = theme('system_powered_by');
+ return $block;
+ case 'help':
+ $block['subject'] = NULL;
+ $block['content'] = menu_get_active_help();
+ return $block;
+ default:
+ // All system menu blocks.
+ $system_menus = menu_list_system_menus();
+ if (isset($system_menus[$delta])) {
+ $block['subject'] = t($system_menus[$delta]);
+ $block['content'] = menu_tree($delta);
+ return $block;
+ }
+ break;
+ }
+}
+
+/**
+ * Implements hook_preprocess_block().
+ */
+function system_preprocess_block(&$variables) {
+ // System menu blocks should get the same class as menu module blocks.
+ if ($variables['block']->module == 'system' && in_array($variables['block']->delta, array_keys(menu_list_system_menus()))) {
+ $variables['classes_array'][] = 'block-menu';
+ }
+}
+
+/**
+ * Provide a single block on the administration overview page.
+ *
+ * @param $item
+ * The menu item to be displayed.
+ */
+function system_admin_menu_block($item) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ // If we are calling this function for a menu item that corresponds to a
+ // local task (for example, admin/tasks), then we want to retrieve the
+ // parent item's child links, not this item's (since this item won't have
+ // any).
+ if ($item['tab_root'] != $item['path']) {
+ $item = menu_get_item($item['tab_root_href']);
+ }
+
+ if (!isset($item['mlid'])) {
+ $item += db_query("SELECT mlid, menu_name FROM {menu_links} ml WHERE ml.router_path = :path AND module = 'system'", array(':path' => $item['path']))->fetchAssoc();
+ }
+
+ if (isset($cache[$item['mlid']])) {
+ return $cache[$item['mlid']];
+ }
+
+ $content = array();
+ $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
+ $query->join('menu_router', 'm', 'm.path = ml.router_path');
+ $query
+ ->fields('ml')
+ // Weight should be taken from {menu_links}, not {menu_router}.
+ ->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')))
+ ->condition('ml.plid', $item['mlid'])
+ ->condition('ml.menu_name', $item['menu_name'])
+ ->condition('ml.hidden', 0);
+
+ foreach ($query->execute() as $link) {
+ _menu_link_translate($link);
+ if ($link['access']) {
+ // The link description, either derived from 'description' in
+ // hook_menu() or customized via menu module is used as title attribute.
+ if (!empty($link['localized_options']['attributes']['title'])) {
+ $link['description'] = $link['localized_options']['attributes']['title'];
+ unset($link['localized_options']['attributes']['title']);
+ }
+ // Prepare for sorting as in function _menu_tree_check_access().
+ // The weight is offset so it is always positive, with a uniform 5-digits.
+ $key = (50000 + $link['weight']) . ' ' . drupal_strtolower($link['title']) . ' ' . $link['mlid'];
+ $content[$key] = $link;
+ }
+ }
+ ksort($content);
+ $cache[$item['mlid']] = $content;
+ return $content;
+}
+
+/**
+ * Checks the existence of the directory specified in $form_element.
+ *
+ * This function is called from the system_settings form to check all core
+ * file directories (file_public_path, file_private_path, file_temporary_path).
+ *
+ * @param $form_element
+ * The form element containing the name of the directory to check.
+ */
+function system_check_directory($form_element) {
+ $directory = $form_element['#value'];
+ if (strlen($directory) == 0) {
+ return $form_element;
+ }
+
+ if (!is_dir($directory) && !drupal_mkdir($directory, NULL, TRUE)) {
+ // If the directory does not exists and cannot be created.
+ form_set_error($form_element['#parents'][0], t('The directory %directory does not exist and could not be created.', array('%directory' => $directory)));
+ watchdog('file system', 'The directory %directory does not exist and could not be created.', array('%directory' => $directory), WATCHDOG_ERROR);
+ }
+
+ if (is_dir($directory) && !is_writable($directory) && !drupal_chmod($directory)) {
+ // If the directory is not writable and cannot be made so.
+ form_set_error($form_element['#parents'][0], t('The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory)));
+ watchdog('file system', 'The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory), WATCHDOG_ERROR);
+ }
+ elseif (is_dir($directory)) {
+ if ($form_element['#name'] == 'file_public_path') {
+ // Create public .htaccess file.
+ file_save_htaccess($directory, FALSE);
+ }
+ else {
+ // Create private .htaccess file.
+ file_save_htaccess($directory);
+ }
+ }
+
+ return $form_element;
+}
+
+/**
+ * Retrieves the current status of an array of files in the system table.
+ *
+ * @param $files
+ * An array of files to check.
+ * @param $type
+ * The type of the files.
+ */
+function system_get_files_database(&$files, $type) {
+ // Extract current files from database.
+ $result = db_query("SELECT filename, name, type, status, schema_version, weight FROM {system} WHERE type = :type", array(':type' => $type));
+ foreach ($result as $file) {
+ if (isset($files[$file->name]) && is_object($files[$file->name])) {
+ $file->uri = $file->filename;
+ foreach ($file as $key => $value) {
+ if (!isset($files[$file->name]->$key)) {
+ $files[$file->name]->$key = $value;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Updates the records in the system table based on the files array.
+ *
+ * @param $files
+ * An array of files.
+ * @param $type
+ * The type of the files.
+ */
+function system_update_files_database(&$files, $type) {
+ $result = db_query("SELECT * FROM {system} WHERE type = :type", array(':type' => $type));
+
+ // Add all files that need to be deleted to a DatabaseCondition.
+ $delete = db_or();
+ foreach ($result as $file) {
+ if (isset($files[$file->name]) && is_object($files[$file->name])) {
+ // Keep the old filename from the database in case the file has moved.
+ $old_filename = $file->filename;
+
+ $updated_fields = array();
+
+ // Handle info specially, compare the serialized value.
+ $serialized_info = serialize($files[$file->name]->info);
+ if ($serialized_info != $file->info) {
+ $updated_fields['info'] = $serialized_info;
+ }
+ unset($file->info);
+
+ // Scan remaining fields to find only the updated values.
+ foreach ($file as $key => $value) {
+ if (isset($files[$file->name]->$key) && $files[$file->name]->$key != $value) {
+ $updated_fields[$key] = $files[$file->name]->$key;
+ }
+ }
+
+ // Update the record.
+ if (count($updated_fields)) {
+ db_update('system')
+ ->fields($updated_fields)
+ ->condition('filename', $old_filename)
+ ->execute();
+ }
+
+ // Indicate that the file exists already.
+ $files[$file->name]->exists = TRUE;
+ }
+ else {
+ // File is not found in file system, so delete record from the system table.
+ $delete->condition('filename', $file->filename);
+ }
+ }
+
+ if (count($delete) > 0) {
+ // Delete all missing files from the system table, but only if the plugin
+ // has never been installed.
+ db_delete('system')
+ ->condition($delete)
+ ->condition('schema_version', -1)
+ ->execute();
+ }
+
+ // All remaining files are not in the system table, so we need to add them.
+ $query = db_insert('system')->fields(array('filename', 'name', 'type', 'owner', 'info'));
+ foreach ($files as &$file) {
+ if (isset($file->exists)) {
+ unset($file->exists);
+ }
+ else {
+ $query->values(array(
+ 'filename' => $file->uri,
+ 'name' => $file->name,
+ 'type' => $type,
+ 'owner' => isset($file->owner) ? $file->owner : '',
+ 'info' => serialize($file->info),
+ ));
+ $file->type = $type;
+ $file->status = 0;
+ $file->schema_version = -1;
+ }
+ }
+ $query->execute();
+
+ // If any module or theme was moved to a new location, we need to reset the
+ // system_list() cache or we will continue to load the old copy, look for
+ // schema updates in the wrong place, etc.
+ system_list_reset();
+}
+
+/**
+ * Returns an array of information about enabled modules or themes.
+ *
+ * This function returns the information from the {system} table corresponding
+ * to the cached contents of the .info file for each active module or theme.
+ *
+ * @param $type
+ * Either 'module' or 'theme'.
+ * @param $name
+ * (optional) The name of a module or theme whose information shall be
+ * returned. If omitted, all records for the provided $type will be returned.
+ * If $name does not exist in the provided $type or is not enabled, an empty
+ * array will be returned.
+ *
+ * @return
+ * An associative array of module or theme information keyed by name, or only
+ * information for $name, if given. If no records are available, an empty
+ * array is returned.
+ *
+ * @see system_rebuild_module_data()
+ * @see system_rebuild_theme_data()
+ */
+function system_get_info($type, $name = NULL) {
+ $info = array();
+ if ($type == 'module') {
+ $type = 'module_enabled';
+ }
+ $list = system_list($type);
+ foreach ($list as $shortname => $item) {
+ if (!empty($item->status)) {
+ $info[$shortname] = $item->info;
+ }
+ }
+ if (isset($name)) {
+ return isset($info[$name]) ? $info[$name] : array();
+ }
+ return $info;
+}
+
+/**
+ * Helper function to scan and collect module .info data.
+ *
+ * @return
+ * An associative array of module information.
+ */
+function _system_rebuild_module_data() {
+ // Find modules
+ $modules = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0);
+
+ // Include the install profile in modules that are loaded.
+ $profile = drupal_get_profile();
+ $modules[$profile] = new stdClass();
+ $modules[$profile]->name = $profile;
+ $modules[$profile]->uri = 'profiles/' . $profile . '/' . $profile . '.profile';
+ $modules[$profile]->filename = $profile . '.profile';
+
+ // Install profile hooks are always executed last.
+ $modules[$profile]->weight = 1000;
+
+ // Set defaults for module info.
+ $defaults = array(
+ 'dependencies' => array(),
+ 'description' => '',
+ 'package' => 'Other',
+ 'version' => NULL,
+ 'php' => DRUPAL_MINIMUM_PHP,
+ 'files' => array(),
+ 'bootstrap' => 0,
+ );
+
+ // Read info files for each module.
+ foreach ($modules as $key => $module) {
+ // The module system uses the key 'filename' instead of 'uri' so copy the
+ // value so it will be used by the modules system.
+ $modules[$key]->filename = $module->uri;
+
+ // Look for the info file.
+ $module->info = drupal_parse_info_file(dirname($module->uri) . '/' . $module->name . '.info');
+
+ // Skip modules that don't provide info.
+ if (empty($module->info)) {
+ unset($modules[$key]);
+ continue;
+ }
+
+ // Merge in defaults and save.
+ $modules[$key]->info = $module->info + $defaults;
+
+ // Prefix stylesheets and scripts with module path.
+ $path = dirname($module->uri);
+ if (isset($module->info['stylesheets'])) {
+ $module->info['stylesheets'] = _system_info_add_path($module->info['stylesheets'], $path);
+ }
+ if (isset($module->info['scripts'])) {
+ $module->info['scripts'] = _system_info_add_path($module->info['scripts'], $path);
+ }
+
+ // Install profiles are hidden by default, unless explicitly specified
+ // otherwise in the .info file.
+ if ($key == $profile && !isset($modules[$key]->info['hidden'])) {
+ $modules[$key]->info['hidden'] = TRUE;
+ }
+
+ // Invoke hook_system_info_alter() to give installed modules a chance to
+ // modify the data in the .info files if necessary.
+ $type = 'module';
+ drupal_alter('system_info', $modules[$key]->info, $modules[$key], $type);
+ }
+
+ if (isset($modules[$profile])) {
+ // The install profile is required, if it's a valid module.
+ $modules[$profile]->info['required'] = TRUE;
+ // Add a default distribution name if the profile did not provide one. This
+ // matches the default value used in install_profile_info().
+ if (!isset($modules[$profile]->info['distribution_name'])) {
+ $modules[$profile]->info['distribution_name'] = 'Drupal';
+ }
+ }
+
+ return $modules;
+}
+
+/**
+ * Rebuild, save, and return data about all currently available modules.
+ *
+ * @return
+ * Array of all available modules and their data.
+ */
+function system_rebuild_module_data() {
+ $modules_cache = &drupal_static(__FUNCTION__);
+ // Only rebuild once per request. $modules and $modules_cache cannot be
+ // combined into one variable, because the $modules_cache variable is reset by
+ // reference from system_list_reset() during the rebuild.
+ if (!isset($modules_cache)) {
+ $modules = _system_rebuild_module_data();
+ ksort($modules);
+ system_get_files_database($modules, 'module');
+ system_update_files_database($modules, 'module');
+ $modules = _module_build_dependencies($modules);
+ $modules_cache = $modules;
+ }
+ return $modules_cache;
+}
+
+/**
+ * Refresh bootstrap column in the system table.
+ *
+ * This is called internally by module_enable/disable() to flag modules that
+ * implement hooks used during bootstrap, such as hook_boot(). These modules
+ * are loaded earlier to invoke the hooks.
+ */
+function _system_update_bootstrap_status() {
+ $bootstrap_modules = array();
+ foreach (bootstrap_hooks() as $hook) {
+ foreach (module_implements($hook) as $module) {
+ $bootstrap_modules[] = $module;
+ }
+ }
+ $query = db_update('system')->fields(array('bootstrap' => 0));
+ if ($bootstrap_modules) {
+ db_update('system')
+ ->fields(array('bootstrap' => 1))
+ ->condition('name', $bootstrap_modules, 'IN')
+ ->execute();
+ $query->condition('name', $bootstrap_modules, 'NOT IN');
+ }
+ $query->execute();
+ // Reset the cached list of bootstrap modules.
+ system_list_reset();
+}
+
+/**
+ * Helper function to scan and collect theme .info data and their engines.
+ *
+ * @return
+ * An associative array of themes information.
+ */
+function _system_rebuild_theme_data() {
+ // Find themes
+ $themes = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'themes');
+ // Find theme engines
+ $engines = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines');
+
+ // Set defaults for theme info.
+ $defaults = array(
+ 'engine' => 'phptemplate',
+ 'regions' => array(
+ 'sidebar_first' => 'Left sidebar',
+ 'sidebar_second' => 'Right sidebar',
+ 'content' => 'Content',
+ 'header' => 'Header',
+ 'footer' => 'Footer',
+ 'highlighted' => 'Highlighted',
+ 'help' => 'Help',
+ 'page_top' => 'Page top',
+ 'page_bottom' => 'Page bottom',
+ ),
+ 'description' => '',
+ 'features' => _system_default_theme_features(),
+ 'screenshot' => 'screenshot.png',
+ 'php' => DRUPAL_MINIMUM_PHP,
+ 'stylesheets' => array(),
+ 'scripts' => array(),
+ );
+
+ $sub_themes = array();
+ // Read info files for each theme
+ foreach ($themes as $key => $theme) {
+ $themes[$key]->filename = $theme->uri;
+ $themes[$key]->info = drupal_parse_info_file($theme->uri) + $defaults;
+
+ // Invoke hook_system_info_alter() to give installed modules a chance to
+ // modify the data in the .info files if necessary.
+ $type = 'theme';
+ drupal_alter('system_info', $themes[$key]->info, $themes[$key], $type);
+
+ if (!empty($themes[$key]->info['base theme'])) {
+ $sub_themes[] = $key;
+ }
+ if ($themes[$key]->info['engine'] == 'theme') {
+ $filename = dirname($themes[$key]->uri) . '/' . $themes[$key]->name . '.theme';
+ if (file_exists($filename)) {
+ $themes[$key]->owner = $filename;
+ $themes[$key]->prefix = $key;
+ }
+ }
+ else {
+ $engine = $themes[$key]->info['engine'];
+ if (isset($engines[$engine])) {
+ $themes[$key]->owner = $engines[$engine]->uri;
+ $themes[$key]->prefix = $engines[$engine]->name;
+ $themes[$key]->template = TRUE;
+ }
+ }
+
+ // Prefix stylesheets and scripts with module path.
+ $path = dirname($theme->uri);
+ $theme->info['stylesheets'] = _system_info_add_path($theme->info['stylesheets'], $path);
+ $theme->info['scripts'] = _system_info_add_path($theme->info['scripts'], $path);
+
+ // Give the screenshot proper path information.
+ if (!empty($themes[$key]->info['screenshot'])) {
+ $themes[$key]->info['screenshot'] = $path . '/' . $themes[$key]->info['screenshot'];
+ }
+ }
+
+ // Now that we've established all our master themes, go back and fill in data
+ // for subthemes.
+ foreach ($sub_themes as $key) {
+ $themes[$key]->base_themes = system_find_base_themes($themes, $key);
+ // Don't proceed if there was a problem with the root base theme.
+ if (!current($themes[$key]->base_themes)) {
+ continue;
+ }
+ $base_key = key($themes[$key]->base_themes);
+ foreach (array_keys($themes[$key]->base_themes) as $base_theme) {
+ $themes[$base_theme]->sub_themes[$key] = $themes[$key]->info['name'];
+ }
+ // Copy the 'owner' and 'engine' over if the top level theme uses a theme
+ // engine.
+ if (isset($themes[$base_key]->owner)) {
+ if (isset($themes[$base_key]->info['engine'])) {
+ $themes[$key]->info['engine'] = $themes[$base_key]->info['engine'];
+ $themes[$key]->owner = $themes[$base_key]->owner;
+ $themes[$key]->prefix = $themes[$base_key]->prefix;
+ }
+ else {
+ $themes[$key]->prefix = $key;
+ }
+ }
+ }
+
+ return $themes;
+}
+
+/**
+ * Rebuild, save, and return data about all currently available themes.
+ *
+ * @return
+ * Array of all available themes and their data.
+ */
+function system_rebuild_theme_data() {
+ $themes = _system_rebuild_theme_data();
+ ksort($themes);
+ system_get_files_database($themes, 'theme');
+ system_update_files_database($themes, 'theme');
+ return $themes;
+}
+
+/**
+ * Prefixes all values in an .info file array with a given path.
+ *
+ * This helper function is mainly used to prefix all array values of an .info
+ * file property with a single given path (to the module or theme); e.g., to
+ * prefix all values of the 'stylesheets' or 'scripts' properties with the file
+ * path to the defining module/theme.
+ *
+ * @param $info
+ * A nested array of data of an .info file to be processed.
+ * @param $path
+ * A file path to prepend to each value in $info.
+ *
+ * @return
+ * The $info array with prefixed values.
+ *
+ * @see _system_rebuild_module_data()
+ * @see _system_rebuild_theme_data()
+ */
+function _system_info_add_path($info, $path) {
+ foreach ($info as $key => $value) {
+ // Recurse into nested values until we reach the deepest level.
+ if (is_array($value)) {
+ $info[$key] = _system_info_add_path($info[$key], $path);
+ }
+ // Unset the original value's key and set the new value with prefix, using
+ // the original value as key, so original values can still be looked up.
+ else {
+ unset($info[$key]);
+ $info[$value] = $path . '/' . $value;
+ }
+ }
+ return $info;
+}
+
+/**
+ * Returns an array of default theme features.
+ */
+function _system_default_theme_features() {
+ return array(
+ 'logo',
+ 'favicon',
+ 'name',
+ 'slogan',
+ 'node_user_picture',
+ 'comment_user_picture',
+ 'comment_user_verification',
+ 'main_menu',
+ 'secondary_menu',
+ );
+}
+
+/**
+ * Find all the base themes for the specified theme.
+ *
+ * Themes can inherit templates and function implementations from earlier themes.
+ *
+ * @param $themes
+ * An array of available themes.
+ * @param $key
+ * The name of the theme whose base we are looking for.
+ * @param $used_keys
+ * A recursion parameter preventing endless loops.
+ * @return
+ * Returns an array of all of the theme's ancestors; the first element's value
+ * will be NULL if an error occurred.
+ */
+function system_find_base_themes($themes, $key, $used_keys = array()) {
+ $base_key = $themes[$key]->info['base theme'];
+ // Does the base theme exist?
+ if (!isset($themes[$base_key])) {
+ return array($base_key => NULL);
+ }
+
+ $current_base_theme = array($base_key => $themes[$base_key]->info['name']);
+
+ // Is the base theme itself a child of another theme?
+ if (isset($themes[$base_key]->info['base theme'])) {
+ // Do we already know the base themes of this theme?
+ if (isset($themes[$base_key]->base_themes)) {
+ return $themes[$base_key]->base_themes + $current_base_theme;
+ }
+ // Prevent loops.
+ if (!empty($used_keys[$base_key])) {
+ return array($base_key => NULL);
+ }
+ $used_keys[$base_key] = TRUE;
+ return system_find_base_themes($themes, $base_key, $used_keys) + $current_base_theme;
+ }
+ // If we get here, then this is our parent theme.
+ return $current_base_theme;
+}
+
+/**
+ * Get a list of available regions from a specified theme.
+ *
+ * @param $theme_key
+ * The name of a theme.
+ * @param $show
+ * Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden
+ * regions.
+ * @return
+ * An array of regions in the form $region['name'] = 'description'.
+ */
+function system_region_list($theme_key, $show = REGIONS_ALL) {
+ $themes = list_themes();
+ if (!isset($themes[$theme_key])) {
+ return array();
+ }
+
+ $list = array();
+ $info = $themes[$theme_key]->info;
+ // If requested, suppress hidden regions. See block_admin_display_form().
+ foreach ($info['regions'] as $name => $label) {
+ if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) {
+ $list[$name] = t($label);
+ }
+ }
+
+ return $list;
+}
+
+/**
+ * Implements hook_system_info_alter().
+ */
+function system_system_info_alter(&$info, $file, $type) {
+ // Remove page-top from the blocks UI since it is reserved for modules to
+ // populate from outside the blocks system.
+ if ($type == 'theme') {
+ $info['regions_hidden'][] = 'page_top';
+ $info['regions_hidden'][] = 'page_bottom';
+ }
+}
+
+/**
+ * Get the name of the default region for a given theme.
+ *
+ * @param $theme
+ * The name of a theme.
+ * @return
+ * A string that is the region name.
+ */
+function system_default_region($theme) {
+ $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE));
+ return isset($regions[0]) ? $regions[0] : '';
+}
+
+/**
+ * Add default buttons to a form and set its prefix.
+ *
+ * @param $form
+ * An associative array containing the structure of the form.
+ *
+ * @return
+ * The form structure.
+ *
+ * @see system_settings_form_submit()
+ * @ingroup forms
+ */
+function system_settings_form($form) {
+ $form['actions']['#type'] = 'actions';
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+
+ if (!empty($_POST) && form_get_errors()) {
+ drupal_set_message(t('The settings have not been saved because of the errors.'), 'error');
+ }
+ $form['#submit'][] = 'system_settings_form_submit';
+ // By default, render the form using theme_system_settings_form().
+ if (!isset($form['#theme'])) {
+ $form['#theme'] = 'system_settings_form';
+ }
+ return $form;
+}
+
+/**
+ * Execute the system_settings_form.
+ *
+ * If you want node type configure style handling of your checkboxes,
+ * add an array_filter value to your form.
+ */
+function system_settings_form_submit($form, &$form_state) {
+ // Exclude unnecessary elements.
+ form_state_values_clean($form_state);
+
+ foreach ($form_state['values'] as $key => $value) {
+ if (is_array($value) && isset($form_state['values']['array_filter'])) {
+ $value = array_keys(array_filter($value));
+ }
+ variable_set($key, $value);
+ }
+
+ drupal_set_message(t('The configuration options have been saved.'));
+}
+
+/**
+ * Helper function to sort requirements.
+ */
+function _system_sort_requirements($a, $b) {
+ if (!isset($a['weight'])) {
+ if (!isset($b['weight'])) {
+ return strcmp($a['title'], $b['title']);
+ }
+ return -$b['weight'];
+ }
+ return isset($b['weight']) ? $a['weight'] - $b['weight'] : $a['weight'];
+}
+
+/**
+ * Generates a form array for a confirmation form.
+ *
+ * This function returns a complete form array for confirming an action. The
+ * form contains a confirm button as well as a cancellation link that allows a
+ * user to abort the action.
+ *
+ * If the submit handler for a form that implements confirm_form() is invoked,
+ * the user successfully confirmed the action. You should never directly
+ * inspect $_POST to see if an action was confirmed.
+ *
+ * Note - if the parameters $question, $description, $yes, or $no could contain
+ * any user input (such as node titles or taxonomy terms), it is the
+ * responsibility of the code calling confirm_form() to sanitize them first with
+ * a function like check_plain() or filter_xss().
+ *
+ * @param $form
+ * Additional elements to add to the form. These can be regular form elements,
+ * #value elements, etc., and their values will be available to the submit
+ * handler.
+ * @param $question
+ * The question to ask the user (e.g. "Are you sure you want to delete the
+ * block <em>foo</em>?"). The page title will be set to this value.
+ * @param $path
+ * The page to go to if the user cancels the action. This can be either:
+ * - A string containing a Drupal path.
+ * - An associative array with a 'path' key. Additional array values are
+ * passed as the $options parameter to l().
+ * If the 'destination' query parameter is set in the URL when viewing a
+ * confirmation form, that value will be used instead of $path.
+ * @param $description
+ * Additional text to display. Defaults to t('This action cannot be undone.').
+ * @param $yes
+ * A caption for the button that confirms the action (e.g. "Delete",
+ * "Replace", ...). Defaults to t('Confirm').
+ * @param $no
+ * A caption for the link which cancels the action (e.g. "Cancel"). Defaults
+ * to t('Cancel').
+ * @param $name
+ * The internal name used to refer to the confirmation item.
+ *
+ * @return
+ * The form array.
+ */
+function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, $no = NULL, $name = 'confirm') {
+ $description = isset($description) ? $description : t('This action cannot be undone.');
+
+ // Prepare cancel link.
+ if (isset($_GET['destination'])) {
+ $options = drupal_parse_url(urldecode($_GET['destination']));
+ }
+ elseif (is_array($path)) {
+ $options = $path;
+ }
+ else {
+ $options = array('path' => $path);
+ }
+
+ drupal_set_title($question, PASS_THROUGH);
+
+ $form['#attributes']['class'][] = 'confirmation';
+ $form['description'] = array('#markup' => $description);
+ $form[$name] = array('#type' => 'hidden', '#value' => 1);
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => $yes ? $yes : t('Confirm'),
+ );
+ $form['actions']['cancel'] = array(
+ '#type' => 'link',
+ '#title' => $no ? $no : t('Cancel'),
+ '#href' => $options['path'],
+ '#options' => $options,
+ );
+ // By default, render the form using theme_confirm_form().
+ if (!isset($form['#theme'])) {
+ $form['#theme'] = 'confirm_form';
+ }
+ return $form;
+}
+
+/**
+ * Determines if the current user is in compact mode.
+ *
+ * @return
+ * TRUE when in compact mode, FALSE when in expanded mode.
+ */
+function system_admin_compact_mode() {
+ // PHP converts dots into underscores in cookie names to avoid problems with
+ // its parser, so we use a converted cookie name.
+ return isset($_COOKIE['Drupal_visitor_admin_compact_mode']) ? $_COOKIE['Drupal_visitor_admin_compact_mode'] : variable_get('admin_compact_mode', FALSE);
+}
+
+/**
+ * Menu callback; Sets whether the admin menu is in compact mode or not.
+ *
+ * @param $mode
+ * Valid values are 'on' and 'off'.
+ */
+function system_admin_compact_page($mode = 'off') {
+ user_cookie_save(array('admin_compact_mode' => ($mode == 'on')));
+ drupal_goto();
+}
+
+/**
+ * Generate a list of tasks offered by a specified module.
+ *
+ * @param $module
+ * Module name.
+ * @param $info
+ * The module's information, as provided by system_get_info().
+ *
+ * @return
+ * An array of task links.
+ */
+function system_get_module_admin_tasks($module, $info) {
+ $links = &drupal_static(__FUNCTION__);
+
+ if (!isset($links)) {
+ $links = array();
+ $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
+ $query->join('menu_router', 'm', 'm.path = ml.router_path');
+ $query
+ ->fields('ml')
+ // Weight should be taken from {menu_links}, not {menu_router}.
+ ->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')))
+ ->condition('ml.link_path', 'admin/%', 'LIKE')
+ ->condition('ml.hidden', 0, '>=')
+ ->condition('ml.module', 'system')
+ ->condition('m.number_parts', 1, '>')
+ ->condition('m.page_callback', 'system_admin_menu_block_page', '<>');
+ foreach ($query->execute() as $link) {
+ _menu_link_translate($link);
+ if ($link['access']) {
+ $links[$link['router_path']] = $link;
+ }
+ }
+ }
+
+ $admin_tasks = array();
+ $titles = array();
+ if ($menu = module_invoke($module, 'menu')) {
+ foreach ($menu as $path => $item) {
+ if (isset($links[$path])) {
+ $task = $links[$path];
+ // The link description, either derived from 'description' in
+ // hook_menu() or customized via menu module is used as title attribute.
+ if (!empty($task['localized_options']['attributes']['title'])) {
+ $task['description'] = $task['localized_options']['attributes']['title'];
+ unset($task['localized_options']['attributes']['title']);
+ }
+
+ // Check the admin tasks for duplicate names. If one is found,
+ // append the parent menu item's title to differentiate.
+ $duplicate_path = array_search($task['title'], $titles);
+ if ($duplicate_path !== FALSE) {
+ if ($parent = menu_link_load($task['plid'])) {
+ // Append the parent item's title to this task's title.
+ $task['title'] = t('@original_title (@parent_title)', array('@original_title' => $task['title'], '@parent_title' => $parent['title']));
+ }
+ if ($parent = menu_link_load($admin_tasks[$duplicate_path]['plid'])) {
+ // Append the parent item's title to the duplicated task's title.
+ // We use $links[$duplicate_path] in case there are triplicates.
+ $admin_tasks[$duplicate_path]['title'] = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]['title'], '@parent_title' => $parent['title']));
+ }
+ }
+ else {
+ $titles[$path] = $task['title'];
+ }
+
+ $admin_tasks[$path] = $task;
+ }
+ }
+ }
+
+ // Append link for permissions.
+ if (module_hook($module, 'permission')) {
+ $item = menu_get_item('admin/people/permissions');
+ if (!empty($item['access'])) {
+ $item['link_path'] = $item['href'];
+ $item['title'] = t('Configure @module permissions', array('@module' => $info['name']));
+ unset($item['description']);
+ $item['localized_options']['fragment'] = 'module-' . $module;
+ $admin_tasks["admin/people/permissions#module-$module"] = $item;
+ }
+ }
+
+ return $admin_tasks;
+}
+
+/**
+ * Implements hook_cron().
+ *
+ * Remove older rows from flood and batch table. Remove old temporary files.
+ */
+function system_cron() {
+ // Cleanup the flood.
+ db_delete('flood')
+ ->condition('expiration', REQUEST_TIME, '<')
+ ->execute();
+
+ // Remove temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+ // Use separate placeholders for the status to avoid a bug in some versions
+ // of PHP. See http://drupal.org/node/352956.
+ $result = db_query('SELECT fid FROM {file_managed} WHERE status <> :permanent AND timestamp < :timestamp', array(
+ ':permanent' => FILE_STATUS_PERMANENT,
+ ':timestamp' => REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE
+ ));
+ foreach ($result as $row) {
+ if ($file = file_load($row->fid)) {
+ $references = file_usage_list($file);
+ if (empty($references)) {
+ if (!file_delete($file)) {
+ watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->uri), WATCHDOG_ERROR);
+ }
+ }
+ else {
+ watchdog('file system', 'Did not delete temporary file "%path" during garbage collection because it is in use by the following modules: %modules.', array('%path' => $file->uri, '%modules' => implode(', ', array_keys($references))), WATCHDOG_INFO);
+ }
+ }
+ }
+
+ $core = array('cache', 'path', 'filter', 'page', 'form', 'menu');
+ $cache_bins = array_merge(module_invoke_all('flush_caches'), $core);
+ foreach ($cache_bins as $bin) {
+ cache($bin)->expire();
+ }
+
+ // Cleanup the batch table and the queue for failed batches.
+ db_delete('batch')
+ ->condition('timestamp', REQUEST_TIME - 864000, '<')
+ ->execute();
+ db_delete('queue')
+ ->condition('created', REQUEST_TIME - 864000, '<')
+ ->condition('name', 'drupal_batch:%', 'LIKE')
+ ->execute();
+
+ // Reset expired items in the default queue implementation table. If that's
+ // not used, this will simply be a no-op.
+ db_update('queue')
+ ->fields(array(
+ 'expire' => 0,
+ ))
+ ->condition('expire', 0, '<>')
+ ->condition('expire', REQUEST_TIME, '<')
+ ->execute();
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function system_flush_caches() {
+ // Rebuild list of date formats.
+ system_date_formats_rebuild();
+ // Reset the menu static caches.
+ menu_reset_static_cache();
+}
+
+/**
+ * Implements hook_action_info().
+ */
+function system_action_info() {
+ return array(
+ 'system_message_action' => array(
+ 'type' => 'system',
+ 'label' => t('Display a message to the user'),
+ 'configurable' => TRUE,
+ 'triggers' => array('any'),
+ ),
+ 'system_send_email_action' => array(
+ 'type' => 'system',
+ 'label' => t('Send e-mail'),
+ 'configurable' => TRUE,
+ 'triggers' => array('any'),
+ ),
+ 'system_block_ip_action' => array(
+ 'type' => 'user',
+ 'label' => t('Ban IP address of current user'),
+ 'configurable' => FALSE,
+ 'triggers' => array('any'),
+ ),
+ 'system_goto_action' => array(
+ 'type' => 'system',
+ 'label' => t('Redirect to URL'),
+ 'configurable' => TRUE,
+ 'triggers' => array('any'),
+ ),
+ );
+}
+
+/**
+ * Return a form definition so the Send email action can be configured.
+ *
+ * @param $context
+ * Default values (if we are editing an existing action instance).
+ *
+ * @return
+ * Form definition.
+ *
+ * @see system_send_email_action_validate()
+ * @see system_send_email_action_submit()
+ */
+function system_send_email_action_form($context) {
+ // Set default values for form.
+ if (!isset($context['recipient'])) {
+ $context['recipient'] = '';
+ }
+ if (!isset($context['subject'])) {
+ $context['subject'] = '';
+ }
+ if (!isset($context['message'])) {
+ $context['message'] = '';
+ }
+
+ $form['recipient'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Recipient'),
+ '#default_value' => $context['recipient'],
+ '#maxlength' => '254',
+ '#description' => t('The e-mail address to which the message should be sent OR enter [node:author:mail], [comment:author:mail], etc. if you would like to send an e-mail to the author of the original post.'),
+ );
+ $form['subject'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Subject'),
+ '#default_value' => $context['subject'],
+ '#maxlength' => '254',
+ '#description' => t('The subject of the message.'),
+ );
+ $form['message'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Message'),
+ '#default_value' => $context['message'],
+ '#cols' => '80',
+ '#rows' => '20',
+ '#description' => t('The message that should be sent. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
+ );
+ return $form;
+}
+
+/**
+ * Validate system_send_email_action form submissions.
+ */
+function system_send_email_action_validate($form, $form_state) {
+ $form_values = $form_state['values'];
+ // Validate the configuration form.
+ if (!valid_email_address($form_values['recipient']) && strpos($form_values['recipient'], ':mail') === FALSE) {
+ // We want the literal %author placeholder to be emphasized in the error message.
+ form_set_error('recipient', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]')));
+ }
+}
+
+/**
+ * Process system_send_email_action form submissions.
+ */
+function system_send_email_action_submit($form, $form_state) {
+ $form_values = $form_state['values'];
+ // Process the HTML form to store configuration. The keyed array that
+ // we return will be serialized to the database.
+ $params = array(
+ 'recipient' => $form_values['recipient'],
+ 'subject' => $form_values['subject'],
+ 'message' => $form_values['message'],
+ );
+ return $params;
+}
+
+/**
+ * Sends an e-mail message.
+ *
+ * @param object $entity
+ * An optional node object, which will be added as $context['node'] if
+ * provided.
+ * @param array $context
+ * Array with the following elements:
+ * - 'recipient': E-mail message recipient. This will be passed through
+ * token_replace().
+ * - 'subject': The subject of the message. This will be passed through
+ * token_replace().
+ * - 'message': The message to send. This will be passed through
+ * token_replace().
+ * - Other elements will be used as the data for token replacement.
+ *
+ * @ingroup actions
+ */
+function system_send_email_action($entity, $context) {
+ if (empty($context['node'])) {
+ $context['node'] = $entity;
+ }
+
+ $recipient = token_replace($context['recipient'], $context);
+
+ // If the recipient is a registered user with a language preference, use
+ // the recipient's preferred language. Otherwise, use the system default
+ // language.
+ $recipient_account = user_load_by_mail($recipient);
+ if ($recipient_account) {
+ $language = user_preferred_language($recipient_account);
+ }
+ else {
+ $language = language_default();
+ }
+ $params = array('context' => $context);
+
+ if (drupal_mail('system', 'action_send_email', $recipient, $language, $params)) {
+ watchdog('action', 'Sent email to %recipient', array('%recipient' => $recipient));
+ }
+ else {
+ watchdog('error', 'Unable to send email to %recipient', array('%recipient' => $recipient));
+ }
+}
+
+/**
+ * Implements hook_mail().
+ */
+function system_mail($key, &$message, $params) {
+ $context = $params['context'];
+
+ $subject = token_replace($context['subject'], $context);
+ $body = token_replace($context['message'], $context);
+
+ $message['subject'] .= str_replace(array("\r", "\n"), '', $subject);
+ $message['body'][] = $body;
+}
+
+function system_message_action_form($context) {
+ $form['message'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Message'),
+ '#default_value' => isset($context['message']) ? $context['message'] : '',
+ '#required' => TRUE,
+ '#rows' => '8',
+ '#description' => t('The message to be displayed to the current user. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
+ );
+ return $form;
+}
+
+function system_message_action_submit($form, $form_state) {
+ return array('message' => $form_state['values']['message']);
+}
+
+/**
+ * Sends a message to the current user's screen.
+ *
+ * @param object $entity
+ * An optional node object, which will be added as $context['node'] if
+ * provided.
+ * @param array $context
+ * Array with the following elements:
+ * - 'message': The message to send. This will be passed through
+ * token_replace().
+ * - Other elements will be used as the data for token replacement in
+ * the message.
+ *
+ * @ingroup actions
+ */
+function system_message_action(&$entity, $context = array()) {
+ if (empty($context['node'])) {
+ $context['node'] = $entity;
+ }
+
+ $context['message'] = token_replace(filter_xss_admin($context['message']), $context);
+ drupal_set_message($context['message']);
+}
+
+/**
+ * Settings form for system_goto_action().
+ */
+function system_goto_action_form($context) {
+ $form['url'] = array(
+ '#type' => 'textfield',
+ '#title' => t('URL'),
+ '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'),
+ '#default_value' => isset($context['url']) ? $context['url'] : '',
+ '#required' => TRUE,
+ );
+ return $form;
+}
+
+function system_goto_action_submit($form, $form_state) {
+ return array(
+ 'url' => $form_state['values']['url']
+ );
+}
+
+/**
+ * Redirects to a different URL.
+ *
+ * @param $entity
+ * Ignored.
+ * @param array $context
+ * Array with the following elements:
+ * - 'url': URL to redirect to. This will be passed through
+ * token_replace().
+ * - Other elements will be used as the data for token replacement.
+ *
+ * @ingroup actions
+ */
+function system_goto_action($entity, $context) {
+ drupal_goto(token_replace($context['url'], $context));
+}
+
+/**
+ * Blocks the current user's IP address.
+ *
+ * @ingroup actions
+ */
+function system_block_ip_action() {
+ $ip = ip_address();
+ db_insert('blocked_ips')
+ ->fields(array('ip' => $ip))
+ ->execute();
+ watchdog('action', 'Banned IP address %ip', array('%ip' => $ip));
+}
+
+/**
+ * Generate an array of time zones and their local time&date.
+ *
+ * @param $blank
+ * If evaluates true, prepend an empty time zone option to the array.
+ */
+function system_time_zones($blank = NULL) {
+ $zonelist = timezone_identifiers_list();
+ $zones = $blank ? array('' => t('- None selected -')) : array();
+ foreach ($zonelist as $zone) {
+ // Because many time zones exist in PHP only for backward compatibility
+ // reasons and should not be used, the list is filtered by a regular
+ // expression.
+ if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
+ $zones[$zone] = t('@zone: @date', array('@zone' => t(str_replace('_', ' ', $zone)), '@date' => format_date(REQUEST_TIME, 'custom', variable_get('date_format_long', 'l, F j, Y - H:i') . ' O', $zone)));
+ }
+ }
+ // Sort the translated time zones alphabetically.
+ asort($zones);
+ return $zones;
+}
+
+/**
+ * Checks whether the server is capable of issuing HTTP requests.
+ *
+ * The function sets the drupal_http_request_fail system variable to TRUE if
+ * drupal_http_request() does not work and then the system status report page
+ * will contain an error.
+ *
+ * @return
+ * TRUE if this installation can issue HTTP requests.
+ */
+function system_check_http_request() {
+ // Try to get the content of the front page via drupal_http_request().
+ $result = drupal_http_request(url('', array('absolute' => TRUE)), array('max_redirects' => 0));
+ // We only care that we get a http response - this means that Drupal
+ // can make a http request.
+ $works = isset($result->code) && ($result->code >= 100) && ($result->code < 600);
+ variable_set('drupal_http_request_fails', !$works);
+ return $works;
+}
+
+/**
+ * Menu callback; Retrieve a JSON object containing a suggested time zone name.
+ */
+function system_timezone($abbreviation = '', $offset = -1, $is_daylight_saving_time = NULL) {
+ // An abbreviation of "0" passed in the callback arguments should be
+ // interpreted as the empty string.
+ $abbreviation = $abbreviation ? $abbreviation : '';
+ $timezone = timezone_name_from_abbr($abbreviation, intval($offset), $is_daylight_saving_time);
+ drupal_json_output($timezone);
+}
+
+/**
+ * Returns HTML for the Powered by Drupal text.
+ *
+ * @ingroup themeable
+ */
+function theme_system_powered_by() {
+ return '<span>' . t('Powered by <a href="@poweredby">Drupal</a>', array('@poweredby' => 'http://drupal.org')) . '</span>';
+}
+
+/**
+ * Returns HTML for a link to show or hide inline help descriptions.
+ *
+ * @ingroup themeable
+ */
+function theme_system_compact_link() {
+ $output = '<div class="compact-link">';
+ if (system_admin_compact_mode()) {
+ $output .= l(t('Show descriptions'), 'admin/compact/off', array('attributes' => array('title' => t('Expand layout to include descriptions.')), 'query' => drupal_get_destination()));
+ }
+ else {
+ $output .= l(t('Hide descriptions'), 'admin/compact/on', array('attributes' => array('title' => t('Compress layout by hiding descriptions.')), 'query' => drupal_get_destination()));
+ }
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Implements hook_image_toolkits().
+ */
+function system_image_toolkits() {
+ include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'system') . '/' . 'image.gd.inc';
+ return array(
+ 'gd' => array(
+ 'title' => t('GD2 image manipulation toolkit'),
+ 'available' => function_exists('image_gd_check_settings') && image_gd_check_settings(),
+ ),
+ );
+}
+
+/**
+ * Attempts to get a file using drupal_http_request and to store it locally.
+ *
+ * @param $url
+ * The URL of the file to grab.
+ *
+ * @param $destination
+ * Stream wrapper URI specifying where the file should be placed. If a
+ * directory path is provided, the file is saved into that directory under
+ * its original name. If the path contains a filename as well, that one will
+ * be used instead.
+ * If this value is omitted, the site's default files scheme will be used,
+ * usually "public://".
+ *
+ * @param $managed boolean
+ * If this is set to TRUE, the file API hooks will be invoked and the file is
+ * registered in the database.
+ *
+ * @param $replace boolean
+ * Replace behavior when the destination file already exists:
+ * - FILE_EXISTS_REPLACE: Replace the existing file.
+ * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
+ * unique.
+ * - FILE_EXISTS_ERROR: Do nothing and return FALSE.
+ *
+ * @return
+ * On success the location the file was saved to, FALSE on failure.
+ */
+function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $replace = FILE_EXISTS_RENAME) {
+ $parsed_url = parse_url($url);
+ if (!isset($destination)) {
+ $path = file_build_uri(basename($parsed_url['path']));
+ }
+ else {
+ if (is_dir(drupal_realpath($destination))) {
+ // Prevent URIs with triple slashes when glueing parts together.
+ $path = str_replace('///', '//', "$destination/") . basename($parsed_url['path']);
+ }
+ else {
+ $path = $destination;
+ }
+ }
+ $result = drupal_http_request($url);
+ if ($result->code != 200) {
+ drupal_set_message(t('HTTP error @errorcode occurred when trying to fetch @remote.', array('@errorcode' => $result->code, '@remote' => $url)), 'error');
+ return FALSE;
+ }
+ $local = $managed ? file_save_data($result->data, $path, $replace) : file_unmanaged_save_data($result->data, $path, $replace);
+ if (!$local) {
+ drupal_set_message(t('@remote could not be saved to @path.', array('@remote' => $url, '@path' => $path)), 'error');
+ }
+
+ return $local;
+}
+
+/**
+ * Implements hook_page_alter().
+ */
+function system_page_alter(&$page) {
+ // Find all non-empty page regions, and add a theme wrapper function that
+ // allows them to be consistently themed.
+ $regions = system_region_list($GLOBALS['theme']);
+ foreach (array_keys($regions) as $region) {
+ if (!empty($page[$region])) {
+ $page[$region]['#theme_wrappers'][] = 'region';
+ $page[$region]['#region'] = $region;
+ }
+ }
+}
+
+/**
+ * Run the automated cron if enabled.
+ */
+function system_run_automated_cron() {
+ // If the site is not fully installed, suppress the automated cron run.
+ // Otherwise it could be triggered prematurely by Ajax requests during
+ // installation.
+ if (($threshold = variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD)) > 0 && variable_get('install_task') == 'done') {
+ $cron_last = variable_get('cron_last', NULL);
+ if (!isset($cron_last) || (REQUEST_TIME - $cron_last > $threshold)) {
+ drupal_cron_run();
+ }
+ }
+}
+
+/**
+ * Gets the list of available date types and attributes.
+ *
+ * @param $type
+ * (optional) The date type name.
+ *
+ * @return
+ * An associative array of date type information keyed by the date type name.
+ * Each date type information array has the following elements:
+ * - type: The machine-readable name of the date type.
+ * - title: The human-readable name of the date type.
+ * - locked: A boolean indicating whether or not this date type should be
+ * configurable from the user interface.
+ * - module: The name of the module that defined this date type in its
+ * hook_date_format_types(). An empty string if the date type was
+ * user-defined.
+ * - is_new: A boolean indicating whether or not this date type is as of yet
+ * unsaved in the database.
+ * If $type was defined, only a single associative array with the above
+ * elements is returned.
+ */
+function system_get_date_types($type = NULL) {
+ $types = &drupal_static(__FUNCTION__);
+
+ if (!isset($types)) {
+ $types = _system_date_format_types_build();
+ }
+
+ return $type ? (isset($types[$type]) ? $types[$type] : FALSE) : $types;
+}
+
+/**
+ * Implements hook_date_format_types().
+ */
+function system_date_format_types() {
+ return array(
+ 'long' => t('Long'),
+ 'medium' => t('Medium'),
+ 'short' => t('Short'),
+ );
+}
+
+/**
+ * Implements hook_date_formats().
+ */
+function system_date_formats() {
+ include_once DRUPAL_ROOT . '/includes/date.inc';
+ return system_default_date_formats();
+}
+
+/**
+ * Gets the list of defined date formats and attributes.
+ *
+ * @param $type
+ * (optional) The date type name.
+ *
+ * @return
+ * An associative array of date formats. The top-level keys are the names of
+ * the date types that the date formats belong to. The values are in turn
+ * associative arrays keyed by the format string, with the following keys:
+ * - dfid: The date format ID.
+ * - format: The format string.
+ * - type: The machine-readable name of the date type.
+ * - locales: An array of language codes. This can include both 2 character
+ * language codes like 'en and 'fr' and 5 character language codes like
+ * 'en-gb' and 'en-us'.
+ * - locked: A boolean indicating whether or not this date type should be
+ * configurable from the user interface.
+ * - module: The name of the module that defined this date format in its
+ * hook_date_formats(). An empty string if the format was user-defined.
+ * - is_new: A boolean indicating whether or not this date type is as of yet
+ * unsaved in the database.
+ * If $type was defined, only the date formats associated with the given date
+ * type are returned, in a single associative array keyed by format string.
+ */
+function system_get_date_formats($type = NULL) {
+ $date_formats = &drupal_static(__FUNCTION__);
+
+ if (!isset($date_formats)) {
+ $date_formats = _system_date_formats_build();
+ }
+
+ return $type ? (isset($date_formats[$type]) ? $date_formats[$type] : FALSE) : $date_formats;
+}
+
+/**
+ * Gets the format details for a particular format ID.
+ *
+ * @param $dfid
+ * A date format ID.
+ *
+ * @return
+ * A date format object with the following properties:
+ * - dfid: The date format ID.
+ * - format: The date format string.
+ * - type: The name of the date type.
+ * - locked: Whether the date format can be changed or not.
+ */
+function system_get_date_format($dfid) {
+ return db_query('SELECT df.dfid, df.format, df.type, df.locked FROM {date_formats} df WHERE df.dfid = :dfid', array(':dfid' => $dfid))->fetch();
+}
+
+/**
+ * Resets the database cache of date formats and saves all new date formats.
+ */
+function system_date_formats_rebuild() {
+ drupal_static_reset('system_get_date_formats');
+ $date_formats = system_get_date_formats(NULL);
+
+ foreach ($date_formats as $type => $formats) {
+ foreach ($formats as $format => $info) {
+ system_date_format_save($info);
+ }
+ }
+
+ // Rebuild configured date formats locale list.
+ drupal_static_reset('system_date_format_locale');
+ system_date_format_locale();
+
+ _system_date_formats_build();
+}
+
+/**
+ * Gets the appropriate date format string for a date type and locale.
+ *
+ * @param $langcode
+ * (optional) Language code for the current locale. This can be a 2 character
+ * language code like 'en' and 'fr' or a 5 character language code like
+ * 'en-gb' and 'en-us'.
+ * @param $type
+ * (optional) The date type name.
+ *
+ * @return
+ * If $type and $langcode are specified, returns the corresponding date format
+ * string. If only $langcode is specified, returns an array of all date
+ * format strings for that locale, keyed by the date type. If neither is
+ * specified, or if no matching formats are found, returns FALSE.
+ */
+function system_date_format_locale($langcode = NULL, $type = NULL) {
+ $formats = &drupal_static(__FUNCTION__);
+
+ if (empty($formats)) {
+ $formats = array();
+ $result = db_query("SELECT format, type, language FROM {date_format_locale}");
+ foreach ($result as $record) {
+ if (!isset($formats[$record->language])) {
+ $formats[$record->language] = array();
+ }
+ $formats[$record->language][$record->type] = $record->format;
+ }
+ }
+
+ if ($type && $langcode && !empty($formats[$langcode][$type])) {
+ return $formats[$langcode][$type];
+ }
+ elseif ($langcode && !empty($formats[$langcode])) {
+ return $formats[$langcode];
+ }
+
+ return FALSE;
+}
+
+/**
+ * Builds and returns information about available date types.
+ *
+ * @return
+ * An associative array of date type information keyed by name. Each date type
+ * information array has the following elements:
+ * - type: The machine-readable name of the date type.
+ * - title: The human-readable name of the date type.
+ * - locked: A boolean indicating whether or not this date type should be
+ * configurable from the user interface.
+ * - module: The name of the module that defined this format in its
+ * hook_date_format_types(). An empty string if the format was user-defined.
+ * - is_new: A boolean indicating whether or not this date type is as of yet
+ * unsaved in the database.
+ */
+function _system_date_format_types_build() {
+ $types = array();
+
+ // Get list of modules that implement hook_date_format_types().
+ $modules = module_implements('date_format_types');
+
+ foreach ($modules as $module) {
+ $module_types = module_invoke($module, 'date_format_types');
+ foreach ($module_types as $module_type => $type_title) {
+ $type = array();
+ $type['module'] = $module;
+ $type['type'] = $module_type;
+ $type['title'] = $type_title;
+ $type['locked'] = 1;
+ // Will be over-ridden later if in the db.
+ $type['is_new'] = TRUE;
+ $types[$module_type] = $type;
+ }
+ }
+
+ // Get custom formats added to the database by the end user.
+ $result = db_query('SELECT dft.type, dft.title, dft.locked FROM {date_format_type} dft ORDER BY dft.title');
+ foreach ($result as $record) {
+ if (!isset($types[$record->type])) {
+ $type = array();
+ $type['is_new'] = FALSE;
+ $type['module'] = '';
+ $type['type'] = $record->type;
+ $type['title'] = $record->title;
+ $type['locked'] = $record->locked;
+ $types[$record->type] = $type;
+ }
+ else {
+ $type = array();
+ $type['is_new'] = FALSE; // Over-riding previous setting.
+ $types[$record->type] = array_merge($types[$record->type], $type);
+ }
+ }
+
+ // Allow other modules to modify these date types.
+ drupal_alter('date_format_types', $types);
+
+ return $types;
+}
+
+/**
+ * Builds and returns information about available date formats.
+ *
+ * @return
+ * An associative array of date formats. The top-level keys are the names of
+ * the date types that the date formats belong to. The values are in turn
+ * associative arrays keyed by format with the following keys:
+ * - dfid: The date format ID.
+ * - format: The PHP date format string.
+ * - type: The machine-readable name of the date type the format belongs to.
+ * - locales: An array of language codes. This can include both 2 character
+ * language codes like 'en and 'fr' and 5 character language codes like
+ * 'en-gb' and 'en-us'.
+ * - locked: A boolean indicating whether or not this date type should be
+ * configurable from the user interface.
+ * - module: The name of the module that defined this format in its
+ * hook_date_formats(). An empty string if the format was user-defined.
+ * - is_new: A boolean indicating whether or not this date type is as of yet
+ * unsaved in the database.
+ */
+function _system_date_formats_build() {
+ $date_formats = array();
+
+ // First handle hook_date_format_types().
+ $types = _system_date_format_types_build();
+ foreach ($types as $type => $info) {
+ system_date_format_type_save($info);
+ }
+
+ // Get formats supplied by various contrib modules.
+ $module_formats = module_invoke_all('date_formats');
+
+ foreach ($module_formats as $module_format) {
+ // System types are locked.
+ $module_format['locked'] = 1;
+ // If no date type is specified, assign 'custom'.
+ if (!isset($module_format['type'])) {
+ $module_format['type'] = 'custom';
+ }
+ if (!in_array($module_format['type'], array_keys($types))) {
+ continue;
+ }
+ if (!isset($date_formats[$module_format['type']])) {
+ $date_formats[$module_format['type']] = array();
+ }
+
+ // If another module already set this format, merge in the new settings.
+ if (isset($date_formats[$module_format['type']][$module_format['format']])) {
+ $date_formats[$module_format['type']][$module_format['format']] = array_merge_recursive($date_formats[$module_format['type']][$module_format['format']], $module_format);
+ }
+ else {
+ // This setting will be overridden later if it already exists in the db.
+ $module_format['is_new'] = TRUE;
+ $date_formats[$module_format['type']][$module_format['format']] = $module_format;
+ }
+ }
+
+ // Get custom formats added to the database by the end user.
+ $result = db_query('SELECT df.dfid, df.format, df.type, df.locked, dfl.language FROM {date_formats} df LEFT JOIN {date_format_type} dft ON df.type = dft.type LEFT JOIN {date_format_locale} dfl ON df.format = dfl.format AND df.type = dfl.type ORDER BY df.type, df.format');
+ foreach ($result as $record) {
+ // If this date type isn't set, initialise the array.
+ if (!isset($date_formats[$record->type])) {
+ $date_formats[$record->type] = array();
+ }
+ $format = (array) $record;
+ $format['is_new'] = FALSE; // It's in the db, so override this setting.
+ // If this format not already present, add it to the array.
+ if (!isset($date_formats[$record->type][$record->format])) {
+ $format['module'] = '';
+ $format['locales'] = array($record->language);
+ $date_formats[$record->type][$record->format] = $format;
+ }
+ // Format already present, so merge in settings.
+ else {
+ if (!empty($record->language)) {
+ $format['locales'] = array_merge($date_formats[$record->type][$record->format]['locales'], array($record->language));
+ }
+ $date_formats[$record->type][$record->format] = array_merge($date_formats[$record->type][$record->format], $format);
+ }
+ }
+
+ // Allow other modules to modify these formats.
+ drupal_alter('date_formats', $date_formats);
+
+ return $date_formats;
+}
+
+/**
+ * Saves a date type to the database.
+ *
+ * @param $type
+ * A date type array containing the following keys:
+ * - type: The machine-readable name of the date type.
+ * - title: The human-readable name of the date type.
+ * - locked: A boolean indicating whether or not this date type should be
+ * configurable from the user interface.
+ * - is_new: A boolean indicating whether or not this date type is as of yet
+ * unsaved in the database.
+ */
+function system_date_format_type_save($type) {
+ $info = array();
+ $info['type'] = $type['type'];
+ $info['title'] = $type['title'];
+ $info['locked'] = $type['locked'];
+
+ // Update date_format table.
+ if (!empty($type['is_new'])) {
+ drupal_write_record('date_format_type', $info);
+ }
+ else {
+ drupal_write_record('date_format_type', $info, 'type');
+ }
+}
+
+/**
+ * Deletes a date type from the database.
+ *
+ * @param $type
+ * The machine-readable name of the date type.
+ */
+function system_date_format_type_delete($type) {
+ db_delete('date_formats')
+ ->condition('type', $type)
+ ->execute();
+ db_delete('date_format_type')
+ ->condition('type', $type)
+ ->execute();
+ db_delete('date_format_locale')
+ ->condition('type', $type)
+ ->execute();
+}
+
+/**
+ * Saves a date format to the database.
+ *
+ * @param $date_format
+ * A date format array containing the following keys:
+ * - type: The name of the date type this format is associated with.
+ * - format: The PHP date format string.
+ * - locked: A boolean indicating whether or not this format should be
+ * configurable from the user interface.
+ * @param $dfid
+ * If set, replace the existing date format having this ID with the
+ * information specified in $date_format.
+ *
+ * @see system_get_date_types()
+ * @see http://php.net/date
+ */
+function system_date_format_save($date_format, $dfid = 0) {
+ $info = array();
+ $info['dfid'] = $dfid;
+ $info['type'] = $date_format['type'];
+ $info['format'] = $date_format['format'];
+ $info['locked'] = $date_format['locked'];
+
+ // Update date_format table.
+ if (!empty($date_format['is_new'])) {
+ drupal_write_record('date_formats', $info);
+ }
+ else {
+ $keys = ($dfid ? array('dfid') : array('format', 'type'));
+ drupal_write_record('date_formats', $info, $keys);
+ }
+
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+
+ $locale_format = array();
+ $locale_format['type'] = $date_format['type'];
+ $locale_format['format'] = $date_format['format'];
+
+ // Check if the suggested language codes are configured and enabled.
+ if (!empty($date_format['locales'])) {
+ foreach ($date_format['locales'] as $langcode) {
+ // Only proceed if language is enabled.
+ if (isset($languages[$langcode])) {
+ $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE type = :type AND language = :language', 0, 1, array(':type' => $date_format['type'], ':language' => $langcode))->fetchField();
+ if (!$is_existing) {
+ $locale_format['language'] = $langcode;
+ drupal_write_record('date_format_locale', $locale_format);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Deletes a date format from the database.
+ *
+ * @param $dfid
+ * The date format ID.
+ */
+function system_date_format_delete($dfid) {
+ db_delete('date_formats')
+ ->condition('dfid', $dfid)
+ ->execute();
+}
+
+/**
+ * Implements hook_archiver_info().
+ */
+function system_archiver_info() {
+ $archivers['tar'] = array(
+ 'class' => 'ArchiverTar',
+ 'extensions' => array('tar', 'tgz', 'tar.gz', 'tar.bz2'),
+ );
+ if (function_exists('zip_open')) {
+ $archivers['zip'] = array(
+ 'class' => 'ArchiverZip',
+ 'extensions' => array('zip'),
+ );
+ }
+ return $archivers;
+}
+
+/**
+ * Returns HTML for a confirmation form.
+ *
+ * By default this does not alter the appearance of a form at all,
+ * but is provided as a convenience for themers.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_confirm_form($variables) {
+ return drupal_render_children($variables['form']);
+}
+
+/**
+ * Returns HTML for a system settings form.
+ *
+ * By default this does not alter the appearance of a form at all,
+ * but is provided as a convenience for themers.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_system_settings_form($variables) {
+ return drupal_render_children($variables['form']);
+}
+
+/**
+ * Returns HTML for an exposed filter form.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: An associative array containing the structure of the form.
+ *
+ * @return
+ * A string containing an HTML-formatted form.
+ *
+ * @ingroup themeable
+ */
+function theme_exposed_filters($variables) {
+ $form = $variables['form'];
+ $output = '';
+
+ if (isset($form['current'])) {
+ $items = array();
+ foreach (element_children($form['current']) as $key) {
+ $items[] = drupal_render($form['current'][$key]);
+ }
+ $output .= theme('item_list', array('items' => $items, 'attributes' => array('class' => array('clearfix', 'current-filters'))));
+ }
+
+ $output .= drupal_render_children($form);
+
+ return '<div class="exposed-filters">' . $output . '</div>';
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function system_admin_paths() {
+ $paths = array(
+ 'admin' => TRUE,
+ 'admin/*' => TRUE,
+ 'batch' => TRUE,
+ // This page should not be treated as administrative since it outputs its
+ // own content (outside of any administration theme).
+ 'admin/reports/status/php' => FALSE,
+ );
+ return $paths;
+}
diff --git a/core/modules/system/system.queue.inc b/core/modules/system/system.queue.inc
new file mode 100644
index 00000000000..00d39406093
--- /dev/null
+++ b/core/modules/system/system.queue.inc
@@ -0,0 +1,371 @@
+<?php
+
+/**
+ * @file
+ * Queue functionality.
+ */
+
+/**
+ * @defgroup queue Queue operations
+ * @{
+ * Queue items to allow later processing.
+ *
+ * The queue system allows placing items in a queue and processing them later.
+ * The system tries to ensure that only one consumer can process an item.
+ *
+ * Before a queue can be used it needs to be created by
+ * DrupalQueueInterface::createQueue().
+ *
+ * Items can be added to the queue by passing an arbitrary data object to
+ * DrupalQueueInterface::createItem().
+ *
+ * To process an item, call DrupalQueueInterface::claimItem() and specify how
+ * long you want to have a lease for working on that item. When finished
+ * processing, the item needs to be deleted by calling
+ * DrupalQueueInterface::deleteItem(). If the consumer dies, the item will be
+ * made available again by the DrupalQueueInterface implementation once the
+ * lease expires. Another consumer will then be able to receive it when calling
+ * DrupalQueueInterface::claimItem(). Due to this, the processing code should
+ * be aware that an item might be handed over for processing more than once.
+ *
+ * The $item object used by the DrupalQueueInterface can contain arbitrary
+ * metadata depending on the implementation. Systems using the interface should
+ * only rely on the data property which will contain the information passed to
+ * DrupalQueueInterface::createItem(). The full queue item returned by
+ * DrupalQueueInterface::claimItem() needs to be passed to
+ * DrupalQueueInterface::deleteItem() once processing is completed.
+ *
+ * There are two kinds of queue backends available: reliable, which preserves
+ * the order of messages and guarantees that every item will be executed at
+ * least once. The non-reliable kind only does a best effort to preserve order
+ * in messages and to execute them at least once but there is a small chance
+ * that some items get lost. For example, some distributed back-ends like
+ * Amazon SQS will be managing jobs for a large set of producers and consumers
+ * where a strict FIFO ordering will likely not be preserved. Another example
+ * would be an in-memory queue backend which might lose items if it crashes.
+ * However, such a backend would be able to deal with significantly more writes
+ * than a reliable queue and for many tasks this is more important. See
+ * aggregator_cron() for an example of how can this not be a problem. Another
+ * example is doing Twitter statistics -- the small possibility of losing a few
+ * items is insignificant next to power of the queue being able to keep up with
+ * writes. As described in the processing section, regardless of the queue
+ * being reliable or not, the processing code should be aware that an item
+ * might be handed over for processing more than once (because the processing
+ * code might time out before it finishes).
+ */
+
+/**
+ * Factory class for interacting with queues.
+ */
+class DrupalQueue {
+ /**
+ * Returns the queue object for a given name.
+ *
+ * The following variables can be set by variable_set or $conf overrides:
+ * - queue_class_$name: the class to be used for the queue $name.
+ * - queue_default_class: the class to use when queue_class_$name is not
+ * defined. Defaults to SystemQueue, a reliable backend using SQL.
+ * - queue_default_reliable_class: the class to use when queue_class_$name is
+ * not defined and the queue_default_class is not reliable. Defaults to
+ * SystemQueue.
+ *
+ * @param $name
+ * Arbitrary string. The name of the queue to work with.
+ * @param $reliable
+ * TRUE if the ordering of items and guaranteeing every item executes at
+ * least once is important, FALSE if scalability is the main concern.
+ *
+ * @return
+ * The queue object for a given name.
+ */
+ public static function get($name, $reliable = FALSE) {
+ static $queues;
+ if (!isset($queues[$name])) {
+ $class = variable_get('queue_class_' . $name, NULL);
+ if (!$class) {
+ $class = variable_get('queue_default_class', 'SystemQueue');
+ }
+ $object = new $class($name);
+ if ($reliable && !$object instanceof DrupalReliableQueueInterface) {
+ $class = variable_get('queue_default_reliable_class', 'SystemQueue');
+ $object = new $class($name);
+ }
+ $queues[$name] = $object;
+ }
+ return $queues[$name];
+ }
+}
+
+interface DrupalQueueInterface {
+ /**
+ * Start working with a queue.
+ *
+ * @param $name
+ * Arbitrary string. The name of the queue to work with.
+ */
+ public function __construct($name);
+
+ /**
+ * Add a queue item and store it directly to the queue.
+ *
+ * @param $data
+ * Arbitrary data to be associated with the new task in the queue.
+ * @return
+ * TRUE if the item was successfully created and was (best effort) added
+ * to the queue, otherwise FALSE. We don't guarantee the item was
+ * committed to disk etc, but as far as we know, the item is now in the
+ * queue.
+ */
+ public function createItem($data);
+
+ /**
+ * Retrieve the number of items in the queue.
+ *
+ * This is intended to provide a "best guess" count of the number of items in
+ * the queue. Depending on the implementation and the setup, the accuracy of
+ * the results of this function may vary.
+ *
+ * e.g. On a busy system with a large number of consumers and items, the
+ * result might only be valid for a fraction of a second and not provide an
+ * accurate representation.
+ *
+ * @return
+ * An integer estimate of the number of items in the queue.
+ */
+ public function numberOfItems();
+
+ /**
+ * Claim an item in the queue for processing.
+ *
+ * @param $lease_time
+ * How long the processing is expected to take in seconds, defaults to an
+ * hour. After this lease expires, the item will be reset and another
+ * consumer can claim the item. For idempotent tasks (which can be run
+ * multiple times without side effects), shorter lease times would result
+ * in lower latency in case a consumer fails. For tasks that should not be
+ * run more than once (non-idempotent), a larger lease time will make it
+ * more rare for a given task to run multiple times in cases of failure,
+ * at the cost of higher latency.
+ * @return
+ * On success we return an item object. If the queue is unable to claim an
+ * item it returns false. This implies a best effort to retrieve an item
+ * and either the queue is empty or there is some other non-recoverable
+ * problem.
+ */
+ public function claimItem($lease_time = 3600);
+
+ /**
+ * Delete a finished item from the queue.
+ *
+ * @param $item
+ * The item returned by DrupalQueueInterface::claimItem().
+ */
+ public function deleteItem($item);
+
+ /**
+ * Release an item that the worker could not process, so another
+ * worker can come in and process it before the timeout expires.
+ *
+ * @param $item
+ * @return boolean
+ */
+ public function releaseItem($item);
+
+ /**
+ * Create a queue.
+ *
+ * Called during installation and should be used to perform any necessary
+ * initialization operations. This should not be confused with the
+ * constructor for these objects, which is called every time an object is
+ * instantiated to operate on a queue. This operation is only needed the
+ * first time a given queue is going to be initialized (for example, to make
+ * a new database table or directory to hold tasks for the queue -- it
+ * depends on the queue implementation if this is necessary at all).
+ */
+ public function createQueue();
+
+ /**
+ * Delete a queue and every item in the queue.
+ */
+ public function deleteQueue();
+}
+
+/**
+ * Reliable queue interface.
+ *
+ * Classes implementing this interface preserve the order of messages and
+ * guarantee that every item will be executed at least once.
+ */
+interface DrupalReliableQueueInterface extends DrupalQueueInterface {
+}
+
+/**
+ * Default queue implementation.
+ */
+class SystemQueue implements DrupalReliableQueueInterface {
+ /**
+ * The name of the queue this instance is working with.
+ *
+ * @var string
+ */
+ protected $name;
+
+ public function __construct($name) {
+ $this->name = $name;
+ }
+
+ public function createItem($data) {
+ // During a Drupal 6.x to 8.x update, drupal_get_schema() does not contain
+ // the queue table yet, so we cannot rely on drupal_write_record().
+ $query = db_insert('queue')
+ ->fields(array(
+ 'name' => $this->name,
+ 'data' => serialize($data),
+ // We cannot rely on REQUEST_TIME because many items might be created
+ // by a single request which takes longer than 1 second.
+ 'created' => time(),
+ ));
+ return (bool) $query->execute();
+ }
+
+ public function numberOfItems() {
+ return db_query('SELECT COUNT(item_id) FROM {queue} WHERE name = :name', array(':name' => $this->name))->fetchField();
+ }
+
+ public function claimItem($lease_time = 30) {
+ // Claim an item by updating its expire fields. If claim is not successful
+ // another thread may have claimed the item in the meantime. Therefore loop
+ // until an item is successfully claimed or we are reasonably sure there
+ // are no unclaimed items left.
+ while (TRUE) {
+ $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created ASC', 0, 1, array(':name' => $this->name))->fetchObject();
+ if ($item) {
+ // Try to update the item. Only one thread can succeed in UPDATEing the
+ // same row. We cannot rely on REQUEST_TIME because items might be
+ // claimed by a single consumer which runs longer than 1 second. If we
+ // continue to use REQUEST_TIME instead of the current time(), we steal
+ // time from the lease, and will tend to reset items before the lease
+ // should really expire.
+ $update = db_update('queue')
+ ->fields(array(
+ 'expire' => time() + $lease_time,
+ ))
+ ->condition('item_id', $item->item_id)
+ ->condition('expire', 0);
+ // If there are affected rows, this update succeeded.
+ if ($update->execute()) {
+ $item->data = unserialize($item->data);
+ return $item;
+ }
+ }
+ else {
+ // No items currently available to claim.
+ return FALSE;
+ }
+ }
+ }
+
+ public function releaseItem($item) {
+ $update = db_update('queue')
+ ->fields(array(
+ 'expire' => 0,
+ ))
+ ->condition('item_id', $item->item_id);
+ return $update->execute();
+ }
+
+ public function deleteItem($item) {
+ db_delete('queue')
+ ->condition('item_id', $item->item_id)
+ ->execute();
+ }
+
+ public function createQueue() {
+ // All tasks are stored in a single database table (which is created when
+ // Drupal is first installed) so there is nothing we need to do to create
+ // a new queue.
+ }
+
+ public function deleteQueue() {
+ db_delete('queue')
+ ->condition('name', $this->name)
+ ->execute();
+ }
+}
+
+/**
+ * Static queue implementation.
+ *
+ * This allows "undelayed" variants of processes relying on the Queue
+ * interface. The queue data resides in memory. It should only be used for
+ * items that will be queued and dequeued within a given page request.
+ */
+class MemoryQueue implements DrupalQueueInterface {
+ /**
+ * The queue data.
+ *
+ * @var array
+ */
+ protected $queue;
+
+ /**
+ * Counter for item ids.
+ *
+ * @var int
+ */
+ protected $id_sequence;
+
+ public function __construct($name) {
+ $this->queue = array();
+ $this->id_sequence = 0;
+ }
+
+ public function createItem($data) {
+ $item = new stdClass();
+ $item->item_id = $this->id_sequence++;
+ $item->data = $data;
+ $item->created = time();
+ $item->expire = 0;
+ $this->queue[$item->item_id] = $item;
+ }
+
+ public function numberOfItems() {
+ return count($this->queue);
+ }
+
+ public function claimItem($lease_time = 30) {
+ foreach ($this->queue as $key => $item) {
+ if ($item->expire == 0) {
+ $item->expire = time() + $lease_time;
+ $this->queue[$key] = $item;
+ return $item;
+ }
+ }
+ return FALSE;
+ }
+
+ public function deleteItem($item) {
+ unset($this->queue[$item->item_id]);
+ }
+
+ public function releaseItem($item) {
+ if (isset($this->queue[$item->item_id]) && $this->queue[$item->item_id]->expire != 0) {
+ $this->queue[$item->item_id]->expire = 0;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ public function createQueue() {
+ // Nothing needed here.
+ }
+
+ public function deleteQueue() {
+ $this->queue = array();
+ $this->id_sequence = 0;
+ }
+}
+
+/**
+ * @} End of "defgroup queue".
+ */
diff --git a/core/modules/system/system.tar.inc b/core/modules/system/system.tar.inc
new file mode 100644
index 00000000000..32bf7f066e5
--- /dev/null
+++ b/core/modules/system/system.tar.inc
@@ -0,0 +1,1892 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * File::CSV
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 1997-2008,
+ * Vincent Blavet <vincent@phpconcept.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * @category File_Formats
+ * @package Archive_Tar
+ * @author Vincent Blavet <vincent@phpconcept.net>
+ * @copyright 1997-2008 The Authors
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: Id: Tar.php,v 1.43 2008/10/30 17:58:42 dufuz Exp
+ * @link http://pear.php.net/package/Archive_Tar
+ */
+
+//require_once 'PEAR.php';
+//
+//
+define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
+define ('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
+
+/**
+* Creates a (compressed) Tar archive
+*
+* @author Vincent Blavet <vincent@phpconcept.net>
+* @version Revision: 1.43
+* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+* @package Archive_Tar
+*/
+class Archive_Tar // extends PEAR
+{
+ /**
+ * @var string Name of the Tar
+ */
+ var $_tarname='';
+
+ /**
+ * @var boolean if true, the Tar file will be gzipped
+ */
+ var $_compress=false;
+
+ /**
+ * @var string Type of compression : 'none', 'gz' or 'bz2'
+ */
+ var $_compress_type='none';
+
+ /**
+ * @var string Explode separator
+ */
+ var $_separator=' ';
+
+ /**
+ * @var file descriptor
+ */
+ var $_file=0;
+
+ /**
+ * @var string Local Tar name of a remote Tar (http:// or ftp://)
+ */
+ var $_temp_tarname='';
+
+ // {{{ constructor
+ /**
+ * Archive_Tar Class constructor. This flavour of the constructor only
+ * declare a new Archive_Tar object, identifying it by the name of the
+ * tar file.
+ * If the compress argument is set the tar will be read or created as a
+ * gzip or bz2 compressed TAR file.
+ *
+ * @param string $p_tarname The name of the tar archive to create
+ * @param string $p_compress can be null, 'gz' or 'bz2'. This
+ * parameter indicates if gzip or bz2 compression
+ * is required. For compatibility reason the
+ * boolean value 'true' means 'gz'.
+ * @access public
+ */
+// function Archive_Tar($p_tarname, $p_compress = null)
+ function __construct($p_tarname, $p_compress = null)
+ {
+// $this->PEAR();
+ $this->_compress = false;
+ $this->_compress_type = 'none';
+ if (($p_compress === null) || ($p_compress == '')) {
+ if (@file_exists($p_tarname)) {
+ if ($fp = @fopen($p_tarname, "rb")) {
+ // look for gzip magic cookie
+ $data = fread($fp, 2);
+ fclose($fp);
+ if ($data == "\37\213") {
+ $this->_compress = true;
+ $this->_compress_type = 'gz';
+ // No sure it's enought for a magic code ....
+ } elseif ($data == "BZ") {
+ $this->_compress = true;
+ $this->_compress_type = 'bz2';
+ }
+ }
+ } else {
+ // probably a remote file or some file accessible
+ // through a stream interface
+ if (substr($p_tarname, -2) == 'gz') {
+ $this->_compress = true;
+ $this->_compress_type = 'gz';
+ } elseif ((substr($p_tarname, -3) == 'bz2') ||
+ (substr($p_tarname, -2) == 'bz')) {
+ $this->_compress = true;
+ $this->_compress_type = 'bz2';
+ }
+ }
+ } else {
+ if (($p_compress === true) || ($p_compress == 'gz')) {
+ $this->_compress = true;
+ $this->_compress_type = 'gz';
+ } else if ($p_compress == 'bz2') {
+ $this->_compress = true;
+ $this->_compress_type = 'bz2';
+ } else {
+ die("Unsupported compression type '$p_compress'\n".
+ "Supported types are 'gz' and 'bz2'.\n");
+ return false;
+ }
+ }
+ $this->_tarname = $p_tarname;
+ if ($this->_compress) { // assert zlib or bz2 extension support
+ if ($this->_compress_type == 'gz')
+ $extname = 'zlib';
+ else if ($this->_compress_type == 'bz2')
+ $extname = 'bz2';
+
+ if (!extension_loaded($extname)) {
+// PEAR::loadExtension($extname);
+ $this->loadExtension($extname);
+ }
+ if (!extension_loaded($extname)) {
+ die("The extension '$extname' couldn't be found.\n".
+ "Please make sure your version of PHP was built ".
+ "with '$extname' support.\n");
+ return false;
+ }
+ }
+ }
+ // }}}
+
+ /**
+ * OS independent PHP extension load. Remember to take care
+ * on the correct extension name for case sensitive OSes.
+ * The function is the copy of PEAR::loadExtension().
+ *
+ * @param string $ext The extension name
+ * @return bool Success or not on the dl() call
+ */
+ function loadExtension($ext)
+ {
+ if (!extension_loaded($ext)) {
+ // if either returns true dl() will produce a FATAL error, stop that
+ if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
+ return false;
+ }
+
+ if (OS_WINDOWS) {
+ $suffix = '.dll';
+ } elseif (PHP_OS == 'HP-UX') {
+ $suffix = '.sl';
+ } elseif (PHP_OS == 'AIX') {
+ $suffix = '.a';
+ } elseif (PHP_OS == 'OSX') {
+ $suffix = '.bundle';
+ } else {
+ $suffix = '.so';
+ }
+
+ return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+ }
+
+ return true;
+ }
+
+
+ // {{{ destructor
+// function _Archive_Tar()
+ function __destruct()
+ {
+ $this->_close();
+ // ----- Look for a local copy to delete
+ if ($this->_temp_tarname != '')
+ @drupal_unlink($this->_temp_tarname);
+// $this->_PEAR();
+ }
+ // }}}
+
+ // {{{ create()
+ /**
+ * This method creates the archive file and add the files / directories
+ * that are listed in $p_filelist.
+ * If a file with the same name exist and is writable, it is replaced
+ * by the new tar.
+ * The method return false and a PEAR error text.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * For each directory added in the archive, the files and
+ * sub-directories are also added.
+ * See also createModify() method for more details.
+ *
+ * @param array $p_filelist An array of filenames and directory names, or a
+ * single string with names separated by a single
+ * blank space.
+ * @return true on success, false on error.
+ * @see createModify()
+ * @access public
+ */
+ function create($p_filelist)
+ {
+ return $this->createModify($p_filelist, '', '');
+ }
+ // }}}
+
+ // {{{ add()
+ /**
+ * This method add the files / directories that are listed in $p_filelist in
+ * the archive. If the archive does not exist it is created.
+ * The method return false and a PEAR error text.
+ * The files and directories listed are only added at the end of the archive,
+ * even if a file with the same name is already archived.
+ * See also createModify() method for more details.
+ *
+ * @param array $p_filelist An array of filenames and directory names, or a
+ * single string with names separated by a single
+ * blank space.
+ * @return true on success, false on error.
+ * @see createModify()
+ * @access public
+ */
+ function add($p_filelist)
+ {
+ return $this->addModify($p_filelist, '', '');
+ }
+ // }}}
+
+ // {{{ extract()
+ function extract($p_path='')
+ {
+ return $this->extractModify($p_path, '');
+ }
+ // }}}
+
+ // {{{ listContent()
+ function listContent()
+ {
+ $v_list_detail = array();
+
+ if ($this->_openRead()) {
+ if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
+ unset($v_list_detail);
+ $v_list_detail = 0;
+ }
+ $this->_close();
+ }
+
+ return $v_list_detail;
+ }
+ // }}}
+
+ // {{{ createModify()
+ /**
+ * This method creates the archive file and add the files / directories
+ * that are listed in $p_filelist.
+ * If the file already exists and is writable, it is replaced by the
+ * new tar. It is a create and not an add. If the file exists and is
+ * read-only or is a directory it is not replaced. The method return
+ * false and a PEAR error text.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * The path indicated in $p_remove_dir will be removed from the
+ * memorized path of each file / directory listed when this path
+ * exists. By default nothing is removed (empty path '')
+ * The path indicated in $p_add_dir will be added at the beginning of
+ * the memorized path of each file / directory listed. However it can
+ * be set to empty ''. The adding of a path is done after the removing
+ * of path.
+ * The path add/remove ability enables the user to prepare an archive
+ * for extraction in a different path than the origin files are.
+ * See also addModify() method for file adding properties.
+ *
+ * @param array $p_filelist An array of filenames and directory names,
+ * or a single string with names separated by
+ * a single blank space.
+ * @param string $p_add_dir A string which contains a path to be added
+ * to the memorized path of each element in
+ * the list.
+ * @param string $p_remove_dir A string which contains a path to be
+ * removed from the memorized path of each
+ * element in the list, when relevant.
+ * @return boolean true on success, false on error.
+ * @access public
+ * @see addModify()
+ */
+ function createModify($p_filelist, $p_add_dir, $p_remove_dir='')
+ {
+ $v_result = true;
+
+ if (!$this->_openWrite())
+ return false;
+
+ if ($p_filelist != '') {
+ if (is_array($p_filelist))
+ $v_list = $p_filelist;
+ elseif (is_string($p_filelist))
+ $v_list = explode($this->_separator, $p_filelist);
+ else {
+ $this->_cleanFile();
+ $this->_error('Invalid file list');
+ return false;
+ }
+
+ $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
+ }
+
+ if ($v_result) {
+ $this->_writeFooter();
+ $this->_close();
+ } else
+ $this->_cleanFile();
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ addModify()
+ /**
+ * This method add the files / directories listed in $p_filelist at the
+ * end of the existing archive. If the archive does not yet exists it
+ * is created.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * The path indicated in $p_remove_dir will be removed from the
+ * memorized path of each file / directory listed when this path
+ * exists. By default nothing is removed (empty path '')
+ * The path indicated in $p_add_dir will be added at the beginning of
+ * the memorized path of each file / directory listed. However it can
+ * be set to empty ''. The adding of a path is done after the removing
+ * of path.
+ * The path add/remove ability enables the user to prepare an archive
+ * for extraction in a different path than the origin files are.
+ * If a file/dir is already in the archive it will only be added at the
+ * end of the archive. There is no update of the existing archived
+ * file/dir. However while extracting the archive, the last file will
+ * replace the first one. This results in a none optimization of the
+ * archive size.
+ * If a file/dir does not exist the file/dir is ignored. However an
+ * error text is send to PEAR error.
+ * If a file/dir is not readable the file/dir is ignored. However an
+ * error text is send to PEAR error.
+ *
+ * @param array $p_filelist An array of filenames and directory
+ * names, or a single string with names
+ * separated by a single blank space.
+ * @param string $p_add_dir A string which contains a path to be
+ * added to the memorized path of each
+ * element in the list.
+ * @param string $p_remove_dir A string which contains a path to be
+ * removed from the memorized path of
+ * each element in the list, when
+ * relevant.
+ * @return true on success, false on error.
+ * @access public
+ */
+ function addModify($p_filelist, $p_add_dir, $p_remove_dir='')
+ {
+ $v_result = true;
+
+ if (!$this->_isArchive())
+ $v_result = $this->createModify($p_filelist, $p_add_dir,
+ $p_remove_dir);
+ else {
+ if (is_array($p_filelist))
+ $v_list = $p_filelist;
+ elseif (is_string($p_filelist))
+ $v_list = explode($this->_separator, $p_filelist);
+ else {
+ $this->_error('Invalid file list');
+ return false;
+ }
+
+ $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ addString()
+ /**
+ * This method add a single string as a file at the
+ * end of the existing archive. If the archive does not yet exists it
+ * is created.
+ *
+ * @param string $p_filename A string which contains the full
+ * filename path that will be associated
+ * with the string.
+ * @param string $p_string The content of the file added in
+ * the archive.
+ * @return true on success, false on error.
+ * @access public
+ */
+ function addString($p_filename, $p_string)
+ {
+ $v_result = true;
+
+ if (!$this->_isArchive()) {
+ if (!$this->_openWrite()) {
+ return false;
+ }
+ $this->_close();
+ }
+
+ if (!$this->_openAppend())
+ return false;
+
+ // Need to check the get back to the temporary file ? ....
+ $v_result = $this->_addString($p_filename, $p_string);
+
+ $this->_writeFooter();
+
+ $this->_close();
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ extractModify()
+ /**
+ * This method extract all the content of the archive in the directory
+ * indicated by $p_path. When relevant the memorized path of the
+ * files/dir can be modified by removing the $p_remove_path path at the
+ * beginning of the file/dir path.
+ * While extracting a file, if the directory path does not exists it is
+ * created.
+ * While extracting a file, if the file already exists it is replaced
+ * without looking for last modification date.
+ * While extracting a file, if the file already exists and is write
+ * protected, the extraction is aborted.
+ * While extracting a file, if a directory with the same name already
+ * exists, the extraction is aborted.
+ * While extracting a directory, if a file with the same name already
+ * exists, the extraction is aborted.
+ * While extracting a file/directory if the destination directory exist
+ * and is write protected, or does not exist but can not be created,
+ * the extraction is aborted.
+ * If after extraction an extracted file does not show the correct
+ * stored file size, the extraction is aborted.
+ * When the extraction is aborted, a PEAR error text is set and false
+ * is returned. However the result can be a partial extraction that may
+ * need to be manually cleaned.
+ *
+ * @param string $p_path The path of the directory where the
+ * files/dir need to by extracted.
+ * @param string $p_remove_path Part of the memorized path that can be
+ * removed if present at the beginning of
+ * the file/dir path.
+ * @return boolean true on success, false on error.
+ * @access public
+ * @see extractList()
+ */
+ function extractModify($p_path, $p_remove_path)
+ {
+ $v_result = true;
+ $v_list_detail = array();
+
+ if ($v_result = $this->_openRead()) {
+ $v_result = $this->_extractList($p_path, $v_list_detail,
+ "complete", 0, $p_remove_path);
+ $this->_close();
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ extractInString()
+ /**
+ * This method extract from the archive one file identified by $p_filename.
+ * The return value is a string with the file content, or NULL on error.
+ * @param string $p_filename The path of the file to extract in a string.
+ * @return a string with the file content or NULL.
+ * @access public
+ */
+ function extractInString($p_filename)
+ {
+ if ($this->_openRead()) {
+ $v_result = $this->_extractInString($p_filename);
+ $this->_close();
+ } else {
+ $v_result = NULL;
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ extractList()
+ /**
+ * This method extract from the archive only the files indicated in the
+ * $p_filelist. These files are extracted in the current directory or
+ * in the directory indicated by the optional $p_path parameter.
+ * If indicated the $p_remove_path can be used in the same way as it is
+ * used in extractModify() method.
+ * @param array $p_filelist An array of filenames and directory names,
+ * or a single string with names separated
+ * by a single blank space.
+ * @param string $p_path The path of the directory where the
+ * files/dir need to by extracted.
+ * @param string $p_remove_path Part of the memorized path that can be
+ * removed if present at the beginning of
+ * the file/dir path.
+ * @return true on success, false on error.
+ * @access public
+ * @see extractModify()
+ */
+ function extractList($p_filelist, $p_path='', $p_remove_path='')
+ {
+ $v_result = true;
+ $v_list_detail = array();
+
+ if (is_array($p_filelist))
+ $v_list = $p_filelist;
+ elseif (is_string($p_filelist))
+ $v_list = explode($this->_separator, $p_filelist);
+ else {
+ $this->_error('Invalid string list');
+ return false;
+ }
+
+ if ($v_result = $this->_openRead()) {
+ $v_result = $this->_extractList($p_path, $v_list_detail, "partial",
+ $v_list, $p_remove_path);
+ $this->_close();
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ setAttribute()
+ /**
+ * This method set specific attributes of the archive. It uses a variable
+ * list of parameters, in the format attribute code + attribute values :
+ * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
+ * @param mixed $argv variable list of attributes and values
+ * @return true on success, false on error.
+ * @access public
+ */
+ function setAttribute()
+ {
+ $v_result = true;
+
+ // ----- Get the number of variable list of arguments
+ if (($v_size = func_num_args()) == 0) {
+ return true;
+ }
+
+ // ----- Get the arguments
+ $v_att_list = &func_get_args();
+
+ // ----- Read the attributes
+ $i=0;
+ while ($i<$v_size) {
+
+ // ----- Look for next option
+ switch ($v_att_list[$i]) {
+ // ----- Look for options that request a string value
+ case ARCHIVE_TAR_ATT_SEPARATOR :
+ // ----- Check the number of parameters
+ if (($i+1) >= $v_size) {
+ $this->_error('Invalid number of parameters for '
+ .'attribute ARCHIVE_TAR_ATT_SEPARATOR');
+ return false;
+ }
+
+ // ----- Get the value
+ $this->_separator = $v_att_list[$i+1];
+ $i++;
+ break;
+
+ default :
+ $this->_error('Unknow attribute code '.$v_att_list[$i].'');
+ return false;
+ }
+
+ // ----- Next attribute
+ $i++;
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ _error()
+ function _error($p_message)
+ {
+ // ----- To be completed
+// $this->raiseError($p_message);
+ throw new Exception($p_message);
+ }
+ // }}}
+
+ // {{{ _warning()
+ function _warning($p_message)
+ {
+ // ----- To be completed
+// $this->raiseError($p_message);
+ throw new Exception($p_message);
+ }
+ // }}}
+
+ // {{{ _isArchive()
+ function _isArchive($p_filename=NULL)
+ {
+ if ($p_filename == NULL) {
+ $p_filename = $this->_tarname;
+ }
+ clearstatcache();
+ return @is_file($p_filename) && !@is_link($p_filename);
+ }
+ // }}}
+
+ // {{{ _openWrite()
+ function _openWrite()
+ {
+ if ($this->_compress_type == 'gz')
+ $this->_file = @gzopen($this->_tarname, "wb9");
+ else if ($this->_compress_type == 'bz2')
+ $this->_file = @bzopen($this->_tarname, "w");
+ else if ($this->_compress_type == 'none')
+ $this->_file = @fopen($this->_tarname, "wb");
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ if ($this->_file == 0) {
+ $this->_error('Unable to open in write mode \''
+ .$this->_tarname.'\'');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _openRead()
+ function _openRead()
+ {
+ if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
+
+ // ----- Look if a local copy need to be done
+ if ($this->_temp_tarname == '') {
+ $this->_temp_tarname = uniqid('tar').'.tmp';
+ if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
+ $this->_error('Unable to open in read mode \''
+ .$this->_tarname.'\'');
+ $this->_temp_tarname = '';
+ return false;
+ }
+ if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
+ $this->_error('Unable to open in write mode \''
+ .$this->_temp_tarname.'\'');
+ $this->_temp_tarname = '';
+ return false;
+ }
+ while ($v_data = @fread($v_file_from, 1024))
+ @fwrite($v_file_to, $v_data);
+ @fclose($v_file_from);
+ @fclose($v_file_to);
+ }
+
+ // ----- File to open if the local copy
+ $v_filename = $this->_temp_tarname;
+
+ } else
+ // ----- File to open if the normal Tar file
+ $v_filename = $this->_tarname;
+
+ if ($this->_compress_type == 'gz')
+ $this->_file = @gzopen($v_filename, "rb");
+ else if ($this->_compress_type == 'bz2')
+ $this->_file = @bzopen($v_filename, "r");
+ else if ($this->_compress_type == 'none')
+ $this->_file = @fopen($v_filename, "rb");
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ if ($this->_file == 0) {
+ $this->_error('Unable to open in read mode \''.$v_filename.'\'');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _openReadWrite()
+ function _openReadWrite()
+ {
+ if ($this->_compress_type == 'gz')
+ $this->_file = @gzopen($this->_tarname, "r+b");
+ else if ($this->_compress_type == 'bz2') {
+ $this->_error('Unable to open bz2 in read/write mode \''
+ .$this->_tarname.'\' (limitation of bz2 extension)');
+ return false;
+ } else if ($this->_compress_type == 'none')
+ $this->_file = @fopen($this->_tarname, "r+b");
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ if ($this->_file == 0) {
+ $this->_error('Unable to open in read/write mode \''
+ .$this->_tarname.'\'');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _close()
+ function _close()
+ {
+ //if (isset($this->_file)) {
+ if (is_resource($this->_file)) {
+ if ($this->_compress_type == 'gz')
+ @gzclose($this->_file);
+ else if ($this->_compress_type == 'bz2')
+ @bzclose($this->_file);
+ else if ($this->_compress_type == 'none')
+ @fclose($this->_file);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ $this->_file = 0;
+ }
+
+ // ----- Look if a local copy need to be erase
+ // Note that it might be interesting to keep the url for a time : ToDo
+ if ($this->_temp_tarname != '') {
+ @drupal_unlink($this->_temp_tarname);
+ $this->_temp_tarname = '';
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _cleanFile()
+ function _cleanFile()
+ {
+ $this->_close();
+
+ // ----- Look for a local copy
+ if ($this->_temp_tarname != '') {
+ // ----- Remove the local copy but not the remote tarname
+ @drupal_unlink($this->_temp_tarname);
+ $this->_temp_tarname = '';
+ } else {
+ // ----- Remove the local tarname file
+ @drupal_unlink($this->_tarname);
+ }
+ $this->_tarname = '';
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeBlock()
+ function _writeBlock($p_binary_data, $p_len=null)
+ {
+ if (is_resource($this->_file)) {
+ if ($p_len === null) {
+ if ($this->_compress_type == 'gz')
+ @gzputs($this->_file, $p_binary_data);
+ else if ($this->_compress_type == 'bz2')
+ @bzwrite($this->_file, $p_binary_data);
+ else if ($this->_compress_type == 'none')
+ @fputs($this->_file, $p_binary_data);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+ } else {
+ if ($this->_compress_type == 'gz')
+ @gzputs($this->_file, $p_binary_data, $p_len);
+ else if ($this->_compress_type == 'bz2')
+ @bzwrite($this->_file, $p_binary_data, $p_len);
+ else if ($this->_compress_type == 'none')
+ @fputs($this->_file, $p_binary_data, $p_len);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ }
+ }
+ return true;
+ }
+ // }}}
+
+ // {{{ _readBlock()
+ function _readBlock()
+ {
+ $v_block = null;
+ if (is_resource($this->_file)) {
+ if ($this->_compress_type == 'gz')
+ $v_block = @gzread($this->_file, 512);
+ else if ($this->_compress_type == 'bz2')
+ $v_block = @bzread($this->_file, 512);
+ else if ($this->_compress_type == 'none')
+ $v_block = @fread($this->_file, 512);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+ }
+ return $v_block;
+ }
+ // }}}
+
+ // {{{ _jumpBlock()
+ function _jumpBlock($p_len=null)
+ {
+ if (is_resource($this->_file)) {
+ if ($p_len === null)
+ $p_len = 1;
+
+ if ($this->_compress_type == 'gz') {
+ @gzseek($this->_file, gztell($this->_file)+($p_len*512));
+ }
+ else if ($this->_compress_type == 'bz2') {
+ // ----- Replace missing bztell() and bzseek()
+ for ($i=0; $i<$p_len; $i++)
+ $this->_readBlock();
+ } else if ($this->_compress_type == 'none')
+ @fseek($this->_file, ftell($this->_file)+($p_len*512));
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ }
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeFooter()
+ function _writeFooter()
+ {
+ if (is_resource($this->_file)) {
+ // ----- Write the last 0 filled block for end of archive
+ $v_binary_data = pack('a1024', '');
+ $this->_writeBlock($v_binary_data);
+ }
+ return true;
+ }
+ // }}}
+
+ // {{{ _addList()
+ function _addList($p_list, $p_add_dir, $p_remove_dir)
+ {
+ $v_result=true;
+ $v_header = array();
+
+ // ----- Remove potential windows directory separator
+ $p_add_dir = $this->_translateWinPath($p_add_dir);
+ $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
+
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
+ }
+
+ if (sizeof($p_list) == 0)
+ return true;
+
+ foreach ($p_list as $v_filename) {
+ if (!$v_result) {
+ break;
+ }
+
+ // ----- Skip the current tar name
+ if ($v_filename == $this->_tarname)
+ continue;
+
+ if ($v_filename == '')
+ continue;
+
+ if (!file_exists($v_filename)) {
+ $this->_warning("File '$v_filename' does not exist");
+ continue;
+ }
+
+ // ----- Add the file or directory header
+ if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
+ return false;
+
+ if (@is_dir($v_filename) && !@is_link($v_filename)) {
+ if (!($p_hdir = opendir($v_filename))) {
+ $this->_warning("Directory '$v_filename' can not be read");
+ continue;
+ }
+ while (false !== ($p_hitem = readdir($p_hdir))) {
+ if (($p_hitem != '.') && ($p_hitem != '..')) {
+ if ($v_filename != ".")
+ $p_temp_list[0] = $v_filename.'/'.$p_hitem;
+ else
+ $p_temp_list[0] = $p_hitem;
+
+ $v_result = $this->_addList($p_temp_list,
+ $p_add_dir,
+ $p_remove_dir);
+ }
+ }
+
+ unset($p_temp_list);
+ unset($p_hdir);
+ unset($p_hitem);
+ }
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ _addFile()
+ function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir)
+ {
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
+ }
+
+ if ($p_filename == '') {
+ $this->_error('Invalid file name');
+ return false;
+ }
+
+ // ----- Calculate the stored filename
+ $p_filename = $this->_translateWinPath($p_filename, false);;
+ $v_stored_filename = $p_filename;
+ if (strcmp($p_filename, $p_remove_dir) == 0) {
+ return true;
+ }
+ if ($p_remove_dir != '') {
+ if (substr($p_remove_dir, -1) != '/')
+ $p_remove_dir .= '/';
+
+ if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
+ $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
+ }
+ $v_stored_filename = $this->_translateWinPath($v_stored_filename);
+ if ($p_add_dir != '') {
+ if (substr($p_add_dir, -1) == '/')
+ $v_stored_filename = $p_add_dir.$v_stored_filename;
+ else
+ $v_stored_filename = $p_add_dir.'/'.$v_stored_filename;
+ }
+
+ $v_stored_filename = $this->_pathReduction($v_stored_filename);
+
+ if ($this->_isArchive($p_filename)) {
+ if (($v_file = @fopen($p_filename, "rb")) == 0) {
+ $this->_warning("Unable to open file '".$p_filename
+ ."' in binary read mode");
+ return true;
+ }
+
+ if (!$this->_writeHeader($p_filename, $v_stored_filename))
+ return false;
+
+ while (($v_buffer = fread($v_file, 512)) != '') {
+ $v_binary_data = pack("a512", "$v_buffer");
+ $this->_writeBlock($v_binary_data);
+ }
+
+ fclose($v_file);
+
+ } else {
+ // ----- Only header for dir
+ if (!$this->_writeHeader($p_filename, $v_stored_filename))
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _addString()
+ function _addString($p_filename, $p_string)
+ {
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
+ }
+
+ if ($p_filename == '') {
+ $this->_error('Invalid file name');
+ return false;
+ }
+
+ // ----- Calculate the stored filename
+ $p_filename = $this->_translateWinPath($p_filename, false);;
+
+ if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
+ time(), 384, "", 0, 0))
+ return false;
+
+ $i=0;
+ while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') {
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeHeader()
+ function _writeHeader($p_filename, $p_stored_filename)
+ {
+ if ($p_stored_filename == '')
+ $p_stored_filename = $p_filename;
+ $v_reduce_filename = $this->_pathReduction($p_stored_filename);
+
+ if (strlen($v_reduce_filename) > 99) {
+ if (!$this->_writeLongHeader($v_reduce_filename))
+ return false;
+ }
+
+ $v_info = lstat($p_filename);
+ $v_uid = sprintf("%6s ", DecOct($v_info[4]));
+ $v_gid = sprintf("%6s ", DecOct($v_info[5]));
+ $v_perms = sprintf("%6s ", DecOct($v_info['mode']));
+
+ $v_mtime = sprintf("%11s", DecOct($v_info['mode']));
+
+ $v_linkname = '';
+
+ if (@is_link($p_filename)) {
+ $v_typeflag = '2';
+ $v_linkname = readlink($p_filename);
+ $v_size = sprintf("%11s ", DecOct(0));
+ } elseif (@is_dir($p_filename)) {
+ $v_typeflag = "5";
+ $v_size = sprintf("%11s ", DecOct(0));
+ } else {
+ $v_typeflag = '';
+ clearstatcache();
+ $v_size = sprintf("%11s ", DecOct($v_info['size']));
+ }
+
+ $v_magic = '';
+
+ $v_version = '';
+
+ $v_uname = '';
+
+ $v_gname = '';
+
+ $v_devmajor = '';
+
+ $v_devminor = '';
+
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12A12",
+ $v_reduce_filename, $v_perms, $v_uid,
+ $v_gid, $v_size, $v_mtime);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156, $j=0; $i<512; $i++, $j++)
+ $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+ // ----- Write the first 148 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum
+ $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->_writeBlock($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_last, 356);
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeHeaderBlock()
+ function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
+ $p_type='', $p_uid=0, $p_gid=0)
+ {
+ $p_filename = $this->_pathReduction($p_filename);
+
+ if (strlen($p_filename) > 99) {
+ if (!$this->_writeLongHeader($p_filename))
+ return false;
+ }
+
+ if ($p_type == "5") {
+ $v_size = sprintf("%11s ", DecOct(0));
+ } else {
+ $v_size = sprintf("%11s ", DecOct($p_size));
+ }
+
+ $v_uid = sprintf("%6s ", DecOct($p_uid));
+ $v_gid = sprintf("%6s ", DecOct($p_gid));
+ $v_perms = sprintf("%6s ", DecOct($p_perms));
+
+ $v_mtime = sprintf("%11s", DecOct($p_mtime));
+
+ $v_linkname = '';
+
+ $v_magic = '';
+
+ $v_version = '';
+
+ $v_uname = '';
+
+ $v_gname = '';
+
+ $v_devmajor = '';
+
+ $v_devminor = '';
+
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12A12",
+ $p_filename, $v_perms, $v_uid, $v_gid,
+ $v_size, $v_mtime);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $p_type, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156, $j=0; $i<512; $i++, $j++)
+ $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+ // ----- Write the first 148 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum
+ $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->_writeBlock($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_last, 356);
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeLongHeader()
+ function _writeLongHeader($p_filename)
+ {
+ $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
+
+ $v_typeflag = 'L';
+
+ $v_linkname = '';
+
+ $v_magic = '';
+
+ $v_version = '';
+
+ $v_uname = '';
+
+ $v_gname = '';
+
+ $v_devmajor = '';
+
+ $v_devminor = '';
+
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12A12",
+ '././@LongLink', 0, 0, 0, $v_size, 0);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156, $j=0; $i<512; $i++, $j++)
+ $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+ // ----- Write the first 148 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum
+ $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->_writeBlock($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_last, 356);
+
+ // ----- Write the filename as content of the block
+ $i=0;
+ while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') {
+ $v_binary_data = pack("a512", "$v_buffer");
+ $this->_writeBlock($v_binary_data);
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _readHeader()
+ function _readHeader($v_binary_data, &$v_header)
+ {
+ if (strlen($v_binary_data)==0) {
+ $v_header['filename'] = '';
+ return true;
+ }
+
+ if (strlen($v_binary_data) != 512) {
+ $v_header['filename'] = '';
+ $this->_error('Invalid block size : '.strlen($v_binary_data));
+ return false;
+ }
+
+ if (!is_array($v_header)) {
+ $v_header = array();
+ }
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum+=ord(substr($v_binary_data,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156; $i<512; $i++)
+ $v_checksum+=ord(substr($v_binary_data,$i,1));
+
+ $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/"
+ ."a8checksum/a1typeflag/a100link/a6magic/a2version/"
+ ."a32uname/a32gname/a8devmajor/a8devminor",
+ $v_binary_data);
+
+ // ----- Extract the checksum
+ $v_header['checksum'] = OctDec(trim($v_data['checksum']));
+ if ($v_header['checksum'] != $v_checksum) {
+ $v_header['filename'] = '';
+
+ // ----- Look for last block (empty block)
+ if (($v_checksum == 256) && ($v_header['checksum'] == 0))
+ return true;
+
+ $this->_error('Invalid checksum for file "'.$v_data['filename']
+ .'" : '.$v_checksum.' calculated, '
+ .$v_header['checksum'].' expected');
+ return false;
+ }
+
+ // ----- Extract the properties
+ $v_header['filename'] = trim($v_data['filename']);
+ if ($this->_maliciousFilename($v_header['filename'])) {
+ $this->_error('Malicious .tar detected, file "' . $v_header['filename'] .
+ '" will not install in desired directory tree');
+ return false;
+ }
+ $v_header['mode'] = OctDec(trim($v_data['mode']));
+ $v_header['uid'] = OctDec(trim($v_data['uid']));
+ $v_header['gid'] = OctDec(trim($v_data['gid']));
+ $v_header['size'] = OctDec(trim($v_data['size']));
+ $v_header['mtime'] = OctDec(trim($v_data['mtime']));
+ if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
+ $v_header['size'] = 0;
+ }
+ $v_header['link'] = trim($v_data['link']);
+ /* ----- All these fields are removed form the header because
+ they do not carry interesting info
+ $v_header[magic] = trim($v_data[magic]);
+ $v_header[version] = trim($v_data[version]);
+ $v_header[uname] = trim($v_data[uname]);
+ $v_header[gname] = trim($v_data[gname]);
+ $v_header[devmajor] = trim($v_data[devmajor]);
+ $v_header[devminor] = trim($v_data[devminor]);
+ */
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _maliciousFilename()
+ /**
+ * Detect and report a malicious file name
+ *
+ * @param string $file
+ * @return bool
+ * @access private
+ */
+ function _maliciousFilename($file)
+ {
+ if (strpos($file, '/../') !== false) {
+ return true;
+ }
+ if (strpos($file, '../') === 0) {
+ return true;
+ }
+ return false;
+ }
+ // }}}
+
+ // {{{ _readLongHeader()
+ function _readLongHeader(&$v_header)
+ {
+ $v_filename = '';
+ $n = floor($v_header['size']/512);
+ for ($i=0; $i<$n; $i++) {
+ $v_content = $this->_readBlock();
+ $v_filename .= $v_content;
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ $v_filename .= $v_content;
+ }
+
+ // ----- Read the next header
+ $v_binary_data = $this->_readBlock();
+
+ if (!$this->_readHeader($v_binary_data, $v_header))
+ return false;
+
+ $v_filename = trim($v_filename);
+ $v_header['filename'] = $v_filename;
+ if ($this->_maliciousFilename($v_filename)) {
+ $this->_error('Malicious .tar detected, file "' . $v_filename .
+ '" will not install in desired directory tree');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _extractInString()
+ /**
+ * This method extract from the archive one file identified by $p_filename.
+ * The return value is a string with the file content, or NULL on error.
+ * @param string $p_filename The path of the file to extract in a string.
+ * @return a string with the file content or NULL.
+ * @access private
+ */
+ function _extractInString($p_filename)
+ {
+ $v_result_str = "";
+
+ While (strlen($v_binary_data = $this->_readBlock()) != 0)
+ {
+ if (!$this->_readHeader($v_binary_data, $v_header))
+ return NULL;
+
+ if ($v_header['filename'] == '')
+ continue;
+
+ // ----- Look for long filename
+ if ($v_header['typeflag'] == 'L') {
+ if (!$this->_readLongHeader($v_header))
+ return NULL;
+ }
+
+ if ($v_header['filename'] == $p_filename) {
+ if ($v_header['typeflag'] == "5") {
+ $this->_error('Unable to extract in string a directory '
+ .'entry {'.$v_header['filename'].'}');
+ return NULL;
+ } else {
+ $n = floor($v_header['size']/512);
+ for ($i=0; $i<$n; $i++) {
+ $v_result_str .= $this->_readBlock();
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ $v_result_str .= substr($v_content, 0,
+ ($v_header['size'] % 512));
+ }
+ return $v_result_str;
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size']/512)));
+ }
+ }
+
+ return NULL;
+ }
+ // }}}
+
+ // {{{ _extractList()
+ function _extractList($p_path, &$p_list_detail, $p_mode,
+ $p_file_list, $p_remove_path)
+ {
+ $v_result=true;
+ $v_nb = 0;
+ $v_extract_all = true;
+ $v_listing = false;
+
+ $p_path = $this->_translateWinPath($p_path, false);
+ if ($p_path == '' || (substr($p_path, 0, 1) != '/'
+ && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
+ $p_path = "./".$p_path;
+ }
+ $p_remove_path = $this->_translateWinPath($p_remove_path);
+
+ // ----- Look for path to remove format (should end by /)
+ if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
+ $p_remove_path .= '/';
+ $p_remove_path_size = strlen($p_remove_path);
+
+ switch ($p_mode) {
+ case "complete" :
+ $v_extract_all = TRUE;
+ $v_listing = FALSE;
+ break;
+ case "partial" :
+ $v_extract_all = FALSE;
+ $v_listing = FALSE;
+ break;
+ case "list" :
+ $v_extract_all = FALSE;
+ $v_listing = TRUE;
+ break;
+ default :
+ $this->_error('Invalid extract mode ('.$p_mode.')');
+ return false;
+ }
+
+ clearstatcache();
+
+ while (strlen($v_binary_data = $this->_readBlock()) != 0)
+ {
+ $v_extract_file = FALSE;
+ $v_extraction_stopped = 0;
+
+ if (!$this->_readHeader($v_binary_data, $v_header))
+ return false;
+
+ if ($v_header['filename'] == '') {
+ continue;
+ }
+
+ // ----- Look for long filename
+ if ($v_header['typeflag'] == 'L') {
+ if (!$this->_readLongHeader($v_header))
+ return false;
+ }
+
+ if ((!$v_extract_all) && (is_array($p_file_list))) {
+ // ----- By default no unzip if the file is not found
+ $v_extract_file = false;
+
+ for ($i=0; $i<sizeof($p_file_list); $i++) {
+ // ----- Look if it is a directory
+ if (substr($p_file_list[$i], -1) == '/') {
+ // ----- Look if the directory is in the filename path
+ if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
+ && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
+ == $p_file_list[$i])) {
+ $v_extract_file = TRUE;
+ break;
+ }
+ }
+
+ // ----- It is a file, so compare the file names
+ elseif ($p_file_list[$i] == $v_header['filename']) {
+ $v_extract_file = TRUE;
+ break;
+ }
+ }
+ } else {
+ $v_extract_file = TRUE;
+ }
+
+ // ----- Look if this file need to be extracted
+ if (($v_extract_file) && (!$v_listing))
+ {
+ if (($p_remove_path != '')
+ && (substr($v_header['filename'], 0, $p_remove_path_size)
+ == $p_remove_path))
+ $v_header['filename'] = substr($v_header['filename'],
+ $p_remove_path_size);
+ if (($p_path != './') && ($p_path != '/')) {
+ while (substr($p_path, -1) == '/')
+ $p_path = substr($p_path, 0, strlen($p_path)-1);
+
+ if (substr($v_header['filename'], 0, 1) == '/')
+ $v_header['filename'] = $p_path.$v_header['filename'];
+ else
+ $v_header['filename'] = $p_path.'/'.$v_header['filename'];
+ }
+ if (file_exists($v_header['filename'])) {
+ if ( (@is_dir($v_header['filename']))
+ && ($v_header['typeflag'] == '')) {
+ $this->_error('File '.$v_header['filename']
+ .' already exists as a directory');
+ return false;
+ }
+ if ( ($this->_isArchive($v_header['filename']))
+ && ($v_header['typeflag'] == "5")) {
+ $this->_error('Directory '.$v_header['filename']
+ .' already exists as a file');
+ return false;
+ }
+ if (!is_writeable($v_header['filename'])) {
+ $this->_error('File '.$v_header['filename']
+ .' already exists and is write protected');
+ return false;
+ }
+ if (filemtime($v_header['filename']) > $v_header['mtime']) {
+ // To be completed : An error or silent no replace ?
+ }
+ }
+
+ // ----- Check the directory availability and create it if necessary
+ elseif (($v_result
+ = $this->_dirCheck(($v_header['typeflag'] == "5"
+ ?$v_header['filename']
+ :dirname($v_header['filename'])))) != 1) {
+ $this->_error('Unable to create path for '.$v_header['filename']);
+ return false;
+ }
+
+ if ($v_extract_file) {
+ if ($v_header['typeflag'] == "5") {
+ if (!@file_exists($v_header['filename'])) {
+ // Drupal integration.
+ // Changed the code to use drupal_mkdir() instead of mkdir().
+ if (!@drupal_mkdir($v_header['filename'], 0777)) {
+ $this->_error('Unable to create directory {'
+ .$v_header['filename'].'}');
+ return false;
+ }
+ }
+ } elseif ($v_header['typeflag'] == "2") {
+ if (@file_exists($v_header['filename'])) {
+ @drupal_unlink($v_header['filename']);
+ }
+ if (!@symlink($v_header['link'], $v_header['filename'])) {
+ $this->_error('Unable to extract symbolic link {'
+ .$v_header['filename'].'}');
+ return false;
+ }
+ } else {
+ if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
+ $this->_error('Error while opening {'.$v_header['filename']
+ .'} in write binary mode');
+ return false;
+ } else {
+ $n = floor($v_header['size']/512);
+ for ($i=0; $i<$n; $i++) {
+ $v_content = $this->_readBlock();
+ fwrite($v_dest_file, $v_content, 512);
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
+ }
+
+ @fclose($v_dest_file);
+
+ // ----- Change the file mode, mtime
+ @touch($v_header['filename'], $v_header['mtime']);
+ if ($v_header['mode'] & 0111) {
+ // make file executable, obey umask
+ $mode = fileperms($v_header['filename']) | (~umask() & 0111);
+ @chmod($v_header['filename'], $mode);
+ }
+ }
+
+ // ----- Check the file size
+ clearstatcache();
+ if (filesize($v_header['filename']) != $v_header['size']) {
+ $this->_error('Extracted file '.$v_header['filename']
+ .' does not have the correct file size \''
+ .filesize($v_header['filename'])
+ .'\' ('.$v_header['size']
+ .' expected). Archive may be corrupted.');
+ return false;
+ }
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size']/512)));
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size']/512)));
+ }
+
+ /* TBC : Seems to be unused ...
+ if ($this->_compress)
+ $v_end_of_file = @gzeof($this->_file);
+ else
+ $v_end_of_file = @feof($this->_file);
+ */
+
+ if ($v_listing || $v_extract_file || $v_extraction_stopped) {
+ // ----- Log extracted files
+ if (($v_file_dir = dirname($v_header['filename']))
+ == $v_header['filename'])
+ $v_file_dir = '';
+ if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
+ $v_file_dir = '/';
+
+ $p_list_detail[$v_nb++] = $v_header;
+ if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
+ return true;
+ }
+ }
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _openAppend()
+ function _openAppend()
+ {
+ if (filesize($this->_tarname) == 0)
+ return $this->_openWrite();
+
+ if ($this->_compress) {
+ $this->_close();
+
+ if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
+ $this->_error('Error while renaming \''.$this->_tarname
+ .'\' to temporary file \''.$this->_tarname
+ .'.tmp\'');
+ return false;
+ }
+
+ if ($this->_compress_type == 'gz')
+ $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
+ elseif ($this->_compress_type == 'bz2')
+ $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
+
+ if ($v_temp_tar == 0) {
+ $this->_error('Unable to open file \''.$this->_tarname
+ .'.tmp\' in binary read mode');
+ @rename($this->_tarname.".tmp", $this->_tarname);
+ return false;
+ }
+
+ if (!$this->_openWrite()) {
+ @rename($this->_tarname.".tmp", $this->_tarname);
+ return false;
+ }
+
+ if ($this->_compress_type == 'gz') {
+ while (!@gzeof($v_temp_tar)) {
+ $v_buffer = @gzread($v_temp_tar, 512);
+ if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+ // do not copy end blocks, we will re-make them
+ // after appending
+ continue;
+ }
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ @gzclose($v_temp_tar);
+ }
+ elseif ($this->_compress_type == 'bz2') {
+ while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
+ if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+ continue;
+ }
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ @bzclose($v_temp_tar);
+ }
+
+ if (!@drupal_unlink($this->_tarname.".tmp")) {
+ $this->_error('Error while deleting temporary file \''
+ .$this->_tarname.'.tmp\'');
+ }
+
+ } else {
+ // ----- For not compressed tar, just add files before the last
+ // one or two 512 bytes block
+ if (!$this->_openReadWrite())
+ return false;
+
+ clearstatcache();
+ $v_size = filesize($this->_tarname);
+
+ // We might have zero, one or two end blocks.
+ // The standard is two, but we should try to handle
+ // other cases.
+ fseek($this->_file, $v_size - 1024);
+ if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
+ fseek($this->_file, $v_size - 1024);
+ }
+ elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
+ fseek($this->_file, $v_size - 512);
+ }
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _append()
+ function _append($p_filelist, $p_add_dir='', $p_remove_dir='')
+ {
+ if (!$this->_openAppend())
+ return false;
+
+ if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
+ $this->_writeFooter();
+
+ $this->_close();
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _dirCheck()
+
+ /**
+ * Check if a directory exists and create it (including parent
+ * dirs) if not.
+ *
+ * @param string $p_dir directory to check
+ *
+ * @return bool TRUE if the directory exists or was created
+ */
+ function _dirCheck($p_dir)
+ {
+ clearstatcache();
+ if ((@is_dir($p_dir)) || ($p_dir == ''))
+ return true;
+
+ $p_parent_dir = dirname($p_dir);
+
+ if (($p_parent_dir != $p_dir) &&
+ ($p_parent_dir != '') &&
+ (!$this->_dirCheck($p_parent_dir)))
+ return false;
+
+ // Drupal integration.
+ // Changed the code to use drupal_mkdir() instead of mkdir().
+ if (!@drupal_mkdir($p_dir, 0777)) {
+ $this->_error("Unable to create directory '$p_dir'");
+ return false;
+ }
+
+ return true;
+ }
+
+ // }}}
+
+ // {{{ _pathReduction()
+
+ /**
+ * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
+ * rand emove double slashes.
+ *
+ * @param string $p_dir path to reduce
+ *
+ * @return string reduced path
+ *
+ * @access private
+ *
+ */
+ function _pathReduction($p_dir)
+ {
+ $v_result = '';
+
+ // ----- Look for not empty path
+ if ($p_dir != '') {
+ // ----- Explode path by directory names
+ $v_list = explode('/', $p_dir);
+
+ // ----- Study directories from last to first
+ for ($i=sizeof($v_list)-1; $i>=0; $i--) {
+ // ----- Look for current path
+ if ($v_list[$i] == ".") {
+ // ----- Ignore this directory
+ // Should be the first $i=0, but no check is done
+ }
+ else if ($v_list[$i] == "..") {
+ // ----- Ignore it and ignore the $i-1
+ $i--;
+ }
+ else if ( ($v_list[$i] == '')
+ && ($i!=(sizeof($v_list)-1))
+ && ($i!=0)) {
+ // ----- Ignore only the double '//' in path,
+ // but not the first and last /
+ } else {
+ $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/'
+ .$v_result:'');
+ }
+ }
+ }
+ $v_result = strtr($v_result, '\\', '/');
+ return $v_result;
+ }
+
+ // }}}
+
+ // {{{ _translateWinPath()
+ function _translateWinPath($p_path, $p_remove_disk_letter=true)
+ {
+ if (defined('OS_WINDOWS') && OS_WINDOWS) {
+ // ----- Look for potential disk letter
+ if ( ($p_remove_disk_letter)
+ && (($v_position = strpos($p_path, ':')) != false)) {
+ $p_path = substr($p_path, $v_position+1);
+ }
+ // ----- Change potential windows directory separator
+ if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
+ $p_path = strtr($p_path, '\\', '/');
+ }
+ }
+ return $p_path;
+ }
+ // }}}
+
+}
+?>
diff --git a/core/modules/system/system.test b/core/modules/system/system.test
new file mode 100644
index 00000000000..f84bc781785
--- /dev/null
+++ b/core/modules/system/system.test
@@ -0,0 +1,2564 @@
+<?php
+
+/**
+ * @file
+ * Tests for system.module.
+ */
+
+/**
+ * Helper class for module test cases.
+ */
+class ModuleTestCase extends DrupalWebTestCase {
+ protected $admin_user;
+
+ function setUp() {
+ parent::setUp('system_test');
+
+ $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Assert there are tables that begin with the specified base table name.
+ *
+ * @param $base_table
+ * Beginning of table name to look for.
+ * @param $count
+ * (optional) Whether or not to assert that there are tables that match the
+ * specified base table. Defaults to TRUE.
+ */
+ function assertTableCount($base_table, $count = TRUE) {
+ $tables = db_find_tables(Database::getConnection()->prefixTables('{' . $base_table . '}') . '%');
+
+ if ($count) {
+ return $this->assertTrue($tables, t('Tables matching "@base_table" found.', array('@base_table' => $base_table)));
+ }
+ return $this->assertFalse($tables, t('Tables matching "@base_table" not found.', array('@base_table' => $base_table)));
+ }
+
+ /**
+ * Assert that all tables defined in a module's hook_schema() exist.
+ *
+ * @param $module
+ * The name of the module.
+ */
+ function assertModuleTablesExist($module) {
+ $tables = array_keys(drupal_get_schema_unprocessed($module));
+ $tables_exist = TRUE;
+ foreach ($tables as $table) {
+ if (!db_table_exists($table)) {
+ $tables_exist = FALSE;
+ }
+ }
+ return $this->assertTrue($tables_exist, t('All database tables defined by the @module module exist.', array('@module' => $module)));
+ }
+
+ /**
+ * Assert that none of the tables defined in a module's hook_schema() exist.
+ *
+ * @param $module
+ * The name of the module.
+ */
+ function assertModuleTablesDoNotExist($module) {
+ $tables = array_keys(drupal_get_schema_unprocessed($module));
+ $tables_exist = FALSE;
+ foreach ($tables as $table) {
+ if (db_table_exists($table)) {
+ $tables_exist = TRUE;
+ }
+ }
+ return $this->assertFalse($tables_exist, t('None of the database tables defined by the @module module exist.', array('@module' => $module)));
+ }
+
+ /**
+ * Assert the list of modules are enabled or disabled.
+ *
+ * @param $modules
+ * Module list to check.
+ * @param $enabled
+ * Expected module state.
+ */
+ function assertModules(array $modules, $enabled) {
+ module_list(TRUE);
+ foreach ($modules as $module) {
+ if ($enabled) {
+ $message = 'Module "@module" is enabled.';
+ }
+ else {
+ $message = 'Module "@module" is not enabled.';
+ }
+ $this->assertEqual(module_exists($module), $enabled, t($message, array('@module' => $module)));
+ }
+ }
+
+ /**
+ * Verify a log entry was entered for a module's status change.
+ * Called in the same way of the expected original watchdog() execution.
+ *
+ * @param $type
+ * The category to which this message belongs.
+ * @param $message
+ * The message to store in the log. Keep $message translatable
+ * by not concatenating dynamic values into it! Variables in the
+ * message should be added by using placeholder strings alongside
+ * the variables argument to declare the value of the placeholders.
+ * See t() for documentation on how $message and $variables interact.
+ * @param $variables
+ * Array of variables to replace in the message on display or
+ * NULL if message is already translated or not possible to
+ * translate.
+ * @param $severity
+ * The severity of the message, as per RFC 3164.
+ * @param $link
+ * A link to associate with the message.
+ */
+ function assertLogMessage($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = '') {
+ $count = db_select('watchdog', 'w')
+ ->condition('type', $type)
+ ->condition('message', $message)
+ ->condition('variables', serialize($variables))
+ ->condition('severity', $severity)
+ ->condition('link', $link)
+ ->countQuery()
+ ->execute()
+ ->fetchField();
+ $this->assertTrue($count > 0, t('watchdog table contains @count rows for @message', array('@count' => $count, '@message' => $message)));
+ }
+}
+
+/**
+ * Test module enabling/disabling functionality.
+ */
+class EnableDisableTestCase extends ModuleTestCase {
+ protected $profile = 'testing';
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Enable/disable modules',
+ 'description' => 'Enable/disable core module and confirm table creation/deletion.',
+ 'group' => 'Module',
+ );
+ }
+
+ /**
+ * Test that all core modules can be enabled, disabled and uninstalled.
+ */
+ function testEnableDisable() {
+ // Try to enable, disable and uninstall all core modules, unless they are
+ // hidden or required.
+ $modules = system_rebuild_module_data();
+ foreach ($modules as $name => $module) {
+ if ($module->info['package'] != 'Core' || !empty($module->info['hidden']) || !empty($module->info['required'])) {
+ unset($modules[$name]);
+ }
+ }
+ $this->assertTrue(count($modules), t('Found @count core modules that we can try to enable in this test.', array('@count' => count($modules))));
+
+ // Enable the dblog module first, since we will be asserting the presence
+ // of log messages throughout the test.
+ if (isset($modules['dblog'])) {
+ $modules = array('dblog' => $modules['dblog']) + $modules;
+ }
+
+ // Set a variable so that the hook implementations in system_test.module
+ // will display messages via drupal_set_message().
+ variable_set('test_verbose_module_hooks', TRUE);
+
+ // Throughout this test, some modules may be automatically enabled (due to
+ // dependencies). We'll keep track of them in an array, so we can handle
+ // them separately.
+ $automatically_enabled = array();
+
+ // Go through each module in the list and try to enable it (unless it was
+ // already enabled automatically due to a dependency).
+ foreach ($modules as $name => $module) {
+ if (empty($automatically_enabled[$name])) {
+ // Start a list of modules that we expect to be enabled this time.
+ $modules_to_enable = array($name);
+
+ // Find out if the module has any dependencies that aren't enabled yet;
+ // if so, add them to the list of modules we expect to be automatically
+ // enabled.
+ foreach (array_keys($module->requires) as $dependency) {
+ if (isset($modules[$dependency]) && empty($automatically_enabled[$dependency])) {
+ $modules_to_enable[] = $dependency;
+ $automatically_enabled[$dependency] = TRUE;
+ }
+ }
+
+ // Check that each module is not yet enabled and does not have any
+ // database tables yet.
+ foreach ($modules_to_enable as $module_to_enable) {
+ $this->assertModules(array($module_to_enable), FALSE);
+ $this->assertModuleTablesDoNotExist($module_to_enable);
+ }
+
+ // Install and enable the module.
+ $edit = array();
+ $edit['modules[Core][' . $name . '][enable]'] = $name;
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ // Handle the case where modules were installed along with this one and
+ // where we therefore hit a confirmation screen.
+ if (count($modules_to_enable) > 1) {
+ $this->drupalPost(NULL, array(), t('Continue'));
+ }
+ $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+
+ // Check that hook_modules_installed() and hook_modules_enabled() were
+ // invoked with the expected list of modules, that each module's
+ // database tables now exist, and that appropriate messages appear in
+ // the logs.
+ foreach ($modules_to_enable as $module_to_enable) {
+ $this->assertText(t('hook_modules_installed fired for @module', array('@module' => $module_to_enable)));
+ $this->assertText(t('hook_modules_enabled fired for @module', array('@module' => $module_to_enable)));
+ $this->assertModules(array($module_to_enable), TRUE);
+ $this->assertModuleTablesExist($module_to_enable);
+ $this->assertLogMessage('system', "%module module installed.", array('%module' => $module_to_enable), WATCHDOG_INFO);
+ $this->assertLogMessage('system', "%module module enabled.", array('%module' => $module_to_enable), WATCHDOG_INFO);
+ }
+
+ // Disable and uninstall the original module, and check appropriate
+ // hooks, tables, and log messages. (Later, we'll go back and do the
+ // same thing for modules that were enabled automatically.) Skip this
+ // for the dblog module, because that is needed for the test; we'll go
+ // back and do that one at the end also.
+ if ($name != 'dblog') {
+ $this->assertSuccessfulDisableAndUninstall($name);
+ }
+ }
+ }
+
+ // Go through all modules that were automatically enabled, and try to
+ // disable and uninstall them one by one.
+ while (!empty($automatically_enabled)) {
+ $initial_count = count($automatically_enabled);
+ foreach (array_keys($automatically_enabled) as $name) {
+ // If the module can't be disabled due to dependencies, skip it and try
+ // again the next time. Otherwise, try to disable it.
+ $this->drupalGet('admin/modules');
+ $disabled_checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Core][' . $name . '][enable]"]');
+ if (empty($disabled_checkbox) && $name != 'dblog') {
+ unset($automatically_enabled[$name]);
+ $this->assertSuccessfulDisableAndUninstall($name);
+ }
+ }
+ $final_count = count($automatically_enabled);
+ // If all checkboxes were disabled, something is really wrong with the
+ // test. Throw a failure and avoid an infinite loop.
+ if ($initial_count == $final_count) {
+ $this->fail(t('Remaining modules could not be disabled.'));
+ break;
+ }
+ }
+
+ // Disable and uninstall the dblog module last, since we needed it for
+ // assertions in all the above tests.
+ if (isset($modules['dblog'])) {
+ $this->assertSuccessfulDisableAndUninstall('dblog');
+ }
+
+ // Now that all modules have been tested, go back and try to enable them
+ // all again at once. This tests two things:
+ // - That each module can be successfully enabled again after being
+ // uninstalled.
+ // - That enabling more than one module at the same time does not lead to
+ // any errors.
+ $edit = array();
+ foreach (array_keys($modules) as $name) {
+ $edit['modules[Core][' . $name . '][enable]'] = $name;
+ }
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+ }
+
+ /**
+ * Tests entity cache after enabling a module with a dependency on an enitity
+ * providing module.
+ *
+ * @see entity_cache_test_watchdog()
+ */
+ function testEntityCache() {
+ module_enable(array('entity_cache_test'));
+ $info = variable_get('entity_cache_test');
+ $this->assertEqual($info['label'], 'Entity Cache Test', 'Entity info label is correct.');
+ $this->assertEqual($info['controller class'], 'DrupalDefaultEntityController', 'Entity controller class info is correct.');
+ }
+
+ /**
+ * Disables and uninstalls a module and asserts that it was done correctly.
+ *
+ * @param $module
+ * The name of the module to disable and uninstall.
+ */
+ function assertSuccessfulDisableAndUninstall($module) {
+ // Disable the module.
+ $edit = array();
+ $edit['modules[Core][' . $module . '][enable]'] = FALSE;
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+ $this->assertModules(array($module), FALSE);
+
+ // Check that the appropriate hook was fired and the appropriate log
+ // message appears.
+ $this->assertText(t('hook_modules_disabled fired for @module', array('@module' => $module)));
+ $this->assertLogMessage('system', "%module module disabled.", array('%module' => $module), WATCHDOG_INFO);
+
+ // Check that the module's database tables still exist.
+ $this->assertModuleTablesExist($module);
+
+ // Uninstall the module.
+ $edit = array();
+ $edit['uninstall[' . $module . ']'] = $module;
+ $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+ $this->drupalPost(NULL, NULL, t('Uninstall'));
+ $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
+ $this->assertModules(array($module), FALSE);
+
+ // Check that the appropriate hook was fired and the appropriate log
+ // message appears. (But don't check for the log message if the dblog
+ // module was just uninstalled, since the {watchdog} table won't be there
+ // anymore.)
+ $this->assertText(t('hook_modules_uninstalled fired for @module', array('@module' => $module)));
+ if ($module != 'dblog') {
+ $this->assertLogMessage('system', "%module module uninstalled.", array('%module' => $module), WATCHDOG_INFO);
+ }
+
+ // Check that the module's database tables no longer exist.
+ $this->assertModuleTablesDoNotExist($module);
+ }
+}
+
+/**
+ * Tests failure of hook_requirements('install').
+ */
+class HookRequirementsTestCase extends ModuleTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Requirements hook failure',
+ 'description' => "Attempts enabling a module that fails hook_requirements('install').",
+ 'group' => 'Module',
+ );
+ }
+
+ /**
+ * Assert that a module cannot be installed if it fails hook_requirements().
+ */
+ function testHookRequirementsFailure() {
+ $this->assertModules(array('requirements1_test'), FALSE);
+
+ // Attempt to install the requirements1_test module.
+ $edit = array();
+ $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Makes sure the module was NOT installed.
+ $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.'));
+ $this->assertModules(array('requirements1_test'), FALSE);
+ }
+}
+
+/**
+ * Test module dependency functionality.
+ */
+class ModuleDependencyTestCase extends ModuleTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Module dependencies',
+ 'description' => 'Enable module without dependency enabled.',
+ 'group' => 'Module',
+ );
+ }
+
+ /**
+ * Attempt to enable translation module without locale enabled.
+ */
+ function testEnableWithoutDependency() {
+ // Attempt to enable content translation without locale enabled.
+ $edit = array();
+ $edit['modules[Core][translation][enable]'] = 'translation';
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('Some required modules must be enabled'), t('Dependency required.'));
+
+ $this->assertModules(array('translation', 'locale'), FALSE);
+
+ // Assert that the locale tables weren't enabled.
+ $this->assertTableCount('languages', FALSE);
+ $this->assertTableCount('locale', FALSE);
+
+ $this->drupalPost(NULL, NULL, t('Continue'));
+ $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+
+ $this->assertModules(array('translation', 'locale'), TRUE);
+
+ // Assert that the locale tables were enabled.
+ $this->assertTableCount('languages', TRUE);
+ $this->assertTableCount('locale', TRUE);
+ }
+
+ /**
+ * Attempt to enable a module with a missing dependency.
+ */
+ function testMissingModules() {
+ // Test that the system_dependencies_test module is marked
+ // as missing a dependency.
+ $this->drupalGet('admin/modules');
+ $this->assertRaw(t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst('_missing_dependency'))), t('A module with missing dependencies is marked as such.'));
+ $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="modules[Testing][system_dependencies_test][enable]"]');
+ $this->assert(count($checkbox) == 1, t('Checkbox for the module is disabled.'));
+
+ // Force enable the system_dependencies_test module.
+ module_enable(array('system_dependencies_test'), FALSE);
+
+ // Verify that the module is forced to be disabled when submitting
+ // the module page.
+ $this->drupalPost('admin/modules', array(), t('Save configuration'));
+ $this->assertText(t('The @module module is missing, so the following module will be disabled: @depends.', array('@module' => '_missing_dependency', '@depends' => 'system_dependencies_test')), t('The module missing dependencies will be disabled.'));
+
+ // Confirm.
+ $this->drupalPost(NULL, NULL, t('Continue'));
+
+ // Verify that the module has been disabled.
+ $this->assertModules(array('system_dependencies_test'), FALSE);
+ }
+
+ /**
+ * Tests enabling a module that depends on a module which fails hook_requirements().
+ */
+ function testEnableRequirementsFailureDependency() {
+ $this->assertModules(array('requirements1_test'), FALSE);
+ $this->assertModules(array('requirements2_test'), FALSE);
+
+ // Attempt to install both modules at the same time.
+ $edit = array();
+ $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
+ $edit['modules[Testing][requirements2_test][enable]'] = 'requirements2_test';
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Makes sure the modules were NOT installed.
+ $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.'));
+ $this->assertModules(array('requirements1_test'), FALSE);
+ $this->assertModules(array('requirements2_test'), FALSE);
+
+ // Makes sure that already enabled modules the failing modules depend on
+ // were not disabled.
+ $this->assertModules(array('comment'), TRUE);
+
+ }
+
+ /**
+ * Tests that module dependencies are enabled in the correct order via the
+ * UI. Dependencies should be enabled before their dependents.
+ */
+ function testModuleEnableOrder() {
+ module_enable(array('module_test'), FALSE);
+ $this->resetAll();
+ $this->assertModules(array('module_test'), TRUE);
+ variable_set('dependency_test', 'dependency');
+ // module_test creates a dependency chain: forum depends on poll, which
+ // depends on php. The correct enable order is, php, poll, forum.
+ $expected_order = array('php', 'poll', 'forum');
+
+ // Enable the modules through the UI, verifying that the dependency chain
+ // is correct.
+ $edit = array();
+ $edit['modules[Core][forum][enable]'] = 'forum';
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertModules(array('forum'), FALSE);
+ $this->assertText(t('You must enable the Poll, PHP filter modules to install Forum.'), t('Dependency chain created.'));
+ $edit['modules[Core][poll][enable]'] = 'poll';
+ $edit['modules[Core][php][enable]'] = 'php';
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertModules(array('forum', 'poll', 'php'), TRUE);
+
+ // Check the actual order which is saved by module_test_modules_enabled().
+ $this->assertIdentical(variable_get('test_module_enable_order', FALSE), $expected_order, t('Modules enabled in the correct order.'));
+ }
+
+ /**
+ * Tests attempting to uninstall a module that has installed dependents.
+ */
+ function testUninstallDependents() {
+ // Enable the forum module.
+ $edit = array('modules[Core][forum][enable]' => 'forum');
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertModules(array('forum'), TRUE);
+
+ // Disable forum and comment. Both should now be installed but disabled.
+ $edit = array('modules[Core][forum][enable]' => FALSE);
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertModules(array('forum'), FALSE);
+ $edit = array('modules[Core][comment][enable]' => FALSE);
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertModules(array('comment'), FALSE);
+
+ // Check that the taxonomy module cannot be uninstalled.
+ $this->drupalGet('admin/modules/uninstall');
+ $checkbox = $this->xpath('//input[@type="checkbox" and @disabled="disabled" and @name="uninstall[comment]"]');
+ $this->assert(count($checkbox) == 1, t('Checkbox for uninstalling the comment module is disabled.'));
+
+ // Uninstall the forum module, and check that taxonomy now can also be
+ // uninstalled.
+ $edit = array('uninstall[forum]' => 'forum');
+ $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+ $this->drupalPost(NULL, NULL, t('Uninstall'));
+ $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
+ $edit = array('uninstall[comment]' => 'comment');
+ $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+ $this->drupalPost(NULL, NULL, t('Uninstall'));
+ $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
+ }
+}
+
+/**
+ * Test module dependency on specific versions.
+ */
+class ModuleVersionTestCase extends ModuleTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Module versions',
+ 'description' => 'Check module version dependencies.',
+ 'group' => 'Module',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('module_test');
+ }
+
+ /**
+ * Test version dependencies.
+ */
+ function testModuleVersions() {
+ $dependencies = array(
+ // Alternating between being compatible and incompatible with 8.x-2.4-beta3.
+ // The first is always a compatible.
+ 'common_test',
+ // Branch incompatibility.
+ 'common_test (1.x)',
+ // Branch compatibility.
+ 'common_test (2.x)',
+ // Another branch incompatibility.
+ 'common_test (>2.x)',
+ // Another branch compatibility.
+ 'common_test (<=2.x)',
+ // Another branch incompatibility.
+ 'common_test (<2.x)',
+ // Another branch compatibility.
+ 'common_test (>=2.x)',
+ // Nonsense, misses a dash. Incompatible with everything.
+ 'common_test (=8.x2.x, >=2.4)',
+ // Core version is optional. Compatible.
+ 'common_test (=8.x-2.x, >=2.4-alpha2)',
+ // Test !=, explicitly incompatible.
+ 'common_test (=2.x, !=2.4-beta3)',
+ // Three operations. Compatible.
+ 'common_test (=2.x, !=2.3, <2.5)',
+ // Testing extra version. Incompatible.
+ 'common_test (<=2.4-beta2)',
+ // Testing extra version. Compatible.
+ 'common_test (>2.4-beta2)',
+ // Testing extra version. Incompatible.
+ 'common_test (>2.4-rc0)',
+ );
+ variable_set('dependencies', $dependencies);
+ $n = count($dependencies);
+ for ($i = 0; $i < $n; $i++) {
+ $this->drupalGet('admin/modules');
+ $checkbox = $this->xpath('//input[@id="edit-modules-testing-module-test-enable"]');
+ $this->assertEqual(!empty($checkbox[0]['disabled']), $i % 2, $dependencies[$i]);
+ }
+ }
+}
+
+/**
+ * Test required modules functionality.
+ */
+class ModuleRequiredTestCase extends ModuleTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Required modules',
+ 'description' => 'Attempt disabling of required modules.',
+ 'group' => 'Module',
+ );
+ }
+
+ /**
+ * Assert that core required modules cannot be disabled.
+ */
+ function testDisableRequired() {
+ $module_info = system_get_info('module');
+ $this->drupalGet('admin/modules');
+ foreach ($module_info as $module => $info) {
+ // Check to make sure the checkbox for each required module is disabled
+ // and checked (or absent from the page if the module is also hidden).
+ if (!empty($info['required'])) {
+ $field_name = "modules[{$info['package']}][$module][enable]";
+ if (empty($info['hidden'])) {
+ $this->assertFieldByXPath("//input[@name='$field_name' and @disabled='disabled' and @checked='checked']", '', t('Field @name was disabled and checked.', array('@name' => $field_name)));
+ }
+ else {
+ $this->assertNoFieldByName($field_name);
+ }
+ }
+ }
+ }
+}
+
+class IPAddressBlockingTestCase extends DrupalWebTestCase {
+ protected $blocking_user;
+
+ /**
+ * Implement getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'IP address blocking',
+ 'description' => 'Test IP address blocking.',
+ 'group' => 'System'
+ );
+ }
+
+ /**
+ * Implement setUp().
+ */
+ function setUp() {
+ parent::setUp();
+
+ // Create user.
+ $this->blocking_user = $this->drupalCreateUser(array('block IP addresses'));
+ $this->drupalLogin($this->blocking_user);
+ }
+
+ /**
+ * Test a variety of user input to confirm correct validation and saving of data.
+ */
+ function testIPAddressValidation() {
+ $this->drupalGet('admin/config/people/ip-blocking');
+
+ // Block a valid IP address.
+ $edit = array();
+ $edit['ip'] = '192.168.1.1';
+ $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
+ $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField();
+ $this->assertTrue($ip, t('IP address found in database.'));
+ $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $edit['ip'])), t('IP address was blocked.'));
+
+ // Try to block an IP address that's already blocked.
+ $edit = array();
+ $edit['ip'] = '192.168.1.1';
+ $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
+ $this->assertText(t('This IP address is already blocked.'));
+
+ // Try to block a reserved IP address.
+ $edit = array();
+ $edit['ip'] = '255.255.255.255';
+ $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
+ $this->assertText(t('Enter a valid IP address.'));
+
+ // Try to block a reserved IP address.
+ $edit = array();
+ $edit['ip'] = 'test.example.com';
+ $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
+ $this->assertText(t('Enter a valid IP address.'));
+
+ // Submit an empty form.
+ $edit = array();
+ $edit['ip'] = '';
+ $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
+ $this->assertText(t('Enter a valid IP address.'));
+
+ // Pass an IP address as a URL parameter and submit it.
+ $submit_ip = '1.2.3.4';
+ $this->drupalPost('admin/config/people/ip-blocking/' . $submit_ip, NULL, t('Add'));
+ $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->fetchField();
+ $this->assertTrue($ip, t('IP address found in database'));
+ $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $submit_ip)), t('IP address was blocked.'));
+
+ // Submit your own IP address. This fails, although it works when testing manually.
+ // TODO: on some systems this test fails due to a bug or inconsistency in cURL.
+ // $edit = array();
+ // $edit['ip'] = ip_address();
+ // $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save'));
+ // $this->assertText(t('You may not block your own IP address.'));
+ }
+}
+
+class CronRunTestCase extends DrupalWebTestCase {
+ /**
+ * Implement getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Cron run',
+ 'description' => 'Test cron run.',
+ 'group' => 'System'
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('common_test', 'common_test_cron_helper'));
+ }
+
+ /**
+ * Test cron runs.
+ */
+ function testCronRun() {
+ global $base_url;
+
+ // Run cron anonymously without any cron key.
+ $this->drupalGet($base_url . '/cron.php', array('external' => TRUE));
+ $this->assertResponse(403);
+
+ // Run cron anonymously with a random cron key.
+ $key = $this->randomName(16);
+ $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
+ $this->assertResponse(403);
+
+ // Run cron anonymously with the valid cron key.
+ $key = variable_get('cron_key', 'drupal');
+ $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
+ $this->assertResponse(200);
+ }
+
+ /**
+ * Ensure that the automatic cron run feature is working.
+ *
+ * In these tests we do not use REQUEST_TIME to track start time, because we
+ * need the exact time when cron is triggered.
+ */
+ function testAutomaticCron() {
+ // Ensure cron does not run when the cron threshold is enabled and was
+ // not passed.
+ $cron_last = time();
+ $cron_safe_threshold = 100;
+ variable_set('cron_last', $cron_last);
+ variable_set('cron_safe_threshold', $cron_safe_threshold);
+ $this->drupalGet('');
+ $this->assertTrue($cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is not passed.'));
+
+ // Test if cron runs when the cron threshold was passed.
+ $cron_last = time() - 200;
+ variable_set('cron_last', $cron_last);
+ $this->drupalGet('');
+ sleep(1);
+ $this->assertTrue($cron_last < variable_get('cron_last', NULL), t('Cron runs when the cron threshold is passed.'));
+
+ // Disable the cron threshold through the interface.
+ $admin_user = $this->drupalCreateUser(array('administer site configuration'));
+ $this->drupalLogin($admin_user);
+ $this->drupalPost('admin/config/system/cron', array('cron_safe_threshold' => 0), t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+ $this->drupalLogout();
+
+ // Test if cron does not run when the cron threshold is disabled.
+ $cron_last = time() - 200;
+ variable_set('cron_last', $cron_last);
+ $this->drupalGet('');
+ $this->assertTrue($cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is disabled.'));
+ }
+
+ /**
+ * Ensure that temporary files are removed.
+ *
+ * Create files for all the possible combinations of age and status. We are
+ * using UPDATE statements rather than file_save() because it would set the
+ * timestamp.
+ */
+ function testTempFileCleanup() {
+ // Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+ $temp_old = file_save_data('');
+ db_update('file_managed')
+ ->fields(array(
+ 'status' => 0,
+ 'timestamp' => 1,
+ ))
+ ->condition('fid', $temp_old->fid)
+ ->execute();
+ $this->assertTrue(file_exists($temp_old->uri), t('Old temp file was created correctly.'));
+
+ // Temporary file that is less than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+ $temp_new = file_save_data('');
+ db_update('file_managed')
+ ->fields(array('status' => 0))
+ ->condition('fid', $temp_new->fid)
+ ->execute();
+ $this->assertTrue(file_exists($temp_new->uri), t('New temp file was created correctly.'));
+
+ // Permanent file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+ $perm_old = file_save_data('');
+ db_update('file_managed')
+ ->fields(array('timestamp' => 1))
+ ->condition('fid', $temp_old->fid)
+ ->execute();
+ $this->assertTrue(file_exists($perm_old->uri), t('Old permanent file was created correctly.'));
+
+ // Permanent file that is newer than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+ $perm_new = file_save_data('');
+ $this->assertTrue(file_exists($perm_new->uri), t('New permanent file was created correctly.'));
+
+ // Run cron and then ensure that only the old, temp file was deleted.
+ $this->cronRun();
+ $this->assertFalse(file_exists($temp_old->uri), t('Old temp file was correctly removed.'));
+ $this->assertTrue(file_exists($temp_new->uri), t('New temp file was correctly ignored.'));
+ $this->assertTrue(file_exists($perm_old->uri), t('Old permanent file was correctly ignored.'));
+ $this->assertTrue(file_exists($perm_new->uri), t('New permanent file was correctly ignored.'));
+ }
+
+ /**
+ * Make sure exceptions thrown on hook_cron() don't affect other modules.
+ */
+ function testCronExceptions() {
+ variable_del('common_test_cron');
+ // The common_test module throws an exception. If it isn't caught, the tests
+ // won't finish successfully.
+ // The common_test_cron_helper module sets the 'common_test_cron' variable.
+ $this->cronRun();
+ $result = variable_get('common_test_cron');
+ $this->assertEqual($result, 'success', t('Cron correctly handles exceptions thrown during hook_cron() invocations.'));
+ }
+}
+
+class AdminMetaTagTestCase extends DrupalWebTestCase {
+ /**
+ * Implement getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Fingerprinting meta tag',
+ 'description' => 'Confirm that the fingerprinting meta tag appears as expected.',
+ 'group' => 'System'
+ );
+ }
+
+ /**
+ * Verify that the meta tag HTML is generated correctly.
+ */
+ public function testMetaTag() {
+ list($version, ) = explode('.', VERSION);
+ $string = '<meta name="Generator" content="Drupal ' . $version . ' (http://drupal.org)" />';
+ $this->drupalGet('node');
+ $this->assertRaw($string, t('Fingerprinting meta tag generated correctly.'), t('System'));
+ }
+}
+
+/**
+ * Tests custom access denied functionality.
+ */
+class AccessDeniedTestCase extends DrupalWebTestCase {
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => '403 functionality',
+ 'description' => 'Tests page access denied functionality, including custom 403 pages.',
+ 'group' => 'System'
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create an administrative user.
+ $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer site configuration', 'administer blocks'));
+ }
+
+ function testAccessDenied() {
+ $this->drupalGet('admin');
+ $this->assertText(t('Access denied'), t('Found the default 403 page'));
+ $this->assertResponse(403);
+
+ $this->drupalLogin($this->admin_user);
+ $edit = array(
+ 'title' => $this->randomName(10),
+ 'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(100)))),
+ );
+ $node = $this->drupalCreateNode($edit);
+
+ // Use a custom 403 page.
+ $this->drupalPost('admin/config/system/site-information', array('site_403' => 'node/' . $node->nid), t('Save configuration'));
+
+ $this->drupalLogout();
+ $this->drupalGet('admin');
+ $this->assertText($node->title, t('Found the custom 403 page'));
+
+ // Logout and check that the user login block is shown on custom 403 pages.
+ $this->drupalLogout();
+
+ $this->drupalGet('admin');
+ $this->assertText($node->title, t('Found the custom 403 page'));
+ $this->assertText(t('User login'), t('Blocks are shown on the custom 403 page'));
+
+ // Log back in and remove the custom 403 page.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalPost('admin/config/system/site-information', array('site_403' => ''), t('Save configuration'));
+
+ // Logout and check that the user login block is shown on default 403 pages.
+ $this->drupalLogout();
+
+ $this->drupalGet('admin');
+ $this->assertText(t('Access denied'), t('Found the default 403 page'));
+ $this->assertResponse(403);
+ $this->assertText(t('User login'), t('Blocks are shown on the default 403 page'));
+
+ // Log back in, set the custom 403 page to /user and remove the block
+ $this->drupalLogin($this->admin_user);
+ variable_set('site_403', 'user');
+ $this->drupalPost('admin/structure/block', array('blocks[user_login][region]' => '-1'), t('Save blocks'));
+
+ // Check that we can log in from the 403 page.
+ $this->drupalLogout();
+ $edit = array(
+ 'name' => $this->admin_user->name,
+ 'pass' => $this->admin_user->pass_raw,
+ );
+ $this->drupalPost('admin/config/system/site-information', $edit, t('Log in'));
+
+ // Check that we're still on the same page.
+ $this->assertText(t('Site information'));
+ }
+}
+
+class PageNotFoundTestCase extends DrupalWebTestCase {
+ protected $admin_user;
+
+ /**
+ * Implement getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => '404 functionality',
+ 'description' => "Tests page not found functionality, including custom 404 pages.",
+ 'group' => 'System'
+ );
+ }
+
+ /**
+ * Implement setUp().
+ */
+ function setUp() {
+ parent::setUp();
+
+ // Create an administrative user.
+ $this->admin_user = $this->drupalCreateUser(array('administer site configuration'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ function testPageNotFound() {
+ $this->drupalGet($this->randomName(10));
+ $this->assertText(t('Page not found'), t('Found the default 404 page'));
+
+ $edit = array(
+ 'title' => $this->randomName(10),
+ 'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(100)))),
+ );
+ $node = $this->drupalCreateNode($edit);
+
+ // Use a custom 404 page.
+ $this->drupalPost('admin/config/system/site-information', array('site_404' => 'node/' . $node->nid), t('Save configuration'));
+
+ $this->drupalGet($this->randomName(10));
+ $this->assertText($node->title, t('Found the custom 404 page'));
+ }
+}
+
+/**
+ * Tests site maintenance functionality.
+ */
+class SiteMaintenanceTestCase extends DrupalWebTestCase {
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Site maintenance mode functionality',
+ 'description' => 'Test access to site while in maintenance mode.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create a user allowed to access site in maintenance mode.
+ $this->user = $this->drupalCreateUser(array('access site in maintenance mode'));
+ // Create an administrative user.
+ $this->admin_user = $this->drupalCreateUser(array('administer site configuration', 'access site in maintenance mode'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Verify site maintenance mode functionality.
+ */
+ function testSiteMaintenance() {
+ // Turn on maintenance mode.
+ $edit = array(
+ 'maintenance_mode' => 1,
+ );
+ $this->drupalPost('admin/config/development/maintenance', $edit, t('Save configuration'));
+
+ $admin_message = t('Operating in maintenance mode. <a href="@url">Go online.</a>', array('@url' => url('admin/config/development/maintenance')));
+ $user_message = t('Operating in maintenance mode.');
+ $offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')));
+
+ $this->drupalGet('');
+ $this->assertRaw($admin_message, t('Found the site maintenance mode message.'));
+
+ // Logout and verify that offline message is displayed.
+ $this->drupalLogout();
+ $this->drupalGet('');
+ $this->assertText($offline_message);
+ $this->drupalGet('node');
+ $this->assertText($offline_message);
+ $this->drupalGet('user/register');
+ $this->assertText($offline_message);
+
+ // Verify that user is able to log in.
+ $this->drupalGet('user');
+ $this->assertNoText($offline_message);
+ $this->drupalGet('user/login');
+ $this->assertNoText($offline_message);
+
+ // Log in user and verify that maintenance mode message is displayed
+ // directly after login.
+ $edit = array(
+ 'name' => $this->user->name,
+ 'pass' => $this->user->pass_raw,
+ );
+ $this->drupalPost(NULL, $edit, t('Log in'));
+ $this->assertText($user_message);
+
+ // Log in administrative user and configure a custom site offline message.
+ $this->drupalLogout();
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('admin/config/development/maintenance');
+ $this->assertNoRaw($admin_message, t('Site maintenance mode message not displayed.'));
+
+ $offline_message = 'Sorry, not online.';
+ $edit = array(
+ 'maintenance_mode_message' => $offline_message,
+ );
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+
+ // Logout and verify that custom site offline message is displayed.
+ $this->drupalLogout();
+ $this->drupalGet('');
+ $this->assertRaw($offline_message, t('Found the site offline message.'));
+
+ // Verify that custom site offline message is not displayed on user/password.
+ $this->drupalGet('user/password');
+ $this->assertText(t('Username or e-mail address'), t('Anonymous users can access user/password'));
+
+ // Submit password reset form.
+ $edit = array(
+ 'name' => $this->user->name,
+ );
+ $this->drupalPost('user/password', $edit, t('E-mail new password'));
+ $mails = $this->drupalGetMails();
+ $start = strpos($mails[0]['body'], 'user/reset/'. $this->user->uid);
+ $path = substr($mails[0]['body'], $start, 66 + strlen($this->user->uid));
+
+ // Log in with temporary login link.
+ $this->drupalPost($path, array(), t('Log in'));
+ $this->assertText($user_message);
+ }
+}
+
+/**
+ * Tests generic date and time handling capabilities of Drupal.
+ */
+class DateTimeFunctionalTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Date and time',
+ 'description' => 'Configure date and time settings. Test date formatting and time zone handling, including daylight saving time.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('locale'));
+
+ // Create admin user and log in admin user.
+ $this->admin_user = $this->drupalCreateUser(array('administer site configuration'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+
+ /**
+ * Test time zones and DST handling.
+ */
+ function testTimeZoneHandling() {
+ // Setup date/time settings for Honolulu time.
+ variable_set('date_default_timezone', 'Pacific/Honolulu');
+ variable_set('configurable_timezones', 0);
+ variable_set('date_format_medium', 'Y-m-d H:i:s O');
+
+ // Create some nodes with different authored-on dates.
+ $date1 = '2007-01-31 21:00:00 -1000';
+ $date2 = '2007-07-31 21:00:00 -1000';
+ $node1 = $this->drupalCreateNode(array('created' => strtotime($date1), 'type' => 'article'));
+ $node2 = $this->drupalCreateNode(array('created' => strtotime($date2), 'type' => 'article'));
+
+ // Confirm date format and time zone.
+ $this->drupalGet("node/$node1->nid");
+ $this->assertText('2007-01-31 21:00:00 -1000', t('Date should be identical, with GMT offset of -10 hours.'));
+ $this->drupalGet("node/$node2->nid");
+ $this->assertText('2007-07-31 21:00:00 -1000', t('Date should be identical, with GMT offset of -10 hours.'));
+
+ // Set time zone to Los Angeles time.
+ variable_set('date_default_timezone', 'America/Los_Angeles');
+
+ // Confirm date format and time zone.
+ $this->drupalGet("node/$node1->nid");
+ $this->assertText('2007-01-31 23:00:00 -0800', t('Date should be two hours ahead, with GMT offset of -8 hours.'));
+ $this->drupalGet("node/$node2->nid");
+ $this->assertText('2007-08-01 00:00:00 -0700', t('Date should be three hours ahead, with GMT offset of -7 hours.'));
+ }
+
+ /**
+ * Test date type configuration.
+ */
+ function testDateTypeConfiguration() {
+ // Confirm system date types appear.
+ $this->drupalGet('admin/config/regional/date-time');
+ $this->assertText(t('Medium'), 'System date types appear in date type list.');
+ $this->assertNoRaw('href="/admin/config/regional/date-time/types/medium/delete"', 'No delete link appear for system date types.');
+
+ // Add custom date type.
+ $this->clickLink(t('Add date type'));
+ $date_type = strtolower($this->randomName(8));
+ $machine_name = 'machine_' . $date_type;
+ $date_format = 'd.m.Y - H:i';
+ $edit = array(
+ 'date_type' => $date_type,
+ 'machine_name' => $machine_name,
+ 'date_format' => $date_format,
+ );
+ $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type'));
+ $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time', array('absolute' => TRUE)), t('Correct page redirection.'));
+ $this->assertText(t('New date type added successfully.'), 'Date type added confirmation message appears.');
+ $this->assertText($date_type, 'Custom date type appears in the date type list.');
+ $this->assertText(t('delete'), 'Delete link for custom date type appears.');
+
+ // Delete custom date type.
+ $this->clickLink(t('delete'));
+ $this->drupalPost('admin/config/regional/date-time/types/' . $machine_name . '/delete', array(), t('Remove'));
+ $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time', array('absolute' => TRUE)), t('Correct page redirection.'));
+ $this->assertText(t('Removed date type ' . $date_type), 'Custom date type removed.');
+ }
+
+ /**
+ * Test date format configuration.
+ */
+ function testDateFormatConfiguration() {
+ // Confirm 'no custom date formats available' message appears.
+ $this->drupalGet('admin/config/regional/date-time/formats');
+ $this->assertText(t('No custom date formats available.'), 'No custom date formats message appears.');
+
+ // Add custom date format.
+ $this->clickLink(t('Add format'));
+ $edit = array(
+ 'date_format' => 'Y',
+ );
+ $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format'));
+ $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), t('Correct page redirection.'));
+ $this->assertNoText(t('No custom date formats available.'), 'No custom date formats message does not appear.');
+ $this->assertText(t('Custom date format added.'), 'Custom date format added.');
+
+ // Ensure custom date format appears in date type configuration options.
+ $this->drupalGet('admin/config/regional/date-time');
+ $this->assertRaw('<option value="Y">', 'Custom date format appears in options.');
+
+ // Edit custom date format.
+ $this->drupalGet('admin/config/regional/date-time/formats');
+ $this->clickLink(t('edit'));
+ $edit = array(
+ 'date_format' => 'Y m',
+ );
+ $this->drupalPost($this->getUrl(), $edit, t('Save format'));
+ $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), t('Correct page redirection.'));
+ $this->assertText(t('Custom date format updated.'), 'Custom date format successfully updated.');
+
+ // Delete custom date format.
+ $this->clickLink(t('delete'));
+ $this->drupalPost($this->getUrl(), array(), t('Remove'));
+ $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), t('Correct page redirection.'));
+ $this->assertText(t('Removed date format'), 'Custom date format removed successfully.');
+ }
+
+ /**
+ * Test if the date formats are stored properly.
+ */
+ function testDateFormatStorage() {
+ $date_format = array(
+ 'type' => 'short',
+ 'format' => 'dmYHis',
+ 'locked' => 0,
+ 'is_new' => 1,
+ );
+ system_date_format_save($date_format);
+
+ $format = db_select('date_formats', 'df')
+ ->fields('df', array('format'))
+ ->condition('type', 'short')
+ ->condition('format', 'dmYHis')
+ ->execute()
+ ->fetchField();
+ $this->verbose($format);
+ $this->assertEqual('dmYHis', $format, 'Unlocalized date format resides in general table.');
+
+ $format = db_select('date_format_locale', 'dfl')
+ ->fields('dfl', array('format'))
+ ->condition('type', 'short')
+ ->condition('format', 'dmYHis')
+ ->execute()
+ ->fetchField();
+ $this->assertFalse($format, 'Unlocalized date format resides not in localized table.');
+
+ // Enable German language
+ $language = (object) array(
+ 'language' => 'de',
+ 'default' => TRUE,
+ );
+ locale_language_save($language);
+
+ $date_format = array(
+ 'type' => 'short',
+ 'format' => 'YMDHis',
+ 'locales' => array('de', 'tr'),
+ 'locked' => 0,
+ 'is_new' => 1,
+ );
+ system_date_format_save($date_format);
+
+ $format = db_select('date_format_locale', 'dfl')
+ ->fields('dfl', array('format'))
+ ->condition('type', 'short')
+ ->condition('format', 'YMDHis')
+ ->condition('language', 'de')
+ ->execute()
+ ->fetchField();
+ $this->assertEqual('YMDHis', $format, 'Localized date format resides in localized table.');
+
+ $format = db_select('date_formats', 'df')
+ ->fields('df', array('format'))
+ ->condition('type', 'short')
+ ->condition('format', 'YMDHis')
+ ->execute()
+ ->fetchField();
+ $this->assertEqual('YMDHis', $format, 'Localized date format resides in general table too.');
+
+ $format = db_select('date_format_locale', 'dfl')
+ ->fields('dfl', array('format'))
+ ->condition('type', 'short')
+ ->condition('format', 'YMDHis')
+ ->condition('language', 'tr')
+ ->execute()
+ ->fetchColumn();
+ $this->assertFalse($format, 'Localized date format for disabled language is ignored.');
+ }
+}
+
+class PageTitleFiltering extends DrupalWebTestCase {
+ protected $content_user;
+ protected $saved_title;
+
+ /**
+ * Implement getInfo().
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'HTML in page titles',
+ 'description' => 'Tests correct handling or conversion by drupal_set_title() and drupal_get_title() and checks the correct escaping of site name and slogan.',
+ 'group' => 'System'
+ );
+ }
+
+ /**
+ * Implement setUp().
+ */
+ function setUp() {
+ parent::setUp();
+
+ $this->content_user = $this->drupalCreateUser(array('create page content', 'access content', 'administer themes', 'administer site configuration'));
+ $this->drupalLogin($this->content_user);
+ $this->saved_title = drupal_get_title();
+ }
+
+ /**
+ * Reset page title.
+ */
+ function tearDown() {
+ // Restore the page title.
+ drupal_set_title($this->saved_title, PASS_THROUGH);
+
+ parent::tearDown();
+ }
+
+ /**
+ * Tests the handling of HTML by drupal_set_title() and drupal_get_title()
+ */
+ function testTitleTags() {
+ $title = "string with <em>HTML</em>";
+ // drupal_set_title's $filter is CHECK_PLAIN by default, so the title should be
+ // returned with check_plain().
+ drupal_set_title($title, CHECK_PLAIN);
+ $this->assertTrue(strpos(drupal_get_title(), '<em>') === FALSE, t('Tags in title converted to entities when $output is CHECK_PLAIN.'));
+ // drupal_set_title's $filter is passed as PASS_THROUGH, so the title should be
+ // returned with HTML.
+ drupal_set_title($title, PASS_THROUGH);
+ $this->assertTrue(strpos(drupal_get_title(), '<em>') !== FALSE, t('Tags in title are not converted to entities when $output is PASS_THROUGH.'));
+ // Generate node content.
+ $langcode = LANGUAGE_NONE;
+ $edit = array(
+ "title" => '!SimpleTest! ' . $title . $this->randomName(20),
+ "body[$langcode][0][value]" => '!SimpleTest! test body' . $this->randomName(200),
+ );
+ // Create the node with HTML in the title.
+ $this->drupalPost('node/add/page', $edit, t('Save'));
+
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $this->assertNotNull($node, 'Node created and found in database');
+ $this->drupalGet("node/" . $node->nid);
+ $this->assertText(check_plain($edit["title"]), 'Check to make sure tags in the node title are converted.');
+ }
+ /**
+ * Test if the title of the site is XSS proof.
+ */
+ function testTitleXSS() {
+ // Set some title with JavaScript and HTML chars to escape.
+ $title = '</title><script type="text/javascript">alert("Title XSS!");</script> & < > " \' ';
+ $title_filtered = check_plain($title);
+
+ $slogan = '<script type="text/javascript">alert("Slogan XSS!");</script>';
+ $slogan_filtered = filter_xss_admin($slogan);
+
+ // Activate needed appearance settings.
+ $edit = array(
+ 'toggle_name' => TRUE,
+ 'toggle_slogan' => TRUE,
+ 'toggle_main_menu' => TRUE,
+ 'toggle_secondary_menu' => TRUE,
+ );
+ $this->drupalPost('admin/appearance/settings', $edit, t('Save configuration'));
+
+ // Set title and slogan.
+ $edit = array(
+ 'site_name' => $title,
+ 'site_slogan' => $slogan,
+ );
+ $this->drupalPost('admin/config/system/site-information', $edit, t('Save configuration'));
+
+ // Load frontpage.
+ $this->drupalGet('');
+
+ // Test the title.
+ $this->assertNoRaw($title, 'Check for the unfiltered version of the title.');
+ // Adding </title> so we do not test the escaped version from drupal_set_title().
+ $this->assertRaw($title_filtered . '</title>', 'Check for the filtered version of the title.');
+
+ // Test the slogan.
+ $this->assertNoRaw($slogan, 'Check for the unfiltered version of the slogan.');
+ $this->assertRaw($slogan_filtered, 'Check for the filtered version of the slogan.');
+ }
+}
+
+/**
+ * Test front page functionality and administration.
+ */
+class FrontPageTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Front page',
+ 'description' => 'Tests front page functionality and administration.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('system_test');
+
+ // Create admin user, log in admin user, and create one node.
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'administer site configuration'));
+ $this->drupalLogin($this->admin_user);
+ $this->node_path = "node/" . $this->drupalCreateNode(array('promote' => 1))->nid;
+
+ // Enable front page logging in system_test.module.
+ variable_set('front_page_output', 1);
+ }
+
+ /**
+ * Test front page functionality.
+ */
+ function testDrupalIsFrontPage() {
+ $this->drupalGet('');
+ $this->assertText(t('On front page.'), t('Path is the front page.'));
+ $this->drupalGet('node');
+ $this->assertText(t('On front page.'), t('Path is the front page.'));
+ $this->drupalGet($this->node_path);
+ $this->assertNoText(t('On front page.'), t('Path is not the front page.'));
+
+ // Change the front page to an invalid path.
+ $edit = array('site_frontpage' => 'kittens');
+ $this->drupalPost('admin/config/system/site-information', $edit, t('Save configuration'));
+ $this->assertText(t("The path '@path' is either invalid or you do not have access to it.", array('@path' => $edit['site_frontpage'])));
+
+ // Change the front page to a valid path.
+ $edit['site_frontpage'] = $this->node_path;
+ $this->drupalPost('admin/config/system/site-information', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), t('The front page path has been saved.'));
+
+ $this->drupalGet('');
+ $this->assertText(t('On front page.'), t('Path is the front page.'));
+ $this->drupalGet('node');
+ $this->assertNoText(t('On front page.'), t('Path is not the front page.'));
+ $this->drupalGet($this->node_path);
+ $this->assertText(t('On front page.'), t('Path is the front page.'));
+ }
+}
+
+class SystemBlockTestCase extends DrupalWebTestCase {
+ protected $profile = 'testing';
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Block functionality',
+ 'description' => 'Configure and move powered-by block.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('block');
+
+ // Create and login user
+ $admin_user = $this->drupalCreateUser(array('administer blocks', 'access administration pages'));
+ $this->drupalLogin($admin_user);
+ }
+
+ /**
+ * Test displaying and hiding the powered-by and help blocks.
+ */
+ function testSystemBlocks() {
+ // Set block title and some settings to confirm that the interface is available.
+ $this->drupalPost('admin/structure/block/manage/system/powered-by/configure', array('title' => $this->randomName(8)), t('Save block'));
+ $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.'));
+
+ // Set the powered-by block to the footer region.
+ $edit = array();
+ $edit['blocks[system_powered-by][region]'] = 'footer';
+ $edit['blocks[system_main][region]'] = 'content';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to footer region.'));
+
+ // Confirm that the block is being displayed.
+ $this->drupalGet('node');
+ $this->assertRaw('id="block-system-powered-by"', t('Block successfully being displayed on the page.'));
+
+ // Set the block to the disabled region.
+ $edit = array();
+ $edit['blocks[system_powered-by][region]'] = '-1';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+ // Confirm that the block is hidden.
+ $this->assertNoRaw('id="block-system-powered-by"', t('Block no longer appears on page.'));
+
+ // For convenience of developers, set the block to its default settings.
+ $edit = array();
+ $edit['blocks[system_powered-by][region]'] = 'footer';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->drupalPost('admin/structure/block/manage/system/powered-by/configure', array('title' => ''), t('Save block'));
+
+ // Set the help block to the help region.
+ $edit = array();
+ $edit['blocks[system_help][region]'] = 'help';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+ // Test displaying the help block with block caching enabled.
+ variable_set('block_cache', TRUE);
+ $this->drupalGet('admin/structure/block/add');
+ $this->assertRaw(t('Use this page to create a new custom block.'));
+ $this->drupalGet('admin/index');
+ $this->assertRaw(t('This page shows you all available administration tasks for each module.'));
+ }
+}
+
+/**
+ * Test main content rendering fallback provided by system module.
+ */
+class SystemMainContentFallback extends DrupalWebTestCase {
+ protected $admin_user;
+ protected $web_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Main content rendering fallback',
+ 'description' => ' Test system module main content rendering fallback.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('system_test');
+
+ // Create and login admin user.
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'access administration pages',
+ 'administer site configuration',
+ 'administer modules',
+ 'administer blocks',
+ 'administer nodes',
+ ));
+ $this->drupalLogin($this->admin_user);
+
+ // Create a web user.
+ $this->web_user = $this->drupalCreateUser(array('access user profiles', 'access content'));
+ }
+
+ /**
+ * Test availability of main content.
+ */
+ function testMainContentFallback() {
+ $edit = array();
+ // Disable the dashboard module, which depends on the block module.
+ $edit['modules[Core][dashboard][enable]'] = FALSE;
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+ // Disable the block module.
+ $edit['modules[Core][block][enable]'] = FALSE;
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+ module_list(TRUE);
+ $this->assertFalse(module_exists('block'), t('Block module disabled.'));
+
+ // At this point, no region is filled and fallback should be triggered.
+ $this->drupalGet('admin/config/system/site-information');
+ $this->assertField('site_name', t('Admin interface still available.'));
+
+ // Fallback should not trigger when another module is handling content.
+ $this->drupalGet('system-test/main-content-handling');
+ $this->assertRaw('id="system-test-content"', t('Content handled by another module'));
+ $this->assertText(t('Content to test main content fallback'), t('Main content still displayed.'));
+
+ // Fallback should trigger when another module
+ // indicates that it is not handling the content.
+ $this->drupalGet('system-test/main-content-fallback');
+ $this->assertText(t('Content to test main content fallback'), t('Main content fallback properly triggers.'));
+
+ // Fallback should not trigger when another module is handling content.
+ // Note that this test ensures that no duplicate
+ // content gets created by the fallback.
+ $this->drupalGet('system-test/main-content-duplication');
+ $this->assertNoText(t('Content to test main content fallback'), t('Main content not duplicated.'));
+
+ // Request a user* page and see if it is displayed.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('user/' . $this->web_user->uid . '/edit');
+ $this->assertField('mail', t('User interface still available.'));
+
+ // Enable the block module again.
+ $this->drupalLogin($this->admin_user);
+ $edit = array();
+ $edit['modules[Core][block][enable]'] = 'block';
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+ module_list(TRUE);
+ $this->assertTrue(module_exists('block'), t('Block module re-enabled.'));
+ }
+}
+
+/**
+ * Tests for the theme interface functionality.
+ */
+class SystemThemeFunctionalTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Theme interface functionality',
+ 'description' => 'Tests the theme interface functionality by enabling and switching themes, and using an administration theme.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'view the administration theme', 'administer themes', 'bypass node access', 'administer blocks'));
+ $this->drupalLogin($this->admin_user);
+ $this->node = $this->drupalCreateNode();
+ }
+
+ /**
+ * Test the theme settings form.
+ */
+ function testThemeSettings() {
+ // Specify a filesystem path to be used for the logo.
+ $file = current($this->drupalGetTestFiles('image'));
+ $fullpath = drupal_realpath($file->uri);
+ $edit = array(
+ 'default_logo' => FALSE,
+ 'logo_path' => $fullpath,
+ );
+ $this->drupalPost('admin/appearance/settings', $edit, t('Save configuration'));
+ $this->drupalGet('node');
+ $this->assertRaw($fullpath, t('Logo path successfully changed.'));
+
+ // Upload a file to use for the logo.
+ $file = current($this->drupalGetTestFiles('image'));
+ $edit = array(
+ 'default_logo' => FALSE,
+ 'logo_path' => '',
+ 'files[logo_upload]' => drupal_realpath($file->uri),
+ );
+ $options = array();
+ $this->drupalPost('admin/appearance/settings', $edit, t('Save configuration'), $options);
+ $this->drupalGet('node');
+ $this->assertRaw($file->name, t('Logo file successfully uploaded.'));
+ }
+
+ /**
+ * Test the administration theme functionality.
+ */
+ function testAdministrationTheme() {
+ theme_enable(array('stark'));
+ variable_set('theme_default', 'stark');
+ // Enable an administration theme and show it on the node admin pages.
+ $edit = array(
+ 'admin_theme' => 'seven',
+ 'node_admin_theme' => TRUE,
+ );
+ $this->drupalPost('admin/appearance', $edit, t('Save configuration'));
+
+ $this->drupalGet('admin/config');
+ $this->assertRaw('themes/seven', t('Administration theme used on an administration page.'));
+
+ $this->drupalGet('node/' . $this->node->nid);
+ $this->assertRaw('themes/stark', t('Site default theme used on node page.'));
+
+ $this->drupalGet('node/add');
+ $this->assertRaw('themes/seven', t('Administration theme used on the add content page.'));
+
+ $this->drupalGet('node/' . $this->node->nid . '/edit');
+ $this->assertRaw('themes/seven', t('Administration theme used on the edit content page.'));
+
+ // Disable the admin theme on the node admin pages.
+ $edit = array(
+ 'node_admin_theme' => FALSE,
+ );
+ $this->drupalPost('admin/appearance', $edit, t('Save configuration'));
+
+ $this->drupalGet('admin/config');
+ $this->assertRaw('themes/seven', t('Administration theme used on an administration page.'));
+
+ $this->drupalGet('node/add');
+ $this->assertRaw('themes/stark', t('Site default theme used on the add content page.'));
+
+ // Reset to the default theme settings.
+ variable_set('theme_default', 'bartik');
+ $edit = array(
+ 'admin_theme' => '0',
+ 'node_admin_theme' => FALSE,
+ );
+ $this->drupalPost('admin/appearance', $edit, t('Save configuration'));
+
+ $this->drupalGet('admin');
+ $this->assertRaw('themes/bartik', t('Site default theme used on administration page.'));
+
+ $this->drupalGet('node/add');
+ $this->assertRaw('themes/bartik', t('Site default theme used on the add content page.'));
+ }
+
+ /**
+ * Test switching the default theme.
+ */
+ function testSwitchDefaultTheme() {
+ // Enable "stark" and set it as the default theme.
+ theme_enable(array('stark'));
+ $this->drupalGet('admin/appearance');
+ $this->clickLink(t('Set default'), 1);
+ $this->assertTrue(variable_get('theme_default', '') == 'stark', t('Site default theme switched successfully.'));
+
+ // Test the default theme on the secondary links (blocks admin page).
+ $this->drupalGet('admin/structure/block');
+ $this->assertText('Stark(' . t('active tab') . ')', t('Default local task on blocks admin page is the default theme.'));
+ // Switch back to Bartik and test again to test that the menu cache is cleared.
+ $this->drupalGet('admin/appearance');
+ $this->clickLink(t('Set default'), 0);
+ $this->drupalGet('admin/structure/block');
+ $this->assertText('Bartik(' . t('active tab') . ')', t('Default local task on blocks admin page has changed.'));
+ }
+}
+
+
+/**
+ * Test the basic queue functionality.
+ */
+class QueueTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Queue functionality',
+ 'description' => 'Queues and dequeues a set of items to check the basic queue functionality.',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Queues and dequeues a set of items to check the basic queue functionality.
+ */
+ function testQueue() {
+ // Create two queues.
+ $queue1 = DrupalQueue::get($this->randomName());
+ $queue1->createQueue();
+ $queue2 = DrupalQueue::get($this->randomName());
+ $queue2->createQueue();
+
+ // Create four items.
+ $data = array();
+ for ($i = 0; $i < 4; $i++) {
+ $data[] = array($this->randomName() => $this->randomName());
+ }
+
+ // Queue items 1 and 2 in the queue1.
+ $queue1->createItem($data[0]);
+ $queue1->createItem($data[1]);
+
+ // Retrieve two items from queue1.
+ $items = array();
+ $new_items = array();
+
+ $items[] = $item = $queue1->claimItem();
+ $new_items[] = $item->data;
+
+ $items[] = $item = $queue1->claimItem();
+ $new_items[] = $item->data;
+
+ // First two dequeued items should match the first two items we queued.
+ $this->assertEqual($this->queueScore($data, $new_items), 2, t('Two items matched'));
+
+ // Add two more items.
+ $queue1->createItem($data[2]);
+ $queue1->createItem($data[3]);
+
+ $this->assertTrue($queue1->numberOfItems(), t('Queue 1 is not empty after adding items.'));
+ $this->assertFalse($queue2->numberOfItems(), t('Queue 2 is empty while Queue 1 has items'));
+
+ $items[] = $item = $queue1->claimItem();
+ $new_items[] = $item->data;
+
+ $items[] = $item = $queue1->claimItem();
+ $new_items[] = $item->data;
+
+ // All dequeued items should match the items we queued exactly once,
+ // therefore the score must be exactly 4.
+ $this->assertEqual($this->queueScore($data, $new_items), 4, t('Four items matched'));
+
+ // There should be no duplicate items.
+ $this->assertEqual($this->queueScore($new_items, $new_items), 4, t('Four items matched'));
+
+ // Delete all items from queue1.
+ foreach ($items as $item) {
+ $queue1->deleteItem($item);
+ }
+
+ // Check that both queues are empty.
+ $this->assertFalse($queue1->numberOfItems(), t('Queue 1 is empty'));
+ $this->assertFalse($queue2->numberOfItems(), t('Queue 2 is empty'));
+ }
+
+ /**
+ * This function returns the number of equal items in two arrays.
+ */
+ function queueScore($items, $new_items) {
+ $score = 0;
+ foreach ($items as $item) {
+ foreach ($new_items as $new_item) {
+ if ($item === $new_item) {
+ $score++;
+ }
+ }
+ }
+ return $score;
+ }
+}
+
+/**
+ * Test token replacement in strings.
+ */
+class TokenReplaceTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Token replacement',
+ 'description' => 'Generates text using placeholders for dummy content to check token replacement.',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Creates a user and a node, then tests the tokens generated from them.
+ */
+ function testTokenReplacement() {
+ // Create the initial objects.
+ $account = $this->drupalCreateUser();
+ $node = $this->drupalCreateNode(array('uid' => $account->uid));
+ $node->title = '<blink>Blinking Text</blink>';
+ global $user, $language;
+
+ $source = '[node:title]'; // Title of the node we passed in
+ $source .= '[node:author:name]'; // Node author's name
+ $source .= '[node:created:since]'; // Time since the node was created
+ $source .= '[current-user:name]'; // Current user's name
+ $source .= '[date:short]'; // Short date format of REQUEST_TIME
+ $source .= '[user:name]'; // No user passed in, should be untouched
+ $source .= '[bogus:token]'; // Non-existent token
+
+ $target = check_plain($node->title);
+ $target .= check_plain($account->name);
+ $target .= format_interval(REQUEST_TIME - $node->created, 2, $language->language);
+ $target .= check_plain($user->name);
+ $target .= format_date(REQUEST_TIME, 'short', '', NULL, $language->language);
+
+ // Test that the clear parameter cleans out non-existent tokens.
+ $result = token_replace($source, array('node' => $node), array('language' => $language, 'clear' => TRUE));
+ $result = $this->assertEqual($target, $result, 'Valid tokens replaced while invalid tokens cleared out.');
+
+ // Test without using the clear parameter (non-existent token untouched).
+ $target .= '[user:name]';
+ $target .= '[bogus:token]';
+ $result = token_replace($source, array('node' => $node), array('language' => $language));
+ $this->assertEqual($target, $result, 'Valid tokens replaced while invalid tokens ignored.');
+
+ // Check that the results of token_generate are sanitized properly. This does NOT
+ // test the cleanliness of every token -- just that the $sanitize flag is being
+ // passed properly through the call stack and being handled correctly by a 'known'
+ // token, [node:title].
+ $raw_tokens = array('title' => '[node:title]');
+ $generated = token_generate('node', $raw_tokens, array('node' => $node));
+ $this->assertEqual($generated['[node:title]'], check_plain($node->title), t('Token sanitized.'));
+
+ $generated = token_generate('node', $raw_tokens, array('node' => $node), array('sanitize' => FALSE));
+ $this->assertEqual($generated['[node:title]'], $node->title, t('Unsanitized token generated properly.'));
+ }
+
+ /**
+ * Test whether token-replacement works in various contexts.
+ */
+ function testSystemTokenRecognition() {
+ global $language;
+
+ // Generate prefixes and suffixes for the token context.
+ $tests = array(
+ array('prefix' => 'this is the ', 'suffix' => ' site'),
+ array('prefix' => 'this is the', 'suffix' => 'site'),
+ array('prefix' => '[', 'suffix' => ']'),
+ array('prefix' => '', 'suffix' => ']]]'),
+ array('prefix' => '[[[', 'suffix' => ''),
+ array('prefix' => ':[:', 'suffix' => '--]'),
+ array('prefix' => '-[-', 'suffix' => ':]:'),
+ array('prefix' => '[:', 'suffix' => ']'),
+ array('prefix' => '[site:', 'suffix' => ':name]'),
+ array('prefix' => '[site:', 'suffix' => ']'),
+ );
+
+ // Check if the token is recognized in each of the contexts.
+ foreach ($tests as $test) {
+ $input = $test['prefix'] . '[site:name]' . $test['suffix'];
+ $expected = $test['prefix'] . 'Drupal' . $test['suffix'];
+ $output = token_replace($input, array(), array('language' => $language));
+ $this->assertTrue($output == $expected, t('Token recognized in string %string', array('%string' => $input)));
+ }
+ }
+
+ /**
+ * Tests the generation of all system site information tokens.
+ */
+ function testSystemSiteTokenReplacement() {
+ global $language;
+ $url_options = array(
+ 'absolute' => TRUE,
+ 'language' => $language,
+ );
+
+ // Set a few site variables.
+ variable_set('site_name', '<strong>Drupal<strong>');
+ variable_set('site_slogan', '<blink>Slogan</blink>');
+
+ // Generate and test sanitized tokens.
+ $tests = array();
+ $tests['[site:name]'] = check_plain(variable_get('site_name', 'Drupal'));
+ $tests['[site:slogan]'] = check_plain(variable_get('site_slogan', ''));
+ $tests['[site:mail]'] = 'simpletest@example.com';
+ $tests['[site:url]'] = url('<front>', $url_options);
+ $tests['[site:url-brief]'] = preg_replace(array('!^https?://!', '!/$!'), '', url('<front>', $url_options));
+ $tests['[site:login-url]'] = url('user', $url_options);
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.'));
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array(), array('language' => $language));
+ $this->assertEqual($output, $expected, t('Sanitized system site information token %token replaced.', array('%token' => $input)));
+ }
+
+ // Generate and test unsanitized tokens.
+ $tests['[site:name]'] = variable_get('site_name', 'Drupal');
+ $tests['[site:slogan]'] = variable_get('site_slogan', '');
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array(), array('language' => $language, 'sanitize' => FALSE));
+ $this->assertEqual($output, $expected, t('Unsanitized system site information token %token replaced.', array('%token' => $input)));
+ }
+ }
+
+ /**
+ * Tests the generation of all system date tokens.
+ */
+ function testSystemDateTokenReplacement() {
+ global $language;
+
+ // Set time to one hour before request.
+ $date = REQUEST_TIME - 3600;
+
+ // Generate and test tokens.
+ $tests = array();
+ $tests['[date:short]'] = format_date($date, 'short', '', NULL, $language->language);
+ $tests['[date:medium]'] = format_date($date, 'medium', '', NULL, $language->language);
+ $tests['[date:long]'] = format_date($date, 'long', '', NULL, $language->language);
+ $tests['[date:custom:m/j/Y]'] = format_date($date, 'custom', 'm/j/Y', NULL, $language->language);
+ $tests['[date:since]'] = format_interval((REQUEST_TIME - $date), 2, $language->language);
+ $tests['[date:raw]'] = filter_xss($date);
+
+ // Test to make sure that we generated something for each token.
+ $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.'));
+
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('date' => $date), array('language' => $language));
+ $this->assertEqual($output, $expected, t('Date token %token replaced.', array('%token' => $input)));
+ }
+ }
+}
+
+class InfoFileParserTestCase extends DrupalUnitTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Info file format parser',
+ 'description' => 'Tests proper parsing of a .info file formatted string.',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Test drupal_parse_info_format().
+ */
+ function testDrupalParseInfoFormat() {
+ $config = '
+simple = Value
+quoted = " Value"
+multiline = "Value
+ Value"
+array[] = Value1
+array[] = Value2
+array_assoc[a] = Value1
+array_assoc[b] = Value2
+array_deep[][][] = Value
+array_deep_assoc[a][b][c] = Value
+array_space[a b] = Value';
+
+ $expected = array(
+ 'simple' => 'Value',
+ 'quoted' => ' Value',
+ 'multiline' => "Value\n Value",
+ 'array' => array(
+ 0 => 'Value1',
+ 1 => 'Value2',
+ ),
+ 'array_assoc' => array(
+ 'a' => 'Value1',
+ 'b' => 'Value2',
+ ),
+ 'array_deep' => array(
+ 0 => array(
+ 0 => array(
+ 0 => 'Value',
+ ),
+ ),
+ ),
+ 'array_deep_assoc' => array(
+ 'a' => array(
+ 'b' => array(
+ 'c' => 'Value',
+ ),
+ ),
+ ),
+ 'array_space' => array(
+ 'a b' => 'Value',
+ ),
+ );
+
+ $parsed = drupal_parse_info_format($config);
+
+ $this->assertEqual($parsed['simple'], $expected['simple'], t('Set a simple value.'));
+ $this->assertEqual($parsed['quoted'], $expected['quoted'], t('Set a simple value in quotes.'));
+ $this->assertEqual($parsed['multiline'], $expected['multiline'], t('Set a multiline value.'));
+ $this->assertEqual($parsed['array'], $expected['array'], t('Set a simple array.'));
+ $this->assertEqual($parsed['array_assoc'], $expected['array_assoc'], t('Set an associative array.'));
+ $this->assertEqual($parsed['array_deep'], $expected['array_deep'], t('Set a nested array.'));
+ $this->assertEqual($parsed['array_deep_assoc'], $expected['array_deep_assoc'], t('Set a nested associative array.'));
+ $this->assertEqual($parsed['array_space'], $expected['array_space'], t('Set an array with a whitespace in the key.'));
+ $this->assertEqual($parsed, $expected, t('Entire parsed .info string and expected array are identical.'));
+ }
+}
+
+/**
+ * Tests the effectiveness of hook_system_info_alter().
+ */
+class SystemInfoAlterTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'System info alter',
+ 'description' => 'Tests the effectiveness of hook_system_info_alter().',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Tests that {system}.info is rebuilt after a module that implements
+ * hook_system_info_alter() is enabled. Also tests if core *_list() functions
+ * return freshly altered info.
+ */
+ function testSystemInfoAlter() {
+ // Enable our test module. Flush all caches, which we assert is the only
+ // thing necessary to use the rebuilt {system}.info.
+ module_enable(array('module_test'), FALSE);
+ drupal_flush_all_caches();
+ $this->assertTrue(module_exists('module_test'), t('Test module is enabled.'));
+
+ $info = $this->getSystemInfo('seven', 'theme');
+ $this->assertTrue(isset($info['regions']['test_region']), t('Altered theme info was added to {system}.info.'));
+ $seven_regions = system_region_list('seven');
+ $this->assertTrue(isset($seven_regions['test_region']), t('Altered theme info was returned by system_region_list().'));
+ $system_list_themes = system_list('theme');
+ $info = $system_list_themes['seven']->info;
+ $this->assertTrue(isset($info['regions']['test_region']), t('Altered theme info was returned by system_list().'));
+ $list_themes = list_themes();
+ $this->assertTrue(isset($list_themes['seven']->info['regions']['test_region']), t('Altered theme info was returned by list_themes().'));
+
+ // Disable the module and verify that {system}.info is rebuilt without it.
+ module_disable(array('module_test'), FALSE);
+ drupal_flush_all_caches();
+ $this->assertFalse(module_exists('module_test'), t('Test module is disabled.'));
+
+ $info = $this->getSystemInfo('seven', 'theme');
+ $this->assertFalse(isset($info['regions']['test_region']), t('Altered theme info was removed from {system}.info.'));
+ $seven_regions = system_region_list('seven');
+ $this->assertFalse(isset($seven_regions['test_region']), t('Altered theme info was not returned by system_region_list().'));
+ $system_list_themes = system_list('theme');
+ $info = $system_list_themes['seven']->info;
+ $this->assertFalse(isset($info['regions']['test_region']), t('Altered theme info was not returned by system_list().'));
+ $list_themes = list_themes();
+ $this->assertFalse(isset($list_themes['seven']->info['regions']['test_region']), t('Altered theme info was not returned by list_themes().'));
+ }
+
+ /**
+ * Returns the info array as it is stored in {system}.
+ *
+ * @param $name
+ * The name of the record in {system}.
+ * @param $type
+ * The type of record in {system}.
+ *
+ * @return
+ * Array of info, or FALSE if the record is not found.
+ */
+ function getSystemInfo($name, $type) {
+ $raw_info = db_query("SELECT info FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField();
+ return $raw_info ? unserialize($raw_info) : FALSE;
+ }
+}
+
+/**
+ * Tests for the update system functionality.
+ */
+class UpdateScriptFunctionalTest extends DrupalWebTestCase {
+ private $update_url;
+ private $update_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Update functionality',
+ 'description' => 'Tests the update script access and functionality.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('update_script_test');
+ $this->update_url = $GLOBALS['base_url'] . '/update.php';
+ $this->update_user = $this->drupalCreateUser(array('administer software updates'));
+ }
+
+ /**
+ * Tests access to the update script.
+ */
+ function testUpdateAccess() {
+ // Try accessing update.php without the proper permission.
+ $regular_user = $this->drupalCreateUser();
+ $this->drupalLogin($regular_user);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertResponse(403);
+
+ // Try accessing update.php as an anonymous user.
+ $this->drupalLogout();
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertResponse(403);
+
+ // Access the update page with the proper permission.
+ $this->drupalLogin($this->update_user);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertResponse(200);
+
+ // Access the update page as user 1.
+ $user1 = user_load(1);
+ $user1->pass_raw = user_password();
+ require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
+ $user1->pass = user_hash_password(trim($user1->pass_raw));
+ db_query("UPDATE {users} SET pass = :pass WHERE uid = :uid", array(':pass' => $user1->pass, ':uid' => $user1->uid));
+ $this->drupalLogin($user1);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertResponse(200);
+ }
+
+ /**
+ * Tests that requirements warnings and errors are correctly displayed.
+ */
+ function testRequirements() {
+ $this->drupalLogin($this->update_user);
+
+ // If there are no requirements warnings or errors, we expect to be able to
+ // go through the update process uninterrupted.
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->drupalPost(NULL, array(), t('Continue'));
+ $this->assertText(t('No pending updates.'), t('End of update process was reached.'));
+
+ // If there is a requirements warning, we expect it to be initially
+ // displayed, but clicking the link to proceed should allow us to go
+ // through the rest of the update process uninterrupted. (First run this
+ // test with pending updates to make sure they can be run successfully;
+ // then try again without pending updates to make sure that works too.)
+ variable_set('update_script_test_requirement_type', REQUIREMENT_WARNING);
+ drupal_set_installed_schema_version('update_script_test', drupal_get_installed_schema_version('update_script_test') - 1);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertText('This is a requirements warning provided by the update_script_test module.');
+ $this->clickLink('try again');
+ $this->assertNoText('This is a requirements warning provided by the update_script_test module.');
+ $this->drupalPost(NULL, array(), t('Continue'));
+ $this->drupalPost(NULL, array(), t('Apply pending updates'));
+ $this->assertText(t('The update_script_test_update_8000() update was executed successfully.'), t('End of update process was reached.'));
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertText('This is a requirements warning provided by the update_script_test module.');
+ $this->clickLink('try again');
+ $this->assertNoText('This is a requirements warning provided by the update_script_test module.');
+ $this->drupalPost(NULL, array(), t('Continue'));
+ $this->assertText(t('No pending updates.'), t('End of update process was reached.'));
+
+ // If there is a requirements error, it should be displayed even after
+ // clicking the link to proceed (since the problem that triggered the error
+ // has not been fixed).
+ variable_set('update_script_test_requirement_type', REQUIREMENT_ERROR);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertText('This is a requirements error provided by the update_script_test module.');
+ $this->clickLink('try again');
+ $this->assertText('This is a requirements error provided by the update_script_test module.');
+ }
+
+ /**
+ * Tests the effect of using the update script on the theme system.
+ */
+ function testThemeSystem() {
+ // Since visiting update.php triggers a rebuild of the theme system from an
+ // unusual maintenance mode environment, we check that this rebuild did not
+ // put any incorrect information about the themes into the database.
+ $original_theme_data = db_query("SELECT * FROM {system} WHERE type = 'theme' ORDER BY name")->fetchAll();
+ $this->drupalLogin($this->update_user);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $final_theme_data = db_query("SELECT * FROM {system} WHERE type = 'theme' ORDER BY name")->fetchAll();
+ $this->assertEqual($original_theme_data, $final_theme_data, t('Visiting update.php does not alter the information about themes stored in the database.'));
+ }
+}
+
+/**
+ * Functional tests for the flood control mechanism.
+ */
+class FloodFunctionalTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Flood control mechanism',
+ 'description' => 'Functional tests for the flood control mechanism.',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Test flood control mechanism clean-up.
+ */
+ function testCleanUp() {
+ $threshold = 1;
+ $window_expired = -1;
+ $name = 'flood_test_cleanup';
+
+ // Register expired event.
+ flood_register_event($name, $window_expired);
+ // Verify event is not allowed.
+ $this->assertFalse(flood_is_allowed($name, $threshold));
+ // Run cron and verify event is now allowed.
+ $this->cronRun();
+ $this->assertTrue(flood_is_allowed($name, $threshold));
+
+ // Register unexpired event.
+ flood_register_event($name);
+ // Verify event is not allowed.
+ $this->assertFalse(flood_is_allowed($name, $threshold));
+ // Run cron and verify event is still not allowed.
+ $this->cronRun();
+ $this->assertFalse(flood_is_allowed($name, $threshold));
+ }
+}
+
+/**
+ * Test HTTP file downloading capability.
+ */
+class RetrieveFileTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'HTTP file retrieval',
+ 'description' => 'Checks HTTP file fetching and error handling.',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Invokes system_retrieve_file() in several scenarios.
+ */
+ function testFileRetrieving() {
+ // Test 404 handling by trying to fetch a randomly named file.
+ drupal_mkdir($sourcedir = 'public://' . $this->randomName());
+ $filename = $this->randomName();
+ $url = file_create_url($sourcedir . '/' . $filename);
+ $retrieved_file = system_retrieve_file($url);
+ $this->assertFalse($retrieved_file, t('Non-existent file not fetched.'));
+
+ // Actually create that file, download it via HTTP and test the returned path.
+ file_put_contents($sourcedir . '/' . $filename, 'testing');
+ $retrieved_file = system_retrieve_file($url);
+ $this->assertEqual($retrieved_file, 'public://' . $filename, t('Sane path for downloaded file returned (public:// scheme).'));
+ $this->assertTrue(is_file($retrieved_file), t('Downloaded file does exist (public:// scheme).'));
+ $this->assertEqual(filesize($retrieved_file), 7, t('File size of downloaded file is correct (public:// scheme).'));
+ file_unmanaged_delete($retrieved_file);
+
+ // Test downloading file to a different location.
+ drupal_mkdir($targetdir = 'temporary://' . $this->randomName());
+ $retrieved_file = system_retrieve_file($url, $targetdir);
+ $this->assertEqual($retrieved_file, "$targetdir/$filename", t('Sane path for downloaded file returned (temporary:// scheme).'));
+ $this->assertTrue(is_file($retrieved_file), t('Downloaded file does exist (temporary:// scheme).'));
+ $this->assertEqual(filesize($retrieved_file), 7, t('File size of downloaded file is correct (temporary:// scheme).'));
+ file_unmanaged_delete($retrieved_file);
+
+ file_unmanaged_delete_recursive($sourcedir);
+ file_unmanaged_delete_recursive($targetdir);
+ }
+}
+
+/**
+ * Functional tests shutdown functions.
+ */
+class ShutdownFunctionsTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Shutdown functions',
+ 'description' => 'Functional tests for shutdown functions',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('system_test');
+ }
+
+ /**
+ * Test shutdown functions.
+ */
+ function testShutdownFunctions() {
+ $arg1 = $this->randomName();
+ $arg2 = $this->randomName();
+ $this->drupalGet('system-test/shutdown-functions/' . $arg1 . '/' . $arg2);
+ $this->assertText(t('First shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2)));
+ $this->assertText(t('Second shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2)));
+
+ // Make sure exceptions displayed through _drupal_render_exception_safe()
+ // are correctly escaped.
+ $this->assertRaw('Drupal is &amp;lt;blink&amp;gt;awesome&amp;lt;/blink&amp;gt;.');
+ }
+}
+
+/**
+ * Tests administrative overview pages.
+ */
+class SystemAdminTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Administrative pages',
+ 'description' => 'Tests output on administrative pages and compact mode functionality.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ // testAdminPages() requires Locale module.
+ parent::setUp(array('locale'));
+
+ // Create an administrator with all permissions, as well as a regular user
+ // who can only access administration pages and perform some Locale module
+ // administrative tasks, but not all of them.
+ $this->admin_user = $this->drupalCreateUser(array_keys(module_invoke_all('permission')));
+ $this->web_user = $this->drupalCreateUser(array(
+ 'access administration pages',
+ 'translate interface',
+ ));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Tests output on administrative listing pages.
+ */
+ function testAdminPages() {
+ // Go to Administration.
+ $this->drupalGet('admin');
+
+ // Verify that all visible, top-level administration links are listed on
+ // the main administration page.
+ foreach (menu_get_router() as $path => $item) {
+ if (strpos($path, 'admin/') === 0 && ($item['type'] & MENU_VISIBLE_IN_TREE) && $item['_number_parts'] == 2) {
+ $this->assertLink($item['title']);
+ $this->assertLinkByHref($path);
+ $this->assertText($item['description']);
+ }
+ }
+
+ // For each administrative listing page on which the Locale module appears,
+ // verify that there are links to the module's primary configuration pages,
+ // but no links to its individual sub-configuration pages. Also verify that
+ // a user with access to only some Locale module administration pages only
+ // sees links to the pages they have access to.
+ $admin_list_pages = array(
+ 'admin/index',
+ 'admin/config',
+ 'admin/config/regional',
+ );
+
+ foreach ($admin_list_pages as $page) {
+ // For the administrator, verify that there are links to Locale's primary
+ // configuration pages, but no links to individual sub-configuration
+ // pages.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet($page);
+ $this->assertLinkByHref('admin/config');
+ $this->assertLinkByHref('admin/config/regional/settings');
+ $this->assertLinkByHref('admin/config/regional/date-time');
+ $this->assertLinkByHref('admin/config/regional/language');
+ $this->assertNoLinkByHref('admin/config/regional/language/configure/session');
+ $this->assertNoLinkByHref('admin/config/regional/language/configure/url');
+ $this->assertLinkByHref('admin/config/regional/translate');
+ // On admin/index only, the administrator should also see a "Configure
+ // permissions" link for the Locale module.
+ if ($page == 'admin/index') {
+ $this->assertLinkByHref("admin/people/permissions#module-locale");
+ }
+
+ // For a less privileged user, verify that there are no links to Locale's
+ // primary configuration pages, but a link to the translate page exists.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet($page);
+ $this->assertLinkByHref('admin/config');
+ $this->assertNoLinkByHref('admin/config/regional/settings');
+ $this->assertNoLinkByHref('admin/config/regional/date-time');
+ $this->assertNoLinkByHref('admin/config/regional/language');
+ $this->assertNoLinkByHref('admin/config/regional/language/configure/session');
+ $this->assertNoLinkByHref('admin/config/regional/language/configure/url');
+ $this->assertLinkByHref('admin/config/regional/translate');
+ // This user cannot configure permissions, so even on admin/index should
+ // not see a "Configure permissions" link for the Locale module.
+ if ($page == 'admin/index') {
+ $this->assertNoLinkByHref("admin/people/permissions#module-locale");
+ }
+ }
+ }
+
+ /**
+ * Test compact mode.
+ */
+ function testCompactMode() {
+ $this->drupalGet('admin/compact/on');
+ $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode turns on.'));
+ $this->drupalGet('admin/compact/on');
+ $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode remains on after a repeat call.'));
+ $this->drupalGet('');
+ $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode persists on new requests.'));
+
+ $this->drupalGet('admin/compact/off');
+ $this->assertEqual($this->cookies['Drupal.visitor.admin_compact_mode']['value'], 'deleted', t('Compact mode turns off.'));
+ $this->drupalGet('admin/compact/off');
+ $this->assertEqual($this->cookies['Drupal.visitor.admin_compact_mode']['value'], 'deleted', t('Compact mode remains off after a repeat call.'));
+ $this->drupalGet('');
+ $this->assertTrue($this->cookies['Drupal.visitor.admin_compact_mode']['value'], t('Compact mode persists on new requests.'));
+ }
+}
+
+/**
+ * Tests authorize.php and related hooks.
+ */
+class SystemAuthorizeCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Authorize API',
+ 'description' => 'Tests the authorize.php script and related API.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('system_test'));
+
+ variable_set('allow_authorize_operations', TRUE);
+
+ // Create an administrator user.
+ $this->admin_user = $this->drupalCreateUser(array('administer software updates'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ /**
+ * Helper function to initialize authorize.php and load it via drupalGet().
+ *
+ * Initializing authorize.php needs to happen in the child Drupal
+ * installation, not the parent. So, we visit a menu callback provided by
+ * system_test.module which calls system_authorized_init() to initialize the
+ * $_SESSION inside the test site, not the framework site. This callback
+ * redirects to authorize.php when it's done initializing.
+ *
+ * @see system_authorized_init().
+ */
+ function drupalGetAuthorizePHP($page_title = 'system-test-auth') {
+ $this->drupalGet('system-test/authorize-init/' . $page_title);
+ }
+
+ /**
+ * Tests the FileTransfer hooks
+ */
+ function testFileTransferHooks() {
+ $page_title = $this->randomName(16);
+ $this->drupalGetAuthorizePHP($page_title);
+ $this->assertTitle(strtr('@title | Drupal', array('@title' => $page_title)), 'authorize.php page title is correct.');
+ $this->assertNoText('It appears you have reached this page in error.');
+ $this->assertText('To continue, provide your server connection details');
+ // Make sure we see the new connection method added by system_test.
+ $this->assertRaw('System Test FileTransfer');
+ // Make sure the settings form callback works.
+ $this->assertText('System Test Username');
+ }
+}
+
+/**
+ * Test the handling of requests containing 'index.php'.
+ */
+class SystemIndexPhpTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Index.php handling',
+ 'description' => "Test the handling of requests containing 'index.php'.",
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ }
+
+ /**
+ * Test index.php handling.
+ */
+ function testIndexPhpHandling() {
+ $index_php = $GLOBALS['base_url'] . '/index.php';
+
+ $this->drupalGet($index_php, array('external' => TRUE));
+ $this->assertResponse(200, t('Make sure index.php returns a valid page.'));
+
+ $this->drupalGet($index_php, array('external' => TRUE, 'query' => array('q' => 'user')));
+ $this->assertResponse(200, t('Make sure index.php?q=user returns a valid page.'));
+
+ $this->drupalGet($index_php .'/user', array('external' => TRUE));
+ $this->assertResponse(404, t("Make sure index.php/user returns a 'page not found'."));
+ }
+}
+
+/**
+ * Tests uuid.inc and related functions.
+ */
+class UuidUnitTestCase extends DrupalUnitTestCase {
+
+ /**
+ * The UUID object to be used for generating UUIDs.
+ *
+ * @var Uuid
+ */
+ protected $uuid;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'UUID handling',
+ 'description' => "Test the handling of Universally Unique IDentifiers (UUIDs).",
+ 'group' => 'System',
+ );
+ }
+
+ public function setUp() {
+ // Initiate the generator. This will lazy-load uuid.inc.
+ $this->uuid = new Uuid();
+ parent::setUp();
+ }
+
+ /**
+ * Test generating a UUID.
+ */
+ public function testGenerateUuid() {
+ $uuid = $this->uuid->generate();
+ $this->assertTrue($this->uuid->isValid($uuid), 'UUID generation works.');
+ }
+
+ /**
+ * Test that generated UUIDs are unique.
+ */
+ public function testUuidIsUnique() {
+ $uuid1 = $this->uuid->generate();
+ $uuid2 = $this->uuid->generate();
+ $this->assertNotEqual($uuid1, $uuid2, 'Same UUID was not generated twice.');
+ }
+
+ /**
+ * Test UUID validation.
+ */
+ function testUuidValidation() {
+ // These valid UUIDs.
+ $uuid_fqdn = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
+ $uuid_min = '00000000-0000-0000-0000-000000000000';
+ $uuid_max = 'ffffffff-ffff-ffff-ffff-ffffffffffff';
+
+ $this->assertTrue($this->uuid->isValid($uuid_fqdn), t('FQDN namespace UUID (@uuid) is valid', array('@uuid' => $uuid_fqdn)));
+ $this->assertTrue($this->uuid->isValid($uuid_min), t('Minimum UUID value (@uuid) is valid', array('@uuid' => $uuid_min)));
+ $this->assertTrue($this->uuid->isValid($uuid_max), t('Maximum UUID value (@uuid) is valid', array('@uuid' => $uuid_max)));
+
+ // These are invalid UUIDs.
+ $invalid_format = '0ab26e6b-f074-4e44-9da-601205fa0e976';
+ $invalid_length = '0ab26e6b-f074-4e44-9daf-1205fa0e9761f';
+
+ $this->assertFalse($this->uuid->isValid($invalid_format), t('@uuid is not a valid UUID', array('@uuid' => $invalid_format)));
+ $this->assertFalse($this->uuid->isValid($invalid_length), t('@uuid is not a valid UUID', array('@uuid' => $invalid_length)));
+
+ }
+}
diff --git a/core/modules/system/system.theme-rtl.css b/core/modules/system/system.theme-rtl.css
new file mode 100644
index 00000000000..0cd7fa6431f
--- /dev/null
+++ b/core/modules/system/system.theme-rtl.css
@@ -0,0 +1,53 @@
+
+/**
+ * @file
+ * RTL styles for common markup.
+ */
+
+/**
+ * HTML elements.
+ */
+th {
+ text-align: right;
+ padding-left: 1em;
+ padding-right: 0;
+}
+
+/**
+ * Markup generated by theme_item_list().
+ */
+.item-list ul li {
+ margin: 0 1.5em 0.25em 0;
+}
+
+/**
+ * Markup generated by theme_more_link().
+ */
+.more-link {
+ text-align: left;
+}
+
+/**
+ * Markup generated by theme_more_help_link().
+ */
+.more-help-link {
+ text-align: left;
+}
+.more-help-link a {
+ background-position: 100% 50%;
+ padding: 1px 20px 1px 0;
+}
+
+/**
+ * Collapsible fieldsets.
+ */
+html.js fieldset.collapsible .fieldset-legend {
+ background-position: 98% 75%;
+ padding-left: 0;
+ padding-right: 15px;
+}
+html.js fieldset.collapsed .fieldset-legend {
+ background-image: url(../../misc/menu-collapsed-rtl.png);
+ background-position: 98% 50%;
+}
+
diff --git a/core/modules/system/system.theme.css b/core/modules/system/system.theme.css
new file mode 100644
index 00000000000..f34a965ec99
--- /dev/null
+++ b/core/modules/system/system.theme.css
@@ -0,0 +1,239 @@
+
+/**
+ * @file
+ * Basic styling for common markup.
+ */
+
+/**
+ * HTML elements.
+ */
+fieldset {
+ margin-bottom: 1em;
+ padding: 0.5em;
+}
+form {
+ margin: 0;
+ padding: 0;
+}
+hr {
+ border: 1px solid gray;
+ height: 1px;
+}
+img {
+ border: 0;
+}
+table {
+ border-collapse: collapse;
+}
+th {
+ border-bottom: 3px solid #ccc;
+ padding-right: 1em; /* LTR */
+ text-align: left; /* LTR */
+}
+tr.even,
+tr.odd {
+ background-color: #eee;
+ border-bottom: 1px solid #ccc;
+ padding: 0.1em 0.6em;
+}
+
+/**
+ * Markup generated by theme_tablesort_indicator().
+ */
+th.active img {
+ display: inline;
+}
+td.active {
+ background-color: #ddd;
+}
+
+/**
+ * Markup generated by theme_item_list().
+ */
+.item-list .title {
+ font-weight: bold;
+}
+.item-list ul {
+ margin: 0 0 0.75em 0;
+ padding: 0;
+}
+.item-list ul li {
+ margin: 0 0 0.25em 1.5em; /* LTR */
+ padding: 0;
+}
+
+/**
+ * Markup generated by Form API.
+ */
+.form-item,
+.form-actions {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+tr.odd .form-item,
+tr.even .form-item {
+ margin-top: 0;
+ margin-bottom: 0;
+ white-space: nowrap;
+}
+.form-item .description {
+ font-size: 0.85em;
+}
+label {
+ display: block;
+ font-weight: bold;
+}
+label.option {
+ display: inline;
+ font-weight: normal;
+}
+.form-checkboxes .form-item,
+.form-radios .form-item {
+ margin-top: 0.4em;
+ margin-bottom: 0.4em;
+}
+.form-type-radio .description,
+.form-type-checkbox .description {
+ margin-left: 2.4em;
+}
+input.form-checkbox,
+input.form-radio {
+ vertical-align: middle;
+}
+.marker,
+.form-required {
+ color: #f00;
+}
+abbr.form-required, abbr.tabledrag-changed, abbr.ajax-changed {
+ border-bottom: none;
+}
+.form-item input.error,
+.form-item textarea.error,
+.form-item select.error {
+ border: 2px solid red;
+}
+
+/**
+ * Inline items.
+ */
+.container-inline .form-actions,
+.container-inline.form-actions {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+/**
+ * Markup generated by theme_more_link().
+ */
+.more-link {
+ text-align: right; /* LTR */
+}
+
+/**
+ * Markup generated by theme_more_help_link().
+ */
+.more-help-link {
+ text-align: right; /* LTR */
+}
+.more-help-link a {
+ background: url(../../misc/help.png) 0 50% no-repeat; /* LTR */
+ padding: 1px 0 1px 20px; /* LTR */
+}
+
+/**
+ * Markup generated by theme_pager().
+ */
+.item-list .pager {
+ clear: both;
+ text-align: center;
+}
+.item-list .pager li {
+ background-image: none;
+ display: inline;
+ list-style-type: none;
+ padding: 0.5em;
+}
+.pager-current {
+ font-weight: bold;
+}
+
+/**
+ * Autocomplete.
+ *
+ * @see autocomplete.js
+ */
+/* Suggestion list */
+#autocomplete li.selected {
+ background: #0072b9;
+ color: #fff;
+}
+
+/**
+ * Collapsible fieldsets.
+ *
+ * @see collapse.js
+ */
+html.js fieldset.collapsible .fieldset-legend {
+ background: url(../../misc/menu-expanded.png) 5px 65% no-repeat; /* LTR */
+ padding-left: 15px; /* LTR */
+}
+html.js fieldset.collapsed .fieldset-legend {
+ background-image: url(../../misc/menu-collapsed.png); /* LTR */
+ background-position: 5px 50%; /* LTR */
+}
+.fieldset-legend span.summary {
+ color: #999;
+ font-size: 0.9em;
+ margin-left: 0.5em;
+}
+
+/**
+ * TableDrag behavior.
+ *
+ * @see tabledrag.js
+ */
+tr.drag {
+ background-color: #fffff0;
+}
+tr.drag-previous {
+ background-color: #ffd;
+}
+.tabledrag-toggle-weight {
+ font-size: 0.9em;
+}
+body div.tabledrag-changed-warning {
+ margin-bottom: 0.5em;
+}
+
+/**
+ * TableSelect behavior.
+ *
+ * @see tableselect.js
+*/
+tr.selected td {
+ background: #ffc;
+}
+td.checkbox,
+th.checkbox {
+ text-align: center;
+}
+
+/**
+ * Progress bar.
+ *
+ * @see progress.js
+ */
+.progress {
+ font-weight: bold;
+}
+.progress .bar {
+ background: #ccc;
+ border-color: #666;
+ margin: 0 0.2em;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+.progress .filled {
+ background: #0072b9 url(../../misc/progress.gif);
+}
diff --git a/core/modules/system/system.tokens.inc b/core/modules/system/system.tokens.inc
new file mode 100644
index 00000000000..b612d1057e6
--- /dev/null
+++ b/core/modules/system/system.tokens.inc
@@ -0,0 +1,269 @@
+<?php
+
+/**
+ * @file
+ * Builds placeholder replacement tokens system-wide data.
+ *
+ * This file handles tokens for the global 'site' token type, as well as
+ * 'date' and 'file' tokens.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function system_token_info() {
+ $types['site'] = array(
+ 'name' => t("Site information"),
+ 'description' => t("Tokens for site-wide settings and other global information."),
+ );
+ $types['date'] = array(
+ 'name' => t("Dates"),
+ 'description' => t("Tokens related to times and dates."),
+ );
+ $types['file'] = array(
+ 'name' => t("Files"),
+ 'description' => t("Tokens related to uploaded files."),
+ 'needs-data' => 'file',
+ );
+
+ // Site-wide global tokens.
+ $site['name'] = array(
+ 'name' => t("Name"),
+ 'description' => t("The name of the site."),
+ );
+ $site['slogan'] = array(
+ 'name' => t("Slogan"),
+ 'description' => t("The slogan of the site."),
+ );
+ $site['mail'] = array(
+ 'name' => t("Email"),
+ 'description' => t("The administrative email address for the site."),
+ );
+ $site['url'] = array(
+ 'name' => t("URL"),
+ 'description' => t("The URL of the site's front page."),
+ );
+ $site['url-brief'] = array(
+ 'name' => t("URL (brief)"),
+ 'description' => t("The URL of the site's front page without the protocol."),
+ );
+ $site['login-url'] = array(
+ 'name' => t("Login page"),
+ 'description' => t("The URL of the site's login page."),
+ );
+
+ // Date related tokens.
+ $date['short'] = array(
+ 'name' => t("Short format"),
+ 'description' => t("A date in 'short' format. (%date)", array('%date' => format_date(REQUEST_TIME, 'short'))),
+ );
+ $date['medium'] = array(
+ 'name' => t("Medium format"),
+ 'description' => t("A date in 'medium' format. (%date)", array('%date' => format_date(REQUEST_TIME, 'medium'))),
+ );
+ $date['long'] = array(
+ 'name' => t("Long format"),
+ 'description' => t("A date in 'long' format. (%date)", array('%date' => format_date(REQUEST_TIME, 'long'))),
+ );
+ $date['custom'] = array(
+ 'name' => t("Custom format"),
+ 'description' => t("A date in a custom format. See !php-date for details.", array('!php-date' => l(t('the PHP documentation'), 'http://php.net/manual/en/function.date.php'))),
+ );
+ $date['since'] = array(
+ 'name' => t("Time-since"),
+ 'description' => t("A date in 'time-since' format. (%date)", array('%date' => format_interval(REQUEST_TIME - 360, 2))),
+ );
+ $date['raw'] = array(
+ 'name' => t("Raw timestamp"),
+ 'description' => t("A date in UNIX timestamp format (%date)", array('%date' => REQUEST_TIME)),
+ );
+
+
+ // File related tokens.
+ $file['fid'] = array(
+ 'name' => t("File ID"),
+ 'description' => t("The unique ID of the uploaded file."),
+ );
+ $file['name'] = array(
+ 'name' => t("File name"),
+ 'description' => t("The name of the file on disk."),
+ );
+ $file['path'] = array(
+ 'name' => t("Path"),
+ 'description' => t("The location of the file relative to Drupal root."),
+ );
+ $file['mime'] = array(
+ 'name' => t("MIME type"),
+ 'description' => t("The MIME type of the file."),
+ );
+ $file['size'] = array(
+ 'name' => t("File size"),
+ 'description' => t("The size of the file."),
+ );
+ $file['url'] = array(
+ 'name' => t("URL"),
+ 'description' => t("The web-accessible URL for the file."),
+ );
+ $file['timestamp'] = array(
+ 'name' => t("Timestamp"),
+ 'description' => t("The date the file was most recently changed."),
+ 'type' => 'date',
+ );
+ $file['owner'] = array(
+ 'name' => t("Owner"),
+ 'description' => t("The user who originally uploaded the file."),
+ 'type' => 'user',
+ );
+
+ return array(
+ 'types' => $types,
+ 'tokens' => array(
+ 'site' => $site,
+ 'date' => $date,
+ 'file' => $file,
+ ),
+ );
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function system_tokens($type, $tokens, array $data = array(), array $options = array()) {
+ $url_options = array('absolute' => TRUE);
+ if (isset($options['language'])) {
+ $url_options['language'] = $options['language'];
+ $language_code = $options['language']->language;
+ }
+ else {
+ $language_code = NULL;
+ }
+ $sanitize = !empty($options['sanitize']);
+
+ $replacements = array();
+
+ if ($type == 'site') {
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ case 'name':
+ $site_name = variable_get('site_name', 'Drupal');
+ $replacements[$original] = $sanitize ? check_plain($site_name) : $site_name;
+ break;
+
+ case 'slogan':
+ $slogan = variable_get('site_slogan', '');
+ $replacements[$original] = $sanitize ? check_plain($slogan) : $slogan;
+ break;
+
+ case 'mail':
+ $replacements[$original] = variable_get('site_mail', '');
+ break;
+
+ case 'url':
+ $replacements[$original] = url('<front>', $url_options);
+ break;
+
+ case 'url-brief':
+ $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', url('<front>', $url_options));
+ break;
+
+ case 'login-url':
+ $replacements[$original] = url('user', $url_options);
+ break;
+ }
+ }
+ }
+
+ elseif ($type == 'date') {
+ if (empty($data['date'])) {
+ $date = REQUEST_TIME;
+ }
+ else {
+ $date = $data['date'];
+ }
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ case 'short':
+ $replacements[$original] = format_date($date, 'short', '', NULL, $language_code);
+ break;
+
+ case 'medium':
+ $replacements[$original] = format_date($date, 'medium', '', NULL, $language_code);
+ break;
+
+ case 'long':
+ $replacements[$original] = format_date($date, 'long', '', NULL, $language_code);
+ break;
+
+ case 'since':
+ $replacements[$original] = format_interval((REQUEST_TIME - $date), 2, $language_code);
+ break;
+
+ case 'raw':
+ $replacements[$original] = $sanitize ? check_plain($date) : $date;
+ break;
+ }
+ }
+
+ if ($created_tokens = token_find_with_prefix($tokens, 'custom')) {
+ foreach ($created_tokens as $name => $original) {
+ $replacements[$original] = format_date($date, 'custom', $name, NULL, $language_code);
+ }
+ }
+ }
+
+ elseif ($type == 'file' && !empty($data['file'])) {
+ $file = $data['file'];
+
+ foreach ($tokens as $name => $original) {
+ switch ($name) {
+ // Basic keys and values.
+ case 'fid':
+ $replacements[$original] = $file->fid;
+ break;
+
+ // Essential file data
+ case 'name':
+ $replacements[$original] = $sanitize ? check_plain($file->filename) : $file->filename;
+ break;
+
+ case 'path':
+ $replacements[$original] = $sanitize ? check_plain($file->uri) : $file->uri;
+ break;
+
+ case 'mime':
+ $replacements[$original] = $sanitize ? check_plain($file->filemime) : $file->filemime;
+ break;
+
+ case 'size':
+ $replacements[$original] = format_size($file->filesize);
+ break;
+
+ case 'url':
+ $replacements[$original] = $sanitize ? check_plain(file_create_url($file->uri)) : file_create_url($file->uri);
+ break;
+
+ // These tokens are default variations on the chained tokens handled below.
+ case 'timestamp':
+ $replacements[$original] = format_date($file->timestamp, 'medium', '', NULL, $language_code);
+ break;
+
+ case 'owner':
+ $account = user_load($file->uid);
+ $name = format_username($account);
+ $replacements[$original] = $sanitize ? check_plain($name) : $name;
+ break;
+ }
+ }
+
+ if ($date_tokens = token_find_with_prefix($tokens, 'timestamp')) {
+ $replacements += token_generate('date', $date_tokens, array('date' => $file->timestamp), $options);
+ }
+
+ if (($owner_tokens = token_find_with_prefix($tokens, 'owner')) && $account = user_load($file->uid)) {
+ $replacements += token_generate('user', $owner_tokens, array('user' => $account), $options);
+ }
+ }
+
+ return $replacements;
+}
diff --git a/core/modules/system/system.updater.inc b/core/modules/system/system.updater.inc
new file mode 100644
index 00000000000..0df1ad955c8
--- /dev/null
+++ b/core/modules/system/system.updater.inc
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Subclasses of the Updater class to update Drupal core knows how to update.
+ * At this time, only modules and themes are supported.
+ */
+
+/**
+ * Class for updating modules using FileTransfer classes via authorize.php.
+ */
+class ModuleUpdater extends Updater implements DrupalUpdaterInterface {
+
+ /**
+ * Return the directory where a module should be installed.
+ *
+ * If the module is already installed, drupal_get_path() will return
+ * a valid path and we should install it there (although we need to use an
+ * absolute path, so we prepend DRUPAL_ROOT). If we're installing a new
+ * module, we always want it to go into sites/all/modules, since that's
+ * where all the documentation recommends users install their modules, and
+ * there's no way that can conflict on a multi-site installation, since
+ * the Update manager won't let you install a new module if it's already
+ * found on your system, and if there was a copy in sites/all, we'd see it.
+ */
+ public function getInstallDirectory() {
+ if ($relative_path = drupal_get_path('module', $this->name)) {
+ $relative_path = dirname($relative_path);
+ }
+ else {
+ $relative_path = 'sites/all/modules';
+ }
+ return DRUPAL_ROOT . '/' . $relative_path;
+ }
+
+ public function isInstalled() {
+ return (bool) drupal_get_path('module', $this->name);
+ }
+
+ public static function canUpdateDirectory($directory) {
+ if (file_scan_directory($directory, '/.*\.module$/')) {
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ public static function canUpdate($project_name) {
+ return (bool) drupal_get_path('module', $project_name);
+ }
+
+ /**
+ * Return available database schema updates one a new version is installed.
+ */
+ public function getSchemaUpdates() {
+ require_once DRUPAL_ROOT . '/includes/install.inc';
+ require_once DRUPAL_ROOT . '/includes/update.inc';
+
+ if (_update_get_project_type($project) != 'module') {
+ return array();
+ }
+ module_load_include('install', $project);
+
+ if (!$updates = drupal_get_schema_versions($project)) {
+ return array();
+ }
+ $updates_to_run = array();
+ $modules_with_updates = update_get_update_list();
+ if ($updates = $modules_with_updates[$project]) {
+ if ($updates['start']) {
+ return $updates['pending'];
+ }
+ }
+ return array();
+ }
+
+ public function postInstallTasks() {
+ return array(
+ l(t('Enable newly added modules'), 'admin/modules'),
+ l(t('Administration pages'), 'admin'),
+ );
+ }
+
+ public function postUpdateTasks() {
+ // We don't want to check for DB updates here, we do that once for all
+ // updated modules on the landing page.
+ }
+
+}
+
+/**
+ * Class for updating themes using FileTransfer classes via authorize.php.
+ */
+class ThemeUpdater extends Updater implements DrupalUpdaterInterface {
+
+ /**
+ * Return the directory where a theme should be installed.
+ *
+ * If the theme is already installed, drupal_get_path() will return
+ * a valid path and we should install it there (although we need to use an
+ * absolute path, so we prepend DRUPAL_ROOT). If we're installing a new
+ * theme, we always want it to go into sites/all/themes, since that's
+ * where all the documentation recommends users install their themes, and
+ * there's no way that can conflict on a multi-site installation, since
+ * the Update manager won't let you install a new theme if it's already
+ * found on your system, and if there was a copy in sites/all, we'd see it.
+ */
+ public function getInstallDirectory() {
+ if ($relative_path = drupal_get_path('theme', $this->name)) {
+ $relative_path = dirname($relative_path);
+ }
+ else {
+ $relative_path = 'sites/all/themes';
+ }
+ return DRUPAL_ROOT . '/' . $relative_path;
+ }
+
+ public function isInstalled() {
+ return (bool) drupal_get_path('theme', $this->name);
+ }
+
+ static function canUpdateDirectory($directory) {
+ // This is a lousy test, but don't know how else to confirm it is a theme.
+ if (file_scan_directory($directory, '/.*\.module$/')) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ public static function canUpdate($project_name) {
+ return (bool) drupal_get_path('theme', $project_name);
+ }
+
+ public function postInstall() {
+ // Update the system table.
+ clearstatcache();
+ system_rebuild_theme_data();
+
+ }
+
+ public function postInstallTasks() {
+ return array(
+ l(t('Enable newly added themes'), 'admin/appearance'),
+ l(t('Administration pages'), 'admin'),
+ );
+ }
+}
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
new file mode 100644
index 00000000000..7fee81cb670
--- /dev/null
+++ b/core/modules/system/theme.api.php
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * @defgroup themeable Default theme implementations
+ * @{
+ * Functions and templates for the user interface to be implemented by themes.
+ *
+ * Drupal's presentation layer is a pluggable system known as the theme
+ * layer. Each theme can take control over most of Drupal's output, and
+ * has complete control over the CSS.
+ *
+ * Inside Drupal, the theme layer is utilized by the use of the theme()
+ * function, which is passed the name of a component (the theme hook)
+ * and an array of variables. For example,
+ * theme('table', array('header' => $header, 'rows' => $rows));
+ * Additionally, the theme() function can take an array of theme
+ * hooks, which can be used to provide 'fallback' implementations to
+ * allow for more specific control of output. For example, the function:
+ * theme(array('table__foo', 'table'), $variables) would look to see if
+ * 'table__foo' is registered anywhere; if it is not, it would 'fall back'
+ * to the generic 'table' implementation. This can be used to attach specific
+ * theme functions to named objects, allowing the themer more control over
+ * specific types of output.
+ *
+ * As of Drupal 6, every theme hook is required to be registered by the
+ * module that owns it, so that Drupal can tell what to do with it and
+ * to make it simple for themes to identify and override the behavior
+ * for these calls.
+ *
+ * The theme hooks are registered via hook_theme(), which returns an
+ * array of arrays with information about the hook. It describes the
+ * arguments the function or template will need, and provides
+ * defaults for the template in case they are not filled in. If the default
+ * implementation is a function, by convention it is named theme_HOOK().
+ *
+ * Each module should provide a default implementation for theme_hooks that
+ * it registers. This implementation may be either a function or a template;
+ * if it is a function it must be specified via hook_theme(). By convention,
+ * default implementations of theme hooks are named theme_HOOK. Default
+ * template implementations are stored in the module directory.
+ *
+ * Drupal's default template renderer is a simple PHP parsing engine that
+ * includes the template and stores the output. Drupal's theme engines
+ * can provide alternate template engines, such as XTemplate, Smarty and
+ * PHPTal. The most common template engine is PHPTemplate (included with
+ * Drupal and implemented in phptemplate.engine, which uses Drupal's default
+ * template renderer.
+ *
+ * In order to create theme-specific implementations of these hooks, themes can
+ * implement their own version of theme hooks, either as functions or templates.
+ * These implementations will be used instead of the default implementation. If
+ * using a pure .theme without an engine, the .theme is required to implement
+ * its own version of hook_theme() to tell Drupal what it is implementing;
+ * themes utilizing an engine will have their well-named theming functions
+ * automatically registered for them. While this can vary based upon the theme
+ * engine, the standard set by phptemplate is that theme functions should be
+ * named THEMENAME_HOOK. For example, for Drupal's default theme (Bartik) to
+ * implement the 'table' hook, the phptemplate.engine would find
+ * bartik_table().
+ *
+ * The theme system is described and defined in theme.inc.
+ *
+ * @see theme()
+ * @see hook_theme()
+ *
+ * @} End of "defgroup themeable".
+ */
+
+/**
+ * Allow themes to alter the theme-specific settings form.
+ *
+ * With this hook, themes can alter the theme-specific settings form in any way
+ * allowable by Drupal's Forms API, such as adding form elements, changing
+ * default values and removing form elements. See the Forms API documentation on
+ * api.drupal.org for detailed information.
+ *
+ * Note that the base theme's form alterations will be run before any sub-theme
+ * alterations.
+ *
+ * @param $form
+ * Nested array of form elements that comprise the form.
+ * @param $form_state
+ * A keyed array containing the current state of the form.
+ */
+function hook_form_system_theme_settings_alter(&$form, &$form_state) {
+ // Add a checkbox to toggle the breadcrumb trail.
+ $form['toggle_breadcrumb'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Display the breadcrumb'),
+ '#default_value' => theme_get_setting('toggle_breadcrumb'),
+ '#description' => t('Show a trail of links from the homepage to the current page.'),
+ );
+}
+
+/**
+ * Preprocess theme variables.
+ *
+ * This hook allows modules to preprocess theme variables for theme templates.
+ * It is called for all invocations of theme(), to allow modules to add to
+ * or override variables for all theme hooks.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param $variables
+ * The variables array (modify in place).
+ * @param $hook
+ * The name of the theme hook.
+ */
+function hook_preprocess(&$variables, $hook) {
+ static $hooks;
+
+ // Add contextual links to the variables, if the user has permission.
+
+ if (!user_access('access contextual links')) {
+ return;
+ }
+
+ if (!isset($hooks)) {
+ $hooks = theme_get_registry();
+ }
+
+ // Determine the primary theme function argument.
+ if (isset($hooks[$hook]['variables'])) {
+ $keys = array_keys($hooks[$hook]['variables']);
+ $key = $keys[0];
+ }
+ else {
+ $key = $hooks[$hook]['render element'];
+ }
+
+ if (isset($variables[$key])) {
+ $element = $variables[$key];
+ }
+
+ if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) {
+ $variables['title_suffix']['contextual_links'] = contextual_links_view($element);
+ if (!empty($variables['title_suffix']['contextual_links'])) {
+ $variables['classes_array'][] = 'contextual-links-region';
+ }
+ }
+}
+
+/**
+ * Preprocess theme variables for a specific theme hook.
+ *
+ * This hook allows modules to preprocess theme variables for a specific theme
+ * hook. It should only be used if a module needs to override or add to the
+ * theme preprocessing for a theme hook it didn't define.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param $variables
+ * The variables array (modify in place).
+ */
+function hook_preprocess_HOOK(&$variables) {
+ // This example is from rdf_preprocess_image(). It adds an RDF attribute
+ // to the image hook's variables.
+ $variables['attributes']['typeof'] = array('foaf:Image');
+}
+
+/**
+ * Process theme variables.
+ *
+ * This hook allows modules to process theme variables for theme templates.
+ * It is called for all invocations of theme(), to allow modules to add to
+ * or override variables for all theme hooks.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param $variables
+ * The variables array (modify in place).
+ * @param $hook
+ * The name of the theme hook.
+ */
+function hook_process(&$variables, $hook) {
+ // Wraps variables in RDF wrappers.
+ if (!empty($variables['rdf_template_variable_attributes_array'])) {
+ foreach ($variables['rdf_template_variable_attributes_array'] as $variable_name => $attributes) {
+ $context = array(
+ 'hook' => $hook,
+ 'variable_name' => $variable_name,
+ 'variables' => $variables,
+ );
+ $variables[$variable_name] = theme('rdf_template_variable_wrapper', array('content' => $variables[$variable_name], 'attributes' => $attributes, 'context' => $context));
+ }
+ }
+}
+
+/**
+ * Process theme variables for a specific theme hook.
+ *
+ * This hook allows modules to process theme variables for a specific theme
+ * hook. It should only be used if a module needs to override or add to the
+ * theme processing for a theme hook it didn't define.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param $variables
+ * The variables array (modify in place).
+ */
+function hook_process_HOOK(&$variables) {
+ $variables['classes'] .= ' my_added_class';
+}
+
+/**
+ * Respond to themes being enabled.
+ *
+ * @param array $theme_list
+ * Array containing the names of the themes being enabled.
+ *
+ * @see theme_enable()
+ */
+function hook_themes_enabled($theme_list) {
+ foreach ($theme_list as $theme) {
+ block_theme_initialize($theme);
+ }
+}
+
+/**
+ * Respond to themes being disabled.
+ *
+ * @param array $theme_list
+ * Array containing the names of the themes being disabled.
+ *
+ * @see theme_disable()
+ */
+function hook_themes_disabled($theme_list) {
+ // Clear all update module caches.
+ _update_cache_clear();
+}