5 * Common functions that many Drupal modules will need to reference.
7 * The functions that are critical and need to be available even when serving
8 * a cached page are instead located in bootstrap.inc.
12 * @defgroup php_wrappers PHP wrapper functions
14 * Functions that are wrappers or custom implementations of PHP functions.
16 * Certain PHP functions should not be used in Drupal. Instead, Drupal's
17 * replacement functions should be used.
19 * For example, for improved or more secure UTF8-handling, or RFC-compliant
20 * handling of URLs in Drupal.
22 * For ease of use and memorizing, all these wrapper functions use the same name
23 * as the original PHP function, but prefixed with "drupal_". Beware, however,
24 * that not all wrapper functions support the same arguments as the original
27 * You should always use these wrapper functions in your code.
31 * $my_substring = substr($original_string, 0, 5);
36 * $my_substring = drupal_substr($original_string, 0, 5);
43 * Return status for saving which involved creating a new item.
45 define('SAVED_NEW', 1);
48 * Return status for saving which involved an update to an existing item.
50 define('SAVED_UPDATED', 2);
53 * Return status for saving which deleted an existing item.
55 define('SAVED_DELETED', 3);
58 * The default group for system CSS files added to the page.
60 define('CSS_SYSTEM', -100);
63 * The default group for module CSS files added to the page.
65 define('CSS_DEFAULT', 0);
68 * The default group for theme CSS files added to the page.
70 define('CSS_THEME', 100);
73 * The default group for JavaScript and jQuery libraries added to the page.
75 define('JS_LIBRARY', -100);
78 * The default group for module JavaScript code added to the page.
80 define('JS_DEFAULT', 0);
83 * The default group for theme JavaScript code added to the page.
85 define('JS_THEME', 100);
88 * Error code indicating that the request exceeded the specified timeout.
90 * @see drupal_http_request()
92 define('HTTP_REQUEST_TIMEOUT', -1);
95 * Constants defining cache granularity for blocks and renderable arrays.
97 * Modules specify the caching patterns for their blocks using binary
98 * combinations of these constants in their hook_block_info():
99 * $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE;
100 * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is
101 * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
104 * The block cache is cleared in cache_clear_all(), and uses the same clearing
105 * policy than page cache (node, comment, user, taxonomy added or updated...).
106 * Blocks requiring more fine-grained clearing might consider disabling the
107 * built-in block cache (DRUPAL_NO_CACHE) and roll their own.
109 * Note that user 1 is excluded from block caching.
113 * The block should not get cached.
115 * This setting should be used:
116 * - For simple blocks (notably those that do not perform any db query), where
117 * querying the db cache would be more expensive than directly generating the
119 * - For blocks that change too frequently.
121 define('DRUPAL_NO_CACHE', -1);
124 * The block is handling its own caching in its hook_block_view().
126 * From the perspective of the block cache system, this is equivalent to
127 * DRUPAL_NO_CACHE. Useful when time based expiration is needed or a site uses
128 * a node access which invalidates standard block cache.
130 define('DRUPAL_CACHE_CUSTOM', -2);
133 * The block or element can change depending on the user's roles.
135 * This is the default setting for blocks, used when the block does not specify
138 define('DRUPAL_CACHE_PER_ROLE', 0x0001);
141 * The block or element can change depending on the user.
143 * This setting can be resource-consuming for sites with large number of users,
144 * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
146 define('DRUPAL_CACHE_PER_USER', 0x0002);
149 * The block or element can change depending on the page being viewed.
151 define('DRUPAL_CACHE_PER_PAGE', 0x0004);
154 * The block or element is the same for every user and page that it is visible.
156 define('DRUPAL_CACHE_GLOBAL', 0x0008);
159 * Adds content to a specified region.
162 * Page region the content is added to.
164 * Content to be added.
166 function drupal_add_region_content($region = NULL
, $data = NULL
) {
167 static
$content = array();
169 if (isset($region) && isset($data)) {
170 $content[$region][] = $data;
176 * Gets assigned content for a given region.
179 * A specified region to fetch content for. If NULL, all regions will be
182 * Content to be inserted between imploded array elements.
184 function drupal_get_region_content($region = NULL
, $delimiter = ' ') {
185 $content = drupal_add_region_content();
186 if (isset($region)) {
187 if (isset($content[$region]) && is_array($content[$region])) {
188 return implode($delimiter, $content[$region]);
192 foreach (array_keys($content) as
$region) {
193 if (is_array($content[$region])) {
194 $content[$region] = implode($delimiter, $content[$region]);
202 * Gets the name of the currently active install profile.
204 * When this function is called during Drupal's initial installation process,
205 * the name of the profile that's about to be installed is stored in the global
206 * installation state. At all other times, the standard Drupal systems variable
207 * table contains the name of the current profile, and we can call
208 * variable_get() to determine what one is active.
211 * The name of the install profile.
213 function drupal_get_profile() {
214 global $install_state;
216 if (isset($install_state['parameters']['profile'])) {
217 $profile = $install_state['parameters']['profile'];
220 $profile = variable_get('install_profile', 'standard');
228 * Sets the breadcrumb trail for the current page.
231 * Array of links, starting with "home" and proceeding up to but not including
234 function drupal_set_breadcrumb($breadcrumb = NULL
) {
235 $stored_breadcrumb = &drupal_static(__FUNCTION__
);
237 if (isset($breadcrumb)) {
238 $stored_breadcrumb = $breadcrumb;
240 return $stored_breadcrumb;
244 * Gets the breadcrumb trail for the current page.
246 function drupal_get_breadcrumb() {
247 $breadcrumb = drupal_set_breadcrumb();
249 if (!isset($breadcrumb)) {
250 $breadcrumb = menu_get_active_breadcrumb();
257 * Returns a string containing RDF namespace declarations for use in XML and
260 function drupal_get_rdf_namespaces() {
261 $xml_rdf_namespaces = array();
263 // Serializes the RDF namespaces in XML namespace syntax.
264 if (function_exists('rdf_get_namespaces')) {
265 foreach (rdf_get_namespaces() as
$prefix => $uri) {
266 $xml_rdf_namespaces[] = 'xmlns:' .
$prefix .
'="' .
$uri .
'"';
269 return count($xml_rdf_namespaces) ?
"\n " .
implode("\n ", $xml_rdf_namespaces) : '';
273 * Adds output to the HEAD tag of the HTML page.
275 * This function can be called as long the headers aren't sent. Pass no
276 * arguments (or NULL for both) to retrieve the currently stored elements.
279 * A renderable array. If the '#type' key is not set then 'html_tag' will be
280 * added as the default '#type'.
282 * A unique string key to allow implementations of hook_html_head_alter() to
283 * identify the element in $data. Required if $data is not NULL.
286 * An array of all stored HEAD elements.
288 * @see theme_html_tag()
290 function drupal_add_html_head($data = NULL
, $key = NULL
) {
291 $stored_head = &drupal_static(__FUNCTION__
);
293 if (!isset($stored_head)) {
294 // Make sure the defaults, including Content-Type, come first.
295 $stored_head = _drupal_default_html_head();
298 if (isset($data) && isset($key)) {
299 if (!isset($data['#type'])) {
300 $data['#type'] = 'html_tag';
302 $stored_head[$key] = $data;
308 * Returns elements that are always displayed in the HEAD tag of the HTML page.
310 function _drupal_default_html_head() {
311 // Add default elements. Make sure the Content-Type comes first because the
312 // IE browser may be vulnerable to XSS via encoding attacks from any content
313 // that comes before this META tag, such as a TITLE tag.
314 $elements['system_meta_content_type'] = array(
315 '#type' => 'html_tag',
317 '#attributes' => array(
318 'http-equiv' => 'Content-Type',
319 'content' => 'text/html; charset=utf-8',
321 // Security: This always has to be output first.
324 // Show Drupal and the major version number in the META GENERATOR tag.
325 // Get the major version.
326 list($version, ) = explode('.', VERSION
);
327 $elements['system_meta_generator'] = array(
328 '#type' => 'html_tag',
330 '#attributes' => array(
331 'name' => 'Generator',
332 'content' => 'Drupal ' .
$version .
' (http://drupal.org)',
335 // Also send the generator in the HTTP header.
336 $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
341 * Retrieves output to be displayed in the HEAD tag of the HTML page.
343 function drupal_get_html_head() {
344 $elements = drupal_add_html_head();
345 drupal_alter('html_head', $elements);
346 return drupal_render($elements);
350 * Adds a feed URL for the current page.
352 * This function can be called as long the HTML header hasn't been sent.
355 * An internal system path or a fully qualified external URL of the feed.
357 * The title of the feed.
359 function drupal_add_feed($url = NULL
, $title = '') {
360 $stored_feed_links = &drupal_static(__FUNCTION__
, array());
363 $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
365 drupal_add_html_head_link(array(
366 'rel' => 'alternate',
367 'type' => 'application/rss+xml',
369 // Force the URL to be absolute, for consistency with other <link> tags
371 'href' => url($url, array('absolute' => TRUE
)),
374 return $stored_feed_links;
378 * Gets the feed URLs for the current page.
381 * A delimiter to split feeds by.
383 function drupal_get_feeds($delimiter = "\n") {
384 $feeds = drupal_add_feed();
385 return implode($feeds, $delimiter);
389 * @defgroup http_handling HTTP handling
391 * Functions to properly handle HTTP responses.
395 * Processes a URL query parameter array to remove unwanted elements.
398 * (optional) An array to be processed. Defaults to $_GET.
400 * (optional) A list of $query array keys to remove. Use "parent[child]" to
401 * exclude nested items. Defaults to array('q').
403 * Internal use only. Used to build the $query array key for nested items.
406 * An array containing query parameters, which can be used for url().
408 function drupal_get_query_parameters(array $query = NULL
, array $exclude = array('q'), $parent = '') {
409 // Set defaults, if none given.
410 if (!isset($query)) {
413 // If $exclude is empty, there is nothing to filter.
414 if (empty($exclude)) {
418 $exclude = array_flip($exclude);
422 foreach ($query as
$key => $value) {
423 $string_key = ($parent ?
$parent .
'[' .
$key .
']' : $key);
424 if (isset($exclude[$string_key])) {
428 if (is_array($value)) {
429 $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
432 $params[$key] = $value;
440 * Splits a URL-encoded query string into an array.
443 * The query string to split.
446 * An array of url decoded couples $param_name => $value.
448 function drupal_get_query_array($query) {
450 if (!empty($query)) {
451 foreach (explode('&', $query) as
$param) {
452 $param = explode('=', $param);
453 $result[$param[0]] = isset($param[1]) ?
rawurldecode($param[1]) : '';
460 * Parses an array into a valid, rawurlencoded query string.
462 * This differs from http_build_query() as we need to rawurlencode() (instead of
463 * urlencode()) all query parameters.
466 * The query parameter array to be processed, e.g. $_GET.
468 * Internal use only. Used to build the $query array key for nested items.
471 * A rawurlencoded string which can be used as or appended to the URL query
474 * @see drupal_get_query_parameters()
475 * @ingroup php_wrappers
477 function drupal_http_build_query(array $query, $parent = '') {
480 foreach ($query as
$key => $value) {
481 $key = ($parent ?
$parent .
'[' .
rawurlencode($key) .
']' : rawurlencode($key));
483 // Recurse into children.
484 if (is_array($value)) {
485 $params[] = drupal_http_build_query($value, $key);
487 // If a query parameter value is NULL, only append its key.
488 elseif (!isset($value)) {
492 // For better readability of paths in query strings, we decode slashes.
493 $params[] = $key .
'=' .
str_replace('%2F', '/', rawurlencode($value));
497 return implode('&', $params);
501 * Prepares a 'destination' URL query parameter for use with drupal_goto().
503 * Used to direct the user back to the referring page after completing a form.
504 * By default the current URL is returned. If a destination exists in the
505 * previous request, that destination is returned. As such, a destination can
506 * persist across multiple pages.
510 function drupal_get_destination() {
511 $destination = &drupal_static(__FUNCTION__
);
513 if (isset($destination)) {
517 if (isset($_GET['destination'])) {
518 $destination = array('destination' => $_GET['destination']);
522 $query = drupal_http_build_query(drupal_get_query_parameters());
524 $path .
= '?' .
$query;
526 $destination = array('destination' => $path);
532 * Parses a system URL string into an associative array suitable for url().
534 * This function should only be used for URLs that have been generated by the
535 * system, resp. url(). It should not be used for URLs that come from external
536 * sources, or URLs that link to external resources.
538 * The returned array contains a 'path' that may be passed separately to url().
541 * $options = drupal_parse_url($_GET['destination']);
542 * $my_url = url($options['path'], $options);
543 * $my_link = l('Example link', $options['path'], $options);
546 * This is required, because url() does not support relative URLs containing a
547 * query string or fragment in its $path argument. Instead, any query string
548 * needs to be parsed into an associative query parameter array in
549 * $options['query'] and the fragment into $options['fragment'].
552 * The URL string to parse, f.e. $_GET['destination'].
555 * An associative array containing the keys:
556 * - 'path': The path of the URL. If the given $url is external, this includes
557 * the scheme and host.
558 * - 'query': An array of query parameters of $url, if existent.
559 * - 'fragment': The fragment of $url, if existent.
563 * @ingroup php_wrappers
565 function drupal_parse_url($url) {
572 // External URLs: not using parse_url() here, so we do not have to rebuild
573 // the scheme, host, and path without having any use for it.
574 if (strpos($url, '://') !== FALSE
) {
575 // Split off everything before the query string into 'path'.
576 $parts = explode('?', $url);
577 $options['path'] = $parts[0];
578 // If there is a query string, transform it into keyed query parameters.
579 if (isset($parts[1])) {
580 $query_parts = explode('#', $parts[1]);
581 parse_str($query_parts[0], $options['query']);
582 // Take over the fragment, if there is any.
583 if (isset($query_parts[1])) {
584 $options['fragment'] = $query_parts[1];
590 // parse_url() does not support relative URLs, so make it absolute. E.g. the
591 // relative URL "foo/bar:1" isn't properly parsed.
592 $parts = parse_url('http://example.com/' .
$url);
593 // Strip the leading slash that was just added.
594 $options['path'] = substr($parts['path'], 1);
595 if (isset($parts['query'])) {
596 parse_str($parts['query'], $options['query']);
598 if (isset($parts['fragment'])) {
599 $options['fragment'] = $parts['fragment'];
602 // The 'q' parameter contains the path of the current page if clean URLs are
603 // disabled. It overrides the 'path' of the URL when present, even if clean
604 // URLs are enabled, due to how Apache rewriting rules work.
605 if (isset($options['query']['q'])) {
606 $options['path'] = $options['query']['q'];
607 unset($options['query']['q']);
614 * Encodes a Drupal path for use in a URL.
616 * For aesthetic reasons slashes are not escaped.
618 * Note that url() takes care of calling this function, so a path passed to that
619 * function should not be encoded in advance.
622 * The Drupal path to encode.
624 function drupal_encode_path($path) {
625 return str_replace('%2F', '/', rawurlencode($path));
629 * Sends the user to a different Drupal page.
631 * This issues an on-site HTTP redirect. The function makes sure the redirected
632 * URL is formatted correctly.
634 * Usually the redirected URL is constructed from this function's input
635 * parameters. However you may override that behavior by setting a
636 * destination in either the $_REQUEST-array (i.e. by using
637 * the query string of an URI) This is used to direct the user back to
638 * the proper page after completing a form. For example, after editing
639 * a post on the 'admin/content'-page or after having logged on using the
640 * 'user login'-block in a sidebar. The function drupal_get_destination()
641 * can be used to help set the destination URL.
643 * Drupal will ensure that messages set by drupal_set_message() and other
644 * session data are written to the database before the user is redirected.
646 * This function ends the request; use it instead of a return in your menu
650 * A Drupal path or a full URL.
652 * An associative array of additional URL options to pass to url().
653 * @param $http_response_code
654 * Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
655 * - 301 Moved Permanently (the recommended value for most redirects)
656 * - 302 Found (default in Drupal and PHP, sometimes used for spamming search
661 * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
662 * Note: Other values are defined by RFC 2616, but are rarely used and poorly
665 * @see drupal_get_destination()
668 function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
669 // A destination in $_GET always overrides the function arguments.
670 // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
671 if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
672 $destination = drupal_parse_url($_GET['destination']);
673 $path = $destination['path'];
674 $options['query'] = $destination['query'];
675 $options['fragment'] = $destination['fragment'];
678 drupal_alter('drupal_goto', $path, $options, $http_response_code);
680 // The 'Location' HTTP header must be absolute.
681 $options['absolute'] = TRUE
;
683 $url = url($path, $options);
685 header('Location: ' .
$url, TRUE
, $http_response_code);
687 // The "Location" header sends a redirect status code to the HTTP daemon. In
688 // some cases this can be wrong, so we make sure none of the code below the
689 // drupal_goto() call gets executed upon redirection.
694 * Delivers a "site is under maintenance" message to the browser.
696 * Page callback functions wanting to report a "site offline" message should
697 * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
698 * functions that are invoked in contexts where that return value might not
699 * bubble up to menu_execute_active_handler() should call drupal_site_offline().
701 function drupal_site_offline() {
702 drupal_deliver_page(MENU_SITE_OFFLINE
);
706 * Delivers a "page not found" error to the browser.
708 * Page callback functions wanting to report a "page not found" message should
709 * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
710 * functions that are invoked in contexts where that return value might not
711 * bubble up to menu_execute_active_handler() should call drupal_not_found().
713 function drupal_not_found() {
714 drupal_deliver_page(MENU_NOT_FOUND
);
718 * Delivers an "access denied" error to the browser.
720 * Page callback functions wanting to report an "access denied" message should
721 * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
722 * functions that are invoked in contexts where that return value might not
723 * bubble up to menu_execute_active_handler() should call
724 * drupal_access_denied().
726 function drupal_access_denied() {
727 drupal_deliver_page(MENU_ACCESS_DENIED
);
731 * Performs an HTTP request.
733 * This is a flexible and powerful HTTP client implementation. Correctly
734 * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
737 * A string containing a fully qualified URI.
738 * @param array $options
739 * (optional) An array that can have one or more of the following elements:
740 * - headers: An array containing request headers to send as name/value pairs.
741 * - method: A string containing the request method. Defaults to 'GET'.
742 * - data: A string containing the request body, formatted as
743 * 'param=value¶m=value&...'. Defaults to NULL.
744 * - max_redirects: An integer representing how many times a redirect
745 * may be followed. Defaults to 3.
746 * - timeout: A float representing the maximum number of seconds the function
747 * call may take. The default is 30 seconds. If a timeout occurs, the error
748 * code is set to the HTTP_REQUEST_TIMEOUT constant.
749 * - context: A context resource created with stream_context_create().
752 * An object that can have one or more of the following components:
753 * - request: A string containing the request body that was sent.
754 * - code: An integer containing the response status code, or the error code
755 * if an error occurred.
756 * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
757 * - status_message: The status message from the response, if a response was
759 * - redirect_code: If redirected, an integer containing the initial response
761 * - redirect_url: If redirected, a string containing the URL of the redirect
763 * - error: If an error occurred, the error message. Otherwise not set.
764 * - headers: An array containing the response headers as name/value pairs.
765 * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
766 * easy access the array keys are returned in lower case.
767 * - data: A string containing the response body that was received.
769 function drupal_http_request($url, array $options = array()) {
770 $result = new
stdClass();
772 // Parse the URL and make sure we can handle the schema.
773 $uri = @
parse_url($url);
776 $result->error
= 'unable to parse URL';
777 $result->code
= -1001;
781 if (!isset($uri['scheme'])) {
782 $result->error
= 'missing schema';
783 $result->code
= -1002;
787 timer_start(__FUNCTION__
);
789 // Merge the default options.
791 'headers' => array(),
794 'max_redirects' => 3,
798 // stream_socket_client() requires timeout to be a float.
799 $options['timeout'] = (float) $options['timeout'];
801 switch ($uri['scheme']) {
804 $port = isset($uri['port']) ?
$uri['port'] : 80;
805 $socket = 'tcp://' .
$uri['host'] .
':' .
$port;
806 // RFC 2616: "non-standard ports MUST, default ports MAY be included".
807 // We don't add the standard port to prevent from breaking rewrite rules
808 // checking the host that do not take into account the port number.
809 $options['headers']['Host'] = $uri['host'] .
($port != 80 ?
':' .
$port : '');
812 // Note: Only works when PHP is compiled with OpenSSL support.
813 $port = isset($uri['port']) ?
$uri['port'] : 443;
814 $socket = 'ssl://' .
$uri['host'] .
':' .
$port;
815 $options['headers']['Host'] = $uri['host'] .
($port != 443 ?
':' .
$port : '');
818 $result->error
= 'invalid schema ' .
$uri['scheme'];
819 $result->code
= -1003;
823 if (empty($options['context'])) {
824 $fp = @
stream_socket_client($socket, $errno, $errstr, $options['timeout']);
827 // Create a stream with context. Allows verification of a SSL certificate.
828 $fp = @
stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT
, $options['context']);
831 // Make sure the socket opened properly.
833 // When a network error occurs, we use a negative number so it does not
834 // clash with the HTTP status codes.
835 $result->code
= -$errno;
836 $result->error
= trim($errstr) ?
trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
838 // Mark that this request failed. This will trigger a check of the web
839 // server's ability to make outgoing HTTP requests the next time that
840 // requirements checking is performed.
841 // See system_requirements().
842 variable_set('drupal_http_request_fails', TRUE
);
847 // Construct the path to act on.
848 $path = isset($uri['path']) ?
$uri['path'] : '/';
849 if (isset($uri['query'])) {
850 $path .
= '?' .
$uri['query'];
853 // Merge the default headers.
854 $options['headers'] += array(
855 'User-Agent' => 'Drupal (+http://drupal.org/)',
858 // Only add Content-Length if we actually have any content or if it is a POST
859 // or PUT request. Some non-standard servers get confused by Content-Length in
860 // at least HEAD/GET requests, and Squid always requires Content-Length in
861 // POST/PUT requests.
862 $content_length = strlen($options['data']);
863 if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
864 $options['headers']['Content-Length'] = $content_length;
867 // If the server URL has a user then attempt to use basic authentication.
868 if (isset($uri['user'])) {
869 $options['headers']['Authorization'] = 'Basic ' .
base64_encode($uri['user'] .
(isset($uri['pass']) ?
':' .
$uri['pass'] : ''));
872 // If the database prefix is being used by SimpleTest to run the tests in a copied
873 // database then set the user-agent header to the database prefix so that any
874 // calls to other Drupal pages will run the SimpleTest prefixed database. The
875 // user-agent is used to ensure that multiple testing sessions running at the
876 // same time won't interfere with each other as they would if the database
877 // prefix were stored statically in a file or database variable.
878 $test_info = &$GLOBALS['drupal_test_info'];
879 if (!empty($test_info['test_run_id'])) {
880 $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
883 $request = $options['method'] .
' ' .
$path .
" HTTP/1.0\r\n";
884 foreach ($options['headers'] as
$name => $value) {
885 $request .
= $name .
': ' .
trim($value) .
"\r\n";
887 $request .
= "\r\n" .
$options['data'];
888 $result->request
= $request;
889 // Calculate how much time is left of the original timeout value.
890 $timeout = $options['timeout'] - timer_read(__FUNCTION__
) / 1000;
892 stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
893 fwrite($fp, $request);
896 // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
897 // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
898 // instead must invoke stream_get_meta_data() each iteration.
899 $info = stream_get_meta_data($fp);
900 $alive = !$info['eof'] && !$info['timed_out'];
904 // Calculate how much time is left of the original timeout value.
905 $timeout = $options['timeout'] - timer_read(__FUNCTION__
) / 1000;
907 $info['timed_out'] = TRUE
;
910 stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
911 $chunk = fread($fp, 1024);
913 $info = stream_get_meta_data($fp);
914 $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
918 if ($info['timed_out']) {
919 $result->code
= HTTP_REQUEST_TIMEOUT
;
920 $result->error
= 'request timed out';
923 // Parse response headers from the response body.
924 // Be tolerant of malformed HTTP responses that separate header and body with
925 // \n\n or \r\r instead of \r\n\r\n.
926 list($response, $result->data
) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
927 $response = preg_split("/\r\n|\n|\r/", $response);
929 // Parse the response status line.
930 list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
931 $result->protocol
= $protocol;
932 $result->status_message
= $status_message;
934 $result->headers
= array();
936 // Parse the response headers.
937 while ($line = trim(array_shift($response))) {
938 list($name, $value) = explode(':', $line, 2);
939 $name = strtolower($name);
940 if (isset($result->headers
[$name]) && $name == 'set-cookie') {
941 // RFC 2109: the Set-Cookie response header comprises the token Set-
942 // Cookie:, followed by a comma-separated list of one or more cookies.
943 $result->headers
[$name] .
= ',' .
trim($value);
946 $result->headers
[$name] = trim($value);
952 101 => 'Switching Protocols',
956 203 => 'Non-Authoritative Information',
958 205 => 'Reset Content',
959 206 => 'Partial Content',
960 300 => 'Multiple Choices',
961 301 => 'Moved Permanently',
964 304 => 'Not Modified',
966 307 => 'Temporary Redirect',
967 400 => 'Bad Request',
968 401 => 'Unauthorized',
969 402 => 'Payment Required',
972 405 => 'Method Not Allowed',
973 406 => 'Not Acceptable',
974 407 => 'Proxy Authentication Required',
975 408 => 'Request Time-out',
978 411 => 'Length Required',
979 412 => 'Precondition Failed',
980 413 => 'Request Entity Too Large',
981 414 => 'Request-URI Too Large',
982 415 => 'Unsupported Media Type',
983 416 => 'Requested range not satisfiable',
984 417 => 'Expectation Failed',
985 500 => 'Internal Server Error',
986 501 => 'Not Implemented',
987 502 => 'Bad Gateway',
988 503 => 'Service Unavailable',
989 504 => 'Gateway Time-out',
990 505 => 'HTTP Version not supported',
992 // RFC 2616 states that all unknown HTTP codes must be treated the same as the
993 // base code in their class.
994 if (!isset($responses[$code])) {
995 $code = floor($code / 100) * 100;
997 $result->code
= $code;
1001 case
304: // Not modified
1003 case
301: // Moved permanently
1004 case
302: // Moved temporarily
1005 case
307: // Moved temporarily
1006 $location = $result->headers
['location'];
1007 $options['timeout'] -= timer_read(__FUNCTION__
) / 1000;
1008 if ($options['timeout'] <= 0) {
1009 $result->code
= HTTP_REQUEST_TIMEOUT
;
1010 $result->error
= 'request timed out';
1012 elseif ($options['max_redirects']) {
1013 // Redirect to the new location.
1014 $options['max_redirects']--;
1015 $result = drupal_http_request($location, $options);
1016 $result->redirect_code
= $code;
1018 if (!isset($result->redirect_url
)) {
1019 $result->redirect_url
= $location;
1023 $result->error
= $status_message;
1029 * @} End of "HTTP handling".
1033 * Strips slashes from a string or array of strings.
1035 * Callback for array_walk() within fix_gpx_magic().
1038 * An individual string or array of strings from superglobals.
1040 function _fix_gpc_magic(&$item) {
1041 if (is_array($item)) {
1042 array_walk($item, '_fix_gpc_magic');
1045 $item = stripslashes($item);
1050 * Strips slashes from $_FILES items.
1052 * Callback for array_walk() within fix_gpc_magic().
1054 * The tmp_name key is skipped keys since PHP generates single backslashes for
1055 * file paths on Windows systems.
1058 * An item from $_FILES.
1060 * The key for the item within $_FILES.
1062 * @see http://php.net/manual/en/features.file-upload.php#42280
1064 function _fix_gpc_magic_files(&$item, $key) {
1065 if ($key != 'tmp_name') {
1066 if (is_array($item)) {
1067 array_walk($item, '_fix_gpc_magic_files');
1070 $item = stripslashes($item);
1076 * Fixes double-escaping caused by "magic quotes" in some PHP installations.
1078 * @see _fix_gpc_magic()
1079 * @see _fix_gpc_magic_files()
1081 function fix_gpc_magic() {
1082 static
$fixed = FALSE
;
1083 if (!$fixed && ini_get('magic_quotes_gpc')) {
1084 array_walk($_GET, '_fix_gpc_magic');
1085 array_walk($_POST, '_fix_gpc_magic');
1086 array_walk($_COOKIE, '_fix_gpc_magic');
1087 array_walk($_REQUEST, '_fix_gpc_magic');
1088 array_walk($_FILES, '_fix_gpc_magic_files');
1094 * @defgroup validation Input validation
1096 * Functions to validate user input.
1100 * Verifies the syntax of the given e-mail address.
1102 * Empty e-mail addresses are allowed. See RFC 2822 for details.
1105 * A string containing an e-mail address.
1108 * TRUE if the address is in a valid format.
1110 function valid_email_address($mail) {
1111 return (bool
)filter_var($mail, FILTER_VALIDATE_EMAIL
);
1115 * Verifies the syntax of the given URL.
1117 * This function should only be used on actual URLs. It should not be used for
1118 * Drupal menu paths, which can contain arbitrary characters.
1119 * Valid values per RFC 3986.
1121 * The URL to verify.
1123 * Whether the URL is absolute (beginning with a scheme such as "http:").
1126 * TRUE if the URL is in a valid format.
1128 function valid_url($url, $absolute = FALSE
) {
1130 return (bool
)preg_match("
1131 /^ # Start at the beginning of the text
1132 (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes
1133 (?: # Userinfo (optional) which is typically
1134 (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
1135 (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
1138 (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
1139 |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
1141 (?::[0-9]+)? # Server port number (optional)
1143 (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
1148 return (bool
)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
1153 * @} End of "defgroup validation".
1157 * Registers an event for the current visitor to the flood control mechanism.
1160 * The name of an event.
1162 * Optional number of seconds before this event expires. Defaults to 3600 (1
1163 * hour). Typically uses the same value as the flood_is_allowed() $window
1164 * parameter. Expired events are purged on cron run to prevent the flood table
1165 * from growing indefinitely.
1166 * @param $identifier
1167 * Optional identifier (defaults to the current user's IP address).
1169 function flood_register_event($name, $window = 3600, $identifier = NULL
) {
1170 if (!isset($identifier)) {
1171 $identifier = ip_address();
1176 'identifier' => $identifier,
1177 'timestamp' => REQUEST_TIME
,
1178 'expiration' => REQUEST_TIME
+ $window,
1184 * Makes the flood control mechanism forget an event for the current visitor.
1187 * The name of an event.
1188 * @param $identifier
1189 * Optional identifier (defaults to the current user's IP address).
1191 function flood_clear_event($name, $identifier = NULL
) {
1192 if (!isset($identifier)) {
1193 $identifier = ip_address();
1196 ->condition('event', $name)
1197 ->condition('identifier', $identifier)
1202 * Checks whether a user is allowed to proceed with the specified event.
1204 * Events can have thresholds saying that each user can only do that event
1205 * a certain number of times in a time window. This function verifies that the
1206 * current user has not exceeded this threshold.
1209 * The unique name of the event.
1211 * The maximum number of times each user can do this event per time window.
1213 * Number of seconds in the time window for this event (default is 3600
1214 * seconds, or 1 hour).
1215 * @param $identifier
1216 * Unique identifier of the current user. Defaults to their IP address.
1219 * TRUE if the user is allowed to proceed. FALSE if they have exceeded the
1220 * threshold and should not be allowed to proceed.
1222 function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL
) {
1223 if (!isset($identifier)) {
1224 $identifier = ip_address();
1226 $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array(
1228 ':identifier' => $identifier,
1229 ':timestamp' => REQUEST_TIME
- $window))
1231 return ($number < $threshold);
1235 * @defgroup sanitization Sanitization functions
1237 * Functions to sanitize values.
1239 * See http://drupal.org/writing-secure-code for information
1240 * on writing secure code.
1244 * Strips dangerous protocols (e.g. 'javascript:') from a URI.
1246 * This function must be called for all URIs within user-entered input prior
1247 * to being output to an HTML attribute value. It is often called as part of
1248 * check_url() or filter_xss(), but those functions return an HTML-encoded
1249 * string, so this function can be called independently when the output needs to
1250 * be a plain-text string for passing to t(), l(), drupal_attributes(), or
1251 * another function that will call check_plain() separately.
1254 * A plain-text URI that might contain dangerous protocols.
1257 * A plain-text URI stripped of dangerous protocols. As with all plain-text
1258 * strings, this return value must not be output to an HTML page without
1259 * check_plain() being called on it. However, it can be passed to functions
1260 * expecting plain-text strings.
1264 function drupal_strip_dangerous_protocols($uri) {
1265 static
$allowed_protocols;
1267 if (!isset($allowed_protocols)) {
1268 $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal')));
1271 // Iteratively remove any invalid protocol found.
1274 $colonpos = strpos($uri, ':');
1275 if ($colonpos > 0) {
1276 // We found a colon, possibly a protocol. Verify.
1277 $protocol = substr($uri, 0, $colonpos);
1278 // If a colon is preceded by a slash, question mark or hash, it cannot
1279 // possibly be part of the URL scheme. This must be a relative URL, which
1280 // inherits the (safe) protocol of the base document.
1281 if (preg_match('![/?#]!', $protocol)) {
1284 // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
1285 // (URI Comparison) scheme comparison must be case-insensitive.
1286 if (!isset($allowed_protocols[strtolower($protocol)])) {
1287 $uri = substr($uri, $colonpos + 1);
1290 } while ($before != $uri);
1296 * Strips dangerous protocols from a URI and encodes it for output to HTML.
1299 * A plain-text URI that might contain dangerous protocols.
1302 * A URI stripped of dangerous protocols and encoded for output to an HTML
1303 * attribute value. Because it is already encoded, it should not be set as a
1304 * value within a $attributes array passed to drupal_attributes(), because
1305 * drupal_attributes() expects those values to be plain-text strings. To pass
1306 * a filtered URI to drupal_attributes(), call
1307 * drupal_strip_dangerous_protocols() instead.
1309 * @see drupal_strip_dangerous_protocols()
1311 function check_url($uri) {
1312 return check_plain(drupal_strip_dangerous_protocols($uri));
1316 * Applies a very permissive XSS/HTML filter for admin-only use.
1318 * Use only for fields where it is impractical to use the
1319 * whole filter system, but where some (mainly inline) mark-up
1320 * is desired (so check_plain() is not acceptable).
1322 * Allows all tags that can be used inside an HTML body, save
1323 * for scripts and styles.
1325 function filter_xss_admin($string) {
1326 return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'mark', 'menu', 'meter', 'nav', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr'));
1330 * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
1332 * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
1333 * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
1335 * This code does four things:
1336 * - Removes characters and constructs that can trick browsers.
1337 * - Makes sure all HTML entities are well-formed.
1338 * - Makes sure all HTML tags and attributes are well-formed.
1339 * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
1343 * The string with raw HTML in it. It will be stripped of everything that can
1344 * cause an XSS attack.
1345 * @param $allowed_tags
1346 * An array of allowed tags.
1349 * An XSS safe version of $string, or an empty string if $string is not
1352 * @see drupal_validate_utf8()
1353 * @ingroup sanitization
1355 function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
1356 // Only operate on valid UTF-8 strings. This is necessary to prevent cross
1357 // site scripting issues on Internet Explorer 6.
1358 if (!drupal_validate_utf8($string)) {
1361 // Store the text format.
1362 _filter_xss_split($allowed_tags, TRUE
);
1363 // Remove NULL characters (ignored by some browsers).
1364 $string = str_replace(chr(0), '', $string);
1365 // Remove Netscape 4 JS entities.
1366 $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
1368 // Defuse all HTML entities.
1369 $string = str_replace('&', '&', $string);
1370 // Change back only well-formed entities in our whitelist:
1371 // Decimal numeric entities.
1372 $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string);
1373 // Hexadecimal numeric entities.
1374 $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
1376 $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
1378 return preg_replace_callback('%
1380 <(?=[^a-zA-Z!/]) # a lone <
1382 <!--.*?--> # a comment
1384 <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
1387 )%x', '_filter_xss_split', $string);
1391 * Processes an HTML tag.
1394 * An array with various meaning depending on the value of $store.
1395 * If $store is TRUE then the array contains the allowed tags.
1396 * If $store is FALSE then the array has one element, the HTML tag to process.
1398 * Whether to store $m.
1401 * If the element isn't allowed, an empty string. Otherwise, the cleaned up
1402 * version of the HTML element.
1404 function _filter_xss_split($m, $store = FALSE
) {
1405 static
$allowed_html;
1408 $allowed_html = array_flip($m);
1414 if (substr($string, 0, 1) != '<') {
1415 // We matched a lone ">" character.
1418 elseif (strlen($string) == 1) {
1419 // We matched a lone "<" character.
1423 if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
1424 // Seriously malformed.
1428 $slash = trim($matches[1]);
1429 $elem = &$matches[2];
1430 $attrlist = &$matches[3];
1431 $comment = &$matches[4];
1437 if (!isset($allowed_html[strtolower($elem)])) {
1438 // Disallowed HTML element.
1450 // Is there a closing XHTML slash at the end of the attributes?
1451 $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
1452 $xhtml_slash = $count ?
' /' : '';
1454 // Clean up attributes.
1455 $attr2 = implode(' ', _filter_xss_attributes($attrlist));
1456 $attr2 = preg_replace('/[<>]/', '', $attr2);
1457 $attr2 = strlen($attr2) ?
' ' .
$attr2 : '';
1459 return "<$elem$attr2$xhtml_slash>";
1463 * Processes a string of HTML attributes.
1466 * Cleaned up version of the HTML attributes.
1468 function _filter_xss_attributes($attr) {
1473 while (strlen($attr) != 0) {
1474 // Was the last operation successful?
1479 // Attribute name, href for instance.
1480 if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
1481 $attrname = strtolower($match[1]);
1482 $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
1483 $working = $mode = 1;
1484 $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
1489 // Equals sign or valueless ("selected").
1490 if (preg_match('/^\s*=\s*/', $attr)) {
1491 $working = 1; $mode = 2;
1492 $attr = preg_replace('/^\s*=\s*/', '', $attr);
1496 if (preg_match('/^\s+/', $attr)) {
1497 $working = 1; $mode = 0;
1499 $attrarr[] = $attrname;
1501 $attr = preg_replace('/^\s+/', '', $attr);
1506 // Attribute value, a URL after href= for instance.
1507 if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
1508 $thisval = filter_xss_bad_protocol($match[1]);
1511 $attrarr[] = "$attrname=\"$thisval\"";
1515 $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
1519 if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
1520 $thisval = filter_xss_bad_protocol($match[1]);
1523 $attrarr[] = "$attrname='$thisval'";
1525 $working = 1; $mode = 0;
1526 $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
1530 if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
1531 $thisval = filter_xss_bad_protocol($match[1]);
1534 $attrarr[] = "$attrname=\"$thisval\"";
1536 $working = 1; $mode = 0;
1537 $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
1542 if ($working == 0) {
1543 // Not well formed; remove and try again.
1544 $attr = preg_replace('/
1547 "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
1549 \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
1551 \S # - a non-whitespace character
1552 )* # any number of the above three
1553 \s* # any number of whitespaces
1559 // The attribute list ends with a valueless attribute like "selected".
1560 if ($mode == 1 && !$skip) {
1561 $attrarr[] = $attrname;
1567 * Processes an HTML attribute value and strips dangerous protocols from URLs.
1570 * The string with the attribute value.
1572 * (deprecated) Whether to decode entities in the $string. Set to FALSE if the
1573 * $string is in plain text, TRUE otherwise. Defaults to TRUE. This parameter
1574 * is deprecated and will be removed in Drupal 8. To process a plain-text URI,
1575 * call drupal_strip_dangerous_protocols() or check_url() instead.
1578 * Cleaned up and HTML-escaped version of $string.
1580 function filter_xss_bad_protocol($string, $decode = TRUE
) {
1581 // Get the plain text representation of the attribute value (i.e. its meaning).
1582 // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML
1583 // string that needs decoding.
1585 if (!function_exists('decode_entities')) {
1586 require_once DRUPAL_ROOT .
'/includes/unicode.inc';
1589 $string = decode_entities($string);
1591 return check_plain(drupal_strip_dangerous_protocols($string));
1595 * @} End of "defgroup sanitization".
1599 * @defgroup format Formatting
1601 * Functions to format numbers, strings, dates, etc.
1605 * Formats an RSS channel.
1607 * Arbitrary elements may be added using the $args associative array.
1609 function format_rss_channel($title, $link, $description, $items, $langcode = NULL
, $args = array()) {
1610 global $language_content;
1611 $langcode = $langcode ?
$langcode : $language_content->language
;
1613 $output = "<channel>\n";
1614 $output .
= ' <title>' .
check_plain($title) .
"</title>\n";
1615 $output .
= ' <link>' .
check_url($link) .
"</link>\n";
1617 // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
1618 // We strip all HTML tags, but need to prevent double encoding from properly
1619 // escaped source data (such as & becoming &amp;).
1620 $output .
= ' <description>' .
check_plain(decode_entities(strip_tags($description))) .
"</description>\n";
1621 $output .
= ' <language>' .
check_plain($langcode) .
"</language>\n";
1622 $output .
= format_xml_elements($args);
1624 $output .
= "</channel>\n";
1630 * Formats a single RSS item.
1632 * Arbitrary elements may be added using the $args associative array.
1634 function format_rss_item($title, $link, $description, $args = array()) {
1635 $output = "<item>\n";
1636 $output .
= ' <title>' .
check_plain($title) .
"</title>\n";
1637 $output .
= ' <link>' .
check_url($link) .
"</link>\n";
1638 $output .
= ' <description>' .
check_plain($description) .
"</description>\n";
1639 $output .
= format_xml_elements($args);
1640 $output .
= "</item>\n";
1646 * Formats XML elements.
1649 * An array where each item represents an element and is either a:
1650 * - (key => value) pair (<key>value</key>)
1651 * - Associative array with fields:
1652 * - 'key': element name
1653 * - 'value': element contents
1654 * - 'attributes': associative array of element attributes
1656 * In both cases, 'value' can be a simple string, or it can be another array
1657 * with the same format as $array itself for nesting.
1659 function format_xml_elements($array) {
1661 foreach ($array as
$key => $value) {
1662 if (is_numeric($key)) {
1663 if ($value['key']) {
1664 $output .
= ' <' .
$value['key'];
1665 if (isset($value['attributes']) && is_array($value['attributes'])) {
1666 $output .
= drupal_attributes($value['attributes']);
1669 if (isset($value['value']) && $value['value'] != '') {
1670 $output .
= '>' .
(is_array($value['value']) ?
format_xml_elements($value['value']) : check_plain($value['value'])) .
'</' .
$value['key'] .
">\n";
1678 $output .
= ' <' .
$key .
'>' .
(is_array($value) ?
format_xml_elements($value) : check_plain($value)) .
"</$key>\n";
1685 * Formats a string containing a count of items.
1687 * This function ensures that the string is pluralized correctly. Since t() is
1688 * called by this function, make sure not to pass already-localized strings to
1693 * $output = format_plural($node->comment_count, '1 comment', '@count comments');
1696 * Example with additional replacements:
1698 * $output = format_plural($update_count,
1699 * 'Changed the content type of 1 post from %old-type to %new-type.',
1700 * 'Changed the content type of @count posts from %old-type to %new-type.',
1701 * array('%old-type' => $info->old_type, '%new-type' => $info->new_type)));
1705 * The item count to display.
1707 * The string for the singular case. Make sure it is clear this is singular,
1708 * to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
1709 * use @count in the singular string.
1711 * The string for the plural case. Make sure it is clear this is plural, to
1712 * ease translation. Use @count in place of the item count, as in
1713 * "@count new comments".
1715 * An associative array of replacements to make after translation. Instances
1716 * of any key in this array are replaced with the corresponding value.
1717 * Based on the first character of the key, the value is escaped and/or
1718 * themed. See format_string(). Note that you do not need to include @count
1719 * in this array; this replacement is done automatically for the plural case.
1721 * An associative array of additional options. See t() for allowed keys.
1724 * A translated string.
1727 * @see format_string()
1729 function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
1730 $args['@count'] = $count;
1732 return t($singular, $args, $options);
1735 // Get the plural index through the gettext formula.
1736 $index = (function_exists('locale_get_plural')) ?
locale_get_plural($count, isset($options['langcode']) ?
$options['langcode'] : NULL
) : -1;
1737 // Backwards compatibility.
1739 return t($plural, $args, $options);
1744 return t($singular, $args, $options);
1746 return t($plural, $args, $options);
1748 unset($args['@count']);
1749 $args['@count[' .
$index .
']'] = $count;
1750 return t(strtr($plural, array('@count' => '@count[' .
$index .
']')), $args, $options);
1756 * Parses a given byte count.
1759 * A size expressed as a number of bytes with optional SI or IEC binary unit
1760 * prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
1763 * An integer representation of the size in bytes.
1765 function parse_size($size) {
1766 $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
1767 $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
1769 // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
1770 return round($size * pow(DRUPAL_KILOBYTE
, stripos('bkmgtpezy', $unit[0])));
1773 return round($size);
1778 * Generates a string representation for the given byte count.
1783 * Optional language code to translate to a language other than what is used
1784 * to display the page.
1787 * A translated string representation of the size.
1789 function format_size($size, $langcode = NULL
) {
1790 if ($size < DRUPAL_KILOBYTE
) {
1791 return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
1794 $size = $size / DRUPAL_KILOBYTE
; // Convert bytes to kilobytes.
1796 t('@size KB', array(), array('langcode' => $langcode)),
1797 t('@size MB', array(), array('langcode' => $langcode)),
1798 t('@size GB', array(), array('langcode' => $langcode)),
1799 t('@size TB', array(), array('langcode' => $langcode)),
1800 t('@size PB', array(), array('langcode' => $langcode)),
1801 t('@size EB', array(), array('langcode' => $langcode)),
1802 t('@size ZB', array(), array('langcode' => $langcode)),
1803 t('@size YB', array(), array('langcode' => $langcode)),
1805 foreach ($units as
$unit) {
1806 if (round($size, 2) >= DRUPAL_KILOBYTE
) {
1807 $size = $size / DRUPAL_KILOBYTE
;
1813 return str_replace('@size', round($size, 2), $unit);
1818 * Formats a time interval with the requested granularity.
1821 * The length of the interval in seconds.
1822 * @param $granularity
1823 * How many different units to display in the string.
1825 * Optional language code to translate to a language other than
1826 * what is used to display the page.
1829 * A translated string representation of the interval.
1831 function format_interval($timestamp, $granularity = 2, $langcode = NULL
) {
1833 '1 year|@count years' => 31536000,
1834 '1 month|@count months' => 2592000,
1835 '1 week|@count weeks' => 604800,
1836 '1 day|@count days' => 86400,
1837 '1 hour|@count hours' => 3600,
1838 '1 min|@count min' => 60,
1839 '1 sec|@count sec' => 1
1842 foreach ($units as
$key => $value) {
1843 $key = explode('|', $key);
1844 if ($timestamp >= $value) {
1845 $output .
= ($output ?
' ' : '') .
format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
1846 $timestamp %
= $value;
1850 if ($granularity == 0) {
1854 return $output ?
$output : t('0 sec', array(), array('langcode' => $langcode));
1858 * Formats a date, using a date type or a custom date format string.
1861 * A UNIX timestamp to format.
1863 * (optional) The format to use, one of:
1864 * - 'short', 'medium', or 'long' (the corresponding built-in date formats).
1865 * - The name of a date type defined by a module in hook_date_format_types(),
1866 * if it's been assigned a format.
1867 * - The machine name of an administrator-defined date format.
1868 * - 'custom', to use $format.
1869 * Defaults to 'medium'.
1871 * (optional) If $type is 'custom', a PHP date format string suitable for
1872 * input to date(). Use a backslash to escape ordinary text, so it does not
1873 * get interpreted as date format characters.
1875 * (optional) Time zone identifier, as described at
1876 * http://php.net/manual/en/timezones.php Defaults to the time zone used to
1879 * (optional) Language code to translate to. Defaults to the language used to
1883 * A translated date string in the requested format.
1885 function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL
, $langcode = NULL
) {
1886 // Use the advanced drupal_static() pattern, since this is called very often.
1887 static
$drupal_static_fast;
1888 if (!isset($drupal_static_fast)) {
1889 $drupal_static_fast['timezones'] = &drupal_static(__FUNCTION__
);
1891 $timezones = &$drupal_static_fast['timezones'];
1893 if (!isset($timezone)) {
1894 $timezone = date_default_timezone_get();
1896 // Store DateTimeZone objects in an array rather than repeatedly
1897 // constructing identical objects over the life of a request.
1898 if (!isset($timezones[$timezone])) {
1899 $timezones[$timezone] = timezone_open($timezone);
1902 // Use the default langcode if none is set.
1904 if (empty($langcode)) {
1905 $langcode = isset($language->language
) ?
$language->language
: 'en';
1910 $format = variable_get('date_format_short', 'm/d/Y - H:i');
1914 $format = variable_get('date_format_long', 'l, F j, Y - H:i');
1918 // No change to format.
1923 // Retrieve the format of the custom $type passed.
1924 if ($type != 'medium') {
1925 $format = variable_get('date_format_' .
$type, '');
1927 // Fall back to 'medium'.
1928 if ($format === '') {
1929 $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
1934 // Create a DateTime object from the timestamp.
1935 $date_time = date_create('@' .
$timestamp);
1936 // Set the time zone for the DateTime object.
1937 date_timezone_set($date_time, $timezones[$timezone]);
1939 // Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'.
1940 // xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the
1942 // Paired backslashes are isolated to prevent errors in read-ahead evaluation.
1943 // The read-ahead expression ensures that A matches, but not \A.
1944 $format = preg_replace(array('/\\\\\\\\/', '/(?<!\\\\)([AaeDlMTF])/'), array("\xEF\\\\\\\\\xFF", "\xEF\\\\\$1\$1\xFF"), $format);
1946 // Call date_format().
1947 $format = date_format($date_time, $format);
1949 // Pass the langcode to _format_date_callback().
1950 _format_date_callback(NULL
, $langcode);
1952 // Translate the marked sequences.
1953 return preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', '_format_date_callback', $format);
1957 * Returns an ISO8601 formatted date based on the given date.
1959 * Callback for use within hook_rdf_mapping() implementations.
1965 * An ISO8601 formatted date.
1967 function date_iso8601($date) {
1968 // The DATE_ISO8601 constant cannot be used here because it does not match
1969 // date('c') and produces invalid RDF markup.
1970 return date('c', $date);
1974 * Translates a formatted date string.
1976 * Callback for preg_replace_callback() within format_date().
1978 function _format_date_callback(array $matches = NULL
, $new_langcode = NULL
) {
1979 // We cache translations to avoid redundant and rather costly calls to t().
1980 static
$cache, $langcode;
1982 if (!isset($matches)) {
1983 $langcode = $new_langcode;
1987 $code = $matches[1];
1988 $string = $matches[2];
1990 if (!isset($cache[$langcode][$code][$string])) {
1992 'langcode' => $langcode,
1996 $options['context'] = 'Long month name';
2000 $cache[$langcode][$code][$string] = $string;
2003 $cache[$langcode][$code][$string] = t($string, array(), $options);
2006 return $cache[$langcode][$code][$string];
2010 * Format a username.
2012 * By default, the passed-in object's 'name' property is used if it exists, or
2013 * else, the site-defined value for the 'anonymous' variable. However, a module
2014 * may override this by implementing hook_username_alter(&$name, $account).
2016 * @see hook_username_alter()
2019 * The account object for the user whose name is to be formatted.
2022 * An unsanitized string with the username to display. The code receiving
2023 * this result must ensure that check_plain() is called on it before it is
2024 * printed to the page.
2026 function format_username($account) {
2027 $name = !empty($account->name
) ?
$account->name
: variable_get('anonymous', t('Anonymous'));
2028 drupal_alter('username', $name, $account);
2033 * @} End of "defgroup format".
2037 * Generates an internal or external URL.
2039 * When creating links in modules, consider whether l() could be a better
2040 * alternative than url().
2043 * The internal path or external URL being linked to, such as "node/34" or
2044 * "http://example.com/foo". A few notes:
2045 * - If you provide a full URL, it will be considered an external URL.
2046 * - If you provide only the path (e.g. "node/34"), it will be
2047 * considered an internal link. In this case, it should be a system URL,
2048 * and it will be replaced with the alias, if one exists. Additional query
2049 * arguments for internal paths must be supplied in $options['query'], not
2050 * included in $path.
2051 * - If you provide an internal path and $options['alias'] is set to TRUE, the
2052 * path is assumed already to be the correct path alias, and the alias is
2054 * - The special string '<front>' generates a link to the site's base URL.
2055 * - If your external URL contains a query (e.g. http://example.com/foo?a=b),
2056 * then you can either URL encode the query keys and values yourself and
2057 * include them in $path, or use $options['query'] to let this function
2060 * An associative array of additional options, with the following elements:
2061 * - 'query': An array of query key/value-pairs (without any URL-encoding) to
2062 * append to the URL.
2063 * - 'fragment': A fragment identifier (named anchor) to append to the URL.
2064 * Do not include the leading '#' character.
2065 * - 'absolute': Defaults to FALSE. Whether to force the output to be an
2066 * absolute link (beginning with http:). Useful for links that will be
2067 * displayed outside the site, such as in an RSS feed.
2068 * - 'alias': Defaults to FALSE. Whether the given path is a URL alias
2070 * - 'external': Whether the given path is an external URL.
2071 * - 'language': An optional language object. If the path being linked to is
2072 * internal to the site, $options['language'] is used to look up the alias
2073 * for the URL. If $options['language'] is omitted, the global $language_url
2075 * - 'https': Whether this URL should point to a secure location. If not
2076 * defined, the current scheme is used, so the user stays on http or https
2077 * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can
2078 * only be enforced when the variable 'https' is set to TRUE.
2079 * - 'base_url': Only used internally, to modify the base URL when a language
2080 * dependent URL requires so.
2081 * - 'prefix': Only used internally, to modify the path when a language
2082 * dependent URL requires so.
2083 * - 'script': The script filename in Drupal's root directory to use when
2084 * clean URLs are disabled, such as 'index.php'. Defaults to an empty
2085 * string, as most modern web servers automatically find 'index.php'. If
2086 * clean URLs are disabled, the value of $path is appended as query
2087 * parameter 'q' to $options['script'] in the returned URL. When deploying
2088 * Drupal on a web server that cannot be configured to automatically find
2089 * index.php, then hook_url_outbound_alter() can be implemented to force
2090 * this value to 'index.php'.
2091 * - 'entity_type': The entity type of the object that called url(). Only
2092 * set if url() is invoked by entity_uri().
2093 * - 'entity': The entity object (such as a node) for which the URL is being
2094 * generated. Only set if url() is invoked by entity_uri().
2097 * A string containing a URL to the given path.
2099 function url($path = NULL
, array $options = array()) {
2100 // Merge in defaults.
2104 'absolute' => FALSE
,
2109 if (!isset($options['external'])) {
2110 // Return an external link if $path contains an allowed absolute URL. Only
2111 // call the slow drupal_strip_dangerous_protocols() if $path contains a ':'
2112 // before any / ? or #. Note: we could use url_is_external($path) here, but
2113 // that would require another function call, and performance inside url() is
2115 $colonpos = strpos($path, ':');
2116 $options['external'] = ($colonpos !== FALSE
&& !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
2119 // Preserve the original path before altering or aliasing.
2120 $original_path = $path;
2122 // Allow other modules to alter the outbound URL and options.
2123 drupal_alter('url_outbound', $path, $options, $original_path);
2125 if (isset($options['fragment']) && $options['fragment'] !== '') {
2126 $options['fragment'] = '#' .
$options['fragment'];
2129 if ($options['external']) {
2130 // Split off the fragment.
2131 if (strpos($path, '#') !== FALSE
) {
2132 list($path, $old_fragment) = explode('#', $path, 2);
2133 // If $options contains no fragment, take it over from the path.
2134 if (isset($old_fragment) && !$options['fragment']) {
2135 $options['fragment'] = '#' .
$old_fragment;
2138 // Append the query.
2139 if ($options['query']) {
2140 $path .
= (strpos($path, '?') !== FALSE ?
'&' : '?') .
drupal_http_build_query($options['query']);
2142 if (isset($options['https']) && variable_get('https', FALSE
)) {
2143 if ($options['https'] === TRUE
) {
2144 $path = str_replace('http://', 'https://', $path);
2146 elseif ($options['https'] === FALSE
) {
2147 $path = str_replace('https://', 'http://', $path);
2151 return $path .
$options['fragment'];
2154 global $base_url, $base_secure_url, $base_insecure_url;
2156 // The base_url might be rewritten from the language rewrite in domain mode.
2157 if (!isset($options['base_url'])) {
2158 if (isset($options['https']) && variable_get('https', FALSE
)) {
2159 if ($options['https'] === TRUE
) {
2160 $options['base_url'] = $base_secure_url;
2161 $options['absolute'] = TRUE
;
2163 elseif ($options['https'] === FALSE
) {
2164 $options['base_url'] = $base_insecure_url;
2165 $options['absolute'] = TRUE
;
2169 $options['base_url'] = $base_url;
2173 // The special path '<front>' links to the default front page.
2174 if ($path == '<front>') {
2177 elseif (!empty($path) && !$options['alias']) {
2178 $language = isset($options['language']) && isset($options['language']->language
) ?
$options['language']->language
: '';
2179 $alias = drupal_get_path_alias($original_path, $language);
2180 if ($alias != $original_path) {
2185 $base = $options['absolute'] ?
$options['base_url'] .
'/' : base_path();
2186 $prefix = empty($path) ?
rtrim($options['prefix'], '/') : $options['prefix'];
2189 if (!empty($GLOBALS['conf']['clean_url'])) {
2190 $path = drupal_encode_path($prefix .
$path);
2191 if ($options['query']) {
2192 return $base .
$path .
'?' .
drupal_http_build_query($options['query']) .
$options['fragment'];
2195 return $base .
$path .
$options['fragment'];
2198 // Without Clean URLs.
2200 $path = $prefix .
$path;
2202 if (!empty($path)) {
2203 $query['q'] = $path;
2205 if ($options['query']) {
2206 // We do not use array_merge() here to prevent overriding $path via query
2208 $query += $options['query'];
2210 $query = $query ?
('?' .
drupal_http_build_query($query)) : '';
2211 $script = isset($options['script']) ?
$options['script'] : '';
2212 return $base .
$script .
$query .
$options['fragment'];
2217 * Returns TRUE if a path is external to Drupal (e.g. http://example.com).
2219 * If a path cannot be assessed by Drupal's menu handler, then we must
2220 * treat it as potentially insecure.
2223 * The internal path or external URL being linked to, such as "node/34" or
2224 * "http://example.com/foo".
2227 * Boolean TRUE or FALSE, where TRUE indicates an external path.
2229 function url_is_external($path) {
2230 $colonpos = strpos($path, ':');
2231 // Avoid calling drupal_strip_dangerous_protocols() if there is any
2232 // slash (/), hash (#) or question_mark (?) before the colon (:)
2233 // occurrence - if any - as this would clearly mean it is not a URL.
2234 return $colonpos !== FALSE
&& !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path;
2238 * Formats an attribute string for an HTTP header.
2240 * @param $attributes
2241 * An associative array of attributes such as 'rel'.
2244 * A ; separated string ready for insertion in a HTTP header. No escaping is
2245 * performed for HTML entities, so this string is not safe to be printed.
2247 * @see drupal_add_http_header()
2249 function drupal_http_header_attributes(array $attributes = array()) {
2250 foreach ($attributes as
$attribute => &$data) {
2251 if (is_array($data)) {
2252 $data = implode(' ', $data);
2254 $data = $attribute .
'="' .
$data .
'"';
2256 return $attributes ?
' ' .
implode('; ', $attributes) : '';
2260 * Converts an associative array to an XML/HTML tag attribute string.
2262 * Each array key and its value will be formatted into an attribute string.
2263 * If a value is itself an array, then its elements are concatenated to a single
2264 * space-delimited string (for example, a class attribute with multiple values).
2266 * Attribute values are sanitized by running them through check_plain().
2267 * Attribute names are not automatically sanitized. When using user-supplied
2268 * attribute names, it is strongly recommended to allow only white-listed names,
2269 * since certain attributes carry security risks and can be abused.
2271 * Examples of security aspects when using drupal_attributes:
2273 * // By running the value in the following statement through check_plain,
2274 * // the malicious script is neutralized.
2275 * drupal_attributes(array('title' => t('<script>steal_cookie();</script>')));
2277 * // The statement below demonstrates dangerous use of drupal_attributes, and
2278 * // will return an onmouseout attribute with JavaScript code that, when used
2279 * // as attribute in a tag, will cause users to be redirected to another site.
2281 * // In this case, the 'onmouseout' attribute should not be whitelisted --
2282 * // you don't want users to have the ability to add this attribute or others
2283 * // that take JavaScript commands.
2284 * drupal_attributes(array('onmouseout' => 'window.location="http://malicious.com/";')));
2287 * @param $attributes
2288 * An associative array of key-value pairs to be converted to attributes.
2291 * A string ready for insertion in a tag (starts with a space).
2293 * @ingroup sanitization
2295 function drupal_attributes(array $attributes = array()) {
2296 foreach ($attributes as
$attribute => &$data) {
2297 $data = implode(' ', (array) $data);
2298 $data = $attribute .
'="' .
check_plain($data) .
'"';
2300 return $attributes ?
' ' .
implode(' ', $attributes) : '';
2304 * Formats an internal or external URL link as an HTML anchor tag.
2306 * This function correctly handles aliased paths, and adds an 'active' class
2307 * attribute to links that point to the current page (for theming), so all
2308 * internal links output by modules should be generated by this function if
2312 * The link text for the anchor tag.
2314 * The internal path or external URL being linked to, such as "node/34" or
2315 * "http://example.com/foo". After the url() function is called to construct
2316 * the URL from $path and $options, the resulting URL is passed through
2317 * check_plain() before it is inserted into the HTML anchor tag, to ensure
2318 * well-formed HTML. See url() for more information and notes.
2319 * @param array $options
2320 * An associative array of additional options, with the following elements:
2321 * - 'attributes': An associative array of HTML attributes to apply to the
2322 * anchor tag. If element 'class' is included, it must be an array; 'title'
2323 * must be a string; other elements are more flexible, as they just need
2324 * to work in a call to drupal_attributes($options['attributes']).
2325 * - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
2326 * example, to make an image tag into a link, this must be set to TRUE, or
2327 * you will see the escaped HTML image tag. $text is not sanitized if
2328 * 'html' is TRUE. The calling function must ensure that $text is already
2330 * - 'language': An optional language object. If the path being linked to is
2331 * internal to the site, $options['language'] is used to determine whether
2332 * the link is "active", or pointing to the current page (the language as
2333 * well as the path must match). This element is also used by url().
2334 * - Additional $options elements used by the url() function.
2337 * An HTML string containing a link to the given path.
2339 function l($text, $path, array $options = array()) {
2340 global $language_url;
2341 static
$use_theme = NULL
;
2343 // Merge in defaults.
2345 'attributes' => array(),
2349 // Append active class.
2350 if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
2351 (empty($options['language']) || $options['language']->language
== $language_url->language
)) {
2352 $options['attributes']['class'][] = 'active';
2355 // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
2356 // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
2357 if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE
) {
2358 $options['attributes']['title'] = strip_tags($options['attributes']['title']);
2361 // Determine if rendering of the link is to be done with a theme function
2362 // or the inline default. Inline is faster, but if the theme system has been
2363 // loaded and a module or theme implements a preprocess or process function
2364 // or overrides the theme_link() function, then invoke theme(). Preliminary
2365 // benchmarks indicate that invoking theme() can slow down the l() function
2366 // by 20% or more, and that some of the link-heavy Drupal pages spend more
2367 // than 10% of the total page request time in the l() function.
2368 if (!isset($use_theme) && function_exists('theme')) {
2369 // Allow edge cases to prevent theme initialization and force inline link
2371 if (variable_get('theme_link', TRUE
)) {
2372 drupal_theme_initialize();
2373 $registry = theme_get_registry();
2374 // We don't want to duplicate functionality that's in theme(), so any
2375 // hint of a module or theme doing anything at all special with the 'link'
2376 // theme hook should simply result in theme() being called. This includes
2377 // the overriding of theme_link() with an alternate function or template,
2378 // the presence of preprocess or process functions, or the presence of
2380 $use_theme = !isset($registry['link']['function']) || ($registry['link']['function'] != 'theme_link');
2381 $use_theme = $use_theme || !empty($registry['link']['preprocess functions']) || !empty($registry['link']['process functions']) || !empty($registry['link']['includes']);
2388 return theme('link', array('text' => $text, 'path' => $path, 'options' => $options));
2390 // The result of url() is a plain-text URL. Because we are using it here
2391 // in an HTML argument context, we need to encode it properly.
2392 return '<a href="' .
check_plain(url($path, $options)) .
'"' .
drupal_attributes($options['attributes']) .
'>' .
($options['html'] ?
$text : check_plain($text)) .
'</a>';
2396 * Delivers a page callback result to the browser in the appropriate format.
2398 * This function is most commonly called by menu_execute_active_handler(), but
2399 * can also be called by error conditions such as drupal_not_found(),
2400 * drupal_access_denied(), and drupal_site_offline().
2402 * When a user requests a page, index.php calls menu_execute_active_handler(),
2403 * which calls the 'page callback' function registered in hook_menu(). The page
2404 * callback function can return one of:
2405 * - NULL: to indicate no content.
2406 * - An integer menu status constant: to indicate an error condition.
2407 * - A string of HTML content.
2408 * - A renderable array of content.
2409 * Returning a renderable array rather than a string of HTML is preferred,
2410 * because that provides modules with more flexibility in customizing the final
2413 * When the page callback returns its constructed content to
2414 * menu_execute_active_handler(), this function gets called. The purpose of
2415 * this function is to determine the most appropriate 'delivery callback'
2416 * function to route the content to. The delivery callback function then
2417 * sends the content to the browser in the needed format. The default delivery
2418 * callback is drupal_deliver_html_page(), which delivers the content as an HTML
2419 * page, complete with blocks in addition to the content. This default can be
2420 * overridden on a per menu router item basis by setting 'delivery callback' in
2421 * hook_menu() or hook_menu_alter(), and can also be overridden on a per request
2422 * basis in hook_page_delivery_callback_alter().
2424 * For example, the same page callback function can be used for an HTML
2425 * version of the page and an Ajax version of the page. The page callback
2426 * function just needs to decide what content is to be returned and the
2427 * delivery callback function will send it as an HTML page or an Ajax
2428 * response, as appropriate.
2430 * In order for page callbacks to be reusable in different delivery formats,
2431 * they should not issue any "print" or "echo" statements, but instead just
2434 * Also note that this function does not perform access checks. The delivery
2435 * callback function specified in hook_menu(), hook_menu_alter(), or
2436 * hook_page_delivery_callback_alter() will be called even if the router item
2437 * access checks fail. This is intentional (it is needed for JSON and other
2438 * purposes), but it has security implications. Do not call this function
2439 * directly unless you understand the security implications, and be careful in
2440 * writing delivery callbacks, so that they do not violate security. See
2441 * drupal_deliver_html_page() for an example of a delivery callback that
2442 * respects security.
2444 * @param $page_callback_result
2445 * The result of a page callback. Can be one of:
2446 * - NULL: to indicate no content.
2447 * - An integer menu status constant: to indicate an error condition.
2448 * - A string of HTML content.
2449 * - A renderable array of content.
2450 * @param $default_delivery_callback
2451 * (Optional) If given, it is the name of a delivery function most likely
2452 * to be appropriate for the page request as determined by the calling
2453 * function (e.g., menu_execute_active_handler()). If not given, it is
2454 * determined from the menu router information of the current page.
2456 * @see menu_execute_active_handler()
2458 * @see hook_menu_alter()
2459 * @see hook_page_delivery_callback_alter()
2461 function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL
) {
2462 if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) {
2463 $default_delivery_callback = $router_item['delivery_callback'];
2465 $delivery_callback = !empty($default_delivery_callback) ?
$default_delivery_callback : 'drupal_deliver_html_page';
2466 // Give modules a chance to alter the delivery callback used, based on
2467 // request-time context (e.g., HTTP request headers).
2468 drupal_alter('page_delivery_callback', $delivery_callback);
2469 if (function_exists($delivery_callback)) {
2470 $delivery_callback($page_callback_result);
2473 // If a delivery callback is specified, but doesn't exist as a function,
2474 // something is wrong, but don't print anything, since it's not known
2475 // what format the response needs to be in.
2476 watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR
);
2481 * Packages and sends the result of a page callback to the browser as HTML.
2483 * @param $page_callback_result
2484 * The result of a page callback. Can be one of:
2485 * - NULL: to indicate no content.
2486 * - An integer menu status constant: to indicate an error condition.
2487 * - A string of HTML content.
2488 * - A renderable array of content.
2490 * @see drupal_deliver_page()
2492 function drupal_deliver_html_page($page_callback_result) {
2493 // Emit the correct charset HTTP header, but not if the page callback
2494 // result is NULL, since that likely indicates that it printed something
2495 // in which case, no further headers may be sent, and not if code running
2496 // for this page request has already set the content type header.
2497 if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) {
2498 drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
2501 // Send appropriate HTTP-Header for browsers and search engines.
2503 drupal_add_http_header('Content-Language', $language->language
);
2505 // Menu status constants are integers; page content is a string or array.
2506 if (is_int($page_callback_result)) {
2507 // @todo: Break these up into separate functions?
2508 switch ($page_callback_result) {
2509 case MENU_NOT_FOUND
:
2510 // Print a 404 page.
2511 drupal_add_http_header('Status', '404 Not Found');
2513 watchdog('page not found', check_plain($_GET['q']), NULL
, WATCHDOG_WARNING
);
2515 // Check for and return a fast 404 page if configured.
2518 // Keep old path for reference, and to allow forms to redirect to it.
2519 if (!isset($_GET['destination'])) {
2520 $_GET['destination'] = $_GET['q'];
2523 $path = drupal_get_normal_path(variable_get('site_404', ''));
2524 if ($path && $path != $_GET['q']) {
2525 // Custom 404 handler. Set the active item in case there are tabs to
2526 // display, or other dependencies on the path.
2527 menu_set_active_item($path);
2528 $return = menu_execute_active_handler($path, FALSE
);
2531 if (empty($return) || $return == MENU_NOT_FOUND
|| $return == MENU_ACCESS_DENIED
) {
2532 // Standard 404 handler.
2533 drupal_set_title(t('Page not found'));
2534 $return = t('The requested page "@path" could not be found.', array('@path' => request_uri()));
2537 drupal_set_page_content($return);
2538 $page = element_info('page');
2539 print drupal_render_page($page);
2542 case MENU_ACCESS_DENIED
:
2543 // Print a 403 page.
2544 drupal_add_http_header('Status', '403 Forbidden');
2545 watchdog('access denied', check_plain($_GET['q']), NULL
, WATCHDOG_WARNING
);
2547 // Keep old path for reference, and to allow forms to redirect to it.
2548 if (!isset($_GET['destination'])) {
2549 $_GET['destination'] = $_GET['q'];
2552 $path = drupal_get_normal_path(variable_get('site_403', ''));
2553 if ($path && $path != $_GET['q']) {
2554 // Custom 403 handler. Set the active item in case there are tabs to
2555 // display or other dependencies on the path.
2556 menu_set_active_item($path);
2557 $return = menu_execute_active_handler($path, FALSE
);
2560 if (empty($return) || $return == MENU_NOT_FOUND
|| $return == MENU_ACCESS_DENIED
) {
2561 // Standard 403 handler.
2562 drupal_set_title(t('Access denied'));
2563 $return = t('You are not authorized to access this page.');
2566 print drupal_render_page($return);
2569 case MENU_SITE_OFFLINE
:
2570 // Print a 503 page.
2571 drupal_maintenance_theme();
2572 drupal_add_http_header('Status', '503 Service unavailable');
2573 drupal_set_title(t('Site under maintenance'));
2574 print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',
2575 t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
2579 elseif (isset($page_callback_result)) {
2580 // Print anything besides a menu constant, assuming it's not NULL or
2582 print drupal_render_page($page_callback_result);
2585 // Perform end-of-request tasks.
2586 drupal_page_footer();
2590 * Performs end-of-request tasks.
2592 * This function sets the page cache if appropriate, and allows modules to
2593 * react to the closing of the page by calling hook_exit().
2595 function drupal_page_footer() {
2598 module_invoke_all('exit');
2600 // Commit the user session, if needed.
2601 drupal_session_commit();
2603 if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) {
2604 drupal_serve_page_from_cache($cache);
2610 _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE
);
2611 drupal_cache_system_paths();
2612 module_implements_write_cache();
2613 system_run_automated_cron();
2617 * Performs end-of-request tasks.
2619 * In some cases page requests need to end without calling drupal_page_footer().
2620 * In these cases, call drupal_exit() instead. There should rarely be a reason
2621 * to call exit instead of drupal_exit();
2623 * @param $destination
2624 * If this function is called from drupal_goto(), then this argument
2625 * will be a fully-qualified URL that is the destination of the redirect.
2626 * This should be passed along to hook_exit() implementations.
2628 function drupal_exit($destination = NULL
) {
2629 if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL
) {
2630 if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE
!= 'update') {
2631 module_invoke_all('exit', $destination);
2633 drupal_session_commit();
2639 * Forms an associative array from a linear array.
2641 * This function walks through the provided array and constructs an associative
2642 * array out of it. The keys of the resulting array will be the values of the
2643 * input array. The values will be the same as the keys unless a function is
2644 * specified, in which case the output of the function is used for the values
2650 * A name of a function to apply to all values before output.
2653 * An associative array.
2655 function drupal_map_assoc($array, $function = NULL
) {
2656 // array_combine() fails with empty arrays:
2657 // http://bugs.php.net/bug.php?id=34857.
2658 $array = !empty($array) ?
array_combine($array, $array) : array();
2659 if (is_callable($function)) {
2660 $array = array_map($function, $array);
2666 * Attempts to set the PHP maximum execution time.
2668 * This function is a wrapper around the PHP function set_time_limit().
2669 * When called, set_time_limit() restarts the timeout counter from zero.
2670 * In other words, if the timeout is the default 30 seconds, and 25 seconds
2671 * into script execution a call such as set_time_limit(20) is made, the
2672 * script will run for a total of 45 seconds before timing out.
2674 * It also means that it is possible to decrease the total time limit if
2675 * the sum of the new time limit and the current time spent running the
2676 * script is inferior to the original time limit. It is inherent to the way
2677 * set_time_limit() works, it should rather be called with an appropriate
2678 * value every time you need to allocate a certain amount of time
2679 * to execute a task than only once at the beginning of the script.
2681 * Before calling set_time_limit(), we check if this function is available
2682 * because it could be disabled by the server administrator. We also hide all
2683 * the errors that could occur when calling set_time_limit(), because it is
2684 * not possible to reliably ensure that PHP or a security extension will
2685 * not issue a warning/error if they prevent the use of this function.
2687 * @param $time_limit
2688 * An integer specifying the new time limit, in seconds. A value of 0
2689 * indicates unlimited execution time.
2691 * @ingroup php_wrappers
2693 function drupal_set_time_limit($time_limit) {
2694 if (function_exists('set_time_limit')) {
2695 @
set_time_limit($time_limit);
2700 * Returns the path to a system item (module, theme, etc.).
2703 * The type of the item (i.e. theme, theme_engine, module, profile).
2705 * The name of the item for which the path is requested.
2708 * The path to the requested item.
2710 function drupal_get_path($type, $name) {
2711 return dirname(drupal_get_filename($type, $name));
2715 * Returns the base URL path (i.e., directory) of the Drupal installation.
2717 * base_path() adds a "/" to the beginning and end of the returned path if the
2718 * path is not empty. At the very least, this will return "/".
2721 * - http://example.com returns "/" because the path is empty.
2722 * - http://example.com/drupal/folder returns "/drupal/folder/".
2724 function base_path() {
2725 return $GLOBALS['base_path'];
2729 * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
2731 * This function can be called as long the HTML header hasn't been sent, which
2732 * on normal pages is up through the preprocess step of theme('html'). Adding
2733 * a link will overwrite a prior link with the exact same 'rel' and 'href'
2736 * @param $attributes
2737 * Associative array of element attributes including 'href' and 'rel'.
2739 * Optional flag to determine if a HTTP 'Link:' header should be sent.
2741 function drupal_add_html_head_link($attributes, $header = FALSE
) {
2744 '#attributes' => $attributes,
2746 $href = $attributes['href'];
2749 // Also add a HTTP header "Link:".
2750 $href = '<' .
check_plain($attributes['href']) .
'>;';
2751 unset($attributes['href']);
2752 $element['#attached']['drupal_add_http_header'][] = array('Link', $href .
drupal_http_header_attributes($attributes), TRUE
);
2755 drupal_add_html_head($element, 'drupal_add_html_head_link:' .
$attributes['rel'] .
':' .
$href);
2759 * Adds a cascading stylesheet to the stylesheet queue.
2761 * Calling drupal_static_reset('drupal_add_css') will clear all cascading
2762 * stylesheets added so far.
2764 * If CSS aggregation/compression is enabled, all cascading style sheets added
2765 * with $options['preprocess'] set to TRUE will be merged into one aggregate
2766 * file and compressed by removing all extraneous white space.
2767 * Preprocessed inline stylesheets will not be aggregated into this single file;
2768 * instead, they are just compressed upon output on the page. Externally hosted
2769 * stylesheets are never aggregated or compressed.
2771 * The reason for aggregating the files is outlined quite thoroughly here:
2772 * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
2773 * to request overhead, one bigger file just loads faster than two smaller ones
2776 * $options['preprocess'] should be only set to TRUE when a file is required for
2777 * all typical visitors and most pages of a site. It is critical that all
2778 * preprocessed files are added unconditionally on every page, even if the
2779 * files do not happen to be needed on a page. This is normally done by calling
2780 * drupal_add_css() in a hook_init() implementation.
2782 * Non-preprocessed files should only be added to the page when they are
2786 * (optional) The stylesheet data to be added, depending on what is passed
2787 * through to the $options['type'] parameter:
2788 * - 'file': The path to the CSS file relative to the base_path(), or a
2789 * stream wrapper URI. For example: "modules/devel/devel.css" or
2790 * "public://generated_css/stylesheet_1.css". Note that Modules should
2791 * always prefix the names of their CSS files with the module name; for
2792 * example, system-menus.css rather than simply menus.css. Themes can
2793 * override module-supplied CSS files based on their filenames, and this
2794 * prefixing helps prevent confusing name collisions for theme developers.
2795 * See drupal_get_css() where the overrides are performed. Also, if the
2796 * direction of the current language is right-to-left (Hebrew, Arabic,
2797 * etc.), the function will also look for an RTL CSS file and append it to
2798 * the list. The name of this file should have an '-rtl.css' suffix. For
2799 * example, a CSS file called 'mymodule-name.css' will have a
2800 * 'mymodule-name-rtl.css' file added to the list, if exists in the same
2801 * directory. This CSS file should contain overrides for properties which
2802 * should be reversed or otherwise different in a right-to-left display.
2803 * - 'inline': A string of CSS that should be placed in the given scope. Note
2804 * that it is better practice to use 'file' stylesheets, rather than
2805 * 'inline', as the CSS would then be aggregated and cached.
2806 * - 'external': The absolute path to an external CSS file that is not hosted
2807 * on the local server. These files will not be aggregated if CSS
2808 * aggregation is enabled.
2810 * (optional) A string defining the 'type' of CSS that is being added in the
2811 * $data parameter ('file', 'inline', or 'external'), or an array which can
2812 * have any or all of the following keys:
2813 * - 'type': The type of stylesheet being added. Available options are 'file',
2814 * 'inline' or 'external'. Defaults to 'file'.
2815 * - 'basename': Force a basename for the file being added. Modules are
2816 * expected to use stylesheets with unique filenames, but integration of
2817 * external libraries may make this impossible. The basename of
2818 * 'modules/node/node.css' is 'node.css'. If the external library "node.js"
2819 * ships with a 'node.css', then a different, unique basename would be
2821 * - 'group': A number identifying the group in which to add the stylesheet.
2822 * Available constants are:
2823 * - CSS_SYSTEM: Any system-layer CSS.
2824 * - CSS_DEFAULT: Any module-layer CSS.
2825 * - CSS_THEME: Any theme-layer CSS.
2826 * The group number serves as a weight: the markup for loading a stylesheet
2827 * within a lower weight group is output to the page before the markup for
2828 * loading a stylesheet within a higher weight group, so CSS within higher
2829 * weight groups take precendence over CSS within lower weight groups.
2830 * - 'every_page': For optimal front-end performance when aggregation is
2831 * enabled, this should be set to TRUE if the stylesheet is present on every
2832 * page of the website for users for whom it is present at all. This
2833 * defaults to FALSE. It is set to TRUE for stylesheets added via module and
2834 * theme .info files. Modules that add stylesheets within hook_init()
2835 * implementations, or from other code that ensures that the stylesheet is
2836 * added to all website pages, should also set this flag to TRUE. All
2837 * stylesheets within the same group that have the 'every_page' flag set to
2838 * TRUE and do not have 'preprocess' set to FALSE are aggregated together
2839 * into a single aggregate file, and that aggregate file can be reused
2840 * across a user's entire site visit, leading to faster navigation between
2841 * pages. However, stylesheets that are only needed on pages less frequently
2842 * visited, can be added by code that only runs for those particular pages,
2843 * and that code should not set the 'every_page' flag. This minimizes the
2844 * size of the aggregate file that the user needs to download when first
2845 * visiting the website. Stylesheets without the 'every_page' flag are
2846 * aggregated into a separate aggregate file. This other aggregate file is
2847 * likely to change from page to page, and each new aggregate file needs to
2848 * be downloaded when first encountered, so it should be kept relatively
2849 * small by ensuring that most commonly needed stylesheets are added to
2851 * - 'weight': The weight of the stylesheet specifies the order in which the
2852 * CSS will appear relative to other stylesheets with the same group and
2853 * 'every_page' flag. The exact ordering of stylesheets is as follows:
2855 * - Then by the 'every_page' flag, with TRUE coming before FALSE.
2857 * - Then by the order in which the CSS was added. For example, all else
2858 * being the same, a stylesheet added by a call to drupal_add_css() that
2859 * happened later in the page request gets added to the page after one for
2860 * which drupal_add_css() happened earlier in the page request.
2861 * - 'media': The media type for the stylesheet, e.g., all, print, screen.
2862 * Defaults to 'all'.
2863 * - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the
2864 * styles will be aggregated and compressed. Defaults to TRUE.
2865 * - 'browsers': An array containing information specifying which browsers
2866 * should load the CSS item. See drupal_pre_render_conditional_comments()
2870 * An array of queued cascading stylesheets.
2872 * @see drupal_get_css()
2874 function drupal_add_css($data = NULL
, $options = NULL
) {
2875 $css = &drupal_static(__FUNCTION__
, array());
2877 // Construct the options, taking the defaults into consideration.
2878 if (isset($options)) {
2879 if (!is_array($options)) {
2880 $options = array('type' => $options);
2887 // Create an array of CSS files for each media type first, since each type needs to be served
2888 // to the browser differently.
2892 'group' => CSS_DEFAULT
,
2894 'every_page' => FALSE
,
2896 'preprocess' => TRUE
,
2898 'browsers' => array(),
2900 $options['browsers'] += array(
2905 // Files with a query string cannot be preprocessed.
2906 if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE
) {
2907 $options['preprocess'] = FALSE
;
2910 // Always add a tiny value to the weight, to conserve the insertion order.
2911 $options['weight'] += count($css) / 1000;
2913 // Add the data to the CSS array depending on the type.
2914 switch ($options['type']) {
2916 // For inline stylesheets, we don't want to use the $data as the array
2917 // key as $data could be a very long string of CSS.
2921 // Local and external files must keep their name as the associative key
2922 // so the same CSS file is not be added twice.
2923 $css[$data] = $options;
2931 * Returns a themed representation of all stylesheets to attach to the page.
2933 * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
2934 * This ensures proper cascading of styles so themes can easily override
2935 * module styles through CSS selectors.
2937 * Themes may replace module-defined CSS files by adding a stylesheet with the
2938 * same filename. For example, themes/bartik/system-menus.css would replace
2939 * modules/system/system-menus.css. This allows themes to override complete
2940 * CSS files, rather than specific selectors, when necessary.
2942 * If the original CSS file is being overridden by a theme, the theme is
2943 * responsible for supplying an accompanying RTL CSS file to replace the
2947 * (optional) An array of CSS files. If no array is provided, the default
2948 * stylesheets array is used instead.
2949 * @param $skip_alter
2950 * (optional) If set to TRUE, this function skips calling drupal_alter() on
2951 * $css, useful when the calling function passes a $css array that has already
2955 * A string of XHTML CSS tags.
2957 * @see drupal_add_css()
2959 function drupal_get_css($css = NULL
, $skip_alter = FALSE
) {
2961 $css = drupal_add_css();
2964 // Allow modules and themes to alter the CSS items.
2966 drupal_alter('css', $css);
2969 // Sort CSS items, so that they appear in the correct order.
2970 uasort($css, 'drupal_sort_css_js');
2972 // Remove the overridden CSS files. Later CSS files override former ones.
2973 $previous_item = array();
2974 foreach ($css as
$key => $item) {
2975 if ($item['type'] == 'file') {
2976 // If defined, force a unique basename for this file.
2977 $basename = isset($item['basename']) ?
$item['basename'] : drupal_basename($item['data']);
2978 if (isset($previous_item[$basename])) {
2979 // Remove the previous item that shared the same base name.
2980 unset($css[$previous_item[$basename]]);
2982 $previous_item[$basename] = $key;
2986 // Render the HTML needed to load the CSS.
2988 '#type' => 'styles',
2992 // Provide the page with information about the individual CSS files used,
2993 // information not otherwise available when CSS aggregation is enabled.
2994 $setting['ajaxPageState']['css'] = array_fill_keys(array_keys($css), 1);
2995 $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
2997 return drupal_render($styles);
3001 * Sorts CSS and JavaScript resources.
3003 * Callback for uasort() within:
3004 * - drupal_get_css()
3007 * This sort order helps optimize front-end performance while providing modules
3008 * and themes with the necessary control for ordering the CSS and JavaScript
3009 * appearing on a page.
3012 * First item for comparison. The compared items should be associative arrays
3013 * of member items from drupal_add_css() or drupal_add_js().
3015 * Second item for comparison.
3017 * @see drupal_add_css()
3018 * @see drupal_add_js()
3020 function drupal_sort_css_js($a, $b) {
3021 // First order by group, so that, for example, all items in the CSS_SYSTEM
3022 // group appear before items in the CSS_DEFAULT group, which appear before
3023 // all items in the CSS_THEME group. Modules may create additional groups by
3024 // defining their own constants.
3025 if ($a['group'] < $b['group']) {
3028 elseif ($a['group'] > $b['group']) {
3031 // Within a group, order all infrequently needed, page-specific files after
3032 // common files needed throughout the website. Separating this way allows for
3033 // the aggregate file generated for all of the common files to be reused
3034 // across a site visit without being cut by a page using a less common file.
3035 elseif ($a['every_page'] && !$b['every_page']) {
3038 elseif (!$a['every_page'] && $b['every_page']) {
3041 // Finally, order by weight.
3042 elseif ($a['weight'] < $b['weight']) {
3045 elseif ($a['weight'] > $b['weight']) {
3054 * Default callback to group CSS items.
3056 * This function arranges the CSS items that are in the #items property of the
3057 * styles element into groups. Arranging the CSS items into groups serves two
3058 * purposes. When aggregation is enabled, files within a group are aggregated
3059 * into a single file, significantly improving page loading performance by
3060 * minimizing network traffic overhead. When aggregation is disabled, grouping
3061 * allows multiple files to be loaded from a single STYLE tag, enabling sites
3062 * with many modules enabled or a complex theme being used to stay within IE's
3063 * 31 CSS inclusion tag limit: http://drupal.org/node/228818.
3065 * This function puts multiple items into the same group if they are groupable
3066 * and if they are for the same 'media' and 'browsers'. Items of the 'file' type
3067 * are groupable if their 'preprocess' flag is TRUE, items of the 'inline' type
3068 * are always groupable, and items of the 'external' type are never groupable.
3069 * This function also ensures that the process of grouping items does not change
3070 * their relative order. This requirement may result in multiple groups for the
3071 * same type, media, and browsers, if needed to accommodate other items in
3075 * An array of CSS items, as returned by drupal_add_css(), but after
3076 * alteration performed by drupal_get_css().
3079 * An array of CSS groups. Each group contains the same keys (e.g., 'media',
3080 * 'data', etc.) as a CSS item from the $css parameter, with the value of
3081 * each key applying to the group as a whole. Each group also contains an
3082 * 'items' key, which is the subset of items from $css that are in the group.
3084 * @see drupal_pre_render_styles()
3085 * @see system_element_info()
3087 function drupal_group_css($css) {
3089 // If a group can contain multiple items, we track the information that must
3090 // be the same for each item in the group, so that when we iterate the next
3091 // item, we can determine if it can be put into the current group, or if a
3092 // new group needs to be made for it.
3093 $current_group_keys = NULL
;
3094 // When creating a new group, we pre-increment $i, so by initializing it to
3095 // -1, the first group will have index 0.
3097 foreach ($css as
$item) {
3098 // The browsers for which the CSS item needs to be loaded is part of the
3099 // information that determines when a new group is needed, but the order of
3100 // keys in the array doesn't matter, and we don't want a new group if all
3101 // that's different is that order.
3102 ksort($item['browsers']);
3104 // If the item can be grouped with other items, set $group_keys to an array
3105 // of information that must be the same for all items in its group. If the
3106 // item can't be grouped with other items, set $group_keys to FALSE. We
3107 // put items into a group that can be aggregated together: whether they will
3108 // be aggregated is up to the _drupal_css_aggregate() function or an
3109 // override of that function specified in hook_css_alter(), but regardless
3110 // of the details of that function, a group represents items that can be
3111 // aggregated. Since a group may be rendered with a single HTML tag, all
3112 // items in the group must share the same information that would need to be
3113 // part of that HTML tag.
3114 switch ($item['type']) {
3116 // Group file items if their 'preprocess' flag is TRUE.
3117 // Help ensure maximum reuse of aggregate files by only grouping
3118 // together items that share the same 'group' value and 'every_page'
3119 // flag. See drupal_add_css() for details about that.
3120 $group_keys = $item['preprocess'] ?
array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE
;
3123 // Always group inline items.
3124 $group_keys = array($item['type'], $item['media'], $item['browsers']);
3127 // Do not group external items.
3128 $group_keys = FALSE
;
3132 // If the group keys don't match the most recent group we're working with,
3133 // then a new group must be made.
3134 if ($group_keys !== $current_group_keys) {
3136 // Initialize the new group with the same properties as the first item
3137 // being placed into it. The item's 'data' and 'weight' properties are
3138 // unique to the item and should not be carried over to the group.
3139 $groups[$i] = $item;
3140 unset($groups[$i]['data'], $groups[$i]['weight']);
3141 $groups[$i]['items'] = array();
3142 $current_group_keys = $group_keys ?
$group_keys : NULL
;
3145 // Add the item to the current group.
3146 $groups[$i]['items'][] = $item;
3152 * Default callback to aggregate CSS files and inline content.
3154 * Having the browser load fewer CSS files results in much faster page loads
3155 * than when it loads many small files. This function aggregates files within
3156 * the same group into a single file unless the site-wide setting to do so is
3157 * disabled (commonly the case during site development). To optimize download,
3158 * it also compresses the aggregate files by removing comments, whitespace, and
3159 * other unnecessary content. Additionally, this functions aggregates inline
3160 * content together, regardless of the site-wide aggregation setting.
3162 * @param $css_groups
3163 * An array of CSS groups as returned by drupal_group_css(). This function
3164 * modifies the group's 'data' property for each group that is aggregated.
3166 * @see drupal_group_css()
3167 * @see drupal_pre_render_styles()
3168 * @see system_element_info()
3170 function drupal_aggregate_css(&$css_groups) {
3171 $preprocess_css = (variable_get('preprocess_css', FALSE
) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE
!= 'update'));
3173 // For each group that needs aggregation, aggregate its items.
3174 foreach ($css_groups as
$key => $group) {
3175 switch ($group['type']) {
3176 // If a file group can be aggregated into a single file, do so, and set
3177 // the group's data property to the file path of the aggregate file.
3179 if ($group['preprocess'] && $preprocess_css) {
3180 $css_groups[$key]['data'] = drupal_build_css_cache($group['items']);
3183 // Aggregate all inline CSS content into the group's data property.
3185 $css_groups[$key]['data'] = '';
3186 foreach ($group['items'] as
$item) {
3187 $css_groups[$key]['data'] .
= drupal_load_stylesheet_content($item['data'], $item['preprocess']);
3195 * #pre_render callback to add the elements needed for CSS tags to be rendered.
3197 * For production websites, LINK tags are preferable to STYLE tags with @import
3198 * statements, because:
3199 * - They are the standard tag intended for linking to a resource.
3200 * - On Firefox 2 and perhaps other browsers, CSS files included with @import
3201 * statements don't get saved when saving the complete web page for offline
3202 * use: http://drupal.org/node/145218.
3203 * - On IE, if only LINK tags and no @import statements are used, all the CSS
3204 * files are downloaded in parallel, resulting in faster page load, but if
3205 * @import statements are used and span across multiple STYLE tags, all the
3206 * ones from one STYLE tag must be downloaded before downloading begins for
3207 * the next STYLE tag. Furthermore, IE7 does not support media declaration on
3208 * the @import statement, so multiple STYLE tags must be used when different
3209 * files are for different media types. Non-IE browsers always download in
3210 * parallel, so this is an IE-specific performance quirk:
3211 * http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
3213 * However, IE has an annoying limit of 31 total CSS inclusion tags
3214 * (http://drupal.org/node/228818) and LINK tags are limited to one file per
3215 * tag, whereas STYLE tags can contain multiple @import statements allowing
3216 * multiple files to be loaded per tag. When CSS aggregation is disabled, a
3217 * Drupal site can easily have more than 31 CSS files that need to be loaded, so
3218 * using LINK tags exclusively would result in a site that would display
3219 * incorrectly in IE. Depending on different needs, different strategies can be
3220 * employed to decide when to use LINK tags and when to use STYLE tags.
3222 * The strategy employed by this function is to use LINK tags for all aggregate
3223 * files and for all files that cannot be aggregated (e.g., if 'preprocess' is
3224 * set to FALSE or the type is 'external'), and to use STYLE tags for groups
3225 * of files that could be aggregated together but aren't (e.g., if the site-wide
3226 * aggregation setting is disabled). This results in all LINK tags when
3227 * aggregation is enabled, a guarantee that as many or only slightly more tags
3228 * are used with aggregation disabled than enabled (so that if the limit were to
3229 * be crossed with aggregation enabled, the site developer would also notice the
3230 * problem while aggregation is disabled), and an easy way for a developer to
3231 * view HTML source while aggregation is disabled and know what files will be
3232 * aggregated together when aggregation becomes enabled.
3234 * This function evaluates the aggregation enabled/disabled condition on a group
3235 * by group basis by testing whether an aggregate file has been made for the
3236 * group rather than by testing the site-wide aggregation setting. This allows
3237 * this function to work correctly even if modules have implemented custom
3238 * logic for grouping and aggregating files.
3241 * A render array containing:
3242 * - '#items': The CSS items as returned by drupal_add_css() and altered by
3244 * - '#group_callback': A function to call to group #items to enable the use
3245 * of fewer tags by aggregating files and/or using multiple @import
3246 * statements within a single tag.
3247 * - '#aggregate_callback': A function to call to aggregate the items within
3248 * the groups arranged by the #group_callback function.
3251 * A render array that will render to a string of XHTML CSS tags.
3253 * @see drupal_get_css()
3255 function drupal_pre_render_styles($elements) {
3256 // Group and aggregate the items.
3257 if (isset($elements['#group_callback'])) {
3258 $elements['#groups'] = $elements['#group_callback']($elements['#items']);
3260 if (isset($elements['#aggregate_callback'])) {
3261 $elements['#aggregate_callback']($elements['#groups']);
3264 // A dummy query-string is added to filenames, to gain control over
3265 // browser-caching. The string changes on every update or full cache
3266 // flush, forcing browsers to load a new copy of the files, as the
3268 $query_string = variable_get('css_js_query_string', '0');
3270 // For inline CSS to validate as XHTML, all CSS containing XHTML needs to be
3271 // wrapped in CDATA. To make that backwards compatible with HTML 4, we need to
3272 // comment out the CDATA-tag.
3273 $embed_prefix = "\n<!--/*--><![CDATA[/*><!--*/\n";
3274 $embed_suffix = "\n/*]]>*/-->\n";
3276 // Defaults for LINK and STYLE elements.
3277 $link_element_defaults = array(
3278 '#type' => 'html_tag',
3280 '#attributes' => array(
3281 'type' => 'text/css',
3282 'rel' => 'stylesheet',
3285 $style_element_defaults = array(
3286 '#type' => 'html_tag',
3288 '#attributes' => array(
3289 'type' => 'text/css',
3293 // Loop through each group.
3294 foreach ($elements['#groups'] as
$group) {
3295 switch ($group['type']) {
3296 // For file items, there are three possibilites.
3297 // - The group has been aggregated: in this case, output a LINK tag for
3298 // the aggregate file.
3299 // - The group can be aggregated but has not been (most likely because
3300 // the site administrator disabled the site-wide setting): in this case,
3301 // output as few STYLE tags for the group as possible, using @import
3302 // statement for each file in the group. This enables us to stay within
3303 // IE's limit of 31 total CSS inclusion tags.
3304 // - The group contains items not eligible for aggregation (their
3305 // 'preprocess' flag has been set to FALSE): in this case, output a LINK
3306 // tag for each file.
3308 // The group has been aggregated into a single file: output a LINK tag
3309 // for the aggregate file.
3310 if (isset($group['data'])) {
3311 $element = $link_element_defaults;
3312 $element['#attributes']['href'] = file_create_url($group['data']);
3313 $element['#attributes']['media'] = $group['media'];
3314 $element['#browsers'] = $group['browsers'];
3315 $elements[] = $element;
3317 // The group can be aggregated, but hasn't been: combine multiple items
3318 // into as few STYLE tags as possible.
3319 elseif ($group['preprocess']) {
3321 foreach ($group['items'] as
$item) {
3322 // A theme's .info file may have an entry for a file that doesn't
3323 // exist as a way of overriding a module or base theme CSS file from
3324 // being added to the page. Normally, file_exists() calls that need
3325 // to run for every page request should be minimized, but this one
3326 // is okay, because it only runs when CSS aggregation is disabled.
3327 // On a server under heavy enough load that file_exists() calls need
3328 // to be minimized, CSS aggregation should be enabled, in which case
3329 // this code is not run. When aggregation is enabled,
3330 // drupal_load_stylesheet() checks file_exists(), but only when
3331 // building the aggregate file, which is then reused for many page
3333 if (file_exists($item['data'])) {
3334 // The dummy query string needs to be added to the URL to control
3335 // browser-caching. IE7 does not support a media type on the
3336 // @import statement, so we instead specify the media for the
3337 // group on the STYLE tag.
3338 $import[] = '@import url("' .
check_plain(file_create_url($item['data']) .
'?' .
$query_string) .
'");';
3341 // In addition to IE's limit of 31 total CSS inclusion tags, it also
3342 // has a limit of 31 @import statements per STYLE tag.
3343 while (!empty($import)) {
3344 $import_batch = array_slice($import, 0, 31);
3345 $import = array_slice($import, 31);
3346 $element = $style_element_defaults;
3347 $element['#value'] = implode("\n", $import_batch);
3348 $element['#attributes']['media'] = $group['media'];
3349 $element['#browsers'] = $group['browsers'];
3350 $elements[] = $element;
3353 // The group contains items ineligible for aggregation: output a LINK
3354 // tag for each file.
3356 foreach ($group['items'] as
$item) {
3357 $element = $link_element_defaults;
3358 // We do not check file_exists() here, because this code runs for
3359 // files whose 'preprocess' is set to FALSE, and therefore, even
3360 // when aggregation is enabled, and we want to avoid needlessly
3361 // taxing a server that may be under heavy load. The file_exists()
3362 // performed above for files whose 'preprocess' is TRUE is done for
3363 // the benefit of theme .info files, but code that deals with files
3364 // whose 'preprocess' is FALSE is responsible for ensuring the file
3366 // The dummy query string needs to be added to the URL to control
3368 $query_string_separator = (strpos($item['data'], '?') !== FALSE
) ?
'&' : '?';
3369 $element['#attributes']['href'] = file_create_url($item['data']) .
$query_string_separator .
$query_string;
3370 $element['#attributes']['media'] = $item['media'];
3371 $element['#browsers'] = $group['browsers'];
3372 $elements[] = $element;
3376 // For inline content, the 'data' property contains the CSS content. If
3377 // the group's 'data' property is set, then output it in a single STYLE
3378 // tag. Otherwise, output a separate STYLE tag for each item.
3380 if (isset($group['data'])) {
3381 $element = $style_element_defaults;
3382 $element['#value'] = $group['data'];
3383 $element['#value_prefix'] = $embed_prefix;
3384 $element['#value_suffix'] = $embed_suffix;
3385 $element['#attributes']['media'] = $group['media'];
3386 $element['#browsers'] = $group['browsers'];
3387 $elements[] = $element;
3390 foreach ($group['items'] as
$item) {
3391 $element = $style_element_defaults;
3392 $element['#value'] = $item['data'];
3393 $element['#value_prefix'] = $embed_prefix;
3394 $element['#value_suffix'] = $embed_suffix;
3395 $element['#attributes']['media'] = $item['media'];
3396 $element['#browsers'] = $group['browsers'];
3397 $elements[] = $element;
3401 // Output a LINK tag for each external item. The item's 'data' property
3402 // contains the full URL.
3404 foreach ($group['items'] as
$item) {
3405 $element = $link_element_defaults;
3406 $element['#attributes']['href'] = $item['data'];
3407 $element['#attributes']['media'] = $item['media'];
3408 $element['#browsers'] = $group['browsers'];
3409 $elements[] = $element;
3419 * Aggregates and optimizes CSS files into a cache file in the files directory.
3421 * The file name for the CSS cache file is generated from the hash of the
3422 * aggregated contents of the files in $css. This forces proxies and browsers
3423 * to download new CSS when the CSS changes.
3425 * The cache file name is retrieved on a page load via a lookup variable that
3426 * contains an associative array. The array key is the hash of the file names
3427 * in $css while the value is the cache file name. The cache file is generated
3428 * in two cases. First, if there is no file name value for the key, which will
3429 * happen if a new file name has been added to $css or after the lookup
3430 * variable is emptied to force a rebuild of the cache. Second, the cache file
3431 * is generated if it is missing on disk. Old cache files are not deleted
3432 * immediately when the lookup variable is emptied, but are deleted after a set
3433 * period by drupal_delete_file_if_stale(). This ensures that files referenced
3434 * by a cached page will still be available.
3437 * An array of CSS files to aggregate and compress into one file.
3440 * The URI of the CSS cache file, or FALSE if the file could not be saved.
3442 function drupal_build_css_cache($css) {
3445 $map = variable_get('drupal_css_cache_files', array());
3446 $key = hash('sha256', serialize($css));
3447 if (isset($map[$key])) {
3451 if (empty($uri) || !file_exists($uri)) {
3452 // Build aggregate CSS file.
3453 foreach ($css as
$stylesheet) {
3454 // Only 'file' stylesheets can be aggregated.
3455 if ($stylesheet['type'] == 'file') {
3456 $contents = drupal_load_stylesheet($stylesheet['data'], TRUE
);
3458 // Build the base URL of this CSS file: start with the full URL.
3459 $css_base_url = file_create_url($stylesheet['data']);
3460 // Move to the parent.
3461 $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
3462 // Simplify to a relative URL if the stylesheet URL starts with the
3463 // base URL of the website.
3464 if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
3465 $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
3468 _drupal_build_css_path(NULL
, $css_base_url .
'/');
3469 // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
3470 $data .
= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
3474 // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
3475 // @import rules must proceed any other style, so we move those to the top.
3476 $regexp = '/@import[^;]+;/i';
3477 preg_match_all($regexp, $data, $matches);
3478 $data = preg_replace($regexp, '', $data);
3479 $data = implode('', $matches[0]) .
$data;
3481 // Prefix filename to prevent blocking by firewalls which reject files
3482 // starting with "ad*".
3483 $filename = 'css_' .
drupal_hash_base64($data) .
'.css';
3484 // Create the css/ within the files folder.
3485 $csspath = 'public://css';
3486 $uri = $csspath .
'/' .
$filename;
3487 // Create the CSS file.
3488 file_prepare_directory($csspath, FILE_CREATE_DIRECTORY
);
3489 if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE
)) {
3492 // If CSS gzip compression is enabled, clean URLs are enabled (which means
3493 // that rewrite rules are working) and the zlib extension is available then
3494 // create a gzipped version of this file. This file is served conditionally
3495 // to browsers that accept gzip using .htaccess rules.
3496 if (variable_get('css_gzip_compression', TRUE
) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
3497 if (!file_exists($uri .
'.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP
), $uri .
'.gz', FILE_EXISTS_REPLACE
)) {
3501 // Save the updated map.
3503 variable_set('drupal_css_cache_files', $map);