PageRenderTime 59ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/phpBB3/includes/functions.php

http://pbb-png1.googlecode.com/
PHP | 2461 lines | 1670 code | 358 blank | 433 comment | 403 complexity | cd048a42dd93f5f08411884ce552b9c8 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * @package phpBB3
  5. * @version $Id: functions.php 9153 2008-12-02 17:02:56Z acydburn $
  6. * @copyright (c) 2005 phpBB Group
  7. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8. *
  9. */
  10. /**
  11. * @ignore
  12. */
  13. if (!defined('IN_PHPBB'))
  14. {
  15. exit;
  16. }
  17. // Common global functions
  18. /**
  19. * set_var
  20. *
  21. * Set variable, used by {@link request_var the request_var function}
  22. *
  23. * @access private
  24. */
  25. function set_var(&$result, $var, $type, $multibyte = false)
  26. {
  27. settype($var, $type);
  28. $result = $var;
  29. if ($type == 'string')
  30. {
  31. $result = trim(htmlspecialchars(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result), ENT_COMPAT, 'UTF-8'));
  32. if (!empty($result))
  33. {
  34. // Make sure multibyte characters are wellformed
  35. if ($multibyte)
  36. {
  37. if (!preg_match('/^./u', $result))
  38. {
  39. $result = '';
  40. }
  41. }
  42. else
  43. {
  44. // no multibyte, allow only ASCII (0-127)
  45. $result = preg_replace('/[\x80-\xFF]/', '?', $result);
  46. }
  47. }
  48. $result = (STRIP) ? stripslashes($result) : $result;
  49. }
  50. }
  51. /**
  52. * request_var
  53. *
  54. * Used to get passed variable
  55. */
  56. function request_var($var_name, $default, $multibyte = false, $cookie = false)
  57. {
  58. if (!$cookie && isset($_COOKIE[$var_name]))
  59. {
  60. if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
  61. {
  62. return (is_array($default)) ? array() : $default;
  63. }
  64. $_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
  65. }
  66. if (!isset($_REQUEST[$var_name]) || (is_array($_REQUEST[$var_name]) && !is_array($default)) || (is_array($default) && !is_array($_REQUEST[$var_name])))
  67. {
  68. return (is_array($default)) ? array() : $default;
  69. }
  70. $var = $_REQUEST[$var_name];
  71. if (!is_array($default))
  72. {
  73. $type = gettype($default);
  74. }
  75. else
  76. {
  77. list($key_type, $type) = each($default);
  78. $type = gettype($type);
  79. $key_type = gettype($key_type);
  80. if ($type == 'array')
  81. {
  82. reset($default);
  83. $default = current($default);
  84. list($sub_key_type, $sub_type) = each($default);
  85. $sub_type = gettype($sub_type);
  86. $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
  87. $sub_key_type = gettype($sub_key_type);
  88. }
  89. }
  90. if (is_array($var))
  91. {
  92. $_var = $var;
  93. $var = array();
  94. foreach ($_var as $k => $v)
  95. {
  96. set_var($k, $k, $key_type);
  97. if ($type == 'array' && is_array($v))
  98. {
  99. foreach ($v as $_k => $_v)
  100. {
  101. if (is_array($_v))
  102. {
  103. $_v = null;
  104. }
  105. set_var($_k, $_k, $sub_key_type);
  106. set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
  107. }
  108. }
  109. else
  110. {
  111. if ($type == 'array' || is_array($v))
  112. {
  113. $v = null;
  114. }
  115. set_var($var[$k], $v, $type, $multibyte);
  116. }
  117. }
  118. }
  119. else
  120. {
  121. set_var($var, $var, $type, $multibyte);
  122. }
  123. return $var;
  124. }
  125. /**
  126. * Set config value. Creates missing config entry.
  127. */
  128. function set_config($config_name, $config_value, $is_dynamic = false)
  129. {
  130. global $db, $cache, $config;
  131. $sql = 'UPDATE ' . CONFIG_TABLE . "
  132. SET config_value = '" . $db->sql_escape($config_value) . "'
  133. WHERE config_name = '" . $db->sql_escape($config_name) . "'";
  134. $db->sql_query($sql);
  135. if (!$db->sql_affectedrows() && !isset($config[$config_name]))
  136. {
  137. $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
  138. 'config_name' => $config_name,
  139. 'config_value' => $config_value,
  140. 'is_dynamic' => ($is_dynamic) ? 1 : 0));
  141. $db->sql_query($sql);
  142. }
  143. $config[$config_name] = $config_value;
  144. if (!$is_dynamic)
  145. {
  146. $cache->destroy('config');
  147. }
  148. }
  149. /**
  150. * Generates an alphanumeric random string of given length
  151. */
  152. function gen_rand_string($num_chars = 8)
  153. {
  154. $rand_str = unique_id();
  155. $rand_str = str_replace('0', 'Z', strtoupper(base_convert($rand_str, 16, 35)));
  156. return substr($rand_str, 0, $num_chars);
  157. }
  158. /**
  159. * Return unique id
  160. * @param string $extra additional entropy
  161. */
  162. function unique_id($extra = 'c')
  163. {
  164. static $dss_seeded = false;
  165. global $config;
  166. $val = $config['rand_seed'] . microtime();
  167. $val = md5($val);
  168. $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
  169. if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
  170. {
  171. set_config('rand_seed', $config['rand_seed'], true);
  172. set_config('rand_seed_last_update', time(), true);
  173. $dss_seeded = true;
  174. }
  175. return substr($val, 4, 16);
  176. }
  177. /**
  178. * Return formatted string for filesizes
  179. */
  180. function get_formatted_filesize($bytes, $add_size_lang = true)
  181. {
  182. global $user;
  183. if ($bytes >= pow(2, 20))
  184. {
  185. return ($add_size_lang) ? round($bytes / 1024 / 1024, 2) . ' ' . $user->lang['MIB'] : round($bytes / 1024 / 1024, 2);
  186. }
  187. if ($bytes >= pow(2, 10))
  188. {
  189. return ($add_size_lang) ? round($bytes / 1024, 2) . ' ' . $user->lang['KIB'] : round($bytes / 1024, 2);
  190. }
  191. return ($add_size_lang) ? ($bytes) . ' ' . $user->lang['BYTES'] : ($bytes);
  192. }
  193. /**
  194. * Determine whether we are approaching the maximum execution time. Should be called once
  195. * at the beginning of the script in which it's used.
  196. * @return bool Either true if the maximum execution time is nearly reached, or false
  197. * if some time is still left.
  198. */
  199. function still_on_time($extra_time = 15)
  200. {
  201. static $max_execution_time, $start_time;
  202. $time = explode(' ', microtime());
  203. $current_time = $time[0] + $time[1];
  204. if (empty($max_execution_time))
  205. {
  206. $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
  207. // If zero, then set to something higher to not let the user catch the ten seconds barrier.
  208. if ($max_execution_time === 0)
  209. {
  210. $max_execution_time = 50 + $extra_time;
  211. }
  212. $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
  213. // For debugging purposes
  214. // $max_execution_time = 10;
  215. global $starttime;
  216. $start_time = (empty($starttime)) ? $current_time : $starttime;
  217. }
  218. return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
  219. }
  220. /**
  221. *
  222. * @version Version 0.1 / slightly modified for phpBB 3.0.x (using $H$ as hash type identifier)
  223. *
  224. * Portable PHP password hashing framework.
  225. *
  226. * Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
  227. * the public domain.
  228. *
  229. * There's absolutely no warranty.
  230. *
  231. * The homepage URL for this framework is:
  232. *
  233. * http://www.openwall.com/phpass/
  234. *
  235. * Please be sure to update the Version line if you edit this file in any way.
  236. * It is suggested that you leave the main version number intact, but indicate
  237. * your project name (after the slash) and add your own revision information.
  238. *
  239. * Please do not change the "private" password hashing method implemented in
  240. * here, thereby making your hashes incompatible. However, if you must, please
  241. * change the hash type identifier (the "$P$") to something different.
  242. *
  243. * Obviously, since this code is in the public domain, the above are not
  244. * requirements (there can be none), but merely suggestions.
  245. *
  246. *
  247. * Hash the password
  248. */
  249. function phpbb_hash($password)
  250. {
  251. $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  252. $random_state = unique_id();
  253. $random = '';
  254. $count = 6;
  255. if (($fh = @fopen('/dev/urandom', 'rb')))
  256. {
  257. $random = fread($fh, $count);
  258. fclose($fh);
  259. }
  260. if (strlen($random) < $count)
  261. {
  262. $random = '';
  263. for ($i = 0; $i < $count; $i += 16)
  264. {
  265. $random_state = md5(unique_id() . $random_state);
  266. $random .= pack('H*', md5($random_state));
  267. }
  268. $random = substr($random, 0, $count);
  269. }
  270. $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);
  271. if (strlen($hash) == 34)
  272. {
  273. return $hash;
  274. }
  275. return md5($password);
  276. }
  277. /**
  278. * Check for correct password
  279. *
  280. * @param string $password The password in plain text
  281. * @param string $hash The stored password hash
  282. *
  283. * @return bool Returns true if the password is correct, false if not.
  284. */
  285. function phpbb_check_hash($password, $hash)
  286. {
  287. $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  288. if (strlen($hash) == 34)
  289. {
  290. return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
  291. }
  292. return (md5($password) === $hash) ? true : false;
  293. }
  294. /**
  295. * Generate salt for hash generation
  296. */
  297. function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
  298. {
  299. if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
  300. {
  301. $iteration_count_log2 = 8;
  302. }
  303. $output = '$H$';
  304. $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
  305. $output .= _hash_encode64($input, 6, $itoa64);
  306. return $output;
  307. }
  308. /**
  309. * Encode hash
  310. */
  311. function _hash_encode64($input, $count, &$itoa64)
  312. {
  313. $output = '';
  314. $i = 0;
  315. do
  316. {
  317. $value = ord($input[$i++]);
  318. $output .= $itoa64[$value & 0x3f];
  319. if ($i < $count)
  320. {
  321. $value |= ord($input[$i]) << 8;
  322. }
  323. $output .= $itoa64[($value >> 6) & 0x3f];
  324. if ($i++ >= $count)
  325. {
  326. break;
  327. }
  328. if ($i < $count)
  329. {
  330. $value |= ord($input[$i]) << 16;
  331. }
  332. $output .= $itoa64[($value >> 12) & 0x3f];
  333. if ($i++ >= $count)
  334. {
  335. break;
  336. }
  337. $output .= $itoa64[($value >> 18) & 0x3f];
  338. }
  339. while ($i < $count);
  340. return $output;
  341. }
  342. /**
  343. * The crypt function/replacement
  344. */
  345. function _hash_crypt_private($password, $setting, &$itoa64)
  346. {
  347. $output = '*';
  348. // Check for correct hash
  349. if (substr($setting, 0, 3) != '$H$')
  350. {
  351. return $output;
  352. }
  353. $count_log2 = strpos($itoa64, $setting[3]);
  354. if ($count_log2 < 7 || $count_log2 > 30)
  355. {
  356. return $output;
  357. }
  358. $count = 1 << $count_log2;
  359. $salt = substr($setting, 4, 8);
  360. if (strlen($salt) != 8)
  361. {
  362. return $output;
  363. }
  364. /**
  365. * We're kind of forced to use MD5 here since it's the only
  366. * cryptographic primitive available in all versions of PHP
  367. * currently in use. To implement our own low-level crypto
  368. * in PHP would result in much worse performance and
  369. * consequently in lower iteration counts and hashes that are
  370. * quicker to crack (by non-PHP code).
  371. */
  372. if (PHP_VERSION >= 5)
  373. {
  374. $hash = md5($salt . $password, true);
  375. do
  376. {
  377. $hash = md5($hash . $password, true);
  378. }
  379. while (--$count);
  380. }
  381. else
  382. {
  383. $hash = pack('H*', md5($salt . $password));
  384. do
  385. {
  386. $hash = pack('H*', md5($hash . $password));
  387. }
  388. while (--$count);
  389. }
  390. $output = substr($setting, 0, 12);
  391. $output .= _hash_encode64($hash, 16, $itoa64);
  392. return $output;
  393. }
  394. /**
  395. * Global function for chmodding directories and files for internal use
  396. * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions.
  397. * The function determines owner and group from common.php file and sets the same to the provided file. Permissions are mapped to the group, user always has rw(x) permission.
  398. * The function uses bit fields to build the permissions.
  399. * The function sets the appropiate execute bit on directories.
  400. *
  401. * Supported constants representing bit fields are:
  402. *
  403. * CHMOD_ALL - all permissions (7)
  404. * CHMOD_READ - read permission (4)
  405. * CHMOD_WRITE - write permission (2)
  406. * CHMOD_EXECUTE - execute permission (1)
  407. *
  408. * NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions.
  409. *
  410. * @param $filename The file/directory to be chmodded
  411. * @param $perms Permissions to set
  412. * @return true on success, otherwise false
  413. *
  414. * @author faw, phpBB Group
  415. */
  416. function phpbb_chmod($filename, $perms = CHMOD_READ)
  417. {
  418. // Return if the file no longer exists.
  419. if (!file_exists($filename))
  420. {
  421. return false;
  422. }
  423. if (!function_exists('fileowner') || !function_exists('filegroup'))
  424. {
  425. $file_uid = $file_gid = false;
  426. $common_php_owner = $common_php_group = false;
  427. }
  428. else
  429. {
  430. global $phpbb_root_path, $phpEx;
  431. // Determine owner/group of common.php file and the filename we want to change here
  432. $common_php_owner = fileowner($phpbb_root_path . 'common.' . $phpEx);
  433. $common_php_group = filegroup($phpbb_root_path . 'common.' . $phpEx);
  434. $file_uid = fileowner($filename);
  435. $file_gid = filegroup($filename);
  436. // Try to set the owner to the same common.php has
  437. if ($common_php_owner !== $file_uid && $common_php_owner !== false && $file_uid !== false)
  438. {
  439. // Will most likely not work
  440. if (@chown($filename, $common_php_owner));
  441. {
  442. clearstatcache();
  443. $file_uid = fileowner($filename);
  444. }
  445. }
  446. // Try to set the group to the same common.php has
  447. if ($common_php_group !== $file_gid && $common_php_group !== false && $file_gid !== false)
  448. {
  449. if (@chgrp($filename, $common_php_group));
  450. {
  451. clearstatcache();
  452. $file_gid = filegroup($filename);
  453. }
  454. }
  455. }
  456. // And the owner and the groups PHP is running under.
  457. $php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false;
  458. $php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false;
  459. // Who is PHP?
  460. if ($file_uid === false || $file_gid === false || $php_uid === false || $php_gids === false)
  461. {
  462. $php = NULL;
  463. }
  464. else if ($file_uid == $php_uid /* && $common_php_owner !== false && $common_php_owner === $file_uid*/)
  465. {
  466. $php = 'owner';
  467. }
  468. else if (in_array($file_gid, $php_gids))
  469. {
  470. $php = 'group';
  471. }
  472. else
  473. {
  474. $php = 'other';
  475. }
  476. // Owner always has read/write permission
  477. $owner = CHMOD_READ | CHMOD_WRITE;
  478. if (is_dir($filename))
  479. {
  480. $owner |= CHMOD_EXECUTE;
  481. // Only add execute bit to the permission if the dir needs to be readable
  482. if ($perms & CHMOD_READ)
  483. {
  484. $perms |= CHMOD_EXECUTE;
  485. }
  486. }
  487. switch ($php)
  488. {
  489. case null:
  490. case 'owner':
  491. /* ATTENTION: if php is owner or NULL we set it to group here. This is the most failsafe combination for the vast majority of server setups.
  492. $result = @chmod($filename, ($owner << 6) + (0 << 3) + (0 << 0));
  493. clearstatcache();
  494. if (!is_null($php) || (is_readable($filename) && is_writable($filename)))
  495. {
  496. break;
  497. }
  498. */
  499. case 'group':
  500. $result = @chmod($filename, ($owner << 6) + ($perms << 3) + (0 << 0));
  501. clearstatcache();
  502. if (!is_null($php) || ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || is_writable($filename))))
  503. {
  504. break;
  505. }
  506. case 'other':
  507. $result = @chmod($filename, ($owner << 6) + ($perms << 3) + ($perms << 0));
  508. clearstatcache();
  509. if (!is_null($php) || ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || is_writable($filename))))
  510. {
  511. break;
  512. }
  513. default:
  514. return false;
  515. break;
  516. }
  517. return $result;
  518. }
  519. // Compatibility functions
  520. if (!function_exists('array_combine'))
  521. {
  522. /**
  523. * A wrapper for the PHP5 function array_combine()
  524. * @param array $keys contains keys for the resulting array
  525. * @param array $values contains values for the resulting array
  526. *
  527. * @return Returns an array by using the values from the keys array as keys and the
  528. * values from the values array as the corresponding values. Returns false if the
  529. * number of elements for each array isn't equal or if the arrays are empty.
  530. */
  531. function array_combine($keys, $values)
  532. {
  533. $keys = array_values($keys);
  534. $values = array_values($values);
  535. $n = sizeof($keys);
  536. $m = sizeof($values);
  537. if (!$n || !$m || ($n != $m))
  538. {
  539. return false;
  540. }
  541. $combined = array();
  542. for ($i = 0; $i < $n; $i++)
  543. {
  544. $combined[$keys[$i]] = $values[$i];
  545. }
  546. return $combined;
  547. }
  548. }
  549. if (!function_exists('str_split'))
  550. {
  551. /**
  552. * A wrapper for the PHP5 function str_split()
  553. * @param array $string contains the string to be converted
  554. * @param array $split_length contains the length of each chunk
  555. *
  556. * @return Converts a string to an array. If the optional split_length parameter is specified,
  557. * the returned array will be broken down into chunks with each being split_length in length,
  558. * otherwise each chunk will be one character in length. FALSE is returned if split_length is
  559. * less than 1. If the split_length length exceeds the length of string, the entire string is
  560. * returned as the first (and only) array element.
  561. */
  562. function str_split($string, $split_length = 1)
  563. {
  564. if ($split_length < 1)
  565. {
  566. return false;
  567. }
  568. else if ($split_length >= strlen($string))
  569. {
  570. return array($string);
  571. }
  572. else
  573. {
  574. preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches);
  575. return $matches[0];
  576. }
  577. }
  578. }
  579. if (!function_exists('stripos'))
  580. {
  581. /**
  582. * A wrapper for the PHP5 function stripos
  583. * Find position of first occurrence of a case-insensitive string
  584. *
  585. * @param string $haystack is the string to search in
  586. * @param string $needle is the string to search for
  587. *
  588. * @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive.
  589. * Note that the needle may be a string of one or more characters.
  590. * If needle is not found, stripos() will return boolean FALSE.
  591. */
  592. function stripos($haystack, $needle)
  593. {
  594. if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
  595. {
  596. return strpos($haystack, $m[0]);
  597. }
  598. return false;
  599. }
  600. }
  601. /**
  602. * Checks if a path ($path) is absolute or relative
  603. *
  604. * @param string $path Path to check absoluteness of
  605. * @return boolean
  606. */
  607. function is_absolute($path)
  608. {
  609. return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:/#i', $path))) ? true : false;
  610. }
  611. /**
  612. * @author Chris Smith <chris@project-minerva.org>
  613. * @copyright 2006 Project Minerva Team
  614. * @param string $path The path which we should attempt to resolve.
  615. * @return mixed
  616. */
  617. function phpbb_own_realpath($path)
  618. {
  619. // Now to perform funky shizzle
  620. // Switch to use UNIX slashes
  621. $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
  622. $path_prefix = '';
  623. // Determine what sort of path we have
  624. if (is_absolute($path))
  625. {
  626. $absolute = true;
  627. if ($path[0] == '/')
  628. {
  629. // Absolute path, *NIX style
  630. $path_prefix = '';
  631. }
  632. else
  633. {
  634. // Absolute path, Windows style
  635. // Remove the drive letter and colon
  636. $path_prefix = $path[0] . ':';
  637. $path = substr($path, 2);
  638. }
  639. }
  640. else
  641. {
  642. // Relative Path
  643. // Prepend the current working directory
  644. if (function_exists('getcwd'))
  645. {
  646. // This is the best method, hopefully it is enabled!
  647. $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
  648. $absolute = true;
  649. if (preg_match('#^[a-z]:#i', $path))
  650. {
  651. $path_prefix = $path[0] . ':';
  652. $path = substr($path, 2);
  653. }
  654. else
  655. {
  656. $path_prefix = '';
  657. }
  658. }
  659. else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
  660. {
  661. // Warning: If chdir() has been used this will lie!
  662. // Warning: This has some problems sometime (CLI can create them easily)
  663. $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
  664. $absolute = true;
  665. $path_prefix = '';
  666. }
  667. else
  668. {
  669. // We have no way of getting the absolute path, just run on using relative ones.
  670. $absolute = false;
  671. $path_prefix = '.';
  672. }
  673. }
  674. // Remove any repeated slashes
  675. $path = preg_replace('#/{2,}#', '/', $path);
  676. // Remove the slashes from the start and end of the path
  677. $path = trim($path, '/');
  678. // Break the string into little bits for us to nibble on
  679. $bits = explode('/', $path);
  680. // Remove any . in the path, renumber array for the loop below
  681. $bits = array_values(array_diff($bits, array('.')));
  682. // Lets get looping, run over and resolve any .. (up directory)
  683. for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
  684. {
  685. // @todo Optimise
  686. if ($bits[$i] == '..' )
  687. {
  688. if (isset($bits[$i - 1]))
  689. {
  690. if ($bits[$i - 1] != '..')
  691. {
  692. // We found a .. and we are able to traverse upwards, lets do it!
  693. unset($bits[$i]);
  694. unset($bits[$i - 1]);
  695. $i -= 2;
  696. $max -= 2;
  697. $bits = array_values($bits);
  698. }
  699. }
  700. else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
  701. {
  702. // We have an absolute path trying to descend above the root of the filesystem
  703. // ... Error!
  704. return false;
  705. }
  706. }
  707. }
  708. // Prepend the path prefix
  709. array_unshift($bits, $path_prefix);
  710. $resolved = '';
  711. $max = sizeof($bits) - 1;
  712. // Check if we are able to resolve symlinks, Windows cannot.
  713. $symlink_resolve = (function_exists('readlink')) ? true : false;
  714. foreach ($bits as $i => $bit)
  715. {
  716. if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
  717. {
  718. // Path Exists
  719. if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
  720. {
  721. // Resolved a symlink.
  722. $resolved = $link . (($i == $max) ? '' : '/');
  723. continue;
  724. }
  725. }
  726. else
  727. {
  728. // Something doesn't exist here!
  729. // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
  730. // return false;
  731. }
  732. $resolved .= $bit . (($i == $max) ? '' : '/');
  733. }
  734. // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
  735. // because we must be inside that basedir, the question is where...
  736. // @internal The slash in is_dir() gets around an open_basedir restriction
  737. if (!@file_exists($resolved) || (!is_dir($resolved . '/') && !is_file($resolved)))
  738. {
  739. return false;
  740. }
  741. // Put the slashes back to the native operating systems slashes
  742. $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
  743. // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
  744. if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
  745. {
  746. return substr($resolved, 0, -1);
  747. }
  748. return $resolved; // We got here, in the end!
  749. }
  750. if (!function_exists('realpath'))
  751. {
  752. /**
  753. * A wrapper for realpath
  754. * @ignore
  755. */
  756. function phpbb_realpath($path)
  757. {
  758. return phpbb_own_realpath($path);
  759. }
  760. }
  761. else
  762. {
  763. /**
  764. * A wrapper for realpath
  765. */
  766. function phpbb_realpath($path)
  767. {
  768. $realpath = realpath($path);
  769. // Strangely there are provider not disabling realpath but returning strange values. :o
  770. // We at least try to cope with them.
  771. if ($realpath === $path || $realpath === false)
  772. {
  773. return phpbb_own_realpath($path);
  774. }
  775. // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
  776. if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
  777. {
  778. $realpath = substr($realpath, 0, -1);
  779. }
  780. return $realpath;
  781. }
  782. }
  783. if (!function_exists('htmlspecialchars_decode'))
  784. {
  785. /**
  786. * A wrapper for htmlspecialchars_decode
  787. * @ignore
  788. */
  789. function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
  790. {
  791. return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
  792. }
  793. }
  794. // functions used for building option fields
  795. /**
  796. * Pick a language, any language ...
  797. */
  798. function language_select($default = '')
  799. {
  800. global $db;
  801. $sql = 'SELECT lang_iso, lang_local_name
  802. FROM ' . LANG_TABLE . '
  803. ORDER BY lang_english_name';
  804. $result = $db->sql_query($sql);
  805. $lang_options = '';
  806. while ($row = $db->sql_fetchrow($result))
  807. {
  808. $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
  809. $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
  810. }
  811. $db->sql_freeresult($result);
  812. return $lang_options;
  813. }
  814. /**
  815. * Pick a template/theme combo,
  816. */
  817. function style_select($default = '', $all = false)
  818. {
  819. global $db;
  820. $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
  821. $sql = 'SELECT style_id, style_name
  822. FROM ' . STYLES_TABLE . "
  823. $sql_where
  824. ORDER BY style_name";
  825. $result = $db->sql_query($sql);
  826. $style_options = '';
  827. while ($row = $db->sql_fetchrow($result))
  828. {
  829. $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
  830. $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
  831. }
  832. $db->sql_freeresult($result);
  833. return $style_options;
  834. }
  835. /**
  836. * Pick a timezone
  837. */
  838. function tz_select($default = '', $truncate = false)
  839. {
  840. global $user;
  841. $tz_select = '';
  842. foreach ($user->lang['tz_zones'] as $offset => $zone)
  843. {
  844. if ($truncate)
  845. {
  846. $zone_trunc = truncate_string($zone, 50, 255, false, '...');
  847. }
  848. else
  849. {
  850. $zone_trunc = $zone;
  851. }
  852. if (is_numeric($offset))
  853. {
  854. $selected = ($offset == $default) ? ' selected="selected"' : '';
  855. $tz_select .= '<option title="'.$zone.'" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
  856. }
  857. }
  858. return $tz_select;
  859. }
  860. // Functions handling topic/post tracking/marking
  861. /**
  862. * Marks a topic/forum as read
  863. * Marks a topic as posted to
  864. *
  865. * @param int $user_id can only be used with $mode == 'post'
  866. */
  867. function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
  868. {
  869. global $db, $user, $config;
  870. if ($mode == 'all')
  871. {
  872. if ($forum_id === false || !sizeof($forum_id))
  873. {
  874. if ($config['load_db_lastread'] && $user->data['is_registered'])
  875. {
  876. // Mark all forums read (index page)
  877. $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
  878. $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
  879. $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
  880. }
  881. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  882. {
  883. $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  884. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  885. unset($tracking_topics['tf']);
  886. unset($tracking_topics['t']);
  887. unset($tracking_topics['f']);
  888. $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
  889. $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
  890. $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics);
  891. unset($tracking_topics);
  892. if ($user->data['is_registered'])
  893. {
  894. $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
  895. }
  896. }
  897. }
  898. return;
  899. }
  900. else if ($mode == 'topics')
  901. {
  902. // Mark all topics in forums read
  903. if (!is_array($forum_id))
  904. {
  905. $forum_id = array($forum_id);
  906. }
  907. // Add 0 to forums array to mark global announcements correctly
  908. $forum_id[] = 0;
  909. if ($config['load_db_lastread'] && $user->data['is_registered'])
  910. {
  911. $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
  912. WHERE user_id = {$user->data['user_id']}
  913. AND " . $db->sql_in_set('forum_id', $forum_id);
  914. $db->sql_query($sql);
  915. $sql = 'SELECT forum_id
  916. FROM ' . FORUMS_TRACK_TABLE . "
  917. WHERE user_id = {$user->data['user_id']}
  918. AND " . $db->sql_in_set('forum_id', $forum_id);
  919. $result = $db->sql_query($sql);
  920. $sql_update = array();
  921. while ($row = $db->sql_fetchrow($result))
  922. {
  923. $sql_update[] = $row['forum_id'];
  924. }
  925. $db->sql_freeresult($result);
  926. if (sizeof($sql_update))
  927. {
  928. $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
  929. SET mark_time = ' . time() . "
  930. WHERE user_id = {$user->data['user_id']}
  931. AND " . $db->sql_in_set('forum_id', $sql_update);
  932. $db->sql_query($sql);
  933. }
  934. if ($sql_insert = array_diff($forum_id, $sql_update))
  935. {
  936. $sql_ary = array();
  937. foreach ($sql_insert as $f_id)
  938. {
  939. $sql_ary[] = array(
  940. 'user_id' => (int) $user->data['user_id'],
  941. 'forum_id' => (int) $f_id,
  942. 'mark_time' => time()
  943. );
  944. }
  945. $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
  946. }
  947. }
  948. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  949. {
  950. $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  951. $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
  952. foreach ($forum_id as $f_id)
  953. {
  954. $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
  955. if (isset($tracking['tf'][$f_id]))
  956. {
  957. unset($tracking['tf'][$f_id]);
  958. }
  959. foreach ($topic_ids36 as $topic_id36)
  960. {
  961. unset($tracking['t'][$topic_id36]);
  962. }
  963. if (isset($tracking['f'][$f_id]))
  964. {
  965. unset($tracking['f'][$f_id]);
  966. }
  967. $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
  968. }
  969. if (isset($tracking['tf']) && empty($tracking['tf']))
  970. {
  971. unset($tracking['tf']);
  972. }
  973. $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
  974. $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
  975. unset($tracking);
  976. }
  977. return;
  978. }
  979. else if ($mode == 'topic')
  980. {
  981. if ($topic_id === false || $forum_id === false)
  982. {
  983. return;
  984. }
  985. if ($config['load_db_lastread'] && $user->data['is_registered'])
  986. {
  987. $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
  988. SET mark_time = ' . (($post_time) ? $post_time : time()) . "
  989. WHERE user_id = {$user->data['user_id']}
  990. AND topic_id = $topic_id";
  991. $db->sql_query($sql);
  992. // insert row
  993. if (!$db->sql_affectedrows())
  994. {
  995. $db->sql_return_on_error(true);
  996. $sql_ary = array(
  997. 'user_id' => (int) $user->data['user_id'],
  998. 'topic_id' => (int) $topic_id,
  999. 'forum_id' => (int) $forum_id,
  1000. 'mark_time' => ($post_time) ? (int) $post_time : time(),
  1001. );
  1002. $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  1003. $db->sql_return_on_error(false);
  1004. }
  1005. }
  1006. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1007. {
  1008. $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1009. $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
  1010. $topic_id36 = base_convert($topic_id, 10, 36);
  1011. if (!isset($tracking['t'][$topic_id36]))
  1012. {
  1013. $tracking['tf'][$forum_id][$topic_id36] = true;
  1014. }
  1015. $post_time = ($post_time) ? $post_time : time();
  1016. $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
  1017. // If the cookie grows larger than 10000 characters we will remove the smallest value
  1018. // This can result in old topics being unread - but most of the time it should be accurate...
  1019. if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
  1020. {
  1021. //echo 'Cookie grown too large' . print_r($tracking, true);
  1022. // We get the ten most minimum stored time offsets and its associated topic ids
  1023. $time_keys = array();
  1024. for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
  1025. {
  1026. $min_value = min($tracking['t']);
  1027. $m_tkey = array_search($min_value, $tracking['t']);
  1028. unset($tracking['t'][$m_tkey]);
  1029. $time_keys[$m_tkey] = $min_value;
  1030. }
  1031. // Now remove the topic ids from the array...
  1032. foreach ($tracking['tf'] as $f_id => $topic_id_ary)
  1033. {
  1034. foreach ($time_keys as $m_tkey => $min_value)
  1035. {
  1036. if (isset($topic_id_ary[$m_tkey]))
  1037. {
  1038. $tracking['f'][$f_id] = $min_value;
  1039. unset($tracking['tf'][$f_id][$m_tkey]);
  1040. }
  1041. }
  1042. }
  1043. if ($user->data['is_registered'])
  1044. {
  1045. $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
  1046. $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
  1047. }
  1048. else
  1049. {
  1050. $tracking['l'] = max($time_keys);
  1051. }
  1052. }
  1053. $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
  1054. $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
  1055. }
  1056. return;
  1057. }
  1058. else if ($mode == 'post')
  1059. {
  1060. if ($topic_id === false)
  1061. {
  1062. return;
  1063. }
  1064. $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
  1065. if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
  1066. {
  1067. $db->sql_return_on_error(true);
  1068. $sql_ary = array(
  1069. 'user_id' => (int) $use_user_id,
  1070. 'topic_id' => (int) $topic_id,
  1071. 'topic_posted' => 1
  1072. );
  1073. $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  1074. $db->sql_return_on_error(false);
  1075. }
  1076. return;
  1077. }
  1078. }
  1079. /**
  1080. * Get topic tracking info by using already fetched info
  1081. */
  1082. function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
  1083. {
  1084. global $config, $user;
  1085. $last_read = array();
  1086. if (!is_array($topic_ids))
  1087. {
  1088. $topic_ids = array($topic_ids);
  1089. }
  1090. foreach ($topic_ids as $topic_id)
  1091. {
  1092. if (!empty($rowset[$topic_id]['mark_time']))
  1093. {
  1094. $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
  1095. }
  1096. }
  1097. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  1098. if (sizeof($topic_ids))
  1099. {
  1100. $mark_time = array();
  1101. // Get global announcement info
  1102. if ($global_announce_list && sizeof($global_announce_list))
  1103. {
  1104. if (!isset($forum_mark_time[0]))
  1105. {
  1106. global $db;
  1107. $sql = 'SELECT mark_time
  1108. FROM ' . FORUMS_TRACK_TABLE . "
  1109. WHERE user_id = {$user->data['user_id']}
  1110. AND forum_id = 0";
  1111. $result = $db->sql_query($sql);
  1112. $row = $db->sql_fetchrow($result);
  1113. $db->sql_freeresult($result);
  1114. if ($row)
  1115. {
  1116. $mark_time[0] = $row['mark_time'];
  1117. }
  1118. }
  1119. else
  1120. {
  1121. if ($forum_mark_time[0] !== false)
  1122. {
  1123. $mark_time[0] = $forum_mark_time[0];
  1124. }
  1125. }
  1126. }
  1127. if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
  1128. {
  1129. $mark_time[$forum_id] = $forum_mark_time[$forum_id];
  1130. }
  1131. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
  1132. foreach ($topic_ids as $topic_id)
  1133. {
  1134. if ($global_announce_list && isset($global_announce_list[$topic_id]))
  1135. {
  1136. $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
  1137. }
  1138. else
  1139. {
  1140. $last_read[$topic_id] = $user_lastmark;
  1141. }
  1142. }
  1143. }
  1144. return $last_read;
  1145. }
  1146. /**
  1147. * Get topic tracking info from db (for cookie based tracking only this function is used)
  1148. */
  1149. function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
  1150. {
  1151. global $config, $user;
  1152. $last_read = array();
  1153. if (!is_array($topic_ids))
  1154. {
  1155. $topic_ids = array($topic_ids);
  1156. }
  1157. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1158. {
  1159. global $db;
  1160. $sql = 'SELECT topic_id, mark_time
  1161. FROM ' . TOPICS_TRACK_TABLE . "
  1162. WHERE user_id = {$user->data['user_id']}
  1163. AND " . $db->sql_in_set('topic_id', $topic_ids);
  1164. $result = $db->sql_query($sql);
  1165. while ($row = $db->sql_fetchrow($result))
  1166. {
  1167. $last_read[$row['topic_id']] = $row['mark_time'];
  1168. }
  1169. $db->sql_freeresult($result);
  1170. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  1171. if (sizeof($topic_ids))
  1172. {
  1173. $sql = 'SELECT forum_id, mark_time
  1174. FROM ' . FORUMS_TRACK_TABLE . "
  1175. WHERE user_id = {$user->data['user_id']}
  1176. AND forum_id " .
  1177. (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
  1178. $result = $db->sql_query($sql);
  1179. $mark_time = array();
  1180. while ($row = $db->sql_fetchrow($result))
  1181. {
  1182. $mark_time[$row['forum_id']] = $row['mark_time'];
  1183. }
  1184. $db->sql_freeresult($result);
  1185. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
  1186. foreach ($topic_ids as $topic_id)
  1187. {
  1188. if ($global_announce_list && isset($global_announce_list[$topic_id]))
  1189. {
  1190. $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
  1191. }
  1192. else
  1193. {
  1194. $last_read[$topic_id] = $user_lastmark;
  1195. }
  1196. }
  1197. }
  1198. }
  1199. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1200. {
  1201. global $tracking_topics;
  1202. if (!isset($tracking_topics) || !sizeof($tracking_topics))
  1203. {
  1204. $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1205. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  1206. }
  1207. if (!$user->data['is_registered'])
  1208. {
  1209. $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
  1210. }
  1211. else
  1212. {
  1213. $user_lastmark = $user->data['user_lastmark'];
  1214. }
  1215. foreach ($topic_ids as $topic_id)
  1216. {
  1217. $topic_id36 = base_convert($topic_id, 10, 36);
  1218. if (isset($tracking_topics['t'][$topic_id36]))
  1219. {
  1220. $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
  1221. }
  1222. }
  1223. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  1224. if (sizeof($topic_ids))
  1225. {
  1226. $mark_time = array();
  1227. if ($global_announce_list && sizeof($global_announce_list))
  1228. {
  1229. if (isset($tracking_topics['f'][0]))
  1230. {
  1231. $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
  1232. }
  1233. }
  1234. if (isset($tracking_topics['f'][$forum_id]))
  1235. {
  1236. $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
  1237. }
  1238. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
  1239. foreach ($topic_ids as $topic_id)
  1240. {
  1241. if ($global_announce_list && isset($global_announce_list[$topic_id]))
  1242. {
  1243. $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
  1244. }
  1245. else
  1246. {
  1247. $last_read[$topic_id] = $user_lastmark;
  1248. }
  1249. }
  1250. }
  1251. }
  1252. return $last_read;
  1253. }
  1254. /**
  1255. * Check for read forums and update topic tracking info accordingly
  1256. *
  1257. * @param int $forum_id the forum id to check
  1258. * @param int $forum_last_post_time the forums last post time
  1259. * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
  1260. * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
  1261. *
  1262. * @return true if complete forum got marked read, else false.
  1263. */
  1264. function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
  1265. {
  1266. global $db, $tracking_topics, $user, $config;
  1267. // Determine the users last forum mark time if not given.
  1268. if ($mark_time_forum === false)
  1269. {
  1270. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1271. {
  1272. $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
  1273. }
  1274. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1275. {
  1276. $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1277. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  1278. if (!$user->data['is_registered'])
  1279. {
  1280. $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
  1281. }
  1282. $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark'];
  1283. }
  1284. }
  1285. // Check the forum for any left unread topics.
  1286. // If there are none, we mark the forum as read.
  1287. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1288. {
  1289. if ($mark_time_forum >= $forum_last_post_time)
  1290. {
  1291. // We do not need to mark read, this happened before. Therefore setting this to true
  1292. $row = true;
  1293. }
  1294. else
  1295. {
  1296. $sql = 'SELECT t.forum_id FROM ' . TOPICS_TABLE . ' t
  1297. LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id'] . ')
  1298. WHERE t.forum_id = ' . $forum_id . '
  1299. AND t.topic_last_post_time > ' . $mark_time_forum . '
  1300. AND t.topic_moved_id = 0
  1301. AND (tt.topic_id IS NULL OR tt.mark_time < t.topic_last_post_time)
  1302. GROUP BY t.forum_id';
  1303. $result = $db->sql_query_limit($sql, 1);
  1304. $row = $db->sql_fetchrow($result);
  1305. $db->sql_freeresult($result);
  1306. }
  1307. }
  1308. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1309. {
  1310. // Get information from cookie
  1311. $row = false;
  1312. if (!isset($tracking_topics['tf'][$forum_id]))
  1313. {
  1314. // We do not need to mark read, this happened before. Therefore setting this to true
  1315. $row = true;
  1316. }
  1317. else
  1318. {
  1319. $sql = 'SELECT topic_id
  1320. FROM ' . TOPICS_TABLE . '
  1321. WHERE forum_id = ' . $forum_id . '
  1322. AND topic_last_post_time > ' . $mark_time_forum . '
  1323. AND topic_moved_id = 0';
  1324. $result = $db->sql_query($sql);
  1325. $check_forum = $tracking_topics['tf'][$forum_id];
  1326. $unread = false;
  1327. while ($row = $db->sql_fetchrow($result))
  1328. {
  1329. if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
  1330. {
  1331. $unread = true;
  1332. break;
  1333. }
  1334. }
  1335. $db->sql_freeresult($result);
  1336. $row = $unread;
  1337. }
  1338. }
  1339. else
  1340. {
  1341. $row = true;
  1342. }
  1343. if (!$row)
  1344. {
  1345. markread('topics', $forum_id);
  1346. return true;
  1347. }
  1348. return false;
  1349. }
  1350. /**
  1351. * Transform an array into a serialized format
  1352. */
  1353. function tracking_serialize($input)
  1354. {
  1355. $out = '';
  1356. foreach ($input as $key => $value)
  1357. {
  1358. if (is_array($value))
  1359. {
  1360. $out .= $key . ':(' . tracking_serialize($value) . ');';
  1361. }
  1362. else
  1363. {
  1364. $out .= $key . ':' . $value . ';';
  1365. }
  1366. }
  1367. return $out;
  1368. }
  1369. /**
  1370. * Transform a serialized array into an actual array
  1371. */
  1372. function tracking_unserialize($string, $max_depth = 3)
  1373. {
  1374. $n = strlen($string);
  1375. if ($n > 10010)
  1376. {
  1377. die('Invalid data supplied');
  1378. }
  1379. $data = $stack = array();
  1380. $key = '';
  1381. $mode = 0;
  1382. $level = &$data;
  1383. for ($i = 0; $i < $n; ++$i)
  1384. {
  1385. switch ($mode)
  1386. {
  1387. case 0:
  1388. switch ($string[$i])
  1389. {
  1390. case ':':
  1391. $level[$key] = 0;
  1392. $mode = 1;
  1393. break;
  1394. case ')':
  1395. unset($level);
  1396. $level = array_pop($stack);
  1397. $mode = 3;
  1398. break;
  1399. default:
  1400. $key .= $string[$i];
  1401. }
  1402. break;
  1403. case 1:
  1404. switch ($string[$i])
  1405. {
  1406. case '(':
  1407. if (sizeof($stack) >= $max_depth)
  1408. {
  1409. die('Invalid data supplied');
  1410. }
  1411. $stack[] = &$level;
  1412. $level[$key] = array();
  1413. $level = &$level[$key];
  1414. $key = '';
  1415. $mode = 0;
  1416. break;
  1417. default:
  1418. $level[$key] = $string[$i];
  1419. $mode = 2;
  1420. break;
  1421. }
  1422. break;
  1423. case 2:
  1424. switch ($string[$i])
  1425. {
  1426. case ')':
  1427. unset($level);
  1428. $level = array_pop($stack);
  1429. $mode = 3;
  1430. break;
  1431. case ';':
  1432. $key = '';
  1433. $mode = 0;
  1434. break;
  1435. default:
  1436. $level[$key] .= $string[$i];
  1437. break;
  1438. }
  1439. break;
  1440. case 3:
  1441. switch ($string[$i])
  1442. {
  1443. case ')':
  1444. unset($level);
  1445. $level = array_pop($stack);
  1446. break;
  1447. case ';':
  1448. $key = '';
  1449. $mode = 0;
  1450. break;
  1451. default:
  1452. die('Invalid data supplied');
  1453. break;
  1454. }
  1455. break;
  1456. }
  1457. }
  1458. if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
  1459. {
  1460. die('Invalid data supplied');
  1461. }
  1462. return $level;
  1463. }
  1464. // Pagination functions
  1465. /**
  1466. * Pagination routine, generates page number sequence
  1467. * tpl_prefix is for using different pagination blocks at one page
  1468. */
  1469. function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '')
  1470. {
  1471. global $template, $user;
  1472. // Make sure $per_page is a valid value
  1473. $per_page = ($per_page <= 0) ? 1 : $per_page;
  1474. $seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>';
  1475. $total_pages = ceil($num_items / $per_page);
  1476. if ($total_pages == 1 || !$num_items)
  1477. {
  1478. return false;
  1479. }
  1480. $on_page = floor($start_item / $per_page) + 1;
  1481. $url_delim = (strpos($base_url, '?') === false) ? '?' : '&amp;';
  1482. $page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>';
  1483. if ($total_pages > 5)
  1484. {
  1485. $start_cnt = min(max(1, $on_page - 4), $total_pages - 5);
  1486. $end_cnt = max(min($total_pages, $on_page + 4), 6);
  1487. $page_string .= ($start_cnt > 1) ? ' ... ' : $seperator;
  1488. for ($i = $start_cnt + 1; $i < $end_cnt; $i++)
  1489. {
  1490. $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
  1491. if ($i < $end_cnt - 1)
  1492. {
  1493. $page_string .= $seperator;
  1494. }
  1495. }
  1496. $page_string .= ($end_cnt < $total_pages) ? ' ... ' : $seperator;
  1497. }
  1498. else
  1499. {
  1500. $page_string .= $seperator;
  1501. for ($i = 2; $i < $total_pages; $i++)
  1502. {
  1503. $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
  1504. if ($i < $total_pages)
  1505. {
  1506. $page_string .= $seperator;
  1507. }
  1508. }
  1509. }
  1510. $page_string .= ($on_page == $total_pages) ? '<strong>' . $total_pages . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($total_pages - 1) * $per_page) . '">' . $total_pages . '</a>';
  1511. if ($add_prevnext_text)
  1512. {
  1513. if ($on_page != 1)
  1514. {
  1515. $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a>&nbsp;&nbsp;' . $page_string;
  1516. }
  1517. if ($on_page != $total_pages)
  1518. {
  1519. $page_string .= '&nbsp;&nbsp;<a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>';
  1520. }
  1521. }
  1522. $template->assign_vars(array(
  1523. $tpl_prefix . 'BASE_URL' => $base_url,
  1524. 'A_' . $tpl_prefix . 'BASE_URL' => addslashes($base_url),
  1525. $tpl_prefix . 'PER_PAGE' => $per_page,
  1526. $tpl_prefix . 'PREVIOUS_PAGE' => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page),
  1527. $tpl_prefix . 'NEXT_PAGE' => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page),
  1528. $tpl_prefix . 'TOTAL_PAGES' => $total_pages,
  1529. ));
  1530. return $page_string;
  1531. }
  1532. /**
  1533. * Return current page (pagination)
  1534. */
  1535. function on_page($num_items, $per_page, $start)
  1536. {
  1537. global $template, $user;
  1538. // Make sure $per_page is a valid value
  1539. $per_page = ($per_page <= 0) ? 1 : $per_page;
  1540. $on_page = floor($start / $per_page) + 1;
  1541. $template->assign_vars(array(
  1542. 'ON_PAGE' => $on_page)
  1543. );
  1544. return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1));
  1545. }
  1546. // Server functions (building urls, redirecting...)
  1547. /**
  1548. * Append session id to url.
  1549. * This function supports hooks.
  1550. *
  1551. * @param string $url The url the session id needs to be appended to (can have params)
  1552. * @param mixed $params String or array of additional url parameters
  1553. * @param bool $is_amp Is url using &amp; (true) or & (false)
  1554. * @param string $session_id Possibility to use a custom session id instead of the global one
  1555. *
  1556. * Examples:
  1557. * <code>
  1558. * append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&amp;f=2");
  1559. * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&amp;f=2');
  1560. * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
  1561. * append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
  1562. * </code>
  1563. *
  1564. */
  1565. function append_sid($url, $params = false, $is_amp = true, $session_id = false)
  1566. {
  1567. global $_SID, $_EXTRA_URL, $phpbb_hook;
  1568. // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropiatly.
  1569. // They could mimick most of what is within this function
  1570. if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
  1571. {
  1572. if ($phpbb_hook->hook_return(__FUNCTION__))
  1573. {
  1574. return $phpbb_hook->hook_return_result(__FUNCTION__);
  1575. }
  1576. }
  1577. $params_is_array = is_array($params);
  1578. // Get anchor
  1579. $anchor = '';
  1580. if (strpos($url, '#') !== false)
  1581. {
  1582. list($url, $anchor) = explode('#', $url, 2);
  1583. $anchor = '#' . $anchor;
  1584. }
  1585. else if (!$params_is_array && strpos($params, '#') !== false)
  1586. {
  1587. list($params, $anchor) = explode('#', $params, 2);
  1588. $anchor = '#' . $anchor;
  1589. }
  1590. // Handle really simple cases quickly
  1591. if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
  1592. {
  1593. if ($params === false)
  1594. {
  1595. return $url;
  1596. }
  1597. $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&amp;' : '&');
  1598. return $url . ($params !== false ? $url_delim. $params : '');
  1599. }
  1600. // Assign sid if session id is not specified
  1601. if ($session_id === false)
  1602. {
  1603. $session_id = $_SID;
  1604. }
  1605. $amp_delim = ($is_amp) ? '&amp;' : '&';
  1606. $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
  1607. // Appending custom url parameter?
  1608. $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
  1609. // Use the short variant if possible ;)
  1610. if ($params === false)
  1611. {
  1612. // Append session id
  1613. if (!$session_id)
  1614. {
  1615. return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
  1616. }
  1617. else
  1618. {
  1619. return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
  1620. }
  1621. }
  1622. // Build string if parameters are specified as array
  1623. if (is_array($params))
  1624. {
  1625. $output = array();
  1626. foreach ($params as $key => $item)
  1627. {
  1628. if ($item === NULL)
  1629. {
  1630. continue;
  1631. }
  1632. if ($key == '#')
  1633. {
  1634. $anchor = '#' . $item;
  1635. continue;
  1636. }
  1637. $output[] = $key . '=' . $item;
  1638. }
  1639. $params = implode($amp_delim, $output);
  1640. }
  1641. // Append session id and parameters (even if they are empty)
  1642. // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
  1643. return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
  1644. }
  1645. /**
  1646. * Generate board url (example: http://www.example.com/phpBB)
  1647. * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
  1648. */
  1649. function generate_board_url($without_script_path = false)
  1650. {
  1651. global $config, $user;
  1652. $server_name = $user->host;
  1653. $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
  1654. // Forcing server vars is the only way to specify/override the protocol
  1655. if ($config['force_server_vars'] || !$server_name)
  1656. {
  1657. $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
  1658. $server_name = $config['server_name'];
  1659. $server_port = (int) $config['server_port'];
  1660. $script_path = $config['script_path'];
  1661. $url = $server_protocol . $server_name;
  1662. $cookie_secure = $config['cookie_secure'];
  1663. }
  1664. else
  1665. {
  1666. // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
  1667. $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
  1668. $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
  1669. $script_path = $user->page['root_script_path'];
  1670. }
  1671. if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
  1672. {
  1673. // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
  1674. if (strpos($server_name, ':') === false)
  1675. {
  1676. $url .= ':' . $server_port;
  1677. }
  1678. }
  1679. if (!$without_script_path)
  1680. {
  1681. $url .= $script_path;
  1682. }
  1683. // Strip / from the end
  1684. if (substr($url, -1, 1) == '/')
  1685. {
  1686. $url = substr($url, 0, -1);
  1687. }
  1688. return $url;
  1689. }
  1690. /**
  1691. * Redirects the user to another page then exits the script nicely
  1692. * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
  1693. *
  1694. * @param string $url The url to redirect to
  1695. * @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
  1696. * @param bool $disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
  1697. */
  1698. function redirect($url, $return = false, $disable_cd_check = false)
  1699. {
  1700. global $db, $cache, $config, $user, $phpbb_root_path;
  1701. if (empty($user->lang))
  1702. {
  1703. $user->add_lang('common');
  1704. }
  1705. if (!$return)
  1706. {
  1707. garbage_collection();
  1708. }
  1709. // Make sure no &amp;'s are in, this will break the redirect
  1710. $url = str_replace('&amp;', '&', $url);
  1711. // Determine which type of redirect we need to handle...
  1712. $url_parts = parse_url($url);
  1713. if ($url_parts === false)
  1714. {
  1715. // Malformed url, redirect to current page...
  1716. $url = generate_board_url() . '/' . $user->page['page'];
  1717. }
  1718. else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
  1719. {
  1720. // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
  1721. if (!$disable_cd_check && $url_parts['host'] !== $user->host)
  1722. {
  1723. $url = generate_board_url();
  1724. }
  1725. }
  1726. else if ($url[0] == '/')
  1727. {
  1728. // Absolute uri, prepend direct url...
  1729. $url = generate_board_url(true) . $url;
  1730. }
  1731. else
  1732. {
  1733. // Relative uri
  1734. $pathinfo = pathinfo($url);
  1735. // Is the uri pointing to the current directory?
  1736. if ($pathinfo['dirname'] == '.')
  1737. {
  1738. $url = str_replace('./', '', $url);
  1739. // Strip / from the beginning
  1740. if ($url && substr($url, 0, 1) == '/')
  1741. {
  1742. $url = substr($url, 1);
  1743. }
  1744. if ($user->page['page_dir'])
  1745. {
  1746. $url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url;
  1747. }
  1748. else
  1749. {
  1750. $url = generate_board_url() . '/' . $url;
  1751. }
  1752. }
  1753. else
  1754. {
  1755. // Used ./ before, but $phpbb_root_path is working better with urls within another root path
  1756. $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path)));
  1757. $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname'])));
  1758. $intersection = array_intersect_assoc($root_dirs, $page_dirs);
  1759. $root_dirs = array_diff_assoc($root_dirs, $intersection);
  1760. $page_dirs = array_diff_assoc($page_dirs, $intersection);
  1761. $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
  1762. // Strip / from the end
  1763. if ($dir && substr($dir, -1, 1) == '/')
  1764. {
  1765. $dir = substr($dir, 0, -1);
  1766. }
  1767. // Strip / from the beginning
  1768. if ($dir && substr($dir, 0, 1) == '/')
  1769. {
  1770. $dir = substr($dir, 1);
  1771. }
  1772. $url = str_replace($pathinfo['dirname'] . '/', '', $url);
  1773. // Strip / from the beginning
  1774. if (substr($url, 0, 1) == '/')
  1775. {
  1776. $url = substr($url, 1);
  1777. }
  1778. $url = (!empty($dir) ? $dir . '/' : '') . $url;
  1779. $url = generate_board_url() . '/' . $url;
  1780. }
  1781. }
  1782. // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
  1783. if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
  1784. {
  1785. trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
  1786. }
  1787. // Now, also check the protocol and for a valid url the last time...
  1788. $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
  1789. $url_parts = parse_url($url);
  1790. if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
  1791. {
  1792. trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
  1793. }
  1794. if ($return)
  1795. {
  1796. return $url;
  1797. }
  1798. // Redirect via an HTML form for PITA webservers
  1799. if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
  1800. {
  1801. header('Refresh: 0; URL=' . $url);
  1802. echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
  1803. echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">';
  1804. echo '<head>';
  1805. echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
  1806. echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
  1807. echo '<title>' . $user->lang['REDIRECT'] . '</title>';
  1808. echo '</head>';
  1809. echo '<body>';
  1810. echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
  1811. echo '</body>';
  1812. echo '</html>';
  1813. exit;
  1814. }
  1815. // Behave as per HTTP/1.1 spec for others
  1816. header('Location: ' . $url);
  1817. exit;
  1818. }
  1819. /**
  1820. * Re-Apply session id after page reloads
  1821. */
  1822. function reapply_sid($url)
  1823. {
  1824. global $phpEx, $phpbb_root_path;
  1825. if ($url === "index.$phpEx")
  1826. {
  1827. return append_sid("index.$phpEx");
  1828. }
  1829. else if ($url === "{$phpbb_root_path}index.$phpEx")
  1830. {
  1831. return append_sid("{$phpbb_root_path}index.$phpEx");
  1832. }
  1833. // Remove previously added sid
  1834. if (strpos($url, '?sid=') !== false)
  1835. {
  1836. $url = preg_replace('/(\?)sid=[a-z0-9]+(&amp;|&)?/', '\1', $url);
  1837. }
  1838. else if (strpos($url, '&sid=') !== false)
  1839. {
  1840. $url = preg_replace('/&sid=[a-z0-9]+(&)?/', '\1', $url);
  1841. }
  1842. else if (strpos($url, '&amp;sid=') !== false)
  1843. {
  1844. $url = preg_replace('/&amp;sid=[a-z0-9]+(&amp;)?/', '\1', $url);
  1845. }
  1846. return append_sid($url);
  1847. }
  1848. /**
  1849. * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
  1850. */
  1851. function build_url($strip_vars = false)
  1852. {
  1853. global $user, $phpbb_root_path;
  1854. // Append SID
  1855. $redirect = append_sid($user->page['page'], false, false);
  1856. // Add delimiter if not there...
  1857. if (strpos($redirect, '?') === false)
  1858. {
  1859. $redirect .= '?';
  1860. }
  1861. // Strip vars...
  1862. if ($strip_vars !== false && strpos($redirect, '?') !== false)
  1863. {
  1864. if (!is_array($strip_vars))
  1865. {
  1866. $strip_vars = array($strip_vars);
  1867. }
  1868. $query = $_query = array();
  1869. $args = substr($redirect, strpos($redirect, '?') + 1);
  1870. $args = ($args) ? explode('&', $args) : array();
  1871. $redirect = substr($redirect, 0, strpos($redirect, '?'));
  1872. foreach ($args as $argument)
  1873. {
  1874. $arguments = explode('=', $argument);
  1875. $key = $arguments[0];
  1876. unset($arguments[0]);
  1877. $query[$key] = implode('=', $arguments);
  1878. }
  1879. // Strip the vars off
  1880. foreach ($strip_vars as $strip)
  1881. {
  1882. if (isset($query[$strip]))
  1883. {
  1884. unset($query[$strip]);
  1885. }
  1886. }
  1887. // Glue the remaining parts together... already urlencoded
  1888. foreach ($query as $key => $value)
  1889. {
  1890. $_query[] = $key . '=' . $value;
  1891. }
  1892. $query = implode('&', $_query);
  1893. $redirect .= ($query) ? '?' . $query : '';
  1894. }
  1895. return $phpbb_root_path . str_replace('&', '&amp;', $redirect);
  1896. }
  1897. /**
  1898. * Meta refresh assignment
  1899. * Adds META template variable with meta http tag.
  1900. *
  1901. * @param int $time Time in seconds for meta refresh tag
  1902. * @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
  1903. * @param bool $disable_cd_check If true, meta_refresh() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
  1904. */
  1905. function meta_refresh($time, $url, $disable_cd_check = false)
  1906. {
  1907. global $template;
  1908. $url = redirect($url, true, $disable_cd_check);
  1909. //-- mod: Prime Quick Login -------------------------------------------------//
  1910. $template->assign_var('AUTO_REFRESH', $time);
  1911. //-- end: Prime Quick Login -------------------------------------------------//
  1912. $url = str_replace('&', '&amp;', $url);
  1913. // For XHTML compatibility we change back & to &amp;
  1914. $template->assign_vars(array(
  1915. 'META' => '<meta http-equiv="refresh" content="' . $time . ';url=' . $url . '" />')
  1916. );
  1917. return $url;
  1918. }
  1919. //Form validation
  1920. /**
  1921. * Add a secret hash for use in links/GET requests
  1922. * @param string $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
  1923. * @return string the hash
  1924. */
  1925. function generate_link_hash($link_name)
  1926. {
  1927. global $user;
  1928. if (!isset($user->data["hash_$link_name"]))
  1929. {
  1930. $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
  1931. }
  1932. return $user->data["hash_$link_name"];
  1933. }
  1934. /**
  1935. * checks a link hash - for GET requests
  1936. * @param string $token the submitted token
  1937. * @param string $link_name The name of the link
  1938. * @return boolean true if all is fine
  1939. */
  1940. function check_link_hash($token, $link_name)
  1941. {
  1942. return $token === generate_link_hash($link_name);
  1943. }
  1944. /**
  1945. * Add a secret token to the form (requires the S_FORM_TOKEN template variable)
  1946. * @param string $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
  1947. */
  1948. function add_form_key($form_name)
  1949. {
  1950. global $config, $template, $user;
  1951. $now = time();
  1952. $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
  1953. $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
  1954. $s_fields = build_hidden_fields(array(
  1955. 'creation_time' => $now,
  1956. 'form_token' => $token,
  1957. ));
  1958. $template->assign_vars(array(
  1959. 'S_FORM_TOKEN' => $s_fields,
  1960. ));
  1961. }
  1962. /**
  1963. * Check the form key. Required for all altering actions not secured by confirm_box
  1964. * @param string $form_name The name of the form; has to match the name used in add_form_key, otherwise no restrictions apply
  1965. * @param int $timespan The maximum acceptable age for a submitted form in seconds. Defaults to the config setting.
  1966. * @param string $return_page The address for the return link
  1967. * @param bool $trigger If true, the function will triger an error when encountering an invalid form
  1968. */
  1969. function check_form_key($form_name, $timespan = false, $return_page = '', $trigger = false)
  1970. {
  1971. global $config, $user;
  1972. if ($timespan === false)
  1973. {
  1974. // we enforce a minimum value of half a minute here.
  1975. $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
  1976. }
  1977. if (isset($_POST['creation_time']) && isset($_POST['form_token']))
  1978. {
  1979. $creation_time = abs(request_var('creation_time', 0));
  1980. $token = request_var('form_token', '');
  1981. $diff = time() - $creation_time;
  1982. // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
  1983. if ($diff && ($diff <= $timespan || $timespan === -1))
  1984. {
  1985. $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
  1986. $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
  1987. if ($key === $token)
  1988. {
  1989. return true;
  1990. }
  1991. }
  1992. }
  1993. if ($trigger)
  1994. {
  1995. trigger_error($user->lang['FORM_INVALID'] . $return_page);
  1996. }
  1997. return false;
  1998. }
  1999. // Message/Login boxes
  2000. /**
  2001. * Build Confirm box
  2002. * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
  2003. * @param string $title Title/Message used for confirm box.
  2004. * message text is _CONFIRM appended to title.
  2005. * If title cannot be found in user->lang a default one is displayed
  2006. * If title_CONFIRM cannot be found in user->lang the text given is used.
  2007. * @param string $hidden Hidden variables
  2008. * @param string $html_body Template used for confirm box
  2009. * @param string $u_action Custom form action
  2010. */
  2011. function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
  2012. {
  2013. global $user, $template, $db;
  2014. global $phpEx, $phpbb_root_path;
  2015. if (isset($_POST['cancel']))
  2016. {
  2017. return false;
  2018. }
  2019. $confirm = false;
  2020. if (isset($_POST['confirm']))
  2021. {
  2022. // language frontier
  2023. if ($_POST['confirm'] === $user->lang['YES'])
  2024. {
  2025. $confirm = true;
  2026. }
  2027. }
  2028. if ($check && $confirm)
  2029. {
  2030. $user_id = request_var('user_id', 0);
  2031. $session_id = request_var('sess', '');
  2032. $confirm_key = request_var('confirm_key', '');
  2033. if ($user_id != $user->data['user_id'] || $session_id != $user->session_id || !$confirm_key || !$user->data['user_last_confirm_key'] || $confirm_key != $user->data['user_last_confirm_key'])
  2034. {
  2035. return false;
  2036. }
  2037. // Reset user_last_confirm_key
  2038. $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
  2039. WHERE user_id = " . $user->data['user_id'];
  2040. $db->sql_query($sql);
  2041. return true;
  2042. }
  2043. else if ($check)
  2044. {
  2045. return false;
  2046. }
  2047. $s_hidden_fields = build_hidden_fields(array(
  2048. 'user_id' => $user->data['user_id'],
  2049. 'sess' => $user->session_id,
  2050. 'sid' => $user->session_id)
  2051. );
  2052. // generate activation key
  2053. $confirm_key = gen_rand_string(10);
  2054. if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
  2055. {
  2056. adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
  2057. }
  2058. else
  2059. {
  2060. page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
  2061. }
  2062. $template->set_filenames(array(
  2063. 'body' => $html_body)
  2064. );
  2065. // If activation key already exist, we better do not re-use the key (something very strange is going on...)
  2066. if (request_var('confirm_key', ''))
  2067. {
  2068. // This should not occur, therefore we cancel the operation to safe the user
  2069. return false;
  2070. }
  2071. // re-add sid / transform & to &amp; for user->page (user->page is always using &)
  2072. $use_page = ($u_action) ? $phpbb_root_path . $u_action : $phpbb_root_path . str_replace('&', '&amp;', $user->page['page']);
  2073. $u_action = reapply_sid($use_page);
  2074. $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
  2075. $template->assign_vars(array(
  2076. 'MESSAGE_TITLE' => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
  2077. 'MESSAGE_TEXT' => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
  2078. 'YES_VALUE' => $user->lang['YES'],
  2079. 'S_CONFIRM_ACTION' => $u_action,
  2080. 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields)
  2081. );
  2082. $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
  2083. WHERE user_id = " . $user->data['user_id'];
  2084. $db->sql_query($sql);
  2085. if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
  2086. {
  2087. adm_page_footer();
  2088. }
  2089. else
  2090. {
  2091. page_footer();
  2092. }
  2093. }
  2094. /**
  2095. * Generate login box or verify password
  2096. */
  2097. function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
  2098. {
  2099. global $db, $user, $template, $a