· 10 years ago · Feb 11, 2016, 10:15 PM
1<?php
2/*======================================================================*\
3|| #################################################################### ||
4|| # vBulletin 4.2.2 - Nulled By VietVBB Team
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', '4.2.2'); // this should match installsteps.php
14define('SIMPLE_VERSION', '422'); // see vB_Datastore::check_options()
15define('YUI_VERSION', '2.9.0'); // define the YUI version we bundle
16define('JQUERY_VERSION', '1.6.4'); // define the jQuery version we use
17define('JQUERY_MOBILE_VERSION', '1.0'); // define the jQuery Mobile Style Version
18
19/**#@+
20* The maximum sizes for the "small" profile avatars
21*/
22define('FIXED_SIZE_AVATAR_WIDTH', 60);
23define('FIXED_SIZE_AVATAR_HEIGHT', 80);
24/**#@-*/
25
26/**#@+
27* These make up the bit field to disable specific types of BB codes.
28*/
29define('ALLOW_BBCODE_BASIC', 1);
30define('ALLOW_BBCODE_COLOR', 2);
31define('ALLOW_BBCODE_SIZE', 4);
32define('ALLOW_BBCODE_FONT', 8);
33define('ALLOW_BBCODE_ALIGN', 16);
34define('ALLOW_BBCODE_LIST', 32);
35define('ALLOW_BBCODE_URL', 64);
36define('ALLOW_BBCODE_CODE', 128);
37define('ALLOW_BBCODE_PHP', 256);
38define('ALLOW_BBCODE_HTML', 512);
39define('ALLOW_BBCODE_IMG', 1024);
40define('ALLOW_BBCODE_QUOTE', 2048);
41define('ALLOW_BBCODE_CUSTOM', 4096);
42define('ALLOW_BBCODE_VIDEO', 8192);
43/**#@-*/
44
45/**#@+
46* These make up the bit field to control what "special" BB codes are found in the text.
47*/
48define('BBCODE_HAS_IMG', 1);
49define('BBCODE_HAS_ATTACH', 2);
50define('BBCODE_HAS_SIGPIC', 4);
51define('BBCODE_HAS_RELPATH',8);
52/**#@-*/
53
54/**#@+
55* Bitfield values for the inline moderation javascript selector which should be self-explanitory
56*/
57define('POST_FLAG_INVISIBLE', 1);
58define('POST_FLAG_DELETED', 2);
59define('POST_FLAG_ATTACH', 4);
60define('POST_FLAG_GUEST', 8);
61/**#@-*/
62
63// #############################################################################
64// MySQL Database Class
65
66/**#@+
67* The type of result set to return from the database for a specific row.
68*/
69define('DBARRAY_BOTH', 0);
70define('DBARRAY_ASSOC', 1);
71define('DBARRAY_NUM', 2);
72/**#@-*/
73
74/**
75* Class to interface with a database
76*
77* This class also handles data replication between a master and slave(s) servers
78*
79* @package vBulletin
80* @version $Revision: 78140 $
81* @date $Date: 2013-10-16 14:51:06 -0700 (Wed, 16 Oct 2013) $
82*/
83class vB_Database
84{
85 /**
86 * Array of function names, mapping a simple name to the RDBMS specific function name
87 *
88 * @var array
89 */
90 var $functions = array(
91 'connect' => 'mysql_connect',
92 'pconnect' => 'mysql_pconnect',
93 'select_db' => 'mysql_select_db',
94 'query' => 'mysql_query',
95 'query_unbuffered' => 'mysql_unbuffered_query',
96 'fetch_row' => 'mysql_fetch_row',
97 'fetch_array' => 'mysql_fetch_array',
98 'fetch_field' => 'mysql_fetch_field',
99 'free_result' => 'mysql_free_result',
100 'data_seek' => 'mysql_data_seek',
101 'error' => 'mysql_error',
102 'errno' => 'mysql_errno',
103 'affected_rows' => 'mysql_affected_rows',
104 'num_rows' => 'mysql_num_rows',
105 'num_fields' => 'mysql_num_fields',
106 'field_name' => 'mysql_field_name',
107 'insert_id' => 'mysql_insert_id',
108 'escape_string' => 'mysql_real_escape_string',
109 'real_escape_string' => 'mysql_real_escape_string',
110 'close' => 'mysql_close',
111 'client_encoding' => 'mysql_client_encoding',
112 'ping' => 'mysql_ping',
113 );
114
115 /**
116 * The vBulletin registry object
117 *
118 * @var vB_Registry
119 */
120 var $registry = null;
121
122 /**
123 * Array of constants for use in fetch_array
124 *
125 * @var array
126 */
127 var $fetchtypes = array(
128 DBARRAY_NUM => MYSQL_NUM,
129 DBARRAY_ASSOC => MYSQL_ASSOC,
130 DBARRAY_BOTH => MYSQL_BOTH
131 );
132
133 /**
134 * Full name of the system
135 *
136 * @var string
137 */
138 var $appname = 'vBulletin';
139
140 /**
141 * Short name of the system
142 *
143 * @var string
144 */
145 var $appshortname = 'vBulletin';
146
147 /**
148 * Database name
149 *
150 * @var string
151 */
152 var $database = null;
153
154 /**
155 * Link variable. The connection to the master/write server.
156 *
157 * @var string
158 */
159 var $connection_master = null;
160
161 /**
162 * Link variable. The connection to the slave/read server(s).
163 *
164 * @var string
165 */
166 var $connection_slave = null;
167
168 /**
169 * Link variable. The connection last used.
170 *
171 * @var string
172 */
173 var $connection_recent = null;
174
175 /**
176 * Whether or not we will be using different connections for read and write queries
177 *
178 * @var boolean
179 */
180 var $multiserver = false;
181
182 /**
183 * Array of queries to be executed when the script shuts down
184 *
185 * @var array
186 */
187 var $shutdownqueries = array();
188
189 /**
190 * The contents of the most recent SQL query string.
191 *
192 * @var string
193 */
194 var $sql = '';
195
196 /**
197 * Whether or not to show and halt on database errors
198 *
199 * @var boolean
200 */
201 var $reporterror = true;
202
203 /**
204 * The text of the most recent database error message
205 *
206 * @var string
207 */
208 var $error = '';
209
210 /**
211 * The error number of the most recent database error message
212 *
213 * @var integer
214 */
215 var $errno = '';
216
217 /**
218 * SQL Query String
219 *
220 * @var integer The maximum size of query string permitted by the master server
221 */
222 var $maxpacket = 0;
223
224 /**
225 * Track lock status of tables. True if a table lock has been issued
226 *
227 * @var bool
228 */
229 var $locked = false;
230
231 /**
232 * Number of queries executed
233 *
234 * @var integer The number of SQL queries run by the system
235 */
236 var $querycount = 0;
237
238 /**
239 * Constructor. If x_real_escape_string() is available, switches to use that
240 * function over x_escape_string().
241 *
242 * @param vB_Registry Registry object
243 */
244 function vB_Database(&$registry)
245 {
246 if (is_object($registry))
247 {
248 $this->registry =& $registry;
249 }
250 else
251 {
252 trigger_error("vB_Database::Registry object is not an object", E_USER_ERROR);
253 }
254 }
255
256 /**
257 * Connects to the specified database server(s)
258 *
259 * @param string Name of the database that we will be using for select_db()
260 * @param string Name of the master (write) server - should be either 'localhost' or an IP address
261 * @param integer Port for the master server
262 * @param string Username to connect to the master server
263 * @param string Password associated with the username for the master server
264 * @param boolean Whether or not to use persistent connections to the master server
265 * @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
266 * @param integer (Optional) Port of the slave server
267 * @param string (Optional) Username to connect to the slave server
268 * @param string (Optional) Password associated with the username for the slave server
269 * @param boolean (Optional) Whether or not to use persistent connections to the slave server
270 * @param string (Optional) Parse given MySQL config file to set options
271 * @param string (Optional) Connection Charset MySQLi / PHP 5.1.0+ or 5.0.5+ / MySQL 4.1.13+ or MySQL 5.1.10+ Only
272 *
273 * @return none
274 */
275 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 = '')
276 {
277 $this->database = $database;
278
279 $w_port = $w_port ? $w_port : 3306;
280 $r_port = $r_port ? $r_port : 3306;
281
282 $this->connection_master = $this->db_connect($w_servername, $w_port, $w_username, $w_password, $w_usepconnect, $configfile, $charset);
283 $this->multiserver = false;
284 $this->connection_slave =& $this->connection_master;
285
286 if ($this->connection_master)
287 {
288 $this->select_db($this->database);
289 }
290 }
291
292 /**
293 * Initialize database connection(s)
294 *
295 * Connects to the specified master database server, and also to the slave server if it is specified
296 *
297 * @param string Name of the database server - should be either 'localhost' or an IP address
298 * @param integer Port of the database server (usually 3306)
299 * @param string Username to connect to the database server
300 * @param string Password associated with the username for the database server
301 * @param boolean Whether or not to use persistent connections to the database server
302 * @param string Not applicable; config file for MySQLi only
303 * @param string Force connection character set (to prevent collation errors)
304 *
305 * @return boolean
306 */
307 function db_connect($servername, $port, $username, $password, $usepconnect, $configfile = '', $charset = '')
308 {
309 if (function_exists('catch_db_error'))
310 {
311 set_error_handler('catch_db_error');
312 }
313
314 // catch_db_error will handle exiting, no infinite loop here
315 do
316 {
317 $link = $this->functions[$usepconnect ? 'pconnect' : 'connect']("$servername:$port", $username, $password);
318 }
319 while ($link == false AND $this->reporterror);
320
321 restore_error_handler();
322
323 if (!empty($charset))
324 {
325 if (function_exists('mysql_set_charset'))
326 {
327 mysql_set_charset($charset);
328 }
329 else
330 {
331 $this->sql = "SET NAMES $charset";
332 $this->execute_query(true, $link);
333 }
334 }
335
336 return $link;
337 }
338
339 /**
340 * Selects a database to use
341 *
342 * @param string The name of the database located on the database server(s)
343 *
344 * @return boolean
345 */
346 function select_db($database = '')
347 {
348 if ($database != '')
349 {
350 $this->database = $database;
351 }
352
353 if ($check_write = @$this->select_db_wrapper($this->database, $this->connection_master))
354 {
355 $this->connection_recent =& $this->connection_master;
356 return true;
357 }
358 else
359 {
360 $this->connection_recent =& $this->connection_master;
361 if (!file_exists(DIR . '/install/install.php'))
362 {
363 $this->halt('Cannot use database ' . $this->database);
364 }
365 return false;
366 }
367 }
368
369 /**
370 * Simple wrapper for select_db(), to allow argument order changes
371 *
372 * @param string Database name
373 * @param integer Link identifier
374 *
375 * @return boolean
376 */
377 function select_db_wrapper($database = '', $link = null)
378 {
379 return $this->functions['select_db']($database, $link);
380 }
381
382 /**
383 * Forces the sql_mode varaible to a specific mode. Certain modes may be
384 * incompatible with vBulletin. Applies to MySQL 4.1+.
385 *
386 * @param string The mode to set the sql_mode variable to
387 */
388 function force_sql_mode($mode)
389 {
390 $reset_errors = $this->reporterror;
391 if ($reset_errors)
392 {
393 $this->hide_errors();
394 }
395
396 $this->query_write("SET @@sql_mode = '" . $this->escape_string($mode) . "'");
397
398 if ($reset_errors)
399 {
400 $this->show_errors();
401 }
402 }
403
404 /**
405 * Executes an SQL query through the specified connection
406 *
407 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
408 * @param string The connection ID to the database server
409 *
410 * @return string
411 */
412 function &execute_query($buffered = true, &$link)
413 {
414 $this->connection_recent =& $link;
415 $this->querycount++;
416
417 if ($queryresult = $this->functions[$buffered ? 'query' : 'query_unbuffered']($this->sql, $link))
418 {
419 // unset $sql to lower memory .. this isn't an error, so it's not needed
420 $this->sql = '';
421
422 return $queryresult;
423 }
424 else
425 {
426 $this->halt();
427
428 // unset $sql to lower memory .. error will have already been thrown
429 $this->sql = '';
430 }
431 }
432
433 /**
434 * Executes a data-writing SQL query through the 'master' database connection
435 *
436 * @param string The text of the SQL query to be executed
437 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is buffered.
438 *
439 * @return string
440 */
441 function query_write($sql, $buffered = true)
442 {
443 $this->sql =& $sql;
444 return $this->execute_query($buffered, $this->connection_master);
445 }
446
447 /**
448 * Executes a data-reading SQL query through the 'master' database connection
449 * we don't know if the 'read' database is up to date so be on the safe side
450 *
451 * @param string The text of the SQL query to be executed
452 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is buffered.
453 *
454 * @return string
455 */
456 function query_read($sql, $buffered = true)
457 {
458 $this->sql =& $sql;
459 return $this->execute_query($buffered, $this->connection_master);
460 }
461
462 /**
463 * Executes a data-reading SQL query through the 'slave' database connection
464 *
465 * @param string The text of the SQL query to be executed
466 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is buffered.
467 *
468 * @return string
469 */
470 function query_read_slave($sql, $buffered = true)
471 {
472 $this->sql =& $sql;
473 return $this->execute_query($buffered, $this->connection_master);
474 }
475
476 /**
477 * Executes an SQL query, using either the write connection
478 *
479 * @deprecated Deprecated as of 3.6. Use query_(read/write)
480 *
481 * @param string The text of the SQL query to be executed
482 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
483 *
484 * @return string
485 */
486 function query($sql, $buffered = true)
487 {
488 $this->sql =& $sql;
489 return $this->execute_query($buffered, $this->connection_master);
490 }
491
492 /**
493 * Executes a data-reading SQL query, then returns an array of the data from the first row from the result set
494 *
495 * @param string The text of the SQL query to be executed
496 * @param string One of (NUM, ASSOC, BOTH)
497 *
498 * @return array
499 */
500 function &query_first($sql, $type = DBARRAY_ASSOC)
501 {
502 $this->sql =& $sql;
503 $queryresult = $this->execute_query(true, $this->connection_master);
504 $returnarray = $this->fetch_array($queryresult, $type);
505 $this->free_result($queryresult);
506 return $returnarray;
507 }
508
509 /**
510 * Executes a FOUND_ROWS query to get the results of SQL_CALC_FOUND_ROWS
511 *
512 * @return integer
513 */
514 function found_rows()
515 {
516 $this->sql = "SELECT FOUND_ROWS()";
517 $queryresult = $this->execute_query(true, $this->connection_recent);
518 $returnarray = $this->fetch_array($queryresult, DBARRAY_NUM);
519 $this->free_result($queryresult);
520
521 return intval($returnarray[0]);
522 }
523
524 /**
525 * 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
526 *
527 * @param string The text of the SQL query to be executed
528 * @param string One of (NUM, ASSOC, BOTH)
529 *
530 * @return array
531 */
532 function &query_first_slave($sql, $type = DBARRAY_ASSOC)
533 {
534 $returnarray = $this->query_first($sql, $type);
535 return $returnarray;
536 }
537
538 /**
539 * Executes an INSERT INTO query, using extended inserts if possible
540 *
541 * @param string Name of the table into which data should be inserted
542 * @param string Comma-separated list of the fields to affect
543 * @param array Array of SQL values
544 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
545 *
546 * @return mixed
547 */
548 function &query_insert($table, $fields, &$values, $buffered = true)
549 {
550 return $this->insert_multiple("INSERT INTO $table $fields VALUES", $values, $buffered);
551 }
552
553 /**
554 * Executes a REPLACE INTO query, using extended inserts if possible
555 *
556 * @param string Name of the table into which data should be inserted
557 * @param string Comma-separated list of the fields to affect
558 * @param array Array of SQL values
559 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
560 *
561 * @return mixed
562 */
563 function &query_replace($table, $fields, &$values, $buffered = true)
564 {
565 return $this->insert_multiple("REPLACE INTO $table $fields VALUES", $values, $buffered);
566 }
567
568 /**
569 * Executes an INSERT or REPLACE query with multiple values, splitting large queries into manageable chunks based on $this->maxpacket
570 *
571 * @param string The text of the first part of the SQL query to be executed - example "INSERT INTO table (field1, field2) VALUES"
572 * @param mixed The values to be inserted. Example: (0 => "('value1', 'value2')", 1 => "('value3', 'value4')")
573 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
574 *
575 * @return mixed
576 */
577 function insert_multiple($sql, &$values, $buffered)
578 {
579 if ($this->maxpacket == 0)
580 {
581 // must do a READ query on the WRITE link here!
582 $vars = $this->query_write("SHOW VARIABLES LIKE 'max_allowed_packet'");
583 $var = $this->fetch_row($vars);
584 $this->maxpacket = $var[1];
585 $this->free_result($vars);
586 }
587
588 $i = 0;
589 $num_values = sizeof($values);
590 $this->sql = $sql;
591
592 while ($i < $num_values)
593 {
594 $sql_length = strlen($this->sql);
595 $value_length = strlen("\r\n" . $values["$i"] . ",");
596
597 if (($sql_length + $value_length) < $this->maxpacket)
598 {
599 $this->sql .= "\r\n" . $values["$i"] . ",";
600 unset($values["$i"]);
601 $i++;
602 }
603 else
604 {
605 $this->sql = (substr($this->sql, -1) == ',') ? substr($this->sql, 0, -1) : $this->sql;
606 $this->execute_query($buffered, $this->connection_master);
607 $this->sql = $sql;
608 }
609 }
610 if ($this->sql != $sql)
611 {
612 $this->sql = (substr($this->sql, -1) == ',') ? substr($this->sql, 0, -1) : $this->sql;
613 $this->execute_query($buffered, $this->connection_master);
614 }
615
616 if (sizeof($values) == 1)
617 {
618 return $this->insert_id();
619 }
620 else
621 {
622 return true;
623 }
624 }
625
626 /**
627 * Registers an SQL query to be executed at shutdown time. If shutdown functions are disabled, the query is run immediately.
628 *
629 * @param string The text of the SQL query to be executed
630 * @param mixed (Optional) Allows particular shutdown queries to be labelled
631 *
632 * @return boolean
633 */
634 function shutdown_query($sql, $arraykey = -1)
635 {
636 if ($arraykey === -1)
637 {
638 $this->shutdownqueries[] = $sql;
639 return true;
640 }
641 else
642 {
643 $this->shutdownqueries["$arraykey"] = $sql;
644 return true;
645 }
646 }
647
648 /**
649 * Returns the number of rows contained within a query result set
650 *
651 * @param string The query result ID we are dealing with
652 *
653 * @return integer
654 */
655 function num_rows($queryresult)
656 {
657 return @$this->functions['num_rows']($queryresult);
658 }
659
660 /**
661 * Returns the number of fields contained within a query result set
662 *
663 * @param string The query result ID we are dealing with
664 *
665 * @return integer
666 */
667 function num_fields($queryresult)
668 {
669 return @$this->functions['num_fields']($queryresult);
670 }
671
672 /**
673 * Returns the name of a field from within a query result set
674 *
675 * @param string The query result ID we are dealing with
676 * @param integer The index position of the field
677 *
678 * @return string
679 */
680 function field_name($queryresult, $index)
681 {
682 return @$this->functions['field_name']($queryresult, $index);
683 }
684
685 /**
686 * Returns the ID of the item just inserted into an auto-increment field
687 *
688 * @return integer
689 */
690 function insert_id()
691 {
692 return @$this->functions['insert_id']($this->connection_master);
693 }
694
695 /**
696 * Returns the name of the character set
697 *
698 * @return string
699 */
700 function client_encoding()
701 {
702 return @$this->functions['client_encoding']($this->connection_master);
703 }
704
705 /**
706 * Closes the connection to the database server
707 *
708 * @return integer
709 */
710 function close()
711 {
712 return @$this->functions['close']($this->connection_master);
713 }
714
715 /**
716 * Escapes a string to make it safe to be inserted into an SQL query
717 *
718 * @param string The string to be escaped
719 *
720 * @return string
721 */
722 function escape_string($string)
723 {
724 return $this->functions['real_escape_string']($string, $this->connection_master);
725 }
726
727 /**
728 * Escapes a string using the appropriate escape character for the RDBMS for use in LIKE conditions
729 *
730 * @param string The string to be escaped
731 *
732 * @return string
733 */
734 function escape_string_like($string)
735 {
736 return str_replace(array('%', '_') , array('\%' , '\_') , $this->escape_string($string));
737 }
738
739 /**
740 * Takes a piece of data and prepares it to be put into an SQL query by adding quotes etc.
741 *
742 * @param mixed The data to be used
743 *
744 * @return mixed The prepared data
745 */
746 function sql_prepare($value)
747 {
748 if (is_string($value))
749 {
750 return "'" . $this->escape_string($value) . "'";
751 }
752 else if (is_numeric($value) AND $value + 0 == $value)
753 {
754 return $value;
755 }
756 else if (is_bool($value))
757 {
758 return $value ? 1 : 0;
759 }
760 else if (is_null($value))
761 {
762 return "''";
763 }
764 else if (is_array($value))
765 {
766 foreach ($value as $key => $item)
767 {
768 $value[$key] = $this->sql_prepare($item);
769 }
770 return $value;
771 }
772 else
773 {
774 return "'" . $this->escape_string($value) . "'";
775 }
776 }
777
778 /**
779 * Fetches a row from a query result and returns the values from that row as an array
780 *
781 * The value of $type defines whether the array will have numeric or associative keys, or both
782 *
783 * @param string The query result ID we are dealing with
784 * @param integer One of DBARRAY_ASSOC / DBARRAY_NUM / DBARRAY_BOTH
785 *
786 * @return array
787 */
788 function fetch_array($queryresult, $type = DBARRAY_ASSOC)
789 {
790 static $hook_code = false;
791
792 if ($hook_code === false AND class_exists('vBulletinHook', false))
793 {
794 $hook_code['pre_fetch'] = vBulletinHook::fetch_hook('database_pre_fetch_array');
795 $hook_code['post_fetch'] = vBulletinHook::fetch_hook('database_post_fetch_array');
796 }
797
798 if ($hook_code['pre_fetch'])
799 {
800 $result = false;
801
802 eval($hook_code['pre_fetch']);
803
804 if ($result)
805 {
806 return $result;
807 }
808 }
809
810 $result = @$this->functions['fetch_array']($queryresult, $this->fetchtypes["$type"]);
811
812 if ($hook_code['post_fetch'])
813 {
814 eval($hook_code['post_fetch']);
815 }
816
817 return $result;
818 }
819
820 /**
821 * Fetches a row from a query result and returns the values from that row as an array with numeric keys
822 *
823 * @param string The query result ID we are dealing with
824 *
825 * @return array
826 */
827 function fetch_row($queryresult)
828 {
829 static $hook_code = false;
830
831 if ($hook_code === false AND class_exists('vBulletinHook', false))
832 {
833 $hook_code['pre_fetch'] = vBulletinHook::fetch_hook('database_pre_fetch_row');
834 $hook_code['post_fetch'] = vBulletinHook::fetch_hook('database_post_fetch_row');
835 }
836
837 if ($hook_code['pre_fetch'])
838 {
839 $result = false;
840
841 eval($hook_code['pre_fetch']);
842
843 if ($result)
844 {
845 return $result;
846 }
847 }
848
849 $result = @$this->functions['fetch_row']($queryresult);
850
851 if ($hook_code['post_fetch'])
852 {
853 eval($hook_code['post_fetch']);
854 }
855
856 return $result;
857 }
858
859 /**
860 * Fetches a row information from a query result and returns the values from that row as an array
861 *
862 * @param string The query result ID we are dealing with
863 *
864 * @return array
865 */
866 function fetch_field($queryresult)
867 {
868 static $hook_code = false;
869
870 if ($hook_code === false AND class_exists('vBulletinHook', false))
871 {
872 $hook_code['pre_fetch'] = vBulletinHook::fetch_hook('database_pre_fetch_field');
873 $hook_code['post_fetch'] = vBulletinHook::fetch_hook('database_post_fetch_field');
874 }
875
876 if ($hook_code['pre_fetch'])
877 {
878 $result = false;
879
880 eval($hook_code['pre_fetch']);
881
882 if ($result)
883 {
884 return $result;
885 }
886 }
887
888 $result = @$this->functions['fetch_field']($queryresult);
889
890 if ($hook_code['post_fetch'])
891 {
892 eval($hook_code['post_fetch']);
893 }
894
895 return $result;
896 }
897
898 /**
899 * Moves the internal result pointer within a query result set
900 *
901 * @param string The query result ID we are dealing with
902 * @param integer The position to which to move the pointer (first position is 0)
903 *
904 * @return boolean
905 */
906 function data_seek($queryresult, $index)
907 {
908 return @$this->functions['data_seek']($queryresult, $index);
909 }
910
911 /**
912 * Frees all memory associated with the specified query result
913 *
914 * @param string The query result ID we are dealing with
915 *
916 * @return boolean
917 */
918 function free_result($queryresult)
919 {
920 $this->sql = '';
921 return @$this->functions['free_result']($queryresult);
922 }
923
924 /**
925 * Retuns the number of rows affected by the most recent insert/replace/update query
926 *
927 * @return integer
928 */
929 function affected_rows()
930 {
931 $this->rows = $this->functions['affected_rows']($this->connection_recent);
932 return $this->rows;
933 }
934
935 /**
936 * Ping connection and reconnect
937 * Don't use this in a manner that could cause a loop condition
938 *
939 */
940 function ping()
941 {
942 if (!@$this->functions['ping']($this->connection_master))
943 {
944 $this->close();
945 // make database connection
946 $this->connect(
947 $this->registry->config['Database']['dbname'],
948 $this->registry->config['MasterServer']['servername'],
949 $this->registry->config['MasterServer']['port'],
950 $this->registry->config['MasterServer']['username'],
951 $this->registry->config['MasterServer']['password'],
952 $this->registry->config['MasterServer']['usepconnect'],
953 $this->registry->config['SlaveServer']['servername'],
954 $this->registry->config['SlaveServer']['port'],
955 $this->registry->config['SlaveServer']['username'],
956 $this->registry->config['SlaveServer']['password'],
957 $this->registry->config['SlaveServer']['usepconnect'],
958 $this->registry->config['Mysqli']['ini_file'],
959 (isset($this->registry->config['Mysqli']['charset']) ? $this->registry->config['Mysqli']['charset'] : '')
960 );
961 }
962 }
963
964 /**
965 * Lock tables
966 *
967 * @param mixed List of tables to lock
968 * @param string Type of lock to perform
969 *
970 */
971 function lock_tables($tablelist)
972 {
973 if (!empty($tablelist) AND is_array($tablelist))
974 {
975 // Don't lock tables if we know we might get stuck with them locked (pconnect = true)
976 // mysqli doesn't support pconnect! YAY!
977 // PHP 5.5+ only supports mysqli, so no pconnect again.
978 if (version_compare(phpversion(), '5.5.0', '<')
979 AND strtolower($this->registry->config['Database']['dbtype']) != 'mysqli'
980 AND $this->registry->config['MasterServer']['usepconnect'])
981 {
982 return;
983 }
984
985 $sql = '';
986 foreach($tablelist AS $name => $type)
987 {
988 $sql .= (!empty($sql) ? ', ' : '') . TABLE_PREFIX . $name . " " . $type;
989 }
990
991 $this->query_write("LOCK TABLES $sql");
992 $this->locked = true;
993 }
994 }
995
996 /**
997 * Unlock tables
998 *
999 */
1000 function unlock_tables()
1001 {
1002 // Must be called from exec_shutdown as tables can get stuck locked if pconnects are enabled
1003 // Note: the above case never actually happens as we skip the lock if pconnects are enabled (to be safe)
1004 if ($this->locked)
1005 {
1006 $this->query_write("UNLOCK TABLES");
1007 }
1008 }
1009
1010 /**
1011 * Returns the text of the error message from previous database operation
1012 *
1013 * @return string
1014 */
1015 function error()
1016 {
1017 if ($this->connection_recent === null)
1018 {
1019 $this->error = '';
1020 }
1021 else
1022 {
1023 $this->error = $this->functions['error']($this->connection_recent);
1024 }
1025 return $this->error;
1026 }
1027
1028 /**
1029 * Returns the numerical value of the error message from previous database operation
1030 *
1031 * @return integer
1032 */
1033 function errno()
1034 {
1035 if ($this->connection_recent === null)
1036 {
1037 $this->errno = 0;
1038 }
1039 else
1040 {
1041 $this->errno = $this->functions['errno']($this->connection_recent);
1042 }
1043 return $this->errno;
1044 }
1045
1046 /**
1047 * Switches database error display ON
1048 */
1049 function show_errors()
1050 {
1051 $this->reporterror = true;
1052 }
1053
1054 /**
1055 * Switches database error display OFF
1056 */
1057 function hide_errors()
1058 {
1059 $this->reporterror = false;
1060 }
1061
1062 /**
1063 * Halts execution of the entire system and displays an error message
1064 *
1065 * @param string Text of the error message. Leave blank to use $this->sql as error text.
1066 *
1067 * @return integer
1068 */
1069 function halt($errortext = '')
1070 {
1071 global $vbulletin;
1072
1073 if ($this->connection_recent)
1074 {
1075 $this->error = $this->error($this->connection_recent);
1076 $this->errno = $this->errno($this->connection_recent);
1077 }
1078
1079 if ($this->reporterror)
1080 {
1081 if ($errortext == '')
1082 {
1083 $this->sql = "Invalid SQL:\r\n" . chop($this->sql) . ';';
1084 $errortext =& $this->sql;
1085 }
1086
1087 // Try and stop e-mail flooding.
1088 if (!$vbulletin->options['disableerroremail'])
1089 {
1090 if (!$vbulletin->options['safeupload'])
1091 {
1092 $tempdir = ini_get('upload_tmp_dir');
1093 }
1094 else
1095 {
1096 $tempdir = $vbulletin->options['tmppath'] . '/';
1097 }
1098
1099 $unique = md5(COOKIE_SALT);
1100 $tempfile = $tempdir."zdberr$unique.dat";
1101
1102 /* If its less than a minute since the last e-mail
1103 and the error code is the same as last time, disable e-mail */
1104 if ($data = @file_get_contents($tempfile))
1105 {
1106 $errc = intval(substr($data, 10));
1107 $time = intval(substr($data, 0, 10));
1108 if ($time AND (TIMENOW - $time) < 60
1109 AND intval($this->errno) == $errc)
1110 {
1111 $vbulletin->options['disableerroremail'] = true;
1112 }
1113 else
1114 {
1115 $data = TIMENOW.intval($this->errno);
1116 @file_put_contents($tempfile, $data);
1117 }
1118 }
1119 else
1120 {
1121 $data = TIMENOW.intval($this->errno);
1122 @file_put_contents($tempfile, $data);
1123 }
1124 }
1125
1126 $vboptions =& $vbulletin->options;
1127 $technicalemail =& $vbulletin->config['Database']['technicalemail'];
1128 $bbuserinfo =& $vbulletin->userinfo;
1129 $requestdate = date('l, F jS Y @ h:i:s A', TIMENOW);
1130 $date = date('l, F jS Y @ h:i:s A');
1131 $scriptpath = str_replace('&', '&', $vbulletin->scriptpath);
1132 $referer = REFERRER;
1133 $ipaddress = IPADDRESS;
1134 $classname = get_class($this);
1135
1136 if ($this->connection_recent)
1137 {
1138 $this->hide_errors();
1139 list($mysqlversion) = $this->query_first("SELECT VERSION() AS version", DBARRAY_NUM);
1140 $this->show_errors();
1141 }
1142
1143 $display_db_error = (VB_AREA == 'Upgrade' OR VB_AREA == 'Install' OR $vbulletin->userinfo['permissions']['adminpermissions'] & $vbulletin->bf_ugp_adminpermissions['cancontrolpanel']);
1144
1145 // Hide the MySQL Version if its going in the source
1146 if (!$display_db_error)
1147 {
1148 $mysqlversion = '';
1149 }
1150
1151 eval('$message = "' . str_replace('"', '\"', file_get_contents(DIR . '/includes/database_error_message.html')) . '";');
1152
1153 // add a backtrace to the message
1154 if ($vbulletin->debug)
1155 {
1156 $trace = debug_backtrace();
1157 $trace_output = "\n";
1158
1159 foreach ($trace AS $index => $trace_item)
1160 {
1161 $param = (in_array($trace_item['function'], array('require', 'require_once', 'include', 'include_once')) ? $trace_item['args'][0] : '');
1162
1163 // remove path
1164 $param = str_replace(DIR, '[path]', $param);
1165 $trace_item['file'] = str_replace(DIR, '[path]', $trace_item['file']);
1166
1167 $trace_output .= "#$index $trace_item[class]$trace_item[type]$trace_item[function]($param) called in $trace_item[file] on line $trace_item[line]\n";
1168 }
1169
1170 $message .= "\n\nStack Trace:\n$trace_output\n";
1171 }
1172
1173 require_once(DIR . '/includes/functions_log_error.php');
1174 if (function_exists('log_vbulletin_error'))
1175 {
1176 log_vbulletin_error($message, 'database');
1177 }
1178
1179 if ($technicalemail != '' AND !$vbulletin->options['disableerroremail'] AND verify_email_vbulletin_error($this->errno, 'database'))
1180 {
1181 // If vBulletinHook is defined then we know that options are loaded, so we can then use vbmail
1182 if (class_exists('vBulletinHook', false))
1183 {
1184 @vbmail($technicalemail, $this->appshortname . ' Database Error!', $message, true, $technicalemail);
1185 }
1186 else
1187 {
1188 @mail($technicalemail, $this->appshortname . ' Database Error!', preg_replace("#(\r\n|\r|\n)#s", (@ini_get('sendmail_path') === '') ? "\r\n" : "\n", $message), "From: $technicalemail");
1189 }
1190 }
1191
1192 if (defined('STDIN'))
1193 {
1194 echo $message;
1195 exit;
1196 }
1197
1198 // send ajax reponse after sending error email
1199 if ($vbulletin->GPC['ajax'])
1200 {
1201 require_once(DIR . '/includes/class_xml.php');
1202 $xml = new vB_AJAX_XML_Builder($vbulletin, 'text/xml');
1203
1204 $error = '<p>Database Error</p>';
1205 if ($vbulletin->debug OR VB_AREA == 'Upgrade')
1206 {
1207 $error .= "\r\n\r\n$errortext";
1208 $error .= "\r\n\r\n{$this->error}";
1209 }
1210
1211 eval('$ajaxmessage = "' . str_replace('"', '\"', file_get_contents(DIR . '/includes/database_error_message_ajax.html')) . '";');
1212
1213 $xml->add_group('errors');
1214 $xml->add_tag('error', $error);
1215 $xml->add_tag('error_html', $ajaxmessage);
1216 $xml->close_group('errors');
1217
1218 $xml->print_xml();
1219 }
1220
1221 if (!headers_sent())
1222 {
1223 if (SAPI_NAME == 'cgi' OR SAPI_NAME == 'cgi-fcgi')
1224 {
1225 header('Status: 503 Service Unavailable');
1226 }
1227 else
1228 {
1229 header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
1230 }
1231 }
1232
1233 if ($display_db_error)
1234 {
1235 // display error message on screen
1236 $message = '<form><textarea rows="15" cols="70" wrap="off" id="message">' . htmlspecialchars_uni($message) . '</textarea></form>';
1237 }
1238 else if ($vbulletin->debug)
1239 {
1240 // display hidden error message
1241 $message = "\r\n<!--\r\n" . htmlspecialchars_uni($message) . "\r\n-->\r\n";
1242 }
1243 else
1244 {
1245 $message = '';
1246 }
1247
1248 if ($vbulletin->options['bburl'])
1249 {
1250 $imagepath = $vbulletin->options['bburl'];
1251 }
1252 else
1253 {
1254 // this might not work with too many slashes in the archive
1255 $imagepath = (VB_AREA == 'Forum' ? '.' : '..');
1256 }
1257
1258 eval('$message = "' . str_replace('"', '\"', file_get_contents(DIR . '/includes/database_error_page.html')) . '";');
1259
1260 // This is needed so IE doesn't show the pretty error messages
1261 $message .= str_repeat(' ', 512);
1262 die($message);
1263 }
1264 else if (!empty($errortext))
1265 {
1266 $this->error = $errortext;
1267 }
1268 }
1269}
1270
1271// #############################################################################
1272// MySQLi Database Class
1273
1274/**
1275* Class to interface with a MySQL 4.1 database
1276*
1277* This class also handles data replication between a master and slave(s) servers
1278*
1279* @package vBulletin
1280* @version $Revision: 78140 $
1281* @date $Date: 2013-10-16 14:51:06 -0700 (Wed, 16 Oct 2013) $
1282*/
1283class vB_Database_MySQLi extends vB_Database
1284{
1285 /**
1286 * Array of function names, mapping a simple name to the RDBMS specific function name
1287 *
1288 * @var array
1289 */
1290 var $functions = array(
1291 'connect' => 'mysqli_real_connect',
1292 'pconnect' => 'mysqli_real_connect', // mysqli doesn't support persistent connections THANK YOU!
1293 'select_db' => 'mysqli_select_db',
1294 'query' => 'mysqli_query',
1295 'query_unbuffered' => 'mysqli_unbuffered_query',
1296 'fetch_row' => 'mysqli_fetch_row',
1297 'fetch_array' => 'mysqli_fetch_array',
1298 'fetch_field' => 'mysqli_fetch_field',
1299 'free_result' => 'mysqli_free_result',
1300 'data_seek' => 'mysqli_data_seek',
1301 'error' => 'mysqli_error',
1302 'errno' => 'mysqli_errno',
1303 'affected_rows' => 'mysqli_affected_rows',
1304 'num_rows' => 'mysqli_num_rows',
1305 'num_fields' => 'mysqli_num_fields',
1306 'field_name' => 'mysqli_field_tell',
1307 'insert_id' => 'mysqli_insert_id',
1308 'escape_string' => 'mysqli_real_escape_string',
1309 'real_escape_string' => 'mysqli_real_escape_string',
1310 'close' => 'mysqli_close',
1311 'client_encoding' => 'mysqli_character_set_name',
1312 'ping' => 'mysqli_ping',
1313 );
1314
1315 /**
1316 * Array of constants for use in fetch_array
1317 *
1318 * @var array
1319 */
1320 var $fetchtypes = array(
1321 DBARRAY_NUM => MYSQLI_NUM,
1322 DBARRAY_ASSOC => MYSQLI_ASSOC,
1323 DBARRAY_BOTH => MYSQLI_BOTH
1324 );
1325
1326 /**
1327 * Initialize database connection(s)
1328 *
1329 * Connects to the specified master database server, and also to the slave server if it is specified
1330 *
1331 * @param string Name of the database server - should be either 'localhost' or an IP address
1332 * @param integer Port of the database server - usually 3306
1333 * @param string Username to connect to the database server
1334 * @param string Password associated with the username for the database server
1335 * @param string Persistent Connections - Not supported with MySQLi
1336 * @param string Configuration file from config.php.ini (my.ini / my.cnf)
1337 * @param string Mysqli Connection Charset PHP 5.1.0+ or 5.0.5+ / MySQL 4.1.13+ or MySQL 5.1.10+ Only
1338 *
1339 * @return object Mysqli Resource
1340 */
1341 function db_connect($servername, $port, $username, $password, $usepconnect, $configfile = '', $charset = '')
1342 {
1343 if (function_exists('catch_db_error'))
1344 {
1345 set_error_handler('catch_db_error');
1346 }
1347
1348 $link = mysqli_init();
1349 # Set Options Connection Options
1350 if (!empty($configfile))
1351 {
1352 mysqli_options($link, MYSQLI_READ_DEFAULT_FILE, $configfile);
1353 }
1354
1355 // this will execute at most 5 times, see catch_db_error()
1356 do
1357 {
1358 $connect = $this->functions['connect']($link, $servername, $username, $password, '', $port);
1359 }
1360 while ($connect == false AND $this->reporterror);
1361
1362 restore_error_handler();
1363
1364 if (!empty($charset))
1365 {
1366 if (function_exists('mysqli_set_charset'))
1367 {
1368 mysqli_set_charset($link, $charset);
1369 }
1370 else
1371 {
1372 $this->sql = "SET NAMES $charset";
1373 $this->execute_query(true, $link);
1374 }
1375 }
1376
1377 return (!$connect) ? false : $link;
1378 }
1379
1380 /**
1381 * Executes an SQL query through the specified connection
1382 *
1383 * @param boolean Whether or not to run this query buffered (true) or unbuffered (false). Default is unbuffered.
1384 * @param string The connection ID to the database server
1385 *
1386 * @return string
1387 */
1388 function &execute_query($buffered = true, &$link)
1389 {
1390 $this->connection_recent =& $link;
1391 $this->querycount++;
1392
1393 if ($queryresult = mysqli_query($link, $this->sql, ($buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT)))
1394 {
1395 // unset $sql to lower memory .. this isn't an error, so it's not needed
1396 $this->sql = '';
1397
1398 return $queryresult;
1399 }
1400 else
1401 {
1402 $this->halt();
1403
1404 // unset $sql to lower memory .. error will have already been thrown
1405 $this->sql = '';
1406 }
1407 }
1408
1409 /**
1410 * Simple wrapper for select_db(), to allow argument order changes
1411 *
1412 * @param string Database name
1413 * @param integer Link identifier
1414 *
1415 * @return boolean
1416 */
1417 function select_db_wrapper($database = '', $link = null)
1418 {
1419 return $this->functions['select_db']($link, $database);
1420 }
1421
1422 /**
1423 * Escapes a string to make it safe to be inserted into an SQL query
1424 *
1425 * @param string The string to be escaped
1426 *
1427 * @return string
1428 */
1429 function escape_string($string)
1430 {
1431 return $this->functions['real_escape_string']($this->connection_master, $string);
1432 }
1433
1434 /**
1435 * Returns the name of a field from within a query result set
1436 *
1437 * @param string The query result ID we are dealing with
1438 * @param integer The index position of the field
1439 *
1440 * @return string
1441 */
1442 function field_name($queryresult, $index)
1443 {
1444 $field = @$this->functions['fetch_field']($queryresult);
1445 return $field->name;
1446 }
1447
1448 /**
1449 * Switches database error display ON
1450 */
1451 function show_errors()
1452 {
1453 $this->reporterror = true;
1454 mysqli_report(MYSQLI_REPORT_ERROR);
1455 }
1456
1457 /**
1458 * Switches database error display OFF
1459 */
1460 function hide_errors()
1461 {
1462 $this->reporterror = false;
1463 mysqli_report(MYSQLI_REPORT_OFF);
1464 }
1465
1466}
1467
1468// #############################################################################
1469// datastore class
1470
1471/**
1472* Class for fetching and initializing the vBulletin datastore from the database
1473*
1474* @package vBulletin
1475* @version $Revision: 78140 $
1476* @date $Date: 2013-10-16 14:51:06 -0700 (Wed, 16 Oct 2013) $
1477*/
1478class vB_Datastore
1479{
1480 /**
1481 * Default items that are always loaded by fetch();
1482 *
1483 * @var array
1484 */
1485 var $defaultitems = array(
1486 'navdata',
1487 'options',
1488 'bitfields',
1489 'attachmentcache',
1490 'forumcache',
1491 'usergroupcache',
1492 'stylecache',
1493 'languagecache',
1494 'products',
1495 'pluginlist',
1496 'cron',
1497 'profilefield',
1498 'loadcache',
1499 'noticecache',
1500 'activitystream',
1501 'routes',
1502 );
1503
1504 /**
1505 * All of the entries that have already been fetched
1506 *
1507 * @var array string
1508 */
1509 static $registered = array();
1510
1511 /**
1512 * This variable should be set to be a reference to the registry object
1513 *
1514 * @var vB_Registry
1515 */
1516 var $registry = null;
1517
1518 /**
1519 * This variable should be set to be a reference to the database object
1520 *
1521 * @var vB_Database
1522 */
1523 var $dbobject = null;
1524
1525 /**
1526 * Unique prefix for item's title, required for multiple forums on the same server using the same classes that read/write to memory
1527 *
1528 * @var string
1529 */
1530 var $prefix = '';
1531
1532 /**
1533 * Whether we have verified that options were loaded correctly.
1534 *
1535 * @var bool
1536 */
1537 var $checked_options;
1538
1539 /**
1540 * Constructor - establishes the database object to use for datastore queries
1541 *
1542 * @param vB_Registry The registry object
1543 * @param vB_Database The database object
1544 */
1545 function vB_Datastore(&$registry, &$dbobject)
1546 {
1547 $this->registry =& $registry;
1548 $this->dbobject =& $dbobject;
1549
1550 $this->prefix =& $this->registry->config['Datastore']['prefix'];
1551
1552 if (defined('SKIP_DEFAULTDATASTORE'))
1553 {
1554 $this->defaultitems = array('options', 'bitfields', 'pluginlist');
1555 }
1556
1557 if (!is_object($registry))
1558 {
1559 trigger_error('<strong>vB_Datastore</strong>: $this->registry is not an object', E_USER_ERROR);
1560 }
1561 if (!is_object($dbobject))
1562 {
1563 trigger_error('<strong>vB_Datastore</strong>: $this->dbobject is not an object!', E_USER_ERROR);
1564 }
1565 }
1566
1567 /**
1568 * Sorts the data returned from the cache and places it into appropriate places
1569 *
1570 * @param string The name of the data item to be processed
1571 * @param mixed The data associated with the title
1572 * @param integer If the data needs to be unserialized, 0 = no, 1 = yes, 2 = auto detect
1573 *
1574 * @return boolean
1575 */
1576 function register($title, $data, $unserialize_detect = 2)
1577 {
1578 // specifies whether or not $data should be an array
1579 $try_unserialize = (($unserialize_detect == 2) AND ($data[0] == 'a' AND $data[1] == ':'));
1580
1581 if ($try_unserialize OR $unserialize_detect == 1)
1582 {
1583 // unserialize returned an error so return false
1584 if (($data = unserialize($data)) === false)
1585 {
1586 return false;
1587 }
1588 }
1589
1590 if ($title == 'bitfields')
1591 {
1592 $registry =& $this->registry;
1593
1594 foreach (array_keys($data) AS $group)
1595 {
1596 $registry->{'bf_' . $group} =& $data["$group"];
1597
1598 $group_prefix = 'bf_' . $group . '_';
1599 $group_info =& $data["$group"];
1600
1601 foreach (array_keys($group_info) AS $subgroup)
1602 {
1603 $registry->{$group_prefix . $subgroup} =& $group_info["$subgroup"];
1604 }
1605 }
1606 }
1607 else if (!empty($title))
1608 {
1609 $this->registry->$title = $data;
1610 }
1611
1612 // Ensure items are not refetched
1613 self::$registered[] = $title;
1614
1615 return true;
1616 }
1617
1618 /**
1619 * Prepares a list of items for fetching.
1620 * Items that are already fetched are skipped.
1621 *
1622 * @param array string $items - Array of item titles that are required
1623 * @return array string - An array of items that need to be fetched
1624 */
1625 function prepare_itemarray($items)
1626 {
1627 if ($items)
1628 {
1629 if (is_array($items))
1630 {
1631 $itemarray = $items;
1632 }
1633 else
1634 {
1635 $itemarray = explode(',', $items);
1636
1637 foreach($itemarray AS &$title)
1638 {
1639 $title = trim($title);
1640 }
1641 }
1642 // Include default items
1643 $itemarray = array_merge($itemarray, $this->defaultitems);
1644 }
1645 else
1646 {
1647 $itemarray = $this->defaultitems;
1648 }
1649
1650 // Remove anything that is already loaded
1651 $itemarray = array_diff($itemarray, vB_DataStore::$registered);
1652
1653 return $itemarray;
1654 }
1655
1656 /**
1657 * Prepares an array of items into a list.
1658 * The result is a comma delimited, db escaped, quoted list for use in SQL.
1659 *
1660 * @param array string $items - An array of item titles
1661 * @param bool $prepare_items - Wether to check the items first
1662 *
1663 * @return string - A sql safe comma delimited list
1664 */
1665 function prepare_itemlist($items, $prepare_items = false)
1666 {
1667 if (is_string($items) OR $prepare_items)
1668 {
1669 $items = $this->prepare_itemarray($items);
1670 }
1671
1672 if (!sizeof($items))
1673 {
1674 return false;
1675 }
1676
1677 foreach ($items AS &$item)
1678 {
1679 $item = "'" . $this->dbobject->escape_string($item) . "'";
1680 }
1681
1682 return implode(',', $items);
1683 }
1684
1685 /**
1686 * Fetches the contents of the datastore from the database
1687 *
1688 * @param array Array of items to fetch from the datastore
1689 *
1690 * @return boolean
1691 */
1692 function fetch($items)
1693 {
1694 if ($items = $this->prepare_itemlist($items, true))
1695 {
1696 $result = $this->do_db_fetch($items);
1697 if (!$result)
1698 {
1699 return false;
1700 }
1701 }
1702
1703 $this->check_options();
1704 return true;
1705 }
1706
1707 /**
1708 * Performs the actual fetching of the datastore items for the database, child classes may use this
1709 *
1710 * @param string title of the datastore item
1711 *
1712 * @return bool Valid Query?
1713 */
1714 function do_db_fetch($itemlist)
1715 {
1716 $db =& $this->dbobject;
1717
1718 $db->hide_errors();
1719 $dataitems = $db->query_read("
1720 SELECT *
1721 FROM " . TABLE_PREFIX . "datastore
1722 WHERE title IN ($itemlist)
1723 ");
1724 $db->show_errors();
1725 while ($dataitem = $db->fetch_array($dataitems))
1726 {
1727 $this->register($dataitem['title'], $dataitem['data'], (isset($dataitem['unserialize']) ? $dataitem['unserialize'] : 2));
1728 }
1729 $db->free_result($dataitems);
1730
1731 return (!$db->errno());
1732 }
1733
1734 /**
1735 * Checks that the options item has come out of the datastore correctly
1736 * and sets the 'versionnumber' variable
1737 */
1738 function check_options()
1739 {
1740 if ($this->checked_options)
1741 {
1742 return;
1743 }
1744
1745 if (!isset($this->registry->options['templateversion']))
1746 {
1747 // fatal error - options not loaded correctly
1748 require_once(DIR . '/includes/adminfunctions.php');
1749 require_once(DIR . '/includes/functions.php');
1750 $this->register('options', build_options(), 0);
1751 }
1752
1753 // set the short version number
1754 $this->registry->options['simpleversion'] = SIMPLE_VERSION . (isset($this->registry->config['Misc']['jsver']) ? $this->registry->config['Misc']['jsver'] : '');
1755
1756 // set the version number variable
1757 $this->registry->versionnumber =& $this->registry->options['templateversion'];
1758
1759 $this->checked_options = true;
1760 }
1761}
1762
1763// #############################################################################
1764// input handler class
1765
1766/**#@+
1767* Ways of cleaning input. Should be mostly self-explanatory.
1768*/
1769define('TYPE_NOCLEAN', 0); // no change
1770
1771define('TYPE_BOOL', 1); // force boolean
1772define('TYPE_INT', 2); // force integer
1773define('TYPE_UINT', 3); // force unsigned integer
1774define('TYPE_NUM', 4); // force number
1775define('TYPE_UNUM', 5); // force unsigned number
1776define('TYPE_UNIXTIME', 6); // force unix datestamp (unsigned integer)
1777define('TYPE_STR', 7); // force trimmed string
1778define('TYPE_NOTRIM', 8); // force string - no trim
1779define('TYPE_NOHTML', 9); // force trimmed string with HTML made safe
1780define('TYPE_ARRAY', 10); // force array
1781define('TYPE_FILE', 11); // force file
1782define('TYPE_BINARY', 12); // force binary string
1783define('TYPE_NOHTMLCOND', 13); // force trimmed string with HTML made safe if determined to be unsafe
1784
1785define('TYPE_ARRAY_BOOL', 101);
1786define('TYPE_ARRAY_INT', 102);
1787define('TYPE_ARRAY_UINT', 103);
1788define('TYPE_ARRAY_NUM', 104);
1789define('TYPE_ARRAY_UNUM', 105);
1790define('TYPE_ARRAY_UNIXTIME', 106);
1791define('TYPE_ARRAY_STR', 107);
1792define('TYPE_ARRAY_NOTRIM', 108);
1793define('TYPE_ARRAY_NOHTML', 109);
1794define('TYPE_ARRAY_ARRAY', 110);
1795define('TYPE_ARRAY_FILE', 11); // An array of "Files" behaves differently than other <input> arrays. TYPE_FILE handles both types.
1796define('TYPE_ARRAY_BINARY', 112);
1797define('TYPE_ARRAY_NOHTMLCOND',113);
1798
1799define('TYPE_ARRAY_KEYS_INT', 202);
1800define('TYPE_ARRAY_KEYS_STR', 207);
1801
1802define('TYPE_CONVERT_SINGLE', 100); // value to subtract from array types to convert to single types
1803define('TYPE_CONVERT_KEYS', 200); // value to subtract from array => keys types to convert to single types
1804/**#@-*/
1805
1806// temporary
1807define('INT', TYPE_INT);
1808define('STR', TYPE_STR);
1809define('STR_NOHTML', TYPE_NOHTML);
1810define('FILE', TYPE_FILE);
1811
1812/**
1813* Class to handle and sanitize variables from GET, POST and COOKIE etc
1814*
1815* @package vBulletin
1816* @version $Revision: 78140 $
1817* @date $Date: 2013-10-16 14:51:06 -0700 (Wed, 16 Oct 2013) $
1818*/
1819class vB_Input_Cleaner
1820{
1821 /**
1822 * Translation table for short name to long name
1823 *
1824 * @var array
1825 */
1826 var $shortvars = array(
1827 'f' => 'forumid',
1828 't' => 'threadid',
1829 'p' => 'postid',
1830 'u' => 'userid',
1831 'a' => 'announcementid',
1832 'c' => 'calendarid',
1833 'e' => 'eventid',
1834 'q' => 'query',
1835 'pp' => 'perpage',
1836 'page' => 'pagenumber',
1837 'sort' => 'sortfield',
1838 'order' => 'sortorder',
1839 );
1840
1841 /**
1842 * Translation table for short superglobal name to long superglobal name
1843 *
1844 * @var array
1845 */
1846 var $superglobal_lookup = array(
1847 'g' => '_GET',
1848 'p' => '_POST',
1849 'r' => '_REQUEST',
1850 'c' => '_COOKIE',
1851 's' => '_SERVER',
1852 'e' => '_ENV',
1853 'f' => '_FILES'
1854 );
1855
1856 /**
1857 * System state. The complete URL of the current page, without sessionhash
1858 *
1859 * @var string
1860 */
1861 var $scriptpath = '';
1862
1863 /**
1864 * Reload URL. Complete URL of the current page including sessionhash
1865 *
1866 * @var string
1867 */
1868 var $reloadurl = '';
1869
1870 /**
1871 * System state. The complete URL of the page for Who's Online purposes
1872 *
1873 * @var string
1874 */
1875 var $wolpath = '';
1876
1877 /**
1878 * System state. The complete URL of the referring page
1879 *
1880 * @var string
1881 */
1882 var $url = '';
1883
1884 /**
1885 * System state. The IP address of the current visitor
1886 *
1887 * @var string
1888 */
1889 var $ipaddress = '';
1890
1891 /**
1892 * System state. An attempt to find a second IP for the current visitor (proxy etc)
1893 *
1894 * @var string
1895 */
1896 var $alt_ip = '';
1897
1898 /**
1899 * A reference to the main registry object
1900 *
1901 * @var vB_Registry
1902 */
1903 var $registry = null;
1904
1905 /**
1906 * Keep track of variables that have already been cleaned
1907 *
1908 * @var array
1909 */
1910 var $cleaned_vars = array();
1911
1912 /**
1913 * Constructor
1914 *
1915 * First, reverses the effects of magic quotes on GPC
1916 * Second, translates short variable names to long (u --> userid)
1917 * Third, deals with $_COOKIE[userid] conflicts
1918 *
1919 * @param vB_Registry The instance of the vB_Registry object
1920 */
1921 function vB_Input_Cleaner(&$registry)
1922 {
1923 $this->registry =& $registry;
1924
1925 if (!is_array($GLOBALS))
1926 {
1927 die('<strong>Fatal Error:</strong> Invalid URL.');
1928 }
1929
1930 // resolve the request URL
1931 $this->resolve_request_url($registry);
1932
1933 // store a relative path that includes the sessionhash for reloadurl
1934 $registry->reloadurl = $this->xss_clean($this->add_query(VB_URL_PATH, VB_URL_QUERY_RAW));
1935 // store the current script
1936 $registry->script = SCRIPT;
1937
1938 // store the scriptpath
1939 $registry->scriptpath = $this->xss_clean($this->add_query(VB_URL_PATH, VB_URL_QUERY));
1940
1941 // overwrite GET[x] and REQUEST[x] with POST[x] if it exists (overrides server's GPC order preference)
1942 if ($_SERVER['REQUEST_METHOD'] == 'POST')
1943 {
1944 foreach (array_keys($_POST) AS $key)
1945 {
1946 if (isset($_GET["$key"]))
1947 {
1948 $_GET["$key"] = $_REQUEST["$key"] = $_POST["$key"];
1949 }
1950 }
1951 }
1952
1953 // deal with session bypass situation
1954 if (!defined('SESSION_BYPASS'))
1955 {
1956 define('SESSION_BYPASS', !empty($_REQUEST['bypass']));
1957 }
1958
1959 // reverse the effects of magic quotes if necessary
1960 if (function_exists('get_magic_quotes_gpc') AND get_magic_quotes_gpc())
1961 {
1962 $this->stripslashes_deep($_REQUEST); // needed for some reason (at least on php5 - not tested on php4)
1963 $this->stripslashes_deep($_GET);
1964 $this->stripslashes_deep($_POST);
1965 $this->stripslashes_deep($_COOKIE);
1966
1967 if (is_array($_FILES))
1968 {
1969 foreach ($_FILES AS $key => $val)
1970 {
1971 $_FILES["$key"]['tmp_name'] = str_replace('\\', '\\\\', $val['tmp_name']);
1972 }
1973 $this->stripslashes_deep($_FILES);
1974 }
1975 }
1976
1977 //Deprecated/Removed as of php 5.3
1978 if (version_compare(phpversion(), '5.3.0', '<'))
1979 {
1980 @set_magic_quotes_runtime(0);
1981 @ini_set('magic_quotes_sybase', 0);
1982 }
1983
1984 foreach (array('_GET', '_POST') AS $arrayname)
1985 {
1986 if (isset($GLOBALS["$arrayname"]['do']))
1987 {
1988 $GLOBALS["$arrayname"]['do'] = trim($GLOBALS["$arrayname"]['do']);
1989 }
1990
1991 $this->convert_shortvars($GLOBALS["$arrayname"]);
1992 }
1993
1994 // set the AJAX flag if we have got an AJAX submission
1995 // unless the request explictly doesn't want us to. The problem with this is that it hits any XMLHttpRequest
1996 // even if we have a request for which the ajax handling is not appropriate (for example JQUERY mobile which
1997 // uses XMLHttpRequest for everything and expects html to come back). Ideally we'd use a less blunt force
1998 // approach to handling AJAX behavior in the first place, but this allows specific requests to avoid it.
1999 if ($_SERVER['REQUEST_METHOD'] == 'POST' AND $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' AND
2000 !(isset($_REQUEST['forcenoajax']) AND $_REQUEST['forcenoajax']))
2001 {
2002 $_POST['ajax'] = $_REQUEST['ajax'] = 1;
2003 }
2004
2005 // reverse the effects of register_globals if necessary
2006 if (@ini_get('register_globals') OR !@ini_get('gpc_order'))
2007 {
2008 foreach ($this->superglobal_lookup AS $arrayname)
2009 {
2010 $registry->superglobal_size["$arrayname"] = sizeof($GLOBALS["$arrayname"]);
2011
2012 foreach (array_keys($GLOBALS["$arrayname"]) AS $varname)
2013 {
2014 // make sure we dont unset any global arrays like _SERVER
2015 if (!in_array($varname, $this->superglobal_lookup))
2016 {
2017 unset($GLOBALS["$varname"]);
2018 }
2019 }
2020 }
2021 }
2022 else
2023 {
2024 foreach ($this->superglobal_lookup AS $arrayname)
2025 {
2026 $registry->superglobal_size["$arrayname"] = sizeof($GLOBALS["$arrayname"]);
2027 }
2028 }
2029
2030 // deal with cookies that may conflict with _GET and _POST data, and create our own _REQUEST with no _COOKIE input
2031 foreach (array_keys($_COOKIE) AS $varname)
2032 {
2033 unset($_REQUEST["$varname"]);
2034 if (isset($_POST["$varname"]))
2035 {
2036 $_REQUEST["$varname"] =& $_POST["$varname"];
2037 }
2038 else if (isset($_GET["$varname"]))
2039 {
2040 $_REQUEST["$varname"] =& $_GET["$varname"];
2041 }
2042 }
2043
2044 // fetch client IP address
2045 $registry->ipaddress = $this->fetch_ip();
2046 $registry->alt_ip = $this->fetch_alt_ip();
2047
2048 // fetch url of current page for Who's Online
2049 if (!defined('SKIP_WOLPATH') OR !SKIP_WOLPATH)
2050 {
2051 $registry->wolpath = $this->fetch_wolpath();
2052 define('WOLPATH', $registry->wolpath);
2053 }
2054
2055 // define some useful contants related to environment
2056 define('USER_AGENT', $_SERVER['HTTP_USER_AGENT']);
2057 define('REFERRER', $_SERVER['HTTP_REFERER']);
2058
2059 // All requests passed from API client should be in UTF-8 encoding and we need to convert it back to vB's current encoding.
2060 // We also need to do this this for the ajax requests.
2061 if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' OR (defined('VB_API') AND VB_API === true))
2062 {
2063 define('NEED_DECODE', true);
2064 }
2065
2066 }
2067
2068 /**
2069 * Resolves information about the request URL.
2070 */
2071 function resolve_request_url($registry)
2072 {
2073 // Get server port
2074 $port = intval($_SERVER['SERVER_PORT']);
2075 $port = in_array($port, array(80, 443)) ? '' : ':' . $port;
2076
2077 // resolve the request scheme
2078 $scheme = ((':443' == $port) OR (isset($_SERVER['HTTPS']) AND $_SERVER['HTTPS'] AND ($_SERVER['HTTPS'] != 'off'))) ? 'https://' : 'http://';
2079
2080 if ($scheme == 'http://' AND $_SERVER['SERVER_PORT'] == 443)
2081 {
2082 $port = ':443';
2083 }
2084
2085 $host = $this->fetch_server_value('HTTP_HOST');
2086 $name = $this->fetch_server_value('SERVER_NAME');
2087
2088 // If host exists use it, otherwise fallback to servername.
2089 $host = ( !empty($host) ? $host : $name );
2090
2091 // resolve the query
2092 $query = ($query = $this->fetch_server_value('QUERY_STRING')) ? '?' . $query : '';
2093 $query = $this->urlencode_query($query);
2094
2095 // resolve the path and query
2096 if (!($scriptpath = $this->fetch_server_value('REQUEST_URI')))
2097 {
2098 if (!($scriptpath = $this->fetch_server_value('UNENCODED_URL')))
2099 {
2100 $scriptpath = $this->fetch_server_value('HTTP_X_REWRITE_URL');
2101 }
2102 }
2103
2104 if ($scriptpath)
2105 {
2106 $scriptpath = $this->urlencode_query($scriptpath);
2107 $query = '';
2108 }
2109 else
2110 {
2111 // server hasn't provided a URI, try to resolve one
2112 if (!$scriptpath = $this->fetch_server_value('PATH_INFO'))
2113 {
2114 if (!$scriptpath = $this->fetch_server_value('REDIRECT_URL'))
2115 {
2116 if (!($scriptpath = $this->fetch_server_value('URL')))
2117 {
2118 if (!($scriptpath = $this->fetch_server_value('PHP_SELF')))
2119 {
2120 $scriptpath = $this->fetch_server_value('SCRIPT_NAME');
2121 }
2122 }
2123 }
2124 }
2125 }
2126
2127 // build the URL
2128 $url = $scheme . $host . '/' . ltrim($scriptpath, '/\\') . $query;
2129
2130 // store a literal version
2131 define('VB_URL', $url);
2132
2133 // check relative path
2134 if (defined('VB_RELATIVE_PATH'))
2135 {
2136 define('VB_URL_RELATIVE_PATH', trim(VB_RELATIVE_PATH, '/') . '/');
2137 }
2138 else
2139 {
2140 define('VB_URL_RELATIVE_PATH', '');
2141 }
2142
2143 // Set URL info
2144 $url_info = $this->parse_url(VB_URL);
2145 $url_info['path'] = (isset($url_info['path']) ? '/' . ltrim($url_info['path'], '/\\') : '');
2146 $url_info['query_raw'] = (isset($url_info['query']) ? $url_info['query'] : '');
2147 $url_info['query'] = $this->strip_sessionhash($url_info['query']);
2148 $url_info['query'] = trim($url_info['query'], '?&') ? $url_info['query'] : '';
2149
2150 /*
2151 values seen in the wild:
2152
2153 CGI+suexec:
2154 SCRIPT_NAME: /vb4/admincp/index.php
2155 ORIG_SCRIPT_NAME: /cgi-sys/php53-fcgi-starter.fcgi
2156
2157 CGI #1:
2158 SCRIPT_NAME: /index.php
2159 ORIG_SCRIPT_NAME: /search/foo
2160
2161 CGI #2:
2162 SCRIPT_NAME: /index.php/search/foo
2163 ORIG_SCRIPT_NAME: /index.php
2164
2165 */
2166
2167 if (substr(PHP_SAPI, -3) == 'cgi' AND (isset($_SERVER['ORIG_SCRIPT_NAME']) AND !empty($_SERVER['ORIG_SCRIPT_NAME'])))
2168 {
2169 if (substr($_SERVER['SCRIPT_NAME'], 0, strlen($_SERVER['ORIG_SCRIPT_NAME'])) == $_SERVER['ORIG_SCRIPT_NAME'])
2170 {
2171 // cgi #2 above
2172 $url_info['script'] = $_SERVER['ORIG_SCRIPT_NAME'];
2173 }
2174 else
2175 {
2176 // cgi #1 and CGI+suexec above
2177 $url_info['script'] = $_SERVER['SCRIPT_NAME'];
2178 }
2179 }
2180 else
2181 {
2182 $url_info['script'] = (isset($_SERVER['ORIG_SCRIPT_NAME']) AND !empty($_SERVER['ORIG_SCRIPT_NAME'])) ? $_SERVER['ORIG_SCRIPT_NAME'] : $_SERVER['SCRIPT_NAME'];
2183 }
2184 $url_info['script'] = '/' . ltrim($url_info['script'], '/\\');
2185
2186 // define constants
2187 define('VB_URL_SCHEME', $url_info['scheme']);
2188 define('VB_URL_HOST', $url_info['host']);
2189 define('VB_URL_PORT', $port);
2190 define('VB_URL_SCRIPT_PATH', rtrim(dirname($url_info['script']), '/\\') . '/');
2191 define('VB_URL_SCRIPT', basename($url_info['script']));
2192 define('VB_URL_PATH', urldecode($url_info['path']));
2193 define('VB_URL_PATH_RAW', $url_info['path']);
2194 define('VB_URL_QUERY', $url_info['query'] ? $url_info['query'] : '');
2195 define('VB_URL_QUERY_RAW', $url_info['query_raw']);
2196 define('VB_URL_CLEAN', $this->xss_clean($this->strip_sessionhash(VB_URL)));
2197 define('VB_URL_WEBROOT', $this->xss_clean(VB_URL_SCHEME . '://' . VB_URL_HOST . VB_URL_PORT));
2198 define('VB_URL_BASE_PATH', $this->xss_clean(VB_URL_SCHEME . '://' . VB_URL_HOST . VB_URL_PORT . VB_URL_SCRIPT_PATH . VB_URL_RELATIVE_PATH));
2199
2200 // legacy constants
2201 define('SCRIPT', $_SERVER['SCRIPT_NAME']);
2202 define('SCRIPTPATH', $this->xss_clean($this->add_query(VB_URL_PATH)));
2203 define('REQ_PROTOCOL', $url_info['scheme']);
2204 define('VB_HTTP_HOST', $url_info['host']);
2205 }
2206
2207 /**
2208 * Workaround for a UTF8 compatible parse_url
2209 */
2210
2211 function parse_url($url, $component = -1)
2212 {
2213 // Taken from /rfc3986#section-2
2214 $safechars =array(':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'' ,'(', ')', '*', '+', ',', ';', '=');
2215 $trans = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D');
2216 $encodedurl = str_replace($trans, $safechars, urlencode($url));
2217
2218 $parsed = @parse_url($encodedurl, $component);
2219 if(is_array($parsed))
2220 {
2221 foreach ($parsed AS $index => $element)
2222 {
2223 $parsed[$index] = urldecode($element);
2224 }
2225 }
2226 else
2227 {
2228 $parsed = urldecode($parsed);
2229 }
2230
2231 return $parsed;
2232 }
2233
2234 function urlencode_query($url)
2235 {
2236 $useragent = strtolower($_SERVER['HTTP_USER_AGENT']);
2237 if (strpos($useragent, 'opera') !== false)
2238 {
2239 preg_match('#opera(/| )([0-9\.]+)#', $useragent, $regs);
2240 $isopera = $regs[2];
2241 }
2242 if (strpos($useragent, 'msie ') !== false AND !$isopera)
2243 {
2244 preg_match('#msie ([0-9\.]+)#', $useragent, $regs);
2245 $isie = $regs[1];
2246 }
2247 if (!$isie)
2248 {
2249 return $url;
2250 }
2251
2252 $querystring = array();
2253 $bits = explode('?', $url);
2254 if ($bits[1])
2255 {
2256 $bits[1] = urldecode($bits[1]);
2257 $subbits = explode('&', $bits[1]);
2258 foreach ($subbits AS $querypart)
2259 {
2260 $querybit = explode('=', $querypart);
2261 if ($querybit[1])
2262 {
2263 $querystring[] = urlencode($querybit[0]) . '=' . urlencode($querybit[1]);
2264 }
2265 else
2266 {
2267 $querystring[] = urlencode($querybit[0]);
2268 }
2269 }
2270 return $bits[0] . '?' . implode('&', $querystring);
2271 }
2272 return $url;
2273 }
2274
2275 /**
2276 * Fetches a value from $_SERVER or $_ENV
2277 *
2278 * @param string $name
2279 * @return string
2280 */
2281 function fetch_server_value($name)
2282 {
2283 if (isset($_SERVER[$name]) AND $_SERVER[$name])
2284 {
2285 return $_SERVER[$name];
2286 }
2287
2288 if (isset($_ENV[$name]) AND $_ENV[$name])
2289 {
2290 return $_ENV[$name];
2291 }
2292
2293 return false;
2294 }
2295
2296
2297 /**
2298 * Adds a query string to a path, fixing the query characters.
2299 *
2300 * @param string The path to add the query to
2301 * @param string The query string to add to the path
2302 *
2303 * @return string The resulting string
2304 */
2305 function add_query($path, $query = false)
2306 {
2307 if (false === $query)
2308 {
2309 $query = VB_URL_QUERY;
2310 }
2311
2312 if (!$query OR !($query = trim($query, '?&')))
2313 {
2314 return $path;
2315 }
2316
2317 return $path . '?' . $query;
2318 }
2319
2320 /**
2321 * Adds a fragment to a path
2322 *
2323 * @param string The path to add the fragment to
2324 * @param string The fragment to add to the path
2325 *
2326 * @return string The resulting string
2327 */
2328 function add_fragment($path, $fragment = false)
2329 {
2330 if (!$fragment)
2331 {
2332 return $path;
2333 }
2334
2335 return $path . '#' . $fragment;
2336 }
2337
2338
2339 /**
2340 * Makes data in an array safe to use
2341 *
2342 * @param array The source array containing the data to be cleaned
2343 * @param array Array of variable names and types we want to extract from the source array
2344 *
2345 * @return array
2346 */
2347 function &clean_array(&$source, $variables)
2348 {
2349 $return = array();
2350
2351 foreach ($variables AS $varname => $vartype)
2352 {
2353 $return["$varname"] =& $this->clean($source["$varname"], $vartype, isset($source["$varname"]));
2354 }
2355
2356 return $return;
2357 }
2358
2359 /**
2360 * Makes GPC variables safe to use
2361 *
2362 * @param string Either, g, p, c, r or f (corresponding to get, post, cookie, request and files)
2363 * @param array Array of variable names and types we want to extract from the source array
2364 *
2365 * @return array
2366 */
2367 function clean_array_gpc($source, $variables)
2368 {
2369 $sg =& $GLOBALS[$this->superglobal_lookup["$source"]];
2370
2371 foreach ($variables AS $varname => $vartype)
2372 {
2373 // clean a variable only once unless its a different type
2374 if (!isset($this->cleaned_vars["$varname"]) OR $this->cleaned_vars["$varname"] != $vartype)
2375 {
2376 $this->registry->GPC_exists["$varname"] = isset($sg["$varname"]);
2377 $this->registry->GPC["$varname"] =& $this->clean(
2378 $sg["$varname"],
2379 $vartype,
2380 isset($sg["$varname"])
2381 );
2382 if ((defined('NEED_DECODE') AND NEED_DECODE === true))
2383 {
2384 switch ($vartype)
2385 {
2386 case TYPE_STR:
2387 case TYPE_NOTRIM:
2388 case TYPE_NOHTML:
2389 case TYPE_NOHTMLCOND:
2390 if (!($charset = vB_Template_Runtime::fetchStyleVar('charset')))
2391 {
2392 $charset = $this->registry->userinfo['lang_charset'];
2393 }
2394
2395 $lower_charset = strtolower($charset);
2396 if ($lower_charset != 'utf-8')
2397 {
2398 if ($lower_charset == 'iso-8859-1')
2399 {
2400 $this->registry->GPC["$varname"] = to_charset(ncrencode($this->registry->GPC["$varname"], true, true), 'utf-8');
2401 }
2402 else
2403 {
2404 $this->registry->GPC["$varname"] = to_charset($this->registry->GPC["$varname"], 'utf-8');
2405 }
2406 }
2407 if (function_exists('html_entity_decode') AND defined('VB_API') AND VB_API == true)
2408 {
2409 // this converts certain { entities to their actual character
2410 // note: we don't want to convert >, etc as that undoes the effects of STR_NOHTML
2411 $this->registry->GPC["$varname"] = preg_replace('#&([a-z]+);#i', '&$1;', $this->registry->GPC["$varname"]);
2412
2413 if ($lower_charset == 'windows-1251')
2414 {
2415 // there's a bug in PHP5 html_entity_decode that decodes some entities that
2416 // it shouldn't. So double encode them to ensure they don't get decoded.
2417 $this->registry->GPC["$varname"] = preg_replace('/&#(128|129|1[3-9][0-9]|2[0-4][0-9]|25[0-5]);/', '&#$1;', $this->registry->GPC["$varname"]);
2418 }
2419
2420 $this->registry->GPC["$varname"] = @html_entity_decode($this->registry->GPC["$varname"], ENT_COMPAT, $lower_charset);
2421 }
2422 }
2423 }
2424 $this->cleaned_vars["$varname"] = $vartype;
2425 }
2426 }
2427 }
2428
2429 /**
2430 * Makes a single GPC variable safe to use and returns it
2431 *
2432 * @param array The source array containing the data to be cleaned
2433 * @param string The name of the variable in which we are interested
2434 * @param integer The type of the variable in which we are interested
2435 *
2436 * @return mixed
2437 */
2438 function &clean_gpc($source, $varname, $vartype = TYPE_NOCLEAN)
2439 {
2440 // clean a variable only once unless its a different type
2441 if (!isset($this->cleaned_vars["$varname"]) OR $this->cleaned_vars["$varname"] != $vartype)
2442 {
2443 $sg =& $GLOBALS[$this->superglobal_lookup["$source"]];
2444
2445 $this->registry->GPC_exists["$varname"] = isset($sg["$varname"]);
2446 $this->registry->GPC["$varname"] =& $this->clean(
2447 $sg["$varname"],
2448 $vartype,
2449 isset($sg["$varname"])
2450 );
2451 $this->cleaned_vars["$varname"] = $vartype;
2452 }
2453
2454 return $this->registry->GPC["$varname"];
2455 }
2456
2457 /**
2458 * Makes a single variable safe to use and returns it
2459 *
2460 * @param mixed The variable to be cleaned
2461 * @param integer The type of the variable in which we are interested
2462 * @param boolean Whether or not the variable to be cleaned actually is set
2463 *
2464 * @return mixed The cleaned value
2465 */
2466 function &clean(&$var, $vartype = TYPE_NOCLEAN, $exists = true)
2467 {
2468 if ($exists)
2469 {
2470 if ($vartype < TYPE_CONVERT_SINGLE)
2471 {
2472 $this->do_clean($var, $vartype);
2473 }
2474 else if (is_array($var))
2475 {
2476 if ($vartype >= TYPE_CONVERT_KEYS)
2477 {
2478 $var = array_keys($var);
2479 $vartype -= TYPE_CONVERT_KEYS;
2480 }
2481 else
2482 {
2483 $vartype -= TYPE_CONVERT_SINGLE;
2484 }
2485
2486 foreach (array_keys($var) AS $key)
2487 {
2488 $this->do_clean($var["$key"], $vartype);
2489 }
2490 }
2491 else
2492 {
2493 $var = array();
2494 }
2495 return $var;
2496 }
2497 else
2498 {
2499 // We use $newvar here to prevent overwrite superglobals. See bug #28898.
2500 if ($vartype < TYPE_CONVERT_SINGLE)
2501 {
2502 switch ($vartype)
2503 {
2504 case TYPE_INT:
2505 case TYPE_UINT:
2506 case TYPE_NUM:
2507 case TYPE_UNUM:
2508 case TYPE_UNIXTIME:
2509 {
2510 $newvar = 0;
2511 break;
2512 }
2513 case TYPE_STR:
2514 case TYPE_NOHTML:
2515 case TYPE_NOTRIM:
2516 case TYPE_NOHTMLCOND:
2517 {
2518 $newvar = '';
2519 break;
2520 }
2521 case TYPE_BOOL:
2522 {
2523 $newvar = 0;
2524 break;
2525 }
2526 case TYPE_ARRAY:
2527 case TYPE_FILE:
2528 {
2529 $newvar = array();
2530 break;
2531 }
2532 case TYPE_NOCLEAN:
2533 {
2534 $newvar = null;
2535 break;
2536 }
2537 default:
2538 {
2539 $newvar = null;
2540 }
2541 }
2542 }
2543 else
2544 {
2545 $newvar = array();
2546 }
2547
2548 return $newvar;
2549 }
2550 }
2551
2552 /**
2553 * Does the actual work to make a variable safe
2554 *
2555 * @param mixed The data we want to make safe
2556 * @param integer The type of the data
2557 *
2558 * @return mixed
2559 */
2560 function &do_clean(&$data, $type)
2561 {
2562 static $booltypes = array('1', 'yes', 'y', 'true', 'on');
2563
2564 switch ($type)
2565 {
2566 case TYPE_NUM:
2567 case TYPE_UNUM:
2568 // Account for language specific separators
2569 if (isset($this->registry->userinfo['lang_decimalsep']) AND $this->registry->userinfo['lang_decimalsep'] != '')
2570 {
2571 $data = strtr($data, array($this->registry->userinfo['lang_decimalsep'] => '.', $this->registry->userinfo['lang_thousandsep'] => ''));
2572 }
2573 }
2574
2575 switch ($type)
2576 {
2577 case TYPE_INT: $data = intval($data); break;
2578 case TYPE_UINT: $data = ($data = intval($data)) < 0 ? 0 : $data; break;
2579 case TYPE_NUM: $data = strval($data) + 0; break;
2580 case TYPE_UNUM: $data = strval($data) + 0;
2581 $data = ($data < 0) ? 0 : $data; break;
2582 case TYPE_BINARY: $data = strval($data); break;
2583 case TYPE_STR: $data = trim(strval($data)); break;
2584 case TYPE_NOTRIM: $data = strval($data); break;
2585 case TYPE_NOHTML: $data = htmlspecialchars_uni(trim(strval($data))); break;
2586 case TYPE_BOOL: $data = in_array(strtolower($data), $booltypes) ? 1 : 0; break;
2587 case TYPE_ARRAY: $data = (is_array($data)) ? $data : array(); break;
2588 case TYPE_NOHTMLCOND:
2589 {
2590 $data = trim(strval($data));
2591 if (strcspn($data, '<>"') < strlen($data) OR (strpos($data, '&') !== false AND !preg_match('/&(#[0-9]+|amp|lt|gt|quot);/si', $data)))
2592 {
2593 // data is not htmlspecialchars because it still has characters or entities it shouldn't
2594 $data = htmlspecialchars_uni($data);
2595 }
2596 break;
2597 }
2598 case TYPE_FILE:
2599 {
2600 // perhaps redundant :p
2601 if (is_array($data))
2602 {
2603 if (is_array($data['name']))
2604 {
2605 $files = count($data['name']);
2606 for ($index = 0; $index < $files; $index++)
2607 {
2608 $data['name']["$index"] = trim(strval($data['name']["$index"]));
2609 $data['type']["$index"] = trim(strval($data['type']["$index"]));
2610 $data['tmp_name']["$index"] = trim(strval($data['tmp_name']["$index"]));
2611 $data['error']["$index"] = intval($data['error']["$index"]);
2612 $data['size']["$index"] = intval($data['size']["$index"]);
2613 }
2614 }
2615 else
2616 {
2617 $data['name'] = trim(strval($data['name']));
2618 $data['type'] = trim(strval($data['type']));
2619 $data['tmp_name'] = trim(strval($data['tmp_name']));
2620 $data['error'] = intval($data['error']);
2621 $data['size'] = intval($data['size']);
2622 }
2623 }
2624 else
2625 {
2626 $data = array(
2627 'name' => '',
2628 'type' => '',
2629 'tmp_name' => '',
2630 'error' => 0,
2631 'size' => 4, // UPLOAD_ERR_NO_FILE
2632 );
2633 }
2634 break;
2635 }
2636 case TYPE_UNIXTIME:
2637 {
2638 if (is_array($data))
2639 {
2640 $data = $this->clean($data, TYPE_ARRAY_UINT);
2641 if ($data['month'] AND $data['day'] AND $data['year'])
2642 {
2643 require_once(DIR . '/includes/functions_misc.php');
2644 $data = vbmktime($data['hour'], $data['minute'], $data['second'], $data['month'], $data['day'], $data['year']);
2645 }
2646 else
2647 {
2648 $data = 0;
2649 }
2650 }
2651 else
2652 {
2653 $data = ($data = intval($data)) < 0 ? 0 : $data;
2654 }
2655 break;
2656 }
2657 // null actions should be deifned here so we can still catch typos below
2658 case TYPE_NOCLEAN:
2659 {
2660 break;
2661 }
2662
2663 default:
2664 {
2665 if ($this->registry->debug)
2666 {
2667 trigger_error('vB_Input_Cleaner::do_clean() Invalid data type specified', E_USER_WARNING);
2668 }
2669 }
2670 }
2671
2672 // strip out characters that really have no business being in non-binary data
2673 switch ($type)
2674 {
2675 case TYPE_STR:
2676 case TYPE_NOTRIM:
2677 case TYPE_NOHTML:
2678 case TYPE_NOHTMLCOND:
2679 $data = str_replace(chr(0), '', $data);
2680 }
2681
2682 return $data;
2683 }
2684
2685 /**
2686 * Removes HTML characters and potentially unsafe scripting words from a string
2687 *
2688 * @param string The variable we want to make safe
2689 *
2690 * @return string
2691 */
2692 function xss_clean($var)
2693 {
2694 static
2695 $preg_find = array('#^javascript#i', '#^vbscript#i'),
2696 $preg_replace = array('java script', 'vb script');
2697
2698 return preg_replace($preg_find, $preg_replace, htmlspecialchars_uni(trim($var)));
2699 }
2700
2701 /**
2702 * Removes HTML characters and potentially unsafe scripting words from a URL
2703 * Note: The query string is preserved.
2704 *
2705 * @param string The url to clean
2706 * @return string
2707 */
2708 function xss_clean_url($url)
2709 {
2710 $query = $this->parse_url($url, PHP_URL_QUERY);
2711 $fragment = $this->parse_url($url, PHP_URL_FRAGMENT);
2712 $clean_url = false;
2713
2714 if ($query)
2715 {
2716 $url = substr($url, 0, strpos($url, '?'));
2717 $url = $this->xss_clean($url);
2718 $clean_url = true;
2719 }
2720
2721 if ($fragment AND !$clean_url)
2722 {
2723 $url = substr($url, 0, strpos($url, '#'));
2724 $url = $this->xss_clean($url);
2725 $clean_url = true;
2726 }
2727
2728 if (!$clean_url)
2729 {
2730 $url = $this->xss_clean($url);
2731 }
2732
2733 $query = ($query) ? '?' . $query : '';
2734 $fragment = ($fragment) ? '#' . $fragment : '';
2735 $url = $url . $query . $fragment;
2736
2737 return $url;
2738 }
2739
2740
2741 /**
2742 * Cleans a query string.
2743 * Unicode is decoded, url entities are kept encoded, and slashes are preserved.
2744 *
2745 * @param string $path
2746 * @return string
2747 */
2748 function utf8_clean_path($path, $reencode = true)
2749 {
2750 $path = explode('/', $path);
2751 $path = array_map('urldecode', $path);
2752
2753 if ($reencode)
2754 {
2755 $path = array_map('urlencode_uni', $path);
2756 }
2757
2758 $path = implode('/', $path);
2759
2760 return $path;
2761 }
2762
2763
2764 /**
2765 * Reverses the effects of magic_quotes on an entire array of variables
2766 *
2767 * @param array The array on which we want to work
2768 */
2769 function stripslashes_deep(&$value, $depth = 0)
2770 {
2771 if (is_array($value))
2772 {
2773 foreach ($value AS $key => $val)
2774 {
2775 if (is_string($val))
2776 {
2777 $value["$key"] = stripslashes($val);
2778 }
2779 else if (is_array($val) AND $depth < 10)
2780 {
2781 $this->stripslashes_deep($value["$key"], $depth + 1);
2782 }
2783 }
2784 }
2785 }
2786
2787 /**
2788 * Turns $_POST['t'] into $_POST['threadid'] etc.
2789 *
2790 * @param array The name of the array
2791 */
2792 function convert_shortvars(&$array, $setglobals = true)
2793 {
2794 // extract long variable names from short variable names
2795 foreach ($this->shortvars AS $shortname => $longname)
2796 {
2797 if (isset($array["$shortname"]) AND !isset($array["$longname"]))
2798 {
2799 $array["$longname"] =& $array["$shortname"];
2800 if ($setglobals)
2801 {
2802 $GLOBALS['_REQUEST']["$longname"] =& $array["$shortname"];
2803 }
2804 }
2805 }
2806 }
2807
2808 /**
2809 * Strips out the s=gobbledygook& rubbish from URLs
2810 *
2811 * @param string The URL string from which to remove the session stuff
2812 *
2813 * @return string
2814 */
2815 function strip_sessionhash($string)
2816 {
2817 $string = preg_replace('/(s|sessionhash)=[a-z0-9]{32}?&?/', '', $string);
2818 return $string;
2819 }
2820
2821 /**
2822 * Fetches the 'basepath' variable that can be used as <base>.
2823 *
2824 * @return string
2825 */
2826 function fetch_basepath($rel_modifier = false)
2827 {
2828 if ($this->registry->basepath != '')
2829 {
2830 return $this->registry->basepath;
2831 }
2832
2833 if ($this->registry->options['bburl_basepath'])
2834 {
2835 $basepath = trim($this->registry->options['bburl'], '/\\') . '/';
2836 }
2837 else
2838 {
2839 $basepath = VB_URL_BASE_PATH;
2840 }
2841
2842 return $basepath = $basepath . ($rel_modifier ? $this->xss_clean($rel_modifier) : '');
2843 }
2844
2845 /**
2846 * Fetches the path for the current request relative to the basepath.
2847 * This is useful for local anchors (<a href="{vb:raw relpath}#post">).
2848 *
2849 * Substracts any overlap between basepath and path with the following results:
2850 *
2851 * base: http://www.example.com/forums/
2852 * path: /forums/content.php
2853 * result: content.php
2854 *
2855 * base: http://www.example.com/forums/admincp
2856 * path: /forums/content/1-Article
2857 * result: ../content/1-Article
2858 *
2859 * @return string
2860 */
2861 function fetch_relpath($path = false)
2862 {
2863 if (!$path AND ($this->registry->relpath != ''))
2864 {
2865 return $this->registry->relpath;
2866 }
2867
2868 // if no path specified, use the request path
2869 if (!$path)
2870 {
2871 if ($_SERVER['REQUEST_METHOD'] == 'POST' AND $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' AND $_POST['relpath'])
2872 {
2873 $relpath = unhtmlspecialchars($_POST['relpath']);
2874 $query = '';
2875 }
2876 else
2877 {
2878 $relpath = VB_URL_PATH;
2879 $query = VB_URL_QUERY;
2880 }
2881 }
2882 else
2883 {
2884 // if the path is already absolute there's nothing to do
2885 if (strpos($path, '://'))
2886 {
2887 return $path;
2888 }
2889
2890 if (!$path)
2891 {
2892 return $path;
2893 }
2894
2895 $relpath = $this->parse_url($path, PHP_URL_PATH);
2896 $query = $this->parse_url($path, PHP_URL_QUERY);
2897 $fragment = $this->parse_url($path, PHP_URL_FRAGMENT);
2898 }
2899
2900 $relpath = ltrim($relpath, '/');
2901 $basepath = $this->parse_url($this->fetch_basepath(), PHP_URL_PATH);
2902 $basepath = trim($basepath, '/');
2903
2904 // get path segments for comparison
2905 $relpath = explode('/', $relpath);
2906 $basepath = explode('/', $basepath);
2907
2908 // remove segments that basepath and relpath share
2909 foreach ($basepath AS $segment)
2910 {
2911 if ($segment == current($relpath))
2912 {
2913 array_shift($basepath);
2914 array_shift($relpath);
2915 }
2916 else
2917 {
2918 break;
2919 }
2920 }
2921
2922 // rebuild the relpath
2923 $relpath = implode('/', $relpath);
2924
2925 /*
2926 // if basepath is in another dir, back out of it
2927 if ($diff = sizeof($basepath))
2928 {
2929 $relpath = str_repeat('../', $diff) . $relpath;
2930 }
2931 */
2932
2933 // add the query string if the current path is being used
2934 if ($query)
2935 {
2936 $relpath = $this->add_query($relpath, $query);
2937 }
2938
2939 // add the fragment back
2940 if ($fragment)
2941 {
2942 $relpath = $this->add_fragment($relpath, $fragment);
2943 }
2944
2945 return $relpath;
2946 }
2947
2948 /**
2949 * Fetches the 'wolpath' variable - ie: the same as 'scriptpath' but with a handler for the POST request method
2950 *
2951 * @return string
2952 */
2953 function fetch_wolpath()
2954 {
2955 $wolpath = SCRIPTPATH;
2956
2957 if ($_SERVER['REQUEST_METHOD'] == 'POST')
2958 {
2959 // Tag the variables back on to the filename if we are coming from POST so that WOL can access them.
2960 $tackon = '';
2961
2962 if (is_array($_POST))
2963 {
2964 foreach ($_POST AS $varname => $value)
2965 {
2966 switch ($varname)
2967 {
2968 case 'forumid':
2969 case 'threadid':
2970 case 'postid':
2971 case 'userid':
2972 case 'eventid':
2973 case 'calendarid':
2974 case 'do':
2975 case 'method': // postings.php
2976 case 'dowhat': // private.php
2977 {
2978 $tackon .= ($tackon == '' ? '' : '&') . $varname . '=' . $value;
2979 break;
2980 }
2981 }
2982 }
2983 }
2984 if ($tackon != '')
2985 {
2986 $wolpath .= (strpos($wolpath, '?') !== false ? '&' : '?') . "$tackon";
2987 }
2988 }
2989
2990 return $wolpath;
2991 }
2992
2993 /**
2994 * Fetches the 'url' variable - usually the URL of the previous page in the history
2995 *
2996 * @return string
2997 */
2998 function fetch_url()
2999 {
3000 $scriptpath = SCRIPTPATH;
3001
3002 //note regarding the default url if not set or inappropriate.
3003 //started out as index.php then moved to options['forumhome'] . '.php' when that option was added.
3004 //now we've changed to to the forumhome url since there is now quite a bit of logic around that.
3005 //Its not clear, however, with the expansion of vb if that's the most appropriate generic landing
3006 //place (perhaps it *should* be index.php).
3007 //In any case there are several places in the code that check for the default page url and change it
3008 //to something more appropriate. If the default url changes, so do those checks.
3009 //The solution is, most likely, to make some note when vbulletin->url is the default so it can be overridden
3010 //without worrying about what the exact text is.
3011 if (empty($_REQUEST['url']))
3012 {
3013 $url = (!empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '');
3014 }
3015 else
3016 {
3017 $temp_url = $_REQUEST['url'];
3018 if (!empty($_SERVER['HTTP_REFERER']) AND $temp_url == $_SERVER['HTTP_REFERER'])
3019 {
3020 //$url = 'index.php'
3021 // I am unsure why we redirect to forumhome if we have $url defined and it matches HTTP_REFERRER
3022 // Must be a security check that has been here since at least 2003
3023 // So to keep from breaking something we will check if the url is something we know
3024 $found = false;
3025 $pathinfo = $this->parse_url($temp_url);
3026 $options = array(
3027 $this->registry->options['vbforum_url'],
3028 $this->registry->options['vbblog_url'],
3029 $this->registry->options['vbcms_url'],
3030 $this->registry->options['bburl']
3031 );
3032 foreach($options AS $value)
3033 {
3034 if ($value AND $info = $this->parse_url($value))
3035 {
3036 if ("{$pathinfo['scheme']}://{$pathinfo['host']}" == "{$info['scheme']}://{$info['host']}")
3037 {
3038 $found = true;
3039 $url = $temp_url;
3040 break;
3041 }
3042 }
3043 }
3044
3045 if (!$found)
3046 {
3047 $url = fetch_seo_url('forumhome|nosession', array());
3048 }
3049 }
3050 else
3051 {
3052 $url = $temp_url;
3053 }
3054 }
3055
3056 if ($url == $scriptpath OR empty($url))
3057 {
3058 //$url = 'index.php';
3059 $url = fetch_seo_url('forumhome|nosession', array());
3060 }
3061
3062 //not a lot of point in doing this as a seperate step.
3063 // if $url is set to forum home page, check it against options
3064 //if ($url == 'index.php' AND $this->registry->options['forumhome'] != 'index')
3065 //{
3066 // $url = $this->registry->options['forumhome'] . '.php';
3067 //}
3068
3069 $url = $this->xss_clean($url);
3070 return $url;
3071 }
3072
3073 /**
3074 * Fetches the IP address of the current visitor
3075 *
3076 * @return string
3077 */
3078 function fetch_ip()
3079 {
3080 return $_SERVER['REMOTE_ADDR'];
3081 }
3082
3083 /**
3084 * Fetches an alternate IP address of the current visitor, attempting to detect proxies etc.
3085 *
3086 * @return string
3087 */
3088 function fetch_alt_ip()
3089 {
3090 $alt_ip = $_SERVER['REMOTE_ADDR'];
3091
3092 if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
3093 {
3094 $altip = $_SERVER['HTTP_X_FORWARDED_FOR'];
3095 }
3096 else if (isset($_SERVER['HTTP_CLIENT_IP']))
3097 {
3098 $altip = $_SERVER['HTTP_CLIENT_IP'];
3099 }
3100 else if (isset($_SERVER['HTTP_FROM']))
3101 {
3102 $altip = $_SERVER['HTTP_FROM'];
3103 }
3104 else
3105 {
3106 $altip = false;
3107 }
3108
3109 if ($altip AND $this->filter_ip($altip))
3110 {
3111 $alt_ip = $altip;
3112 }
3113
3114 return $alt_ip;
3115 }
3116
3117 /**
3118 * Validate the IP address (both ipv4 & ipv6)
3119 *
3120 * @return string
3121 */
3122 function filter_ip($ip)
3123 {
3124 return filter_var($ip, FILTER_VALIDATE_IP);
3125 }
3126}
3127
3128// #############################################################################
3129// data registry class
3130
3131/**
3132* Class to store commonly-used variables
3133*
3134* @package vBulletin
3135* @version $Revision: 78140 $
3136* @date $Date: 2013-10-16 14:51:06 -0700 (Wed, 16 Oct 2013) $
3137*/
3138class vB_Registry
3139{
3140 // general objects
3141 /**
3142 * Datastore object.
3143 *
3144 * @var vB_Datastore
3145 */
3146 var $datastore;
3147
3148 /**
3149 * Input cleaner object.
3150 *
3151 * @var vB_Input_Cleaner
3152 */
3153 var $input;
3154
3155 /**
3156 * Database object.
3157 *
3158 * @var vB_Database
3159 */
3160 var $db;
3161
3162 // user/session related
3163 /**
3164 * Array of info about the current browsing user. In the case of a registered
3165 * user, this will be results of fetch_userinfo(). A guest will have slightly
3166 * different entries.
3167 *
3168 * @var array
3169 */
3170 var $userinfo;
3171
3172 /**
3173 * Session object.
3174 *
3175 * @var vB_Session
3176 */
3177 var $session;
3178
3179 /**
3180 * Array of do actions that are exempt from checks
3181 *
3182 * @var array
3183 */
3184 var $csrf_skip_list = array();
3185
3186 // configuration
3187 /**
3188 * Array of data from config.php.
3189 *
3190 * @var array
3191 */
3192 var $config;
3193
3194 // GPC input
3195 /**
3196 * Array of data that has been cleaned by the input cleaner.
3197 *
3198 * @var array
3199 */
3200 var $GPC = array();
3201
3202 /**
3203 * Array of booleans. When cleaning a variable, you often lose the ability
3204 * to determine if it was specified in the user's input. Entries in this
3205 * array are true if the variable existed before cleaning.
3206 *
3207 * @var array
3208 */
3209 var $GPC_exists = array();
3210
3211 /**
3212 * The size of the super global arrays.
3213 *
3214 * @var array
3215 */
3216 var $superglobal_size = array();
3217
3218 // single variables
3219 /**
3220 * IP Address of the current browsing user.
3221 *
3222 * @var string
3223 */
3224 var $ipaddress;
3225
3226 /**
3227 * Alternate IP for the browsing user. This attempts to use various HTTP headers
3228 * to find the real IP of a user that may be behind a proxy.
3229 *
3230 * @var string
3231 */
3232 var $alt_ip;
3233
3234 /**
3235 * The URL of the currently browsed page.
3236 *
3237 * @var string
3238 */
3239 var $scriptpath;
3240
3241 /**
3242 * The request basepath.
3243 * Use for <base>
3244 *
3245 * @var string
3246 */
3247 var $basepath;
3248
3249 /**
3250 * Similar to the URL of the current page, but expands some items and includes
3251 * data submitted via POST. Used for Who's Online purposes.
3252 *
3253 * @var string
3254 */
3255 var $wolpath;
3256
3257 /**
3258 * The URL of the current page, without anything after the '?'.
3259 *
3260 * @var string
3261 */
3262 var $script;
3263
3264 /**
3265 * Generally the URL of the referring page if there is one, though it is often
3266 * set in various places of the code. Used to determine the page to redirect
3267 * to, if necessary.
3268 *
3269 * @var string
3270 */
3271 var $url;
3272
3273 // usergroup permission bitfields
3274 /**#@+
3275 * Bitfield arrays for usergroup permissions.
3276 *
3277 * @var array
3278 */
3279 var $bf_ugp;
3280 // $bf_ugp_x is a reference to $bf_ugp['x']
3281 var $bf_ugp_adminpermissions;
3282 var $bf_ugp_calendarpermissions;
3283 var $bf_ugp_forumpermissions;
3284 var $bf_ugp_genericoptions;
3285 var $bf_ugp_genericpermissions;
3286 var $bf_ugp_pmpermissions;
3287 var $bf_ugp_wolpermissions;
3288 var $bf_ugp_visitormessagepermissions;
3289 /**#@-*/
3290
3291 // misc bitfield arrays
3292 /**#@+
3293 * Bitfield arrays for miscellaneous permissions and options.
3294 *
3295 * @var array
3296 */
3297 var $bf_misc;
3298 // $bf_misc_x is a reference to $bf_misc['x']
3299 var $bf_misc_calmoderatorpermissions;
3300 var $bf_misc_forumoptions;
3301 var $bf_misc_intperms;
3302 var $bf_misc_languageoptions;
3303 var $bf_misc_moderatorpermissions;
3304 var $bf_misc_useroptions;
3305 var $bf_misc_hvcheck;
3306 /**#@-*/
3307
3308 /**#@+
3309 * Results for specific entries in the datastore.
3310 *
3311 * @var mixed Mixed, though mostly arrays.
3312 */
3313 var $options = null;
3314 var $attachmentcache = null;
3315 var $avatarcache = null;
3316 var $birthdaycache = null;
3317 var $eventcache = null;
3318 var $forumcache = null;
3319 var $iconcache = null;
3320 var $markupcache = null;
3321 var $stylecache = null;
3322 var $languagecache = null;
3323 var $smiliecache = null;
3324 var $usergroupcache = null;
3325 var $bbcodecache = null;
3326 var $socialsitecache = null;
3327 var $cron = null;
3328 var $mailqueue = null;
3329 var $banemail = null;
3330 var $maxloggedin = null;
3331 var $pluginlist = null;
3332 var $products = null;
3333 var $ranks = null;
3334 var $statement = null;
3335 var $userstats = null;
3336 var $wol_spiders = null;
3337 var $loadcache = null;
3338 var $noticecache = null;
3339 var $prefixcache = null;
3340 /**#@-*/
3341
3342 /**#@+
3343 * Miscellaneous variables
3344 *
3345 * @var mixed
3346 */
3347 var $bbcode_style = array('code' => -1, 'html' => -1, 'php' => -1, 'quote' => -1);
3348 var $templatecache = array();
3349 var $iforumcache = array();
3350 var $versionnumber;
3351 var $nozip;
3352 var $debug;
3353 var $noheader;
3354 public $stylevars;
3355
3356 /**
3357 * Shutdown handler
3358 *
3359 * @var vB_Shutdown
3360 */
3361 var $shutdown;
3362 /**#@-*/
3363
3364 /**
3365 * For storing global information specific to the CMS
3366 *
3367 * @var array
3368 */
3369 var $vbcms = array();
3370
3371
3372 /**
3373 * For storing information of the API Client
3374 *
3375 * @var array
3376 */
3377 var $apiclient = array();
3378
3379 /**
3380 * Constructor - initializes the nozip system,
3381 * and calls and instance of the vB_Input_Cleaner class
3382 */
3383 function vB_Registry()
3384 {
3385 // variable to allow bypassing of gzip compression
3386 $this->nozip = defined('NOZIP') ? true : (@ini_get('zlib.output_compression') ? true : false);
3387 // variable that controls HTTP header output
3388 $this->noheader = defined('NOHEADER') ? true : false;
3389
3390 @ini_set('zend.ze1_compatibility_mode', 0);
3391
3392 // initialize the input handler
3393 $this->input = new vB_Input_Cleaner($this);
3394
3395 // initialize the shutdown handler
3396 $this->shutdown = vB_Shutdown::instance();
3397
3398 $this->csrf_skip_list = (defined('CSRF_SKIP_LIST') ? explode(',', CSRF_SKIP_LIST) : array());
3399 }
3400
3401 /**
3402 * Fetches database/system configuration
3403 */
3404 function fetch_config()
3405 {
3406 // parse the config file
3407 $config = array();
3408 include(CWD . '/includes/config.php');
3409
3410 if (sizeof($config) == 0)
3411 {
3412 if (file_exists(CWD. '/includes/config.php'))
3413 {
3414 // config.php exists, but does not define $config
3415 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.');
3416 }
3417 else
3418 {
3419 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');
3420 }
3421 }
3422
3423 $this->config =& $config;
3424 // if a configuration exists for this exact HTTP host, use it
3425 if (isset($this->config["$_SERVER[HTTP_HOST]"]))
3426 {
3427 $this->config['MasterServer'] = $this->config["$_SERVER[HTTP_HOST]"];
3428 }
3429
3430 // define table and cookie prefix constants
3431 define('TABLE_PREFIX', trim($this->config['Database']['tableprefix']));
3432 define('COOKIE_PREFIX', (empty($this->config['Misc']['cookieprefix']) ? 'bb' : $this->config['Misc']['cookieprefix']) . '_');
3433
3434 // set debug mode
3435 $this->debug = !empty($this->config['Misc']['debug']);
3436 define('DEBUG', $this->debug);
3437
3438 $proxy = false;
3439 if (isset($this->config['Misc']['proxyiplist']))
3440 {
3441 $proxylist = array_map('trim', explode(',', $this->config['Misc']['proxyiplist']));
3442
3443 if (in_array($this->ipaddress, $proxylist))
3444 {
3445 $proxy = true;
3446 if (isset($this->config['Misc']['proxyipheader'])
3447 AND isset($_SERVER[$this->config['Misc']['proxyipheader']]))
3448 {
3449 $altip = $_SERVER[$this->config['Misc']['proxyipheader']];
3450 if ($this->input->filter_ip($altip))
3451 {
3452 $this->alt_ip = $altip;
3453 }
3454 }
3455 }
3456 }
3457
3458 if ($proxy)
3459 {
3460 define('ALT_IP', $this->ipaddress);
3461 define('IPADDRESS', $this->alt_ip);
3462 }
3463 else
3464 {
3465 define('IPADDRESS', $this->ipaddress);
3466 define('ALT_IP', $this->alt_ip);
3467 }
3468
3469 define('SESSION_HOST', substr(IPADDRESS, 0, 15));
3470 }
3471
3472 /**
3473 * Takes the contents of an array and recursively uses each title/data
3474 * pair to create a new defined constant.
3475 */
3476 function array_define($array)
3477 {
3478 foreach ($array AS $title => $data)
3479 {
3480 if (is_array($data))
3481 {
3482 vB_Registry::array_define($data);
3483 }
3484 else
3485 {
3486 define(strtoupper($title), $data);
3487 }
3488 }
3489 }
3490
3491 /**
3492 * Check if a user has a specific permission
3493 *
3494 * This is intended to replace direct acces to the userinfo['permissions'] array.
3495 *
3496 * For example:
3497 * $vbulletin->check_user_permission('genericpermissions', 'cancreatetag')
3498 *
3499 * which replaces
3500 * ($vbulletin->userinfo['permissions']['genericpermissions'] &
3501 * $vbulletin->bf_ugp_genericpermissions['cancreatetag'])
3502 *
3503 * @param string $group the permission group to check
3504 * @param string $permission the permission to check within the group
3505 * @return bool If the user has the requested permission
3506 */
3507 public function check_user_permission($group, $permission)
3508 {
3509 return (bool) ($this->userinfo['permissions'][$group] &
3510 $this->{'bf_ugp_' . $group}[$permission]);
3511 }
3512}
3513
3514// #############################################################################
3515// session management class
3516
3517/**
3518* Class to handle sessions
3519*
3520* Creates, updates, and validates sessions; retrieves user info of browsing user
3521*
3522* @package vBulletin
3523* @version $Revision: 78140 $
3524* @date $Date: 2013-10-16 14:51:06 -0700 (Wed, 16 Oct 2013) $
3525*/
3526class vB_Session
3527{
3528 /**
3529 * The individual session variables. Equivalent to $session from the past.
3530 *
3531 * @var array
3532 */
3533 var $vars = array();
3534
3535 /**
3536 * A list of variables in the $vars member that are in the database. Includes their types.
3537 *
3538 * @var array
3539 */
3540 var $db_fields = array(
3541 'sessionhash' => TYPE_STR,
3542 'userid' => TYPE_INT,
3543 'host' => TYPE_STR,
3544 'idhash' => TYPE_STR,
3545 'lastactivity' => TYPE_INT,
3546 'location' => TYPE_STR,
3547 'styleid' => TYPE_INT,
3548 'languageid' => TYPE_INT,
3549 'loggedin' => TYPE_INT,
3550 'inforum' => TYPE_INT,
3551 'inthread' => TYPE_INT,
3552 'incalendar' => TYPE_INT,
3553 'badlocation' => TYPE_INT,
3554 'useragent' => TYPE_STR,
3555 'bypass' => TYPE_INT,
3556 'profileupdate' => TYPE_INT,
3557 'apiclientid' => TYPE_INT,
3558 'apiaccesstoken'=> TYPE_STR,
3559 );
3560
3561 /**
3562 * An array of changes. Used to prevent superfluous updates from being made.
3563 *
3564 * @var array
3565 */
3566 var $changes = array();
3567
3568 /**
3569 * Whether the session was created or existed previously
3570 *
3571 * @var bool
3572 */
3573 var $created = false;
3574
3575 /**
3576 * Reference to a vB_Registry object that keeps various data we need.
3577 *
3578 * @var vB_Registry
3579 */
3580 var $registry = null;
3581
3582 /**
3583 * Information about the user that this session belongs to.
3584 *
3585 * @var array
3586 */
3587 var $userinfo = null;
3588
3589 /**
3590 * Is the sessionhash to be passed through URLs?
3591 *
3592 * @var boolean
3593 */
3594 var $visible = true;
3595
3596 /**
3597 * Constructor. Attempts to grab a session that matches parameters, but will create one if it can't.
3598 *
3599 * @param vB_Registry Reference to a registry object
3600 * @param string Previously specified sessionhash
3601 * @param integer User ID (passed in through a cookie)
3602 * @param string Password, must arrive in cookie format: md5(md5(md5(password) . salt) . 'abcd1234')
3603 * @param integer Style ID for this session
3604 * @param integer Language ID for this session
3605 */
3606 function vB_Session(&$registry, $sessionhash = '', $userid = 0, $password = '', $styleid = 0, $languageid = 0)
3607 {
3608 $userid = intval($userid);
3609 $styleid = intval($styleid);
3610 $languageid = intval($languageid);
3611
3612 $this->registry =& $registry;
3613 $db =& $this->registry->db;
3614 $gotsession = false;
3615
3616 $this->registry->input->clean_gpc('r', 'api');
3617 if (!defined('SESSION_IDHASH'))
3618 {
3619 if (!VB_API AND !$this->registry->GPC['api'])
3620 {
3621 define('SESSION_IDHASH', md5($_SERVER['HTTP_USER_AGENT'] . $this->fetch_substr_ip($this->getIp()))); // this should *never* change during a session
3622 }
3623 else
3624 {
3625 define('SESSION_IDHASH', md5($this->fetch_substr_ip($this->getIp()))); // API session idhash won't have User Agent compiled.
3626 }
3627 }
3628
3629 if (!defined('SKIP_SESSIONCREATE'))
3630 {
3631 $test = false;
3632
3633 if (defined('UNIT_TESTING') AND UNIT_TESTING === true)
3634 {
3635 $test = ($session = $db->query_first_slave("
3636 SELECT *
3637 FROM " . TABLE_PREFIX . "session
3638 WHERE sessionhash = '" . $db->escape_string($sessionhash) . "'
3639 AND lastactivity > " . (TIMENOW - $registry->options['cookietimeout']) . "
3640 "));
3641 }
3642 // apiaccesstoken specified, so see if it already exists
3643 elseif (defined('VB_API') AND VB_API === true AND $this->registry->apiclient['apiaccesstoken'])
3644 {
3645 $test = ($session = $db->query_first_slave("
3646 SELECT *
3647 FROM " . TABLE_PREFIX . "session
3648 WHERE apiaccesstoken = '" . $db->escape_string($this->registry->apiclient['apiaccesstoken']) . "'
3649 AND lastactivity > " . (TIMENOW - $registry->options['cookietimeout']) . "
3650 AND idhash = '" . $this->registry->db->escape_string(SESSION_IDHASH) . "'
3651 ") AND $this->fetch_substr_ip($session['host']) == $this->fetch_substr_ip($this->getIp()));
3652 }
3653 // sessionhash specified, so see if it already exists
3654 elseif ($sessionhash)
3655 {
3656 $test = ($session = $db->query_first_slave("
3657 SELECT *
3658 FROM " . TABLE_PREFIX . "session
3659 WHERE sessionhash = '" . $db->escape_string($sessionhash) . "'
3660 AND lastactivity > " . (TIMENOW - $registry->options['cookietimeout']) . "
3661 AND idhash = '" . $this->registry->db->escape_string(SESSION_IDHASH) . "'
3662 ") AND $this->fetch_substr_ip($session['host']) == $this->fetch_substr_ip($this->getIp()));
3663 }
3664
3665 if ($test)
3666 {
3667 $gotsession = true;
3668 $this->vars =& $session;
3669 $this->created = false;
3670
3671 // found a session - get the userinfo
3672 if ($session['userid'] != 0)
3673 {
3674 $useroptions = (defined('IN_CONTROL_PANEL') ? 16 : 0) + (defined('AVATAR_ON_NAVBAR') ? 2 : 0);
3675 $userinfo = fetch_userinfo($session['userid'], $useroptions, (!empty($languageid) ? $languageid : $session['languageid']));
3676 $this->userinfo =& $userinfo;
3677 }
3678 }
3679 }
3680
3681 // API 'Remember Me'. UserID is stored in apiclient table.
3682 if (($gotsession == false OR empty($session['userid'])) AND defined('VB_API') AND VB_API === true AND $this->registry->apiclient['userid'] AND !defined('SKIP_SESSIONCREATE'))
3683 {
3684 $useroptions = (defined('IN_CONTROL_PANEL') ? FETCH_USERINFO_ADMIN : 0) + (defined('AVATAR_ON_NAVBAR') ? FETCH_USERINFO_AVATAR : 0);
3685 $userinfo = fetch_userinfo($this->registry->apiclient['userid'], $useroptions, $languageid);
3686
3687 $gotsession = true;
3688
3689 // combination is valid
3690 if (!empty($session['sessionhash']))
3691 {
3692 // old session still exists; kill it
3693 $db->shutdown_query("
3694 DELETE FROM " . TABLE_PREFIX . "session
3695 WHERE sessionhash = '" . $this->registry->db->escape_string($session['sessionhash']). "'
3696 ");
3697 }
3698
3699 $this->vars = $this->fetch_session($userinfo['userid']);
3700 $this->created = true;
3701
3702 $this->userinfo =& $userinfo;
3703 }
3704
3705 // or maybe we can use a cookie..
3706 if (($gotsession == false OR empty($session['userid'])) AND $userid AND $password AND !defined('SKIP_SESSIONCREATE') AND !VB_API)
3707 {
3708 $useroptions = (defined('IN_CONTROL_PANEL') ? FETCH_USERINFO_ADMIN : 0) + (defined('AVATAR_ON_NAVBAR') ? FETCH_USERINFO_AVATAR : 0);
3709 $userinfo = fetch_userinfo($userid, $useroptions, $languageid);
3710
3711 if (md5($userinfo['password'] . COOKIE_SALT) == $password)
3712 {
3713 $gotsession = true;
3714
3715 // combination is valid
3716 if (!empty($session['sessionhash']))
3717 {
3718 // old session still exists; kill it
3719 $db->shutdown_query("
3720 DELETE FROM " . TABLE_PREFIX . "session
3721 WHERE sessionhash = '" . $this->registry->db->escape_string($session['sessionhash']). "'
3722 ");
3723 }
3724
3725 $this->vars = $this->fetch_session($userinfo['userid']);
3726 $this->created = true;
3727
3728 $this->userinfo =& $userinfo;
3729 }
3730 }
3731
3732 // at this point, we're a guest, so lets try to *find* a session
3733 // you can prevent this check from being run by passing in a userid with no password
3734 if ($gotsession == false AND $userid == 0 AND !defined('SKIP_SESSIONCREATE'))
3735 {
3736 if ($session = $db->query_first_slave("
3737 SELECT *
3738 FROM " . TABLE_PREFIX . "session
3739 WHERE userid = 0
3740 AND host = '" . $this->registry->db->escape_string($this->getIp()) . "'
3741 AND idhash = '" . $this->registry->db->escape_string(SESSION_IDHASH) . "'
3742 LIMIT 1
3743 "))
3744 {
3745 $gotsession = true;
3746
3747 $this->vars =& $session;
3748 $this->created = false;
3749 }
3750 }
3751
3752 // well, nothing worked, time to create a new session
3753 if ($gotsession == false)
3754 {
3755 $gotsession = true;
3756
3757 $this->vars = $this->fetch_session(0);
3758 $this->created = true;
3759 }
3760
3761 $this->vars['dbsessionhash'] = $this->vars['sessionhash'];
3762
3763 $this->set('styleid', $styleid);
3764 $this->set('languageid', $languageid);
3765 if ($this->created == false)
3766 {
3767 $this->set('useragent', USER_AGENT);
3768 $this->set('lastactivity', TIMENOW);
3769 if (!defined('LOCATION_BYPASS'))
3770 {
3771 $this->set('location', WOLPATH);
3772 }
3773 $this->set('bypass', SESSION_BYPASS);
3774 }
3775 }
3776
3777 /**
3778 * Saves the session into the database by inserting it or updating an existing one.
3779 */
3780 function save()
3781 {
3782 if (defined('SKIP_SESSIONCREATE'))
3783 {
3784 return;
3785 }
3786
3787 $cleaned = $this->build_query_array();
3788
3789 // since the sessionhash can be blanked out, lets make sure we pull from "dbsessionhash"
3790 $cleaned['sessionhash'] = "'" . $this->registry->db->escape_string($this->vars['dbsessionhash']) . "'";
3791
3792 if ($this->created == true)
3793 {
3794 if($this->registry->options['enablespiders'])//VBIV-5766
3795 {
3796 require_once(DIR . '/includes/class_xml.php');
3797 $xmlobj = new vB_XML_Parser(false, DIR . '/includes/xml/spiders_vbulletin.xml');
3798 $spiderdata = $xmlobj->parse();
3799 $spiders = "";
3800
3801 if (is_array($spiderdata['spider']))
3802 {
3803 foreach ($spiderdata['spider'] AS $spiderling)
3804 {
3805 $spiders .= ($spiders ? '|' : '') . preg_quote($spiderling['ident'], '#');
3806 }
3807 }
3808
3809 unset($spiderdata, $xmlobj);
3810
3811 //isbot to distinguish between bots and guests in session table VBIV-5766
3812 if (preg_match('#(' . $spiders . ')#si', $cleaned['useragent']))
3813 {
3814 $cleaned['isbot'] = true;
3815 }
3816 }// end VBIV-5766
3817
3818 /*insert query*/
3819 $this->registry->db->query_write("
3820 INSERT IGNORE INTO " . TABLE_PREFIX . "session
3821 (" . implode(', ', array_keys($cleaned)) . ")
3822 VALUES
3823 (" . implode(', ', $cleaned) . ")
3824 ");
3825 }
3826 else
3827 {
3828 // update query
3829
3830 unset($this->changes['sessionhash']); // the sessionhash is not updateable
3831 $update = array();
3832 foreach ($cleaned AS $key => $value)
3833 {
3834 if (!empty($this->changes["$key"]))
3835 {
3836 $update[] = "$key = $value";
3837 }
3838 }
3839
3840 if (sizeof($update) > 0)
3841 {
3842 // note that $cleaned['sessionhash'] has been escaped as necessary above!
3843 $this->registry->db->query_write("
3844 UPDATE " . TABLE_PREFIX . "session
3845 SET " . implode(', ', $update) . "
3846 WHERE sessionhash = $cleaned[sessionhash]
3847 ");
3848 }
3849 }
3850
3851 $this->changes = array();
3852 }
3853
3854 /**
3855 * Builds an array that can be used to build a query to insert/update the session
3856 *
3857 * @return array Array of column name => prepared value
3858 */
3859 function build_query_array()
3860 {
3861 $return = array();
3862 foreach ($this->db_fields AS $fieldname => $cleantype)
3863 {
3864 switch ($cleantype)
3865 {
3866 case TYPE_INT:
3867 $cleaned = intval($this->vars["$fieldname"]);
3868 break;
3869 case TYPE_STR:
3870 default:
3871 $cleaned = "'" . $this->registry->db->escape_string($this->vars["$fieldname"]) . "'";
3872 }
3873 $return["$fieldname"] = $cleaned;
3874 }
3875
3876 return $return;
3877 }
3878
3879 /**
3880 * Sets a session variable and updates the change list.
3881 *
3882 * @param string Name of session variable to update
3883 * @param string Value to update it with
3884 */
3885 function set($key, $value)
3886 {
3887 if (!isset($this->vars["$key"]) OR $this->vars["$key"] != $value)
3888 {
3889 $this->vars["$key"] = $value;
3890 $this->changes["$key"] = true;
3891 }
3892 }
3893
3894 /**
3895 * Sets the session visibility (whether session info shows up in a URL). Updates are put in the $vars member.
3896 *
3897 * @param bool Whether the session elements should be visible.
3898 */
3899 function set_session_visibility($invisible)
3900 {
3901 $this->visible = !$invisible;
3902
3903 if ($invisible)
3904 {
3905 $this->vars['sessionhash'] = '';
3906 $this->vars['sessionurl'] = '';
3907 $this->vars['sessionurl_q'] = '';
3908 $this->vars['sessionurl_js'] = '';
3909 }
3910 else
3911 {
3912 if (!VB_API)
3913 {
3914 $this->vars['sessionurl'] = 's=' . $this->vars['dbsessionhash'] . '&';
3915 $this->vars['sessionurl_q'] = '?s=' . $this->vars['dbsessionhash'];
3916 $this->vars['sessionurl_js'] = 's=' . $this->vars['dbsessionhash'] . '&';
3917 }
3918 else
3919 {
3920 $this->vars['sessionurl'] = 's=' . $this->vars['dbsessionhash'] . '&api=1&';
3921 $this->vars['sessionurl_q'] = '?s=' . $this->vars['dbsessionhash'] . '&api=1';
3922 $this->vars['sessionurl_js'] = 's=' . $this->vars['dbsessionhash'] . '&api=1&';
3923 }
3924 }
3925 }
3926
3927 /**
3928 * Fetches a valid sessionhash value, not necessarily the one tied to this session.
3929 *
3930 * @return string 32-character sessionhash
3931 */
3932 function fetch_sessionhash()
3933 {
3934 return md5(uniqid(microtime(), true));
3935 }
3936
3937 /**
3938 * Returns the IP address with the specified number of octets removed
3939 *
3940 * @param string IP address
3941 *
3942 * @return string truncated IP address
3943 */
3944 function fetch_substr_ip($ip, $length = null)
3945 {
3946 if ($length === null OR $length > 3)
3947 {
3948 $length = $this->registry->options['ipcheck'];
3949 }
3950 return implode('.', array_slice(explode('.', $ip), 0, 4 - $length));
3951 }
3952
3953 /**
3954 * Fetches a default session. Used when creating a new session.
3955 *
3956 * @param integer User ID the session should be for
3957 *
3958 * @return array Array of session variables
3959 */
3960 function fetch_session($userid = 0)
3961 {
3962 $sessionhash = $this->fetch_sessionhash();
3963 if (!defined('SKIP_SESSIONCREATE'))
3964 {
3965 vbsetcookie('sessionhash', $sessionhash, false, false, true);
3966 }
3967
3968 $session = array(
3969 'sessionhash' => $sessionhash,
3970 'dbsessionhash' => $sessionhash,
3971 'userid' => intval($userid),
3972 'host' => $this->getIp(),
3973 'idhash' => SESSION_IDHASH,
3974 'lastactivity' => TIMENOW,
3975 'location' => defined('LOCATION_BYPASS') ? '' : WOLPATH,
3976 'styleid' => 0,
3977 'languageid' => 0,
3978 'loggedin' => intval($userid) ? 1 : 0,
3979 'inforum' => 0,
3980 'inthread' => 0,
3981 'incalendar' => 0,
3982 'badlocation' => 0,
3983 'profileupdate' => 0,
3984 'useragent' => USER_AGENT,
3985 'bypass' => SESSION_BYPASS
3986 );
3987
3988 if (defined('VB_API') AND VB_API === true)
3989 {
3990 if ($this->registry->apiclient['apiaccesstoken'])
3991 {
3992 // Access Token is valid here because it's validated in init.php
3993 $accesstoken = $this->registry->apiclient['apiaccesstoken'];
3994 }
3995 else
3996 {
3997 // Generate an accesstoken
3998 $accesstoken = fetch_random_string();
3999
4000 $this->registry->apiclient['apiaccesstoken'] = $accesstoken;
4001 }
4002
4003 $session['apiaccesstoken'] = $accesstoken;
4004
4005 if ($this->registry->apiclient['apiclientid'])
4006 {
4007 $session['apiclientid'] = intval($this->registry->apiclient['apiclientid']);
4008 // Save accesstoken to apiclient table
4009 $this->registry->db->query_write("UPDATE " . TABLE_PREFIX . "apiclient SET
4010 apiaccesstoken = '" . $this->registry->db->escape_string($accesstoken) . "',
4011 lastactivity = " . TIMENOW . "
4012 WHERE apiclientid = $session[apiclientid]");
4013 }
4014 }
4015
4016 ($hook = vBulletinHook::fetch_hook('fetch_session_complete')) ? eval($hook) : false;
4017
4018 return $session;
4019
4020 }
4021
4022 /**
4023 * Returns ip for this session
4024 *
4025 * @return string ip
4026 */
4027 private function getIp()
4028 {
4029 if(isset($this->registry->options['facebookapp_ip'])) {
4030 $ips = explode(',', $this->registry->options['facebookapp_ip']);
4031 foreach($ips as $ip) {
4032 if(trim($ip) == $this->registry->ipaddress) {
4033 return $this->registry->alt_ip;
4034 }
4035 }
4036 }
4037 return SESSION_HOST;
4038 }
4039
4040 /**
4041 * Returns appropriate user info for the owner of this session.
4042 *
4043 * @return array Array of user information.
4044 */
4045 function &fetch_userinfo()
4046 {
4047 if ($this->userinfo)
4048 {
4049 // we already calculated this
4050 return $this->userinfo;
4051 }
4052 else if ($this->vars['userid'] AND !defined('SKIP_USERINFO'))
4053 {
4054 // user is logged in
4055 $useroptions = (defined('IN_CONTROL_PANEL') ? FETCH_USERINFO_ADMIN : 0) + (defined('AVATAR_ON_NAVBAR') ? FETCH_USERINFO_AVATAR : 0);
4056 $this->userinfo = fetch_userinfo($this->vars['userid'], $useroptions, $this->vars['languageid']);
4057 return $this->userinfo;
4058 }
4059 else
4060 {
4061 // guest setup
4062 $this->userinfo = array(
4063 'userid' => 0,
4064 'usergroupid' => 1,
4065 'username' => (!empty($_REQUEST['username']) ? htmlspecialchars_uni($_REQUEST['username']) : ''),
4066 'password' => '',
4067 'email' => '',
4068 'styleid' => $this->vars['styleid'],
4069 'languageid' => $this->vars['languageid'],
4070 'lastactivity' => $this->vars['lastactivity'],
4071 'daysprune' => 0,
4072 'timezoneoffset' => $this->registry->options['timeoffset'],
4073 'dstonoff' => $this->registry->options['dstonoff'],
4074 'showsignatures' => 1,
4075 'showavatars' => 1,
4076 'showimages' => 1,
4077 'showusercss' => 1,
4078 'dstauto' => 0,
4079 'maxposts' => -1,
4080 'startofweek' => 1,
4081 'threadedmode' => $this->registry->options['threadedmode'],
4082 'securitytoken' => 'guest',
4083 'securitytoken_raw' => 'guest',
4084 'realstyleid' => $this->registry->options['styleid'],
4085 );
4086
4087 $this->userinfo['options'] =
4088 $this->registry->bf_misc_useroptions['showsignatures'] | $this->registry->bf_misc_useroptions['showavatars'] |
4089 $this->registry->bf_misc_useroptions['showimages'] | $this->registry->bf_misc_useroptions['dstauto'] |
4090 $this->registry->bf_misc_useroptions['showusercss'];
4091
4092 if (!defined('SKIP_USERINFO'))
4093 {
4094 // get default language
4095 $phraseinfo = $this->registry->db->query_first_slave("
4096 SELECT languageid" . fetch_language_fields_sql(0) . "
4097 FROM " . TABLE_PREFIX . "language
4098 WHERE languageid = " . (!empty($this->vars['languageid']) ? $this->vars['languageid'] : intval($this->registry->options['languageid'])) . "
4099 ");
4100 if (empty($phraseinfo))
4101 { // can't phrase this since we can't find the language
4102 trigger_error('The requested language does not exist, reset via tools.php.', E_USER_ERROR);
4103 }
4104 foreach($phraseinfo AS $_arrykey => $_arryval)
4105 {
4106 $this->userinfo["$_arrykey"] = $_arryval;
4107 }
4108 unset($phraseinfo);
4109 }
4110
4111 return $this->userinfo;
4112 }
4113 }
4114
4115 /**
4116 * Updates the last visit and last activity times for guests and registered users (differently).
4117 * Last visit is set to the last activity time (before it's updated) only when a certain
4118 * time has lapsed. Last activity is always set to the specified time.
4119 *
4120 * @param integer Time stamp for last visit time (guest only)
4121 * @param integer Time stamp for last activity time (guest only)
4122 */
4123 function do_lastvisit_update($lastvisit = 0, $lastactivity = 0)
4124 {
4125 // update last visit/activity stuff
4126 if ($this->vars['userid'] == 0)
4127 {
4128 // guest -- emulate last visit/activity for registered users by cookies
4129 if ($lastvisit)
4130 {
4131 // we've been here before
4132 $this->userinfo['lastvisit'] = intval($lastvisit);
4133 $this->userinfo['lastactivity'] = ($lastvisit ? intval($lastvisit) : TIMENOW);
4134
4135 // here's the emulation
4136 if (TIMENOW - $this->userinfo['lastactivity'] > $this->registry->options['cookietimeout'])
4137 {
4138 $this->userinfo['lastvisit'] = $this->userinfo['lastactivity'];
4139
4140 vbsetcookie('lastvisit', $this->userinfo['lastactivity']);
4141 }
4142 }
4143 else
4144 {
4145 // first visit!
4146 $this->userinfo['lastactivity'] = TIMENOW;
4147 $this->userinfo['lastvisit'] = TIMENOW;
4148
4149 vbsetcookie('lastvisit', TIMENOW);
4150 }
4151 vbsetcookie('lastactivity', $lastactivity);
4152 }
4153 else
4154 {
4155 // registered user
4156 if (!SESSION_BYPASS)
4157 {
4158 if (TIMENOW - $this->userinfo['lastactivity'] > $this->registry->options['cookietimeout'])
4159 {
4160 // see if session has 'expired' and if new post indicators need resetting
4161 $this->registry->db->shutdown_query("
4162 UPDATE " . TABLE_PREFIX . "user
4163 SET
4164 lastvisit = lastactivity,
4165 lastactivity = " . TIMENOW . "
4166 WHERE userid = " . $this->userinfo['userid'] . "
4167 ", 'lastvisit');
4168
4169 $this->userinfo['lastvisit'] = $this->userinfo['lastactivity'];
4170 }
4171 else
4172 {
4173 // if this line is removed (say to be replaced by a cron job, you will need to change all of the 'online'
4174 // status indicators as they use $userinfo['lastactivity'] to determine if a user is online which relies
4175 // on this to be updated in real time.
4176 $this->registry->db->shutdown_query("
4177 UPDATE " . TABLE_PREFIX . "user
4178 SET lastactivity = " . TIMENOW . "
4179 WHERE userid = " . $this->userinfo['userid'] . "
4180 ", 'lastvisit');
4181 }
4182 }
4183 }
4184 }
4185}
4186
4187/**
4188* Class to handle shutdown
4189*
4190* @package vBulletin
4191* @version $Revision: 78140 $
4192* @author vBulletin Development Team
4193* @date $Date: 2013-10-16 14:51:06 -0700 (Wed, 16 Oct 2013) $
4194*/
4195class vB_Shutdown
4196{
4197 /**
4198 * A reference to the singleton instance
4199 *
4200 * @var vB_Cache_Observer
4201 */
4202 protected static $instance;
4203
4204 /**
4205 * An array of shutdown callbacks to call on shutdown
4206 */
4207 protected $callbacks;
4208
4209 /**
4210 * Constructor protected to enforce singleton use.
4211 * @see instance()
4212 */
4213 protected function __construct(){}
4214
4215 /**
4216 * Returns singleton instance of self.
4217 *
4218 * @return vB_Shutdown
4219 */
4220 public static function instance()
4221 {
4222 if (!isset(self::$instance))
4223 {
4224 $class = __CLASS__;
4225 self::$instance = new $class();
4226 }
4227
4228 return self::$instance;
4229 }
4230
4231 /**
4232 * Add callback to be executed at shutdown
4233 *
4234 * @param array $callback - Call back to call on shutdown
4235 */
4236 public function add($callback)
4237 {
4238 if (!is_array($this->callbacks))
4239 {
4240 $this->callbacks = array();
4241 }
4242
4243 $this->callbacks[] = $callback;
4244 }
4245
4246 // only called when an object is destroyed, so $this is appropriate
4247 public function shutdown()
4248 {
4249 if (sizeof($this->callbacks))
4250 {
4251 foreach ($this->callbacks AS $callback)
4252 {
4253 call_user_func($callback);
4254 }
4255
4256 unset($this->callbacks);
4257 }
4258 }
4259
4260 public function __wakeup()
4261 {
4262 unset($this->callbacks);
4263 }
4264}
4265
4266/**
4267* This class implements variable-registration-based template evaluation,
4268* wrapped around the legacy template format. It will be extended in the
4269* future to support the new format/syntax without requiring changes to
4270* code written with it.
4271*
4272* Currently these vars are automatically registered: $vbphrase
4273* $show, $bbuserinfo, $session, $vboptions
4274*
4275* @package vBulletin
4276*/
4277class vB_Template
4278{
4279 /**
4280 * Preregistered variables.
4281 * Variables can be preregistered before a template is created and will be
4282 * imported and reset when the template is created.
4283 * The array should be in the form array(template_name => array(key => variable))
4284 *
4285 * @var array mixed
4286 */
4287 protected static $pre_registered = array();
4288
4289 /**
4290 * Name of the template to render
4291 *
4292 * @var string
4293 */
4294 protected $template = '';
4295
4296 /**
4297 * Array of registered variables.
4298 * @see vB_Template::preRegister()
4299 *
4300 * @var array
4301 */
4302 protected $registered = array();
4303
4304 /**
4305 * Whether the globally accessible vars have been registered.
4306 *
4307 * @var bool
4308 */
4309 protected $registered_globals;
4310
4311 /**
4312 * Debug helper to count how many times a template was used on a page.
4313 *
4314 * @var array
4315 */
4316 public static $template_usage = array();
4317
4318 /**
4319 * Debug helper to list the templates that were fetched out of the database (not cached properly).
4320 *
4321 * @var array
4322 */
4323 public static $template_queries = array();
4324
4325 /**
4326 * Hook code for register.
4327 * @see vB_Template::register()
4328 *
4329 * @var string
4330 */
4331 protected static $hook_code = false;
4332
4333
4334
4335 /**
4336 * Factory method to create the template object.
4337 * Will choose the correct template type based on the request. Any preregistered
4338 * variables are also registered and cleared from the preregister cache.
4339 *
4340 * @param string Name of the template to be evaluated
4341 * @return vB_Template Template object
4342 */
4343 public static function create($template_name, $forcenoapi = false)
4344 {
4345 static $output_type;
4346
4347 if (defined('VB_API') AND VB_API AND !$forcenoapi)
4348 {
4349 // TODO: Use an option to enable/disable the api output
4350
4351 if (!isset($output_type))
4352 {
4353// global $vbulletin;
4354//
4355// $vbulletin->input->clean_gpc('r', 'api');
4356// $output_type = in_array($vbulletin->GPC['api'], array('xml', 'json')) ? $vbulletin->GPC['api'] : 'json';
4357 // Currently we support json only
4358 $output_type = 'json';
4359 }
4360
4361 if ($output_type == 'xml')
4362 {
4363 $template = new vB_Template_XML($template_name);
4364 }
4365 else
4366 {
4367 $template = new vB_Template_JSON($template_name);
4368 }
4369
4370 if (!VB_API_CMS)
4371 {
4372 global $show;
4373 $copyofshow = $show;
4374 self::remove_common_show($copyofshow);
4375 $template->register('show', $copyofshow);
4376 }
4377 }
4378 else
4379 {
4380 $template = new vB_Template($template_name);
4381 }
4382
4383 if (isset(self::$pre_registered[$template_name]))
4384 {
4385 $template->quickRegister(self::$pre_registered[$template_name]);
4386 // TODO: Reinstate once search uses a single template object
4387 // unset(self::$pre_registered[$template_name]);
4388 }
4389
4390 return $template;
4391 }
4392
4393 /**
4394 * Unset common items in $show array for API
4395 */
4396 protected static function remove_common_show(&$show)
4397 {
4398 // Unset common show variables
4399 unset(
4400 $show['old_explorer'], $show['rtl'], $show['admincplink'], $show['modcplink'],
4401 $show['registerbutton'], $show['searchbuttons'], $show['quicksearch'],
4402 $show['memberslist'], $show['guest'], $show['member'], $show['popups'],
4403 $show['nojs_link'], $show['pmwarning'], $show['pmstats'], $show['pmmainlink'],
4404 $show['pmtracklink'], $show['pmsendlink'], $show['siglink'], $show['avatarlink'],
4405 $show['detailedtime'], $show['profilepiclink'], $show['wollink'], $show['spacer'],
4406 $show['dst_correction'], $show['contactus'], $show['nopasswordempty'],
4407 $show['quick_links_groups'], $show['quick_links_albums'], $show['friends_and_contacts'],
4408 $show['communitylink'], $show['search_engine'], $show['editor_css']
4409 );
4410 }
4411
4412 /**
4413 * Protected constructor to enforce the factory pattern.
4414 * Ensures the chrome templates have been processed.
4415 */
4416 protected function __construct($template_name)
4417 {
4418 global $bootstrap;
4419
4420 if (!empty($bootstrap) AND !$bootstrap->called('template'))
4421 {
4422 $bootstrap->process_templates();
4423 }
4424
4425 $this->template = $template_name;
4426 }
4427
4428 /**
4429 * Returns the name of the template that will be rendered.
4430 *
4431 * @return string
4432 */
4433 public function get_template_name()
4434 {
4435 return $this->template;
4436 }
4437
4438 /**
4439 * Preregisters variables before template instantiation.
4440 *
4441 * @param string The name of the template to register for
4442 * @param array The variables to register
4443 */
4444 public static function preRegister($template_name, array $variables = NULL)
4445 {
4446 if ($variables)
4447 {
4448 if (!isset(self::$pre_registered[$template_name]))
4449 {
4450 self::$pre_registered[$template_name] = array();
4451 }
4452
4453 self::$pre_registered[$template_name] = array_merge(self::$pre_registered[$template_name], $variables);
4454 }
4455 }
4456
4457 /**
4458 * Register a variable with the template.
4459 *
4460 * @param string Name of the variable to be registered
4461 * @param mixed Value to be registered. This may be a scalar or an array.
4462 * @param bool Whether to overwrite existing vars
4463 * @return bool Whether the var was registered
4464 */
4465 public function register($name, $value, $overwrite = true)
4466 {
4467 if (!$overwrite AND $this->is_registered($name))
4468 {
4469 return false;
4470 }
4471
4472 // Run register hook
4473 self::assert_register_hook();
4474
4475 if (self::$hook_code)
4476 {
4477 eval(self::$hook_code);
4478 }
4479
4480 $this->registered[$name] = $value;
4481
4482 return true;
4483 }
4484
4485 /**
4486 * Registers an array of variables with the template.
4487 *
4488 * @param mixed Assoc array of name => value to be registered
4489 */
4490 public function quickRegister($values, $overwrite = true)
4491 {
4492 if (!is_array($values))
4493 {
4494 return;
4495 }
4496
4497 foreach ($values AS $name => $value)
4498 {
4499 $this->register($name, $value, $overwrite);
4500 }
4501 }
4502
4503 /**
4504 * Registers a named global variable with the template.
4505 *
4506 * @param string The global to register
4507 * @param bool Whether to overwrite on a name collision
4508 */
4509 public function register_global($name, $overwrite = true)
4510 {
4511 if (!$overwrite AND $this->is_registered($name))
4512 {
4513 return false;
4514 }
4515
4516 return isset($GLOBALS[$name]) ? $this->register_ref($name, $GLOBALS[$name]) : false;
4517 }
4518
4519 /**
4520 * Registers a reference to a variable.
4521 *
4522 * @param string Name of the variable to be registered
4523 * @param mixed Value to be registered. This may be a scalar or an array
4524 * @param bool Whether to overwrite existing vars
4525 * @return bool Whether the var was registered
4526 */
4527 public function register_ref($name, &$value, $overwrite = true)
4528 {
4529 if (!$overwrite AND $this->is_registered($name))
4530 {
4531 return false;
4532 }
4533
4534 // Run register hook
4535 self::assert_register_hook();
4536
4537 if (self::$hook_code)
4538 {
4539 eval(self::$hook_code);
4540 }
4541
4542 $this->registered[$name] =& $value;
4543
4544 return true;
4545 }
4546
4547 /**
4548 * Unregisters a previously registered variable.
4549 *
4550 * @param string Name of variable to be unregistered
4551 * @return mixed Null if the variable wasn't registered, otherwise the value of the variable
4552 */
4553 public function unregister($name)
4554 {
4555 if (isset($this->registered[$name]))
4556 {
4557 $value = $this->registered[$name];
4558 unset($this->registered[$name]);
4559 return $value;
4560 }
4561 else
4562 {
4563 return null;
4564 }
4565 }
4566
4567 /**
4568 * Determines if a named variable is registered.
4569 *
4570 * @param string Name of variable to check
4571 * @return bool
4572 */
4573 public function is_registered($name)
4574 {
4575 return isset($this->registered[$name]);
4576 }
4577
4578 /**
4579 * Return the value of a registered variable or all registered values
4580 * If no variable name is specified then all variables are returned.
4581 *
4582 * @param string The name of the variable to get the value for.
4583 * @return mixed If a name is specified, the value of the variable or null if it doesn't exist.
4584 */
4585 public function registered($name = '')
4586 {
4587 if ($name !== '')
4588 {
4589 return (isset($this->registered[$name]) ? $this->registered[$name] : null);
4590 }
4591 else
4592 {
4593 return $this->registered;
4594 }
4595 }
4596
4597 /**
4598 * Caches the register var hook code locally.
4599 */
4600 protected static function assert_register_hook()
4601 {
4602 if (self::$hook_code === false AND class_exists('vBulletinHook', false))
4603 {
4604 self::$hook_code = vBulletinHook::fetch_hook('template_register_var');
4605 }
4606 }
4607
4608 /**
4609 * Automatically register the page-level templates footer, header,
4610 * and headinclude based on their global values.
4611 */
4612 public function register_page_templates()
4613 {
4614 // Only method forum requires these templates
4615 if (defined('VB_API') AND VB_API === true AND VB_ENTRY !== 'forum.php')
4616 {
4617 return true;
4618 }
4619
4620 $this->register_global('footer');
4621 $this->register_global('header');
4622 $this->register_global('headinclude');
4623 $this->register_global('headinclude_bottom');
4624
4625 ($hook = vBulletinHook::fetch_hook('page_templates')) ? eval($hook) : false;
4626 }
4627
4628 /**
4629 * Register globally accessible vars.
4630 *
4631 * @param bool $final_render - Whether we are rendering the final response
4632 */
4633 protected function register_globals($final_render = false)
4634 {
4635 if ($this->registered_globals)
4636 {
4637 return;
4638 }
4639 $this->registered_globals = true;
4640
4641 global $vbulletin, $style;
4642
4643 $this->register_ref('bbuserinfo', $vbulletin->userinfo);
4644 $this->register_ref('vboptions', $vbulletin->options);
4645 $this->register_ref('session', $vbulletin->session->vars);
4646 $this->register('relpath', htmlspecialchars_uni($vbulletin->input->fetch_relpath()));
4647
4648 $this->register_global('vbphrase');
4649 $this->register_global('vbcollapse');
4650 $this->register_global('ad_location');
4651 $this->register_global('style');
4652
4653 $this->register_global('show', false);
4654 $this->register_global('template_hook', false);
4655
4656 $vbcsspath = vB_Template::fetch_css_path();
4657 $this->register('vbcsspath', $vbcsspath);
4658 $this->register('yui_version', YUI_VERSION);
4659
4660 if ($vbulletin->products['vbcms'])
4661 {
4662 $this->register('vb_suite_installed', true);
4663 }
4664
4665 // If we're using bgclass, we might be using exec_switch_bg()
4666 // but we can only be sure if we match the global value.
4667 // A hack that will hopefully go away.
4668 if (isset($bgclass) AND $bgclass == $GLOBALS['bgclass'])
4669 {
4670 $this->register_ref('bgclass', $GLOBALS['bgclass']);
4671 }
4672 }
4673
4674
4675 /**
4676 * Renders the template.
4677 *
4678 * @param boolean Whether to suppress the HTML comment surrounding option (for JS, etc)
4679 * @return string Rendered version of the template
4680 */
4681 public function render($suppress_html_comments = false, $final_render = false)
4682 {
4683 // Register globally accessible data
4684 $this->register_globals($final_render);
4685
4686 // Render the output in the appropriate format
4687 return $this->render_output($suppress_html_comments);
4688 }
4689
4690
4691 /**
4692 * Renders the output after preperation.
4693 * @see vB_Template::render()
4694 *
4695 * @param boolean Whether to suppress the HTML comment surrounding option (for JS, etc)
4696 * @return string
4697 */
4698 protected function render_output($suppress_html_comments = false)
4699 {
4700 //This global statement is here to expose $vbulletin to the templates.
4701 //It must remain in the same function as the template eval
4702 global $vbulletin;
4703 extract($this->registered, EXTR_SKIP | EXTR_REFS);
4704
4705 $actioned = false;
4706 ($hook = vBulletinHook::fetch_hook('template_render_output')) ? eval($hook) : false;
4707
4708 if (!$actioned)
4709 {
4710 $template_code = self::fetch_template($this->template);
4711 }
4712
4713 if (strpos($template_code, '$final_rendered') !== false)
4714 {
4715 eval($template_code);
4716 }
4717 else
4718 {
4719 eval('$final_rendered = "' . $template_code . '";');
4720 }
4721
4722 if ($vbulletin->options['addtemplatename'] AND !$suppress_html_comments)
4723 {
4724 $template_name = preg_replace('#[^a-z0-9_]#i', '', $this->template);
4725 $final_rendered = "<!-- BEGIN TEMPLATE: $template_name -->\n$final_rendered\n<!-- END TEMPLATE: $template_name -->";
4726 }
4727
4728 return $final_rendered;
4729 }
4730
4731
4732 /**
4733 * Returns the CSS path needed for the {vb:cssfile} template tag
4734 *
4735 * @return string CSS path
4736 */
4737 public static function fetch_css_path()
4738 {
4739 global $vbulletin, $style, $foruminfo;
4740
4741 if ($vbulletin->options['storecssasfile'])
4742 {
4743 $vbcsspath = 'clientscript/vbulletin_css/style' . str_pad($style['styleid'], 5, '0', STR_PAD_LEFT) . $vbulletin->stylevars['textdirection']['string'][0] . '/';
4744 }
4745 else
4746 {
4747 // Forum ID added when in forums with style overrides and the "Allow Users To Change Styles"
4748 // option is off, otherwise the requested styleid will be denied. Not added across the board
4749 // to ensure the highest cache hit rate possible. Not needed when CSS is stored as files.
4750 // See bug: VBIV-5647
4751 $forumid = intval($foruminfo['forumid']);
4752 $forum_styleid = intval($foruminfo['styleid']);
4753 if (!$vbulletin->options['allowchangestyles'] AND $forumid > 0 AND $forum_styleid > 0)
4754 {
4755 $add_forumid = '&forumid=' . $forumid;
4756 }
4757 else
4758 {
4759 $add_forumid = '';
4760 }
4761
4762 // textdirection var added to prevent cache if admin modified language text_direction. See bug #32640
4763 $vbcsspath = 'css.php?styleid=' . $style['styleid'] . $add_forumid . '&langid=' . LANGUAGEID . '&d=' . $style['dateline'] . '&td=' . $vbulletin->stylevars['textdirection']['string'] . '&sheet=';
4764 }
4765
4766 return $vbulletin->options['cssurl'] . $vbcsspath;
4767 }
4768
4769 /**
4770 * Returns a single template from the templatecache or the database and returns
4771 * the raw contents of it. Note that text will be escaped for eval'ing.
4772 *
4773 * @param string Name of template to be fetched
4774 *
4775 * @return string
4776 */
4777 public static function fetch_template_raw($template_name)
4778 {
4779 $template_code = self::fetch_template($template_name);
4780
4781 if (strpos($template_code, '$final_rendered') !== false)
4782 {
4783 return preg_replace('#^\$final_rendered = \'(.*)\';$#s', '\\1', $template_code);
4784 }
4785 else
4786 {
4787 return $template_code;
4788 }
4789 }
4790
4791 /**
4792 * Returns a single template from the templatecache or the database
4793 *
4794 * @param string Name of template to be fetched
4795 *
4796 * @return string
4797 */
4798 protected static function fetch_template($template_name)
4799 {
4800 global $vbulletin, $tempusagecache, $templateassoc;
4801
4802 // use legacy postbit if necessary
4803 if ($vbulletin->options['legacypostbit'] AND $template_name == 'postbit')
4804 {
4805 $template_name = 'postbit_legacy';
4806 }
4807
4808 $fetched = false;
4809 ($hook = vBulletinHook::fetch_hook('fetch_template_start')) ? eval($hook) : false;
4810
4811 if (!$fetched)
4812 {
4813 if (isset($vbulletin->templatecache["$template_name"]))
4814 {
4815 $template = $vbulletin->templatecache["$template_name"];
4816 }
4817 else
4818 {
4819 self::$template_queries[$template_name] = true;
4820 $fetch_tid = intval($templateassoc["$template_name"]);
4821 if (!$fetch_tid)
4822 {
4823 $gettemp = array('template' => '');
4824 }
4825 else
4826 {
4827 $gettemp = $vbulletin->db->query_first_slave("
4828 SELECT template
4829 FROM " . TABLE_PREFIX . "template
4830 WHERE templateid = $fetch_tid
4831 ");
4832 }
4833 $template = $gettemp['template'];
4834 $vbulletin->templatecache["$template_name"] = $template;
4835 }
4836 }
4837
4838 if (!isset(self::$template_usage[$template_name]))
4839 {
4840 self::$template_usage[$template_name] = 1;
4841 }
4842 else
4843 {
4844 self::$template_usage[$template_name]++;
4845 }
4846
4847 ($hook = vBulletinHook::fetch_hook('fetch_template_complete')) ? eval($hook) : false;
4848
4849 return $template;
4850 }
4851}
4852
4853abstract class vB_Template_Data extends vB_Template
4854{
4855 /**
4856 * Registered templates and their local vars.
4857 * The array should be in the form:
4858 * array(template_name => array(registered, registered [,...]))
4859 *
4860 * @var array
4861 */
4862 protected static $registered_templates = array();
4863
4864 /**
4865 * Prefix for the template token.
4866 * If this is matched as the prefix of a registered variable then the value is
4867 * picked up from $registered_templates.
4868 */
4869 protected static $token_prefix = '_-_-template-_-_';
4870
4871 /**
4872 * Register a variable with the template.
4873 * If the variable is prefixed with the template token then it is assumed as a
4874 * child template and picked up from $registered_templates.
4875 *
4876 * @param string Name of the variable to be registered
4877 * @param mixed Value to be registered. This may be a scalar or an array.
4878 * @param bool Whether to overwrite existing vars
4879 * @return bool Whether the var was registered
4880 */
4881 public function register($name, $value, $overwrite = true)
4882 {
4883 if (!$overwrite AND $this->is_registered($name))
4884 {
4885 return false;
4886 }
4887
4888 // Run register hook
4889 self::assert_register_hook();
4890
4891 if (self::$hook_code)
4892 {
4893 eval(self::$hook_code);
4894 }
4895
4896 if (defined('VB_API_CMS') AND VB_API_CMS === true)
4897 {
4898 $value = $this->escapeView($value);
4899 }
4900
4901 // Convert any tokenised templates into the local vars
4902 $this->parse_token($value);
4903
4904 $this->registered[$name] = $value;
4905
4906 return true;
4907 }
4908
4909
4910 /**
4911 * Identical to register, but registers a value as a reference.
4912 *
4913 * @param string Name of the variable to be registered
4914 * @param mixed Value to be registered. This may be a scalar or an array.
4915 * @param bool Whether to overwrite existing vars
4916 * @return bool Whether the var was registered
4917 */
4918 public function register_ref($name, &$value, $overwrite = true)
4919 {
4920 if (!$overwrite AND $this->is_registered($name))
4921 {
4922 return false;
4923 }
4924
4925 // Run register hook
4926 self::assert_register_hook();
4927
4928 if (self::$hook_code)
4929 {
4930 eval(self::$hook_code);
4931 }
4932
4933 if (defined('VB_API_CMS') AND VB_API_CMS === true)
4934 {
4935 $value = $this->escapeView($value);
4936 }
4937
4938 // Convert any tokenised templates into the local vars
4939 $this->parse_token($value);
4940
4941 $this->registered[$name] = &$value;
4942
4943 return true;
4944 }
4945
4946
4947 /**
4948 * Checks if a registered value is a template token.
4949 * If it is, the registered vars of the child template are picked up and
4950 * assigned to this template.
4951 *
4952 * @param string Name of the variable to be registered
4953 * @param mixed Value to be registered. This may be a scalar or an array.
4954 * @return bool Whether the value was picked up as a token, or the resovled value
4955 */
4956 public function parse_token(&$value)
4957 {
4958 if (is_array($value))
4959 {
4960 array_walk($value, array($this, 'parse_token'));
4961 }
4962 else
4963 {
4964 $matched = false;
4965 $matches = array();
4966 if (is_string($value) AND preg_match_all('#' . preg_quote(self::$token_prefix) . '(.+?):(\d+)#', $value, $matches, PREG_SET_ORDER))
4967 {
4968 $old_value = $value;
4969 $value = array();
4970
4971 foreach ($matches AS $match)
4972 {
4973 $template_name = $match[1];
4974 $index = intval($match[2]);
4975
4976 if (isset(self::$registered_templates[$template_name][$index]))
4977 {
4978 $value[] = self::$registered_templates[$template_name][$index];
4979 $matched = true;
4980 }
4981 }
4982
4983 if (sizeof($value) <= 1)
4984 {
4985 $value = current($value);
4986 }
4987 }
4988 }
4989
4990 return $matched;
4991 }
4992
4993 protected function whitelist_filter()
4994 {
4995 global $VB_API_WHITELIST;
4996
4997 // errormessage should be always added to the whitelist
4998 $VB_API_WHITELIST['response']['errormessage'] = '*';
4999 if (!$VB_API_WHITELIST['show'] AND !is_array($VB_API_WHITELIST['show']))
5000 {
5001 $VB_API_WHITELIST['show'] = '*';
5002 }
5003
5004 $temp = array();
5005 $this->whitelist_filter_recur($VB_API_WHITELIST, $temp, $this->registered);
5006 $this->registered = $temp;
5007
5008 }
5009
5010 protected function whitelist_filter_recur($whitelist, &$arr, &$registered)
5011 {
5012 foreach ($whitelist as $k => $v)
5013 {
5014 if ($k !== '*')
5015 {
5016 if (is_numeric($k) AND isset($registered[$v]))
5017 {
5018 if (is_array($registered[$v]))
5019 {
5020 $this->removeShow($registered[$v]);
5021 }
5022 $arr[$v] = $registered[$v];
5023 }
5024 elseif (array_key_exists($k, (array)$registered))
5025 {
5026 if ($v === '*')
5027 {
5028 if (is_array($registered[$v]))
5029 {
5030 $this->removeShow($registered[$v]);
5031 }
5032 $arr[$k] = $registered[$k];
5033 }
5034 elseif (is_array($v))
5035 {
5036 $arr[$k] = array();
5037 $this->whitelist_filter_recur($whitelist[$k], $arr[$k], $registered[$k]);
5038 if (empty($arr[$k]))
5039 {
5040 unset($arr[$k]);
5041 }
5042 }
5043 }
5044 }
5045 elseif ($k === '*')
5046 {
5047 if (is_array($registered))
5048 {
5049 $registeredkeys = array_keys($registered);
5050 if (is_numeric($registeredkeys[0]))
5051 {
5052 foreach ($registered as $k2 => $v2)
5053 {
5054 if (is_array($whitelist[$k]) AND !in_array('show', array_keys($whitelist[$k])))
5055 {
5056 if (is_array($registered[$k2]))
5057 {
5058 $this->removeShow($registered[$k2]);
5059 }
5060 }
5061 $arr[$k2] = array();
5062 $this->whitelist_filter_recur($whitelist[$k], $arr[$k2], $registered[$k2]);
5063 }
5064 }
5065 else
5066 {
5067 if (is_array($whitelist[$k]) AND !in_array('show', array_keys($whitelist[$k])))
5068 {
5069 if (is_array($registered))
5070 {
5071 $this->removeShow($registered);
5072 }
5073 }
5074 $this->whitelist_filter_recur($whitelist[$k], $arr, $registered);
5075 }
5076 }
5077 else
5078 {
5079 $arr = $registered;
5080 unset($registered);
5081 }
5082 }
5083 }
5084 }
5085
5086 protected function removeShow(&$arr)
5087 {
5088 if (is_array($arr))
5089 {
5090 unset($arr['show']);
5091 foreach($arr as &$v)
5092 {
5093 $this->removeShow($v);
5094 }
5095 }
5096 }
5097
5098 protected function escapeView($value)
5099 {
5100 if (is_array($value))
5101 {
5102 foreach ($value AS &$el)
5103 {
5104 $el = $this->escapeView($el);
5105 }
5106 }
5107
5108 if ($value instanceof vB_View)
5109 {
5110 $value = $value->render();
5111 }
5112 else if ($value instanceof vB_Phrase)
5113 {
5114 $value = (string)$value;
5115 }
5116
5117 return $value;
5118 }
5119
5120
5121 /**
5122 * Renders the template.
5123 *
5124 * @param boolean Whether to suppress the HTML comment surrounding option (for JS, etc)
5125 * @return string Rendered version of the template
5126 */
5127 public function render($suppress_html_comments = false, $final = false)
5128 {
5129 global $vbulletin, $show;
5130
5131 $callback = vB_APICallback::instance();
5132
5133 if ($final)
5134 {
5135 self::remove_common_show($show);
5136
5137 // register whitelisted globals
5138 $this->register_globals();
5139
5140 $callback->setname('result_prewhitelist');
5141 $callback->addParamRef(0, $this->registered);
5142 $callback->callback();
5143
5144 if (!($vbulletin->debug AND $vbulletin->GPC['showall']))
5145 {
5146 $this->whitelist_filter();
5147 }
5148
5149 $callback->setname('result_overwrite');
5150 $callback->addParamRef(0, $this->registered);
5151 $callback->callback();
5152
5153 if ($vbulletin->debug AND $vbulletin->GPC['debug'])
5154 {
5155 return '<pre>'.htmlspecialchars_uni(var_export($this->registered, true)).'</pre>' . '<br />' . number_format((memory_get_usage() / 1024)) . 'KB';
5156 }
5157 else
5158 {
5159 // only render data on final render
5160 return $this->render_output($suppress_html_comments);
5161 }
5162 }
5163 else
5164 {
5165 $callback->setname('result_prerender');
5166 $callback->addParam(0, $this->template);
5167 $callback->addParamRef(1, $this->registered);
5168 $callback->callback();
5169 }
5170
5171
5172 return $this->render_token();
5173 }
5174
5175
5176 /**
5177 * Buffers locally registered vars and returns a token representation of the template.
5178 *
5179 * @return string
5180 */
5181 protected function render_token()
5182 {
5183 if (!isset(self::$registered_templates[$this->template]))
5184 {
5185 self::$registered_templates[$this->template] = array();
5186 }
5187
5188 // Buffer local vars to be picked up by the parent template
5189 self::$registered_templates[$this->template][] = $this->registered;
5190
5191 $index = sizeof(self::$registered_templates[$this->template])-1;
5192
5193 return self::$token_prefix . $this->template . ':' . $index;
5194 }
5195
5196
5197 /**
5198 * Renders the output after preperation.
5199 * @see vB_Template::render()
5200 *
5201 * @param boolean Whether to suppress the HTML comment surrounding option (for JS, etc)
5202 * @return string
5203 */
5204 protected function render_output($suppress_html_comments = false)
5205 {
5206 return false;
5207 }
5208
5209 public static function dump_templates()
5210 {
5211 return print_r(self::$registered_templates,1);
5212 }
5213}
5214
5215
5216class vB_Template_XML extends vB_Template_Data
5217{
5218 /**
5219 * Renders the output after preperation.
5220 * @see vB_Template::render()
5221 *
5222 * @param boolean Whether to suppress the HTML comment surrounding option (for JS, etc)
5223 * @return string
5224 */
5225 protected function render_output($suppress_html_comments = false)
5226 {
5227 return xmlrpc_encode($this->registered);
5228 }
5229}
5230
5231class vB_Template_JSON extends vB_Template_Data
5232{
5233 /**
5234 * Renders the output after preperation.
5235 * @see vB_Template::render()
5236 *
5237 * @param boolean Whether to suppress the HTML comment surrounding option (for JS, etc)
5238 * @return string
5239 */
5240 protected function render_output($suppress_html_comments = false)
5241 {
5242 if (!($charset = vB_Template_Runtime::fetchStyleVar('charset')))
5243 {
5244 global $vbulletin;
5245 $charset = $vbulletin->userinfo['lang_charset'];
5246 }
5247
5248 $lower_charset = strtolower($charset);
5249 if ($lower_charset != 'utf-8')
5250 {
5251 // Browsers tend to interpret character set iso-8859-1 as windows-1252
5252 if ($lower_charset == 'iso-8859-1')
5253 {
5254 $lower_charset = 'windows-1252';
5255 }
5256 $this->processregistered($this->registered, $lower_charset);
5257 }
5258
5259 return json_encode($this->registered);
5260 }
5261
5262 private function processregistered(&$value, $charset)
5263 {
5264 global $VB_API_REQUESTS;
5265
5266 if (is_array($value))
5267 {
5268 foreach ($value AS &$el)
5269 {
5270 $this->processregistered($el, $charset);
5271 }
5272 }
5273
5274 if (is_string($value))
5275 {
5276 $value = preg_replace_callback('/&#([0-9]+);/siU', 'convert_int_to_utf8_callback', to_utf8($value, $charset, true));
5277 $trimmed = trim($value);
5278 if ($VB_API_REQUESTS['api_version'] > 1 AND ($trimmed == 'checked="checked"' OR $trimmed == 'selected="selected"'))
5279 {
5280 $value = 1;
5281 }
5282 }
5283
5284 if ($VB_API_REQUESTS['api_version'] > 1 AND is_bool($value))
5285 {
5286 if ($value)
5287 {
5288 $value = 1;
5289 }
5290 else
5291 {
5292 $value = 0;
5293 }
5294 }
5295 }
5296}
5297
5298class vB_Template_Runtime
5299{
5300 public static $units = array('%', 'px', 'pt', 'em', 'ex', 'pc', 'in', 'cm', 'mm');
5301
5302 public static function date($timestamp, $format = 'r')
5303 {
5304 if (empty($format))
5305 {
5306 $format = 'r';
5307 }
5308 return vbdate($format, intval($timestamp), true);
5309 }
5310
5311 public static function time($timestamp)
5312 {
5313 global $vbulletin;
5314 if (empty($timestamp)) { $timestamp = 0; }
5315 return vbdate($vbulletin->options['timeformat'], $timestamp);
5316 }
5317
5318 public static function escapeJS($javascript)
5319 {
5320 return addcslashes($javascript, "'\\");
5321 }
5322
5323 public static function numberFormat($number, $decimals = 0)
5324 {
5325 return vb_number_format($number, $decimals);
5326 }
5327
5328 public static function urlEncode($text)
5329 {
5330 return urlencode($text);
5331 }
5332
5333 public static function parsePhrase($phraseName)
5334 {
5335 global $vbphrase;
5336 $arg_list = func_get_args();
5337 $arg_list[0] = $vbphrase[$phraseName];
5338 return construct_phrase_from_array($arg_list);
5339 }
5340
5341 public static function addStyleVar($name, $value, $datatype = 'string')
5342 {
5343 global $vbulletin;
5344
5345 switch ($datatype)
5346 {
5347 case 'string':
5348 $vbulletin->stylevars["$name"] = array(
5349 'datatype' => $datatype,
5350 'string' => $value,
5351 );
5352 break;
5353 case 'imgdir':
5354 $vbulletin->stylevars["$name"] = array(
5355 'datatype' => 'imagedir',
5356 'imagedir' => $value,
5357 );
5358 break;
5359 }
5360 }
5361
5362 public static function fetchStyleVar($stylevar)
5363 {
5364 global $vbulletin;
5365
5366 $parts = explode('.', $stylevar);
5367 $base_stylevar = $vbulletin->stylevars[$parts[0]];
5368
5369 // this for accessing subparts of a complex data type
5370 if (isset($parts[1]))
5371 {
5372 $types = array(
5373 'background' => array(
5374 'backgroundColor' => 'color',
5375 'backgroundImage' => 'image',
5376 'backgroundRepeat' => 'repeat',
5377 'backgroundPositionX' => 'x',
5378 'backgroundPositionY' => 'y',
5379 'backgroundPositionUnits' => 'units'
5380 ),
5381
5382 'font' => array(
5383 'fontWeight' => 'weight',
5384 'units' => 'units',
5385 'fontSize' => 'size',
5386 'fontFamily' => 'family',
5387 'fontStyle' => 'style',
5388 'fontVariant' => 'variant',
5389 ),
5390
5391 'padding' => array(
5392 'units' => 'units',
5393 'paddingTop' => 'top',
5394 'paddingRight' => 'right',
5395 'paddingBottom' => 'bottom',
5396 'paddingLeft' => 'left',
5397 ),
5398
5399 'margin' => array(
5400 'units' => 'units',
5401 'marginTop' => 'top',
5402 'marginRight' => 'right',
5403 'marginBottom' => 'bottom',
5404 'marginLeft' => 'left',
5405 ),
5406
5407 'border' => array(
5408 'borderStyle' => 'style',
5409 'units' => 'units',
5410 'borderWidth' => 'width',
5411 'borderColor' => 'color',
5412 ),
5413 );
5414
5415 //handle is same for margin and padding -- allows the top value to be
5416 //used for all padding values
5417 if (in_array($base_stylevar['datatype'], array('padding', 'margin')) AND $parts[1] <> 'units')
5418 {
5419 if (isset($base_stylevar['same']) AND $base_stylevar['same'])
5420 {
5421 $parts[1] = $base_stylevar['datatype'] . 'Top';
5422 }
5423 }
5424
5425 if (isset($types[$base_stylevar['datatype']]))
5426 {
5427 $mapping = $types[$base_stylevar['datatype']][$parts[1]];
5428 $output = $base_stylevar[$mapping];
5429 }
5430 else
5431 {
5432 $output = $base_stylevar;
5433 for ($i = 1; $i < sizeof($parts); $i++) {
5434 $output = $output[$parts[$i]];
5435 }
5436 }
5437 }
5438 else
5439 {
5440 $output = '';
5441
5442 switch($base_stylevar['datatype'])
5443 {
5444 case 'color':
5445 $output = $base_stylevar['color'];
5446 break;
5447
5448 case 'background':
5449 switch ($base_stylevar['x'])
5450 {
5451 case 'stylevar-left':
5452 $base_stylevar['x'] = $vbulletin->stylevars['left']['string'];break;
5453 case 'stylevar-right':
5454 $base_stylevar['x'] = $vbulletin->stylevars['right']['string'];break;
5455 default:
5456 $base_stylevar['x'] = $base_stylevar['x'].$base_stylevar['units'];break;
5457 }
5458 $output = $base_stylevar['color'] . ' ' . (!empty($base_stylevar['image']) ? "$base_stylevar[image]" : 'none') . ' ' .
5459 $base_stylevar['repeat'] . ' ' .$base_stylevar['x'] . ' ' .
5460 $base_stylevar['y'] .
5461 $base_stylevar['units'];
5462 break;
5463
5464 case 'textdecoration':
5465 if ($base_stylevar['none'])
5466 {
5467 $output = 'none';
5468 }
5469 else
5470 {
5471 unset($base_stylevar['datatype'], $base_stylevar['none']);
5472 $output = implode(' ', array_keys(array_filter($base_stylevar)));
5473 }
5474 break;
5475
5476 case 'font':
5477 $output = $base_stylevar['style'] . ' ' . $base_stylevar['variant'] . ' ' .
5478 $base_stylevar['weight'] . ' ' . $base_stylevar['size'] . $base_stylevar['units'] . ' ' .
5479 $base_stylevar['family'];
5480 break;
5481
5482 case 'imagedir':
5483 $output = $base_stylevar['imagedir'];
5484 break;
5485
5486 case 'string':
5487 $output = $base_stylevar['string'];
5488 break;
5489
5490 case 'numeric':
5491 $output = $base_stylevar['numeric'];
5492 break;
5493
5494 case 'size':
5495 $output = $base_stylevar['size'] . $base_stylevar['units'];
5496 break;
5497
5498 case 'url':
5499 $output = $base_stylevar['url'];
5500 break;
5501
5502 case 'path':
5503 $output = $base_stylevar['path'];
5504 break;
5505
5506 case 'fontlist':
5507 $output = implode(',', preg_split('/[\r\n]+/', trim($base_stylevar['fontlist']), -1, PREG_SPLIT_NO_EMPTY));
5508 break;
5509
5510 case 'border':
5511 $output = $base_stylevar['width'] . $base_stylevar['units'] . ' ' .
5512 $base_stylevar['style'] . ' ' . $base_stylevar['color'];
5513 break;
5514
5515 case 'dimension':
5516 $output = 'width: ' . intval($base_stylevar['width']) . $base_stylevar['units'] .
5517 '; height: ' . intval($base_stylevar['height']) . $base_stylevar['units'] . ';';
5518 break;
5519
5520 case 'padding':
5521 case 'margin':
5522 foreach (array('top', 'right', 'bottom', 'left') AS $side)
5523 {
5524 if ($base_stylevar[$side] != 'auto')
5525 {
5526 $base_stylevar[$side] = $base_stylevar[$side] . $base_stylevar['units'];
5527 }
5528 }
5529 if (isset($base_stylevar['same']) AND $base_stylevar['same'])
5530 {
5531 $output = $base_stylevar['top'];
5532 }
5533 else
5534 {
5535 if (vB_Template_Runtime::fetchStyleVar('textdirection') == 'ltr')
5536 {
5537 $output = $base_stylevar['top'] . ' ' . $base_stylevar['right'] . ' ' . $base_stylevar['bottom'] . ' ' . $base_stylevar['left'];
5538 }
5539 else
5540 {
5541 $output = $base_stylevar['top'] . ' ' . $base_stylevar['left'] . ' ' . $base_stylevar['bottom'] . ' ' . $base_stylevar['right'];
5542 }
5543 }
5544 break;
5545 }
5546 }
5547
5548 return $output;
5549 }
5550
5551 public static function runMaths($str)
5552 {
5553 //this would usually be dangerous, but none of the units make sense
5554 //in a math string anyway. Note that there is ambiguty between the '%'
5555 //unit and the modulo operator. We don't allow the latter anyway
5556 //(though we do allow bitwise operations !?)
5557 $units_found = null;
5558 foreach (self::$units AS $unit)
5559 {
5560 if (strpos($str, $unit))
5561 {
5562 $units_found[] = $unit;
5563 }
5564 }
5565
5566 //mixed units.
5567 if (count($units_found) > 1)
5568 {
5569 return "/* ~~cannot perform math on mixed units ~~ found (" .
5570 implode(",", $units_found) . ") in $str */";
5571 }
5572
5573 $str = preg_replace('#([^+\-*=/\(\)\d\^<>&|\.]*)#', '', $str);
5574
5575 if (empty($str))
5576 {
5577 $str = '0';
5578 }
5579 else
5580 {
5581 //hack: if the math string is invalid we can get a php parse error here.
5582 //a bad expression or even a bad variable value (blank instead of a number) can
5583 //cause this to occur. This fails quietly, but also sets the status code to 500
5584 //(but, due to a bug in php only if display_errors is *off* -- if display errors
5585 //is on, then it will work just fine only $str below will not be set.
5586 //
5587 //This can result is say an almost correct css file being ignored by the browser
5588 //for reasons that aren't clear (and goes away if you turn error reporting on).
5589 //We can check to see if eval hit a parse error and, if so, we'll attempt to
5590 //clear the 500 status (this does more harm then good) and send an error
5591 //to the file. Since math is mostly used in css, we'll provide error text
5592 //that works best with that.
5593 $status = @eval("\$str = $str;");
5594 if ($status === false)
5595 {
5596 if (!headers_sent())
5597 {
5598 header($_SERVER['SERVER_PROTOCOL'] . ' 200 OK');
5599 }
5600 return "/* Invalid math expression */";
5601 }
5602
5603 if (count($units_found) == 1)
5604 {
5605 $str = $str.$units_found[0];
5606 }
5607 }
5608 return $str;
5609 }
5610
5611 public static function linkBuild($type, $info = array(), $extra = array(), $primaryid = null, $primarytitle = null)
5612 {
5613 //allow strings of form of query strings for info or extra. This allows us to hard code some values
5614 //in the templates instead of having to pass everything in from the php code. Limitations
5615 //in the markup do not allow us to build arrays in the template so we need to use strings.
5616 //We still can't build strings from variables to pass here so we can't mix hardcoded and
5617 //passed values, but we do what we can.
5618
5619 if (is_string($info))
5620 {
5621 parse_str($info, $new_vals);
5622 $info = $new_vals;
5623 }
5624
5625 if (is_string($extra))
5626 {
5627 parse_str($extra, $new_vals);
5628 $extra = $new_vals;
5629 }
5630
5631 return fetch_seo_url($type, $info, $extra, $primaryid, $primarytitle);
5632 }
5633}
5634
5635// #############################################################################
5636// misc functions
5637
5638// #############################################################################
5639/**
5640* Feeds database connection errors into the halt() method of the vB_Database class.
5641*
5642* @param integer Error number
5643* @param string PHP error text string
5644* @param strig File that contained the error
5645* @param integer Line in the file that contained the error
5646*/
5647function catch_db_error($errno, $errstr, $errfile, $errline)
5648{
5649 global $db;
5650 static $failures;
5651
5652 if (strstr($errstr, 'Lost connection') AND $failures < 5)
5653 {
5654 $failures++;
5655 return;
5656 }
5657
5658 if (is_object($db))
5659 {
5660 $db->halt("$errstr\r\n$errfile on line $errline");
5661 }
5662 else
5663 {
5664 vb_error_handler($errno, $errstr, $errfile, $errline);
5665 }
5666}
5667
5668// #############################################################################
5669/**
5670* Removes the full path from being disclosed on any errors
5671*
5672* @param integer Error number
5673* @param string PHP error text string
5674* @param strig File that contained the error
5675* @param integer Line in the file that contained the error
5676*/
5677function vb_error_handler($errno, $errstr, $errfile, $errline)
5678{
5679 global $vbulletin;
5680
5681 if (defined('SKIP_ALL_ERRORS'))
5682 {
5683 return;
5684 }
5685
5686 switch ($errno)
5687 {
5688 case E_NOTICE:
5689 // Just ignore these completely //
5690 break;
5691
5692 case E_WARNING:
5693 case E_USER_WARNING:
5694 /* Don't log warnings here due to the false bug reports
5695 about valid warnings that we suppress, but still appear in the log
5696 */
5697 if (!error_reporting() OR !ini_get('display_errors'))
5698 {
5699 return;
5700 }
5701 $errfile = str_replace(DIR, '....', $errfile);
5702 $errstr = str_replace(DIR, '....', $errstr);
5703 echo "<br /><b>Warning</b>: $errstr in <strong>$errfile</strong> on line <strong>$errline</strong><br />";
5704 break;
5705
5706 case E_USER_ERROR:
5707 require_once(DIR . '/includes/functions_log_error.php');
5708 $message = "Fatal error: $errstr in $errfile on line $errline";
5709 log_vbulletin_error($message, 'php');
5710
5711 if (!headers_sent())
5712 {
5713 if (SAPI_NAME == 'cgi' OR SAPI_NAME == 'cgi-fcgi')
5714 {
5715 header('Status: 500 Internal Server Error');
5716 }
5717 else
5718 {
5719 header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error');
5720 }
5721 }
5722
5723 if (error_reporting() OR ini_get('display_errors'))
5724 {
5725 $errfile = str_replace(DIR, '....', $errfile);
5726 $errstr = str_replace(DIR, '....', $errstr);
5727 echo "<br /><strong>Fatal error:</strong> $errstr in <strong>$errfile</strong> on line <strong>$errline</strong><br />";
5728 if (function_exists('debug_print_backtrace') AND ($vbulletin->userinfo['usergroupid'] == 6 OR ($vbulletin->userinfo['permissions']['adminpermissions'] & $vbulletin->bf_ugp_adminpermissions)))
5729 {
5730 // This is needed so IE doesn't show the pretty error messages
5731 echo str_repeat(' ', 512);
5732 debug_print_backtrace();
5733 }
5734 }
5735 exit;
5736 break;
5737
5738 default:
5739 if (defined('SKIP_DS_ERRORS'))
5740 {
5741 break;
5742 }
5743
5744 if (error_reporting() OR ini_get('display_errors'))
5745 {
5746 if (defined('DIR'))
5747 {
5748 require_once(DIR . '/includes/functions_log_error.php');
5749 $message = "Warning: $errstr in $errfile on line $errline";
5750 log_vbulletin_error($message, 'php');
5751 }
5752
5753 $errfile = str_replace(DIR, '....', $errfile);
5754 $errstr = str_replace(DIR, '....', $errstr);
5755 echo "<br /><b>Warning</b>: $errstr in <b>$errfile</b> on line <b>$errline</b><br />";
5756 }
5757 break;
5758 }
5759}
5760
5761// #############################################################################
5762/**
5763* Unicode-safe version of htmlspecialchars_uni()
5764*
5765* @param string Text to be made html-safe
5766*
5767* @return string
5768*/
5769function htmlspecialchars_uni($text, $entities = true)
5770{
5771 if ($entities)
5772 {
5773 $text = preg_replace_callback(
5774 '/&((#([0-9]+)|[a-z]+);)?/si',
5775 'htmlspecialchars_uni_callback',
5776 $text
5777 );
5778 }
5779 else
5780 {
5781 $text = preg_replace(
5782 // translates all non-unicode entities
5783 '/&(?!(#[0-9]+|[a-z]+);)/si',
5784 '&',
5785 $text
5786 );
5787 }
5788
5789 return str_replace(
5790 // replace special html characters
5791 array('<', '>', '"'),
5792 array('<', '>', '"'),
5793 $text
5794 );
5795}
5796
5797function htmlspecialchars_uni_callback($matches)
5798{
5799 if (count($matches) == 1)
5800 {
5801 return '&';
5802 }
5803
5804 if (strpos($matches[2], '#') === false)
5805 {
5806 // > like
5807 if ($matches[2] == 'shy')
5808 {
5809 return '­';
5810 }
5811 else
5812 {
5813 return "&$matches[2];";
5814 }
5815 }
5816 else
5817 {
5818 // Only convert chars that are in ISO-8859-1
5819 if (($matches[3] >= 32 AND $matches[3] <= 126)
5820 OR
5821 ($matches[3] >= 160 AND $matches[3] <= 255))
5822 {
5823 return "&#$matches[3];";
5824 }
5825 else
5826 {
5827 return "&#$matches[3];";
5828 }
5829 }
5830}
5831
5832
5833function css_escape_string($string)
5834{
5835 static $map = null;
5836 //url(<something>) is valid.
5837
5838 $checkstr = strtolower(trim($string));
5839 $add_url = false;
5840 if ((substr($checkstr, 0, 4) == 'url(') AND (substr($checkstr,-1,1) == ')'))
5841 {
5842 //we need to leave the "url()" part alone.
5843 $add_url = true;
5844 $string = trim($string);
5845 $string = substr($string,4, strlen($string)- 5);
5846 if ((($string[0] == '"') AND (substr($checkstr,-1,1) == '"'))
5847 OR
5848 (($string[0] == "'") AND (substr($checkstr,-1,1) == "'")))
5849 {
5850 $string = substr($string,1, strlen($string)- 2);
5851 }
5852 }
5853
5854 if(is_null($map))
5855 {
5856 $chars = array(
5857 '\\', '!', '@', '#', '$', '%', '^', '*', '"', "'",
5858 '<', '>', ',', '`', '~','/','&', '.',':', ')','(', ';'
5859 );
5860
5861 foreach ($chars as $char)
5862 {
5863 $map[$char] = '\\' . dechex(ord($char)) . ' ';
5864 }
5865 //var_dump($map);
5866 }
5867
5868 $string = str_replace(array_keys($map), $map, $string);
5869
5870 //add back the url() if we need it.
5871 if ($add_url)
5872 {
5873 $string = 'url(\'' . $string . '\')';
5874 }
5875 return $string;
5876}
5877/*======================================================================*\
5878|| ####################################################################
5879|| # Downloaded
5880|| # CVS: $RCSfile$ - $Revision: 78140 $
5881|| ####################################################################
5882\*======================================================================*/