Issue #1402962 by xjm, marcingy, oriol_e9g, msonnabaum: Fixed Render cache shouldn...
[project/drupal.git] / includes / common.inc
CommitLineData
d852a999 1<?php
d852a999 2
5a667eb5 3/**
50d78e98 4 * @file
50d78e98
DB
5 * Common functions that many Drupal modules will need to reference.
6 *
7 * The functions that are critical and need to be available even when serving
8 * a cached page are instead located in bootstrap.inc.
9 */
10
11/**
67f2c101
DB
12 * @defgroup php_wrappers PHP wrapper functions
13 * @{
14 * Functions that are wrappers or custom implementations of PHP functions.
15 *
16 * Certain PHP functions should not be used in Drupal. Instead, Drupal's
17 * replacement functions should be used.
18 *
19 * For example, for improved or more secure UTF8-handling, or RFC-compliant
20 * handling of URLs in Drupal.
21 *
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
25 * functions.
26 *
27 * You should always use these wrapper functions in your code.
28 *
29 * Wrong:
30 * @code
31 * $my_substring = substr($original_string, 0, 5);
32 * @endcode
33 *
34 * Correct:
35 * @code
36 * $my_substring = drupal_substr($original_string, 0, 5);
37 * @endcode
38 *
437ba36d 39 * @}
67f2c101
DB
40 */
41
42/**
89ae34de
SW
43 * Return status for saving which involved creating a new item.
44 */
45define('SAVED_NEW', 1);
46
47/**
48 * Return status for saving which involved an update to an existing item.
49 */
50define('SAVED_UPDATED', 2);
51
52/**
53 * Return status for saving which deleted an existing item.
54 */
55define('SAVED_DELETED', 3);
56
57/**
facc5810 58 * The default group for system CSS files added to the page.
1a5c71e2
DB
59 */
60define('CSS_SYSTEM', -100);
61
62/**
facc5810 63 * The default group for module CSS files added to the page.
1a5c71e2
DB
64 */
65define('CSS_DEFAULT', 0);
66
67/**
facc5810 68 * The default group for theme CSS files added to the page.
1a5c71e2
DB
69 */
70define('CSS_THEME', 100);
71
72/**
767d72d4 73 * The default group for JavaScript and jQuery libraries added to the page.
0762f600
AB
74 */
75define('JS_LIBRARY', -100);
76
77/**
facc5810 78 * The default group for module JavaScript code added to the page.
0762f600
AB
79 */
80define('JS_DEFAULT', 0);
81
82/**
facc5810 83 * The default group for theme JavaScript code added to the page.
0762f600
AB
84 */
85define('JS_THEME', 100);
86
87/**
767d72d4 88 * Error code indicating that the request exceeded the specified timeout.
89 *
90 * @see drupal_http_request()
36e3d552 91 */
a3db9f3f 92define('HTTP_REQUEST_TIMEOUT', -1);
36e3d552
DB
93
94/**
b4786ff0
DB
95 * Constants defining cache granularity for blocks and renderable arrays.
96 *
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
102 * implement
103 *
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.
108 *
109 * Note that user 1 is excluded from block caching.
110 */
111
112/**
767d72d4 113 * The block should not get cached.
114 *
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
118 * content.
119 * - For blocks that change too frequently.
b4786ff0
DB
120 */
121define('DRUPAL_NO_CACHE', -1);
122
123/**
767d72d4 124 * The block is handling its own caching in its hook_block_view().
125 *
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.
b4786ff0
DB
129 */
130define('DRUPAL_CACHE_CUSTOM', -2);
131
132/**
767d72d4 133 * The block or element can change depending on the user's roles.
134 *
135 * This is the default setting for blocks, used when the block does not specify
136 * anything.
b4786ff0
DB
137 */
138define('DRUPAL_CACHE_PER_ROLE', 0x0001);
139
140/**
767d72d4 141 * The block or element can change depending on the user.
142 *
b4786ff0
DB
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.
145 */
146define('DRUPAL_CACHE_PER_USER', 0x0002);
147
148/**
149 * The block or element can change depending on the page being viewed.
150 */
151define('DRUPAL_CACHE_PER_PAGE', 0x0004);
152
153/**
767d72d4 154 * The block or element is the same for every user and page that it is visible.
b4786ff0
DB
155 */
156define('DRUPAL_CACHE_GLOBAL', 0x0008);
157
158/**
767d72d4 159 * Adds content to a specified region.
26fa7c73
DB
160 *
161 * @param $region
02c85927 162 * Page region the content is added to.
26fa7c73 163 * @param $data
02c85927 164 * Content to be added.
26fa7c73 165 */
02c85927 166function drupal_add_region_content($region = NULL, $data = NULL) {
26fa7c73
DB
167 static $content = array();
168
0274491d 169 if (isset($region) && isset($data)) {
26fa7c73
DB
170 $content[$region][] = $data;
171 }
172 return $content;
173}
174
175/**
767d72d4 176 * Gets assigned content for a given region.
26fa7c73
DB
177 *
178 * @param $region
2fcaa6a9
H
179 * A specified region to fetch content for. If NULL, all regions will be
180 * returned.
26fa7c73 181 * @param $delimiter
8c852108 182 * Content to be inserted between imploded array elements.
26fa7c73 183 */
02c85927
DB
184function drupal_get_region_content($region = NULL, $delimiter = ' ') {
185 $content = drupal_add_region_content();
7f01d4f0
DB
186 if (isset($region)) {
187 if (isset($content[$region]) && is_array($content[$region])) {
db427455 188 return implode($delimiter, $content[$region]);
7f01d4f0 189 }
26fa7c73
DB
190 }
191 else {
192 foreach (array_keys($content) as $region) {
193 if (is_array($content[$region])) {
db427455 194 $content[$region] = implode($delimiter, $content[$region]);
26fa7c73
DB
195 }
196 }
197 return $content;
198 }
199}
200
201/**
767d72d4 202 * Gets the name of the currently active install profile.
716293e0
AB
203 *
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
767d72d4 207 * table contains the name of the current profile, and we can call
208 * variable_get() to determine what one is active.
716293e0
AB
209 *
210 * @return $profile
211 * The name of the install profile.
212 */
213function drupal_get_profile() {
214 global $install_state;
215
216 if (isset($install_state['parameters']['profile'])) {
217 $profile = $install_state['parameters']['profile'];
218 }
219 else {
bb293ccf 220 $profile = variable_get('install_profile', 'standard');
716293e0
AB
221 }
222
223 return $profile;
224}
225
226
227/**
767d72d4 228 * Sets the breadcrumb trail for the current page.
3904790e 229 *
b84b6e42
DB
230 * @param $breadcrumb
231 * Array of links, starting with "home" and proceeding up to but not including
232 * the current page.
0d7e2050 233 */
26735ac5 234function drupal_set_breadcrumb($breadcrumb = NULL) {
3876e6ac 235 $stored_breadcrumb = &drupal_static(__FUNCTION__);
26735ac5 236
c9de4646 237 if (isset($breadcrumb)) {
26735ac5
DB
238 $stored_breadcrumb = $breadcrumb;
239 }
240 return $stored_breadcrumb;
241}
242
b84b6e42 243/**
767d72d4 244 * Gets the breadcrumb trail for the current page.
b84b6e42 245 */
26735ac5
DB
246function drupal_get_breadcrumb() {
247 $breadcrumb = drupal_set_breadcrumb();
248
c9de4646 249 if (!isset($breadcrumb)) {
26735ac5 250 $breadcrumb = menu_get_active_breadcrumb();
26735ac5
DB
251 }
252
253 return $breadcrumb;
254}
26735ac5
DB
255
256/**
cca6d06c 257 * Returns a string containing RDF namespace declarations for use in XML and
509e397b 258 * XHTML output.
151ed277
DB
259 */
260function drupal_get_rdf_namespaces() {
151ed277 261 $xml_rdf_namespaces = array();
cca6d06c
AB
262
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 . '"';
267 }
151ed277 268 }
6826f864 269 return count($xml_rdf_namespaces) ? "\n " . implode("\n ", $xml_rdf_namespaces) : '';
151ed277
DB
270}
271
272/**
767d72d4 273 * Adds output to the HEAD tag of the HTML page.
b274bf87 274 *
0d8515de
AB
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.
277 *
278 * @param $data
279 * A renderable array. If the '#type' key is not set then 'html_tag' will be
280 * added as the default '#type'.
281 * @param $key
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.
284 *
285 * @return
286 * An array of all stored HEAD elements.
287 *
288 * @see theme_html_tag()
6fc2070a 289 */
0d8515de
AB
290function drupal_add_html_head($data = NULL, $key = NULL) {
291 $stored_head = &drupal_static(__FUNCTION__);
6fc2070a 292
0d8515de
AB
293 if (!isset($stored_head)) {
294 // Make sure the defaults, including Content-Type, come first.
295 $stored_head = _drupal_default_html_head();
296 }
297
298 if (isset($data) && isset($key)) {
299 if (!isset($data['#type'])) {
300 $data['#type'] = 'html_tag';
301 }
302 $stored_head[$key] = $data;
6fc2070a
DB
303 }
304 return $stored_head;
305}
306
b84b6e42 307/**
0d8515de
AB
308 * Returns elements that are always displayed in the HEAD tag of the HTML page.
309 */
310function _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',
316 '#tag' => 'meta',
317 '#attributes' => array(
318 'http-equiv' => 'Content-Type',
319 'content' => 'text/html; charset=utf-8',
320 ),
321 // Security: This always has to be output first.
322 '#weight' => -1000,
323 );
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',
329 '#tag' => 'meta',
330 '#attributes' => array(
331 'name' => 'Generator',
332 'content' => 'Drupal ' . $version . ' (http://drupal.org)',
333 ),
334 );
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']);
337 return $elements;
338}
339
340/**
767d72d4 341 * Retrieves output to be displayed in the HEAD tag of the HTML page.
b84b6e42 342 */
6fc2070a 343function drupal_get_html_head() {
0d8515de
AB
344 $elements = drupal_add_html_head();
345 drupal_alter('html_head', $elements);
346 return drupal_render($elements);
6fc2070a 347}
6fc2070a
DB
348
349/**
767d72d4 350 * Adds a feed URL for the current page.
a58d8771 351 *
a1674271
DB
352 * This function can be called as long the HTML header hasn't been sent.
353 *
a58d8771 354 * @param $url
14779b97 355 * An internal system path or a fully qualified external URL of the feed.
2d059380 356 * @param $title
2fcaa6a9 357 * The title of the feed.
a58d8771 358 */
2d059380 359function drupal_add_feed($url = NULL, $title = '') {
3876e6ac 360 $stored_feed_links = &drupal_static(__FUNCTION__, array());
a58d8771 361
0d8515de 362 if (isset($url)) {
c05f2181 363 $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
2d059380 364
14779b97
AB
365 drupal_add_html_head_link(array(
366 'rel' => 'alternate',
367 'type' => 'application/rss+xml',
368 'title' => $title,
369 // Force the URL to be absolute, for consistency with other <link> tags
370 // output by Drupal.
371 'href' => url($url, array('absolute' => TRUE)),
372 ));
a58d8771
ND
373 }
374 return $stored_feed_links;
375}
376
377/**
767d72d4 378 * Gets the feed URLs for the current page.
a58d8771
ND
379 *
380 * @param $delimiter
2fcaa6a9 381 * A delimiter to split feeds by.
a58d8771
ND
382 */
383function drupal_get_feeds($delimiter = "\n") {
384 $feeds = drupal_add_feed();
385 return implode($feeds, $delimiter);
386}
387
6fc2070a 388/**
44c2bfdc 389 * @defgroup http_handling HTTP handling
ebba90fe 390 * @{
b84b6e42 391 * Functions to properly handle HTTP responses.
ebba90fe
DB
392 */
393
394/**
767d72d4 395 * Processes a URL query parameter array to remove unwanted elements.
d6a164c4
GK
396 *
397 * @param $query
598e7392 398 * (optional) An array to be processed. Defaults to $_GET.
d6a164c4 399 * @param $exclude
598e7392
DB
400 * (optional) A list of $query array keys to remove. Use "parent[child]" to
401 * exclude nested items. Defaults to array('q').
d6a164c4 402 * @param $parent
598e7392
DB
403 * Internal use only. Used to build the $query array key for nested items.
404 *
d6a164c4 405 * @return
598e7392 406 * An array containing query parameters, which can be used for url().
d6a164c4 407 */
598e7392
DB
408function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') {
409 // Set defaults, if none given.
410 if (!isset($query)) {
411 $query = $_GET;
412 }
413 // If $exclude is empty, there is nothing to filter.
414 if (empty($exclude)) {
415 return $query;
416 }
417 elseif (!$parent) {
418 $exclude = array_flip($exclude);
419 }
d6a164c4 420
598e7392 421 $params = array();
d6a164c4 422 foreach ($query as $key => $value) {
598e7392
DB
423 $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
424 if (isset($exclude[$string_key])) {
425 continue;
d6a164c4
GK
426 }
427
598e7392
DB
428 if (is_array($value)) {
429 $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
430 }
431 else {
432 $params[$key] = $value;
d6a164c4 433 }
598e7392
DB
434 }
435
436 return $params;
437}
438
439/**
767d72d4 440 * Splits a URL-encoded query string into an array.
1b9cde9d
AB
441 *
442 * @param $query
443 * The query string to split.
444 *
445 * @return
446 * An array of url decoded couples $param_name => $value.
447 */
448function drupal_get_query_array($query) {
449 $result = array();
450 if (!empty($query)) {
451 foreach (explode('&', $query) as $param) {
452 $param = explode('=', $param);
453 $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
454 }
455 }
456 return $result;
457}
458
459/**
767d72d4 460 * Parses an array into a valid, rawurlencoded query string.
598e7392
DB
461 *
462 * This differs from http_build_query() as we need to rawurlencode() (instead of
463 * urlencode()) all query parameters.
464 *
465 * @param $query
466 * The query parameter array to be processed, e.g. $_GET.
467 * @param $parent
468 * Internal use only. Used to build the $query array key for nested items.
469 *
470 * @return
471 * A rawurlencoded string which can be used as or appended to the URL query
472 * string.
473 *
474 * @see drupal_get_query_parameters()
475 * @ingroup php_wrappers
476 */
477function drupal_http_build_query(array $query, $parent = '') {
478 $params = array();
479
480 foreach ($query as $key => $value) {
481 $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key));
d6a164c4 482
598e7392 483 // Recurse into children.
d6a164c4 484 if (is_array($value)) {
598e7392
DB
485 $params[] = drupal_http_build_query($value, $key);
486 }
487 // If a query parameter value is NULL, only append its key.
488 elseif (!isset($value)) {
489 $params[] = $key;
d6a164c4
GK
490 }
491 else {
598e7392 492 // For better readability of paths in query strings, we decode slashes.
598e7392 493 $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
d6a164c4
GK
494 }
495 }
496
497 return implode('&', $params);
498}
499
500/**
767d72d4 501 * Prepares a 'destination' URL query parameter for use with drupal_goto().
b274bf87 502 *
2fcaa6a9
H
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.
fbec279e
DB
507 *
508 * @see drupal_goto()
509 */
510function drupal_get_destination() {
598e7392
DB
511 $destination = &drupal_static(__FUNCTION__);
512
513 if (isset($destination)) {
514 return $destination;
515 }
516
ee8aa910 517 if (isset($_GET['destination'])) {
598e7392 518 $destination = array('destination' => $_GET['destination']);
31387c5a
DB
519 }
520 else {
598e7392
DB
521 $path = $_GET['q'];
522 $query = drupal_http_build_query(drupal_get_query_parameters());
d6a164c4 523 if ($query != '') {
56d2664a 524 $path .= '?' . $query;
68432ae6 525 }
598e7392
DB
526 $destination = array('destination' => $path);
527 }
528 return $destination;
529}
530
531/**
767d72d4 532 * Parses a system URL string into an associative array suitable for url().
64a1a0d6
AB
533 *
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.
598e7392
DB
537 *
538 * The returned array contains a 'path' that may be passed separately to url().
539 * For example:
540 * @code
541 * $options = drupal_parse_url($_GET['destination']);
542 * $my_url = url($options['path'], $options);
543 * $my_link = l('Example link', $options['path'], $options);
544 * @endcode
545 *
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'].
550 *
551 * @param $url
552 * The URL string to parse, f.e. $_GET['destination'].
553 *
554 * @return
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.
560 *
561 * @see url()
562 * @see drupal_goto()
563 * @ingroup php_wrappers
564 */
565function drupal_parse_url($url) {
566 $options = array(
567 'path' => NULL,
568 'query' => array(),
569 'fragment' => '',
570 );
571
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];
585 }
586 }
587 }
588 // Internal URLs.
589 else {
087a54a6
DB
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);
598e7392
DB
595 if (isset($parts['query'])) {
596 parse_str($parts['query'], $options['query']);
597 }
598 if (isset($parts['fragment'])) {
599 $options['fragment'] = $parts['fragment'];
600 }
601 }
64a1a0d6
AB
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']);
608 }
598e7392
DB
609
610 return $options;
611}
612
613/**
1df3cfff 614 * Encodes a Drupal path for use in a URL.
598e7392 615 *
1df3cfff 616 * For aesthetic reasons slashes are not escaped.
598e7392 617 *
1df3cfff
DB
618 * Note that url() takes care of calling this function, so a path passed to that
619 * function should not be encoded in advance.
598e7392
DB
620 *
621 * @param $path
1df3cfff 622 * The Drupal path to encode.
598e7392
DB
623 */
624function drupal_encode_path($path) {
1df3cfff 625 return str_replace('%2F', '/', rawurlencode($path));
fbec279e
DB
626}
627
628/**
767d72d4 629 * Sends the user to a different Drupal page.
0d7e2050 630 *
323d9fe0
DB
631 * This issues an on-site HTTP redirect. The function makes sure the redirected
632 * URL is formatted correctly.
0d7e2050 633 *
fbec279e 634 * Usually the redirected URL is constructed from this function's input
9cff02e9 635 * parameters. However you may override that behavior by setting a
ca144061 636 * destination in either the $_REQUEST-array (i.e. by using
fee4182c 637 * the query string of an URI) This is used to direct the user back to
9cff02e9 638 * the proper page after completing a form. For example, after editing
926606ee 639 * a post on the 'admin/content'-page or after having logged on using the
9cff02e9 640 * 'user login'-block in a sidebar. The function drupal_get_destination()
fbec279e
DB
641 * can be used to help set the destination URL.
642 *
a3f3cb34
H
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.
323d9fe0 645 *
e1642603
AB
646 * This function ends the request; use it instead of a return in your menu
647 * callback.
323d9fe0
DB
648 *
649 * @param $path
1fab8db4 650 * A Drupal path or a full URL.
e1642603
AB
651 * @param $options
652 * An associative array of additional URL options to pass to url().
1c7f089d
SW
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
657 * engines)
658 * - 303 See Other
659 * - 304 Not Modified
660 * - 305 Use Proxy
2fcaa6a9 661 * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
1c7f089d 662 * Note: Other values are defined by RFC 2616, but are rarely used and poorly
2fcaa6a9 663 * supported.
e1642603 664 *
fbec279e 665 * @see drupal_get_destination()
e1642603 666 * @see url()
0d7e2050 667 */
e1642603
AB
668function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
669 // A destination in $_GET always overrides the function arguments.
8a27a7dd
AB
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'])) {
7d2d610f 672 $destination = drupal_parse_url($_GET['destination']);
e1642603
AB
673 $path = $destination['path'];
674 $options['query'] = $destination['query'];
675 $options['fragment'] = $destination['fragment'];
fbec279e 676 }
fbec279e 677
e1642603
AB
678 drupal_alter('drupal_goto', $path, $options, $http_response_code);
679
680 // The 'Location' HTTP header must be absolute.
681 $options['absolute'] = TRUE;
6586b764 682
e1642603 683 $url = url($path, $options);
0d7e2050 684
56d2664a 685 header('Location: ' . $url, TRUE, $http_response_code);
eaf4dd39
DB
686
687 // The "Location" header sends a redirect status code to the HTTP daemon. In
2fcaa6a9
H
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.
24c259cd 690 drupal_exit($url);
0d7e2050
KM
691}
692
709b9005 693/**
767d72d4 694 * Delivers a "site is under maintenance" message to the browser.
f42bca3b
DB
695 *
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().
709b9005
DB
700 */
701function drupal_site_offline() {
f42bca3b 702 drupal_deliver_page(MENU_SITE_OFFLINE);
709b9005
DB
703}
704
0d7e2050 705/**
767d72d4 706 * Delivers a "page not found" error to the browser.
f42bca3b
DB
707 *
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().
0d7e2050 712 */
670a2922 713function drupal_not_found() {
f42bca3b 714 drupal_deliver_page(MENU_NOT_FOUND);
670a2922 715}
94f6e94f
DB
716
717/**
767d72d4 718 * Delivers an "access denied" error to the browser.
f42bca3b
DB
719 *
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
767d72d4 723 * bubble up to menu_execute_active_handler() should call
724 * drupal_access_denied().
7231c88a
DB
725 */
726function drupal_access_denied() {
f42bca3b 727 drupal_deliver_page(MENU_ACCESS_DENIED);
7231c88a
DB
728}
729
730/**
767d72d4 731 * Performs an HTTP request.
94f6e94f 732 *
445823f6
DB
733 * This is a flexible and powerful HTTP client implementation. Correctly
734 * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
50d78e98
DB
735 *
736 * @param $url
737 * A string containing a fully qualified URI.
c705279a
DB
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&param=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().
7ec15ec2 750 *
c705279a
DB
751 * @return object
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
758 * received.
759 * - redirect_code: If redirected, an integer containing the initial response
760 * status code.
d8d8589b 761 * - redirect_url: If redirected, a string containing the URL of the redirect
762 * target.
c705279a
DB
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.
94f6e94f 768 */
445823f6 769function drupal_http_request($url, array $options = array()) {
365439ff 770 $result = new stdClass();
7988f875 771
8e38d459
AB
772 // Parse the URL and make sure we can handle the schema.
773 $uri = @parse_url($url);
774
775 if ($uri == FALSE) {
ab4e39da 776 $result->error = 'unable to parse URL';
f5d1094b 777 $result->code = -1001;
89fffd24
DB
778 return $result;
779 }
780
8e38d459
AB
781 if (!isset($uri['scheme'])) {
782 $result->error = 'missing schema';
783 $result->code = -1002;
784 return $result;
785 }
89fffd24 786
36e3d552
DB
787 timer_start(__FUNCTION__);
788
789 // Merge the default options.
790 $options += array(
791 'headers' => array(),
792 'method' => 'GET',
793 'data' => NULL,
794 'max_redirects' => 3,
30ecab37
DB
795 'timeout' => 30.0,
796 'context' => NULL,
36e3d552 797 );
30ecab37
DB
798 // stream_socket_client() requires timeout to be a float.
799 $options['timeout'] = (float) $options['timeout'];
36e3d552 800
94f6e94f
DB
801 switch ($uri['scheme']) {
802 case 'http':
f88676a5 803 case 'feed':
b9f2b7e2 804 $port = isset($uri['port']) ? $uri['port'] : 80;
30ecab37
DB
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 : '');
94f6e94f
DB
810 break;
811 case 'https':
af0463c6 812 // Note: Only works when PHP is compiled with OpenSSL support.
b9f2b7e2 813 $port = isset($uri['port']) ? $uri['port'] : 443;
30ecab37
DB
814 $socket = 'ssl://' . $uri['host'] . ':' . $port;
815 $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
94f6e94f
DB
816 break;
817 default:
56d2664a 818 $result->error = 'invalid schema ' . $uri['scheme'];
f5d1094b 819 $result->code = -1003;
94f6e94f
DB
820 return $result;
821 }
822
30ecab37
DB
823 if (empty($options['context'])) {
824 $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
825 }
826 else {
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']);
829 }
830
50d78e98 831 // Make sure the socket opened properly.
94f6e94f 832 if (!$fp) {
e6a4b82e
H
833 // When a network error occurs, we use a negative number so it does not
834 // clash with the HTTP status codes.
318ac983 835 $result->code = -$errno;
30ecab37 836 $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
534287b1
DB
837
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.
ec0f197e 841 // See system_requirements().
534287b1
DB
842 variable_set('drupal_http_request_fails', TRUE);
843
94f6e94f
DB
844 return $result;
845 }
846
50d78e98 847 // Construct the path to act on.
b9f2b7e2
DB
848 $path = isset($uri['path']) ? $uri['path'] : '/';
849 if (isset($uri['query'])) {
56d2664a 850 $path .= '?' . $uri['query'];
94f6e94f
DB
851 }
852
445823f6
DB
853 // Merge the default headers.
854 $options['headers'] += array(
445823f6 855 'User-Agent' => 'Drupal (+http://drupal.org/)',
94f6e94f
DB
856 );
857
1eb38eed
DB
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.
1a089351
DB
862 $content_length = strlen($options['data']);
863 if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
864 $options['headers']['Content-Length'] = $content_length;
1eb38eed
DB
865 }
866
867 // If the server URL has a user then attempt to use basic authentication.
ba3c558f 868 if (isset($uri['user'])) {
04bcd011 869 $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ''));
ba3c558f
H
870 }
871
6aea1d08
DB
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.
267ebfb7
DB
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']);
6aea1d08
DB
881 }
882
ada73058 883 $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
445823f6 884 foreach ($options['headers'] as $name => $value) {
ada73058 885 $request .= $name . ': ' . trim($value) . "\r\n";
94f6e94f 886 }
ada73058 887 $request .= "\r\n" . $options['data'];
94f6e94f 888 $result->request = $request;
30ecab37
DB
889 // Calculate how much time is left of the original timeout value.
890 $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
891 if ($timeout > 0) {
892 stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
893 fwrite($fp, $request);
894 }
94f6e94f 895
78276cc9
DB
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'];
b3265bcb 901 $response = '';
78276cc9
DB
902
903 while ($alive) {
36e3d552
DB
904 // Calculate how much time is left of the original timeout value.
905 $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
906 if ($timeout <= 0) {
78276cc9
DB
907 $info['timed_out'] = TRUE;
908 break;
36e3d552
DB
909 }
910 stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
78276cc9
DB
911 $chunk = fread($fp, 1024);
912 $response .= $chunk;
913 $info = stream_get_meta_data($fp);
914 $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
94f6e94f
DB
915 }
916 fclose($fp);
917
78276cc9
DB
918 if ($info['timed_out']) {
919 $result->code = HTTP_REQUEST_TIMEOUT;
920 $result->error = 'request timed out';
921 return $result;
922 }
445823f6 923 // Parse response headers from the response body.
9f9e9c38
DB
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);
445823f6 927 $response = preg_split("/\r\n|\n|\r/", $response);
77ec01f5 928
445823f6 929 // Parse the response status line.
e9946015
DB
930 list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
931 $result->protocol = $protocol;
932 $result->status_message = $status_message;
933
94f6e94f 934 $result->headers = array();
94f6e94f 935
445823f6
DB
936 // Parse the response headers.
937 while ($line = trim(array_shift($response))) {
34792fa8
DB
938 list($name, $value) = explode(':', $line, 2);
939 $name = strtolower($name);
940 if (isset($result->headers[$name]) && $name == 'set-cookie') {
7d9f29ac
DB
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.
34792fa8 943 $result->headers[$name] .= ',' . trim($value);
7d9f29ac
DB
944 }
945 else {
34792fa8 946 $result->headers[$name] = trim($value);
7d9f29ac 947 }
94f6e94f
DB
948 }
949
94f6e94f 950 $responses = array(
445823f6
DB
951 100 => 'Continue',
952 101 => 'Switching Protocols',
953 200 => 'OK',
954 201 => 'Created',
955 202 => 'Accepted',
956 203 => 'Non-Authoritative Information',
957 204 => 'No Content',
958 205 => 'Reset Content',
959 206 => 'Partial Content',
960 300 => 'Multiple Choices',
961 301 => 'Moved Permanently',
962 302 => 'Found',
963 303 => 'See Other',
964 304 => 'Not Modified',
965 305 => 'Use Proxy',
966 307 => 'Temporary Redirect',
967 400 => 'Bad Request',
968 401 => 'Unauthorized',
969 402 => 'Payment Required',
970 403 => 'Forbidden',
971 404 => 'Not Found',
972 405 => 'Method Not Allowed',
973 406 => 'Not Acceptable',
974 407 => 'Proxy Authentication Required',
975 408 => 'Request Time-out',
976 409 => 'Conflict',
977 410 => 'Gone',
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',
94f6e94f 991 );
2fcaa6a9
H
992 // RFC 2616 states that all unknown HTTP codes must be treated the same as the
993 // base code in their class.
94f6e94f
DB
994 if (!isset($responses[$code])) {
995 $code = floor($code / 100) * 100;
996 }
445823f6 997 $result->code = $code;
94f6e94f
DB
998
999 switch ($code) {
1000 case 200: // OK
1001 case 304: // Not modified
1002 break;
1003 case 301: // Moved permanently
1004 case 302: // Moved temporarily
1005 case 307: // Moved temporarily
34792fa8 1006 $location = $result->headers['location'];
36e3d552
DB
1007 $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
1008 if ($options['timeout'] <= 0) {
1009 $result->code = HTTP_REQUEST_TIMEOUT;
1010 $result->error = 'request timed out';
1011 }
1012 elseif ($options['max_redirects']) {
445823f6
DB
1013 // Redirect to the new location.
1014 $options['max_redirects']--;
1015 $result = drupal_http_request($location, $options);
73356fde 1016 $result->redirect_code = $code;
94f6e94f 1017 }
d8d8589b 1018 if (!isset($result->redirect_url)) {
1019 $result->redirect_url = $location;
1020 }
94f6e94f
DB
1021 break;
1022 default:
e9946015 1023 $result->error = $status_message;
94f6e94f
DB
1024 }
1025
94f6e94f
DB
1026 return $result;
1027}
b84b6e42
DB
1028/**
1029 * @} End of "HTTP handling".
1030 */
670a2922 1031
767d72d4 1032/**
1033 * Strips slashes from a string or array of strings.
1034 *
1035 * Callback for array_walk() within fix_gpx_magic().
1036 *
1037 * @param $item
1038 * An individual string or array of strings from superglobals.
1039 */
6957d786 1040function _fix_gpc_magic(&$item) {
009b1afe 1041 if (is_array($item)) {
81565fda
KM
1042 array_walk($item, '_fix_gpc_magic');
1043 }
1044 else {
c96a130e 1045 $item = stripslashes($item);
26e0b9b7
DB
1046 }
1047}
1048
50d78e98 1049/**
767d72d4 1050 * Strips slashes from $_FILES items.
1d95dcbf 1051 *
767d72d4 1052 * Callback for array_walk() within fix_gpc_magic().
1053 *
1054 * The tmp_name key is skipped keys since PHP generates single backslashes for
1055 * file paths on Windows systems.
1056 *
1057 * @param $item
1058 * An item from $_FILES.
1059 * @param $key
1060 * The key for the item within $_FILES.
1061 *
1062 * @see http://php.net/manual/en/features.file-upload.php#42280
1d95dcbf
ND
1063 */
1064function _fix_gpc_magic_files(&$item, $key) {
1065 if ($key != 'tmp_name') {
1066 if (is_array($item)) {
1067 array_walk($item, '_fix_gpc_magic_files');
1068 }
1069 else {
1070 $item = stripslashes($item);
1071 }
1072 }
1073}
1074
1075/**
767d72d4 1076 * Fixes double-escaping caused by "magic quotes" in some PHP installations.
1077 *
1078 * @see _fix_gpc_magic()
1079 * @see _fix_gpc_magic_files()
50d78e98 1080 */
ec332667 1081function fix_gpc_magic() {
570dcc57 1082 static $fixed = FALSE;
50d78e98 1083 if (!$fixed && ini_get('magic_quotes_gpc')) {
009b1afe
DB
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');
1d95dcbf 1088 array_walk($_FILES, '_fix_gpc_magic_files');
009b1afe 1089 }
570dcc57 1090 $fixed = TRUE;
ec332667
DB
1091}
1092
0d7e2050 1093/**
b84b6e42 1094 * @defgroup validation Input validation
ebba90fe 1095 * @{
b84b6e42 1096 * Functions to validate user input.
0d7e2050
KM
1097 */
1098
1099/**
767d72d4 1100 * Verifies the syntax of the given e-mail address.
50d78e98
DB
1101 *
1102 * Empty e-mail addresses are allowed. See RFC 2822 for details.
d1be768b 1103 *
50d78e98 1104 * @param $mail
11b9259d 1105 * A string containing an e-mail address.
767d72d4 1106 *
94f6e94f 1107 * @return
50d78e98 1108 * TRUE if the address is in a valid format.
d1be768b 1109 */
02f437a2 1110function valid_email_address($mail) {
ae31a4ab 1111 return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
d1be768b
DB
1112}
1113
1114/**
767d72d4 1115 * Verifies the syntax of the given URL.
8759ca73 1116 *
b3dae716
DB
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.
18ad0f69 1119 * Valid values per RFC 3986.
be230467 1120 * @param $url
50d78e98 1121 * The URL to verify.
be230467 1122 * @param $absolute
b84b6e42 1123 * Whether the URL is absolute (beginning with a scheme such as "http:").
767d72d4 1124 *
be230467 1125 * @return
50d78e98 1126 * TRUE if the URL is in a valid format.
8759ca73 1127 */
be230467 1128function valid_url($url, $absolute = FALSE) {
93cf70d7 1129 if ($absolute) {
18ad0f69 1130 return (bool)preg_match("
388fe5b6 1131 /^ # Start at the beginning of the text
f88676a5 1132 (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes
388fe5b6
DB
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
18ad0f69 1136 )?
388fe5b6
DB
1137 (?:
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
1140 )
1141 (?::[0-9]+)? # Server port number (optional)
1142 (?:[\/|\?]
b986cc55 1143 (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
388fe5b6 1144 *)?
18ad0f69 1145 $/xi", $url);
93cf70d7
DB
1146 }
1147 else {
b986cc55 1148 return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
93cf70d7 1149 }
8759ca73
DB
1150}
1151
50d78e98 1152/**
279d2d4c
H
1153 * @} End of "defgroup validation".
1154 */
1155
1156/**
767d72d4 1157 * Registers an event for the current visitor to the flood control mechanism.
9bf33e5a
DB
1158 *
1159 * @param $name
2fcaa6a9 1160 * The name of an event.
2dc3c05a
DB
1161 * @param $window
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.
1da6ef52
AB
1166 * @param $identifier
1167 * Optional identifier (defaults to the current user's IP address).
9bf33e5a 1168 */
2dc3c05a 1169function flood_register_event($name, $window = 3600, $identifier = NULL) {
1da6ef52
AB
1170 if (!isset($identifier)) {
1171 $identifier = ip_address();
1172 }
ae2e4ccd
DB
1173 db_insert('flood')
1174 ->fields(array(
1175 'event' => $name,
1da6ef52 1176 'identifier' => $identifier,
ae2e4ccd 1177 'timestamp' => REQUEST_TIME,
2dc3c05a 1178 'expiration' => REQUEST_TIME + $window,
ae2e4ccd
DB
1179 ))
1180 ->execute();
9bf33e5a
DB
1181}
1182
1183/**
767d72d4 1184 * Makes the flood control mechanism forget an event for the current visitor.
1da6ef52
AB
1185 *
1186 * @param $name
1187 * The name of an event.
1188 * @param $identifier
1189 * Optional identifier (defaults to the current user's IP address).
1190 */
1191function flood_clear_event($name, $identifier = NULL) {
1192 if (!isset($identifier)) {
1193 $identifier = ip_address();
1194 }
1195 db_delete('flood')
1196 ->condition('event', $name)
1197 ->condition('identifier', $identifier)
1198 ->execute();
1199}
1200
1201/**
767d72d4 1202 * Checks whether a user is allowed to proceed with the specified event.
2fcaa6a9 1203 *
89ade199
AB
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.
9bf33e5a
DB
1207 *
1208 * @param $name
89ade199 1209 * The unique name of the event.
19d10a4e 1210 * @param $threshold
89ade199 1211 * The maximum number of times each user can do this event per time window.
1da6ef52 1212 * @param $window
89ade199
AB
1213 * Number of seconds in the time window for this event (default is 3600
1214 * seconds, or 1 hour).
1da6ef52 1215 * @param $identifier
89ade199
AB
1216 * Unique identifier of the current user. Defaults to their IP address.
1217 *
9bf33e5a 1218 * @return
89ade199
AB
1219 * TRUE if the user is allowed to proceed. FALSE if they have exceeded the
1220 * threshold and should not be allowed to proceed.
9bf33e5a 1221 */
1da6ef52
AB
1222function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) {
1223 if (!isset($identifier)) {
1224 $identifier = ip_address();
1225 }
1226 $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array(
ae2e4ccd 1227 ':event' => $name,
1da6ef52
AB
1228 ':identifier' => $identifier,
1229 ':timestamp' => REQUEST_TIME - $window))
ae2e4ccd
DB
1230 ->fetchField();
1231 return ($number < $threshold);
9bf33e5a
DB
1232}
1233
ebba90fe 1234/**
a5f42fd0
DB
1235 * @defgroup sanitization Sanitization functions
1236 * @{
1237 * Functions to sanitize values.
df52ebb7
DB
1238 *
1239 * See http://drupal.org/writing-secure-code for information
1240 * on writing secure code.
a5f42fd0
DB
1241 */
1242
1243/**
9e6313e8
AB
1244 * Strips dangerous protocols (e.g. 'javascript:') from a URI.
1245 *
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.
1252 *
1253 * @param $uri
1254 * A plain-text URI that might contain dangerous protocols.
1255 *
1256 * @return
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.
1261 *
1262 * @see check_url()
1263 */
1264function drupal_strip_dangerous_protocols($uri) {
1265 static $allowed_protocols;
1266
1267 if (!isset($allowed_protocols)) {
c7e9857d 1268 $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal')));
9e6313e8
AB
1269 }
1270
1271 // Iteratively remove any invalid protocol found.
1272 do {
1273 $before = $uri;
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)) {
1282 break;
1283 }
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);
1288 }
1289 }
1290 } while ($before != $uri);
1291
1292 return $uri;
1293}
1294
1295/**
767d72d4 1296 * Strips dangerous protocols from a URI and encodes it for output to HTML.
9e6313e8
AB
1297 *
1298 * @param $uri
1299 * A plain-text URI that might contain dangerous protocols.
1300 *
1301 * @return
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.
1308 *
1309 * @see drupal_strip_dangerous_protocols()
29172616
DB
1310 */
1311function check_url($uri) {
9e6313e8 1312 return check_plain(drupal_strip_dangerous_protocols($uri));
29172616
DB
1313}
1314
1315/**
767d72d4 1316 * Applies a very permissive XSS/HTML filter for admin-only use.
a5f42fd0
DB
1317 *
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).
1321 *
1322 * Allows all tags that can be used inside an HTML body, save
1323 * for scripts and styles.
1324 */
1325function filter_xss_admin($string) {
17e3bac0 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'));
a5f42fd0
DB
1327}
1328
1329/**
767d72d4 1330 * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
a5f42fd0 1331 *
65d3b5dd
DB
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.
a5f42fd0
DB
1334 *
1335 * This code does four things:
65d3b5dd
DB
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.
1340 * javascript:).
a5f42fd0
DB
1341 *
1342 * @param $string
65d3b5dd
DB
1343 * The string with raw HTML in it. It will be stripped of everything that can
1344 * cause an XSS attack.
a5f42fd0
DB
1345 * @param $allowed_tags
1346 * An array of allowed tags.
65d3b5dd
DB
1347 *
1348 * @return
1349 * An XSS safe version of $string, or an empty string if $string is not
1350 * valid UTF-8.
1351 *
1352 * @see drupal_validate_utf8()
1353 * @ingroup sanitization
a5f42fd0
DB
1354 */
1355function 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)) {
1359 return '';
1360 }
ec0f197e 1361 // Store the text format.
a5f42fd0 1362 _filter_xss_split($allowed_tags, TRUE);
ec0f197e 1363 // Remove NULL characters (ignored by some browsers).
a5f42fd0 1364 $string = str_replace(chr(0), '', $string);
ec0f197e 1365 // Remove Netscape 4 JS entities.
a5f42fd0
DB
1366 $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
1367
ec0f197e 1368 // Defuse all HTML entities.
a5f42fd0 1369 $string = str_replace('&', '&amp;', $string);
ec0f197e 1370 // Change back only well-formed entities in our whitelist:
1371 // Decimal numeric entities.
a5f42fd0 1372 $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
ec0f197e 1373 // Hexadecimal numeric entities.
a5f42fd0 1374 $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
ec0f197e 1375 // Named entities.
c90e1672 1376 $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
a5f42fd0
DB
1377
1378 return preg_replace_callback('%
1379 (
1380 <(?=[^a-zA-Z!/]) # a lone <
1381 | # or
9d912261
DB
1382 <!--.*?--> # a comment
1383 | # or
a5f42fd0
DB
1384 <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
1385 | # or
1386 > # just a >
1387 )%x', '_filter_xss_split', $string);
1388}
1389
1390/**
1391 * Processes an HTML tag.
1392 *
1393 * @param $m
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.
1397 * @param $store
1398 * Whether to store $m.
767d72d4 1399 *
a5f42fd0
DB
1400 * @return
1401 * If the element isn't allowed, an empty string. Otherwise, the cleaned up
1402 * version of the HTML element.
1403 */
1404function _filter_xss_split($m, $store = FALSE) {
1405 static $allowed_html;
1406
1407 if ($store) {
1408 $allowed_html = array_flip($m);
1409 return;
1410 }
1411
1412 $string = $m[1];
1413
1414 if (substr($string, 0, 1) != '<') {
ec0f197e 1415 // We matched a lone ">" character.
a5f42fd0
DB
1416 return '&gt;';
1417 }
1418 elseif (strlen($string) == 1) {
ec0f197e 1419 // We matched a lone "<" character.
a5f42fd0
DB
1420 return '&lt;';
1421 }
1422
9d912261 1423 if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
ec0f197e 1424 // Seriously malformed.
a5f42fd0
DB
1425 return '';
1426 }
1427
1428 $slash = trim($matches[1]);
1429 $elem = &$matches[2];
1430 $attrlist = &$matches[3];
9d912261
DB
1431 $comment = &$matches[4];
1432
1433 if ($comment) {
1434 $elem = '!--';
1435 }
a5f42fd0
DB
1436
1437 if (!isset($allowed_html[strtolower($elem)])) {
ec0f197e 1438 // Disallowed HTML element.
a5f42fd0
DB
1439 return '';
1440 }
1441
9d912261
DB
1442 if ($comment) {
1443 return $comment;
1444 }
1445
a5f42fd0
DB
1446 if ($slash != '') {
1447 return "</$elem>";
1448 }
1449
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 ? ' /' : '';
1453
ec0f197e 1454 // Clean up attributes.
a5f42fd0
DB
1455 $attr2 = implode(' ', _filter_xss_attributes($attrlist));
1456 $attr2 = preg_replace('/[<>]/', '', $attr2);
1457 $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
1458
1459 return "<$elem$attr2$xhtml_slash>";
1460}
1461
1462/**
1463 * Processes a string of HTML attributes.
1464 *
1465 * @return
1466 * Cleaned up version of the HTML attributes.
1467 */
1468function _filter_xss_attributes($attr) {
1469 $attrarr = array();
1470 $mode = 0;
1471 $attrname = '';
1472
1473 while (strlen($attr) != 0) {
1474 // Was the last operation successful?
1475 $working = 0;
1476
1477 switch ($mode) {
1478 case 0:
ec0f197e 1479 // Attribute name, href for instance.
a5f42fd0
DB
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);
1485 }
1486 break;
1487
1488 case 1:
ec0f197e 1489 // Equals sign or valueless ("selected").
a5f42fd0
DB
1490 if (preg_match('/^\s*=\s*/', $attr)) {
1491 $working = 1; $mode = 2;
1492 $attr = preg_replace('/^\s*=\s*/', '', $attr);
1493 break;
1494 }
1495
1496 if (preg_match('/^\s+/', $attr)) {
1497 $working = 1; $mode = 0;
1498 if (!$skip) {
1499 $attrarr[] = $attrname;
1500 }
1501 $attr = preg_replace('/^\s+/', '', $attr);
1502 }
1503 break;
1504
1505 case 2:
ec0f197e 1506 // Attribute value, a URL after href= for instance.
a5f42fd0
DB
1507 if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
1508 $thisval = filter_xss_bad_protocol($match[1]);
1509
1510 if (!$skip) {
1511 $attrarr[] = "$attrname=\"$thisval\"";
1512 }
1513 $working = 1;
1514 $mode = 0;
1515 $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
1516 break;
1517 }
1518
1519 if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
1520 $thisval = filter_xss_bad_protocol($match[1]);
1521
1522 if (!$skip) {
1523 $attrarr[] = "$attrname='$thisval'";
1524 }
1525 $working = 1; $mode = 0;
1526 $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
1527 break;
1528 }
1529
1530 if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
1531 $thisval = filter_xss_bad_protocol($match[1]);
1532
1533 if (!$skip) {
1534 $attrarr[] = "$attrname=\"$thisval\"";
1535 }
1536 $working = 1; $mode = 0;
1537 $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
1538 }
1539 break;
1540 }
1541
1542 if ($working == 0) {
ec0f197e 1543 // Not well formed; remove and try again.
a5f42fd0
DB
1544 $attr = preg_replace('/
1545 ^
1546 (
1547 "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
1548 | # or
1549 \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
1550 | # or
1551 \S # - a non-whitespace character
1552 )* # any number of the above three
1553 \s* # any number of whitespaces
1554 /x', '', $attr);
1555 $mode = 0;
1556 }
1557 }
1558
3c4be3ab 1559 // The attribute list ends with a valueless attribute like "selected".
e4be0eb7 1560 if ($mode == 1 && !$skip) {
a5f42fd0
DB
1561 $attrarr[] = $attrname;
1562 }
1563 return $attrarr;
1564}
1565
1566/**
767d72d4 1567 * Processes an HTML attribute value and strips dangerous protocols from URLs.
a5f42fd0
DB
1568 *
1569 * @param $string
1570 * The string with the attribute value.
1571 * @param $decode
767d72d4 1572 * (deprecated) Whether to decode entities in the $string. Set to FALSE if the
9e6313e8
AB
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.
767d72d4 1576 *
a5f42fd0
DB
1577 * @return
1578 * Cleaned up and HTML-escaped version of $string.
1579 */
1580function filter_xss_bad_protocol($string, $decode = TRUE) {
a5f42fd0 1581 // Get the plain text representation of the attribute value (i.e. its meaning).
9e6313e8
AB
1582 // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML
1583 // string that needs decoding.
a5f42fd0 1584 if ($decode) {
90a95fd1
AB
1585 if (!function_exists('decode_entities')) {
1586 require_once DRUPAL_ROOT . '/includes/unicode.inc';
1587 }
1588
a5f42fd0
DB
1589 $string = decode_entities($string);
1590 }
9e6313e8 1591 return check_plain(drupal_strip_dangerous_protocols($string));
a5f42fd0
DB
1592}
1593
1594/**
1595 * @} End of "defgroup sanitization".
1596 */
1597
1598/**
b84b6e42 1599 * @defgroup format Formatting
ebba90fe 1600 * @{
b84b6e42 1601 * Functions to format numbers, strings, dates, etc.
ebba90fe
DB
1602 */
1603
50d78e98
DB
1604/**
1605 * Formats an RSS channel.
1606 *
1607 * Arbitrary elements may be added using the $args associative array.
1608 */
36e87e19 1609function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
2ad0a2c1
DB
1610 global $language_content;
1611 $langcode = $langcode ? $langcode : $language_content->language;
f8329dd4 1612
009b1afe 1613 $output = "<channel>\n";
56d2664a
DB
1614 $output .= ' <title>' . check_plain($title) . "</title>\n";
1615 $output .= ' <link>' . check_url($link) . "</link>\n";
9a014043
SW
1616
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 &amp becoming &amp;amp;).
56d2664a
DB
1620 $output .= ' <description>' . check_plain(decode_entities(strip_tags($description))) . "</description>\n";
1621 $output .= ' <language>' . check_plain($langcode) . "</language>\n";
0fef1502 1622 $output .= format_xml_elements($args);
d852a999
DB
1623 $output .= $items;
1624 $output .= "</channel>\n";
1625
1626 return $output;
1627}
1628
50d78e98 1629/**
767d72d4 1630 * Formats a single RSS item.
50d78e98
DB
1631 *
1632 * Arbitrary elements may be added using the $args associative array.
1633 */
f8329dd4 1634function format_rss_item($title, $link, $description, $args = array()) {
009b1afe 1635 $output = "<item>\n";
56d2664a
DB
1636 $output .= ' <title>' . check_plain($title) . "</title>\n";
1637 $output .= ' <link>' . check_url($link) . "</link>\n";
1638 $output .= ' <description>' . check_plain($description) . "</description>\n";
0fef1502
SW
1639 $output .= format_xml_elements($args);
1640 $output .= "</item>\n";
1641
1642 return $output;
1643}
1644
1645/**
767d72d4 1646 * Formats XML elements.
0fef1502
SW
1647 *
1648 * @param $array
3780b176 1649 * An array where each item represents an element and is either a:
0fef1502
SW
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
1655 *
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.
1658 */
1659function format_xml_elements($array) {
f7440d4d 1660 $output = '';
0fef1502
SW
1661 foreach ($array as $key => $value) {
1662 if (is_numeric($key)) {
7931c778 1663 if ($value['key']) {
56d2664a 1664 $output .= ' <' . $value['key'];
764f1177 1665 if (isset($value['attributes']) && is_array($value['attributes'])) {
7931c778
DB
1666 $output .= drupal_attributes($value['attributes']);
1667 }
1668
446b2927 1669 if (isset($value['value']) && $value['value'] != '') {
56d2664a 1670 $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '</' . $value['key'] . ">\n";
7931c778
DB
1671 }
1672 else {
1673 $output .= " />\n";
1674 }
1675 }
1676 }
1677 else {
56d2664a 1678 $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : check_plain($value)) . "</$key>\n";
7931c778 1679 }
f8329dd4 1680 }
d852a999
DB
1681 return $output;
1682}
1683
9a23223e 1684/**
767d72d4 1685 * Formats a string containing a count of items.
9a23223e 1686 *
50d78e98 1687 * This function ensures that the string is pluralized correctly. Since t() is
2fcaa6a9
H
1688 * called by this function, make sure not to pass already-localized strings to
1689 * it.
50d78e98 1690 *
ae093d13
DB
1691 * For example:
1692 * @code
1693 * $output = format_plural($node->comment_count, '1 comment', '@count comments');
1694 * @endcode
1695 *
1696 * Example with additional replacements:
1697 * @code
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)));
1702 * @endcode
1703 *
50d78e98
DB
1704 * @param $count
1705 * The item count to display.
1706 * @param $singular
767d72d4 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.
50d78e98 1710 * @param $plural
767d72d4 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".
ae093d13 1714 * @param $args
767d72d4 1715 * An associative array of replacements to make after translation. Instances
ae093d13 1716 * of any key in this array are replaced with the corresponding value.
767d72d4 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.
28b2f098 1720 * @param $options
767d72d4 1721 * An associative array of additional options. See t() for allowed keys.
1722 *
50d78e98
DB
1723 * @return
1724 * A translated string.
767d72d4 1725 *
1726 * @see t()
1727 * @see format_string()
9a23223e 1728 */
28b2f098 1729function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
da3c19b3 1730 $args['@count'] = $count;
ae093d13 1731 if ($count == 1) {
28b2f098 1732 return t($singular, $args, $options);
ae093d13 1733 }
1831e1b6 1734
2fcaa6a9 1735 // Get the plural index through the gettext formula.
28b2f098 1736 $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1;
2fcaa6a9
H
1737 // Backwards compatibility.
1738 if ($index < 0) {
28b2f098 1739 return t($plural, $args, $options);
1831e1b6
DB
1740 }
1741 else {
1742 switch ($index) {
1743 case "0":
28b2f098 1744 return t($singular, $args, $options);
1831e1b6 1745 case "1":
28b2f098 1746 return t($plural, $args, $options);
1831e1b6 1747 default:
ae093d13 1748 unset($args['@count']);
56d2664a 1749 $args['@count[' . $index . ']'] = $count;
28b2f098 1750 return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options);
1831e1b6
DB
1751 }
1752 }
d852a999
DB
1753}
1754
ebba90fe 1755/**
767d72d4 1756 * Parses a given byte count.
f28aa5f3
DB
1757 *
1758 * @param $size
5fbdca02
DB
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).
767d72d4 1761 *
f28aa5f3 1762 * @return
5fbdca02 1763 * An integer representation of the size in bytes.
f28aa5f3
DB
1764 */
1765function parse_size($size) {
5fbdca02
DB
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.
1768 if ($unit) {
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])));
1771 }
1772 else {
1773 return round($size);
f28aa5f3
DB
1774 }
1775}
1776
1777/**
767d72d4 1778 * Generates a string representation for the given byte count.
ebba90fe 1779 *
50d78e98 1780 * @param $size
2fcaa6a9 1781 * A size in bytes.
36e87e19 1782 * @param $langcode
2fcaa6a9
H
1783 * Optional language code to translate to a language other than what is used
1784 * to display the page.
767d72d4 1785 *
50d78e98
DB
1786 * @return
1787 * A translated string representation of the size.
ebba90fe 1788 */
36e87e19 1789function format_size($size, $langcode = NULL) {
5fbdca02 1790 if ($size < DRUPAL_KILOBYTE) {
28b2f098 1791 return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
d852a999 1792 }
f1c86928 1793 else {
5fbdca02 1794 $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
6fbbaf06 1795 $units = array(
28b2f098
DB
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)),
6fbbaf06
DB
1804 );
1805 foreach ($units as $unit) {
5fbdca02
DB
1806 if (round($size, 2) >= DRUPAL_KILOBYTE) {
1807 $size = $size / DRUPAL_KILOBYTE;
ecb032a2
DB
1808 }
1809 else {
1810 break;
1811 }
f1c86928 1812 }
6fbbaf06 1813 return str_replace('@size', round($size, 2), $unit);
d852a999 1814 }
d852a999
DB
1815}
1816
ebba90fe 1817/**
767d72d4 1818 * Formats a time interval with the requested granularity.
ebba90fe 1819 *
50d78e98
DB
1820 * @param $timestamp
1821 * The length of the interval in seconds.
1822 * @param $granularity
1823 * How many different units to display in the string.
36e87e19
H
1824 * @param $langcode
1825 * Optional language code to translate to a language other than
1826 * what is used to display the page.
767d72d4 1827 *
50d78e98
DB
1828 * @return
1829 * A translated string representation of the interval.
ebba90fe 1830 */
36e87e19 1831function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
f5d0e11f
DB
1832 $units = array(
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
1840 );
be230467 1841 $output = '';
01f252ab 1842 foreach ($units as $key => $value) {
50d78e98 1843 $key = explode('|', $key);
d852a999 1844 if ($timestamp >= $value) {
28b2f098 1845 $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
d852a999 1846 $timestamp %= $value;
fd86977c
DB
1847 $granularity--;
1848 }
1849
1850 if ($granularity == 0) {
1851 break;
d852a999
DB
1852 }
1853 }
28b2f098 1854 return $output ? $output : t('0 sec', array(), array('langcode' => $langcode));
d852a999
DB
1855}
1856
ebba90fe 1857/**
35d221ea 1858 * Formats a date, using a date type or a custom date format string.
ebba90fe 1859 *
50d78e98 1860 * @param $timestamp
35d221ea 1861 * A UNIX timestamp to format.
50d78e98 1862 * @param $type
35d221ea
AB
1863 * (optional) The format to use, one of:
1864 * - 'short', 'medium', or 'long' (the corresponding built-in date formats).
913adae6
AB
1865 * - The name of a date type defined by a module in hook_date_format_types(),
1866 * if it's been assigned a format.
35d221ea
AB
1867 * - The machine name of an administrator-defined date format.
1868 * - 'custom', to use $format.
1869 * Defaults to 'medium'.
50d78e98 1870 * @param $format
35d221ea
AB
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.
50d78e98 1874 * @param $timezone
35d221ea
AB
1875 * (optional) Time zone identifier, as described at
1876 * http://php.net/manual/en/timezones.php Defaults to the time zone used to
1877 * display the page.
36e87e19 1878 * @param $langcode
35d221ea
AB
1879 * (optional) Language code to translate to. Defaults to the language used to
1880 * display the page.
1881 *
50d78e98
DB
1882 * @return
1883 * A translated date string in the requested format.
ebba90fe 1884 */
36e87e19 1885function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
b1434988 1886 // Use the advanced drupal_static() pattern, since this is called very often.
3ede6199
AB
1887 static $drupal_static_fast;
1888 if (!isset($drupal_static_fast)) {
1889 $drupal_static_fast['timezones'] = &drupal_static(__FUNCTION__);
1890 }
1891 $timezones = &$drupal_static_fast['timezones'];
b1434988 1892
e41e8acd 1893 if (!isset($timezone)) {
ff301288 1894 $timezone = date_default_timezone_get();
97c2aa2b 1895 }
53f53b6d 1896 // Store DateTimeZone objects in an array rather than repeatedly
af3f94b3 1897 // constructing identical objects over the life of a request.
53f53b6d
AB
1898 if (!isset($timezones[$timezone])) {
1899 $timezones[$timezone] = timezone_open($timezone);
1900 }
d852a999 1901
3ffb9f4a
DB
1902 // Use the default langcode if none is set.
1903 global $language;
1904 if (empty($langcode)) {
1905 $langcode = isset($language->language) ? $language->language : 'en';
1906 }
1907
d852a999 1908 switch ($type) {
14b233ec 1909 case 'short':
97c2aa2b 1910 $format = variable_get('date_format_short', 'm/d/Y - H:i');
d852a999 1911 break;
35d221ea 1912
14b233ec 1913 case 'long':
97c2aa2b 1914 $format = variable_get('date_format_long', 'l, F j, Y - H:i');
d852a999 1915 break;
35d221ea 1916
97c2aa2b 1917 case 'custom':
2fcaa6a9 1918 // No change to format.
d852a999 1919 break;
35d221ea 1920
97c2aa2b 1921 case 'medium':
d852a999 1922 default:
35d221ea
AB
1923 // Retrieve the format of the custom $type passed.
1924 if ($type != 'medium') {
1925 $format = variable_get('date_format_' . $type, '');
1926 }
1927 // Fall back to 'medium'.
1928 if ($format === '') {
1929 $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
1930 }
1931 break;
d80140b4
DB
1932 }
1933
53f53b6d
AB
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]);
1938
3ffb9f4a
DB
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
1941 // input string.
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);
1945
1946 // Call date_format().
1947 $format = date_format($date_time, $format);
1948
1949 // Pass the langcode to _format_date_callback().
1950 _format_date_callback(NULL, $langcode);
1951
1952 // Translate the marked sequences.
1953 return preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', '_format_date_callback', $format);
1954}
1955
1956/**
d4d1522c
DB
1957 * Returns an ISO8601 formatted date based on the given date.
1958 *
767d72d4 1959 * Callback for use within hook_rdf_mapping() implementations.
d4d1522c
DB
1960 *
1961 * @param $date
1962 * A UNIX timestamp.
767d72d4 1963 *
d4d1522c
DB
1964 * @return string
1965 * An ISO8601 formatted date.
1966 */
1967function 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);
1971}
1972
1973/**
767d72d4 1974 * Translates a formatted date string.
1975 *
1976 * Callback for preg_replace_callback() within format_date().
3ffb9f4a
DB
1977 */
1978function _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;
1981
1982 if (!isset($matches)) {
1983 $langcode = $new_langcode;
1984 return;
1985 }
1986
1987 $code = $matches[1];
1988 $string = $matches[2];
1989
1990 if (!isset($cache[$langcode][$code][$string])) {
1991 $options = array(
1992 'langcode' => $langcode,
1993 );
1994
1995 if ($code == 'F') {
1996 $options['context'] = 'Long month name';
d80140b4 1997 }
3ffb9f4a
DB
1998
1999 if ($code == '') {
2000 $cache[$langcode][$code][$string] = $string;
6a95a002 2001 }
d80140b4 2002 else {
3ffb9f4a 2003 $cache[$langcode][$code][$string] = t($string, array(), $options);
d80140b4 2004 }
d852a999 2005 }
3ffb9f4a 2006 return $cache[$langcode][$code][$string];
d852a999
DB
2007}
2008
ebba90fe 2009/**
ca8eee75
AB
2010 * Format a username.
2011 *
8cdf750e 2012 * By default, the passed-in object's 'name' property is used if it exists, or
ca8eee75
AB
2013 * else, the site-defined value for the 'anonymous' variable. However, a module
2014 * may override this by implementing hook_username_alter(&$name, $account).
2015 *
2016 * @see hook_username_alter()
2017 *
2018 * @param $account
2019 * The account object for the user whose name is to be formatted.
2020 *
2021 * @return
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.
2025 */
2026function format_username($account) {
2027 $name = !empty($account->name) ? $account->name : variable_get('anonymous', t('Anonymous'));
2028 drupal_alter('username', $name, $account);
2029 return $name;
2030}
2031
2032/**
b84b6e42
DB
2033 * @} End of "defgroup format".
2034 */
d852a999 2035
0d7e2050 2036/**
0b66c971
DB
2037 * Generates an internal or external URL.
2038 *
2039 * When creating links in modules, consider whether l() could be a better
2040 * alternative than url().
50d78e98
DB
2041 *
2042 * @param $path
0b66c971
DB
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
2053 * not looked up.
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
2058 * URL encode them.
dc5843bd 2059 * @param $options
0b66c971 2060 * An associative array of additional options, with the following elements:
598e7392 2061 * - 'query': An array of query key/value-pairs (without any URL-encoding) to
0b66c971
DB
2062 * append to the URL.
2063 * - 'fragment': A fragment identifier (named anchor) to append to the URL.
2064 * Do not include the leading '#' character.
598e7392
DB
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
0b66c971 2067 * displayed outside the site, such as in an RSS feed.
598e7392
DB
2068 * - 'alias': Defaults to FALSE. Whether the given path is a URL alias
2069 * already.
2070 * - 'external': Whether the given path is an external URL.
e3da79e2
DB
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
7c9948f0
DB
2073 * for the URL. If $options['language'] is omitted, the global $language_url
2074 * will be used.
598e7392 2075 * - 'https': Whether this URL should point to a secure location. If not
14c1c505 2076 * defined, the current scheme is used, so the user stays on http or https
598e7392
DB
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.
5abc116f
DB
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'.
767d72d4 2091 * - 'entity_type': The entity type of the object that called url(). Only
2092 * set if url() is invoked by entity_uri().
02b74638
DB
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().
598e7392 2095 *
50d78e98 2096 * @return
2fcaa6a9 2097 * A string containing a URL to the given path.
50d78e98 2098 */
eabf7ab4 2099function url($path = NULL, array $options = array()) {
2fcaa6a9 2100 // Merge in defaults.
dc5843bd 2101 $options += array(
c389c905 2102 'fragment' => '',
598e7392 2103 'query' => array(),
c389c905
DB
2104 'absolute' => FALSE,
2105 'alias' => FALSE,
5bb6927e 2106 'prefix' => ''
c389c905 2107 );
cd7b8f09 2108
c389c905 2109 if (!isset($options['external'])) {
9e6313e8
AB
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
2114 // critical.
c389c905 2115 $colonpos = strpos($path, ':');
9e6313e8 2116 $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
c389c905 2117 }
eb0caa35 2118
cd7b8f09
AB
2119 // Preserve the original path before altering or aliasing.
2120 $original_path = $path;
2121
2975da0d 2122 // Allow other modules to alter the outbound URL and options.
7f4598d8 2123 drupal_alter('url_outbound', $path, $options, $original_path);
cd7b8f09 2124
a2d78da0 2125 if (isset($options['fragment']) && $options['fragment'] !== '') {
56d2664a 2126 $options['fragment'] = '#' . $options['fragment'];
dc5843bd 2127 }
c243918e 2128
c389c905 2129 if ($options['external']) {
2fcaa6a9 2130 // Split off the fragment.
d692d438 2131 if (strpos($path, '#') !== FALSE) {
c243918e 2132 list($path, $old_fragment) = explode('#', $path, 2);
598e7392 2133 // If $options contains no fragment, take it over from the path.
dc5843bd 2134 if (isset($old_fragment) && !$options['fragment']) {
56d2664a 2135 $options['fragment'] = '#' . $old_fragment;
c243918e
SW
2136 }
2137 }
2fcaa6a9 2138 // Append the query.
dc5843bd 2139 if ($options['query']) {
598e7392 2140 $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($options['query']);
c243918e 2141 }
14c1c505
AB
2142 if (isset($options['https']) && variable_get('https', FALSE)) {
2143 if ($options['https'] === TRUE) {
2144 $path = str_replace('http://', 'https://', $path);
2145 }
2146 elseif ($options['https'] === FALSE) {
2147 $path = str_replace('https://', 'http://', $path);
2148 }
2149 }
2fcaa6a9 2150 // Reassemble.
dc5843bd 2151 return $path . $options['fragment'];
c243918e 2152 }
1c3a3032 2153
6586b764 2154 global $base_url, $base_secure_url, $base_insecure_url;
a1ae4da7 2155
6586b764 2156 // The base_url might be rewritten from the language rewrite in domain mode.
5395f208 2157 if (!isset($options['base_url'])) {
6586b764
DB
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;
2162 }
2163 elseif ($options['https'] === FALSE) {
2164 $options['base_url'] = $base_insecure_url;
2165 $options['absolute'] = TRUE;
2166 }
2167 }
2168 else {
2169 $options['base_url'] = $base_url;
2170 }
5395f208 2171 }
f137b269 2172
34a5d460 2173 // The special path '<front>' links to the default front page.
d40bb1e9
H
2174 if ($path == '<front>') {
2175 $path = '';
5bb6927e 2176 }
d40bb1e9 2177 elseif (!empty($path) && !$options['alias']) {
1b9cde9d 2178 $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
cd7b8f09
AB
2179 $alias = drupal_get_path_alias($original_path, $language);
2180 if ($alias != $original_path) {
2181 $path = $alias;
2182 }
d40bb1e9
H
2183 }
2184
56d2664a 2185 $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
d40bb1e9 2186 $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
d40bb1e9 2187
598e7392
DB
2188 // With Clean URLs.
2189 if (!empty($GLOBALS['conf']['clean_url'])) {
2190 $path = drupal_encode_path($prefix . $path);
5bb6927e 2191 if ($options['query']) {
598e7392 2192 return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
2647a943
DB
2193 }
2194 else {
5bb6927e 2195 return $base . $path . $options['fragment'];
2647a943
DB
2196 }
2197 }
598e7392 2198 // Without Clean URLs.
2647a943 2199 else {
598e7392
DB
2200 $path = $prefix . $path;
2201 $query = array();
5bb6927e 2202 if (!empty($path)) {
598e7392 2203 $query['q'] = $path;
5bb6927e 2204 }
598e7392
DB
2205 if ($options['query']) {
2206 // We do not use array_merge() here to prevent overriding $path via query
2207 // parameters.
2208 $query += $options['query'];
5622bce2 2209 }
5abc116f
DB
2210 $query = $query ? ('?' . drupal_http_build_query($query)) : '';
2211 $script = isset($options['script']) ? $options['script'] : '';
2212 return $base . $script . $query . $options['fragment'];
2647a943 2213 }
8043cb99
DB
2214}
2215
50d78e98 2216/**
767d72d4 2217 * Returns TRUE if a path is external to Drupal (e.g. http://example.com).
e4afcae1
DB
2218 *
2219 * If a path cannot be assessed by Drupal's menu handler, then we must
2220 * treat it as potentially insecure.
2221 *
2222 * @param $path
2223 * The internal path or external URL being linked to, such as "node/34" or
2224 * "http://example.com/foo".
767d72d4 2225 *
c4afc66d 2226 * @return
e4afcae1 2227 * Boolean TRUE or FALSE, where TRUE indicates an external path.
14c1c505
AB
2228 */
2229function url_is_external($path) {
2230 $colonpos = strpos($path, ':');
0c92275f 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.
9e6313e8 2234 return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path;
14c1c505
AB
2235}
2236
2237/**
767d72d4 2238 * Formats an attribute string for an HTTP header.
0d8515de
AB
2239 *
2240 * @param $attributes
2241 * An associative array of attributes such as 'rel'.
2242 *
2243 * @return
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.
2246 *
2247 * @see drupal_add_http_header()
2248 */
2249function drupal_http_header_attributes(array $attributes = array()) {
2250 foreach ($attributes as $attribute => &$data) {
2251 if (is_array($data)) {
2252 $data = implode(' ', $data);
2253 }
2254 $data = $attribute . '="' . $data . '"';
2255 }
2256 return $attributes ? ' ' . implode('; ', $attributes) : '';
2257}
2258
2259/**
767d72d4 2260 * Converts an associative array to an XML/HTML tag attribute string.
50d78e98 2261 *
e091d1ac
DB
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).
2265 *
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.
2270 *
2271 * Examples of security aspects when using drupal_attributes:
2272 * @code
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>')));
2276 *
2277 * // The statement below demonstrates dangerous use of drupal_attributes, and
fa39282e 2278 * // will return an onmouseout attribute with JavaScript code that, when used
e091d1ac
DB
2279 * // as attribute in a tag, will cause users to be redirected to another site.
2280 * //
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/";')));
2285 * @endcode
5a8452c5 2286 *
50d78e98 2287 * @param $attributes
e091d1ac
DB
2288 * An associative array of key-value pairs to be converted to attributes.
2289 *
50d78e98 2290 * @return
c628ea8d 2291 * A string ready for insertion in a tag (starts with a space).
e091d1ac
DB
2292 *
2293 * @ingroup sanitization
50d78e98 2294 */
de290947
DB
2295function drupal_attributes(array $attributes = array()) {
2296 foreach ($attributes as $attribute => &$data) {
6e6761ed 2297 $data = implode(' ', (array) $data);
de290947 2298 $data = $attribute . '="' . check_plain($data) . '"';
f8329dd4 2299 }
de290947 2300 return $attributes ? ' ' . implode(' ', $attributes) : '';
48805032 2301}
89b2069e 2302
50d78e98 2303/**
0b66c971 2304 * Formats an internal or external URL link as an HTML anchor tag.
50d78e98 2305 *
0b66c971
DB
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
2309 * possible.
50d78e98
DB
2310 *
2311 * @param $text
0b66c971 2312 * The link text for the anchor tag.
50d78e98 2313 * @param $path
0b66c971
DB
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
f5ebd774
AB
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']).
0b66c971
DB
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
6923d2aa 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
2329 * safe.
0b66c971
DB
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.
e3da79e2 2335 *
50d78e98 2336 * @return
0b66c971 2337 * An HTML string containing a link to the given path.
50d78e98 2338 */
eabf7ab4 2339function l($text, $path, array $options = array()) {
1b9cde9d 2340 global $language_url;
031a6876 2341 static $use_theme = NULL;
afc9df99 2342
2fcaa6a9 2343 // Merge in defaults.
dc5843bd 2344 $options += array(
c9de4646
DB
2345 'attributes' => array(),
2346 'html' => FALSE,
2347 );
dc5843bd 2348
2fcaa6a9 2349 // Append active class.
afc9df99 2350 if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
1b9cde9d 2351 (empty($options['language']) || $options['language']->language == $language_url->language)) {
36ec1896 2352 $options['attributes']['class'][] = 'active';
37148276 2353 }
98a5fb14
H
2354
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']);
2359 }
2360
031a6876
AB
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
2370 // rendering.
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
2379 // include files.
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']);
2382 }
2383 else {
2384 $use_theme = FALSE;
2385 }
2386 }
2387 if ($use_theme) {
2388 return theme('link', array('text' => $text, 'path' => $path, 'options' => $options));
2389 }
9a887f86
AB
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.
16dcae23 2392 return '<a href="' . check_plain(url($path, $options)) . '"' . drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text : check_plain($text)) . '</a>';
8043cb99
DB
2393}
2394
50d78e98 2395/**
98745678 2396 * Delivers a page callback result to the browser in the appropriate format.
f42bca3b
DB
2397 *
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().
2401 *
98745678 2402 * When a user requests a page, index.php calls menu_execute_active_handler(),
f42bca3b
DB
2403 * which calls the 'page callback' function registered in hook_menu(). The page
2404 * callback function can return one of:
98745678
AB
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.
f42bca3b
DB
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
2411 * result.
2412 *
2413 * When the page callback returns its constructed content to
98745678 2414 * menu_execute_active_handler(), this function gets called. The purpose of
f42bca3b
DB
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
98745678 2418 * callback is drupal_deliver_html_page(), which delivers the content as an HTML
f42bca3b 2419 * page, complete with blocks in addition to the content. This default can be
435e4e03
DB
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().
f42bca3b
DB
2423 *
2424 * For example, the same page callback function can be used for an HTML
fa39282e 2425 * version of the page and an Ajax version of the page. The page callback
f42bca3b 2426 * function just needs to decide what content is to be returned and the
fa39282e 2427 * delivery callback function will send it as an HTML page or an Ajax
f42bca3b
DB
2428 * response, as appropriate.
2429 *
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
f0a8150b 2432 * return content.
f42bca3b 2433 *
98745678
AB
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.
2443 *
f42bca3b
DB
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
435e4e03 2454 * determined from the menu router information of the current page.
f42bca3b
DB
2455 *
2456 * @see menu_execute_active_handler()
2457 * @see hook_menu()
2458 * @see hook_menu_alter()
f42bca3b
DB
2459 * @see hook_page_delivery_callback_alter()
2460 */
2461function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) {
2462 if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) {
f42bca3b
DB
2463 $default_delivery_callback = $router_item['delivery_callback'];
2464 }
2465 $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page';
435e4e03
DB
2466 // Give modules a chance to alter the delivery callback used, based on
2467 // request-time context (e.g., HTTP request headers).
f42bca3b
DB
2468 drupal_alter('page_delivery_callback', $delivery_callback);
2469 if (function_exists($delivery_callback)) {
2470 $delivery_callback($page_callback_result);
2471 }
2472 else {
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.
dde18582 2476 watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR);
f42bca3b
DB
2477 }
2478}
2479
2480/**
767d72d4 2481 * Packages and sends the result of a page callback to the browser as HTML.
f42bca3b
DB
2482 *
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.
2489 *
076d3986 2490 * @see drupal_deliver_page()
f42bca3b
DB
2491 */
2492function drupal_deliver_html_page($page_callback_result) {
4d18e65a
DB
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');
2499 }
2500
50fb0beb 2501 // Send appropriate HTTP-Header for browsers and search engines.
2502 global $language;
2503 drupal_add_http_header('Content-Language', $language->language);
2504
f42bca3b
DB
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?
f0a8150b 2508 switch ($page_callback_result) {
f42bca3b
DB
2509 case MENU_NOT_FOUND:
2510 // Print a 404 page.
e1ce11da 2511 drupal_add_http_header('Status', '404 Not Found');
f42bca3b 2512
ae284259 2513 watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
f42bca3b 2514
e120a6e8 2515 // Check for and return a fast 404 page if configured.
2516 drupal_fast_404();
2517
f42bca3b
DB
2518 // Keep old path for reference, and to allow forms to redirect to it.
2519 if (!isset($_GET['destination'])) {
2520 $_GET['destination'] = $_GET['q'];
2521 }
2522
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);
2529 }
2530
2531 if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
2532 // Standard 404 handler.
2533 drupal_set_title(t('Page not found'));
e120a6e8 2534 $return = t('The requested page "@path" could not be found.', array('@path' => request_uri()));
f42bca3b
DB
2535 }
2536
2537 drupal_set_page_content($return);
2538 $page = element_info('page');
2539 print drupal_render_page($page);
2540 break;
2541
2542 case MENU_ACCESS_DENIED:
2543 // Print a 403 page.
e1ce11da 2544 drupal_add_http_header('Status', '403 Forbidden');
ae284259 2545 watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
f42bca3b
DB
2546
2547 // Keep old path for reference, and to allow forms to redirect to it.
2548 if (!isset($_GET['destination'])) {
2549 $_GET['destination'] = $_GET['q'];
2550 }
2551
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);
2558 }
2559
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.');
2564 }
2565
2566 print drupal_render_page($return);
2567 break;
2568
2569 case MENU_SITE_OFFLINE:
2570 // Print a 503 page.
2571 drupal_maintenance_theme();
e1ce11da 2572 drupal_add_http_header('Status', '503 Service unavailable');
f42bca3b
DB
2573 drupal_set_title(t('Site under maintenance'));
2574 print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',
f0a8150b 2575 t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
f42bca3b
DB
2576 break;
2577 }
2578 }
2579 elseif (isset($page_callback_result)) {
2580 // Print anything besides a menu constant, assuming it's not NULL or
2581 // undefined.
2582 print drupal_render_page($page_callback_result);
2583 }
2584
2585 // Perform end-of-request tasks.
2586 drupal_page_footer();
2587}
2588
2589/**
767d72d4 2590 * Performs end-of-request tasks.
50d78e98
DB
2591 *
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().
2594 */
57c7d7b4 2595function drupal_page_footer() {
d20e4571
DB
2596 global $user;
2597
e474fbbd 2598 module_invoke_all('exit');
2e18cb89 2599
e474fbbd
DB
2600 // Commit the user session, if needed.
2601 drupal_session_commit();
4028362f 2602
47653eae 2603 if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) {
e474fbbd
DB
2604 drupal_serve_page_from_cache($cache);
2605 }
2606 else {
2607 ob_flush();
2608 }
2e18cb89 2609
59ece2e3 2610 _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
61f4dfc9 2611 drupal_cache_system_paths();
a8b3369b 2612 module_implements_write_cache();
51839f24 2613 system_run_automated_cron();
d852a999
DB
2614}
2615
b817bdb3 2616/**
767d72d4 2617 * Performs end-of-request tasks.
24c259cd
DB
2618 *
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();
2622 *
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.
2627 */
2628function 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);
2632 }
2633 drupal_session_commit();
2634 }
2635 exit;
2636}
2637
2638/**
767d72d4 2639 * Forms an associative array from a linear array.
b5c18e8a 2640 *
50d78e98
DB
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
2645 * instead.
2646 *
2647 * @param $array
2648 * A linear array.
2649 * @param $function
2fcaa6a9 2650 * A name of a function to apply to all values before output.
6dabf4fc 2651 *
2652 * @return
50d78e98 2653 * An associative array.
b5c18e8a
DB
2654 */
2655function drupal_map_assoc($array, $function = NULL) {
15bca6e4
DB
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();
6f607b80
DB
2659 if (is_callable($function)) {
2660 $array = array_map($function, $array);
b5c18e8a 2661 }
6f607b80 2662 return $array;
b5c18e8a
DB
2663}
2664
2665/**
69169330
AB
2666 * Attempts to set the PHP maximum execution time.
2667 *
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.
8b86d0da 2673 *
69169330
AB
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.
8b86d0da 2680 *
69169330
AB
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.
2686 *
2687 * @param $time_limit
2688 * An integer specifying the new time limit, in seconds. A value of 0
2689 * indicates unlimited execution time.
67f2c101
DB
2690 *
2691 * @ingroup php_wrappers
69169330
AB
2692 */
2693function drupal_set_time_limit($time_limit) {
2694 if (function_exists('set_time_limit')) {
2695 @set_time_limit($time_limit);
2696 }
2697}
2698
2699/**
5d759ccb
DB
2700 * Returns the path to a system item (module, theme, etc.).
2701 *
2702 * @param $type
db7f5e4c 2703 * The type of the item (i.e. theme, theme_engine, module, profile).
5d759ccb
DB
2704 * @param $name
2705 * The name of the item for which the path is requested.
2706 *
2707 * @return
2708 * The path to the requested item.
2709 */
2710function drupal_get_path($type, $name) {
2711 return dirname(drupal_get_filename($type, $name));
2712}
2713
7fe195a0 2714/**
767d72d4 2715 * Returns the base URL path (i.e., directory) of the Drupal installation.
9af602fe 2716 *
767d72d4 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 "/".
9af602fe 2719 *
c4c3e0a2
DB
2720 * Examples:
2721 * - http://example.com returns "/" because the path is empty.
2722 * - http://example.com/drupal/folder returns "/drupal/folder/".
048bec13
DB
2723 */
2724function base_path() {
2725 return $GLOBALS['base_path'];
2726}
2727
2728/**
767d72d4 2729 * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
a1674271 2730 *
767d72d4 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'
2734 * attributes.
2d6b6d49 2735 *
0d8515de
AB
2736 * @param $attributes
2737 * Associative array of element attributes including 'href' and 'rel'.
2738 * @param $header
2739 * Optional flag to determine if a HTTP 'Link:' header should be sent.
2740 */
2741function drupal_add_html_head_link($attributes, $header = FALSE) {
2742 $element = array(
2743 '#tag' => 'link',
2744 '#attributes' => $attributes,
2745 );
2746 $href = $attributes['href'];
2747
2748 if ($header) {
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);
2753 }
2754
2755 drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . $href);
a597354b
SW
2756}
2757
a597354b 2758/**
a0cca9a4
AB
2759 * Adds a cascading stylesheet to the stylesheet queue.
2760 *
8b63d832
AB
2761 * Calling drupal_static_reset('drupal_add_css') will clear all cascading
2762 * stylesheets added so far.
2763 *
37c3c7ec
DB
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.
2770 *
2771 * The reason for aggregating the files is outlined quite thoroughly here:
bb460ee7
DB
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
2774 * half its size."
2775 *
37c3c7ec
DB
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.
bb460ee7 2781 *
37c3c7ec
DB
2782 * Non-preprocessed files should only be added to the page when they are
2783 * actually needed.
bb460ee7 2784 *
a0cca9a4
AB
2785 * @param $data
2786 * (optional) The stylesheet data to be added, depending on what is passed
2787 * through to the $options['type'] parameter:
13f4debe 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
bb460ee7
DB
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
767d72d4 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
a0cca9a4
AB
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
bb460ee7
DB
2804 * that it is better practice to use 'file' stylesheets, rather than
2805 * 'inline', as the CSS would then be aggregated and cached.
6e3832f4 2806 * - 'external': The absolute path to an external CSS file that is not hosted
bb460ee7
DB
2807 * on the local server. These files will not be aggregated if CSS
2808 * aggregation is enabled.
df2cf40d 2809 * @param $options
a0cca9a4 2810 * (optional) A string defining the 'type' of CSS that is being added in the
bb460ee7
DB
2811 * $data parameter ('file', 'inline', or 'external'), or an array which can
2812 * have any or all of the following keys:
6e3832f4
AB
2813 * - 'type': The type of stylesheet being added. Available options are 'file',
2814 * 'inline' or 'external'. Defaults to 'file'.
9b8c393d
DB
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
2820 * 'node.js.css'.
facc5810
DB
2821 * - 'group': A number identifying the group in which to add the stylesheet.
2822 * Available constants are:
bb460ee7
DB
2823 * - CSS_SYSTEM: Any system-layer CSS.
2824 * - CSS_DEFAULT: Any module-layer CSS.
2825 * - CSS_THEME: Any theme-layer CSS.
facc5810
DB
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
2850 * every page.
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:
2854 * - First by group.
2855 * - Then by the 'every_page' flag, with TRUE coming before FALSE.
2856 * - Then by weight.
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.
a0cca9a4
AB
2861 * - 'media': The media type for the stylesheet, e.g., all, print, screen.
2862 * Defaults to 'all'.
37c3c7ec 2863 * - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the
facc5810 2864 * styles will be aggregated and compressed. Defaults to TRUE.
96b9d512
DB
2865 * - 'browsers': An array containing information specifying which browsers
2866 * should load the CSS item. See drupal_pre_render_conditional_comments()
2867 * for details.
bb460ee7 2868 *
2c0571b8 2869 * @return
a0cca9a4 2870 * An array of queued cascading stylesheets.
eeb0bd86 2871 *
2872 * @see drupal_get_css()
2c0571b8 2873 */
a0cca9a4 2874function drupal_add_css($data = NULL, $options = NULL) {
8b63d832 2875 $css = &drupal_static(__FUNCTION__, array());
b86f45b1 2876
d5968378
AB
2877 // Construct the options, taking the defaults into consideration.
2878 if (isset($options)) {
2879 if (!is_array($options)) {
2880 $options = array('type' => $options);
2881 }
2882 }
2883 else {
2884 $options = array();
2885 }
2886
b86f45b1
SW
2887 // Create an array of CSS files for each media type first, since each type needs to be served
2888 // to the browser differently.
a0cca9a4 2889 if (isset($data)) {
df2cf40d 2890 $options += array(
1a5c71e2 2891 'type' => 'file',
facc5810
DB
2892 'group' => CSS_DEFAULT,
2893 'weight' => 0,
2894 'every_page' => FALSE,
df2cf40d 2895 'media' => 'all',
facc5810 2896 'preprocess' => TRUE,
1a5c71e2 2897 'data' => $data,
96b9d512
DB
2898 'browsers' => array(),
2899 );
2900 $options['browsers'] += array(
2901 'IE' => TRUE,
2902 '!IE' => TRUE,
ae2e4ccd 2903 );
df2cf40d 2904
facc5810
DB
2905 // Files with a query string cannot be preprocessed.
2906 if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
2907 $options['preprocess'] = FALSE;
2908 }
2909
1a5c71e2
DB
2910 // Always add a tiny value to the weight, to conserve the insertion order.
2911 $options['weight'] += count($css) / 1000;
2c0571b8 2912
1a5c71e2
DB
2913 // Add the data to the CSS array depending on the type.
2914 switch ($options['type']) {
1a5c71e2
DB
2915 case 'inline':
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.
2918 $css[] = $options;
2919 break;
6e3832f4
AB
2920 default:
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;
860947d3
H
2924 }
2925 }
a8ceb761 2926
2c0571b8
DB
2927 return $css;
2928}
2929
2930/**
767d72d4 2931 * Returns a themed representation of all stylesheets to attach to the page.
2fcaa6a9 2932 *
7b699125
DB
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.
2936 *
2937 * Themes may replace module-defined CSS files by adding a stylesheet with the
e3a52979 2938 * same filename. For example, themes/bartik/system-menus.css would replace
7b699125
DB
2939 * modules/system/system-menus.css. This allows themes to override complete
2940 * CSS files, rather than specific selectors, when necessary.
2941 *
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
2944 * module's.
2c0571b8
DB
2945 *
2946 * @param $css
2fcaa6a9
H
2947 * (optional) An array of CSS files. If no array is provided, the default
2948 * stylesheets array is used instead.
5a23b3fd
DB
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
2952 * been altered.
eeb0bd86 2953 *
2c0571b8
DB
2954 * @return
2955 * A string of XHTML CSS tags.
eeb0bd86 2956 *
2957 * @see drupal_add_css()
2c0571b8 2958 */
5a23b3fd 2959function drupal_get_css($css = NULL, $skip_alter = FALSE) {
b86f45b1 2960 if (!isset($css)) {
2c0571b8
DB
2961 $css = drupal_add_css();
2962 }
2963
242c47de 2964 // Allow modules and themes to alter the CSS items.
5a23b3fd
DB
2965 if (!$skip_alter) {
2966 drupal_alter('css', $css);
2967 }
1a5c71e2 2968
facc5810
DB
2969 // Sort CSS items, so that they appear in the correct order.
2970 uasort($css, 'drupal_sort_css_js');
1a5c71e2 2971
af3f94b3 2972 // Remove the overridden CSS files. Later CSS files override former ones.
1a5c71e2
DB
2973 $previous_item = array();
2974 foreach ($css as $key => $item) {
2975 if ($item['type'] == 'file') {
9b8c393d 2976 // If defined, force a unique basename for this file.
81644345 2977 $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']);
1a5c71e2
DB
2978 if (isset($previous_item[$basename])) {
2979 // Remove the previous item that shared the same base name.
2980 unset($css[$previous_item[$basename]]);
7b699125 2981 }
1a5c71e2
DB
2982 $previous_item[$basename] = $key;
2983 }
2984 }
2985
96b9d512
DB
2986 // Render the HTML needed to load the CSS.
2987 $styles = array(
2988 '#type' => 'styles',
2989 '#items' => $css,
2990 );
5a23b3fd
DB
2991
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);
2996
96b9d512
DB
2997 return drupal_render($styles);
2998}
2999
3000/**
767d72d4 3001 * Sorts CSS and JavaScript resources.
3002 *
3003 * Callback for uasort() within:
3004 * - drupal_get_css()
3005 * - drupal_get_js()
facc5810
DB
3006 *
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.
767d72d4 3010 *
3011 * @param $a
3012 * First item for comparison. The compared items should be associative arrays
3013 * of member items from drupal_add_css() or drupal_add_js().
3014 * @param $b
3015 * Second item for comparison.
3016 *
3017 * @see drupal_add_css()
3018 * @see drupal_add_js()
facc5810
DB
3019 */
3020function drupal_sort_css_js($a, $b) {
3021 // First order by group, so that, for example, all items in the CSS_SYSTEM
0e783334 3022 // group appear before items in the CSS_DEFAULT group, which appear before
facc5810
DB
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']) {
3026 return -1;
3027 }
3028 elseif ($a['group'] > $b['group']) {
3029 return 1;
3030 }
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']) {
3036 return -1;
3037 }
3038 elseif (!$a['every_page'] && $b['every_page']) {
3039 return 1;
3040 }
3041 // Finally, order by weight.
3042 elseif ($a['weight'] < $b['weight']) {
3043 return -1;
3044 }
3045 elseif ($a['weight'] > $b['weight']) {
3046 return 1;
3047 }
3048 else {
3049 return 0;
3050 }
3051}
3052
3053/**
96b9d512
DB