bootstrap.inc 25 KB
Newer Older
Dries's avatar
   
Dries committed
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
define('CACHE_PERMANENT', 0);
define('CACHE_TEMPORARY', -1);
Dries's avatar
   
Dries committed
11

12
define('CACHE_DISABLED', 0);
13
define('CACHE_ENABLED', 1);
14

15
16
17
18
define('WATCHDOG_NOTICE', 0);
define('WATCHDOG_WARNING', 1);
define('WATCHDOG_ERROR', 2);

19
20
21
22
23
define('DRUPAL_BOOTSTRAP_DATABASE', 0);
define('DRUPAL_BOOTSTRAP_SESSION', 1);
define('DRUPAL_BOOTSTRAP_PAGE_CACHE', 2);
define('DRUPAL_BOOTSTRAP_FULL', 3);

Dries's avatar
   
Dries committed
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Start the timer with the specified name.  If you start and stop
 * 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;
Steven Wittens's avatar
Steven Wittens committed
37
  $timers[$name]['count'] = isset($timers[$name]['count']) ? $timers[$name]['count']++ : 1;
Dries's avatar
   
Dries committed
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
}

/**
 * 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;

  list($usec, $sec) = explode(' ', microtime());
  $stop = (float)$usec + (float)$sec;
  $diff = round(($stop - $timers[$name]['start']) * 1000, 2);

  return $timers[$name]['time'] + $diff;
}

/**
 * Stop the timer with the specified name.
 *
 * @param name
 *   The name of the timer.
 * @return
 *   A timer array.  The array contains the number of times the
 *   timer has been started and stopped (count) and the accumulated
 *   timer value in ms (time).
 */
function timer_stop($name) {
  global $timers;

  list($usec, $sec) = explode(' ', microtime());
  $stop = (float)$usec + (float)$sec;
  $diff = round(($stop - $timers[$name]['start']) * 1000, 2);

  $timers[$name]['time'] += $diff;

  unset($timers[$name]['start']);

  return $timers[$name];
}
81

Dries's avatar
   
Dries committed
82
83
84
/**
 * Locate the appropriate configuration file.
 *
Dries's avatar
Dries committed
85
86
 * Try finding a matching configuration directory by stripping the
 * website's hostname from left to right and pathname from right to
Dries's avatar
   
Dries committed
87
88
89
 * left.  The first configuration file found will be used, the
 * remaining will ignored.  If no configuration file is found,
 * return a default value '$confdir/default'.
Dries's avatar
Dries committed
90
 *
91
 * Example for a fictitious site installed at
Dries's avatar
   
Dries committed
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
 * http://www.drupal.org/mysite/test/ the 'settings.php' is
 * searched in the following directories:
 *
 *  1. $confdir/www.drupal.org.mysite.test
 *  2. $confdir/drupal.org.mysite.test
 *  3. $confdir/org.mysite.test
 *
 *  4. $confdir/www.drupal.org.mysite
 *  5. $confdir/drupal.org.mysite
 *  6. $confdir/org.mysite
 *
 *  7. $confdir/www.drupal.org
 *  8. $confdir/drupal.org
 *  9. $confdir/org
 *
 * 10. $confdir/default
Dries's avatar
   
Dries committed
108
109
 */
function conf_init() {
Dries's avatar
Dries committed
110
  static $conf = '';
Dries's avatar
   
Dries committed
111

Dries's avatar
Dries committed
112
113
114
  if ($conf) {
    return $conf;
  }
Dries's avatar
   
Dries committed
115

Dries's avatar
   
Dries committed
116
  $confdir = 'sites';
Dries's avatar
Dries committed
117
  $uri = explode('/', $_SERVER['PHP_SELF']);
118
  $server = explode('.', rtrim($_SERVER['HTTP_HOST'], '.'));
Dries's avatar
Dries committed
119
120
121
122
123
124
125
  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));
      if (file_exists("$confdir/$dir/settings.php")) {
        $conf = "$confdir/$dir";
        return $conf;
      }
Dries's avatar
   
Dries committed
126
127
    }
  }
Dries's avatar
Dries committed
128
129
  $conf = "$confdir/default";
  return $conf;
Dries's avatar
   
Dries committed
130
131
}

Dries's avatar
Dries committed
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
 * Returns and optionally sets the filename for a system item (module,
 * theme, etc.).  The filename, whether provided, cached, or retrieved
 * from the database, is only returned if the file exists.
 *
 * @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.
 */
function drupal_get_filename($type, $name, $filename = NULL) {
  static $files = array();

  if (!$files[$type]) {
    $files[$type] = array();
  }

  if ($filename && file_exists($filename)) {
    $files[$type][$name] = $filename;
  }
  elseif ($files[$type][$name]) {
    // nothing
  }
  elseif (($file = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s' AND type = '%s'", $name, $type))) && file_exists($file)) {
    $files[$type][$name] = $file;
  }
  else {
    $config = conf_init();
    $dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s");
Dries's avatar
   
Dries committed
167
    $file = (($type == 'theme_engine') ? "$name.engine" : "$name.$type");
Dries's avatar
Dries committed
168
169
170
171
172
173
174
175
176
177
178
179

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

  return $files[$type][$name];
}

Dries's avatar
   
Dries committed
180
181
182
183
184
185
186
/**
 * 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
187
function variable_init($conf = array()) {
Dries's avatar
   
Dries committed
188
189
190
191
192
193
194
195
  // NOTE: caching the variables improves performance with 20% when serving cached pages.
  if ($cached = cache_get('variables')) {
    $variables = unserialize($cached->data);
  }
  else {
    $result = db_query('SELECT * FROM {variable}');
    while ($variable = db_fetch_object($result)) {
      $variables[$variable->name] = unserialize($variable->value);
Dries's avatar
   
Dries committed
196
    }
Dries's avatar
   
Dries committed
197
198
199
200
201
    cache_set('variables', serialize($variables));
  }

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

Dries's avatar
   
Dries committed
204
  return $variables;
Dries's avatar
   
Dries committed
205
206
}

Dries's avatar
   
Dries committed
207
208
209
210
211
212
213
214
215
216
/**
 * 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
217
218
219
220
221
222
function variable_get($name, $default) {
  global $conf;

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

Dries's avatar
   
Dries committed
223
224
225
226
227
228
229
230
231
/**
 * 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
232
233
234
function variable_set($name, $value) {
  global $conf;

235
  db_lock_table('variable');
Dries's avatar
   
Dries committed
236
237
  db_query("DELETE FROM {variable} WHERE name = '%s'", $name);
  db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, serialize($value));
238
  db_unlock_tables();
Dries's avatar
   
Dries committed
239

Dries's avatar
   
Dries committed
240
  cache_clear_all('variables');
Dries's avatar
   
Dries committed
241
242
243
244

  $conf[$name] = $value;
}

Dries's avatar
   
Dries committed
245
246
247
248
249
250
/**
 * Unset a persistent variable.
 *
 * @param $name
 *   The name of the variable to undefine.
 */
Dries's avatar
   
Dries committed
251
252
253
254
function variable_del($name) {
  global $conf;

  db_query("DELETE FROM {variable} WHERE name = '%s'", $name);
Dries's avatar
   
Dries committed
255
  cache_clear_all('variables');
Dries's avatar
   
Dries committed
256
257
258
259

  unset($conf[$name]);
}

Dries's avatar
   
Dries committed
260
261
262
263
264
265
/**
 * Return data from the persistent cache.
 *
 * @param $key
 *   The cache ID of the data to retrieve.
 */
Dries's avatar
   
Dries committed
266
function cache_get($key) {
267
268
  global $user;

269
  // Garbage collection necessary when enforcing a minimum cache lifetime
270
  $cache_flush = variable_get('cache_flush', 0);
271
  if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= time())) {
272
273
274
275
276
277
    // Time to flush old cache data
    db_query("DELETE FROM {cache} WHERE expire != %d AND expire <= %d", CACHE_PERMANENT, $cache_flush);
    variable_set('cache_flush', 0);
  }

  $cache = db_fetch_object(db_query("SELECT data, created, headers, expire FROM {cache} WHERE cid = '%s'", $key));
Dries's avatar
   
Dries committed
278
  if (isset($cache->data)) {
279
280
281
    // If the data is permanent or we're not enforcing a minimum cache lifetime
    // always return the cached data.
    if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) {
282
283
      $cache->data = db_decode_blob($cache->data);
    }
284
285
286
287
288
    // If enforcing a minimum cache lifetime, validate that the data is
    // currenly valid for this user before we return it by making sure the
    // cache entry was created before the timestamp in the current session's
    // cache timer.  The cache variable is loaded into the $user object by
    // sess_read() in session.inc.
289
290
291
292
293
294
295
296
297
    else {
      if ($user->cache > $cache->created) {
        // This cache data is too old and thus not valid for us, ignore it.
        return 0;
      }
      else {
        $cache->data = db_decode_blob($cache->data);
      }
    }
Dries's avatar
   
Dries committed
298
299
300
    return $cache;
  }
  return 0;
Dries's avatar
   
Dries committed
301
302
}

Dries's avatar
   
Dries committed
303
304
305
306
307
308
309
310
/**
 * Store data in the persistent cache.
 *
 * @param $cid
 *   The cache ID of the data to store.
 * @param $data
 *   The data to store in the cache. Complex data types must be serialized first.
 * @param $expire
311
312
313
314
315
316
317
 *   One of the following values:
 *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
 *     explicitly told to using cache_clear_all() with a cache ID.
 *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
 *     general cache wipe.
 *   - A Unix timestamp: Indicates that the item should be kept at least until
 *     the given time, after which it behaves like CACHE_TEMPORARY.
Dries's avatar
   
Dries committed
318
319
320
 * @param $headers
 *   A string containing HTTP header information for cached pages.
 */
321
function cache_set($cid, $data, $expire = CACHE_PERMANENT, $headers = NULL) {
Dries's avatar
   
Dries committed
322
323
  $data = db_encode_blob($data);

324
  db_lock_table('cache');
325
  db_query("UPDATE {cache} SET data = '%s', created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $data, time(), $expire, $headers, $cid);
Dries's avatar
   
Dries committed
326
  if (!db_affected_rows()) {
Dries's avatar
   
Dries committed
327
    @db_query("INSERT INTO {cache} (cid, data, created, expire, headers) VALUES ('%s', '%s', %d, %d, '%s')", $cid, $data, time(), $expire, $headers);
328
  }
329
  db_unlock_tables();
Dries's avatar
   
Dries committed
330
331
}

Dries's avatar
   
Dries committed
332
333
334
335
/**
 * Expire data from the cache.
 *
 * @param $cid
336
337
 *   If set, the cache ID to delete. Otherwise, all cache entries that can
 *   expire are deleted.
338
339
340
341
 *
 * @param $wildcard
 *   If set to true, the $cid is treated as a substring to match rather than a
 *   complete ID.
Dries's avatar
   
Dries committed
342
 */
343
function cache_clear_all($cid = NULL, $wildcard = false) {
344
345
  global $user;

Dries's avatar
   
Dries committed
346
  if (empty($cid)) {
347
348
349
350
351
    if (variable_get('cache_lifetime', 0)) {
      // We store the time in the current user's $user->cache variable which
      // will be saved into the sessions table by sess_write().  We then
      // simulate that the cache was flushed for this user by not returning
      // cached data that was cached before the timestamp.
352
      $user->cache = time();
353
354
355
356

      $cache_flush = variable_get('cache_flush', 0);
      if ($cache_flush == 0) {
        // This is the first request to clear the cache, start a timer.
357
358
        variable_set('cache_flush', time());
      }
359
360
361
362
363
364
365
366
367
368
      else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) {
        // Clear the cache for everyone, cache_flush_delay seconds have
        // passed since the first request to clear the cache.
        db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time());
        variable_set('cache_flush', 0);
      }
    }
    else {
      // No minimum cache lifetime, flush all temporary cache entries now.
      db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time());
369
    }
Dries's avatar
   
Dries committed
370
371
  }
  else {
372
373
374
375
376
377
    if ($wildcard) {
      db_query("DELETE FROM {cache} WHERE cid LIKE '%%%s%%'", $cid);
    }
    else {
      db_query("DELETE FROM {cache} WHERE cid = '%s'", $cid);
    }
Dries's avatar
   
Dries committed
378
379
380
  }
}

Dries's avatar
   
Dries committed
381
382
383
/**
 * Store the current page in the cache.
 */
Dries's avatar
   
Dries committed
384
function page_set_cache() {
Dries's avatar
   
Dries committed
385
  global $user, $base_url;
Dries's avatar
   
Dries committed
386

387
388
  if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET') {
    // This will fail in some cases, see page_get_cache() for the explanation.
Dries's avatar
   
Dries committed
389
    if ($data = ob_get_contents()) {
Dries's avatar
   
Dries committed
390
391
392
393
394
395
396
397
      if (function_exists('gzencode')) {
        if (version_compare(phpversion(), '4.2', '>=')) {
          $data = gzencode($data, 9, FORCE_GZIP);
        }
        else {
          $data = gzencode($data, FORCE_GZIP);
        }
      }
398
      ob_end_flush();
399
      cache_set($base_url . request_uri(), $data, CACHE_TEMPORARY, drupal_get_headers());
Dries's avatar
   
Dries committed
400
401
402
403
    }
  }
}

Dries's avatar
   
Dries committed
404
405
406
407
408
409
410
411
/**
 * Retrieve the current page from the cache.
 *
 * Note, we do not serve cached pages when status messages are waiting (from
 * a redirected form submission which was completed).
 * Because the output handler is not activated, the resulting page will not
 * get cached either.
 */
Dries's avatar
   
Dries committed
412
function page_get_cache() {
Dries's avatar
   
Dries committed
413
  global $user, $base_url;
Dries's avatar
   
Dries committed
414
415

  $cache = NULL;
416

417
  if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0) {
Dries's avatar
   
Dries committed
418
    $cache = cache_get($base_url . request_uri());
Dries's avatar
   
Dries committed
419
420
421
422
423
424
425
426
427

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

  return $cache;
}

Dries's avatar
Dries committed
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
/**
 * Call all init or exit hooks without including all modules.
 *
 * @param $op
 *   The name of the bootstrap hook we wish to invoke.
 */
function bootstrap_invoke_all($op) {
  foreach (module_list(FALSE, TRUE) as $module) {
    drupal_load('module', $module);
    module_invoke($module, $op);
 }
}

/**
 * Includes a file with the provided type and name.  This prevents
 * 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();

456
  if (isset($files[$type][$name])) {
Dries's avatar
Dries committed
457
458
459
460
461
462
    return TRUE;
  }

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

  if ($filename) {
463
    include_once "./$filename";
Dries's avatar
Dries committed
464
465
466
467
468
469
470
471
472
    $files[$type][$name] = TRUE;

    return TRUE;
  }

  return FALSE;
}

/**
473
474
475
476
477
478
 * Given an alias, return its Drupal system URL if one exists. Given a Drupal
 * system URL return its alias if one exists.
 *
 * @param $action
 *   One of the following values:
 *   - wipe: delete the alias cache.
479
480
 *   - alias: return an alias for a given Drupal system path (if one exists).
 *   - source: return the Drupal system URL for a path alias (if one exists).
481
482
 * @param $path
 *   The path to investigate for corresponding aliases or system URLs.
Dries's avatar
Dries committed
483
 */
484
485
486
function drupal_lookup_path($action, $path = '') {
  static $map = array();
  static $count = NULL;
Dries's avatar
Dries committed
487

488

489
490
  if ($count === NULL) {
    $count = db_result(db_query('SELECT COUNT(pid) FROM {url_alias}'));
Dries's avatar
Dries committed
491
492
  }

493
494
495
496
  if ($action == 'wipe') {
    $map = array();
  }
  elseif ($count > 0 && $path != '') {
497
    if ($action == 'alias') {
498
499
500
501
502
503
504
505
506
507
508
      if (isset($map[$path])) {
        return $map[$path];
      }
      if ($alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s'", $path))) {
        $map[$path] = $alias;
        return $alias;
      }
      else {
        $map[$path] = $path;
      }
    }
509
    elseif ($action == 'source') {
510
511
512
513
514
515
516
517
518
519
520
521
      if ($alias = array_search($path, $map)) {
        return $alias;
      }
      if (!isset($map[$path])) {
        if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s'", $path))) {
          $map[$src] = $path;
          return $src;
        }
        else {
          $map[$path] = $path;
        }
      }
Dries's avatar
Dries committed
522
523
524
    }
  }

525
  return FALSE;
Dries's avatar
Dries committed
526
527
528
529
530
531
}

/**
 * Given an internal Drupal path, return the alias set by the administrator.
 */
function drupal_get_path_alias($path) {
532
533
534
  $result = $path;
  if ($alias = drupal_lookup_path('alias', $path)) {
    $result = $alias;
Dries's avatar
Dries committed
535
  }
536
537
  if (function_exists('custom_url_rewrite')) {
    $result = custom_url_rewrite('alias', $result, $path);
Dries's avatar
Dries committed
538
  }
539
  return $result;
Dries's avatar
Dries committed
540
541
542
543
544
545
546
547
548
549
550
}

/**
 * Get the title of the current page, for display on the page and in the title bar.
 */
function drupal_get_title() {
  $title = drupal_set_title();

  if (!isset($title)) {
    // during a bootstrap, menu.inc is not included and thus we cannot provide a title
    if (function_exists('menu_get_active_title')) {
551
      $title = check_plain(menu_get_active_title());
Dries's avatar
Dries committed
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
    }
  }

  return $title;
}

/**
 * Set the title of the current page, for display on the page and in the title bar.
 */
function drupal_set_title($title = NULL) {
  static $stored_title;

  if (isset($title)) {
    $stored_title = $title;
  }
  return $stored_title;
}

Dries's avatar
   
Dries committed
570
571
572
/**
 * Set HTTP headers in preparation for a page response.
 */
Dries's avatar
   
Dries committed
573
function drupal_page_header() {
574
  if (variable_get('cache', 0)) {
Dries's avatar
   
Dries committed
575
    if ($cache = page_get_cache()) {
576
      bootstrap_invoke_all('init');
Dries's avatar
   
Dries committed
577
      // Set default values:
578
      $date = gmdate('D, d M Y H:i:s', $cache->created) .' GMT';
Dries's avatar
   
Dries committed
579
580
581
      $etag = '"'. md5($date) .'"';

      // Check http headers:
582
583
      $modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] == $date : NULL;
      if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && ($timestamp = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) != -1) {
Dries's avatar
Dries committed
584
585
586
587
588
        $modified_since = $cache->created <= $timestamp;
      }
      else {
        $modified_since = NULL;
      }
589
      $none_match = !empty($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] == $etag : NULL;
Dries's avatar
   
Dries committed
590
591
592

      // The type checking here is very important, be careful when changing entries.
      if (($modified_since !== NULL || $none_match !== NULL) && $modified_since !== false && $none_match !== false) {
593
        header('HTTP/1.0 304 Not Modified');
Dries's avatar
   
Dries committed
594
595
596
597
598
599
        exit();
      }

      // Send appropriate response:
      header("Last-Modified: $date");
      header("ETag: $etag");
Dries's avatar
   
Dries committed
600

Dries's avatar
   
Dries committed
601
      // Determine if the browser accepts gzipped data.
Dries's avatar
   
Dries committed
602
      if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === false && function_exists('gzencode')) {
Dries's avatar
   
Dries committed
603
        // Strip the gzip header and run uncompress.
Dries's avatar
   
Dries committed
604
605
606
607
608
609
        $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
      }
      elseif (function_exists('gzencode')) {
        header('Content-Encoding: gzip');
      }

Dries's avatar
   
Dries committed
610
611
612
      // Send the original request's headers.  We send them one after
      // another so PHP's header() function can deal with duplicate
      // headers.
613
      $headers = explode("\n", $cache->headers);
Dries's avatar
   
Dries committed
614
615
616
617
      foreach ($headers as $header) {
        header($header);
      }

Dries's avatar
   
Dries committed
618
      print $cache->data;
Dries's avatar
Dries committed
619
      bootstrap_invoke_all('exit');
Dries's avatar
   
Dries committed
620
621
      exit();
    }
622
623
624
625
626
627
628
    else {
      header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
      header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
      header("Cache-Control: no-store, no-cache, must-revalidate");
      header("Cache-Control: post-check=0, pre-check=0", false);
      header("Pragma: no-cache");
    }
Dries's avatar
   
Dries committed
629
630
631
  }
}

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

Dries's avatar
   
Dries committed
639
640
641
642
643
644
645
646
/**
 * 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
647
648
649
650
651
652
653
654
655
656
657
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
658
659
660
/**
 * Return the URI of the referring page.
 */
Dries's avatar
   
Dries committed
661
function referer_uri() {
662
  if (isset($_SERVER['HTTP_REFERER'])) {
663
    return $_SERVER['HTTP_REFERER'];
Dries's avatar
   
Dries committed
664
665
666
  }
}

Dries's avatar
   
Dries committed
667
668
669
670
671
672
673
674
675
676
677
/**
 * Return a component of the current Drupal path.
 *
 * When viewing a page at the path "admin/node/configure", for example, arg(0)
 * would return "admin", arg(1) would return "node", and arg(2) would return
 * "configure".
 *
 * Avoid use of this function where possible, as resulting code is hard to read.
 * Instead, attempt to use named arguments in menu callback functions. See the
 * explanation in menu.inc for how to construct callbacks that take arguments.
 */
Dries's avatar
   
Dries committed
678
function arg($index) {
Dries's avatar
   
Dries committed
679
  static $arguments, $q;
Dries's avatar
   
Dries committed
680

681
682
  if (empty($arguments) || $q != $_GET['q']) {
    $arguments = explode('/', $_GET['q']);
683
    $q = $_GET['q'];
Dries's avatar
   
Dries committed
684
685
  }

686
  if (isset($arguments[$index])) {
Dries's avatar
   
Dries committed
687
688
    return $arguments[$index];
  }
Dries's avatar
   
Dries committed
689
690
}

Dries's avatar
   
Dries committed
691
/**
692
 * Prepare a URL for use in an HTML attribute.
Dries's avatar
   
Dries committed
693
 *
694
 * We replace ( and ) with their url-encoded equivalents to prevent XSS attacks.
Dries's avatar
   
Dries committed
695
 */
Dries's avatar
   
Dries committed
696
697
698
function check_url($uri) {
  $uri = htmlspecialchars($uri, ENT_QUOTES);

699
  $uri = strtr($uri, array('(' => '%28', ')' => '%29'));
Dries's avatar
   
Dries committed
700
701
702
703

  return $uri;
}

Dries's avatar
   
Dries committed
704
705
706
707
/**
 * Since request_uri() is only available on Apache, we generate an
 * equivalent using other environment vars.
 */
Dries's avatar
   
Dries committed
708
709
function request_uri() {

710
711
  if (isset($_SERVER['REQUEST_URI'])) {
    $uri = $_SERVER['REQUEST_URI'];
Dries's avatar
   
Dries committed
712
713
  }
  else {
714
715
716
717
718
719
    if (isset($_SERVER['argv'])) {
      $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['argv'][0];
    }
    else {
      $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
    }
Dries's avatar
   
Dries committed
720
  }
721

722
  return $uri;
Dries's avatar
   
Dries committed
723
}
Dries's avatar
Dries committed
724

Dries's avatar
   
Dries committed
725
726
727
728
729
730
731
/**
 * Log a system message.
 *
 * @param $type
 *   The category to which this message belongs.
 * @param $message
 *   The message to store in the log.
732
733
734
735
736
 * @param $severity
 *   The severity of the message. One of the following values:
 *   - WATCHDOG_NOTICE
 *   - WATCHDOG_WARNING
 *   - WATCHDOG_ERROR
Dries's avatar
   
Dries committed
737
738
739
 * @param $link
 *   A link to associate with the message.
 */
740
function watchdog($type, $message, $severity = WATCHDOG_NOTICE, $link = NULL) {
Dries's avatar
   
Dries committed
741
  global $user;
742
  db_query("INSERT INTO {watchdog} (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (%d, '%s', '%s', %d, '%s', '%s', '%s', '%s', %d)", $user->uid, $type, $message, $severity, $link, request_uri(), referer_uri(), $_SERVER['REMOTE_ADDR'], time());
Dries's avatar
   
Dries committed
743
744
}

Dries's avatar
   
Dries committed
745
/**
746
 * Set a message which reflects the status of the performed operation.
Dries's avatar
   
Dries committed
747
 *
748
749
 * If the function is called with no arguments, this function returns all set
 * messages without clearing them.
Dries's avatar
   
Dries committed
750
 *
751
752
753
754
755
756
757
 * @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'
 *   - 'error'
Dries's avatar
   
Dries committed
758
759
 */
function drupal_set_message($message = NULL, $type = 'status') {
760
  if (isset($message)) {
Dries's avatar
   
Dries committed
761
762
763
764
765
766
767
768
769
    if (!isset($_SESSION['messages'])) {
      $_SESSION['messages'] = array();
    }

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

    $_SESSION['messages'][$type][] = $message;
770
771
772
773
774
  }

  return $_SESSION['messages'];
}

Dries's avatar
   
Dries committed
775
776
777
778
779
/**
 * Return all messages that have been set.
 *
 * As a side effect, this function clears the message queue.
 */
780
781
782
783
784
785
786
function drupal_get_messages() {
  $messages = drupal_set_message();
  $_SESSION['messages'] = array();

  return $messages;
}

Dries's avatar
   
Dries committed
787
788
789
/**
 * Perform an access check for a given mask and rule type. Rules are usually created via admin/access/rules page.
 */
790
function drupal_is_denied($type, $mask) {
Dries's avatar
   
Dries committed
791
792
793
794
795
796
  $allow = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 1 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
  $deny = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 0 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));

  return $deny && !$allow;
}

797
798
799
800
801
/**
 * 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
 * phases too. The most important usage is that if you want to access
 * Drupal database from a script without loading anything else, you can
802
 * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
803
804
 *
 * @param $phase
805
806
807
808
809
810
811
 *   A constant. Allowed values are:
 *     DRUPAL_BOOTSTRAP_DATABASE: initialize database layer.
 *     DRUPAL_BOOTSTRAP_SESSION: initialize session handling.
 *     DRUPAL_BOOTSTRAP_PAGE_CACHE: load bootstrap.inc and module.inc, start
 *       the variable system and try to serve a page from the cache.
 *     DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input
 *       data.
812
813
 */
function drupal_bootstrap($phase) {
814
  static $phases = array(DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_PAGE_CACHE, DRUPAL_BOOTSTRAP_FULL);
815

816
  while (!is_null($current_phase = array_shift($phases))) {
817
818
819
820
821
822
    _drupal_bootstrap($current_phase);
    if ($phase == $current_phase) {
      return;
    }
  }
}
Dries's avatar
   
Dries committed
823

824
825
function _drupal_bootstrap($phase) {
  global $conf;
Dries's avatar
   
Dries committed
826

827
  switch ($phase) {
828
    case DRUPAL_BOOTSTRAP_DATABASE:
829
830
      global $db_url, $db_prefix, $base_url;
      $conf = array();
831
832
833
834
835
      require_once conf_init() .'/settings.php';
      require_once './includes/database.inc';
      // Initialize the default database.
      db_set_active();
      break;
836
837

    case DRUPAL_BOOTSTRAP_SESSION:
838
839
840
841
      require_once './includes/session.inc';
      session_set_save_handler("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");
      session_start();
      break;
842
843

    case DRUPAL_BOOTSTRAP_PAGE_CACHE:
844
845
846
847
848
849
850
851
852
853
      require_once './includes/module.inc';
      // Start a page timer:
      timer_start('page');

      // deny access to hosts which were banned. t() is not yet available.
      if (drupal_is_denied('host', $_SERVER['REMOTE_ADDR'])) {
        header('HTTP/1.0 403 Forbidden');
        print "Sorry, ". $_SERVER['REMOTE_ADDR']. " has been banned.";
        exit();
      }
Dries's avatar
   
Dries committed
854

855
856
857
858
      // Initialize configuration variables, using values from conf.php if available.
      $conf = variable_init(isset($conf) ? $conf : array());
      drupal_page_header();
      break;
859
860

    case DRUPAL_BOOTSTRAP_FULL:
861
862
863
864
      require_once './includes/common.inc';
      _drupal_bootstrap_full();
      break;
  }
Dries's avatar
   
Dries committed
865
866
}

867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
/**
 * Enables use of the theme system without requiring database access. Since
 * there is not database access no theme will be enabled and the default
 * themable fuctions will be called. Some themable functions can not be used
 * without the full Drupal API loaded. For example, theme_page() is
 * unavailable and theme_maintenance_page() must be used in its place.
 */
function drupal_maintenance_theme() {
  global $theme;
  require_once './includes/theme.inc';
  require_once './includes/common.inc';
  require_once './includes/unicode.inc';
  unicode_check();
  $theme = '';
}