PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/include/parser.php

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