bootstrap.inc 26 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('.', implode('.', array_reverse(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
/**
 * 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();

151
  if (!isset($files[$type])) {
Dries's avatar
Dries committed
152
153
154
    $files[$type] = array();
  }

155
  if (!empty($filename) && file_exists($filename)) {
Dries's avatar
Dries committed
156
157
    $files[$type][$name] = $filename;
  }
158
  elseif (isset($files[$type][$name])) {
Dries's avatar
Dries committed
159
160
161
162
163
164
165
166
    // 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
/**
 * Store the current page in the cache.
383
384
385
386
387
388
389
390
391
392
 *
 * We try to store a gzipped version of the cache. This requires the
 * PHP zlib extension (http://php.net/manual/en/ref.zlib.php).
 * Presence of the extension is checked by testing for the function
 * gzencode. There are two compression algorithms: gzip and deflate.
 * The majority of all modern browsers support gzip or both of them.
 * We thus only deal with the gzip variant and unzip the cache in case
 * the browser does not accept gzip encoding.
 *
 * @see drupal_page_header
Dries's avatar
   
Dries committed
393
 */
Dries's avatar
 
Dries committed
394
function page_set_cache() {
Dries's avatar
   
Dries committed
395
  global $user, $base_url;
Dries's avatar
 
Dries committed
396

397
398
  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
399
    if ($data = ob_get_contents()) {
400
      $cache = TRUE;
Dries's avatar
   
Dries committed
401
      if (function_exists('gzencode')) {
402
403
404
405
        // We do not store the data in case the zlib mode is deflate.
        // This should be rarely happening.
        if (zlib_get_coding_type() == 'deflate') {
          $cache = FALSE;
Dries's avatar
   
Dries committed
406
        }
407
408
        else if (zlib_get_coding_type() == FALSE) {
          $data = gzencode($data, 9, FORCE_GZIP);
Dries's avatar
   
Dries committed
409
        }
410
411
        // The remaining case is 'gzip' which means the data is
        // already compressed and nothing left to do but to store it.
Dries's avatar
   
Dries committed
412
      }
413
      ob_end_flush();
414
415
416
      if ($cache && $data) {
        cache_set($base_url . request_uri(), $data, CACHE_TEMPORARY, drupal_get_headers());
      }
Dries's avatar
 
Dries committed
417
418
419
420
    }
  }
}

Dries's avatar
   
Dries committed
421
422
423
424
425
426
427
428
/**
 * 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
429
function page_get_cache() {
Dries's avatar
   
Dries committed
430
  global $user, $base_url;
Dries's avatar
 
Dries committed
431
432

  $cache = NULL;
433

434
  if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0) {
Dries's avatar
   
Dries committed
435
    $cache = cache_get($base_url . request_uri());
Dries's avatar
 
Dries committed
436
437
438
439
440
441
442
443
444

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

  return $cache;
}

Dries's avatar
Dries committed
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
/**
 * 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();

473
  if (isset($files[$type][$name])) {
Dries's avatar
Dries committed
474
475
476
477
478
479
    return TRUE;
  }

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

  if ($filename) {
480
    include_once "./$filename";
Dries's avatar
Dries committed
481
482
483
484
485
486
487
488
489
    $files[$type][$name] = TRUE;

    return TRUE;
  }

  return FALSE;
}

/**
490
491
492
493
494
495
 * 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.
496
497
 *   - 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).
498
499
 * @param $path
 *   The path to investigate for corresponding aliases or system URLs.
Dries's avatar
Dries committed
500
 */
501
502
503
function drupal_lookup_path($action, $path = '') {
  static $map = array();
  static $count = NULL;
Dries's avatar
Dries committed
504

505

506
507
  if ($count === NULL) {
    $count = db_result(db_query('SELECT COUNT(pid) FROM {url_alias}'));
Dries's avatar
Dries committed
508
509
  }

510
511
512
513
  if ($action == 'wipe') {
    $map = array();
  }
  elseif ($count > 0 && $path != '') {
514
    if ($action == 'alias') {
515
516
517
518
519
520
521
522
523
524
525
      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;
      }
    }
526
    elseif ($action == 'source') {
527
528
529
530
531
532
533
534
535
536
537
538
      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
539
540
541
    }
  }

542
  return FALSE;
Dries's avatar
Dries committed
543
544
545
546
547
548
}

/**
 * Given an internal Drupal path, return the alias set by the administrator.
 */
function drupal_get_path_alias($path) {
549
550
551
  $result = $path;
  if ($alias = drupal_lookup_path('alias', $path)) {
    $result = $alias;
Dries's avatar
Dries committed
552
  }
553
554
  if (function_exists('custom_url_rewrite')) {
    $result = custom_url_rewrite('alias', $result, $path);
Dries's avatar
Dries committed
555
  }
556
  return $result;
Dries's avatar
Dries committed
557
558
559
560
561
562
563
564
565
566
567
}

/**
 * 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')) {
568
      $title = check_plain(menu_get_active_title());
Dries's avatar
Dries committed
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
    }
  }

  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
587
588
/**
 * Set HTTP headers in preparation for a page response.
589
590
 *
 * @see page_set_cache
Dries's avatar
   
Dries committed
591
 */
Dries's avatar
 
Dries committed
592
function drupal_page_header() {
593
  if (variable_get('cache', 0)) {
Dries's avatar
 
Dries committed
594
    if ($cache = page_get_cache()) {
595
      bootstrap_invoke_all('init');
Dries's avatar
 
Dries committed
596
      // Set default values:
597
      $date = gmdate('D, d M Y H:i:s', $cache->created) .' GMT';
Dries's avatar
 
Dries committed
598
599
600
      $etag = '"'. md5($date) .'"';

      // Check http headers:
601
602
      $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
603
604
605
606
607
        $modified_since = $cache->created <= $timestamp;
      }
      else {
        $modified_since = NULL;
      }
608
      $none_match = !empty($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] == $etag : NULL;
Dries's avatar
 
Dries committed
609
610
611

      // 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) {
612
        header('HTTP/1.0 304 Not Modified');
Dries's avatar
 
Dries committed
613
614
615
616
617
618
        exit();
      }

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

Dries's avatar
   
Dries committed
620
      // Determine if the browser accepts gzipped data.
Dries's avatar
   
Dries committed
621
      if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === false && function_exists('gzencode')) {
Dries's avatar
   
Dries committed
622
        // Strip the gzip header and run uncompress.
Dries's avatar
   
Dries committed
623
624
625
626
627
628
        $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
      }
      elseif (function_exists('gzencode')) {
        header('Content-Encoding: gzip');
      }

Dries's avatar
   
Dries committed
629
630
631
      // Send the original request's headers.  We send them one after
      // another so PHP's header() function can deal with duplicate
      // headers.
632
      $headers = explode("\n", $cache->headers);
Dries's avatar
   
Dries committed
633
634
635
636
      foreach ($headers as $header) {
        header($header);
      }

Dries's avatar
   
Dries committed
637
      print $cache->data;
Dries's avatar
Dries committed
638
      bootstrap_invoke_all('exit');
Dries's avatar
 
Dries committed
639
640
      exit();
    }
641
642
643
644
645
646
647
    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
648
649
650
  }
}

Dries's avatar
   
Dries committed
651
652
653
/**
 * Define the critical hooks that force modules to always be loaded.
 */
Dries's avatar
 
Dries committed
654
655
656
657
function bootstrap_hooks() {
  return array('init', 'exit');
}

Dries's avatar
   
Dries committed
658
659
660
661
662
663
664
665
/**
 * 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
666
667
668
669
670
671
672
673
674
675
676
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
677
678
679
/**
 * Return the URI of the referring page.
 */
Dries's avatar
 
Dries committed
680
function referer_uri() {
681
  if (isset($_SERVER['HTTP_REFERER'])) {
682
    return $_SERVER['HTTP_REFERER'];
Dries's avatar
 
Dries committed
683
684
685
  }
}

Dries's avatar
   
Dries committed
686
687
688
689
690
691
692
693
694
695
696
/**
 * 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
697
function arg($index) {
Dries's avatar
   
Dries committed
698
  static $arguments, $q;
Dries's avatar
 
Dries committed
699

700
701
  if (empty($arguments) || $q != $_GET['q']) {
    $arguments = explode('/', $_GET['q']);
702
    $q = $_GET['q'];
Dries's avatar
 
Dries committed
703
704
  }

705
  if (isset($arguments[$index])) {
Dries's avatar
   
Dries committed
706
707
    return $arguments[$index];
  }
Dries's avatar
 
Dries committed
708
709
}

Dries's avatar
   
Dries committed
710
/**
711
 * Prepare a URL for use in an HTML attribute.
Dries's avatar
   
Dries committed
712
 *
713
 * We replace ( and ) with their url-encoded equivalents to prevent XSS attacks.
Dries's avatar
   
Dries committed
714
 */
Dries's avatar
 
Dries committed
715
716
717
function check_url($uri) {
  $uri = htmlspecialchars($uri, ENT_QUOTES);

718
  $uri = strtr($uri, array('(' => '%28', ')' => '%29'));
Dries's avatar
 
Dries committed
719
720
721
722

  return $uri;
}

Dries's avatar
   
Dries committed
723
724
/**
 * Since request_uri() is only available on Apache, we generate an
725
 * equivalent using other environment variables.
Dries's avatar
   
Dries committed
726
 */
Dries's avatar
 
Dries committed
727
728
function request_uri() {

729
730
  if (isset($_SERVER['REQUEST_URI'])) {
    $uri = $_SERVER['REQUEST_URI'];
Dries's avatar
 
Dries committed
731
732
  }
  else {
733
734
735
736
737
738
    if (isset($_SERVER['argv'])) {
      $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['argv'][0];
    }
    else {
      $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
    }
Dries's avatar
 
Dries committed
739
  }
740

741
  return $uri;
Dries's avatar
 
Dries committed
742
}
Dries's avatar
Dries committed
743

Dries's avatar
   
Dries committed
744
745
746
747
748
749
750
/**
 * Log a system message.
 *
 * @param $type
 *   The category to which this message belongs.
 * @param $message
 *   The message to store in the log.
751
752
753
754
755
 * @param $severity
 *   The severity of the message. One of the following values:
 *   - WATCHDOG_NOTICE
 *   - WATCHDOG_WARNING
 *   - WATCHDOG_ERROR
Dries's avatar
   
Dries committed
756
757
758
 * @param $link
 *   A link to associate with the message.
 */
759
function watchdog($type, $message, $severity = WATCHDOG_NOTICE, $link = NULL) {
Dries's avatar
   
Dries committed
760
  global $user;
761
  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
762
763
}

Dries's avatar
   
Dries committed
764
/**
765
 * Set a message which reflects the status of the performed operation.
Dries's avatar
   
Dries committed
766
 *
767
768
 * If the function is called with no arguments, this function returns all set
 * messages without clearing them.
Dries's avatar
   
Dries committed
769
 *
770
771
772
773
774
775
776
 * @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
777
778
 */
function drupal_set_message($message = NULL, $type = 'status') {
779
  if (isset($message)) {
Dries's avatar
   
Dries committed
780
781
782
783
784
785
786
787
788
    if (!isset($_SESSION['messages'])) {
      $_SESSION['messages'] = array();
    }

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

    $_SESSION['messages'][$type][] = $message;
789
790
  }

791
792
  // messages not set when DB connection fails
  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
793
794
}

Dries's avatar
   
Dries committed
795
796
797
798
799
/**
 * Return all messages that have been set.
 *
 * As a side effect, this function clears the message queue.
 */
800
801
802
803
804
805
806
function drupal_get_messages() {
  $messages = drupal_set_message();
  $_SESSION['messages'] = array();

  return $messages;
}

Dries's avatar
   
Dries committed
807
808
809
/**
 * Perform an access check for a given mask and rule type. Rules are usually created via admin/access/rules page.
 */
810
function drupal_is_denied($type, $mask) {
Dries's avatar
   
Dries committed
811
812
813
814
815
816
  $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;
}

817
818
819
820
821
/**
 * 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
822
 * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
823
824
 *
 * @param $phase
825
826
827
828
829
830
831
 *   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.
832
833
 */
function drupal_bootstrap($phase) {
834
  static $phases = array(DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_PAGE_CACHE, DRUPAL_BOOTSTRAP_FULL);
835

836
  while (!is_null($current_phase = array_shift($phases))) {
837
838
839
840
841
842
    _drupal_bootstrap($current_phase);
    if ($phase == $current_phase) {
      return;
    }
  }
}
Dries's avatar
   
Dries committed
843

844
845
function _drupal_bootstrap($phase) {
  global $conf;
Dries's avatar
 
Dries committed
846

847
  switch ($phase) {
848
    case DRUPAL_BOOTSTRAP_DATABASE:
849
850
      global $db_url, $db_prefix, $base_url;
      $conf = array();
851
852
853
854
855
      require_once conf_init() .'/settings.php';
      require_once './includes/database.inc';
      // Initialize the default database.
      db_set_active();
      break;
856
857

    case DRUPAL_BOOTSTRAP_SESSION:
858
859
860
861
      require_once './includes/session.inc';
      session_set_save_handler("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");
      session_start();
      break;
862
863

    case DRUPAL_BOOTSTRAP_PAGE_CACHE:
864
865
866
867
868
869
870
      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');
871
        print 'Sorry, '. $_SERVER['REMOTE_ADDR']. ' has been banned.';
872
873
        exit();
      }
Dries's avatar
   
Dries committed
874

875
876
877
878
      // Initialize configuration variables, using values from conf.php if available.
      $conf = variable_init(isset($conf) ? $conf : array());
      drupal_page_header();
      break;
879
880

    case DRUPAL_BOOTSTRAP_FULL:
881
882
883
884
      require_once './includes/common.inc';
      _drupal_bootstrap_full();
      break;
  }
Dries's avatar
   
Dries committed
885
886
}

887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
/**
 * 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 = '';
}