PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/nacridan/forum/include/parser.php

https://gitlab.com/nacridan/Nacridan
PHP | 987 lines | 688 code | 150 blank | 149 comment | 192 complexity | e5211201e246f826d283676133e47ead MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright (C) 2008-2012 FluxBB
  4. * based on code by Rickard Andersson copyright (C) 2002-2008 PunBB
  5. * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
  6. */
  7. // Make sure no one attempts to run this script "directly"
  8. if (!defined('PUN'))
  9. exit;
  10. // Global variables
  11. /* regular expression to match nested BBCode LIST tags
  12. '%
  13. \[list # match opening bracket and tag name of outermost LIST tag
  14. (?:=([1a*]))?+ # optional attribute capture in group 1
  15. \] # closing bracket of outermost opening LIST tag
  16. ( # capture contents of LIST tag in group 2
  17. (?: # non capture group for either contents or whole nested LIST
  18. [^\[]*+ # unroll the loop! consume everything up to next [ (normal *)
  19. (?: # (See "Mastering Regular Expressions" chapter 6 for details)
  20. (?! # negative lookahead ensures we are NOT on [LIST*] or [/LIST]
  21. \[list # opening LIST tag
  22. (?:=[1a*])?+ # with optional attribute
  23. \] # closing bracket of opening LIST tag
  24. | # or...
  25. \[/list\] # a closing LIST tag
  26. ) # end negative lookahead assertion (we are not on a LIST tag)
  27. \[ # match the [ which is NOT the start of LIST tag (special)
  28. [^\[]*+ # consume everything up to next [ (normal *)
  29. )*+ # finish up "unrolling the loop" technique (special (normal*))*
  30. | # or...
  31. (?R) # recursively match a whole nested LIST element
  32. )* # as many times as necessary until deepest nested LIST tag grabbed
  33. ) # end capturing contents of LIST tag into group 2
  34. \[/list\] # match outermost closing LIST tag
  35. %iex' */
  36. $re_list = '%\[list(?:=([1a*]))?+\]((?:[^\[]*+(?:(?!\[list(?:=[1a*])?+\]|\[/list\])\[[^\[]*+)*+|(?R))*)\[/list\]%i';
  37. // Here you can add additional smilies if you like (please note that you must escape single quote and backslash)
  38. $smilies = array(
  39. ':)' => 'smile.png',
  40. '=)' => 'smile.png',
  41. ':|' => 'neutral.png',
  42. '=|' => 'neutral.png',
  43. ':(' => 'sad.png',
  44. '=(' => 'sad.png',
  45. ':D' => 'big_smile.png',
  46. '=D' => 'big_smile.png',
  47. ':o' => 'yikes.png',
  48. ':O' => 'yikes.png',
  49. ';)' => 'wink.png',
  50. ':/' => 'hmm.png',
  51. ':P' => 'tongue.png',
  52. ':p' => 'tongue.png',
  53. ':lol:' => 'lol.png',
  54. ':mad:' => 'mad.png',
  55. ':rolleyes:' => 'roll.png',
  56. ':cool:' => 'cool.png');
  57. //
  58. // Make sure all BBCodes are lower case and do a little cleanup
  59. //
  60. function preparse_bbcode($text, &$errors, $is_signature = false)
  61. {
  62. global $pun_config, $lang_common, $lang_post, $re_list;
  63. // Remove empty tags
  64. while (($new_text = strip_empty_bbcode($text)) !== false)
  65. {
  66. if ($new_text != $text)
  67. {
  68. $text = $new_text;
  69. if ($new_text == '')
  70. {
  71. $errors[] = $lang_post['Empty after strip'];
  72. return '';
  73. }
  74. }
  75. else
  76. break;
  77. }
  78. if ($is_signature)
  79. {
  80. global $lang_profile;
  81. if (preg_match('%\[/?(?:quote|code|list|h)\b[^\]]*\]%i', $text))
  82. $errors[] = $lang_profile['Signature quote/code/list/h'];
  83. }
  84. // If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched)
  85. if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false)
  86. list($inside, $text) = extract_blocks($text, '[code]', '[/code]');
  87. // Tidy up lists
  88. $temp = preg_replace_callback($re_list, create_function('$matches', 'return preparse_list_tag($matches[2], $matches[1]);'), $text);
  89. // If the regex failed
  90. if (is_null($temp))
  91. $errors[] = $lang_common['BBCode list size error'];
  92. else
  93. $text = str_replace('*'."\0".']', '*]', $temp);
  94. if ($pun_config['o_make_links'] == '1')
  95. $text = do_clickable($text);
  96. $temp_text = false;
  97. if (empty($errors))
  98. $temp_text = preparse_tags($text, $errors, $is_signature);
  99. if ($temp_text !== false)
  100. $text = $temp_text;
  101. // If we split up the message before we have to concatenate it together again (code tags)
  102. if (isset($inside))
  103. {
  104. $outside = explode("\1", $text);
  105. $text = '';
  106. $num_tokens = count($outside);
  107. for ($i = 0; $i < $num_tokens; ++$i)
  108. {
  109. $text .= $outside[$i];
  110. if (isset($inside[$i]))
  111. $text .= '[code]'.$inside[$i].'[/code]';
  112. }
  113. unset($inside);
  114. }
  115. // Remove empty tags
  116. while (($new_text = strip_empty_bbcode($text)) !== false)
  117. {
  118. if ($new_text != $text)
  119. {
  120. $text = $new_text;
  121. if ($new_text == '')
  122. {
  123. $errors[] = $lang_post['Empty after strip'];
  124. break;
  125. }
  126. }
  127. else
  128. break;
  129. }
  130. return pun_trim($text);
  131. }
  132. //
  133. // Strip empty bbcode tags from some text
  134. //
  135. function strip_empty_bbcode($text)
  136. {
  137. // If the message contains a code tag we have to split it up (empty tags within [code][/code] are fine)
  138. if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false)
  139. list($inside, $text) = extract_blocks($text, '[code]', '[/code]');
  140. // Remove empty tags
  141. while (!is_null($new_text = preg_replace('%\[(b|u|s|ins|del|em|i|h|colou?r|quote|img|url|email|list|topic|post|forum|user)(?:\=[^\]]*)?\]\s*\[/\1\]%', '', $text)))
  142. {
  143. if ($new_text != $text)
  144. $text = $new_text;
  145. else
  146. break;
  147. }
  148. // If we split up the message before we have to concatenate it together again (code tags)
  149. if (isset($inside))
  150. {
  151. $parts = explode("\1", $text);
  152. $text = '';
  153. foreach ($parts as $i => $part)
  154. {
  155. $text .= $part;
  156. if (isset($inside[$i]))
  157. $text .= '[code]'.$inside[$i].'[/code]';
  158. }
  159. }
  160. // Remove empty code tags
  161. while (!is_null($new_text = preg_replace('%\[(code)\]\s*\[/\1\]%', '', $text)))
  162. {
  163. if ($new_text != $text)
  164. $text = $new_text;
  165. else
  166. break;
  167. }
  168. return $text;
  169. }
  170. //
  171. // Check the structure of bbcode tags and fix simple mistakes where possible
  172. //
  173. function preparse_tags($text, &$errors, $is_signature = false)
  174. {
  175. global $lang_common, $pun_config, $pun_user;
  176. // Start off by making some arrays of bbcode tags and what we need to do with each one
  177. // List of all the tags
  178. $tags = array('quote', 'code', 'b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'img', 'list', '*', 'h', 'topic', 'post', 'forum', 'user');
  179. // List of tags that we need to check are open (You could not put b,i,u in here then illegal nesting like [b][i][/b][/i] would be allowed)
  180. $tags_opened = $tags;
  181. // and tags we need to check are closed (the same as above, added it just in case)
  182. $tags_closed = $tags;
  183. // Tags we can nest and the depth they can be nested to
  184. $tags_nested = array('quote' => $pun_config['o_quote_depth'], 'list' => 5, '*' => 5);
  185. // Tags to ignore the contents of completely (just code)
  186. $tags_ignore = array('code');
  187. // Tags not allowed
  188. $tags_forbidden = array();
  189. // Block tags, block tags can only go within another block tag, they cannot be in a normal tag
  190. $tags_block = array('quote', 'code', 'list', 'h', '*');
  191. // Inline tags, we do not allow new lines in these
  192. $tags_inline = array('b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'h', 'topic', 'post', 'forum', 'user');
  193. // Tags we trim interior space
  194. $tags_trim = array('img');
  195. // Tags we remove quotes from the argument
  196. $tags_quotes = array('url', 'email', 'img', 'topic', 'post', 'forum', 'user');
  197. // Tags we limit bbcode in
  198. $tags_limit_bbcode = array(
  199. '*' => array('b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'list', 'img', 'code', 'topic', 'post', 'forum', 'user'),
  200. 'list' => array('*'),
  201. 'url' => array('img'),
  202. 'email' => array('img'),
  203. 'topic' => array('img'),
  204. 'post' => array('img'),
  205. 'forum' => array('img'),
  206. 'user' => array('img'),
  207. 'img' => array(),
  208. 'h' => array('b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'topic', 'post', 'forum', 'user'),
  209. );
  210. // Tags we can automatically fix bad nesting
  211. $tags_fix = array('quote', 'b', 'i', 'u', 's', 'ins', 'del', 'em', 'color', 'colour', 'url', 'email', 'h', 'topic', 'post', 'forum', 'user');
  212. // Disallow URL tags
  213. if ($pun_user['g_post_links'] != '1')
  214. $tags_forbidden[] = 'url';
  215. $split_text = preg_split('%(\[[\*a-zA-Z0-9-/]*?(?:=.*?)?\])%', $text, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  216. $open_tags = array('fluxbb-bbcode');
  217. $open_args = array('');
  218. $opened_tag = 0;
  219. $new_text = '';
  220. $current_ignore = '';
  221. $current_nest = '';
  222. $current_depth = array();
  223. $limit_bbcode = $tags;
  224. $count_ignored = array();
  225. foreach ($split_text as $current)
  226. {
  227. if ($current == '')
  228. continue;
  229. // Are we dealing with a tag?
  230. if (substr($current, 0, 1) != '[' || substr($current, -1, 1) != ']')
  231. {
  232. // It's not a bbcode tag so we put it on the end and continue
  233. // If we are nested too deeply don't add to the end
  234. if ($current_nest)
  235. continue;
  236. $current = str_replace("\r\n", "\n", $current);
  237. $current = str_replace("\r", "\n", $current);
  238. if (in_array($open_tags[$opened_tag], $tags_inline) && strpos($current, "\n") !== false)
  239. {
  240. // Deal with new lines
  241. $split_current = preg_split('%(\n\n+)%', $current, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  242. $current = '';
  243. if (!pun_trim($split_current[0], "\n")) // The first part is a linebreak so we need to handle any open tags first
  244. array_unshift($split_current, '');
  245. for ($i = 1; $i < count($split_current); $i += 2)
  246. {
  247. $temp_opened = array();
  248. $temp_opened_arg = array();
  249. $temp = $split_current[$i - 1];
  250. while (!empty($open_tags))
  251. {
  252. $temp_tag = array_pop($open_tags);
  253. $temp_arg = array_pop($open_args);
  254. if (in_array($temp_tag , $tags_inline))
  255. {
  256. array_push($temp_opened, $temp_tag);
  257. array_push($temp_opened_arg, $temp_arg);
  258. $temp .= '[/'.$temp_tag.']';
  259. }
  260. else
  261. {
  262. array_push($open_tags, $temp_tag);
  263. array_push($open_args, $temp_arg);
  264. break;
  265. }
  266. }
  267. $current .= $temp.$split_current[$i];
  268. $temp = '';
  269. while (!empty($temp_opened))
  270. {
  271. $temp_tag = array_pop($temp_opened);
  272. $temp_arg = array_pop($temp_opened_arg);
  273. if (empty($temp_arg))
  274. $temp .= '['.$temp_tag.']';
  275. else
  276. $temp .= '['.$temp_tag.'='.$temp_arg.']';
  277. array_push($open_tags, $temp_tag);
  278. array_push($open_args, $temp_arg);
  279. }
  280. $current .= $temp;
  281. }
  282. if (array_key_exists($i - 1, $split_current))
  283. $current .= $split_current[$i - 1];
  284. }
  285. if (in_array($open_tags[$opened_tag], $tags_trim))
  286. $new_text .= pun_trim($current);
  287. else
  288. $new_text .= $current;
  289. continue;
  290. }
  291. // Get the name of the tag
  292. $current_arg = '';
  293. if (strpos($current, '/') === 1)
  294. {
  295. $current_tag = substr($current, 2, -1);
  296. }
  297. else if (strpos($current, '=') === false)
  298. {
  299. $current_tag = substr($current, 1, -1);
  300. }
  301. else
  302. {
  303. $current_tag = substr($current, 1, strpos($current, '=')-1);
  304. $current_arg = substr($current, strpos($current, '=')+1, -1);
  305. }
  306. $current_tag = strtolower($current_tag);
  307. // Is the tag defined?
  308. if (!in_array($current_tag, $tags))
  309. {
  310. // It's not a bbcode tag so we put it on the end and continue
  311. if (!$current_nest)
  312. $new_text .= $current;
  313. continue;
  314. }
  315. // We definitely have a bbcode tag
  316. // Make the tag string lower case
  317. if ($equalpos = strpos($current,'='))
  318. {
  319. // We have an argument for the tag which we don't want to make lowercase
  320. if (strlen(substr($current, $equalpos)) == 2)
  321. {
  322. // Empty tag argument
  323. $errors[] = sprintf($lang_common['BBCode error empty attribute'], $current_tag);
  324. return false;
  325. }
  326. $current = strtolower(substr($current, 0, $equalpos)).substr($current, $equalpos);
  327. }
  328. else
  329. $current = strtolower($current);
  330. // This is if we are currently in a tag which escapes other bbcode such as code
  331. // We keep a count of ignored bbcodes (code tags) so we can nest them, but
  332. // only balanced sets of tags can be nested
  333. if ($current_ignore)
  334. {
  335. // Increase the current ignored tags counter
  336. if ('['.$current_ignore.']' == $current)
  337. $count_ignored[$current_tag]++;
  338. // Decrease the current ignored tags counter
  339. if ('[/'.$current_ignore.']' == $current)
  340. $count_ignored[$current_tag]--;
  341. if ('[/'.$current_ignore.']' == $current && $count_ignored[$current_tag] == 0)
  342. {
  343. // We've finished the ignored section
  344. $current = '[/'.$current_tag.']';
  345. $current_ignore = '';
  346. $count_ignored = array();
  347. }
  348. $new_text .= $current;
  349. continue;
  350. }
  351. // Is the tag forbidden?
  352. if (in_array($current_tag, $tags_forbidden))
  353. {
  354. if (isset($lang_common['BBCode error tag '.$current_tag.' not allowed']))
  355. $errors[] = sprintf($lang_common['BBCode error tag '.$current_tag.' not allowed']);
  356. else
  357. $errors[] = sprintf($lang_common['BBCode error tag not allowed'], $current_tag);
  358. return false;
  359. }
  360. if ($current_nest)
  361. {
  362. // We are currently too deeply nested so lets see if we are closing the tag or not
  363. if ($current_tag != $current_nest)
  364. continue;
  365. if (substr($current, 1, 1) == '/')
  366. $current_depth[$current_nest]--;
  367. else
  368. $current_depth[$current_nest]++;
  369. if ($current_depth[$current_nest] <= $tags_nested[$current_nest])
  370. $current_nest = '';
  371. continue;
  372. }
  373. // Check the current tag is allowed here
  374. if (!in_array($current_tag, $limit_bbcode) && $current_tag != $open_tags[$opened_tag])
  375. {
  376. $errors[] = sprintf($lang_common['BBCode error invalid nesting'], $current_tag, $open_tags[$opened_tag]);
  377. return false;
  378. }
  379. if (substr($current, 1, 1) == '/')
  380. {
  381. // This is if we are closing a tag
  382. if ($opened_tag == 0 || !in_array($current_tag, $open_tags))
  383. {
  384. // We tried to close a tag which is not open
  385. if (in_array($current_tag, $tags_opened))
  386. {
  387. $errors[] = sprintf($lang_common['BBCode error no opening tag'], $current_tag);
  388. return false;
  389. }
  390. }
  391. else
  392. {
  393. // Check nesting
  394. while (true)
  395. {
  396. // Nesting is ok
  397. if ($open_tags[$opened_tag] == $current_tag)
  398. {
  399. array_pop($open_tags);
  400. array_pop($open_args);
  401. $opened_tag--;
  402. break;
  403. }
  404. // Nesting isn't ok, try to fix it
  405. if (in_array($open_tags[$opened_tag], $tags_closed) && in_array($current_tag, $tags_closed))
  406. {
  407. if (in_array($current_tag, $open_tags))
  408. {
  409. $temp_opened = array();
  410. $temp_opened_arg = array();
  411. $temp = '';
  412. while (!empty($open_tags))
  413. {
  414. $temp_tag = array_pop($open_tags);
  415. $temp_arg = array_pop($open_args);
  416. if (!in_array($temp_tag, $tags_fix))
  417. {
  418. // We couldn't fix nesting
  419. $errors[] = sprintf($lang_common['BBCode error no closing tag'], array_pop($temp_opened));
  420. return false;
  421. }
  422. array_push($temp_opened, $temp_tag);
  423. array_push($temp_opened_arg, $temp_arg);
  424. if ($temp_tag == $current_tag)
  425. break;
  426. else
  427. $temp .= '[/'.$temp_tag.']';
  428. }
  429. $current = $temp.$current;
  430. $temp = '';
  431. array_pop($temp_opened);
  432. array_pop($temp_opened_arg);
  433. while (!empty($temp_opened))
  434. {
  435. $temp_tag = array_pop($temp_opened);
  436. $temp_arg = array_pop($temp_opened_arg);
  437. if (empty($temp_arg))
  438. $temp .= '['.$temp_tag.']';
  439. else
  440. $temp .= '['.$temp_tag.'='.$temp_arg.']';
  441. array_push($open_tags, $temp_tag);
  442. array_push($open_args, $temp_arg);
  443. }
  444. $current .= $temp;
  445. $opened_tag--;
  446. break;
  447. }
  448. else
  449. {
  450. // We couldn't fix nesting
  451. $errors[] = sprintf($lang_common['BBCode error no opening tag'], $current_tag);
  452. return false;
  453. }
  454. }
  455. else if (in_array($open_tags[$opened_tag], $tags_closed))
  456. break;
  457. else
  458. {
  459. array_pop($open_tags);
  460. array_pop($open_args);
  461. $opened_tag--;
  462. }
  463. }
  464. }
  465. if (in_array($current_tag, array_keys($tags_nested)))
  466. {
  467. if (isset($current_depth[$current_tag]))
  468. $current_depth[$current_tag]--;
  469. }
  470. if (in_array($open_tags[$opened_tag], array_keys($tags_limit_bbcode)))
  471. $limit_bbcode = $tags_limit_bbcode[$open_tags[$opened_tag]];
  472. else
  473. $limit_bbcode = $tags;
  474. $new_text .= $current;
  475. continue;
  476. }
  477. else
  478. {
  479. // We are opening a tag
  480. if (in_array($current_tag, array_keys($tags_limit_bbcode)))
  481. $limit_bbcode = $tags_limit_bbcode[$current_tag];
  482. else
  483. $limit_bbcode = $tags;
  484. if (in_array($current_tag, $tags_block) && !in_array($open_tags[$opened_tag], $tags_block) && $opened_tag != 0)
  485. {
  486. // We tried to open a block tag within a non-block tag
  487. $errors[] = sprintf($lang_common['BBCode error invalid nesting'], $current_tag, $open_tags[$opened_tag]);
  488. return false;
  489. }
  490. if (in_array($current_tag, $tags_ignore))
  491. {
  492. // It's an ignore tag so we don't need to worry about what's inside it
  493. $current_ignore = $current_tag;
  494. $count_ignored[$current_tag] = 1;
  495. $new_text .= $current;
  496. continue;
  497. }
  498. // Deal with nested tags
  499. if (in_array($current_tag, $open_tags) && !in_array($current_tag, array_keys($tags_nested)))
  500. {
  501. // We nested a tag we shouldn't
  502. $errors[] = sprintf($lang_common['BBCode error invalid self-nesting'], $current_tag);
  503. return false;
  504. }
  505. else if (in_array($current_tag, array_keys($tags_nested)))
  506. {
  507. // We are allowed to nest this tag
  508. if (isset($current_depth[$current_tag]))
  509. $current_depth[$current_tag]++;
  510. else
  511. $current_depth[$current_tag] = 1;
  512. // See if we are nested too deep
  513. if ($current_depth[$current_tag] > $tags_nested[$current_tag])
  514. {
  515. $current_nest = $current_tag;
  516. continue;
  517. }
  518. }
  519. // Remove quotes from arguments for certain tags
  520. if (strpos($current, '=') !== false && in_array($current_tag, $tags_quotes))
  521. {
  522. $current = preg_replace('%\['.$current_tag.'=("|\'|)(.*?)\\1\]\s*%i', '['.$current_tag.'=$2]', $current);
  523. }
  524. if (in_array($current_tag, array_keys($tags_limit_bbcode)))
  525. $limit_bbcode = $tags_limit_bbcode[$current_tag];
  526. $open_tags[] = $current_tag;
  527. $open_args[] = $current_arg;
  528. $opened_tag++;
  529. $new_text .= $current;
  530. continue;
  531. }
  532. }
  533. // Check we closed all the tags we needed to
  534. foreach ($tags_closed as $check)
  535. {
  536. if (in_array($check, $open_tags))
  537. {
  538. // We left an important tag open
  539. $errors[] = sprintf($lang_common['BBCode error no closing tag'], $check);
  540. return false;
  541. }
  542. }
  543. if ($current_ignore)
  544. {
  545. // We left an ignore tag open
  546. $errors[] = sprintf($lang_common['BBCode error no closing tag'], $current_ignore);
  547. return false;
  548. }
  549. return $new_text;
  550. }
  551. //
  552. // Preparse the contents of [list] bbcode
  553. //
  554. function preparse_list_tag($content, $type = '*')
  555. {
  556. global $lang_common, $re_list;
  557. if (strlen($type) != 1)
  558. $type = '*';
  559. if (strpos($content,'[list') !== false)
  560. {
  561. $content = preg_replace_callback($re_list, create_function('$matches', 'return preparse_list_tag($matches[2], $matches[1]);'), $content);
  562. }
  563. $items = explode('[*]', str_replace('\"', '"', $content));
  564. $content = '';
  565. foreach ($items as $item)
  566. {
  567. if (pun_trim($item) != '')
  568. $content .= '[*'."\0".']'.str_replace('[/*]', '', pun_trim($item)).'[/*'."\0".']'."\n";
  569. }
  570. return '[list='.$type.']'."\n".$content.'[/list]';
  571. }
  572. //
  573. // Truncate URL if longer than 55 characters (add http:// or ftp:// if missing)
  574. //
  575. function handle_url_tag($url, $link = '', $bbcode = false)
  576. {
  577. $url = pun_trim($url);
  578. // Deal with [url][img]http://example.com/test.png[/img][/url]
  579. if (preg_match('%<img src=\"(.*?)\"%', $url, $matches))
  580. return handle_url_tag($matches[1], $url, $bbcode);
  581. $full_url = str_replace(array(' ', '\'', '`', '"'), array('%20', '', '', ''), $url);
  582. if (strpos($url, 'www.') === 0) // If it starts with www, we add http://
  583. $full_url = 'http://'.$full_url;
  584. else if (strpos($url, 'ftp.') === 0) // Else if it starts with ftp, we add ftp://
  585. $full_url = 'ftp://'.$full_url;
  586. else if (strpos($url, '/') === 0) // Allow for relative URLs that start with a slash
  587. $full_url = get_base_url(true).$full_url;
  588. else if (!preg_match('#^([a-z0-9]{3,6})://#', $url)) // Else if it doesn't start with abcdef://, we add http://
  589. $full_url = 'http://'.$full_url;
  590. // Ok, not very pretty :-)
  591. if ($bbcode)
  592. {
  593. if ($full_url == $link)
  594. return '[url]'.$link.'[/url]';
  595. else
  596. return '[url='.$full_url.']'.$link.'[/url]';
  597. }
  598. else
  599. {
  600. if ($link == '' || $link == $url)
  601. {
  602. $url = pun_htmlspecialchars_decode($url);
  603. $link = utf8_strlen($url) > 55 ? utf8_substr($url, 0 , 39).' … '.utf8_substr($url, -10) : $url;
  604. $link = pun_htmlspecialchars($link);
  605. }
  606. else
  607. $link = stripslashes($link);
  608. return '<a href="'.$full_url.'" rel="nofollow">'.$link.'</a>';
  609. }
  610. }
  611. //
  612. // Turns an URL from the [img] tag into an <img> tag or a <a href...> tag
  613. //
  614. function handle_img_tag($url, $is_signature = false, $alt = null)
  615. {
  616. global $lang_common, $pun_user;
  617. if (is_null($alt))
  618. $alt = basename($url);
  619. $img_tag = '<a href="'.$url.'" rel="nofollow">&lt;'.$lang_common['Image link'].' - '.$alt.'&gt;</a>';
  620. if ($is_signature && $pun_user['show_img_sig'] != '0')
  621. $img_tag = '<img class="sigimage" src="'.$url.'" alt="'.$alt.'" />';
  622. else if (!$is_signature && $pun_user['show_img'] != '0')
  623. $img_tag = '<span class="postimg"><img src="'.$url.'" alt="'.$alt.'" /></span>';
  624. return $img_tag;
  625. }
  626. //
  627. // Parse the contents of [list] bbcode
  628. //
  629. function handle_list_tag($content, $type = '*')
  630. {
  631. global $re_list;
  632. if (strlen($type) != 1)
  633. $type = '*';
  634. if (strpos($content,'[list') !== false)
  635. {
  636. $content = preg_replace_callback($re_list, create_function('$matches', 'return handle_list_tag($matches[2], $matches[1]);'), $content);
  637. }
  638. $content = preg_replace('#\s*\[\*\](.*?)\[/\*\]\s*#s', '<li><p>$1</p></li>', pun_trim($content));
  639. if ($type == '*')
  640. $content = '<ul>'.$content.'</ul>';
  641. else
  642. if ($type == 'a')
  643. $content = '<ol class="alpha">'.$content.'</ol>';
  644. else
  645. $content = '<ol class="decimal">'.$content.'</ol>';
  646. return '</p>'.$content.'<p>';
  647. }
  648. //
  649. // Convert BBCodes to their HTML equivalent
  650. //
  651. function do_bbcode($text, $is_signature = false)
  652. {
  653. global $lang_common, $pun_user, $pun_config, $re_list;
  654. if (strpos($text, '[quote') !== false)
  655. {
  656. $text = preg_replace('%\[quote\]\s*%', '</p><div class="quotebox"><blockquote><div><p>', $text);
  657. $text = preg_replace_callback('%\[quote=(&quot;|&\#039;|"|\'|)(.*?)\\1\]%s', create_function('$matches', 'global $lang_common; return "</p><div class=\"quotebox\"><cite>".str_replace(array(\'[\', \'\\"\'), array(\'&#91;\', \'"\'), $matches[2])." ".$lang_common[\'wrote\']."</cite><blockquote><div><p>";'), $text);
  658. $text = preg_replace('%\s*\[\/quote\]%S', '</p></div></blockquote></div><p>', $text);
  659. }
  660. if (!$is_signature)
  661. {
  662. $pattern_callback[] = $re_list;
  663. $replace_callback[] = 'handle_list_tag($matches[2], $matches[1])';
  664. }
  665. $pattern[] = '%\[b\](.*?)\[/b\]%ms';
  666. $pattern[] = '%\[i\](.*?)\[/i\]%ms';
  667. $pattern[] = '%\[u\](.*?)\[/u\]%ms';
  668. $pattern[] = '%\[s\](.*?)\[/s\]%ms';
  669. $pattern[] = '%\[del\](.*?)\[/del\]%ms';
  670. $pattern[] = '%\[ins\](.*?)\[/ins\]%ms';
  671. $pattern[] = '%\[em\](.*?)\[/em\]%ms';
  672. $pattern[] = '%\[colou?r=([a-zA-Z]{3,20}|\#[0-9a-fA-F]{6}|\#[0-9a-fA-F]{3})](.*?)\[/colou?r\]%ms';
  673. $pattern[] = '%\[h\](.*?)\[/h\]%ms';
  674. $replace[] = '<strong>$1</strong>';
  675. $replace[] = '<em>$1</em>';
  676. $replace[] = '<span class="bbu">$1</span>';
  677. $replace[] = '<span class="bbs">$1</span>';
  678. $replace[] = '<del>$1</del>';
  679. $replace[] = '<ins>$1</ins>';
  680. $replace[] = '<em>$1</em>';
  681. $replace[] = '<span style="color: $1">$2</span>';
  682. $replace[] = '</p><h5>$1</h5><p>';
  683. if (($is_signature && $pun_config['p_sig_img_tag'] == '1') || (!$is_signature && $pun_config['p_message_img_tag'] == '1'))
  684. {
  685. $pattern_callback[] = '%\[img\]((ht|f)tps?://)([^\s<"]*?)\[/img\]%';
  686. $pattern_callback[] = '%\[img=([^\[]*?)\]((ht|f)tps?://)([^\s<"]*?)\[/img\]%';
  687. if ($is_signature)
  688. {
  689. $replace_callback[] = 'handle_img_tag($matches[1].$matches[3], true)';
  690. $replace_callback[] = 'handle_img_tag($matches[2].$matches[4], true, $matches[1])';
  691. }
  692. else
  693. {
  694. $replace_callback[] = 'handle_img_tag($matches[1].$matches[3], false)';
  695. $replace_callback[] = 'handle_img_tag($matches[2].$matches[4], false, $matches[1])';
  696. }
  697. }
  698. $pattern_callback[] = '%\[url\]([^\[]*?)\[/url\]%';
  699. $pattern_callback[] = '%\[url=([^\[]+?)\](.*?)\[/url\]%';
  700. $pattern[] = '%\[email\]([^\[]*?)\[/email\]%';
  701. $pattern[] = '%\[email=([^\[]+?)\](.*?)\[/email\]%';
  702. $pattern_callback[] = '%\[topic\]([1-9]\d*)\[/topic\]%';
  703. $pattern_callback[] = '%\[topic=([1-9]\d*)\](.*?)\[/topic\]%';
  704. $pattern_callback[] = '%\[post\]([1-9]\d*)\[/post\]%';
  705. $pattern_callback[] = '%\[post=([1-9]\d*)\](.*?)\[/post\]%';
  706. $pattern_callback[] = '%\[forum\]([1-9]\d*)\[/forum\]%';
  707. $pattern_callback[] = '%\[forum=([1-9]\d*)\](.*?)\[/forum\]%';
  708. $pattern_callback[] = '%\[user\]([1-9]\d*)\[/user\]%';
  709. $pattern_callback[] = '%\[user=([1-9]\d*)\](.*?)\[/user\]%';
  710. $replace_callback[] = 'handle_url_tag($matches[1])';
  711. $replace_callback[] = 'handle_url_tag($matches[1], $matches[2])';
  712. $replace[] = '<a href="mailto:$1">$1</a>';
  713. $replace[] = '<a href="mailto:$1">$2</a>';
  714. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?id=\'.$matches[1])';
  715. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?id=\'.$matches[1], $matches[2])';
  716. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?pid=\'.$matches[1].\'#p\'.$matches[1])';
  717. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/viewtopic.php?pid=\'.$matches[1].\'#p\'.$matches[1], $matches[2])';
  718. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/viewforum.php?id=\'.$matches[1])';
  719. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/viewforum.php?id=\'.$matches[1], $matches[2])';
  720. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/profile.php?id=\'.$matches[1])';
  721. $replace_callback[] = 'handle_url_tag(\''.get_base_url(true).'/profile.php?id=\'.$matches[1], $matches[2])';
  722. // This thing takes a while! :)
  723. $text = preg_replace($pattern, $replace, $text);
  724. $count = count($pattern_callback);
  725. for($i = 0 ; $i < $count ; $i++)
  726. {
  727. $text = preg_replace_callback($pattern_callback[$i], create_function('$matches', 'return '.$replace_callback[$i].';'), $text);
  728. }
  729. return $text;
  730. }
  731. //
  732. // Make hyperlinks clickable
  733. //
  734. function do_clickable($text)
  735. {
  736. $text = ' '.$text;
  737. $text = ucp_preg_replace_callback('%(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(https?|ftp|news){1}://([\p{L}\p{N}\-]+\.([\p{L}\p{N}\-]+\.)*[\p{L}\p{N}]+(:[0-9]+)?(/(?:[^\s\[]*[^\s.,?!\[;:-])?)?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])%ui', 'stripslashes($matches[1].$matches[2].$matches[3].$matches[4]).handle_url_tag($matches[5]."://".$matches[6], $matches[5]."://".$matches[6], true).stripslashes($matches[4].forum_array_key($matches, 10).forum_array_key($matches, 11).forum_array_key($matches, 12))', $text);
  738. $text = ucp_preg_replace_callback('%(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(www|ftp)\.(([\p{L}\p{N}\-]+\.)+[\p{L}\p{N}]+(:[0-9]+)?(/(?:[^\s\[]*[^\s.,?!\[;:-])?)?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])%ui','stripslashes($matches[1].$matches[2].$matches[3].$matches[4]).handle_url_tag($matches[5].".".$matches[6], $matches[5].".".$matches[6], true).stripslashes($matches[4].forum_array_key($matches, 10).forum_array_key($matches, 11).forum_array_key($matches, 12))', $text);
  739. return substr($text, 1);
  740. }
  741. //
  742. // Return an array key, if it exists, otherwise return an empty string
  743. //
  744. function forum_array_key($arr, $key)
  745. {
  746. return isset($arr[$key]) ? $arr[$key] : '';
  747. }
  748. //
  749. // Convert a series of smilies to images
  750. //
  751. function do_smilies($text)
  752. {
  753. global $smilies;
  754. $text = ' '.$text.' ';
  755. foreach ($smilies as $smiley_text => $smiley_img)
  756. {
  757. if (strpos($text, $smiley_text) !== false)
  758. $text = ucp_preg_replace('%(?<=[>\s])'.preg_quote($smiley_text, '%').'(?=[^\p{L}\p{N}])%um', '<img src="'.pun_htmlspecialchars(get_base_url(true).'/img/smilies/'.$smiley_img).'" width="15" height="15" alt="'.substr($smiley_img, 0, strrpos($smiley_img, '.')).'" />', $text);
  759. }
  760. return substr($text, 1, -1);
  761. }
  762. //
  763. // Parse message text
  764. //
  765. function parse_message($text, $hide_smilies)
  766. {
  767. global $pun_config, $lang_common, $pun_user;
  768. if ($pun_config['o_censoring'] == '1')
  769. $text = censor_words($text);
  770. // Convert applicable characters to HTML entities
  771. $text = pun_htmlspecialchars($text);
  772. // If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched)
  773. if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false)
  774. list($inside, $text) = extract_blocks($text, '[code]', '[/code]');
  775. if ($pun_config['p_message_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false)
  776. $text = do_bbcode($text);
  777. if ($pun_config['o_smilies'] == '1' && $pun_user['show_smilies'] == '1' && $hide_smilies == '0')
  778. $text = do_smilies($text);
  779. // Deal with newlines, tabs and multiple spaces
  780. $pattern = array("\n", "\t", ' ', ' ');
  781. $replace = array('<br />', '&#160; &#160; ', '&#160; ', ' &#160;');
  782. $text = str_replace($pattern, $replace, $text);
  783. // If we split up the message before we have to concatenate it together again (code tags)
  784. if (isset($inside))
  785. {
  786. $parts = explode("\1", $text);
  787. $text = '';
  788. foreach ($parts as $i => $part)
  789. {
  790. $text .= $part;
  791. if (isset($inside[$i]))
  792. {
  793. $num_lines = (substr_count($inside[$i], "\n"));
  794. $text .= '</p><div class="codebox"><pre'.(($num_lines > 28) ? ' class="vscroll"' : '').'><code>'.pun_trim($inside[$i], "\n\r").'</code></pre></div><p>';
  795. }
  796. }
  797. }
  798. return clean_paragraphs($text);
  799. }
  800. //
  801. // Clean up paragraphs and line breaks
  802. //
  803. function clean_paragraphs($text)
  804. {
  805. // Add paragraph tag around post, but make sure there are no empty paragraphs
  806. $text = '<p>'.$text.'</p>';
  807. // Replace any breaks next to paragraphs so our replace below catches them
  808. $text = preg_replace('%(</?p>)(?:\s*?<br />){1,2}%i', '$1', $text);
  809. $text = preg_replace('%(?:<br />\s*?){1,2}(</?p>)%i', '$1', $text);
  810. // Remove any empty paragraph tags (inserted via quotes/lists/code/etc) which should be stripped
  811. $text = str_replace('<p></p>', '', $text);
  812. $text = preg_replace('%<br />\s*?<br />%i', '</p><p>', $text);
  813. $text = str_replace('<p><br />', '<br /><p>', $text);
  814. $text = str_replace('<br /></p>', '</p><br />', $text);
  815. $text = str_replace('<p></p>', '<br /><br />', $text);
  816. return $text;
  817. }
  818. //
  819. // Parse signature text
  820. //
  821. function parse_signature($text)
  822. {
  823. global $pun_config, $lang_common, $pun_user;
  824. if ($pun_config['o_censoring'] == '1')
  825. $text = censor_words($text);
  826. // Convert applicable characters to HTML entities
  827. $text = pun_htmlspecialchars($text);
  828. if ($pun_config['p_sig_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false)
  829. $text = do_bbcode($text, true);
  830. if ($pun_config['o_smilies_sig'] == '1' && $pun_user['show_smilies'] == '1')
  831. $text = do_smilies($text);
  832. // Deal with newlines, tabs and multiple spaces
  833. $pattern = array("\n", "\t", ' ', ' ');
  834. $replace = array('<br />', '&#160; &#160; ', '&#160; ', ' &#160;');
  835. $text = str_replace($pattern, $replace, $text);
  836. return clean_paragraphs($text);
  837. }