PageRenderTime 63ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/include/functions.php

https://github.com/gencer/fluxbb
PHP | 2248 lines | 1403 code | 472 blank | 373 comment | 312 complexity | 1d1a85e4ecd2a83e8792f84ddac567b0 MD5 | raw file
Possible License(s): GPL-2.0

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

  1. <?php
  2. /**
  3. * Copyright (C) 2008-2012 FluxBB
  4. * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
  5. * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
  6. */
  7. //
  8. // Collect some board statistics
  9. //
  10. function fetch_board_stats()
  11. {
  12. global $cache, $db;
  13. return $cache->remember('boardstats', function() use ($db) {
  14. $stats = array();
  15. // Count total registered users
  16. $query = $db->select(array('total_users' => '(COUNT(u.id) - 1) AS total_users'), 'users AS u');
  17. $query->where = 'u.group_id != :group_id';
  18. $params = array(':group_id' => PUN_UNVERIFIED);
  19. $stats = array_merge($stats, current($query->run($params)));
  20. unset ($query, $params);
  21. // Fetch last user
  22. $query = $db->select(array('id' => 'u.id', 'username' => 'u.username'), 'users AS u');
  23. $query->where = 'group_id != :group_id';
  24. $query->order = array('registered' => 'u.registered DESC');
  25. $query->limit = 1;
  26. $params = array(':group_id' => PUN_UNVERIFIED);
  27. $stats['last_user'] = current($query->run($params));
  28. unset ($query, $params);
  29. return $stats;
  30. });
  31. }
  32. //
  33. // Return current timestamp (with microseconds) as a float
  34. //
  35. function get_microtime()
  36. {
  37. list($usec, $sec) = explode(' ', microtime());
  38. return ((float)$usec + (float)$sec);
  39. }
  40. //
  41. // Cookie stuff!
  42. //
  43. function check_cookie(&$pun_user)
  44. {
  45. global $db, $db_type, $pun_config, $flux_config;
  46. $now = time();
  47. // If the cookie is set and it matches the correct pattern, then read the values from it
  48. if (isset($_COOKIE[$flux_config['cookie']['name']]) && preg_match('%^(\d+)\|([0-9a-fA-F]+)\|(\d+)\|([0-9a-fA-F]+)$%', $_COOKIE[$flux_config['cookie']['name']], $matches))
  49. {
  50. $cookie = array(
  51. 'user_id' => intval($matches[1]),
  52. 'password_hash' => $matches[2],
  53. 'expiration_time' => intval($matches[3]),
  54. 'cookie_hash' => $matches[4],
  55. );
  56. }
  57. // If it has a non-guest user, and hasn't expired
  58. if (isset($cookie) && $cookie['user_id'] > 1 && $cookie['expiration_time'] > $now)
  59. {
  60. // If the cookie has been tampered with
  61. if (forum_hmac($cookie['user_id'].'|'.$cookie['expiration_time'], $flux_config['cookie']['seed'].'_cookie_hash') != $cookie['cookie_hash'])
  62. {
  63. $expire = $now + 31536000; // The cookie expires after a year
  64. pun_setcookie(1, pun_hash(uniqid(rand(), true)), $expire);
  65. set_default_user();
  66. return;
  67. }
  68. // Check if there's a user with the user ID and password hash from the cookie
  69. $query = $db->select(array('user' => 'u.*', 'group' => 'g.*', 'logged' => 'o.logged', 'idle' => 'o.idle'), 'users AS u');
  70. $query->innerJoin('g', 'groups AS g', 'u.group_id = g.g_id');
  71. $query->leftJoin('o', 'online AS o', 'o.user_id = u.id');
  72. $query->where = 'u.id = :user_id';
  73. $params = array(':user_id' => $cookie['user_id']);
  74. $result = $query->run($params);
  75. unset ($query, $params);
  76. // If the password is invalid
  77. if (empty($result) || forum_hmac($result[0]['password'], $flux_config['cookie']['seed'].'_password_hash') !== $cookie['password_hash'])
  78. {
  79. $expire = $now + 31536000; // The cookie expires after a year
  80. pun_setcookie(1, pun_hash(uniqid(rand(), true)), $expire);
  81. set_default_user();
  82. return;
  83. }
  84. $pun_user = $result[0];
  85. unset ($result);
  86. // Send a new, updated cookie with a new expiration timestamp
  87. $expire = ($cookie['expiration_time'] > $now + $pun_config['o_timeout_visit']) ? $now + 1209600 : $now + $pun_config['o_timeout_visit'];
  88. pun_setcookie($pun_user['id'], $pun_user['password'], $expire);
  89. // Set a default language if the user selected language no longer exists
  90. if (!file_exists(PUN_ROOT.'lang/'.$pun_user['language']))
  91. $pun_user['language'] = $pun_config['o_default_lang'];
  92. // Set a default style if the user selected style no longer exists
  93. if (!file_exists(PUN_ROOT.'style/'.$pun_user['style'].'.css'))
  94. $pun_user['style'] = $pun_config['o_default_style'];
  95. if (!$pun_user['disp_topics'])
  96. $pun_user['disp_topics'] = $pun_config['o_disp_topics_default'];
  97. if (!$pun_user['disp_posts'])
  98. $pun_user['disp_posts'] = $pun_config['o_disp_posts_default'];
  99. // Define this if you want this visit to affect the online list and the users last visit data
  100. if (!defined('PUN_QUIET_VISIT'))
  101. {
  102. // Update the online list
  103. if (!$pun_user['logged'])
  104. {
  105. $pun_user['logged'] = $now;
  106. // REPLACE INTO avoids a user having two rows in the online table
  107. $query = $db->replace(array('user_id' => ':user_id', 'logged' => ':logged'), 'online', array('ident' => ':ident'));
  108. $params = array(':user_id' => $pun_user['id'], ':ident' => $pun_user['username'], ':logged' => $pun_user['logged']);
  109. $query->run($params);
  110. unset ($query, $params);
  111. // Reset tracked topics
  112. set_tracked_topics(null);
  113. }
  114. else
  115. {
  116. // Special case: We've timed out, but no other user has browsed the forums since we timed out
  117. if ($pun_user['logged'] < ($now-$pun_config['o_timeout_visit']))
  118. {
  119. $query = $db->update(array('last_visit' => ':logged'), 'users');
  120. $query->where = 'id = :user_id';
  121. $params = array(':logged' => $pun_user['logged'], ':user_id' => $pun_user['id']);
  122. $query->run($params);
  123. unset ($query, $params);
  124. $pun_user['last_visit'] = $pun_user['logged'];
  125. }
  126. $query = $db->update(array('logged' => ':now', 'idle' => '0'), 'online');
  127. $query->where = 'user_id = :user_id';
  128. $params = array(':now' => $now, ':user_id' => $pun_user['id']);
  129. $query->run($params);
  130. unset ($query, $params);
  131. // Update tracked topics with the current expire time
  132. if (isset($_COOKIE[$flux_config['cookie']['name'].'_track']))
  133. forum_setcookie($flux_config['cookie']['name'].'_track', $_COOKIE[$flux_config['cookie']['name'].'_track'], $now + $pun_config['o_timeout_visit']);
  134. }
  135. }
  136. else
  137. {
  138. if (!$pun_user['logged'])
  139. $pun_user['logged'] = $pun_user['last_visit'];
  140. }
  141. $pun_user['is_guest'] = false;
  142. $pun_user['is_admmod'] = $pun_user['g_id'] == PUN_ADMIN || $pun_user['g_moderator'] == '1';
  143. }
  144. else
  145. set_default_user();
  146. }
  147. //
  148. // Converts the CDATA end sequence ]]> into ]]&gt;
  149. //
  150. function escape_cdata($str)
  151. {
  152. return str_replace(']]>', ']]&gt;', $str);
  153. }
  154. //
  155. // Authenticates the provided username and password against the user database
  156. // $user can be either a user ID (integer) or a username (string)
  157. // $password can be either a plaintext password or a password hash including salt ($password_is_hash must be set accordingly)
  158. //
  159. function authenticate_user($user, $password, $password_is_hash = false)
  160. {
  161. global $db, $pun_user;
  162. // Check if there's a user matching $user and $password
  163. $query = $db->select(array('users' => 'u.*', 'group' => 'g.*', 'logged' => 'o.logged', 'idle' => 'o.idle'), 'users AS u');
  164. $query->innerJoin('g', 'groups AS g', 'g.g_id = u.group_id');
  165. $query->leftJoin('o', 'online AS o', 'o.user_id = u.id');
  166. $params = array();
  167. if (is_int($user))
  168. {
  169. $query->where = 'u.id = :user_id';
  170. $params[':user_id'] = $user;
  171. }
  172. else
  173. {
  174. $query->where = 'u.username = :username';
  175. $params[':username'] = $user;
  176. }
  177. $result = $query->run($params);
  178. if (empty($result))
  179. {
  180. set_default_user();
  181. return;
  182. }
  183. $pun_user = $result[0];
  184. unset ($result, $query, $params);
  185. if (($password_is_hash && $password != $pun_user['password']) ||
  186. (!$password_is_hash && pun_hash($password) != $pun_user['password']))
  187. set_default_user();
  188. else
  189. $pun_user['is_guest'] = false;
  190. }
  191. //
  192. // Try to determine the current URL
  193. //
  194. function get_current_url($max_length = 0)
  195. {
  196. $protocol = get_current_protocol();
  197. $port = (isset($_SERVER['SERVER_PORT']) && (($_SERVER['SERVER_PORT'] != '80' && $protocol == 'http') || ($_SERVER['SERVER_PORT'] != '443' && $protocol == 'https')) && strpos($_SERVER['HTTP_HOST'], ':') === false) ? ':'.$_SERVER['SERVER_PORT'] : '';
  198. $url = urldecode($protocol.'://'.$_SERVER['HTTP_HOST'].$port.$_SERVER['REQUEST_URI']);
  199. if (strlen($url) <= $max_length || $max_length == 0)
  200. return $url;
  201. // We can't find a short enough url
  202. return null;
  203. }
  204. //
  205. // Fetch the current protocol in use - http or https
  206. //
  207. function get_current_protocol()
  208. {
  209. $protocol = 'http';
  210. // Check if the server is claiming to using HTTPS
  211. if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
  212. $protocol = 'https';
  213. // If we are behind a reverse proxy try to decide which protocol it is using
  214. if (defined('FORUM_BEHIND_REVERSE_PROXY'))
  215. {
  216. // Check if we are behind a Microsoft based reverse proxy
  217. if (!empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) != 'off')
  218. $protocol = 'https';
  219. // Check if we're behind a "proper" reverse proxy, and what protocol it's using
  220. if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']))
  221. $protocol = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']);
  222. }
  223. return $protocol;
  224. }
  225. //
  226. // Fetch the base_url, optionally support HTTPS and HTTP
  227. //
  228. function get_base_url($support_https = false)
  229. {
  230. global $flux_config;
  231. static $base_url;
  232. if (!$support_https)
  233. return $flux_config['base_url'];
  234. if (!isset($base_url))
  235. {
  236. // Make sure we are using the correct protocol
  237. $base_url = str_replace(array('http://', 'https://'), get_current_protocol().'://', $flux_config['base_url']);
  238. }
  239. return $base_url;
  240. }
  241. //
  242. // Fill $pun_user with default values (for guests)
  243. //
  244. function set_default_user()
  245. {
  246. global $db, $db_type, $pun_user, $pun_config;
  247. $remote_addr = get_remote_address();
  248. // Fetch guest user
  249. $query = $db->select(array('user' => 'u.*', 'group' => 'g.*', 'logged' => 'o.logged', 'last_post' => 'o.last_post', 'last_search' => 'o.last_search'), 'users AS u');
  250. $query->innerJoin('g', 'groups AS g', 'u.group_id = g.g_id');
  251. $query->leftJoin('o', 'online AS o', 'o.ident = :ident');
  252. $query->where = 'u.id = 1';
  253. $params = array(':ident' => $remote_addr);
  254. $result = $query->run($params);
  255. unset ($query, $params);
  256. if (empty($result))
  257. exit('Unable to fetch guest information. Your database must contain both a guest user and a guest user group.');
  258. $pun_user = $result[0];
  259. unset ($result);
  260. // Update online list
  261. if (!$pun_user['logged'])
  262. {
  263. $pun_user['logged'] = time();
  264. // REPLACE INTO avoids a user having two rows in the online table
  265. $query = $db->replace(array('user_id' => '1', 'logged' => ':logged'), 'online', array('ident' => ':ident'));
  266. $params = array(':ident' => $remote_addr, ':logged' => $pun_user['logged']);
  267. $query->run($params);
  268. unset ($query, $params);
  269. }
  270. else {
  271. $query = $db->update(array('logged' => ':now'), 'online');
  272. $query->where = 'ident = :ident';
  273. $params = array(':now' => time(), ':ident' => $remote_addr);
  274. $query->run($params);
  275. unset ($query, $params);
  276. }
  277. $pun_user['disp_topics'] = $pun_config['o_disp_topics_default'];
  278. $pun_user['disp_posts'] = $pun_config['o_disp_posts_default'];
  279. $pun_user['timezone'] = $pun_config['o_default_timezone'];
  280. $pun_user['dst'] = $pun_config['o_default_dst'];
  281. $pun_user['language'] = $pun_config['o_default_lang'];
  282. $pun_user['style'] = $pun_config['o_default_style'];
  283. $pun_user['is_guest'] = true;
  284. $pun_user['is_admmod'] = false;
  285. }
  286. //
  287. // SHA1 HMAC with PHP 4 fallback
  288. //
  289. function forum_hmac($data, $key, $raw_output = false)
  290. {
  291. if (function_exists('hash_hmac'))
  292. return hash_hmac('sha1', $data, $key, $raw_output);
  293. // If key size more than blocksize then we hash it once
  294. if (strlen($key) > 64)
  295. $key = pack('H*', sha1($key)); // we have to use raw output here to match the standard
  296. // Ensure we're padded to exactly one block boundary
  297. $key = str_pad($key, 64, chr(0x00));
  298. $hmac_opad = str_repeat(chr(0x5C), 64);
  299. $hmac_ipad = str_repeat(chr(0x36), 64);
  300. // Do inner and outer padding
  301. for ($i = 0;$i < 64;$i++) {
  302. $hmac_opad[$i] = $hmac_opad[$i] ^ $key[$i];
  303. $hmac_ipad[$i] = $hmac_ipad[$i] ^ $key[$i];
  304. }
  305. // Finally, calculate the HMAC
  306. $hash = sha1($hmac_opad.pack('H*', sha1($hmac_ipad.$data)));
  307. // If we want raw output then we need to pack the final result
  308. if ($raw_output)
  309. $hash = pack('H*', $hash);
  310. return $hash;
  311. }
  312. //
  313. // Set a cookie, FluxBB style!
  314. // Wrapper for forum_setcookie
  315. //
  316. function pun_setcookie($user_id, $password_hash, $expire)
  317. {
  318. global $flux_config;
  319. forum_setcookie($flux_config['cookie']['name'], $user_id.'|'.forum_hmac($password_hash, $flux_config['cookie']['seed'].'_password_hash').'|'.$expire.'|'.forum_hmac($user_id.'|'.$expire, $flux_config['cookie']['seed'].'_cookie_hash'), $expire);
  320. }
  321. //
  322. // Set a cookie, FluxBB style!
  323. //
  324. function forum_setcookie($name, $value, $expire)
  325. {
  326. global $flux_config;
  327. // Enable sending of a P3P header
  328. header('P3P: CP="CUR ADM"');
  329. if (version_compare(PHP_VERSION, '5.2.0', '>='))
  330. setcookie($name, $value, $expire, $flux_config['cookie']['path'], $flux_config['cookie']['domain'], $flux_config['cookie']['secure'], true);
  331. else
  332. setcookie($name, $value, $expire, $flux_config['cookie']['path'].'; HttpOnly', $flux_config['cookie']['domain'], $flux_config['cookie']['secure']);
  333. }
  334. //
  335. // Check whether the connecting user is banned (and delete any expired bans while we're at it)
  336. //
  337. function check_bans()
  338. {
  339. global $cache, $db, $pun_config, $lang, $pun_user, $pun_bans;
  340. // Admins and moderators aren't affected
  341. if ($pun_user['is_admmod'] || !$pun_bans)
  342. return;
  343. // Add a dot or a colon (depending on IPv4/IPv6) at the end of the IP address to prevent banned address
  344. // 192.168.0.5 from matching e.g. 192.168.0.50
  345. $user_ip = get_remote_address();
  346. $user_ip .= (strpos($user_ip, '.') !== false) ? '.' : ':';
  347. $bans_altered = false;
  348. $is_banned = false;
  349. $query = $db->delete('bans');
  350. $query->where = 'id = :ban_id';
  351. foreach ($pun_bans as $cur_ban)
  352. {
  353. // Has this ban expired?
  354. if ($cur_ban['expire'] != '' && $cur_ban['expire'] <= time())
  355. {
  356. $params = array(':ban_id' => $cur_ban['id']);
  357. $query->run($params);
  358. unset ($params);
  359. $bans_altered = true;
  360. continue;
  361. }
  362. if ($cur_ban['username'] != '' && utf8_strtolower($pun_user['username']) == utf8_strtolower($cur_ban['username']))
  363. $is_banned = true;
  364. if ($cur_ban['ip'] != '')
  365. {
  366. $cur_ban_ips = explode(' ', $cur_ban['ip']);
  367. $num_ips = count($cur_ban_ips);
  368. for ($i = 0; $i < $num_ips; ++$i)
  369. {
  370. // Add the proper ending to the ban
  371. if (strpos($user_ip, '.') !== false)
  372. $cur_ban_ips[$i] = $cur_ban_ips[$i].'.';
  373. else
  374. $cur_ban_ips[$i] = $cur_ban_ips[$i].':';
  375. if (substr($user_ip, 0, strlen($cur_ban_ips[$i])) == $cur_ban_ips[$i])
  376. {
  377. $is_banned = true;
  378. break;
  379. }
  380. }
  381. }
  382. if ($is_banned)
  383. {
  384. $query = $db->delete('online');
  385. $query->where = 'ident = :username';
  386. $params = array(':username' => $pun_user['username']);
  387. $query->run($params);
  388. unset ($query, $params);
  389. message($lang->t('Ban message').' '.(($cur_ban['expire'] != '') ? $lang->t('Ban message 2').' '.strtolower(format_time($cur_ban['expire'], true)).'. ' : '').(($cur_ban['message'] != '') ? $lang->t('Ban message 3').'<br /><br /><strong>'.pun_htmlspecialchars($cur_ban['message']).'</strong><br /><br />' : '<br /><br />').$lang->t('Ban message 4').' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.', true);
  390. }
  391. }
  392. unset ($query);
  393. // If we removed any expired bans during our run-through, we need to regenerate the bans cache
  394. if ($bans_altered)
  395. $cache->delete('bans');
  396. }
  397. //
  398. // Check username
  399. //
  400. function check_username($username, $exclude_id = null)
  401. {
  402. global $db, $pun_config, $errors, $lang, $pun_bans;
  403. $lang->load('prof_reg');
  404. $lang->load('register');
  405. // Convert multiple whitespace characters into one (to prevent people from registering with indistinguishable usernames)
  406. $username = preg_replace('%\s+%s', ' ', $username);
  407. // Validate username
  408. if (pun_strlen($username) < 2)
  409. $errors[] = $lang->t('Username too short');
  410. else if (pun_strlen($username) > 25) // This usually doesn't happen since the form element only accepts 25 characters
  411. $errors[] = $lang->t('Username too long');
  412. else if (!strcasecmp($username, 'Guest') || !strcasecmp($username, $lang->t('Guest')))
  413. $errors[] = $lang->t('Username guest');
  414. else if (preg_match('%[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}%', $username) || preg_match('%((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))%', $username))
  415. $errors[] = $lang->t('Username IP');
  416. else if ((strpos($username, '[') !== false || strpos($username, ']') !== false) && strpos($username, '\'') !== false && strpos($username, '"') !== false)
  417. $errors[] = $lang->t('Username reserved chars');
  418. else if (preg_match('%(?:\[/?(?:b|u|s|ins|del|em|i|h|colou?r|quote|code|img|url|email|list|\*|topic|post|forum|user)\]|\[(?:img|url|quote|list)=)%i', $username))
  419. $errors[] = $lang->t('Username BBCode');
  420. // Check username for any censored words
  421. if ($pun_config['o_censoring'] == '1' && censor_words($username) != $username)
  422. $errors[] = $lang->t('Username censor');
  423. // Check that the username (or a too similar username) is not already registered
  424. $query = $db->select(array('username' => 'u.username'), 'users AS u');
  425. $query->where = '(u.username LIKE :username OR u.username LIKE :clean_username) AND u.id > 1';
  426. $params = array(':username' => $username, ':clean_username' => ucp_preg_replace('%[^\p{L}\p{N}]%u', '', $username));
  427. if ($exclude_id)
  428. {
  429. $query->where .= ' AND u.id != :exclude_id';
  430. $params[':exclude_id'] = $exclude_id;
  431. }
  432. $result = $query->run($params);
  433. if (!empty($result))
  434. $errors[] = $lang->t('Username dupe 1').' '.pun_htmlspecialchars($result[0]['username']).'. '.$lang->t('Username dupe 2');
  435. unset ($query, $params, $result);
  436. // Check username for any banned usernames
  437. foreach ($pun_bans as $cur_ban)
  438. {
  439. if ($cur_ban['username'] != '' && utf8_strtolower($username) == utf8_strtolower($cur_ban['username']))
  440. {
  441. $errors[] = $lang->t('Banned username');
  442. break;
  443. }
  444. }
  445. }
  446. //
  447. // Update "Users online"
  448. //
  449. function update_users_online()
  450. {
  451. global $db, $pun_config;
  452. $now = time();
  453. // Fetch all online list entries that are older than "o_timeout_online"
  454. $query = $db->select(array('user_id' => 'o.user_id', 'ident' => 'o.ident', 'logged' => 'o.logged', 'idle' => 'o.idle'), 'online AS o');
  455. $query->where = 'logged < :logged';
  456. $params = array(':logged' => $now - $pun_config['o_timeout_online']);
  457. $result = $query->run($params);
  458. unset ($query, $params);
  459. // Query for deleting an online entry
  460. $query_delete = $db->delete('online');
  461. $query_delete->where = 'ident = :ident';
  462. // Query for updating users last visit time
  463. $query_update_user = $db->update(array('last_visit' => ':logged'), 'users');
  464. $query_update_user->where = 'id = :user_id';
  465. // Query for updating online idle
  466. $query_update_online = $db->update(array('idle' => 1), 'online');
  467. $query_update_online->where = 'user_id = :user_id';
  468. foreach ($result as $cur_user)
  469. {
  470. // If the entry is a guest, delete it
  471. if ($cur_user['user_id'] == '1')
  472. {
  473. $params = array(':ident' => $cur_user['ident']);
  474. $query_delete->run($params);
  475. unset ($params);
  476. }
  477. else
  478. {
  479. // If the entry is older than "o_timeout_visit", update last_visit for the user in question, then delete him/her from the online list
  480. if ($cur_user['logged'] < ($now - $pun_config['o_timeout_visit']))
  481. {
  482. $params = array(':logged' => $cur_user['logged'], ':user_id' => $cur_user['user_id']);
  483. $query_update_user->run($params);
  484. unset ($params);
  485. $params = array(':ident' => $cur_user['ident']);
  486. $query_delete->run($params);
  487. unset ($params);
  488. }
  489. else if ($cur_user['idle'] == '0')
  490. {
  491. $params = array(':user_id' => $cur_user['user_id']);
  492. $query_update_online->run($params);
  493. unset ($params);
  494. }
  495. }
  496. }
  497. unset ($result, $query_delete, $query_update_user, $query_update_online);
  498. }
  499. //
  500. // Display the profile navigation menu
  501. //
  502. function generate_profile_menu($page = '')
  503. {
  504. global $lang, $pun_config, $pun_user, $id;
  505. $lang->load('profile');
  506. ?>
  507. <div id="profile" class="block2col">
  508. <div class="blockmenu">
  509. <h2><span><?php echo $lang->t('Profile menu') ?></span></h2>
  510. <div class="box">
  511. <div class="inbox">
  512. <ul>
  513. <li<?php if ($page == 'essentials') echo ' class="isactive"'; ?>><a href="profile.php?section=essentials&amp;id=<?php echo $id ?>"><?php echo $lang->t('Section essentials') ?></a></li>
  514. <li<?php if ($page == 'personal') echo ' class="isactive"'; ?>><a href="profile.php?section=personal&amp;id=<?php echo $id ?>"><?php echo $lang->t('Section personal') ?></a></li>
  515. <li<?php if ($page == 'messaging') echo ' class="isactive"'; ?>><a href="profile.php?section=messaging&amp;id=<?php echo $id ?>"><?php echo $lang->t('Section messaging') ?></a></li>
  516. <?php if ($pun_config['o_avatars'] == '1' || $pun_config['o_signatures'] == '1'): ?> <li<?php if ($page == 'personality') echo ' class="isactive"'; ?>><a href="profile.php?section=personality&amp;id=<?php echo $id ?>"><?php echo $lang->t('Section personality') ?></a></li>
  517. <?php endif; ?> <li<?php if ($page == 'display') echo ' class="isactive"'; ?>><a href="profile.php?section=display&amp;id=<?php echo $id ?>"><?php echo $lang->t('Section display') ?></a></li>
  518. <li<?php if ($page == 'privacy') echo ' class="isactive"'; ?>><a href="profile.php?section=privacy&amp;id=<?php echo $id ?>"><?php echo $lang->t('Section privacy') ?></a></li>
  519. <?php if ($pun_user['g_id'] == PUN_ADMIN || ($pun_user['g_moderator'] == '1' && $pun_user['g_mod_ban_users'] == '1')): ?> <li<?php if ($page == 'admin') echo ' class="isactive"'; ?>><a href="profile.php?section=admin&amp;id=<?php echo $id ?>"><?php echo $lang->t('Section admin') ?></a></li>
  520. <?php endif; ?> </ul>
  521. </div>
  522. </div>
  523. </div>
  524. <?php
  525. }
  526. //
  527. // Outputs markup to display a user's avatar
  528. //
  529. function generate_avatar_markup($user_id)
  530. {
  531. global $pun_config;
  532. $filetypes = array('jpg', 'gif', 'png');
  533. $avatar_markup = '';
  534. foreach ($filetypes as $cur_type)
  535. {
  536. $path = $pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type;
  537. if (file_exists(PUN_ROOT.$path) && $img_size = getimagesize(PUN_ROOT.$path))
  538. {
  539. $avatar_markup = '<img src="'.pun_htmlspecialchars(get_base_url(true).'/'.$path.'?m='.filemtime(PUN_ROOT.$path)).'" '.$img_size[3].' alt="" />';
  540. break;
  541. }
  542. }
  543. return $avatar_markup;
  544. }
  545. //
  546. // Generate browser's title
  547. //
  548. function generate_page_title($page_title, $p = null)
  549. {
  550. global $pun_config, $lang;
  551. $page_title = array_reverse($page_title);
  552. if ($p != null)
  553. $page_title[0] .= ' ('.$lang->t('Page', forum_number_format($p)).')';
  554. $crumbs = implode($lang->t('Title separator'), $page_title);
  555. return $crumbs;
  556. }
  557. //
  558. // Save array of tracked topics in cookie
  559. //
  560. function set_tracked_topics($tracked_topics)
  561. {
  562. global $flux_config, $pun_config;
  563. $cookie_data = '';
  564. if (!empty($tracked_topics))
  565. {
  566. // Sort the arrays (latest read first)
  567. arsort($tracked_topics['topics'], SORT_NUMERIC);
  568. arsort($tracked_topics['forums'], SORT_NUMERIC);
  569. // Homebrew serialization (to avoid having to run unserialize() on cookie data)
  570. foreach ($tracked_topics['topics'] as $id => $timestamp)
  571. $cookie_data .= 't'.$id.'='.$timestamp.';';
  572. foreach ($tracked_topics['forums'] as $id => $timestamp)
  573. $cookie_data .= 'f'.$id.'='.$timestamp.';';
  574. // Enforce a byte size limit (4096 minus some space for the cookie name - defaults to 4048)
  575. if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
  576. {
  577. $cookie_data = substr($cookie_data, 0, FORUM_MAX_COOKIE_SIZE);
  578. $cookie_data = substr($cookie_data, 0, strrpos($cookie_data, ';')).';';
  579. }
  580. }
  581. forum_setcookie($flux_config['cookie']['name'].'_track', $cookie_data, time() + $pun_config['o_timeout_visit']);
  582. $_COOKIE[$flux_config['cookie']['name'].'_track'] = $cookie_data; // Set it directly in $_COOKIE as well
  583. }
  584. //
  585. // Extract array of tracked topics from cookie
  586. //
  587. function get_tracked_topics()
  588. {
  589. global $flux_config;
  590. $cookie_data = isset($_COOKIE[$flux_config['cookie']['name'].'_track']) ? $_COOKIE[$flux_config['cookie']['name'].'_track'] : false;
  591. if (!$cookie_data)
  592. return array('topics' => array(), 'forums' => array());
  593. if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
  594. return array('topics' => array(), 'forums' => array());
  595. // Unserialize data from cookie
  596. $tracked_topics = array('topics' => array(), 'forums' => array());
  597. $temp = explode(';', $cookie_data);
  598. foreach ($temp as $t)
  599. {
  600. $type = substr($t, 0, 1) == 'f' ? 'forums' : 'topics';
  601. $id = intval(substr($t, 1));
  602. $timestamp = intval(substr($t, strpos($t, '=') + 1));
  603. if ($id > 0 && $timestamp > 0)
  604. $tracked_topics[$type][$id] = $timestamp;
  605. }
  606. return $tracked_topics;
  607. }
  608. //
  609. // Update posts, topics, last_post, last_post_id and last_poster for a forum
  610. //
  611. function update_forum($forum_id)
  612. {
  613. global $db;
  614. $query = $db->select(array('num_topics' => 'COUNT(t.id) AS num_topics', 'num_replies' => 'SUM(t.num_replies) AS num_replies'), 'topics AS t');
  615. $query->where = 't.forum_id = :forum_id';
  616. $params = array(':forum_id' => $forum_id);
  617. $result = $query->run($params);
  618. $num_topics = $result[0]['num_topics'];
  619. $num_posts = $result[0]['num_topics'] + $result[0]['num_replies'];
  620. unset ($result, $query, $params);
  621. $query = $db->select(array('last_post' => 't.last_post AS posted', 'last_post_id' => 't.last_post_id AS id', 'last_poster' => 't.last_poster AS poster'), 'topics AS t');
  622. $query->where = 't.forum_id = :forum_id AND t.moved_to IS NULL';
  623. $query->order = array('last_post' => 't.last_post DESC');
  624. $query->limit = 1;
  625. $params = array(':forum_id' => $forum_id);
  626. $result = $query->run($params);
  627. unset ($query, $params);
  628. $query = $db->update(array('num_topics' => ':num_topics', 'num_posts' => ':num_posts', 'last_post' => ':last_post', 'last_post_id' => ':last_post_id', 'last_poster' => ':last_poster'), 'forums');
  629. $query->where = 'id = :forum_id';
  630. // There are topics in the forum
  631. if (!empty($result))
  632. {
  633. $last_post = $result[0];
  634. unset ($result);
  635. $params = array(':num_topics' => $num_topics, ':num_posts' => $num_posts, ':last_post' => $last_post['posted'], ':last_post_id' => $last_post['id'], ':last_poster' => $last_post['poster'], ':forum_id' => $forum_id);
  636. }
  637. // There are no topics
  638. else
  639. {
  640. unset ($result);
  641. $params = array(':num_topics' => $num_topics, ':num_posts' => $num_posts, ':last_post' => null, ':last_post_id' => null, ':last_poster' => null, ':forum_id' => $forum_id);
  642. }
  643. $query->run($params);
  644. unset ($query, $params);
  645. }
  646. //
  647. // Deletes any avatars owned by the specified user ID
  648. //
  649. function delete_avatar($user_id)
  650. {
  651. global $pun_config;
  652. $filetypes = array('jpg', 'gif', 'png');
  653. // Delete user avatar
  654. foreach ($filetypes as $cur_type)
  655. {
  656. if (file_exists(PUN_ROOT.$pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type))
  657. @unlink(PUN_ROOT.$pun_config['o_avatars_dir'].'/'.$user_id.'.'.$cur_type);
  658. }
  659. }
  660. //
  661. // Delete a topic and all of it's posts
  662. //
  663. function delete_topic($topic_id)
  664. {
  665. global $db;
  666. // Delete the topic and any redirect topics
  667. $query = $db->delete('topics');
  668. $query->where = 'id = :topic_id OR moved_to = :topic_id';
  669. $params = array(':topic_id' => $topic_id);
  670. $query->run($params);
  671. unset($query, $params);
  672. // Create a list of the post IDs in this topic
  673. $query = $db->select(array('id' => 'p.id'), 'posts AS p');
  674. $query->where = 'p.topic_id = :topic_id';
  675. $params = array(':topic_id' => $topic_id);
  676. $result = $query->run($params);
  677. $post_ids = array();
  678. foreach ($result as $row)
  679. $post_ids[] = $row['id'];
  680. unset($query, $params, $result);
  681. // Make sure we have a list of post IDs
  682. if (!empty($post_ids))
  683. {
  684. strip_search_index($post_ids);
  685. // Delete posts in topic
  686. $query = $db->delete('posts');
  687. $query->where = 'topic_id = :topic_id';
  688. $params = array(':topic_id' => $topic_id);
  689. $query->run($params);
  690. unset($query, $params);
  691. }
  692. // Delete any subscriptions for this topic
  693. $query = $db->delete('topic_subscriptions');
  694. $query->where = 'topic_id = :topic_id';
  695. $params = array(':topic_id' => $topic_id);
  696. $query->run($params);
  697. unset($query, $params);
  698. }
  699. //
  700. // Delete a single post
  701. //
  702. function delete_post($post_id, $topic_id)
  703. {
  704. global $db;
  705. $query = $db->select(array('id' => 'p.id', 'poster' => 'p.poster', 'posted' => 'p.posted'), 'posts AS p');
  706. $query->where = 'p.topic_id = :topic_id';
  707. $query->order = array('p.id DESC');
  708. $query->limit = 2;
  709. $params = array(':topic_id' => $topic_id);
  710. $result = $query->run($params);
  711. if (count($result) > 0)
  712. $last_id = $result[0]['id'];
  713. if (count($result) > 1)
  714. list($second_last_id, $second_poster, $second_posted) = array($result[1]['id'], $result[1]['poster'], $result[1]['posted']);
  715. else
  716. list($second_last_id, $second_poster, $second_posted) = array('', '', '');
  717. unset($query, $params, $result);
  718. // Delete the post
  719. $query = $db->delete('posts');
  720. $query->where = 'id = :post_id';
  721. $params = array(':post_id' => $post_id);
  722. $query->run($params);
  723. unset($query, $params);
  724. strip_search_index($post_id);
  725. // Count number of replies in the topic
  726. $query = $db->select(array('post_count' => 'COUNT(p.id) AS post_count'), 'posts AS p');
  727. $query->where = 'topic_id = :topic_id';
  728. $params = array(':topic_id' => $topic_id);
  729. $result = $query->run($params);
  730. $num_replies = $result[0]['post_count'] - 1;
  731. unset($query, $params, $result);
  732. // If the message we deleted is the most recent in the topic (at the end of the topic)
  733. if ($last_id == $post_id)
  734. {
  735. // If there is a $second_last_id there is more than 1 reply to the topic
  736. if (!empty($second_last_id))
  737. {
  738. $query = $db->update(array('last_post' => ':second_posted', 'last_post_id' => ':second_last_id', 'last_poster' => ':second_poster', 'num_replies' => ':num_replies'), 'topics');
  739. $query->where = 'id = :topic_id';
  740. $params = array(':second_posted' => $second_posted, ':second_last_id' => $second_last_id, ':second_poster' => $second_poster, ':num_replies' => $num_replies, ':topic_id' => $topic_id);
  741. $query->run($params);
  742. unset($query, $params);
  743. }
  744. else
  745. {
  746. // We deleted the only reply, so now last_post/last_post_id/last_poster is posted/id/poster from the topic itself
  747. $query = $db->update(array('last_post' => 'posted', 'last_post_id' => 'id', 'last_poster' => 'poster', 'num_replies' => ':num_replies'), 'topics');
  748. $query->where = 'id = :topic_id';
  749. $params = array(':num_replies' => $num_replies, ':topic_id' => $topic_id);
  750. $query->run($params);
  751. unset($query, $params);
  752. }
  753. }
  754. else
  755. {
  756. // Otherwise we just decrement the reply counter
  757. $query = $db->update(array('num_replies' => ':num_replies'), 'topics');
  758. $query->where = 'id = :topic_id';
  759. $params = array(':num_replies' => $num_replies, ':topic_id' => $topic_id);
  760. $query->run($params);
  761. unset($query, $params);
  762. }
  763. }
  764. //
  765. // Replace censored words in $text
  766. //
  767. function censor_words($text)
  768. {
  769. global $cache, $db;
  770. static $censors;
  771. // If not already built in a previous call, build an array of censor words and their replacement text
  772. if (!isset($censors))
  773. {
  774. $censors = $cache->remember('censors', function() use ($db) {
  775. $censors = array();
  776. $query = $db->select(array('search_for' => 'c.search_for', 'replace_with' => 'c.replace_with'), 'censoring AS c');
  777. $params = array();
  778. $result = $query->run($params);
  779. foreach ($result as $cur_censor)
  780. {
  781. $cur_censor['search_for'] = '/(?<=[^\p{L}\p{N}])('.str_replace('\*', '[\p{L}\p{N}]*?', preg_quote($cur_censor['search_for'], '/')).')(?=[^\p{L}\p{N}])/iu';
  782. $censors[$cur_censor['search_for']] = $cur_censor['replace_with'];
  783. }
  784. unset ($result, $query, $params);
  785. return $censors;
  786. });
  787. }
  788. if (!empty($censors))
  789. $text = substr(ucp_preg_replace(array_keys($censors), array_values($censors), ' '.$text.' '), 1, -1);
  790. return $text;
  791. }
  792. //
  793. // Determines the correct title for $user
  794. // $user must contain the elements 'username', 'title', 'posts', 'g_id' and 'g_user_title'
  795. //
  796. function get_title($user)
  797. {
  798. global $cache, $db, $pun_config, $pun_bans, $lang;
  799. static $ban_list, $pun_ranks;
  800. // If not already built in a previous call, build an array of lowercase banned usernames
  801. if (empty($ban_list))
  802. {
  803. $ban_list = array();
  804. foreach ($pun_bans as $cur_ban)
  805. $ban_list[] = strtolower($cur_ban['username']);
  806. }
  807. // If not already loaded in a previous call, load the cached ranks
  808. if ($pun_config['o_ranks'] == '1' && !isset($pun_ranks))
  809. {
  810. $pun_ranks = $cache->remember('ranks', function() use ($db) {
  811. $ranks = array();
  812. // Get the rank list from the DB
  813. $query = $db->select(array('ranks' => 'r.*'), 'ranks AS r');
  814. $query->order = array('min_posts' => 'r.min_posts ASC');
  815. $params = array();
  816. $ranks = $query->run($params);
  817. unset ($query, $params);
  818. return $ranks;
  819. });
  820. }
  821. // If the user has a custom title
  822. if ($user['title'] != '')
  823. $user_title = pun_htmlspecialchars($user['title']);
  824. // If the user is banned
  825. else if (in_array(strtolower($user['username']), $ban_list))
  826. $user_title = $lang->t('Banned');
  827. // If the user group has a default user title
  828. else if ($user['g_user_title'] != '')
  829. $user_title = pun_htmlspecialchars($user['g_user_title']);
  830. // If the user is a guest
  831. else if ($user['g_id'] == PUN_GUEST)
  832. $user_title = $lang->t('Guest');
  833. else
  834. {
  835. // Are there any ranks?
  836. if ($pun_config['o_ranks'] == '1' && !empty($pun_ranks))
  837. {
  838. foreach ($pun_ranks as $cur_rank)
  839. {
  840. if ($user['num_posts'] >= $cur_rank['min_posts'])
  841. $user_title = pun_htmlspecialchars($cur_rank['rank']);
  842. }
  843. }
  844. // If the user didn't "reach" any rank (or if ranks are disabled), we assign the default
  845. if (!isset($user_title))
  846. $user_title = $lang->t('Member');
  847. }
  848. return $user_title;
  849. }
  850. //
  851. // Generate a string with numbered links (for multipage scripts)
  852. //
  853. function paginate($num_pages, $cur_page, $link)
  854. {
  855. global $lang;
  856. $pages = array();
  857. $link_to_all = false;
  858. // If $cur_page == -1, we link to all pages (used in viewforum.php)
  859. if ($cur_page == -1)
  860. {
  861. $cur_page = 1;
  862. $link_to_all = true;
  863. }
  864. if ($num_pages <= 1)
  865. $pages = array('<strong class="item1">1</strong>');
  866. else
  867. {
  868. // Add a previous page link
  869. if ($num_pages > 1 && $cur_page > 1)
  870. $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.($cur_page - 1).'">'.$lang->t('Previous').'</a>';
  871. if ($cur_page > 3)
  872. {
  873. $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p=1">1</a>';
  874. if ($cur_page > 5)
  875. $pages[] = '<span class="spacer">'.$lang->t('Spacer').'</span>';
  876. }
  877. // Don't ask me how the following works. It just does, OK? :-)
  878. for ($current = ($cur_page == 5) ? $cur_page - 3 : $cur_page - 2, $stop = ($cur_page + 4 == $num_pages) ? $cur_page + 4 : $cur_page + 3; $current < $stop; ++$current)
  879. {
  880. if ($current < 1 || $current > $num_pages)
  881. continue;
  882. else if ($current != $cur_page || $link_to_all)
  883. $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.$current.'">'.forum_number_format($current).'</a>';
  884. else
  885. $pages[] = '<strong'.(empty($pages) ? ' class="item1"' : '').'>'.forum_number_format($current).'</strong>';
  886. }
  887. if ($cur_page <= ($num_pages-3))
  888. {
  889. if ($cur_page != ($num_pages-3) && $cur_page != ($num_pages-4))
  890. $pages[] = '<span class="spacer">'.$lang->t('Spacer').'</span>';
  891. $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.$num_pages.'">'.forum_number_format($num_pages).'</a>';
  892. }
  893. // Add a next page link
  894. if ($num_pages > 1 && !$link_to_all && $cur_page < $num_pages)
  895. $pages[] = '<a'.(empty($pages) ? ' class="item1"' : '').' href="'.$link.'&amp;p='.($cur_page +1).'">'.$lang->t('Next').'</a>';
  896. }
  897. return implode(' ', $pages);
  898. }
  899. //
  900. // Display a message
  901. //
  902. function message($message, $no_back_link = false)
  903. {
  904. global $db, $cache, $lang, $pun_config, $pun_start, $tpl_main, $pun_user;
  905. if (!defined('PUN_HEADER'))
  906. {
  907. $page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang->t('Info'));
  908. define('PUN_ACTIVE_PAGE', 'index');
  909. require PUN_ROOT.'header.php';
  910. }
  911. ?>
  912. <div id="msg" class="block">
  913. <h2><span><?php echo $lang->t('Info') ?></span></h2>
  914. <div class="box">
  915. <div class="inbox">
  916. <p><?php echo $message ?></p>
  917. <?php if (!$no_back_link): ?> <p><a href="javascript: history.go(-1)"><?php echo $lang->t('Go back') ?></a></p>
  918. <?php endif; ?> </div>
  919. </div>
  920. </div>
  921. <?php
  922. require PUN_ROOT.'footer.php';
  923. }
  924. //
  925. // Format a time string according to $time_format and time zones
  926. //
  927. function format_time($timestamp, $date_only = false, $date_format = null, $time_format = null, $time_only = false, $no_text = false)
  928. {
  929. global $pun_config, $lang, $pun_user, $forum_date_formats, $forum_time_formats;
  930. if ($timestamp == '')
  931. return $lang->t('Never');
  932. $diff = ($pun_user['timezone'] + $pun_user['dst']) * 3600;
  933. $timestamp += $diff;
  934. $now = time();
  935. if($date_format == null)
  936. $date_format = $forum_date_formats[$pun_user['date_format']];
  937. if($time_format == null)
  938. $time_format = $forum_time_formats[$pun_user['time_format']];
  939. $date = gmdate($date_format, $timestamp);
  940. $today = gmdate($date_format, $now+$diff);
  941. $yesterday = gmdate($date_format, $now+$diff-86400);
  942. if(!$no_text)
  943. {
  944. if ($date == $today)
  945. $date = $lang->t('Today');
  946. else if ($date == $yesterday)
  947. $date = $lang->t('Yesterday');
  948. }
  949. if ($date_only)
  950. return $date;
  951. else if ($time_only)
  952. return gmdate($time_format, $timestamp);
  953. else
  954. return $date.' '.gmdate($time_format, $timestamp);
  955. }
  956. //
  957. // A wrapper for PHP's number_format function
  958. //
  959. function forum_number_format($number, $decimals = 0)
  960. {
  961. global $lang;
  962. return is_numeric($number) ? number_format($number, $decimals, $lang->t('lang_decimal_point'), $lang->t('lang_thousands_sep')) : $number;
  963. }
  964. //
  965. // Generate a random key of length $len
  966. //
  967. function random_key($len, $readable = false, $hash = false)
  968. {
  969. $key = '';
  970. if ($hash)
  971. $key = substr(pun_hash(uniqid(rand(), true)), 0, $len);
  972. else if ($readable)
  973. {
  974. $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  975. for ($i = 0; $i < $len; ++$i)
  976. $key .= substr($chars, (mt_rand() % strlen($chars)), 1);
  977. }
  978. else
  979. {
  980. for ($i = 0; $i < $len; ++$i)
  981. $key .= chr(mt_rand(33, 126));
  982. }
  983. return $key;
  984. }
  985. //
  986. // Make sure that HTTP_REFERER matches base_url/script
  987. //
  988. function confirm_referrer($script, $error_msg = false)
  989. {
  990. global $pun_config, $lang;
  991. // There is no referrer
  992. if (empty($_SERVER['HTTP_REFERER']))
  993. message($error_msg ? $error_msg : $lang->t('Bad referrer'));
  994. $referrer = parse_url(strtolower($_SERVER['HTTP_REFERER']));
  995. // Remove www subdomain if it exists
  996. if (strpos($referrer['host'], 'www.') === 0)
  997. $referrer['host'] = substr($referrer['host'], 4);
  998. $valid = parse_url(strtolower(get_base_url().'/'.$script));
  999. // Remove www subdomain if it exists
  1000. if (strpos($valid['host'], 'www.') === 0)
  1001. $valid['host'] = substr($valid['host'], 4);
  1002. // Check the host and path match. Ignore the scheme, port, etc.
  1003. if ($referrer['host'] != $valid['host'] || $referrer['path'] != $valid['path'])
  1004. message($error_msg ? $error_msg : $lang->t('Bad referrer'));
  1005. }
  1006. //
  1007. // Generate a random password of length $len
  1008. // Compatibility wrapper for random_key
  1009. //
  1010. function random_pass($len)
  1011. {
  1012. return random_key($len, true);
  1013. }
  1014. //
  1015. // Compute a hash of $str
  1016. //
  1017. function pun_hash($str)
  1018. {
  1019. return sha1($str);
  1020. }
  1021. //
  1022. // Try to determine the correct remote IP-address
  1023. //
  1024. function get_remote_address()
  1025. {
  1026. $remote_addr = $_SERVER['REMOTE_ADDR'];
  1027. // If we are behind a reverse proxy try to find the real users IP
  1028. if (defined('FORUM_BEHIND_REVERSE_PROXY'))
  1029. {
  1030. if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
  1031. {
  1032. // The general format of the field is:
  1033. // X-Forwarded-For: client1, proxy1, proxy2
  1034. // where the value is a comma+space separated list of IP addresses, the left-most being the farthest downstream client,
  1035. // and each successive proxy that passed the request adding the IP address where it received the request from.
  1036. $forwarded_for = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
  1037. $forwarded_for = trim($forwarded_for[0]);
  1038. if (@preg_match('%^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$%', $forwarded_for) || @preg_match('%^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$%', $forwarded_for))
  1039. $remote_addr = $forwarded_for;
  1040. }
  1041. }
  1042. return $remote_addr;
  1043. }
  1044. //
  1045. // Calls htmlspecialchars with a few options already set
  1046. //
  1047. function pun_htmlspecialchars($str)
  1048. {
  1049. return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
  1050. }
  1051. //
  1052. // Calls htmlspecialchars_decode with a few options already set
  1053. //
  1054. function pun_htmlspecialchars_decode($str)
  1055. {
  1056. if (function_exists('htmlspecialchars_decode'))
  1057. return htmlspecialchars_decode($str, ENT_QUOTES);
  1058. static $translations;
  1059. if (!isset($translations))
  1060. {
  1061. $translations = get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES);
  1062. $translations['&#039;'] = '\''; // get_html_translation_table doesn't include &#039; which is what htmlspecialchars translates ' to, but apparently that is okay?! http://bugs.php.net/bug.php?id=25927
  1063. $translations = array_flip($translations);
  1064. }
  1065. return strtr($str, $translations);
  1066. }
  1067. //
  1068. // A wrapper for utf8_strlen for compatibility
  1069. //
  1070. function pun_strlen($str)
  1071. {
  1072. return utf8_strlen($str);
  1073. }
  1074. //
  1075. // Convert \r\n and \r to \n
  1076. //
  1077. function pun_linebreaks($str)
  1078. {
  1079. return str_replace("\r", "\n", str_replace("\r\n", "\n", $str));
  1080. }
  1081. //
  1082. // A wrapper for utf8_trim for compatibility
  1083. //
  1084. function pun_trim($str, $charlist = false)
  1085. {
  1086. return utf8_trim($str, $charlist);
  1087. }
  1088. //
  1089. // Checks if a string is in all uppercase
  1090. //
  1091. function is_all_uppercase($string)
  1092. {
  1093. return utf8_strtoupper($string) == $string && utf8_strtolower($string) != $string;
  1094. }
  1095. //
  1096. // Inserts $element into $input at $offset
  1097. // $offset can be either a numerical offset to insert at (eg: 0 inserts at the beginning of the array)
  1098. // or a string, which is the key that the new element should be inserted before
  1099. // $key is optional: it's used when inserting a new key/value pair into an associative array
  1100. //
  1101. function array_insert(&$input, $offset, $element, $key = null)
  1102. {
  1103. if ($key == null)
  1104. $key = $offset;
  1105. // Determine the proper offset if we're using a string
  1106. if (!is_int($offset))
  1107. $offset = array_search($offset, array_keys($input), true);
  1108. // Out of bounds checks
  1109. if ($offset > count($input))
  1110. $offset = count($input);
  1111. else if ($offset < 0)
  1112. $offset = 0;
  1113. $input = array_merge(array_slice($input, 0, $offset), array($key => $element), array_slice($input, $offset));
  1114. }
  1115. //
  1116. // Display a message when board is in maintenance mode
  1117. //
  1118. function maintenance_message()
  1119. {
  1120. global $db, $pun_config, $lang, $pun_user;
  1121. // Send no-cache headers
  1122. header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
  1123. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  1124. header('Cache-Control: post-check=0, pre-check=0', false);
  1125. header('Pragma: no-cache'); // For HTTP/1.0 compatibility
  1126. // Send the Content-type header in case the web server is setup to send something else
  1127. header('Content-type: text/html; charset=utf-8');
  1128. // Deal with newlines, tabs and multiple spaces
  1129. $pattern = array("\t", ' ', ' ');
  1130. $replace = array('&#160; &#160; ', '&#160; ', ' &#160;');
  1131. $message = str_replace($pattern, $replace, $pun_config['o_maintenance_message']);
  1132. if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/maintenance.tpl'))
  1133. {
  1134. $tpl_file = PUN_ROOT.'style/'.$pun_user['style'].'/maintenance.tpl';
  1135. $tpl_inc_dir = PUN_ROOT.'style/'.$pun_user['style'].'/';
  1136. }
  1137. else
  1138. {
  1139. $tpl_file = PUN_ROOT.'include/template/maintenance.tpl';
  1140. $tpl_inc_dir = PUN_ROOT.'include/user/';
  1141. }
  1142. $tpl_maint = file_get_contents($tpl_file);
  1143. // START SUBST - <pun_include "*">
  1144. preg_match_all('%<pun_include "([^/\\\\]*?)\.(php[45]?|inc|html?|txt)">%i', $tpl_maint, $pun_includes, PREG_SET_ORDER);
  1145. foreach ($pun_includes as $cur_include)
  1146. {
  1147. ob_start();
  1148. // Allow for overriding user includes, too.
  1149. if (file_exists($tpl_inc_dir.$cur_include[1].'.'.$cur_include[2]))
  1150. require $tpl_inc_dir.$cur_include[1].'.'.$cur_include[2];
  1151. else if (file_exists(PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2]))
  1152. require PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2];
  1153. else
  1154. error($lang->t('Pun include error', htmlspecialchars($cur_include[0]), basename($tpl_file)));
  1155. $tpl_temp = ob_get_contents();
  1156. $tpl_maint = str_replace($cur_include[0], $tpl_temp, $tpl_maint);
  1157. ob_end_clean();
  1158. }
  1159. // END SUBST - <pun_include "*">
  1160. // START SUBST - <pun_language>
  1161. $tpl_maint = str_replace('<pun_language>', $lang->t('lang_identifier'), $tpl_maint);
  1162. // END SUBST - <pun_language>
  1163. // START SUBST - <pun_content_direction>
  1164. $tpl_maint = str_replace('<pun_content_direction>', $lang->t('lang_direction'), $tpl_maint);
  1165. // END SUBST - <pun_content_direction>
  1166. // START SUBST - <pun_head>
  1167. ob_start();
  1168. $page_title = array(pun_htmlspecialchars($pun_config['o_board_title']), $lang->t('Maintenance'));
  1169. ?>
  1170. <title><?php echo generate_page_title($page_title) ?></title>
  1171. <link rel="stylesheet" type="text/css" href="style/<?php echo $pun_user['style'].'.css' ?>" />
  1172. <?php
  1173. $tpl_temp = trim(ob_get_contents());
  1174. $tpl_maint = str_replace('<pun_head>', $tpl_temp, $tpl_maint);
  1175. ob_end_clean();
  1176. // END SUBST - <pun_head>
  1177. // START SUBST - <pun_maint_main>
  1178. ob_start();
  1179. ?>
  1180. <div class="block">
  1181. <h2><?php echo $lang->t('Maintenance') ?></h2>
  1182. <div class="box">
  1183. <div class="inbox">
  1184. <p><?php echo $message ?></p>
  1185. </div>
  1186. </div>
  1187. </div>
  1188. <?php
  1189. $tpl_temp = trim(ob_get_contents());
  1190. $tpl_maint = str_replace('<pun_maint_main>', $tpl_temp, $tpl_maint);
  1191. ob_end_clean();
  1192. // END SUBST - <pun_maint_main>
  1193. // End the transaction
  1194. $db->end_transaction();
  1195. // Close the db connection (and free up any result data)
  1196. $db->close();
  1197. exit($tpl_maint);
  1198. }
  1199. //
  1200. // Display $message and redirect user to $destination_url
  1201. //
  1202. function redirect($destination_url, $message)
  1203. {
  1204. global $db, $cache, $pun_config, $lang, $pun_user;
  1205. // Prefix with base_url (unless there's already a valid URI)
  1206. if (strpos($destination_url, 'http://') !== 0 && strpos($destination_url, 'https://') !== 0 && strpos($destination_url, '/') !== 0)
  1207. $destination_url = get_base_url(true).'/'.$destination_url;
  1208. // Do a little spring cleaning
  1209. $destination_url = preg_replace('%([\r\n])|(\%0[ad])|(;\s*data\s*:)%i', '', $destination_url);
  1210. // If the delay is 0 seconds, we might as well skip the redirect all together
  1211. if ($pun_config['o_redirect_delay'] == '0')
  1212. header('Location: '.str_replace('&amp;', '&', $destination_url));
  1213. // Send no-cache headers
  1214. header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
  1215. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  1216. header('Cache-Control: post-check=0, pre-check=0', false);
  1217. header('Pragma: no-cache'); // For HTTP/1.0 compatibility
  1218. // Send the Content-type header in case the web server is setup to send something else
  1219. header('Content-type: text/html; charset=utf-8');
  1220. if (file_exists(PUN_ROOT.'style/'.$pun_user['style'].'/redirect.tpl'))
  1221. {
  1222. $tpl_file = PUN_ROOT.'style/'.$pun_user['style'].'/redirect.tpl';
  1223. $tpl_inc_dir = PUN_ROOT.'style/'.$pun_user['style'].'/';
  1224. }
  1225. else
  1226. {
  1227. $tpl_file = PUN_ROOT.'include/template/redirect.tpl';
  1228. $tpl_inc_dir = PUN_ROOT.'include/user/';
  1229. }
  1230. $tpl_redir = file_get_contents($tpl_file);
  1231. // START SUBST - <pun_include "*">
  1232. preg_match_all('%<pun_include "([^/\\\\]*?)\.(php[45]?|inc|html?|txt)">%i', $tpl_redir, $pun_includes, PREG_SET_ORDER);
  1233. foreach ($pun_includes as $cur_include)
  1234. {
  1235. ob_start();
  1236. // Allow for overriding user includes, too.
  1237. if (file_exists($tpl_inc_dir.$cur_include[1].'.'.$cur_include[2]))
  1238. require $tpl_inc_dir.$cur_include[1].'.'.$cur_include[2];
  1239. else if (file_exists(PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2]))
  1240. require PUN_ROOT.'include/user/'.$cur_include[1].'.'.$cur_include[2];
  1241. else
  1242. error($lang->t('Pun include error', htmlspecialchars($cur_include[0]), basename($tpl_file)));
  1243. $tpl_temp = ob_get_contents();
  1244. $tpl_redir = str_replace($cur_include[0], $tpl_temp, $tpl_redir);
  1245. ob_end_clean();
  1246. }
  1247. // END SUBST - <pun_include "*">
  1248. // START SUBST - <pun_language>
  1249. $tpl_redir = str_replace('<pun_language>', $lang->t('lang_identifier'), $tpl_redir);
  1250. // END SUBST - <pun_language>
  1251. // START SUBST - <pun_content_direction>
  1252. $tpl_redir = str_replace('<pun_content_direction>', $lang->t('lang_direction'), $tpl_redir);
  1253. // END SUBST - <pun_content_direction>
  1254. // START SUBST - <pun_head>
  1255. ob_start();
  1256. $page_title = array(pun_htmlspecialch

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