common.inc 185 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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
 * Constants defining cache granularity for blocks and renderable arrays.
 *
 * Modules specify the caching patterns for their blocks using binary
 * combinations of these constants in their hook_block_info():
 *   $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE;
 * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is
 * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
 * implement
 *
 * The block cache is cleared in cache_clear_all(), and uses the same clearing
 * policy than page cache (node, comment, user, taxonomy added or updated...).
 * Blocks requiring more fine-grained clearing might consider disabling the
 * built-in block cache (DRUPAL_NO_CACHE) and roll their own.
 *
 * Note that user 1 is excluded from block caching.
 */

/**
 * The block should not get cached. This setting should be used:
 * - for simple blocks (notably those that do not perform any db query),
 * where querying the db cache would be more expensive than directly generating
 * the content.
 * - for blocks that change too frequently.
 */
define('DRUPAL_NO_CACHE', -1);

/**
 * The block is handling its own caching in its hook_block_view(). From the
 * perspective of the block cache system, this is equivalent to DRUPAL_NO_CACHE.
 * Useful when time based expiration is needed or a site uses a node access
 * which invalidates standard block cache.
 */
define('DRUPAL_CACHE_CUSTOM', -2);

/**
 * The block or element can change depending on the roles the user viewing the
 * page belongs to. This is the default setting for blocks, used when the block
 * does not specify anything.
 */
define('DRUPAL_CACHE_PER_ROLE', 0x0001);

/**
 * The block or element can change depending on the user viewing the page.
 * This setting can be resource-consuming for sites with large number of users,
 * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
 */
define('DRUPAL_CACHE_PER_USER', 0x0002);

/**
 * The block or element can change depending on the page being viewed.
 */
define('DRUPAL_CACHE_PER_PAGE', 0x0004);

/**
 * The block or element is the same for every user on every page where it is visible.
 */
define('DRUPAL_CACHE_GLOBAL', 0x0008);

138
/**
139
 * Add content to a specified region.
140
141
 *
 * @param $region
142
 *   Page region the content is added to.
143
 * @param $data
144
 *   Content to be added.
145
 */
146
function drupal_add_region_content($region = NULL, $data = NULL) {
147
148
149
150
151
152
153
154
155
  static $content = array();

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

/**
156
 * Get assigned content for a given region.
157
158
 *
 * @param $region
159
160
 *   A specified region to fetch content for. If NULL, all regions will be
 *   returned.
161
 * @param $delimiter
162
 *   Content to be inserted between imploded array elements.
163
 */
164
165
function drupal_get_region_content($region = NULL, $delimiter = ' ') {
  $content = drupal_add_region_content();
166
167
  if (isset($region)) {
    if (isset($content[$region]) && is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
168
      return implode($delimiter, $content[$region]);
169
    }
170
171
172
173
  }
  else {
    foreach (array_keys($content) as $region) {
      if (is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
174
        $content[$region] = implode($delimiter, $content[$region]);
175
176
177
178
179
180
      }
    }
    return $content;
  }
}

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/**
 * Get the name of the currently active install profile.
 *
 * When this function is called during Drupal's initial installation process,
 * the name of the profile that's about to be installed is stored in the global
 * installation state. At all other times, the standard Drupal systems variable
 * table contains the name of the current profile, and we can call variable_get()
 * to determine what one is active.
 *
 * @return $profile
 *   The name of the install profile.
 */
function drupal_get_profile() {
  global $install_state;

  if (isset($install_state['parameters']['profile'])) {
    $profile = $install_state['parameters']['profile'];
  }
  else {
    $profile = variable_get('install_profile', 'default');
  }

  return $profile;
}


Dries's avatar
   
Dries committed
207
/**
Dries's avatar
   
Dries committed
208
 * Set the breadcrumb trail for the current page.
Dries's avatar
   
Dries committed
209
 *
Dries's avatar
   
Dries committed
210
211
212
 * @param $breadcrumb
 *   Array of links, starting with "home" and proceeding up to but not including
 *   the current page.
Kjartan's avatar
Kjartan committed
213
 */
Dries's avatar
   
Dries committed
214
function drupal_set_breadcrumb($breadcrumb = NULL) {
215
  $stored_breadcrumb = &drupal_static(__FUNCTION__);
Dries's avatar
   
Dries committed
216

217
  if (!is_null($breadcrumb)) {
Dries's avatar
   
Dries committed
218
219
220
221
222
    $stored_breadcrumb = $breadcrumb;
  }
  return $stored_breadcrumb;
}

Dries's avatar
   
Dries committed
223
224
225
/**
 * Get the breadcrumb trail for the current page.
 */
Dries's avatar
   
Dries committed
226
227
228
function drupal_get_breadcrumb() {
  $breadcrumb = drupal_set_breadcrumb();

229
  if (is_null($breadcrumb)) {
Dries's avatar
   
Dries committed
230
231
232
233
234
235
    $breadcrumb = menu_get_active_breadcrumb();
  }

  return $breadcrumb;
}

236
/**
237
238
 * Return a string containing RDF namespaces for the <html> tag of an XHTML
 * page.
239
240
241
242
243
244
245
246
247
248
 */
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
249
/**
Dries's avatar
   
Dries committed
250
 * Add output to the head tag of the HTML page.
251
 *
Dries's avatar
   
Dries committed
252
 * This function can be called as long the headers aren't sent.
Dries's avatar
Dries committed
253
 */
254
function drupal_add_html_head($data = NULL) {
255
  $stored_head = &drupal_static(__FUNCTION__, '');
Dries's avatar
Dries committed
256
257

  if (!is_null($data)) {
258
    $stored_head .= $data . "\n";
Dries's avatar
Dries committed
259
260
261
262
  }
  return $stored_head;
}

Dries's avatar
   
Dries committed
263
264
265
/**
 * Retrieve output to be displayed in the head tag of the HTML page.
 */
Dries's avatar
Dries committed
266
function drupal_get_html_head() {
Dries's avatar
   
Dries committed
267
  $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
268
  return $output . drupal_add_html_head();
Dries's avatar
Dries committed
269
270
}

Dries's avatar
   
Dries committed
271
/**
272
 * Reset the static variable which holds the aliases mapped for this request.
Dries's avatar
   
Dries committed
273
 */
274
275
function drupal_clear_path_cache() {
  drupal_lookup_path('wipe');
Dries's avatar
   
Dries committed
276
}
Kjartan's avatar
Kjartan committed
277

278
/**
279
280
 * Add a feed URL for the current page.
 *
281
282
 * This function can be called as long the HTML header hasn't been sent.
 *
283
 * @param $url
284
 *   A url for the feed.
285
 * @param $title
286
 *   The title of the feed.
287
 */
288
function drupal_add_feed($url = NULL, $title = '') {
289
  $stored_feed_links = &drupal_static(__FUNCTION__, array());
290

291
  if (!is_null($url) && !isset($stored_feed_links[$url])) {
292
    $stored_feed_links[$url] = theme('feed_icon', $url, $title);
293
294
295
296
297

    drupal_add_link(array('rel' => 'alternate',
                          'type' => 'application/rss+xml',
                          'title' => $title,
                          'href' => $url));
298
299
300
301
302
303
304
305
  }
  return $stored_feed_links;
}

/**
 * Get the feed URLs for the current page.
 *
 * @param $delimiter
306
 *   A delimiter to split feeds by.
307
308
309
310
311
312
 */
function drupal_get_feeds($delimiter = "\n") {
  $feeds = drupal_add_feed();
  return implode($feeds, $delimiter);
}

Dries's avatar
   
Dries committed
313
314
315
/**
 * @name HTTP handling
 * @{
Dries's avatar
   
Dries committed
316
 * Functions to properly handle HTTP responses.
Dries's avatar
   
Dries committed
317
318
 */

319
320
321
322
/**
 * Parse an array into a valid urlencoded query string.
 *
 * @param $query
323
 *   The array to be processed e.g. $_GET.
324
 * @param $exclude
325
326
 *   The array filled with keys to be excluded. Use parent[child] to exclude
 *   nested items.
327
 * @param $parent
328
 *   Should not be passed, only used in recursive calls.
329
 * @return
330
 *   An urlencoded string which can be appended to/as the URL query string.
331
332
333
334
335
 */
function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
  $params = array();

  foreach ($query as $key => $value) {
336
    $key = rawurlencode($key);
337
    if ($parent) {
338
      $key = $parent . '[' . $key . ']';
339
340
    }

341
    if (in_array($key, $exclude)) {
342
343
344
345
346
347
348
      continue;
    }

    if (is_array($value)) {
      $params[] = drupal_query_string_encode($value, $exclude, $key);
    }
    else {
349
      $params[] = $key . '=' . rawurlencode($value);
350
351
352
353
354
355
    }
  }

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

356
/**
357
 * Prepare a destination query string for use in combination with drupal_goto().
358
 *
359
360
361
362
 * 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.
363
364
365
366
 *
 * @see drupal_goto()
 */
function drupal_get_destination() {
367
368
  if (isset($_GET['destination'])) {
    return 'destination=' . urlencode($_GET['destination']);
369
370
  }
  else {
371
372
    // Use $_GET here to retrieve the original path in source form.
    $path = isset($_GET['q']) ? $_GET['q'] : '';
373
374
    $query = drupal_query_string_encode($_GET, array('q'));
    if ($query != '') {
375
      $path .= '?' . $query;
376
    }
377
    return 'destination=' . urlencode($path);
378
379
380
  }
}

Kjartan's avatar
Kjartan committed
381
/**
Dries's avatar
   
Dries committed
382
 * Send the user to a different Drupal page.
Kjartan's avatar
Kjartan committed
383
 *
Dries's avatar
   
Dries committed
384
385
 * This issues an on-site HTTP redirect. The function makes sure the redirected
 * URL is formatted correctly.
Kjartan's avatar
Kjartan committed
386
 *
387
 * Usually the redirected URL is constructed from this function's input
388
 * parameters. However you may override that behavior by setting a
389
 * destination in either the $_REQUEST-array (i.e. by using
390
 * the query string of an URI) This is used to direct the user back to
391
 * the proper page after completing a form. For example, after editing
392
 * a post on the 'admin/content'-page or after having logged on using the
393
 * 'user login'-block in a sidebar. The function drupal_get_destination()
394
395
 * can be used to help set the destination URL.
 *
396
397
 * 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
398
 *
399
 * This function ends the request; use it instead of a return in your menu callback.
Dries's avatar
   
Dries committed
400
401
 *
 * @param $path
402
 *   A Drupal path or a full URL.
Dries's avatar
   
Dries committed
403
 * @param $query
404
 *   A query string component, if any.
Dries's avatar
   
Dries committed
405
 * @param $fragment
406
 *   A destination fragment identifier (named anchor).
407
408
409
410
411
412
413
414
 * @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
415
 *   - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
416
 *   Note: Other values are defined by RFC 2616, but are rarely used and poorly
417
 *   supported.
418
 * @see drupal_get_destination()
Kjartan's avatar
Kjartan committed
419
 */
420
function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
421
422
  if (isset($_GET['destination'])) {
    extract(parse_url(urldecode($_GET['destination'])));
423
424
  }

425
426
427
428
429
430
431
432
  $args = array(
    'path' => &$path,
    'query' => &$query,
    'fragment' => &$fragment,
    'http_response_code' => &$http_response_code,
  );
  drupal_alter('drupal_goto', $args);

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

435
  // Allow modules to react to the end of the page request before redirecting.
436
  // We do not want this while running update.php.
437
  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
438
439
    module_invoke_all('exit', $url);
  }
Dries's avatar
   
Dries committed
440

441
442
443
  // Commit the session, if necessary. We need all session data written to the
  // database before redirecting.
  drupal_session_commit();
444

445
  header('Location: ' . $url, TRUE, $http_response_code);
446
447

  // The "Location" header sends a redirect status code to the HTTP daemon. In
448
449
  // 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
450
451
452
  exit();
}

453
/**
454
 * Generates a site offline message.
455
456
 */
function drupal_site_offline() {
457
  drupal_maintenance_theme();
458
  drupal_set_header('503 Service unavailable');
459
460
  drupal_set_title(t('Site under maintenance'));
  print theme('maintenance_page', filter_xss_admin(variable_get('maintenance_mode_message',
461
    t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
462
463
}

Kjartan's avatar
Kjartan committed
464
465
466
/**
 * Generates a 404 error if the request can not be handled.
 */
Dries's avatar
   
Dries committed
467
function drupal_not_found() {
468
  drupal_set_header('404 Not Found');
469

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

472
  // Keep old path for reference, and to allow forms to redirect to it.
473
474
  if (!isset($_GET['destination'])) {
    $_GET['destination'] = $_GET['q'];
475
476
  }

Dries's avatar
   
Dries committed
477
  $path = drupal_get_normal_path(variable_get('site_404', ''));
drumm's avatar
drumm committed
478
  if ($path && $path != $_GET['q']) {
479
480
    // Custom 404 handler. Set the active item in case there are tabs to
    // display, or other dependencies on the path.
481
    menu_set_active_item($path);
482
    $return = menu_execute_active_handler($path);
483
  }
Dries's avatar
   
Dries committed
484

485
  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
486
    // Standard 404 handler.
drumm's avatar
drumm committed
487
    drupal_set_title(t('Page not found'));
488
    $return = t('The requested page could not be found.');
Dries's avatar
   
Dries committed
489
  }
490

491
492
  drupal_set_page_content($return);
  $page = element_info('page');
493
  print drupal_render_page($page);
Dries's avatar
   
Dries committed
494
}
Dries's avatar
   
Dries committed
495

Dries's avatar
   
Dries committed
496
497
498
499
/**
 * Generates a 403 error if the request is not allowed.
 */
function drupal_access_denied() {
500
  drupal_set_header('403 Forbidden');
501
  watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
Dries's avatar
   
Dries committed
502

503
  // Keep old path for reference, and to allow forms to redirect to it.
504
505
  if (!isset($_GET['destination'])) {
    $_GET['destination'] = $_GET['q'];
506
507
  }

Dries's avatar
   
Dries committed
508
  $path = drupal_get_normal_path(variable_get('site_403', ''));
drumm's avatar
drumm committed
509
  if ($path && $path != $_GET['q']) {
510
511
    // Custom 403 handler. Set the active item in case there are tabs to
    // display or other dependencies on the path.
512
    menu_set_active_item($path);
513
    $return = menu_execute_active_handler($path);
514
  }
Dries's avatar
   
Dries committed
515

516
  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
517
    // Standard 403 handler.
drumm's avatar
drumm committed
518
519
    drupal_set_title(t('Access denied'));
    $return = t('You are not authorized to access this page.');
Dries's avatar
   
Dries committed
520
  }
521
522

  print drupal_render_page($return);
Dries's avatar
   
Dries committed
523
524
}

Dries's avatar
   
Dries committed
525
/**
Dries's avatar
   
Dries committed
526
 * Perform an HTTP request.
Dries's avatar
   
Dries committed
527
 *
528
529
 * 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
530
531
532
 *
 * @param $url
 *   A string containing a fully qualified URI.
533
534
535
536
537
538
539
540
541
542
543
 * @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.
544
545
546
547
 *   - 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
548
 * @return
549
550
551
552
553
554
 *   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.
555
 *   - protocol
556
 *       The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
557
 *   - status_message
558
 *       The status message from the response, if a response was received.
559
560
561
562
563
 *   - redirect_code
 *       If redirected, an integer containing the initial response status code.
 *   - redirect_url
 *       If redirected, a string containing the redirection location.
 *   - error
564
 *       If an error occurred, the error message. Otherwise not set.
565
566
567
568
 *   - 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
569
 */
570
function drupal_http_request($url, array $options = array()) {
571
  global $db_prefix;
572

573
  $result = new stdClass();
Dries's avatar
   
Dries committed
574

575
  // Parse the URL and make sure we can handle the schema.
576
  $uri = @parse_url($url);
577

578
579
  if ($uri == FALSE) {
    $result->error = 'unable to parse URL';
580
    $result->code = -1001;
581
582
583
    return $result;
  }

584
585
  if (!isset($uri['scheme'])) {
    $result->error = 'missing schema';
586
    $result->code = -1002;
587
588
589
    return $result;
  }

590
591
592
593
594
595
596
597
598
599
600
  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
601
602
  switch ($uri['scheme']) {
    case 'http':
Dries's avatar
Dries committed
603
      $port = isset($uri['port']) ? $uri['port'] : 80;
604
      $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
605
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
Dries's avatar
   
Dries committed
606
607
      break;
    case 'https':
608
      // Note: Only works when PHP is compiled with OpenSSL support.
Dries's avatar
Dries committed
609
      $port = isset($uri['port']) ? $uri['port'] : 443;
610
      $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
611
      $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
Dries's avatar
   
Dries committed
612
613
      break;
    default:
614
      $result->error = 'invalid schema ' . $uri['scheme'];
615
      $result->code = -1003;
Dries's avatar
   
Dries committed
616
617
618
      return $result;
  }

Dries's avatar
   
Dries committed
619
  // Make sure the socket opened properly.
Dries's avatar
   
Dries committed
620
  if (!$fp) {
621
622
    // When a network error occurs, we use a negative number so it does not
    // clash with the HTTP status codes.
623
624
    $result->code = -$errno;
    $result->error = trim($errstr);
625
626
627
628
629
630
631

    // 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
632
633
634
    return $result;
  }

Dries's avatar
   
Dries committed
635
  // Construct the path to act on.
Dries's avatar
Dries committed
636
637
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
638
    $path .= '?' . $uri['query'];
Dries's avatar
   
Dries committed
639
640
  }

641
642
643
  // Merge the default headers.
  $options['headers'] += array(
    'User-Agent' => 'Drupal (+http://drupal.org/)',
Dries's avatar
   
Dries committed
644
645
  );

646
647
648
649
650
  // 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;

651
652
653
654
  // 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.
655
656
657
  $content_length = strlen($options['data']);
  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
    $options['headers']['Content-Length'] = $content_length;
658
659
660
  }

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

665
666
667
668
669
670
  // 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.
671
672
  if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
    $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
673
674
  }

675
  $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
676
  foreach ($options['headers'] as $name => $value) {
677
    $request .= $name . ': ' . trim($value) . "\r\n";
Dries's avatar
   
Dries committed
678
  }
679
  $request .= "\r\n" . $options['data'];
Dries's avatar
   
Dries committed
680
681
682
683
684
  $result->request = $request;

  fwrite($fp, $request);

  // Fetch response.
685
  $response = '';
686
687
688
689
690
691
692
693
694
695
  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
696
697
698
  }
  fclose($fp);

699
700
701
  // 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);
702

703
  // Parse the response status line.
704
705
706
707
  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
  $result->protocol = $protocol;
  $result->status_message = $status_message;

Dries's avatar
   
Dries committed
708
709
  $result->headers = array();

710
711
  // Parse the response headers.
  while ($line = trim(array_shift($response))) {
Dries's avatar
   
Dries committed
712
    list($header, $value) = explode(':', $line, 2);
713
714
715
    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.
716
      $result->headers[$header] .= ',' . trim($value);
717
718
719
720
    }
    else {
      $result->headers[$header] = trim($value);
    }
Dries's avatar
   
Dries committed
721
722
723
  }

  $responses = array(
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
    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
764
  );
765
766
  // 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
767
768
769
  if (!isset($responses[$code])) {
    $code = floor($code / 100) * 100;
  }
770
  $result->code = $code;
Dries's avatar
   
Dries committed
771
772
773
774
775
776
777
778
779

  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'];
780
781
782
783
784
785
      $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
      if ($options['timeout'] <= 0) {
        $result->code = HTTP_REQUEST_TIMEOUT;
        $result->error = 'request timed out';
      }
      elseif ($options['max_redirects']) {
786
787
788
        // Redirect to the new location.
        $options['max_redirects']--;
        $result = drupal_http_request($location, $options);
789
        $result->redirect_code = $code;
Dries's avatar
   
Dries committed
790
791
792
793
      }
      $result->redirect_url = $location;
      break;
    default:
794
      $result->error = $status_message;
Dries's avatar
   
Dries committed
795
796
797
798
  }

  return $result;
}
Dries's avatar
   
Dries committed
799
800
801
/**
 * @} End of "HTTP handling".
 */
Dries's avatar
   
Dries committed
802

Dries's avatar
   
Dries committed
803
/**
804
 * Custom PHP error handler.
805
 *
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
 * @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'
    );
835
836
    $caller = _drupal_get_last_caller(debug_backtrace());

837
    // We treat recoverable errors as fatal.
838
839
840
841
842
843
844
    _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);
845
846
847
848
849
850
851
852
853
854
855
856
  }
}

/**
 * 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
857
 */
858
function _drupal_exception_handler($exception) {
859
860
861
862
863
864
865
866
867
868
869
870
  // 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) {
871
872
  $message = $exception->getMessage();

873
874
875
876
877
878
879
880
881
882
  $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.
883
    $db_functions = array('db_query',  'db_query_range', 'update_sql');
884
    while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
885
        ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
886
        in_array($caller['function'], $db_functions))) {
887
888
889
      // We remove that call.
      array_shift($backtrace);
    }
890
891
892
    if (isset($exception->query_string, $exception->args)) {
      $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
    }
893
  }
894
  $caller = _drupal_get_last_caller($backtrace);
895

896
897
  return array(
    '%type' => get_class($exception),
898
    '%message' => $message,
899
900
901
902
    '%function' => $caller['function'],
    '%file' => $caller['file'],
    '%line' => $caller['line'],
  );
903
}
904

905
906
907
/**
 * Log a PHP error or exception, display an error page in fatal cases.
 *
908
909
 * @param $error
 *   An array with the following keys: %type, %message, %function, %file, %line.
910
911
912
 * @param $fatal
 *   TRUE if the error is fatal.
 */
913
function _drupal_log_error($error, $fatal = FALSE) {
914
  // Initialize a maintenance theme if the boostrap was not complete.
915
  // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
916
917
  if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
    unset($GLOBALS['theme']);
918
919
920
    if (!defined('MAINTENANCE_MODE')) {
      define('MAINTENANCE_MODE', 'error');
    }
921
922
    drupal_maintenance_theme();
  }
923

924
925
  // When running inside the testing framework, we relay the errors
  // to the tested site by the way of HTTP headers.
926
  if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
927
928
    // $number does not use drupal_static as it should not be reset
    // as it uniquely identifies each PHP error.
929
930
    static $number = 0;
    $assertion = array(
931
932
      $error['%message'],
      $error['%type'],
933
934
935
936
937
      array(
        'function' => $error['%function'],
        'file' => $error['%file'],
        'line' => $error['%line'],
      ),
938
939
940
941
942
    );
    header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
    $number++;
  }

943
944
945
946
  try {
    watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
  }
  catch (Exception $e) {
947
948
    // Ignore any additional watchdog exception, as that probably means
    // that the database was not initialized correctly.
949
  }
Dries's avatar
   
Dries committed
950

951
  if ($fatal) {
952
    drupal_set_header('500 Service unavailable (with message)');
953
954
955
956
957
958
959
  }

  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
960
    }
961
962
963
964
965
966
967
  }
  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')) {
968
969
970
971
972
973
974
975
976
977
      $class = 'error';

      // If error type is 'User notice' then treat it as debug information
      // instead of an error message, see dd().
      if ($error['%type'] == 'User notice') {
        $error['%type'] = 'Debug';
        $class = 'status';
      }

      drupal_set_message(t('%type: %message in %function (line %line of %file).', $error), $class);
978
979
980
981
982
983
    }

    if ($fatal) {
      drupal_set_title(t('Error'));
      // We fallback to a maintenance page at this point, because the page generation
      // itself can generate errors.
984
      print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'));
985
      exit;
986
    }
Dries's avatar
   
Dries committed
987
988
989
  }
}

990
/**
991
 * Gets the last caller from a backtrace.
992
993
994
995
996
997
998
 *
 * @param $backtrace
 *   A standard PHP backtrace.
 * @return
 *   An associative array with keys 'file', 'line' and 'function'.
 */
function _drupal_get_last_caller($backtrace) {
999
1000
  // Errors that occur inside PHP internal functions do not generate
  // information about file and line. Ignore black listed functions.