PageRenderTime 80ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 2ms

/forum/includes/functions.php

https://bitbucket.org/itoxable/chiron-gaming
PHP | 9366 lines | 3620 code | 4516 blank | 1230 comment | 698 complexity | 633df8271eca39e3557c27dc48085068 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. session_start();
  3. /**
  4. *
  5. * @package phpBB3
  6. * @version $Id$
  7. * @copyright (c) 2005 phpBB Group
  8. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  9. *
  10. */
  11. /**
  12. * @ignore
  13. */
  14. if (!defined('IN_PHPBB'))
  15. {
  16. //exit;
  17. }
  18. // Common global functions
  19. /**
  20. * set_var
  21. *
  22. * Set variable, used by {@link request_var the request_var function}
  23. *
  24. * @access private
  25. */
  26. function set_var(&$result, $var, $type, $multibyte = false)
  27. {
  28. settype($var, $type);
  29. $result = $var;
  30. if ($type == 'string')
  31. {
  32. $result = trim(htmlspecialchars(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result), ENT_COMPAT, 'UTF-8'));
  33. if (!empty($result))
  34. {
  35. // Make sure multibyte characters are wellformed
  36. if ($multibyte)
  37. {
  38. if (!preg_match('/^./u', $result))
  39. {
  40. $result = '';
  41. }
  42. }
  43. else
  44. {
  45. // no multibyte, allow only ASCII (0-127)
  46. $result = preg_replace('/[\x80-\xFF]/', '?', $result);
  47. }
  48. }
  49. $result = (STRIP) ? stripslashes($result) : $result;
  50. }
  51. }
  52. /**
  53. * request_var
  54. *
  55. * Used to get passed variable
  56. */
  57. function request_var($var_name, $default, $multibyte = false, $cookie = false)
  58. {
  59. if (!$cookie && isset($_COOKIE[$var_name]))
  60. {
  61. if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
  62. {
  63. return (is_array($default)) ? array() : $default;
  64. }
  65. $_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
  66. }
  67. $super_global = ($cookie) ? '_COOKIE' : '_REQUEST';
  68. if (!isset($GLOBALS[$super_global][$var_name]) || is_array($GLOBALS[$super_global][$var_name]) != is_array($default))
  69. {
  70. return (is_array($default)) ? array() : $default;
  71. }
  72. $var = $GLOBALS[$super_global][$var_name];
  73. if (!is_array($default))
  74. {
  75. $type = gettype($default);
  76. }
  77. else
  78. {
  79. list($key_type, $type) = each($default);
  80. $type = gettype($type);
  81. $key_type = gettype($key_type);
  82. if ($type == 'array')
  83. {
  84. reset($default);
  85. $default = current($default);
  86. list($sub_key_type, $sub_type) = each($default);
  87. $sub_type = gettype($sub_type);
  88. $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
  89. $sub_key_type = gettype($sub_key_type);
  90. }
  91. }
  92. if (is_array($var))
  93. {
  94. $_var = $var;
  95. $var = array();
  96. foreach ($_var as $k => $v)
  97. {
  98. set_var($k, $k, $key_type);
  99. if ($type == 'array' && is_array($v))
  100. {
  101. foreach ($v as $_k => $_v)
  102. {
  103. if (is_array($_v))
  104. {
  105. $_v = null;
  106. }
  107. set_var($_k, $_k, $sub_key_type, $multibyte);
  108. set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
  109. }
  110. }
  111. else
  112. {
  113. if ($type == 'array' || is_array($v))
  114. {
  115. $v = null;
  116. }
  117. set_var($var[$k], $v, $type, $multibyte);
  118. }
  119. }
  120. }
  121. else
  122. {
  123. set_var($var, $var, $type, $multibyte);
  124. }
  125. return $var;
  126. }
  127. /**
  128. * Set config value. Creates missing config entry.
  129. */
  130. function set_config($config_name, $config_value, $is_dynamic = false)
  131. {
  132. global $db, $cache, $config;
  133. $sql = 'UPDATE ' . CONFIG_TABLE . "
  134. SET config_value = '" . $db->sql_escape($config_value) . "'
  135. WHERE config_name = '" . $db->sql_escape($config_name) . "'";
  136. $db->sql_query($sql);
  137. if (!$db->sql_affectedrows() && !isset($config[$config_name]))
  138. {
  139. $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
  140. 'config_name' => $config_name,
  141. 'config_value' => $config_value,
  142. 'is_dynamic' => ($is_dynamic) ? 1 : 0));
  143. $db->sql_query($sql);
  144. }
  145. $config[$config_name] = $config_value;
  146. if (!$is_dynamic)
  147. {
  148. $cache->destroy('config');
  149. }
  150. }
  151. /**
  152. * Set dynamic config value with arithmetic operation.
  153. */
  154. function set_config_count($config_name, $increment, $is_dynamic = false)
  155. {
  156. global $db, $cache;
  157. switch ($db->sql_layer)
  158. {
  159. case 'firebird':
  160. // Precision must be from 1 to 18
  161. $sql_update = 'CAST(CAST(config_value as DECIMAL(18, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
  162. break;
  163. case 'postgres':
  164. // Need to cast to text first for PostgreSQL 7.x
  165. $sql_update = 'CAST(CAST(config_value::text as DECIMAL(255, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
  166. break;
  167. // MySQL, SQlite, mssql, mssql_odbc, oracle
  168. default:
  169. $sql_update = 'config_value + ' . (int) $increment;
  170. break;
  171. }
  172. $db->sql_query('UPDATE ' . CONFIG_TABLE . ' SET config_value = ' . $sql_update . " WHERE config_name = '" . $db->sql_escape($config_name) . "'");
  173. if (!$is_dynamic)
  174. {
  175. $cache->destroy('config');
  176. }
  177. }
  178. /**
  179. * Generates an alphanumeric random string of given length
  180. *
  181. * @return string
  182. */
  183. function gen_rand_string($num_chars = 8)
  184. {
  185. // [a, z] + [0, 9] = 36
  186. return substr(strtoupper(base_convert(unique_id(), 16, 36)), 0, $num_chars);
  187. }
  188. /**
  189. * Generates a user-friendly alphanumeric random string of given length
  190. * We remove 0 and O so users cannot confuse those in passwords etc.
  191. *
  192. * @return string
  193. */
  194. function gen_rand_string_friendly($num_chars = 8)
  195. {
  196. $rand_str = unique_id();
  197. // Remove Z and Y from the base_convert(), replace 0 with Z and O with Y
  198. // [a, z] + [0, 9] - {z, y} = [a, z] + [0, 9] - {0, o} = 34
  199. $rand_str = str_replace(array('0', 'O'), array('Z', 'Y'), strtoupper(base_convert($rand_str, 16, 34)));
  200. return substr($rand_str, 0, $num_chars);
  201. }
  202. /**
  203. * Return unique id
  204. * @param string $extra additional entropy
  205. */
  206. function unique_id($extra = 'c')
  207. {
  208. static $dss_seeded = false;
  209. global $config;
  210. $val = $config['rand_seed'] . microtime();
  211. $val = md5($val);
  212. $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
  213. if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
  214. {
  215. set_config('rand_seed_last_update', time(), true);
  216. set_config('rand_seed', $config['rand_seed'], true);
  217. $dss_seeded = true;
  218. }
  219. return substr($val, 4, 16);
  220. }
  221. /**
  222. * Wrapper for mt_rand() which allows swapping $min and $max parameters.
  223. *
  224. * PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
  225. * (since PHP 5.3.4, see http://bugs.php.net/46587)
  226. *
  227. * @param int $min Lowest value to be returned
  228. * @param int $max Highest value to be returned
  229. *
  230. * @return int Random integer between $min and $max (or $max and $min)
  231. */
  232. function phpbb_mt_rand($min, $max)
  233. {
  234. return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
  235. }
  236. /**
  237. * Return formatted string for filesizes
  238. *
  239. * @param int $value filesize in bytes
  240. * @param bool $string_only true if language string should be returned
  241. * @param array $allowed_units only allow these units (data array indexes)
  242. *
  243. * @return mixed data array if $string_only is false
  244. * @author bantu
  245. */
  246. function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
  247. {
  248. global $user;
  249. $available_units = array(
  250. 'gb' => array(
  251. 'min' => 1073741824, // pow(2, 30)
  252. 'index' => 3,
  253. 'si_unit' => 'GB',
  254. 'iec_unit' => 'GIB',
  255. ),
  256. 'mb' => array(
  257. 'min' => 1048576, // pow(2, 20)
  258. 'index' => 2,
  259. 'si_unit' => 'MB',
  260. 'iec_unit' => 'MIB',
  261. ),
  262. 'kb' => array(
  263. 'min' => 1024, // pow(2, 10)
  264. 'index' => 1,
  265. 'si_unit' => 'KB',
  266. 'iec_unit' => 'KIB',
  267. ),
  268. 'b' => array(
  269. 'min' => 0,
  270. 'index' => 0,
  271. 'si_unit' => 'BYTES', // Language index
  272. 'iec_unit' => 'BYTES', // Language index
  273. ),
  274. );
  275. foreach ($available_units as $si_identifier => $unit_info)
  276. {
  277. if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
  278. {
  279. continue;
  280. }
  281. if ($value >= $unit_info['min'])
  282. {
  283. $unit_info['si_identifier'] = $si_identifier;
  284. break;
  285. }
  286. }
  287. unset($available_units);
  288. for ($i = 0; $i < $unit_info['index']; $i++)
  289. {
  290. $value /= 1024;
  291. }
  292. $value = round($value, 2);
  293. // Lookup units in language dictionary
  294. $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
  295. $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
  296. // Default to IEC
  297. $unit_info['unit'] = $unit_info['iec_unit'];
  298. if (!$string_only)
  299. {
  300. $unit_info['value'] = $value;
  301. return $unit_info;
  302. }
  303. return $value . ' ' . $unit_info['unit'];
  304. }
  305. /**
  306. * Determine whether we are approaching the maximum execution time. Should be called once
  307. * at the beginning of the script in which it's used.
  308. * @return bool Either true if the maximum execution time is nearly reached, or false
  309. * if some time is still left.
  310. */
  311. function still_on_time($extra_time = 15)
  312. {
  313. static $max_execution_time, $start_time;
  314. $time = explode(' ', microtime());
  315. $current_time = $time[0] + $time[1];
  316. if (empty($max_execution_time))
  317. {
  318. $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
  319. // If zero, then set to something higher to not let the user catch the ten seconds barrier.
  320. if ($max_execution_time === 0)
  321. {
  322. $max_execution_time = 50 + $extra_time;
  323. }
  324. $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
  325. // For debugging purposes
  326. // $max_execution_time = 10;
  327. global $starttime;
  328. $start_time = (empty($starttime)) ? $current_time : $starttime;
  329. }
  330. return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
  331. }
  332. /**
  333. *
  334. * @version Version 0.1 / slightly modified for phpBB 3.0.x (using $H$ as hash type identifier)
  335. *
  336. * Portable PHP password hashing framework.
  337. *
  338. * Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
  339. * the public domain.
  340. *
  341. * There's absolutely no warranty.
  342. *
  343. * The homepage URL for this framework is:
  344. *
  345. * http://www.openwall.com/phpass/
  346. *
  347. * Please be sure to update the Version line if you edit this file in any way.
  348. * It is suggested that you leave the main version number intact, but indicate
  349. * your project name (after the slash) and add your own revision information.
  350. *
  351. * Please do not change the "private" password hashing method implemented in
  352. * here, thereby making your hashes incompatible. However, if you must, please
  353. * change the hash type identifier (the "$P$") to something different.
  354. *
  355. * Obviously, since this code is in the public domain, the above are not
  356. * requirements (there can be none), but merely suggestions.
  357. *
  358. *
  359. * Hash the password
  360. */
  361. function phpbb_hash($password)
  362. {
  363. $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  364. $random_state = unique_id();
  365. $random = '';
  366. $count = 6;
  367. if (($fh = @fopen('/dev/urandom', 'rb')))
  368. {
  369. $random = fread($fh, $count);
  370. fclose($fh);
  371. }
  372. if (strlen($random) < $count)
  373. {
  374. $random = '';
  375. for ($i = 0; $i < $count; $i += 16)
  376. {
  377. $random_state = md5(unique_id() . $random_state);
  378. $random .= pack('H*', md5($random_state));
  379. }
  380. $random = substr($random, 0, $count);
  381. }
  382. $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);
  383. if (strlen($hash) == 34)
  384. {
  385. return $hash;
  386. }
  387. return md5($password);
  388. }
  389. /**
  390. * Check for correct password
  391. *
  392. * @param string $password The password in plain text
  393. * @param string $hash The stored password hash
  394. *
  395. * @return bool Returns true if the password is correct, false if not.
  396. */
  397. function phpbb_check_hash($password, $hash)
  398. {
  399. $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  400. if (strlen($hash) == 34)
  401. {
  402. return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
  403. }
  404. return (md5($password) === $hash) ? true : false;
  405. }
  406. /**
  407. * Generate salt for hash generation
  408. */
  409. function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
  410. {
  411. if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
  412. {
  413. $iteration_count_log2 = 8;
  414. }
  415. $output = '$H$';
  416. $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
  417. $output .= _hash_encode64($input, 6, $itoa64);
  418. return $output;
  419. }
  420. /**
  421. * Encode hash
  422. */
  423. function _hash_encode64($input, $count, &$itoa64)
  424. {
  425. $output = '';
  426. $i = 0;
  427. do
  428. {
  429. $value = ord($input[$i++]);
  430. $output .= $itoa64[$value & 0x3f];
  431. if ($i < $count)
  432. {
  433. $value |= ord($input[$i]) << 8;
  434. }
  435. $output .= $itoa64[($value >> 6) & 0x3f];
  436. if ($i++ >= $count)
  437. {
  438. break;
  439. }
  440. if ($i < $count)
  441. {
  442. $value |= ord($input[$i]) << 16;
  443. }
  444. $output .= $itoa64[($value >> 12) & 0x3f];
  445. if ($i++ >= $count)
  446. {
  447. break;
  448. }
  449. $output .= $itoa64[($value >> 18) & 0x3f];
  450. }
  451. while ($i < $count);
  452. return $output;
  453. }
  454. /**
  455. * The crypt function/replacement
  456. */
  457. function _hash_crypt_private($password, $setting, &$itoa64)
  458. {
  459. $output = '*';
  460. // Check for correct hash
  461. if (substr($setting, 0, 3) != '$H$' && substr($setting, 0, 3) != '$P$')
  462. {
  463. return $output;
  464. }
  465. $count_log2 = strpos($itoa64, $setting[3]);
  466. if ($count_log2 < 7 || $count_log2 > 30)
  467. {
  468. return $output;
  469. }
  470. $count = 1 << $count_log2;
  471. $salt = substr($setting, 4, 8);
  472. if (strlen($salt) != 8)
  473. {
  474. return $output;
  475. }
  476. /**
  477. * We're kind of forced to use MD5 here since it's the only
  478. * cryptographic primitive available in all versions of PHP
  479. * currently in use. To implement our own low-level crypto
  480. * in PHP would result in much worse performance and
  481. * consequently in lower iteration counts and hashes that are
  482. * quicker to crack (by non-PHP code).
  483. */
  484. if (PHP_VERSION >= 5)
  485. {
  486. $hash = md5($salt . $password, true);
  487. do
  488. {
  489. $hash = md5($hash . $password, true);
  490. }
  491. while (--$count);
  492. }
  493. else
  494. {
  495. $hash = pack('H*', md5($salt . $password));
  496. do
  497. {
  498. $hash = pack('H*', md5($hash . $password));
  499. }
  500. while (--$count);
  501. }
  502. $output = substr($setting, 0, 12);
  503. $output .= _hash_encode64($hash, 16, $itoa64);
  504. return $output;
  505. }
  506. /**
  507. * Hashes an email address to a big integer
  508. *
  509. * @param string $email Email address
  510. *
  511. * @return string Unsigned Big Integer
  512. */
  513. function phpbb_email_hash($email)
  514. {
  515. return sprintf('%u', crc32(strtolower($email))) . strlen($email);
  516. }
  517. /**
  518. * Global function for chmodding directories and files for internal use
  519. *
  520. * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions.
  521. * The function determines owner and group from common.php file and sets the same to the provided file.
  522. * The function uses bit fields to build the permissions.
  523. * The function sets the appropiate execute bit on directories.
  524. *
  525. * Supported constants representing bit fields are:
  526. *
  527. * CHMOD_ALL - all permissions (7)
  528. * CHMOD_READ - read permission (4)
  529. * CHMOD_WRITE - write permission (2)
  530. * CHMOD_EXECUTE - execute permission (1)
  531. *
  532. * 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.
  533. *
  534. * @param string $filename The file/directory to be chmodded
  535. * @param int $perms Permissions to set
  536. *
  537. * @return bool true on success, otherwise false
  538. * @author faw, phpBB Group
  539. */
  540. function phpbb_chmod($filename, $perms = CHMOD_READ)
  541. {
  542. static $_chmod_info;
  543. // Return if the file no longer exists.
  544. if (!file_exists($filename))
  545. {
  546. return false;
  547. }
  548. // Determine some common vars
  549. if (empty($_chmod_info))
  550. {
  551. if (!function_exists('fileowner') || !function_exists('filegroup'))
  552. {
  553. // No need to further determine owner/group - it is unknown
  554. $_chmod_info['process'] = false;
  555. }
  556. else
  557. {
  558. global $phpbb_root_path, $phpEx;
  559. // Determine owner/group of common.php file and the filename we want to change here
  560. $common_php_owner = @fileowner($phpbb_root_path . 'common.' . $phpEx);
  561. $common_php_group = @filegroup($phpbb_root_path . 'common.' . $phpEx);
  562. // And the owner and the groups PHP is running under.
  563. $php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false;
  564. $php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false;
  565. // If we are unable to get owner/group, then do not try to set them by guessing
  566. if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group)
  567. {
  568. $_chmod_info['process'] = false;
  569. }
  570. else
  571. {
  572. $_chmod_info = array(
  573. 'process' => true,
  574. 'common_owner' => $common_php_owner,
  575. 'common_group' => $common_php_group,
  576. 'php_uid' => $php_uid,
  577. 'php_gids' => $php_gids,
  578. );
  579. }
  580. }
  581. }
  582. if ($_chmod_info['process'])
  583. {
  584. $file_uid = @fileowner($filename);
  585. $file_gid = @filegroup($filename);
  586. // Change owner
  587. if (@chown($filename, $_chmod_info['common_owner']))
  588. {
  589. clearstatcache();
  590. $file_uid = @fileowner($filename);
  591. }
  592. // Change group
  593. if (@chgrp($filename, $_chmod_info['common_group']))
  594. {
  595. clearstatcache();
  596. $file_gid = @filegroup($filename);
  597. }
  598. // If the file_uid/gid now match the one from common.php we can process further, else we are not able to change something
  599. if ($file_uid != $_chmod_info['common_owner'] || $file_gid != $_chmod_info['common_group'])
  600. {
  601. $_chmod_info['process'] = false;
  602. }
  603. }
  604. // Still able to process?
  605. if ($_chmod_info['process'])
  606. {
  607. if ($file_uid == $_chmod_info['php_uid'])
  608. {
  609. $php = 'owner';
  610. }
  611. else if (in_array($file_gid, $_chmod_info['php_gids']))
  612. {
  613. $php = 'group';
  614. }
  615. else
  616. {
  617. // Since we are setting the everyone bit anyway, no need to do expensive operations
  618. $_chmod_info['process'] = false;
  619. }
  620. }
  621. // We are not able to determine or change something
  622. if (!$_chmod_info['process'])
  623. {
  624. $php = 'other';
  625. }
  626. // Owner always has read/write permission
  627. $owner = CHMOD_READ | CHMOD_WRITE;
  628. if (is_dir($filename))
  629. {
  630. $owner |= CHMOD_EXECUTE;
  631. // Only add execute bit to the permission if the dir needs to be readable
  632. if ($perms & CHMOD_READ)
  633. {
  634. $perms |= CHMOD_EXECUTE;
  635. }
  636. }
  637. switch ($php)
  638. {
  639. case 'owner':
  640. $result = @chmod($filename, ($owner << 6) + (0 << 3) + (0 << 0));
  641. clearstatcache();
  642. if (is_readable($filename) && phpbb_is_writable($filename))
  643. {
  644. break;
  645. }
  646. case 'group':
  647. $result = @chmod($filename, ($owner << 6) + ($perms << 3) + (0 << 0));
  648. clearstatcache();
  649. if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
  650. {
  651. break;
  652. }
  653. case 'other':
  654. $result = @chmod($filename, ($owner << 6) + ($perms << 3) + ($perms << 0));
  655. clearstatcache();
  656. if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
  657. {
  658. break;
  659. }
  660. default:
  661. return false;
  662. break;
  663. }
  664. return $result;
  665. }
  666. /**
  667. * Test if a file/directory is writable
  668. *
  669. * This function calls the native is_writable() when not running under
  670. * Windows and it is not disabled.
  671. *
  672. * @param string $file Path to perform write test on
  673. * @return bool True when the path is writable, otherwise false.
  674. */
  675. function phpbb_is_writable($file)
  676. {
  677. if (strtolower(substr(PHP_OS, 0, 3)) === 'win' || !function_exists('is_writable'))
  678. {
  679. if (file_exists($file))
  680. {
  681. // Canonicalise path to absolute path
  682. $file = phpbb_realpath($file);
  683. if (is_dir($file))
  684. {
  685. // Test directory by creating a file inside the directory
  686. $result = @tempnam($file, 'i_w');
  687. if (is_string($result) && file_exists($result))
  688. {
  689. unlink($result);
  690. // Ensure the file is actually in the directory (returned realpathed)
  691. return (strpos($result, $file) === 0) ? true : false;
  692. }
  693. }
  694. else
  695. {
  696. $handle = @fopen($file, 'r+');
  697. if (is_resource($handle))
  698. {
  699. fclose($handle);
  700. return true;
  701. }
  702. }
  703. }
  704. else
  705. {
  706. // file does not exist test if we can write to the directory
  707. $dir = dirname($file);
  708. if (file_exists($dir) && is_dir($dir) && phpbb_is_writable($dir))
  709. {
  710. return true;
  711. }
  712. }
  713. return false;
  714. }
  715. else
  716. {
  717. return is_writable($file);
  718. }
  719. }
  720. // Compatibility functions
  721. if (!function_exists('array_combine'))
  722. {
  723. /**
  724. * A wrapper for the PHP5 function array_combine()
  725. * @param array $keys contains keys for the resulting array
  726. * @param array $values contains values for the resulting array
  727. *
  728. * @return Returns an array by using the values from the keys array as keys and the
  729. * values from the values array as the corresponding values. Returns false if the
  730. * number of elements for each array isn't equal or if the arrays are empty.
  731. */
  732. function array_combine($keys, $values)
  733. {
  734. $keys = array_values($keys);
  735. $values = array_values($values);
  736. $n = sizeof($keys);
  737. $m = sizeof($values);
  738. if (!$n || !$m || ($n != $m))
  739. {
  740. return false;
  741. }
  742. $combined = array();
  743. for ($i = 0; $i < $n; $i++)
  744. {
  745. $combined[$keys[$i]] = $values[$i];
  746. }
  747. return $combined;
  748. }
  749. }
  750. if (!function_exists('str_split'))
  751. {
  752. /**
  753. * A wrapper for the PHP5 function str_split()
  754. * @param array $string contains the string to be converted
  755. * @param array $split_length contains the length of each chunk
  756. *
  757. * @return Converts a string to an array. If the optional split_length parameter is specified,
  758. * the returned array will be broken down into chunks with each being split_length in length,
  759. * otherwise each chunk will be one character in length. FALSE is returned if split_length is
  760. * less than 1. If the split_length length exceeds the length of string, the entire string is
  761. * returned as the first (and only) array element.
  762. */
  763. function str_split($string, $split_length = 1)
  764. {
  765. if ($split_length < 1)
  766. {
  767. return false;
  768. }
  769. else if ($split_length >= strlen($string))
  770. {
  771. return array($string);
  772. }
  773. else
  774. {
  775. preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches);
  776. return $matches[0];
  777. }
  778. }
  779. }
  780. if (!function_exists('stripos'))
  781. {
  782. /**
  783. * A wrapper for the PHP5 function stripos
  784. * Find position of first occurrence of a case-insensitive string
  785. *
  786. * @param string $haystack is the string to search in
  787. * @param string $needle is the string to search for
  788. *
  789. * @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive.
  790. * Note that the needle may be a string of one or more characters.
  791. * If needle is not found, stripos() will return boolean FALSE.
  792. */
  793. function stripos($haystack, $needle)
  794. {
  795. if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
  796. {
  797. return strpos($haystack, $m[0]);
  798. }
  799. return false;
  800. }
  801. }
  802. /**
  803. * Checks if a path ($path) is absolute or relative
  804. *
  805. * @param string $path Path to check absoluteness of
  806. * @return boolean
  807. */
  808. function is_absolute($path)
  809. {
  810. return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:[/\\\]#i', $path))) ? true : false;
  811. }
  812. /**
  813. * @author Chris Smith <chris@project-minerva.org>
  814. * @copyright 2006 Project Minerva Team
  815. * @param string $path The path which we should attempt to resolve.
  816. * @return mixed
  817. */
  818. function phpbb_own_realpath($path)
  819. {
  820. // Now to perform funky shizzle
  821. // Switch to use UNIX slashes
  822. $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
  823. $path_prefix = '';
  824. // Determine what sort of path we have
  825. if (is_absolute($path))
  826. {
  827. $absolute = true;
  828. if ($path[0] == '/')
  829. {
  830. // Absolute path, *NIX style
  831. $path_prefix = '';
  832. }
  833. else
  834. {
  835. // Absolute path, Windows style
  836. // Remove the drive letter and colon
  837. $path_prefix = $path[0] . ':';
  838. $path = substr($path, 2);
  839. }
  840. }
  841. else
  842. {
  843. // Relative Path
  844. // Prepend the current working directory
  845. if (function_exists('getcwd'))
  846. {
  847. // This is the best method, hopefully it is enabled!
  848. $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
  849. $absolute = true;
  850. if (preg_match('#^[a-z]:#i', $path))
  851. {
  852. $path_prefix = $path[0] . ':';
  853. $path = substr($path, 2);
  854. }
  855. else
  856. {
  857. $path_prefix = '';
  858. }
  859. }
  860. else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
  861. {
  862. // Warning: If chdir() has been used this will lie!
  863. // Warning: This has some problems sometime (CLI can create them easily)
  864. $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
  865. $absolute = true;
  866. $path_prefix = '';
  867. }
  868. else
  869. {
  870. // We have no way of getting the absolute path, just run on using relative ones.
  871. $absolute = false;
  872. $path_prefix = '.';
  873. }
  874. }
  875. // Remove any repeated slashes
  876. $path = preg_replace('#/{2,}#', '/', $path);
  877. // Remove the slashes from the start and end of the path
  878. $path = trim($path, '/');
  879. // Break the string into little bits for us to nibble on
  880. $bits = explode('/', $path);
  881. // Remove any . in the path, renumber array for the loop below
  882. $bits = array_values(array_diff($bits, array('.')));
  883. // Lets get looping, run over and resolve any .. (up directory)
  884. for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
  885. {
  886. // @todo Optimise
  887. if ($bits[$i] == '..' )
  888. {
  889. if (isset($bits[$i - 1]))
  890. {
  891. if ($bits[$i - 1] != '..')
  892. {
  893. // We found a .. and we are able to traverse upwards, lets do it!
  894. unset($bits[$i]);
  895. unset($bits[$i - 1]);
  896. $i -= 2;
  897. $max -= 2;
  898. $bits = array_values($bits);
  899. }
  900. }
  901. else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
  902. {
  903. // We have an absolute path trying to descend above the root of the filesystem
  904. // ... Error!
  905. return false;
  906. }
  907. }
  908. }
  909. // Prepend the path prefix
  910. array_unshift($bits, $path_prefix);
  911. $resolved = '';
  912. $max = sizeof($bits) - 1;
  913. // Check if we are able to resolve symlinks, Windows cannot.
  914. $symlink_resolve = (function_exists('readlink')) ? true : false;
  915. foreach ($bits as $i => $bit)
  916. {
  917. if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
  918. {
  919. // Path Exists
  920. if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
  921. {
  922. // Resolved a symlink.
  923. $resolved = $link . (($i == $max) ? '' : '/');
  924. continue;
  925. }
  926. }
  927. else
  928. {
  929. // Something doesn't exist here!
  930. // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
  931. // return false;
  932. }
  933. $resolved .= $bit . (($i == $max) ? '' : '/');
  934. }
  935. // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
  936. // because we must be inside that basedir, the question is where...
  937. // @internal The slash in is_dir() gets around an open_basedir restriction
  938. if (!@file_exists($resolved) || (!@is_dir($resolved . '/') && !is_file($resolved)))
  939. {
  940. return false;
  941. }
  942. // Put the slashes back to the native operating systems slashes
  943. $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
  944. // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
  945. if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
  946. {
  947. return substr($resolved, 0, -1);
  948. }
  949. return $resolved; // We got here, in the end!
  950. }
  951. if (!function_exists('realpath'))
  952. {
  953. /**
  954. * A wrapper for realpath
  955. * @ignore
  956. */
  957. function phpbb_realpath($path)
  958. {
  959. return phpbb_own_realpath($path);
  960. }
  961. }
  962. else
  963. {
  964. /**
  965. * A wrapper for realpath
  966. */
  967. function phpbb_realpath($path)
  968. {
  969. $realpath = realpath($path);
  970. // Strangely there are provider not disabling realpath but returning strange values. :o
  971. // We at least try to cope with them.
  972. if ($realpath === $path || $realpath === false)
  973. {
  974. return phpbb_own_realpath($path);
  975. }
  976. // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
  977. if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
  978. {
  979. $realpath = substr($realpath, 0, -1);
  980. }
  981. return $realpath;
  982. }
  983. }
  984. if (!function_exists('htmlspecialchars_decode'))
  985. {
  986. /**
  987. * A wrapper for htmlspecialchars_decode
  988. * @ignore
  989. */
  990. function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
  991. {
  992. return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
  993. }
  994. }
  995. // functions used for building option fields
  996. /**
  997. * Pick a language, any language ...
  998. */
  999. function language_select($default = '')
  1000. {
  1001. global $db;
  1002. $sql = 'SELECT lang_iso, lang_local_name
  1003. FROM ' . LANG_TABLE . '
  1004. ORDER BY lang_english_name';
  1005. $result = $db->sql_query($sql);
  1006. $lang_options = '';
  1007. while ($row = $db->sql_fetchrow($result))
  1008. {
  1009. $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
  1010. $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
  1011. }
  1012. $db->sql_freeresult($result);
  1013. return $lang_options;
  1014. }
  1015. /**
  1016. * Pick a template/theme combo,
  1017. */
  1018. function style_select($default = '', $all = false)
  1019. {
  1020. global $db;
  1021. $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
  1022. $sql = 'SELECT style_id, style_name
  1023. FROM ' . STYLES_TABLE . "
  1024. $sql_where
  1025. ORDER BY style_name";
  1026. $result = $db->sql_query($sql);
  1027. $style_options = '';
  1028. while ($row = $db->sql_fetchrow($result))
  1029. {
  1030. $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
  1031. $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
  1032. }
  1033. $db->sql_freeresult($result);
  1034. return $style_options;
  1035. }
  1036. /**
  1037. * Pick a timezone
  1038. */
  1039. function tz_select($default = '', $truncate = false)
  1040. {
  1041. global $user;
  1042. $tz_select = '';
  1043. foreach ($user->lang['tz_zones'] as $offset => $zone)
  1044. {
  1045. if ($truncate)
  1046. {
  1047. $zone_trunc = truncate_string($zone, 50, 255, false, '...');
  1048. }
  1049. else
  1050. {
  1051. $zone_trunc = $zone;
  1052. }
  1053. if (is_numeric($offset))
  1054. {
  1055. $selected = ($offset == $default) ? ' selected="selected"' : '';
  1056. $tz_select .= '<option title="' . $zone . '" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
  1057. }
  1058. }
  1059. return $tz_select;
  1060. }
  1061. // Functions handling topic/post tracking/marking
  1062. /**
  1063. * Marks a topic/forum as read
  1064. * Marks a topic as posted to
  1065. *
  1066. * @param int $user_id can only be used with $mode == 'post'
  1067. */
  1068. function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
  1069. {
  1070. global $db, $user, $config;
  1071. if ($mode == 'all')
  1072. {
  1073. if ($forum_id === false || !sizeof($forum_id))
  1074. {
  1075. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1076. {
  1077. // Mark all forums read (index page)
  1078. $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
  1079. $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
  1080. $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
  1081. }
  1082. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1083. {
  1084. $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1085. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  1086. unset($tracking_topics['tf']);
  1087. unset($tracking_topics['t']);
  1088. unset($tracking_topics['f']);
  1089. $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
  1090. $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
  1091. $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics);
  1092. unset($tracking_topics);
  1093. if ($user->data['is_registered'])
  1094. {
  1095. $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
  1096. }
  1097. }
  1098. }
  1099. return;
  1100. }
  1101. else if ($mode == 'topics')
  1102. {
  1103. // Mark all topics in forums read
  1104. if (!is_array($forum_id))
  1105. {
  1106. $forum_id = array($forum_id);
  1107. }
  1108. // Add 0 to forums array to mark global announcements correctly
  1109. // $forum_id[] = 0;
  1110. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1111. {
  1112. $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
  1113. WHERE user_id = {$user->data['user_id']}
  1114. AND " . $db->sql_in_set('forum_id', $forum_id);
  1115. $db->sql_query($sql);
  1116. $sql = 'SELECT forum_id
  1117. FROM ' . FORUMS_TRACK_TABLE . "
  1118. WHERE user_id = {$user->data['user_id']}
  1119. AND " . $db->sql_in_set('forum_id', $forum_id);
  1120. $result = $db->sql_query($sql);
  1121. $sql_update = array();
  1122. while ($row = $db->sql_fetchrow($result))
  1123. {
  1124. $sql_update[] = (int) $row['forum_id'];
  1125. }
  1126. $db->sql_freeresult($result);
  1127. if (sizeof($sql_update))
  1128. {
  1129. $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
  1130. SET mark_time = ' . time() . "
  1131. WHERE user_id = {$user->data['user_id']}
  1132. AND " . $db->sql_in_set('forum_id', $sql_update);
  1133. $db->sql_query($sql);
  1134. }
  1135. if ($sql_insert = array_diff($forum_id, $sql_update))
  1136. {
  1137. $sql_ary = array();
  1138. foreach ($sql_insert as $f_id)
  1139. {
  1140. $sql_ary[] = array(
  1141. 'user_id' => (int) $user->data['user_id'],
  1142. 'forum_id' => (int) $f_id,
  1143. 'mark_time' => time()
  1144. );
  1145. }
  1146. $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
  1147. }
  1148. }
  1149. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1150. {
  1151. $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1152. $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
  1153. foreach ($forum_id as $f_id)
  1154. {
  1155. $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
  1156. if (isset($tracking['tf'][$f_id]))
  1157. {
  1158. unset($tracking['tf'][$f_id]);
  1159. }
  1160. foreach ($topic_ids36 as $topic_id36)
  1161. {
  1162. unset($tracking['t'][$topic_id36]);
  1163. }
  1164. if (isset($tracking['f'][$f_id]))
  1165. {
  1166. unset($tracking['f'][$f_id]);
  1167. }
  1168. $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
  1169. }
  1170. if (isset($tracking['tf']) && empty($tracking['tf']))
  1171. {
  1172. unset($tracking['tf']);
  1173. }
  1174. $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
  1175. $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
  1176. unset($tracking);
  1177. }
  1178. return;
  1179. }
  1180. else if ($mode == 'topic')
  1181. {
  1182. if ($topic_id === false || $forum_id === false)
  1183. {
  1184. return;
  1185. }
  1186. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1187. {
  1188. $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
  1189. SET mark_time = ' . (($post_time) ? $post_time : time()) . "
  1190. WHERE user_id = {$user->data['user_id']}
  1191. AND topic_id = $topic_id";
  1192. $db->sql_query($sql);
  1193. // insert row
  1194. if (!$db->sql_affectedrows())
  1195. {
  1196. $db->sql_return_on_error(true);
  1197. $sql_ary = array(
  1198. 'user_id' => (int) $user->data['user_id'],
  1199. 'topic_id' => (int) $topic_id,
  1200. 'forum_id' => (int) $forum_id,
  1201. 'mark_time' => ($post_time) ? (int) $post_time : time(),
  1202. );
  1203. $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  1204. $db->sql_return_on_error(false);
  1205. }
  1206. }
  1207. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1208. {
  1209. $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1210. $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
  1211. $topic_id36 = base_convert($topic_id, 10, 36);
  1212. if (!isset($tracking['t'][$topic_id36]))
  1213. {
  1214. $tracking['tf'][$forum_id][$topic_id36] = true;
  1215. }
  1216. $post_time = ($post_time) ? $post_time : time();
  1217. $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
  1218. // If the cookie grows larger than 10000 characters we will remove the smallest value
  1219. // This can result in old topics being unread - but most of the time it should be accurate...
  1220. if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
  1221. {
  1222. //echo 'Cookie grown too large' . print_r($tracking, true);
  1223. // We get the ten most minimum stored time offsets and its associated topic ids
  1224. $time_keys = array();
  1225. for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
  1226. {
  1227. $min_value = min($tracking['t']);
  1228. $m_tkey = array_search($min_value, $tracking['t']);
  1229. unset($tracking['t'][$m_tkey]);
  1230. $time_keys[$m_tkey] = $min_value;
  1231. }
  1232. // Now remove the topic ids from the array...
  1233. foreach ($tracking['tf'] as $f_id => $topic_id_ary)
  1234. {
  1235. foreach ($time_keys as $m_tkey => $min_value)
  1236. {
  1237. if (isset($topic_id_ary[$m_tkey]))
  1238. {
  1239. $tracking['f'][$f_id] = $min_value;
  1240. unset($tracking['tf'][$f_id][$m_tkey]);
  1241. }
  1242. }
  1243. }
  1244. if ($user->data['is_registered'])
  1245. {
  1246. $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
  1247. $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
  1248. }
  1249. else
  1250. {
  1251. $tracking['l'] = max($time_keys);
  1252. }
  1253. }
  1254. $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
  1255. $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
  1256. }
  1257. return;
  1258. }
  1259. else if ($mode == 'post')
  1260. {
  1261. if ($topic_id === false)
  1262. {
  1263. return;
  1264. }
  1265. $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
  1266. if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
  1267. {
  1268. $db->sql_return_on_error(true);
  1269. $sql_ary = array(
  1270. 'user_id' => (int) $use_user_id,
  1271. 'topic_id' => (int) $topic_id,
  1272. 'topic_posted' => 1
  1273. );
  1274. $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  1275. $db->sql_return_on_error(false);
  1276. }
  1277. return;
  1278. }
  1279. }
  1280. /**
  1281. * Get topic tracking info by using already fetched info
  1282. */
  1283. function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
  1284. {
  1285. global $config, $user;
  1286. $last_read = array();
  1287. if (!is_array($topic_ids))
  1288. {
  1289. $topic_ids = array($topic_ids);
  1290. }
  1291. foreach ($topic_ids as $topic_id)
  1292. {
  1293. if (!empty($rowset[$topic_id]['mark_time']))
  1294. {
  1295. $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
  1296. }
  1297. }
  1298. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  1299. if (sizeof($topic_ids))
  1300. {
  1301. $mark_time = array();
  1302. // Get global announcement info
  1303. if ($global_announce_list && sizeof($global_announce_list))
  1304. {
  1305. if (!isset($forum_mark_time[0]))
  1306. {
  1307. global $db;
  1308. $sql = 'SELECT mark_time
  1309. FROM ' . FORUMS_TRACK_TABLE . "
  1310. WHERE user_id = {$user->data['user_id']}
  1311. AND forum_id = 0";
  1312. $result = $db->sql_query($sql);
  1313. $row = $db->sql_fetchrow($result);
  1314. $db->sql_freeresult($result);
  1315. if ($row)
  1316. {
  1317. $mark_time[0] = $row['mark_time'];
  1318. }
  1319. }
  1320. else
  1321. {
  1322. if ($forum_mark_time[0] !== false)
  1323. {
  1324. $mark_time[0] = $forum_mark_time[0];
  1325. }
  1326. }
  1327. }
  1328. if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
  1329. {
  1330. $mark_time[$forum_id] = $forum_mark_time[$forum_id];
  1331. }
  1332. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
  1333. foreach ($topic_ids as $topic_id)
  1334. {
  1335. if ($global_announce_list && isset($global_announce_list[$topic_id]))
  1336. {
  1337. $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
  1338. }
  1339. else
  1340. {
  1341. $last_read[$topic_id] = $user_lastmark;
  1342. }
  1343. }
  1344. }
  1345. return $last_read;
  1346. }
  1347. /**
  1348. * Get topic tracking info from db (for cookie based tracking only this function is used)
  1349. */
  1350. function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
  1351. {
  1352. global $config, $user;
  1353. $last_read = array();
  1354. if (!is_array($topic_ids))
  1355. {
  1356. $topic_ids = array($topic_ids);
  1357. }
  1358. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1359. {
  1360. global $db;
  1361. $sql = 'SELECT topic_id, mark_time
  1362. FROM ' . TOPICS_TRACK_TABLE . "
  1363. WHERE user_id = {$user->data['user_id']}
  1364. AND " . $db->sql_in_set('topic_id', $topic_ids);
  1365. $result = $db->sql_query($sql);
  1366. while ($row = $db->sql_fetchrow($result))
  1367. {
  1368. $last_read[$row['topic_id']] = $row['mark_time'];
  1369. }
  1370. $db->sql_freeresult($result);
  1371. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  1372. if (sizeof($topic_ids))
  1373. {
  1374. $sql = 'SELECT forum_id, mark_time
  1375. FROM ' . FORUMS_TRACK_TABLE . "
  1376. WHERE user_id = {$user->data['user_id']}
  1377. AND forum_id " .
  1378. (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
  1379. $result = $db->sql_query($sql);
  1380. $mark_time = array();
  1381. while ($row = $db->sql_fetchrow($result))
  1382. {
  1383. $mark_time[$row['forum_id']] = $row['mark_time'];
  1384. }
  1385. $db->sql_freeresult($result);
  1386. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
  1387. foreach ($topic_ids as $topic_id)
  1388. {
  1389. if ($global_announce_list && isset($global_announce_list[$topic_id]))
  1390. {
  1391. $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
  1392. }
  1393. else
  1394. {
  1395. $last_read[$topic_id] = $user_lastmark;
  1396. }
  1397. }
  1398. }
  1399. }
  1400. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1401. {
  1402. global $tracking_topics;
  1403. if (!isset($tracking_topics) || !sizeof($tracking_topics))
  1404. {
  1405. $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1406. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  1407. }
  1408. if (!$user->data['is_registered'])
  1409. {
  1410. $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
  1411. }
  1412. else
  1413. {
  1414. $user_lastmark = $user->data['user_lastmark'];
  1415. }
  1416. foreach ($topic_ids as $topic_id)
  1417. {
  1418. $topic_id36 = base_convert($topic_id, 10, 36);
  1419. if (isset($tracking_topics['t'][$topic_id36]))
  1420. {
  1421. $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
  1422. }
  1423. }
  1424. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  1425. if (sizeof($topic_ids))
  1426. {
  1427. $mark_time = array();
  1428. if ($global_announce_list && sizeof($global_announce_list))
  1429. {
  1430. if (isset($tracking_topics['f'][0]))
  1431. {
  1432. $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
  1433. }
  1434. }
  1435. if (isset($tracking_topics['f'][$forum_id]))
  1436. {
  1437. $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
  1438. }
  1439. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
  1440. foreach ($topic_ids as $topic_id)
  1441. {
  1442. if ($global_announce_list && isset($global_announce_list[$topic_id]))
  1443. {
  1444. $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
  1445. }
  1446. else
  1447. {
  1448. $last_read[$topic_id] = $user_lastmark;
  1449. }
  1450. }
  1451. }
  1452. }
  1453. return $last_read;
  1454. }
  1455. /**
  1456. * Get list of unread topics
  1457. *
  1458. * @param int $user_id User ID (or false for current user)
  1459. * @param string $sql_extra Extra WHERE SQL statement
  1460. * @param string $sql_sort ORDER BY SQL sorting statement
  1461. * @param string $sql_limit Limits the size of unread topics list, 0 for unlimited query
  1462. * @param string $sql_limit_offset Sets the offset of the first row to search, 0 to search from the start
  1463. *
  1464. * @return array[int][int] Topic ids as keys, mark_time of topic as value
  1465. */
  1466. function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
  1467. {
  1468. global $config, $db, $user;
  1469. $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
  1470. // Data array we're going to return
  1471. $unread_topics = array();
  1472. if (empty($sql_sort))
  1473. {
  1474. $sql_sort = 'ORDER BY t.topic_last_post_time DESC';
  1475. }
  1476. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1477. {
  1478. // Get list of the unread topics
  1479. $last_mark = (int) $user->data['user_lastmark'];
  1480. $sql_array = array(
  1481. 'SELECT' => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
  1482. 'FROM' => array(TOPICS_TABLE => 't'),
  1483. 'LEFT_JOIN' => array(
  1484. array(
  1485. 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'),
  1486. 'ON' => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
  1487. ),
  1488. array(
  1489. 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'),
  1490. 'ON' => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
  1491. ),
  1492. ),
  1493. 'WHERE' => "
  1494. t.topic_last_post_time > $last_mark AND
  1495. (
  1496. (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
  1497. (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
  1498. (tt.mark_time IS NULL AND ft.mark_time IS NULL)
  1499. )
  1500. $sql_extra
  1501. $sql_sort",
  1502. );
  1503. $sql = $db->sql_build_query('SELECT', $sql_array);
  1504. $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
  1505. while ($row = $db->sql_fetchrow($result))
  1506. {
  1507. $topic_id = (int) $row['topic_id'];
  1508. $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
  1509. }
  1510. $db->sql_freeresult($result);
  1511. }
  1512. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1513. {
  1514. global $tracking_topics;
  1515. if (empty($tracking_topics))
  1516. {
  1517. $tracking_topics = request_var($config['cookie_name'] . '_track', '', false, true);
  1518. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  1519. }
  1520. if (!$user->data['is_registered'])
  1521. {
  1522. $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
  1523. }
  1524. else
  1525. {
  1526. $user_lastmark = (int) $user->data['user_lastmark'];
  1527. }
  1528. $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
  1529. FROM ' . TOPICS_TABLE . ' t
  1530. WHERE t.topic_last_post_time > ' . $user_lastmark . "
  1531. $sql_extra
  1532. $sql_sort";
  1533. $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
  1534. while ($row = $db->sql_fetchrow($result))
  1535. {
  1536. $forum_id = (int) $row['forum_id'];
  1537. $topic_id = (int) $row['topic_id'];
  1538. $topic_id36 = base_convert($topic_id, 10, 36);
  1539. if (isset($tracking_topics['t'][$topic_id36]))
  1540. {
  1541. $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
  1542. if ($row['topic_last_post_time'] > $last_read)
  1543. {
  1544. $unread_topics[$topic_id] = $last_read;
  1545. }
  1546. }
  1547. else if (isset($tracking_topics['f'][$forum_id]))
  1548. {
  1549. $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
  1550. if ($row['topic_last_post_time'] > $mark_time)
  1551. {
  1552. $unread_topics[$topic_id] = $mark_time;
  1553. }
  1554. }
  1555. else
  1556. {
  1557. $unread_topics[$topic_id] = $user_lastmark;
  1558. }
  1559. }
  1560. $db->sql_freeresult($result);
  1561. }
  1562. return $unread_topics;
  1563. }
  1564. /**
  1565. * Check for read forums and update topic tracking info accordingly
  1566. *
  1567. * @param int $forum_id the forum id to check
  1568. * @param int $forum_last_post_time the forums last post time
  1569. * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
  1570. * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
  1571. *
  1572. * @return true if complete forum got marked read, else false.
  1573. */
  1574. function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
  1575. {
  1576. global $db, $tracking_topics, $user, $config;
  1577. // Determine the users last forum mark time if not given.
  1578. if ($mark_time_forum === false)
  1579. {
  1580. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1581. {
  1582. $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
  1583. }
  1584. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1585. {
  1586. $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
  1587. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  1588. if (!$user->data['is_registered'])
  1589. {
  1590. $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
  1591. }
  1592. $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_top

Large files files are truncated, but you can click here to view the full file