bootstrap.inc 30 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
Dries's avatar
   
Dries committed
220
 */
221
function conf_path() {
Dries's avatar
Dries committed
222
  static $conf = '';
Dries's avatar
 
Dries committed
223

Dries's avatar
Dries committed
224
225
226
  if ($conf) {
    return $conf;
  }
Dries's avatar
 
Dries committed
227

Dries's avatar
   
Dries committed
228
  $confdir = 'sites';
229
  $uri = explode('/', $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_FILENAME']);
230
  $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
Dries's avatar
Dries committed
231
232
233
234
235
236
237
  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
238
239
    }
  }
Dries's avatar
Dries committed
240
241
  $conf = "$confdir/default";
  return $conf;
Dries's avatar
 
Dries committed
242
243
}

244
245
246
247
248
/**
 * Unsets all disallowed global variables. See $allowed for what's allowed.
 */
function drupal_unset_globals() {
  if (ini_get('register_globals')) {
249
    $allowed = array('_ENV' => 1, '_GET' => 1, '_POST' => 1, '_COOKIE' => 1, '_FILES' => 1, '_SERVER' => 1, '_REQUEST' => 1, 'access_check' => 1, 'GLOBALS' => 1);
250
    foreach ($GLOBALS as $key => $value) {
251
252
253
254
255
256
257
      if (!isset($allowed[$key])) {
        unset($GLOBALS[$key]);
      }
    }
  }
}

258
259
260
261
/**
 * Loads the configuration and sets the base URL correctly.
 */
function conf_init() {
262
263
  global $base_url, $base_path, $base_root;

Dries's avatar
Dries committed
264
265
266
267
  // Export the following settings.php variables to the global namespace
  global $db_url, $db_prefix, $conf, $installed_profile;
  $conf = array();

268
  include_once './'. conf_path() .'/settings.php';
269
270
271
272

  if (isset($base_url)) {
    // Parse fixed base URL from settings.php.
    $parts = parse_url($base_url);
273
274
275
    if (!isset($parts['path'])) {
      $parts['path'] = '';
    }
276
    $base_path = $parts['path'] .'/';
277
278
279
280
281
282
283
    // 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';
    $base_url = $base_root .= '://'. $_SERVER['HTTP_HOST'];
284
    if ($dir = trim(dirname($_SERVER['PHP_SELF']), '\,/')) {
285
286
287
288
289
290
291
292
293
294
      $base_path = "/$dir";
      $base_url .= $base_path;
      $base_path .= '/';
    }
    else {
      $base_path = '/';
    }
  }
}

Dries's avatar
Dries committed
295
296
/**
 * Returns and optionally sets the filename for a system item (module,
297
 * theme, etc.). The filename, whether provided, cached, or retrieved
Dries's avatar
Dries committed
298
299
 * from the database, is only returned if the file exists.
 *
Dries's avatar
Dries committed
300
301
302
303
304
305
306
307
308
309
310
311
 * 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
312
313
314
315
316
317
318
319
320
321
322
 * @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
323
function drupal_get_filename($type, $name, $filename = NULL) {
Dries's avatar
Dries committed
324
  static $files = array();
Dries's avatar
Dries committed
325
  global $active_db;
Dries's avatar
Dries committed
326

327
  if (!isset($files[$type])) {
Dries's avatar
Dries committed
328
329
330
    $files[$type] = array();
  }

331
  if (!empty($filename) && file_exists($filename)) {
Dries's avatar
Dries committed
332
333
    $files[$type][$name] = $filename;
  }
334
  elseif (isset($files[$type][$name])) {
Dries's avatar
Dries committed
335
336
    // nothing
  }
Dries's avatar
Dries committed
337
338
339
340
341
  // 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.
  elseif ($active_db && (($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
342
343
344
    $files[$type][$name] = $file;
  }
  else {
Dries's avatar
Dries committed
345
346
    // 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
347
    $config = conf_path();
Dries's avatar
Dries committed
348
    $dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s");
Dries's avatar
   
Dries committed
349
    $file = (($type == 'theme_engine') ? "$name.engine" : "$name.$type");
Dries's avatar
Dries committed
350
351
352
353
354
355
356
357
358

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

359
360
361
  if (isset($files[$type][$name])) {
    return $files[$type][$name];
  }
Dries's avatar
Dries committed
362
363
}

Dries's avatar
   
Dries committed
364
365
366
367
368
369
370
/**
 * 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
371
function variable_init($conf = array()) {
372
  // NOTE: caching the variables improves performance by 20% when serving cached pages.
373
  if ($cached = cache_get('variables', 'cache')) {
374
    $variables = $cached->data;
Dries's avatar
   
Dries committed
375
376
377
378
379
  }
  else {
    $result = db_query('SELECT * FROM {variable}');
    while ($variable = db_fetch_object($result)) {
      $variables[$variable->name] = unserialize($variable->value);
Dries's avatar
 
Dries committed
380
    }
381
    cache_set('variables', $variables);
Dries's avatar
   
Dries committed
382
383
384
385
  }

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

Dries's avatar
   
Dries committed
388
  return $variables;
Dries's avatar
 
Dries committed
389
390
}

Dries's avatar
   
Dries committed
391
392
393
394
395
396
397
398
399
400
/**
 * 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
401
402
403
404
405
406
function variable_get($name, $default) {
  global $conf;

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

Dries's avatar
   
Dries committed
407
408
409
410
411
412
413
414
415
/**
 * 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
416
417
418
function variable_set($name, $value) {
  global $conf;

419
  db_lock_table('variable');
Dries's avatar
 
Dries committed
420
421
  db_query("DELETE FROM {variable} WHERE name = '%s'", $name);
  db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, serialize($value));
422
  db_unlock_tables();
Dries's avatar
   
Dries committed
423

424
  cache_clear_all('variables', 'cache');
Dries's avatar
 
Dries committed
425
426
427
428

  $conf[$name] = $value;
}

Dries's avatar
   
Dries committed
429
430
431
432
433
434
/**
 * Unset a persistent variable.
 *
 * @param $name
 *   The name of the variable to undefine.
 */
Dries's avatar
 
Dries committed
435
436
437
438
function variable_del($name) {
  global $conf;

  db_query("DELETE FROM {variable} WHERE name = '%s'", $name);
439
  cache_clear_all('variables', 'cache');
Dries's avatar
 
Dries committed
440
441
442
443

  unset($conf[$name]);
}

444

Dries's avatar
   
Dries committed
445
446
447
/**
 * Retrieve the current page from the cache.
 *
448
 * Note: we do not serve cached pages when status messages are waiting (from
Dries's avatar
   
Dries committed
449
450
 * a redirected form submission which was completed).
 */
Dries's avatar
 
Dries committed
451
function page_get_cache() {
452
  global $user, $base_root;
Dries's avatar
 
Dries committed
453
454

  $cache = NULL;
455

456
  if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0) {
457
    $cache = cache_get($base_root . request_uri(), 'cache_page');
Dries's avatar
 
Dries committed
458
459
460
461
462
463
464
465
466

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

  return $cache;
}

Dries's avatar
Dries committed
467
468
469
/**
 * Call all init or exit hooks without including all modules.
 *
470
 * @param $hook
Dries's avatar
Dries committed
471
472
 *   The name of the bootstrap hook we wish to invoke.
 */
473
function bootstrap_invoke_all($hook) {
474
  foreach (module_list(TRUE, TRUE) as $module) {
Dries's avatar
Dries committed
475
    drupal_load('module', $module);
476
    module_invoke($module, $hook);
Dries's avatar
Dries committed
477
478
479
480
 }
}

/**
481
 * Includes a file with the provided type and name. This prevents
Dries's avatar
Dries committed
482
483
484
485
486
487
488
489
490
491
492
493
494
 * 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();

495
  if (isset($files[$type][$name])) {
Dries's avatar
Dries committed
496
497
498
499
500
501
    return TRUE;
  }

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

  if ($filename) {
502
    include_once "./$filename";
Dries's avatar
Dries committed
503
504
505
506
507
508
509
510
    $files[$type][$name] = TRUE;

    return TRUE;
  }

  return FALSE;
}

Dries's avatar
   
Dries committed
511
512
/**
 * Set HTTP headers in preparation for a page response.
513
 *
514
515
516
517
 * 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.
 *
518
 * @see page_set_cache
Dries's avatar
   
Dries committed
519
 */
Dries's avatar
 
Dries committed
520
function drupal_page_header() {
521
  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
522
  header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT");
523
  header("Cache-Control: store, no-cache, must-revalidate");
524
525
  header("Cache-Control: post-check=0, pre-check=0", FALSE);
}
Dries's avatar
   
Dries committed
526

527
528
529
530
531
532
533
534
535
536
537
538
/**
 * 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';
539
  $etag = '"'. md5($last_modified) .'"';
540
541
542
543
544
545
546
547
548
549
550
551
552

  // 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();
  }
553

554
555
556
  // Send appropriate response:
  header("Last-Modified: $last_modified");
  header("ETag: $etag");
557

558
559
560
  // The following headers force validation of cache:
  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
  header("Cache-Control: must-revalidate");
561

562
563
564
565
  // 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));
566
  }
567
568
  elseif (function_exists('gzencode')) {
    header('Content-Encoding: gzip');
Dries's avatar
 
Dries committed
569
  }
570
571
572
573
574
575
576
577
578
579

  // 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
580
581
}

Dries's avatar
   
Dries committed
582
583
584
/**
 * Define the critical hooks that force modules to always be loaded.
 */
Dries's avatar
 
Dries committed
585
function bootstrap_hooks() {
586
  return array('boot', 'exit');
Dries's avatar
 
Dries committed
587
588
}

Dries's avatar
   
Dries committed
589
590
591
592
593
594
595
596
/**
 * 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
597
598
599
600
601
602
603
604
605
606
607
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
608
609
610
/**
 * Return the URI of the referring page.
 */
Dries's avatar
 
Dries committed
611
function referer_uri() {
612
  if (isset($_SERVER['HTTP_REFERER'])) {
613
    return $_SERVER['HTTP_REFERER'];
Dries's avatar
 
Dries committed
614
615
616
  }
}

Dries's avatar
Dries committed
617
618
619
620
621
622
623
/**
 * Encode special characters in a plain-text string for display as HTML.
 */
function check_plain($text) {
  return htmlspecialchars($text, ENT_QUOTES);
}

Dries's avatar
   
Dries committed
624
/**
625
626
 * Since $_SERVER['REQUEST_URI'] is only available on Apache, we
 * generate an equivalent using other environment variables.
Dries's avatar
   
Dries committed
627
 */
Dries's avatar
 
Dries committed
628
function request_uri() {
629
630
631
632
633
634
635

  if (isset($_SERVER['REQUEST_URI'])) {
    $uri = $_SERVER['REQUEST_URI'];
  }
  else {
    if (isset($_SERVER['argv'])) {
      $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['argv'][0];
636
637
    }
    else {
638
      $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
639
    }
Dries's avatar
 
Dries committed
640
  }
641

642
  return $uri;
Dries's avatar
 
Dries committed
643
}
Dries's avatar
Dries committed
644

Dries's avatar
   
Dries committed
645
646
647
648
649
650
/**
 * Log a system message.
 *
 * @param $type
 *   The category to which this message belongs.
 * @param $message
651
652
 *   The message to store in the log. See t() for documentation
 *   on how $message and $variables interact. Keep $message
653
 *   translatable by not concatenating dynamic values into it!
654
655
656
657
 * @param $variables
 *   Array of variables to replace in the message on display or
 *   NULL if message is already translated or not possible to
 *   translate.
658
 * @param $severity
659
 *   The severity of the message, as per RFC 3164
Dries's avatar
   
Dries committed
660
661
 * @param $link
 *   A link to associate with the message.
662
663
 *
 * @see watchdog_severity_levels
Dries's avatar
   
Dries committed
664
 */
665
function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
666
  global $user, $base_root;
667

668
669
670
671
  // Prepare the fields to be logged
  $log_message = array(
    'type'        => $type,
    'message'     => $message,
672
    'variables'   => $variables,
673
674
675
676
677
678
679
680
681
682
683
684
    'severity'    => $severity,
    'link'        => $link,
    'user'        => $user,
    'request_uri' => $base_root . request_uri(),
    'referer'     => referer_uri(),
    'ip'          => $_SERVER['REMOTE_ADDR'],
    'timestamp'   => time(),
    );

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

Dries's avatar
   
Dries committed
688
/**
689
 * Set a message which reflects the status of the performed operation.
Dries's avatar
   
Dries committed
690
 *
691
692
 * If the function is called with no arguments, this function returns all set
 * messages without clearing them.
Dries's avatar
   
Dries committed
693
 *
694
695
696
697
698
699
700
 * @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
701
702
 */
function drupal_set_message($message = NULL, $type = 'status') {
703
  if ($message) {
Dries's avatar
   
Dries committed
704
705
706
707
708
709
710
711
712
    if (!isset($_SESSION['messages'])) {
      $_SESSION['messages'] = array();
    }

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

    $_SESSION['messages'][$type][] = $message;
713
714
  }

715
716
  // messages not set when DB connection fails
  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
717
718
}

Dries's avatar
   
Dries committed
719
720
721
/**
 * Return all messages that have been set.
 *
722
723
 * @param $type
 *   (optional) Only return messages of this type.
724
725
 * @param $clear_queue
 *   (optional) Set to FALSE if you do not want to clear the messages queue
726
727
728
729
730
 * @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
731
 */
732
function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
733
  if ($messages = drupal_set_message()) {
734
    if ($type) {
735
736
737
      if ($clear_queue) {
         unset($_SESSION['messages'][$type]);
      }
738
739
740
      if (isset($messages[$type])) {
        return array($type => $messages[$type]);
      }
741
742
    }
    else {
743
744
745
      if ($clear_queue) {
         unset($_SESSION['messages']);
      }
746
747
      return $messages;
    }
748
  }
749
  return array();
750
751
}

Dries's avatar
   
Dries committed
752
/**
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
 * Perform an access check for a given mask and rule type. Rules are usually
 * created via admin/user/rules page.
 *
 * If any allow rule matches, access is allowed. Otherwise, if any deny rule
 * matches, access is denied.  If no rule matches, access is allowed.
 *
 * @param $type string
 *   Type of access to check: Allowed values are:
 *     - 'host': host name or IP address
 *     - 'mail': e-mail address
 *     - 'user': username
 * @param $mask string
 *   String or mask to test: '_' matches any character, '%' matches any
 *   number of characters.
 * @return bool
 *   TRUE if access is denied, FALSE if access is allowed.
Dries's avatar
   
Dries committed
769
 */
770
function drupal_is_denied($type, $mask) {
771
772
773
774
775
776
777
778
779
780
781
782
  // Because this function is called for every page request, both cached
  // and non-cached pages, we tried to optimize it as much as possible.
  // We deny access if the only matching records in the {access} table have
  // status 0. If any have status 1, or if there are no matching records,
  // we allow access. So, select matching records in decreasing order of
  // 'status', returning NOT(status) for the first. If any have status 1,
  // they come first, and we return NOT(status) = 0 (allowed). Otherwise,
  // if we have some with status 0, we return 1 (denied). If no matching
  // records, we get no return from db_result, so we return (bool)NULL = 0
  // (allowed).
  // The use of ORDER BY / LIMIT is more efficient than "MAX(status) = 0"
  // in PostgreSQL <= 8.0.
783
  return (bool) db_result(db_query_range("SELECT CASE WHEN status=1 THEN 0 ELSE 1 END FROM {access} WHERE type = '%s' AND LOWER('%s') LIKE LOWER(mask) ORDER BY status DESC", $type, $mask, 0, 1));
Dries's avatar
   
Dries committed
784
785
}

786
/**
787
 * Generates a default anonymous $user object.
788
789
790
 *
 * @return Object - the user object.
 */
791
function drupal_anonymous_user($session = '') {
792
793
794
795
796
  $user = new stdClass();
  $user->uid = 0;
  $user->hostname = $_SERVER['REMOTE_ADDR'];
  $user->roles = array();
  $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
797
  $user->session = $session;
798
  $user->cache = 0;
799
800
801
  return $user;
}

802
803
804
/**
 * 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
805
 * phases too. The most important usage is that if you want to access the
806
 * Drupal database from a script without loading anything else, you can
807
 * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
808
809
 *
 * @param $phase
810
 *   A constant. Allowed values are:
811
812
 *     DRUPAL_BOOTSTRAP_CONFIGURATION: initialize configuration.
 *     DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: try to call a non-database cache fetch routine.
813
 *     DRUPAL_BOOTSTRAP_DATABASE: initialize database layer.
814
 *     DRUPAL_BOOTSTRAP_ACCESS: identify and reject banned hosts.
815
 *     DRUPAL_BOOTSTRAP_SESSION: initialize session handling.
816
 *     DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start
817
 *       the variable system and try to serve a page from the cache.
818
 *     DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page.
819
 *     DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request.
820
 *     DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data.
821
822
 */
function drupal_bootstrap($phase) {
823
  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);
824

825
  while (!is_null($current_phase = array_shift($phases))) {
826
827
828
829
830
831
    _drupal_bootstrap($current_phase);
    if ($phase == $current_phase) {
      return;
    }
  }
}
Dries's avatar
   
Dries committed
832

833
834
function _drupal_bootstrap($phase) {
  global $conf;
Dries's avatar
 
Dries committed
835

836
  switch ($phase) {
837

838
    case DRUPAL_BOOTSTRAP_CONFIGURATION:
839
      drupal_unset_globals();
840
841
      // Initialize the configuration
      conf_init();
842
      break;
843

844
    case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE:
845
      _drupal_cache_init($phase);
846
      break;
847

848
    case DRUPAL_BOOTSTRAP_DATABASE:
849
      // Initialize the default database.
850
      require_once './includes/database.inc';
851
852
      db_set_active();
      break;
853

854
855
856
    case DRUPAL_BOOTSTRAP_ACCESS:
      // Deny access to hosts which were banned - t() is not yet available.
      if (drupal_is_denied('host', $_SERVER['REMOTE_ADDR'])) {
857
        header('HTTP/1.1 403 Forbidden');
858
        print 'Sorry, '. $_SERVER['REMOTE_ADDR'] .' has been banned.';
859
860
861
862
        exit();
      }
      break;

863
    case DRUPAL_BOOTSTRAP_SESSION:
864
      require_once variable_get('session_inc', './includes/session.inc');
865
      session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
866
867
      session_start();
      break;
868

869
    case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
870
871
872
873
      // Initialize configuration variables, using values from settings.php if available.
      $conf = variable_init(isset($conf) ? $conf : array());

      _drupal_cache_init($phase);
874

875
876
877
      // Start a page timer:
      timer_start('page');

878
879
      drupal_page_header();
      break;
880

881
882
883
884
    case DRUPAL_BOOTSTRAP_LANGUAGE:
      drupal_init_language();
      break;

885
886
887
888
889
890
    case DRUPAL_BOOTSTRAP_PATH:
      require_once './includes/path.inc';
      // Initialize $_GET['q'] prior to loading modules and invoking hook_init().
      drupal_init_path();
      break;

891
    case DRUPAL_BOOTSTRAP_FULL:
892
893
894
895
      require_once './includes/common.inc';
      _drupal_bootstrap_full();
      break;
  }
Dries's avatar
   
Dries committed
896
897
}

898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
/**
 * Initialize the caching strategy, which loads at different stages within
 * Drupal's bootstrap process.
 */
function _drupal_cache_init($phase) {
  require_once variable_get('cache_inc', './includes/cache.inc');

  if ($phase == DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE && variable_get('page_cache_fastpath', 0)) {
    if (page_cache_fastpath()) {
      exit();
    }
  }
  elseif ($phase == DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE) {
    if ($cache = page_get_cache()) {
      if (variable_get('cache', CACHE_DISABLED) == CACHE_AGGRESSIVE) {
        drupal_page_cache_header($cache);
        exit();
      }
      elseif (variable_get('cache', CACHE_DISABLED) == CACHE_NORMAL) {
        require_once './includes/module.inc';
918
        bootstrap_invoke_all('boot');
919
920
921
922
923
924
925
926
927
        drupal_page_cache_header($cache);
        bootstrap_invoke_all('exit');
        exit();
      }
    }
    require_once './includes/module.inc';
  }
}

928
929
930
/**
 * Enables use of the theme system without requiring database access. Since
 * there is not database access no theme will be enabled and the default
931
 * themeable functions will be called. Some themeable functions can not be used
932
933
934
935
936
 * 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;
937
  require_once './includes/path.inc';
938
939
940
  require_once './includes/theme.inc';
  require_once './includes/common.inc';
  require_once './includes/unicode.inc';
941
  require_once './modules/filter/filter.module';
942
  unicode_check();
943
944
  drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'module');
  drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'module');
945
  $theme = '';
946
947
948
949
950

  // Special case registry of theme functions used by the installer
  $themes = drupal_common_themes();
  foreach ($themes as $hook => $info) {
    if (!isset($info['file']) && !isset($info['function'])) {
951
      $themes[$hook]['function'] = 'theme_'. $hook;
952
953
954
    }
  }
  _theme_set_registry($themes);
955
}
956
957
958
959
960
961
962
963
964
965
966
967

/**
 * Return the name of the localisation function. Use in code that needs to
 * run both during installation and normal operation.
 */
function get_t() {
  static $t;
  if (is_null($t)) {
    $t = function_exists('install_main') ? 'st' : 't';
  }
  return $t;
}
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000

/**
 *  Choose a language for the current page, based on site and user preferences.
 */
function drupal_init_language() {
  global $language, $user;

  // Ensure the language is correctly returned, even without multilanguage support.
  // Useful for eg. XML/HTML 'lang' attributes.
  if (variable_get('language_count', 1) == 1) {
    $language = language_default();
  }
  else {
    include_once './includes/language.inc';
    $language = language_initialize();
  }
}

/**
 * Get a list of languages set up indexed by the specified key
 *
 * @param $field The field to index the list with.
 * @param $reset Boolean to request a reset of the list.
 */
function language_list($field = 'language', $reset = FALSE) {
  static $languages = NULL;

  // Reset language list
  if ($reset) {
    $languages = NULL;
  }

  // Init language list