bootstrap.inc 46.9 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
 * Log message severity -- Emergency: system is unusable.
39
 *
40
41
 * @see watchdog()
 * @see watchdog_severity_levels()
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
define('WATCHDOG_EMERG', 0);

/**
 * Log message severity -- Alert: action must be taken immediately.
 *
 * @see watchdog()
 * @see watchdog_severity_levels()
 */
define('WATCHDOG_ALERT', 1);

/**
 * Log message severity -- Critical: critical conditions.
 *
 * @see watchdog()
 * @see watchdog_severity_levels()
 */
define('WATCHDOG_CRITICAL', 2);

/**
 * Log message severity -- Error: error conditions.
 *
 * @see watchdog()
 * @see watchdog_severity_levels()
 */
define('WATCHDOG_ERROR', 3);

/**
 * Log message severity -- Warning: warning conditions.
 *
 * @see watchdog()
 * @see watchdog_severity_levels()
 */
define('WATCHDOG_WARNING', 4);

/**
 * Log message severity -- Notice: normal but significant condition.
 *
 * @see watchdog()
 * @see watchdog_severity_levels()
 */
define('WATCHDOG_NOTICE', 5);

/**
 * Log message severity -- Informational: informational messages.
 *
 * @see watchdog()
 * @see watchdog_severity_levels()
 */
define('WATCHDOG_INFO', 6);

/**
 * Log message severity -- Debug: debug-level messages.
 *
 * @see watchdog()
 * @see watchdog_severity_levels()
 */
define('WATCHDOG_DEBUG', 7);
100

101
102
103
/**
 * First bootstrap phase: initialize configuration.
 */
104
define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0);
105
106
107
108
109

/**
 * Second bootstrap phase: try to call a non-database cache
 * fetch routine.
 */
110
define('DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE', 1);
111
112
113
114

/**
 * Third bootstrap phase: initialize database layer.
 */
115
define('DRUPAL_BOOTSTRAP_DATABASE', 2);
116
117
118
119

/**
 * Fourth bootstrap phase: identify and reject banned hosts.
 */
120
define('DRUPAL_BOOTSTRAP_ACCESS', 3);
121
122
123
124

/**
 * Fifth bootstrap phase: initialize session handling.
 */
125
define('DRUPAL_BOOTSTRAP_SESSION', 4);
126
127
128
129
130

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

/**
134
 * Seventh bootstrap phase: find out language of the page.
135
 */
136
137
138
139
140
141
define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);

/**
 * Eighth bootstrap phase: set $_GET['q'] to Drupal path of request.
 */
define('DRUPAL_BOOTSTRAP_PATH', 7);
142
143
144
145
146

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

149
150
151
/**
 * Role ID for anonymous users; should match what's in the "role" table.
 */
152
define('DRUPAL_ANONYMOUS_RID', 1);
153
154
155
156

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

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/**
 * 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);

183
184
185
/**
 * For convenience, define a short form of the request time global.
 */
186
define('REQUEST_TIME', $_SERVER['REQUEST_TIME']);
187

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/**
 * @name Title text filtering flags
 * @{
 * Flags for use in drupal_set_title().
 */

/**
 * Flag for drupal_set_title(); text is not sanitized, so run check_plain().
 */
define('CHECK_PLAIN', 0);

/**
 * Flag for drupal_set_title(); text has already been sanitized.
 */
define('PASS_THROUGH', -1);

/**
 * @} End of "Title text filtering flags".
 */


Dries's avatar
   
Dries committed
209
/**
210
 * Start the timer with the specified name. If you start and stop
Dries's avatar
   
Dries committed
211
212
213
214
215
216
217
218
219
 * the same timer multiple times, the measured intervals will be
 * accumulated.
 *
 * @param name
 *   The name of the timer.
 */
function timer_start($name) {
  global $timers;

220
  $timers[$name]['start'] = microtime(TRUE);
221
  $timers[$name]['count'] = isset($timers[$name]['count']) ? ++$timers[$name]['count'] : 1;
Dries's avatar
   
Dries committed
222
223
224
225
226
227
228
229
230
231
232
233
234
}

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

235
  if (isset($timers[$name]['start'])) {
236
    $stop = microtime(TRUE);
237
    $diff = round(($stop - $timers[$name]['start']) * 1000, 2);
Dries's avatar
   
Dries committed
238

239
240
241
242
    if (isset($timers[$name]['time'])) {
      $diff += $timers[$name]['time'];
    }
    return $diff;
243
  }
Dries's avatar
   
Dries committed
244
245
246
247
248
249
250
251
}

/**
 * Stop the timer with the specified name.
 *
 * @param name
 *   The name of the timer.
 * @return
252
 *   A timer array. The array contains the number of times the
Dries's avatar
   
Dries committed
253
254
255
256
257
258
 *   timer has been started and stopped (count) and the accumulated
 *   timer value in ms (time).
 */
function timer_stop($name) {
  global $timers;

259
  $timers[$name]['time'] = timer_read($name);
Dries's avatar
   
Dries committed
260
261
262
263
  unset($timers[$name]['start']);

  return $timers[$name];
}
264

Dries's avatar
   
Dries committed
265
/**
266
 * Find the appropriate configuration directory.
Dries's avatar
   
Dries committed
267
 *
268
269
 * Try finding a matching configuration directory by stripping the website's
 * hostname from left to right and pathname from right to left. The first
270
 * configuration file found will be used; the remaining will ignored. If no
271
 * configuration file is found, return a default value '$confdir/default'.
Dries's avatar
Dries committed
272
 *
273
 * Example for a fictitious site installed at
274
275
 * http://www.drupal.org:8080/mysite/test/ the 'settings.php' is searched in
 * the following directories:
Dries's avatar
   
Dries committed
276
 *
277
278
279
280
 *  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
281
 *
282
283
284
285
 *  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
286
 *
287
288
289
290
 *  9. $confdir/8080.www.drupal.org
 * 10. $confdir/www.drupal.org
 * 11. $confdir/drupal.org
 * 12. $confdir/org
Dries's avatar
   
Dries committed
291
 *
292
 * 13. $confdir/default
293
 *
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
 * If a file named sites.php is present in the $confdir, it will be loaded
 * prior to scanning for directories.  It should define an associative array
 * named $sites, which maps domains to directories.  It should be in the form
 * of:
 *
 * $sites = array(
 *   'The url to alias' => 'A directory within the sites directory'
 * );
 *
 * For example:
 *
 * $sites = array(
 *   'devexample.com' => 'example.com',
 *   'localhost/example' => 'example.com',
 * );
 *
 * The above array will cause Drupal to look for a directory named
 * "example.com" in the sites directory whenever a request comes from
 * "example.com", "devexample.com", or "localhost/example". That is useful
 * on development servers, where the domain name may not be the same as the
 * domain of the live server.  Since Drupal stores file paths into the database
 * (files, system table, etc.) this will ensure the paths are correct while
 * accessed on development servers.
 *
318
319
320
321
322
323
324
325
326
327
 * @param $require_settings
 *   Only configuration directories with an existing settings.php file
 *   will be recognized. Defaults to TRUE. During initial installation,
 *   this is set to FALSE so that Drupal can detect a matching directory,
 *   then create a new settings.php file in it.
 * @param reset
 *   Force a full search for matching directories even if one had been
 *   found previously.
 * @return
 *   The path of the matching directory.
Dries's avatar
   
Dries committed
328
 */
329
function conf_path($require_settings = TRUE, $reset = FALSE) {
Dries's avatar
Dries committed
330
  static $conf = '';
Dries's avatar
 
Dries committed
331

332
  if ($conf && !$reset) {
Dries's avatar
Dries committed
333
334
    return $conf;
  }
Dries's avatar
 
Dries committed
335

Dries's avatar
   
Dries committed
336
  $confdir = 'sites';
337
338
339
340
341
342
343

  $sites = array();
  if (file_exists(DRUPAL_ROOT . '/' . $confdir . '/sites.php')) {
    // This will overwrite $sites with the desired mappings.
    include(DRUPAL_ROOT . '/' . $confdir . '/sites.php');
  }

344
  $uri = explode('/', $_SERVER['SCRIPT_NAME'] ? $_SERVER['SCRIPT_NAME'] : $_SERVER['SCRIPT_FILENAME']);
345
  $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
Dries's avatar
Dries committed
346
347
348
  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));
349
350
351
352
      if (isset($sites[$dir]) && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $sites[$dir])) {
        $dir = $sites[$dir];
      }
      if (file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir . '/settings.php') || (!$require_settings && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir))) {
Dries's avatar
Dries committed
353
354
355
        $conf = "$confdir/$dir";
        return $conf;
      }
Dries's avatar
 
Dries committed
356
357
    }
  }
Dries's avatar
Dries committed
358
359
  $conf = "$confdir/default";
  return $conf;
Dries's avatar
 
Dries committed
360
361
}

362
/**
363
364
365
366
367
 * Initialize variables needed for the rest of the execution.
 */
function drupal_initialize_variables() {
  if (!isset($_SERVER['HTTP_REFERER'])) {
    $_SERVER['HTTP_REFERER'] = '';
368
  }
369
370
371
  if (!isset($_SERVER['SERVER_PROTOCOL']) || ($_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.0' && $_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.1')) {
    $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0';
  }
372
373
374
375
  // Enforce E_ALL, but allow users to set levels not part of E_ALL.
  error_reporting(E_ALL | error_reporting());
  // Prevent PHP from generating HTML errors messages.
  ini_set('html_errors', 0);
376
377
}

378
/**
379
380
 * Loads the configuration and sets the base URL, cookie domain, and
 * session name correctly.
381
382
 */
function conf_init() {
383
384
  global $base_url, $base_path, $base_root;

Dries's avatar
Dries committed
385
  // Export the following settings.php variables to the global namespace
386
  global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access;
Dries's avatar
Dries committed
387
388
  $conf = array();

389
390
  if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) {
    include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php';
391
  }
392
393
394
395

  if (isset($base_url)) {
    // Parse fixed base URL from settings.php.
    $parts = parse_url($base_url);
396
397
398
    if (!isset($parts['path'])) {
      $parts['path'] = '';
    }
399
    $base_path = $parts['path'] . '/';
400
401
402
403
404
405
    // 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';
406
407
408

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

    // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not
    // be modified by a visitor.
    if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) {
414
415
416
417
418
419
420
421
      $base_path = "/$dir";
      $base_url .= $base_path;
      $base_path .= '/';
    }
    else {
      $base_path = '/';
    }
  }
422
423
424
425
426
427

  if ($cookie_domain) {
    // If the user specifies the cookie domain, also use it for session name.
    $session_name = $cookie_domain;
  }
  else {
428
429
430
    // Otherwise use $base_url as session name, without the protocol
    // to use the same session identifiers across http and https.
    list( , $session_name) = explode('://', $base_url, 2);
431
    // We escape the hostname because it can be modified by a visitor.
432
    if (!empty($_SERVER['HTTP_HOST'])) {
433
      $cookie_domain = check_plain($_SERVER['HTTP_HOST']);
434
435
    }
  }
436
437
438
439
440
441
442
443
444
  // To prevent session cookies from being hijacked, a user can configure the
  // SSL version of their website to only transfer session cookies via SSL by
  // using PHP's session.cookie_secure setting. The browser will then use two
  // separate session cookies for the HTTPS and HTTP versions of the site. So we
  // must use different session identifiers for HTTPS and HTTP to prevent a
  // cookie collision.
  if (ini_get('session.cookie_secure')) {
    $session_name .= 'SSL';
  }
445
446
447
448
449
  // Strip leading periods, www., and port numbers from cookie domain.
  $cookie_domain = ltrim($cookie_domain, '.');
  if (strpos($cookie_domain, 'www.') === 0) {
    $cookie_domain = substr($cookie_domain, 4);
  }
450
  $cookie_domain = explode(':', $cookie_domain);
451
  $cookie_domain = '.' . $cookie_domain[0];
452
453
454
455
456
  // Per RFC 2109, cookie domains must contain at least one dot other than the
  // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
  if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
    ini_set('session.cookie_domain', $cookie_domain);
  }
457
  session_name('SESS' . md5($session_name));
458
459
}

Dries's avatar
Dries committed
460
461
/**
 * Returns and optionally sets the filename for a system item (module,
462
 * theme, etc.). The filename, whether provided, cached, or retrieved
Dries's avatar
Dries committed
463
464
 * from the database, is only returned if the file exists.
 *
Dries's avatar
Dries committed
465
466
467
468
469
470
471
472
473
474
475
476
 * 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
477
478
479
480
481
482
483
484
485
486
487
 * @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
488
function drupal_get_filename($type, $name, $filename = NULL) {
Dries's avatar
Dries committed
489
490
  static $files = array();

491
  if (!isset($files[$type])) {
Dries's avatar
Dries committed
492
493
494
    $files[$type] = array();
  }

495
  if (!empty($filename) && file_exists($filename)) {
Dries's avatar
Dries committed
496
497
    $files[$type][$name] = $filename;
  }
498
  elseif (isset($files[$type][$name])) {
Dries's avatar
Dries committed
499
500
    // nothing
  }
Dries's avatar
Dries committed
501
502
503
504
  // 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.
505
  elseif (db_is_active() && (($file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField()) && file_exists($file))) {
Dries's avatar
Dries committed
506
507
508
    $files[$type][$name] = $file;
  }
  else {
Dries's avatar
Dries committed
509
510
    // 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
511
    $config = conf_path();
Dries's avatar
Dries committed
512
    $dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s");
Dries's avatar
   
Dries committed
513
    $file = (($type == 'theme_engine') ? "$name.engine" : "$name.$type");
Dries's avatar
Dries committed
514
515
516
517
518
519
520
521
522

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

523
524
525
  if (isset($files[$type][$name])) {
    return $files[$type][$name];
  }
Dries's avatar
Dries committed
526
527
}

Dries's avatar
   
Dries committed
528
529
530
531
532
533
534
/**
 * 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
535
function variable_init($conf = array()) {
536
  // NOTE: caching the variables improves performance by 20% when serving cached pages.
537
  if ($cached = cache_get('variables', 'cache')) {
538
    $variables = $cached->data;
Dries's avatar
   
Dries committed
539
540
  }
  else {
541
    $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed());
542
    cache_set('variables', $variables);
Dries's avatar
   
Dries committed
543
544
545
546
  }

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

Dries's avatar
   
Dries committed
549
  return $variables;
Dries's avatar
 
Dries committed
550
551
}

Dries's avatar
   
Dries committed
552
553
554
555
556
557
558
559
560
561
/**
 * 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
562
563
564
565
566
567
function variable_get($name, $default) {
  global $conf;

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

Dries's avatar
   
Dries committed
568
569
570
571
572
573
574
575
576
/**
 * 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
577
578
579
function variable_set($name, $value) {
  global $conf;

580
  db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute();
Dries's avatar
   
Dries committed
581

582
  cache_clear_all('variables', 'cache');
Dries's avatar
 
Dries committed
583
584
585
586

  $conf[$name] = $value;
}

Dries's avatar
   
Dries committed
587
588
589
590
591
592
/**
 * Unset a persistent variable.
 *
 * @param $name
 *   The name of the variable to undefine.
 */
Dries's avatar
 
Dries committed
593
594
595
function variable_del($name) {
  global $conf;

596
597
598
  db_delete('variable')
    ->condition('name', $name)
    ->execute();
599
  cache_clear_all('variables', 'cache');
Dries's avatar
 
Dries committed
600
601
602
603

  unset($conf[$name]);
}

604

Dries's avatar
   
Dries committed
605
606
607
/**
 * Retrieve the current page from the cache.
 *
608
 * Note: we do not serve cached pages when status messages are waiting (from
Dries's avatar
   
Dries committed
609
610
 * a redirected form submission which was completed).
 */
Dries's avatar
 
Dries committed
611
function page_get_cache() {
612
  global $user, $base_root;
Dries's avatar
 
Dries committed
613
614

  $cache = NULL;
615

616
  if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_set_message()) == 0) {
617
    $cache = cache_get($base_root . request_uri(), 'cache_page');
Dries's avatar
 
Dries committed
618
619
620
621
622
623
624
625
626

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

  return $cache;
}

Dries's avatar
Dries committed
627
628
629
/**
 * Call all init or exit hooks without including all modules.
 *
630
 * @param $hook
Dries's avatar
Dries committed
631
632
 *   The name of the bootstrap hook we wish to invoke.
 */
633
function bootstrap_invoke_all($hook) {
634
  foreach (module_list(TRUE, TRUE) as $module) {
635
    module_invoke($module, $hook);
636
  }
Dries's avatar
Dries committed
637
638
639
}

/**
640
 * Includes a file with the provided type and name. This prevents
Dries's avatar
Dries committed
641
642
643
644
645
646
647
648
649
650
651
652
653
 * 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();

654
  if (isset($files[$type][$name])) {
Dries's avatar
Dries committed
655
656
657
658
659
660
    return TRUE;
  }

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

  if ($filename) {
661
    include_once DRUPAL_ROOT . '/' . $filename;
Dries's avatar
Dries committed
662
663
664
665
666
667
668
669
    $files[$type][$name] = TRUE;

    return TRUE;
  }

  return FALSE;
}

Dries's avatar
   
Dries committed
670
671
/**
 * Set HTTP headers in preparation for a page response.
672
 *
673
674
675
676
 * 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.
 *
677
 * @see page_set_cache()
Dries's avatar
   
Dries committed
678
 */
Dries's avatar
 
Dries committed
679
function drupal_page_header() {
680
  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
681
  header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
682
  header("Cache-Control: store, no-cache, must-revalidate");
683
684
  header("Cache-Control: post-check=0, pre-check=0", FALSE);
}
Dries's avatar
   
Dries committed
685

686
687
688
689
690
691
692
693
694
695
696
/**
 * 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:
697
698
  $last_modified = gmdate('D, d M Y H:i:s', $cache->created) . ' GMT';
  $etag = '"' . md5($last_modified) . '"';
699
700
701
702
703
704
705
706

  // 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
707
    header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
708
709
    // All 304 responses must send an etag if the 200 response for the same object contained an etag
    header("Etag: $etag");
710
    return;
711
  }
712

713
714
715
  // Send appropriate response:
  header("Last-Modified: $last_modified");
  header("ETag: $etag");
716

717
718
719
  // The following headers force validation of cache:
  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
  header("Cache-Control: must-revalidate");
720

721
722
723
724
725
726
727
728
729
  if (variable_get('page_compression', TRUE)) {
    // Determine if the browser accepts gzipped data.
    if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === FALSE && function_exists('gzencode')) {
      // Strip the gzip header and run uncompress.
      $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
    }
    elseif (function_exists('gzencode')) {
      header('Content-Encoding: gzip');
    }
Dries's avatar
 
Dries committed
730
  }
731
732
733
734
735
736
737
738
739
740

  // 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
741
742
}

Dries's avatar
   
Dries committed
743
744
745
/**
 * Define the critical hooks that force modules to always be loaded.
 */
Dries's avatar
 
Dries committed
746
function bootstrap_hooks() {
747
  return array('boot', 'exit');
Dries's avatar
 
Dries committed
748
749
}

Dries's avatar
   
Dries committed
750
751
752
753
754
755
756
757
/**
 * 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
758
759
760
761
762
763
764
765
766
767
768
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
769
770
/**
 * Encode special characters in a plain-text string for display as HTML.
Gábor Hojtsy's avatar
Gábor Hojtsy committed
771
772
773
 *
 * Uses drupal_validate_utf8 to prevent cross site scripting attacks on
 * Internet Explorer 6.
Dries's avatar
Dries committed
774
775
 */
function check_plain($text) {
Gábor Hojtsy's avatar
Gábor Hojtsy committed
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
  return drupal_validate_utf8($text) ? htmlspecialchars($text, ENT_QUOTES) : '';
}

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

Dries's avatar
   
Dries committed
815
/**
816
817
 * Since $_SERVER['REQUEST_URI'] is only available on Apache, we
 * generate an equivalent using other environment variables.
Dries's avatar
   
Dries committed
818
 */
Dries's avatar
 
Dries committed
819
function request_uri() {
820
821
822
823
824
825

  if (isset($_SERVER['REQUEST_URI'])) {
    $uri = $_SERVER['REQUEST_URI'];
  }
  else {
    if (isset($_SERVER['argv'])) {
826
      $uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['argv'][0];
827
    }
828
    elseif (isset($_SERVER['QUERY_STRING'])) {
829
      $uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
830
    }
831
832
833
    else {
      $uri = $_SERVER['SCRIPT_NAME'];
    }
Dries's avatar
 
Dries committed
834
  }
835

836
  return $uri;
Dries's avatar
 
Dries committed
837
}
Dries's avatar
Dries committed
838

Dries's avatar
   
Dries committed
839
840
841
842
843
844
/**
 * Log a system message.
 *
 * @param $type
 *   The category to which this message belongs.
 * @param $message
845
846
 *   The message to store in the log. See t() for documentation
 *   on how $message and $variables interact. Keep $message
847
 *   translatable by not concatenating dynamic values into it!
848
849
850
851
 * @param $variables
 *   Array of variables to replace in the message on display or
 *   NULL if message is already translated or not possible to
 *   translate.
852
 * @param $severity
853
 *   The severity of the message, as per RFC 3164
Dries's avatar
   
Dries committed
854
855
 * @param $link
 *   A link to associate with the message.
856
 *
857
 * @see watchdog_severity_levels()
Dries's avatar
   
Dries committed
858
 */
859
function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
860
  global $user, $base_root;
861

862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
  static $in_error_state = FALSE;

  // It is possible that the error handling will itself trigger an error.  In that case, we could
  // end up in an infinite loop.  To avoid that, we implement a simple static semaphore.
  if (!$in_error_state) {
    $in_error_state = TRUE;

    // Prepare the fields to be logged
    $log_message = array(
      'type'        => $type,
      'message'     => $message,
      'variables'   => $variables,
      'severity'    => $severity,
      'link'        => $link,
      'user'        => $user,
      'request_uri' => $base_root . request_uri(),
878
      'referer'     => $_SERVER['HTTP_REFERER'],
879
      'ip'          => ip_address(),
880
      'timestamp'   => REQUEST_TIME,
881
882
    );

883
884
885
886
    // Call the logging hooks to log/process the message
    foreach (module_implements('watchdog', TRUE) as $module) {
      module_invoke($module, 'watchdog', $log_message);
    }
887
  }
888
  $in_error_state = FALSE;
Dries's avatar
   
Dries committed
889
890
}

Dries's avatar
   
Dries committed
891
/**
892
 * Set a message which reflects the status of the performed operation.
Dries's avatar
   
Dries committed
893
 *
894
895
 * If the function is called with no arguments, this function returns all set
 * messages without clearing them.
Dries's avatar
   
Dries committed
896
 *
897
898
899
900
901
902
 * @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'
903
 *   - 'warning'
904
 *   - 'error'
905
906
907
 * @param $repeat
 *   If this is FALSE and the message is already set, then the message won't
 *   be repeated.
Dries's avatar
   
Dries committed
908
 */
909
function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
910
  if ($message) {
Dries's avatar
   
Dries committed
911
912
913
914
915
916
917
918
    if (!isset($_SESSION['messages'])) {
      $_SESSION['messages'] = array();
    }

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

919
920
921
    if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
      $_SESSION['messages'][$type][] = $message;
    }
922
923
  }

924
925
  // messages not set when DB connection fails
  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
926
927
}

Dries's avatar
   
Dries committed
928
929
930
/**
 * Return all messages that have been set.
 *
931
932
 * @param $type
 *   (optional) Only return messages of this type.
933
934
 * @param $clear_queue
 *   (optional) Set to FALSE if you do not want to clear the messages queue
935
936
937
938
939
 * @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
940
 */
941
function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
942
  if ($messages = drupal_set_message()) {
943
    if ($type) {
944
      if ($clear_queue) {
945
        unset($_SESSION['messages'][$type]);
946
      }
947
948
949
      if (isset($messages[$type])) {
        return array($type => $messages[$type]);
      }
950
951
    }
    else {
952
      if ($clear_queue) {
953
        unset($_SESSION['messages']);
954
      }
955
956
      return $messages;
    }
957
  }
958
  return array();
959
960
}

Dries's avatar
   
Dries committed
961
/**
962
963
964
965
966
967
968
969
970
 * Check to see if an IP address has been blocked.
 *
 * Blocked IP addresses are stored in the database by default. However for
 * performance reasons we allow an override in settings.php. This allows us
 * to avoid querying the database at this critical stage of the bootstrap if
 * an administrative interface for IP address blocking is not required.
 *
 * @param $ip string
 *   IP address to check.
971
972
 * @return bool
 *   TRUE if access is denied, FALSE if access is allowed.
Dries's avatar
   
Dries committed
973
 */
974
975
976
977
978
979
980
981
982
function drupal_is_denied($ip) {
  // Because this function is called on every page request, we first check
  // for an array of IP addresses in settings.php before querying the
  // database.
  $blocked_ips = variable_get('blocked_ips', NULL);
  if (isset($blocked_ips) && is_array($blocked_ips)) {
    return in_array($ip, $blocked_ips);
  }
  else {
983
    return (bool)db_query("SELECT 1 FROM {blocked_ips} WHERE ip = :ip", array(':ip' => $ip))->fetchField();
984
  }
Dries's avatar
   
Dries committed
985
986
}

987
/**
988
 * Generates a default anonymous $user object.
989
990
991
 *
 * @return Object - the user object.
 */
992
function drupal_anonymous_user($session = '') {
993
994
  $user = new stdClass();
  $user->uid = 0;
995
  $user->hostname = ip_address();
996
997
  $user->roles = array();
  $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
998
  $user->session = $session;
999
  $user->cache = 0;
1000
  return $user;
For faster browsing, not all history is shown. View entire blame