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

/include/functions.php

https://gitlab.com/PantherForum/Panther
PHP | 3557 lines | 2655 code | 533 blank | 369 comment | 600 complexity | bb35deb376c836e731ef3695094b0a72 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. * Copyright (C) 2015 Panther (https://www.pantherforum.org)
  4. * based on code by FluxBB copyright (C) 2008-2012 FluxBB
  5. * License: http://www.gnu.org/licenses/gpl.html GPL version 3 or higher
  6. */
  7. //
  8. // Cookie stuff!
  9. //
  10. function check_cookie(&$panther_user)
  11. {
  12. global $db, $panther_config;
  13. $now = time();
  14. // If the cookie is set and it matches the correct pattern, then read the values from it
  15. if (isset($_COOKIE[$panther_config['o_cookie_name']]) && preg_match('%^(\d+)\|([0-9a-fA-F]+)\|(\d+)\|([0-9a-fA-F]+)$%', $_COOKIE[$panther_config['o_cookie_name']], $matches))
  16. {
  17. $cookie = array(
  18. 'user_id' => intval($matches[1]),
  19. 'password_hash' => $matches[2],
  20. 'expiration_time' => intval($matches[3]),
  21. 'cookie_hash' => $matches[4],
  22. );
  23. }
  24. // If it has a non-guest user, and hasn't expired
  25. if (isset($cookie) && $cookie['user_id'] > 1 && $cookie['expiration_time'] > $now)
  26. {
  27. // If the cookie has been tampered with
  28. if (!panther_hash_equals(hash_hmac('sha512', $cookie['user_id'].'|'.$cookie['expiration_time'], $panther_config['o_cookie_seed'].'_cookie_hash'), $cookie['cookie_hash']))
  29. {
  30. $expire = $now + 31536000; // The cookie expires after a year
  31. panther_setcookie(1, panther_hash(uniqid(rand(), true)), $expire);
  32. set_default_user();
  33. return;
  34. }
  35. $data = array(
  36. ':id' => $cookie['user_id'],
  37. );
  38. // Check if there's a user with the user ID and password hash from the cookie
  39. $ps = $db->run('SELECT u.*, g.*, o.logged, o.idle FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.user_id=u.id WHERE u.id=:id', $data);
  40. $panther_user = $ps->fetch();
  41. // If user authorisation failed
  42. if (!isset($panther_user['id']) || !panther_hash_equals(hash_hmac('sha512', $panther_user['login_key'], $panther_config['o_cookie_seed'].'_password_hash'), $cookie['password_hash']))
  43. {
  44. $expire = $now + 31536000; // The cookie expires after a year
  45. panther_setcookie(1, panther_hash(uniqid(rand(), true)), $expire);
  46. set_default_user();
  47. return;
  48. }
  49. // Send a new, updated cookie with a new expiration timestamp
  50. $expire = ($cookie['expiration_time'] > $now + $panther_config['o_timeout_visit']) ? $now + 1209600 : $now + $panther_config['o_timeout_visit'];
  51. panther_setcookie($panther_user['id'], $panther_user['login_key'], $expire);
  52. // Set a default language if the user selected language no longer exists
  53. if (!file_exists(PANTHER_ROOT.'lang/'.$panther_user['language']))
  54. $panther_user['language'] = $panther_config['o_default_lang'];
  55. $style_root = (($panther_config['o_style_path'] != 'style') ? $panther_config['o_style_path'] : PANTHER_ROOT.$panther_config['o_style_path']).'/';
  56. // Set a default style if the user selected style no longer exists
  57. if (!file_exists($style_root.$panther_user['style'].'.css'))
  58. $panther_user['style'] = $panther_config['o_default_style'];
  59. if (!$panther_user['disp_topics'])
  60. $panther_user['disp_topics'] = $panther_config['o_disp_topics_default'];
  61. if (!$panther_user['disp_posts'])
  62. $panther_user['disp_posts'] = $panther_config['o_disp_posts_default'];
  63. // Define this if you want this visit to affect the online list and the users last visit data
  64. if (!defined('PANTHER_QUIET_VISIT'))
  65. {
  66. // Update the online list
  67. if (!$panther_user['logged'])
  68. {
  69. $panther_user['logged'] = $now;
  70. $data = array(
  71. ':id' => $panther_user['id'],
  72. ':ident' => $panther_user['username'],
  73. ':logged' => $panther_user['logged'],
  74. );
  75. // REPLACE INTO avoids a user having two rows in the online table
  76. $db->run('REPLACE INTO '.$db->prefix.'online (user_id, ident, logged) VALUES (:id, :ident, :logged)', $data);
  77. // Reset tracked topics
  78. set_tracked_topics(null);
  79. }
  80. else
  81. {
  82. $data = array(
  83. ':id' => $panther_user['id'],
  84. );
  85. // Special case: We've timed out, but no other user has browsed the forums since we timed out
  86. if ($panther_user['logged'] < ($now-$panther_config['o_timeout_visit']))
  87. {
  88. $update = array(
  89. 'last_visit' => $panther_user['logged'],
  90. );
  91. $db->update('users', $update, 'id=:id', $data);
  92. $panther_user['last_visit'] = $panther_user['logged'];
  93. }
  94. $update = array(
  95. 'logged' => $now,
  96. );
  97. if ($panther_user['idle'] == '1')
  98. $update['idle'] = 0;
  99. $db->update('online', $update, 'user_id=:id', $data);
  100. // Update tracked topics with the current expire time
  101. if (isset($_COOKIE[$panther_config['o_cookie_name'].'_track']))
  102. forum_setcookie($panther_config['o_cookie_name'].'_track', $_COOKIE[$panther_config['o_cookie_name'].'_track'], $now + $panther_config['o_timeout_visit']);
  103. }
  104. }
  105. else
  106. {
  107. if (!$panther_user['logged'])
  108. $panther_user['logged'] = $panther_user['last_visit'];
  109. }
  110. $panther_user['is_guest'] = false;
  111. $panther_user['is_admmod'] = $panther_user['g_id'] == PANTHER_ADMIN || $panther_user['g_moderator'] == '1';
  112. $panther_user['is_admin'] = $panther_user['g_id'] == PANTHER_ADMIN || $panther_user['g_moderator'] == '1' && $panther_user['g_admin'] == '1';
  113. $panther_user['is_bot'] = false;
  114. }
  115. else
  116. set_default_user();
  117. }
  118. function panther_hash_equals($hash, $input)
  119. {
  120. if (function_exists('hash_equals'))
  121. return hash_equals((string)$hash, $input);
  122. $input_length = strlen($input);
  123. if ($input_length !== strlen($hash))
  124. return false;
  125. $result = 0;
  126. for ($i = 0; $i < $input_length; $i++)
  127. $result |= ord($input[$i]) ^ ord($hash[$i]);
  128. return $result === 0;
  129. }
  130. //
  131. // Try to determine the current URL
  132. //
  133. function get_current_url($max_length = 0)
  134. {
  135. $protocol = get_current_protocol();
  136. $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'] : '';
  137. $url = urldecode($protocol.'://'.$_SERVER['HTTP_HOST'].$port.$_SERVER['REQUEST_URI']);
  138. if (strlen($url) <= $max_length || $max_length == 0)
  139. return $url;
  140. // We can't find a short enough url
  141. return null;
  142. }
  143. //
  144. // Fetch the current protocol in use - http or https
  145. //
  146. function get_current_protocol()
  147. {
  148. $protocol = 'http';
  149. // Check if the server is claiming to using HTTPS
  150. if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
  151. $protocol = 'https';
  152. // If we are behind a reverse proxy try to decide which protocol it is using
  153. if (defined('FORUM_BEHIND_REVERSE_PROXY'))
  154. {
  155. // Check if we are behind a Microsoft based reverse proxy
  156. if (!empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) != 'off')
  157. $protocol = 'https';
  158. // Check if we're behind a "proper" reverse proxy, and what protocol it's using
  159. if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']))
  160. $protocol = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']);
  161. }
  162. return $protocol;
  163. }
  164. function check_ssl_state()
  165. {
  166. global $panther_config;
  167. if ($panther_config['o_force_ssl'] == '1' && get_current_protocol() == 'http')
  168. {
  169. header('Location: '.str_replace('http://', 'https://', get_current_url()));
  170. exit;
  171. }
  172. }
  173. //
  174. // Fetch the base_url, optionally support HTTPS and HTTP
  175. //
  176. function get_base_url($support_https = true)
  177. {
  178. global $panther_config;
  179. static $base_url;
  180. if (!$support_https)
  181. return $panther_config['o_base_url'];
  182. if (!isset($base_url))
  183. {
  184. // Make sure we are using the correct protocol
  185. $base_url = str_replace(array('http://', 'https://'), get_current_protocol().'://', $panther_config['o_base_url']);
  186. }
  187. return $base_url;
  188. }
  189. //
  190. // Fetch admin IDs
  191. //
  192. function get_admin_ids()
  193. {
  194. if (file_exists(FORUM_CACHE_DIR.'cache_admins.php'))
  195. include FORUM_CACHE_DIR.'cache_admins.php';
  196. if (!defined('PANTHER_ADMINS_LOADED'))
  197. {
  198. if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
  199. require PANTHER_ROOT.'include/cache.php';
  200. generate_admins_cache();
  201. require FORUM_CACHE_DIR.'cache_admins.php';
  202. }
  203. return $panther_admins;
  204. }
  205. //
  206. // Fill $panther_user with default values (for guests)
  207. //
  208. function set_default_user()
  209. {
  210. global $db, $panther_user, $panther_config;
  211. $remote_addr = get_remote_address();
  212. $remote_addr = isbotex($remote_addr);
  213. $data = array(
  214. ':ident' => $remote_addr,
  215. );
  216. // Fetch guest user
  217. $ps = $db->run('SELECT u.*, g.*, o.logged, o.last_post, o.last_search FROM '.$db->prefix.'users AS u INNER JOIN '.$db->prefix.'groups AS g ON u.group_id=g.g_id LEFT JOIN '.$db->prefix.'online AS o ON o.ident=:ident WHERE u.id=1', $data);
  218. if (!$ps->rowCount())
  219. error_handler(E_ERROR, 'Unable to fetch guest information. Your database must contain both a guest user and a guest user group.', __FILE__, __LINE__);
  220. $panther_user = $ps->fetch();
  221. // Update online list
  222. if (!$panther_user['logged'])
  223. {
  224. $panther_user['logged'] = time();
  225. $data = array(
  226. ':ident' => $remote_addr,
  227. ':logged' => $panther_user['logged'],
  228. );
  229. // REPLACE INTO avoids a user having two rows in the online table
  230. $db->run('REPLACE INTO '.$db->prefix.'online (user_id, ident, logged) VALUES(1, :ident, :logged)', $data);
  231. }
  232. else
  233. {
  234. $update = array(
  235. 'logged' => time(),
  236. );
  237. $data = array(
  238. ':ident' => $remote_addr,
  239. );
  240. $db->update('online', $update, 'ident=:ident', $data);
  241. }
  242. $panther_user['disp_topics'] = $panther_config['o_disp_topics_default'];
  243. $panther_user['disp_posts'] = $panther_config['o_disp_posts_default'];
  244. $panther_user['timezone'] = $panther_config['o_default_timezone'];
  245. $panther_user['dst'] = $panther_config['o_default_dst'];
  246. $panther_user['language'] = $panther_config['o_default_lang'];
  247. $panther_user['style'] = $panther_config['o_default_style'];
  248. $panther_user['is_guest'] = true;
  249. $panther_user['is_admmod'] = false;
  250. $panther_user['is_admin'] = false;
  251. $panther_user['is_bot'] = (strpos($remote_addr, '[Bot]') !== false);
  252. }
  253. //
  254. // Set a cookie, Panther style!
  255. // Wrapper for forum_setcookie
  256. //
  257. function panther_setcookie($user_id, $password_hash, $expire)
  258. {
  259. global $panther_config;
  260. forum_setcookie($panther_config['o_cookie_name'], $user_id.'|'.hash_hmac('sha512', $password_hash, $panther_config['o_cookie_seed'].'_password_hash').'|'.$expire.'|'.hash_hmac('sha512', $user_id.'|'.$expire, $panther_config['o_cookie_seed'].'_cookie_hash'), $expire);
  261. }
  262. //
  263. // Set a cookie, Panther style!
  264. //
  265. function forum_setcookie($name, $value, $expire)
  266. {
  267. global $panther_config;
  268. if ($expire - time() - $panther_config['o_timeout_visit'] < 1)
  269. $expire = 0;
  270. // Enable sending of a P3P header
  271. header('P3P: CP="CUR ADM"');
  272. setcookie($name, $value, $expire, $panther_config['o_cookie_path'], $panther_config['o_cookie_domain'], $panther_config['o_cookie_secure'], true);
  273. }
  274. //
  275. // Check whether the connecting user is banned (and delete any expired bans while we're at it)
  276. //
  277. function check_bans()
  278. {
  279. global $db, $panther_config, $lang_common, $panther_user, $panther_bans;
  280. // Admins and moderators aren't affected
  281. if ($panther_user['is_admmod'] || !$panther_bans)
  282. return;
  283. // Add a dot or a colon (depending on IPv4/IPv6) at the end of the IP address to prevent banned address
  284. // 192.168.0.5 from matching e.g. 192.168.0.50
  285. $user_ip = get_remote_address();
  286. $user_ip .= (strpos($user_ip, '.') !== false) ? '.' : ':';
  287. $bans_altered = false;
  288. $is_banned = false;
  289. foreach ($panther_bans as $cur_ban)
  290. {
  291. // Has this ban expired?
  292. if ($cur_ban['expire'] != '' && $cur_ban['expire'] <= time())
  293. {
  294. $data = array(
  295. ':id' => $cur_ban['id'],
  296. );
  297. $db->delete('bans', 'id=:id', $data);
  298. $bans_altered = true;
  299. continue;
  300. }
  301. if ($cur_ban['username'] != '' && utf8_strtolower($panther_user['username']) == utf8_strtolower($cur_ban['username']))
  302. $is_banned = true;
  303. if ($cur_ban['ip'] != '')
  304. {
  305. $cur_ban_ips = explode(' ', $cur_ban['ip']);
  306. $num_ips = count($cur_ban_ips);
  307. for ($i = 0; $i < $num_ips; ++$i)
  308. {
  309. // Add the proper ending to the ban
  310. if (strpos($user_ip, '.') !== false)
  311. $cur_ban_ips[$i] = $cur_ban_ips[$i].'.';
  312. else
  313. $cur_ban_ips[$i] = $cur_ban_ips[$i].':';
  314. if (substr($user_ip, 0, strlen($cur_ban_ips[$i])) == $cur_ban_ips[$i])
  315. {
  316. $is_banned = true;
  317. break;
  318. }
  319. }
  320. }
  321. if ($is_banned)
  322. {
  323. $data = array(
  324. ':ident' => $panther_user['username'],
  325. );
  326. $db->delete('online', 'ident=:ident', $data);
  327. message($lang_common['Ban message'].' '.(($cur_ban['expire'] != '') ? $lang_common['Ban message 2'].' '.strtolower(format_time($cur_ban['expire'], true)).'. ' : '').(($cur_ban['message'] != '') ? $lang_common['Ban message 3'].'<br /><br /><strong>'.$cur_ban['message'].'</strong><br /><br />' : '<br /><br />').$lang_common['Ban message 4'].' '.$panther_config['o_admin_email'], true);
  328. }
  329. }
  330. // If we removed any expired bans during our run-through, we need to regenerate the bans cache
  331. if ($bans_altered)
  332. {
  333. if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
  334. require PANTHER_ROOT.'include/cache.php';
  335. generate_bans_cache();
  336. }
  337. }
  338. //
  339. // Check username
  340. //
  341. function check_username($username, $exclude_id = null)
  342. {
  343. global $db, $panther_config, $errors, $lang_prof_reg, $lang_register, $lang_common, $panther_bans;
  344. // Include UTF-8 function
  345. require_once PANTHER_ROOT.'include/utf8/strcasecmp.php';
  346. // Convert multiple whitespace characters into one (to prevent people from registering with indistinguishable usernames)
  347. $username = preg_replace('%\s+%s', ' ', $username);
  348. // Validate username
  349. if (panther_strlen($username) < 2)
  350. $errors[] = $lang_prof_reg['Username too short'];
  351. else if (panther_strlen($username) > 25) // This usually doesn't happen since the form element only accepts 25 characters
  352. $errors[] = $lang_prof_reg['Username too long'];
  353. else if (!strcasecmp($username, 'Guest') || !utf8_strcasecmp($username, $lang_common['Guest']))
  354. $errors[] = $lang_prof_reg['Username guest'];
  355. 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))
  356. $errors[] = $lang_prof_reg['Username IP'];
  357. else if ((strpos($username, '[') !== false || strpos($username, ']') !== false) && strpos($username, '\'') !== false && strpos($username, '"') !== false)
  358. $errors[] = $lang_prof_reg['Username reserved chars'];
  359. 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))
  360. $errors[] = $lang_prof_reg['Username BBCode'];
  361. // Check username for any censored words
  362. if ($panther_config['o_censoring'] == '1' && censor_words($username) != $username)
  363. $errors[] = $lang_register['Username censor'];
  364. $where_cond = '(UPPER(username)=UPPER(:username) OR UPPER(username)=UPPER(:username2)) AND id>1';
  365. $data = array(
  366. ':username' => $username,
  367. ':username2' => ucp_preg_replace('%[^\p{L}\p{N}]%u', '', $username),
  368. );
  369. // Check that the username (or a too similar username) is not already registered
  370. if (!is_null($exclude_id))
  371. {
  372. $where_cond .= ' AND id!=:id';
  373. $data[':id'] = $exclude_id;
  374. }
  375. $ps = $db->select('users', 'username', $data, $where_cond);
  376. if ($ps->rowCount())
  377. {
  378. $busy = $ps->fetchColumn();
  379. $errors[] = $lang_register['Username dupe 1'].' '.$busy.'. '.$lang_register['Username dupe 2'];
  380. }
  381. // Check username for any banned usernames
  382. foreach ($panther_bans as $cur_ban)
  383. {
  384. if ($cur_ban['username'] != '' && utf8_strtolower($username) == utf8_strtolower($cur_ban['username']))
  385. {
  386. $errors[] = $lang_prof_reg['Banned username'];
  387. break;
  388. }
  389. }
  390. }
  391. //
  392. // Update "Users online"
  393. //
  394. function update_users_online()
  395. {
  396. global $db, $panther_config, $panther_user;
  397. $cur_position = substr($_SERVER['REQUEST_URI'], 1);
  398. $server_base = dirname($_SERVER['PHP_SELF']);
  399. if ($server_base !== '/')
  400. $cur_position = substr($cur_position, strlen($server_base));
  401. $cur_position = ($cur_position == '') ? 'index.php' : $cur_position;
  402. $now = time();
  403. $online['users'] = $online['guests'] = array();
  404. // Fetch all online list entries that are older than "o_timeout_online"
  405. $ps = $db->run('SELECT o.user_id, o.ident, o.logged, o.idle, u.group_id FROM '.$db->prefix.'online AS o LEFT JOIN '.$db->prefix.'users AS u ON o.user_id=u.id');
  406. foreach ($ps as $cur_user)
  407. {
  408. if ($cur_user['logged'] < ($now - $panther_config['o_timeout_online']))
  409. {
  410. // If the entry is a guest, delete it
  411. if ($cur_user['user_id'] == '1')
  412. {
  413. $data = array(
  414. ':ident' => $cur_user['ident']
  415. );
  416. $db->delete('online', 'ident=:ident', $data);
  417. }
  418. else
  419. {
  420. // 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
  421. if ($cur_user['logged'] < ($now - $panther_config['o_timeout_visit']))
  422. {
  423. $update = array(
  424. 'last_visit' => $cur_user['logged'],
  425. );
  426. $data = array(
  427. ':id' => $cur_user['user_id'],
  428. );
  429. $db->update('users', $update, 'id=:id', $data);
  430. $db->delete('online', 'user_id=:id', $data);
  431. }
  432. }
  433. }
  434. else
  435. {
  436. if ($cur_user['user_id'] == 1)
  437. $online['guests'][] = array('ident' => $cur_user['ident'], 'group_id' => PANTHER_GUEST);
  438. else
  439. $online['users'][$cur_user['user_id']] = array('username' => $cur_user['ident'], 'group_id' => $cur_user['group_id'], 'id' => $cur_user['user_id']);
  440. }
  441. }
  442. if (!$panther_user['is_bot'])
  443. {
  444. $update = array(
  445. 'currently' => $cur_position,
  446. );
  447. $data = array();
  448. if ($panther_user['is_guest'])
  449. {
  450. $field = 'ident';
  451. $data[':ident'] = get_remote_address();
  452. }
  453. else
  454. {
  455. $field = 'user_id';
  456. $data[':ident'] = $panther_user['id'];
  457. }
  458. $db->update('online', $update, $field.'=:ident', $data);
  459. }
  460. return $online;
  461. }
  462. //
  463. // Display the profile navigation menu
  464. //
  465. function generate_profile_menu($page = '')
  466. {
  467. global $lang_profile, $panther_config, $panther_user, $id, $panther_url;
  468. $sections = array(
  469. array('page' => 'essentials', 'link' => panther_link($panther_url['profile_essentials'], array($id)), 'lang' => $lang_profile['Section essentials']),
  470. array('page' => 'personal', 'link' => panther_link($panther_url['profile_personal'], array($id)), 'lang' => $lang_profile['Section personal']),
  471. array('page' => 'messaging', 'link' => panther_link($panther_url['profile_messaging'], array($id)), 'lang' => $lang_profile['Section messaging']),
  472. );
  473. if ($panther_config['o_avatars'] == '1' || $panther_config['o_signatures'] == '1')
  474. $sections[] = array('page' => 'personality', 'link' => panther_link($panther_url['profile_personality'], array($id)), 'lang' => $lang_profile['Section personality']);
  475. $sections[] = array('page' => 'display', 'link' => panther_link($panther_url['profile_display'], array($id)), 'lang' => $lang_profile['Section display']);
  476. $sections[] = array('page' => 'privacy', 'link' => panther_link($panther_url['profile_privacy'], array($id)), 'lang' => $lang_profile['Section privacy']);
  477. $sections[] = array('page' => 'view', 'link' => panther_link($panther_url['profile_view'], array($id)), 'lang' => $lang_profile['Section view']);
  478. if ($panther_user['is_admin'] || ($panther_user['g_moderator'] == '1' && $panther_user['g_mod_ban_users'] == '1'))
  479. $sections[] = array('page' => 'admin', 'link' => panther_link($panther_url['profile_admin'], array($id)), 'lang' => $lang_profile['Section admin']);
  480. $tpl = load_template('profile_sidebar.tpl');
  481. echo $tpl->render(
  482. array(
  483. 'lang_profile' => $lang_profile,
  484. 'sections' => $sections,
  485. 'page' => $page,
  486. )
  487. );
  488. }
  489. //
  490. // Display PM menu
  491. //
  492. function generate_pm_menu($page = 2) // Default to inbox
  493. {
  494. global $panther_url, $lang_pm, $panther_user, $db;
  495. static $folders;
  496. $percent = ($panther_user['g_pm_limit'] != '0') ? round(ceil($panther_user['num_pms'] / $panther_user['g_pm_limit']*100), 0) : 0;
  497. $limit = ($panther_user['g_pm_limit'] == '0') ? '&infin;' : $panther_user['g_pm_limit'];
  498. $data = array(
  499. ':uid' => $panther_user['id'],
  500. );
  501. $folders = array();
  502. $ps = $db->select('folders', 'name, id', $data, 'user_id=:uid OR user_id=1', 'id, user_id ASC');
  503. foreach ($ps as $folder)
  504. {
  505. $data = array(
  506. ':id' => $folder['id'],
  507. ':uid' => $panther_user['id'],
  508. );
  509. $ps1 = $db->select('pms_data', 'COUNT(topic_id)', $data, 'user_id=:uid AND deleted=0 AND (folder_id=:id '.(($folder['id'] == 1) ? 'OR viewed=0)' : ')'));
  510. $amount = $ps1->fetchColumn();
  511. $folders[] = array(
  512. 'id' => $folder['id'],
  513. 'link' => panther_link($panther_url['box'], array($folder['id'])),
  514. 'name' => $folder['name'],
  515. 'amount' => $amount,
  516. );
  517. }
  518. $tpl = load_template('pm_sidebar.tpl');
  519. return $tpl->render(
  520. array(
  521. 'lang_pm' => $lang_pm,
  522. 'folders' => $folders,
  523. 'percent' => $percent,
  524. 'num_pms' => forum_number_format($panther_user['num_pms']),
  525. 'limit' => forum_number_format($limit),
  526. 'blocked_link' => panther_link($panther_url['pms_blocked']),
  527. 'folders_link' => panther_link($panther_url['pms_folders']),
  528. 'page' => $page,
  529. )
  530. );
  531. }
  532. //
  533. // Outputs markup to display a user's avatar
  534. //
  535. function generate_avatar_markup($user_id, $user_email, $use_gravatar = 0, $size = array())
  536. {
  537. global $panther_config;
  538. static $user_avatar_cache = array();
  539. $avatar_path = ($panther_config['o_avatars_dir'] != '') ? $panther_config['o_avatars_path'] : PANTHER_ROOT.$panther_config['o_avatars_path'];
  540. $avatar_dir = ($panther_config['o_avatars_dir'] != '') ? $panther_config['o_avatars_dir'] : get_base_url(true).'/'.$panther_config['o_avatars_path'];
  541. if (!isset($user_avatar_cache[$user_id]))
  542. {
  543. if ($use_gravatar == 1)
  544. {
  545. $params = (count($size) == 2) ? array($size[0], $size[1]) : array($panther_config['o_avatars_width'], $panther_config['o_avatars_height']);
  546. $user_avatar_cache[$user_id] = '<img src="https://www.gravatar.com/avatar.php?gravatar_id='.md5(strtolower($user_email)).'&amp;size='.$params[0].'" width="'.$params[0].'" height="'.$params[1].'" alt="" />';
  547. }
  548. else if ($panther_config['o_avatar_upload'] == '1')
  549. {
  550. $filetypes = array('jpg', 'gif', 'png');
  551. foreach ($filetypes as $cur_type)
  552. {
  553. $path = $avatar_path.$user_id.'.'.$cur_type;
  554. $url = $avatar_dir.$user_id.'.'.$cur_type;
  555. if (file_exists($path) && $img_size = getimagesize($path))
  556. {
  557. $size = (count($size) == 2 ? 'width="'.$size[0].'" height="'.$size[1].'"' : $img_size[3]);
  558. $user_avatar_cache[$user_id] = '<img src="'.$url.'?m='.filemtime($path).'" '.$size.' alt="" />';
  559. break;
  560. }
  561. }
  562. }
  563. // If there's no avatar set, we mustn't have one uploaded. Set the default!
  564. if (!isset($user_avatar_cache[$user_id]))
  565. {
  566. $path = $avatar_path.'1.'.$panther_config['o_avatar'];
  567. $url = $avatar_dir.'1.'.$panther_config['o_avatar'];
  568. $img_size = getimagesize($path);
  569. $size = (count($size) == 2 ? 'width="'.$size[0].'" height="'.$size[1].'"' : $img_size[3]);
  570. $user_avatar_cache[$user_id] = '<img src="'.$url.'?m='.filemtime($path).'" '.$size.' alt="" />';
  571. }
  572. }
  573. return $user_avatar_cache[$user_id];
  574. }
  575. //
  576. // Generate browser's title
  577. //
  578. function generate_page_title($page_title, $p = null)
  579. {
  580. global $lang_common;
  581. if (!is_array($page_title))
  582. $page_title = array($page_title);
  583. $page_title = array_reverse($page_title);
  584. if ($p > 1)
  585. $page_title[0] .= ' ('.sprintf($lang_common['Page'], forum_number_format($p)).')';
  586. $crumbs = implode($lang_common['Title separator'], $page_title);
  587. return $crumbs;
  588. }
  589. //
  590. // Save array of tracked topics in cookie
  591. //
  592. function set_tracked_topics($tracked_topics)
  593. {
  594. global $panther_config;
  595. $cookie_data = '';
  596. if (!empty($tracked_topics))
  597. {
  598. // Sort the arrays (latest read first)
  599. arsort($tracked_topics['topics'], SORT_NUMERIC);
  600. arsort($tracked_topics['forums'], SORT_NUMERIC);
  601. // Homebrew serialization (to avoid having to run unserialize() on cookie data)
  602. foreach ($tracked_topics['topics'] as $id => $timestamp)
  603. $cookie_data .= 't'.$id.'='.$timestamp.';';
  604. foreach ($tracked_topics['forums'] as $id => $timestamp)
  605. $cookie_data .= 'f'.$id.'='.$timestamp.';';
  606. // Enforce a byte size limit (4096 minus some space for the cookie name - defaults to 4048)
  607. if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
  608. {
  609. $cookie_data = substr($cookie_data, 0, FORUM_MAX_COOKIE_SIZE);
  610. $cookie_data = substr($cookie_data, 0, strrpos($cookie_data, ';')).';';
  611. }
  612. }
  613. forum_setcookie($panther_config['o_cookie_name'].'_track', $cookie_data, time() + $panther_config['o_timeout_visit']);
  614. $_COOKIE[$panther_config['o_cookie_name'].'_track'] = $cookie_data; // Set it directly in $_COOKIE as well
  615. }
  616. //
  617. // Extract array of tracked topics from cookie
  618. //
  619. function get_tracked_topics()
  620. {
  621. global $panther_config;
  622. $cookie_data = isset($_COOKIE[$panther_config['o_cookie_name'].'_track']) ? $_COOKIE[$panther_config['o_cookie_name'].'_track'] : false;
  623. if (!$cookie_data)
  624. return array('topics' => array(), 'forums' => array());
  625. if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
  626. return array('topics' => array(), 'forums' => array());
  627. // Unserialize data from cookie
  628. $tracked_topics = array('topics' => array(), 'forums' => array());
  629. $temp = explode(';', $cookie_data);
  630. foreach ($temp as $t)
  631. {
  632. $type = substr($t, 0, 1) == 'f' ? 'forums' : 'topics';
  633. $id = intval(substr($t, 1));
  634. $timestamp = intval(substr($t, strpos($t, '=') + 1));
  635. if ($id > 0 && $timestamp > 0)
  636. $tracked_topics[$type][$id] = $timestamp;
  637. }
  638. return $tracked_topics;
  639. }
  640. //
  641. // Update posts, topics, last_post, last_post_id and last_poster for a forum
  642. //
  643. function update_forum($forum_id)
  644. {
  645. global $db;
  646. $data = array(
  647. ':id' => $forum_id,
  648. );
  649. $ps = $db->select('topics', 'COUNT(id), SUM(num_replies)', $data, 'forum_id=:id AND approved=1 AND deleted=0');
  650. list($num_topics, $num_posts) = $ps->fetch(PDO::FETCH_NUM);
  651. $num_posts = $num_posts + $num_topics; // $num_posts is only the sum of all replies (we have to add the topic posts)
  652. $data = array(
  653. ':id' => $forum_id
  654. );
  655. $ps = $db->select('topics', 'last_post, last_post_id, last_poster, subject, id', $data, 'forum_id=:id AND approved=1 AND deleted=0 AND moved_to IS NULL ORDER BY last_post DESC LIMIT 1');
  656. if ($ps->rowCount()) // There are topics in the forum
  657. {
  658. list($last_post, $last_post_id, $last_poster, $last_topic, $last_topic_id) = $ps->fetch(PDO::FETCH_NUM);
  659. $update = array(
  660. 'num_topics' => $num_topics,
  661. 'num_posts' => $num_posts,
  662. 'last_post' => $last_post,
  663. 'last_post_id' => $last_post_id,
  664. 'last_topic' => $last_topic,
  665. 'last_topic_id' => $last_topic_id,
  666. 'last_poster' => $last_poster,
  667. );
  668. $data = array(
  669. ':id' => $forum_id,
  670. );
  671. $db->update('forums', $update, 'id=:id', $data);
  672. }
  673. else // There are no topics
  674. {
  675. $data = array(
  676. ':num_topics' => $num_topics,
  677. ':num_posts' => $num_posts,
  678. ':id' => $forum_id,
  679. );
  680. // Annoyingly PDO does not allow NULL values to be added in prepared statements. When added it becomes 'NULL', so we have to run the query manually instead.
  681. $db->run('UPDATE '.$db->prefix.'forums SET num_topics=:num_topics, num_posts=:num_posts, last_post=NULL, last_post_id=NULL, last_poster=NULL, last_topic=\'\', last_topic_id=0 WHERE id=:id', $data);
  682. }
  683. }
  684. //
  685. // Deletes any avatars owned by the specified user ID
  686. //
  687. function delete_avatar($user_id)
  688. {
  689. global $panther_config;
  690. $filetypes = array('jpg', 'gif', 'png');
  691. $avatar_path = ($panther_config['o_avatars_dir'] != '') ? $panther_config['o_avatars_path'] : PANTHER_ROOT.$panther_config['o_avatars_path'];
  692. // Delete user avatar
  693. foreach ($filetypes as $cur_type)
  694. {
  695. if (file_exists($avatar_path.$user_id.'.'.$cur_type))
  696. @unlink($avatar_path.$user_id.'.'.$cur_type);
  697. }
  698. }
  699. //
  700. // Delete a topic and all of its posts
  701. //
  702. function delete_topic($topic_id)
  703. {
  704. global $db, $panther_config;
  705. // Delete the topic and any redirect topics
  706. attach_delete_thread($topic_id);
  707. $data = array(
  708. ':id' => $topic_id,
  709. );
  710. $topic = array(
  711. ':id' => $topic_id,
  712. ':moved_to' => $topic_id,
  713. );
  714. $update = array(
  715. 'deleted' => 1,
  716. );
  717. $post_ids = array();
  718. $db->update('topics', $update, 'id=:id OR moved_to=:moved_to', $topic);
  719. $db->delete('polls', 'topic_id=:id', $data);
  720. // Get all post IDs from this topic.
  721. $ps = $db->select('posts', 'id', $data, 'topic_id=:id');
  722. foreach ($ps as $cur_post)
  723. $post_ids[] = $cur_post['id'];
  724. // Make sure we have a list of post IDs
  725. if (!empty($post_ids))
  726. {
  727. strip_search_index($post_ids); // Should be an array
  728. $db->update('posts', $update, 'topic_id=:id', $data);
  729. }
  730. if ($panther_config['o_delete_full'] == '1')
  731. permanently_delete_topic($topic_id);
  732. }
  733. //
  734. // Delete a single post
  735. //
  736. function delete_post($post_id, $topic_id)
  737. {
  738. global $db, $panther_config;
  739. $topic_data = array(
  740. ':id' => $topic_id,
  741. );
  742. $post_data = array(
  743. ':id' => $post_id,
  744. );
  745. $ps = $db->select('posts', 'id, poster, posted', $topic_data, 'topic_id=:id AND approved=1 AND deleted=0', 'id DESC LIMIT 2');
  746. list($last_id, ,) = $ps->fetch(PDO::FETCH_NUM);
  747. list($second_last_id, $second_poster, $second_posted) = $ps->fetch(PDO::FETCH_NUM);
  748. // Delete the post
  749. attach_delete_post($post_id);
  750. $update = array(
  751. 'deleted' => 1,
  752. );
  753. $db->update('posts', $update, 'id=:id', $post_data);
  754. strip_search_index(array($post_id));
  755. // Count number of replies in the topic
  756. $ps = $db->select('posts', 'COUNT(id)', $topic_data, 'topic_id=:id AND approved=1 AND deleted=0');
  757. $num_replies = $ps->fetchColumn() - 1; // Decrement the deleted post
  758. // If the message we deleted is the most recent in the topic (at the end of the topic)
  759. if ($last_id == $post_id)
  760. {
  761. // If there is a $second_last_id there is more than 1 reply to the topic
  762. if (!empty($second_last_id))
  763. {
  764. $update = array(
  765. 'last_post' => $second_posted,
  766. 'last_post_id' => $second_last_id,
  767. 'last_poster' => $second_poster,
  768. 'num_replies' => $num_replies,
  769. );
  770. $data = array(
  771. ':id' => $topic_id,
  772. );
  773. $db->update('topics', $update, 'id=:id', $data);
  774. }
  775. else
  776. {
  777. $data = array(
  778. ':id' => $topic_id,
  779. ':num_replies' => $num_replies-1,
  780. );
  781. // We deleted the only reply, so now last_post/last_post_id/last_poster is posted/id/poster from the topic itself
  782. $db->run('UPDATE '.$db->prefix.'topics SET last_post=posted, last_post_id=id, last_poster=poster, num_replies=:num_replies WHERE id=:id', $data);
  783. }
  784. }
  785. else // Otherwise we just decrement the reply counter
  786. {
  787. $update = array(
  788. 'num_replies' => $num_replies,
  789. );
  790. $db->update('topics', $update, 'id=:id', $topic_data);
  791. }
  792. if ($panther_config['o_delete_full'] == '1')
  793. permanently_delete_post($post_id);
  794. }
  795. //
  796. // Permanently delete a single post
  797. //
  798. function permanently_delete_post($id)
  799. {
  800. global $db;
  801. $data = array(
  802. ':id' => $id,
  803. );
  804. $db->delete('posts', 'id=:id AND deleted=1', $data); // Since we've already stripped the search index, all we need to do is delete the row
  805. }
  806. //
  807. // Permanently delete a topic
  808. //
  809. function permanently_delete_topic($id)
  810. {
  811. global $db;
  812. $data = array(
  813. ':id' => $id,
  814. ':moved_to' => $id,
  815. );
  816. $db->delete('topics', '(id=:id OR moved_to=:moved_to) AND deleted=1', $data);
  817. unset($data[':moved_to']);
  818. $db->delete('posts', 'topic_id=? AND deleted=1', array_values($data));
  819. // Delete any subscriptions for this topic
  820. $db->delete('topic_subscriptions', 'topic_id=?', array_values($data));
  821. }
  822. //
  823. // Delete every .php file in the forum's cache directory
  824. //
  825. function forum_clear_cache()
  826. {
  827. $files = array_diff(scandir(FORUM_CACHE_DIR), array('.', '..'));
  828. foreach ($files as $file)
  829. {
  830. if (substr($file, -4) == '.php')
  831. @unlink(FORUM_CACHE_DIR.$file);
  832. }
  833. }
  834. //
  835. // Replace censored words in $text
  836. //
  837. function censor_words($text)
  838. {
  839. global $db;
  840. static $search_for, $replace_with;
  841. // If not already built in a previous call, build an array of censor words and their replacement text
  842. if (!isset($search_for))
  843. {
  844. if (file_exists(FORUM_CACHE_DIR.'cache_censoring.php'))
  845. include FORUM_CACHE_DIR.'cache_censoring.php';
  846. if (!defined('PANTHER_CENSOR_LOADED'))
  847. {
  848. if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
  849. require PANTHER_ROOT.'include/cache.php';
  850. generate_censoring_cache();
  851. require FORUM_CACHE_DIR.'cache_censoring.php';
  852. }
  853. }
  854. if (!empty($search_for))
  855. $text = substr(ucp_preg_replace($search_for, $replace_with, ' '.$text.' '), 1, -1);
  856. return $text;
  857. }
  858. //
  859. // Determines the correct title for $user
  860. // $user must contain the elements 'username', 'title', 'posts', 'g_id' and 'g_user_title'
  861. //
  862. function get_title($user)
  863. {
  864. global $panther_bans, $lang_common, $panther_config;
  865. static $ban_list, $panther_ranks;
  866. // If not already built in a previous call, build an array of lowercase banned usernames
  867. if (empty($ban_list))
  868. {
  869. $ban_list = array();
  870. foreach ($panther_bans as $cur_ban)
  871. $ban_list[] = utf8_strtolower($cur_ban['username']);
  872. }
  873. // If not already loaded in a previous call, load the cached ranks
  874. if ($panther_config['o_ranks'] == '1' && !defined('PANTHER_RANKS_LOADED'))
  875. {
  876. if (file_exists(FORUM_CACHE_DIR.'cache_ranks.php'))
  877. include FORUM_CACHE_DIR.'cache_ranks.php';
  878. if (!defined('PANTHER_RANKS_LOADED'))
  879. {
  880. if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
  881. require PANTHER_ROOT.'include/cache.php';
  882. generate_ranks_cache();
  883. require FORUM_CACHE_DIR.'cache_ranks.php';
  884. }
  885. }
  886. // If the user has a custom title
  887. if ($user['title'] != '')
  888. $user_title = $user['title'];
  889. // If the user is banned
  890. else if (in_array(utf8_strtolower($user['username']), $ban_list))
  891. $user_title = $lang_common['Banned'];
  892. // If the user group has a default user title
  893. else if ($user['g_user_title'] != '')
  894. $user_title = $user['g_user_title'];
  895. // If the user is a guest
  896. else if ($user['g_id'] == PANTHER_GUEST)
  897. $user_title = $lang_common['Guest'];
  898. // If nothing else helps, we assign the default
  899. else
  900. {
  901. // Are there any ranks?
  902. if ($panther_config['o_ranks'] == '1' && !empty($panther_ranks))
  903. {
  904. foreach ($panther_ranks as $cur_rank)
  905. {
  906. if ($user['num_posts'] >= $cur_rank['min_posts'])
  907. $user_title = $cur_rank['rank'];
  908. }
  909. }
  910. // If the user didn't "reach" any rank (or if ranks are disabled), we assign the default
  911. if (!isset($user_title))
  912. $user_title = $lang_common['Member'];
  913. }
  914. return $user_title;
  915. }
  916. //
  917. // Generate a string with numbered links (for multipage scripts)
  918. //
  919. function paginate($num_pages, $cur_page, $link, $args = null)
  920. {
  921. global $lang_common, $panther_config, $panther_url;
  922. $pages = array();
  923. $link_to_all = false;
  924. // If $cur_page == -1, we link to all pages (used in viewforum.php)
  925. if ($cur_page == -1)
  926. {
  927. $cur_page = 1;
  928. $link_to_all = true;
  929. }
  930. if ($num_pages > 1)
  931. {
  932. if ($cur_page > 1)
  933. $pages[] = array('item' => true, 'href' => str_replace('#', '', get_sublink($link, $panther_url['page'], ($cur_page - 1), $args)), 'current' => $lang_common['Previous']);
  934. if ($cur_page > 3)
  935. {
  936. $pages[] = array('item' => (empty($pages) ? true : false), 'href' => $link, 'current' => 1);
  937. if ($cur_page > 5)
  938. $pages[] = $lang_common['Spacer'];
  939. }
  940. // Don't ask me how the following works. It just does, OK? =)
  941. 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)
  942. {
  943. if ($current < 1 || $current > $num_pages)
  944. continue;
  945. else if ($current != $cur_page || $link_to_all)
  946. $pages[] = array('item' => (empty($pages) ? true : false), 'href' => str_replace('#', '', get_sublink($link, $panther_url['page'], $current, $args)), 'current' => forum_number_format($current));
  947. else
  948. $pages[] = array('item' => (empty($pages) ? true : false), 'current' => forum_number_format($current));
  949. }
  950. if ($cur_page <= ($num_pages-3))
  951. {
  952. if ($cur_page != ($num_pages-3) && $cur_page != ($num_pages-4))
  953. $pages[] = $lang_common['Spacer'];
  954. $pages[] = array('item' => (empty($pages) ? true : false), 'href' => get_sublink($link, $panther_url['page'], $num_pages, $args), 'current' => forum_number_format($num_pages));
  955. }
  956. // Add a next page link
  957. if ($num_pages > 1 && !$link_to_all && $cur_page < $num_pages)
  958. $pages[] = array('item' => (empty($pages) ? true : false), 'rel' => 'next', 'href' => get_sublink($link, $panther_url['page'], ($cur_page + 1), $args), 'current' => $lang_common['Next']);
  959. }
  960. $tpl = load_template('pagination.tpl');
  961. return $tpl->render(
  962. array(
  963. 'num_pages' => $num_pages,
  964. 'cur_page' => $cur_page,
  965. 'pages' => $pages,
  966. 'link' => $link,
  967. )
  968. );
  969. }
  970. //
  971. // Display a message
  972. //
  973. function message($message, $no_back_link = false, $http_status = null)
  974. {
  975. global $db, $lang_common, $panther_config, $panther_start, $tpl_main, $panther_user, $panther_url;
  976. // Did we receive a custom header?
  977. if (!is_null($http_status))
  978. header('HTTP/1.1 '.$http_status);
  979. if (!defined('PANTHER_HEADER'))
  980. {
  981. $page_title = array($panther_config['o_board_title'], $lang_common['Info']);
  982. define('PANTHER_ACTIVE_PAGE', 'index');
  983. require PANTHER_ROOT.'header.php';
  984. }
  985. $tpl = load_template('message.tpl');
  986. echo $tpl->render(
  987. array(
  988. 'lang_common' => $lang_common,
  989. 'message' => $message,
  990. 'no_back_link' => $no_back_link,
  991. )
  992. );
  993. require PANTHER_ROOT.'footer.php';
  994. }
  995. //
  996. // Format a time string according to $time_format and time zones
  997. //
  998. function format_time($timestamp, $date_only = false, $date_format = null, $time_format = null, $time_only = false, $no_text = false)
  999. {
  1000. global $lang_common, $panther_user, $forum_date_formats, $forum_time_formats;
  1001. if ($timestamp == '')
  1002. return $lang_common['Never'];
  1003. $diff = ($panther_user['timezone'] + $panther_user['dst']) * 3600;
  1004. $timestamp += $diff;
  1005. $now = time();
  1006. if(is_null($date_format))
  1007. $date_format = $forum_date_formats[$panther_user['date_format']];
  1008. if(is_null($time_format))
  1009. $time_format = $forum_time_formats[$panther_user['time_format']];
  1010. $date = gmdate($date_format, $timestamp);
  1011. $today = gmdate($date_format, $now+$diff);
  1012. $yesterday = gmdate($date_format, $now+$diff-86400);
  1013. if (!$no_text)
  1014. {
  1015. if ($date == $today)
  1016. $date = $lang_common['Today'];
  1017. else if ($date == $yesterday)
  1018. $date = $lang_common['Yesterday'];
  1019. }
  1020. if ($date_only)
  1021. return $date;
  1022. else if ($time_only)
  1023. return gmdate($time_format, $timestamp);
  1024. else
  1025. return $date.' '.gmdate($time_format, $timestamp);
  1026. }
  1027. //
  1028. // A wrapper for PHP's number_format function
  1029. //
  1030. function forum_number_format($number, $decimals = 0)
  1031. {
  1032. global $lang_common;
  1033. return is_numeric($number) ? number_format($number, $decimals, $lang_common['lang_decimal_point'], $lang_common['lang_thousands_sep']) : $number;
  1034. }
  1035. //
  1036. // Generate a random key of length $len
  1037. //
  1038. function random_key($len, $readable = false, $hash = false)
  1039. {
  1040. if (!function_exists('secure_random_bytes'))
  1041. include PANTHER_ROOT.'include/srand.php';
  1042. $key = secure_random_bytes($len);
  1043. if ($hash)
  1044. return substr(bin2hex($key), 0, $len);
  1045. else if ($readable)
  1046. {
  1047. $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  1048. $result = '';
  1049. for ($i = 0; $i < $len; ++$i)
  1050. $result .= substr($chars, (ord($key[$i]) % strlen($chars)), 1);
  1051. return $result;
  1052. }
  1053. return $key;
  1054. }
  1055. //
  1056. // Make sure that user is using a valid token
  1057. //
  1058. function confirm_referrer($script, $use_ip = true)
  1059. {
  1060. global $lang_common, $panther_user;
  1061. // Yeah, pretty complex ternary =)
  1062. $sent_hash = ((isset($_POST['csrf_token'])) ? panther_trim($_POST['csrf_token']) : (isset($_GET['csrf_token']) ? panther_trim($_GET['csrf_token']) : ''));
  1063. if (!panther_hash_equals(generate_csrf_token($script, $use_ip), $sent_hash))
  1064. message($lang_common['Bad referrer']);
  1065. }
  1066. //
  1067. // Generate a csrf token
  1068. //
  1069. function generate_csrf_token($script = 'nothing', $use_ip = true)
  1070. {
  1071. global $panther_user;
  1072. $script = ($script != 'nothing') ? $script : panther_trim(basename($_SERVER['SCRIPT_NAME']));
  1073. return panther_hash($panther_user['id'].$script.$panther_user['salt'].(($use_ip ? get_remote_address() : '')).$panther_user['login_key']);
  1074. }
  1075. //
  1076. // Validate the given redirect URL, use the fallback otherwise
  1077. //
  1078. function validate_redirect($redirect_url, $fallback_url)
  1079. {
  1080. $referrer = parse_url(strtolower($redirect_url));
  1081. // Make sure the host component exists
  1082. if (!isset($referrer['host']))
  1083. $referrer['host'] = '';
  1084. // Remove www subdomain if it exists
  1085. if (strpos($referrer['host'], 'www.') === 0)
  1086. $referrer['host'] = substr($referrer['host'], 4);
  1087. // Make sure the path component exists
  1088. if (!isset($referrer['path']))
  1089. $referrer['path'] = '';
  1090. $valid = parse_url(strtolower(get_base_url()));
  1091. // Remove www subdomain if it exists
  1092. if (strpos($valid['host'], 'www.') === 0)
  1093. $valid['host'] = substr($valid['host'], 4);
  1094. // Make sure the path component exists
  1095. if (!isset($valid['path']))
  1096. $valid['path'] = '';
  1097. if ($referrer['host'] == $valid['host'] && preg_match('%^'.preg_quote($valid['path'], '%').'/(.*?)\.php%i', $referrer['path']))
  1098. return $redirect_url;
  1099. else
  1100. return $fallback_url;
  1101. }
  1102. //
  1103. // Generate a random password of length $len
  1104. // Compatibility wrapper for random_key
  1105. //
  1106. function random_pass($len)
  1107. {
  1108. return random_key($len, true);
  1109. }
  1110. //
  1111. // Compute a hash of $str
  1112. //
  1113. function panther_hash($str)
  1114. {
  1115. return hash('sha512', $str);
  1116. }
  1117. //
  1118. // Try to determine the correct remote IP-address
  1119. //
  1120. function get_remote_address()
  1121. {
  1122. $remote_addr = $_SERVER['REMOTE_ADDR'];
  1123. // If we are behind a reverse proxy try to find the real users IP
  1124. if (defined('FORUM_BEHIND_REVERSE_PROXY'))
  1125. {
  1126. if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
  1127. {
  1128. // The general format of the field is:
  1129. // X-Forwarded-For: client1, proxy1, proxy2
  1130. // where the value is a comma+space separated list of IP addresses, the left-most being the farthest downstream client,
  1131. // and each successive proxy that passed the request adding the IP address where it received the request from.
  1132. $forwarded_for = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
  1133. $forwarded_for = trim($forwarded_for[0]);
  1134. 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))
  1135. $remote_addr = $forwarded_for;
  1136. }
  1137. }
  1138. return $remote_addr;
  1139. }
  1140. //
  1141. // Calls htmlspecialchars with a few options already set
  1142. // As of 1.1.0, this has been deprecated and will be removed soon. Use Twig instead.
  1143. //
  1144. function panther_htmlspecialchars($str)
  1145. {
  1146. return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
  1147. }
  1148. //
  1149. // Calls htmlspecialchars_decode with a few options already set
  1150. //
  1151. function panther_htmlspecialchars_decode($str)
  1152. {
  1153. if (function_exists('htmlspecialchars_decode'))
  1154. return htmlspecialchars_decode($str, ENT_QUOTES);
  1155. static $translations;
  1156. if (!isset($translations))
  1157. {
  1158. $translations = get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES);
  1159. $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
  1160. $translations = array_flip($translations);
  1161. }
  1162. return strtr($str, $translations);
  1163. }
  1164. //
  1165. // A wrapper for utf8_strlen for compatibility
  1166. //
  1167. function panther_strlen($str)
  1168. {
  1169. return utf8_strlen($str);
  1170. }
  1171. //
  1172. // Convert \r\n and \r to \n
  1173. //
  1174. function panther_linebreaks($str)
  1175. {
  1176. return str_replace(array("\r\n", "\r"), "\n", $str);
  1177. }
  1178. //
  1179. // A wrapper for utf8_trim for compatibility
  1180. //
  1181. function panther_trim($str, $charlist = false)
  1182. {
  1183. return is_string($str) ? utf8_trim($str, $charlist) : '';
  1184. }
  1185. //
  1186. // Checks if a string is in all uppercase
  1187. //
  1188. function is_all_uppercase($string)
  1189. {
  1190. return utf8_strtoupper($string) == $string && utf8_strtolower($string) != $string;
  1191. }
  1192. //
  1193. // Inserts $element into $input at $offset
  1194. // $offset can be either a numerical offset to insert at (eg: 0 inserts at the beginning of the array)
  1195. // or a string, which is the key that the new element should be inserted before
  1196. // $key is optional: it's used when inserting a new key/value pair into an associative array
  1197. //
  1198. function array_insert(&$input, $offset, $element, $key = null)
  1199. {
  1200. if (is_null($key))
  1201. $key = $offset;
  1202. // Determine the proper offset if we're using a string
  1203. if (!is_int($offset))
  1204. $offset = array_search($offset, array_keys($input), true);
  1205. // Out of bounds checks
  1206. if ($offset > count($input))
  1207. $offset = count($input);
  1208. else if ($offset < 0)
  1209. $offset = 0;
  1210. $input = array_merge(array_slice($input, 0, $offset), array($key => $element), array_slice($input, $offset));
  1211. }
  1212. //
  1213. // Return a template object from Twig
  1214. //
  1215. function load_template($tpl_file)
  1216. {
  1217. global $panther_user, $panther_config, $tpl_manager;
  1218. $style_root = (($panther_config['o_style_path'] != 'style') ? $panther_config['o_style_path'] : PANTHER_ROOT.$panther_config['o_style_path']).'/'.$panther_user['style'].'/templates/';
  1219. if (file_exists($style_root.$tpl_file))
  1220. $tpl_file = $tpl_manager->loadTemplate('@style/'.$tpl_file);
  1221. else
  1222. $tpl_file = $tpl_manager->loadTemplate('@core/'.$tpl_file);
  1223. return $tpl_file;
  1224. }
  1225. //
  1226. // Display a message when board is in maintenance mode
  1227. //
  1228. function maintenance_message()
  1229. {
  1230. global $db, $panther_config, $lang_common, $panther_user;
  1231. // Send no-cache headers
  1232. header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
  1233. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  1234. header('Cache-Control: post-check=0, pre-check=0', false);
  1235. header('Pragma: no-cache'); // For HTTP/1.0 compatibility
  1236. // Send the Content-type header in case the web server is setup to send something else
  1237. header('Content-type: text/html; charset=utf-8');
  1238. // Deal with newlines, tabs and multiple spaces
  1239. $pattern = array("\t", ' ', ' ');
  1240. $replace = array('&#160; &#160; ', '&#160; ', ' &#160;');
  1241. $message = str_replace($pattern, $replace, $panther_config['o_maintenance_message']);
  1242. $tpl = load_template('maintenance.tpl');
  1243. echo $tpl->render(
  1244. array(
  1245. 'lang_common' => $lang_common,
  1246. 'message' => $message,
  1247. 'page_title' => generate_page_title(array($panther_config['o_board_title'], $lang_common['Maintenance'])),
  1248. 'style' => (($panther_config['o_style_dir']) != '' ? $panther_config['o_style_dir'] : get_base_url().'/style/').$panther_user['style'],
  1249. 'panther_config' => $panther_config,
  1250. )
  1251. );
  1252. // End the transaction
  1253. $db->end_transaction();
  1254. exit;
  1255. }
  1256. //
  1257. // Display $message and redirect user to $destination_url
  1258. //
  1259. function redirect($destination_url, $message)
  1260. {
  1261. global $db, $panther_config, $lang_common, $panther_user;
  1262. // Prefix with base_url (unless there's already a valid URI)
  1263. if (strpos($destination_url, 'http://') !== 0 && strpos($destination_url, 'https://') !== 0 && strpos($destination_url, '/') !== 0)
  1264. $destination_url = get_base_url(true).'/'.$destination_url;
  1265. // Do a little spring cleaning
  1266. $destination_url = preg_replace('%([\r\n])|(\%0[ad])|(;\s*data\s*:)%i', '', $destination_url);
  1267. // If the delay is 0 seconds, we might as well skip the redirect all together
  1268. if ($panther_config['o_redirect_delay'] == '0')
  1269. {
  1270. $db->end_transaction();
  1271. header('Location: '.str_replace('&amp;', '&', $destination_url));
  1272. exit;
  1273. }
  1274. // Send no-cache headers
  1275. header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
  1276. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  1277. header('Cache-Control: post-check=0, pre-check=0', false);
  1278. header('Pragma: no-cache'); // For HTTP/1.0 compatibility
  1279. // Send the Content-type header in case the web server is setup to send something else
  1280. header('Content-type: text/html; charset=utf-8');
  1281. $tpl = load_template('redirect.tpl');
  1282. echo $tpl->render(
  1283. array(
  1284. 'lang_common' => $lang_common,
  1285. 'destination_url' => $destination_url,
  1286. 'message' => $message,
  1287. 'queries' => ($panther_config['o_show_queries'] == '1') ? display_saved_queries() : '',
  1288. 'panther_config' => $panther_config,
  1289. 'page_title' => generate_page_title(array($panther_config['o_board_title'], $lang_common['Redirecting'])),
  1290. 'css_url' => (($panther_config['o_style_dir']) != '' ? $panther_config['o_style_dir'] : get_base_url().'/style/').$panther_user['style'],
  1291. )
  1292. );
  1293. $db->end_transaction();
  1294. exit;
  1295. }
  1296. //
  1297. // Handle PHP errors
  1298. //
  1299. function error_handler($errno = 0, $errstr = 'Error', $errfile = 'unknown', $errline = 0)
  1300. {
  1301. global $panther_config, $lang_common, $panther_user, $panther_url;
  1302. if (!is_int($errno)) // Make sure set_exception_handler doesn't intefere
  1303. {
  1304. $errstr = $errno;
  1305. $errno = 1;
  1306. }
  1307. // Needed to ensure it doesn't appear after completion on every page
  1308. if ($errno < 1)
  1309. exit;
  1310. $error = error_get_last();
  1311. // If we want to supress errors
  1312. if (error_reporting() == 0)
  1313. return;
  1314. // Check if we're dealing with a fatal error (annoyingly these have to be handled seperately with register_shutdown_function)
  1315. if ($error['type'] == E_ERROR)
  1316. {
  1317. $errno = $error['type'];
  1318. $errstr = $error['message'];
  1319. $errfile = $error['file'];
  1320. $errline = $error['line'];
  1321. }
  1322. // Empty all output buffers and stop buffering (only if we've started)
  1323. if (ob_get_level() > 0 && ob_get_length())
  1324. @ob_clean();
  1325. // Set some default settings if the script failed before $panther_config could be populated
  1326. if (empty($panther_config))
  1327. {
  1328. // Make an educated guess regarding base_url
  1329. $base_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://'; // protocol
  1330. $base_url .= preg_replace('%:(80|443)$%', '', $_SERVER['HTTP_HOST']); // host[:port]
  1331. $base_url .= str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])); // path
  1332. if (substr($base_url, -1) == '/')
  1333. $base_url = substr($base_url, 0, -1);
  1334. $panther_config = array(
  1335. 'o_board_title' => 'Panther',
  1336. 'o_gzip' => '0',
  1337. 'o_debug_mode' => '1',
  1338. 'o_webmaster_email' => $_SERVER['SERVER_ADMIN'],
  1339. 'o_default_style' => 'Pantherone',
  1340. 'o_base_url' => $base_url,
  1341. 'o_style_dir' => get_base_url().'/style/',
  1342. 'o_style_path' => 'style',
  1343. );
  1344. }
  1345. // Don't send HTML
  1346. if (defined('PANTHER_AJAX_REQUEST'))
  1347. exit((($panther_config['o_debug_mode'] == '1') ? 'Errno ['.$errno.'] '.$errstr.' in '.$errfile.' on line '.$errline : 'A server error was encountered.'));
  1348. if (empty($panther_user))
  1349. {
  1350. $panther_user = array(
  1351. 'style' => $panther_config['o_default_style'],
  1352. 'is_admin' => false,
  1353. 'is_admmod' => false,
  1354. 'is_guest' => true,
  1355. );
  1356. }
  1357. // Set some default translations if the script failed before $lang_common could be populated
  1358. $style_path = (($panther_config['o_style_path'] != 'style') ? $panther_config['o_style_path'] : PANTHER_ROOT.$panther_config['o_style_path']).'/';
  1359. if (empty($lang_common))
  1360. {
  1361. $lang_common = array(
  1362. 'Title separator' => ' | ',
  1363. 'Page' => 'Page %s'
  1364. );
  1365. }
  1366. // "Restart" output buffering if we are using ob_gzhandler (since the gzip header is already sent)
  1367. if ($panther_config['o_gzip'] && extension_loaded('zlib') && !ob_get_length())
  1368. ob_start('ob_gzhandler');
  1369. // Send no-cache headers
  1370. header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
  1371. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  1372. header('Cache-Control: post-check=0, pre-check=0', false);
  1373. header('Pragma: no-cache'); // For HTTP/1.0 compatibility
  1374. // Send the Content-type header in case the web server is setup to send something else
  1375. header('Content-type: text/html; charset=utf-8');
  1376. // Send headers telling people we're down
  1377. header('HTTP/1.1 503 Service Temporarily Unavailable');
  1378. header('Status: 503 Service Temporarily Unavailable');
  1379. $tpl = load_template('server_error.tpl');
  1380. echo $tpl->render(
  1381. array(
  1382. 'panther_config' => $panther_config,
  1383. 'error_style' => (($panther_config['o_style_dir']) != '' ? $panther_config['o_style_dir'] : get_base_url().'/style/').(file_exists($style_path.$panther_user['style'].'/error.css') ? $panther_user['style'] : 'imports'),
  1384. 'page_title' => generate_page_title(array($panther_config['o_board_title'], 'Server Error')),
  1385. 'errrno' => $errno,
  1386. 'errstr' => $errstr,
  1387. 'errfile' => $errfile,
  1388. 'errline' => $errline,
  1389. 'index' => panther_link($panther_url['index']),
  1390. )
  1391. );
  1392. exit;
  1393. }
  1394. //
  1395. // Display a database error message
  1396. //
  1397. function error($error, $sql = '', $parameters = array())
  1398. {
  1399. global $panther_config, $lang_common, $panther_user, $panther_url;
  1400. if (defined('PANTHER_AJAX_REQUEST'))
  1401. exit('A database error was encountered.');
  1402. // Empty all output buffers and stop buffering (only if we've started)
  1403. if (ob_get_level() > 0 && ob_get_length())
  1404. @ob_clean();
  1405. // Set some default settings if the script failed before $panther_config could be populated
  1406. if (!empty($panther_config))
  1407. {
  1408. // Make an educated guess regarding base_url
  1409. $base_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://'; // protocol
  1410. $base_url .= preg_replace('%:(80|443)$%', '', $_SERVER['HTTP_HOST']); // host[:port]
  1411. $base_url .= str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])); // path
  1412. if (substr($base_url, -1) == '/')
  1413. $base_url = substr($base_url, 0, -1);
  1414. $panther_config = array(
  1415. 'o_board_title' => 'Panther',
  1416. 'o_gzip' => '0',
  1417. 'o_debug_mode' => '1',
  1418. 'o_webmaster_email' => $_SERVER['SERVER_ADMIN'],
  1419. 'o_default_style' => 'Oxygen',
  1420. 'o_base_url' => $base_url,
  1421. 'o_style_dir' => 'style/',
  1422. 'o_style_path' => '/style/',
  1423. );
  1424. }
  1425. // "Restart" output buffering if we are using ob_gzhandler (since the gzip header is already sent)
  1426. if ($panther_config['o_gzip'] && extension_loaded('zlib') && !ob_get_length())
  1427. ob_start('ob_gzhandler');
  1428. if (empty($panther_user))
  1429. {
  1430. $panther_user = array(
  1431. 'style' => $panther_config['o_default_style'],
  1432. 'is_admin' => false,
  1433. 'is_admmod' => false,
  1434. 'is_guest' => true,
  1435. );
  1436. }
  1437. // Set some default translations if the script failed before $lang_common could be populated
  1438. if (empty($lang_common))
  1439. {
  1440. $lang_common = array(
  1441. 'Title separator' => ' | ',
  1442. 'Page' => 'Page %s'
  1443. );
  1444. }
  1445. $style_path = ($panther_config['o_style_dir'] != '') ? $panther_config['o_style_dir'] : PANTHER_ROOT.$panther_config['o_style_dir'].'style/';
  1446. // Send no-cache headers
  1447. header('Expires: Thu, 21 Jul 1977 07:30:00 GMT'); // When yours truly first set eyes on this world! :)
  1448. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  1449. header('Cache-Control: post-check=0, pre-check=0', false);
  1450. header('Pragma: no-cache'); // For HTTP/1.0 compatibility
  1451. // Send the Content-type header in case the web server is setup to send something else
  1452. header('Content-type: text/html; charset=utf-8');
  1453. if (file_exists($style_path.$panther_user['style'].'/error.css'))
  1454. $error_style = (($panther_config['o_style_dir']) != '' ? $panther_config['o_style_dir'] : get_base_url().'/style/').$panther_user['style'];
  1455. else
  1456. $error_style = (($panther_config['o_style_dir']) != '' ? $panther_config['o_style_dir'] : get_base_url().'/style/').'imports/';
  1457. // Send headers telling people we're down
  1458. header('HTTP/1.1 503 Service Temporarily Unavailable');
  1459. header('Status: 503 Service Temporarily Unavailable');
  1460. // Annoyingly, print_r(), var_dump(), and our custom function dump() all print HTML out directtly. Time to use ob_get_contents ...
  1461. ob_start();
  1462. dump($parameters);
  1463. $debug = trim(ob_get_contents());
  1464. ob_end_clean();
  1465. $tpl = load_template('db_error.tpl');
  1466. echo $tpl->render(
  1467. array(
  1468. 'panther_config' => $panther_config,
  1469. 'error_style' => $error_style,
  1470. 'page_title' => generate_page_title(array($panther_config['o_board_title'], 'Database Error')),
  1471. 'error' => $error,
  1472. 'sql' => $sql,
  1473. 'debug' => $debug,
  1474. 'index' => panther_link($panther_url['index']),
  1475. )
  1476. );
  1477. exit;
  1478. }
  1479. //
  1480. // Removes any "bad" characters (characters which mess with the display of a page, are invisible, etc) from user input
  1481. //
  1482. function forum_remove_bad_characters()
  1483. {
  1484. $_GET = remove_bad_characters($_GET);
  1485. $_POST = remove_bad_characters($_POST);
  1486. $_COOKIE = remove_bad_characters($_COOKIE);
  1487. $_REQUEST = remove_bad_characters($_REQUEST);
  1488. }
  1489. //
  1490. // Removes any "bad" characters (characters which mess with the display of a page, are invisible, etc) from the given string
  1491. // See: http://kb.mozillazine.org/Network.IDN.blacklist_chars
  1492. //
  1493. function remove_bad_characters($array)
  1494. {
  1495. static $bad_utf8_chars;
  1496. if (!isset($bad_utf8_chars))
  1497. {
  1498. $bad_utf8_chars = array(
  1499. "\xcc\xb7" => '', // COMBINING SHORT SOLIDUS OVERLAY 0337 *
  1500. "\xcc\xb8" => '', // COMBINING LONG SOLIDUS OVERLAY 0338 *
  1501. "\xe1\x85\x9F" => '', // HANGUL CHOSEONG FILLER 115F *
  1502. "\xe1\x85\xA0" => '', // HANGUL JUNGSEONG FILLER 1160 *
  1503. "\xe2\x80\x8b" => '', // ZERO WIDTH SPACE 200B *
  1504. "\xe2\x80\x8c" => '', // ZERO WIDTH NON-JOINER 200C
  1505. "\xe2\x80\x8d" => '', // ZERO WIDTH JOINER 200D
  1506. "\xe2\x80\x8e" => '', // LEFT-TO-RIGHT MARK 200E
  1507. "\xe2\x80\x8f" => '', // RIGHT-TO-LEFT MARK 200F
  1508. "\xe2\x80\xaa" => '', // LEFT-TO-RIGHT EMBEDDING 202A
  1509. "\xe2\x80\xab" => '', // RIGHT-TO-LEFT EMBEDDING 202B
  1510. "\xe2\x80\xac" => '', // POP DIRECTIONAL FORMATTING 202C
  1511. "\xe2\x80\xad" => '', // LEFT-TO-RIGHT OVERRIDE 202D
  1512. "\xe2\x80\xae" => '', // RIGHT-TO-LEFT OVERRIDE 202E
  1513. "\xe2\x80\xaf" => '', // NARROW NO-BREAK SPACE 202F *
  1514. "\xe2\x81\x9f" => '', // MEDIUM MATHEMATICAL SPACE 205F *
  1515. "\xe2\x81\xa0" => '', // WORD JOINER 2060
  1516. "\xe3\x85\xa4" => '', // HANGUL FILLER 3164 *
  1517. "\xef\xbb\xbf" => '', // ZERO WIDTH NO-BREAK SPACE FEFF
  1518. "\xef\xbe\xa0" => '', // HALFWIDTH HANGUL FILLER FFA0 *
  1519. "\xef\xbf\xb9" => '', // INTERLINEAR ANNOTATION ANCHOR FFF9 *
  1520. "\xef\xbf\xba" => '', // INTERLINEAR ANNOTATION SEPARATOR FFFA *
  1521. "\xef\xbf\xbb" => '', // INTERLINEAR ANNOTATION TERMINATOR FFFB *
  1522. "\xef\xbf\xbc" => '', // OBJECT REPLACEMENT CHARACTER FFFC *
  1523. "\xef\xbf\xbd" => '', // REPLACEMENT CHARACTER FFFD *
  1524. "\xe2\x80\x80" => ' ', // EN QUAD 2000 *
  1525. "\xe2\x80\x81" => ' ', // EM QUAD 2001 *
  1526. "\xe2\x80\x82" => ' ', // EN SPACE 2002 *
  1527. "\xe2\x80\x83" => ' ', // EM SPACE 2003 *
  1528. "\xe2\x80\x84" => ' ', // THREE-PER-EM SPACE 2004 *
  1529. "\xe2\x80\x85" => ' ', // FOUR-PER-EM SPACE 2005 *
  1530. "\xe2\x80\x86" => ' ', // SIX-PER-EM SPACE 2006 *
  1531. "\xe2\x80\x87" => ' ', // FIGURE SPACE 2007 *
  1532. "\xe2\x80\x88" => ' ', // PANTHERCTUATION SPACE 2008 *
  1533. "\xe2\x80\x89" => ' ', // THIN SPACE 2009 *
  1534. "\xe2\x80\x8a" => ' ', // HAIR SPACE 200A *
  1535. "\xE3\x80\x80" => ' ', // IDEOGRAPHIC SPACE 3000 *
  1536. );
  1537. }
  1538. if (is_array($array))
  1539. return array_map('remove_bad_characters', $array);
  1540. // Strip out any invalid characters
  1541. $array = utf8_bad_strip($array);
  1542. // Remove control characters
  1543. $array = preg_replace('%[\x00-\x08\x0b-\x0c\x0e-\x1f]%', '', $array);
  1544. // Replace some "bad" characters
  1545. $array = str_replace(array_keys($bad_utf8_chars), array_values($bad_utf8_chars), $array);
  1546. return $array;
  1547. }
  1548. //
  1549. // Converts the file size in bytes to a human readable file size
  1550. //
  1551. function file_size($size)
  1552. {
  1553. global $lang_common;
  1554. $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB');
  1555. for ($i = 0; $size > 1024; $i++)
  1556. $size /= 1024;
  1557. return sprintf($lang_common['Size unit '.$units[$i]], round($size, 2));
  1558. }
  1559. //
  1560. // Fetch a list of available styles
  1561. //
  1562. function forum_list_styles()
  1563. {
  1564. global $panther_config;
  1565. $styles = array();
  1566. $style = ($panther_config['o_style_path'] != 'style') ? $panther_config['o_style_path'] : PANTHER_ROOT.$panther_config['o_style_path'];
  1567. $styles = array();
  1568. $files = array_diff(scandir($style), array('.', '..'));
  1569. foreach ($files as $style)
  1570. {
  1571. if (substr($style, -4) == '.css')
  1572. $styles[] = substr($style, 0, -4);
  1573. }
  1574. natcasesort($styles);
  1575. return $styles;
  1576. }
  1577. //
  1578. // Fetch a list of available language packs
  1579. //
  1580. function forum_list_langs()
  1581. {
  1582. $languages = array();
  1583. $files = array_diff(scandir(PANTHER_ROOT.'lang'), array('.', '..'));
  1584. foreach ($files as $language)
  1585. {
  1586. if (is_dir(PANTHER_ROOT.'lang/'.$language) && file_exists(PANTHER_ROOT.'lang/'.$language.'/common.php'))
  1587. $languages[] = $language;
  1588. }
  1589. natcasesort($languages);
  1590. return $languages;
  1591. }
  1592. //
  1593. // Get all URL schemes
  1594. //
  1595. function get_url_schemes()
  1596. {
  1597. $schemes = array();
  1598. $files = array_diff(scandir(PANTHER_ROOT.'include/url'), array('.', '..'));
  1599. foreach ($files as $scheme)
  1600. {
  1601. if (preg_match('/^[a-z0-9-_]+\.(php)$/i', $scheme))
  1602. $schemes[] = $scheme;
  1603. }
  1604. return $schemes;
  1605. }
  1606. //
  1607. // For forum passwords, show this...
  1608. //
  1609. function show_forum_login_box($id)
  1610. {
  1611. global $lang_common, $panther_config, $panther_start, $tpl_main, $panther_user, $db, $panther_url;
  1612. $required_fields = array('req_password' => $lang_common['Password']);
  1613. $focus_element = array('request_pass', 'req_password');
  1614. $data = array(
  1615. ':id' => $id,
  1616. );
  1617. $ps = $db->select('forums', 'forum_name', $data, 'id=:id');
  1618. $forum_name = url_friendly($ps->fetchColumn());
  1619. $redirect_url = validate_redirect(get_current_url(), null);
  1620. if (!isset($redirect_url))
  1621. $redirect_url = panther_link($panther_url['forum'], array($id, $forum_name));
  1622. else if (preg_match('%viewtopic\.php\?pid=(\d+)$%', $redirect_url, $matches))
  1623. $redirect_url .= '#p'.$matches[1];
  1624. $page_title = array($panther_config['o_board_title'], $lang_common['Info']);
  1625. define('PANTHER_ACTIVE_PAGE', 'index');
  1626. require PANTHER_ROOT.'header.php';
  1627. $tpl = load_template('forum_password.tpl');
  1628. echo $tpl->render(
  1629. array(
  1630. 'lang_common' => $lang_common,
  1631. 'form_action' => panther_link($panther_url['forum'], array($id, $forum_name)),
  1632. 'csrf_token' => generate_csrf_token('viewforum.php'),
  1633. 'redirect_url' => $redirect_url,
  1634. )
  1635. );
  1636. require PANTHER_ROOT.'footer.php';
  1637. }
  1638. //
  1639. // Check if given forum password is indeed valid
  1640. //
  1641. function validate_login_attempt($id)
  1642. {
  1643. global $lang_common, $db, $panther_url;
  1644. confirm_referrer('viewforum.php');
  1645. $password = isset($_POST['req_password']) ? panther_trim($_POST['req_password']) : '';
  1646. $redirect_url = validate_redirect($_POST['redirect_url'], panther_link($panther_url['index'])); // If we've tampered, or maybe something just went wrong, send them back to the board index
  1647. $data = array(
  1648. ':id' => $id,
  1649. );
  1650. $ps = $db->select('forums', 'password, salt', $data, 'id=:id');
  1651. if (!$ps->rowCount())
  1652. message($lang_common['Bad request']);
  1653. else
  1654. $cur_forum = $ps->fetch();
  1655. if (panther_hash($password.panther_hash($cur_forum['salt'])) == $cur_forum['password'])
  1656. {
  1657. set_forum_login_cookie($id, $cur_forum['password']);
  1658. header('Location: '.$redirect_url);
  1659. exit;
  1660. }
  1661. else
  1662. message($lang_common['incorrect password']);
  1663. }
  1664. function set_forum_login_cookie($id, $forum_password)
  1665. {
  1666. global $panther_config;
  1667. $cookie_data = isset($_COOKIE[$panther_config['o_cookie_name'].'_forums']) ? $_COOKIE[$panther_config['o_cookie_name'].'_forums'] : '';
  1668. if (!$cookie_data || strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
  1669. $cookie_data = '';
  1670. $cookie_data = unserialize($cookie_data);
  1671. $salt = random_key(64, true);
  1672. $cookie_hash = panther_hash($forum_password.panther_hash($salt));
  1673. $cookie_data[$id] = array('hash' => $cookie_hash, 'salt' => $salt);
  1674. forum_setcookie($panther_config['o_cookie_name'].'_forums', serialize($cookie_data), time() + $panther_config['o_timeout_visit']);
  1675. $_COOKIE[$panther_config['o_cookie_name'].'_forums'] = serialize($cookie_data);
  1676. }
  1677. function check_forum_login_cookie($id, $forum_password, $return = false)
  1678. {
  1679. global $panther_config;
  1680. $cookie_data = isset($_COOKIE[$panther_config['o_cookie_name'].'_forums']) ? $_COOKIE[$panther_config['o_cookie_name'].'_forums'] : '';
  1681. if (!$cookie_data || strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE)
  1682. $cookie_data = '';
  1683. // If it's empty, define as a blank array to avoid 'must be a boolean' error
  1684. $cookie_data = ($cookie_data !== '') ? unserialize($cookie_data) : array();
  1685. if (!array_key_exists($id, $cookie_data))
  1686. {
  1687. if (!$return)
  1688. show_forum_login_box($id);
  1689. else
  1690. return false;
  1691. }
  1692. else
  1693. {
  1694. if ($cookie_data[$id]['hash'] !== panther_hash($forum_password.panther_hash($cookie_data[$id]['salt'])))
  1695. {
  1696. if (!$return)
  1697. show_forum_login_box($id);
  1698. else
  1699. return false;
  1700. }
  1701. else
  1702. return true;
  1703. }
  1704. }
  1705. //
  1706. // Generate a cache ID based on the last modification time for all stopwords files
  1707. //
  1708. function generate_stopwords_cache_id()
  1709. {
  1710. $files = glob(PANTHER_ROOT.'lang/*/stop_words.txt');
  1711. if ($files === false)
  1712. return 'cache_id_error';
  1713. $hash = array();
  1714. foreach ($files as $file)
  1715. {
  1716. $hash[] = $file;
  1717. $hash[] = filemtime($file);
  1718. }
  1719. return panther_hash(implode('|', $hash));
  1720. }
  1721. //
  1722. // Split text into chunks ($inside contains all text inside $start and $end, and $outside contains all text outside)
  1723. //
  1724. function split_text($text, $start, $end, $retab = true)
  1725. {
  1726. global $panther_config;
  1727. $result = array(0 => array(), 1 => array()); // 0 = inside, 1 = outside
  1728. // split the text into parts
  1729. $parts = preg_split('%'.preg_quote($start, '%').'(.*)'.preg_quote($end, '%').'%Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
  1730. $num_parts = count($parts);
  1731. // preg_split results in outside parts having even indices, inside parts having odd
  1732. for ($i = 0;$i < $num_parts;$i++)
  1733. $result[1 - ($i % 2)][] = $parts[$i];
  1734. if ($panther_config['o_indent_num_spaces'] != 8 && $retab)
  1735. {
  1736. $spaces = str_repeat(' ', $panther_config['o_indent_num_spaces']);
  1737. $result[1] = str_replace("\t", $spaces, $result[1]);
  1738. }
  1739. return $result;
  1740. }
  1741. function extract_blocks($text, $start, $end, $retab = true)
  1742. {
  1743. global $panther_config;
  1744. $code = array();
  1745. $start_len = strlen($start);
  1746. $end_len = strlen($end);
  1747. $regex = '%(?:'.preg_quote($start, '%').'|'.preg_quote($end, '%').')%';
  1748. $matches = array();
  1749. if (preg_match_all($regex, $text, $matches))
  1750. {
  1751. $counter = $offset = 0;
  1752. $start_pos = $end_pos = false;
  1753. foreach ($matches[0] as $match)
  1754. {
  1755. if ($match == $start)
  1756. {
  1757. if ($counter == 0)
  1758. $start_pos = strpos($text, $start);
  1759. $counter++;
  1760. }
  1761. elseif ($match == $end)
  1762. {
  1763. $counter--;
  1764. if ($counter == 0)
  1765. $end_pos = strpos($text, $end, $offset + 1);
  1766. $offset = strpos($text, $end, $offset + 1);
  1767. }
  1768. if ($start_pos !== false && $end_pos !== false)
  1769. {
  1770. $code[] = substr($text, $start_pos + $start_len,
  1771. $end_pos - $start_pos - $start_len);
  1772. $text = substr_replace($text, "\1", $start_pos,
  1773. $end_pos - $start_pos + $end_len);
  1774. $start_pos = $end_pos = false;
  1775. $offset = 0;
  1776. }
  1777. }
  1778. }
  1779. if ($panther_config['o_indent_num_spaces'] != 8 && $retab)
  1780. {
  1781. $spaces = str_repeat(' ', $panther_config['o_indent_num_spaces']);
  1782. $text = str_replace("\t", $spaces, $text);
  1783. }
  1784. return array($code, $text);
  1785. }
  1786. function url_valid($url)
  1787. {
  1788. if (strpos($url, 'www.') === 0) $url = 'http://'. $url;
  1789. if (strpos($url, 'ftp.') === 0) $url = 'ftp://'. $url;
  1790. if (!preg_match('/# Valid absolute URI having a non-empty, valid DNS host.
  1791. ^
  1792. (?P<scheme>[A-Za-z][A-Za-z0-9+\-.]*):\/\/
  1793. (?P<authority>
  1794. (?:(?P<userinfo>(?:[A-Za-z0-9\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*)@)?
  1795. (?P<host>
  1796. (?P<IP_literal>
  1797. \[
  1798. (?:
  1799. (?P<IPV6address>
  1800. (?: (?:[0-9A-Fa-f]{1,4}:){6}
  1801. | ::(?:[0-9A-Fa-f]{1,4}:){5}
  1802. | (?: [0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}
  1803. | (?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}
  1804. | (?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}
  1805. | (?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?:: [0-9A-Fa-f]{1,4}:
  1806. | (?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::
  1807. )
  1808. (?P<ls32>[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}
  1809. | (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
  1810. (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
  1811. )
  1812. | (?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?:: [0-9A-Fa-f]{1,4}
  1813. | (?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::
  1814. )
  1815. | (?P<IPvFuture>[Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&\'()*+,;=:]+)
  1816. )
  1817. \]
  1818. )
  1819. | (?P<IPv4address>(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
  1820. (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))
  1821. | (?P<regname>(?:[A-Za-z0-9\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})+)
  1822. )
  1823. (?::(?P<port>[0-9]*))?
  1824. )
  1825. (?P<path_abempty>(?:\/(?:[A-Za-z0-9\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)
  1826. (?:\?(?P<query> (?:[A-Za-z0-9\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*))?
  1827. (?:\#(?P<fragment> (?:[A-Za-z0-9\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*))?
  1828. $
  1829. /mx', $url, $m)) return FALSE;
  1830. switch ($m['scheme'])
  1831. {
  1832. case 'https':
  1833. case 'http':
  1834. if ($m['userinfo']) return FALSE; // HTTP scheme does not allow userinfo.
  1835. break;
  1836. case 'ftps':
  1837. case 'ftp':
  1838. break;
  1839. default:
  1840. return FALSE; // Unrecognised URI scheme. Default to FALSE.
  1841. }
  1842. // Validate host name conforms to DNS "dot-separated-parts".
  1843. if ($m{'regname'}) // If host regname specified, check for DNS conformance.
  1844. {
  1845. if (!preg_match('/# HTTP DNS host name.
  1846. ^ # Anchor to beginning of string.
  1847. (?!.{256}) # Overall host length is less than 256 chars.
  1848. (?: # Group dot separated host part alternatives.
  1849. [0-9A-Za-z]\. # Either a single alphanum followed by dot
  1850. | # or... part has more than one char (63 chars max).
  1851. [0-9A-Za-z] # Part first char is alphanum (no dash).
  1852. [\-0-9A-Za-z]{0,61} # Internal chars are alphanum plus dash.
  1853. [0-9A-Za-z] # Part last char is alphanum (no dash).
  1854. \. # Each part followed by literal dot.
  1855. )* # One or more parts before top level domain.
  1856. (?: # Top level domains
  1857. [A-Za-z]{2,63}| # Country codes are exactly two alpha chars.
  1858. xn--[0-9A-Za-z]{4,59}) # Internationalized Domain Name (IDN)
  1859. $ # Anchor to end of string.
  1860. /ix', $m['host'])) return FALSE;
  1861. }
  1862. $m['url'] = $url;
  1863. for ($i = 0; isset($m[$i]); ++$i) unset($m[$i]);
  1864. return $m; // return TRUE == array of useful named $matches plus the valid $url.
  1865. }
  1866. //
  1867. // Replace string matching regular expression
  1868. //
  1869. // This function takes care of possibly disabled unicode properties in PCRE builds
  1870. //
  1871. function ucp_preg_replace($pattern, $replace, $subject, $callback = false)
  1872. {
  1873. if($callback)
  1874. $replaced = preg_replace_callback($pattern, create_function('$matches', 'return '.$replace.';'), $subject);
  1875. else
  1876. $replaced = preg_replace($pattern, $replace, $subject);
  1877. // If preg_replace() returns false, this probably means unicode support is not built-in, so we need to modify the pattern a little
  1878. if ($replaced === false)
  1879. {
  1880. if (is_array($pattern))
  1881. {
  1882. foreach ($pattern as $cur_key => $cur_pattern)
  1883. $pattern[$cur_key] = str_replace('\p{L}\p{N}', '\w', $cur_pattern);
  1884. $replaced = preg_replace($pattern, $replace, $subject);
  1885. }
  1886. else
  1887. $replaced = preg_replace(str_replace('\p{L}\p{N}', '\w', $pattern), $replace, $subject);
  1888. }
  1889. return $replaced;
  1890. }
  1891. //
  1892. // A wrapper for ucp_preg_replace
  1893. //
  1894. function ucp_preg_replace_callback($pattern, $replace, $subject)
  1895. {
  1896. return ucp_preg_replace($pattern, $replace, $subject, true);
  1897. }
  1898. //
  1899. // Replace four-byte characters with a question mark
  1900. //
  1901. // As MySQL cannot properly handle four-byte characters with the default utf-8
  1902. // charset up until version 5.5.3 (where a special charset has to be used), they
  1903. // need to be replaced, by question marks in this case.
  1904. //
  1905. function strip_bad_multibyte_chars($str)
  1906. {
  1907. $result = '';
  1908. $length = strlen($str);
  1909. for ($i = 0; $i < $length; $i++)
  1910. {
  1911. // Replace four-byte characters (11110www 10zzzzzz 10yyyyyy 10xxxxxx)
  1912. $ord = ord($str[$i]);
  1913. if ($ord >= 240 && $ord <= 244)
  1914. {
  1915. $result .= '?';
  1916. $i += 3;
  1917. }
  1918. else
  1919. $result .= $str[$i];
  1920. }
  1921. return $result;
  1922. }
  1923. //
  1924. // Check whether a file/folder is writable.
  1925. //
  1926. // This function also works on Windows Server where ACLs seem to be ignored.
  1927. //
  1928. function forum_is_writable($path)
  1929. {
  1930. if (is_dir($path))
  1931. {
  1932. $path = rtrim($path, '/').'/';
  1933. return forum_is_writable($path.uniqid(mt_rand()).'.tmp');
  1934. }
  1935. // Check temporary file for read/write capabilities
  1936. $rm = file_exists($path);
  1937. $f = @fopen($path, 'a');
  1938. if ($f === false)
  1939. return false;
  1940. fclose($f);
  1941. if (!$rm)
  1942. @unlink($path);
  1943. return true;
  1944. }
  1945. //
  1946. // Display executed queries (if enabled)
  1947. //
  1948. function display_saved_queries()
  1949. {
  1950. global $db, $lang_common;
  1951. // Get the queries so that we can print them out
  1952. $saved_queries = $db->get_saved_queries();
  1953. $queries = array();
  1954. $query_time_total = 0.0;
  1955. foreach ($saved_queries as $cur_query)
  1956. {
  1957. $query_time_total += $cur_query[1];
  1958. $queries[] = array(
  1959. 'sql' => $cur_query[0],
  1960. 'time' => $cur_query[1],
  1961. );
  1962. }
  1963. $tpl = load_template('debug.tpl');
  1964. return $tpl->render(
  1965. array(
  1966. 'lang_common' => $lang_common,
  1967. 'query_time_total' => $query_time_total,
  1968. 'queries' => $queries,
  1969. )
  1970. );
  1971. }
  1972. //
  1973. // Dump contents of variable(s)
  1974. //
  1975. function dump()
  1976. {
  1977. echo '<pre>';
  1978. $num_args = func_num_args();
  1979. for ($i = 0; $i < $num_args; ++$i)
  1980. {
  1981. print_r(func_get_arg($i));
  1982. echo "\n\n";
  1983. }
  1984. echo '</pre>';
  1985. }
  1986. function check_queue($form_username, $attempt, $db)
  1987. {
  1988. $data = array(
  1989. ':timeout' => (TIMEOUT * 1000),
  1990. ':username' => $form_username,
  1991. );
  1992. $ps = $db->select('login_queue', 'id', $data, 'last_checked > NOW() - INTERVAL :timeout MICROSECOND AND username = :username', 'id ASC LIMIT 1');
  1993. $id = $ps->fetchColumn();
  1994. // Due to the fact we're not updating with data, we can't use the update() method. Instead, we have to use run() to avoid the string 'CURRENT_TIMESTAMP' being the value entered.
  1995. $db->run('UPDATE '.$db->prefix.'login_queue SET last_checked = CURRENT_TIMESTAMP WHERE id=? LIMIT 1', array($attempt));
  1996. return ($id == $attempt) ? true : false;
  1997. }
  1998. function isbot($ua)
  1999. {
  2000. if ('' == panther_trim($ua)) return false;
  2001. $ual = strtolower($ua);
  2002. if (strstr($ual, 'bot') || strstr($ual, 'spider') || strstr($ual, 'crawler')) return true;
  2003. if (strstr($ua, 'Mozilla/'))
  2004. {
  2005. if (strstr($ua, 'Gecko')) return false;
  2006. if (strstr($ua, '(compatible; MSIE ') && strstr($ua, 'Windows')) return false;
  2007. }
  2008. else if (strstr($ua, 'Opera/'))
  2009. {
  2010. if (strstr($ua, 'Presto/')) return false;
  2011. }
  2012. return true;
  2013. }
  2014. function isbotex($ra)
  2015. {
  2016. $ua = getenv('HTTP_USER_AGENT');
  2017. if (!isbot($ua)) return $ra;
  2018. $pat = array(
  2019. '%(https?://|www\.).*%i',
  2020. '%.*compatible[^\s]*%i',
  2021. '%[\w\.-]+@[\w\.-]+.*%',
  2022. '%.*?([^\s]+(bot|spider|crawler)[^\s]*).*%i',
  2023. '%(?<=[\s_-])(bot|spider|crawler).*%i',
  2024. '%(Mozilla|Gecko|Firefox|AppleWebKit)[^\s]*%i',
  2025. // '%(MSIE|Windows|\.NET|Linux)[^;]+%i',
  2026. // '%[^\s]*\.(com|html)[^\s]*%i',
  2027. '%\/[v\d]+.*%',
  2028. '%[^0-9a-z\.]+%i'
  2029. );
  2030. $rep = array(
  2031. ' ',
  2032. ' ',
  2033. ' ',
  2034. '$1',
  2035. ' ',
  2036. ' ',
  2037. // ' ',
  2038. // ' ',
  2039. ' ',
  2040. ' '
  2041. );
  2042. $ua = panther_trim(preg_replace($pat, $rep, $ua));
  2043. if (empty($ua)) return $ra.'[Bot]Unknown';
  2044. $a = explode(' ', $ua);
  2045. $ua = $a[0];
  2046. if (strlen($ua) < 20 && !empty($a[1])) $ua.= ' '.$a[1];
  2047. if (strlen($ua) > 25) $ua = 'Unknown';
  2048. return $ra.'[Bot]'.$ua;
  2049. }
  2050. function generate_user_location($url)
  2051. {
  2052. global $db, $panther_user, $lang_online, $panther_url;
  2053. static $perms;
  2054. if (!defined('PANTHER_FP_LOADED'))
  2055. {
  2056. $perms = array();
  2057. if (file_exists(FORUM_CACHE_DIR.'cache_perms.php'))
  2058. require FORUM_CACHE_DIR.'cache_perms.php';
  2059. else
  2060. {
  2061. if (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))
  2062. require PANTHER_ROOT.'include/cache.php';
  2063. generate_perms_cache();
  2064. require FORUM_CACHE_DIR.'cache_perms.php';
  2065. }
  2066. }
  2067. switch ($url)
  2068. {
  2069. case null:
  2070. $location = $lang_online['bot'];
  2071. break;
  2072. case 'index.php':
  2073. $location = $lang_online['viewing index'];
  2074. break;
  2075. case stristr($url, 'userlist.php'):
  2076. $location = $lang_online['viewing userlist'];
  2077. break;
  2078. case 'online.php':
  2079. $location = $lang_online['viewing online'];
  2080. break;
  2081. case 'misc.php?action=rules':
  2082. $location = $lang_online['viewing rules'];
  2083. break;
  2084. case stristr($url, 'search'):
  2085. $location = $lang_online['searching'];
  2086. break;
  2087. case stristr($url, 'help'):
  2088. $location = $lang_online['bbcode help'];
  2089. break;
  2090. case stristr($url, 'profile'):
  2091. $id = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2092. $data = array(
  2093. ':id' => $id,
  2094. );
  2095. $ps = $db->select('users', 'username, group_id', $data, 'id=:id');
  2096. $user = $ps->fetch();
  2097. $username = colourize_group($user['username'], $user['group_id'], $id);
  2098. $location = sprintf($lang_online['viewing profile'], $username);
  2099. break;
  2100. case stristr($url, 'pms_'):
  2101. $location = $lang_online['private messaging'];
  2102. break;
  2103. case stristr($url, 'admin'):
  2104. $location = $lang_online['administration'];
  2105. break;
  2106. case stristr($url, 'login'):
  2107. $location = $lang_online['login'];
  2108. break;
  2109. case stristr($url, 'viewforum.php'):
  2110. if (strpos($url, '&p=')!== false)
  2111. {
  2112. preg_match('~&p=(.*)~', $url, $replace);
  2113. $url = str_replace($replace[0], '', $url);
  2114. }
  2115. $id = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2116. $data = array(
  2117. ':id' => $id,
  2118. );
  2119. $ps = $db->select('forums', 'forum_name', $data, 'id=:id');
  2120. $forum_name = $ps->fetchColumn();
  2121. if (!isset($perms[$panther_user['g_id'].'_'.$id]))
  2122. $perms[$panther_user['g_id'].'_'.$id] = $perms['_'];
  2123. if ($perms[$panther_user['g_id'].'_'.$id]['read_forum'] == '1' || is_null($perms[$panther_user['g_id'].'_'.$id]['read_forum']))
  2124. $location = array('href' => panther_link($panther_url['forum'], array($id, url_friendly($forum_name))), 'name' => $forum_name, 'lang' => $lang_online['viewing forum']);
  2125. else
  2126. $location = $lang_online['in hidden forum'];
  2127. break;
  2128. case stristr($url, 'viewtopic.php?pid'): //Now for the nasty part =)
  2129. $pid = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2130. $data = array(
  2131. ':id' => $pid,
  2132. );
  2133. $ps = $db->run('SELECT t.subject, t.forum_id AS fid FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'topics AS t ON p.topic_id=t.id WHERE p.id=:id', $data);
  2134. $info = $ps->fetch();
  2135. if (!isset($perms[$panther_user['g_id'].'_'.$info['fid']]))
  2136. $perms[$panther_user['g_id'].'_'.$info['fid']] = $perms['_'];
  2137. if ($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum'] == '1' || is_null($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum']))
  2138. $location = array('href' => panther_link($panther_url['post'], array($pid)), 'name' => $info['subject'], 'lang' => $lang_online['viewing topic']);
  2139. else
  2140. $location = $lang_online['in hidden forum'];
  2141. break;
  2142. case stristr($url, 'viewtopic.php?id'):
  2143. if (strpos($url, '&p=')!== false)
  2144. {
  2145. preg_match('~&p=(.*)~', $url, $replace);
  2146. $url = str_replace($replace[0], '', $url);
  2147. }
  2148. $id = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2149. $data = array(
  2150. ':id' => $id,
  2151. );
  2152. $ps = $db->select('topics', 'subject, forum_id AS fid', $data, 'id=:id');
  2153. $info = $ps->fetch();
  2154. if (!isset($perms[$panther_user['g_id'].'_'.$info['fid']]))
  2155. $perms[$panther_user['g_id'].'_'.$info['fid']] = $perms['_'];
  2156. if ($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum'] == '1' || is_null($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum']))
  2157. $location = array('href' => panther_link($panther_url['topic'], array($id, url_friendly($info['subject']))), 'name' => $info['subject'], 'lang' => $lang_online['viewing topic']);
  2158. else
  2159. $location = $lang_online['in hidden forum'];
  2160. break;
  2161. case stristr($url, 'post.php?action=post'):
  2162. $location = $lang_online['posting'];
  2163. break;
  2164. case stristr($url, 'post.php?fid'):
  2165. $fid = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2166. $data = array(
  2167. ':id' => $fid,
  2168. );
  2169. $ps = $db->select('forums', 'forum_name', $data, 'id=:id');
  2170. $forum_name = $ps->fetchColumn();
  2171. if (!isset($perms[$panther_user['g_id'].'_'.$fid]))
  2172. $perms[$panther_user['g_id'].'_'.$fid] = $perms['_'];
  2173. if ($perms[$panther_user['g_id'].'_'.$fid]['read_forum'] == '1' || is_null($perms[$panther_user['g_id'].'_'.$fid]['read_forum']))
  2174. $location = array('href' => panther_link($panther_url['forum'], array($fid, url_friendly($forum_name))), 'lang' => $forum_name, 'lang' => $lang_online['posting topic']);
  2175. else
  2176. $location = $lang_online['in hidden forum'];
  2177. break;
  2178. case stristr($url, 'post.php?tid'):
  2179. $tid = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2180. $data = array(
  2181. ':id' => $tid,
  2182. );
  2183. $ps = $db->select('topics', 'subject, forum_id AS fid', $data, 'id=:id');
  2184. $info = $ps->fetch();
  2185. if (!isset($perms[$panther_user['g_id'].'_'.$info['fid']]))
  2186. $perms[$panther_user['g_id'].'_'.$info['fid']] = $perms['_'];
  2187. if ($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum'] == '1' || is_null($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum']))
  2188. $location = array('href' => panther_link($panther_url['topic'], array($tid, url_friendly($info['subject']))), 'name' => $info['subject'], 'lang' => $lang_online['replying to topic']);
  2189. else
  2190. $location = $lang_online['in hidden forum'];
  2191. break;
  2192. case stristr($url, 'edit.php?id'):
  2193. $id = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2194. $data = array(
  2195. ':id' => $id,
  2196. );
  2197. $ps = $db->run('SELECT t.subject, t.forum_id AS fid FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'topics AS t ON p.topic_id=t.id WHERE p.id=:id', $data);
  2198. $info = $ps->fetch();
  2199. if (!isset($perms[$panther_user['g_id'].'_'.$info['fid']]))
  2200. $perms[$panther_user['g_id'].'_'.$info['fid']] = $perms['_'];
  2201. if ($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum'] == '1' || is_null($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum']))
  2202. $location = array('href' => panther_link($panther_url['post'], array($id)), 'name' => $info['subject'], 'lang' => $lang_online['editing topic']);
  2203. else
  2204. $location = $lang_online['in hidden forum'];
  2205. break;
  2206. case stristr($url, 'delete.php?id'):
  2207. $id = filter_var($url, FILTER_SANITIZE_NUMBER_INT);
  2208. $data = array(
  2209. ':id' => $id,
  2210. );
  2211. $ps = $db->run('SELECT t.subject, t.forum_id AS fid FROM '.$db->prefix.'posts AS p INNER JOIN '.$db->prefix.'topics AS t ON p.topic_id=t.id WHERE p.id=:id', $data);
  2212. $info = $ps->fetch();
  2213. if (!isset($perms[$panther_user['g_id'].'_'.$info['fid']]))
  2214. $perms[$panther_user['g_id'].'_'.$info['fid']] = $perms['_'];
  2215. if ($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum'] == '1' || is_null($perms[$panther_user['g_id'].'_'.$info['fid']]['read_forum']))
  2216. $location = array('href' => panther_link($panther_url['post'], array($id)), 'name' => $info['subject'], 'lang' => $lang_online['deleting post']);
  2217. else
  2218. $location = $lang_online['in hidden forum'];
  2219. break;
  2220. case stristr($url, 'moderate.php'):
  2221. $location = $lang_online['moderating'];
  2222. break;
  2223. case stristr($url, 'register.php'):
  2224. $location = $lang_online['register'];
  2225. break;
  2226. case stristr($url, 'misc.php?action=leaders'):
  2227. $location = $lang_online['viewing team'];
  2228. break;
  2229. case '-':
  2230. $location = $lang_online['not online'];
  2231. break;
  2232. default:
  2233. $location = $url;
  2234. break;
  2235. }
  2236. return $location;
  2237. }
  2238. function format_time_difference($logged, $lang_online)
  2239. {
  2240. $difference = time() - $logged;
  2241. $intervals = array('minute'=> 60);
  2242. if ($difference < 60)
  2243. {
  2244. if ($difference == '1')
  2245. $difference = sprintf($lang_online['second ago'], $difference);
  2246. else
  2247. $difference = sprintf($lang_online['seconds ago'], $difference);
  2248. }
  2249. if ($difference >= 60)
  2250. {
  2251. $difference = floor($difference/$intervals['minute']);
  2252. if ($difference == '1')
  2253. $difference = sprintf($lang_online['minute ago'], $difference);
  2254. else
  2255. $difference = sprintf($lang_online['minutes ago'], $difference);
  2256. }
  2257. return $difference;
  2258. }
  2259. function colourize_group($username, $gid, $user_id = 1)
  2260. {
  2261. global $panther_user, $panther_url;
  2262. static $colourize_cache = array();
  2263. if (!isset($colourize_cache[$username]))
  2264. {
  2265. $name = '<span class="gid'.$gid.'">'.panther_htmlspecialchars($username).'</span>';
  2266. if ($panther_user['g_view_users'] == 1 && $user_id > 1)
  2267. $colourize_cache[$username] = '<a href="'.panther_link($panther_url['profile'], array($user_id, url_friendly($username))).'">'.$name.'</a>';
  2268. else
  2269. $colourize_cache[$username] = $name;
  2270. }
  2271. return $colourize_cache[$username];
  2272. }
  2273. function attach_delete_thread($id = 0)
  2274. {
  2275. global $db, $panther_config;
  2276. // Should we orhpan any attachments
  2277. if ($panther_config['o_create_orphans'] == 0)
  2278. {
  2279. $data = array(
  2280. ':id' => $id,
  2281. );
  2282. $ps = $db->run('SELECT a.id FROM '.$db->prefix.'attachments AS a LEFT JOIN '.$db->prefix.'posts AS p ON a.post_id=p.id WHERE p.topic_id=:id', $data);
  2283. if ($ps->rowCount())
  2284. {
  2285. $ps->setFetchMode(PDO::FETCH_COLUMN, 0);
  2286. foreach ($ps as $attach_id)
  2287. {
  2288. if (!delete_attachment($attach_id))
  2289. continue;
  2290. }
  2291. }
  2292. }
  2293. }
  2294. function attach_delete_post($id = 0)
  2295. {
  2296. global $db;
  2297. $data = array(
  2298. ':id' => $id,
  2299. );
  2300. $ps = $db->run('SELECT a.id FROM '.$db->prefix.'attachments AS a WHERE a.post_id=:id', $data);
  2301. if ($ps->rowCount())
  2302. {
  2303. $ps->setFetchMode(PDO::FETCH_COLUMN, 0);
  2304. foreach ($ps as $attach_id)
  2305. {
  2306. if (!delete_attachment($attach_id))
  2307. continue;
  2308. }
  2309. }
  2310. }
  2311. function delete_attachment($item = 0)
  2312. {
  2313. global $db, $panther_user, $panther_config;
  2314. $data = array(
  2315. ':uid' => $panther_user['g_id'],
  2316. ':id' => $item,
  2317. );
  2318. // Make sure the item actually exists
  2319. $can_delete = false;
  2320. $ps = $db->run('SELECT a.owner, a.location FROM '.$db->prefix.'attachments AS a LEFT JOIN '.$db->prefix.'posts AS p ON a.post_id=p.id LEFT JOIN '.$db->prefix.'topics AS t ON p.topic_id=t.id LEFT JOIN '.$db->prefix.'forum_perms AS fp ON t.forum_id=fp.forum_id AND fp.group_id=:uid WHERE a.id=:id', $data);
  2321. if (!$ps->rowCount())
  2322. message($lang_common['Bad request']);
  2323. else
  2324. $attachment = $ps->fetch();
  2325. $data = array(
  2326. ':id' => $item,
  2327. );
  2328. $db->delete('attachments', 'id=:id', $data);
  2329. if ($panther_config['o_create_orphans'] == '0')
  2330. @unlink($panther_config['o_attachments_dir'].$attachment['location']);
  2331. return true;
  2332. }
  2333. function file_upload_error_message($code)
  2334. {
  2335. switch ($code)
  2336. {
  2337. case UPLOAD_ERR_INI_SIZE:
  2338. return 'The uploaded file exceeds the upload_max_filesize configuration setting in php.ini';
  2339. case UPLOAD_ERR_FORM_SIZE:
  2340. return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form';
  2341. case UPLOAD_ERR_PARTIAL:
  2342. return 'The uploaded file was only partially uploaded';
  2343. case UPLOAD_ERR_NO_FILE:
  2344. return 'No file was uploaded';
  2345. case UPLOAD_ERR_NO_TMP_DIR:
  2346. return 'Missing a temporary folder';
  2347. case UPLOAD_ERR_CANT_WRITE:
  2348. return 'Failed to write file to disk';
  2349. case UPLOAD_ERR_EXTENSION:
  2350. return 'File upload stopped by extension';
  2351. default:
  2352. return 'An unknown upload error was encountered';
  2353. }
  2354. }
  2355. function create_attachment($name = '', $mime = '', $size = 0, $tmp_name = '', $post_id = 0, $message = 0)
  2356. {
  2357. global $db, $panther_user, $panther_config;
  2358. $unique_name = attach_generate_filename($panther_config['o_attachments_dir'], $message, $size);
  2359. if (!move_uploaded_file($tmp_name, $panther_config['o_attachments_dir'].$unique_name))
  2360. error_handler(E_ERROR, 'Unable to move file from: '.$tmp_name.' to '.$panther_config['o_attachments_dir'].$unique_name, __FILE__, __LINE__);
  2361. if (strlen($mime) < 1)
  2362. $mime = attach_create_mime(attach_find_extention($name));
  2363. $insert = array(
  2364. 'owner' => $panther_user['id'],
  2365. 'post_id' => $post_id,
  2366. 'filename' => $name,
  2367. 'extension' => attach_get_extension($name),
  2368. 'mime' => $mime,
  2369. 'location' => $unique_name,
  2370. 'size' => $size
  2371. );
  2372. $db->insert('attachments', $insert);
  2373. return true;
  2374. }
  2375. function attach_generate_filename($storagepath, $messagelength = 0, $size = 0)
  2376. {
  2377. // Login keys are one time use only. Use this as salt too.
  2378. global $panther_user;
  2379. $newfile = md5($messagelength.$size.$panther_user['login_key'].random_key(18)).'.attach';
  2380. if (!is_file($storagepath.$newfile))
  2381. return $newfile;
  2382. else
  2383. return attach_generate_filename($storagepath, $messagelength, $size);
  2384. }
  2385. function attach_create_mime($extension = '')
  2386. {
  2387. // Some of these may well no longer exist.
  2388. $mimes = array (
  2389. 'diff' => 'text/x-diff',
  2390. 'patch' => 'text/x-diff',
  2391. 'rtf' => 'text/richtext',
  2392. 'html' => 'text/html',
  2393. 'htm' => 'text/html',
  2394. 'aiff' => 'audio/x-aiff',
  2395. 'iff' => 'audio/x-aiff',
  2396. 'basic' => 'audio/basic',
  2397. 'wav' => 'audio/wav',
  2398. 'gif' => 'image/gif',
  2399. 'jpg' => 'image/jpeg',
  2400. 'jpeg' => 'image/pjpeg',
  2401. 'tif' => 'image/tiff',
  2402. 'png' => 'image/x-png',
  2403. 'xbm' => 'image/x-xbitmap',
  2404. 'bmp' => 'image/bmp',
  2405. 'xjg' => 'image/x-jg',
  2406. 'emf' => 'image/x-emf',
  2407. 'wmf' => 'image/x-wmf',
  2408. 'avi' => 'video/avi',
  2409. 'mpg' => 'video/mpeg',
  2410. 'mpeg' => 'video/mpeg',
  2411. 'ps' => 'application/postscript',
  2412. 'b64' => 'application/base64',
  2413. 'macbinhex' => 'application/macbinhex40',
  2414. 'pdf' => 'application/pdf',
  2415. 'xzip' => 'application/x-compressed',
  2416. 'zip' => 'application/x-zip-compressed',
  2417. 'gzip' => 'application/x-gzip-compressed',
  2418. 'java' => 'application/java',
  2419. 'msdownload' => 'application/x-msdownload'
  2420. );
  2421. foreach ($mimes as $type => $mime)
  2422. {
  2423. if ($extension == $type)
  2424. return $mime;
  2425. }
  2426. return 'application/octet-stream';
  2427. }
  2428. function attach_get_extension($filename = '')
  2429. {
  2430. if (strlen($filename) < 1)
  2431. return '';
  2432. return strtolower(ltrim(strrchr($filename, "."), "."));
  2433. }
  2434. function check_file_extension($file_name)
  2435. {
  2436. global $panther_config;
  2437. $actual_extension = attach_get_extension($file_name);
  2438. $always_deny = explode(',', $panther_config['o_always_deny']);
  2439. foreach ($always_deny as $ext)
  2440. {
  2441. if ($ext == $actual_extension)
  2442. return false;
  2443. }
  2444. return true;
  2445. }
  2446. function attach_icon($extension)
  2447. {
  2448. global $panther_config, $panther_user;
  2449. static $base_url, $attach_icons;
  2450. $icon_dir = ($panther_config['o_attachment_icon_dir'] != '') ? $panther_config['o_attachment_icon_dir'] : get_base_url(true).'/'.$panther_config['o_attachment_icon_path'].'/';
  2451. if ($panther_user['show_img'] == 0 || $panther_config['o_attachment_icons'] == 0)
  2452. return '';
  2453. if (!isset($attach_icons))
  2454. {
  2455. $attach_icons = array();
  2456. $extensions = explode(',', $panther_config['o_attachment_extensions']);
  2457. $icons = explode(',', $panther_config['o_attachment_images']);
  2458. for ($i = 0; $i < count($extensions); $i++)
  2459. {
  2460. if (!isset($extensions[$i]) || !isset($icons[$i]))
  2461. break;
  2462. $attach_icons[$extensions[$i]] = $icons[$i];
  2463. }
  2464. }
  2465. $icon = isset($attach_icons[$extension]) ? $attach_icons[$extension] : 'unknown.png';
  2466. return array('file' => $icon_dir.$icon, 'extension' => $extension);
  2467. }
  2468. function return_bytes($val)
  2469. {
  2470. $last = strtolower($val[strlen($val)-1]);
  2471. switch($last)
  2472. {
  2473. case 'g': // The 'G' modifier is available since PHP 5.1.0
  2474. $val *= 1024;
  2475. case 'm':
  2476. $val *= 1024;
  2477. case 'k':
  2478. $val *= 1024;
  2479. }
  2480. return $val;
  2481. }
  2482. function check_authentication()
  2483. {
  2484. global $lang_admin_common, $db, $panther_config, $panther_user;
  2485. function send_authentication()
  2486. {
  2487. global $lang_admin_common;
  2488. header('WWW-Authenticate: Basic realm="Panther Admin CP"');
  2489. header('HTTP/1.1 401 Unauthorized');
  2490. message($lang_admin_common['Unauthorised']);
  2491. }
  2492. if ($panther_config['o_http_authentication'] == '1')
  2493. {
  2494. if (!isset($_SERVER['PHP_AUTH_USER']) && !isset($_SERVER['PHP_AUTH_PW']))
  2495. send_authentication();
  2496. else if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']))
  2497. {
  2498. $form_username = panther_trim($_SERVER['PHP_AUTH_USER']);
  2499. $form_password = panther_trim($_SERVER['PHP_AUTH_PW']);
  2500. $data = array(
  2501. ':id' => $panther_user['id'],
  2502. ':username' => $form_username,
  2503. );
  2504. $ps = $db->select('users', 'password, salt', $data, 'username=:username AND id=:id');
  2505. if (!$ps->rowCount())
  2506. send_authentication();
  2507. else
  2508. {
  2509. $cur_user = $ps->fetch();
  2510. if (panther_hash($form_password.$cur_user['salt']) != $cur_user['password'])
  2511. send_authentication();
  2512. }
  2513. }
  2514. }
  2515. }
  2516. // Generate link to another page on the forum
  2517. function panther_link($link, $args = null)
  2518. {
  2519. global $panther_config;
  2520. static $base_url = null;
  2521. $base_url = (!$base_url) ? get_base_url() : $base_url;
  2522. $gen_link = $link;
  2523. if ($args == null)
  2524. $gen_link = $base_url.'/'.$link;
  2525. else if (!is_array($args))
  2526. $gen_link = $base_url.'/'.str_replace('$1', $args, $link);
  2527. else
  2528. {
  2529. for ($i = 0; isset($args[$i]); ++$i)
  2530. $gen_link = str_replace('$'.($i + 1), $args[$i], $gen_link);
  2531. $gen_link = $base_url.'/'.$gen_link;
  2532. }
  2533. return $gen_link;
  2534. }
  2535. // Generate a hyperlink with parameters and anchor and a subsection such as a subpage
  2536. function get_sublink($link, $sublink, $subarg, $args = null)
  2537. {
  2538. global $panther_config, $panther_url;
  2539. static $base_url = null;
  2540. $base_url = (!$base_url) ? get_base_url() : $base_url;
  2541. if ($sublink == $panther_url['page'] && $subarg == 1)
  2542. return panther_link($link, $args);
  2543. $gen_link = $link;
  2544. if (!is_array($args) && $args != null)
  2545. $gen_link = str_replace('$1', $args, $link);
  2546. else
  2547. {
  2548. for ($i = 0; isset($args[$i]); ++$i)
  2549. $gen_link = str_replace('$'.($i + 1), $args[$i], $gen_link);
  2550. }
  2551. if (isset($panther_url['insertion_find']))
  2552. $gen_link = $base_url.'/'.str_replace($panther_url['insertion_find'], str_replace('$1', str_replace('$1', $subarg, $sublink), $panther_url['insertion_replace']), $gen_link);
  2553. else
  2554. $gen_link = $base_url.'/'.$gen_link.str_replace('$1', $subarg, $sublink);
  2555. return $gen_link;
  2556. }
  2557. // Make a string safe to use in a URL
  2558. function url_friendly($str)
  2559. {
  2560. static $url_replace;
  2561. if (!isset($url_replace))
  2562. require PANTHER_ROOT.'include/url_replace.php';
  2563. $str = strtr($str, $url_replace);
  2564. $str = strtolower(utf8_decode($str));
  2565. $str = panther_trim(preg_replace(array('/[^a-z0-9\s]/', '/[\s]+/'), array('', '-'), $str), '-');
  2566. return $str;
  2567. }
  2568. //
  2569. // Generate a one time use login key to set in the cookie
  2570. //
  2571. function generate_login_key($uid = 1)
  2572. {
  2573. global $db, $panther_user;
  2574. $key = random_pass(60);
  2575. $data = array(
  2576. ':key' => $key,
  2577. );
  2578. $ps = $db->select('users', 1, $data, 'login_key=:key');
  2579. if ($ps->rowCount()) // There is already a key with this string (keys are unique)
  2580. generate_login_key();
  2581. else
  2582. {
  2583. $data = array(
  2584. ':id' => ($uid !=1) ? $uid : $panther_user['id'],
  2585. );
  2586. $update = array(
  2587. 'login_key' => $key,
  2588. );
  2589. $db->update('users', $update, 'id=:id', $data);
  2590. return $key;
  2591. }
  2592. }
  2593. function check_archive_rules($archive_rules, $tid = 0)
  2594. {
  2595. global $cur_topic, $db, $lang_common;
  2596. $day = (24*60*60); // Set some useful time related stuff
  2597. $month = (24*60*60*date('t'));
  2598. $year = ($month*12);
  2599. $sql = $data = array();
  2600. if ($archive_rules['closed'] != '2')
  2601. {
  2602. $data[] = $archive_rules['closed'];
  2603. $sql[] = 'closed=?';
  2604. }
  2605. if ($archive_rules['sticky'] != '2')
  2606. {
  2607. $data[] = $archive_rules['sticky'];
  2608. $sql[] = 'sticky=?';
  2609. }
  2610. if ($archive_rules['time'] != '0')
  2611. {
  2612. switch ($archive_rules['unit'])
  2613. {
  2614. case 'years':
  2615. $seconds = $archive_rules['time']*$year;
  2616. break;
  2617. case 'months':
  2618. $seconds = $archive_rules['time']*$month;
  2619. break;
  2620. case 'days':
  2621. default:
  2622. $seconds = $archive_rules['time']*$day;
  2623. break;
  2624. }
  2625. $data[] = (time()-$seconds);
  2626. $sql[] = 'last_post<?';
  2627. }
  2628. if ($archive_rules['forums'][0] != '0')
  2629. {
  2630. $forums = '';
  2631. for ($i = 0; $i < count($i); $i++)
  2632. {
  2633. $forums .= ($forums != '') ? ',?' : '?';
  2634. $data[] = $archive_rules['forums'][$i];
  2635. }
  2636. $sql[] = 'forum_id IN('.$forums.')';
  2637. }
  2638. if ($tid != 0)
  2639. {
  2640. $sql[] = 'id=?';
  2641. $data[] = $tid;
  2642. $fetch = 1;
  2643. }
  2644. else
  2645. {
  2646. $fetch = 'id';
  2647. $sql[] = 'archived=0 AND deleted=0 AND approved=1'; // Make sure to get topics that have the ability to be archived
  2648. }
  2649. $ps = $db->select('topics', $fetch, $data, implode(' AND ', $sql));
  2650. if ($tid != 0)
  2651. {
  2652. if ($ps->rowCount()) // Time to archive!
  2653. {
  2654. $update = array(
  2655. 'archived' => 1,
  2656. );
  2657. $data = array(
  2658. ':id' => $tid,
  2659. );
  2660. return $db->update('topics', $update, 'id=:id', $data);
  2661. }
  2662. else
  2663. return 0;
  2664. }
  2665. else
  2666. {
  2667. $topics = array(
  2668. 'count' => $ps->rowCount(),
  2669. 'topics' => array(),
  2670. );
  2671. $ps->setFetchMode(PDO::FETCH_COLUMN, 0);
  2672. foreach ($ps as $tid)
  2673. $topics['topics'][] = $tid;
  2674. }
  2675. return $topics;
  2676. }
  2677. //
  2678. // Format time in seconds to display as hours/days/months/never
  2679. //
  2680. function format_expiration_time($seconds)
  2681. {
  2682. global $lang_warnings;
  2683. $seconds = intval($seconds);
  2684. if ($seconds <= 0)
  2685. return $lang_warnings['Never'];
  2686. else if ($seconds % (30*24*60*60) == '0')
  2687. {
  2688. //Months
  2689. $expiration_value = $seconds / (30*24*60*60);
  2690. return sprintf($lang_warnings['No of months'], $expiration_value);
  2691. }
  2692. else if ($seconds % (24*60*60) == '0')
  2693. {
  2694. //Days
  2695. $expiration_value = $seconds / (24*60*60);
  2696. return sprintf($lang_warnings['No of days'], $expiration_value);
  2697. }
  2698. else
  2699. {
  2700. //Hours
  2701. $expiration_value = $seconds / (60*60);
  2702. return sprintf($lang_warnings['No of hours'], $expiration_value);
  2703. }
  2704. }
  2705. //
  2706. // Get expiration time (in seconds)
  2707. //
  2708. function get_expiration_time($value = 0, $unit)
  2709. {
  2710. $value = abs(intval($value));
  2711. if ($value == '0')
  2712. $expiration_time = 0;
  2713. else if ($unit == 'minutes')
  2714. $expiration_time = $value*60;
  2715. else if ($unit == 'hours')
  2716. $expiration_time = $value*60*60;
  2717. else if ($unit == 'days')
  2718. $expiration_time = $value*24*60*60;
  2719. else if ($unit == 'months')
  2720. $expiration_time = $value*30*24*60*60;
  2721. else if ($unit == 'never')
  2722. $expiration_time = 0;
  2723. else
  2724. $expiration_time = 0;
  2725. return $expiration_time;
  2726. }
  2727. //
  2728. // Checks when a posting ban has expired
  2729. //
  2730. function format_posting_ban_expiration($expires, $lang_profile)
  2731. {
  2732. $month = (24*60*60*date('t'));
  2733. $day = (24*60*60);
  2734. $hour = (60*60);
  2735. $minute = 60;
  2736. switch(true)
  2737. {
  2738. case ($expires > $month):
  2739. $seconds = array(round($expires/$month), ($expires % $month), $lang_profile['Months']);
  2740. break;
  2741. case ($expires > $day):
  2742. $seconds = array(round($expires/$day), ($expires % $day), $lang_profile['Days']);
  2743. break;
  2744. case ($expires > $hour):
  2745. $seconds = array(round($expires/$hour), ($expires % $hour), $lang_profile['Hours']);
  2746. break;
  2747. case ($expires > $minute):
  2748. $seconds = array(round($expires/$minute), ($expires % $minute), $lang_profile['Minutes']);
  2749. break;
  2750. default:
  2751. $seconds = array(10, 0, $lang_profile['Never']);
  2752. break;
  2753. }
  2754. return $seconds;
  2755. }
  2756. function check_posting_ban()
  2757. {
  2758. global $panther_user, $db, $lang_common;
  2759. if ($panther_user['posting_ban'] != '0')
  2760. {
  2761. if ($panther_user['posting_ban'] < time())
  2762. {
  2763. $update = array(
  2764. 'posting_ban' => 0,
  2765. );
  2766. $data = array(
  2767. ':id' => $panther_user['id'],
  2768. );
  2769. $db->update('users', $update, 'id=:id', $data);
  2770. }
  2771. else
  2772. message(sprintf($lang_common['posting_ban'], format_time($panther_user['posting_ban'])));
  2773. }
  2774. }
  2775. function stopforumspam_report($api_key, $remote_address, $email, $username, $message)
  2776. {
  2777. $context = stream_context_create(
  2778. array('http' =>
  2779. array(
  2780. 'method' => 'POST',
  2781. 'header' => 'Content-type: application/x-www-form-urlencoded',
  2782. 'content' => http_build_query(
  2783. array(
  2784. 'ip_addr' => $remote_address,
  2785. 'email' => $email,
  2786. 'username' => $username,
  2787. 'evidence' => $message,
  2788. 'api_key' => $api_key,
  2789. )
  2790. ),
  2791. )
  2792. )
  2793. );
  2794. return @file_get_contents('http://www.stopforumspam.com/add', false, $context) ? true : false;
  2795. }
  2796. //
  2797. // Compress image using TinyPNG compression API
  2798. //
  2799. function compress_image($image)
  2800. {
  2801. global $panther_config;
  2802. if ($panther_config['o_tinypng_api'] == '')
  2803. return;
  2804. if (substr($image, strrpos($image, '.')+1) == 'gif') // Then it can't be compressed, and will return nothing causing the error handler
  2805. {
  2806. $key = max(array_keys(explode('/', $image)));
  2807. cache_cloudflare($image[$key]);
  2808. return;
  2809. }
  2810. if (is_callable('curl_init'))
  2811. {
  2812. $request = curl_init();
  2813. curl_setopt_array($request, array(
  2814. CURLOPT_URL => "https://api.tinypng.com/shrink",
  2815. CURLOPT_USERPWD => "api:".$panther_config['o_tinypng_api'],
  2816. CURLOPT_POSTFIELDS => file_get_contents($image),
  2817. CURLOPT_BINARYTRANSFER => true,
  2818. CURLOPT_RETURNTRANSFER => true,
  2819. CURLOPT_HEADER => true,
  2820. CURLOPT_SSL_VERIFYPEER => true
  2821. ));
  2822. $response = curl_exec($request);
  2823. if (curl_getinfo($request, CURLINFO_HTTP_CODE) === 201)
  2824. {
  2825. $headers = substr($response, 0, curl_getinfo($request, CURLINFO_HEADER_SIZE));
  2826. foreach (explode("\r\n", $headers) as $header)
  2827. {
  2828. if (substr($header, 0, 10) === "Location: ")
  2829. {
  2830. $request = curl_init();
  2831. curl_setopt_array($request, array(
  2832. CURLOPT_URL => substr($header, 10),
  2833. CURLOPT_RETURNTRANSFER => true,
  2834. CURLOPT_SSL_VERIFYPEER => true
  2835. ));
  2836. // Replace the image with the compressed one
  2837. file_put_contents($image, curl_exec($request));
  2838. }
  2839. }
  2840. }
  2841. else
  2842. error_handler(E_ERROR, curl_error($request), __FILE__, __LINE__);
  2843. // We only want this doing if the first one works
  2844. $key = max(array_keys(explode('/', $image)));
  2845. cache_cloudflare($image[$key]);
  2846. }
  2847. }
  2848. //
  2849. // Cache images through CloudFlare API
  2850. //
  2851. function cache_cloudflare($file)
  2852. {
  2853. global $panther_config;
  2854. if ($panther_config['o_cloudflare_api'] != '')
  2855. return;
  2856. if (is_callable('curl_init'))
  2857. {
  2858. $request = curl_init("https://www.cloudflare.com/api_json.html");
  2859. curl_setopt($request, CURLOPT_RETURNTRANSFER, 1);
  2860. curl_setopt($request, CURLOPT_FOLLOWLOCATION, 1);
  2861. curl_setopt($request, CURLOPT_SSL_VERIFYPEER, false);
  2862. $params = array(
  2863. 'a' => 'zone_file_purge',
  2864. 'tkn' => $panther_config['o_cloudflare_api'],
  2865. 'email' => $panther_config['o_clouflare_email'],
  2866. 'z' => $panther_config['o_clouflare_domain'],
  2867. 'url' => $panther_config['o_clouflare_domain'].$file,
  2868. );
  2869. curl_setopt($request, CURLOPT_POST, 1);
  2870. curl_setopt($request, CURLOPT_POSTFIELDS, http_build_query($params));
  2871. $response = curl_exec($request);
  2872. if (curl_getinfo($request, CURLINFO_HTTP_CODE) === 201)
  2873. {
  2874. $result = json_decode($response, true);
  2875. curl_close($request);
  2876. if ($result['msg'] != 'success')
  2877. error_handler(E_ERROR, $result['msg'], __FILE__, __LINE__);
  2878. return true;
  2879. }
  2880. }
  2881. }
  2882. function xml_to_array($raw_xml)
  2883. {
  2884. $xml_array = array();
  2885. $xml_parser = xml_parser_create();
  2886. xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0);
  2887. xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0);
  2888. xml_parse_into_struct($xml_parser, $raw_xml, $parsed_xml);
  2889. xml_parser_free($xml_parser);
  2890. foreach ($parsed_xml as $xml_elem)
  2891. {
  2892. $x_tag = $xml_elem['tag'];
  2893. $x_level = $xml_elem['level'];
  2894. $x_type = $xml_elem['type'];
  2895. if ($x_level != 1 && $x_type == 'close')
  2896. {
  2897. if (isset($multi_key[$x_tag][$x_level]))
  2898. $multi_key[$x_tag][$x_level] = 1;
  2899. else
  2900. $multi_key[$x_tag][$x_level] = 0;
  2901. }
  2902. if ($x_level != 1 && $x_type == 'complete')
  2903. {
  2904. if (isset($tmp) && $tmp == $x_tag)
  2905. $multi_key[$x_tag][$x_level] = 1;
  2906. $tmp = $x_tag;
  2907. }
  2908. }
  2909. foreach ($parsed_xml as $xml_elem)
  2910. {
  2911. $x_tag = $xml_elem['tag'];
  2912. $x_level = $xml_elem['level'];
  2913. $x_type = $xml_elem['type'];
  2914. if ($x_type == 'open')
  2915. $level[$x_level] = $x_tag;
  2916. $start_level = 1;
  2917. $php_stmt = '$xml_array';
  2918. if ($x_type == 'close' && $x_level != 1)
  2919. $multi_key[$x_tag][$x_level]++;
  2920. while ($start_level < $x_level)
  2921. {
  2922. $php_stmt .= '[$level['.$start_level.']]';
  2923. if (isset($multi_key[$level[$start_level]][$start_level]) && $multi_key[$level[$start_level]][$start_level])
  2924. $php_stmt .= '['.($multi_key[$level[$start_level]][$start_level]-1).']';
  2925. ++$start_level;
  2926. }
  2927. $add = '';
  2928. if (isset($multi_key[$x_tag][$x_level]) && $multi_key[$x_tag][$x_level] && ($x_type == 'open' || $x_type == 'complete'))
  2929. {
  2930. if (!isset($multi_key2[$x_tag][$x_level]))
  2931. $multi_key2[$x_tag][$x_level] = 0;
  2932. else
  2933. $multi_key2[$x_tag][$x_level]++;
  2934. $add = '['.$multi_key2[$x_tag][$x_level].']';
  2935. }
  2936. if (isset($xml_elem['value']) && panther_trim($xml_elem['value']) != '' && !isset($xml_elem['attributes']))
  2937. {
  2938. if ($x_type == 'open')
  2939. $php_stmt_main = $php_stmt.'[$x_type]'.$add.'[\'content\'] = $xml_elem[\'value\'];';
  2940. else
  2941. $php_stmt_main = $php_stmt.'[$x_tag]'.$add.' = $xml_elem[\'value\'];';
  2942. eval($php_stmt_main);
  2943. }
  2944. if (isset($xml_elem['attributes']))
  2945. {
  2946. if (isset($xml_elem['value']))
  2947. {
  2948. $php_stmt_main = $php_stmt.'[$x_tag]'.$add.'[\'content\'] = $xml_elem[\'value\'];';
  2949. eval($php_stmt_main);
  2950. }
  2951. foreach ($xml_elem['attributes'] as $key=>$value)
  2952. {
  2953. $php_stmt_att=$php_stmt.'[$x_tag]'.$add.'[\'attributes\'][$key] = $value;';
  2954. eval($php_stmt_att);
  2955. }
  2956. }
  2957. }
  2958. // Make sure there's an array of hooks (even if there is only one)
  2959. if (isset($xml_array['extension']['hooks']) && isset($xml_array['extension']['hooks']['hook']))
  2960. {
  2961. if (!is_array(current($xml_array['extension']['hooks']['hook'])))
  2962. $xml_array['extension']['hooks']['hook'] = array($xml_array['extension']['hooks']['hook']);
  2963. }
  2964. return $xml_array;
  2965. }
  2966. function validate_xml($xml, $errors)
  2967. {
  2968. global $lang_admin_extensions;
  2969. if (!isset($xml['extension']))
  2970. $errors[] = $lang_admin_extensions['Extension not valid'];
  2971. else
  2972. {
  2973. $extension = $xml['extension'];
  2974. if (!isset($extension['attributes']['engine']) || $extension['attributes']['engine'] != '1.0')
  2975. $errors[] = $lang_admin_extensions['Extension engine malformed'];
  2976. else if (!isset($extension['title']) || $extension['title'] == '')
  2977. $errors[] = $lang_admin_extensions['Extension title malformed'];
  2978. else if (!isset($extension['version']) || !is_numeric($extension['version']))
  2979. $errors[] = $lang_admin_extensions['Extension version malformed'];
  2980. else if (!isset($extension['description']) || $extension['description'] == '')
  2981. $errors[] = $lang_admin_extensions['Extension description malformed'];
  2982. else if (!isset($extension['author']) || $extension['author'] == '')
  2983. $errors[] = $lang_admin_extensions['Extension author malformed'];
  2984. else if (!isset($extension['supported_versions']))
  2985. $errors[] = $lang_admin_extensions['Extension versions malformed'];
  2986. if (!isset($extension['hooks']) || !is_array($extension['hooks']))
  2987. $errors[] = $lang_admin_extensions['Extension hooks malformed'];
  2988. else
  2989. {
  2990. if (!isset($extension['hooks']['hook']) || !is_array($extension['hooks']['hook']))
  2991. $errors[] = $lang_admin_extensions['Extension hooks missing'];
  2992. else
  2993. {
  2994. foreach ($extension['hooks']['hook'] as $hook)
  2995. {
  2996. if (!isset($hook['content']) || $hook['content'] == '')
  2997. $errors[] = $lang_admin_extensions['Extension hook content missing'];
  2998. else if (!isset($hook['attributes']['id']) || $hook['attributes']['id'] == '')
  2999. $errors[] = $lang_admin_extensions['Extension hook id missing'];
  3000. }
  3001. }
  3002. }
  3003. }
  3004. return $errors;
  3005. }
  3006. function get_extensions($hook)
  3007. {
  3008. global $panther_extensions;
  3009. return (isset($panther_extensions[$hook])) ? implode("\n", $panther_extensions[$hook]) : false;
  3010. }
  3011. ($hook = get_extensions('functions_after_functions')) ? eval($hook) : null;