/[drupal]/drupal/includes/common.inc
ViewVC logotype

Contents of /drupal/includes/common.inc

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


Revision 1.1040 - (show annotations) (download) (as text)
Tue Nov 3 06:47:22 2009 UTC (3 weeks, 5 days ago) by webchick
Branch: MAIN
Changes since 1.1039: +180 -29 lines
File MIME type: text/x-php
#552478 by pwolanin, samj, dropcube, and sun: Improve link/header API and support on node/comment pages rel=canonical and rel=shortlink standards.
<
1 <?php
2 // $Id: common.inc,v 1.1039 2009/11/03 05:27:18 webchick Exp $
3
4 /**
5 * @file
6 * Common functions that many Drupal modules will need to reference.
7 *
8 * The functions that are critical and need to be available even when serving
9 * a cached page are instead located in bootstrap.inc.
10 */
11
12 /**
13 * @defgroup php_wrappers PHP wrapper functions
14 * @{
15 * Functions that are wrappers or custom implementations of PHP functions.
16 *
17 * Certain PHP functions should not be used in Drupal. Instead, Drupal's
18 * replacement functions should be used.
19 *
20 * For example, for improved or more secure UTF8-handling, or RFC-compliant
21 * handling of URLs in Drupal.
22 *
23 * For ease of use and memorizing, all these wrapper functions use the same name
24 * as the original PHP function, but prefixed with "drupal_". Beware, however,
25 * that not all wrapper functions support the same arguments as the original
26 * functions.
27 *
28 * You should always use these wrapper functions in your code.
29 *
30 * Wrong:
31 * @code
32 * $my_substring = substr($original_string, 0, 5);
33 * @endcode
34 *
35 * Correct:
36 * @code
37 * $my_substring = drupal_substr($original_string, 0, 5);
38 * @endcode
39 *
40 * @} End of "defgroup php_wrappers".
41 */
42
43 /**
44 * Error reporting level: display no errors.
45 */
46 define('ERROR_REPORTING_HIDE', 0);
47
48 /**
49 * Error reporting level: display errors and warnings.
50 */
51 define('ERROR_REPORTING_DISPLAY_SOME', 1);
52
53 /**
54 * Error reporting level: display all messages.
55 */
56 define('ERROR_REPORTING_DISPLAY_ALL', 2);
57
58 /**
59 * Return status for saving which involved creating a new item.
60 */
61 define('SAVED_NEW', 1);
62
63 /**
64 * Return status for saving which involved an update to an existing item.
65 */
66 define('SAVED_UPDATED', 2);
67
68 /**
69 * Return status for saving which deleted an existing item.
70 */
71 define('SAVED_DELETED', 3);
72
73 /**
74 * The default weight of system CSS files added to the page.
75 */
76 define('CSS_SYSTEM', -100);
77
78 /**
79 * The default weight of CSS files added to the page.
80 */
81 define('CSS_DEFAULT', 0);
82
83 /**
84 * The default weight of theme CSS files added to the page.
85 */
86 define('CSS_THEME', 100);
87
88 /**
89 * The weight of JavaScript libraries, settings or jQuery plugins being
90 * added to the page.
91 */
92 define('JS_LIBRARY', -100);
93
94 /**
95 * The default weight of JavaScript being added to the page.
96 */
97 define('JS_DEFAULT', 0);
98
99 /**
100 * The weight of theme JavaScript code being added to the page.
101 */
102 define('JS_THEME', 100);
103
104 /**
105 * Error code indicating that the request made by drupal_http_request() exceeded
106 * the specified timeout.
107 */
108 define('HTTP_REQUEST_TIMEOUT', 1);
109
110 /**
111 * Constants defining cache granularity for blocks and renderable arrays.
112 *
113 * Modules specify the caching patterns for their blocks using binary
114 * combinations of these constants in their hook_block_info():
115 * $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE;
116 * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is
117 * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
118 * implement
119 *
120 * The block cache is cleared in cache_clear_all(), and uses the same clearing
121 * policy than page cache (node, comment, user, taxonomy added or updated...).
122 * Blocks requiring more fine-grained clearing might consider disabling the
123 * built-in block cache (DRUPAL_NO_CACHE) and roll their own.
124 *
125 * Note that user 1 is excluded from block caching.
126 */
127
128 /**
129 * The block should not get cached. This setting should be used:
130 * - for simple blocks (notably those that do not perform any db query),
131 * where querying the db cache would be more expensive than directly generating
132 * the content.
133 * - for blocks that change too frequently.
134 */
135 define('DRUPAL_NO_CACHE', -1);
136
137 /**
138 * The block is handling its own caching in its hook_block_view(). From the
139 * perspective of the block cache system, this is equivalent to DRUPAL_NO_CACHE.
140 * Useful when time based expiration is needed or a site uses a node access
141 * which invalidates standard block cache.
142 */
143 define('DRUPAL_CACHE_CUSTOM', -2);
144
145 /**
146 * The block or element can change depending on the roles the user viewing the
147 * page belongs to. This is the default setting for blocks, used when the block
148 * does not specify anything.
149 */
150 define('DRUPAL_CACHE_PER_ROLE', 0x0001);
151
152 /**
153 * The block or element can change depending on the user viewing the page.
154 * This setting can be resource-consuming for sites with large number of users,
155 * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
156 */
157 define('DRUPAL_CACHE_PER_USER', 0x0002);
158
159 /**
160 * The block or element can change depending on the page being viewed.
161 */
162 define('DRUPAL_CACHE_PER_PAGE', 0x0004);
163
164 /**
165 * The block or element is the same for every user on every page where it is visible.
166 */
167 define('DRUPAL_CACHE_GLOBAL', 0x0008);
168
169 /**
170 * Add content to a specified region.
171 *
172 * @param $region
173 * Page region the content is added to.
174 * @param $data
175 * Content to be added.
176 */
177 function drupal_add_region_content($region = NULL, $data = NULL) {
178 static $content = array();
179
180 if (!is_null($region) && !is_null($data)) {
181 $content[$region][] = $data;
182 }
183 return $content;
184 }
185
186 /**
187 * Get assigned content for a given region.
188 *
189 * @param $region
190 * A specified region to fetch content for. If NULL, all regions will be
191 * returned.
192 * @param $delimiter
193 * Content to be inserted between imploded array elements.
194 */
195 function drupal_get_region_content($region = NULL, $delimiter = ' ') {
196 $content = drupal_add_region_content();
197 if (isset($region)) {
198 if (isset($content[$region]) && is_array($content[$region])) {
199 return implode($delimiter, $content[$region]);
200 }
201 }
202 else {
203 foreach (array_keys($content) as $region) {
204 if (is_array($content[$region])) {
205 $content[$region] = implode($delimiter, $content[$region]);
206 }
207 }
208 return $content;
209 }
210 }
211
212 /**
213 * Get the name of the currently active install profile.
214 *
215 * When this function is called during Drupal's initial installation process,
216 * the name of the profile that's about to be installed is stored in the global
217 * installation state. At all other times, the standard Drupal systems variable
218 * table contains the name of the current profile, and we can call variable_get()
219 * to determine what one is active.
220 *
221 * @return $profile
222 * The name of the install profile.
223 */
224 function drupal_get_profile() {
225 global $install_state;
226
227 if (isset($install_state['parameters']['profile'])) {
228 $profile = $install_state['parameters']['profile'];
229 }
230 else {
231 $profile = variable_get('install_profile', 'default');
232 }
233
234 return $profile;
235 }
236
237
238 /**
239 * Set the breadcrumb trail for the current page.
240 *
241 * @param $breadcrumb
242 * Array of links, starting with "home" and proceeding up to but not including
243 * the current page.
244 */
245 function drupal_set_breadcrumb($breadcrumb = NULL) {
246 $stored_breadcrumb = &drupal_static(__FUNCTION__);
247
248 if (!is_null($breadcrumb)) {
249 $stored_breadcrumb = $breadcrumb;
250 }
251 return $stored_breadcrumb;
252 }
253
254 /**
255 * Get the breadcrumb trail for the current page.
256 */
257 function drupal_get_breadcrumb() {
258 $breadcrumb = drupal_set_breadcrumb();
259
260 if (is_null($breadcrumb)) {
261 $breadcrumb = menu_get_active_breadcrumb();
262 }
263
264 return $breadcrumb;
265 }
266
267 /**
268 * Return a string containing RDF namespaces for the <html> tag of an XHTML
269 * page.
270 */
271 function drupal_get_rdf_namespaces() {
272 // Serialize the RDF namespaces used in RDFa annotation.
273 $xml_rdf_namespaces = array();
274 foreach (module_invoke_all('rdf_namespaces') as $prefix => $uri) {
275 $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
276 }
277 return implode("\n ", $xml_rdf_namespaces);
278 }
279
280 /**
281 * Add output to the head tag of the HTML page.
282 *
283 * This function can be called as long the headers aren't sent. Pass no
284 * arguments (or NULL for both) to retrieve the currently stored elements.
285 *
286 * @param $data
287 * A renderable array. If the '#type' key is not set then 'html_tag' will be
288 * added as the default '#type'.
289 * @param $key
290 * A unique string key to allow implementations of hook_html_head_alter() to
291 * identify the element in $data. Required if $data is not NULL.
292 *
293 * @return
294 * An array of all stored HEAD elements.
295 *
296 * @see theme_html_tag()
297 */
298 function drupal_add_html_head($data = NULL, $key = NULL) {
299 $stored_head = &drupal_static(__FUNCTION__);
300
301 if (!isset($stored_head)) {
302 // Make sure the defaults, including Content-Type, come first.
303 $stored_head = _drupal_default_html_head();
304 }
305
306 if (isset($data) && isset($key)) {
307 if (!isset($data['#type'])) {
308 $data['#type'] = 'html_tag';
309 }
310 $stored_head[$key] = $data;
311 }
312 return $stored_head;
313 }
314
315 /**
316 * Returns elements that are always displayed in the HEAD tag of the HTML page.
317 */
318 function _drupal_default_html_head() {
319 // Add default elements. Make sure the Content-Type comes first because the
320 // IE browser may be vulnerable to XSS via encoding attacks from any content
321 // that comes before this META tag, such as a TITLE tag.
322 $elements['system_meta_content_type'] = array(
323 '#type' => 'html_tag',
324 '#tag' => 'meta',
325 '#attributes' => array(
326 'http-equiv' => 'Content-Type',
327 'content' => 'text/html; charset=utf-8',
328 ),
329 // Security: This always has to be output first.
330 '#weight' => -1000,
331 );
332 // Show Drupal and the major version number in the META GENERATOR tag.
333 // Get the major version.
334 list($version, ) = explode('.', VERSION);
335 $elements['system_meta_generator'] = array(
336 '#type' => 'html_tag',
337 '#tag' => 'meta',
338 '#attributes' => array(
339 'name' => 'Generator',
340 'content' => 'Drupal ' . $version . ' (http://drupal.org)',
341 ),
342 );
343 // Also send the generator in the HTTP header.
344 $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
345 return $elements;
346 }
347
348 /**
349 * Retrieve output to be displayed in the HEAD tag of the HTML page.
350 */
351 function drupal_get_html_head() {
352 $elements = drupal_add_html_head();
353 drupal_alter('html_head', $elements);
354 return drupal_render($elements);
355 }
356
357 /**
358 * Reset the static variable which holds the aliases mapped for this request.
359 */
360 function drupal_clear_path_cache() {
361 drupal_lookup_path('wipe');
362 }
363
364 /**
365 * Add a feed URL for the current page.
366 *
367 * This function can be called as long the HTML header hasn't been sent.
368 *
369 * @param $url
370 * A url for the feed.
371 * @param $title
372 * The title of the feed.
373 */
374 function drupal_add_feed($url = NULL, $title = '') {
375 $stored_feed_links = &drupal_static(__FUNCTION__, array());
376
377 if (isset($url)) {
378 $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
379
380 drupal_add_html_head_link(array('rel' => 'alternate',
381 'type' => 'application/rss+xml',
382 'title' => $title,
383 'href' => $url));
384 }
385 return $stored_feed_links;
386 }
387
388 /**
389 * Get the feed URLs for the current page.
390 *
391 * @param $delimiter
392 * A delimiter to split feeds by.
393 */
394 function drupal_get_feeds($delimiter = "\n") {
395 $feeds = drupal_add_feed();
396 return implode($feeds, $delimiter);
397 }
398
399 /**
400 * @name HTTP handling
401 * @{
402 * Functions to properly handle HTTP responses.
403 */
404
405 /**
406 * Process a URL query parameter array to remove unwanted elements.
407 *
408 * @param $query
409 * (optional) An array to be processed. Defaults to $_GET.
410 * @param $exclude
411 * (optional) A list of $query array keys to remove. Use "parent[child]" to
412 * exclude nested items. Defaults to array('q').
413 * @param $parent
414 * Internal use only. Used to build the $query array key for nested items.
415 *
416 * @return
417 * An array containing query parameters, which can be used for url().
418 */
419 function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') {
420 // Set defaults, if none given.
421 if (!isset($query)) {
422 $query = $_GET;
423 }
424 // If $exclude is empty, there is nothing to filter.
425 if (empty($exclude)) {
426 return $query;
427 }
428 elseif (!$parent) {
429 $exclude = array_flip($exclude);
430 }
431
432 $params = array();
433 foreach ($query as $key => $value) {
434 $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
435 if (isset($exclude[$string_key])) {
436 continue;
437 }
438
439 if (is_array($value)) {
440 $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
441 }
442 else {
443 $params[$key] = $value;
444 }
445 }
446
447 return $params;
448 }
449
450 /**
451 * Split an URL-encoded query string into an array.
452 *
453 * @param $query
454 * The query string to split.
455 *
456 * @return
457 * An array of url decoded couples $param_name => $value.
458 */
459 function drupal_get_query_array($query) {
460 $result = array();
461 if (!empty($query)) {
462 foreach (explode('&', $query) as $param) {
463 $param = explode('=', $param);
464 $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
465 }
466 }
467 return $result;
468 }
469
470 /**
471 * Parse an array into a valid, rawurlencoded query string.
472 *
473 * This differs from http_build_query() as we need to rawurlencode() (instead of
474 * urlencode()) all query parameters.
475 *
476 * @param $query
477 * The query parameter array to be processed, e.g. $_GET.
478 * @param $parent
479 * Internal use only. Used to build the $query array key for nested items.
480 *
481 * @return
482 * A rawurlencoded string which can be used as or appended to the URL query
483 * string.
484 *
485 * @see drupal_get_query_parameters()
486 * @ingroup php_wrappers
487 */
488 function drupal_http_build_query(array $query, $parent = '') {
489 $params = array();
490
491 foreach ($query as $key => $value) {
492 $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key));
493
494 // Recurse into children.
495 if (is_array($value)) {
496 $params[] = drupal_http_build_query($value, $key);
497 }
498 // If a query parameter value is NULL, only append its key.
499 elseif (!isset($value)) {
500 $params[] = $key;
501 }
502 else {
503 // For better readability of paths in query strings, we decode slashes.
504 // @see drupal_encode_path()
505 $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
506 }
507 }
508
509 return implode('&', $params);
510 }
511
512 /**
513 * Prepare a 'destination' URL query parameter for use in combination with drupal_goto().
514 *
515 * Used to direct the user back to the referring page after completing a form.
516 * By default the current URL is returned. If a destination exists in the
517 * previous request, that destination is returned. As such, a destination can
518 * persist across multiple pages.
519 *
520 * @see drupal_goto()
521 */
522 function drupal_get_destination() {
523 $destination = &drupal_static(__FUNCTION__);
524
525 if (isset($destination)) {
526 return $destination;
527 }
528
529 if (isset($_GET['destination'])) {
530 $destination = array('destination' => $_GET['destination']);
531 }
532 else {
533 $path = $_GET['q'];
534 $query = drupal_http_build_query(drupal_get_query_parameters());
535 if ($query != '') {
536 $path .= '?' . $query;
537 }
538 $destination = array('destination' => $path);
539 }
540 return $destination;
541 }
542
543 /**
544 * Wrapper around parse_url() to parse a system URL string into an associative array, suitable for url().
545 *
546 * This function should only be used for URLs that have been generated by the
547 * system, resp. url(). It should not be used for URLs that come from external
548 * sources, or URLs that link to external resources.
549 *
550 * The returned array contains a 'path' that may be passed separately to url().
551 * For example:
552 * @code
553 * $options = drupal_parse_url($_GET['destination']);
554 * $my_url = url($options['path'], $options);
555 * $my_link = l('Example link', $options['path'], $options);
556 * @endcode
557 *
558 * This is required, because url() does not support relative URLs containing a
559 * query string or fragment in its $path argument. Instead, any query string
560 * needs to be parsed into an associative query parameter array in
561 * $options['query'] and the fragment into $options['fragment'].
562 *
563 * @param $url
564 * The URL string to parse, f.e. $_GET['destination'].
565 *
566 * @return
567 * An associative array containing the keys:
568 * - 'path': The path of the URL. If the given $url is external, this includes
569 * the scheme and host.
570 * - 'query': An array of query parameters of $url, if existent.
571 * - 'fragment': The fragment of $url, if existent.
572 *
573 * @see url()
574 * @see drupal_goto()
575 * @ingroup php_wrappers
576 */
577 function drupal_parse_url($url) {
578 $options = array(
579 'path' => NULL,
580 'query' => array(),
581 'fragment' => '',
582 );
583
584 // External URLs: not using parse_url() here, so we do not have to rebuild
585 // the scheme, host, and path without having any use for it.
586 if (strpos($url, '://') !== FALSE) {
587 // Split off everything before the query string into 'path'.
588 $parts = explode('?', $url);
589 $options['path'] = $parts[0];
590 // If there is a query string, transform it into keyed query parameters.
591 if (isset($parts[1])) {
592 $query_parts = explode('#', $parts[1]);
593 parse_str($query_parts[0], $options['query']);
594 // Take over the fragment, if there is any.
595 if (isset($query_parts[1])) {
596 $options['fragment'] = $query_parts[1];
597 }
598 }
599 }
600 // Internal URLs.
601 else {
602 // parse_url() does not support relative URLs, so make it absolute. E.g. the
603 // relative URL "foo/bar:1" isn't properly parsed.
604 $parts = parse_url('http://example.com/' . $url);
605 // Strip the leading slash that was just added.
606 $options['path'] = substr($parts['path'], 1);
607 if (isset($parts['query'])) {
608 parse_str($parts['query'], $options['query']);
609 }
610 if (isset($parts['fragment'])) {
611 $options['fragment'] = $parts['fragment'];
612 }
613 }
614 // The 'q' parameter contains the path of the current page if clean URLs are
615 // disabled. It overrides the 'path' of the URL when present, even if clean
616 // URLs are enabled, due to how Apache rewriting rules work.
617 if (isset($options['query']['q'])) {
618 $options['path'] = $options['query']['q'];
619 unset($options['query']['q']);
620 }
621
622 return $options;
623 }
624
625 /**
626 * Encode a path for usage in a URL.
627 *
628 * Wrapper around rawurlencode() which avoids Apache quirks. Should be used when
629 * placing arbitrary data into the path component of an URL.
630 *
631 * Do not use this function to pass a path to url(). url() properly handles
632 * and encodes paths internally.
633 * This function should only be used on paths, not on query string arguments.
634 * Otherwise, unwanted double encoding will occur.
635 *
636 * Notes:
637 * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature'
638 * in Apache where it 404s on any path containing '%2F'.
639 * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean
640 * URLs are used, which are interpreted as delimiters by PHP. These
641 * characters are double escaped so PHP will still see the encoded version.
642 * - With clean URLs, Apache changes '//' to '/', so every second slash is
643 * double escaped.
644 *
645 * @param $path
646 * The URL path component to encode.
647 */
648 function drupal_encode_path($path) {
649 if (!empty($GLOBALS['conf']['clean_url'])) {
650 return str_replace(array('%2F', '%26', '%23', '//'),
651 array('/', '%2526', '%2523', '/%252F'),
652 rawurlencode($path)
653 );
654 }
655 else {
656 return str_replace('%2F', '/', rawurlencode($path));
657 }
658 }
659
660 /**
661 * Send the user to a different Drupal page.
662 *
663 * This issues an on-site HTTP redirect. The function makes sure the redirected
664 * URL is formatted correctly.
665 *
666 * Usually the redirected URL is constructed from this function's input
667 * parameters. However you may override that behavior by setting a
668 * destination in either the $_REQUEST-array (i.e. by using
669 * the query string of an URI) This is used to direct the user back to
670 * the proper page after completing a form. For example, after editing
671 * a post on the 'admin/content'-page or after having logged on using the
672 * 'user login'-block in a sidebar. The function drupal_get_destination()
673 * can be used to help set the destination URL.
674 *
675 * Drupal will ensure that messages set by drupal_set_message() and other
676 * session data are written to the database before the user is redirected.
677 *
678 * This function ends the request; use it instead of a return in your menu
679 * callback.
680 *
681 * @param $path
682 * A Drupal path or a full URL.
683 * @param $options
684 * An associative array of additional URL options to pass to url().
685 * @param $http_response_code
686 * Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
687 * - 301 Moved Permanently (the recommended value for most redirects)
688 * - 302 Found (default in Drupal and PHP, sometimes used for spamming search
689 * engines)
690 * - 303 See Other
691 * - 304 Not Modified
692 * - 305 Use Proxy
693 * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
694 * Note: Other values are defined by RFC 2616, but are rarely used and poorly
695 * supported.
696 *
697 * @see drupal_get_destination()
698 * @see url()
699 */
700 function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
701 // A destination in $_GET always overrides the function arguments.
702 if (isset($_GET['destination'])) {
703 $destination = drupal_parse_url(urldecode($_GET['destination']));
704 $path = $destination['path'];
705 $options['query'] = $destination['query'];
706 $options['fragment'] = $destination['fragment'];
707 }
708
709 drupal_alter('drupal_goto', $path, $options, $http_response_code);
710
711 // The 'Location' HTTP header must be absolute.
712 $options['absolute'] = TRUE;
713
714 $url = url($path, $options);
715
716 header('Location: ' . $url, TRUE, $http_response_code);
717
718 // The "Location" header sends a redirect status code to the HTTP daemon. In
719 // some cases this can be wrong, so we make sure none of the code below the
720 // drupal_goto() call gets executed upon redirection.
721 drupal_exit($url);
722 }
723
724 /**
725 * Deliver a "site is under maintenance" message to the browser.
726 *
727 * Page callback functions wanting to report a "site offline" message should
728 * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
729 * functions that are invoked in contexts where that return value might not
730 * bubble up to menu_execute_active_handler() should call drupal_site_offline().
731 */
732 function drupal_site_offline() {
733 drupal_deliver_page(MENU_SITE_OFFLINE);
734 }
735
736 /**
737 * Deliver a "page not found" error to the browser.
738 *
739 * Page callback functions wanting to report a "page not found" message should
740 * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
741 * functions that are invoked in contexts where that return value might not
742 * bubble up to menu_execute_active_handler() should call drupal_not_found().
743 */
744 function drupal_not_found() {
745 drupal_deliver_page(MENU_NOT_FOUND);
746 }
747
748 /**
749 * Deliver a "access denied" error to the browser.
750 *
751 * Page callback functions wanting to report an "access denied" message should
752 * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
753 * functions that are invoked in contexts where that return value might not
754 * bubble up to menu_execute_active_handler() should call drupal_access_denied().
755 */
756 function drupal_access_denied() {
757 drupal_deliver_page(MENU_ACCESS_DENIED);
758 }
759
760 /**
761 * Perform an HTTP request.
762 *
763 * This is a flexible and powerful HTTP client implementation. Correctly
764 * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
765 *
766 * @param $url
767 * A string containing a fully qualified URI.
768 * @param $options
769 * (optional) An array which can have one or more of following keys:
770 * - headers
771 * An array containing request headers to send as name/value pairs.
772 * - method
773 * A string containing the request method. Defaults to 'GET'.
774 * - data
775 * A string containing the request body. Defaults to NULL.
776 * - max_redirects
777 * An integer representing how many times a redirect may be followed.
778 * Defaults to 3.
779 * - timeout
780 * A float representing the maximum number of seconds the function call
781 * may take. The default is 30 seconds. If a timeout occurs, the error
782 * code is set to the HTTP_REQUEST_TIMEOUT constant.
783 * @return
784 * An object which can have one or more of the following parameters:
785 * - request
786 * A string containing the request body that was sent.
787 * - code
788 * An integer containing the response status code, or the error code if
789 * an error occurred.
790 * - protocol
791 * The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
792 * - status_message
793 * The status message from the response, if a response was received.
794 * - redirect_code
795 * If redirected, an integer containing the initial response status code.
796 * - redirect_url
797 * If redirected, a string containing the redirection location.
798 * - error
799 * If an error occurred, the error message. Otherwise not set.
800 * - headers
801 * An array containing the response headers as name/value pairs.
802 * - data
803 * A string containing the response body that was received.
804 */
805 function drupal_http_request($url, array $options = array()) {
806 global $db_prefix;
807
808 $result = new stdClass();
809
810 // Parse the URL and make sure we can handle the schema.
811 $uri = @parse_url($url);
812
813 if ($uri == FALSE) {
814 $result->error = 'unable to parse URL';
815 $result->code = -1001;
816 return $result;
817 }
818
819 if (!isset($uri['scheme'])) {
820 $result->error = 'missing schema';
821 $result->code = -1002;
822 return $result;
823 }
824
825 timer_start(__FUNCTION__);
826
827 // Merge the default options.
828 $options += array(
829 'headers' => array(),
830 'method' => 'GET',
831 'data' => NULL,
832 'max_redirects' => 3,
833 'timeout' => 30,
834 );
835
836 switch ($uri['scheme']) {
837 case 'http':
838 $port = isset($uri['port']) ? $uri['port'] : 80;
839 $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
840 $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
841 break;
842 case 'https':
843 // Note: Only works when PHP is compiled with OpenSSL support.
844 $port = isset($uri['port']) ? $uri['port'] : 443;
845 $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
846 $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
847 break;
848 default:
849 $result->error = 'invalid schema ' . $uri['scheme'];
850 $result->code = -1003;
851 return $result;
852 }
853
854 // Make sure the socket opened properly.
855 if (!$fp) {
856 // When a network error occurs, we use a negative number so it does not
857 // clash with the HTTP status codes.
858 $result->code = -$errno;
859 $result->error = trim($errstr);
860
861 // Mark that this request failed. This will trigger a check of the web
862 // server's ability to make outgoing HTTP requests the next time that
863 // requirements checking is performed.
864 // @see system_requirements()
865 variable_set('drupal_http_request_fails', TRUE);
866
867 return $result;
868 }
869
870 // Construct the path to act on.
871 $path = isset($uri['path']) ? $uri['path'] : '/';
872 if (isset($uri['query'])) {
873 $path .= '?' . $uri['query'];
874 }
875
876 // Merge the default headers.
877 $options['headers'] += array(
878 'User-Agent' => 'Drupal (+http://drupal.org/)',
879 );
880
881 // RFC 2616: "non-standard ports MUST, default ports MAY be included".
882 // We don't add the standard port to prevent from breaking rewrite rules
883 // checking the host that do not take into account the port number.
884 $options['headers']['Host'] = $host;
885
886 // Only add Content-Length if we actually have any content or if it is a POST
887 // or PUT request. Some non-standard servers get confused by Content-Length in
888 // at least HEAD/GET requests, and Squid always requires Content-Length in
889 // POST/PUT requests.
890 $content_length = strlen($options['data']);
891 if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
892 $options['headers']['Content-Length'] = $content_length;
893 }
894
895 // If the server URL has a user then attempt to use basic authentication.
896 if (isset($uri['user'])) {
897 $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
898 }
899
900 // If the database prefix is being used by SimpleTest to run the tests in a copied
901 // database then set the user-agent header to the database prefix so that any
902 // calls to other Drupal pages will run the SimpleTest prefixed database. The
903 // user-agent is used to ensure that multiple testing sessions running at the
904 // same time won't interfere with each other as they would if the database
905 // prefix were stored statically in a file or database variable.
906 if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
907 $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
908 }
909
910 $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
911 foreach ($options['headers'] as $name => $value) {
912 $request .= $name . ': ' . trim($value) . "\r\n";
913 }
914 $request .= "\r\n" . $options['data'];
915 $result->request = $request;
916
917 fwrite($fp, $request);
918
919 // Fetch response.
920 $response = '';
921 while (!feof($fp)) {
922 // Calculate how much time is left of the original timeout value.
923 $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
924 if ($timeout <= 0) {
925 $result->code = HTTP_REQUEST_TIMEOUT;
926 $result->error = 'request timed out';
927 return $result;
928 }
929 stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
930 $response .= fread($fp, 1024);
931 }
932 fclose($fp);
933
934 // Parse response headers from the response body.
935 list($response, $result->data) = explode("\r\n\r\n", $response, 2);
936 $response = preg_split("/\r\n|\n|\r/", $response);
937
938 // Parse the response status line.
939 list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
940 $result->protocol = $protocol;
941 $result->status_message = $status_message;
942
943 $result->headers = array();
944
945 // Parse the response headers.
946 while ($line = trim(array_shift($response))) {
947 list($header, $value) = explode(':', $line, 2);
948 if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
949 // RFC 2109: the Set-Cookie response header comprises the token Set-
950 // Cookie:, followed by a comma-separated list of one or more cookies.
951 $result->headers[$header] .= ',' . trim($value);
952 }
953 else {
954 $result->headers[$header] = trim($value);
955 }
956 }
957
958 $responses = array(
959 100 => 'Continue',
960 101 => 'Switching Protocols',
961 200 => 'OK',
962 201 => 'Created',
963 202 => 'Accepted',
964 203 => 'Non-Authoritative Information',
965 204 => 'No Content',
966 205 => 'Reset Content',
967 206 => 'Partial Content',
968 300 => 'Multiple Choices',
969 301 => 'Moved Permanently',
970 302 => 'Found',
971 303 => 'See Other',
972 304 => 'Not Modified',
973 305 => 'Use Proxy',
974 307 => 'Temporary Redirect',
975 400 => 'Bad Request',
976 401 => 'Unauthorized',
977 402 => 'Payment Required',
978 403 => 'Forbidden',
979 404 => 'Not Found',
980 405 => 'Method Not Allowed',
981 406 => 'Not Acceptable',
982 407 => 'Proxy Authentication Required',
983 408 => 'Request Time-out',
984 409 => 'Conflict',
985 410 => 'Gone',
986 411 => 'Length Required',
987 412 => 'Precondition Failed',
988 413 => 'Request Entity Too Large',
989 414 => 'Request-URI Too Large',
990 415 => 'Unsupported Media Type',
991 416 => 'Requested range not satisfiable',
992 417 => 'Expectation Failed',
993 500 => 'Internal Server Error',
994 501 => 'Not Implemented',
995 502 => 'Bad Gateway',
996 503 => 'Service Unavailable',
997 504 => 'Gateway Time-out',
998 505 => 'HTTP Version not supported',
999 );
1000 // RFC 2616 states that all unknown HTTP codes must be treated the same as the
1001 // base code in their class.
1002 if (!isset($responses[$code])) {
1003 $code = floor($code / 100) * 100;
1004 }
1005 $result->code = $code;
1006
1007 switch ($code) {
1008 case 200: // OK
1009 case 304: // Not modified
1010 break;
1011 case 301: // Moved permanently
1012 case 302: // Moved temporarily
1013 case 307: // Moved temporarily
1014 $location = $result->headers['Location'];
1015 $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
1016 if ($options['timeout'] <= 0) {
1017 $result->code = HTTP_REQUEST_TIMEOUT;
1018 $result->error = 'request timed out';
1019 }
1020 elseif ($options['max_redirects']) {
1021 // Redirect to the new location.
1022 $options['max_redirects']--;
1023 $result = drupal_http_request($location, $options);
1024 $result->redirect_code = $code;
1025 }
1026 $result->redirect_url = $location;
1027 break;
1028 default:
1029 $result->error = $status_message;
1030 }
1031
1032 return $result;
1033 }
1034 /**
1035 * @} End of "HTTP handling".
1036 */
1037
1038 /**
1039 * Custom PHP error handler.
1040 *
1041 * @param $error_level
1042 * The level of the error raised.
1043 * @param $message
1044 * The error message.
1045 * @param $filename
1046 * The filename that the error was raised in.
1047 * @param $line
1048 * The line number the error was raised at.
1049 * @param $context
1050 * An array that points to the active symbol table at the point the error occurred.
1051 */
1052 function _drupal_error_handler($error_level, $message, $filename, $line, $context) {
1053 if ($error_level & error_reporting()) {
1054 // All these constants are documented at http://php.net/manual/en/errorfunc.constants.php
1055 $types = array(
1056 E_ERROR => 'Error',
1057 E_WARNING => 'Warning',
1058 E_PARSE => 'Parse error',
1059 E_NOTICE => 'Notice',
1060 E_CORE_ERROR => 'Core error',
1061 E_CORE_WARNING => 'Core warning',
1062 E_COMPILE_ERROR => 'Compile error',
1063 E_COMPILE_WARNING => 'Compile warning',
1064 E_USER_ERROR => 'User error',
1065 E_USER_WARNING => 'User warning',
1066 E_USER_NOTICE => 'User notice',
1067 E_STRICT => 'Strict warning',
1068 E_RECOVERABLE_ERROR => 'Recoverable fatal error'
1069 );
1070 $caller = _drupal_get_last_caller(debug_backtrace());
1071
1072 // We treat recoverable errors as fatal.
1073 _drupal_log_error(array(
1074 '%type' => isset($types[$error_level]) ? $types[$error_level] : 'Unknown error',
1075 '%message' => $message,
1076 '%function' => $caller['function'],
1077 '%file' => $caller['file'],
1078 '%line' => $caller['line'],
1079 ), $error_level == E_RECOVERABLE_ERROR);
1080 }
1081 }
1082
1083 /**
1084 * Custom PHP exception handler.
1085 *
1086 * Uncaught exceptions are those not enclosed in a try/catch block. They are
1087 * always fatal: the execution of the script will stop as soon as the exception
1088 * handler exits.
1089 *
1090 * @param $exception
1091 * The exception object that was thrown.
1092 */
1093 function _drupal_exception_handler($exception) {
1094 // Log the message to the watchdog and return an error page to the user.
1095 _drupal_log_error(_drupal_decode_exception($exception), TRUE);
1096 }
1097
1098 /**
1099 * Decode an exception, especially to retrive the correct caller.
1100 *
1101 * @param $exception
1102 * The exception object that was thrown.
1103 * @return An error in the format expected by _drupal_log_error().
1104 */
1105 function _drupal_decode_exception($exception) {
1106 $message = $exception->getMessage();
1107
1108 $backtrace = $exception->getTrace();
1109 // Add the line throwing the exception to the backtrace.
1110 array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
1111
1112 // For PDOException errors, we try to return the initial caller,
1113 // skipping internal functions of the database layer.
1114 if ($exception instanceof PDOException) {
1115 // The first element in the stack is the call, the second element gives us the caller.
1116 // We skip calls that occurred in one of the classes of the database layer
1117 // or in one of its global functions.
1118 $db_functions = array('db_query', 'db_query_range');
1119 while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
1120 ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
1121 in_array($caller['function'], $db_functions))) {
1122 // We remove that call.
1123 array_shift($backtrace);
1124 }
1125 if (isset($exception->query_string, $exception->args)) {
1126 $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
1127 }
1128 }
1129 $caller = _drupal_get_last_caller($backtrace);
1130
1131 return array(
1132 '%type' => get_class($exception),
1133 '%message' => $message,
1134 '%function' => $caller['function'],
1135 '%file' => $caller['file'],
1136 '%line' => $caller['line'],
1137 );
1138 }
1139
1140 /**
1141 * Log a PHP error or exception, display an error page in fatal cases.
1142 *
1143 * @param $error
1144 * An array with the following keys: %type, %message, %function, %file, %line.
1145 * @param $fatal
1146 * TRUE if the error is fatal.
1147 */
1148 function _drupal_log_error($error, $fatal = FALSE) {
1149 // Initialize a maintenance theme if the boostrap was not complete.
1150 // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
1151 if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
1152 unset($GLOBALS['theme']);
1153 if (!defined('MAINTENANCE_MODE')) {
1154 define('MAINTENANCE_MODE', 'error');
1155 }
1156 drupal_maintenance_theme();
1157 }
1158
1159 // When running inside the testing framework, we relay the errors
1160 // to the tested site by the way of HTTP headers.
1161 if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
1162 // $number does not use drupal_static as it should not be reset
1163 // as it uniquely identifies each PHP error.
1164 static $number = 0;
1165 $assertion = array(
1166 $error['%message'],
1167 $error['%type'],
1168 array(
1169 'function' => $error['%function'],
1170 'file' => $error['%file'],
1171 'line' => $error['%line'],
1172 ),
1173 );
1174 header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
1175 $number++;
1176 }
1177
1178 try {
1179 watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
1180 }
1181 catch (Exception $e) {
1182 // Ignore any additional watchdog exception, as that probably means
1183 // that the database was not initialized correctly.
1184 }
1185
1186 if ($fatal) {
1187 drupal_add_http_header('500 Service unavailable (with message)');
1188 }
1189
1190 if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
1191 if ($fatal) {
1192 // When called from JavaScript, simply output the error message.
1193 print t('%type: %message in %function (line %line of %file).', $error);
1194 exit;
1195 }
1196 }
1197 else {
1198 // Display the message if the current error reporting level allows this type
1199 // of message to be displayed, and unconditionnaly in update.php.
1200 $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL);
1201 $display_error = $error_level == ERROR_REPORTING_DISPLAY_ALL || ($error_level == ERROR_REPORTING_DISPLAY_SOME && $error['%type'] != 'Notice');
1202 if ($display_error || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) {
1203 $class = 'error';
1204
1205 // If error type is 'User notice' then treat it as debug information
1206 // instead of an error message, see dd().
1207 if ($error['%type'] == 'User notice') {
1208 $error['%type'] = 'Debug';
1209 $class = 'status';
1210 }
1211
1212 drupal_set_message(t('%type: %message in %function (line %line of %file).', $error), $class);
1213 }
1214
1215 if ($fatal) {
1216 drupal_set_title(t('Error'));
1217 // We fallback to a maintenance page at this point, because the page generation
1218 // itself can generate errors.
1219 print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.')));
1220 exit;
1221 }
1222 }
1223 }
1224
1225 /**
1226 * Gets the last caller from a backtrace.
1227 *
1228 * @param $backtrace
1229 * A standard PHP backtrace.
1230 * @return
1231 * An associative array with keys 'file', 'line' and 'function'.
1232 */
1233 function _drupal_get_last_caller($backtrace) {
1234 // Errors that occur inside PHP internal functions do not generate
1235 // information about file and line. Ignore black listed functions.
1236 $blacklist = array('debug');
1237 while (($backtrace && !isset($backtrace[0]['line'])) ||
1238 (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) {
1239 array_shift($backtrace);
1240 }
1241
1242 // The first trace is the call itself.
1243 // It gives us the line and the file of the last call.
1244 $call = $backtrace[0];
1245
1246 // The second call give us the function where the call originated.
1247 if (isset($backtrace[1])) {
1248 if (isset($backtrace[1]['class'])) {
1249 $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
1250 }
1251 else {
1252 $call['function'] = $backtrace[1]['function'] . '()';
1253 }
1254 }
1255 else {
1256 $call['function'] = 'main()';
1257 }
1258 return $call;
1259 }
1260
1261 function _fix_gpc_magic(&$item) {
1262 if (is_array($item)) {
1263 array_walk($item, '_fix_gpc_magic');
1264 }
1265 else {
1266 $item = stripslashes($item);
1267 }
1268 }
1269
1270 /**
1271 * Helper function to strip slashes from $_FILES skipping over the tmp_name keys
1272 * since PHP generates single backslashes for file paths on Windows systems.
1273 *
1274 * tmp_name does not have backslashes added see
1275 * http://php.net/manual/en/features.file-upload.php#42280
1276 */
1277 function _fix_gpc_magic_files(&$item, $key) {
1278 if ($key != 'tmp_name') {
1279 if (is_array($item)) {
1280 array_walk($item, '_fix_gpc_magic_files');
1281 }
1282 else {
1283 $item = stripslashes($item);
1284 }
1285 }
1286 }
1287
1288 /**
1289 * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
1290 */
1291 function fix_gpc_magic() {
1292 $fixed = &drupal_static(__FUNCTION__, FALSE);
1293 if (!$fixed && ini_get('magic_quotes_gpc')) {
1294 array_walk($_GET, '_fix_gpc_magic');
1295 array_walk($_POST, '_fix_gpc_magic');
1296 array_walk($_COOKIE, '_fix_gpc_magic');
1297 array_walk($_REQUEST, '_fix_gpc_magic');
1298 array_walk($_FILES, '_fix_gpc_magic_files');
1299 $fixed = TRUE;
1300 }
1301 }
1302
1303 /**
1304 * Translate strings to the page language or a given language.
1305 *
1306 * Human-readable text that will be displayed somewhere within a page should
1307 * be run through the t() function.
1308 *
1309 * Examples:
1310 * @code
1311 * if (!$info || !$info['extension']) {
1312 * form_set_error('picture_upload', t('The uploaded file was not an image.'));
1313 * }
1314 *
1315 * $form['submit'] = array(
1316 * '#type' => 'submit',
1317 * '#value' => t('Log in'),
1318 * );
1319 * @endcode
1320 *
1321 * Any text within t() can be extracted by translators and changed into
1322 * the equivalent text in their native language.
1323 *
1324 * Special variables called "placeholders" are used to signal dynamic
1325 * information in a string which should not be translated. Placeholders
1326 * can also be used for text that may change from time to time (such as
1327 * link paths) to be changed without requiring updates to translations.
1328 *
1329 * For example:
1330 * @code
1331 * $output = t('There are currently %members and %visitors online.', array(
1332 * '%members' => format_plural($total_users, '1 user', '@count users'),
1333 * '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
1334 * @endcode
1335 *
1336 * There are three styles of placeholders:
1337 * - !variable, which indicates that the text should be inserted as-is. This is
1338 * useful for inserting variables into things like e-mail.
1339 * @code
1340 * $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE))));
1341 * @endcode
1342 *
1343 * - @variable, which indicates that the text should be run through
1344 * check_plain, to escape HTML characters. Use this for any output that's
1345 * displayed within a Drupal page.
1346 * @code
1347 * drupal_set_title($title = t("@name's blog", array('@name' => format_username($account))), PASS_THROUGH);
1348 * @endcode
1349 *
1350 * - %variable, which indicates that the string should be HTML escaped and
1351 * highlighted with theme_placeholder() which shows up by default as
1352 * <em>emphasized</em>.
1353 * @code
1354 * $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => format_username($user), '%name-to' => format_username($account)));
1355 * @endcode
1356 *
1357 * When using t(), try to put entire sentences and strings in one t() call.
1358 * This makes it easier for translators, as it provides context as to what
1359 * each word refers to. HTML markup within translation strings is allowed, but
1360 * should be avoided if possible. The exception are embedded links; link
1361 * titles add a context for translators, so should be kept in the main string.
1362 *
1363 * Here is an example of incorrect usage of t():
1364 * @code
1365 * $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact')));
1366 * @endcode
1367 *
1368 * Here is an example of t() used correctly:
1369 * @code
1370 * $output .= '<p>' . t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) . '</p>';
1371 * @endcode
1372 *
1373 * Avoid escaping quotation marks wherever possible.
1374 *
1375