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

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;
  }
}

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

132
  if (!is_null($breadcrumb)) {
133 134 135 136 137
    $stored_breadcrumb = $breadcrumb;
  }
  return $stored_breadcrumb;
}

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

144
  if (is_null($breadcrumb)) {
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
/**
165
 * Add output to the head tag of the HTML page.
166
 *
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;
}

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() {
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
}

186
/**
187
 * Reset the static variable which holds the aliases mapped for this request.
188
 */
189 190
function drupal_clear_path_cache() {
  drupal_lookup_path('wipe');
191
}
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);
}

228 229 230
/**
 * @name HTTP handling
 * @{
231
 * Functions to properly handle HTTP responses.
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
  }
}

296
/**
297
 * Send the user to a different Drupal page.
298
 *
299 300
 * This issues an on-site HTTP redirect. The function makes sure the redirected
 * URL is formatted correctly.
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.
313
 *
314
 * This function ends the request; use it instead of a return in your menu callback.
315 316
 *
 * @param $path
317
 *   A Drupal path or a full URL.
318
 * @param $query
319
 *   A query string component, if any.
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()
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));
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);
  }
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.
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
}

372 373 374
/**
 * Generates a 404 error if the request can not be handled.
 */
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'];
  }

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
  }
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.');
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);
405
}
406

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);
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'];
  }

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
  }
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.');
431
  }
432 433

  print drupal_render_page($return);
434 435
}

436
/**
437
 * Perform an HTTP request.
438
 *
439 440
 * This is a flexible and powerful HTTP client implementation. Correctly
 * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
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.
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.
480
 */
481
function drupal_http_request($url, array $options = array()) {
482
  global $db_prefix;
483

484
  $result = new stdClass();
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,
  );

510 511
  switch ($uri['scheme']) {
    case 'http':
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']);
515 516
      break;
    case 'https':
517
      // Note: Only works when PHP is compiled with OpenSSL support.
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']);
521 522
      break;
    default:
523
      $result->error = 'invalid schema ' . $uri['scheme'];
524 525 526
      return $result;
  }

527
  // Make sure the socket opened properly.
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);

540 541 542
    return $result;
  }

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

549 550 551
  // Merge the default headers.
  $options['headers'] += array(
    'User-Agent' => 'Drupal (+http://drupal.org/)',
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";
586
  }
587
  $request .= "\r\n" . $options['data'];
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);
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;

616 617
  $result->headers = array();

618 619
  // Parse the response headers.
  while ($line = trim(array_shift($response))) {
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);
    }
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',
672
  );
673 674
  // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  // base code in their class.
675 676 677
  if (!isset($responses[$code])) {
    $code = floor($code / 100) * 100;
  }
678
  $result->code = $code;
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;
698 699 700 701
      }
      $result->redirect_url = $location;
      break;
    default:
702
      $result->error = $status_message;
703 704 705 706
  }

  return $result;
}
707 708 709
/**
 * @} End of "HTTP handling".
 */
710

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.
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
  }
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;
}

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);
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);
    }
  }
}

950
/**
951
 * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
952
 */
953
function fix_gpc_magic() {
954
  $fixed = &drupal_static(__FUNCTION__, FALSE);
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
  }
963 964
}

965
/**
966
 * Translate strings to the page language or a given language.
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:
1043
 * @code
1044
 *   $output .= t("Don't click me.");
1045
 * @endcode
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 dummy function.
 *   }
 * @endcode
 *
 * Having passed strings through t() in a dummy function, it is then
 * okay to pass variables through t().
 *
 * Correct (if a dummy file was used):
 * @code
 *   $time = new Time();
 *   $output .= t($time->today);
 * @endcode
 *
 * However tempting it is, custom data from user input or other non-code
 * sources should not be passed through t(). Doing so leads to the following
 * problems and errors:
 *  - The t() system doesn't support updates to existing strings. When user
 *    data is updated, the next time it's passed through t() a new record is
 *    created instead of an update. The database bloats over time and any
 *    existing translations are orphaned with each update.
 *  - The t() system assumes any data it receives is in English. User data may
 *    be in another language, producing translation errors.
 *  - The "Built-in interface" text group in the locale system is used to
 *    produce translations for storage in .po files. When non-code strings are
 *    passed through t(), they are added to this text group, which is rendered
 *    inaccurate since it is a mix of actual interface strings and various user
 *    input strings of uncertain origin.
 *
 * Incorrect:
 * @code
 *   $item = item_load();
 *   $output .= check_plain(t($item['title']));
 * @endcode
 *
 * Instead, translation of these data can be done through the locale system,
 * either directly or through helper functions provided by contributed
 * modules.
 * @see hook_locale()
 *
 * During installation, st() is used in place of t(). Code that may be called
 * during installation or during normal operation should use the get_t()
 * helper function.
 * @see st()
1140 1141
 * @see get_t()
 *
1142
 * @param $string
1143
 *   A string containing the English string to translate.
1144 1145
 * @param $args
 *   An associative array of replacements to make after translation. Incidences
1146 1147
 *   of any key in this array are replaced with the corresponding value. Based
 *   on the first character of the key, the value is escaped and/or themed:
1148 1149 1150 1151
 *    - !variable: inserted as is
 *    - @variable: escape plain text to HTML (check_plain)
 *    - %variable: escape text and theme as a placeholder for user-submitted
 *      content (check_plain + theme_placeholder)
1152 1153 1154 1155 1156 1157
 * @param $options
 *   An associative array of additional options, with the following keys:
 *     - 'langcode' (default to the current language) The language code to
 *       translate to a language other than what is used to display the page.
 *     - 'context' (default to the empty context) The context the source string
 *       belongs to.
1158 1159
 * @return
 *   The translated string.
1160
 */
1161
function t($string, array $args = array(), array $options = array()) {
1162
  global $language;
1163
  static $custom_strings;
1164

1165 1166 1167 1168 1169 1170
  // Merge in default.
  if (empty($options['langcode'])) {
    $options['langcode'] = isset($language->language) ? $language->language : 'en';
  }
  if (empty($options['context'])) {
    $options['context'] = '';
1171
  }
1172

1173 1174 1175 1176
  // First, check for an array of customized strings. If present, use the array
  // *instead of* database lookups. This is a high performance way to provide a
  // handful of string replacements. See settings.php for examples.
  // Cache the $custom_strings variable to improve performance.
1177 1178
  if (!isset($custom_strings[$options['langcode']])) {
    $custom_strings[$options['langcode']] = variable_get('locale_custom_strings_' . $options['langcode'], array());
1179 1180
  }
  // Custom strings work for English too, even if locale module is disabled.
1181 1182
  if (isset($custom_strings[$options['langcode']][$options['context']][$string])) {
    $string = $custom_strings[$options['langcode']][$options['context']][$string];
1183 1184
  }
  // Translate with locale module if enabled.
1185 1186 1187
  // We don't use drupal_function_exists() here, because it breaks the testing
  // framework if the locale module is enabled in the parent site (we cannot
  // unload functions in PHP).