Line # | Frequency | Source Line |
1 | | <?php |
2 | | // $Id: bootstrap.inc,v 1.206 2008/01/10 22:47:17 goba Exp $ |
3 | |
|
4 | | /** |
5 | | * @file |
6 | | * Functions that need to be loaded on every Drupal request. |
7 | | */ |
8 | |
|
9 | | /** |
10 | | * Indicates that the item should never be removed unless explicitly told to |
11 | | * using cache_clear_all() with a cache ID. |
12 | | */ |
13 | | define('CACHE_PERMANENT', 0); |
14 | |
|
15 | | /** |
16 | | * Indicates that the item should be removed at the next general cache wipe. |
17 | | */ |
18 | | define('CACHE_TEMPORARY', -1); |
19 | |
|
20 | | /** |
21 | | * Indicates that page caching is disabled. |
22 | | */ |
23 | | define('CACHE_DISABLED', 0); |
24 | |
|
25 | | /** |
26 | | * Indicates that page caching is enabled, using "normal" mode. |
27 | | */ |
28 | | define('CACHE_NORMAL', 1); |
29 | |
|
30 | | /** |
31 | | * Indicates that page caching is using "aggressive" mode. This bypasses |
32 | | * loading any modules for additional speed, which may break functionality in |
33 | | * modules that expect to be run on each page load. |
34 | | */ |
35 | | define('CACHE_AGGRESSIVE', 2); |
36 | |
|
37 | | /** |
38 | | * |
39 | | * Severity levels, as defined in RFC 3164 http://www.faqs.org/rfcs/rfc3164.html |
40 | | * @see watchdog() |
41 | | * @see watchdog_severity_levels() |
42 | | */ |
43 | | define('WATCHDOG_EMERG', 0); // Emergency: system is unusable |
44 | | define('WATCHDOG_ALERT', 1); // Alert: action must be taken immediately |
45 | | define('WATCHDOG_CRITICAL', 2); // Critical: critical conditions |
46 | | define('WATCHDOG_ERROR', 3); // Error: error conditions |
47 | | define('WATCHDOG_WARNING', 4); // Warning: warning conditions |
48 | | define('WATCHDOG_NOTICE', 5); // Notice: normal but significant condition |
49 | | define('WATCHDOG_INFO', 6); // Informational: informational messages |
50 | | define('WATCHDOG_DEBUG', 7); // Debug: debug-level messages |
51 | |
|
52 | | /** |
53 | | * First bootstrap phase: initialize configuration. |
54 | | */ |
55 | | define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0); |
56 | |
|
57 | | /** |
58 | | * Second bootstrap phase: try to call a non-database cache |
59 | | * fetch routine. |
60 | | */ |
61 | | define('DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE', 1); |
62 | |
|
63 | | /** |
64 | | * Third bootstrap phase: initialize database layer. |
65 | | */ |
66 | | define('DRUPAL_BOOTSTRAP_DATABASE', 2); |
67 | |
|
68 | | /** |
69 | | * Fourth bootstrap phase: identify and reject banned hosts. |
70 | | */ |
71 | | define('DRUPAL_BOOTSTRAP_ACCESS', 3); |
72 | |
|
73 | | /** |
74 | | * Fifth bootstrap phase: initialize session handling. |
75 | | */ |
76 | | define('DRUPAL_BOOTSTRAP_SESSION', 4); |
77 | |
|
78 | | /** |
79 | | * Sixth bootstrap phase: load bootstrap.inc and module.inc, start |
80 | | * the variable system and try to serve a page from the cache. |
81 | | */ |
82 | | define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 5); |
83 | |
|
84 | | /** |
85 | | * Seventh bootstrap phase: find out language of the page. |
86 | | */ |
87 | | define('DRUPAL_BOOTSTRAP_LANGUAGE', 6); |
88 | |
|
89 | | /** |
90 | | * Eighth bootstrap phase: set $_GET['q'] to Drupal path of request. |
91 | | */ |
92 | | define('DRUPAL_BOOTSTRAP_PATH', 7); |
93 | |
|
94 | | /** |
95 | | * Final bootstrap phase: Drupal is fully loaded; validate and fix |
96 | | * input data. |
97 | | */ |
98 | | define('DRUPAL_BOOTSTRAP_FULL', 8); |
99 | |
|
100 | | /** |
101 | | * Role ID for anonymous users; should match what's in the "role" table. |
102 | | */ |
103 | | define('DRUPAL_ANONYMOUS_RID', 1); |
104 | |
|
105 | | /** |
106 | | * Role ID for authenticated users; should match what's in the "role" table. |
107 | | */ |
108 | | define('DRUPAL_AUTHENTICATED_RID', 2); |
109 | |
|
110 | | /** |
111 | | * No language negotiation. The default language is used. |
112 | | */ |
113 | | define('LANGUAGE_NEGOTIATION_NONE', 0); |
114 | |
|
115 | | /** |
116 | | * Path based negotiation with fallback to default language |
117 | | * if no defined path prefix identified. |
118 | | */ |
119 | | define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1); |
120 | |
|
121 | | /** |
122 | | * Path based negotiation with fallback to user preferences |
123 | | * and browser language detection if no defined path prefix |
124 | | * identified. |
125 | | */ |
126 | | define('LANGUAGE_NEGOTIATION_PATH', 2); |
127 | |
|
128 | | /** |
129 | | * Domain based negotiation with fallback to default language |
130 | | * if no language identified by domain. |
131 | | */ |
132 | | define('LANGUAGE_NEGOTIATION_DOMAIN', 3); |
133 | |
|
134 | | /** |
135 | | * Start the timer with the specified name. If you start and stop |
136 | | * the same timer multiple times, the measured intervals will be |
137 | | * accumulated. |
138 | | * |
139 | | * @param name |
140 | | * The name of the timer. |
141 | | */ |
142 | | function timer_start($name) { |
143 | | global $timers; |
144 | |
|
145 | | list($usec, $sec) = explode(' ', microtime()); |
146 | | $timers[$name]['start'] = (float)$usec + (float)$sec; |
147 | | $timers[$name]['count'] = isset($timers[$name]['count']) ? ++$timers[$name]['count'] : 1; |
148 | | } |
149 | |
|
150 | | /** |
151 | | * Read the current timer value without stopping the timer. |
152 | | * |
153 | | * @param name |
154 | | * The name of the timer. |
155 | | * @return |
156 | | * The current timer value in ms. |
157 | | */ |
158 | | function timer_read($name) { |
159 | | global $timers; |
160 | |
|
161 | | if (isset($timers[$name]['start'])) { |
162 | | list($usec, $sec) = explode(' ', microtime()); |
163 | | $stop = (float)$usec + (float)$sec; |
164 | | $diff = round(($stop - $timers[$name]['start']) * 1000, 2); |
165 | |
|
166 | | if (isset($timers[$name]['time'])) { |
167 | | $diff += $timers[$name]['time']; |
168 | | } |
169 | | return $diff; |
170 | | } |
171 | | } |
172 | |
|
173 | | /** |
174 | | * Stop the timer with the specified name. |
175 | | * |
176 | | * @param name |
177 | | * The name of the timer. |
178 | | * @return |
179 | | * A timer array. The array contains the number of times the |
180 | | * timer has been started and stopped (count) and the accumulated |
181 | | * timer value in ms (time). |
182 | | */ |
183 | | function timer_stop($name) { |
184 | | global $timers; |
185 | |
|
186 | | $timers[$name]['time'] = timer_read($name); |
187 | | unset($timers[$name]['start']); |
188 | |
|
189 | | return $timers[$name]; |
190 | | } |
191 | |
|
192 | | /** |
193 | | * Find the appropriate configuration directory. |
194 | | * |
195 | | * Try finding a matching configuration directory by stripping the website's |
196 | | * 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'. |
199 | | * |
200 | | * Example for a fictitious site installed at |
201 | | * http://www.drupal.org:8080/mysite/test/ the 'settings.php' is searched in |
202 | | * the following directories: |
203 | | * |
204 | | * 1. $confdir/8080.www.drupal.org.mysite.test |
205 | | * 2. $confdir/www.drupal.org.mysite.test |
206 | | * 3. $confdir/drupal.org.mysite.test |
207 | | * 4. $confdir/org.mysite.test |
208 | | * |
209 | | * 5. $confdir/8080.www.drupal.org.mysite |
210 | | * 6. $confdir/www.drupal.org.mysite |
211 | | * 7. $confdir/drupal.org.mysite |
212 | | * 8. $confdir/org.mysite |
213 | | * |
214 | | * 9. $confdir/8080.www.drupal.org |
215 | | * 10. $confdir/www.drupal.org |
216 | | * 11. $confdir/drupal.org |
217 | | * 12. $confdir/org |
218 | | * |
219 | | * 13. $confdir/default |
220 | | * |
221 | | * @param $require_settings |
222 | | * Only configuration directories with an existing settings.php file |
223 | | * will be recognized. Defaults to TRUE. During initial installation, |
224 | | * this is set to FALSE so that Drupal can detect a matching directory, |
225 | | * then create a new settings.php file in it. |
226 | | * @param reset |
227 | | * Force a full search for matching directories even if one had been |
228 | | * found previously. |
229 | | * @return |
230 | | * The path of the matching directory. |
231 | | */ |
232 | | function conf_path($require_settings = TRUE, $reset = FALSE) { |
233 | 1 | static $conf = ''; |
234 | |
|
235 | 1 | if ($conf && !$reset) { |
236 | 1 | return $conf; |
237 | | } |
238 | |
|
239 | | $confdir = 'sites'; |
240 | | $uri = explode('/', $_SERVER['SCRIPT_NAME'] ? $_SERVER['SCRIPT_NAME'] : $_SERVER['SCRIPT_FILENAME']); |
241 | | $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.'))))); |
242 | | for ($i = count($uri) - 1; $i > 0; $i--) { |
243 | | for ($j = count($server); $j > 0; $j--) { |
244 | | $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); |
245 | | if (file_exists("$confdir/$dir/settings.php") || (!$require_settings && file_exists("$confdir/$dir"))) { |
246 | | $conf = "$confdir/$dir"; |
247 | | return $conf; |
248 | | } |
249 | | } |
250 | | } |
251 | | $conf = "$confdir/default"; |
252 | | return $conf; |
253 | | } |
254 | |
|
255 | | /** |
256 | | * Unsets all disallowed global variables. See $allowed for what's allowed. |
257 | | */ |
258 | | function drupal_unset_globals() { |
259 | | if (ini_get('register_globals')) { |
260 | | $allowed = array('_ENV' => 1, '_GET' => 1, '_POST' => 1, '_COOKIE' => 1, '_FILES' => 1, '_SERVER' => 1, '_REQUEST' => 1, 'GLOBALS' => 1); |
261 | | foreach ($GLOBALS as $key => $value) { |
262 | | if (!isset($allowed[$key])) { |
263 | | unset($GLOBALS[$key]); |
264 | | } |
265 | | } |
266 | | } |
267 | | } |
268 | |
|
269 | | /** |
270 | | * Loads the configuration and sets the base URL, cookie domain, and |
271 | | * session name correctly. |
272 | | */ |
273 | | function conf_init() { |
274 | | global $base_url, $base_path, $base_root; |
275 | |
|
276 | | // Export the following settings.php variables to the global namespace |
277 | | global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access; |
278 | | $conf = array(); |
279 | |
|
280 | | if (file_exists('./'. conf_path() .'/settings.php')) { |
281 | | include_once './'. conf_path() .'/settings.php'; |
282 | | } |
283 | |
|
284 | | if (isset($base_url)) { |
285 | | // Parse fixed base URL from settings.php. |
286 | | $parts = parse_url($base_url); |
287 | | if (!isset($parts['path'])) { |
288 | | $parts['path'] = ''; |
289 | | } |
290 | | $base_path = $parts['path'] .'/'; |
291 | | // Build $base_root (everything until first slash after "scheme://"). |
292 | | $base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path'])); |
293 | | } |
294 | | else { |
295 | | // Create base URL |
296 | | $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'; |
297 | |
|
298 | | // As $_SERVER['HTTP_HOST'] is user input, ensure it only contains |
299 | | // characters allowed in hostnames. |
300 | | $base_url = $base_root .= '://'. preg_replace('/[^a-z0-9-:._]/i', '', $_SERVER['HTTP_HOST']); |
301 | |
|
302 | | // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not |
303 | | // be modified by a visitor. |
304 | | if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) { |
305 | | $base_path = "/$dir"; |
306 | | $base_url .= $base_path; |
307 | | $base_path .= '/'; |
308 | | } |
309 | | else { |
310 | | $base_path = '/'; |
311 | | } |
312 | | } |
313 | |
|
314 | | if ($cookie_domain) { |
315 | | // If the user specifies the cookie domain, also use it for session name. |
316 | | $session_name = $cookie_domain; |
317 | | } |
318 | | else { |
319 | | // Otherwise use $base_url as session name, without the protocol |
320 | | // to use the same session identifiers across http and https. |
321 | | list( , $session_name) = explode('://', $base_url, 2); |
322 | | // We escape the hostname because it can be modified by a visitor. |
323 | | if (!empty($_SERVER['HTTP_HOST'])) { |
324 | | $cookie_domain = check_plain($_SERVER['HTTP_HOST']); |
325 | | } |
326 | | } |
327 | | // Strip leading periods, www., and port numbers from cookie domain. |
328 | | $cookie_domain = ltrim($cookie_domain, '.'); |
329 | | if (strpos($cookie_domain, 'www.') === 0) { |
330 | | $cookie_domain = substr($cookie_domain, 4); |
331 | | } |
332 | | $cookie_domain = explode(':', $cookie_domain); |
333 | | $cookie_domain = '.'. $cookie_domain[0]; |
334 | | // Per RFC 2109, cookie domains must contain at least one dot other than the |
335 | | // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain. |
336 | | if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) { |
337 | | ini_set('session.cookie_domain', $cookie_domain); |
338 | | } |
339 | | session_name('SESS'. md5($session_name)); |
340 | | } |
341 | |
|
342 | | /** |
343 | | * Returns and optionally sets the filename for a system item (module, |
344 | | * theme, etc.). The filename, whether provided, cached, or retrieved |
345 | | * from the database, is only returned if the file exists. |
346 | | * |
347 | | * This function plays a key role in allowing Drupal's resources (modules |
348 | | * and themes) to be located in different places depending on a site's |
349 | | * configuration. For example, a module 'foo' may legally be be located |
350 | | * in any of these three places: |
351 | | * |
352 | | * modules/foo/foo.module |
353 | | * sites/all/modules/foo/foo.module |
354 | | * sites/example.com/modules/foo/foo.module |
355 | | * |
356 | | * Calling drupal_get_filename('module', 'foo') will give you one of |
357 | | * the above, depending on where the module is located. |
358 | | * |
359 | | * @param $type |
360 | | * The type of the item (i.e. theme, theme_engine, module). |
361 | | * @param $name |
362 | | * The name of the item for which the filename is requested. |
363 | | * @param $filename |
364 | | * The filename of the item if it is to be set explicitly rather |
365 | | * than by consulting the database. |
366 | | * |
367 | | * @return |
368 | | * The filename of the requested item. |
369 | | */ |
370 | | function drupal_get_filename($type, $name, $filename = NULL) { |
371 | | static $files = array(); |
372 | |
|
373 | 1 | if (!isset($files[$type])) { |
374 | | $files[$type] = array(); |
375 | | } |
376 | |
|
377 | 1 | if (!empty($filename) && file_exists($filename)) { |
378 | 1 | $files[$type][$name] = $filename; |
379 | | } |
380 | 1 | elseif (isset($files[$type][$name])) { |
381 | | // nothing |
382 | | } |
383 | | // Verify that we have an active database connection, before querying |
384 | | // the database. This is required because this function is called both |
385 | | // before we have a database connection (i.e. during installation) and |
386 | | // when a database connection fails. |
387 | | elseif (db_is_active() && (($file = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s' AND type = '%s'", $name, $type))) && file_exists($file))) { |
388 | | $files[$type][$name] = $file; |
389 | | } |
390 | | else { |
391 | | // Fallback to searching the filesystem if the database connection is |
392 | | // not established or the requested file is not found. |
393 | | $config = conf_path(); |
394 | | $dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s"); |
395 | | $file = (($type == 'theme_engine') ? "$name.engine" : "$name.$type"); |
396 | |
|
397 | | foreach (array("$config/$dir/$file", "$config/$dir/$name/$file", "$dir/$file", "$dir/$name/$file") as $file) { |
398 | | if (file_exists($file)) { |
399 | | $files[$type][$name] = $file; |
400 | | break; |
401 | | } |
402 | | } |
403 | | } |
404 | |
|
405 | 1 | if (isset($files[$type][$name])) { |
406 | 1 | return $files[$type][$name]; |
407 | | } |
408 | | } |
409 | |
|
410 | | /** |
411 | | * Load the persistent variable table. |
412 | | * |
413 | | * The variable table is composed of values that have been saved in the table |
414 | | * with variable_set() as well as those explicitly specified in the configuration |
415 | | * file. |
416 | | */ |
417 | | function variable_init($conf = array()) { |
418 | | // NOTE: caching the variables improves performance by 20% when serving cached pages. |
419 | | if ($cached = cache_get('variables', 'cache')) { |
420 | | $variables = $cached->data; |
421 | | } |
422 | | else { |
423 | | $result = db_query('SELECT * FROM {variable}'); |
424 | | while ($variable = db_fetch_object($result)) { |
425 | | $variables[$variable->name] = unserialize($variable->value); |
426 | | } |
427 | | cache_set('variables', $variables); |
428 | | } |
429 | |
|
430 | | foreach ($conf as $name => $value) { |
431 | | $variables[$name] = $value; |
432 | | } |
433 | |
|
434 | | return $variables; |
435 | | } |
436 | |
|
437 | | /** |
438 | | * Return a persistent variable. |
439 | | * |
440 | | * @param $name |
441 | | * The name of the variable to return. |
442 | | * @param $default |
443 | | * The default value to use if this variable has never been set. |
444 | | * @return |
445 | | * The value of the variable. |
446 | | */ |
447 | | function variable_get($name, $default) { |
448 | 1 | global $conf; |
449 | |
|
450 | 1 | return isset($conf[$name]) ? $conf[$name] : $default; |
451 | | } |
452 | |
|
453 | | /** |
454 | | * Set a persistent variable. |
455 | | * |
456 | | * @param $name |
457 | | * The name of the variable to set. |
458 | | * @param $value |
459 | | * The value to set. This can be any PHP data type; these functions take care |
460 | | * of serialization as necessary. |
461 | | */ |
462 | | function variable_set($name, $value) { |
463 | 1 | global $conf; |
464 | |
|
465 | 1 | $serialized_value = serialize($value); |
466 | 1 | db_query("UPDATE {variable} SET value = '%s' WHERE name = '%s'", $serialized_value, $name); |
467 | 1 | if (!db_affected_rows()) { |
468 | 1 | @db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, $serialized_value); |
469 | | } |
470 | |
|
471 | 1 | cache_clear_all('variables', 'cache'); |
472 | |
|
473 | 1 | $conf[$name] = $value; |
474 | | } |
475 | |
|
476 | | /** |
477 | | * Unset a persistent variable. |
478 | | * |
479 | | * @param $name |
480 | | * The name of the variable to undefine. |
481 | | */ |
482 | | function variable_del($name) { |
483 | 1 | global $conf; |
484 | |
|
485 | 1 | db_query("DELETE FROM {variable} WHERE name = '%s'", $name); |
486 | 1 | cache_clear_all('variables', 'cache'); |
487 | |
|
488 | 1 | unset($conf[$name]); |
489 | | } |
490 | |
|
491 | |
|
492 | | /** |
493 | | * Retrieve the current page from the cache. |
494 | | * |
495 | | * Note: we do not serve cached pages when status messages are waiting (from |
496 | | * a redirected form submission which was completed). |
497 | | */ |
498 | | function page_get_cache() { |
499 | | global $user, $base_root; |
500 | |
|
501 | | $cache = NULL; |
502 | |
|
503 | | if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0) { |
504 | | $cache = cache_get($base_root . request_uri(), 'cache_page'); |
505 | |
|
506 | | if (empty($cache)) { |
507 | | ob_start(); |
508 | | } |
509 | | } |
510 | |
|
511 | | return $cache; |
512 | | } |
513 | |
|
514 | | /** |
515 | | * Call all init or exit hooks without including all modules. |
516 | | * |
517 | | * @param $hook |
518 | | * The name of the bootstrap hook we wish to invoke. |
519 | | */ |
520 | | function bootstrap_invoke_all($hook) { |
521 | | foreach (module_list(TRUE, TRUE) as $module) { |
522 | | drupal_load('module', $module); |
523 | | module_invoke($module, $hook); |
524 | | } |
525 | | } |
526 | |
|
527 | | /** |
528 | | * Includes a file with the provided type and name. This prevents |
529 | | * including a theme, engine, module, etc., more than once. |
530 | | * |
531 | | * @param $type |
532 | | * The type of item to load (i.e. theme, theme_engine, module). |
533 | | * @param $name |
534 | | * The name of the item to load. |
535 | | * |
536 | | * @return |
537 | | * TRUE if the item is loaded or has already been loaded. |
538 | | */ |
539 | | function drupal_load($type, $name) { |
540 | | static $files = array(); |
541 | |
|
542 | 1 | if (isset($files[$type][$name])) { |
543 | 1 | return TRUE; |
544 | | } |
545 | |
|
546 | | $filename = drupal_get_filename($type, $name); |
547 | |
|
548 | | if ($filename) { |
549 | | include_once "./$filename"; |
550 | | $files[$type][$name] = TRUE; |
551 | |
|
552 | | return TRUE; |
553 | | } |
554 | |
|
555 | | return FALSE; |
556 | | } |
557 | |
|
558 | | /** |
559 | | * Set HTTP headers in preparation for a page response. |
560 | | * |
561 | | * Authenticated users are always given a 'no-cache' header, and will |
562 | | * fetch a fresh page on every request. This prevents authenticated |
563 | | * users seeing locally cached pages that show them as logged out. |
564 | | * |
565 | | * @see page_set_cache() |
566 | | */ |
567 | | function drupal_page_header() { |
568 | | header("Expires: Sun, 19 Nov 1978 05:00:00 GMT"); |
569 | | header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT"); |
570 | | header("Cache-Control: store, no-cache, must-revalidate"); |
571 | | header("Cache-Control: post-check=0, pre-check=0", FALSE); |
572 | | } |
573 | |
|
574 | | /** |
575 | | * Set HTTP headers in preparation for a cached page response. |
576 | | * |
577 | | * The general approach here is that anonymous users can keep a local |
578 | | * cache of the page, but must revalidate it on every request. Then, |
579 | | * they are given a '304 Not Modified' response as long as they stay |
580 | | * logged out and the page has not been modified. |
581 | | * |
582 | | */ |
583 | | function drupal_page_cache_header($cache) { |
584 | | // Set default values: |
585 | | $last_modified = gmdate('D, d M Y H:i:s', $cache->created) .' GMT'; |
586 | | $etag = '"'. md5($last_modified) .'"'; |
587 | |
|
588 | | // See if the client has provided the required HTTP headers: |
589 | | $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE; |
590 | | $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE; |
591 | |
|
592 | | if ($if_modified_since && $if_none_match |
593 | | && $if_none_match == $etag // etag must match |
594 | | && $if_modified_since == $last_modified) { // if-modified-since must match |
595 | | header('HTTP/1.1 304 Not Modified'); |
596 | | // All 304 responses must send an etag if the 200 response for the same object contained an etag |
597 | | header("Etag: $etag"); |
598 | | exit(); |
599 | | } |
600 | |
|
601 | | // Send appropriate response: |
602 | | header("Last-Modified: $last_modified"); |
603 | | header("ETag: $etag"); |
604 | |
|
605 | | // The following headers force validation of cache: |
606 | | header("Expires: Sun, 19 Nov 1978 05:00:00 GMT"); |
607 | | header("Cache-Control: must-revalidate"); |
608 | |
|
609 | | if (variable_get('page_compression', TRUE)) { |
610 | | // Determine if the browser accepts gzipped data. |
611 | | if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === FALSE && function_exists('gzencode')) { |
612 | | // Strip the gzip header and run uncompress. |
613 | | $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8)); |
614 | | } |
615 | | elseif (function_exists('gzencode')) { |
616 | | header('Content-Encoding: gzip'); |
617 | | } |
618 | | } |
619 | |
|
620 | | // Send the original request's headers. We send them one after |
621 | | // another so PHP's header() function can deal with duplicate |
622 | | // headers. |
623 | | $headers = explode("\n", $cache->headers); |
624 | | foreach ($headers as $header) { |
625 | | header($header); |
626 | | } |
627 | |
|
628 | | print $cache->data; |
629 | | } |
630 | |
|
631 | | /** |
632 | | * Define the critical hooks that force modules to always be loaded. |
633 | | */ |
634 | | function bootstrap_hooks() { |
635 | 1 | return array('boot', 'exit'); |
636 | | } |
637 | |
|
638 | | /** |
639 | | * Unserializes and appends elements from a serialized string. |
640 | | * |
641 | | * @param $obj |
642 | | * The object to which the elements are appended. |
643 | | * @param $field |
644 | | * The attribute of $obj whose value should be unserialized. |
645 | | */ |
646 | | function drupal_unpack($obj, $field = 'data') { |
647 | 1 | if ($obj->$field && $data = unserialize($obj->$field)) { |
648 | | foreach ($data as $key => $value) { |
649 | | if (!isset($obj->$key)) { |
650 | | $obj->$key = $value; |
651 | | } |
652 | | } |
653 | | } |
654 | 1 | return $obj; |
655 | | } |
656 | |
|
657 | | /** |
658 | | * Return the URI of the referring page. |
659 | | */ |
660 | | function referer_uri() { |
661 | 1 | if (isset($_SERVER['HTTP_REFERER'])) { |
662 | 1 | return $_SERVER['HTTP_REFERER']; |
663 | | } |
664 | | } |
665 | |
|
666 | | /** |
667 | | * Encode special characters in a plain-text string for display as HTML. |
668 | | * |
669 | | * Uses drupal_validate_utf8 to prevent cross site scripting attacks on |
670 | | * Internet Explorer 6. |
671 | | */ |
672 | | function check_plain($text) { |
673 | 1 | return drupal_validate_utf8($text) ? htmlspecialchars($text, ENT_QUOTES) : ''; |
674 | | } |
675 | |
|
676 | | /** |
677 | | * Checks whether a string is valid UTF-8. |
678 | | * |
679 | | * All functions designed to filter input should use drupal_validate_utf8 |
680 | | * to ensure they operate on valid UTF-8 strings to prevent bypass of the |
681 | | * filter. |
682 | | * |
683 | | * When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented |
684 | | * as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent |
685 | | * bytes. When these subsequent bytes are HTML control characters such as |
686 | | * quotes or angle brackets, parts of the text that were deemed safe by filters |
687 | | * end up in locations that are potentially unsafe; An onerror attribute that |
688 | | * is outside of a tag, and thus deemed safe by a filter, can be interpreted |
689 | | * by the browser as if it were inside the tag. |
690 | | * |
691 | | * This function exploits preg_match behaviour (since PHP 4.3.5) when used |
692 | | * with the u modifier, as a fast way to find invalid UTF-8. When the matched |
693 | | * string contains an invalid byte sequence, it will fail silently. |
694 | | * |
695 | | * preg_match may not fail on 4 and 5 octet sequences, even though they |
696 | | * are not supported by the specification. |
697 | | * |
698 | | * The specific preg_match behaviour is present since PHP 4.3.5. |
699 | | * |
700 | | * @param $text |
701 | | * The text to check. |
702 | | * @return |
703 | | * TRUE if the text is valid UTF-8, FALSE if not. |
704 | | */ |
705 | | function drupal_validate_utf8($text) { |
706 | 1 | if (strlen($text) == 0) { |
707 | | return TRUE; |
708 | | } |
709 | 1 | return (preg_match('/^./us', $text) == 1); |
710 | | } |
711 | |
|
712 | | /** |
713 | | * Since $_SERVER['REQUEST_URI'] is only available on Apache, we |
714 | | * generate an equivalent using other environment variables. |
715 | | */ |
716 | | function request_uri() { |
717 | |
|
718 | 1 | if (isset($_SERVER['REQUEST_URI'])) { |
719 | 1 | $uri = $_SERVER['REQUEST_URI']; |
720 | | } |
721 | | else { |
722 | | if (isset($_SERVER['argv'])) { |
723 | | $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['argv'][0]; |
724 | | } |
725 | | elseif (isset($_SERVER['QUERY_STRING'])) { |
726 | | $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING']; |
727 | | } |
728 | | else { |
729 | | $uri = $_SERVER['SCRIPT_NAME']; |
730 | | } |
731 | | } |
732 | |
|
733 | 1 | return $uri; |
734 | | } |
735 | |
|
736 | | /** |
737 | | * Log a system message. |
738 | | * |
739 | | * @param $type |
740 | | * The category to which this message belongs. |
741 | | * @param $message |
742 | | * The message to store in the log. See t() for documentation |
743 | | * on how $message and $variables interact. Keep $message |
744 | | * translatable by not concatenating dynamic values into it! |
745 | | * @param $variables |
746 | | * Array of variables to replace in the message on display or |
747 | | * NULL if message is already translated or not possible to |
748 | | * translate. |
749 | | * @param $severity |
750 | | * The severity of the message, as per RFC 3164 |
751 | | * @param $link |
752 | | * A link to associate with the message. |
753 | | * |
754 | | * @see watchdog_severity_levels() |
755 | | */ |
756 | | function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { |
757 | 1 | global $user, $base_root; |
758 | |
|
759 | | // Prepare the fields to be logged |
760 | | $log_message = array( |
761 | 1 | 'type' => $type, |
762 | | 'message' => $message, |
763 | | 'variables' => $variables, |
764 | | 'severity' => $severity, |
765 | | 'link' => $link, |
766 | | 'user' => $user, |
767 | | 'request_uri' => $base_root . request_uri(), |
768 | | 'referer' => referer_uri(), |
769 | | 'ip' => ip_address(), |
770 | | 'timestamp' => time(), |
771 | | ); |
772 | |
|
773 | | // Call the logging hooks to log/process the message |
774 | 1 | foreach (module_implements('watchdog', TRUE) as $module) { |
775 | 1 | module_invoke($module, 'watchdog', $log_message); |
776 | | } |
777 | | } |
778 | |
|
779 | | /** |
780 | | * Set a message which reflects the status of the performed operation. |
781 | | * |
782 | | * If the function is called with no arguments, this function returns all set |
783 | | * messages without clearing them. |
784 | | * |
785 | | * @param $message |
786 | | * The message should begin with a capital letter and always ends with a |
787 | | * period '.'. |
788 | | * @param $type |
789 | | * The type of the message. One of the following values are possible: |
790 | | * - 'status' |
791 | | * - 'warning' |
792 | | * - 'error' |
793 | | * @param $repeat |
794 | | * If this is FALSE and the message is already set, then the message won't |
795 | | * be repeated. |
796 | | */ |
797 | | function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { |
798 | | if ($message) { |
799 | | if (!isset($_SESSION['messages'])) { |
800 | | $_SESSION['messages'] = array(); |
801 | | } |
802 | |
|
803 | | if (!isset($_SESSION['messages'][$type])) { |
804 | | $_SESSION['messages'][$type] = array(); |
805 | | } |
806 | |
|
807 | | if ($repeat || !in_array($message, $_SESSION['messages'][$type])) { |
808 | | $_SESSION['messages'][$type][] = $message; |
809 | | } |
810 | | } |
811 | |
|
812 | | // messages not set when DB connection fails |
813 | | return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL; |
814 | | } |
815 | |
|
816 | | /** |
817 | | * Return all messages that have been set. |
818 | | * |
819 | | * @param $type |
820 | | * (optional) Only return messages of this type. |
821 | | * @param $clear_queue |
822 | | * (optional) Set to FALSE if you do not want to clear the messages queue |
823 | | * @return |
824 | | * An associative array, the key is the message type, the value an array |
825 | | * of messages. If the $type parameter is passed, you get only that type, |
826 | | * or an empty array if there are no such messages. If $type is not passed, |
827 | | * all message types are returned, or an empty array if none exist. |
828 | | */ |
829 | | function drupal_get_messages($type = NULL, $clear_queue = TRUE) { |
830 | | if ($messages = drupal_set_message()) { |
831 | | if ($type) { |
832 | | if ($clear_queue) { |
833 | | unset($_SESSION['messages'][$type]); |
834 | | } |
835 | | if (isset($messages[$type])) { |
836 | | return array($type => $messages[$type]); |
837 | | } |
838 | | } |
839 | | else { |
840 | | if ($clear_queue) { |
841 | | unset($_SESSION['messages']); |
842 | | } |
843 | | return $messages; |
844 | | } |
845 | | } |
846 | | return array(); |
847 | | } |
848 | |
|
849 | | /** |
850 | | * Perform an access check for a given mask and rule type. Rules are usually |
851 | | * created via admin/user/rules page. |
852 | | * |
853 | | * If any allow rule matches, access is allowed. Otherwise, if any deny rule |
854 | | * matches, access is denied. If no rule matches, access is allowed. |
855 | | * |
856 | | * @param $type string |
857 | | * Type of access to check: Allowed values are: |
858 | | * - 'host': host name or IP address |
859 | | * - 'mail': e-mail address |
860 | | * - 'user': username |
861 | | * @param $mask string |
862 | | * String or mask to test: '_' matches any character, '%' matches any |
863 | | * number of characters. |
864 | | * @return bool |
865 | | * TRUE if access is denied, FALSE if access is allowed. |
866 | | */ |
867 | | function drupal_is_denied($type, $mask) { |
868 | | // Because this function is called for every page request, both cached |
869 | | // and non-cached pages, we tried to optimize it as much as possible. |
870 | | // We deny access if the only matching records in the {access} table have |
871 | | // status 0 (deny). If any have status 1 (allow), or if there are no |
872 | | // matching records, we allow access. |
873 | | $sql = "SELECT 1 FROM {access} WHERE type = '%s' AND LOWER('%s') LIKE LOWER(mask) AND status = %d"; |
874 | | return db_result(db_query_range($sql, $type, $mask, 0, 0, 1)) && !db_result(db_query_range($sql, $type, $mask, 1, 0, 1)); |
875 | | } |
876 | |
|
877 | | /** |
878 | | * Generates a default anonymous $user object. |
879 | | * |
880 | | * @return Object - the user object. |
881 | | */ |
882 | | function drupal_anonymous_user($session = '') { |
883 | | $user = new stdClass(); |
884 | | $user->uid = 0; |
885 | | $user->hostname = ip_address(); |
886 | | $user->roles = array(); |
887 | | $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user'; |
888 | | $user->session = $session; |
889 | | $user->cache = 0; |
890 | | return $user; |
891 | | } |
892 | |
|
893 | | /** |
894 | | * A string describing a phase of Drupal to load. Each phase adds to the |
895 | | * previous one, so invoking a later phase automatically runs the earlier |
896 | | * phases too. The most important usage is that if you want to access the |
897 | | * Drupal database from a script without loading anything else, you can |
898 | | * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE). |
899 | | * |
900 | | * @param $phase |
901 | | * A constant. Allowed values are: |
902 | | * DRUPAL_BOOTSTRAP_CONFIGURATION: initialize configuration. |
903 | | * DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: try to call a non-database cache fetch routine. |
904 | | * DRUPAL_BOOTSTRAP_DATABASE: initialize database layer. |
905 | | * DRUPAL_BOOTSTRAP_ACCESS: identify and reject banned hosts. |
906 | | * DRUPAL_BOOTSTRAP_SESSION: initialize session handling. |
907 | | * DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start |
908 | | * the variable system and try to serve a page from the cache. |
909 | | * DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page. |
910 | | * DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request. |
911 | | * DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data. |
912 | | */ |
913 | | function drupal_bootstrap($phase) { |
914 | | static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL), $phase_index = 0; |
915 | |
|
916 | 1 | while ($phase >= $phase_index && isset($phases[$phase_index])) { |
917 | | $current_phase = $phases[$phase_index]; |
918 | | unset($phases[$phase_index++]); |
919 | | _drupal_bootstrap($current_phase); |
920 | | } |
921 | | } |
922 | |
|
923 | | function _drupal_bootstrap($phase) { |
924 | | global $conf; |
925 | |
|
926 | | switch ($phase) { |
927 | |
|
928 | | case DRUPAL_BOOTSTRAP_CONFIGURATION: |
929 | | drupal_unset_globals(); |
930 | | // Start a page timer: |
931 | | timer_start('page'); |
932 | | // Initialize the configuration |
933 | | conf_init(); |
934 | | break; |
935 | |
|
936 | | case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: |
937 | | // Allow specifying special cache handlers in settings.php, like |
938 | | // using memcached or files for storing cache information. |
939 | | require_once variable_get('cache_inc', './includes/cache.inc'); |
940 | | // If the page_cache_fastpath is set to TRUE in settings.php and |
941 | | // page_cache_fastpath (implemented in the special implementation of |
942 | | // cache.inc) printed the page and indicated this with a returned TRUE |
943 | | // then we are done. |
944 | | if (variable_get('page_cache_fastpath', FALSE) && page_cache_fastpath()) { |
945 | | exit; |
946 | | } |
947 | | break; |
948 | |
|
949 | | case DRUPAL_BOOTSTRAP_DATABASE: |
950 | | // Initialize the default database. |
951 | | require_once './includes/database.inc'; |
952 | | db_set_active(); |
953 | | break; |
954 | |
|
955 | | case DRUPAL_BOOTSTRAP_ACCESS: |
956 | | // Deny access to hosts which were banned - t() is not yet available. |
957 | | if (drupal_is_denied('host', ip_address())) { |
958 | | header('HTTP/1.1 403 Forbidden'); |
959 | | print 'Sorry, '. check_plain(ip_address()) .' has been banned.'; |
960 | | exit(); |
961 | | } |
962 | | break; |
963 | |
|
964 | | case DRUPAL_BOOTSTRAP_SESSION: |
965 | | require_once variable_get('session_inc', './includes/session.inc'); |
966 | | session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc'); |
967 | | session_start(); |
968 | | break; |
969 | |
|
970 | | case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: |
971 | | // Initialize configuration variables, using values from settings.php if available. |
972 | | $conf = variable_init(isset($conf) ? $conf : array()); |
973 | | // Load module handling. |
974 | | require_once './includes/module.inc'; |
975 | | $cache_mode = variable_get('cache', CACHE_DISABLED); |
976 | | // Get the page from the cache. |
977 | | $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache(); |
978 | | // If the skipping of the bootstrap hooks is not enforced, call hook_boot. |
979 | | if ($cache_mode != CACHE_AGGRESSIVE) { |
980 | | bootstrap_invoke_all('boot'); |
981 | | } |
982 | | // If there is a cached page, display it. |
983 | | if ($cache) { |
984 | | drupal_page_cache_header($cache); |
985 | | // If the skipping of the bootstrap hooks is not enforced, call hook_exit. |
986 | | if ($cache_mode != CACHE_AGGRESSIVE) { |
987 | | bootstrap_invoke_all('exit'); |
988 | | } |
989 | | // We are done. |
990 | | exit; |
991 | | } |
992 | | // Prepare for non-cached page workflow. |
993 | | drupal_page_header(); |
994 | | break; |
995 | |
|
996 | | case DRUPAL_BOOTSTRAP_LANGUAGE: |
997 | | drupal_init_language(); |
998 | | break; |
999 | |
|
1000 | | case DRUPAL_BOOTSTRAP_PATH: |
1001 | | require_once './includes/path.inc'; |
1002 | | // Initialize $_GET['q'] prior to loading modules and invoking hook_init(). |
1003 | | drupal_init_path(); |
1004 | | break; |
1005 | |
|
1006 | | case DRUPAL_BOOTSTRAP_FULL: |
1007 | | require_once './includes/common.inc'; |
1008 | | _drupal_bootstrap_full(); |
1009 | | break; |
1010 | | } |
1011 | | } |
1012 | |
|
1013 | | /** |
1014 | | * Enables use of the theme system without requiring database access. |
1015 | | * |
1016 | | * Loads and initializes the theme system for site installs, updates and when |
1017 | | * the site is in off-line mode. This also applies when the database fails. |
1018 | | * |
1019 | | * @see _drupal_maintenance_theme() |
1020 | | */ |
1021 | | function drupal_maintenance_theme() { |
1022 | | require_once './includes/theme.maintenance.inc'; |
1023 | | _drupal_maintenance_theme(); |
1024 | | } |
1025 | |
|
1026 | | /** |
1027 | | * Return the name of the localisation function. Use in code that needs to |
1028 | | * run both during installation and normal operation. |
1029 | | */ |
1030 | | function get_t() { |
1031 | 1 | static $t; |
1032 | 1 | if (is_null($t)) { |
1033 | | $t = function_exists('install_main') ? 'st' : 't'; |
1034 | | } |
1035 | 1 | return $t; |
1036 | | } |
1037 | |
|
1038 | | /** |
1039 | | * Choose a language for the current page, based on site and user preferences. |
1040 | | */ |
1041 | | function drupal_init_language() { |
1042 | | global $language, $user; |
1043 | |
|
1044 | | // Ensure the language is correctly returned, even without multilanguage support. |
1045 | | // Useful for eg. XML/HTML 'lang' attributes. |
1046 | | if (variable_get('language_count', 1) == 1) { |
1047 | | $language = language_default(); |
1048 | | } |
1049 | | else { |
1050 | | include_once './includes/language.inc'; |
1051 | | $language = language_initialize(); |
1052 | | } |
1053 | | } |
1054 | |
|
1055 | | /** |
1056 | | * Get a list of languages set up indexed by the specified key |
1057 | | * |
1058 | | * @param $field The field to index the list with. |
1059 | | * @param $reset Boolean to request a reset of the list. |
1060 | | */ |
1061 | | function language_list($field = 'language', $reset = FALSE) { |
1062 | | static $languages = NULL; |
1063 | |
|
1064 | | // Reset language list |
1065 | | if ($reset) { |
1066 | | $languages = NULL; |
1067 | | } |
1068 | |
|
1069 | | // Init language list |
1070 | | if (!isset($languages)) { |
1071 | | if (variable_get('language_count', 1) > 1 || module_exists('locale')) { |
1072 | | $result = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC'); |
1073 | | while ($row = db_fetch_object($result)) { |
1074 | | $languages['language'][$row->language] = $row; |
1075 | | } |
1076 | | } |
1077 | | else { |
1078 | | // No locale module, so use the default language only. |
1079 | | $default = language_default(); |
1080 | | $languages['language'][$default->language] = $default; |
1081 | | } |
1082 | | } |
1083 | |
|
1084 | | // Return the array indexed by the right field |
1085 | | if (!isset($languages[$field])) { |
1086 | | $languages[$field] = array(); |
1087 | | foreach ($languages['language'] as $lang) { |
1088 | | // Some values should be collected into an array |
1089 | | if (in_array($field, array('enabled', 'weight'))) { |
1090 | | $languages[$field][$lang->$field][$lang->language] = $lang; |
1091 | | } |
1092 | | else { |
1093 | | $languages[$field][$lang->$field] = $lang; |
1094 | | } |
1095 | | } |
1096 | | } |
1097 | | return $languages[$field]; |
1098 | | } |
1099 | |
|
1100 | | /** |
1101 | | * Default language used on the site |
1102 | | * |
1103 | | * @param $property |
1104 | | * Optional property of the language object to return |
1105 | | */ |
1106 | | function language_default($property = NULL) { |
1107 | | $language = variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '')); |
1108 | | return $property ? $language->$property : $language; |
1109 | | } |
1110 | |
|
1111 | | /** |
1112 | | * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header |
1113 | | * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address |
1114 | | * of the proxy server, and not the client's. |
1115 | | * |
1116 | | * @return |
1117 | | * IP address of client machine, adjusted for reverse proxy. |
1118 | | */ |
1119 | | function ip_address() { |
1120 | 1 | static $ip_address = NULL; |
1121 | |
|
1122 | 1 | if (!isset($ip_address)) { |
1123 | | $ip_address = $_SERVER['REMOTE_ADDR']; |
1124 | | if (variable_get('reverse_proxy', 0) && array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { |
1125 | | // If an array of known reverse proxy IPs is provided, then trust |
1126 | | // the XFF header if request really comes from one of them. |
1127 | | $reverse_proxy_addresses = variable_get('reverse_proxy_addresses', array()); |
1128 | | if (!empty($reverse_proxy_addresses) && in_array($ip_address, $reverse_proxy_addresses, TRUE)) { |
1129 | | // If there are several arguments, we need to check the most |
1130 | | // recently added one, i.e. the last one. |
1131 | | $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); |
1132 | | } |
1133 | | } |
1134 | | } |
1135 | |
|
1136 | 1 | return $ip_address; |
1137 | | } |