PageRenderTime 69ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/sources/functions.php

http://github.com/usebb/UseBB
PHP | 3682 lines | 1841 code | 884 blank | 957 comment | 475 complexity | b4d531bf6e256716d2bacdf4ff36d2ac MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /*
  3. Copyright (C) 2003-2012 UseBB Team
  4. http://www.usebb.net
  5. $Id$
  6. This file is part of UseBB.
  7. UseBB is free software; you can redistribute it and/or modify
  8. it under the terms of the GNU General Public License as published by
  9. the Free Software Foundation; either version 2 of the License, or
  10. (at your option) any later version.
  11. UseBB is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU General Public License for more details.
  15. You should have received a copy of the GNU General Public License
  16. along with UseBB; if not, write to the Free Software
  17. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. /**
  20. * Functions
  21. *
  22. * Contains all kinds of procedural functions and the functions class.
  23. *
  24. * @author UseBB Team
  25. * @link http://www.usebb.net
  26. * @license GPL-2
  27. * @version $Revision$
  28. * @copyright Copyright (C) 2003-2012 UseBB Team
  29. * @package UseBB
  30. * @subpackage Core
  31. */
  32. //
  33. // Die when called directly in browser
  34. //
  35. if ( !defined('INCLUDED') )
  36. exit();
  37. /**
  38. * Debug output function
  39. *
  40. * Takes variable number of arguments that get printed out to template.
  41. */
  42. function usebb_debug_output() {
  43. global $template;
  44. $numargs = func_num_args();
  45. if ( $template == null || USEBB_IS_PROD_ENV || $numargs == 0 )
  46. return;
  47. $values = array_map('unhtml', array_map('print_r', func_get_args(), array_fill(0, $numargs, true)));
  48. $template->add_raw_content('<pre>'.implode('<br />', $values).'</pre>');
  49. }
  50. /**
  51. * Callback for array_walk
  52. *
  53. * Will add slashes to and trim the value.
  54. * Third parameter disables addslashes (magic_quotes_gpc on)
  55. */
  56. function usebb_clean_input_value(&$value, $key, $mq=false) {
  57. if ( is_array($value) ) {
  58. array_walk($value, 'usebb_clean_input_value', $mq);
  59. } else {
  60. if ( !$mq )
  61. $value = addslashes($value);
  62. $value = trim($value);
  63. }
  64. }
  65. /**
  66. * Callback for array_walk
  67. *
  68. * Will add slashes to the value.
  69. */
  70. function usebb_clean_db_value(&$value, $key) {
  71. if ( is_array($value) )
  72. array_walk($value, 'usebb_clean_db_value');
  73. else
  74. $value = addslashes($value);
  75. }
  76. /**
  77. * Check whether the string contains HTML entities.
  78. *
  79. * @param string $string String to check
  80. * @param bool $num_only Look for &#...; only
  81. * @returns bool Contains entities
  82. */
  83. function contains_entities($string, $num_only=false) {
  84. return preg_match(( $num_only ? '#&\#[^;]+;#' : '#&\#?[^;]+;#' ), $string);
  85. }
  86. /**
  87. * Resets the named entities to the code ones.
  88. *
  89. * Code from the Drupal Atom module, patch by stefanor (at Drupal.org).
  90. * @link http://drupal.org/node/579286
  91. *
  92. * @param string $string String
  93. * @returns string String
  94. */
  95. function named_entities_to_numeric($string) {
  96. $table = array(
  97. "&nbsp;" => "&#160;",
  98. "&iexcl;" => "&#161;",
  99. "&cent;" => "&#162;",
  100. "&pound;" => "&#163;",
  101. "&curren;" => "&#164;",
  102. "&yen;" => "&#165;",
  103. "&brvbar;" => "&#166;",
  104. "&sect;" => "&#167;",
  105. "&uml;" => "&#168;",
  106. "&copy;" => "&#169;",
  107. "&ordf;" => "&#170;",
  108. "&laquo;" => "&#171;",
  109. "&not;" => "&#172;",
  110. "&shy;" => "&#173;",
  111. "&reg;" => "&#174;",
  112. "&macr;" => "&#175;",
  113. "&deg;" => "&#176;",
  114. "&plusmn;" => "&#177;",
  115. "&sup2;" => "&#178;",
  116. "&sup3;" => "&#179;",
  117. "&acute;" => "&#180;",
  118. "&micro;" => "&#181;",
  119. "&para;" => "&#182;",
  120. "&middot;" => "&#183;",
  121. "&cedil;" => "&#184;",
  122. "&sup1;" => "&#185;",
  123. "&ordm;" => "&#186;",
  124. "&raquo;" => "&#187;",
  125. "&frac14;" => "&#188;",
  126. "&frac12;" => "&#189;",
  127. "&frac34;" => "&#190;",
  128. "&iquest;" => "&#191;",
  129. "&Agrave;" => "&#192;",
  130. "&Aacute;" => "&#193;",
  131. "&Acirc;" => "&#194;",
  132. "&Atilde;" => "&#195;",
  133. "&Auml;" => "&#196;",
  134. "&Aring;" => "&#197;",
  135. "&AElig;" => "&#198;",
  136. "&Ccedil;" => "&#199;",
  137. "&Egrave;" => "&#200;",
  138. "&Eacute;" => "&#201;",
  139. "&Ecirc;" => "&#202;",
  140. "&Euml;" => "&#203;",
  141. "&Igrave;" => "&#204;",
  142. "&Iacute;" => "&#205;",
  143. "&Icirc;" => "&#206;",
  144. "&Iuml;" => "&#207;",
  145. "&ETH;" => "&#208;",
  146. "&Ntilde;" => "&#209;",
  147. "&Ograve;" => "&#210;",
  148. "&Oacute;" => "&#211;",
  149. "&Ocirc;" => "&#212;",
  150. "&Otilde;" => "&#213;",
  151. "&Ouml;" => "&#214;",
  152. "&times;" => "&#215;",
  153. "&Oslash;" => "&#216;",
  154. "&Ugrave;" => "&#217;",
  155. "&Uacute;" => "&#218;",
  156. "&Ucirc;" => "&#219;",
  157. "&Uuml;" => "&#220;",
  158. "&Yacute;" => "&#221;",
  159. "&THORN;" => "&#222;",
  160. "&szlig;" => "&#223;",
  161. "&agrave;" => "&#224;",
  162. "&aacute;" => "&#225;",
  163. "&acirc;" => "&#226;",
  164. "&atilde;" => "&#227;",
  165. "&auml;" => "&#228;",
  166. "&aring;" => "&#229;",
  167. "&aelig;" => "&#230;",
  168. "&ccedil;" => "&#231;",
  169. "&egrave;" => "&#232;",
  170. "&eacute;" => "&#233;",
  171. "&ecirc;" => "&#234;",
  172. "&euml;" => "&#235;",
  173. "&igrave;" => "&#236;",
  174. "&iacute;" => "&#237;",
  175. "&icirc;" => "&#238;",
  176. "&iuml;" => "&#239;",
  177. "&eth;" => "&#240;",
  178. "&ntilde;" => "&#241;",
  179. "&ograve;" => "&#242;",
  180. "&oacute;" => "&#243;",
  181. "&ocirc;" => "&#244;",
  182. "&otilde;" => "&#245;",
  183. "&ouml;" => "&#246;",
  184. "&divide;" => "&#247;",
  185. "&oslash;" => "&#248;",
  186. "&ugrave;" => "&#249;",
  187. "&uacute;" => "&#250;",
  188. "&ucirc;" => "&#251;",
  189. "&uuml;" => "&#252;",
  190. "&yacute;" => "&#253;",
  191. "&thorn;" => "&#254;",
  192. "&yuml;" => "&#255;",
  193. "&fnof;" => "&#402;",
  194. "&Alpha;" => "&#913;",
  195. "&Beta;" => "&#914;",
  196. "&Gamma;" => "&#915;",
  197. "&Delta;" => "&#916;",
  198. "&Epsilon;" => "&#917;",
  199. "&Zeta;" => "&#918;",
  200. "&Eta;" => "&#919;",
  201. "&Theta;" => "&#920;",
  202. "&Iota;" => "&#921;",
  203. "&Kappa;" => "&#922;",
  204. "&Lambda;" => "&#923;",
  205. "&Mu;" => "&#924;",
  206. "&Nu;" => "&#925;",
  207. "&Xi;" => "&#926;",
  208. "&Omicron;" => "&#927;",
  209. "&Pi;" => "&#928;",
  210. "&Rho;" => "&#929;",
  211. "&Sigma;" => "&#931;",
  212. "&Tau;" => "&#932;",
  213. "&Upsilon;" => "&#933;",
  214. "&Phi;" => "&#934;",
  215. "&Chi;" => "&#935;",
  216. "&Psi;" => "&#936;",
  217. "&Omega;" => "&#937;",
  218. "&alpha;" => "&#945;",
  219. "&beta;" => "&#946;",
  220. "&gamma;" => "&#947;",
  221. "&delta;" => "&#948;",
  222. "&epsilon;" => "&#949;",
  223. "&zeta;" => "&#950;",
  224. "&eta;" => "&#951;",
  225. "&theta;" => "&#952;",
  226. "&iota;" => "&#953;",
  227. "&kappa;" => "&#954;",
  228. "&lambda;" => "&#955;",
  229. "&mu;" => "&#956;",
  230. "&nu;" => "&#957;",
  231. "&xi;" => "&#958;",
  232. "&omicron;" => "&#959;",
  233. "&pi;" => "&#960;",
  234. "&rho;" => "&#961;",
  235. "&sigmaf;" => "&#962;",
  236. "&sigma;" => "&#963;",
  237. "&tau;" => "&#964;",
  238. "&upsilon;" => "&#965;",
  239. "&phi;" => "&#966;",
  240. "&chi;" => "&#967;",
  241. "&psi;" => "&#968;",
  242. "&omega;" => "&#969;",
  243. "&thetasym;" => "&#977;",
  244. "&upsih;" => "&#978;",
  245. "&piv;" => "&#982;",
  246. "&bull;" => "&#8226;",
  247. "&hellip;" => "&#8230;",
  248. "&prime;" => "&#8242;",
  249. "&Prime;" => "&#8243;",
  250. "&oline;" => "&#8254;",
  251. "&frasl;" => "&#8260;",
  252. "&weierp;" => "&#8472;",
  253. "&image;" => "&#8465;",
  254. "&real;" => "&#8476;",
  255. "&trade;" => "&#8482;",
  256. "&alefsym;" => "&#8501;",
  257. "&larr;" => "&#8592;",
  258. "&uarr;" => "&#8593;",
  259. "&rarr;" => "&#8594;",
  260. "&darr;" => "&#8595;",
  261. "&harr;" => "&#8596;",
  262. "&crarr;" => "&#8629;",
  263. "&lArr;" => "&#8656;",
  264. "&uArr;" => "&#8657;",
  265. "&rArr;" => "&#8658;",
  266. "&dArr;" => "&#8659;",
  267. "&hArr;" => "&#8660;",
  268. "&forall;" => "&#8704;",
  269. "&part;" => "&#8706;",
  270. "&exist;" => "&#8707;",
  271. "&empty;" => "&#8709;",
  272. "&nabla;" => "&#8711;",
  273. "&isin;" => "&#8712;",
  274. "&notin;" => "&#8713;",
  275. "&ni;" => "&#8715;",
  276. "&prod;" => "&#8719;",
  277. "&sum;" => "&#8721;",
  278. "&minus;" => "&#8722;",
  279. "&lowast;" => "&#8727;",
  280. "&radic;" => "&#8730;",
  281. "&prop;" => "&#8733;",
  282. "&infin;" => "&#8734;",
  283. "&ang;" => "&#8736;",
  284. "&and;" => "&#8743;",
  285. "&or;" => "&#8744;",
  286. "&cap;" => "&#8745;",
  287. "&cup;" => "&#8746;",
  288. "&int;" => "&#8747;",
  289. "&there4;" => "&#8756;",
  290. "&sim;" => "&#8764;",
  291. "&cong;" => "&#8773;",
  292. "&asymp;" => "&#8776;",
  293. "&ne;" => "&#8800;",
  294. "&equiv;" => "&#8801;",
  295. "&le;" => "&#8804;",
  296. "&ge;" => "&#8805;",
  297. "&sub;" => "&#8834;",
  298. "&sup;" => "&#8835;",
  299. "&nsub;" => "&#8836;",
  300. "&sube;" => "&#8838;",
  301. "&supe;" => "&#8839;",
  302. "&oplus;" => "&#8853;",
  303. "&otimes;" => "&#8855;",
  304. "&perp;" => "&#8869;",
  305. "&sdot;" => "&#8901;",
  306. "&lceil;" => "&#8968;",
  307. "&rceil;" => "&#8969;",
  308. "&lfloor;" => "&#8970;",
  309. "&rfloor;" => "&#8971;",
  310. "&lang;" => "&#9001;",
  311. "&rang;" => "&#9002;",
  312. "&loz;" => "&#9674;",
  313. "&spades;" => "&#9824;",
  314. "&clubs;" => "&#9827;",
  315. "&hearts;" => "&#9829;",
  316. "&diams;" => "&#9830;",
  317. "&OElig;" => "&#338;",
  318. "&oelig;" => "&#339;",
  319. "&Scaron;" => "&#352;",
  320. "&scaron;" => "&#353;",
  321. "&Yuml;" => "&#376;",
  322. "&circ;" => "&#710;",
  323. "&tilde;" => "&#732;",
  324. "&ensp;" => "&#8194;",
  325. "&emsp;" => "&#8195;",
  326. "&thinsp;" => "&#8201;",
  327. "&zwnj;" => "&#8204;",
  328. "&zwj;" => "&#8205;",
  329. "&lrm;" => "&#8206;",
  330. "&rlm;" => "&#8207;",
  331. "&ndash;" => "&#8211;",
  332. "&mdash;" => "&#8212;",
  333. "&lsquo;" => "&#8216;",
  334. "&rsquo;" => "&#8217;",
  335. "&sbquo;" => "&#8218;",
  336. "&ldquo;" => "&#8220;",
  337. "&rdquo;" => "&#8221;",
  338. "&bdquo;" => "&#8222;",
  339. "&dagger;" => "&#8224;",
  340. "&Dagger;" => "&#8225;",
  341. "&permil;" => "&#8240;",
  342. "&lsaquo;" => "&#8249;",
  343. "&rsaquo;" => "&#8250;",
  344. "&euro;" => "&#8364;",
  345. );
  346. return strtr($string, $table);
  347. }
  348. /**
  349. * Disable HTML in a string without disabling entities
  350. *
  351. * @param string $string String to un-HTML
  352. * @param bool $rss_mode Do hexadecimal escaping of &, < and > ONLY
  353. * @returns string Parsed $string
  354. */
  355. function unhtml($string, $rss_mode=false) {
  356. $string = htmlspecialchars($string);
  357. //
  358. // Code which is necessary to not break numeric entities (quirky support for strange encodings on a page).
  359. // Broken entities (without trailing ;) at string end are stripped since they break XML well-formedness.
  360. //
  361. if ( strpos($string, '&') !== false )
  362. $string = preg_replace(array('#&amp;\#([0-9]+)#', '#&\#?[a-z0-9]+$#'), array('&#\\1', ''), $string);
  363. //
  364. // RSS mode
  365. //
  366. if ( $rss_mode )
  367. $string = named_entities_to_numeric($string);
  368. return $string;
  369. }
  370. /**
  371. * Gives the length of a string and counts a HTML entitiy as one character.
  372. *
  373. * @param string $string String to find length of
  374. * @returns int Length of $string
  375. */
  376. function entities_strlen($string) {
  377. if ( strpos($string, '&') !== false )
  378. $string = preg_replace('#&\#?[^;]+;#', '.', $string);
  379. return strlen($string);
  380. }
  381. /**
  382. * Right trim a string to $length characters, keeping entities as one character.
  383. *
  384. * @param string $string String to trim
  385. * @param int $length Length of new string
  386. * @returns string Trimmed string
  387. */
  388. function entities_rtrim($string, $length) {
  389. if ( function_exists('mb_language') && mb_language() != 'neutral') {
  390. $strlen = 'mb_strlen';
  391. $substr = 'mb_substr';
  392. } else {
  393. $strlen = 'strlen';
  394. $substr = 'substr';
  395. }
  396. if ( strpos($string, '&') === false )
  397. return $substr($string, 0, $length);
  398. $new_string = '';
  399. $new_length = $pos = 0;
  400. $entity_open = false;
  401. while ( $pos < $strlen($string) && ( $new_length < $length || $entity_open ) ) {
  402. $char = $substr($string, $pos, 1);
  403. if ( $char == '&' ) {
  404. $entity_open = true;
  405. } elseif ( $char == ';' && $entity_open ) {
  406. $entity_open = false;
  407. $new_length++;
  408. } elseif ( !$entity_open ) {
  409. $new_length++;
  410. }
  411. $new_string .= $char;
  412. $pos++;
  413. }
  414. return $new_string;
  415. }
  416. /**
  417. * Check if a variable contains a valid integer.
  418. * If so, correct it (intval).
  419. *
  420. * @param string $string String to check
  421. * @returns bool Contains valid integer
  422. */
  423. function valid_int(&$string) {
  424. if ( $string == strval(intval($string)) ) {
  425. $string = (int) $string;
  426. return true;
  427. } else {
  428. return false;
  429. }
  430. }
  431. /**
  432. * checkdnsrr replacement for Windows
  433. *
  434. * @author Zend.com
  435. * @link http://www.zend.com/codex.php?id=370&single=1
  436. * @param string $host host
  437. * @param string $type type
  438. * @returns bool Contains valid integer
  439. */
  440. function checkdnsrr_win($host, $type='') {
  441. $types = array(
  442. 'A',
  443. 'MX',
  444. 'NS',
  445. 'SOA',
  446. 'PTR',
  447. 'CNAME',
  448. 'AAAA',
  449. 'A6',
  450. 'SRV',
  451. 'NAPTR',
  452. 'ANY'
  453. );
  454. $type = ( !empty($type) && in_array($type, $types) ) ? $type : 'MX';
  455. $output = array();
  456. exec('nslookup -type='.$type.' '.$host, $output);
  457. $host_len = strlen($host);
  458. foreach ( $output as $line ) {
  459. if ( !strncasecmp($line, $host, $host_len) )
  460. return true;
  461. }
  462. return false;
  463. }
  464. /**
  465. * Functions
  466. *
  467. * All kinds of functions used everywhere.
  468. *
  469. * @author UseBB Team
  470. * @link http://www.usebb.net
  471. * @license GPL-2
  472. * @version $Revision$
  473. * @copyright Copyright (C) 2003-2012 UseBB Team
  474. * @package UseBB
  475. * @subpackage Core
  476. */
  477. class functions {
  478. /**#@+
  479. * @access private
  480. */
  481. var $board_config = array();
  482. var $board_config_original = array();
  483. var $board_config_defined = array();
  484. var $statistics = array();
  485. var $languages = array();
  486. var $language_sections = array();
  487. var $mod_auth;
  488. var $badwords;
  489. var $updated_forums;
  490. var $available = array('templates' => array(), 'languages' => array());
  491. var $db_tables = array();
  492. var $server_load;
  493. var $is_mbstring;
  494. var $date_format_from_db = FALSE;
  495. /**#@-*/
  496. /**
  497. * @access private
  498. */
  499. function usebb_die($errno, $error, $file, $line) {
  500. global $db, $dbs, $template, $session;
  501. //
  502. // Ignore the ones we don't want
  503. //
  504. if ( ($errno & error_reporting()) == 0 )
  505. return;
  506. //
  507. // Ignore certain messages
  508. //
  509. foreach ( array(
  510. // Might be disabled
  511. 'ini_set', 'ini_get', 'exec()',
  512. // Available since PHP 5.0.0. Removed in PHP 5.3.0
  513. 'ze1_compatibility_mode',
  514. // Not able to access
  515. '/proc/loadavg',
  516. // Unknown languages and such
  517. 'mb_language',
  518. // Garbage data
  519. 'unserialize'
  520. ) as $ignore_warning ) {
  521. if ( strpos($error, $ignore_warning) !== FALSE )
  522. return;
  523. }
  524. //
  525. // Error processing...
  526. //
  527. $errtypes = array(
  528. 1 => 'E_ERROR',
  529. 2 => 'E_WARNING',
  530. 4 => 'E_PARSE',
  531. 8 => 'E_NOTICE',
  532. 16 => 'E_CORE_ERROR',
  533. 32 => 'E_CORE_WARNING',
  534. 64 => 'E_COMPILE_ERROR',
  535. 128 => 'E_COMPILE_WARNING',
  536. 256 => 'E_USER_ERROR',
  537. 512 => 'E_USER_WARNING',
  538. 1024 => 'E_USER_NOTICE',
  539. 2048 => 'E_STRICT',
  540. 4096 => 'E_RECOVERABLE_ERROR',
  541. 8192 => 'E_DEPRECATED',
  542. 16384 => 'E_USER_DEPRECATED',
  543. );
  544. if ( !strncmp($error, 'SQL:', 4) ) {
  545. $errtype = 'SQL_ERROR';
  546. $error = substr($error, 5);
  547. } else {
  548. $errtype = $errtypes[$errno];
  549. }
  550. //
  551. // Log using PHP's mechanism
  552. //
  553. if ( $this->get_config('enable_error_log') ) {
  554. $ip_addr = ( is_object($session) && !empty($session->sess_info['ip_addr']) ) ? $session->sess_info['ip_addr'] : '?';
  555. error_log('[UseBB Error] '
  556. .'['.date('Y-m-d H:i:s').'] '
  557. .'['.$ip_addr.'] '
  558. .'['.$errtype.' - '.preg_replace('#(?:\s+|\s)#', ' ', $error).'] '
  559. .'['.$file.':'.$line.']');
  560. }
  561. //
  562. // Ignore hidden errors on production env (after being logged).
  563. //
  564. if ( USEBB_IS_PROD_ENV && ( ($errno & (USEBB_DEV_ERROR_LEVEL ^ USEBB_PROD_ERROR_LEVEL)) > 0 ) )
  565. return;
  566. //
  567. // Filter some sensitive data
  568. //
  569. //
  570. // Full script path
  571. //
  572. $full_path = substr(dirname(__FILE__), 0, -7);
  573. $file = str_replace($full_path, '', $file);
  574. $error = str_replace($full_path, '', $error);
  575. //
  576. // MySQL username and host for debug levels < extended
  577. //
  578. if ( ( !strncmp($error, 'mysql', 5) || $errtype == 'SQL_ERROR' ) && $this->get_config('debug') < DEBUG_EXTENDED )
  579. $error = preg_replace("#'[^ ]+'?@'?[^ ]+'#", '<em>-filtered-</em>', $error);
  580. $html_msg = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  581. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  582. <head>
  583. <title>UseBB General Error</title>
  584. <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
  585. <style type="text/css">
  586. body {
  587. font-family: sans-serif;
  588. font-size: 10pt;
  589. }
  590. h1 {
  591. color: #369;
  592. }
  593. blockquote {
  594. width: 55%;
  595. border-top: 2px solid silver;
  596. border-bottom: 2px solid silver;
  597. font-family: monospace;
  598. font-size: 8pt;
  599. }
  600. #error {
  601. color: #7f0000;
  602. }
  603. textarea {
  604. width: 98%;
  605. border: 1px solid silver;
  606. padding: 3px;
  607. }
  608. </style>
  609. </head>
  610. <body>
  611. <h1>UseBB General Error</h1>
  612. <p>An error was encountered. We apologize for any inconvenience.</p>
  613. <blockquote>
  614. <p>In file <strong>'.$file.'</strong> on line <strong>'.$line.'</strong>:</p>
  615. <p id="error"><em>'.$errtype.'</em> - '.nl2br($error).'</p>';
  616. //
  617. // Show query with extended debug
  618. //
  619. if ( $errtype == 'SQL_ERROR' && $this->get_config('debug') == DEBUG_EXTENDED ) {
  620. $used_queries = $db->get_used_queries();
  621. if ( count($used_queries) ) {
  622. $html_msg .= '
  623. <p>SQL query causing the error:</p><p><textarea rows="10" cols="60" readonly="readonly">'.unhtml(end($used_queries)).'</textarea></p>';
  624. }
  625. }
  626. $html_msg .= '
  627. </blockquote>';
  628. //
  629. // Installation note if
  630. // - config.php does not exist
  631. // - error "'install' must be removed"
  632. // - mysql*() error "Access denied for user"
  633. // - sql error "Table 'x' doesn't exist" or "Access denied for user"
  634. //
  635. if ( strpos($error, 'config.php does not exist') !== false
  636. || strpos($error, '\'install\' must be removed') !== false
  637. || ( !strncmp($error, 'mysql', 5) && strpos($error, 'Access denied for user') !== false )
  638. || ( $errtype == 'SQL_ERROR' && preg_match("#(?:Table '.+' doesn't exist|Access denied for user)#i", $error) ) ) {
  639. $html_msg .= '
  640. <p><strong>UseBB may not have been installed yet.</strong></p>
  641. <p>If this is the case and you are the owner of this board, please <a href="docs/index.html">see docs/index.html for <strong>installation instructions</strong></a>.</p>
  642. <p>Otherwise, please report this error to the owner.</p>';
  643. } else {
  644. $html_msg .= '
  645. <p>This error should probably not have occured, so please report it to the owner. Thank you for your help.</p>
  646. <p>If you are the owner of this board and you believe this is a bug, please send a bug report.</p>';
  647. }
  648. $html_msg .= '
  649. </body>
  650. </html>';
  651. if ( isset($template) )
  652. ob_end_clean();
  653. die($html_msg);
  654. }
  655. /**
  656. * Get configuration variables
  657. *
  658. * Rewritten to speed things up and use a cache array at July 8th, 2007.
  659. *
  660. * @param string $setting Setting to retrieve
  661. * @param bool $original Use original config.php configuration
  662. * @returns mixed Value of setting
  663. */
  664. function get_config($setting, $original=false) {
  665. global $session;
  666. //
  667. // Really early stage where config file is not loaded yet.
  668. //
  669. if ( !defined('USEBB_VERSION') )
  670. return FALSE;
  671. //
  672. // Load settings into array.
  673. //
  674. if ( !count($this->board_config_original) ) {
  675. $this->board_config_original = array_merge($GLOBALS['dbs'], $GLOBALS['conf']);
  676. $this->board_config_defined = array_keys($this->board_config_original);
  677. }
  678. //
  679. // users_must_activate was renamed to activation_mode.
  680. //
  681. if ( $setting == 'activation_mode' && !isset($this->board_config_original[$setting]) )
  682. $setting = 'users_must_activate';
  683. //
  684. // Some missing (newer) settings have default values and are added to original config.
  685. //
  686. if ( !isset($this->board_config_original[$setting]) ) {
  687. switch ( $setting ) {
  688. case 'search_limit_results':
  689. case 'sig_max_length':
  690. $set_to = 1000;
  691. break;
  692. case 'search_nonindex_words_min_length':
  693. case 'username_min_length':
  694. $set_to = 3;
  695. break;
  696. case 'enable_ip_bans':
  697. case 'enable_badwords_filter':
  698. case 'guests_can_see_contact_info':
  699. case 'show_raw_entities_in_code':
  700. case 'show_never_activated_members':
  701. case 'disable_xhtml_header':
  702. case 'cookie_httponly':
  703. case 'enable_error_log':
  704. case 'error_log_log_hidden':
  705. case 'dnsbl_powered_banning_globally':
  706. $set_to = true;
  707. break;
  708. case 'view_search_min_level':
  709. case 'view_active_topics_min_level':
  710. $set_to = LEVEL_GUEST;
  711. break;
  712. case 'dnsbl_powered_banning_whitelist':
  713. case 'dnsbl_powered_banning_servers':
  714. $set_to = array();
  715. break;
  716. case 'username_max_length':
  717. $set_to = 30;
  718. break;
  719. case 'edit_post_timeout':
  720. $set_to = 900;
  721. break;
  722. case 'mass_email_msg_recipients':
  723. $set_to = 50;
  724. break;
  725. case 'acp_auto_logout':
  726. $set_to = 10;
  727. break;
  728. default:
  729. $set_to = null;
  730. }
  731. if ( isset($set_to) )
  732. $this->board_config_original[$setting] = $set_to;
  733. }
  734. //
  735. // Get original settings when requested.
  736. // Treat a missing one as "false".
  737. //
  738. if ( defined('IS_INSTALLER') || $original )
  739. return ( isset($this->board_config_original[$setting]) ) ? $this->board_config_original[$setting] : false;
  740. //
  741. // As of here, settings are altered and no longer "original",
  742. // e.g. can contain inherited settings from user accounts or be computed.
  743. //
  744. // ====================
  745. //
  746. //
  747. // Settings cache for this request.
  748. //
  749. if ( isset($this->board_config[$setting]) )
  750. return $this->board_config[$setting];
  751. //
  752. // User-based settings.
  753. //
  754. if ( is_object($session) && !empty($session->sess_info['user_id']) && isset($session->sess_info['user_info'][$setting]) ) {
  755. switch ( $setting ) {
  756. case 'language':
  757. $keep_default = ( !in_array($session->sess_info['user_info'][$setting], $this->get_language_packs()) );
  758. break;
  759. case 'template':
  760. $keep_default = ( !in_array($session->sess_info['user_info'][$setting], $this->get_template_sets()) );
  761. break;
  762. default:
  763. $keep_default = false;
  764. }
  765. $this->board_config[$setting] = ( $keep_default ) ? $this->board_config_original[$setting] : $session->sess_info['user_info'][$setting];
  766. if ( !$keep_default && $setting == 'date_format' )
  767. $this->date_format_from_db = TRUE;
  768. return $this->board_config[$setting];
  769. }
  770. //
  771. // Auto-detected settings when empty.
  772. //
  773. if ( in_array($setting, array('board_url', 'cookie_domain', 'cookie_path')) && empty($this->board_config_original[$setting]) ) {
  774. switch ( $setting ) {
  775. case 'board_url':
  776. $path_parts = pathinfo($_SERVER['SCRIPT_NAME']);
  777. if ( ON_WINDOWS )
  778. $path_parts['dirname'] = str_replace('\\', '/', $path_parts['dirname']);
  779. if ( substr($path_parts['dirname'], -1) != '/' )
  780. $path_parts['dirname'] .= '/';
  781. $protocol = ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ) ? 'https' : 'http';
  782. $set_to = $protocol.'://'.$_SERVER['HTTP_HOST'].$path_parts['dirname'];
  783. break;
  784. case 'cookie_domain':
  785. $set_to = ( !empty($_SERVER['SERVER_NAME']) && preg_match('#^(?:[a-z0-9\-]+\.){1,}[a-z]{2,}$#i', $_SERVER['SERVER_NAME']) ) ? preg_replace('#^www\.#', '.', $_SERVER['SERVER_NAME']) : '';
  786. break;
  787. case 'cookie_path':
  788. $set_to = '/';
  789. }
  790. $this->board_config[$setting] = $set_to;
  791. return $this->board_config[$setting];
  792. }
  793. //
  794. // Settings that need validity checking.
  795. //
  796. if ( in_array($setting, array('board_url', 'session_name', 'debug')) ) {
  797. $set_to = $this->board_config_original[$setting];
  798. if ( $setting == 'board_url' && substr($set_to, -1) != '/' )
  799. $set_to .= '/';
  800. if ( $setting == 'session_name' && ( !preg_match('#^[A-Za-z0-9]+$#', $set_to) || preg_match('#^[0-9]+$#', $set_to) ) )
  801. $set_to = 'usebb';
  802. // Only allow extended debug when not in production environment.
  803. if ( $setting == 'debug' && $set_to == DEBUG_EXTENDED && USEBB_IS_PROD_ENV )
  804. $set_to = DEBUG_SIMPLE;
  805. $this->board_config[$setting] = $set_to;
  806. return $this->board_config[$setting];
  807. }
  808. //
  809. // All other settings taken from the original array.
  810. // Use false when setting does not exist.
  811. //
  812. $this->board_config[$setting] = isset($this->board_config_original[$setting]) ? $this->board_config_original[$setting] : false;
  813. return $this->board_config[$setting];
  814. }
  815. /**
  816. * Get board statistics
  817. *
  818. * @param string $stat Statistical value to retrieve
  819. * @returns mixed Statistical value
  820. */
  821. function get_stats($stat) {
  822. global $db;
  823. //
  824. // Already requested, return
  825. //
  826. if ( isset($this->statistics[$stat]) )
  827. return $this->statistics[$stat];
  828. //
  829. // Get requested value
  830. //
  831. switch ( $stat ) {
  832. case 'categories':
  833. $result = $db->query("SELECT COUNT(id) AS count FROM ".TABLE_PREFIX."cats");
  834. $out = $db->fetch_result($result);
  835. $this->statistics[$stat] = $out['count'];
  836. break;
  837. case 'forums':
  838. $result = $db->query("SELECT COUNT(id) AS count FROM ".TABLE_PREFIX."forums");
  839. $out = $db->fetch_result($result);
  840. $this->statistics[$stat] = $out['count'];
  841. break;
  842. case 'viewable_forums':
  843. $result = $db->query("SELECT id, auth FROM ".TABLE_PREFIX."forums");
  844. $this->statistics[$stat] = 0;
  845. while ( $forumdata = $db->fetch_result($result) ) {
  846. if ( $this->auth($forumdata['auth'], 'view', $forumdata['id']) )
  847. $this->statistics[$stat]++;
  848. }
  849. break;
  850. case 'latest_member':
  851. $never_activated_sql = ( $this->get_config('show_never_activated_members') ) ? "" : " WHERE ( active <> 0 OR last_login <> 0 )";
  852. $result = $db->query("SELECT id, displayed_name, regdate FROM ".TABLE_PREFIX."members".$never_activated_sql." ORDER BY id DESC LIMIT 1");
  853. $this->statistics[$stat] = $db->fetch_result($result);
  854. break;
  855. default:
  856. $result = $db->query("SELECT name, content FROM ".TABLE_PREFIX."stats");
  857. while ( $out = $db->fetch_result($result) )
  858. $this->statistics[$out['name']] = $out['content'];
  859. }
  860. if ( isset($this->statistics[$stat]) )
  861. return $this->statistics[$stat];
  862. else
  863. trigger_error('The statistic variable "'.$stat.'" does not exist!', E_USER_ERROR);
  864. }
  865. /**
  866. * Set board statistics
  867. *
  868. * @param string $stat Statistical value to set
  869. * @param mixed $value New value
  870. * @param bool $add Add to current value or not
  871. */
  872. function set_stats($stat, $value, $add=false) {
  873. global $db;
  874. if ( $add )
  875. $value = $this->get_stats($stat) + $value;
  876. $db->query("UPDATE ".TABLE_PREFIX."stats SET content = '".$value."' WHERE name = '".$stat."'");
  877. $this->statistics[$stat] = $value;
  878. }
  879. /**
  880. * Friendly URL builder
  881. *
  882. * @param string $filename base filename to link to
  883. * @param array $vars GET variabeles
  884. * @returns string URL
  885. */
  886. function _make_friendly_url($filename, $vars) {
  887. if ( $filename == 'index' && count($vars) == 0 )
  888. return './';
  889. $url = $filename;
  890. $keyed = array('forum', 'topic', 'post', 'quotepost', 'al');
  891. foreach ( $vars as $key => $val )
  892. $url .= '-' . urlencode(( in_array($key, $keyed) ) ? $key . $val : $val);
  893. $url .= ( $filename == 'rss' ) ? '.xml' : '.html';
  894. return $url;
  895. }
  896. /**
  897. * Interactive URL builder
  898. *
  899. * @param string $filename .php filename to link to
  900. * @param array $vars GET variabeles
  901. * @param bool $html Return HTML URL
  902. * @param bool $enable_sid Enable session ID's
  903. * @param bool $force_php Force linking to .php files
  904. * @param bool $enable_token Enable token (forces .php link)
  905. * @returns string URL
  906. */
  907. function make_url($filename, $vars=array(), $html=true, $enable_sid=true, $force_php=false, $enable_token=false) {
  908. global $session;
  909. //
  910. // Base name
  911. //
  912. $filename = basename($filename, '.php');
  913. //
  914. // Don't keep session key variable
  915. //
  916. if ( is_array($vars) )
  917. unset($vars[$this->get_config('session_name').'_sid']);
  918. else
  919. $vars = array();
  920. //
  921. // No session IDs for search engines
  922. //
  923. $enable_sid = ( $enable_sid && !$session->is_search_engine() );
  924. //
  925. // No friendly URLs for tokenized URLs, admin, installer and activation links
  926. //
  927. $force_php = ( $force_php || $enable_token || $filename == 'admin' || defined('IS_INSTALLER') || ( $filename == 'panel' && isset($vars['act']) && $vars['act'] == 'activate' ) );
  928. //
  929. // Friendly URLs
  930. //
  931. if ( !$force_php && $this->get_config('friendly_urls') )
  932. return $this->_make_friendly_url($filename, $vars);
  933. //
  934. // Build URL
  935. //
  936. $url = $filename . '.php';
  937. //
  938. // Add session variable when needed
  939. //
  940. $SID = SID;
  941. if ( !empty($SID) && $enable_sid && ( !$html || !ini_get('session.use_trans_sid') ) ) {
  942. $SID_parts = explode('=', $SID, 2);
  943. $vars[$SID_parts[0]] = $SID_parts[1];
  944. }
  945. //
  946. // Add token
  947. //
  948. if ( $enable_token )
  949. $vars['_url_token_'] = $this->generate_token();
  950. if ( count($vars) == 0 )
  951. return $url;
  952. $url .= '?';
  953. $delim = ( $html ) ? '&amp;' : '&';
  954. foreach ( $vars as $key => $val )
  955. $url .= urlencode($key) . '=' . urlencode($val) . $delim;
  956. return substr($url, 0, - strlen($delim));
  957. }
  958. /**
  959. * Attaches a SID to URLs which should contain one (e.g. referer URLs)
  960. *
  961. * @param string $url URL
  962. * @returns string URL
  963. */
  964. function attach_sid($url) {
  965. $SID = SID;
  966. if ( empty($SID) || $this->get_config('friendly_urls') || preg_match('/'.preg_quote($SID, '/').'$/', $url) )
  967. return $url;
  968. if ( strpos($url, '?') !== false )
  969. return $url . '&' . $SID;
  970. return $url . '?' . $SID;
  971. }
  972. /**
  973. * Fetch a language file
  974. *
  975. * @param string $language Language name (default language is used when missing)
  976. * @param string $section Section name (main section is used when missing)
  977. * @returns array Language variables
  978. */
  979. function fetch_language($language='', $section='') {
  980. $language = ( !empty($language) && in_array($language, $this->get_language_packs()) ) ? $language : $this->get_config('language');
  981. $section = ( !empty($section) ) ? $section : 'lang';
  982. if ( !isset($this->language_sections[$language]) || !in_array($section, $this->language_sections[$language]) ) {
  983. //
  984. // Not loaded yet
  985. //
  986. if ( $section != 'lang' ) {
  987. //
  988. // Add to current $lang
  989. //
  990. $lang = $GLOBALS['lang'];
  991. if ( !file_exists(ROOT_PATH.'languages/'.$section.'_'.$language.'.php') ) {
  992. //
  993. // Fallback to English
  994. //
  995. if ( $language != 'English' && in_array('English', $this->get_language_packs()) )
  996. require(ROOT_PATH.'languages/'.$section.'_English.php');
  997. else
  998. trigger_error('Section "'.$section.'" for language pack "'.$language.'" could not be found. No English fallback was available. Please use an updated language pack or also upload the English one.', E_USER_ERROR);
  999. } else {
  1000. require(ROOT_PATH.'languages/'.$section.'_'.$language.'.php');
  1001. //
  1002. // Merge with English for missing strings
  1003. //
  1004. if ( $language != 'English' && in_array('English', $this->get_language_packs()) )
  1005. $lang = array_merge($this->fetch_language('English', $section), $lang);
  1006. }
  1007. } else {
  1008. require(ROOT_PATH.'languages/'.$section.'_'.$language.'.php');
  1009. //
  1010. // Merge with English for missing strings
  1011. //
  1012. if ( $language != 'English' && in_array('English', $this->get_language_packs()) )
  1013. $lang = array_merge($this->fetch_language('English', $section), $lang);
  1014. if ( empty($lang['character_encoding']) )
  1015. $lang['character_encoding'] = 'iso-8859-1';
  1016. //
  1017. // UTF-8 patching
  1018. //
  1019. if ( function_exists('mb_internal_encoding') ) {
  1020. // Setting mbstring
  1021. $mb_internal_encoding = ( $lang['character_encoding'] == 'iso-8859-8-i' ) ? 'iso-8859-8' : $lang['character_encoding'];
  1022. $is_mb_language = mb_language($language);
  1023. $is_mb_internal_encoding = mb_internal_encoding($mb_internal_encoding);
  1024. if ( $is_mb_language !== FALSE || $is_mb_internal_encoding !== FALSE ) {
  1025. $this->is_mbstring = TRUE;
  1026. } else {
  1027. // mbstring can not be used, reset
  1028. mb_language('neutral');
  1029. mb_internal_encoding('ISO-8859-1');
  1030. }
  1031. // Reset other parameters
  1032. ini_set('mbstring.http_input', 'pass');
  1033. ini_set('mbstring.http_output', 'pass');
  1034. ini_set('mbstring.func_overload', 0);
  1035. ini_set('mbstring.substitute_character', 'none');
  1036. }
  1037. }
  1038. $this->languages[$language] = $lang;
  1039. }
  1040. if ( !isset($this->language_sections[$language]) )
  1041. $this->language_sections[$language] = array();
  1042. $this->language_sections[$language][] = $section;
  1043. $returned = &$this->languages[$language];
  1044. return $returned;
  1045. }
  1046. /**
  1047. * Kick a user to the login form
  1048. */
  1049. function redir_to_login() {
  1050. global $session, $template, $lang;
  1051. if ( !$session->sess_info['user_id'] ) {
  1052. $_SESSION['referer'] = $_SERVER['REQUEST_URI'];
  1053. $this->redirect('panel.php', array('act' => 'login'));
  1054. } else {
  1055. header(HEADER_403);
  1056. $template->clear_breadcrumbs();
  1057. $template->add_breadcrumb($lang['Note']);
  1058. $template->parse('msgbox', 'global', array(
  1059. 'box_title' => $lang['Note'],
  1060. 'content' => $lang['NotPermitted']
  1061. ));
  1062. }
  1063. }
  1064. /**
  1065. * Generate a date given a timestamp
  1066. *
  1067. * @param int $stamp Unix timestamp
  1068. * @param string $format Date format syntax (identical to PHP's date() - default is used when missing)
  1069. * @param bool $keep_gmt Use GMT and no time zones
  1070. * @param bool $translate Localize dates
  1071. * @returns string Date
  1072. */
  1073. function make_date($stamp, $format='', $keep_gmt=false, $translate=true) {
  1074. global $lang;
  1075. $format = ( !empty($format) ) ? $format : strip_tags($this->get_config('date_format'));
  1076. if ( $this->date_format_from_db )
  1077. $format = stripslashes($format);
  1078. if ( $keep_gmt )
  1079. $date = gmdate($format, $stamp);
  1080. else
  1081. $date = gmdate($format, $stamp + (3600 * $this->get_config('timezone')) + (3600 * $this->get_config('dst')));
  1082. if ( $translate && isset($lang['date_translations']) && is_array($lang['date_translations']) )
  1083. $date = ucfirst(strtr($date, $lang['date_translations']));
  1084. return $date;
  1085. }
  1086. /**
  1087. * Generate a time past string
  1088. *
  1089. * @param int $timestamp Unix timestamp
  1090. * @param int $until Calculate time past until this Unix timestamp (current is used when missing)
  1091. * @returns string Time past
  1092. */
  1093. function time_past($timestamp, $until=null) {
  1094. global $lang;
  1095. $seconds = ( ( is_int($until) ) ? $until : time() ) - $timestamp;
  1096. $times = array();
  1097. $sections = array(
  1098. 'weeks' => 604800,
  1099. 'days' => 86400,
  1100. 'hours' => 3600,
  1101. 'minutes' => 60,
  1102. 'seconds' => 1
  1103. );
  1104. foreach( $sections as $what => $length ) {
  1105. if ( $seconds >= $length ) {
  1106. $times[$what] = ( $length >0 ) ? floor($seconds / $length) : $length;
  1107. $seconds %= $length;
  1108. }
  1109. }
  1110. $sections = array();
  1111. foreach ( $times as $key => $val )
  1112. $sections[] = $val.' '.$lang[ucfirst($key)];
  1113. return array($times, join(', ', $sections));
  1114. }
  1115. /**
  1116. * Generate an e-mail link/text
  1117. *
  1118. * @param array $user User information containing id, email and email_show
  1119. * @returns string HTML
  1120. */
  1121. function show_email($user) {
  1122. global $session, $lang;
  1123. //
  1124. // Possible email_view_level values:
  1125. // - 0: Hide all
  1126. // - 1: Use mail form
  1127. // - 2: Show spam proof
  1128. // - 3: Show raw
  1129. //
  1130. $email_view_level = $this->get_config('email_view_level');
  1131. if ( $this->get_user_level() >= $this->get_config('view_hidden_email_addresses_min_level') ) {
  1132. //
  1133. // This user may view hidden e-mail addresses
  1134. //
  1135. $return = '<a href="mailto:'.$user['email'].'">'.$user['email'].'</a>';
  1136. if ( $email_view_level == 1 )
  1137. $return = '<a href="'.$this->make_url('mail.php', array('id' => $user['id'])).'">'.$lang['SendMessage'].'</a> ('.$return.')';
  1138. } else {
  1139. if ( $email_view_level == 0 || ( !$user['email_show'] && $user['id'] != $session->sess_info['user_id'] ) ) {
  1140. //
  1141. // E-mail addresses are hidden or the user has chosen to keep it hidden
  1142. //
  1143. $return = $lang['Hidden'];
  1144. } else {
  1145. switch ( $email_view_level ) {
  1146. case 1:
  1147. $return = '<a href="'.$this->make_url('mail.php', array('id' => $user['id'])).'">'.$lang['SendMessage'].'</a>';
  1148. break;
  1149. case 2:
  1150. $user['email'] = $this->string_to_entities($user['email']);
  1151. // No break here, since we just want to convert $user['email']
  1152. default:
  1153. $return = '<a href="mailto:'.$user['email'].'">'.$user['email'].'</a>';
  1154. }
  1155. }
  1156. }
  1157. return $return;
  1158. }
  1159. /**
  1160. * Translate an ASCII string to HTML entities
  1161. *
  1162. * This function only works for ASCII characters, nothing else.
  1163. *
  1164. * @param string $string String to convert
  1165. * @returns string Converted string
  1166. */
  1167. function string_to_entities($string) {
  1168. $length = strlen($string);
  1169. $new_string = '';
  1170. for ( $i = 0; $i < $length; $i++ )
  1171. $new_string .= '&#'.ord(substr($string, $i, $i+1)).';';
  1172. return $new_string;
  1173. }
  1174. /**
  1175. * Generate a random key
  1176. *
  1177. * @param bool $is_password Is the random key used as a password?
  1178. * @returns string Random key
  1179. */
  1180. function random_key($is_password=false) {
  1181. if ( !$is_password )
  1182. return md5(mt_rand());
  1183. $chars = range(33, 126); // ! until ~
  1184. $max = count($chars) - 1;
  1185. $passwd_min_length = (int) $this->get_config('passwd_min_length');
  1186. $length = ( $passwd_min_length > 10 ) ? $passwd_min_length : 10;
  1187. do {
  1188. $key = '';
  1189. for ( $i = 0; $i < $length; $i++ )
  1190. $key .= chr($chars[mt_rand(0, $max)]);
  1191. $valid = $this->validate_password($key, true);
  1192. } while ( !$valid );
  1193. return $key;
  1194. }
  1195. /**
  1196. * Send an email
  1197. *
  1198. * Why don't they just send me an e-mail? -- Belgian ad for coffee
  1199. *
  1200. * @param string $subject Subject of e-mail
  1201. * @param string $rawbody Body of e-mail
  1202. * @param array $bodyvars Variables for e-mail body
  1203. * @param string $from_name Name of sender
  1204. * @param string $from_email E-mail of sender
  1205. * @param string $to E-mail of recipient
  1206. * @param string $bcc_email E-mail of BCC recipient (no BCC when missing)
  1207. * @param string $language Language name the e-mail is in (default language when missing)
  1208. * @param string $charset Character set the e-mail is in (default charset when missing)
  1209. */
  1210. function usebb_mail($subject, $rawbody, $bodyvars=array(), $from_name, $from_email, $to, $bcc_email='', $language='', $charset='') {
  1211. global $lang;
  1212. $bodyvars = ( is_array($bodyvars) ) ? $bodyvars : array();
  1213. $is_enable_mbstring = ( function_exists('mb_language') && mb_language() != 'neutral' );
  1214. //
  1215. // Eventually use the right language and character encoding which may be passed
  1216. // in the parameters when another language is used (e.g. subscription notices)
  1217. //
  1218. $language = ( !empty($language) ) ? $language : $this->get_config('language');
  1219. $charset = ( !empty($charset) ) ? $charset : $lang['character_encoding'];
  1220. //
  1221. // Set the correct mb_language when neccessary (when mbstring enabled)
  1222. //
  1223. $is_mbstring = FALSE;
  1224. if ( $this->is_mbstring ) {
  1225. $backup_mb_language = mb_language();
  1226. $backup_mb_internal_encoding = mb_internal_encoding();
  1227. if ( @mb_language($language) !== FALSE && @mb_internal_encoding($charset) !== FALSE )
  1228. $is_mbstring = TRUE;
  1229. }
  1230. $body = str_replace(array("\r\n", "\r"), "\n", $rawbody);
  1231. //
  1232. // Windows: \r\n; other: \n
  1233. //
  1234. $cr = ( ON_WINDOWS ) ? "\r\n" : "\n";
  1235. $body = str_replace("\n", $cr, $rawbody);
  1236. $bodyvars['board_name'] = $this->get_config('board_name');
  1237. $bodyvars['board_link'] = $this->get_config('board_url');
  1238. $bodyvars['admin_email'] = $this->get_config('admin_email');
  1239. foreach ( $bodyvars as $key => $val )
  1240. $body = str_replace('['.$key.']', $val, $body);
  1241. $headers = array();
  1242. if ( $is_mbstring && function_exists('mb_encode_mimeheader') ) {
  1243. $from_name = mb_encode_mimeheader($from_name);
  1244. } else {
  1245. if ( strtolower($charset) == 'utf-8' ) {
  1246. $subject = '=?'.$charset.'?B?'.base64_encode($subject).'?=';
  1247. $from_name = '=?'.$charset.'?B?'.base64_encode($from_name).'?=';
  1248. }
  1249. }
  1250. if ( !empty($bcc_email) )
  1251. $headers[] = 'Bcc: '.$bcc_email;
  1252. $headers[] = 'Date: '.date('r');
  1253. $headers[] = 'Message-Id: '.sprintf("<%s.%s>", substr(md5(time()), 4, 10), $from_email);
  1254. $headers[] = 'X-Mailer: UseBB';
  1255. //
  1256. // Fix for hosts that require From to be a domain name hosted on the same host
  1257. // So, instead we can use a Reply-To header to contain the sender email
  1258. //
  1259. if ( $from_email != $this->get_config('admin_email') && $this->get_config('email_reply-to_header') ) {
  1260. $headers[] = 'From: "'.$from_name.'" <'.$this->get_config('admin_email').'>';
  1261. $headers[] = 'Reply-To: '.$from_email;
  1262. } else {
  1263. $headers[] = 'From: "'.$from_name.'" <'.$from_email.'>';
  1264. }
  1265. // TODO safe mode to be removed in PHP 5.4
  1266. $is_safe_mode = in_array(strtolower(ini_get('safe_mode')), array('1', 'on'));
  1267. if ( $is_mbstring && function_exists('mb_send_mail') ) {
  1268. $mail_func = 'mb_send_mail';
  1269. } else {
  1270. $mail_func = 'mail';
  1271. $headers[] = 'MIME-Version: 1.0';
  1272. $headers[] = 'Content-Type: text/plain; charset='.$charset;
  1273. if ( preg_match('/^(iso-8859-|iso-2022-)/i', $charset))
  1274. $headers[] = 'Content-Transfer-Encoding: 7bit';
  1275. else
  1276. $headers[] = 'Content-Transfer-Encoding: 8bit';
  1277. }
  1278. if ( $is_safe_mode || !$this->get_config('sendmail_sender_parameter') )
  1279. $mail_result = $mail_func($to, $subject, $body, join($cr, $headers));
  1280. else
  1281. $mail_result = $mail_func($to, $subject, $body, join($cr, $headers), '-f'.$from_email);
  1282. if ( !$mail_result )
  1283. trigger_error('Unable to send e-mail!', E_USER_ERROR);
  1284. //
  1285. // Restored language and character encoding.
  1286. //
  1287. if ( $this->is_mbstring ) {
  1288. mb_language($backup_mb_language);
  1289. mb_internal_encoding($backup_mb_internal_encoding);
  1290. }
  1291. }
  1292. /**
  1293. * Set the remember cookie
  1294. *
  1295. * @param int $user_id User ID
  1296. * @param string $passwd_hash Password hash
  1297. */
  1298. function set_al($user_id, $passwd_hash) {
  1299. $content = array(
  1300. intval($user_id),
  1301. $passwd_hash
  1302. );
  1303. $this->setcookie($this->get_config('session_name').'_al', serialize($content), time()+31536000);
  1304. }
  1305. /**
  1306. * Unset the remember cookie
  1307. */
  1308. function unset_al() {
  1309. $this->setcookie($this->get_config('session_name').'_al', '');
  1310. }
  1311. /**
  1312. * Is the remember cookie set?
  1313. *
  1314. * @returns bool Remember cookie set
  1315. */
  1316. function isset_al() {
  1317. $cookie_name = $this->get_config('session_name').'_al';
  1318. if ( empty($_COOKIE[$cookie_name]) )
  1319. return FALSE;
  1320. $value = stripslashes($_COOKIE[$cookie_name]);
  1321. return ( preg_match('/^a:2:\{i:0;i:[0-9]+;i:1;s:32:"[a-z0-9]{32}";\}$/', $value) === 1 );
  1322. }
  1323. /**
  1324. * Get the remember cookie's value
  1325. *
  1326. * @returns mixed Array with user ID and password hash -or- false when not set
  1327. */
  1328. function get_al() {
  1329. if ( !$this->isset_al() )
  1330. return FALSE;
  1331. return unserialize(stripslashes($_COOKIE[$this->get_config('session_name').'_al']));
  1332. }
  1333. /**
  1334. * Get the user's level
  1335. *
  1336. * @returns int User level
  1337. */
  1338. function get_user_level() {
  1339. global $session;
  1340. if ( !isset($session->sess_info['user_id']) )
  1341. trigger_error('You first need to call $session->update() before you can get any session info.', E_USER_ERROR);
  1342. if ( $session->sess_info['user_id'] )
  1343. return $session->sess_info['user_info']['level'];
  1344. else
  1345. return LEVEL_GUEST;
  1346. }
  1347. /**
  1348. * Authorization function
  1349. *
  1350. * Defines whether a user has permission to take a certain action.
  1351. *
  1352. * @param string $auth_int Authorization "integer" (string because of leading zeroes)
  1353. * @param string $action Action to establish
  1354. * @param int $forum_id ID of forum
  1355. * @param bool $self For own account
  1356. * @param array $alternative_user_info When not for own account, array with user information
  1357. * @returns bool Allowed
  1358. */
  1359. function auth($auth_int, $action, $forum_id, $self=true, $alternative_user_info=null) {
  1360. global $session, $db;
  1361. if ( $self )
  1362. $user_info = ( $session->sess_info['user_id'] ) ? $session->sess_info['user_info'] : array('id' => LEVEL_GUEST, 'level' => LEVEL_GUEST);
  1363. else
  1364. $user_info = $alternative_user_info;
  1365. if ( ( $self && $session->sess_info['ip_banned'] ) || ( $this->get_config('board_closed') && $user_info['level'] < LEVEL_ADMIN ) )
  1366. return false;
  1367. //
  1368. // Define the user level
  1369. //
  1370. if ( $user_info['id'] ) {
  1371. //
  1372. // Logged in user
  1373. //
  1374. if ( $user_info['level'] == LEVEL_MOD ) {
  1375. if ( !is_array($this->mod_auth) ) {
  1376. $result = $db->query("SELECT forum_id FROM ".TABLE_PREFIX."moderators WHERE user_id = ".$user_info['id']);
  1377. $this->mod_auth = array();
  1378. while ( $out = $db->fetch_result($result) )
  1379. $this->mod_auth[] = intval($out['forum_id']);
  1380. }
  1381. $userlevel = ( in_array($forum_id, $this->mod_auth) ) ? LEVEL_MOD : LEVEL_MEMBER;
  1382. } else {
  1383. $userlevel = $user_info['level'];
  1384. }
  1385. } else {
  1386. //
  1387. // Guest
  1388. //
  1389. if ( !$this->get_config('guests_can_access_board') )
  1390. return false;
  1391. else
  1392. $userlevel = LEVEL_GUEST;
  1393. }
  1394. //
  1395. // Get the part of the auth integer that
  1396. // corresponds with the action given
  1397. //
  1398. $actions = array(
  1399. 'view' => 0,
  1400. 'read' => 1,
  1401. 'post' => 2,
  1402. 'reply' => 3,
  1403. 'edit' => 4,
  1404. 'move' => 5,
  1405. 'delete' => 6,
  1406. 'lock' => 7,
  1407. 'sticky' => 8,
  1408. 'html' => 9
  1409. );
  1410. $min_level = intval($auth_int[$actions[$action]]);
  1411. //
  1412. // If the user level is equal or greater than the
  1413. // auth integer, return a true, otherwise return a false.
  1414. //
  1415. if ( $userlevel >= $min_level )
  1416. return true;
  1417. else
  1418. return false;
  1419. }
  1420. /**
  1421. * Return a list of moderators, clickable and separated with commas
  1422. *
  1423. * @param int $forum Forum ID
  1424. * @param array $listarray Array with all moderators (automatically requested when missing)
  1425. * @returns string Moderator list
  1426. */
  1427. function get_mods_list($forum, $listarray=false) {
  1428. global $db, $lang;
  1429. $forum_moderators = array();
  1430. if ( is_array($listarray) && count($listarray) ) {
  1431. foreach ( $listarray as $modsdata ) {
  1432. if ( $modsdata['forum_id'] == $forum )
  1433. $forum_moderators[] = $this->make_profile_link($modsdata['id'], $modsdata['displayed_name'], $modsdata['level']);
  1434. }
  1435. if ( !count($forum_moderators) ) {
  1436. return $lang['Nobody'];
  1437. }
  1438. } else {
  1439. $result = $db->query("SELECT u.id, u.displayed_name, u.level FROM ".TABLE_PREFIX."members u, ".TABLE_PREFIX."moderators m WHERE m.forum_id = ".$forum." AND m.user_id = u.id ORDER BY u.displayed_name");
  1440. while ( $modsdata = $db->fetch_result($result) )
  1441. $forum_moderators[] = $this->make_profile_link($modsdata['id'], $modsdata['displayed_name'], $modsdata['level']);
  1442. if ( !count($forum_moderators) ) {
  1443. return $lang['Nobody'];
  1444. }
  1445. }
  1446. //
  1447. // Join all values in the array
  1448. //
  1449. return join(', ', $forum_moderators);
  1450. }
  1451. /**
  1452. * Return a clickable list of pages
  1453. *
  1454. * @param int $pages_number Total number of pages
  1455. * @param int $current_page Current page
  1456. * @param int $items_number Number of items
  1457. * @param int $items_per_page Items per page
  1458. * @param string $page_name .php page name
  1459. * @param int $page_id_val URL id GET value
  1460. * @param bool $back_forward_links Enable back and forward links
  1461. * @param array $url_vars Other URL vars
  1462. * @param bool $force_php Force linking to .php files
  1463. * @returns string HTML
  1464. */
  1465. function make_page_links($pages_number, $current_page, $items_number, $items_per_page, $page_name, $page_id_val=NULL, $back_forward_links=true, $url_vars=array(), $force_php=false) {
  1466. global $lang;
  1467. if ( intval($items_number) > intval($items_per_page) ) {
  1468. $page_links = array();
  1469. $page_links_groups_length = 4;
  1470. if ( !$current_page ) {
  1471. $current_page = $pages_number+1;
  1472. $page_links_groups_length++;
  1473. }
  1474. for ( $i = 1; $i <= $pages_number; $i++ ) {
  1475. if ( $current_page != $i ) {
  1476. if ( $i+$page_links_groups_length >= $current_page && $i-$page_links_groups_length <= $current_page ) {
  1477. if ( valid_int($page_id_val) )
  1478. $url_vars['id'] = $page_id_val;
  1479. $url_vars['page'] = $i;
  1480. $page_links[] = '<a href="'.$this->make_url($page_name, $url_vars, true, true, $force_php).'">'.$i.'</a>';
  1481. } else {
  1482. if ( end($page_links) != '...' )
  1483. $page_links[] = '...';
  1484. }
  1485. } else {
  1486. $page_links[] = '<strong>'.$i.'</strong>';
  1487. }
  1488. }
  1489. $page_links = join(' ', $page_links);
  1490. if ( $back_forward_links ) {
  1491. if ( valid_int($page_id_val) )
  1492. $url_vars['id'] = $page_id_val;
  1493. if ( $current_page > 1 ) {
  1494. $url_vars['page'] = $current_page-1;
  1495. $page_links = '<a href="'.$this->make_url($page_name, $url_vars, true, true, $force_php).'">&lt;</a> '.$page_links;
  1496. }
  1497. if ( $current_page < $pages_number ) {
  1498. $url_vars['page'] = $current_page+1;
  1499. $page_links .= ' <a href="'.$this->make_url($page_name, $url_vars, true, true, $force_php).'">&gt;</a>';
  1500. }
  1501. if ( $current_page > 2 ) {
  1502. $url_vars['page'] = 1;
  1503. $page_links = '<a href="'.$this->make_url($page_name, $url_vars, true, true, $force_php).'">&laquo;</a> '.$page_links;
  1504. }
  1505. if ( $current_page+1 < $pages_number ) {
  1506. $url_vars['page'] = $pages_number;
  1507. $page_links .= ' <a href="'.$this->make_url($page_name, $url_vars, true, true, $force_php).'">&raquo;</a>';
  1508. }
  1509. }
  1510. $page_links = sprintf($lang['PageLinks'], $page_links);
  1511. } else {
  1512. $page_links = sprintf($lang['PageLinks'], '1');
  1513. }
  1514. return $page_links;
  1515. }
  1516. /**
  1517. * Removes BBCode
  1518. *
  1519. * @param string $string Text string to clean
  1520. * @returns string Cleaned text
  1521. */
  1522. function bbcode_clear($string) {
  1523. $existing_tags = array('code', 'b', 'i', 'u', 's', 'img', 'url', 'mailto', 'color', 'size', 'google', 'quote');
  1524. return preg_replace('#\[/?(?:'.join('|', $existing_tags).')(?:=[^\]]*)?\]#i', '', $string);
  1525. }
  1526. /**
  1527. * Check if a post is empty
  1528. *
  1529. * Checks if the post is empty, with and without BBCode
  1530. *
  1531. * @param string $string Text
  1532. * @returns bool Is empty
  1533. */
  1534. function post_empty(&$string) {
  1535. if ( empty($string) || is_array($string) )
  1536. return true;
  1537. $copy = $string;
  1538. $copy = $this->bbcode_clear($copy);
  1539. if ( empty($copy) )
  1540. return true;
  1541. return false;
  1542. }
  1543. /**
  1544. * Cleans up BBCode for parsing
  1545. *
  1546. * Automatically called from within ::markup.
  1547. *
  1548. * @param string $string Text string to preparse
  1549. * @returns string Corrected BBCoded text
  1550. */
  1551. function bbcode_prepare($string) {
  1552. $string = trim($string);
  1553. $existing_tags = array('code', 'b', 'i', 'u', 's', 'img', 'url', 'mailto', 'color', 'size', 'google', 'quote');
  1554. //
  1555. // BBCode tags start with an alphabetic character, eventually followed by non [ and ] characters.
  1556. //
  1557. $parts = array_reverse(preg_split('#(\[/?[a-z][^\[\]]*\])#i', $string, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
  1558. $open_tags = $open_parameters = array();
  1559. $new_string = '';
  1560. while ( count($parts) ) {
  1561. $part = array_pop($parts);
  1562. $matches = array();
  1563. //
  1564. // Add open tag
  1565. //
  1566. if ( preg_match('#^\[([a-z]+)(=[^\]]*)?\]$#i', $part, $matches) ) {
  1567. $matches[1] = strtolower($matches[1]);
  1568. //
  1569. // Transform tags
  1570. //
  1571. if ( end($open_tags) == 'code' ) {
  1572. $new_string .= str_replace(array('[', ']'), array('&#91;', '&#93;'), $part);
  1573. continue;
  1574. }
  1575. //
  1576. // Is already open
  1577. //
  1578. if ( $matches[1] != 'quote' && in_array($matches[1], $open_tags) )
  1579. continue;
  1580. //
  1581. // Only add this if it exists
  1582. //
  1583. if ( in_array($matches[1], $existing_tags) ) {
  1584. array_push($open_tags, $matches[1]);
  1585. array_push($open_parameters, ( isset($matches[2]) ) ? $matches[2] : '');
  1586. }
  1587. $new_string .= $part;
  1588. continue;
  1589. }
  1590. //
  1591. // Add close tag
  1592. //
  1593. if ( preg_match('#^\[/([a-z]+)\]$#i', $part, $matches) ) {
  1594. $matches[1] = strtolower($matches[1]);
  1595. //
  1596. // Transform tags
  1597. //
  1598. if ( end($open_tags) == 'code' && $matches[1] != 'code' ) {
  1599. $new_string .= str_replace(array('[', ']'), array('&#91;', '&#93;'), $part);
  1600. continue;
  1601. }
  1602. //
  1603. // Unexisting tag
  1604. //
  1605. if ( !in_array($matches[1], $existing_tags) ) {
  1606. $new_string .= $part;
  1607. continue;
  1608. }
  1609. //
  1610. // Is current open tag
  1611. //
  1612. if ( end($open_tags) == $matches[1] ) {
  1613. array_pop($open_tags);
  1614. array_pop($open_parameters);
  1615. $new_string .= $part;
  1616. continue;
  1617. }
  1618. //
  1619. // Is other open tag
  1620. //
  1621. if ( in_array($matches[1], $open_tags) ) {
  1622. $to_reopen_tags = $to_reopen_parameters = array();
  1623. while ( $open_tag = array_pop($open_tags) ) {
  1624. $open_parameter = array_pop($open_parameters);
  1625. $new_string .= '[/'.$open_tag.']';
  1626. if ( $open_tag == $matches[1] )
  1627. break;
  1628. array_push($to_reopen_tags, $open_tag);
  1629. array_push($to_reopen_parameters, $open_parameter);
  1630. }
  1631. $to_reopen_tags = array_reverse($to_reopen_tags);
  1632. $to_reopen_parameters = array_reverse($to_reopen_parameters);
  1633. while ( $open_tag = array_pop($to_reopen_tags) ) {
  1634. $open_parameter = array_pop($to_reopen_parameters);
  1635. $new_string .= '['.$open_tag.$open_parameter.']';
  1636. array_push($open_tags, $open_tag);
  1637. array_push($open_parameters, $open_parameter);
  1638. }
  1639. }
  1640. } else {
  1641. //
  1642. // Plain text
  1643. //
  1644. $new_string .= ( end($open_tags) == 'code' && $this->get_config('show_raw_entities_in_code') ) ? str_replace('&#', '&amp;#', $part) : $part;
  1645. }
  1646. }
  1647. //
  1648. // Close opened tags
  1649. //
  1650. while ( $open_tag = array_pop($open_tags) ) {
  1651. $open_parameter = array_pop($open_parameters);
  1652. $new_string .= '[/'.$open_tag.$open_parameter.']';
  1653. }
  1654. //
  1655. // Remove empties
  1656. //
  1657. foreach ( $existing_tags as $existing_tag )
  1658. $new_string = preg_replace('#\[('.$existing_tag.')([^\]]+)?\]\[/(\1)\]#i', '', $new_string);
  1659. return $new_string;
  1660. }
  1661. /**
  1662. * Apply BBCode and smilies to a string
  1663. *
  1664. * @param string $string String to markup
  1665. * @param bool $bbcode Enable BBCode
  1666. * @param bool $smilies Enable smilies
  1667. * @param bool $html Enable HTML
  1668. * @param bool $rss_mode Enable RSS mode
  1669. * @param bool $links Enable links parsing
  1670. * @returns string HTML
  1671. */
  1672. function markup($string, $bbcode=true, $smilies=true, $html=false, $rss_mode=false, $links=true) {
  1673. global $db, $template, $lang;
  1674. static $random;
  1675. $string = preg_replace('#(script|about|applet|activex|chrome):#is', '\\1&#058;', $string);
  1676. //
  1677. // Needed by some BBCode regexps and smilies
  1678. //
  1679. $string = ' '.$string.' ';
  1680. if ( !$html )
  1681. $string = unhtml($string, $rss_mode);
  1682. if ( $smilies ) {
  1683. $all_smilies = $template->get_config('smilies');
  1684. krsort($all_smilies);
  1685. $full_path = ( $rss_mode ) ? $this->get_config('board_url') : ROOT_PATH;
  1686. foreach ( $all_smilies as $pattern => $img )
  1687. $string = preg_replace('#([^"])('.preg_quote(unhtml($pattern), '#').')#', '\\1<img src="'.$full_path.'templates/'.$this->get_config('template').'/smilies/'.$img.'" alt="'.unhtml($pattern).'" />', $string);
  1688. //
  1689. // Entity + smiley fix
  1690. //
  1691. $string = preg_replace('#(&\#?[a-zA-Z0-9]+)<img src="[^"]+" alt="([^"]+)" />#', '\\1\\2', $string);
  1692. }
  1693. if ( $bbcode ) {
  1694. $string = ' '.$this->bbcode_prepare($string).' ';
  1695. $rel = array();
  1696. if ( $this->get_config('target_blank') )
  1697. $rel[] = 'external';
  1698. if ( $this->get_config('rel_nofollow') )
  1699. $rel[] = 'nofollow';
  1700. $rel = ( count($rel) ) ? ' rel="'.join(' ', $rel).'"' : '';
  1701. //
  1702. // Protect from infinite loops.
  1703. // The while loop to parse nested quote tags has the sad side-effect of entering an infinite loop
  1704. // when the parsed text contains $0 or \0.
  1705. // Admittedly, this is a quick and dirty fix. For a nice "fix" I refer to the stack based parser in 2.0.
  1706. //
  1707. if ( $random == NULL )
  1708. $random = $this->random_key();
  1709. $string = str_replace(array('$', "\\"), array('&#36;'.$random, '&#92;'.$random), $string);
  1710. //
  1711. // Parse quote tags
  1712. //
  1713. // Might seem a bit difficultly done, but trimming doesn't work the usual way
  1714. //
  1715. while ( preg_match("#\[quote\](.*?)\[/quote\]#is", $string, $matches) ) {
  1716. $string = preg_replace("#\[quote\]".preg_quote($matches[1], '#')."\[/quote\]#is", sprintf($template->get_config('quote_format'), $lang['Quote'], ' '.trim($matches[1])).' ', $string);
  1717. unset($matches);
  1718. }
  1719. while ( preg_match("#\[quote=(.*?)\](.*?)\[/quote\]#is", $string, $matches) ) {
  1720. $string = preg_replace("#\[quote=".preg_quote($matches[1], '#')."\]".preg_quote($matches[2], '#')."\[/quote\]#is", sprintf($template->get_config('quote_format'), sprintf($lang['Wrote'], $matches[1]), ' '.trim($matches[2]).' '), $string);
  1721. unset($matches);
  1722. }
  1723. //
  1724. // Undo the dirty fixing.
  1725. //
  1726. $string = str_replace(array('&#36;'.$random, '&#92;'.$random), array('$', "\\"), $string);
  1727. //
  1728. // Parse code tags
  1729. //
  1730. preg_match_all("#\[code\](.*?)\[/code\]#is", $string, $matches);
  1731. foreach ( $matches[1] as $oldpart ) {
  1732. $newpart = preg_replace(array('#<img src="[^"]+" alt="([^"]+)" />#', "#\n#", "#\r#"), array('\\1', '<br />', ''), $oldpart); // replace smiley image tags
  1733. $string = str_replace('[code]'.$oldpart.'[/code]', '[code]'.$newpart.'[/code]', $string);
  1734. }
  1735. $string = preg_replace("#\[code\](.*?)\[/code\]#is", sprintf($template->get_config('code_format'), '\\1'), $string);
  1736. //
  1737. // Parse URL's and e-mail addresses enclosed in special characters
  1738. //
  1739. if ( $links ) {
  1740. $ignore_chars = "([^a-z0-9/]|&\#?[a-z0-9]+;)*?";
  1741. for ( $i = 0; $i < 2; $i++ ) {
  1742. $string = preg_replace(array(
  1743. "#([\s]".$ignore_chars.")([\w]+?://[\w\#\$%&~/\.\-;:=,\?@\[\]\+\\\\\'!\(\)\*]*?)(".$ignore_chars."[\s])#is",
  1744. "#([\s]".$ignore_chars.")(www\.[\w\#\$%&~/\.\-;:=,\?@\[\]\+\\\\\'!\(\)\*]*?)(".$ignore_chars."[\s])#is",
  1745. "#([\s]".$ignore_chars.")([a-z0-9&\-_\.\+]+?@[\w\-]+\.([\w\-\.]+\.)?[\w]+)(".$ignore_chars."[\s])#is"
  1746. ), array(
  1747. '\\1<a href="\\3" title="\\3"'.$rel.'>\\3</a>\\4',
  1748. '\\1<a href="http://\\3" title="http://\\3"'.$rel.'>\\3</a>\\4',
  1749. '\\1<a href="mailto:\\2" title="\\3">\\3</a>\\5'
  1750. ), $string);
  1751. }
  1752. }
  1753. //
  1754. // All kinds of BBCode regexps
  1755. //
  1756. $regexps = array(
  1757. // [b]text[/b]
  1758. "#\[b\](.*?)\[/b\]#is" => '<strong>\\1</strong>',
  1759. // [i]text[/i]
  1760. "#\[i\](.*?)\[/i\]#is" => '<em>\\1</em>',
  1761. // [u]text[/u]
  1762. "#\[u\](.*?)\[/u\]#is" => '<span style="text-decoration:underline">\\1</span>',
  1763. // [s]text[/s]
  1764. "#\[s\](.*?)\[/s\]#is" => '<del>\\1</del>',
  1765. // [img]image[/img]
  1766. "#\[img\]([\w]+?://[\w\#\$%&~/\.\-;:=,\?@\[\]\+\\\\\'!\(\)\*]*?)\[/img\]#is" => ( $links ) ? '<img src="\\1" alt="'.$lang['UserPostedImage'].'" class="user-posted-image" />' : '\\1',
  1767. // www.usebb.net
  1768. "#([\s])(www\.[\w\#\$%&~/\.\-;:=,\?@\[\]\+\\\\\'!\(\)\*]*?)#is" => ( $links ) ? '\\1<a href="http://\\2" title="http://\\2"'.$rel.'>\\2</a>\\3' : '\\1\\2\\3',
  1769. // ftp.usebb.net
  1770. "#([\s])(ftp\.[\w\#\$%&~/\.\-;:=,\?@\[\]\+\\\\\'!\(\)\*]*?)([\s])#is" => ( $links ) ? '\\1<a href="ftp://\\2" title="ftp://\\2"'.$rel.'>\\2</a>\\3' : '\\1\\2\\3',
  1771. // [url]http://www.usebb.net[/url]
  1772. "#\[url\]([\w]+?://[\w\#\$%&~/\.\-;:=,\?@\[\]\+\\\\\'!\(\)\*]*?)\[/url\]#is" => ( $links ) ? '<a href="\\1" title="\\1"'.$rel.'>\\1</a>' : '\\1',
  1773. // [url=http://www.usebb.net]UseBB[/url]
  1774. "#\[url=([\w]+?://[\w\#\$%&~/\.\-;:=,\?@\[\]\+\\\\\'!\(\)\*]*?)\](.*?)\[/url\]#is" => ( $links ) ? '<a href="\\1" title="\\1"'.$rel.'>\\2</a>' : '\\2 [\\1]',
  1775. // [mailto]somebody@nonexistent.com[/mailto]
  1776. "#\[mailto\]([a-z0-9&\-_\.\+]+?@[\w\-]+\.([\w\-\.]+\.)?[\w]+)\[/mailto\]#is" => ( $links ) ? '<a href="mailto:\\1" title="\\1">\\1</a>' : '\\1',
  1777. // [mailto=somebody@nonexistent.com]mail me[/mailto]
  1778. "#\[mailto=([a-z0-9&\-_\.\+]+?@[\w\-]+\.([\w\-\.]+\.)?[\w]+)\](.*?)\[/mailto\]#is" => ( $links ) ? '<a href="mailto:\\1" title="\\1">\\3</a>' : '\\3 [\\1]',
  1779. // [color=red]text[/color]
  1780. "#\[color=([\#a-z0-9]+)\](.*?)\[/color\]#is" => '<span style="color:\\1">\\2</span>',
  1781. // [size=999]too big text[/size]
  1782. "#\[size=([0-9]{3,})\](.*?)\[/size\]#is" => '\\2',
  1783. // [size=14]text[/size]
  1784. "#\[size=([0-9]*?)\](.*?)\[/size\]#is" => '<span style="font-size:\\1pt">\\2</span>',
  1785. // [google=keyword]text[/google]
  1786. "#\[google=(.*?)\](.*?)\[/google\]#is" => '<a href="http://www.google.com/search?q=\\1"'.$rel.'>\\2</a>',
  1787. );
  1788. //
  1789. // Now parse those regexps
  1790. //
  1791. foreach ( $regexps as $find => $replace )
  1792. $string = preg_replace($find, $replace, $string);
  1793. //
  1794. // Remove tags from attributes
  1795. //
  1796. if ( strpos($string, '<') !== false ) {
  1797. preg_match_all('#[a-z]+="[^"]*<[^>]*>[^"]*"#', $string, $matches);
  1798. foreach ( $matches[0] as $match )
  1799. $string = str_replace($match, strip_tags($match), $string);
  1800. }
  1801. }
  1802. if ( !$html ) {
  1803. $string = str_replace("\n", "<br />", $string);
  1804. $string = str_replace("\r", "", $string);
  1805. }
  1806. //
  1807. // XML (RSS/Atom) does not define elements such as a, pre, etc.
  1808. // Though, make sure the already escaped < and > are still/double escaped.
  1809. //
  1810. if ( $rss_mode )
  1811. $string = str_replace(array('&lt;', '&gt;', '<', '>'), array('&amp;lt;', '&amp;gt;', '&lt;', '&gt;'), $string);
  1812. return trim($string);
  1813. }
  1814. /**
  1815. * Return the BBCode control buttons
  1816. *
  1817. * @param bool $links Enable controls for links
  1818. * @returns string HTML BBCode controls
  1819. */
  1820. function get_bbcode_controls($links=true) {
  1821. global $lang, $template;
  1822. $controls = array(
  1823. array('[b]', '[/b]', 'B', 'font-weight: bold'),
  1824. array('[i]', '[/i]', 'I', 'font-style: italic'),
  1825. array('[u]', '[/u]', 'U', 'text-decoration: underline'),
  1826. array('[s]', '[/s]', 'S', 'text-decoration: line-through'),
  1827. array('[quote]', '[/quote]', $lang['Quote'], ''),
  1828. array('[code]', '[/code]', $lang['Code'], ''),
  1829. );
  1830. if ( $links ) {
  1831. $controls = array_merge($controls, array(
  1832. array('[img]', '[/img]', $lang['Img'], ''),
  1833. array('[url=http://www.example.com]', '[/url]', $lang['URL'], ''),
  1834. ));
  1835. }
  1836. $controls = array_merge($controls, array(
  1837. array('[color=red]', '[/color]', $lang['Color'], ''),
  1838. array('[size=14]', '[/size]', $lang['Size'], '')
  1839. ));
  1840. $out = array();
  1841. foreach ( $controls as $data )
  1842. $out[] = '<a href="javascript:void(0);" onclick="insert_tags(\''.$data[0].'\', \''.$data[1].'\')" style="'.$data[3].'">'.$data[2].'</a>';
  1843. return join($template->get_config('post_form_bbcode_seperator'), $out);
  1844. }
  1845. /**
  1846. * Return the smiley control graphics
  1847. *
  1848. * @returns string HTML smiley controls
  1849. */
  1850. function get_smiley_controls() {
  1851. global $template;
  1852. $smilies = $template->get_config('smilies');
  1853. $smilies = array_unique($smilies);
  1854. $out = array();
  1855. foreach ( $smilies as $pattern => $img )
  1856. $out[] = '<a href="javascript:void(0)" onclick="insert_smiley(\''.addslashes(unhtml($pattern)).'\')"><img src="templates/'.$this->get_config('template').'/smilies/'.$img.'" alt="'.unhtml($pattern).'" /></a>';
  1857. return join($template->get_config('post_form_smiley_seperator'), $out);
  1858. }
  1859. /**
  1860. * Censor text
  1861. *
  1862. * @param string $string Text to censor
  1863. * @returns string Censored text
  1864. */
  1865. function replace_badwords($string) {
  1866. global $db;
  1867. if ( $this->get_config('enable_badwords_filter') ) {
  1868. //
  1869. // Algorithm borrowed from phpBB
  1870. //
  1871. if ( !isset($this->badwords) ) {
  1872. $result = $db->query("SELECT word, replacement FROM ".TABLE_PREFIX."badwords ORDER BY word ASC");
  1873. $this->badwords = array();
  1874. while ( $data = $db->fetch_result($result) )
  1875. $this->badwords['#\b(?:' . str_replace('\*', '\w*?', preg_quote(stripslashes($data['word']), '#')) . ')\b#i'] = stripslashes($data['replacement']);
  1876. }
  1877. foreach ( $this->badwords as $badword => $replacement )
  1878. $string = preg_replace($badword, $replacement, $string);
  1879. }
  1880. return $string;
  1881. }
  1882. /**
  1883. * Timezone handling
  1884. *
  1885. * @param string $action 'get_zones' or 'check_existance'
  1886. * @param mixed $param Time zone param for 'check_existance'
  1887. * @returns mixed Array with timezones or bool
  1888. */
  1889. function timezone_handler($action, $param=NULL) {
  1890. $timezones = array(
  1891. '-12' => '-12:00',
  1892. '-11' => '-11:00',
  1893. '-10' => '-10:00',
  1894. '-9' => '-9:00',
  1895. '-8' => '-8:00',
  1896. '-7' => '-7:00',
  1897. '-6' => '-6:00',
  1898. '-5' => '-5:00',
  1899. '-4' => '-4:00',
  1900. '-3.5' => '-3:30',
  1901. '-3' => '-3:00',
  1902. '-2' => '-2:00',
  1903. '-1' => '-1:00',
  1904. '0' => '+0:00',
  1905. '+1' => '+1:00',
  1906. '+2' => '+2:00',
  1907. '+3' => '+3:00',
  1908. '+3.5' => '+3:30',
  1909. '+4' => '+4:00',
  1910. '+4.5' => '+4:30',
  1911. '+5' => '+5:00',
  1912. '+5.5' => '+5:30',
  1913. '+6' => '+6:00',
  1914. '+7' => '+7:00',
  1915. '+8' => '+8:00',
  1916. '+9' => '+9:00',
  1917. '+9.5' => '+9:30',
  1918. '+10' => '+10:00',
  1919. '+11' => '+11:00',
  1920. '+12' => '+12:00',
  1921. );
  1922. if ( $action == 'get_zones' ) {
  1923. return $timezones;
  1924. } elseif ( $action == 'check_existance' ) {
  1925. if ( !empty($timezones[$param]) )
  1926. return true;
  1927. else
  1928. return false;
  1929. }
  1930. }
  1931. /**
  1932. * Make a user's profile link
  1933. *
  1934. * @param int $user_id User ID
  1935. * @param string $username Username
  1936. * @param int $level Level
  1937. * @param string $title Title attribute
  1938. * @returns string HTML
  1939. */
  1940. function make_profile_link($user_id, $username, $level, $title=null) {
  1941. switch ( $level ) {
  1942. case LEVEL_ADMIN:
  1943. $levelclass = ' class="administrator"';
  1944. break;
  1945. case LEVEL_MOD:
  1946. $levelclass = ' class="moderator"';
  1947. break;
  1948. case LEVEL_MEMBER:
  1949. $levelclass = '';
  1950. break;
  1951. default:
  1952. trigger_error('User ID '.$user_id.' has a level of '.$level.' which is not possible within UseBB.', E_USER_ERROR);
  1953. }
  1954. $title = ( !empty($title) ) ? ' title="'.unhtml($title).'"' : '';
  1955. return '<a href="'.$this->make_url('profile.php', array('id' => $user_id)).'"'.$levelclass.$title.'>'.unhtml(stripslashes($username)).'</a>';
  1956. }
  1957. /**
  1958. * Create a forum statistics box like on the forum index
  1959. */
  1960. function forum_stats_box() {
  1961. global $db, $template, $lang, $session;
  1962. if ( $this->get_config('enable_forum_stats_box') && $this->get_user_level() >= $this->get_config('view_forum_stats_box_min_level') ) {
  1963. //
  1964. // Timestamp for defining last updated sessions
  1965. //
  1966. $min_updated = time() - ( $this->get_config('online_min_updated') * 60 );
  1967. //
  1968. // Get the session and user information
  1969. //
  1970. $result = $db->query("SELECT u.displayed_name, u.level, u.hide_from_online_list, s.user_id AS id, s.ip_addr, s.updated FROM ( ".TABLE_PREFIX."sessions s LEFT JOIN ".TABLE_PREFIX."members u ON s.user_id = u.id ) WHERE s.updated > ".$min_updated." ORDER BY s.updated DESC");
  1971. //
  1972. // Arrays for holding a list of online guests and members.
  1973. //
  1974. $count = array(
  1975. 'total_members' => 0,
  1976. 'hidden_members' => 0,
  1977. 'guests' => 0
  1978. );
  1979. $list = array(
  1980. 'members' => array(),
  1981. 'guests' => array()
  1982. );
  1983. $memberlist = array();
  1984. while ( $onlinedata = $db->fetch_result($result) ) {
  1985. if ( !$onlinedata['id'] ) {
  1986. //
  1987. // This is a guest
  1988. // Guests will only be counted per IP address
  1989. //
  1990. if ( !in_array($onlinedata['ip_addr'], $list['guests']) ) {
  1991. $count['guests']++;
  1992. $list['guests'][] = $onlinedata['ip_addr'];
  1993. }
  1994. } else {
  1995. //
  1996. // This is a member
  1997. //
  1998. if ( !in_array($onlinedata['id'], $list['members']) ) {
  1999. $title = $this->make_date($onlinedata['updated'], 'h:i:s a');
  2000. if ( !$onlinedata['hide_from_online_list'] ) {
  2001. $memberlist[] = $this->make_profile_link($onlinedata['id'], $onlinedata['displayed_name'], $onlinedata['level'], $title);
  2002. } else {
  2003. if ( $this->get_user_level() == LEVEL_ADMIN )
  2004. $memberlist[] = '<em>'.$this->make_profile_link($onlinedata['id'], $onlinedata['displayed_name'], $onlinedata['level'], $title).'</em>';
  2005. $count['hidden_members']++;
  2006. }
  2007. $count['total_members']++;
  2008. $list['members'][] = $onlinedata['id'];
  2009. }
  2010. }
  2011. }
  2012. $latest_member = $this->get_stats('latest_member');
  2013. if ( $count['total_members'] === 1 && $count['guests'] === 1 )
  2014. $users_online = $lang['MemberGuestOnline'];
  2015. elseif ( $count['total_members'] !== 1 && $count['guests'] === 1 )
  2016. $users_online = $lang['MembersGuestOnline'];
  2017. elseif ( $count['total_members'] === 1 && $count['guests'] !== 1 )
  2018. $users_online = $lang['MemberGuestsOnline'];
  2019. else
  2020. $users_online = $lang['MembersGuestsOnline'];
  2021. //
  2022. // Parse the online box
  2023. //
  2024. $template->parse('forum_stats_box', 'various', array(
  2025. 'small_stats' => sprintf($lang['IndexStats'], $this->get_stats('posts'), $this->get_stats('topics'), $this->get_stats('members')),
  2026. 'newest_member' => ( !$this->get_stats('members') ) ? '' : ' '.sprintf($lang['NewestMemberExtended'], '<a href="'.$this->make_url('profile.php', array('id' => $latest_member['id'])).'">'.unhtml(stripslashes($latest_member['displayed_name'])).'</a>'),
  2027. 'users_online' => sprintf($users_online, $this->get_config('online_min_updated'), $count['total_members'], $count['hidden_members'], $count['guests']),
  2028. 'members_online' => ( count($memberlist) ) ? join(', ', $memberlist) : '',
  2029. 'detailed_list_link' => ( $this->get_config('enable_detailed_online_list') && $this->get_user_level() >= $this->get_config('view_detailed_online_list_min_level') ) ? '<a href="'.$this->make_url('online.php').'">'.$lang['Detailed'].'</a>' : ''
  2030. ));
  2031. }
  2032. }
  2033. /**
  2034. * Get the server's load avarage value
  2035. *
  2036. * @param integer $which What load variable to call ('all' for an array of all)
  2037. * @returns float Server load average
  2038. */
  2039. function get_server_load($which=1) {
  2040. //
  2041. // Afaik, this does not exist at Windows
  2042. //
  2043. if ( ON_WINDOWS )
  2044. return false;
  2045. //
  2046. // Load has not been requested yet
  2047. //
  2048. if ( is_null($this->server_load) ) {
  2049. $found_load = false;
  2050. //
  2051. // First attempt: reading /proc/loadavg
  2052. //
  2053. $file = '/proc/loadavg';
  2054. if ( file_exists($file) && is_readable($file) ) {
  2055. $fh = fopen($file, 'r');
  2056. if ( is_resource($fh) ) {
  2057. $out = fread($fh, 1024);
  2058. fclose($fh);
  2059. if ( preg_match('#([0-9]+\.[0-9]{2}) ([0-9]+\.[0-9]{2}) ([0-9]+\.[0-9]{2})#', $out, $match) ) {
  2060. $this->server_load = array(
  2061. (float)$match[1],
  2062. (float)$match[2],
  2063. (float)$match[3]
  2064. );
  2065. $found_load = true;
  2066. }
  2067. }
  2068. }
  2069. if ( !$found_load ) {
  2070. //
  2071. // Second attempt: executing uptime
  2072. //
  2073. $tmp = array();
  2074. $retval = 1;
  2075. $out = exec('uptime', $tmp, $retval);
  2076. unset($tmp);
  2077. if ( !$retval ) {
  2078. if ( preg_match('#([0-9]+\.[0-9]{2}),? ([0-9]+\.[0-9]{2}),? ([0-9]+\.[0-9]{2})#', $out, $match) ) {
  2079. $this->server_load = array(
  2080. (float)$match[1],
  2081. (float)$match[2],
  2082. (float)$match[3]
  2083. );
  2084. } else {
  2085. $this->server_load = false;
  2086. }
  2087. } else {
  2088. $this->server_load = false;
  2089. }
  2090. }
  2091. }
  2092. if ( !$this->server_load )
  2093. return false;
  2094. elseif ( $which == 'all' )
  2095. return $this->server_load;
  2096. elseif ( is_int($which) )
  2097. return $this->server_load[$which-1];
  2098. }
  2099. /**
  2100. * Define the icon for forums
  2101. *
  2102. * @param int $id Forum ID
  2103. * @param bool $open Open (or locked)
  2104. * @param int $post_time Unix timestamp of update
  2105. * @returns array Array with forum icon and status
  2106. */
  2107. function forum_icon($id, $open, $post_time) {
  2108. global $db, $session, $template, $lang;
  2109. if ( $session->sess_info['user_id'] && !empty($_SESSION['previous_visit']) && !is_array($this->updated_forums) ) {
  2110. $result = $db->query("SELECT t.id, t.forum_id, p.post_time FROM ".TABLE_PREFIX."topics t, ".TABLE_PREFIX."posts p WHERE p.id = t.last_post_id AND p.post_time > ".$_SESSION['previous_visit']);
  2111. $this->updated_forums = array();
  2112. while ( $topicsdata = $db->fetch_result($result) ) {
  2113. if ( !in_array($topicsdata['forum_id'], $this->updated_forums) && ( !isset($_SESSION['viewed_topics']['t'.$topicsdata['id']]) || $_SESSION['viewed_topics']['t'.$topicsdata['id']] < $topicsdata['post_time'] ) )
  2114. $this->updated_forums[] = $topicsdata['forum_id'];
  2115. }
  2116. }
  2117. if ( $session->sess_info['user_id'] && !empty($_SESSION['previous_visit']) && in_array($id, $this->updated_forums) ) {
  2118. if ( $open ) {
  2119. $forum_icon = $template->get_config('open_newposts_icon');
  2120. $forum_status = $lang['NewPosts'];
  2121. } else {
  2122. $forum_icon = $template->get_config('closed_newposts_icon');
  2123. $forum_status = $lang['LockedNewPosts'];
  2124. }
  2125. } else {
  2126. if ( $open ) {
  2127. $forum_icon = $template->get_config('open_nonewposts_icon');
  2128. $forum_status = $lang['NoNewPosts'];
  2129. } else {
  2130. $forum_icon = $template->get_config('closed_nonewposts_icon');
  2131. $forum_status = $lang['LockedNoNewPosts'];
  2132. }
  2133. }
  2134. return array($forum_icon, $forum_status);
  2135. }
  2136. /**
  2137. * Define the icon for topics
  2138. *
  2139. * @param int $id Topic ID
  2140. * @param bool $locked Locked (or open)
  2141. * @param int $post_time Unix timestamp of update
  2142. * @returns array Array with topic icon and status
  2143. */
  2144. function topic_icon($id, $locked, $post_time) {
  2145. global $session, $template, $lang;
  2146. if ( $session->sess_info['user_id'] && !empty($_SESSION['previous_visit']) && $_SESSION['previous_visit'] < $post_time && ( !isset($_SESSION['viewed_topics']['t'.$id]) || $_SESSION['viewed_topics']['t'.$id] < $post_time ) ) {
  2147. if ( !$locked ) {
  2148. $topic_icon = $template->get_config('open_newposts_icon');
  2149. $topic_status = $lang['NewPosts'];
  2150. } else {
  2151. $topic_icon = $template->get_config('closed_newposts_icon');
  2152. $topic_status = $lang['LockedNewPosts'];
  2153. }
  2154. } else {
  2155. if ( !$locked ) {
  2156. $topic_icon = $template->get_config('open_nonewposts_icon');
  2157. $topic_status = $lang['NoNewPosts'];
  2158. } else {
  2159. $topic_icon = $template->get_config('closed_nonewposts_icon');
  2160. $topic_status = $lang['LockedNoNewPosts'];
  2161. }
  2162. }
  2163. return array($topic_icon, $topic_status);
  2164. }
  2165. /**
  2166. * Return birthday input fields
  2167. *
  2168. * @param string $input Input birthday field
  2169. * @returns array Input fields
  2170. */
  2171. function birthday_input_fields($input) {
  2172. global $lang;
  2173. $months = array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
  2174. if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
  2175. $birthday_year = $_POST['birthday_year'];
  2176. $birthday_month = $_POST['birthday_month'];
  2177. $birthday_day = $_POST['birthday_day'];
  2178. } else {
  2179. $birthday = $input;
  2180. $birthday_year = ( $birthday ) ? intval(substr($birthday, 0, 4)) : '';
  2181. $birthday_month = ( $birthday ) ? intval(substr($birthday, 4, 2)) : 0;
  2182. $birthday_day = ( $birthday ) ? intval(substr($birthday, 6, 2)) : 0;
  2183. }
  2184. $birthday_month_input = '<select name="birthday_month"><option value="">'.$lang['Month'].'</option>';
  2185. for ( $i = 1; $i <= 12; $i++ ) {
  2186. $selected = ( $birthday_month == $i ) ? ' selected="selected"' : '';
  2187. $month_name = ( isset($lang['date_translations']) && is_array($lang['date_translations']) ) ? $lang['date_translations'][$months[$i-1]] : $months[$i-1];
  2188. $birthday_month_input .= '<option value="'.$i.'"'.$selected.'>'.$month_name.'</option>';
  2189. }
  2190. $birthday_month_input .= '</select>';
  2191. $birthday_day_input = '<select name="birthday_day"><option value="">'.$lang['Day'].'</option>';
  2192. for ( $i = 1; $i <= 31; $i++ ) {
  2193. $selected = ( $birthday_day == $i ) ? ' selected="selected"' : '';
  2194. $birthday_day_input .= '<option value="'.$i.'"'.$selected.'>'.$i.'</option>';
  2195. }
  2196. $birthday_day_input .= '</select>';
  2197. $birthday_year_input = '<select name="birthday_year"><option value="">'.$lang['Year'].'</option>';
  2198. for ( $i = intval(date('Y')); $i >= 1900; $i-- ) {
  2199. $selected = ( $birthday_year == $i ) ? ' selected="selected"' : '';
  2200. $birthday_year_input .= '<option value="'.$i.'"'.$selected.'>'.$i.'</option>';
  2201. }
  2202. $birthday_year_input .= '</select>';
  2203. return array($birthday_year_input, $birthday_month_input, $birthday_day_input);
  2204. }
  2205. /**
  2206. * Calculate the age of a person based on a birthday date
  2207. *
  2208. * @param int $birthday Unix timestamp
  2209. * @returns int Age
  2210. */
  2211. function calculate_age($birthday) {
  2212. $month = intval(substr($birthday, 4, 2));
  2213. $day = intval(substr($birthday, 6, 2));
  2214. $year = intval(substr($birthday, 0, 4));
  2215. //
  2216. // Because Windows doesn't allow dates before 1970 with mktime(),
  2217. // we perform a trick to calculate dates before 1970.
  2218. //
  2219. if ( $year < 1970 ) {
  2220. $years_before_unix_epoch = 1970 - $year;
  2221. $false_year = $year + ( $years_before_unix_epoch * 2 );
  2222. $timestamp = mktime(0, 0, 0, $month, $day, $false_year);
  2223. $timestamp -= ( $years_before_unix_epoch * 31556926 * 2 );
  2224. } else {
  2225. $timestamp = mktime(0, 0, 0, $month, $day, $year);
  2226. }
  2227. return floor((time()-$timestamp)/31556926);
  2228. }
  2229. /**
  2230. * Get a list of template sets
  2231. *
  2232. * @returns array List of available template sets
  2233. */
  2234. function get_template_sets() {
  2235. if ( !count($this->available['templates']) ) {
  2236. $handle = opendir(ROOT_PATH.'templates');
  2237. while ( false !== ( $template_name = readdir($handle) ) ) {
  2238. if ( is_dir(ROOT_PATH.'templates/'.$template_name) && is_readable(ROOT_PATH.'templates/'.$template_name) && ( $this->get_user_level() == LEVEL_ADMIN || preg_match('#^[^\.]#', $template_name) ) && file_exists(ROOT_PATH.'templates/'.$template_name.'/global.tpl.php') )
  2239. $this->available['templates'][] = $template_name;
  2240. }
  2241. closedir($handle);
  2242. sort($this->available['templates']);
  2243. reset($this->available['templates']);
  2244. }
  2245. return $this->available['templates'];
  2246. }
  2247. /**
  2248. * Get a list of language packs
  2249. *
  2250. * @returns array List of available language packs
  2251. */
  2252. function get_language_packs() {
  2253. if ( !count($this->available['languages']) ) {
  2254. $handle = opendir(ROOT_PATH.'languages');
  2255. while ( false !== ( $language_name = readdir($handle) ) ) {
  2256. if ( preg_match('#^lang_(.+)\.php$#', $language_name, $language_name) )
  2257. $this->available['languages'][] = $language_name[1];
  2258. }
  2259. closedir($handle);
  2260. sort($this->available['languages']);
  2261. reset($this->available['languages']);
  2262. }
  2263. return $this->available['languages'];
  2264. }
  2265. /**
  2266. * Return the sql tables with the table prefix
  2267. *
  2268. * @returns array List of SQL tables with UseBB table prefix
  2269. */
  2270. function get_usebb_tables() {
  2271. global $db;
  2272. if ( !count($this->db_tables) ) {
  2273. $result = $db->query("SHOW TABLES LIKE '".TABLE_PREFIX."%'");
  2274. while ( $out = $db->fetch_result($result) )
  2275. $this->db_tables[] = current($out);
  2276. }
  2277. return $this->db_tables;
  2278. }
  2279. /**
  2280. * Redirect the user to a certain location within UseBB
  2281. *
  2282. * @param string $page .php file to link to
  2283. * @param array $vars Array with GET variables
  2284. * @param string $anchor HTML anchor
  2285. */
  2286. function redirect($page, $vars=array(), $anchor='') {
  2287. $goto = $this->get_config('board_url').$this->make_url($page, $vars, false);
  2288. if ( substr($goto, -2) == './' )
  2289. $goto = substr($goto, 0, strlen($goto)-2);
  2290. if ( !empty($anchor) )
  2291. $goto .= '#'.$anchor;
  2292. $this->raw_redirect($goto);
  2293. }
  2294. /**
  2295. * Redirect with a predefined URL
  2296. *
  2297. * @param string $url URL
  2298. */
  2299. function raw_redirect($url) {
  2300. //
  2301. // Don't use Location on IIS or Abyss
  2302. //
  2303. if ( strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') === false && strpos($_SERVER['SERVER_SOFTWARE'], 'Abyss') === false )
  2304. header('Location: '.$url);
  2305. die('<meta http-equiv="refresh" content="0;URL='.$url.'" />');
  2306. }
  2307. /**
  2308. * Validate a password
  2309. *
  2310. * @param string $password Password
  2311. * @param bool $extended Extended checking (new passwords)
  2312. * @returns bool Valid
  2313. */
  2314. function validate_password($password, $extended=false) {
  2315. $valid = ( preg_match(PWD_PREG, $password) );
  2316. //
  2317. // Only do this for new passwords.
  2318. //
  2319. if ( $valid && $extended )
  2320. $valid = ( !contains_entities($password, true) && preg_match('#[[:alpha:]]#', $password) && preg_match('#[[:digit:]]#', $password) );
  2321. return $valid;
  2322. }
  2323. /**
  2324. * Validate an email address
  2325. *
  2326. * @param string $email_address Email address
  2327. * @returns bool Valid
  2328. */
  2329. function validate_email($email_address) {
  2330. if ( !preg_match(EMAIL_PREG, $email_address) )
  2331. return false;
  2332. if ( $this->get_config('enable_email_dns_check') ) {
  2333. $parts = explode('@', $email_address);
  2334. if ( function_exists('checkdnsrr') && !ON_WINDOWS ) {
  2335. return checkdnsrr($parts[1], 'MX');
  2336. } elseif ( ON_WINDOWS ) {
  2337. return checkdnsrr_win($parts[1], 'MX');
  2338. }
  2339. }
  2340. return true;
  2341. }
  2342. /**
  2343. * Set a cookie
  2344. *
  2345. * This function takes care of past expire values for empty cookies, and
  2346. * uses the HttpOnly flag when enabled.
  2347. *
  2348. * The HttpOnly hack for < PHP 5.2 taken from
  2349. * @link http://blog.mattmecham.com/archives/2006/09/http_only_cookies_without_php.html
  2350. *
  2351. * Note: HttpOnly is disabled when working on a non domain (localhost, IP address)
  2352. * since when cookie_domain is empty and HttpOnly is used, IE 6 and 7 fail to set
  2353. * the cookie, even though the Set-Cookie header is well-formed and valid.
  2354. *
  2355. * @param string $name Name
  2356. * @param string $value Value
  2357. * @param int $expires Expire timestamp (when necessary)
  2358. */
  2359. function setcookie($name, $value, $expires=null) {
  2360. $expires = ( is_null($expires) && empty($value) ) ? time()-31536000 : $expires;
  2361. $domain = $this->get_config('cookie_domain');
  2362. $secure = ( $this->get_config('cookie_secure') ) ? 1 : 0;
  2363. if ( empty($domain) || !$this->get_config('cookie_httponly') )
  2364. setcookie($name, $value, $expires, $this->get_config('cookie_path'), $domain, $secure);
  2365. elseif ( version_compare(PHP_VERSION, '5.2.0RC2', '>=') )
  2366. setcookie($name, $value, $expires, $this->get_config('cookie_path'), $domain, $secure, true);
  2367. else
  2368. setcookie($name, $value, $expires, $this->get_config('cookie_path'), $domain.'; HttpOnly', $secure);
  2369. }
  2370. /**
  2371. * Generate an antispam question
  2372. *
  2373. * @param int $mode Anti-spam mode
  2374. */
  2375. function generate_antispam_question($mode) {
  2376. global $lang;
  2377. switch ( $mode ) {
  2378. case ANTI_SPAM_MATH:
  2379. //
  2380. // Random math question
  2381. //
  2382. $operator = mt_rand(1, 2);
  2383. if ( $operator == 1 ) {
  2384. $num1 = mt_rand(1, 9);
  2385. $num2 = mt_rand(1, 9);
  2386. $_SESSION['antispam_question_question'] = sprintf($lang['AntiSpamQuestionMathPlus'], $num1, $num2);
  2387. $_SESSION['antispam_question_answer'] = $num1 + $num2;
  2388. } else {
  2389. $num1 = mt_rand(1, 9);
  2390. $num2 = mt_rand(1, $num1);
  2391. $_SESSION['antispam_question_question'] = sprintf($lang['AntiSpamQuestionMathMinus'], $num1, $num2);
  2392. $_SESSION['antispam_question_answer'] = $num1 - $num2;
  2393. }
  2394. break;
  2395. case ANTI_SPAM_CUSTOM:
  2396. //
  2397. // Custom admin-defined question
  2398. //
  2399. $questionPairs = $this->get_config('antispam_question_questions');
  2400. if ( !is_array($questionPairs) || !count($questionPairs) )
  2401. trigger_error('No custom anti-spam questions found.', E_USER_ERROR);
  2402. $questions = array_keys($questionPairs);
  2403. $answers = array_values($questionPairs);
  2404. unset($questionPairs);
  2405. $questionId = ( count($questions) == 1 ) ? 0 : mt_rand(0, count($questions)-1);
  2406. $_SESSION['antispam_question_question'] = $questions[$questionId];
  2407. $_SESSION['antispam_question_answer'] = $answers[$questionId];
  2408. break;
  2409. default:
  2410. trigger_error('Spam check mode '.$mode.' does not exist.', E_USER_ERROR);
  2411. }
  2412. }
  2413. /**
  2414. * Pose the anti-spam question
  2415. *
  2416. * This might render a form and halt further page execution.
  2417. */
  2418. function pose_antispam_question() {
  2419. global $session, $template, $lang, $db;
  2420. if ( !$session->sess_info['pose_antispam_question'] )
  2421. return;
  2422. $template->clear_breadcrumbs();
  2423. $template->add_breadcrumb($lang['AntiSpamQuestion']);
  2424. $mode = (int)$this->get_config('antispam_question_mode');
  2425. if ( empty($_SESSION['antispam_question_question']) )
  2426. $this->generate_antispam_question($mode);
  2427. if ( isset($_POST['answer']) && !is_array($_POST['answer']) && !strcasecmp(strval($_POST['answer']), strval($_SESSION['antispam_question_answer'])) ) {
  2428. //
  2429. // Question passed, continuing...
  2430. //
  2431. $_SESSION['antispam_question_posed'] = true;
  2432. unset($_SESSION['antispam_question_question'], $_SESSION['antispam_question_answer']);
  2433. $this->redirect($_SERVER['PHP_SELF'], $_GET);
  2434. return;
  2435. }
  2436. if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
  2437. $template->parse('msgbox', 'global', array(
  2438. 'box_title' => $lang['Error'],
  2439. 'content' => $lang['AntiSpamWrongAnswer']
  2440. ));
  2441. }
  2442. $size = ( $mode === ANTI_SPAM_MATH ) ? 'size="2" maxlength="2"' : 'size="35"';
  2443. $template->parse('anti_spam_question', 'various', array(
  2444. 'form_begin' => '<form action="'.$this->make_url($_SERVER['PHP_SELF'], $_GET).'" method="post">',
  2445. 'question' => unhtml($_SESSION['antispam_question_question']),
  2446. 'answer_input' => '<input type="text" name="answer" id="answer" '.$size.' />',
  2447. 'submit_button' => '<input type="submit" name="submit" value="'.$lang['Send'].'" />',
  2448. 'form_end' => '</form>'
  2449. ));
  2450. $template->set_js_onload("set_focus('answer')");
  2451. //
  2452. // Include the page footer
  2453. //
  2454. require(ROOT_PATH.'sources/page_foot.php');
  2455. exit();
  2456. }
  2457. /**
  2458. * Generate a security token
  2459. *
  2460. * @link https://github.com/usebb/UseBB/wiki/UseBB-1-CSRF
  2461. *
  2462. * @returns string Token
  2463. */
  2464. function generate_token() {
  2465. static $token;
  2466. if ( isset($token) )
  2467. return $token;
  2468. list($usec, $sec) = explode(' ', microtime());
  2469. $time = (float)$usec + (float)$sec;
  2470. $key = $this->random_key();
  2471. if ( !$_SESSION['oldest_token'] )
  2472. $_SESSION['oldest_token'] = $time;
  2473. // For some reason, PHP juggled between dot and comma as decimal separator
  2474. // when using strval() and others. (PHP 5.3.6 on OS X 10.6.7)
  2475. $stime = number_format($time, 4, '.', '');
  2476. $_SESSION['tokens'][$stime] = $key;
  2477. $token = $stime.'-'.$key;
  2478. return $token;
  2479. }
  2480. /**
  2481. * Verify a token
  2482. *
  2483. * @link https://github.com/usebb/UseBB/wiki/UseBB-1-CSRF
  2484. *
  2485. * @param string $try_token Token to test
  2486. * @returns bool Verified
  2487. */
  2488. function verify_token($try_token) {
  2489. if ( !preg_match('#^[0-9]+\.[0-9]{4}\-[0-9a-f]{32}$#', $try_token) )
  2490. return false;
  2491. list($time, $key) = explode('-', $try_token);
  2492. $sess_idx = $time;
  2493. return ( !empty($_SESSION['tokens'][$sess_idx]) && $_SESSION['tokens'][$sess_idx] === $key );
  2494. }
  2495. /**
  2496. * Token error
  2497. *
  2498. * Parse a msgbox template with a suitable message.
  2499. *
  2500. * @link https://github.com/usebb/UseBB/wiki/UseBB-1-CSRF
  2501. *
  2502. * @param string $type Error type ("form" or "url")
  2503. */
  2504. function token_error($type) {
  2505. global $template, $lang;
  2506. $content = '';
  2507. switch ( $type ) {
  2508. case 'form':
  2509. $content = $lang['InvalidFormTokenNotice'];
  2510. break;
  2511. case 'url':
  2512. $content = $lang['InvalidURLTokenNotice'];
  2513. break;
  2514. }
  2515. $template->parse('msgbox', 'global', array(
  2516. 'box_title' => $lang['Note'],
  2517. 'content' => nl2br($content)
  2518. ));
  2519. }
  2520. /**
  2521. * Verify a form for tokens
  2522. *
  2523. * @link https://github.com/usebb/UseBB/wiki/UseBB-1-CSRF
  2524. *
  2525. * @param bool $enable_message Enable error message
  2526. * @returns bool Verified
  2527. */
  2528. function verify_form($enable_message=true) {
  2529. $post_idx = '_form_token_';
  2530. $result = ( !empty($_POST[$post_idx]) && $this->verify_token($_POST[$post_idx]) );
  2531. if ( $enable_message && !$result )
  2532. $this->token_error('form');
  2533. return $result;
  2534. }
  2535. /**
  2536. * Verify a URL for tokens
  2537. *
  2538. * @link https://github.com/usebb/UseBB/wiki/UseBB-1-CSRF
  2539. *
  2540. * @param bool $enable_message Enable error message
  2541. * @returns bool Verified
  2542. */
  2543. function verify_url($enable_message=true) {
  2544. $get_idx = '_url_token_';
  2545. $result = ( !empty($_GET[$get_idx]) && $this->verify_token($_GET[$get_idx]) );
  2546. if ( $enable_message && !$result )
  2547. $this->token_error('url');
  2548. return $result;
  2549. }
  2550. /**
  2551. * Read a remote URL into string
  2552. *
  2553. * @param string $url URL
  2554. * @returns string Contents
  2555. */
  2556. function read_url($url) {
  2557. if ( function_exists('curl_init') && function_exists('curl_exec') ) {
  2558. //
  2559. // cURL
  2560. //
  2561. $curl = curl_init($url);
  2562. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  2563. curl_setopt($curl, CURLOPT_HEADER, false);
  2564. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 3);
  2565. $result = curl_exec($curl);
  2566. if ( $result === FALSE )
  2567. return FALSE;
  2568. $contents = trim($result);
  2569. curl_close($curl);
  2570. return $contents;
  2571. }
  2572. //
  2573. // URL fopen()
  2574. //
  2575. if ( !ini_get('allow_url_fopen') )
  2576. return false;
  2577. $fp = fopen($url, 'r');
  2578. if ( !$fp )
  2579. return false;
  2580. $contents = '';
  2581. if ( function_exists('stream_get_contents') ) {
  2582. //
  2583. // PHP 5 stream
  2584. //
  2585. $result = stream_get_contents($fp);
  2586. if ( $result === FALSE )
  2587. return FALSE;
  2588. $contents = trim($result);
  2589. } else {
  2590. //
  2591. // fread() packet reading
  2592. //
  2593. while ( !feof($fp) ) {
  2594. $result = fread($fp, 8192);
  2595. if ( $result === FALSE )
  2596. return FALSE;
  2597. $contents .= $result;
  2598. }
  2599. $contents = trim($contents);
  2600. }
  2601. fclose($fp);
  2602. return $contents;
  2603. }
  2604. /**
  2605. * Stop Forum Spam API request
  2606. *
  2607. * @link http://www.stopforumspam.com/usage
  2608. *
  2609. * @param string $email Email address
  2610. * @returns mixed FALSE if nothing found, array otherwise
  2611. */
  2612. function sfs_api_request($email) {
  2613. //
  2614. // Not really clean XML parsing code. Will improve for UseBB 2.
  2615. //
  2616. //
  2617. // Session cache
  2618. //
  2619. if ( isset($_SESSION['sfs_ban_cache'][$email]) )
  2620. return $_SESSION['sfs_ban_cache'][$email];
  2621. $result = $this->read_url('http://www.stopforumspam.com/api?email='.urlencode($email));
  2622. //
  2623. // Failed request
  2624. //
  2625. if ( $result === FALSE || !preg_match('#<response[^>]+success="true"[^>]*>#', $result) )
  2626. return FALSE;
  2627. //
  2628. // Not in database
  2629. //
  2630. if ( strpos($result, '<appears>yes</appears>') === FALSE ) {
  2631. $_SESSION['sfs_ban_cache'][$email] = FALSE;
  2632. return FALSE;
  2633. }
  2634. $return = array();
  2635. if ( preg_match('#<lastseen>([0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})</lastseen>#i', $result, $matches) )
  2636. $return['lastseen'] = strtotime($matches[1]);
  2637. if ( preg_match('#<frequency>([0-9]+)</frequency>#i', $result, $matches) )
  2638. $return['frequency'] = (int) $matches[1];
  2639. $_SESSION['sfs_ban_cache'][$email] = $return;
  2640. return $return;
  2641. }
  2642. /**
  2643. * Stop Forum Spam email check
  2644. *
  2645. * Check Stop Forum Spam for a banned email address.
  2646. *
  2647. * @param string $email Email address
  2648. * @returns bool Banned
  2649. */
  2650. function sfs_email_banned($email) {
  2651. global $db;
  2652. if ( !$this->get_config('sfs_email_check') )
  2653. return FALSE;
  2654. $info = $this->sfs_api_request($email);
  2655. //
  2656. // Not banned
  2657. //
  2658. if ( $info === FALSE )
  2659. return FALSE;
  2660. $min_frequency = $this->get_config('sfs_min_frequency');
  2661. $max_lastseen = $this->get_config('sfs_max_lastseen');
  2662. //
  2663. // Does not meet requirements
  2664. //
  2665. if ( ( $min_frequency > 0 && ( !isset($info['frequency']) || $info['frequency'] < $min_frequency ) )
  2666. || ( $max_lastseen > 0 && ( !isset($info['lastseen']) || $info['lastseen'] < time() - $max_lastseen * 86400 ) ) )
  2667. return FALSE;
  2668. if ( $this->get_config('sfs_save_bans') )
  2669. $db->query("INSERT INTO ".TABLE_PREFIX."bans VALUES(NULL, '', '".$email."', '')");
  2670. return TRUE;
  2671. }
  2672. /**
  2673. * Stop Forum Spam API submit
  2674. *
  2675. * Submit account information to the Stop Forum Spam database.
  2676. *
  2677. * @param array $data Array with username, email and ip_addr.
  2678. * @returns bool Success
  2679. */
  2680. function sfs_api_submit($data) {
  2681. $key = $this->get_config('sfs_api_key');
  2682. if ( empty($data['username']) || empty($data['email']) || empty($data['ip_addr']) || empty($key) )
  2683. return FALSE;
  2684. $url = 'http://www.stopforumspam.com/add.php'
  2685. .'?username='.urlencode($data['username'])
  2686. .'&ip_addr='.urlencode($data['ip_addr'])
  2687. .'&email='.urlencode($data['email'])
  2688. .'&api_key='.urlencode($key);
  2689. $result = $this->read_url($url);
  2690. return ( $result !== FALSE );
  2691. }
  2692. /**
  2693. * Active value for user
  2694. *
  2695. * Calculate whether the user gets (in)active or is a potential spammer.
  2696. *
  2697. * @param array $user User array with active, level and posts.
  2698. * @param bool $new_post Whether this is in a query increasing the post count.
  2699. * @param bool $activate Whether this is when activating a user.
  2700. * @returns int Active value
  2701. */
  2702. function user_active_value($user=NULL, $new_post=FALSE, $activate=FALSE) {
  2703. //
  2704. // Potential spammer status not enabled
  2705. //
  2706. if ( !$this->get_config('antispam_disable_post_links')
  2707. && !$this->get_config('antispam_disable_profile_links') )
  2708. return USER_ACTIVE;
  2709. //
  2710. // New (no) user = potential spammer
  2711. //
  2712. if ( $user === NULL )
  2713. return USER_POTENTIAL_SPAMMER;
  2714. //
  2715. // poster_level is sometimes used
  2716. //
  2717. if ( !isset($user['level']) && isset($user['poster_level']) )
  2718. $user['level'] = $user['poster_level'];
  2719. if ( !isset($user['level']) )
  2720. trigger_error('Missing data for calculating active value.', E_USER_ERROR);
  2721. //
  2722. // Guests are potential spammers (when enabled)
  2723. //
  2724. if ( $user['level'] == LEVEL_GUEST && $this->get_config('antispam_status_for_guests') )
  2725. return USER_POTENTIAL_SPAMMER;
  2726. //
  2727. // Only for regular members
  2728. //
  2729. if ( $user['level'] != LEVEL_MEMBER )
  2730. return USER_ACTIVE;
  2731. if ( !isset($user['active']) )
  2732. trigger_error('Missing data for calculating active value.', E_USER_ERROR);
  2733. //
  2734. // Keep status for no new post or active user, unless is activating
  2735. //
  2736. if ( !$activate && ( !$new_post || $user['active'] == USER_ACTIVE ) )
  2737. return $user['active'];
  2738. if ( !isset($user['posts']) )
  2739. trigger_error('Missing data for calculating active value.', E_USER_ERROR);
  2740. $max_posts = (int) $this->get_config('antispam_status_max_posts');
  2741. if ( $new_post )
  2742. $user['posts'] += 1;
  2743. //
  2744. // When max posts is set and user has more posts,
  2745. // user gets active status, otherwise still potential spammer.
  2746. //
  2747. return ( $max_posts > 0 && $user['posts'] > $max_posts )
  2748. ? USER_ACTIVE : USER_POTENTIAL_SPAMMER;
  2749. }
  2750. /**
  2751. * Is potential spammer
  2752. *
  2753. * @param array $user User array with active, level and posts.
  2754. * @param bool $new_post Whether this is for a request increasing the post count.
  2755. * @returns bool Is potential spammer
  2756. */
  2757. function antispam_is_potential_spammer($user, $new_post=FALSE) {
  2758. //
  2759. // poster_level is sometimes used
  2760. //
  2761. if ( !isset($user['level']) && isset($user['poster_level']) )
  2762. $user['level'] = $user['poster_level'];
  2763. //
  2764. // Inactive members are potential spammers whenever the status is enabled
  2765. //
  2766. if ( ($this->get_config('antispam_disable_post_links') || $this->get_config('antispam_disable_profile_links'))
  2767. && $user['level'] == LEVEL_MEMBER && $user['active'] == USER_INACTIVE )
  2768. return TRUE;
  2769. return ( $this->user_active_value($user, $new_post) == USER_POTENTIAL_SPAMMER );
  2770. }
  2771. /**
  2772. * Can post links
  2773. *
  2774. * @param array $user User array with active, level and posts.
  2775. * @param bool $new_post Whether this is for a request increasing the post count.
  2776. * @returns bool Whether can post links
  2777. */
  2778. function antispam_can_post_links($user, $new_post=FALSE) {
  2779. return ( !$this->antispam_is_potential_spammer($user, $new_post)
  2780. || !$this->get_config('antispam_disable_post_links') );
  2781. }
  2782. /**
  2783. * Can add profile links
  2784. *
  2785. * @param array $user User array with active, level and posts.
  2786. * @returns bool Whether can add profile links
  2787. */
  2788. function antispam_can_add_profile_links($user) {
  2789. return ( !$this->antispam_is_potential_spammer($user, FALSE)
  2790. || !$this->get_config('antispam_disable_profile_links') );
  2791. }
  2792. }
  2793. ?>