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

/forum/includes/functions.php

https://github.com/GreyTeardrop/socionicasys-forum
PHP | 4877 lines | 3407 code | 668 blank | 802 comment | 714 complexity | 77a1a3a415cf8219e9f455cc2f33e3b1 MD5 | raw file
Possible License(s): AGPL-1.0, LGPL-3.0, MPL-2.0-no-copyleft-exception

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

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

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