~pressflow/pressflow/6

1 by bzr
Drupal 6.6
1
<?php
1.1.13 by David Strauss
Drupal 6.19
2
// $Id: bootstrap.inc,v 1.206.2.29 2010/08/06 11:50:24 goba Exp $
1 by bzr
Drupal 6.6
3
4
/**
5
 * @file
6
 * Functions that need to be loaded on every Drupal request.
7
 */
8
9
/**
10
 * Indicates that the item should never be removed unless explicitly told to
11
 * using cache_clear_all() with a cache ID.
12
 */
13
define('CACHE_PERMANENT', 0);
14
15
/**
16
 * Indicates that the item should be removed at the next general cache wipe.
17
 */
18
define('CACHE_TEMPORARY', -1);
19
20
/**
21
 * Indicates that page caching is disabled.
22
 */
23
define('CACHE_DISABLED', 0);
24
25
/**
26
 * Indicates that page caching is enabled, using "normal" mode.
27
 */
28
define('CACHE_NORMAL', 1);
29
30
/**
31
 * Indicates that page caching is using "aggressive" mode. This bypasses
32
 * loading any modules for additional speed, which may break functionality in
33
 * modules that expect to be run on each page load.
34
 */
35
define('CACHE_AGGRESSIVE', 2);
36
37
/**
63 by David Strauss
David Strauss: Add an 'external' caching mode that skips the built-in cache but still sends friendly headers downstream.
38
 * Indicates that page caching is using "external" mode. This disables the
39
 * internal page cache but returns headers allowing downstream caches (such
40
 * as Squid, Varnish, and other reverse proxies) to cache full pages. Like
41
 * "aggressive" mode, this may break functionality in some modules.
42
 */
43
define('CACHE_EXTERNAL', 3);
44
45
/**
1.1.11 by David Strauss
Drupal 6.17
46
 * Log message severity -- Emergency: system is unusable.
47
 *
48
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
49
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
50
 *
51
 * @see watchdog()
52
 * @see watchdog_severity_levels()
53
 */
54
define('WATCHDOG_EMERG', 0);
55
56
/**
57
 * Log message severity -- Alert: action must be taken immediately.
58
 *
59
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
60
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
61
 *
62
 * @see watchdog()
63
 * @see watchdog_severity_levels()
64
 */
65
define('WATCHDOG_ALERT', 1);
66
67
/**
68
 * Log message severity -- Critical: critical conditions.
69
 *
70
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
71
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
72
 *
73
 * @see watchdog()
74
 * @see watchdog_severity_levels()
75
 */
76
define('WATCHDOG_CRITICAL', 2);
77
78
/**
79
 * Log message severity -- Error: error conditions.
80
 *
81
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
82
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
83
 *
84
 * @see watchdog()
85
 * @see watchdog_severity_levels()
86
 */
87
define('WATCHDOG_ERROR', 3);
88
89
/**
90
 * Log message severity -- Warning: warning conditions.
91
 *
92
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
93
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
94
 *
95
 * @see watchdog()
96
 * @see watchdog_severity_levels()
97
 */
98
define('WATCHDOG_WARNING', 4);
99
100
/**
101
 * Log message severity -- Notice: normal but significant condition.
102
 *
103
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
104
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
105
 *
106
 * @see watchdog()
107
 * @see watchdog_severity_levels()
108
 */
109
define('WATCHDOG_NOTICE', 5);
110
111
/**
112
 * Log message severity -- Informational: informational messages.
113
 *
114
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
115
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
116
 *
117
 * @see watchdog()
118
 * @see watchdog_severity_levels()
119
 */
120
define('WATCHDOG_INFO', 6);
121
122
/**
123
 * Log message severity -- Debug: debug-level messages.
124
 *
125
 * The WATCHDOG_* constant definitions correspond to the logging severity levels
126
 * defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
127
 *
128
 * @see watchdog()
129
 * @see watchdog_severity_levels()
130
 */
131
define('WATCHDOG_DEBUG', 7);
1 by bzr
Drupal 6.6
132
133
/**
134
 * First bootstrap phase: initialize configuration.
135
 */
136
define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0);
137
138
/**
139
 * Second bootstrap phase: try to call a non-database cache
140
 * fetch routine.
141
 */
142
define('DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE', 1);
143
144
/**
145
 * Third bootstrap phase: initialize database layer.
146
 */
147
define('DRUPAL_BOOTSTRAP_DATABASE', 2);
148
149
/**
150
 * Fourth bootstrap phase: identify and reject banned hosts.
151
 */
152
define('DRUPAL_BOOTSTRAP_ACCESS', 3);
153
154
/**
155
 * Fifth bootstrap phase: initialize session handling.
156
 */
157
define('DRUPAL_BOOTSTRAP_SESSION', 4);
158
159
/**
160
 * Sixth bootstrap phase: load bootstrap.inc and module.inc, start
161
 * the variable system and try to serve a page from the cache.
162
 */
163
define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 5);
164
165
/**
166
 * Seventh bootstrap phase: find out language of the page.
167
 */
168
define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);
169
170
/**
171
 * Eighth bootstrap phase: set $_GET['q'] to Drupal path of request.
172
 */
173
define('DRUPAL_BOOTSTRAP_PATH', 7);
174
175
/**
176
 * Final bootstrap phase: Drupal is fully loaded; validate and fix
177
 * input data.
178
 */
179
define('DRUPAL_BOOTSTRAP_FULL', 8);
180
181
/**
182
 * Role ID for anonymous users; should match what's in the "role" table.
183
 */
184
define('DRUPAL_ANONYMOUS_RID', 1);
185
186
/**
187
 * Role ID for authenticated users; should match what's in the "role" table.
188
 */
189
define('DRUPAL_AUTHENTICATED_RID', 2);
190
191
/**
192
 * No language negotiation. The default language is used.
193
 */
194
define('LANGUAGE_NEGOTIATION_NONE', 0);
195
196
/**
197
 * Path based negotiation with fallback to default language
198
 * if no defined path prefix identified.
199
 */
200
define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1);
201
202
/**
203
 * Path based negotiation with fallback to user preferences
204
 * and browser language detection if no defined path prefix
205
 * identified.
206
 */
207
define('LANGUAGE_NEGOTIATION_PATH', 2);
208
209
/**
210
 * Domain based negotiation with fallback to default language
211
 * if no language identified by domain.
212
 */
213
define('LANGUAGE_NEGOTIATION_DOMAIN', 3);
214
215
/**
1.1.4 by bzr
Drupal 6.10
216
 * Language written left to right. Possible value of $language->direction.
217
 */
218
define('LANGUAGE_LTR', 0);
219
220
/**
221
 * Language written right to left. Possible value of $language->direction.
222
 */
223
define('LANGUAGE_RTL', 1);
224
1.1.13 by David Strauss
Drupal 6.19
225
// Hide E_DEPRECATED messages.
226
if (defined('E_DEPRECATED')) {
227
  error_reporting(error_reporting() & ~E_DEPRECATED);
228
}
229
1.1.4 by bzr
Drupal 6.10
230
/**
1 by bzr
Drupal 6.6
231
 * Start the timer with the specified name. If you start and stop
232
 * the same timer multiple times, the measured intervals will be
233
 * accumulated.
234
 *
235
 * @param name
236
 *   The name of the timer.
237
 */
238
function timer_start($name) {
239
  global $timers;
240
241
  list($usec, $sec) = explode(' ', microtime());
242
  $timers[$name]['start'] = (float)$usec + (float)$sec;
243
  $timers[$name]['count'] = isset($timers[$name]['count']) ? ++$timers[$name]['count'] : 1;
244
}
245
246
/**
247
 * Read the current timer value without stopping the timer.
248
 *
249
 * @param name
250
 *   The name of the timer.
251
 * @return
252
 *   The current timer value in ms.
253
 */
254
function timer_read($name) {
255
  global $timers;
256
257
  if (isset($timers[$name]['start'])) {
258
    list($usec, $sec) = explode(' ', microtime());
259
    $stop = (float)$usec + (float)$sec;
260
    $diff = round(($stop - $timers[$name]['start']) * 1000, 2);
261
262
    if (isset($timers[$name]['time'])) {
263
      $diff += $timers[$name]['time'];
264
    }
265
    return $diff;
266
  }
267
}
268
269
/**
270
 * Stop the timer with the specified name.
271
 *
272
 * @param name
273
 *   The name of the timer.
274
 * @return
275
 *   A timer array. The array contains the number of times the
276
 *   timer has been started and stopped (count) and the accumulated
277
 *   timer value in ms (time).
278
 */
279
function timer_stop($name) {
280
  global $timers;
281
282
  $timers[$name]['time'] = timer_read($name);
283
  unset($timers[$name]['start']);
284
285
  return $timers[$name];
286
}
287
288
/**
289
 * Find the appropriate configuration directory.
290
 *
291
 * Try finding a matching configuration directory by stripping the website's
292
 * hostname from left to right and pathname from right to left. The first
293
 * configuration file found will be used; the remaining will ignored. If no
294
 * configuration file is found, return a default value '$confdir/default'.
295
 *
296
 * Example for a fictitious site installed at
297
 * http://www.drupal.org:8080/mysite/test/ the 'settings.php' is searched in
298
 * the following directories:
299
 *
300
 *  1. $confdir/8080.www.drupal.org.mysite.test
301
 *  2. $confdir/www.drupal.org.mysite.test
302
 *  3. $confdir/drupal.org.mysite.test
303
 *  4. $confdir/org.mysite.test
304
 *
305
 *  5. $confdir/8080.www.drupal.org.mysite
306
 *  6. $confdir/www.drupal.org.mysite
307
 *  7. $confdir/drupal.org.mysite
308
 *  8. $confdir/org.mysite
309
 *
310
 *  9. $confdir/8080.www.drupal.org
311
 * 10. $confdir/www.drupal.org
312
 * 11. $confdir/drupal.org
313
 * 12. $confdir/org
314
 *
315
 * 13. $confdir/default
316
 *
317
 * @param $require_settings
318
 *   Only configuration directories with an existing settings.php file
319
 *   will be recognized. Defaults to TRUE. During initial installation,
320
 *   this is set to FALSE so that Drupal can detect a matching directory,
321
 *   then create a new settings.php file in it.
322
 * @param reset
323
 *   Force a full search for matching directories even if one had been
324
 *   found previously.
325
 * @return
326
 *   The path of the matching directory.
327
 */
328
function conf_path($require_settings = TRUE, $reset = FALSE) {
329
  static $conf = '';
330
331
  if ($conf && !$reset) {
332
    return $conf;
333
  }
334
335
  $confdir = 'sites';
336
  $uri = explode('/', $_SERVER['SCRIPT_NAME'] ? $_SERVER['SCRIPT_NAME'] : $_SERVER['SCRIPT_FILENAME']);
337
  $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
338
  for ($i = count($uri) - 1; $i > 0; $i--) {
339
    for ($j = count($server); $j > 0; $j--) {
340
      $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
341
      if (file_exists("$confdir/$dir/settings.php") || (!$require_settings && file_exists("$confdir/$dir"))) {
342
        $conf = "$confdir/$dir";
343
        return $conf;
344
      }
345
    }
346
  }
347
  $conf = "$confdir/default";
348
  return $conf;
349
}
350
351
/**
352
 * Unsets all disallowed global variables. See $allowed for what's allowed.
353
 */
354
function drupal_unset_globals() {
355
  if (ini_get('register_globals')) {
356
    $allowed = array('_ENV' => 1, '_GET' => 1, '_POST' => 1, '_COOKIE' => 1, '_FILES' => 1, '_SERVER' => 1, '_REQUEST' => 1, 'GLOBALS' => 1);
357
    foreach ($GLOBALS as $key => $value) {
358
      if (!isset($allowed[$key])) {
359
        unset($GLOBALS[$key]);
360
      }
361
    }
362
  }
363
}
364
365
/**
1.1.3 by bzr
Drupal 6.9
366
 * Validate that a hostname (for example $_SERVER['HTTP_HOST']) is safe.
1.1.1 by bzr
Drupal 6.7
367
 *
368
 * As $_SERVER['HTTP_HOST'] is user input, ensure it only contains characters
369
 * allowed in hostnames.  See RFC 952 (and RFC 2181). $_SERVER['HTTP_HOST'] is
370
 * lowercased.
371
 *
372
 * @return
373
 *  TRUE if only containing valid characters, or FALSE otherwise.
374
 */
1.1.3 by bzr
Drupal 6.9
375
function drupal_valid_http_host($host) {
376
  return preg_match('/^\[?(?:[a-z0-9-:\]_]+\.?)+$/', $host);
1.1.1 by bzr
Drupal 6.7
377
}
378
379
/**
1 by bzr
Drupal 6.6
380
 * Loads the configuration and sets the base URL, cookie domain, and
381
 * session name correctly.
382
 */
383
function conf_init() {
384
  global $base_url, $base_path, $base_root;
385
386
  // Export the following settings.php variables to the global namespace
1.4.4 by David Strauss
Properly load from settings.php.
387
  global $db_url, $db_slave_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access;
1 by bzr
Drupal 6.6
388
  $conf = array();
389
1.1.3 by bzr
Drupal 6.9
390
  if (isset($_SERVER['HTTP_HOST'])) {
391
    // As HTTP_HOST is user input, ensure it only contains characters allowed
392
    // in hostnames. See RFC 952 (and RFC 2181).
393
    // $_SERVER['HTTP_HOST'] is lowercased here per specifications.
394
    $_SERVER['HTTP_HOST'] = strtolower($_SERVER['HTTP_HOST']);
395
    if (!drupal_valid_http_host($_SERVER['HTTP_HOST'])) {
396
      // HTTP_HOST is invalid, e.g. if containing slashes it may be an attack.
397
      header('HTTP/1.1 400 Bad Request');
398
      exit;
399
    }
400
  }
401
  else {
402
    // Some pre-HTTP/1.1 clients will not send a Host header. Ensure the key is
403
    // defined for E_ALL compliance.
404
    $_SERVER['HTTP_HOST'] = '';
1.1.1 by bzr
Drupal 6.7
405
  }
406
1 by bzr
Drupal 6.6
407
  if (file_exists('./'. conf_path() .'/settings.php')) {
408
    include_once './'. conf_path() .'/settings.php';
409
  }
410
411
  // Ignore the placeholder url from default.settings.php.
412
  if (isset($db_url) && $db_url == 'mysql://username:password@localhost/databasename') {
413
    $db_url = '';
414
  }
415
416
  if (isset($base_url)) {
417
    // Parse fixed base URL from settings.php.
418
    $parts = parse_url($base_url);
419
    if (!isset($parts['path'])) {
420
      $parts['path'] = '';
421
    }
422
    $base_path = $parts['path'] .'/';
423
    // Build $base_root (everything until first slash after "scheme://").
424
    $base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path']));
425
  }
426
  else {
427
    // Create base URL
428
    $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
429
1.1.1 by bzr
Drupal 6.7
430
    $base_url = $base_root .= '://'. $_SERVER['HTTP_HOST'];
1 by bzr
Drupal 6.6
431
432
    // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not
433
    // be modified by a visitor.
434
    if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) {
435
      $base_path = "/$dir";
436
      $base_url .= $base_path;
437
      $base_path .= '/';
438
    }
439
    else {
440
      $base_path = '/';
441
    }
442
  }
443
444
  if ($cookie_domain) {
445
    // If the user specifies the cookie domain, also use it for session name.
446
    $session_name = $cookie_domain;
447
  }
448
  else {
449
    // Otherwise use $base_url as session name, without the protocol
450
    // to use the same session identifiers across http and https.
451
    list( , $session_name) = explode('://', $base_url, 2);
452
    // We escape the hostname because it can be modified by a visitor.
453
    if (!empty($_SERVER['HTTP_HOST'])) {
454
      $cookie_domain = check_plain($_SERVER['HTTP_HOST']);
1.1.11 by David Strauss
Drupal 6.17
455
      // Strip leading periods, www., and port numbers from cookie domain.
456
      $cookie_domain = ltrim($cookie_domain, '.');
457
      if (strpos($cookie_domain, 'www.') === 0) {
458
        $cookie_domain = substr($cookie_domain, 4);
459
      }
460
      $cookie_domain = explode(':', $cookie_domain);
461
      $cookie_domain = '.'. $cookie_domain[0];
1 by bzr
Drupal 6.6
462
    }
463
  }
464
  // To prevent session cookies from being hijacked, a user can configure the
465
  // SSL version of their website to only transfer session cookies via SSL by
466
  // using PHP's session.cookie_secure setting. The browser will then use two
467
  // separate session cookies for the HTTPS and HTTP versions of the site. So we
468
  // must use different session identifiers for HTTPS and HTTP to prevent a
469
  // cookie collision.
470
  if (ini_get('session.cookie_secure')) {
471
    $session_name .= 'SSL';
472
  }
473
  // Per RFC 2109, cookie domains must contain at least one dot other than the
474
  // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
475
  if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
476
    ini_set('session.cookie_domain', $cookie_domain);
477
  }
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
478
479
  // Use httponly session cookies.
480
  ini_set('session.cookie_httponly', '1');
481
1 by bzr
Drupal 6.6
482
  session_name('SESS'. md5($session_name));
483
}
484
485
/**
486
 * Returns and optionally sets the filename for a system item (module,
487
 * theme, etc.). The filename, whether provided, cached, or retrieved
488
 * from the database, is only returned if the file exists.
489
 *
490
 * This function plays a key role in allowing Drupal's resources (modules
491
 * and themes) to be located in different places depending on a site's
492
 * configuration. For example, a module 'foo' may legally be be located
493
 * in any of these three places:
494
 *
495
 * modules/foo/foo.module
496
 * sites/all/modules/foo/foo.module
497
 * sites/example.com/modules/foo/foo.module
498
 *
499
 * Calling drupal_get_filename('module', 'foo') will give you one of
500
 * the above, depending on where the module is located.
501
 *
502
 * @param $type
503
 *   The type of the item (i.e. theme, theme_engine, module).
504
 * @param $name
505
 *   The name of the item for which the filename is requested.
506
 * @param $filename
507
 *   The filename of the item if it is to be set explicitly rather
508
 *   than by consulting the database.
509
 *
510
 * @return
511
 *   The filename of the requested item.
512
 */
513
function drupal_get_filename($type, $name, $filename = NULL) {
514
  static $files = array();
515
516
  if (!isset($files[$type])) {
517
    $files[$type] = array();
518
  }
519
520
  if (!empty($filename) && file_exists($filename)) {
521
    $files[$type][$name] = $filename;
522
  }
523
  elseif (isset($files[$type][$name])) {
524
    // nothing
525
  }
526
  // Verify that we have an active database connection, before querying
527
  // the database.  This is required because this function is called both
528
  // before we have a database connection (i.e. during installation) and
529
  // when a database connection fails.
530
  elseif (db_is_active() && (($file = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s' AND type = '%s'", $name, $type))) && file_exists($file))) {
531
    $files[$type][$name] = $file;
532
  }
533
  else {
534
    // Fallback to searching the filesystem if the database connection is
535
    // not established or the requested file is not found.
536
    $config = conf_path();
537
    $dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s");
538
    $file = (($type == 'theme_engine') ? "$name.engine" : "$name.$type");
539
540
    foreach (array("$config/$dir/$file", "$config/$dir/$name/$file", "$dir/$file", "$dir/$name/$file") as $file) {
541
      if (file_exists($file)) {
542
        $files[$type][$name] = $file;
543
        break;
544
      }
545
    }
546
  }
547
548
  if (isset($files[$type][$name])) {
549
    return $files[$type][$name];
550
  }
551
}
552
553
/**
554
 * Load the persistent variable table.
555
 *
556
 * The variable table is composed of values that have been saved in the table
557
 * with variable_set() as well as those explicitly specified in the configuration
558
 * file.
559
 */
82.1.6 by David Strauss
Jed Prentice: Avoid infinite loop when the database connection fails in the middle of a request.
560
function variable_init($conf = array(), $regenerate = FALSE, $recursion_depth = 0) {
1 by bzr
Drupal 6.6
561
  // NOTE: caching the variables improves performance by 20% when serving cached pages.
66.1.1 by David Strauss
Initial implementation of write-through variable caching.
562
  if (!$regenerate && $cached = cache_get('variables', 'cache')) {
1 by bzr
Drupal 6.6
563
    $variables = $cached->data;
564
  }
565
  else {
76 by David Strauss
Merge Drupal 6.16. Provide migration path to using upstream lock.inc implementation.
566
    if (defined('MAINTENANCE_MODE') || lock_acquire('variable_cache_regenerate')) {
66.1.6 by David Strauss
Take advantage of the updated cache item instead of serializing rebuilds.
567
      $result = db_query('SELECT * FROM {variable}');
568
      while ($variable = db_fetch_object($result)) {
569
        $variables[$variable->name] = unserialize($variable->value);
570
      }
571
      cache_set('variables', $variables);
76 by David Strauss
Merge Drupal 6.16. Provide migration path to using upstream lock.inc implementation.
572
      if (!defined('MAINTENANCE_MODE')) {
573
        lock_release('variable_cache_regenerate');
574
      }
66.1.6 by David Strauss
Take advantage of the updated cache item instead of serializing rebuilds.
575
    }
576
    else {
66.1.5 by David Strauss
Use locks to prevent stampedes on cache misses.
577
      // Wait for another request that is already doing this work.
578
      lock_wait('variable_cache_regenerate');
66.1.6 by David Strauss
Take advantage of the updated cache item instead of serializing rebuilds.
579
82.1.6 by David Strauss
Jed Prentice: Avoid infinite loop when the database connection fails in the middle of a request.
580
      // Run the function again. Try a limited number of times to avoid 
581
      // infinite recursion if the database connection is invalid for  
582
      // some reason, e.g., mysqld restart, loss of network, etc.
583
      $recursion_depth++;
584
      if ($recursion_depth < 50) {
585
        return variable_init($conf, $regenerate, $recursion_depth);
586
      }
587
588
      $variables = array();
66.1.6 by David Strauss
Take advantage of the updated cache item instead of serializing rebuilds.
589
    }
1 by bzr
Drupal 6.6
590
  }
591
592
  foreach ($conf as $name => $value) {
593
    $variables[$name] = $value;
594
  }
595
596
  return $variables;
597
}
598
599
/**
1.1.13 by David Strauss
Drupal 6.19
600
 * Returns a persistent variable.
601
 *
602
 * Case-sensitivity of the variable_* functions depends on the database
603
 * collation used. To avoid problems, always use lower case for persistent
604
 * variable names.
1 by bzr
Drupal 6.6
605
 *
606
 * @param $name
607
 *   The name of the variable to return.
608
 * @param $default
609
 *   The default value to use if this variable has never been set.
610
 * @return
611
 *   The value of the variable.
1.1.8 by David Strauss
Drupal 6.14
612
 *
613
 * @see variable_del(), variable_set()
1 by bzr
Drupal 6.6
614
 */
615
function variable_get($name, $default) {
616
  global $conf;
617
618
  return isset($conf[$name]) ? $conf[$name] : $default;
619
}
620
621
/**
1.1.13 by David Strauss
Drupal 6.19
622
 * Sets a persistent variable.
623
 *
624
 * Case-sensitivity of the variable_* functions depends on the database
625
 * collation used. To avoid problems, always use lower case for persistent
626
 * variable names.
1 by bzr
Drupal 6.6
627
 *
628
 * @param $name
629
 *   The name of the variable to set.
630
 * @param $value
631
 *   The value to set. This can be any PHP data type; these functions take care
632
 *   of serialization as necessary.
1.1.8 by David Strauss
Drupal 6.14
633
 *
634
 * @see variable_del(), variable_get()
1 by bzr
Drupal 6.6
635
 */
636
function variable_set($name, $value) {
74.2.1 by David Strauss
SimpleTest 1.10
637
  global $conf, $db_prefix;
1 by bzr
Drupal 6.6
638
639
  $serialized_value = serialize($value);
640
  db_query("UPDATE {variable} SET value = '%s' WHERE name = '%s'", $serialized_value, $name);
641
  if (!db_affected_rows()) {
642
    @db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, $serialized_value);
643
  }
644
645
  $conf[$name] = $value;
66.1.1 by David Strauss
Initial implementation of write-through variable caching.
646
74.2.1 by David Strauss
SimpleTest 1.10
647
  // The write-through rebuild optimization isn't compatible with SimpleTest.
88 by David Strauss
Don't assume is a string (versus array) when checking for SimpleTest.
648
  // Because array-based prefixes don't work with SimpleTest, we can assume
649
  // that a non-string prefix indicates lack of SimpleTest operations.
650
  if (is_string($db_prefix) && strpos($db_prefix, 'simpletest') === 0) {
74.2.1 by David Strauss
SimpleTest 1.10
651
    cache_clear_all('variables', 'cache');
652
  }
653
  
66.1.1 by David Strauss
Initial implementation of write-through variable caching.
654
  variable_cache_rebuild();
1 by bzr
Drupal 6.6
655
}
656
657
/**
1.1.13 by David Strauss
Drupal 6.19
658
 * Unsets a persistent variable.
659
 *
660
 * Case-sensitivity of the variable_* functions depends on the database
661
 * collation used. To avoid problems, always use lower case for persistent
662
 * variable names.
1 by bzr
Drupal 6.6
663
 *
664
 * @param $name
665
 *   The name of the variable to undefine.
1.1.8 by David Strauss
Drupal 6.14
666
 *
667
 * @see variable_get(), variable_set()
1 by bzr
Drupal 6.6
668
 */
669
function variable_del($name) {
74.2.1 by David Strauss
SimpleTest 1.10
670
  global $conf, $db_prefix;
1 by bzr
Drupal 6.6
671
672
  db_query("DELETE FROM {variable} WHERE name = '%s'", $name);
673
674
  unset($conf[$name]);
26 by David Strauss
Use a write-through strategy for variable deletion.
675
74.2.1 by David Strauss
SimpleTest 1.10
676
  // The write-through rebuild optimization isn't compatible with SimpleTest.
94 by David Strauss
David Strauss: Fix variable_del() when using an array-based database prefix.
677
  // Because array-based prefixes don't work with SimpleTest, we can assume
678
  // that a non-string prefix indicates lack of SimpleTest operations.
679
  if (is_string($db_prefix) && strpos($db_prefix, 'simpletest') === 0) {
74.2.1 by David Strauss
SimpleTest 1.10
680
    cache_clear_all('variables', 'cache');
681
  }
682
  
66.1.1 by David Strauss
Initial implementation of write-through variable caching.
683
  variable_cache_rebuild();
1 by bzr
Drupal 6.6
684
}
685
66.1.1 by David Strauss
Initial implementation of write-through variable caching.
686
/**
687
 * Schedules a rebuild of the variable cache on shutdown.
688
 */
689
function variable_cache_rebuild() {
690
  static $shutdown_registered = FALSE;
691
  if (!$shutdown_registered) {
692
    register_shutdown_function('variable_init', array(), TRUE);
693
    $shutdown_registered = TRUE;
694
  }
695
}
1 by bzr
Drupal 6.6
696
697
/**
698
 * Retrieve the current page from the cache.
699
 *
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
700
 * Note: we do not serve cached pages to authenticated users, or to anonymous
701
 * users when $_SESSION is non-empty. $_SESSION may contain status messages
702
 * from a form submission, the contents of a shopping cart, or other user-
703
 * specific content that should not be cached and displayed to other users.
1 by bzr
Drupal 6.6
704
 *
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
705
 * @return
1.5.17 by David Strauss
Integrate DamZ's updated work.
706
 *   The cache object, if the page was found in the cache.
1 by bzr
Drupal 6.6
707
 */
1.5.17 by David Strauss
Integrate DamZ's updated work.
708
function page_get_cache() {
1 by bzr
Drupal 6.6
709
  global $user, $base_root;
710
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
711
  if (drupal_page_is_cacheable()) {
1 by bzr
Drupal 6.6
712
    $cache = cache_get($base_root . request_uri(), 'cache_page');
1.5.17 by David Strauss
Integrate DamZ's updated work.
713
714
    // Unserialize the cached page headers
1.5.14 by David Strauss
Move serialization of headers out of cache.inc to improve cache.inc substitution support.
715
    if (isset($cache->headers)) {
716
      $cache->headers = unserialize($cache->headers);
717
    }
1.5.17 by David Strauss
Integrate DamZ's updated work.
718
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
719
    if ($cache) {
720
      return $cache;
721
    }
1.5.17 by David Strauss
Integrate DamZ's updated work.
722
  }
723
}
724
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
725
function drupal_page_is_cacheable($force = NULL) {
1.5.17 by David Strauss
Integrate DamZ's updated work.
726
  static $forced_cache = TRUE;
727
  if (isset($force)) {
728
    $forced_cache = $force;
729
  }
730
731
  $result = $forced_cache
1.5.25 by David Strauss
Remove drupal_session_is_started() in favor of only drupal_session_started().
732
    && !drupal_session_started()
1.5.17 by David Strauss
Integrate DamZ's updated work.
733
    && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')
734
    && !count(drupal_get_messages(NULL, FALSE))
55 by David Strauss
To fix Drush (Drupal.org #487300 and LP #422516), apply Drupal.org #581286 by David Strauss, DamZ, and moshe weitzman
735
    && !drupal_is_cli();
1.5.17 by David Strauss
Integrate DamZ's updated work.
736
737
  return $result;
1 by bzr
Drupal 6.6
738
}
739
740
/**
741
 * Call all init or exit hooks without including all modules.
742
 *
743
 * @param $hook
744
 *   The name of the bootstrap hook we wish to invoke.
745
 */
746
function bootstrap_invoke_all($hook) {
747
  foreach (module_list(TRUE, TRUE) as $module) {
748
    drupal_load('module', $module);
749
    module_invoke($module, $hook);
750
  }
751
}
752
753
/**
754
 * Includes a file with the provided type and name. This prevents
755
 * including a theme, engine, module, etc., more than once.
756
 *
757
 * @param $type
758
 *   The type of item to load (i.e. theme, theme_engine, module).
759
 * @param $name
760
 *   The name of the item to load.
761
 *
762
 * @return
763
 *   TRUE if the item is loaded or has already been loaded.
764
 */
765
function drupal_load($type, $name) {
766
  static $files = array();
767
768
  if (isset($files[$type][$name])) {
769
    return TRUE;
770
  }
771
772
  $filename = drupal_get_filename($type, $name);
773
774
  if ($filename) {
775
    include_once "./$filename";
776
    $files[$type][$name] = TRUE;
777
778
    return TRUE;
779
  }
780
781
  return FALSE;
782
}
783
784
/**
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
785
 * Set an HTTP response header for the current page.
786
 *
787
 * Note: When sending a Content-Type header, always include a 'charset' type,
788
 * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
789
 *
790
 * @param $name
791
 *   The HTTP header name, or a status code followed by a reason phrase, e.g.
792
 *   "404 Not Found".
793
 * @param $value
794
 *   The HTTP header value; if omitted, the specified header is unset.
795
 * @param $append
796
 *   Whether to append the value to an existing header or to replace it.
797
 */
798
function drupal_set_header($name = NULL, $value = NULL, $append = FALSE) {
799
  // The headers as name/value pairs.
800
  static $headers = array();
801
802
  if (!isset($name)) {
803
    return $headers;
804
  }
1.5.30 by David Strauss
Fix Drupal 6 headers API.
805
  
806
  // Support the Drupal 6 header API
807
  if (!isset($value)) {
808
    if (strpos($name, ':') !== FALSE) {
77 by David Strauss
Damien Tournoud: Fix parsing of HTTP headers containing a colon in the value.
809
      $parts = explode(':', $name, 2);
1.5.30 by David Strauss
Fix Drupal 6 headers API.
810
      $name = trim($parts[0]);
811
      $value = trim($parts[1]);
812
    }
813
  }
814
  if (substr($name, 0, 7) == 'HTTP/1.') {
815
    $name = substr($name, 9);
816
  }
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
817
818
  // Save status codes using the special key ":status".
819
  if (preg_match('/^\d{3} /', $name)) {
820
    $value = $name;
821
    $name = $name_lower = ':status';
822
  }
823
  else {
824
    $name_lower = strtolower($name);
825
  }
826
  _drupal_set_preferred_header_name($name);
827
828
  if (!isset($value)) {
829
    $headers[$name_lower] = FALSE;
830
  }
831
  elseif (isset($headers[$name_lower]) && $append) {
832
    // Multiple headers with identical names may be combined using comma (RFC
833
    // 2616, section 4.2).
834
    $headers[$name_lower] .= ',' . $value;
835
  }
836
  else {
837
    $headers[$name_lower] = $value;
838
  }
839
  drupal_send_headers(array($name => $headers[$name_lower]), TRUE);
840
}
841
842
/**
843
 * Get the HTTP response headers for the current page.
844
 *
845
 * @param $name
846
 *   An HTTP header name. If omitted, all headers are returned as name/value
847
 *   pairs. If an array value is FALSE, the header has been unset.
848
 * @return
849
 *   A string containing the header value, or FALSE if the header has been set,
850
 *   or NULL if the header has not been set.
851
 */
852
function drupal_get_header($name = NULL) {
853
  $headers = drupal_set_header();
854
  if (isset($name)) {
855
    $name = strtolower($name);
856
    return isset($headers[$name]) ? $headers[$name] : NULL;
857
  }
858
  else {
859
    return $headers;
860
  }
861
}
862
863
/**
864
 * Header names are case-insensitive, but for maximum compatibility they should
865
 * follow "common form" (see RFC 2617, section 4.2).
866
 */
867
function _drupal_set_preferred_header_name($name = NULL) {
868
  static $header_names = array();
869
870
  if (!isset($name)) {
871
    return $header_names;
872
  }
873
  $header_names[strtolower($name)] = $name;
874
}
875
876
/**
877
 * Send the HTTP response headers previously set using drupal_set_header().
878
 * Add default headers, unless they have been replaced or unset using
879
 * drupal_set_header().
880
 *
881
 * @param $default_headers
882
 *   An array of headers as name/value pairs.
883
 * @param $single
884
 *   If TRUE and headers have already be sent, send only the specified header.
885
 */
886
function drupal_send_headers($default_headers = array(), $only_default = FALSE) {
887
  static $headers_sent = FALSE;
888
  $headers = drupal_get_header();
889
  if ($only_default && $headers_sent) {
890
    $headers = array();
891
  }
892
  $headers_sent = TRUE;
893
894
  $header_names = _drupal_set_preferred_header_name();
895
  foreach ($default_headers as $name => $value) {
896
    $name_lower = strtolower($name);
897
    if (!isset($headers[$name_lower])) {
898
      $headers[$name_lower] = $value;
899
      $header_names[$name_lower] = $name;
900
    }
901
  }
902
  foreach ($headers as $name_lower => $value) {
903
    if ($name_lower == ':status') {
904
      header($_SERVER['SERVER_PROTOCOL'] . ' ' . $value);
905
    }
906
    // Skip headers that have been unset.
907
    elseif ($value) {
908
      header($header_names[$name_lower] . ': ' . $value);
909
    }
910
  }
911
}
912
913
/**
1 by bzr
Drupal 6.6
914
 * Set HTTP headers in preparation for a page response.
915
 *
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
916
 * Authenticated users are always given a 'no-cache' header, and will fetch a
917
 * fresh page on every request. This prevents authenticated users from seeing
918
 * locally cached pages.
919
 *
920
 * Also give each page a unique ETag. This will force clients to include both
921
 * an If-Modified-Since header and an If-None-Match header when doing
922
 * conditional requests for the page (required by RFC 2616, section 13.3.4),
923
 * making the validation more robust. This is a workaround for a bug in Mozilla
924
 * Firefox that is triggered when Drupal's caching is enabled and the user
925
 * accesses Drupal via an HTTP proxy (see
926
 * https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an authenticated
927
 * user requests a page, and then logs out and requests the same page again,
928
 * Firefox may send a conditional request based on the page that was cached
929
 * locally when the user was logged in. If this page did not have an ETag
930
 * header, the request only contains an If-Modified-Since header. The date will
931
 * be recent, because with authenticated users the Last-Modified header always
932
 * refers to the time of the request. If the user accesses Drupal via a proxy
933
 * server, and the proxy already has a cached copy of the anonymous page with an
934
 * older Last-Modified date, the proxy may respond with 304 Not Modified, making
935
 * the client think that the anonymous and authenticated pageviews are
936
 * identical.
1 by bzr
Drupal 6.6
937
 *
938
 * @see page_set_cache()
939
 */
940
function drupal_page_header() {
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
941
  static $headers_sent = FALSE;
942
  if ($headers_sent) {
943
    return TRUE;
944
  }
945
  $headers_sent = TRUE;
22 by David Strauss
Initial, untested support for reverse proxy caches.
946
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
947
  $default_headers = array(
948
    'Last-Modified' => gmdate(DATE_RFC1123, $_SERVER['REQUEST_TIME']),
949
    'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0',
1.5.10 by David Strauss
Fix etag.
950
    'ETag' => '"' . $_SERVER['REQUEST_TIME'] . '"',
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
951
  );
952
  drupal_send_headers($default_headers);
1 by bzr
Drupal 6.6
953
}
954
955
/**
63 by David Strauss
David Strauss: Add an 'external' caching mode that skips the built-in cache but still sends friendly headers downstream.
956
 * Set HTTP headers in for downstream caching.
957
 *
958
 * The headers allow as much as possible in proxies and browsers without any
959
 * particular knowledge about the pages. Used for the external caching mode.
960
 */
961
function drupal_page_cache_header_external() {
962
  // Get headers set in hook_boot(). Keys are lower-case.
963
  $hook_boot_headers = drupal_get_header();
964
965
  $max_age = variable_get('page_cache_max_age', 0);
966
  drupal_set_header('Cache-Control', 'public, max-age=' . $max_age);
967
  drupal_set_header('Last-Modified', gmdate(DATE_RFC1123, $_SERVER['REQUEST_TIME']));
968
969
  // HTTP/1.0 proxies do not support the Vary header, so prevent any caching
970
  // by sending an Expires date in the past. HTTP/1.1 clients ignores the
971
  // Expires header if a Cache-Control: max-age= directive is specified (see RFC
972
  // 2616, section 14.9.3).
973
  drupal_set_header('Expires', 'Sun, 11 Mar 1984 12:00:00 GMT');
974
975
  // Allow HTTP proxies to cache pages for anonymous users without a session
976
  // cookie. The Vary header is used to indicates the set of request-header
977
  // fields that fully determines whether a cache is permitted to use the
978
  // response to reply to a subsequent request for a given URL without
979
  // revalidation. If a Vary header has been set in hook_boot(), it is assumed
980
  // that the module knows how to cache the page.
981
  if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie', FALSE)) {
982
    drupal_set_header('Vary', 'Cookie');
983
  }
984
}
985
986
987
/**
1 by bzr
Drupal 6.6
988
 * Set HTTP headers in preparation for a cached page response.
989
 *
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
990
 * The headers allow as much as possible in proxies and browsers without any
991
 * particular knowledge about the pages. Modules can override these headers
992
 * using drupal_set_header().
1 by bzr
Drupal 6.6
993
 *
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
994
 * If the request is conditional (using If-Modified-Since and If-None-Match),
995
 * and the conditions match those currently in the cache, a 304 Not Modified
996
 * response is sent.
1 by bzr
Drupal 6.6
997
 */
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
998
function drupal_page_cache_header(stdClass $cache) {
999
  // Negotiate whether to use compression.
1000
  $page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib');
1001
  $return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
1002
1003
  // Get headers set in hook_boot(). Keys are lower-case.
1004
  $hook_boot_headers = drupal_get_header();
1005
1006
  // Headers generated in this function, that may be replaced or unset using
1007
  // drupal_set_headers(). Keys are mixed-case.
1008
  $default_headers = array();
1009
1010
  foreach ($cache->headers as $name => $value) {
1011
    // In the case of a 304 response, certain headers must be sent, and the
1012
    // remaining may not (see RFC 2616, section 10.3.5). Do not override
1013
    // headers set in hook_boot().
1014
    $name_lower = strtolower($name);
1015
    if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($hook_boot_headers[$name_lower])) {
1016
      drupal_set_header($name, $value);
1017
      unset($cache->headers[$name]);
1018
    }
1019
  }
1020
1021
  // If a cache is served from a HTTP proxy without hitting the web server,
1022
  // the boot and exit hooks cannot be fired, so only allow caching in
1023
  // proxies with aggressive caching. If the client send a session cookie, do
1024
  // not bother caching the page in a public proxy, because the cached copy
1025
  // will only be served to that particular user due to Vary: Cookie, unless
1026
  // the Vary header has been replaced or unset in hook_boot() (see below).
1.5.29 by David Strauss
Fix undefined constant.
1027
  $max_age = variable_get('cache', CACHE_DISABLED) == CACHE_AGGRESSIVE && (!isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary'])) ? variable_get('page_cache_max_age', 0) : 0;
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1028
  $default_headers['Cache-Control'] = 'public, max-age=' . $max_age;
1029
1030
  // Entity tag should change if the output changes.
1031
  $etag = '"' . $cache->created . '-' . intval($return_compressed) . '"';
1032
  header('Etag: ' . $etag);
1033
1034
  // See if the client has provided the required HTTP headers.
1035
  $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
1036
  $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;
1037
1038
  if ($if_modified_since && $if_none_match
1039
      && $if_none_match == $etag // etag must match
1040
      && $if_modified_since == $cache->created) {  // if-modified-since must match
1041
    header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
1042
    drupal_send_headers($default_headers);
1043
    return;
1044
  }
1045
1046
  // Send the remaining headers.
1047
  foreach ($cache->headers as $name => $value) {
1048
    drupal_set_header($name, $value);
1049
  }
1050
1051
  $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created);
1052
1.5.17 by David Strauss
Integrate DamZ's updated work.
1053
  // HTTP/1.0 proxies do not support the Vary header, so prevent any caching
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1054
  // by sending an Expires date in the past. HTTP/1.1 clients ignores the
1055
  // Expires header if a Cache-Control: max-age= directive is specified (see RFC
1056
  // 2616, section 14.9.3).
35 by David Strauss
Initial merge of updated reverse proxy support.
1057
  $default_headers['Expires'] = 'Sun, 11 Mar 1984 12:00:00 GMT';
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1058
1059
  drupal_send_headers($default_headers);
1060
1061
  // Allow HTTP proxies to cache pages for anonymous users without a session
1062
  // cookie. The Vary header is used to indicates the set of request-header
1063
  // fields that fully determines whether a cache is permitted to use the
1064
  // response to reply to a subsequent request for a given URL without
1065
  // revalidation. If a Vary header has been set in hook_boot(), it is assumed
1066
  // that the module knows how to cache the page.
1067
  if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie', FALSE)) {
1068
    header('Vary: Cookie');
1069
  }
1070
1071
  if ($page_compression) {
1072
    header('Vary: Accept-Encoding', FALSE);
1073
    // If page_compression is enabled, the cache contains gzipped data.
1074
    if ($return_compressed) {
82.1.2 by David Strauss
Fix double output from page caching. Deliver proper gzip-compressed pages without double compression.
1075
      ini_set('zlib.output_compression', '0');
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1076
      header('Content-Encoding: gzip');
1077
    }
1078
    else {
1079
      // The client does not support compression, so unzip the data in the
1080
      // cache. Strip the gzip header and run uncompress.
1 by bzr
Drupal 6.6
1081
      $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
82.1.2 by David Strauss
Fix double output from page caching. Deliver proper gzip-compressed pages without double compression.
1082
      header('X-PF-Uncompressing: 1');
1 by bzr
Drupal 6.6
1083
    }
1084
  }
1085
1086
  print $cache->data;
1087
}
1088
1089
/**
1090
 * Define the critical hooks that force modules to always be loaded.
1091
 */
1092
function bootstrap_hooks() {
1093
  return array('boot', 'exit');
1094
}
1095
1096
/**
1097
 * Unserializes and appends elements from a serialized string.
1098
 *
1099
 * @param $obj
1100
 *   The object to which the elements are appended.
1101
 * @param $field
1102
 *   The attribute of $obj whose value should be unserialized.
1103
 */
1104
function drupal_unpack($obj, $field = 'data') {
1105
  if ($obj->$field && $data = unserialize($obj->$field)) {
1106
    foreach ($data as $key => $value) {
1.1.13 by David Strauss
Drupal 6.19
1107
      if (!empty($key) && !isset($obj->$key)) {
1 by bzr
Drupal 6.6
1108
        $obj->$key = $value;
1109
      }
1110
    }
1111
  }
1112
  return $obj;
1113
}
1114
1115
/**
1116
 * Return the URI of the referring page.
1117
 */
1118
function referer_uri() {
1119
  if (isset($_SERVER['HTTP_REFERER'])) {
1120
    return $_SERVER['HTTP_REFERER'];
1121
  }
1122
}
1123
1124
/**
1125
 * Encode special characters in a plain-text string for display as HTML.
1126
 *
1.1.11 by David Strauss
Drupal 6.17
1127
 * Also validates strings as UTF-8 to prevent cross site scripting attacks on
1 by bzr
Drupal 6.6
1128
 * Internet Explorer 6.
1.1.11 by David Strauss
Drupal 6.17
1129
 *
1130
 * @param $text
1131
 *   The text to be checked or processed.
1132
 * @return
1133
 *   An HTML safe version of $text, or an empty string if $text is not
1134
 *   valid UTF-8.
1135
 *
1136
 * @see drupal_validate_utf8().
1 by bzr
Drupal 6.6
1137
 */
1138
function check_plain($text) {
1.1.11 by David Strauss
Drupal 6.17
1139
  static $php525;
1140
1141
  if (!isset($php525)) {
1142
    $php525 = version_compare(PHP_VERSION, '5.2.5', '>=');
1143
  }
1144
  // We duplicate the preg_match() to validate strings as UTF-8 from
1145
  // drupal_validate_utf8() here. This avoids the overhead of an additional
1146
  // function call, since check_plain() may be called hundreds of times during
1147
  // a request. For PHP 5.2.5+, this check for valid UTF-8 should be handled
1148
  // internally by PHP in htmlspecialchars().
1149
  // @see http://www.php.net/releases/5_2_5.php
1150
  // @todo remove this when support for either IE6 or PHP < 5.2.5 is dropped.
1151
1152
  if ($php525) {
1153
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
1154
  }
1155
  return (preg_match('/^./us', $text) == 1) ? htmlspecialchars($text, ENT_QUOTES, 'UTF-8') : '';
1 by bzr
Drupal 6.6
1156
}
1157
1158
/**
1159
 * Checks whether a string is valid UTF-8.
1160
 *
1161
 * All functions designed to filter input should use drupal_validate_utf8
1162
 * to ensure they operate on valid UTF-8 strings to prevent bypass of the
1163
 * filter.
1164
 *
1165
 * When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented
1166
 * as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent
1167
 * bytes. When these subsequent bytes are HTML control characters such as
1168
 * quotes or angle brackets, parts of the text that were deemed safe by filters
1169
 * end up in locations that are potentially unsafe; An onerror attribute that
1170
 * is outside of a tag, and thus deemed safe by a filter, can be interpreted
1171
 * by the browser as if it were inside the tag.
1172
 *
1173
 * This function exploits preg_match behaviour (since PHP 4.3.5) when used
1174
 * with the u modifier, as a fast way to find invalid UTF-8. When the matched
1175
 * string contains an invalid byte sequence, it will fail silently.
1176
 *
1177
 * preg_match may not fail on 4 and 5 octet sequences, even though they
1178
 * are not supported by the specification.
1179
 *
1180
 * The specific preg_match behaviour is present since PHP 4.3.5.
1181
 *
1182
 * @param $text
1183
 *   The text to check.
1184
 * @return
1185
 *   TRUE if the text is valid UTF-8, FALSE if not.
1186
 */
1187
function drupal_validate_utf8($text) {
1188
  if (strlen($text) == 0) {
1189
    return TRUE;
1190
  }
1.1.11 by David Strauss
Drupal 6.17
1191
  // For performance reasons this logic is duplicated in check_plain().
1 by bzr
Drupal 6.6
1192
  return (preg_match('/^./us', $text) == 1);
1193
}
1194
1195
/**
1196
 * Since $_SERVER['REQUEST_URI'] is only available on Apache, we
1197
 * generate an equivalent using other environment variables.
1198
 */
1199
function request_uri() {
1200
1201
  if (isset($_SERVER['REQUEST_URI'])) {
1202
    $uri = $_SERVER['REQUEST_URI'];
1203
  }
1204
  else {
1205
    if (isset($_SERVER['argv'])) {
1206
      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['argv'][0];
1207
    }
1208
    elseif (isset($_SERVER['QUERY_STRING'])) {
1209
      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING'];
1210
    }
1211
    else {
1212
      $uri = $_SERVER['SCRIPT_NAME'];
1213
    }
1214
  }
1.1.5 by bzr
Drupal 6.11
1215
  // Prevent multiple slashes to avoid cross site requests via the FAPI.
1216
  $uri = '/'. ltrim($uri, '/');
1 by bzr
Drupal 6.6
1217
1218
  return $uri;
1219
}
1220
1221
/**
1222
 * Log a system message.
1223
 *
1224
 * @param $type
1.1.13 by David Strauss
Drupal 6.19
1225
 *   The category to which this message belongs. Can be any string, but the
1226
 *   general practice is to use the name of the module calling watchdog().
1 by bzr
Drupal 6.6
1227
 * @param $message
1228
 *   The message to store in the log. See t() for documentation
1229
 *   on how $message and $variables interact. Keep $message
1230
 *   translatable by not concatenating dynamic values into it!
1231
 * @param $variables
1232
 *   Array of variables to replace in the message on display or
1233
 *   NULL if message is already translated or not possible to
1234
 *   translate.
1235
 * @param $severity
1.1.13 by David Strauss
Drupal 6.19
1236
 *   The severity of the message, as per RFC 3164. Possible values are
1237
 *   WATCHDOG_ERROR, WATCHDOG_WARNING, etc.
1 by bzr
Drupal 6.6
1238
 * @param $link
1239
 *   A link to associate with the message.
1240
 *
1241
 * @see watchdog_severity_levels()
1242
 */
1243
function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
1244
  global $user, $base_root;
1245
1246
  // Prepare the fields to be logged
1247
  $log_message = array(
1248
    'type'        => $type,
1249
    'message'     => $message,
1250
    'variables'   => $variables,
1251
    'severity'    => $severity,
1252
    'link'        => $link,
1253
    'user'        => $user,
1254
    'request_uri' => $base_root . request_uri(),
1255
    'referer'     => referer_uri(),
1256
    'ip'          => ip_address(),
1257
    'timestamp'   => time(),
1258
    );
1259
1260
  // Call the logging hooks to log/process the message
1.1.13 by David Strauss
Drupal 6.19
1261
  foreach (module_implements('watchdog') as $module) {
1 by bzr
Drupal 6.6
1262
    module_invoke($module, 'watchdog', $log_message);
1263
  }
1264
}
1265
1266
/**
1267
 * Set a message which reflects the status of the performed operation.
1268
 *
1269
 * If the function is called with no arguments, this function returns all set
1270
 * messages without clearing them.
1271
 *
1272
 * @param $message
1273
 *   The message should begin with a capital letter and always ends with a
1274
 *   period '.'.
1275
 * @param $type
1276
 *   The type of the message. One of the following values are possible:
1277
 *   - 'status'
1278
 *   - 'warning'
1279
 *   - 'error'
1280
 * @param $repeat
1281
 *   If this is FALSE and the message is already set, then the message won't
1282
 *   be repeated.
1283
 */
1284
function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
1285
  if ($message) {
1286
    if (!isset($_SESSION['messages'])) {
1.5.12 by David Strauss
Allow errors during installation.
1287
      if (function_exists('drupal_set_session')) {
1288
        drupal_set_session('messages', array());
1289
      }
1290
      else {
1291
        $_SESSION['messages'] = array();
1292
      }
1 by bzr
Drupal 6.6
1293
    }
1294
1295
    if (!isset($_SESSION['messages'][$type])) {
1296
      $_SESSION['messages'][$type] = array();
1297
    }
1298
1299
    if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
1300
      $_SESSION['messages'][$type][] = $message;
1301
    }
1302
  }
1303
1304
  // messages not set when DB connection fails
1305
  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
1306
}
1307
1308
/**
1309
 * Return all messages that have been set.
1310
 *
1311
 * @param $type
1312
 *   (optional) Only return messages of this type.
1313
 * @param $clear_queue
1314
 *   (optional) Set to FALSE if you do not want to clear the messages queue
1315
 * @return
1316
 *   An associative array, the key is the message type, the value an array
1317
 *   of messages. If the $type parameter is passed, you get only that type,
1318
 *   or an empty array if there are no such messages. If $type is not passed,
1319
 *   all message types are returned, or an empty array if none exist.
1320
 */
1321
function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
1322
  if ($messages = drupal_set_message()) {
1323
    if ($type) {
1324
      if ($clear_queue) {
1325
        unset($_SESSION['messages'][$type]);
1326
      }
1327
      if (isset($messages[$type])) {
1328
        return array($type => $messages[$type]);
1329
      }
1330
    }
1331
    else {
1332
      if ($clear_queue) {
1333
        unset($_SESSION['messages']);
1334
      }
1335
      return $messages;
1336
    }
1337
  }
1338
  return array();
1339
}
1340
1341
/**
1342
 * Perform an access check for a given mask and rule type. Rules are usually
1343
 * created via admin/user/rules page.
1344
 *
1345
 * If any allow rule matches, access is allowed. Otherwise, if any deny rule
1346
 * matches, access is denied.  If no rule matches, access is allowed.
1347
 *
1348
 * @param $type string
1349
 *   Type of access to check: Allowed values are:
1350
 *     - 'host': host name or IP address
1351
 *     - 'mail': e-mail address
1352
 *     - 'user': username
1353
 * @param $mask string
1354
 *   String or mask to test: '_' matches any character, '%' matches any
1355
 *   number of characters.
1356
 * @return bool
1357
 *   TRUE if access is denied, FALSE if access is allowed.
1358
 */
1359
function drupal_is_denied($type, $mask) {
1360
  // Because this function is called for every page request, both cached
1361
  // and non-cached pages, we tried to optimize it as much as possible.
1362
  // We deny access if the only matching records in the {access} table have
1363
  // status 0 (deny). If any have status 1 (allow), or if there are no
1364
  // matching records, we allow access.
3 by David Strauss
Remove all instances of LOWER().
1365
  $sql = "SELECT 1 FROM {access} WHERE type = '%s' AND '%s' LIKE mask AND status = %d";
1 by bzr
Drupal 6.6
1366
  return db_result(db_query_range($sql, $type, $mask, 0, 0, 1)) && !db_result(db_query_range($sql, $type, $mask, 1, 0, 1));
1367
}
1368
1369
/**
1370
 * Generates a default anonymous $user object.
1371
 *
1372
 * @return Object - the user object.
1373
 */
1374
function drupal_anonymous_user($session = '') {
1375
  $user = new stdClass();
1376
  $user->uid = 0;
1377
  $user->hostname = ip_address();
1378
  $user->roles = array();
1379
  $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
1380
  $user->session = $session;
1381
  $user->cache = 0;
1382
  return $user;
1383
}
1384
1385
/**
1386
 * A string describing a phase of Drupal to load. Each phase adds to the
1387
 * previous one, so invoking a later phase automatically runs the earlier
1388
 * phases too. The most important usage is that if you want to access the
1389
 * Drupal database from a script without loading anything else, you can
1390
 * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
1391
 *
1392
 * @param $phase
1393
 *   A constant. Allowed values are:
1394
 *     DRUPAL_BOOTSTRAP_CONFIGURATION: initialize configuration.
1395
 *     DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: try to call a non-database cache fetch routine.
1396
 *     DRUPAL_BOOTSTRAP_DATABASE: initialize database layer.
1397
 *     DRUPAL_BOOTSTRAP_ACCESS: identify and reject banned hosts.
1398
 *     DRUPAL_BOOTSTRAP_SESSION: initialize session handling.
1399
 *     DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start
1400
 *       the variable system and try to serve a page from the cache.
1401
 *     DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page.
1402
 *     DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request.
1403
 *     DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data.
1404
 */
74.2.2 by David Strauss
Backport drupal_get_bootstrap_phase() from Drupal 7 to support SimpleTest.
1405
function drupal_bootstrap($phase = NULL) {
1 by bzr
Drupal 6.6
1406
  static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL), $phase_index = 0;
1407
74.2.2 by David Strauss
Backport drupal_get_bootstrap_phase() from Drupal 7 to support SimpleTest.
1408
  if (isset($phase)) {
1409
    while ($phase >= $phase_index && isset($phases[$phase_index])) {
1410
      $current_phase = $phases[$phase_index];
1411
      unset($phases[$phase_index++]);
1412
      _drupal_bootstrap($current_phase);
1413
    }
1 by bzr
Drupal 6.6
1414
  }
74.2.2 by David Strauss
Backport drupal_get_bootstrap_phase() from Drupal 7 to support SimpleTest.
1415
  
1416
  return $phase_index;
1 by bzr
Drupal 6.6
1417
}
1418
88.1.1 by David Strauss
Add "pressflow_smart_start" configuration option to enable redirecting to the installer when a valid settings.php is present but no site is installed.
1419
/**
1420
 * Redirect to the installer if an essential table is missing.
1421
 */
1422
function detect_installation_or_run_installer() {
1423
  if (variable_get('pressflow_smart_start', FALSE) && !db_table_exists('access')) {
1424
    include_once 'includes/install.inc';
1425
    install_goto('install.php');
1426
  }
1427
}
1428
1 by bzr
Drupal 6.6
1429
function _drupal_bootstrap($phase) {
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1430
  global $conf, $user, $db_prefix;
1 by bzr
Drupal 6.6
1431
1432
  switch ($phase) {
1433
1434
    case DRUPAL_BOOTSTRAP_CONFIGURATION:
1435
      drupal_unset_globals();
1436
      // Start a page timer:
1437
      timer_start('page');
1438
      // Initialize the configuration
1439
      conf_init();
1440
      break;
1441
1442
    case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE:
1443
      // Allow specifying special cache handlers in settings.php, like
1444
      // using memcached or files for storing cache information.
1445
      require_once variable_get('cache_inc', './includes/cache.inc');
1446
      // If the page_cache_fastpath is set to TRUE in settings.php and
1447
      // page_cache_fastpath (implemented in the special implementation of
1448
      // cache.inc) printed the page and indicated this with a returned TRUE
1449
      // then we are done.
1450
      if (variable_get('page_cache_fastpath', FALSE) && page_cache_fastpath()) {
1451
        exit;
1452
      }
1453
      break;
1454
1455
    case DRUPAL_BOOTSTRAP_DATABASE:
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1456
      // The user agent header is used to pass a database prefix in the request when
1457
      // running tests. However, for security reasons, it is imperative that we
1458
      // validate we ourselves made the request.
47.1.5 by Josh Koenig
merging in changes from Simpletest 6-2.9
1459
      $GLOBALS['simpletest_installed'] = TRUE;
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1460
      if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
74.2.1 by David Strauss
SimpleTest 1.10
1461
        if (!drupal_valid_test_ua($_SERVER['HTTP_USER_AGENT'])) {
1462
          header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
1463
          exit;
1464
        }
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1465
        $db_prefix .= $matches[1];
1466
      }
1467
1 by bzr
Drupal 6.6
1468
      // Initialize the default database.
1469
      require_once './includes/database.inc';
1470
      db_set_active();
1.1.11 by David Strauss
Drupal 6.17
1471
      // Allow specifying alternate lock implementations in settings.php, like
1472
      // those using APC or memcached.
1473
      require_once variable_get('lock_inc', './includes/lock.inc');
1474
      lock_init();
88.1.1 by David Strauss
Add "pressflow_smart_start" configuration option to enable redirecting to the installer when a valid settings.php is present but no site is installed.
1475
      
1476
      // Detect if an installation is present.
1477
      detect_installation_or_run_installer();
1478
1 by bzr
Drupal 6.6
1479
      break;
1480
1481
    case DRUPAL_BOOTSTRAP_ACCESS:
1482
      // Deny access to hosts which were banned - t() is not yet available.
1483
      if (drupal_is_denied('host', ip_address())) {
1484
        header('HTTP/1.1 403 Forbidden');
1485
        print 'Sorry, '. check_plain(ip_address()) .' has been banned.';
1486
        exit();
1487
      }
1488
      break;
1489
1490
    case DRUPAL_BOOTSTRAP_SESSION:
1491
      require_once variable_get('session_inc', './includes/session.inc');
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1492
      drupal_session_initialize();
1 by bzr
Drupal 6.6
1493
      break;
1494
1495
    case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
1496
      // Initialize configuration variables, using values from settings.php if available.
1497
      $conf = variable_init(isset($conf) ? $conf : array());
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1498
1 by bzr
Drupal 6.6
1499
      $cache_mode = variable_get('cache', CACHE_DISABLED);
63 by David Strauss
David Strauss: Add an 'external' caching mode that skips the built-in cache but still sends friendly headers downstream.
1500
      // Get the page from the cache, unless the cache is disabled or external.
1501
      if ($cache_mode != CACHE_DISABLED && $cache_mode != CACHE_EXTERNAL) {
1.5.17 by David Strauss
Integrate DamZ's updated work.
1502
        $cache = page_get_cache();
1503
      }
1504
      else {
1505
        $cache = FALSE;
1506
      }
1507
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1508
      // If the skipping of the bootstrap hooks is not enforced, call hook_init.
1509
      if (!is_object($cache) || $cache_mode != CACHE_AGGRESSIVE) {
1.1.3 by bzr
Drupal 6.9
1510
        // Load module handling.
1511
        require_once './includes/module.inc';
1.5.11 by David Strauss
Fix anon user bootstrap and changes for D6 boot phases.
1512
        bootstrap_invoke_all('boot');
1 by bzr
Drupal 6.6
1513
      }
1.5.17 by David Strauss
Integrate DamZ's updated work.
1514
1 by bzr
Drupal 6.6
1515
      // If there is a cached page, display it.
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1516
      if (is_object($cache)) {
1517
        header('X-Drupal-Cache: HIT');
1 by bzr
Drupal 6.6
1518
        drupal_page_cache_header($cache);
1519
        // If the skipping of the bootstrap hooks is not enforced, call hook_exit.
1520
        if ($cache_mode != CACHE_AGGRESSIVE) {
1521
          bootstrap_invoke_all('exit');
1522
        }
1523
        // We are done.
1524
        exit;
1525
      }
1.5.5 by David Strauss
Initial port of drupal.org #147310 and #201122.
1526
  
63 by David Strauss
David Strauss: Add an 'external' caching mode that skips the built-in cache but still sends friendly headers downstream.
1527
      if (!$cache && drupal_page_is_cacheable() && $cache_mode != CACHE_EXTERNAL) {
1.5.17 by David Strauss
Integrate DamZ's updated work.
1528
        header('X-Drupal-Cache: MISS');
1529
      }
63 by David Strauss
David Strauss: Add an 'external' caching mode that skips the built-in cache but still sends friendly headers downstream.
1530
      
1531
      // If using an external cache and the page is cacheable, set headers.
1532
      if ($cache_mode == CACHE_EXTERNAL && drupal_page_is_cacheable()) {
1533
        drupal_page_cache_header_external();
1534
      }
1.5.17 by David Strauss
Integrate DamZ's updated work.
1535
1 by bzr
Drupal 6.6
1536
      // Prepare for non-cached page workflow.
55 by David Strauss
To fix Drush (Drupal.org #487300 and LP #422516), apply Drupal.org #581286 by David Strauss, DamZ, and moshe weitzman
1537
      if (!drupal_is_cli()) {
50.1.1 by David Strauss
DamZ's fix for drupal.org #484610
1538
        ob_start();
1539
        drupal_page_header();
1540
      }
1 by bzr
Drupal 6.6
1541
      break;
1542
1543
    case DRUPAL_BOOTSTRAP_LANGUAGE:
1544
      drupal_init_language();
1545
      break;
1546
1547
    case DRUPAL_BOOTSTRAP_PATH:
1548
      require_once './includes/path.inc';
1549
      // Initialize $_GET['q'] prior to loading modules and invoking hook_init().
1550
      drupal_init_path();
1551
      break;
1552
1553
    case DRUPAL_BOOTSTRAP_FULL:
1554
      require_once './includes/common.inc';
1555
      _drupal_bootstrap_full();
1556
      break;
1557
  }
1558
}
1559
1560
/**
1561
 * Enables use of the theme system without requiring database access.
1562
 *
1563
 * Loads and initializes the theme system for site installs, updates and when
1564
 * the site is in off-line mode. This also applies when the database fails.
1565
 *
1566
 * @see _drupal_maintenance_theme()
1567
 */
1568
function drupal_maintenance_theme() {
1569
  require_once './includes/theme.maintenance.inc';
1570
  _drupal_maintenance_theme();
1571
}
1572
1573
/**
1574
 * Return the name of the localisation function. Use in code that needs to
1575
 * run both during installation and normal operation.
1576
 */
1577
function get_t() {
1578
  static $t;
1579
  if (is_null($t)) {
1580
    $t = function_exists('install_main') ? 'st' : 't';
1581
  }
1582
  return $t;
1583
}
1584
1585
/**
1586
 *  Choose a language for the current page, based on site and user preferences.
1587
 */
1588
function drupal_init_language() {
1589
  global $language, $user;
1590
1591
  // Ensure the language is correctly returned, even without multilanguage support.
1592
  // Useful for eg. XML/HTML 'lang' attributes.
1593
  if (variable_get('language_count', 1) == 1) {
1594
    $language = language_default();
1595
  }
1596
  else {
1597
    include_once './includes/language.inc';
1598
    $language = language_initialize();
1599
  }
1600
}
1601
1602
/**
1603
 * Get a list of languages set up indexed by the specified key
1604
 *
1605
 * @param $field The field to index the list with.
1606
 * @param $reset Boolean to request a reset of the list.
1607
 */
1608
function language_list($field = 'language', $reset = FALSE) {
1609
  static $languages = NULL;
1610
1611
  // Reset language list
1612
  if ($reset) {
1613
    $languages = NULL;
1614
  }
1615
1616
  // Init language list
1617
  if (!isset($languages)) {
1618
    if (variable_get('language_count', 1) > 1 || module_exists('locale')) {
1619
      $result = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC');
1620
      while ($row = db_fetch_object($result)) {
1621
        $languages['language'][$row->language] = $row;
1622
      }
1623
    }
1624
    else {
1625
      // No locale module, so use the default language only.
1626
      $default = language_default();
1627
      $languages['language'][$default->language] = $default;
1628
    }
1629
  }
1630
1631
  // Return the array indexed by the right field
1632
  if (!isset($languages[$field])) {
1633
    $languages[$field] = array();
1634
    foreach ($languages['language'] as $lang) {
1635
      // Some values should be collected into an array
1636
      if (in_array($field, array('enabled', 'weight'))) {
1637
        $languages[$field][$lang->$field][$lang->language] = $lang;
1638
      }
1639
      else {
1640
        $languages[$field][$lang->$field] = $lang;
1641
      }
1642
    }
1643
  }
1644
  return $languages[$field];
1645
}
1646
1647
/**
1648
 * Default language used on the site
1649
 *
1650
 * @param $property
1651
 *   Optional property of the language object to return
1652
 */
1653
function language_default($property = NULL) {
1654
  $language = variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''));
1655
  return $property ? $language->$property : $language;
1656
}
1657
1658
/**
1659
 * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
1660
 * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address
1661
 * of the proxy server, and not the client's.
1662
 *
1663
 * @return
1664
 *   IP address of client machine, adjusted for reverse proxy.
1665
 */
1666
function ip_address() {
1667
  static $ip_address = NULL;
1668
1669
  if (!isset($ip_address)) {
1670
    $ip_address = $_SERVER['REMOTE_ADDR'];
60.1.1 by David Strauss
Moshe's reverse proxy support patch.
1671
    
60.1.3 by David Strauss
Optimize code to reduce load on non-proxied systems.
1672
    // Only use parts of the X-Forwarded-For (XFF) header that have followed a trusted route.
1673
    // Specifically, identify the leftmost IP address in the XFF header that is not one of ours.
1674
    // An XFF header is: X-Forwarded-For: client1, proxy1, proxy2
67 by David Strauss
Allow configuration of the header to use for X-Forwarded-For. Slightly optimize the processing order.
1675
    if (isset($_SERVER['HTTP_' . variable_get('x_forwarded_for_header', 'X_FORWARDED_FOR')]) && variable_get('reverse_proxy', 0)) {
60.1.3 by David Strauss
Optimize code to reduce load on non-proxied systems.
1676
      // Load trusted reverse proxy server IPs.
1677
      $reverse_proxy_addresses = variable_get('reverse_proxy_addresses', array());
60.1.1 by David Strauss
Moshe's reverse proxy support patch.
1678
      
1679
      // Turn XFF header into an array.
67 by David Strauss
Allow configuration of the header to use for X-Forwarded-For. Slightly optimize the processing order.
1680
      $forwarded = explode(',', $_SERVER['HTTP_' . variable_get('x_forwarded_for_header', 'X_FORWARDED_FOR')]);
60.1.1 by David Strauss
Moshe's reverse proxy support patch.
1681
      
67 by David Strauss
Allow configuration of the header to use for X-Forwarded-For. Slightly optimize the processing order.
1682
      // Trim the forwarded IPs; they may have been delimited by commas and spaces.
1683
      $forwarded = array_map('trim', $forwarded);
1684
60.1.1 by David Strauss
Moshe's reverse proxy support patch.
1685
      // Tack direct client IP onto end of forwarded array.
1686
      $forwarded[] = $ip_address;
60.1.2 by David Strauss
Trim forwarded IPs, which may have spaces.
1687
60.1.1 by David Strauss
Moshe's reverse proxy support patch.
1688
      // Eliminate all trusted IPs.
1689
      $untrusted = array_diff($forwarded, $reverse_proxy_addresses);
1690
      
1691
      // The right-most IP is the most specific we can trust.
1692
      $ip_address = array_pop($untrusted);
60.1.4 by David Strauss
Add missing closing brace.
1693
    }
1 by bzr
Drupal 6.6
1694
  }
1695
1696
  return $ip_address;
1697
}
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1698
1699
/**
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1700
 * Initialize the session handler, starting a session if needed.
1701
 */
1702
function drupal_session_initialize() {
1703
  global $user;
1704
   
1705
  session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
1706
 
1707
  if (isset($_COOKIE[session_name()])) {
1708
    // If a session cookie exists, initialize the session. Otherwise the
1709
    // session is only started on demand in drupal_session_commit(), making
1710
    // anonymous users not use a session cookie unless something is stored in
1711
    // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
1712
    drupal_session_start();
1713
    if (!empty($user->uid) || !empty($_SESSION)) {
1714
      drupal_page_is_cacheable(FALSE);
1715
    }
1716
  }
1717
  else {
1718
    // Set a session identifier for this request. This is necessary because
1719
    // we lazyly start sessions at the end of this request, and some
1720
    // processes (like drupal_get_token()) needs to know the future
1721
    // session ID in advance.
1722
    $user = drupal_anonymous_user();
1723
    session_id(md5(uniqid('', TRUE)));
1724
  }
1725
}
1726
1727
/**
1.5.26 by David Strauss
Backport newer drupal_start_session().
1728
 * Forcefully start a session, preserving already set session data.
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1729
 */
1.5.26 by David Strauss
Backport newer drupal_start_session().
1730
function drupal_session_start() {
1731
  if (!drupal_session_started()) {
1732
    // Save current session data before starting it, as PHP will destroy it.
1733
    $session_data = isset($_SESSION) ? $_SESSION : NULL;
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1734
    session_start();
1.5.26 by David Strauss
Backport newer drupal_start_session().
1735
    drupal_session_started(TRUE);
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1736
    // Restore session data.
1.5.26 by David Strauss
Backport newer drupal_start_session().
1737
    if (!empty($session_data)) {
1738
      $_SESSION += $session_data;
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1739
    }
1740
  }
1741
}
1742
1743
/**
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1744
 * Commit the current session, if necessary.
1745
 *
1746
 * If an anonymous user already has an empty session, destroy it.
1747
 */
1748
function drupal_session_commit() {
1749
  global $user;
1750
1751
  if (empty($user->uid) && empty($_SESSION)) {
1752
    if (drupal_session_started() && drupal_save_session()) {
1753
      // Destroy empty anonymous sessions.
87 by David Strauss
Brian Vuyk, Mark Sonnabaum, and more: Fix session management issue on logout.
1754
      drupal_session_destroy();
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1755
    }
1756
  }
1757
  else if (drupal_save_session()) {
1758
    if (!drupal_session_started()) {
1759
      drupal_session_start();
1760
    }
1761
    // Write the session data.
1762
    session_write_close();
1763
  }
1764
}
1765
1766
/**
1767
 * Return whether a session has been started.
1768
 */  
1769
function drupal_session_started($set = NULL) {
1770
  static $session_started = FALSE;
1771
  if (isset($set)) {
1772
    $session_started = $set;
1773
  }
1774
  return $session_started && session_id();
1775
}
1776
1777
/**
1778
 * Called when an anonymous user becomes authenticated or vice-versa.
1779
 */
1780
function drupal_session_regenerate() {
1781
  global $user;
1782
1783
  // Set the session cookie "httponly" flag to reduce the risk of session
1784
  // stealing via XSS.
1785
  extract(session_get_cookie_params());
47 by David Strauss
Add compatibility with PHP 5.1 for cookie parameters.
1786
1787
  if (version_compare(PHP_VERSION, '5.2.0') === 1) {
1788
    session_set_cookie_params($lifetime, $path, $domain, $secure, TRUE);
1789
  }
1790
  else {
1791
    session_set_cookie_params($lifetime, $path, $domain, $secure);
1792
  }
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1793
1794
  if (drupal_session_started()) {
1795
    $old_session_id = session_id();
1796
    session_regenerate_id();
1797
  }
1798
  else {
1799
    // Start the session when it doesn't exist yet.
1800
    // Preserve the logged in user, as it will be reset to anonymous
1801
    // by _sess_read.
1802
    $account = $user;
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1803
    drupal_session_start();
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1804
    $user = $account;
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1805
  }
1806
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1807
  if (isset($old_session_id)) {
52 by David Strauss
Fix quotes for PostgreSQL (even though Pressflow does not support PostgreSQL).
1808
    db_query("UPDATE {sessions} SET sid = '%s' WHERE sid = '%s'", session_id(), $old_session_id);
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1809
  }
1.5.20 by David Strauss
Move new drupal_* functions from cache.inc to bootstrap.inc to preserve more of the D6 API.
1810
}
1811
1812
/**
1.5.22 by David Strauss
Initial backport of DamZ's latest reverse proxy code cleanup in D7.
1813
 * Determine whether to save session data of the current request.
1814
 *
1815
 * This function allows the caller to temporarily disable writing of
1816
 * session data, should the request end while performing potentially
1817
 * dangerous operations, such as manipulating the global $user object.
1818
 * See http://drupal.org/node/218104 for usage.
1819
 *
1820
 * @param $status
1821
 *   Disables writing of session data when FALSE, (re-)enables
1822
 *   writing when TRUE.
1823
 * @return
1824
 *   FALSE if writing session data has been disabled. Otherwise, TRUE.
1825
 */
1826
function drupal_save_session($status = NULL) {
1827
  static $save_session = TRUE;
1828
  if (isset($status)) {
1829
    $save_session = $status;
1830
  }
1831
  return $save_session;
1832
}
74.2.2 by David Strauss
Backport drupal_get_bootstrap_phase() from Drupal 7 to support SimpleTest.
1833
	
1834
/**
1835
 * Returns the current bootstrap phase for this Drupal process.
1836
 *
1837
 * The current phase is the one most recently completed by drupal_bootstrap().
1838
 *
1839
 * @see drupal_bootstrap()
1840
 */
1841
function drupal_get_bootstrap_phase() {
1842
  return drupal_bootstrap();
1843
}
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1844
1845
/**
1846
 * Validate the HMAC and timestamp of a user agent header from simpletest.
1847
 */
1848
function drupal_valid_test_ua($user_agent) {
74.2.1 by David Strauss
SimpleTest 1.10
1849
//  global $dbatabases;
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1850
  global $db_url;
1851
1852
  list($prefix, $time, $salt, $hmac) = explode(';', $user_agent);
1853
  $check_string =  $prefix . ';' . $time . ';' . $salt;
1854
  // We use the database credentials from settings.php to make the HMAC key, since
1855
  // the database is not yet initialized and we can't access any Drupal variables.
1856
  // The file properties add more entropy not easily accessible to others.
74.2.1 by David Strauss
SimpleTest 1.10
1857
//  $filepath = DRUPAL_ROOT . '/includes/bootstrap.inc';
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1858
  $filepath = './includes/bootstrap.inc';
74.2.1 by David Strauss
SimpleTest 1.10
1859
//  $key = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE);
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1860
  $key = sha1(serialize($db_url) . filectime($filepath) . fileinode($filepath), TRUE);
1861
  // The HMAC must match.
1862
  return $hmac == base64_encode(hash_hmac('sha1', $check_string, $key, TRUE));
1863
}
1864
1865
/**
1866
 * Generate a user agent string with a HMAC and timestamp for simpletest.
1867
 */
1868
function drupal_generate_test_ua($prefix) {
1869
//  global $dbatabases;
1870
  global $db_url;
1871
  static $key;
1872
1873
  if (!isset($key)) {
1874
    // We use the database credentials to make the HMAC key, since we
1875
    // check the HMAC before the database is initialized. filectime()
1876
    // and fileinode() are not easily determined from remote.
74.2.1 by David Strauss
SimpleTest 1.10
1877
//    $filepath = DRUPAL_ROOT . '/includes/bootstrap.inc';
1878
    $filepath = './includes/bootstrap.inc';                
1879
//    $key = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE);
47.1.1 by Josh Koenig
Merging in latest backport of simpletest from Drupal7, including the patch, some bugfixes around the user-agent, and simpletest cases.
1880
    $key = sha1(serialize($db_url) . filectime($filepath) . fileinode($filepath), TRUE);
1881
  }
1882
   // Generate a moderately secure HMAC based on the database credentials.
1883
   $salt = uniqid('', TRUE);
1884
   $check_string = $prefix . ';' . time() . ';' . $salt;
1885
   return  $check_string . ';' . base64_encode(hash_hmac('sha1', $check_string, $key, TRUE));
1886
}
55 by David Strauss
To fix Drush (Drupal.org #487300 and LP #422516), apply Drupal.org #581286 by David Strauss, DamZ, and moshe weitzman
1887
1888
/**
1889
 * Detect whether the current script is running in a command-line environment.
1890
 */
1891
function drupal_is_cli() {
1892
  return ((!isset($_SERVER['SERVER_SOFTWARE']) || $_SERVER['SERVER_SOFTWARE'] == 'PHP CLI')
1893
    && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)));
1894
}
87 by David Strauss
Brian Vuyk, Mark Sonnabaum, and more: Fix session management issue on logout.
1895
1896
/**
1897
 * Destroys all data registered to a session.
1898
 *
1899
 * Placed in bootstrap.inc instead of session.inc as session.inc may be
1900
 * overridden by other session persistance layers (eg., memcache).
1901
 *
1902
 * @ingroup php_wrappers
1903
 */
1904
function drupal_session_destroy() {
1905
  session_destroy();
1906
  
1907
  // Workaround PHP 5.2 fatal error "Failed to initialize storage module".
1908
  // @see http://bugs.php.net/bug.php?id=32330
1909
  session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
1910
}