Advertisement
Guest User

Untitled

a guest
Feb 11th, 2016
264
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 153.26 KB | None | 0 0
  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.  
  13. define('FILE_VERSION', '4.2.2'); // this should match installsteps.php
  14. define('SIMPLE_VERSION', '422'); // see vB_Datastore::check_options()
  15. define('YUI_VERSION', '2.9.0'); // define the YUI version we bundle
  16. define('JQUERY_VERSION', '1.6.4'); // define the jQuery version we use
  17. define('JQUERY_MOBILE_VERSION', '1.0'); // define the jQuery Mobile Style Version
  18.  
  19. /**#@+
  20. * The maximum sizes for the "small" profile avatars
  21. */
  22. define('FIXED_SIZE_AVATAR_WIDTH', 60);
  23. define('FIXED_SIZE_AVATAR_HEIGHT', 80);
  24. /**#@-*/
  25.  
  26. /**#@+
  27. * These make up the bit field to disable specific types of BB codes.
  28. */
  29. define('ALLOW_BBCODE_BASIC', 1);
  30. define('ALLOW_BBCODE_COLOR', 2);
  31. define('ALLOW_BBCODE_SIZE', 4);
  32. define('ALLOW_BBCODE_FONT', 8);
  33. define('ALLOW_BBCODE_ALIGN', 16);
  34. define('ALLOW_BBCODE_LIST', 32);
  35. define('ALLOW_BBCODE_URL', 64);
  36. define('ALLOW_BBCODE_CODE', 128);
  37. define('ALLOW_BBCODE_PHP', 256);
  38. define('ALLOW_BBCODE_HTML', 512);
  39. define('ALLOW_BBCODE_IMG', 1024);
  40. define('ALLOW_BBCODE_QUOTE', 2048);
  41. define('ALLOW_BBCODE_CUSTOM', 4096);
  42. define('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. */
  48. define('BBCODE_HAS_IMG', 1);
  49. define('BBCODE_HAS_ATTACH', 2);
  50. define('BBCODE_HAS_SIGPIC', 4);
  51. define('BBCODE_HAS_RELPATH',8);
  52. /**#@-*/
  53.  
  54. /**#@+
  55. * Bitfield values for the inline moderation javascript selector which should be self-explanitory
  56. */
  57. define('POST_FLAG_INVISIBLE', 1);
  58. define('POST_FLAG_DELETED', 2);
  59. define('POST_FLAG_ATTACH', 4);
  60. define('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. */
  69. define('DBARRAY_BOTH', 0);
  70. define('DBARRAY_ASSOC', 1);
  71. define('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. */
  83. class 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('&amp;', '&', $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. */
  1283. class 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. */
  1478. class 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. */
  1769. define('TYPE_NOCLEAN', 0); // no change
  1770.  
  1771. define('TYPE_BOOL', 1); // force boolean
  1772. define('TYPE_INT', 2); // force integer
  1773. define('TYPE_UINT', 3); // force unsigned integer
  1774. define('TYPE_NUM', 4); // force number
  1775. define('TYPE_UNUM', 5); // force unsigned number
  1776. define('TYPE_UNIXTIME', 6); // force unix datestamp (unsigned integer)
  1777. define('TYPE_STR', 7); // force trimmed string
  1778. define('TYPE_NOTRIM', 8); // force string - no trim
  1779. define('TYPE_NOHTML', 9); // force trimmed string with HTML made safe
  1780. define('TYPE_ARRAY', 10); // force array
  1781. define('TYPE_FILE', 11); // force file
  1782. define('TYPE_BINARY', 12); // force binary string
  1783. define('TYPE_NOHTMLCOND', 13); // force trimmed string with HTML made safe if determined to be unsafe
  1784.  
  1785. define('TYPE_ARRAY_BOOL', 101);
  1786. define('TYPE_ARRAY_INT', 102);
  1787. define('TYPE_ARRAY_UINT', 103);
  1788. define('TYPE_ARRAY_NUM', 104);
  1789. define('TYPE_ARRAY_UNUM', 105);
  1790. define('TYPE_ARRAY_UNIXTIME', 106);
  1791. define('TYPE_ARRAY_STR', 107);
  1792. define('TYPE_ARRAY_NOTRIM', 108);
  1793. define('TYPE_ARRAY_NOHTML', 109);
  1794. define('TYPE_ARRAY_ARRAY', 110);
  1795. define('TYPE_ARRAY_FILE', 11); // An array of "Files" behaves differently than other <input> arrays. TYPE_FILE handles both types.
  1796. define('TYPE_ARRAY_BINARY', 112);
  1797. define('TYPE_ARRAY_NOHTMLCOND',113);
  1798.  
  1799. define('TYPE_ARRAY_KEYS_INT', 202);
  1800. define('TYPE_ARRAY_KEYS_STR', 207);
  1801.  
  1802. define('TYPE_CONVERT_SINGLE', 100); // value to subtract from array types to convert to single types
  1803. define('TYPE_CONVERT_KEYS', 200); // value to subtract from array => keys types to convert to single types
  1804. /**#@-*/
  1805.  
  1806. // temporary
  1807. define('INT', TYPE_INT);
  1808. define('STR', TYPE_STR);
  1809. define('STR_NOHTML', TYPE_NOHTML);
  1810. define('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. */
  1819. class 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 &#123; entities to their actual character
  2410. // note: we don't want to convert &gt;, etc as that undoes the effects of STR_NOHTML
  2411. $this->registry->GPC["$varname"] = preg_replace('#&([a-z]+);#i', '&amp;$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]);/', '&amp;#$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 == '' ? '' : '&amp;') . $varname . '=' . $value;
  2979. break;
  2980. }
  2981. }
  2982. }
  2983. }
  2984. if ($tackon != '')
  2985. {
  2986. $wolpath .= (strpos($wolpath, '?') !== false ? '&amp;' : '?') . "$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. */
  3138. class 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. */
  3526. class 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'] . '&amp;';
  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'] . '&amp;api=1&amp;';
  3921. $this->vars['sessionurl_q'] = '?s=' . $this->vars['dbsessionhash'] . '&amp;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. */
  4195. class 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. */
  4277. class 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 = '&amp;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 . '&amp;langid=' . LANGUAGEID . '&amp;d=' . $style['dateline'] . '&amp;td=' . $vbulletin->stylevars['textdirection']['string'] . '&amp;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.  
  4853. abstract 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.  
  5216. class 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.  
  5231. class 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.  
  5298. class 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. */
  5647. function 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. */
  5677. function 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. */
  5769. function 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. '&amp;',
  5785. $text
  5786. );
  5787. }
  5788.  
  5789. return str_replace(
  5790. // replace special html characters
  5791. array('<', '>', '"'),
  5792. array('&lt;', '&gt;', '&quot;'),
  5793. $text
  5794. );
  5795. }
  5796.  
  5797. function htmlspecialchars_uni_callback($matches)
  5798. {
  5799. if (count($matches) == 1)
  5800. {
  5801. return '&amp;';
  5802. }
  5803.  
  5804. if (strpos($matches[2], '#') === false)
  5805. {
  5806. // &gt; like
  5807. if ($matches[2] == 'shy')
  5808. {
  5809. return '&shy;';
  5810. }
  5811. else
  5812. {
  5813. return "&amp;$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 "&amp;#$matches[3];";
  5824. }
  5825. else
  5826. {
  5827. return "&#$matches[3];";
  5828. }
  5829. }
  5830. }
  5831.  
  5832.  
  5833. function 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. \*======================================================================*/
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement