PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/site/tmp/install_4a925da139185/admin/plugins/phpbb3/bbcode_parser.php

https://bitbucket.org/manchas/jrobotz
PHP | 955 lines | 654 code | 152 blank | 149 comment | 125 complexity | c95695e1cb293d9d0619fe2951141dd2 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, GPL-2.0, Apache-2.0
  1. <?php
  2. /**
  3. * @package JFusion
  4. * @subpackage Models
  5. * @author JFusion development team
  6. * @copyright Copyright (C) 2008 JFusion. All rights reserved.
  7. * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL
  8. */
  9. // no direct access
  10. defined('_JEXEC' ) or die('Restricted access' );
  11. class phpbb_bbcode_parser {
  12. var $text = '';
  13. var $bbcode_uid = false;
  14. var $bbcodes = '';
  15. var $bbcode_bitfield = '';
  16. var $jname = '';
  17. var $db = '';
  18. var $source_url = '';
  19. //needed to parse the bbcode for phpbb
  20. function phpbb_bbcode_parser(&$text, $jname)
  21. {
  22. $this->text = $text;
  23. $this->jname = $jname;
  24. $params =& JFusionFactory::getParams($this->jname);
  25. $source_path = $params->get('source_path');
  26. $this->source_url = $params->get('source_url');
  27. $this->db =& JFusionFactory::getDatabase($this->jname);
  28. define('IN_PHPBB',true);
  29. $table_prefix = $params->get('database_prefix');
  30. include_once($source_path . '/includes/constants.php');
  31. //get a bbcode_uid
  32. if(empty($this->bbcode_uid)) {
  33. $query = "SELECT config_value FROM #__config WHERE config_name = 'rand_seed'";
  34. $this->db->setQuery($query);
  35. $rand_seed = $this->db->loadResult();
  36. $val = $rand_seed . microtime();
  37. $val = md5($val);
  38. $uniqueid = substr($val, 4, 16);
  39. $this->bbcode_uid = substr(base_convert($uniqueid, 16, 36), 0, BBCODE_UID_LEN);
  40. }
  41. //remove unwanted stuff
  42. $match = array('#(script|about|applet|activex|chrome):#i');
  43. $replace = array("\\1&#058;");
  44. $this->text = preg_replace($match, $replace, trim($this->text));
  45. //parse smilies phpbb's way
  46. $this->parse_smilies();
  47. //add phpbb's bbcode_uid to bbcode and generate bbcode_bitfield
  48. if(strpos($this->text, '[') !== false) {
  49. $this->bbcode_bitfield = base64_decode('');
  50. $this->parse_bbcode();
  51. }
  52. }
  53. function parse_smilies()
  54. {
  55. static $smilie_match, $smilie_replace;
  56. if (!is_array($smilie_match))
  57. {
  58. $smilie_match = $smilie_replace = array();
  59. $query = 'SELECT * FROM #__smilies ORDER BY LENGTH(code) DESC';
  60. $this->db->setQuery($query);
  61. $results = $this->db->loadObjectList();
  62. foreach($results as $r) {
  63. $smilie_match[] = '(?<=^|[\n .])' . preg_quote($r->code, '#') . '(?![^<>]*>)';
  64. $smilie_replace[] = '<!-- s' . $r->code . ' --><img src="{SMILIES_PATH}/' . $r->smiley_url . '" alt="' . $r->code . '" title="' . $r->emotion . '" /><!-- s' . $r->code . ' -->';
  65. }
  66. }
  67. $this->text = trim(preg_replace(explode(chr(0), '#' . implode('#' . chr(0) . '#', $smilie_match) . '#'), $smilie_replace, $this->text));
  68. }
  69. function parse_bbcode()
  70. {
  71. if(!is_array($this->bbcodes)) {
  72. $this->bbcodes = array(
  73. 'code' => array('bbcode_id' => 8, 'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#ise' => "\$this->bbcode_code('\$1', '\$2')")),
  74. 'quote' => array('bbcode_id' => 0, 'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#ise' => "\$this->bbcode_quote('\$0')")),
  75. 'attachment' => array('bbcode_id' => 12, 'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#ise' => "\$this->bbcode_attachment('\$1', '\$2')")),
  76. 'b' => array('bbcode_id' => 1, 'regexp' => array('#\[b\](.*?)\[/b\]#ise' => "\$this->bbcode_strong('\$1')")),
  77. 'i' => array('bbcode_id' => 2, 'regexp' => array('#\[i\](.*?)\[/i\]#ise' => "\$this->bbcode_italic('\$1')")),
  78. 'url' => array('bbcode_id' => 3, 'regexp' => array('#\[url(=(.*))?\](.*)\[/url\]#iUe' => "\$this->validate_url('\$2', '\$3')")),
  79. 'img' => array('bbcode_id' => 4, 'regexp' => array('#\[img\](.*)\[/img\]#iUe' => "\$this->bbcode_img('\$1')")),
  80. 'size' => array('bbcode_id' => 5, 'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#ise' => "\$this->bbcode_size('\$1', '\$2')")),
  81. 'color' => array('bbcode_id' => 6, 'regexp' => array('!\[color=(#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!ise' => "\$this->bbcode_color('\$1', '\$2')")),
  82. 'u' => array('bbcode_id' => 7, 'regexp' => array('#\[u\](.*?)\[/u\]#ise' => "\$this->bbcode_underline('\$1')")),
  83. 'list' => array('bbcode_id' => 9, 'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#ise' => "\$this->bbcode_parse_list('\$0')")),
  84. 'email' => array('bbcode_id' => 10, 'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#ise' => "\$this->validate_email('\$1', '\$2')")),
  85. 'flash' => array('bbcode_id' => 11, 'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ie' => "\$this->bbcode_flash('\$1', '\$2', '\$3')"))
  86. );
  87. $query = 'SELECT * FROM #__bbcodes';
  88. $this->db->setQuery($query);
  89. $results = $this->db->loadObjectList();
  90. foreach ($results as $r)
  91. {
  92. $this->bbcodes[$row->bbcode_tag] = array(
  93. 'bbcode_id' => (int) $r->bbcode_id,
  94. 'regexp' => array($r->first_pass_match => str_replace('$uid', $this->bbcode_uid, $row->first_pass_replace))
  95. );
  96. }
  97. }
  98. foreach($this->bbcodes as $name => $data) {
  99. foreach ($data['regexp'] as $search => $replace) {
  100. if (preg_match($search, $this->text)) {
  101. $this->text = preg_replace($search, $replace, $this->text);
  102. $this->set_bbcode_bitfield($data['bbcode_id']);
  103. }
  104. }
  105. }
  106. $this->bbcode_bitfield = base64_encode($this->bbcode_bitfield);
  107. }
  108. function set_bbcode_bitfield($id)
  109. {
  110. $byte = $id >> 3;
  111. $bit = 7 - ($id & 7);
  112. if (strlen($this->bbcode_bitfield) >= $byte + 1) {
  113. $this->bbcode_bitfield[$byte] = $this->bbcode_bitfield[$byte] | chr(1 << $bit);
  114. } else {
  115. $this->bbcode_bitfield .= str_repeat("\0", $byte - strlen($this->bbcode_bitfield));
  116. $this->bbcode_bitfield .= chr(1 << $bit);
  117. }
  118. }
  119. /**
  120. * The following functions were taken from phpBB 3.0.4 to parse bbcode the way phpBB wants it. It has been adapted to fit JFusion's needs
  121. * Original copyright (c) 2005 phpBB Group
  122. * Original license http://opensource.org/licenses/gpl-license.php GNU Public License
  123. *
  124. */
  125. /**
  126. * Making some pre-checks for bbcodes as well as increasing the number of parsed items
  127. */
  128. function check_bbcode($bbcode, &$in)
  129. {
  130. // when using the /e modifier, preg_replace slashes double-quotes but does not
  131. // seem to slash anything else
  132. $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
  133. // Trimming here to make sure no empty bbcodes are parsed accidently
  134. if (trim($in) == '')
  135. {
  136. return false;
  137. }
  138. return true;
  139. }
  140. /**
  141. * Transform some characters in valid bbcodes
  142. */
  143. function bbcode_specialchars($text)
  144. {
  145. $str_from = array('<', '>', '[', ']', '.', ':');
  146. $str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');
  147. return str_replace($str_from, $str_to, $text);
  148. }
  149. /**
  150. * Parse size tag
  151. */
  152. function bbcode_size($stx, $in)
  153. {
  154. global $user, $config;
  155. if (!$this->check_bbcode('size', $in))
  156. {
  157. return $in;
  158. }
  159. // Do not allow size=0
  160. if ($stx <= 0)
  161. {
  162. return '[size=' . $stx . ']' . $in . '[/size]';
  163. }
  164. return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
  165. }
  166. /**
  167. * Parse color tag
  168. */
  169. function bbcode_color($stx, $in)
  170. {
  171. if (!$this->check_bbcode('color', $in))
  172. {
  173. return $in;
  174. }
  175. return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
  176. }
  177. /**
  178. * Parse u tag
  179. */
  180. function bbcode_underline($in)
  181. {
  182. if (!$this->check_bbcode('u', $in))
  183. {
  184. return $in;
  185. }
  186. return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
  187. }
  188. /**
  189. * Parse b tag
  190. */
  191. function bbcode_strong($in)
  192. {
  193. if (!$this->check_bbcode('b', $in))
  194. {
  195. return $in;
  196. }
  197. return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
  198. }
  199. /**
  200. * Parse i tag
  201. */
  202. function bbcode_italic($in)
  203. {
  204. if (!$this->check_bbcode('i', $in))
  205. {
  206. return $in;
  207. }
  208. return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
  209. }
  210. /**
  211. * Parse img tag
  212. */
  213. function bbcode_img($in)
  214. {
  215. global $user, $config;
  216. if (!$this->check_bbcode('img', $in))
  217. {
  218. return $in;
  219. }
  220. $in = trim($in);
  221. $in = str_replace(' ', '%20', $in);
  222. // Checking urls
  223. if (!preg_match('#^' . $this->get_preg_expression('url') . '$#i', $in) && !preg_match('#^' . $this->get_preg_expression('www_url') . '$#i', $in))
  224. {
  225. return '[img]' . $in . '[/img]';
  226. }
  227. // Try to cope with a common user error... not specifying a protocol but only a subdomain
  228. if (!preg_match('#^[a-z0-9]+://#i', $in))
  229. {
  230. $in = 'http://' . $in;
  231. }
  232. return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
  233. }
  234. /**
  235. * Parse flash tag
  236. */
  237. function bbcode_flash($width, $height, $in)
  238. {
  239. global $user, $config;
  240. if (!$this->check_bbcode('flash', $in))
  241. {
  242. return $in;
  243. }
  244. $in = trim($in);
  245. // Do not allow 0-sizes generally being entered
  246. if ($width <= 0 || $height <= 0)
  247. {
  248. return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
  249. }
  250. return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
  251. }
  252. /**
  253. * Parse inline attachments [ia]
  254. */
  255. function bbcode_attachment($stx, $in)
  256. {
  257. if (!$this->check_bbcode('attachment', $in))
  258. {
  259. return $in;
  260. }
  261. return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
  262. }
  263. /**
  264. * Parse code text from code tag
  265. * @access private
  266. */
  267. function bbcode_parse_code($stx, &$code)
  268. {
  269. switch (strtolower($stx))
  270. {
  271. case 'php':
  272. $remove_tags = false;
  273. $str_from = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;', '&#058;');
  274. $str_to = array('<', '>', '[', ']', '.', ':', ':');
  275. $code = str_replace($str_from, $str_to, $code);
  276. if (!preg_match('/\<\?.*?\?\>/is', $code))
  277. {
  278. $remove_tags = true;
  279. $code = "<?php $code ?>";
  280. }
  281. $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
  282. foreach ($conf as $ini_var)
  283. {
  284. @ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
  285. }
  286. // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
  287. $code = htmlspecialchars_decode($code);
  288. $code = highlight_string($code, true);
  289. $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
  290. $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');
  291. if ($remove_tags)
  292. {
  293. $str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
  294. $str_to[] = '';
  295. $str_from[] = '<span class="syntaxdefault">&lt;?php&nbsp;';
  296. $str_to[] = '<span class="syntaxdefault">';
  297. }
  298. $code = str_replace($str_from, $str_to, $code);
  299. $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
  300. if ($remove_tags)
  301. {
  302. $code = preg_replace('#(<span class="[a-z]+">)?\?&gt;(</span>)#', '$1&nbsp;$2', $code);
  303. }
  304. $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
  305. $code = preg_replace('#(?:\s++|&nbsp;)*+</span>$#u', '</span>', $code);
  306. // remove newline at the end
  307. if (!empty($code) && substr($code, -1) == "\n")
  308. {
  309. $code = substr($code, 0, -1);
  310. }
  311. return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
  312. break;
  313. default:
  314. return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
  315. break;
  316. }
  317. }
  318. /**
  319. * Parse code tag
  320. * Expects the argument to start right after the opening [code] tag and to end with [/code]
  321. */
  322. function bbcode_code($stx, $in)
  323. {
  324. if (!$this->check_bbcode('code', $in))
  325. {
  326. return $in;
  327. }
  328. // We remove the hardcoded elements from the code block here because it is not used in code blocks
  329. // Having it here saves us one preg_replace per message containing [code] blocks
  330. // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
  331. $htm_match = $this->get_preg_expression('bbcode_htm');
  332. unset($htm_match[4], $htm_match[5]);
  333. $htm_replace = array('\1', '\1', '\2', '\1');
  334. $out = $code_block = '';
  335. $open = 1;
  336. while ($in)
  337. {
  338. // Determine position and tag length of next code block
  339. preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
  340. $pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
  341. $tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;
  342. // Determine position of ending code tag
  343. $pos2 = stripos($in, '[/code]');
  344. // Which is the next block, ending code or code block
  345. if ($pos !== false && $pos < $pos2)
  346. {
  347. // Open new block
  348. if (!$open)
  349. {
  350. $out .= substr($in, 0, $pos);
  351. $in = substr($in, $pos);
  352. $stx = (isset($buffer[3])) ? $buffer[3] : '';
  353. $code_block = '';
  354. }
  355. else
  356. {
  357. // Already opened block, just append to the current block
  358. $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
  359. $in = substr($in, $pos);
  360. }
  361. $in = substr($in, $tag_length);
  362. $open++;
  363. }
  364. else
  365. {
  366. // Close the block
  367. if ($open == 1)
  368. {
  369. $code_block .= substr($in, 0, $pos2);
  370. $code_block = preg_replace($htm_match, $htm_replace, $code_block);
  371. // Parse this code block
  372. $out .= $this->bbcode_parse_code($stx, $code_block);
  373. $code_block = '';
  374. $open--;
  375. }
  376. else if ($open)
  377. {
  378. // Close one open tag... add to the current code block
  379. $code_block .= substr($in, 0, $pos2 + 7);
  380. $open--;
  381. }
  382. else
  383. {
  384. // end code without opening code... will be always outside code block
  385. $out .= substr($in, 0, $pos2 + 7);
  386. }
  387. $in = substr($in, $pos2 + 7);
  388. }
  389. }
  390. // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
  391. if ($code_block)
  392. {
  393. $code_block = substr($code_block, 0, -7);
  394. $code_block = preg_replace($htm_match, $htm_replace, $code_block);
  395. $out .= $this->bbcode_parse_code($stx, $code_block);
  396. }
  397. return $out;
  398. }
  399. /**
  400. * Parse list bbcode
  401. * Expects the argument to start with a tag
  402. */
  403. function bbcode_parse_list($in)
  404. {
  405. if (!$this->check_bbcode('list', $in))
  406. {
  407. return $in;
  408. }
  409. // $tok holds characters to stop at. Since the string starts with a '[' we'll get everything up to the first ']' which should be the opening [list] tag
  410. $tok = ']';
  411. $out = '[';
  412. // First character is [
  413. $in = substr($in, 1);
  414. $list_end_tags = $item_end_tags = array();
  415. do
  416. {
  417. $pos = strlen($in);
  418. for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
  419. {
  420. $tmp_pos = strpos($in, $tok[$i]);
  421. if ($tmp_pos !== false && $tmp_pos < $pos)
  422. {
  423. $pos = $tmp_pos;
  424. }
  425. }
  426. $buffer = substr($in, 0, $pos);
  427. $tok = $in[$pos];
  428. $in = substr($in, $pos + 1);
  429. if ($tok == ']')
  430. {
  431. // if $tok is ']' the buffer holds a tag
  432. if (strtolower($buffer) == '/list' && sizeof($list_end_tags))
  433. {
  434. // valid [/list] tag, check nesting so that we don't hit false positives
  435. if (sizeof($item_end_tags) && sizeof($item_end_tags) >= sizeof($list_end_tags))
  436. {
  437. // current li tag has not been closed
  438. $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
  439. }
  440. $out .= array_pop($list_end_tags) . ']';
  441. $tok = '[';
  442. }
  443. else if (preg_match('#^list(=[0-9a-z]+)?$#i', $buffer, $m))
  444. {
  445. // sub-list, add a closing tag
  446. if (empty($m[1]) || preg_match('/^=(?:disc|square|circle)$/i', $m[1]))
  447. {
  448. array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
  449. }
  450. else
  451. {
  452. array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
  453. }
  454. $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
  455. $tok = '[';
  456. }
  457. else
  458. {
  459. if (($buffer == '*' || substr($buffer, -2) == '[*') && sizeof($list_end_tags))
  460. {
  461. // the buffer holds a bullet tag and we have a [list] tag open
  462. if (sizeof($item_end_tags) >= sizeof($list_end_tags))
  463. {
  464. if (substr($buffer, -2) == '[*')
  465. {
  466. $out .= substr($buffer, 0, -2) . '[';
  467. }
  468. // current li tag has not been closed
  469. if (preg_match('/\n\[$/', $out, $m))
  470. {
  471. $out = preg_replace('/\n\[$/', '[', $out);
  472. $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
  473. }
  474. else
  475. {
  476. $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
  477. }
  478. }
  479. else
  480. {
  481. $buffer = '*:' . $this->bbcode_uid;
  482. }
  483. $item_end_tags[] = '/*:m:' . $this->bbcode_uid;
  484. }
  485. else if ($buffer == '/*')
  486. {
  487. array_pop($item_end_tags);
  488. $buffer = '/*:' . $this->bbcode_uid;
  489. }
  490. $out .= $buffer . $tok;
  491. $tok = '[]';
  492. }
  493. }
  494. else
  495. {
  496. // Not within a tag, just add buffer to the return string
  497. $out .= $buffer . $tok;
  498. $tok = ($tok == '[') ? ']' : '[]';
  499. }
  500. }
  501. while ($in);
  502. // do we have some tags open? close them now
  503. if (sizeof($item_end_tags))
  504. {
  505. $out .= '[' . implode('][', $item_end_tags) . ']';
  506. }
  507. if (sizeof($list_end_tags))
  508. {
  509. $out .= '[' . implode('][', $list_end_tags) . ']';
  510. }
  511. return $out;
  512. }
  513. /**
  514. * Parse quote bbcode
  515. * Expects the argument to start with a tag
  516. */
  517. function bbcode_quote($in)
  518. {
  519. global $config, $user;
  520. /**
  521. * If you change this code, make sure the cases described within the following reports are still working:
  522. * #3572 - [quote="[test]test"]test [ test[/quote] - (correct: parsed)
  523. * #14667 - [quote]test[/quote] test ] and [ test [quote]test[/quote] (correct: parsed)
  524. * #14770 - [quote="["]test[/quote] (correct: parsed)
  525. * [quote="[i]test[/i]"]test[/quote] (correct: parsed)
  526. * [quote="[quote]test[/quote]"]test[/quote] (correct: parsed - Username displayed as [quote]test[/quote])
  527. * #20735 - [quote]test[/[/b]quote] test [/quote][/quote] test - (correct: quoted: "test[/[/b]quote] test" / non-quoted: "[/quote] test" - also failed if layout distorted)
  528. */
  529. $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
  530. if (!$in)
  531. {
  532. return '';
  533. }
  534. // To let the parser not catch tokens within quote_username quotes we encode them before we start this...
  535. $in = preg_replace('#quote=&quot;(.*?)&quot;\]#ie', "'quote=&quot;' . str_replace(array('[', ']'), array('&#91;', '&#93;'), '\$1') . '&quot;]'", $in);
  536. $tok = ']';
  537. $out = '[';
  538. $in = substr($in, 1);
  539. $close_tags = $error_ary = array();
  540. $buffer = '';
  541. do
  542. {
  543. $pos = strlen($in);
  544. for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
  545. {
  546. $tmp_pos = strpos($in, $tok[$i]);
  547. if ($tmp_pos !== false && $tmp_pos < $pos)
  548. {
  549. $pos = $tmp_pos;
  550. }
  551. }
  552. $buffer .= substr($in, 0, $pos);
  553. $tok = $in[$pos];
  554. $in = substr($in, $pos + 1);
  555. if ($tok == ']')
  556. {
  557. if (strtolower($buffer) == '/quote' && sizeof($close_tags) && substr($out, -1, 1) == '[')
  558. {
  559. // we have found a closing tag
  560. $out .= array_pop($close_tags) . ']';
  561. $tok = '[';
  562. $buffer = '';
  563. /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
  564. * Do not try to think for the user. :/ Do not parse urls/smilies if there is no space - is the same as with other bbcodes too.
  565. * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
  566. if (!$in || $in[0] !== ' ')
  567. {
  568. $out .= ' ';
  569. }*/
  570. }
  571. else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
  572. {
  573. // the buffer holds a valid opening tag
  574. if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])
  575. {
  576. // there are too many nested quotes
  577. $error_ary['quote_depth'] = sprintf($user->lang['QUOTE_DEPTH_EXCEEDED'], $config['max_quote_depth']);
  578. $out .= $buffer . $tok;
  579. $tok = '[]';
  580. $buffer = '';
  581. continue;
  582. }
  583. array_push($close_tags, '/quote:' . $this->bbcode_uid);
  584. if (isset($m[1]) && $m[1])
  585. {
  586. $username = str_replace(array('&#91;', '&#93;'), array('[', ']'), $m[1]);
  587. $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $username);
  588. $end_tags = array();
  589. $error = false;
  590. preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
  591. foreach ($tags[1] as $tag)
  592. {
  593. if ($tag[0] != '/')
  594. {
  595. $end_tags[] = '/' . $tag;
  596. }
  597. else
  598. {
  599. $end_tag = array_pop($end_tags);
  600. $error = ($end_tag != $tag) ? true : false;
  601. }
  602. }
  603. if ($error)
  604. {
  605. $username = $m[1];
  606. }
  607. $out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
  608. }
  609. else
  610. {
  611. $out .= 'quote:' . $this->bbcode_uid . ']';
  612. }
  613. $tok = '[';
  614. $buffer = '';
  615. }
  616. else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
  617. {
  618. // the buffer holds an invalid opening tag
  619. $buffer .= ']';
  620. }
  621. else
  622. {
  623. $out .= $buffer . $tok;
  624. $tok = '[]';
  625. $buffer = '';
  626. }
  627. }
  628. else
  629. {
  630. /**
  631. * Old quote code working fine, but having errors listed in bug #3572
  632. *
  633. * $out .= $buffer . $tok;
  634. * $tok = ($tok == '[') ? ']' : '[]';
  635. * $buffer = '';
  636. */
  637. $out .= $buffer . $tok;
  638. if ($tok == '[')
  639. {
  640. // Search the text for the next tok... if an ending quote comes first, then change tok to []
  641. $pos1 = stripos($in, '[/quote');
  642. // If the token ] comes first, we change it to ]
  643. $pos2 = strpos($in, ']');
  644. // If the token [ comes first, we change it to [
  645. $pos3 = strpos($in, '[');
  646. if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
  647. {
  648. $tok = '[]';
  649. }
  650. else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
  651. {
  652. $tok = '[';
  653. }
  654. else
  655. {
  656. $tok = ']';
  657. }
  658. }
  659. else
  660. {
  661. $tok = '[]';
  662. }
  663. $buffer = '';
  664. }
  665. }
  666. while ($in);
  667. if (sizeof($close_tags))
  668. {
  669. $out .= '[' . implode('][', $close_tags) . ']';
  670. }
  671. foreach ($error_ary as $error_msg)
  672. {
  673. $this->warn_msg[] = $error_msg;
  674. }
  675. return $out;
  676. }
  677. /**
  678. * Validate email
  679. */
  680. function validate_email($var1, $var2)
  681. {
  682. $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
  683. $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
  684. $txt = $var2;
  685. $email = ($var1) ? $var1 : $var2;
  686. $validated = true;
  687. if (!preg_match('/^' . $this->get_preg_expression('email') . '$/i', $email))
  688. {
  689. $validated = false;
  690. }
  691. if (!$validated)
  692. {
  693. return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
  694. }
  695. if ($var1)
  696. {
  697. $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
  698. }
  699. else
  700. {
  701. $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
  702. }
  703. return $retval;
  704. }
  705. /**
  706. * Validate url
  707. *
  708. * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
  709. * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
  710. */
  711. function validate_url($var1, $var2)
  712. {
  713. global $config;
  714. $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
  715. $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
  716. $url = ($var1) ? $var1 : $var2;
  717. if ($var1 && !$var2)
  718. {
  719. $var2 = $var1;
  720. }
  721. if (!$url)
  722. {
  723. return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
  724. }
  725. $valid = false;
  726. $url = str_replace(' ', '%20', $url);
  727. // Checking urls
  728. if (preg_match('#^' . $this->get_preg_expression('url') . '$#i', $url) ||
  729. preg_match('#^' . $this->get_preg_expression('www_url') . '$#i', $url) ||
  730. preg_match('#^' . preg_quote(generate_board_url(), '#') . $this->get_preg_expression('relative_url') . '$#i', $url))
  731. {
  732. $valid = true;
  733. }
  734. if ($valid)
  735. {
  736. // if there is no scheme, then add http schema
  737. if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
  738. {
  739. $url = 'http://' . $url;
  740. }
  741. // Is this a link to somewhere inside this board? If so then remove the session id from the url
  742. if (strpos($url, $this->source_url) !== false && strpos($url, 'sid=') !== false)
  743. {
  744. $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}&amp;/', '\1', $url);
  745. $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}$/', '', $url);
  746. $url = append_sid($url);
  747. }
  748. return ($var1) ? '[url=' . $this->bbcode_specialchars($url) . ':' . $this->bbcode_uid . ']' . $var2 . '[/url:' . $this->bbcode_uid . ']' : '[url:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($url) . '[/url:' . $this->bbcode_uid . ']';
  749. }
  750. return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
  751. }
  752. /**
  753. * This function returns a regular expression pattern for commonly used expressions
  754. * Use with / as delimiter for email mode and # for url modes
  755. * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
  756. */
  757. function get_preg_expression($mode)
  758. {
  759. switch ($mode)
  760. {
  761. case 'email':
  762. return '(?:[a-z0-9\'\.\-_\+\|]++|&amp;)+@[a-z0-9\-]+\.(?:[a-z0-9\-]+\.)*[a-z]+';
  763. break;
  764. case 'bbcode_htm':
  765. return array(
  766. '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
  767. '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
  768. '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
  769. '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
  770. '#<!\-\- .*? \-\->#s',
  771. '#<.*?>#s',
  772. );
  773. break;
  774. // Whoa these look impressive!
  775. // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
  776. // can be found in the develop directory
  777. case 'ipv4':
  778. return '#^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$#';
  779. break;
  780. case 'ipv6':
  781. return '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){5}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:))$#i';
  782. break;
  783. case 'url':
  784. case 'url_inline':
  785. $inline = ($mode == 'url') ? ')' : '';
  786. $scheme = ($mode == 'url') ? '[a-z\d+\-.]' : '[a-z\d+]'; // avoid automatic parsing of "word" in "last word.http://..."
  787. // generated with regex generation file in the develop folder
  788. return "[a-z]$scheme*:/{2}(?:(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  789. break;
  790. case 'www_url':
  791. case 'www_url_inline':
  792. $inline = ($mode == 'www_url') ? ')' : '';
  793. return "www\.(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  794. break;
  795. case 'relative_url':
  796. case 'relative_url_inline':
  797. $inline = ($mode == 'relative_url') ? ')' : '';
  798. return "(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  799. break;
  800. }
  801. return '';
  802. }
  803. }