Spike PHPCoverage Details: common.inc

Line #FrequencySource Line
1 <?php
2 // $Id: common.inc,v 1.762 2008/03/31 20:50:05 dries 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  * Return status for saving which involved creating a new item.
14  */
15 define('SAVED_NEW', 1);
16 
17 /**
18  * Return status for saving which involved an update to an existing item.
19  */
20 define('SAVED_UPDATED', 2);
21 
22 /**
23  * Return status for saving which deleted an existing item.
24  */
25 define('SAVED_DELETED', 3);
26 
27 /**
28  * Set content for a specified region.
29  *
30  * @param $region
31  *   Page region the content is assigned to.
32  * @param $data
33  *   Content to be set.
34  */
35 function drupal_set_content($region = NULL, $data = NULL) {
36   static $content = array();
37 
38   if (!is_null($region) && !is_null($data)) {
39     $content[$region][] = $data;
40   }
41   return $content;
42 }
43 
44 /**
45  * Get assigned content.
46  *
47  * @param $region
48  *   A specified region to fetch content for. If NULL, all regions will be
49  *   returned.
50  * @param $delimiter
51  *   Content to be inserted between exploded array elements.
52  */
53 function drupal_get_content($region = NULL, $delimiter = ' ') {
54   $content = drupal_set_content();
55   if (isset($region)) {
56     if (isset($content[$region]) && is_array($content[$region])) {
57       return implode($delimiter, $content[$region]);
58     }
59   }
60   else {
61     foreach (array_keys($content) as $region) {
62       if (is_array($content[$region])) {
63         $content[$region] = implode($delimiter, $content[$region]);
64       }
65     }
66     return $content;
67   }
68 }
69 
70 /**
71  * Set the breadcrumb trail for the current page.
72  *
73  * @param $breadcrumb
74  *   Array of links, starting with "home" and proceeding up to but not including
75  *   the current page.
76  */
77 function drupal_set_breadcrumb($breadcrumb = NULL) {
78   static $stored_breadcrumb;
79 
80   if (!is_null($breadcrumb)) {
81     $stored_breadcrumb = $breadcrumb;
82   }
83   return $stored_breadcrumb;
84 }
85 
86 /**
87  * Get the breadcrumb trail for the current page.
88  */
89 function drupal_get_breadcrumb() {
90   $breadcrumb = drupal_set_breadcrumb();
91 
92   if (is_null($breadcrumb)) {
93     $breadcrumb = menu_get_active_breadcrumb();
94   }
95 
96   return $breadcrumb;
97 }
98 
99 /**
100  * Add output to the head tag of the HTML page.
101  *
102  * This function can be called as long the headers aren't sent.
103  */
104 function drupal_set_html_head($data = NULL) {
105   static $stored_head = '';
106 
107   if (!is_null($data)) {
108     $stored_head .= $data ."\n";
109   }
110   return $stored_head;
111 }
112 
113 /**
114  * Retrieve output to be displayed in the head tag of the HTML page.
115  */
116 function drupal_get_html_head() {
117   $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
118   return $output . drupal_set_html_head();
119 }
120 
121 /**
122  * Reset the static variable which holds the aliases mapped for this request.
123  */
124 function drupal_clear_path_cache() {
125   drupal_lookup_path('wipe');
126 }
127 
128 /**
129  * Set an HTTP response header for the current page.
130  *
131  * Note: When sending a Content-Type header, always include a 'charset' type,
132  * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
133  */
134 function drupal_set_header($header = NULL) {
135   // We use an array to guarantee there are no leading or trailing delimiters.
136   // Otherwise, header('') could get called when serving the page later, which
137   // ends HTTP headers prematurely on some PHP versions.
138   static $stored_headers = array();
139 
140   if (strlen($header)) {
141     header($header);
142     $stored_headers[] = $header;
143   }
144   return implode("\n", $stored_headers);
145 }
146 
147 /**
148  * Get the HTTP response headers for the current page.
149  */
150 function drupal_get_headers() {
151   return drupal_set_header();
152 }
153 
154 /**
155  * Add a feed URL for the current page.
156  *
157  * @param $url
158  *   A url for the feed.
159  * @param $title
160  *   The title of the feed.
161  */
162 function drupal_add_feed($url = NULL, $title = '') {
163   static $stored_feed_links = array();
164 
165   if (!is_null($url) && !isset($stored_feed_links[$url])) {
166     $stored_feed_links[$url] = theme('feed_icon', $url, $title);
167 
168     drupal_add_link(array('rel' => 'alternate',
169                           'type' => 'application/rss+xml',
170                           'title' => $title,
171                           'href' => $url));
172   }
173   return $stored_feed_links;
174 }
175 
176 /**
177  * Get the feed URLs for the current page.
178  *
179  * @param $delimiter
180  *   A delimiter to split feeds by.
181  */
182 function drupal_get_feeds($delimiter = "\n") {
183   $feeds = drupal_add_feed();
184   return implode($feeds, $delimiter);
185 }
186 
187 /**
188  * @name HTTP handling
189  * @{
190  * Functions to properly handle HTTP responses.
191  */
192 
193 /**
194  * Parse an array into a valid urlencoded query string.
195  *
196  * @param $query
197  *   The array to be processed e.g. $_GET.
198  * @param $exclude
199  *   The array filled with keys to be excluded. Use parent[child] to exclude
200  *   nested items.
201  * @param $parent
202  *   Should not be passed, only used in recursive calls.
203  * @return
204  *   An urlencoded string which can be appended to/as the URL query string.
205  */
206 function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
207   $params = array();
208 
209   foreach ($query as $key => $value) {
210     $key = drupal_urlencode($key);
211     if ($parent) {
212       $key = $parent .'['. $key .']';
213     }
214 
215     if (in_array($key, $exclude)) {
216       continue;
217     }
218 
219     if (is_array($value)) {
220       $params[] = drupal_query_string_encode($value, $exclude, $key);
221     }
222     else {
223       $params[] = $key .'='. drupal_urlencode($value);
224     }
225   }
226 
227   return implode('&', $params);
228 }
229 
230 /**
231  * Prepare a destination query string for use in combination with drupal_goto().
232  *
233  * Used to direct the user back to the referring page after completing a form.
234  * By default the current URL is returned. If a destination exists in the
235  * previous request, that destination is returned. As such, a destination can
236  * persist across multiple pages.
237  *
238  * @see drupal_goto()
239  */
240 function drupal_get_destination() {
241   if (isset($_REQUEST['destination'])) {
242     return 'destination='. urlencode($_REQUEST['destination']);
243   }
244   else {
245     // Use $_GET here to retrieve the original path in source form.
246     $path = isset($_GET['q']) ? $_GET['q'] : '';
247     $query = drupal_query_string_encode($_GET, array('q'));
248     if ($query != '') {
249       $path .= '?'. $query;
250     }
251     return 'destination='. urlencode($path);
252   }
253 }
254 
255 /**
256  * Send the user to a different Drupal page.
257  *
258  * This issues an on-site HTTP redirect. The function makes sure the redirected
259  * URL is formatted correctly.
260  *
261  * Usually the redirected URL is constructed from this function's input
262  * parameters. However you may override that behavior by setting a
263  * <em>destination</em> in either the $_REQUEST-array (i.e. by using
264  * the query string of an URI) or the $_REQUEST['edit']-array (i.e. by
265  * using a hidden form field). This is used to direct the user back to
266  * the proper page after completing a form. For example, after editing
267  * a post on the 'admin/content/node'-page or after having logged on using the
268  * 'user login'-block in a sidebar. The function drupal_get_destination()
269  * can be used to help set the destination URL.
270  *
271  * Drupal will ensure that messages set by drupal_set_message() and other
272  * session data are written to the database before the user is redirected.
273  *
274  * This function ends the request; use it rather than a print theme('page')
275  * statement in your menu callback.
276  *
277  * @param $path
278  *   A Drupal path or a full URL.
279  * @param $query
280  *   A query string component, if any.
281  * @param $fragment
282  *   A destination fragment identifier (named anchor).
283  * @param $http_response_code
284  *   Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
285  *   - 301 Moved Permanently (the recommended value for most redirects)
286  *   - 302 Found (default in Drupal and PHP, sometimes used for spamming search
287  *         engines)
288  *   - 303 See Other
289  *   - 304 Not Modified
290  *   - 305 Use Proxy
291  *   - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
292  *   Note: Other values are defined by RFC 2616, but are rarely used and poorly
293  *   supported.
294  * @see drupal_get_destination()
295  */
296 function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
297 
298   if (isset($_REQUEST['destination'])) {
299     extract(parse_url(urldecode($_REQUEST['destination'])));
300   }
301   else if (isset($_REQUEST['edit']['destination'])) {
302     extract(parse_url(urldecode($_REQUEST['edit']['destination'])));
303   }
304 
305   $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
306   // Remove newlines from the URL to avoid header injection attacks.
307   $url = str_replace(array("\n", "\r"), '', $url);
308 
309   // Allow modules to react to the end of the page request before redirecting.
310   // We do not want this while running update.php.
311   if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
312     module_invoke_all('exit', $url);
313   }
314 
315   // Even though session_write_close() is registered as a shutdown function, we
316   // need all session data written to the database before redirecting.
317   session_write_close();
318 
319   header('Location: '. $url, TRUE, $http_response_code);
320 
321   // The "Location" header sends a redirect status code to the HTTP daemon. In
322   // some cases this can be wrong, so we make sure none of the code below the
323   // drupal_goto() call gets executed upon redirection.
324   exit();
325 }
326 
327 /**
328  * Generates a site off-line message.
329  */
330 function drupal_site_offline() {
331   drupal_maintenance_theme();
332   drupal_set_header('HTTP/1.1 503 Service unavailable');
333   drupal_set_title(t('Site off-line'));
334   print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
335     t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
336 }
337 
338 /**
339  * Generates a 404 error if the request can not be handled.
340  */
341 function drupal_not_found() {
342   drupal_set_header('HTTP/1.1 404 Not Found');
343 
344   watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
345 
346   // Keep old path for reference.
347   if (!isset($_REQUEST['destination'])) {
348     $_REQUEST['destination'] = $_GET['q'];
349   }
350 
351   $path = drupal_get_normal_path(variable_get('site_404', ''));
352   if ($path && $path != $_GET['q']) {
353     // Set the active item in case there are tabs to display, or other
354     // dependencies on the path.
355     menu_set_active_item($path);
356     $return = menu_execute_active_handler($path);
357   }
358 
359   if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
360     drupal_set_title(t('Page not found'));
361     $return = t('The requested page could not be found.');
362   }
363 
364   // To conserve CPU and bandwidth, omit the blocks.
365   print theme('page', $return, FALSE);
366 }
367 
368 /**
369  * Generates a 403 error if the request is not allowed.
370  */
371 function drupal_access_denied() {
372   drupal_set_header('HTTP/1.1 403 Forbidden');
373   watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
374 
375   // Keep old path for reference.
376   if (!isset($_REQUEST['destination'])) {
377     $_REQUEST['destination'] = $_GET['q'];
378   }
379 
380   $path = drupal_get_normal_path(variable_get('site_403', ''));
381   if ($path && $path != $_GET['q']) {
382     // Set the active item in case there are tabs to display or other
383     // dependencies on the path.
384     menu_set_active_item($path);
385     $return = menu_execute_active_handler($path);
386   }
387 
388   if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
389     drupal_set_title(t('Access denied'));
390     $return = t('You are not authorized to access this page.');
391   }
392   print theme('page', $return);
393 }
394 
395 /**
396  * Perform an HTTP request.
397  *
398  * This is a flexible and powerful HTTP client implementation. Correctly handles
399  * GET, POST, PUT or any other HTTP requests. Handles redirects.
400  *
401  * @param $url
402  *   A string containing a fully qualified URI.
403  * @param $headers
404  *   An array containing an HTTP header => value pair.
405  * @param $method
406  *   A string defining the HTTP request to use.
407  * @param $data
408  *   A string containing data to include in the request.
409  * @param $retry
410  *   An integer representing how many times to retry the request in case of a
411  *   redirect.
412  * @return
413  *   An object containing the HTTP request headers, response code, headers,
414  *   data and redirect status.
415  */
416 function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
417   static $self_test = FALSE;
418   $result = new stdClass();
419   // Try to clear the drupal_http_request_fails variable if it's set. We
420   // can't tie this call to any error because there is no surefire way to
421   // tell whether a request has failed, so we add the check to places where
422   // some parsing has failed.
423   if (!$self_test && variable_get('drupal_http_request_fails', FALSE)) {
424     $self_test = TRUE;
425     $works = module_invoke('system', 'check_http_request');
426     $self_test = FALSE;
427     if (!$works) {
428       // Do not bother with further operations if we already know that we
429       // have no chance.
430       $result->error = t("The server can't issue HTTP requests");
431       return $result;
432     }
433   }
434 
435   // Parse the URL and make sure we can handle the schema.
436   $uri = parse_url($url);
437 
438   switch ($uri['scheme']) {
439     case 'http':
440       $port = isset($uri['port']) ? $uri['port'] : 80;
441       $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
442       $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
443       break;
444     case 'https':
445       // Note: Only works for PHP 4.3 compiled with OpenSSL.
446       $port = isset($uri['port']) ? $uri['port'] : 443;
447       $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
448       $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
449       break;
450     default:
451       $result->error = 'invalid schema '. $uri['scheme'];
452       return $result;
453   }
454 
455   // Make sure the socket opened properly.
456   if (!$fp) {
457     // When a network error occurs, we use a negative number so it does not
458     // clash with the HTTP status codes.
459     $result->code = -$errno;
460     $result->error = trim($errstr);
461     return $result;
462   }
463 
464   // Construct the path to act on.
465   $path = isset($uri['path']) ? $uri['path'] : '/';
466   if (isset($uri['query'])) {
467     $path .= '?'. $uri['query'];
468   }
469 
470   // Create HTTP request.
471   $defaults = array(
472     // RFC 2616: "non-standard ports MUST, default ports MAY be included".
473     // We don't add the port to prevent from breaking rewrite rules checking the
474     // host that do not take into account the port number.
475     'Host' => "Host: $host",
476     'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
477     'Content-Length' => 'Content-Length: '. strlen($data)
478   );
479 
480   // If the server url has a user then attempt to use basic authentication
481   if (isset($uri['user'])) {
482     $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
483   }
484 
485   foreach ($headers as $header => $value) {
486     $defaults[$header] = $header .': '. $value;
487   }
488 
489   $request = $method .' '. $path ." HTTP/1.0\r\n";
490   $request .= implode("\r\n", $defaults);
491   $request .= "\r\n\r\n";
492   if ($data) {
493     $request .= $data ."\r\n";
494   }
495   $result->request = $request;
496 
497   fwrite($fp, $request);
498 
499   // Fetch response.
500   $response = '';
501   while (!feof($fp) && $chunk = fread($fp, 1024)) {
502     $response .= $chunk;
503   }
504   fclose($fp);
505 
506   // Parse response.
507   list($split, $result->data) = explode("\r\n\r\n", $response, 2);
508   $split = preg_split("/\r\n|\n|\r/", $split);
509 
510   list($protocol, $code, $text) = explode(' ', trim(array_shift($split)), 3);
511   $result->headers = array();
512 
513   // Parse headers.
514   while ($line = trim(array_shift($split))) {
515     list($header, $value) = explode(':', $line, 2);
516     if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
517       // RFC 2109: the Set-Cookie response header comprises the token Set-
518       // Cookie:, followed by a comma-separated list of one or more cookies.
519       $result->headers[$header] .= ','. trim($value);
520     }
521     else {
522       $result->headers[$header] = trim($value);
523     }
524   }
525 
526   $responses = array(
527