common.inc 165 KB
Newer Older
Dries's avatar
   
Dries committed
1
<?php
2
// $Id$
Dries's avatar
   
Dries committed
3

Dries's avatar
   
Dries committed
4
5
6
7
8
9
10
11
/**
 * @file
 * Common functions that many Drupal modules will need to reference.
 *
 * The functions that are critical and need to be available even when serving
 * a cached page are instead located in bootstrap.inc.
 */

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Error reporting level: display no errors.
 */
define('ERROR_REPORTING_HIDE', 0);

/**
 * Error reporting level: display errors and warnings.
 */
define('ERROR_REPORTING_DISPLAY_SOME', 1);

/**
 * Error reporting level: display all messages.
 */
define('ERROR_REPORTING_DISPLAY_ALL', 2);

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * Return status for saving which involved creating a new item.
 */
define('SAVED_NEW', 1);

/**
 * Return status for saving which involved an update to an existing item.
 */
define('SAVED_UPDATED', 2);

/**
 * Return status for saving which deleted an existing item.
 */
define('SAVED_DELETED', 3);

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * The default weight of system CSS files added to the page.
 */
define('CSS_SYSTEM', -100);

/**
 * The default weight of CSS files added to the page.
 */
define('CSS_DEFAULT', 0);

/**
 * The default weight of theme CSS files added to the page.
 */
define('CSS_THEME', 100);

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
 * The weight of JavaScript libraries, settings or jQuery plugins being
 * added to the page.
 */
define('JS_LIBRARY', -100);

/**
 * The default weight of JavaScript being added to the page.
 */
define('JS_DEFAULT', 0);

/**
 * The weight of theme JavaScript code being added to the page.
 */
define('JS_THEME', 100);

73
74
75
76
77
78
/**
 * Error code indicating that the request made by drupal_http_request() exceeded
 * the specified timeout.
 */
define('HTTP_REQUEST_TIMEOUT', 1);

79
/**
80
 * Add content to a specified region.
81
82
 *
 * @param $region
83
 *   Page region the content is added to.
84
 * @param $data
85
 *   Content to be added.
86
 */
87
function drupal_add_region_content($region = NULL, $data = NULL) {
88
89
90
91
92
93
94
95
96
  static $content = array();

  if (!is_null($region) && !is_null($data)) {
    $content[$region][] = $data;
  }
  return $content;
}

/**
97
 * Get assigned content for a given region.
98
99
 *
 * @param $region
100
101
 *   A specified region to fetch content for. If NULL, all regions will be
 *   returned.
102
 * @param $delimiter
103
 *   Content to be inserted between imploded array elements.
104
 */
105
106
function drupal_get_region_content($region = NULL, $delimiter = ' ') {
  $content = drupal_add_region_content();
107
108
  if (isset($region)) {
    if (isset($content[$region]) && is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
109
      return implode($delimiter, $content[$region]);
110
    }
111
112
113
114
  }
  else {
    foreach (array_keys($content) as $region) {
      if (is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
115
        $content[$region] = implode($delimiter, $content[$region]);
116
117
118
119
120
121
      }
    }
    return $content;
  }
}

Dries's avatar
   
Dries committed
122
/**
Dries's avatar
   
Dries committed
123
 * Set the breadcrumb trail for the current page.
Dries's avatar
   
Dries committed
124
 *
Dries's avatar
   
Dries committed
125
126
127
 * @param $breadcrumb
 *   Array of links, starting with "home" and proceeding up to but not including
 *   the current page.
Kjartan's avatar
Kjartan committed
128
 */
Dries's avatar
   
Dries committed
129
function drupal_set_breadcrumb($breadcrumb = NULL) {
130
  $stored_breadcrumb = &drupal_static(__FUNCTION__);
Dries's avatar
   
Dries committed
131

132
  if (!is_null($breadcrumb)) {
Dries's avatar
   
Dries committed
133
134
135
136
137
    $stored_breadcrumb = $breadcrumb;
  }
  return $stored_breadcrumb;
}

Dries's avatar
   
Dries committed
138
139
140
/**
 * Get the breadcrumb trail for the current page.
 */
Dries's avatar
   
Dries committed
141
142
143
function drupal_get_breadcrumb() {
  $breadcrumb = drupal_set_breadcrumb();

144
  if (is_null($breadcrumb)) {
Dries's avatar
   
Dries committed
145
146
147
148
149
150
    $breadcrumb = menu_get_active_breadcrumb();
  }

  return $breadcrumb;
}

151
/**
152
153
 * Return a string containing RDF namespaces for the <html> tag of an XHTML
 * page.
154
155
156
157
158
159
160
161
162
163
 */
function drupal_get_rdf_namespaces() {
  // Serialize the RDF namespaces used in RDFa annotation.
  $xml_rdf_namespaces = array();
  foreach (module_invoke_all('rdf_namespaces') as $prefix => $uri) {
    $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
  }
  return implode("\n  ", $xml_rdf_namespaces);
}

Dries's avatar
Dries committed
164
/**
Dries's avatar
   
Dries committed
165
 * Add output to the head tag of the HTML page.
166
 *
Dries's avatar
   
Dries committed
167
 * This function can be called as long the headers aren't sent.
Dries's avatar
Dries committed
168
 */
169
function drupal_add_html_head($data = NULL) {
170
  $stored_head = &drupal_static(__FUNCTION__, '');
Dries's avatar
Dries committed
171
172

  if (!is_null($data)) {
173
    $stored_head .= $data . "\n";
Dries's avatar
Dries committed
174
175
176
177
  }
  return $stored_head;
}

Dries's avatar
   
Dries committed
178
179
180
/**
 * Retrieve output to be displayed in the head tag of the HTML page.
 */
Dries's avatar
Dries committed
181
function drupal_get_html_head() {
Dries's avatar
   
Dries committed
182
  $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
183
  return $output . drupal_add_html_head();
Dries's avatar
Dries committed
184
185
}

Dries's avatar
   
Dries committed
186
/**
187
 * Reset the static variable which holds the aliases mapped for this request.
Dries's avatar
   
Dries committed
188
 */
189
190
function drupal_clear_path_cache() {
  drupal_lookup_path('wipe');
Dries's avatar
   
Dries committed
191
}
Kjartan's avatar
Kjartan committed
192

193
/**
194
195
 * Add a feed URL for the current page.
 *
196
197
 * This function can be called as long the HTML header hasn't been sent.
 *
198
 * @param $url
199
 *   A url for the feed.
200
 * @param $title
201
 *   The title of the feed.
202
 */
203
function drupal_add_feed($url = NULL, $title = '') {
204
  $stored_feed_links = &drupal_static(__FUNCTION__, array());
205

206
  if (!is_null($url) && !isset($stored_feed_links[$url])) {
207
    $stored_feed_links[$url] = theme('feed_icon', $url, $title);
208
209
210
211
212

    drupal_add_link(array('rel' => 'alternate',
                          'type' => 'application/rss+xml',
                          'title' => $title,
                          'href' => $url));
213
214
215
216
217
218
219
220
  }
  return $stored_feed_links;
}

/**
 * Get the feed URLs for the current page.
 *
 * @param $delimiter
221
 *   A delimiter to split feeds by.
222
223
224
225
226
227
 */
function drupal_get_feeds($delimiter = "\n") {
  $feeds = drupal_add_feed();
  return implode($feeds, $delimiter);
}

Dries's avatar
   
Dries committed
228
229
230
/**
 * @name HTTP handling
 * @{
Dries's avatar
   
Dries committed
231
 * Functions to properly handle HTTP responses.
Dries's avatar
   
Dries committed
232
233
 */

234
235
236
237
/**
 * Parse an array into a valid urlencoded query string.
 *
 * @param $query
238
 *   The array to be processed e.g. $_GET.
239
 * @param $exclude
240
241
 *   The array filled with keys to be excluded. Use parent[child] to exclude
 *   nested items.
242
 * @param $parent
243
 *   Should not be passed, only used in recursive calls.
244
 * @return
245
 *   An urlencoded string which can be appended to/as the URL query string.
246
247
248
249
250
 */
function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
  $params = array();

  foreach ($query as $key => $value) {
251
    $key = rawurlencode($key);
252
    if ($parent) {
253
      $key = $parent . '[' . $key . ']';
254
255
    }

256
    if (in_array($key, $exclude)) {
257
258
259
260
261
262
263
      continue;
    }

    if (is_array($value)) {
      $params[] = drupal_query_string_encode($value, $exclude, $key);
    }
    else {
264
      $params[] = $key . '=' . rawurlencode($value);
265
266
267
268
269
270
    }
  }

  return implode('&', $params);
}

271
/**
272
 * Prepare a destination query string for use in combination with drupal_goto().
273
 *
274
275
276
277
 * Used to direct the user back to the referring page after completing a form.
 * By default the current URL is returned. If a destination exists in the
 * previous request, that destination is returned. As such, a destination can
 * persist across multiple pages.
278
279
280
281
 *
 * @see drupal_goto()
 */
function drupal_get_destination() {
282
  if (isset($_REQUEST['destination'])) {
283
    return 'destination=' . urlencode($_REQUEST['destination']);
284
285
  }
  else {
286
287
    // Use $_GET here to retrieve the original path in source form.
    $path = isset($_GET['q']) ? $_GET['q'] : '';
288
289
    $query = drupal_query_string_encode($_GET, array('q'));
    if ($query != '') {
290
      $path .= '?' . $query;
291
    }
292
    return 'destination=' . urlencode($path);
293
294
295
  }
}

Kjartan's avatar
Kjartan committed
296
/**
Dries's avatar
   
Dries committed
297
 * Send the user to a different Drupal page.
Kjartan's avatar
Kjartan committed
298
 *
Dries's avatar
   
Dries committed
299
300
 * This issues an on-site HTTP redirect. The function makes sure the redirected
 * URL is formatted correctly.
Kjartan's avatar
Kjartan committed
301
 *
302
 * Usually the redirected URL is constructed from this function's input
303
 * parameters. However you may override that behavior by setting a
304
 * destination in either the $_REQUEST-array (i.e. by using
305
 * the query string of an URI) This is used to direct the user back to
306
 * the proper page after completing a form. For example, after editing
307
 * a post on the 'admin/content'-page or after having logged on using the
308
 * 'user login'-block in a sidebar. The function drupal_get_destination()
309
310
 * can be used to help set the destination URL.
 *
311
312
 * Drupal will ensure that messages set by drupal_set_message() and other
 * session data are written to the database before the user is redirected.
Dries's avatar
   
Dries committed
313
 *
314
 * This function ends the request; use it instead of a return in your menu callback.
Dries's avatar
   
Dries committed
315
316
 *
 * @param $path
317
 *   A Drupal path or a full URL.
Dries's avatar
   
Dries committed
318
 * @param $query
319
 *   A query string component, if any.
Dries's avatar
   
Dries committed
320
 * @param $fragment
321
 *   A destination fragment identifier (named anchor).
322
323
324
325
326
327
328
329
 * @param $http_response_code
 *   Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
 *   - 301 Moved Permanently (the recommended value for most redirects)
 *   - 302 Found (default in Drupal and PHP, sometimes used for spamming search
 *         engines)
 *   - 303 See Other
 *   - 304 Not Modified
 *   - 305 Use Proxy
330
 *   - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
331
 *   Note: Other values are defined by RFC 2616, but are rarely used and poorly
332
 *   supported.
333
 * @see drupal_get_destination()
Kjartan's avatar
Kjartan committed
334
 */
335
function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
336

337
  if (isset($_REQUEST['destination'])) {
338
    extract(parse_url(urldecode($_REQUEST['destination'])));
339
340
  }

341
  $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
Kjartan's avatar
Kjartan committed
342

343
  // Allow modules to react to the end of the page request before redirecting.
344
  // We do not want this while running update.php.
345
  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
346
347
    module_invoke_all('exit', $url);
  }
Dries's avatar
   
Dries committed
348

349
350
351
  // Commit the session, if necessary. We need all session data written to the
  // database before redirecting.
  drupal_session_commit();
352

353
  header('Location: ' . $url, TRUE, $http_response_code);
354
355

  // The "Location" header sends a redirect status code to the HTTP daemon. In
356
357
  // some cases this can be wrong, so we make sure none of the code below the
  // drupal_goto() call gets executed upon redirection.
Kjartan's avatar
Kjartan committed
358
359
360
  exit();
}

361
/**
362
 * Generates a site offline message.
363
364
 */
function drupal_site_offline() {
365
  drupal_maintenance_theme();
366
  drupal_set_header('503 Service unavailable');
367
  drupal_set_title(t('Site offline'));
368
  print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
369
    t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
370
371
}

Kjartan's avatar
Kjartan committed
372
373
374
/**
 * Generates a 404 error if the request can not be handled.
 */
Dries's avatar
   
Dries committed
375
function drupal_not_found() {
376
  drupal_set_header('404 Not Found');
377

378
  watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
379

380
381
382
383
384
  // Keep old path for reference, and to allow forms to redirect to it.
  if (!isset($_REQUEST['destination'])) {
    $_REQUEST['destination'] = $_GET['q'];
  }

Dries's avatar
   
Dries committed
385
  $path = drupal_get_normal_path(variable_get('site_404', ''));
drumm's avatar
drumm committed
386
  if ($path && $path != $_GET['q']) {
387
388
    // Custom 404 handler. Set the active item in case there are tabs to
    // display, or other dependencies on the path.
389
    menu_set_active_item($path);
390
    $return = menu_execute_active_handler($path);
391
  }
Dries's avatar
   
Dries committed
392

393
  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
394
    // Standard 404 handler.
drumm's avatar
drumm committed
395
    drupal_set_title(t('Page not found'));
396
    $return = t('The requested page could not be found.');
Dries's avatar
   
Dries committed
397
  }
398

399
400
  drupal_set_page_content($return);
  $page = element_info('page');
401
402
  // Optionally omit the blocks to conserve CPU and bandwidth.
  $page['#show_blocks'] = variable_get('site_404_blocks', FALSE);
403
404

  print drupal_render_page($page);
Dries's avatar
   
Dries committed
405
}
Dries's avatar
   
Dries committed
406

Dries's avatar
   
Dries committed
407
408
409
410
/**
 * Generates a 403 error if the request is not allowed.
 */
function drupal_access_denied() {
411
  drupal_set_header('403 Forbidden');
412
  watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
Dries's avatar
   
Dries committed
413

414
415
416
417
418
  // Keep old path for reference, and to allow forms to redirect to it.
  if (!isset($_REQUEST['destination'])) {
    $_REQUEST['destination'] = $_GET['q'];
  }

Dries's avatar
   
Dries committed
419
  $path = drupal_get_normal_path(variable_get('site_403', ''));
drumm's avatar
drumm committed
420
  if ($path && $path != $_GET['q']) {
421
422
    // Custom 403 handler. Set the active item in case there are tabs to
    // display or other dependencies on the path.
423
    menu_set_active_item($path);
424
    $return = menu_execute_active_handler($path);
425
  }
Dries's avatar
   
Dries committed
426

427
  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
428
    // Standard 403 handler.
drumm's avatar
drumm committed
429
430
    drupal_set_title(t('Access denied'));
    $return = t('You are not authorized to access this page.');
Dries's avatar
   
Dries committed
431
  }
432
433

  print drupal_render_page($return);
Dries's avatar
   
Dries committed
434
435
}

Dries's avatar
   
Dries committed
436
/**
Dries's avatar
   
Dries committed
437
 * Perform an HTTP request.
Dries's avatar
   
Dries committed
438
 *
439
440
 * This is a flexible and powerful HTTP client implementation. Correctly
 * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
Dries's avatar
   
Dries committed
441
442
443
 *
 * @param $url
 *   A string containing a fully qualified URI.
444
445
446
447
448
449
450
451
452
453
454
 * @param $options
 *   (optional) An array which can have one or more of following keys:
 *   - headers
 *       An array containing request headers to send as name/value pairs.
 *   - method
 *       A string containing the request method. Defaults to 'GET'.
 *   - data
 *       A string containing the request body. Defaults to NULL.
 *   - max_redirects
 *       An integer representing how many times a redirect may be followed.
 *       Defaults to 3.
455
456
457
458
 *   - timeout
 *       A float representing the maximum number of seconds the function call
 *       may take. The default is 30 seconds. If a timeout occurs, the error
 *       code is set to the HTTP_REQUEST_TIMEOUT constant.
Dries's avatar
   
Dries committed
459
 * @return
460
461
462
463
464
465
 *   An object which can have one or more of the following parameters:
 *   - request
 *       A string containing the request body that was sent.
 *   - code
 *       An integer containing the response status code, or the error code if
 *       an error occurred.
466
 *   - protocol
467
 *       The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
468
 *   - status_message
469
 *       The status message from the response, if a response was received.
470
471
472
473
474
 *   - redirect_code
 *       If redirected, an integer containing the initial response status code.
 *   - redirect_url
 *       If redirected, a string containing the redirection location.
 *   - error
475
 *       If an error occurred, the error message. Otherwise not set.
476
477
478
479
 *   - headers
 *       An array containing the response headers as name/value pairs.
 *   - data
 *       A string containing the response body that was received.
Dries's avatar
   
Dries committed
480
 */
481
function drupal_http_request($url, array $options = array()) {
482
  global $db_prefix;
483

484
  $result = new stdClass();
Dries's avatar
   
Dries committed
485

486
  // Parse the URL and make sure we can handle the schema.
487
  $uri = @parse_url($url);
488

489
490
  if ($uri == FALSE) {
    $result->error = 'unable to parse URL';
491
492
493
    return $result;
  }

494
495
  if (!isset($uri['scheme'])) {
    $result->error = 'missing schema';
496
497
498
    return $result;
  }

499
500
501
502
503
504
505
506
507
508
509
  timer_start(__FUNCTION__);

  // Merge the default options.
  $options += array(
    'headers' => array(),
    'method' => 'GET',
    'data' => NULL,
    'max_redirects' => 3,
    'timeout' => 30,
  );

Dries's avatar
   
Dries committed
510
511
  switch ($uri['scheme']) {
    case 'http':
Dries's avatar
Dries committed
512
      $port = isset($uri['port']) ? $uri['port'] : 80;
513
      $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
514
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
Dries's avatar
   
Dries committed
515
516
      break;
    case 'https':
517
      // Note: Only works when PHP is compiled with OpenSSL support.
Dries's avatar
Dries committed
518
      $port = isset($uri['port']) ? $uri['port'] : 443;
519
      $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
520
      $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
Dries's avatar
   
Dries committed
521
522
      break;
    default:
523
      $result->error = 'invalid schema ' . $uri['scheme'];
Dries's avatar
   
Dries committed
524
525
526
      return $result;
  }

Dries's avatar
   
Dries committed
527
  // Make sure the socket opened properly.
Dries's avatar
   
Dries committed
528
  if (!$fp) {
529
530
    // When a network error occurs, we use a negative number so it does not
    // clash with the HTTP status codes.
531
532
    $result->code = -$errno;
    $result->error = trim($errstr);
533
534
535
536
537
538
539

    // Mark that this request failed. This will trigger a check of the web
    // server's ability to make outgoing HTTP requests the next time that
    // requirements checking is performed.
    // @see system_requirements()
    variable_set('drupal_http_request_fails', TRUE);

Dries's avatar
   
Dries committed
540
541
542
    return $result;
  }

Dries's avatar
   
Dries committed
543
  // Construct the path to act on.
Dries's avatar
Dries committed
544
545
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
546
    $path .= '?' . $uri['query'];
Dries's avatar
   
Dries committed
547
548
  }

549
550
551
  // Merge the default headers.
  $options['headers'] += array(
    'User-Agent' => 'Drupal (+http://drupal.org/)',
Dries's avatar
   
Dries committed
552
553
  );

554
555
556
557
558
  // RFC 2616: "non-standard ports MUST, default ports MAY be included".
  // We don't add the standard port to prevent from breaking rewrite rules
  // checking the host that do not take into account the port number.
  $options['headers']['Host'] = $host;

559
560
561
562
  // Only add Content-Length if we actually have any content or if it is a POST
  // or PUT request. Some non-standard servers get confused by Content-Length in
  // at least HEAD/GET requests, and Squid always requires Content-Length in
  // POST/PUT requests.
563
564
565
  $content_length = strlen($options['data']);
  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
    $options['headers']['Content-Length'] = $content_length;
566
567
568
  }

  // If the server URL has a user then attempt to use basic authentication.
569
  if (isset($uri['user'])) {
570
    $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
571
572
  }

573
574
575
576
577
578
  // If the database prefix is being used by SimpleTest to run the tests in a copied
  // database then set the user-agent header to the database prefix so that any
  // calls to other Drupal pages will run the SimpleTest prefixed database. The
  // user-agent is used to ensure that multiple testing sessions running at the
  // same time won't interfere with each other as they would if the database
  // prefix were stored statically in a file or database variable.
579
580
  if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
    $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
581
582
  }

583
  $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
584
  foreach ($options['headers'] as $name => $value) {
585
    $request .= $name . ': ' . trim($value) . "\r\n";
Dries's avatar
   
Dries committed
586
  }
587
  $request .= "\r\n" . $options['data'];
Dries's avatar
   
Dries committed
588
589
590
591
592
  $result->request = $request;

  fwrite($fp, $request);

  // Fetch response.
593
  $response = '';
594
595
596
597
598
599
600
601
602
603
  while (!feof($fp)) {
    // Calculate how much time is left of the original timeout value.
    $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
    if ($timeout <= 0) {
      $result->code = HTTP_REQUEST_TIMEOUT;
      $result->error = 'request timed out';
      return $result;
    }
    stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
    $response .= fread($fp, 1024);
Dries's avatar
   
Dries committed
604
605
606
  }
  fclose($fp);

607
608
609
  // Parse response headers from the response body.
  list($response, $result->data) = explode("\r\n\r\n", $response, 2);
  $response = preg_split("/\r\n|\n|\r/", $response);
610

611
  // Parse the response status line.
612
613
614
615
  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
  $result->protocol = $protocol;
  $result->status_message = $status_message;

Dries's avatar
   
Dries committed
616
617
  $result->headers = array();

618
619
  // Parse the response headers.
  while ($line = trim(array_shift($response))) {
Dries's avatar
   
Dries committed
620
    list($header, $value) = explode(':', $line, 2);
621
622
623
    if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
      // RFC 2109: the Set-Cookie response header comprises the token Set-
      // Cookie:, followed by a comma-separated list of one or more cookies.
624
      $result->headers[$header] .= ',' . trim($value);
625
626
627
628
    }
    else {
      $result->headers[$header] = trim($value);
    }
Dries's avatar
   
Dries committed
629
630
631
  }

  $responses = array(
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
    100 => 'Continue',
    101 => 'Switching Protocols',
    200 => 'OK',
    201 => 'Created',
    202 => 'Accepted',
    203 => 'Non-Authoritative Information',
    204 => 'No Content',
    205 => 'Reset Content',
    206 => 'Partial Content',
    300 => 'Multiple Choices',
    301 => 'Moved Permanently',
    302 => 'Found',
    303 => 'See Other',
    304 => 'Not Modified',
    305 => 'Use Proxy',
    307 => 'Temporary Redirect',
    400 => 'Bad Request',
    401 => 'Unauthorized',
    402 => 'Payment Required',
    403 => 'Forbidden',
    404 => 'Not Found',
    405 => 'Method Not Allowed',
    406 => 'Not Acceptable',
    407 => 'Proxy Authentication Required',
    408 => 'Request Time-out',
    409 => 'Conflict',
    410 => 'Gone',
    411 => 'Length Required',
    412 => 'Precondition Failed',
    413 => 'Request Entity Too Large',
    414 => 'Request-URI Too Large',
    415 => 'Unsupported Media Type',
    416 => 'Requested range not satisfiable',
    417 => 'Expectation Failed',
    500 => 'Internal Server Error',
    501 => 'Not Implemented',
    502 => 'Bad Gateway',
    503 => 'Service Unavailable',
    504 => 'Gateway Time-out',
    505 => 'HTTP Version not supported',
Dries's avatar
   
Dries committed
672
  );
673
674
  // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  // base code in their class.
Dries's avatar
   
Dries committed
675
676
677
  if (!isset($responses[$code])) {
    $code = floor($code / 100) * 100;
  }
678
  $result->code = $code;
Dries's avatar
   
Dries committed
679
680
681
682
683
684
685
686
687

  switch ($code) {
    case 200: // OK
    case 304: // Not modified
      break;
    case 301: // Moved permanently
    case 302: // Moved temporarily
    case 307: // Moved temporarily
      $location = $result->headers['Location'];
688
689
690
691
692
693
      $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
      if ($options['timeout'] <= 0) {
        $result->code = HTTP_REQUEST_TIMEOUT;
        $result->error = 'request timed out';
      }
      elseif ($options['max_redirects']) {
694
695
696
        // Redirect to the new location.
        $options['max_redirects']--;
        $result = drupal_http_request($location, $options);
697
        $result->redirect_code = $code;
Dries's avatar
   
Dries committed
698
699
700
701
      }
      $result->redirect_url = $location;
      break;
    default:
702
      $result->error = $status_message;
Dries's avatar
   
Dries committed
703
704
705
706
  }

  return $result;
}
Dries's avatar
   
Dries committed
707
708
709
/**
 * @} End of "HTTP handling".
 */
Dries's avatar
   
Dries committed
710

Dries's avatar
   
Dries committed
711
/**
712
 * Custom PHP error handler.
713
 *
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
 * @param $error_level
 *   The level of the error raised.
 * @param $message
 *   The error message.
 * @param $filename
 *   The filename that the error was raised in.
 * @param $line
 *   The line number the error was raised at.
 * @param $context
 *   An array that points to the active symbol table at the point the error occurred.
 */
function _drupal_error_handler($error_level, $message, $filename, $line, $context) {
  if ($error_level & error_reporting()) {
    // All these constants are documented at http://php.net/manual/en/errorfunc.constants.php
    $types = array(
      E_ERROR => 'Error',
      E_WARNING => 'Warning',
      E_PARSE => 'Parse error',
      E_NOTICE => 'Notice',
      E_CORE_ERROR => 'Core error',
      E_CORE_WARNING => 'Core warning',
      E_COMPILE_ERROR => 'Compile error',
      E_COMPILE_WARNING => 'Compile warning',
      E_USER_ERROR => 'User error',
      E_USER_WARNING => 'User warning',
      E_USER_NOTICE => 'User notice',
      E_STRICT => 'Strict warning',
      E_RECOVERABLE_ERROR => 'Recoverable fatal error'
    );
743
744
    $caller = _drupal_get_last_caller(debug_backtrace());

745
    // We treat recoverable errors as fatal.
746
747
748
749
750
751
752
    _drupal_log_error(array(
      '%type' => isset($types[$error_level]) ? $types[$error_level] : 'Unknown error',
      '%message' => $message,
      '%function' => $caller['function'],
      '%file' => $caller['file'],
      '%line' => $caller['line'],
    ), $error_level == E_RECOVERABLE_ERROR);
753
754
755
756
757
758
759
760
761
762
763
764
  }
}

/**
 * Custom PHP exception handler.
 *
 * Uncaught exceptions are those not enclosed in a try/catch block. They are
 * always fatal: the execution of the script will stop as soon as the exception
 * handler exits.
 *
 * @param $exception
 *   The exception object that was thrown.
Dries's avatar
   
Dries committed
765
 */
766
function _drupal_exception_handler($exception) {
767
768
769
770
771
772
773
774
775
776
777
778
  // Log the message to the watchdog and return an error page to the user.
  _drupal_log_error(_drupal_decode_exception($exception), TRUE);
}

/**
 * Decode an exception, especially to retrive the correct caller.
 *
 * @param $exception
 *   The exception object that was thrown.
 * @return An error in the format expected by _drupal_log_error().
 */
function _drupal_decode_exception($exception) {
779
780
  $message = $exception->getMessage();

781
782
783
784
785
786
787
788
789
790
791
  $backtrace = $exception->getTrace();
  // Add the line throwing the exception to the backtrace.
  array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));

  // For PDOException errors, we try to return the initial caller,
  // skipping internal functions of the database layer.
  if ($exception instanceof PDOException) {
    // The first element in the stack is the call, the second element gives us the caller.
    // We skip calls that occurred in one of the classes of the database layer
    // or in one of its global functions.
    $db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
792
    while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
793
        ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
794
        in_array($caller['function'], $db_functions))) {
795
796
797
      // We remove that call.
      array_shift($backtrace);
    }
798
799
800
    if (isset($exception->query_string, $exception->args)) {
      $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
    }
801
  }
802
  $caller = _drupal_get_last_caller($backtrace);
803

804
805
  return array(
    '%type' => get_class($exception),
806
    '%message' => $message,
807
808
809
810
    '%function' => $caller['function'],
    '%file' => $caller['file'],
    '%line' => $caller['line'],
  );
811
}
812

813
814
815
/**
 * Log a PHP error or exception, display an error page in fatal cases.
 *
816
817
 * @param $error
 *   An array with the following keys: %type, %message, %function, %file, %line.
818
819
820
 * @param $fatal
 *   TRUE if the error is fatal.
 */
821
function _drupal_log_error($error, $fatal = FALSE) {
822
  // Initialize a maintenance theme if the boostrap was not complete.
823
  // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
824
825
  if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
    unset($GLOBALS['theme']);
826
827
828
    if (!defined('MAINTENANCE_MODE')) {
      define('MAINTENANCE_MODE', 'error');
    }
829
830
    drupal_maintenance_theme();
  }
831

832
833
  // When running inside the testing framework, we relay the errors
  // to the tested site by the way of HTTP headers.
834
  if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
835
836
    // $number does not use drupal_static as it should not be reset
    // as it uniquely identifies each PHP error.
837
838
    static $number = 0;
    $assertion = array(
839
840
      $error['%message'],
      $error['%type'],
841
842
843
844
845
      array(
        'function' => $error['%function'],
        'file' => $error['%file'],
        'line' => $error['%line'],
      ),
846
847
848
849
850
    );
    header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
    $number++;
  }

851
852
853
854
  try {
    watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
  }
  catch (Exception $e) {
855
856
    // Ignore any additional watchdog exception, as that probably means
    // that the database was not initialized correctly.
857
  }
Dries's avatar
   
Dries committed
858

859
  if ($fatal) {
860
    drupal_set_header('500 Service unavailable (with message)');
861
862
863
864
865
866
867
  }

  if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
    if ($fatal) {
      // When called from JavaScript, simply output the error message.
      print t('%type: %message in %function (line %line of %file).', $error);
      exit;
Dries's avatar
Dries committed
868
    }
869
870
871
872
873
874
875
876
877
878
879
880
881
882
  }
  else {
    // Display the message if the current error reporting level allows this type
    // of message to be displayed, and unconditionnaly in update.php.
    $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL);
    $display_error = $error_level == ERROR_REPORTING_DISPLAY_ALL || ($error_level == ERROR_REPORTING_DISPLAY_SOME && $error['%type'] != 'Notice');
    if ($display_error || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) {
      drupal_set_message(t('%type: %message in %function (line %line of %file).', $error), 'error');
    }

    if ($fatal) {
      drupal_set_title(t('Error'));
      // We fallback to a maintenance page at this point, because the page generation
      // itself can generate errors.
883
      print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
884
      exit;
885
    }
Dries's avatar
   
Dries committed
886
887
888
  }
}

889
/**
890
 * Gets the last caller from a backtrace.
891
892
893
894
895
896
897
 *
 * @param $backtrace
 *   A standard PHP backtrace.
 * @return
 *   An associative array with keys 'file', 'line' and 'function'.
 */
function _drupal_get_last_caller($backtrace) {
898
899
900
901
902
903
  // Errors that occur inside PHP internal functions
  // do not generate information about file and line.
  while ($backtrace && !isset($backtrace[0]['line'])) {
    array_shift($backtrace);
  }

904
905
906
  // The first trace is the call itself.
  // It gives us the line and the file of the last call.
  $call = $backtrace[0];
907

908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
  // The second call give us the function where the call originated.
  if (isset($backtrace[1])) {
    if (isset($backtrace[1]['class'])) {
      $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
    }
    else {
      $call['function'] = $backtrace[1]['function'] . '()';
    }
  }
  else {
    $call['function'] = 'main()';
  }
  return $call;
}

Dries's avatar
   
Dries committed
923
function _fix_gpc_magic(&$item) {
Dries's avatar
Dries committed
924
  if (is_array($item)) {
Kjartan's avatar
Kjartan committed
925
926
927
    array_walk($item, '_fix_gpc_magic');
  }
  else {
Kjartan's avatar
Kjartan committed
928
    $item = stripslashes($item);
Dries's avatar
   
Dries committed
929
930
931
  }
}

932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
/**
 * Helper function to strip slashes from $_FILES skipping over the tmp_name keys
 * since PHP generates single backslashes for file paths on Windows systems.
 *
 * tmp_name does not have backslashes added see
 * http://php.net/manual/en/features.file-upload.php#42280
 */
function _fix_gpc_magic_files(&$item, $key) {
  if ($key != 'tmp_name') {
    if (is_array($item)) {
      array_walk($item, '_fix_gpc_magic_files');
    }
    else {
      $item = stripslashes($item);
    }
  }
}

Dries's avatar
   
Dries committed
950
/**
951
 * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
Dries's avatar
   
Dries committed
952
 */
Dries's avatar
   
Dries committed
953
function fix_gpc_magic() {
954
  $fixed = &drupal_static(__FUNCTION__, FALSE);
Dries's avatar
   
Dries committed
955
  if (!$fixed && ini_get('magic_quotes_gpc')) {
Dries's avatar
Dries committed
956
957
958
959
    array_walk($_GET, '_fix_gpc_magic');
    array_walk($_POST, '_fix_gpc_magic');
    array_walk($_COOKIE, '_fix_gpc_magic');
    array_walk($_REQUEST, '_fix_gpc_magic');
960
    array_walk($_FILES, '_fix_gpc_magic_files');
961
    $fixed = TRUE;
Dries's avatar
Dries committed
962
  }
Dries's avatar
   
Dries committed
963
964
}

Kjartan's avatar
Kjartan committed
965
/**
966
 * Translate strings to the page language or a given language.
Kjartan's avatar
Kjartan committed
967
 *
968
 * Human-readable text that will be displayed somewhere within a page should
969
 * be run through the t() function.
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
 *
 * Examples:
 * @code
 *   if (!$info || !$info['extension']) {
 *     form_set_error('picture_upload', t('The uploaded file was not an image.'));
 *   }
 *
 *   $form['submit'] = array(
 *     '#type' => 'submit',
 *     '#value' => t('Log in'),
 *   );
 * @endcode
 *
 * Any text within t() can be extracted by translators and changed into
 * the equivalent text in their native language.
 *
 * Special variables called "placeholders" are used to signal dynamic
 * information in a string which should not be translated. Placeholders
988
989
 * can also be used for text that may change from time to time (such as
 * link paths) to be changed without requiring updates to translations.
990
991
992
993
994
995
996
997
998
999
1000
1001
 *
 * For example:
 * @code
 *   $output = t('There are currently %members and %visitors online.', array(
 *     '%members' => format_plural($total_users, '1 user', '@count users'),
 *     '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
 * @endcode
 *
 * There are three styles of placeholders:
 * - !variable, which indicates that the text should be inserted as-is. This is
 *   useful for inserting variables into things like e-mail.
 *   @code
1002
 *     $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))));
1003
1004
 *   @endcode
 *
1005
1006
1007
 * - @variable, which indicates that the text should be run through
 *   check_plain, to escape HTML characters. Use this for any output that's
 *   displayed within a Drupal page.
1008
 *   @code
1009
 *     drupal_set_title($title = t("@name's blog", array('@name' => $account->name)), PASS_THROUGH);
1010
1011
 *   @endcode
 *
1012
1013
1014
 * - %variable, which indicates that the string should be HTML escaped and
 *   highlighted with theme_placeholder() which shows up by default as
 *   <em>emphasized</em>.
1015
 *   @code
1016
 *     $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
1017
1018
 *   @endcode
 *
1019
 * When using t(), try to put entire sentences and strings in one t() call.
1020
1021
1022
1023
 * This makes it easier for translators, as it provides context as to what
 * each word refers to. HTML markup within translation strings is allowed, but
 * should be avoided if possible. The exception are embedded links; link
 * titles add a context for translators, so should be kept in the main string.
1024
 *
1025
 * Here is an example of incorrect usage of t():
1026
1027
1028
1029
1030
1031
 * @code
 *   $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact')));
 * @endcode
 *
 * Here is an example of t() used correctly:
 * @code
1032
 *   $output .= '<p>' . t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) . '</p>';
1033
1034
 * @endcode
 *
1035
 * Avoid escaping quotation marks wherever possible.
1036
1037
1038
1039
1040
1041
1042
 *
 * Incorrect:
 * @code
 *   $output .= t('Don\'t click me.');
 * @endcode
 *
 * Correct:
Dries's avatar
   
Dries committed
1043
 * @code
1044
 *   $output .= t("Don't click me.");
Dries's avatar
   
Dries committed
1045
 * @endcode
Kjartan's avatar
Kjartan committed
1046
 *
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
 * Because t() is designed for handling code-based strings, in almost all
 * cases, the actual string and not a variable must be passed through t().
 *
 * Extraction of translations is done based on the strings contained in t()
 * calls. If a variable is passed through t(), the content of the variable
 * cannot be extracted from the file for translation.
 *
 * Incorrect:
 * @code
 *   $message = 'An error occurred.';
 *   drupal_set_message(t($message), 'error');
 *   $output .= t($message);
 * @endcode
 *
 * Correct:
 * @code
 *   $message = t('An error occurred.');
 *   drupal_set_message($message, 'error');
 *   $output .= $message;
 * @endcode
 *
 * The only case in which variables can be passed safely through t() is when
 * code-based versions of the same strings will be passed through t() (or
 * otherwise extracted) elsewhere.
 *
 * In some cases, modules may include strings in code that can't use t()
 * calls. For example, a module may use an external PHP application that
 * produces strings that are loaded into variables in Drupal for output.
 * In these cases, module authors may include a dummy file that passes the
 * relevant strings through t(). This approach will allow the strings to be
 * extracted.
 *
 * Sample external (non-Drupal) code:
 * @code
 *   class Time {
 *     public $yesterday = 'Yesterday';
 *     public $today = 'Today';
 *     public $tomorrow = 'Tomorrow';
 *   }
 * @endcode
 *
1088
 * Sample dummy file.
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
 * @code
 *   // Dummy function included in example.potx.inc.
 *   function example_potx() {
 *     $strings = array(
 *       t('Yesterday'),
 *       t('Today'),
 *       t('Tomorrow'),
 *     );
 *     // No return value needed, since this is a