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

Dries's avatar
   
Dries committed
4
5
6
7
/**
 * @file
 * Functions that need to be loaded on every Drupal request.
 */
Dries's avatar
   
Dries committed
8

9
10
11
12
/**
 * Indicates that the item should never be removed unless explicitly told to
 * using cache_clear_all() with a cache ID.
 */
13
define('CACHE_PERMANENT', 0);
14
15
16
17

/**
 * Indicates that the item should be removed at the next general cache wipe.
 */
18
define('CACHE_TEMPORARY', -1);
Dries's avatar
 
Dries committed
19

20
21
22
/**
 * Indicates that page caching is disabled.
 */
23
define('CACHE_DISABLED', 0);
24
25
26
27

/**
 * Indicates that page caching is enabled, using "normal" mode.
 */
28
define('CACHE_NORMAL', 1);
29
30
31
32
33
34

/**
 * Indicates that page caching is using "aggressive" mode. This bypasses
 * loading any modules for additional speed, which may break functionality in
 * modules that expect to be run on each page load.
 */
35
define('CACHE_AGGRESSIVE', 2);
36

37
/**
38
39
 *
 * Severity levels, as defined in RFC 3164 http://www.faqs.org/rfcs/rfc3164.html
40
41
 * @see watchdog()
 * @see watchdog_severity_levels()
42
 */
43
44
45
46
47
48
49
50
define('WATCHDOG_EMERG',    0); // Emergency: system is unusable
define('WATCHDOG_ALERT',    1); // Alert: action must be taken immediately
define('WATCHDOG_CRITICAL', 2); // Critical: critical conditions
define('WATCHDOG_ERROR',    3); // Error: error conditions
define('WATCHDOG_WARNING',  4); // Warning: warning conditions
define('WATCHDOG_NOTICE',   5); // Notice: normal but significant condition
define('WATCHDOG_INFO',     6); // Informational: informational messages
define('WATCHDOG_DEBUG',    7); // Debug: debug-level messages
51

52
53
54
/**
 * First bootstrap phase: initialize configuration.
 */
55
define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0);
56
57
58
59
60

/**
 * Second bootstrap phase: try to call a non-database cache
 * fetch routine.
 */
61
define('DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE', 1);
62
63
64
65

/**
 * Third bootstrap phase: initialize database layer.
 */
66
define('DRUPAL_BOOTSTRAP_DATABASE', 2);
67
68
69
70

/**
 * Fourth bootstrap phase: identify and reject banned hosts.
 */
71
define('DRUPAL_BOOTSTRAP_ACCESS', 3);
72
73
74
75

/**
 * Fifth bootstrap phase: initialize session handling.
 */
76
define('DRUPAL_BOOTSTRAP_SESSION', 4);
77
78
79
80
81

/**
 * Sixth bootstrap phase: load bootstrap.inc and module.inc, start
 * the variable system and try to serve a page from the cache.
 */
82
define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 5);
83
84

/**
85
 * Seventh bootstrap phase: find out language of the page.
86
 */
87
88
89
90
91
92
define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);

/**
 * Eighth bootstrap phase: set $_GET['q'] to Drupal path of request.
 */
define('DRUPAL_BOOTSTRAP_PATH', 7);
93
94
95
96
97

/**
 * Final bootstrap phase: Drupal is fully loaded; validate and fix
 * input data.
 */
98
define('DRUPAL_BOOTSTRAP_FULL', 8);
99

100
101
102
/**
 * Role ID for anonymous users; should match what's in the "role" table.
 */
103
define('DRUPAL_ANONYMOUS_RID', 1);
104
105
106
107

/**
 * Role ID for authenticated users; should match what's in the "role" table.
 */
108
109
define('DRUPAL_AUTHENTICATED_RID', 2);

110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
 * No language negotiation. The default language is used.
 */
define('LANGUAGE_NEGOTIATION_NONE', 0);

/**
 * Path based negotiation with fallback to default language
 * if no defined path prefix identified.
 */
define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1);

/**
 * Path based negotiation with fallback to user preferences
 * and browser language detection if no defined path prefix
 * identified.
 */
define('LANGUAGE_NEGOTIATION_PATH', 2);

/**
 * Domain based negotiation with fallback to default language
 * if no language identified by domain.
 */
define('LANGUAGE_NEGOTIATION_DOMAIN', 3);

Dries's avatar
   
Dries committed
134
/**
135
 * Start the timer with the specified name. If you start and stop
Dries's avatar
   
Dries committed
136
137
138
139
140
141
142
143
144
145
146
 * the same timer multiple times, the measured intervals will be
 * accumulated.
 *
 * @param name
 *   The name of the timer.
 */
function timer_start($name) {
  global $timers;

  list($usec, $sec) = explode(' ', microtime());
  $timers[$name]['start'] = (float)$usec + (float)$sec;
147
  $timers[$name]['count'] = isset($timers[$name]['count']) ? ++$timers[$name]['count'] : 1;
Dries's avatar
   
Dries committed
148
149
150
151
152
153
154
155
156
157
158
159
160
}

/**
 * Read the current timer value without stopping the timer.
 *
 * @param name
 *   The name of the timer.
 * @return
 *   The current timer value in ms.
 */
function timer_read($name) {
  global $timers;

161
162
163
164
  if (isset($timers[$name]['start'])) {
    list($usec, $sec) = explode(' ', microtime());
    $stop = (float)$usec + (float)$sec;
    $diff = round(($stop - $timers[$name]['start']) * 1000, 2);
Dries's avatar
   
Dries committed
165

166
167
168
169
    if (isset($timers[$name]['time'])) {
      $diff += $timers[$name]['time'];
    }
    return $diff;
170
  }
Dries's avatar
   
Dries committed
171
172
173
174
175
176
177
178
}

/**
 * Stop the timer with the specified name.
 *
 * @param name
 *   The name of the timer.
 * @return
179
 *   A timer array. The array contains the number of times the
Dries's avatar
   
Dries committed
180
181
182
183
184
185
 *   timer has been started and stopped (count) and the accumulated
 *   timer value in ms (time).
 */
function timer_stop($name) {
  global $timers;

186
  $timers[$name]['time'] = timer_read($name);
Dries's avatar
   
Dries committed
187
188
189
190
  unset($timers[$name]['start']);

  return $timers[$name];
}
191

Dries's avatar
   
Dries committed
192
/**
193
 * Find the appropriate configuration directory.
Dries's avatar
   
Dries committed
194
 *
195
196
 * Try finding a matching configuration directory by stripping the website's
 * hostname from left to right and pathname from right to left. The first
197
 * configuration file found will be used; the remaining will ignored. If no
198
 * configuration file is found, return a default value '$confdir/default'.
Dries's avatar
Dries committed
199
 *
200
 * Example for a fictitious site installed at
201
202
 * http://www.drupal.org:8080/mysite/test/ the 'settings.php' is searched in
 * the following directories:
Dries's avatar
   
Dries committed
203
 *
204
205
206
207
 *  1. $confdir/8080.www.drupal.org.mysite.test
 *  2. $confdir/www.drupal.org.mysite.test
 *  3. $confdir/drupal.org.mysite.test
 *  4. $confdir/org.mysite.test
Dries's avatar
   
Dries committed
208
 *
209
210
211
212
 *  5. $confdir/8080.www.drupal.org.mysite
 *  6. $confdir/www.drupal.org.mysite
 *  7. $confdir/drupal.org.mysite
 *  8. $confdir/org.mysite
Dries's avatar
   
Dries committed
213
 *
214
215
216
217
 *  9. $confdir/8080.www.drupal.org
 * 10. $confdir/www.drupal.org
 * 11. $confdir/drupal.org
 * 12. $confdir/org
Dries's avatar
   
Dries committed
218
 *
219
 * 13. $confdir/default
220
221
222
223
224
225
226
227
228
229
230
 *
 * @param $require_settings
 *   Only configuration directories with an existing settings.php file
 *   will be recognized. Defaults to TRUE. During initial installation,
 *   this is set to FALSE so that Drupal can detect a matching directory,
 *   then create a new settings.php file in it.
 * @param reset
 *   Force a full search for matching directories even if one had been
 *   found previously.
 * @return
 *   The path of the matching directory.
Dries's avatar
   
Dries committed
231
 */
232
function conf_path($require_settings = TRUE, $reset = FALSE) {
Dries's avatar
Dries committed
233
  static $conf = '';
Dries's avatar
 
Dries committed
234

235
  if ($conf && !$reset) {
Dries's avatar
Dries committed
236
237
    return $conf;
  }
Dries's avatar
 
Dries committed
238

Dries's avatar
   
Dries committed
239
  $confdir = 'sites';
240
  $uri = explode('/', $_SERVER['SCRIPT_NAME'] ? $_SERVER['SCRIPT_NAME'] : $_SERVER['SCRIPT_FILENAME']);
241
  $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
Dries's avatar
Dries committed
242
243
244
  for ($i = count($uri) - 1; $i > 0; $i--) {
    for ($j = count($server); $j > 0; $j--) {
      $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
245
      if (file_exists("$confdir/$dir/settings.php") || (!$require_settings && file_exists("$confdir/$dir"))) {
Dries's avatar
Dries committed
246
247
248
        $conf = "$confdir/$dir";
        return $conf;
      }
Dries's avatar
 
Dries committed
249
250
    }
  }
Dries's avatar
Dries committed
251
252
  $conf = "$confdir/default";
  return $conf;
Dries's avatar
 
Dries committed
253
254
}

255
256
257
258
259
/**
 * Unsets all disallowed global variables. See $allowed for what's allowed.
 */
function drupal_unset_globals() {
  if (ini_get('register_globals')) {
260
    $allowed = array('_ENV' => 1, '_GET' => 1, '_POST' => 1, '_COOKIE' => 1, '_FILES' => 1, '_SERVER' => 1, '_REQUEST' => 1, 'GLOBALS' => 1);
261
    foreach ($GLOBALS as $key => $value) {
262
263
264
265
266
267
268
      if (!isset($allowed[$key])) {
        unset($GLOBALS[$key]);
      }
    }
  }
}

269
/**
270
271
 * Loads the configuration and sets the base URL, cookie domain, and
 * session name correctly.
272
273
 */
function conf_init() {
274
275
  global $base_url, $base_path, $base_root;

Dries's avatar
Dries committed
276
  // Export the following settings.php variables to the global namespace
277
  global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access;
Dries's avatar
Dries committed
278
279
  $conf = array();

280
281
282
  if (file_exists('./'. conf_path() .'/settings.php')) {
    include_once './'. conf_path() .'/settings.php';
  }
283
284
285
286

  if (isset($base_url)) {
    // Parse fixed base URL from settings.php.
    $parts = parse_url($base_url);
287
288
289
    if (!isset($parts['path'])) {
      $parts['path'] = '';
    }
290
    $base_path = $parts['path'] .'/';
291
292
293
294
295
296
    // Build $base_root (everything until first slash after "scheme://").
    $base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path']));
  }
  else {
    // Create base URL
    $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
297
298
299
300
301
302
303
304

    // As $_SERVER['HTTP_HOST'] is user input, ensure it only contains
    // characters allowed in hostnames.
    $base_url = $base_root .= '://'. preg_replace('/[^a-z0-9-:._]/i', '', $_SERVER['HTTP_HOST']);

    // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not
    // be modified by a visitor.
    if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) {
305
306
307
308
309
310
311
312
      $base_path = "/$dir";
      $base_url .= $base_path;
      $base_path .= '/';
    }
    else {
      $base_path = '/';
    }
  }
313
314
315
316
317
318

  if ($cookie_domain) {
    // If the user specifies the cookie domain, also use it for session name.
    $session_name = $cookie_domain;
  }
  else {
319
320
321
    // Otherwise use $base_url as session name, without the protocol
    // to use the same session identifiers across http and https.
    list( , $session_name) = explode('://', $base_url, 2);
322
    // We escape the hostname because it can be modified by a visitor.
323
    if (!empty($_SERVER['HTTP_HOST'])) {
324
      $cookie_domain = check_plain($_SERVER['HTTP_HOST']);
325
326
327
328
329
330
331
    }
  }
  // Strip leading periods, www., and port numbers from cookie domain.
  $cookie_domain = ltrim($cookie_domain, '.');
  if (strpos($cookie_domain, 'www.') === 0) {
    $cookie_domain = substr($cookie_domain, 4);
  }
332
  $cookie_domain = explode(':', $cookie_domain);
333
  $cookie_domain = '.'. $cookie_domain[0];
334
335
336
337
338
339
  // Per RFC 2109, cookie domains must contain at least one dot other than the
  // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
  if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
    ini_set('session.cookie_domain', $cookie_domain);
  }
  session_name('SESS'. md5($session_name));
340
341
}

Dries's avatar
Dries committed
342
343
/**
 * Returns and optionally sets the filename for a system item (module,
344
 * theme, etc.). The filename, whether provided, cached, or retrieved
Dries's avatar
Dries committed
345
346
 * from the database, is only returned if the file exists.
 *
Dries's avatar
Dries committed
347
348
349
350
351
352
353
354
355
356
357
358
 * This function plays a key role in allowing Drupal's resources (modules
 * and themes) to be located in different places depending on a site's
 * configuration. For example, a module 'foo' may legally be be located
 * in any of these three places:
 *
 * modules/foo/foo.module
 * sites/all/modules/foo/foo.module
 * sites/example.com/modules/foo/foo.module
 *
 * Calling drupal_get_filename('module', 'foo') will give you one of
 * the above, depending on where the module is located.
 *
Dries's avatar
Dries committed
359
360
361
362
363
364
365
366
367
368
369
 * @param $type
 *   The type of the item (i.e. theme, theme_engine, module).
 * @param $name
 *   The name of the item for which the filename is requested.
 * @param $filename
 *   The filename of the item if it is to be set explicitly rather
 *   than by consulting the database.
 *
 * @return
 *   The filename of the requested item.
 */
Dries's avatar
Dries committed
370
function drupal_get_filename($type, $name, $filename = NULL) {
Dries's avatar
Dries committed
371
372
  static $files = array();

373
  if (!isset($files[$type])) {
Dries's avatar
Dries committed
374
375
376
    $files[$type] = array();
  }

377
  if (!empty($filename) && file_exists($filename)) {
Dries's avatar
Dries committed
378
379
    $files[$type][$name] = $filename;
  }
380
  elseif (isset($files[$type][$name])) {
Dries's avatar
Dries committed
381
382
    // nothing
  }
Dries's avatar
Dries committed
383
384
385
386
  // Verify that we have an active database connection, before querying
  // the database.  This is required because this function is called both
  // before we have a database connection (i.e. during installation) and
  // when a database connection fails.
387
  elseif (db_is_active() && (($file = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s' AND type = '%s'", $name, $type))) && file_exists($file))) {
Dries's avatar
Dries committed
388
389
390
    $files[$type][$name] = $file;
  }
  else {
Dries's avatar
Dries committed
391
392
    // Fallback to searching the filesystem if the database connection is
    // not established or the requested file is not found.
Steven Wittens's avatar
Steven Wittens committed
393
    $config = conf_path();
Dries's avatar
Dries committed
394
    $dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s");
Dries's avatar
   
Dries committed
395
    $file = (($type == 'theme_engine') ? "$name.engine" : "$name.$type");
Dries's avatar
Dries committed
396
397
398
399
400
401
402
403
404

    foreach (array("$config/$dir/$file", "$config/$dir/$name/$file", "$dir/$file", "$dir/$name/$file") as $file) {
      if (file_exists($file)) {
        $files[$type][$name] = $file;
        break;
      }
    }
  }

405
406
407
  if (isset($files[$type][$name])) {
    return $files[$type][$name];
  }
Dries's avatar
Dries committed
408
409
}

Dries's avatar
   
Dries committed
410
411
412
413
414
415
416
/**
 * Load the persistent variable table.
 *
 * The variable table is composed of values that have been saved in the table
 * with variable_set() as well as those explicitly specified in the configuration
 * file.
 */
Dries's avatar
 
Dries committed
417
function variable_init($conf = array()) {
418
  // NOTE: caching the variables improves performance by 20% when serving cached pages.
419
  if ($cached = cache_get('variables', 'cache')) {
420
    $variables = $cached->data;
Dries's avatar
   
Dries committed
421
422
423
424
425
  }
  else {
    $result = db_query('SELECT * FROM {variable}');
    while ($variable = db_fetch_object($result)) {
      $variables[$variable->name] = unserialize($variable->value);
Dries's avatar
 
Dries committed
426
    }
427
    cache_set('variables', $variables);
Dries's avatar
   
Dries committed
428
429
430
431
  }

  foreach ($conf as $name => $value) {
    $variables[$name] = $value;
Dries's avatar
 
Dries committed
432
433
  }

Dries's avatar
   
Dries committed
434
  return $variables;
Dries's avatar
 
Dries committed
435
436
}

Dries's avatar
   
Dries committed
437
438
439
440
441
442
443
444
445
446
/**
 * Return a persistent variable.
 *
 * @param $name
 *   The name of the variable to return.
 * @param $default
 *   The default value to use if this variable has never been set.
 * @return
 *   The value of the variable.
 */
Dries's avatar
 
Dries committed
447
448
449
450
451
452
function variable_get($name, $default) {
  global $conf;

  return isset($conf[$name]) ? $conf[$name] : $default;
}

Dries's avatar
   
Dries committed
453
454
455
456
457
458
459
460
461
/**
 * Set a persistent variable.
 *
 * @param $name
 *   The name of the variable to set.
 * @param $value
 *   The value to set. This can be any PHP data type; these functions take care
 *   of serialization as necessary.
 */
Dries's avatar
 
Dries committed
462
463
464
function variable_set($name, $value) {
  global $conf;

465
466
467
468
469
  $serialized_value = serialize($value);
  db_query("UPDATE {variable} SET value = '%s' WHERE name = '%s'", $serialized_value, $name);
  if (!db_affected_rows()) {
    @db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, $serialized_value);
  }
Dries's avatar
   
Dries committed
470

471
  cache_clear_all('variables', 'cache');
Dries's avatar
 
Dries committed
472
473
474
475

  $conf[$name] = $value;
}

Dries's avatar
   
Dries committed
476
477
478
479
480
481
/**
 * Unset a persistent variable.
 *
 * @param $name
 *   The name of the variable to undefine.
 */
Dries's avatar
 
Dries committed
482
483
484
485
function variable_del($name) {
  global $conf;

  db_query("DELETE FROM {variable} WHERE name = '%s'", $name);
486
  cache_clear_all('variables', 'cache');
Dries's avatar
 
Dries committed
487
488
489
490

  unset($conf[$name]);
}

491

Dries's avatar
   
Dries committed
492
493
494
/**
 * Retrieve the current page from the cache.
 *
495
 * Note: we do not serve cached pages when status messages are waiting (from
Dries's avatar
   
Dries committed
496
497
 * a redirected form submission which was completed).
 */
Dries's avatar
 
Dries committed
498
function page_get_cache() {
499
  global $user, $base_root;
Dries's avatar
 
Dries committed
500
501

  $cache = NULL;
502

503
  if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0) {
504
    $cache = cache_get($base_root . request_uri(), 'cache_page');
Dries's avatar
 
Dries committed
505
506
507
508
509
510
511
512
513

    if (empty($cache)) {
      ob_start();
    }
  }

  return $cache;
}

Dries's avatar
Dries committed
514
515
516
/**
 * Call all init or exit hooks without including all modules.
 *
517
 * @param $hook
Dries's avatar
Dries committed
518
519
 *   The name of the bootstrap hook we wish to invoke.
 */
520
function bootstrap_invoke_all($hook) {
521
  foreach (module_list(TRUE, TRUE) as $module) {
Dries's avatar
Dries committed
522
    drupal_load('module', $module);
523
    module_invoke($module, $hook);
524
  }
Dries's avatar
Dries committed
525
526
527
}

/**
528
 * Includes a file with the provided type and name. This prevents
Dries's avatar
Dries committed
529
530
531
532
533
534
535
536
537
538
539
540
541
 * including a theme, engine, module, etc., more than once.
 *
 * @param $type
 *   The type of item to load (i.e. theme, theme_engine, module).
 * @param $name
 *   The name of the item to load.
 *
 * @return
 *   TRUE if the item is loaded or has already been loaded.
 */
function drupal_load($type, $name) {
  static $files = array();

542
  if (isset($files[$type][$name])) {
Dries's avatar
Dries committed
543
544
545
546
547
548
    return TRUE;
  }

  $filename = drupal_get_filename($type, $name);

  if ($filename) {
549
    include_once "./$filename";
Dries's avatar
Dries committed
550
551
552
553
554
555
556
557
    $files[$type][$name] = TRUE;

    return TRUE;
  }

  return FALSE;
}

Dries's avatar
   
Dries committed
558
559
/**
 * Set HTTP headers in preparation for a page response.
560
 *
561
562
563
564
 * Authenticated users are always given a 'no-cache' header, and will
 * fetch a fresh page on every request.  This prevents authenticated
 * users seeing locally cached pages that show them as logged out.
 *
565
 * @see page_set_cache()
Dries's avatar
   
Dries committed
566
 */
Dries's avatar
 
Dries committed
567
function drupal_page_header() {
568
  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
569
  header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT");
570
  header("Cache-Control: store, no-cache, must-revalidate");
571
572
  header("Cache-Control: post-check=0, pre-check=0", FALSE);
}
Dries's avatar
   
Dries committed
573

574
575
576
577
578
579
580
581
582
583
584
585
/**
 * Set HTTP headers in preparation for a cached page response.
 *
 * The general approach here is that anonymous users can keep a local
 * cache of the page, but must revalidate it on every request.  Then,
 * they are given a '304 Not Modified' response as long as they stay
 * logged out and the page has not been modified.
 *
 */
function drupal_page_cache_header($cache) {
  // Set default values:
  $last_modified = gmdate('D, d M Y H:i:s', $cache->created) .' GMT';
586
  $etag = '"'. md5($last_modified) .'"';
587
588
589
590
591
592
593
594
595
596
597
598
599

  // See if the client has provided the required HTTP headers:
  $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
  $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;

  if ($if_modified_since && $if_none_match
      && $if_none_match == $etag // etag must match
      && $if_modified_since == $last_modified) {  // if-modified-since must match
    header('HTTP/1.1 304 Not Modified');
    // All 304 responses must send an etag if the 200 response for the same object contained an etag
    header("Etag: $etag");
    exit();
  }
600

601
602
603
  // Send appropriate response:
  header("Last-Modified: $last_modified");
  header("ETag: $etag");
604

605
606
607
  // The following headers force validation of cache:
  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
  header("Cache-Control: must-revalidate");
608

609
610
611
612
613
614
615
616
617
  if (variable_get('page_compression', TRUE)) {
    // Determine if the browser accepts gzipped data.
    if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === FALSE && function_exists('gzencode')) {
      // Strip the gzip header and run uncompress.
      $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
    }
    elseif (function_exists('gzencode')) {
      header('Content-Encoding: gzip');
    }
Dries's avatar
 
Dries committed
618
  }
619
620
621
622
623
624
625
626
627
628

  // Send the original request's headers. We send them one after
  // another so PHP's header() function can deal with duplicate
  // headers.
  $headers = explode("\n", $cache->headers);
  foreach ($headers as $header) {
    header($header);
  }

  print $cache->data;
Dries's avatar
 
Dries committed
629
630
}

Dries's avatar
   
Dries committed
631
632
633
/**
 * Define the critical hooks that force modules to always be loaded.
 */
Dries's avatar
 
Dries committed
634
function bootstrap_hooks() {
635
  return array('boot', 'exit');
Dries's avatar
 
Dries committed
636
637
}

Dries's avatar
   
Dries committed
638
639
640
641
642
643
644
645
/**
 * Unserializes and appends elements from a serialized string.
 *
 * @param $obj
 *   The object to which the elements are appended.
 * @param $field
 *   The attribute of $obj whose value should be unserialized.
 */
Dries's avatar
   
Dries committed
646
647
648
649
650
651
652
653
654
655
656
function drupal_unpack($obj, $field = 'data') {
  if ($obj->$field && $data = unserialize($obj->$field)) {
    foreach ($data as $key => $value) {
      if (!isset($obj->$key)) {
        $obj->$key = $value;
      }
    }
  }
  return $obj;
}

Dries's avatar
   
Dries committed
657
658
659
/**
 * Return the URI of the referring page.
 */
Dries's avatar
 
Dries committed
660
function referer_uri() {
661
  if (isset($_SERVER['HTTP_REFERER'])) {
662
    return $_SERVER['HTTP_REFERER'];
Dries's avatar
 
Dries committed
663
664
665
  }
}

Dries's avatar
Dries committed
666
667
/**
 * Encode special characters in a plain-text string for display as HTML.
Gábor Hojtsy's avatar
Gábor Hojtsy committed
668
669
670
 *
 * Uses drupal_validate_utf8 to prevent cross site scripting attacks on
 * Internet Explorer 6.
Dries's avatar
Dries committed
671
672
 */
function check_plain($text) {
Gábor Hojtsy's avatar
Gábor Hojtsy committed
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
  return drupal_validate_utf8($text) ? htmlspecialchars($text, ENT_QUOTES) : '';
}

/**
 * Checks whether a string is valid UTF-8.
 *
 * All functions designed to filter input should use drupal_validate_utf8
 * to ensure they operate on valid UTF-8 strings to prevent bypass of the
 * filter.
 *
 * When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented
 * as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent
 * bytes. When these subsequent bytes are HTML control characters such as
 * quotes or angle brackets, parts of the text that were deemed safe by filters
 * end up in locations that are potentially unsafe; An onerror attribute that
 * is outside of a tag, and thus deemed safe by a filter, can be interpreted
 * by the browser as if it were inside the tag.
 *
 * This function exploits preg_match behaviour (since PHP 4.3.5) when used
 * with the u modifier, as a fast way to find invalid UTF-8. When the matched
 * string contains an invalid byte sequence, it will fail silently.
 *
 * preg_match may not fail on 4 and 5 octet sequences, even though they
 * are not supported by the specification.
 *
 * The specific preg_match behaviour is present since PHP 4.3.5.
 *
 * @param $text
 *   The text to check.
 * @return
 *   TRUE if the text is valid UTF-8, FALSE if not.
 */
function drupal_validate_utf8($text) {
  if (strlen($text) == 0) {
    return TRUE;
  }
  return (preg_match('/^./us', $text) == 1);
Dries's avatar
Dries committed
710
711
}

Dries's avatar
   
Dries committed
712
/**
713
714
 * Since $_SERVER['REQUEST_URI'] is only available on Apache, we
 * generate an equivalent using other environment variables.
Dries's avatar
   
Dries committed
715
 */
Dries's avatar
 
Dries committed
716
function request_uri() {
717
718
719
720
721
722

  if (isset($_SERVER['REQUEST_URI'])) {
    $uri = $_SERVER['REQUEST_URI'];
  }
  else {
    if (isset($_SERVER['argv'])) {
723
      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['argv'][0];
724
    }
725
    elseif (isset($_SERVER['QUERY_STRING'])) {
726
      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING'];
727
    }
728
729
730
    else {
      $uri = $_SERVER['SCRIPT_NAME'];
    }
Dries's avatar
 
Dries committed
731
  }
732

733
  return $uri;
Dries's avatar
 
Dries committed
734
}
Dries's avatar
Dries committed
735

Dries's avatar
   
Dries committed
736
737
738
739
740
741
/**
 * Log a system message.
 *
 * @param $type
 *   The category to which this message belongs.
 * @param $message
742
743
 *   The message to store in the log. See t() for documentation
 *   on how $message and $variables interact. Keep $message
744
 *   translatable by not concatenating dynamic values into it!
745
746
747
748
 * @param $variables
 *   Array of variables to replace in the message on display or
 *   NULL if message is already translated or not possible to
 *   translate.
749
 * @param $severity
750
 *   The severity of the message, as per RFC 3164
Dries's avatar
   
Dries committed
751
752
 * @param $link
 *   A link to associate with the message.
753
 *
754
 * @see watchdog_severity_levels()
Dries's avatar
   
Dries committed
755
 */
756
function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
757
  global $user, $base_root;
758

759
760
761
762
  // Prepare the fields to be logged
  $log_message = array(
    'type'        => $type,
    'message'     => $message,
763
    'variables'   => $variables,
764
765
766
767
768
    'severity'    => $severity,
    'link'        => $link,
    'user'        => $user,
    'request_uri' => $base_root . request_uri(),
    'referer'     => referer_uri(),
769
    'ip'          => ip_address(),
770
771
772
773
774
775
    'timestamp'   => time(),
    );

  // Call the logging hooks to log/process the message
  foreach (module_implements('watchdog', TRUE) as $module) {
    module_invoke($module, 'watchdog', $log_message);
776
  }
Dries's avatar
   
Dries committed
777
778
}

Dries's avatar
   
Dries committed
779
/**
780
 * Set a message which reflects the status of the performed operation.
Dries's avatar
   
Dries committed
781
 *
782
783
 * If the function is called with no arguments, this function returns all set
 * messages without clearing them.
Dries's avatar
   
Dries committed
784
 *
785
786
787
788
789
790
 * @param $message
 *   The message should begin with a capital letter and always ends with a
 *   period '.'.
 * @param $type
 *   The type of the message. One of the following values are possible:
 *   - 'status'
791
 *   - 'warning'
792
 *   - 'error'
793
794
795
 * @param $repeat
 *   If this is FALSE and the message is already set, then the message won't
 *   be repeated.
Dries's avatar
   
Dries committed
796
 */
797
function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
798
  if ($message) {
Dries's avatar
   
Dries committed
799
800
801
802
803
804
805
806
    if (!isset($_SESSION['messages'])) {
      $_SESSION['messages'] = array();
    }

    if (!isset($_SESSION['messages'][$type])) {
      $_SESSION['messages'][$type] = array();
    }

807
808
809
    if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
      $_SESSION['messages'][$type][] = $message;
    }
810
811
  }

812
813
  // messages not set when DB connection fails
  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
814
815
}

Dries's avatar
   
Dries committed
816
817
818
/**
 * Return all messages that have been set.
 *
819
820
 * @param $type
 *   (optional) Only return messages of this type.
821
822
 * @param $clear_queue
 *   (optional) Set to FALSE if you do not want to clear the messages queue
823
824
825
826
827
 * @return
 *   An associative array, the key is the message type, the value an array
 *   of messages. If the $type parameter is passed, you get only that type,
 *   or an empty array if there are no such messages. If $type is not passed,
 *   all message types are returned, or an empty array if none exist.
Dries's avatar
   
Dries committed
828
 */
829
function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
830
  if ($messages = drupal_set_message()) {
831
    if ($type) {
832
      if ($clear_queue) {
833
        unset($_SESSION['messages'][$type]);
834
      }
835
836
837
      if (isset($messages[$type])) {
        return array($type => $messages[$type]);
      }
838
839
    }
    else {
840
      if ($clear_queue) {
841
        unset($_SESSION['messages']);
842
      }
843
844
      return $messages;
    }
845
  }
846
  return array();
847
848
}

Dries's avatar
   
Dries committed
849
/**
850
851
852
853
854
855
856
857
858
 * Check to see if an IP address has been blocked.
 *
 * Blocked IP addresses are stored in the database by default. However for
 * performance reasons we allow an override in settings.php. This allows us
 * to avoid querying the database at this critical stage of the bootstrap if
 * an administrative interface for IP address blocking is not required.
 *
 * @param $ip string
 *   IP address to check.
859
860
 * @return bool
 *   TRUE if access is denied, FALSE if access is allowed.
Dries's avatar
   
Dries committed
861
 */
862
863
864
865
866
867
868
869
870
871
872
873
function drupal_is_denied($ip) {
  // Because this function is called on every page request, we first check
  // for an array of IP addresses in settings.php before querying the
  // database.
  $blocked_ips = variable_get('blocked_ips', NULL);
  if (isset($blocked_ips) && is_array($blocked_ips)) {
    return in_array($ip, $blocked_ips);
  }
  else {
    $sql = "SELECT 1 FROM {blocked_ips} WHERE ip = '%s'";
    return (bool) db_result(db_query($sql, $ip));
  }
Dries's avatar
   
Dries committed
874
875
}

876
/**
877
 * Generates a default anonymous $user object.
878
879
880
 *
 * @return Object - the user object.
 */
881
function drupal_anonymous_user($session = '') {
882
883
  $user = new stdClass();
  $user->uid = 0;
884
  $user->hostname = ip_address();
885
886
  $user->roles = array();
  $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
887
  $user->session = $session;
888
  $user->cache = 0;
889
890
891
  return $user;
}

892
893
894
/**
 * A string describing a phase of Drupal to load. Each phase adds to the
 * previous one, so invoking a later phase automatically runs the earlier
895
 * phases too. The most important usage is that if you want to access the
896
 * Drupal database from a script without loading anything else, you can
897
 * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
898
899
 *
 * @param $phase
900
 *   A constant. Allowed values are:
901
902
 *     DRUPAL_BOOTSTRAP_CONFIGURATION: initialize configuration.
 *     DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: try to call a non-database cache fetch routine.
903
 *     DRUPAL_BOOTSTRAP_DATABASE: initialize database layer.
904
 *     DRUPAL_BOOTSTRAP_ACCESS: identify and reject banned hosts.
905
 *     DRUPAL_BOOTSTRAP_SESSION: initialize session handling.
906
 *     DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start
907
 *       the variable system and try to serve a page from the cache.
908
 *     DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page.
909
 *     DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request.
910
 *     DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data.
911
912
 */
function drupal_bootstrap($phase) {
913
  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;
914

915
  while ($phase >= $phase_index && isset($phases[$phase_index])) {
916
    $current_phase = $phases[$phase_index];
917
    unset($phases[$phase_index++]);
918
919
920
    _drupal_bootstrap($current_phase);
  }
}
Dries's avatar
   
Dries committed
921

922
923
function _drupal_bootstrap($phase) {
  global $conf;
Dries's avatar
 
Dries committed
924

925
  switch ($phase) {
926

927
    case DRUPAL_BOOTSTRAP_CONFIGURATION:
928
      drupal_unset_globals();
929
930
      // Start a page timer:
      timer_start('page');
931
932
      // Initialize the configuration
      conf_init();
933
      break;
934

935
    case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE:
936
937
938
939
940
941
942
943
944
945
      // Allow specifying special cache handlers in settings.php, like
      // using memcached or files for storing cache information.
      require_once variable_get('cache_inc', './includes/cache.inc');
      // If the page_cache_fastpath is set to TRUE in settings.php and
      // page_cache_fastpath (implemented in the special implementation of
      // cache.inc) printed the page and indicated this with a returned TRUE
      // then we are done.
      if (variable_get('page_cache_fastpath', FALSE) && page_cache_fastpath()) {
        exit;
      }
946
      break;
947

948
    case DRUPAL_BOOTSTRAP_DATABASE:
949
      // Initialize the default database.
950
      require_once './includes/database.inc';
951
952
      db_set_active();
      break;
953

954
    case DRUPAL_BOOTSTRAP_ACCESS:
955
956
      // Deny access to blocked IP addresses - t() is not yet available.
      if (drupal_is_denied(ip_address())) {
957
        header('HTTP/1.1 403 Forbidden');
958
        print 'Sorry, '. check_plain(ip_address()) .' has been banned.';
959
960
961
962
        exit();
      }
      break;

963
    case DRUPAL_BOOTSTRAP_SESSION:
964
      require_once variable_get('session_inc', './includes/session.inc');
965
      session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
966
967
      session_start();
      break;
968

969
    case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
970
971
      // Initialize configuration variables, using values from settings.php if available.
      $conf = variable_init(isset($conf) ? $conf : array());
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
      // Load module handling.
      require_once './includes/module.inc';
      $cache_mode = variable_get('cache', CACHE_DISABLED);
      // Get the page from the cache.
      $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache();
      // If the skipping of the bootstrap hooks is not enforced, call hook_boot.
      if ($cache_mode != CACHE_AGGRESSIVE) {
        bootstrap_invoke_all('boot');
      }
      // If there is a cached page, display it.
      if ($cache) {
        drupal_page_cache_header($cache);
        // If the skipping of the bootstrap hooks is not enforced, call hook_exit.
        if ($cache_mode != CACHE_AGGRESSIVE) {
          bootstrap_invoke_all('exit');
        }
        // We are done.
        exit;
      }
      // Prepare for non-cached page workflow.
992
993
      drupal_page_header();
      break;
994

995
996
997
998
    case DRUPAL_BOOTSTRAP_LANGUAGE:
      drupal_init_language();
      break;

999
1000
    case DRUPAL_BOOTSTRAP_PATH:
      require_once './includes/path.inc';
For faster browsing, not all history is shown. View entire blame