· 10 years ago · Feb 28, 2016, 12:51 AM
1<?php
2/*======================================================================*\
3|| #################################################################### ||
4|| # vBulletin 3.8.7 Patch Level 3 - Licence Number VBF2G48E09
5|| # ---------------------------------------------------------------- # ||
6|| # Copyright �2000-2013 vBulletin Solutions, Inc. All Rights Reserved. ||
7|| # This file may not be redistributed in whole or significant part. # ||
8|| # ---------------- VBULLETIN IS NOT FREE SOFTWARE ---------------- # ||
9|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html # ||
10|| #################################################################### ||
11\*======================================================================*/
12
13define('FILE_VERSION', '3.8.7'); // this should match install.php
14define('SIMPLE_VERSION', '387'); // see vB_Datastore::check_options()
15define('YUI_VERSION', '2.9.0'); // define the YUI version we bundle, used for external YUI
16
17/**#@+
18* The maximum sizes for the "small" profile avatars
19*/
20define('FIXED_SIZE_AVATAR_WIDTH', 60);
21define('FIXED_SIZE_AVATAR_HEIGHT', 80);
22/**#@-*/
23
24/**#@+
25* These make up the bit field to disable specific types of BB codes.
26*/
27define('ALLOW_BBCODE_BASIC', 1);
28define('ALLOW_BBCODE_COLOR', 2);
29define('ALLOW_BBCODE_SIZE', 4);
30define('ALLOW_BBCODE_FONT', 8);
31define('ALLOW_BBCODE_ALIGN', 16);
32define('ALLOW_BBCODE_LIST', 32);
33define('ALLOW_BBCODE_URL', 64);
34define('ALLOW_BBCODE_CODE', 128);
35define('ALLOW_BBCODE_PHP', 256);
36define('ALLOW_BBCODE_HTML', 512);
37define('ALLOW_BBCODE_IMG', 1024);
38define('ALLOW_BBCODE_QUOTE', 2048);
39define('ALLOW_BBCODE_CUSTOM', 4096);
40/**#@-*/
41
42/**#@+
43* These make up the bit field to control what "special" BB codes are found in the text.
44*/
45define('BBCODE_HAS_IMG', 1);
46define('BBCODE_HAS_ATTACH', 2);
47define('BBCODE_HAS_SIGPIC', 4);
48/**#@-*/
49
50/**#@+
51* Bitfield values for the inline moderation javascript selector which should be self-explanitory
52*/
53define('POST_FLAG_INVISIBLE', 1);
54define('POST_FLAG_DELETED', 2);
55define('POST_FLAG_ATTACH', 4);
56define('POST_FLAG_GUEST', 8);
57
58// #############################################################################
59// MySQL Database Class
60
61/**#@+
62* The type of result set to return from the database for a specific row.
63*/
64define('DBARRAY_BOTH', 0);
65define('DBARRAY_ASSOC', 1);
66define('DBARRAY_NUM', 2);
67/**#@-*/
68
69/**
70* Class to interface with a database
71*
72* This class also handles data replication between a master and slave(s) servers
73*
74* @package vBulletin
75* @version $Revision: 43870 $
76* @date $Date: 2011-05-26 14:52:39 -0700 (Thu, 26 May 2011) $
77*/
78class vB_Database
79{
80 /**
81 * Array of function names, mapping a simple name to the RDBMS specific function name
82 *
83 * @var array
84 */
85 var $functions = array(
86 'connect' => 'mysql_connect',
87 'pconnect' => 'mysql_pconnect',
88 'select_db' => 'mysql_select_db',
89 'query' => 'mysql_query',
90 'query_unbuffered' => 'mysql_unbuffered_query',
91 'fetch_row' => 'mysql_fetch_row',
92 'fetch_array' => 'mysql_fetch_array',
93 'fetch_field' => 'mysql_fetch_field',
94 'free_result' => 'mysql_free_result',
95 'data_seek' => 'mysql_data_seek',
96 'error' => 'mysql_error',
97 'errno' => 'mysql_errno',
98 'affected_rows' => 'mysql_affected_rows',
99 'num_rows' => 'mysql_num_rows',
100 'num_fields' => 'mysql_num_fields',
101 'field_name' => 'mysql_field_name',
102 'insert_id' => 'mysql_insert_id',
103 'escape_string' => 'mysql_escape_string',
104 'real_escape_string' => 'mysql_real_escape_string',
105 'close' => 'mysql_close',
106 'client_encoding' => 'mysql_client_encoding',
107 );
108
109 /**
110 * The vBulletin registry object
111 *
112 * @var vB_Registry
113 */
114 var $registry = null;
115
116 /**
117 * Array of constants for use in fetch_array
118 *
119 * @var array
120 */
121 var $fetchtypes = array(
122 DBARRAY_NUM => MYSQL_NUM,
123 DBARRAY_ASSOC => MYSQL_ASSOC,
124 DBARRAY_BOTH => MYSQL_BOTH
125 );
126
127 /**
128 * Full name of the system
129 *
130 * @var string
131 */
132 var $appname = 'vBulletin';
133
134 /**
135 * Short name of the system
136 *
137 * @var string
138 */
139 var $appshortname = 'vBulletin';
140
141 /**
142 * Database name
143 *
144 * @var string
145 */
146 var $database = null;
147
148 /**
149 * Link variable. The connection to the master/write server.
150 *
151 * @var string
152 */
153 var $connection_master = null;
154
155 /**
156 * Link variable. The connection to the slave/read server(s).
157 *
158 * @var string
159 */
160 var $connection_slave = null;
161
162 /**
163 * Link variable. The connection last used.
164 *
165 * @var string
166 */
167 var $connection_recent = null;
168
169 /**
170 * Whether or not we will be using different connections for read and write queries
171 *
172 * @var boolean
173 */
174 var $multiserver = false;
175
176 /**
177 * Array of queries to be executed when the script shuts down
178 *
179 * @var array
180 */
181 var $shutdownqueries = array();
182
183 /**
184 * The contents of the most recent SQL query string.
185 *
186 * @var string
187 */
188 var $sql = '';
189
190 /**
191 * Whether or not to show and halt on database errors
192 *
193 * @var boolean
194 */
195 var $reporterror = true;
196
197 /**
198 * The text of the most recent database error message
199 *
200 * @var string
201 */
202 var $error = '';
203
204 /**
205 * The error number of the most recent database error message
206 *
207 * @var integer
208 */
209 var $errno = '';
210
211 /**
212 * SQL Query String
213 *
214 * @var integer The maximum size of query string permitted by the master server
215 */
216 var $maxpacket = 0;
217
218 /**
219 * Track lock status of tables. True if a table lock has been issued
220 *
221 * @var bool
222 */
223 var $locked = false;
224
225 /**
226 * Number of queries executed
227 *
228 * @var integer The number of SQL queries run by the system
229 */
230 var $querycount = 0;
231
232 /**
233 * Constructor. If x_real_escape_string() is available, switches to use that
234 * function over x_escape_string().
235 *
236 * @param vB_Registry Registry object
237 */
238 function vB_Database(&$registry)
239 {
240 if (is_object($registry))
241 {
242 $this->registry =& $registry;
243 }
244 else
245 {
246 trigger_error("vB_Database::Registry object is not an object", E_USER_ERROR);
247 }
248 }
249
250 /**
251 * Connects to the specified database server(s)
252 *
253 * @param string Name of the database that we will be using for select_db()
254 * @param string Name of the master (write) server - should be either 'localhost' or an IP address
255 * @param integer Port for the master server
256 * @param string Username to connect to the master server
257 * @param string Password associated with the username for the master server
258 * @param boolean Whether or not to use persistent connections to the master server
259 * @param string (Optional) Name of the slave (read) server - should be either left blank or set to 'localhost' or an IP address, but NOT the same as the servername for the master server
260 * @param integer (Optional) Port of the slave server
261 * @param string (Optional) Username to connect to the slave server
262 * @param string (Optional) Password associated with the username for the slave server
263 * @param boolean (Optional) Whether or not to use persistent connections to the slave server
264 * @param string (Optional) Parse given MySQL config file to set options
265 * @param string (Optional) Connection Charset MySQLi / PHP 5.1.0+ or 5.0.5+ / MySQL 4.1.13+ or MySQL 5.1.10+ Only
266 *
267 * @return none
268 */
269 function connect($database, $w_servername, $w_port, $w_username, $w_password, $w_usepconnect = false, $r_servername = '', $r_port = 3306, $r_username = '', $r_password = '', $r_usepconnect = false, $configfile = '', $charset = '')
270 {
271 $this->database = $database;
272
273 $w_port = $w_port ? $w_port : 3306;
274 $r_port = $r_port ? $r_port : 3306;
275
276 $this->connection_master = $this->db_connect($w_servername, $w_port, $w_username, $w_password, $w_usepconnect, $configfile, $charset);
277 $this->multiserver = false;
278 $this->connection_slave =& $this->connection_master;
279
280 if ($this->connection_master)
281 {
282 $this->select_db($this->database);
283 }
284 }
285
286 /**
287 * Initialize database connection(s)
288 *
289 * Connects to the specified master database server, and also to the slave server if it is specified
290 *
291 * @param string Name of the database server - should be either 'localhost' or an IP address
292 * @param integer Port of the database server (usually 3306)
293 * @param string Username to connect to the database server
294 * @param string Password associated with the username for the database server
295 * @param boolean Whether or not to use persistent connections to the database server
296 * @param string Not applicable; config file for MySQLi only
297 * @param string Force connection character set (to prevent collation errors)
298 *
299 * @return boolean
300 */
301 function db_connect($servername, $port, $username, $password, $usepconnect, $configfile = '', $charset = '')
302 {
303 if (function_exists('catch_db_error'))
304 {
305 set_error_handler('catch_db_error');
306 }
307
308 // catch_db_error will handle exiting, no infinite loop here
309 do
310 {
311 $link = $this->functions[$usepconnect ? 'pconnect' : 'connect']("$servername:$port", $username, $password);
312 }
313 while ($link == false AND $this->reporterror);
314
315 restore_error_handler();
316
317 if (!empty($charset))
318 {
319 if (function_exists('mysql_set_charset'))
320 {
321 mysql_set_charset($charset);
322 }
323 else
324 {
325 $this->sql = "SET NAMES $charset";
326 $this->execute_query(true, $link);
327 }
328 }
329
330 return $link;
331 }
332
333 /**
334 * Selects a database to use
335 *
336 * @param string The name of the database located on the database server(s)
337 *
338 * @return boolean
339 */
340 function select_db($database = '')
341 {
342 if ($database != '')
343 {
344 $this->database = $database;
345 }
346
347 if ($check_write = @$this->select_db_wrapper($this->database, $this->connection_master))
348 {
349 $this->connection_recent =& $this->connection_master;
350 return true;
351 }
352 else
353 {
354 $this->connection_recent =& $this->connection_master;
355 $this->halt('Cannot use database ' . $this->database);
356 return false;
357 }
358 }
359
360 /**
361 * Simple wrapper for select_db(), to allow argument order changes
362 *
363 * @param string Database name
364 * @param integer Link identifier
365 *
366 * @return boolean
367 */
368 function select_db_wrapper($database = '', $link = null)
369 {
370 return $this->functions['select_db']($database, $link);
371 }
372
373 /**
374 * Forces the sql_mode varaible to a specific mode. Certain modes may be
375 * incompatible with vBulletin. Applies to MySQL 4.1+.
376 *
377 * @param string The mode to set the sql_mode variable to
378 */
379 function force_sql_mode($mode)
380 {
381 $reset_errors = $this->reporterror;
382 if ($reset_errors)
383 {
384 $this->hide_errors();
385 }
386
387 $this->query_write("SET @@sql_mode = '" . $this->escape_string($mode) . "'");
388
389 if ($reset_errors)
390 {
391 $this->show_errors();
392 }
393 }
394
395 /**
396 * Executes an SQL query through the specified connection
397 *
398 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
399 * @param string The connection ID to the database server
400 *
401 * @return string
402 */
403 function &execute_query($buffered = true, &$link)
404 {
405 $this->connection_recent =& $link;
406 $this->querycount++;
407
408 if ($queryresult = $this->functions[$buffered ? 'query' : 'query_unbuffered']($this->sql, $link))
409 {
410 // unset $sql to lower memory .. this isn't an error, so it's not needed
411 $this->sql = '';
412
413 return $queryresult;
414 }
415 else
416 {
417 $this->halt();
418
419 // unset $sql to lower memory .. error will have already been thrown
420 $this->sql = '';
421 }
422 }
423
424 /**
425 * Executes a data-writing SQL query through the 'master' database connection
426 *
427 * @param string The text of the SQL query to be executed
428 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is buffered.
429 *
430 * @return string
431 */
432 function query_write($sql, $buffered = true)
433 {
434 $this->sql =& $sql;
435 return $this->execute_query($buffered, $this->connection_master);
436 }
437
438 /**
439 * Executes a data-reading SQL query through the 'master' database connection
440 * we don't know if the 'read' database is up to date so be on the safe side
441 *
442 * @param string The text of the SQL query to be executed
443 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is buffered.
444 *
445 * @return string
446 */
447 function query_read($sql, $buffered = true)
448 {
449 $this->sql =& $sql;
450 return $this->execute_query($buffered, $this->connection_master);
451 }
452
453 /**
454 * Executes a data-reading SQL query through the 'slave' database connection
455 *
456 * @param string The text of the SQL query to be executed
457 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is buffered.
458 *
459 * @return string
460 */
461 function query_read_slave($sql, $buffered = true)
462 {
463 $this->sql =& $sql;
464 return $this->execute_query($buffered, $this->connection_master);
465 }
466
467 /**
468 * Executes an SQL query, using either the write connection
469 *
470 * @deprecated Deprecated as of 3.6. Use query_(read/write)
471 *
472 * @param string The text of the SQL query to be executed
473 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
474 *
475 * @return string
476 */
477 function query($sql, $buffered = true)
478 {
479 $this->sql =& $sql;
480 return $this->execute_query($buffered, $this->connection_master);
481 }
482
483 /**
484 * Executes a data-reading SQL query, then returns an array of the data from the first row from the result set
485 *
486 * @param string The text of the SQL query to be executed
487 * @param string One of (NUM, ASSOC, BOTH)
488 *
489 * @return array
490 */
491 function &query_first($sql, $type = DBARRAY_ASSOC)
492 {
493 $this->sql =& $sql;
494 $queryresult = $this->execute_query(true, $this->connection_master);
495 $returnarray = $this->fetch_array($queryresult, $type);
496 $this->free_result($queryresult);
497 return $returnarray;
498 }
499
500 /**
501 * Executes a FOUND_ROWS query to get the results of SQL_CALC_FOUND_ROWS
502 *
503 * @return integer
504 */
505 function found_rows()
506 {
507 $this->sql = "SELECT FOUND_ROWS()";
508 $queryresult = $this->execute_query(true, $this->connection_recent);
509 $returnarray = $this->fetch_array($queryresult, DBARRAY_NUM);
510 $this->free_result($queryresult);
511
512 return intval($returnarray[0]);
513 }
514
515 /**
516 * Executes a data-reading SQL query against the slave server, then returns an array of the data from the first row from the result set
517 *
518 * @param string The text of the SQL query to be executed
519 * @param string One of (NUM, ASSOC, BOTH)
520 *
521 * @return array
522 */
523 function &query_first_slave($sql, $type = DBARRAY_ASSOC)
524 {
525 $returnarray = $this->query_first($sql, $type);
526 return $returnarray;
527 }
528
529 /**
530 * Executes an INSERT INTO query, using extended inserts if possible
531 *
532 * @param string Name of the table into which data should be inserted
533 * @param string Comma-separated list of the fields to affect
534 * @param array Array of SQL values
535 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
536 *
537 * @return mixed
538 */
539 function &query_insert($table, $fields, &$values, $buffered = true)
540 {
541 return $this->insert_multiple("INSERT INTO $table $fields VALUES", $values, $buffered);
542 }
543
544 /**
545 * Executes a REPLACE INTO query, using extended inserts if possible
546 *
547 * @param string Name of the table into which data should be inserted
548 * @param string Comma-separated list of the fields to affect
549 * @param array Array of SQL values
550 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
551 *
552 * @return mixed
553 */
554 function &query_replace($table, $fields, &$values, $buffered = true)
555 {
556 return $this->insert_multiple("REPLACE INTO $table $fields VALUES", $values, $buffered);
557 }
558
559 /**
560 * Executes an INSERT or REPLACE query with multiple values, splitting large queries into manageable chunks based on $this->maxpacket
561 *
562 * @param string The text of the first part of the SQL query to be executed - example "INSERT INTO table (field1, field2) VALUES"
563 * @param mixed The values to be inserted. Example: (0 => "('value1', 'value2')", 1 => "('value3', 'value4')")
564 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
565 *
566 * @return mixed
567 */
568 function insert_multiple($sql, &$values, $buffered)
569 {
570 if ($this->maxpacket == 0)
571 {
572 // must do a READ query on the WRITE link here!
573 $vars = $this->query_write("SHOW VARIABLES LIKE 'max_allowed_packet'");
574 $var = $this->fetch_row($vars);
575 $this->maxpacket = $var[1];
576 $this->free_result($vars);
577 }
578
579 $i = 0;
580 $num_values = sizeof($values);
581 $this->sql = $sql;
582
583 while ($i < $num_values)
584 {
585 $sql_length = strlen($this->sql);
586 $value_length = strlen("\r\n" . $values["$i"] . ",");
587
588 if (($sql_length + $value_length) < $this->maxpacket)
589 {
590 $this->sql .= "\r\n" . $values["$i"] . ",";
591 unset($values["$i"]);
592 $i++;
593 }
594 else
595 {
596 $this->sql = (substr($this->sql, -1) == ',') ? substr($this->sql, 0, -1) : $this->sql;
597 $this->execute_query($buffered, $this->connection_master);
598 $this->sql = $sql;
599 }
600 }
601 if ($this->sql != $sql)
602 {
603 $this->sql = (substr($this->sql, -1) == ',') ? substr($this->sql, 0, -1) : $this->sql;
604 $this->execute_query($buffered, $this->connection_master);
605 }
606
607 if (sizeof($values) == 1)
608 {
609 return $this->insert_id();
610 }
611 else
612 {
613 return true;
614 }
615 }
616
617 /**
618 * Registers an SQL query to be executed at shutdown time. If shutdown functions are disabled, the query is run immediately.
619 *
620 * @param string The text of the SQL query to be executed
621 * @param mixed (Optional) Allows particular shutdown queries to be labelled
622 *
623 * @return boolean
624 */
625 function shutdown_query($sql, $arraykey = -1)
626 {
627 if ($arraykey === -1)
628 {
629 $this->shutdownqueries[] = $sql;
630 return true;
631 }
632 else
633 {
634 $this->shutdownqueries["$arraykey"] = $sql;
635 return true;
636 }
637 }
638
639 /**
640 * Returns the number of rows contained within a query result set
641 *
642 * @param string The query result ID we are dealing with
643 *
644 * @return integer
645 */
646 function num_rows($queryresult)
647 {
648 return @$this->functions['num_rows']($queryresult);
649 }
650
651 /**
652 * Returns the number of fields contained within a query result set
653 *
654 * @param string The query result ID we are dealing with
655 *
656 * @return integer
657 */
658 function num_fields($queryresult)
659 {
660 return @$this->functions['num_fields']($queryresult);
661 }
662
663 /**
664 * Returns the name of a field from within a query result set
665 *
666 * @param string The query result ID we are dealing with
667 * @param integer The index position of the field
668 *
669 * @return string
670 */
671 function field_name($queryresult, $index)
672 {
673 return @$this->functions['field_name']($queryresult, $index);
674 }
675
676 /**
677 * Returns the ID of the item just inserted into an auto-increment field
678 *
679 * @return integer
680 */
681 function insert_id()
682 {
683 return @$this->functions['insert_id']($this->connection_master);
684 }
685
686 /**
687 * Returns the name of the character set
688 *
689 * @return string
690 */
691 function client_encoding()
692 {
693 return @$this->functions['client_encoding']($this->connection_master);
694 }
695
696 /**
697 * Closes the connection to the database server
698 *
699 * @return integer
700 */
701 function close()
702 {
703 return @$this->functions['close']($this->connection_master);
704 }
705
706 /**
707 * Escapes a string to make it safe to be inserted into an SQL query
708 *
709 * @param string The string to be escaped
710 *
711 * @return string
712 */
713 function escape_string($string)
714 {
715 if ($this->functions['escape_string'] == $this->functions['real_escape_string'])
716 {
717 return $this->functions['escape_string']($string, $this->connection_master);
718 }
719 else
720 {
721 return $this->functions['escape_string']($string);
722 }
723 }
724
725 /**
726 * Escapes a string using the appropriate escape character for the RDBMS for use in LIKE conditions
727 *
728 * @param string The string to be escaped
729 *
730 * @return string
731 */
732 function escape_string_like($string)
733 {
734 return str_replace(array('%', '_') , array('\%' , '\_') , $this->escape_string($string));
735 }
736
737 /**
738 * Takes a piece of data and prepares it to be put into an SQL query by adding quotes etc.
739 *
740 * @param mixed The data to be used
741 *
742 * @return mixed The prepared data
743 */
744 function sql_prepare($value)
745 {
746 if (is_string($value))
747 {
748 return "'" . $this->escape_string($value) . "'";
749 }
750 else if (is_numeric($value) AND $value + 0 == $value)
751 {
752 return $value;
753 }
754 else if (is_bool($value))
755 {
756 return $value ? 1 : 0;
757 }
758 else
759 {
760 return "'" . $this->escape_string($value) . "'";
761 }
762 }
763
764 /**
765 * Fetches a row from a query result and returns the values from that row as an array
766 *
767 * The value of $type defines whether the array will have numeric or associative keys, or both
768 *
769 * @param string The query result ID we are dealing with
770 * @param integer One of DBARRAY_ASSOC / DBARRAY_NUM / DBARRAY_BOTH
771 *
772 * @return array
773 */
774 function fetch_array($queryresult, $type = DBARRAY_ASSOC)
775 {
776 return @$this->functions['fetch_array']($queryresult, $this->fetchtypes["$type"]);
777 }
778
779 /**
780 * Fetches a row from a query result and returns the values from that row as an array with numeric keys
781 *
782 * @param string The query result ID we are dealing with
783 *
784 * @return array
785 */
786 function fetch_row($queryresult)
787 {
788 return @$this->functions['fetch_row']($queryresult);
789 }
790
791 /**
792 * Fetches a row information from a query result and returns the values from that row as an array
793 *
794 * @param string The query result ID we are dealing with
795 *
796 * @return array
797 */
798 function fetch_field($queryresult)
799 {
800 return @$this->functions['fetch_field']($queryresult);
801 }
802
803 /**
804 * Moves the internal result pointer within a query result set
805 *
806 * @param string The query result ID we are dealing with
807 * @param integer The position to which to move the pointer (first position is 0)
808 *
809 * @return boolean
810 */
811 function data_seek($queryresult, $index)
812 {
813 return @$this->functions['data_seek']($queryresult, $index);
814 }
815
816 /**
817 * Frees all memory associated with the specified query result
818 *
819 * @param string The query result ID we are dealing with
820 *
821 * @return boolean
822 */
823 function free_result($queryresult)
824 {
825 $this->sql = '';
826 return @$this->functions['free_result']($queryresult);
827 }
828
829 /**
830 * Retuns the number of rows affected by the most recent insert/replace/update query
831 *
832 * @return integer
833 */
834 function affected_rows()
835 {
836 $this->rows = $this->functions['affected_rows']($this->connection_recent);
837 return $this->rows;
838 }
839
840 /**
841 * Lock tables
842 *
843 * @param mixed List of tables to lock
844 * @param string Type of lock to perform
845 *
846 */
847 function lock_tables($tablelist)
848 {
849 if (!empty($tablelist) AND is_array($tablelist))
850 {
851 // Don't lock tables if we know we might get stuck with them locked (pconnect = true)
852 // mysqli doesn't support pconnect! YAY!
853 if (strtolower($this->registry->config['Database']['dbtype']) != 'mysqli' AND $this->registry->config['MasterServer']['usepconnect'])
854 {
855 return;
856 }
857
858 $sql = '';
859 foreach($tablelist AS $name => $type)
860 {
861 $sql .= (!empty($sql) ? ', ' : '') . TABLE_PREFIX . $name . " " . $type;
862 }
863
864 $this->query_write("LOCK TABLES $sql");
865 $this->locked = true;
866
867 }
868 }
869
870 /**
871 * Unlock tables
872 *
873 */
874 function unlock_tables()
875 {
876 # must be called from exec_shutdown as tables can get stuck locked if pconnects are enabled
877 # note: the above case never actually happens as we skip the lock if pconnects are enabled (to be safe) =)
878 if ($this->locked)
879 {
880 $this->query_write("UNLOCK TABLES");
881 }
882 }
883
884 /**
885 * Returns the text of the error message from previous database operation
886 *
887 * @return string
888 */
889 function error()
890 {
891 if ($this->connection_recent === null)
892 {
893 $this->error = '';
894 }
895 else
896 {
897 $this->error = $this->functions['error']($this->connection_recent);
898 }
899 return $this->error;
900 }
901
902 /**
903 * Returns the numerical value of the error message from previous database operation
904 *
905 * @return integer
906 */
907 function errno()
908 {
909 if ($this->connection_recent === null)
910 {
911 $this->errno = 0;
912 }
913 else
914 {
915 $this->errno = $this->functions['errno']($this->connection_recent);
916 }
917 return $this->errno;
918 }
919
920 /**
921 * Switches database error display ON
922 */
923 function show_errors()
924 {
925 $this->reporterror = true;
926 }
927
928 /**
929 * Switches database error display OFF
930 */
931 function hide_errors()
932 {
933 $this->reporterror = false;
934 }
935
936 /**
937 * Halts execution of the entire system and displays an error message
938 *
939 * @param string Text of the error message. Leave blank to use $this->sql as error text.
940 *
941 * @return integer
942 */
943 function halt($errortext = '')
944 {
945 global $vbulletin;
946
947 if ($this->connection_recent)
948 {
949 $this->error = $this->error($this->connection_recent);
950 $this->errno = $this->errno($this->connection_recent);
951 }
952
953 if ($this->reporterror)
954 {
955 if ($errortext == '')
956 {
957 $this->sql = "Invalid SQL:\r\n" . chop($this->sql) . ';';
958 $errortext =& $this->sql;
959 }
960
961 if (!headers_sent())
962 {
963 if (SAPI_NAME == 'cgi' OR SAPI_NAME == 'cgi-fcgi')
964 {
965 header('Status: 503 Service Unavailable');
966 }
967 else
968 {
969 header('HTTP/1.1 503 Service Unavailable');
970 }
971 }
972
973 $vboptions =& $vbulletin->options;
974 $technicalemail =& $vbulletin->config['Database']['technicalemail'];
975 $bbuserinfo =& $vbulletin->userinfo;
976 $requestdate = date('l, F jS Y @ h:i:s A', TIMENOW);
977 $date = date('l, F jS Y @ h:i:s A');
978 $scriptpath = str_replace('&', '&', $vbulletin->scriptpath);
979 $referer = REFERRER;
980 $ipaddress = IPADDRESS;
981 $classname = get_class($this);
982
983 if ($this->connection_recent)
984 {
985 $this->hide_errors();
986 list($mysqlversion) = $this->query_first("SELECT VERSION() AS version", DBARRAY_NUM);
987 $this->show_errors();
988 }
989
990 $display_db_error = (VB_AREA == 'Upgrade' OR VB_AREA == 'Install' OR $vbulletin->userinfo['usergroupid'] == 6 OR ($vbulletin->userinfo['permissions']['adminpermissions'] & $vbulletin->bf_ugp_adminpermissions));
991
992 // Hide the MySQL Version if its going in the source
993 if (!$display_db_error)
994 {
995 $mysqlversion = '';
996 }
997
998 eval('$message = "' . str_replace('"', '\"', file_get_contents(DIR . '/includes/database_error_message.html')) . '";');
999
1000 require_once(DIR . '/includes/functions_log_error.php');
1001 if (function_exists('log_vbulletin_error'))
1002 {
1003 log_vbulletin_error($message, 'database');
1004 }
1005
1006 if ($technicalemail != '' AND !$vbulletin->options['disableerroremail'] AND verify_email_vbulletin_error($this->errno, 'database'))
1007 {
1008 // If vBulletinHook is defined then we know that options are loaded, so we can then use vbmail
1009 if (class_exists('vBulletinHook'))
1010 {
1011 @vbmail($technicalemail, $this->appshortname . ' Database Error!', $message, true, $technicalemail);
1012 }
1013 else
1014 {
1015 @mail($technicalemail, $this->appshortname . ' Database Error!', preg_replace("#(\r\n|\r|\n)#s", (@ini_get('sendmail_path') === '') ? "\r\n" : "\n", $message), "From: $technicalemail");
1016 }
1017 }
1018
1019 if ($display_db_error)
1020 {
1021 // display error message on screen
1022 $message = '<form><textarea rows="15" cols="70" wrap="off" id="message">' . htmlspecialchars_uni($message) . '</textarea></form>';
1023 }
1024 else
1025 {
1026 // display hidden error message
1027 $message = "\r\n<!--\r\n" . htmlspecialchars_uni($message) . "\r\n-->\r\n";
1028 }
1029
1030 if ($vbulletin->options['bburl'])
1031 {
1032 $imagepath = $vbulletin->options['bburl'];
1033 }
1034 else
1035 {
1036 // this might not work with too many slashes in the archive
1037 $imagepath = (VB_AREA == 'Forum' ? '.' : '..');
1038 }
1039
1040 eval('$message = "' . str_replace('"', '\"', file_get_contents(DIR . '/includes/database_error_page.html')) . '";');
1041 // This is needed so IE doesn't show the pretty error messages
1042 $message .= str_repeat(' ', 512);
1043 die($message);
1044 }
1045 else if (!empty($errortext))
1046 {
1047 $this->error = $errortext;
1048 }
1049 }
1050}
1051
1052// #############################################################################
1053// MySQLi Database Class
1054
1055/**
1056* Class to interface with a MySQL 4.1 database
1057*
1058* This class also handles data replication between a master and slave(s) servers
1059*
1060* @package vBulletin
1061* @version $Revision: 43870 $
1062* @date $Date: 2011-05-26 14:52:39 -0700 (Thu, 26 May 2011) $
1063*/
1064class vB_Database_MySQLi extends vB_Database
1065{
1066 /**
1067 * Array of function names, mapping a simple name to the RDBMS specific function name
1068 *
1069 * @var array
1070 */
1071 var $functions = array(
1072 'connect' => 'mysqli_real_connect',
1073 'pconnect' => 'mysqli_real_connect', // mysqli doesn't support persistent connections THANK YOU!
1074 'select_db' => 'mysqli_select_db',
1075 'query' => 'mysqli_query',
1076 'query_unbuffered' => 'mysqli_unbuffered_query',
1077 'fetch_row' => 'mysqli_fetch_row',
1078 'fetch_array' => 'mysqli_fetch_array',
1079 'fetch_field' => 'mysqli_fetch_field',
1080 'free_result' => 'mysqli_free_result',
1081 'data_seek' => 'mysqli_data_seek',
1082 'error' => 'mysqli_error',
1083 'errno' => 'mysqli_errno',
1084 'affected_rows' => 'mysqli_affected_rows',
1085 'num_rows' => 'mysqli_num_rows',
1086 'num_fields' => 'mysqli_num_fields',
1087 'field_name' => 'mysqli_field_tell',
1088 'insert_id' => 'mysqli_insert_id',
1089 'escape_string' => 'mysqli_real_escape_string',
1090 'real_escape_string' => 'mysqli_real_escape_string',
1091 'close' => 'mysqli_close',
1092 'client_encoding' => 'mysqli_client_encoding',
1093 );
1094
1095 /**
1096 * Array of constants for use in fetch_array
1097 *
1098 * @var array
1099 */
1100 var $fetchtypes = array(
1101 DBARRAY_NUM => MYSQLI_NUM,
1102 DBARRAY_ASSOC => MYSQLI_ASSOC,
1103 DBARRAY_BOTH => MYSQLI_BOTH
1104 );
1105
1106 /**
1107 * Initialize database connection(s)
1108 *
1109 * Connects to the specified master database server, and also to the slave server if it is specified
1110 *
1111 * @param string Name of the database server - should be either 'localhost' or an IP address
1112 * @param integer Port of the database server - usually 3306
1113 * @param string Username to connect to the database server
1114 * @param string Password associated with the username for the database server
1115 * @param string Persistent Connections - Not supported with MySQLi
1116 * @param string Configuration file from config.php.ini (my.ini / my.cnf)
1117 * @param string Mysqli Connection Charset PHP 5.1.0+ or 5.0.5+ / MySQL 4.1.13+ or MySQL 5.1.10+ Only
1118 *
1119 * @return object Mysqli Resource
1120 */
1121 function db_connect($servername, $port, $username, $password, $usepconnect, $configfile = '', $charset = '')
1122 {
1123 if (function_exists('catch_db_error'))
1124 {
1125 set_error_handler('catch_db_error');
1126 }
1127
1128 $link = mysqli_init();
1129 # Set Options Connection Options
1130 if (!empty($configfile))
1131 {
1132 mysqli_options($link, MYSQLI_READ_DEFAULT_FILE, $configfile);
1133 }
1134
1135 // this will execute at most 5 times, see catch_db_error()
1136 do
1137 {
1138 $connect = $this->functions['connect']($link, $servername, $username, $password, '', $port);
1139 }
1140 while ($connect == false AND $this->reporterror);
1141
1142 restore_error_handler();
1143
1144 if (!empty($charset))
1145 {
1146 if (function_exists('mysqli_set_charset'))
1147 {
1148 mysqli_set_charset($link, $charset);
1149 }
1150 else
1151 {
1152 $this->sql = "SET NAMES $charset";
1153 $this->execute_query(true, $link);
1154 }
1155 }
1156
1157 return (!$connect) ? false : $link;
1158 }
1159
1160 /**
1161 * Executes an SQL query through the specified connection
1162 *
1163 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
1164 * @param string The connection ID to the database server
1165 *
1166 * @return string
1167 */
1168 function &execute_query($buffered = true, &$link)
1169 {
1170 $this->connection_recent =& $link;
1171 $this->querycount++;
1172
1173 if ($queryresult = mysqli_query($link, $this->sql, ($buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT)))
1174 {
1175 // unset $sql to lower memory .. this isn't an error, so it's not needed
1176 $this->sql = '';
1177
1178 return $queryresult;
1179 }
1180 else
1181 {
1182 $this->halt();
1183
1184 // unset $sql to lower memory .. error will have already been thrown
1185 $this->sql = '';
1186 }
1187 }
1188
1189 /**
1190 * Simple wrapper for select_db(), to allow argument order changes
1191 *
1192 * @param string Database name
1193 * @param integer Link identifier
1194 *
1195 * @return boolean
1196 */
1197 function select_db_wrapper($database = '', $link = null)
1198 {
1199 return $this->functions['select_db']($link, $database);
1200 }
1201
1202 /**
1203 * Escapes a string to make it safe to be inserted into an SQL query
1204 *
1205 * @param string The string to be escaped
1206 *
1207 * @return string
1208 */
1209 function escape_string($string)
1210 {
1211 return $this->functions['real_escape_string']($this->connection_master, $string);
1212 }
1213
1214 /**
1215 * Returns the name of a field from within a query result set
1216 *
1217 * @param string The query result ID we are dealing with
1218 * @param integer The index position of the field
1219 *
1220 * @return string
1221 */
1222 function field_name($queryresult, $index)
1223 {
1224 $field = @$this->functions['fetch_field']($queryresult);
1225 return $field->name;
1226 }
1227}
1228
1229// #############################################################################
1230// datastore class
1231
1232/**
1233* Class for fetching and initializing the vBulletin datastore from the database
1234*
1235* @package vBulletin
1236* @version $Revision: 43870 $
1237* @date $Date: 2011-05-26 14:52:39 -0700 (Thu, 26 May 2011) $
1238*/
1239class vB_Datastore
1240{
1241 /**
1242 * Default items that are always loaded by fetch();
1243 *
1244 * @var array
1245 */
1246 var $defaultitems = array(
1247 'options',
1248 'bitfields',
1249 'attachmentcache',
1250 'forumcache',
1251 'usergroupcache',
1252 'stylecache',
1253 'languagecache',
1254 'products',
1255 'pluginlist',
1256 'cron',
1257 'profilefield',
1258 'loadcache',
1259 'noticecache'
1260 );
1261
1262 /**
1263 * This variable contains a list of all items to be returned from the datastore
1264 *
1265 * @var array
1266 */
1267 var $itemarray = array();
1268
1269 /**
1270 * This variable should be set to be a reference to the registry object
1271 *
1272 * @var vB_Registry
1273 */
1274 var $registry = null;
1275
1276 /**
1277 * This variable should be set to be a reference to the database object
1278 *
1279 * @var vB_Database
1280 */
1281 var $dbobject = null;
1282
1283 /**
1284 * Unique prefix for item's title, required for multiple forums on the same server using the same classes that read/write to memory
1285 *
1286 * @var string
1287 */
1288 var $prefix = '';
1289
1290 /**
1291 * Constructor - establishes the database object to use for datastore queries
1292 *
1293 * @param vB_Registry The registry object
1294 * @param vB_Database The database object
1295 */
1296 function vB_Datastore(&$registry, &$dbobject)
1297 {
1298 $this->registry =& $registry;
1299 $this->dbobject =& $dbobject;
1300
1301 $this->prefix =& $this->registry->config['Datastore']['prefix'];
1302
1303 if (defined('SKIP_DEFAULTDATASTORE'))
1304 {
1305 $this->defaultitems = array('options', 'bitfields', 'pluginlist');
1306 }
1307
1308 if (!is_object($registry))
1309 {
1310 trigger_error('<strong>vB_Datastore</strong>: $this->registry is not an object', E_USER_ERROR);
1311 }
1312 if (!is_object($dbobject))
1313 {
1314 trigger_error('<strong>vB_Datastore</strong>: $this->dbobject is not an object!', E_USER_ERROR);
1315 }
1316 }
1317
1318 /**
1319 * Sorts the data returned from the cache and places it into appropriate places
1320 *
1321 * @param string The name of the data item to be processed
1322 * @param mixed The data associated with the title
1323 * @param integer If the data needs to be unserialized, 0 = no, 1 = yes, 2 = auto detect
1324 *
1325 * @return boolean
1326 */
1327 function register($title, $data, $unserialize_detect = 2)
1328 {
1329 // specifies whether or not $data should be an array
1330 $try_unserialize = (($unserialize_detect == 2) AND ($data[0] == 'a' AND $data[1] == ':'));
1331
1332 if ($try_unserialize OR $unserialize_detect == 1)
1333 {
1334 // unserialize returned an error so return false
1335 if (($data = unserialize($data)) === false)
1336 {
1337 return false;
1338 }
1339 }
1340
1341 if ($title == 'bitfields')
1342 {
1343 $registry =& $this->registry;
1344
1345 foreach (array_keys($data) AS $group)
1346 {
1347 $registry->{'bf_' . $group} =& $data["$group"];
1348
1349 $group_prefix = 'bf_' . $group . '_';
1350 $group_info =& $data["$group"];
1351
1352 foreach (array_keys($group_info) AS $subgroup)
1353 {
1354 $registry->{$group_prefix . $subgroup} =& $group_info["$subgroup"];
1355 }
1356 }
1357 }
1358 else if (!empty($title))
1359 {
1360 $this->registry->$title = $data;
1361 }
1362
1363 return true;
1364 }
1365
1366 /**
1367 * Fetches the contents of the datastore from the database
1368 *
1369 * @param array Array of items to fetch from the datastore
1370 *
1371 * @return void
1372 */
1373 function fetch($itemarray)
1374 {
1375 $db =& $this->dbobject;
1376
1377 $itemlist = "''";
1378
1379 foreach ($this->defaultitems AS $item)
1380 {
1381 $itemlist .= ",'" . $db->escape_string($item) . "'";
1382 }
1383
1384 if (is_array($itemarray))
1385 {
1386 foreach ($itemarray AS $item)
1387 {
1388 $itemlist .= ",'" . $db->escape_string($item) . "'";
1389 }
1390 }
1391
1392 $this->do_db_fetch($itemlist);
1393
1394 $this->check_options();
1395
1396 // set the version number variable
1397 $this->registry->versionnumber =& $this->registry->options['templateversion'];
1398 }
1399
1400 /**
1401 * Performs the actual fetching of the datastore items for the database, child classes may use this
1402 *
1403 * @param string title of the datastore item
1404 *
1405 * @return void
1406 */
1407 function do_db_fetch($itemlist)
1408 {
1409 $db =& $this->dbobject;
1410
1411 $dataitems = $db->query_read("
1412 SELECT *
1413 FROM " . TABLE_PREFIX . "datastore
1414 WHERE title IN ($itemlist)
1415 ");
1416 while ($dataitem = $db->fetch_array($dataitems))
1417 {
1418 $this->register($dataitem['title'], $dataitem['data'], (isset($dataitem['unserialize']) ? $dataitem['unserialize'] : 2));
1419 }
1420 $db->free_result($dataitems);
1421 }
1422
1423 /**
1424 * Checks that the options item has come out of the datastore correctly
1425 * and sets the 'versionnumber' variable
1426 */
1427 function check_options()
1428 {
1429 if (!isset($this->registry->options['templateversion']))
1430 {
1431 // fatal error - options not loaded correctly
1432 require_once(DIR . '/includes/adminfunctions.php');
1433 require_once(DIR . '/includes/functions.php');
1434 $this->register('options', build_options(), 0);
1435 }
1436
1437 // set the short version number
1438 $this->registry->options['simpleversion'] = SIMPLE_VERSION . $this->registry->config['Misc']['jsver'];
1439 }
1440}
1441
1442// #############################################################################
1443// input handler class
1444
1445/**#@+
1446* Ways of cleaning input. Should be mostly self-explanatory.
1447*/
1448define('TYPE_NOCLEAN', 0); // no change
1449
1450define('TYPE_BOOL', 1); // force boolean
1451define('TYPE_INT', 2); // force integer
1452define('TYPE_UINT', 3); // force unsigned integer
1453define('TYPE_NUM', 4); // force number
1454define('TYPE_UNUM', 5); // force unsigned number
1455define('TYPE_UNIXTIME', 6); // force unix datestamp (unsigned integer)
1456define('TYPE_STR', 7); // force trimmed string
1457define('TYPE_NOTRIM', 8); // force string - no trim
1458define('TYPE_NOHTML', 9); // force trimmed string with HTML made safe
1459define('TYPE_ARRAY', 10); // force array
1460define('TYPE_FILE', 11); // force file
1461define('TYPE_BINARY', 12); // force binary string
1462define('TYPE_NOHTMLCOND', 13); // force trimmed string with HTML made safe if determined to be unsafe
1463
1464define('TYPE_ARRAY_BOOL', 101);
1465define('TYPE_ARRAY_INT', 102);
1466define('TYPE_ARRAY_UINT', 103);
1467define('TYPE_ARRAY_NUM', 104);
1468define('TYPE_ARRAY_UNUM', 105);
1469define('TYPE_ARRAY_UNIXTIME', 106);
1470define('TYPE_ARRAY_STR', 107);
1471define('TYPE_ARRAY_NOTRIM', 108);
1472define('TYPE_ARRAY_NOHTML', 109);
1473define('TYPE_ARRAY_ARRAY', 110);
1474define('TYPE_ARRAY_FILE', 11); // An array of "Files" behaves differently than other <input> arrays. TYPE_FILE handles both types.
1475define('TYPE_ARRAY_BINARY', 112);
1476define('TYPE_ARRAY_NOHTMLCOND',113);
1477
1478define('TYPE_ARRAY_KEYS_INT', 202);
1479define('TYPE_ARRAY_KEYS_STR', 207);
1480
1481define('TYPE_CONVERT_SINGLE', 100); // value to subtract from array types to convert to single types
1482define('TYPE_CONVERT_KEYS', 200); // value to subtract from array => keys types to convert to single types
1483/**#@-*/
1484
1485// temporary
1486define('INT', TYPE_INT);
1487define('STR', TYPE_STR);
1488define('STR_NOHTML', TYPE_NOHTML);
1489define('FILE', TYPE_FILE);
1490
1491/**
1492* Class to handle and sanitize variables from GET, POST and COOKIE etc
1493*
1494* @package vBulletin
1495* @version $Revision: 43870 $
1496* @date $Date: 2011-05-26 14:52:39 -0700 (Thu, 26 May 2011) $
1497*/
1498class vB_Input_Cleaner
1499{
1500 /**
1501 * Translation table for short name to long name
1502 *
1503 * @var array
1504 */
1505 var $shortvars = array(
1506 'f' => 'forumid',
1507 't' => 'threadid',
1508 'p' => 'postid',
1509 'u' => 'userid',
1510 'a' => 'announcementid',
1511 'c' => 'calendarid',
1512 'e' => 'eventid',
1513 'q' => 'query',
1514 'pp' => 'perpage',
1515 'page' => 'pagenumber',
1516 'sort' => 'sortfield',
1517 'order' => 'sortorder',
1518 );
1519
1520 /**
1521 * Translation table for short superglobal name to long superglobal name
1522 *
1523 * @var array
1524 */
1525 var $superglobal_lookup = array(
1526 'g' => '_GET',
1527 'p' => '_POST',
1528 'r' => '_REQUEST',
1529 'c' => '_COOKIE',
1530 's' => '_SERVER',
1531 'e' => '_ENV',
1532 'f' => '_FILES'
1533 );
1534
1535 /**
1536 * System state. The complete URL of the current page, without sessionhash
1537 *
1538 * @var string
1539 */
1540 var $scriptpath = '';
1541
1542 /**
1543 * Reload URL. Complete URL of the current page including sessionhash
1544 *
1545 * @var string
1546 */
1547 var $reloadurl = '';
1548
1549 /**
1550 * System state. The complete URL of the page for Who's Online purposes
1551 *
1552 * @var string
1553 */
1554 var $wolpath = '';
1555
1556 /**
1557 * System state. The complete URL of the referring page
1558 *
1559 * @var string
1560 */
1561 var $url = '';
1562
1563 /**
1564 * System state. The IP address of the current visitor
1565 *
1566 * @var string
1567 */
1568 var $ipaddress = '';
1569
1570 /**
1571 * System state. An attempt to find a second IP for the current visitor (proxy etc)
1572 *
1573 * @var string
1574 */
1575 var $alt_ip = '';
1576
1577 /**
1578 * A reference to the main registry object
1579 *
1580 * @var vB_Registry
1581 */
1582 var $registry = null;
1583
1584 /**
1585 * Keep track of variables that have already been cleaned
1586 *
1587 * @var array
1588 */
1589 var $cleaned_vars = array();
1590
1591 /**
1592 * Constructor
1593 *
1594 * First, reverses the effects of magic quotes on GPC
1595 * Second, translates short variable names to long (u --> userid)
1596 * Third, deals with $_COOKIE[userid] conflicts
1597 *
1598 * @param vB_Registry The instance of the vB_Registry object
1599 */
1600 function vB_Input_Cleaner(&$registry)
1601 {
1602 $this->registry =& $registry;
1603
1604 if (!is_array($GLOBALS))
1605 {
1606 die('<strong>Fatal Error:</strong> Invalid URL.');
1607 }
1608
1609 // overwrite GET[x] and REQUEST[x] with POST[x] if it exists (overrides server's GPC order preference)
1610 if ($_SERVER['REQUEST_METHOD'] == 'POST')
1611 {
1612 foreach (array_keys($_POST) AS $key)
1613 {
1614 if (isset($_GET["$key"]))
1615 {
1616 $_GET["$key"] = $_REQUEST["$key"] = $_POST["$key"];
1617 }
1618 }
1619 }
1620
1621 // deal with session bypass situation
1622 if (!defined('SESSION_BYPASS'))
1623 {
1624 define('SESSION_BYPASS', !empty($_REQUEST['bypass']));
1625 }
1626
1627 // reverse the effects of magic quotes if necessary
1628 if (function_exists('get_magic_quotes_gpc') AND get_magic_quotes_gpc())
1629 {
1630 $this->stripslashes_deep($_REQUEST); // needed for some reason (at least on php5 - not tested on php4)
1631 $this->stripslashes_deep($_GET);
1632 $this->stripslashes_deep($_POST);
1633 $this->stripslashes_deep($_COOKIE);
1634
1635 if (is_array($_FILES))
1636 {
1637 foreach ($_FILES AS $key => $val)
1638 {
1639 $_FILES["$key"]['tmp_name'] = str_replace('\\', '\\\\', $val['tmp_name']);
1640 }
1641 $this->stripslashes_deep($_FILES);
1642 }
1643 }
1644 set_magic_quotes_runtime(0);
1645 @ini_set('magic_quotes_sybase', 0);
1646
1647 foreach (array('_GET', '_POST') AS $arrayname)
1648 {
1649 if (isset($GLOBALS["$arrayname"]['do']))
1650 {
1651 $GLOBALS["$arrayname"]['do'] = trim($GLOBALS["$arrayname"]['do']);
1652 }
1653
1654 $this->convert_shortvars($GLOBALS["$arrayname"]);
1655 }
1656
1657 // set the AJAX flag if we have got an AJAX submission
1658 if ($_SERVER['REQUEST_METHOD'] == 'POST' AND $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
1659 {
1660 $_POST['ajax'] = $_REQUEST['ajax'] = 1;
1661 }
1662
1663 // reverse the effects of register_globals if necessary
1664 if (@ini_get('register_globals') OR !@ini_get('gpc_order'))
1665 {
1666 foreach ($this->superglobal_lookup AS $arrayname)
1667 {
1668 $registry->superglobal_size["$arrayname"] = sizeof($GLOBALS["$arrayname"]);
1669
1670 foreach (array_keys($GLOBALS["$arrayname"]) AS $varname)
1671 {
1672 // make sure we dont unset any global arrays like _SERVER
1673 if (!in_array($varname, $this->superglobal_lookup))
1674 {
1675 unset($GLOBALS["$varname"]);
1676 }
1677 }
1678 }
1679 }
1680 else
1681 {
1682 foreach ($this->superglobal_lookup AS $arrayname)
1683 {
1684 $registry->superglobal_size["$arrayname"] = sizeof($GLOBALS["$arrayname"]);
1685 }
1686 }
1687
1688 // deal with cookies that may conflict with _GET and _POST data, and create our own _REQUEST with no _COOKIE input
1689 foreach (array_keys($_COOKIE) AS $varname)
1690 {
1691 unset($_REQUEST["$varname"]);
1692 if (isset($_POST["$varname"]))
1693 {
1694 $_REQUEST["$varname"] =& $_POST["$varname"];
1695 }
1696 else if (isset($_GET["$varname"]))
1697 {
1698 $_REQUEST["$varname"] =& $_GET["$varname"];
1699 }
1700 }
1701
1702 // fetch client IP address
1703 $registry->ipaddress = $this->fetch_ip();
1704 define('IPADDRESS', $registry->ipaddress);
1705
1706 // attempt to fetch IP address from behind proxies - useful, but don't rely on it...
1707 $registry->alt_ip = $this->fetch_alt_ip();
1708 define('ALT_IP', $registry->alt_ip);
1709
1710 // defines if the current page was visited via SSL or not
1711 define('REQ_PROTOCOL', (($_SERVER['HTTPS'] == 'on' OR $_SERVER['HTTPS'] == '1') ? 'https' : 'http'));
1712
1713 // fetch complete url of current page
1714 $registry->scriptpath = $this->fetch_scriptpath();
1715 define('SCRIPTPATH', $registry->scriptpath);
1716
1717 // fetch url of current page without the variable string
1718 $quest_pos = strpos($registry->scriptpath, '?');
1719 if ($quest_pos !== false)
1720 {
1721 $registry->script = substr($registry->scriptpath, 0, $quest_pos);
1722 }
1723 else
1724 {
1725 $registry->script = $registry->scriptpath;
1726 }
1727 define('SCRIPT', $registry->script);
1728
1729 // fetch url of current page for Who's Online
1730 $registry->wolpath = $this->fetch_wolpath();
1731 define('WOLPATH', $registry->wolpath);
1732
1733 // define session constants
1734 define('SESSION_HOST', substr($registry->ipaddress, 0, 15));
1735
1736 // define some useful contants related to environment
1737 define('USER_AGENT', $_SERVER['HTTP_USER_AGENT']);
1738 define('REFERRER', $_SERVER['HTTP_REFERER']);
1739 }
1740
1741 /**
1742 * Makes data in an array safe to use
1743 *
1744 * @param array The source array containing the data to be cleaned
1745 * @param array Array of variable names and types we want to extract from the source array
1746 *
1747 * @return array
1748 */
1749 function &clean_array(&$source, $variables)
1750 {
1751 $return = array();
1752
1753 foreach ($variables AS $varname => $vartype)
1754 {
1755 $return["$varname"] =& $this->clean($source["$varname"], $vartype, isset($source["$varname"]));
1756 }
1757
1758 return $return;
1759 }
1760
1761 /**
1762 * Makes GPC variables safe to use
1763 *
1764 * @param string Either, g, p, c, r or f (corresponding to get, post, cookie, request and files)
1765 * @param array Array of variable names and types we want to extract from the source array
1766 *
1767 * @return array
1768 */
1769 function clean_array_gpc($source, $variables)
1770 {
1771 $sg =& $GLOBALS[$this->superglobal_lookup["$source"]];
1772
1773 foreach ($variables AS $varname => $vartype)
1774 {
1775 // clean a variable only once unless its a different type
1776 if (!isset($this->cleaned_vars["$varname"]) OR $this->cleaned_vars["$varname"] != $vartype)
1777 {
1778 $this->registry->GPC_exists["$varname"] = isset($sg["$varname"]);
1779 $this->registry->GPC["$varname"] =& $this->clean(
1780 $sg["$varname"],
1781 $vartype,
1782 isset($sg["$varname"])
1783 );
1784 $this->cleaned_vars["$varname"] = $vartype;
1785 }
1786 }
1787 }
1788
1789 /**
1790 * Makes a single GPC variable safe to use and returns it
1791 *
1792 * @param array The source array containing the data to be cleaned
1793 * @param string The name of the variable in which we are interested
1794 * @param integer The type of the variable in which we are interested
1795 *
1796 * @return mixed
1797 */
1798 function &clean_gpc($source, $varname, $vartype = TYPE_NOCLEAN)
1799 {
1800 // clean a variable only once unless its a different type
1801 if (!isset($this->cleaned_vars["$varname"]) OR $this->cleaned_vars["$varname"] != $vartype)
1802 {
1803 $sg =& $GLOBALS[$this->superglobal_lookup["$source"]];
1804
1805 $this->registry->GPC_exists["$varname"] = isset($sg["$varname"]);
1806 $this->registry->GPC["$varname"] =& $this->clean(
1807 $sg["$varname"],
1808 $vartype,
1809 isset($sg["$varname"])
1810 );
1811 $this->cleaned_vars["$varname"] = $vartype;
1812 }
1813
1814 return $this->registry->GPC["$varname"];
1815 }
1816
1817 /**
1818 * Makes a single variable safe to use and returns it
1819 *
1820 * @param mixed The variable to be cleaned
1821 * @param integer The type of the variable in which we are interested
1822 * @param boolean Whether or not the variable to be cleaned actually is set
1823 *
1824 * @return mixed The cleaned value
1825 */
1826 function &clean(&$var, $vartype = TYPE_NOCLEAN, $exists = true)
1827 {
1828 if ($exists)
1829 {
1830 if ($vartype < TYPE_CONVERT_SINGLE)
1831 {
1832 $this->do_clean($var, $vartype);
1833 }
1834 else if (is_array($var))
1835 {
1836 if ($vartype >= TYPE_CONVERT_KEYS)
1837 {
1838 $var = array_keys($var);
1839 $vartype -= TYPE_CONVERT_KEYS;
1840 }
1841 else
1842 {
1843 $vartype -= TYPE_CONVERT_SINGLE;
1844 }
1845
1846 foreach (array_keys($var) AS $key)
1847 {
1848 $this->do_clean($var["$key"], $vartype);
1849 }
1850 }
1851 else
1852 {
1853 $var = array();
1854 }
1855 return $var;
1856 }
1857 else
1858 {
1859 if ($vartype < TYPE_CONVERT_SINGLE)
1860 {
1861 switch ($vartype)
1862 {
1863 case TYPE_INT:
1864 case TYPE_UINT:
1865 case TYPE_NUM:
1866 case TYPE_UNUM:
1867 case TYPE_UNIXTIME:
1868 {
1869 $var = 0;
1870 break;
1871 }
1872 case TYPE_STR:
1873 case TYPE_NOHTML:
1874 case TYPE_NOTRIM:
1875 case TYPE_NOHTMLCOND:
1876 {
1877 $var = '';
1878 break;
1879 }
1880 case TYPE_BOOL:
1881 {
1882 $var = 0;
1883 break;
1884 }
1885 case TYPE_ARRAY:
1886 case TYPE_FILE:
1887 {
1888 $var = array();
1889 break;
1890 }
1891 case TYPE_NOCLEAN:
1892 {
1893 $var = null;
1894 break;
1895 }
1896 default:
1897 {
1898 $var = null;
1899 }
1900 }
1901 }
1902 else
1903 {
1904 $var = array();
1905 }
1906
1907 return $var;
1908 }
1909 }
1910
1911 /**
1912 * Does the actual work to make a variable safe
1913 *
1914 * @param mixed The data we want to make safe
1915 * @param integer The type of the data
1916 *
1917 * @return mixed
1918 */
1919 function &do_clean(&$data, $type)
1920 {
1921 static $booltypes = array('1', 'yes', 'y', 'true');
1922
1923 switch ($type)
1924 {
1925 case TYPE_INT: $data = intval($data); break;
1926 case TYPE_UINT: $data = ($data = intval($data)) < 0 ? 0 : $data; break;
1927 case TYPE_NUM: $data = strval($data) + 0; break;
1928 case TYPE_UNUM: $data = strval($data) + 0;
1929 $data = ($data < 0) ? 0 : $data; break;
1930 case TYPE_BINARY: $data = strval($data); break;
1931 case TYPE_STR: $data = trim(strval($data)); break;
1932 case TYPE_NOTRIM: $data = strval($data); break;
1933 case TYPE_NOHTML: $data = htmlspecialchars_uni(trim(strval($data))); break;
1934 case TYPE_BOOL: $data = in_array(strtolower($data), $booltypes) ? 1 : 0; break;
1935 case TYPE_ARRAY: $data = (is_array($data)) ? $data : array(); break;
1936 case TYPE_NOHTMLCOND:
1937 {
1938 $data = trim(strval($data));
1939 if (strcspn($data, '<>"') < strlen($data) OR (strpos($data, '&') !== false AND !preg_match('/&(#[0-9]+|amp|lt|gt|quot);/si', $data)))
1940 {
1941 // data is not htmlspecialchars because it still has characters or entities it shouldn't
1942 $data = htmlspecialchars_uni($data);
1943 }
1944 break;
1945 }
1946 case TYPE_FILE:
1947 {
1948 // perhaps redundant :p
1949 if (is_array($data))
1950 {
1951 if (is_array($data['name']))
1952 {
1953 $files = count($data['name']);
1954 for ($index = 0; $index < $files; $index++)
1955 {
1956 $data['name']["$index"] = trim(strval($data['name']["$index"]));
1957 $data['type']["$index"] = trim(strval($data['type']["$index"]));
1958 $data['tmp_name']["$index"] = trim(strval($data['tmp_name']["$index"]));
1959 $data['error']["$index"] = intval($data['error']["$index"]);
1960 $data['size']["$index"] = intval($data['size']["$index"]);
1961 }
1962 }
1963 else
1964 {
1965 $data['name'] = trim(strval($data['name']));
1966 $data['type'] = trim(strval($data['type']));
1967 $data['tmp_name'] = trim(strval($data['tmp_name']));
1968 $data['error'] = intval($data['error']);
1969 $data['size'] = intval($data['size']);
1970 }
1971 }
1972 else
1973 {
1974 $data = array(
1975 'name' => '',
1976 'type' => '',
1977 'tmp_name' => '',
1978 'error' => 0,
1979 'size' => 4, // UPLOAD_ERR_NO_FILE
1980 );
1981 }
1982 break;
1983 }
1984 case TYPE_UNIXTIME:
1985 {
1986 if (is_array($data))
1987 {
1988 $data = $this->clean($data, TYPE_ARRAY_UINT);
1989 if ($data['month'] AND $data['day'] AND $data['year'])
1990 {
1991 require_once(DIR . '/includes/functions_misc.php');
1992 $data = vbmktime($data['hour'], $data['minute'], $data['second'], $data['month'], $data['day'], $data['year']);
1993 }
1994 else
1995 {
1996 $data = 0;
1997 }
1998 }
1999 else
2000 {
2001 $data = ($data = intval($data)) < 0 ? 0 : $data;
2002 }
2003 break;
2004 }
2005 // null actions should be deifned here so we can still catch typos below
2006 case TYPE_NOCLEAN:
2007 {
2008 break;
2009 }
2010
2011 default:
2012 {
2013 if ($this->registry->debug)
2014 {
2015 trigger_error('vB_Input_Cleaner::do_clean() Invalid data type specified', E_USER_WARNING);
2016 }
2017 }
2018 }
2019
2020 // strip out characters that really have no business being in non-binary data
2021 switch ($type)
2022 {
2023 case TYPE_STR:
2024 case TYPE_NOTRIM:
2025 case TYPE_NOHTML:
2026 case TYPE_NOHTMLCOND:
2027 $data = str_replace(chr(0), '', $data);
2028 }
2029
2030 return $data;
2031 }
2032
2033 /**
2034 * Removes HTML characters and potentially unsafe scripting words from a string
2035 *
2036 * @param string The variable we want to make safe
2037 *
2038 * @return string
2039 */
2040 function xss_clean($var)
2041 {
2042 static
2043 $preg_find = array('#^javascript#i', '#^vbscript#i'),
2044 $preg_replace = array('java script', 'vb script');
2045
2046 return preg_replace($preg_find, $preg_replace, htmlspecialchars(trim($var)));
2047 }
2048
2049 /**
2050 * Reverses the effects of magic_quotes on an entire array of variables
2051 *
2052 * @param array The array on which we want to work
2053 */
2054 function stripslashes_deep(&$value, $depth = 0)
2055 {
2056 if (is_array($value))
2057 {
2058 foreach ($value AS $key => $val)
2059 {
2060 if (is_string($val))
2061 {
2062 $value["$key"] = stripslashes($val);
2063 }
2064 else if (is_array($val) AND $depth < 10)
2065 {
2066 $this->stripslashes_deep($value["$key"], $depth + 1);
2067 }
2068 }
2069 }
2070 }
2071
2072 /**
2073 * Turns $_POST['t'] into $_POST['threadid'] etc.
2074 *
2075 * @param array The name of the array
2076 */
2077 function convert_shortvars(&$array)
2078 {
2079 // extract long variable names from short variable names
2080 foreach ($this->shortvars AS $shortname => $longname)
2081 {
2082 if (isset($array["$shortname"]) AND !isset($array["$longname"]))
2083 {
2084 $array["$longname"] =& $array["$shortname"];
2085 $GLOBALS['_REQUEST']["$longname"] =& $array["$shortname"];
2086 }
2087 }
2088 }
2089
2090 /**
2091 * Strips out the s=gobbledygook& rubbish from URLs
2092 *
2093 * @param string The URL string from which to remove the session stuff
2094 *
2095 * @return string
2096 */
2097 function strip_sessionhash($string)
2098 {
2099 $string = preg_replace('/(s|sessionhash)=[a-z0-9]{32}?&?/', '', $string);
2100 return $string;
2101 }
2102
2103 /**
2104 * Fetches the 'scriptpath' variable - ie: the URI of the current page
2105 *
2106 * @return string
2107 */
2108 function fetch_scriptpath()
2109 {
2110 if ($this->registry->scriptpath != '')
2111 {
2112 return $this->registry->scriptpath;
2113 }
2114 else
2115 {
2116 if ($_SERVER['REQUEST_URI'] OR $_ENV['REQUEST_URI'])
2117 {
2118 $scriptpath = $_SERVER['REQUEST_URI'] ? $_SERVER['REQUEST_URI'] : $_ENV['REQUEST_URI'];
2119 }
2120 else
2121 {
2122 if ($_SERVER['PATH_INFO'] OR $_ENV['PATH_INFO'])
2123 {
2124 $scriptpath = $_SERVER['PATH_INFO'] ? $_SERVER['PATH_INFO'] : $_ENV['PATH_INFO'];
2125 }
2126 else if ($_SERVER['REDIRECT_URL'] OR $_ENV['REDIRECT_URL'])
2127 {
2128 $scriptpath = $_SERVER['REDIRECT_URL'] ? $_SERVER['REDIRECT_URL'] : $_ENV['REDIRECT_URL'];
2129 }
2130 else
2131 {
2132 $scriptpath = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF'];
2133 }
2134
2135 if ($_SERVER['QUERY_STRING'] OR $_ENV['QUERY_STRING'])
2136 {
2137 $scriptpath .= '?' . ($_SERVER['QUERY_STRING'] ? $_SERVER['QUERY_STRING'] : $_ENV['QUERY_STRING']);
2138 }
2139 }
2140
2141 // in the future we should set $registry->script here too
2142 $quest_pos = strpos($scriptpath, '?');
2143 if ($quest_pos !== false)
2144 {
2145 $script = urldecode(substr($scriptpath, 0, $quest_pos));
2146 $scriptpath = $script . substr($scriptpath, $quest_pos);
2147 }
2148 else
2149 {
2150 $scriptpath = urldecode($scriptpath);
2151 }
2152
2153 // store a version that includes the sessionhash
2154 $this->registry->reloadurl = $this->xss_clean($scriptpath);
2155
2156 $scriptpath = $this->strip_sessionhash($scriptpath);
2157 $scriptpath = $this->xss_clean($scriptpath);
2158 $this->registry->scriptpath = $scriptpath;
2159
2160 return $scriptpath;
2161 }
2162 }
2163
2164 /**
2165 * Fetches the 'wolpath' variable - ie: the same as 'scriptpath' but with a handler for the POST request method
2166 *
2167 * @return string
2168 */
2169 function fetch_wolpath()
2170 {
2171 $wolpath = $this->fetch_scriptpath();
2172
2173 if ($_SERVER['REQUEST_METHOD'] == 'POST')
2174 {
2175 // Tag the variables back on to the filename if we are coming from POST so that WOL can access them.
2176 $tackon = '';
2177
2178 if (is_array($_POST))
2179 {
2180 foreach ($_POST AS $varname => $value)
2181 {
2182 switch ($varname)
2183 {
2184 case 'forumid':
2185 case 'threadid':
2186 case 'postid':
2187 case 'userid':
2188 case 'eventid':
2189 case 'calendarid':
2190 case 'do':
2191 case 'method': // postings.php
2192 case 'dowhat': // private.php
2193 {
2194 $tackon .= ($tackon == '' ? '' : '&') . $varname . '=' . $value;
2195 break;
2196 }
2197 }
2198 }
2199 }
2200 if ($tackon != '')
2201 {
2202 $wolpath .= (strpos($wolpath, '?') !== false ? '&' : '?') . "$tackon";
2203 }
2204 }
2205
2206 return $wolpath;
2207 }
2208
2209 /**
2210 * Fetches the 'url' variable - usually the URL of the previous page in the history
2211 *
2212 * @return string
2213 */
2214 function fetch_url()
2215 {
2216 $temp_url = $_REQUEST['url'];
2217
2218 $scriptpath = $this->fetch_scriptpath();
2219
2220 if (empty($temp_url))
2221 {
2222 $url = $_SERVER['HTTP_REFERER'];
2223 }
2224 else
2225 {
2226 if ($temp_url == $_SERVER['HTTP_REFERER'])
2227 {
2228 $url = 'index.php';
2229 }
2230 else
2231 {
2232 $url = $temp_url;
2233 }
2234 }
2235
2236 if ($url == $scriptpath OR empty($url))
2237 {
2238 $url = 'index.php';
2239 }
2240
2241 // if $url is set to forum home page, check it against options
2242 if ($url == 'index.php' AND $this->registry->options['forumhome'] != 'index')
2243 {
2244 $url = $this->registry->options['forumhome'] . '.php';
2245 }
2246
2247 $url = $this->xss_clean($url);
2248
2249 return $url;
2250 }
2251
2252 /**
2253 * Fetches the IP address of the current visitor
2254 *
2255 * @return string
2256 */
2257 function fetch_ip()
2258 {
2259 return $_SERVER['REMOTE_ADDR'];
2260 }
2261
2262 /**
2263 * Fetches an alternate IP address of the current visitor, attempting to detect proxies etc.
2264 *
2265 * @return string
2266 */
2267 function fetch_alt_ip()
2268 {
2269 $alt_ip = $_SERVER['REMOTE_ADDR'];
2270
2271 if (isset($_SERVER['HTTP_CLIENT_IP']))
2272 {
2273 $alt_ip = $_SERVER['HTTP_CLIENT_IP'];
2274 }
2275 else if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches))
2276 {
2277 // try to avoid using an internal IP address, its probably a proxy
2278 $ranges = array(
2279 '10.0.0.0/8' => array(ip2long('10.0.0.0'), ip2long('10.255.255.255')),
2280 '127.0.0.0/8' => array(ip2long('127.0.0.0'), ip2long('127.255.255.255')),
2281 '169.254.0.0/16' => array(ip2long('169.254.0.0'), ip2long('169.254.255.255')),
2282 '172.16.0.0/12' => array(ip2long('172.16.0.0'), ip2long('172.31.255.255')),
2283 '192.168.0.0/16' => array(ip2long('192.168.0.0'), ip2long('192.168.255.255')),
2284 );
2285 foreach ($matches[0] AS $ip)
2286 {
2287 $ip_long = ip2long($ip);
2288 if ($ip_long === false OR $ip_long == -1)
2289 {
2290 continue;
2291 }
2292
2293 $private_ip = false;
2294 foreach ($ranges AS $range)
2295 {
2296 if ($ip_long >= $range[0] AND $ip_long <= $range[1])
2297 {
2298 $private_ip = true;
2299 break;
2300 }
2301 }
2302
2303 if (!$private_ip)
2304 {
2305 $alt_ip = $ip;
2306 break;
2307 }
2308 }
2309 }
2310 else if (isset($_SERVER['HTTP_FROM']))
2311 {
2312 $alt_ip = $_SERVER['HTTP_FROM'];
2313 }
2314
2315 return $alt_ip;
2316 }
2317}
2318
2319// #############################################################################
2320// data registry class
2321
2322/**
2323* Class to store commonly-used variables
2324*
2325* @package vBulletin
2326* @version $Revision: 43870 $
2327* @date $Date: 2011-05-26 14:52:39 -0700 (Thu, 26 May 2011) $
2328*/
2329class vB_Registry
2330{
2331 // general objects
2332 /**
2333 * Datastore object.
2334 *
2335 * @var vB_Datastore
2336 */
2337 var $datastore;
2338
2339 /**
2340 * Input cleaner object.
2341 *
2342 * @var vB_Input_Cleaner
2343 */
2344 var $input;
2345
2346 /**
2347 * Database object.
2348 *
2349 * @var vB_Database
2350 */
2351 var $db;
2352
2353 // user/session related
2354 /**
2355 * Array of info about the current browsing user. In the case of a registered
2356 * user, this will be results of fetch_userinfo(). A guest will have slightly
2357 * different entries.
2358 *
2359 * @var array
2360 */
2361 var $userinfo;
2362
2363 /**
2364 * Session object.
2365 *
2366 * @var vB_Session
2367 */
2368 var $session;
2369
2370 /**
2371 * Array of do actions that are exempt from checks
2372 *
2373 * @var array
2374 */
2375 var $csrf_skip_list = array();
2376
2377 // configuration
2378 /**
2379 * Array of data from config.php.
2380 *
2381 * @var array
2382 */
2383 var $config;
2384
2385 // GPC input
2386 /**
2387 * Array of data that has been cleaned by the input cleaner.
2388 *
2389 * @var array
2390 */
2391 var $GPC = array();
2392
2393 /**
2394 * Array of booleans. When cleaning a variable, you often lose the ability
2395 * to determine if it was specified in the user's input. Entries in this
2396 * array are true if the variable existed before cleaning.
2397 *
2398 * @var array
2399 */
2400 var $GPC_exists = array();
2401
2402 /**
2403 * The size of the super global arrays.
2404 *
2405 * @var array
2406 */
2407 var $superglobal_size = array();
2408
2409 // single variables
2410 /**
2411 * IP Address of the current browsing user.
2412 *
2413 * @var string
2414 */
2415 var $ipaddress;
2416
2417 /**
2418 * Alternate IP for the browsing user. This attempts to use various HTTP headers
2419 * to find the real IP of a user that may be behind a proxy.
2420 *
2421 * @var string
2422 */
2423 var $alt_ip;
2424
2425 /**
2426 * The URL of the currently browsed page.
2427 *
2428 * @var string
2429 */
2430 var $scriptpath;
2431
2432 /**
2433 * Similar to the URL of the current page, but expands some items and includes
2434 * data submitted via POST. Used for Who's Online purposes.
2435 *
2436 * @var string
2437 */
2438 var $wolpath;
2439
2440 /**
2441 * The URL of the current page, without anything after the '?'.
2442 *
2443 * @var string
2444 */
2445 var $script;
2446
2447 /**
2448 * Generally the URL of the referring page if there is one, though it is often
2449 * set in various places of the code. Used to determine the page to redirect
2450 * to, if necessary.
2451 *
2452 * @var string
2453 */
2454 var $url;
2455
2456 // usergroup permission bitfields
2457 /**#@+
2458 * Bitfield arrays for usergroup permissions.
2459 *
2460 * @var array
2461 */
2462 var $bf_ugp;
2463 // $bf_ugp_x is a reference to $bf_ugp['x']
2464 var $bf_ugp_adminpermissions;
2465 var $bf_ugp_calendarpermissions;
2466 var $bf_ugp_forumpermissions;
2467 var $bf_ugp_genericoptions;
2468 var $bf_ugp_genericpermissions;
2469 var $bf_ugp_pmpermissions;
2470 var $bf_ugp_wolpermissions;
2471 var $bf_ugp_visitormessagepermissions;
2472 /**#@-*/
2473
2474 // misc bitfield arrays
2475 /**#@+
2476 * Bitfield arrays for miscellaneous permissions and options.
2477 *
2478 * @var array
2479 */
2480 var $bf_misc;
2481 // $bf_misc_x is a reference to $bf_misc['x']
2482 var $bf_misc_calmoderatorpermissions;
2483 var $bf_misc_forumoptions;
2484 var $bf_misc_intperms;
2485 var $bf_misc_languageoptions;
2486 var $bf_misc_moderatorpermissions;
2487 var $bf_misc_useroptions;
2488 var $bf_misc_hvcheck;
2489 /**#@-*/
2490
2491 /**#@+
2492 * Results for specific entries in the datastore.
2493 *
2494 * @var mixed Mixed, though mostly arrays.
2495 */
2496 var $options = null;
2497 var $attachmentcache = null;
2498 var $avatarcache = null;
2499 var $birthdaycache = null;
2500 var $eventcache = null;
2501 var $forumcache = null;
2502 var $iconcache = null;
2503 var $markupcache = null;
2504 var $stylecache = null;
2505 var $languagecache = null;
2506 var $smiliecache = null;
2507 var $usergroupcache = null;
2508 var $bbcodecache = null;
2509 var $socialsitecache = null;
2510 var $cron = null;
2511 var $mailqueue = null;
2512 var $banemail = null;
2513 var $maxloggedin = null;
2514 var $pluginlist = null;
2515 var $products = null;
2516 var $ranks = null;
2517 var $statement = null;
2518 var $userstats = null;
2519 var $wol_spiders = null;
2520 var $loadcache = null;
2521 var $noticecache = null;
2522 var $prefixcache = null;
2523 /**#@-*/
2524
2525 /**#@+
2526 * Miscellaneous variables
2527 *
2528 * @var mixed
2529 */
2530 var $bbcode_style = array('code' => -1, 'html' => -1, 'php' => -1, 'quote' => -1);
2531 var $templatecache = array();
2532 var $iforumcache = array();
2533 var $versionnumber;
2534 var $nozip;
2535 var $debug;
2536 var $noheader;
2537 var $shutdown;
2538 /**#@-*/
2539
2540 /**
2541 * Constructor - initializes the nozip system,
2542 * and calls and instance of the vB_Input_Cleaner class
2543 */
2544 function vB_Registry()
2545 {
2546 // variable to allow bypassing of gzip compression
2547 $this->nozip = defined('NOZIP') ? true : (@ini_get('zlib.output_compression') ? true : false);
2548 // variable that controls HTTP header output
2549 $this->noheader = defined('NOHEADER') ? true : false;
2550
2551 // initialize the input handler
2552 $this->input =& new vB_Input_Cleaner($this);
2553
2554 // initialize the shutdown handler
2555 $this->shutdown = vB_Shutdown::init();
2556
2557 $this->csrf_skip_list = (defined('CSRF_SKIP_LIST') ? explode(',', CSRF_SKIP_LIST) : array());
2558 }
2559
2560 /**
2561 * Fetches database/system configuration
2562 */
2563 function fetch_config()
2564 {
2565 // parse the config file
2566 $config = array();
2567 include(CWD . '/includes/config.php');
2568
2569 if (sizeof($config) == 0)
2570 {
2571 if (file_exists(CWD. '/includes/config.php'))
2572 {
2573 // config.php exists, but does not define $config
2574 die('<br /><br /><strong>Configuration</strong>: includes/config.php exists, but is not in the 3.6+ format. Please convert your config file via the new config.php.new.');
2575 }
2576 else
2577 {
2578 die('<br /><br /><strong>Configuration</strong>: includes/config.php does not exist. Please fill out the data in config.php.new and rename it to config.php');
2579 }
2580 }
2581
2582 $this->config =& $config;
2583 // if a configuration exists for this exact HTTP host, use it
2584 if (isset($this->config["$_SERVER[HTTP_HOST]"]))
2585 {
2586 $this->config['MasterServer'] = $this->config["$_SERVER[HTTP_HOST]"];
2587 }
2588
2589 // define table and cookie prefix constants
2590 define('TABLE_PREFIX', trim($this->config['Database']['tableprefix']));
2591 define('COOKIE_PREFIX', (empty($this->config['Misc']['cookieprefix']) ? 'bb' : $this->config['Misc']['cookieprefix']));
2592
2593 // set debug mode
2594 $this->debug = !empty($this->config['Misc']['debug']);
2595 define('DEBUG', $this->debug);
2596 }
2597
2598 /**
2599 * Takes the contents of an array and recursively uses each title/data
2600 * pair to create a new defined constant.
2601 */
2602 function array_define($array)
2603 {
2604 foreach ($array AS $title => $data)
2605 {
2606 if (is_array($data))
2607 {
2608 vB_Registry::array_define($data);
2609 }
2610 else
2611 {
2612 define(strtoupper($title), $data);
2613 }
2614 }
2615 }
2616}
2617
2618// #############################################################################
2619// session management class
2620
2621/**
2622* Class to handle sessions
2623*
2624* Creates, updates, and validates sessions; retrieves user info of browsing user
2625*
2626* @package vBulletin
2627* @version $Revision: 43870 $
2628* @date $Date: 2011-05-26 14:52:39 -0700 (Thu, 26 May 2011) $
2629*/
2630class vB_Session
2631{
2632 /**
2633 * The individual session variables. Equivalent to $session from the past.
2634 *
2635 * @var array
2636 */
2637 var $vars = array();
2638
2639 /**
2640 * A list of variables in the $vars member that are in the database. Includes their types.
2641 *
2642 * @var array
2643 */
2644 var $db_fields = array(
2645 'sessionhash' => TYPE_STR,
2646 'userid' => TYPE_INT,
2647 'host' => TYPE_STR,
2648 'idhash' => TYPE_STR,
2649 'lastactivity' => TYPE_INT,
2650 'location' => TYPE_STR,
2651 'styleid' => TYPE_INT,
2652 'languageid' => TYPE_INT,
2653 'loggedin' => TYPE_INT,
2654 'inforum' => TYPE_INT,
2655 'inthread' => TYPE_INT,
2656 'incalendar' => TYPE_INT,
2657 'badlocation' => TYPE_INT,
2658 'useragent' => TYPE_STR,
2659 'bypass' => TYPE_INT,
2660 'profileupdate' => TYPE_INT,
2661 );
2662
2663 /**
2664 * An array of changes. Used to prevent superfluous updates from being made.
2665 *
2666 * @var array
2667 */
2668 var $changes = array();
2669
2670 /**
2671 * Whether the session was created or existed previously
2672 *
2673 * @var bool
2674 */
2675 var $created = false;
2676
2677 /**
2678 * Reference to a vB_Registry object that keeps various data we need.
2679 *
2680 * @var vB_Registry
2681 */
2682 var $registry = null;
2683
2684 /**
2685 * Information about the user that this session belongs to.
2686 *
2687 * @var array
2688 */
2689 var $userinfo = null;
2690
2691 /**
2692 * Is the sessionhash to be passed through URLs?
2693 *
2694 * @var boolean
2695 */
2696 var $visible = true;
2697
2698 /**
2699 * Constructor. Attempts to grab a session that matches parameters, but will create one if it can't.
2700 *
2701 * @param vB_Registry Reference to a registry object
2702 * @param string Previously specified sessionhash
2703 * @param integer User ID (passed in through a cookie)
2704 * @param string Password, must arrive in cookie format: md5(md5(md5(password) . salt) . 'abcd1234')
2705 * @param integer Style ID for this session
2706 * @param integer Language ID for this session
2707 */
2708 function vB_Session(&$registry, $sessionhash = '', $userid = 0, $password = '', $styleid = 0, $languageid = 0)
2709 {
2710 $userid = intval($userid);
2711 $styleid = intval($styleid);
2712 $languageid = intval($languageid);
2713
2714 $this->registry =& $registry;
2715 $db =& $this->registry->db;
2716 $gotsession = false;
2717
2718 if (!defined('SESSION_IDHASH'))
2719 {
2720 define('SESSION_IDHASH', md5($_SERVER['HTTP_USER_AGENT'] . $this->fetch_substr_ip($registry->alt_ip))); // this should *never* change during a session
2721 }
2722
2723 // sessionhash specified, so see if it already exists
2724 if ($sessionhash AND !defined('SKIP_SESSIONCREATE'))
2725 {
2726 if ($session = $db->query_first_slave("
2727 SELECT *
2728 FROM " . TABLE_PREFIX . "session
2729 WHERE sessionhash = '" . $db->escape_string($sessionhash) . "'
2730 AND lastactivity > " . (TIMENOW - $registry->options['cookietimeout']) . "
2731 AND idhash = '" . $this->registry->db->escape_string(SESSION_IDHASH) . "'
2732 ") AND $this->fetch_substr_ip($session['host']) == $this->fetch_substr_ip(SESSION_HOST))
2733 {
2734 $gotsession = true;
2735 $this->vars =& $session;
2736 $this->created = false;
2737
2738 // found a session - get the userinfo
2739 if ($session['userid'] != 0)
2740 {
2741 $useroptions = (defined('IN_CONTROL_PANEL') ? 16 : 0) + (defined('AVATAR_ON_NAVBAR') ? 2 : 0);
2742 $userinfo = fetch_userinfo($session['userid'], $useroptions, (!empty($languageid) ? $languageid : $session['languageid']));
2743 $this->userinfo =& $userinfo;
2744 }
2745 }
2746 }
2747
2748 // or maybe we can use a cookie..
2749 if (($gotsession == false OR empty($session['userid'])) AND $userid AND $password AND !defined('SKIP_SESSIONCREATE'))
2750 {
2751 $useroptions = (defined('IN_CONTROL_PANEL') ? FETCH_USERINFO_ADMIN : 0) + (defined('AVATAR_ON_NAVBAR') ? FETCH_USERINFO_AVATAR : 0);
2752 $userinfo = fetch_userinfo($userid, $useroptions, $languageid);
2753
2754 if (md5($userinfo['password'] . COOKIE_SALT) == $password)
2755 {
2756 $gotsession = true;
2757
2758 // combination is valid
2759 if (!empty($session['sessionhash']))
2760 {
2761 // old session still exists; kill it
2762 $db->shutdown_query("
2763 DELETE FROM " . TABLE_PREFIX . "session
2764 WHERE sessionhash = '" . $this->registry->db->escape_string($session['sessionhash']). "'
2765 ");
2766 }
2767
2768 $this->vars = $this->fetch_session($userinfo['userid']);
2769 $this->created = true;
2770
2771 $this->userinfo =& $userinfo;
2772 }
2773 }
2774
2775 // at this point, we're a guest, so lets try to *find* a session
2776 // you can prevent this check from being run by passing in a userid with no password
2777 if ($gotsession == false AND $userid == 0 AND !defined('SKIP_SESSIONCREATE'))
2778 {
2779 if ($session = $db->query_first_slave("
2780 SELECT *
2781 FROM " . TABLE_PREFIX . "session
2782 WHERE userid = 0
2783 AND host = '" . $this->registry->db->escape_string(SESSION_HOST) . "'
2784 AND idhash = '" . $this->registry->db->escape_string(SESSION_IDHASH) . "'
2785 LIMIT 1
2786 "))
2787 {
2788 $gotsession = true;
2789
2790 $this->vars =& $session;
2791 $this->created = false;
2792 }
2793 }
2794
2795 // well, nothing worked, time to create a new session
2796 if ($gotsession == false)
2797 {
2798 $gotsession = true;
2799
2800 $this->vars = $this->fetch_session(0);
2801 $this->created = true;
2802 }
2803
2804 $this->vars['dbsessionhash'] = $this->vars['sessionhash'];
2805
2806 $this->set('styleid', $styleid);
2807 $this->set('languageid', $languageid);
2808 if ($this->created == false)
2809 {
2810 $this->set('useragent', USER_AGENT);
2811 $this->set('lastactivity', TIMENOW);
2812 if (!defined('LOCATION_BYPASS'))
2813 {
2814 $this->set('location', WOLPATH);
2815 }
2816 $this->set('bypass', SESSION_BYPASS);
2817 }
2818 }
2819
2820 /**
2821 * Saves the session into the database by inserting it or updating an existing one.
2822 */
2823 function save()
2824 {
2825 if (defined('SKIP_SESSIONCREATE'))
2826 {
2827 return;
2828 }
2829
2830 $cleaned = $this->build_query_array();
2831
2832 // since the sessionhash can be blanked out, lets make sure we pull from "dbsessionhash"
2833 $cleaned['sessionhash'] = "'" . $this->registry->db->escape_string($this->vars['dbsessionhash']) . "'";
2834
2835 if ($this->created == true)
2836 {
2837 /*insert query*/
2838 $this->registry->db->query_write("
2839 INSERT IGNORE INTO " . TABLE_PREFIX . "session
2840 (" . implode(', ', array_keys($cleaned)) . ")
2841 VALUES
2842 (" . implode(', ', $cleaned) . ")
2843 ");
2844 }
2845 else
2846 {
2847 // update query
2848
2849 unset($this->changes['sessionhash']); // the sessionhash is not updateable
2850 $update = array();
2851 foreach ($cleaned AS $key => $value)
2852 {
2853 if (!empty($this->changes["$key"]))
2854 {
2855 $update[] = "$key = $value";
2856 }
2857 }
2858
2859 if (sizeof($update) > 0)
2860 {
2861 // note that $cleaned['sessionhash'] has been escaped as necessary above!
2862 $this->registry->db->query_write("
2863 UPDATE " . TABLE_PREFIX . "session
2864 SET " . implode(', ', $update) . "
2865 WHERE sessionhash = $cleaned[sessionhash]
2866 ");
2867 }
2868 }
2869
2870 $this->changes = array();
2871 }
2872
2873 /**
2874 * Builds an array that can be used to build a query to insert/update the session
2875 *
2876 * @return array Array of column name => prepared value
2877 */
2878 function build_query_array()
2879 {
2880 $return = array();
2881 foreach ($this->db_fields AS $fieldname => $cleantype)
2882 {
2883 switch ($cleantype)
2884 {
2885 case TYPE_INT:
2886 $cleaned = intval($this->vars["$fieldname"]);
2887 break;
2888 case TYPE_STR:
2889 default:
2890 $cleaned = "'" . $this->registry->db->escape_string($this->vars["$fieldname"]) . "'";
2891 }
2892 $return["$fieldname"] = $cleaned;
2893 }
2894
2895 return $return;
2896 }
2897
2898 /**
2899 * Sets a session variable and updates the change list.
2900 *
2901 * @param string Name of session variable to update
2902 * @param string Value to update it with
2903 */
2904 function set($key, $value)
2905 {
2906 if (!isset($this->vars["$key"]) OR $this->vars["$key"] != $value)
2907 {
2908 $this->vars["$key"] = $value;
2909 $this->changes["$key"] = true;
2910 }
2911 }
2912
2913 /**
2914 * Sets the session visibility (whether session info shows up in a URL). Updates are put in the $vars member.
2915 *
2916 * @param bool Whether the session elements should be visible.
2917 */
2918 function set_session_visibility($invisible)
2919 {
2920 $this->visible = !$invisible;
2921
2922 if ($invisible)
2923 {
2924 $this->vars['sessionhash'] = '';
2925 $this->vars['sessionurl'] = '';
2926 $this->vars['sessionurl_q'] = '';
2927 $this->vars['sessionurl_js'] = '';
2928 }
2929 else
2930 {
2931 $this->vars['sessionurl'] = 's=' . $this->vars['dbsessionhash'] . '&';
2932 $this->vars['sessionurl_q'] = '?s=' . $this->vars['dbsessionhash'];
2933 $this->vars['sessionurl_js'] = 's=' . $this->vars['dbsessionhash'] . '&';
2934 }
2935 }
2936
2937 /**
2938 * Fetches a valid sessionhash value, not necessarily the one tied to this session.
2939 *
2940 * @return string 32-character sessionhash
2941 */
2942 function fetch_sessionhash()
2943 {
2944 return md5(uniqid(microtime(), true));
2945 }
2946
2947 /**
2948 * Returns the IP address with the specified number of octets removed
2949 *
2950 * @param string IP address
2951 *
2952 * @return string truncated IP address
2953 */
2954 function fetch_substr_ip($ip, $length = null)
2955 {
2956 if ($length === null OR $length > 3)
2957 {
2958 $length = $this->registry->options['ipcheck'];
2959 }
2960 return implode('.', array_slice(explode('.', $ip), 0, 4 - $length));
2961 }
2962
2963 /**
2964 * Fetches a default session. Used when creating a new session.
2965 *
2966 * @param integer User ID the session should be for
2967 *
2968 * @return array Array of session variables
2969 */
2970 function fetch_session($userid = 0)
2971 {
2972 $sessionhash = $this->fetch_sessionhash();
2973 if (!defined('SKIP_SESSIONCREATE'))
2974 {
2975 vbsetcookie('sessionhash', $sessionhash, false, false, true);
2976 }
2977
2978 return array(
2979 'sessionhash' => $sessionhash,
2980 'dbsessionhash' => $sessionhash,
2981 'userid' => intval($userid),
2982 'host' => SESSION_HOST,
2983 'idhash' => SESSION_IDHASH,
2984 'lastactivity' => TIMENOW,
2985 'location' => defined('LOCATION_BYPASS') ? '' : WOLPATH,
2986 'styleid' => 0,
2987 'languageid' => 0,
2988 'loggedin' => intval($userid) ? 1 : 0,
2989 'inforum' => 0,
2990 'inthread' => 0,
2991 'incalendar' => 0,
2992 'badlocation' => 0,
2993 'profileupdate' => 0,
2994 'useragent' => USER_AGENT,
2995 'bypass' => SESSION_BYPASS
2996 );
2997
2998 }
2999
3000 /**
3001 * Returns appropriate user info for the owner of this session.
3002 *
3003 * @return array Array of user information.
3004 */
3005 function &fetch_userinfo()
3006 {
3007 if ($this->userinfo)
3008 {
3009 // we already calculated this
3010 return $this->userinfo;
3011 }
3012 else if ($this->vars['userid'] AND !defined('SKIP_USERINFO'))
3013 {
3014 // user is logged in
3015 $useroptions = (defined('IN_CONTROL_PANEL') ? FETCH_USERINFO_ADMIN : 0) + (defined('AVATAR_ON_NAVBAR') ? FETCH_USERINFO_AVATAR : 0);
3016 $this->userinfo = fetch_userinfo($this->vars['userid'], $useroptions, $this->vars['languageid']);
3017 return $this->userinfo;
3018 }
3019 else
3020 {
3021 // guest setup
3022 $this->userinfo = array(
3023 'userid' => 0,
3024 'usergroupid' => 1,
3025 'username' => (!empty($_REQUEST['username']) ? htmlspecialchars_uni($_REQUEST['username']) : ''),
3026 'password' => '',
3027 'email' => '',
3028 'styleid' => $this->vars['styleid'],
3029 'languageid' => $this->vars['languageid'],
3030 'lastactivity' => $this->vars['lastactivity'],
3031 'daysprune' => 0,
3032 'timezoneoffset' => $this->registry->options['timeoffset'],
3033 'dstonoff' => $this->registry->options['dstonoff'],
3034 'showsignatures' => 1,
3035 'showavatars' => 1,
3036 'showimages' => 1,
3037 'showusercss' => 1,
3038 'dstauto' => 0,
3039 'maxposts' => -1,
3040 'startofweek' => 1,
3041 'threadedmode' => $this->registry->options['threadedmode'],
3042 'securitytoken' => 'guest',
3043 'securitytoken_raw' => 'guest'
3044 );
3045
3046 $this->userinfo['options'] =
3047 $this->registry->bf_misc_useroptions['showsignatures'] | $this->registry->bf_misc_useroptions['showavatars'] |
3048 $this->registry->bf_misc_useroptions['showimages'] | $this->registry->bf_misc_useroptions['dstauto'] |
3049 $this->registry->bf_misc_useroptions['showusercss'];
3050
3051 if (!defined('SKIP_USERINFO'))
3052 {
3053 // get default language
3054 $phraseinfo = $this->registry->db->query_first_slave("
3055 SELECT languageid" . fetch_language_fields_sql(0) . "
3056 FROM " . TABLE_PREFIX . "language
3057 WHERE languageid = " . (!empty($this->vars['languageid']) ? $this->vars['languageid'] : intval($this->registry->options['languageid'])) . "
3058 ");
3059 if (empty($phraseinfo))
3060 { // can't phrase this since we can't find the language
3061 trigger_error('The requested language does not exist, reset via tools.php.', E_USER_ERROR);
3062 }
3063 foreach($phraseinfo AS $_arrykey => $_arryval)
3064 {
3065 $this->userinfo["$_arrykey"] = $_arryval;
3066 }
3067 unset($phraseinfo);
3068 }
3069
3070 return $this->userinfo;
3071 }
3072 }
3073
3074 /**
3075 * Updates the last visit and last activity times for guests and registered users (differently).
3076 * Last visit is set to the last activity time (before it's updated) only when a certain
3077 * time has lapsed. Last activity is always set to the specified time.
3078 *
3079 * @param integer Time stamp for last visit time (guest only)
3080 * @param integer Time stamp for last activity time (guest only)
3081 */
3082 function do_lastvisit_update($lastvisit = 0, $lastactivity = 0)
3083 {
3084 // update last visit/activity stuff
3085 if ($this->vars['userid'] == 0)
3086 {
3087 // guest -- emulate last visit/activity for registered users by cookies
3088 if ($lastvisit)
3089 {
3090 // we've been here before
3091 $this->userinfo['lastvisit'] = intval($lastvisit);
3092 $this->userinfo['lastactivity'] = ($lastvisit ? intval($lastvisit) : TIMENOW);
3093
3094 // here's the emulation
3095 if (TIMENOW - $this->userinfo['lastactivity'] > $this->registry->options['cookietimeout'])
3096 {
3097 $this->userinfo['lastvisit'] = $this->userinfo['lastactivity'];
3098
3099 vbsetcookie('lastvisit', $this->userinfo['lastactivity']);
3100 }
3101 }
3102 else
3103 {
3104 // first visit!
3105 $this->userinfo['lastactivity'] = TIMENOW;
3106 $this->userinfo['lastvisit'] = TIMENOW;
3107
3108 vbsetcookie('lastvisit', TIMENOW);
3109 }
3110 vbsetcookie('lastactivity', $lastactivity);
3111 }
3112 else
3113 {
3114 // registered user
3115 if (!SESSION_BYPASS)
3116 {
3117 if (TIMENOW - $this->userinfo['lastactivity'] > $this->registry->options['cookietimeout'])
3118 {
3119 // see if session has 'expired' and if new post indicators need resetting
3120 $this->registry->db->shutdown_query("
3121 UPDATE " . TABLE_PREFIX . "user
3122 SET
3123 lastvisit = lastactivity,
3124 lastactivity = " . TIMENOW . "
3125 WHERE userid = " . $this->userinfo['userid'] . "
3126 ", 'lastvisit');
3127
3128 $this->userinfo['lastvisit'] = $this->userinfo['lastactivity'];
3129 }
3130 else
3131 {
3132 // if this line is removed (say to be replaced by a cron job, you will need to change all of the 'online'
3133 // status indicators as they use $userinfo['lastactivity'] to determine if a user is online which relies
3134 // on this to be updated in real time.
3135 $this->registry->db->shutdown_query("
3136 UPDATE " . TABLE_PREFIX . "user
3137 SET lastactivity = " . TIMENOW . "
3138 WHERE userid = " . $this->userinfo['userid'] . "
3139 ", 'lastvisit');
3140 }
3141 }
3142 }
3143 }
3144}
3145
3146/**
3147* Class to handle shutdown
3148*
3149* @package vBulletin
3150* @version $Revision: 43870 $
3151* @author Scott
3152* @date $Date: 2011-05-26 14:52:39 -0700 (Thu, 26 May 2011) $
3153*/
3154class vB_Shutdown
3155{
3156 var $shutdown = array();
3157
3158 /**
3159 * Constructor. Empty.
3160 */
3161 function vB_Shutdown()
3162 {
3163 }
3164
3165 /**
3166 * Singleton emulation - use this function to instantiate the class
3167 *
3168 * @return vB_Shutdown
3169 */
3170 function &init()
3171 {
3172 static $instance;
3173
3174 if (!$instance)
3175 {
3176 $instance = new vB_Shutdown();
3177 // we register this but it might not be used
3178 if (phpversion() < '5.0.5')
3179 {
3180 register_shutdown_function(array(&$instance, '__destruct'));
3181 }
3182 }
3183
3184 return $instance;
3185 }
3186
3187 /**
3188 * Add function to be executed at shutdown
3189 *
3190 * @param string Name of function to be executed on shutdown
3191 */
3192 function add($function)
3193 {
3194 $obj =& vB_Shutdown::init();
3195 if (function_exists($function) AND !in_array($function, $obj->shutdown))
3196 {
3197 $obj->shutdown[] = $function;
3198 }
3199 }
3200
3201 // only called when an object is destroyed, so $this is appropriate
3202 function __destruct()
3203 {
3204 if (!empty($this->shutdown))
3205 {
3206 foreach ($this->shutdown AS $key => $funcname)
3207 {
3208 $funcname();
3209 unset($this->shutdown[$key]);
3210 }
3211 }
3212 }
3213
3214 // called if unserialized
3215 function __wakeup()
3216 {
3217 $this->shutdown = array();
3218 }
3219}
3220
3221// #############################################################################
3222// misc functions
3223
3224// #############################################################################
3225/**
3226* Feeds database connection errors into the halt() method of the vB_Database class.
3227*
3228* @param integer Error number
3229* @param string PHP error text string
3230* @param strig File that contained the error
3231* @param integer Line in the file that contained the error
3232*/
3233function catch_db_error($errno, $errstr, $errfile, $errline)
3234{
3235 global $db;
3236 static $failures;
3237
3238 if (strstr($errstr, 'Lost connection') AND $failures < 5)
3239 {
3240 $failures++;
3241 return;
3242 }
3243
3244 if (is_object($db))
3245 {
3246 $db->halt("$errstr\r\n$errfile on line $errline");
3247 }
3248 else
3249 {
3250 vb_error_handler($errno, $errstr, $errfile, $errline);
3251 }
3252}
3253
3254// #############################################################################
3255/**
3256* Removes the full path from being disclosed on any errors
3257*
3258* @param integer Error number
3259* @param string PHP error text string
3260* @param strig File that contained the error
3261* @param integer Line in the file that contained the error
3262*/
3263function vb_error_handler($errno, $errstr, $errfile, $errline)
3264{
3265 global $vbulletin;
3266
3267 switch ($errno)
3268 {
3269 case E_WARNING:
3270 case E_USER_WARNING:
3271 /* Don't log warnings due to to the false bug reports about valid warnings that we suppress, but still appear in the log
3272 require_once(DIR . '/includes/functions_log_error.php');
3273 $message = "Warning: $errstr in $errfile on line $errline";
3274 log_vbulletin_error($message, 'php');
3275 */
3276
3277 if (!error_reporting() OR !ini_get('display_errors'))
3278 {
3279 return;
3280 }
3281 $errfile = str_replace(DIR, '[path]', $errfile);
3282 $errstr = str_replace(DIR, '[path]', $errstr);
3283 echo "<br /><strong>Warning</strong>: $errstr in <strong>$errfile</strong> on line <strong>$errline</strong><br />";
3284 break;
3285
3286 case E_USER_ERROR:
3287 require_once(DIR . '/includes/functions_log_error.php');
3288 $message = "Fatal error: $errstr in $errfile on line $errline";
3289 log_vbulletin_error($message, 'php');
3290
3291 if (!headers_sent())
3292 {
3293 if (SAPI_NAME == 'cgi' OR SAPI_NAME == 'cgi-fcgi')
3294 {
3295 header('Status: 500 Internal Server Error');
3296 }
3297 else
3298 {
3299 header('HTTP/1.1 500 Internal Server Error');
3300 }
3301 }
3302
3303 if (error_reporting() OR ini_get('display_errors'))
3304 {
3305 $errfile = str_replace(DIR, '[path]', $errfile);
3306 $errstr = str_replace(DIR, '[path]', $errstr);
3307 echo "<br /><strong>Fatal error:</strong> $errstr in <strong>$errfile</strong> on line <strong>$errline</strong><br />";
3308 if (function_exists('debug_print_backtrace') AND ($vbulletin->userinfo['usergroupid'] == 6 OR ($vbulletin->userinfo['permissions']['adminpermissions'] & $vbulletin->bf_ugp_adminpermissions)))
3309 {
3310 // This is needed so IE doesn't show the pretty error messages
3311 echo str_repeat(' ', 512);
3312 debug_print_backtrace();
3313 }
3314 }
3315 exit;
3316 break;
3317 }
3318}
3319
3320// #############################################################################
3321/**
3322* Unicode-safe version of htmlspecialchars()
3323*
3324* @param string Text to be made html-safe
3325*
3326* @return string
3327*/
3328function htmlspecialchars_uni($text, $entities = true)
3329{
3330 return str_replace(
3331 // replace special html characters
3332 array('<', '>', '"'),
3333 array('<', '>', '"'),
3334 preg_replace(
3335 // translates all non-unicode entities
3336 '/&(?!' . ($entities ? '#[0-9]+|shy' : '(#[0-9]+|[a-z]+)') . ';)/si',
3337 '&',
3338 $text
3339 )
3340 );
3341}
3342
3343/*======================================================================*\
3344|| ####################################################################
3345|| # Downloaded: 04:08, Mon Jul 1st 2013
3346|| # CVS: $RCSfile$ - $Revision: 43870 $
3347|| ####################################################################
3348\*======================================================================*/