PageRenderTime 52ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/phpBB/includes/message_parser.php

http://github.com/phpbb/phpbb
PHP | 2087 lines | 1393 code | 294 blank | 400 comment | 254 complexity | 69e70d81fcfffc2a2abebf3f2f6995ce MD5 | raw file
Possible License(s): GPL-3.0, AGPL-1.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. *
  4. * This file is part of the phpBB Forum Software package.
  5. *
  6. * @copyright (c) phpBB Limited <https://www.phpbb.com>
  7. * @license GNU General Public License, version 2 (GPL-2.0)
  8. *
  9. * For full copyright and license information, please see
  10. * the docs/CREDITS.txt file.
  11. *
  12. */
  13. /**
  14. * @ignore
  15. */
  16. if (!defined('IN_PHPBB'))
  17. {
  18. exit;
  19. }
  20. if (!class_exists('bbcode'))
  21. {
  22. // The following lines are for extensions which include message_parser.php
  23. // while $phpbb_root_path and $phpEx are out of the script scope
  24. // which may lead to the 'Undefined variable' and 'failed to open stream' errors
  25. if (!isset($phpbb_root_path))
  26. {
  27. global $phpbb_root_path;
  28. }
  29. if (!isset($phpEx))
  30. {
  31. global $phpEx;
  32. }
  33. include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
  34. }
  35. /**
  36. * BBCODE FIRSTPASS
  37. * BBCODE first pass class (functions for parsing messages for db storage)
  38. */
  39. class bbcode_firstpass extends bbcode
  40. {
  41. var $message = '';
  42. var $warn_msg = array();
  43. var $parsed_items = array();
  44. var $mode;
  45. /**
  46. * Parse BBCode
  47. */
  48. function parse_bbcode()
  49. {
  50. if (!$this->bbcodes)
  51. {
  52. $this->bbcode_init();
  53. }
  54. global $user;
  55. $this->bbcode_bitfield = '';
  56. $bitfield = new bitfield();
  57. foreach ($this->bbcodes as $bbcode_name => $bbcode_data)
  58. {
  59. if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
  60. {
  61. foreach ($bbcode_data['regexp'] as $regexp => $replacement)
  62. {
  63. if (preg_match($regexp, $this->message))
  64. {
  65. $this->warn_msg[] = sprintf($user->lang['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']');
  66. continue;
  67. }
  68. }
  69. }
  70. else
  71. {
  72. foreach ($bbcode_data['regexp'] as $regexp => $replacement)
  73. {
  74. // The pattern gets compiled and cached by the PCRE extension,
  75. // it should not demand recompilation
  76. if (preg_match($regexp, $this->message))
  77. {
  78. if (is_callable($replacement))
  79. {
  80. $this->message = preg_replace_callback($regexp, $replacement, $this->message);
  81. }
  82. else
  83. {
  84. $this->message = preg_replace($regexp, $replacement, $this->message);
  85. }
  86. $bitfield->set($bbcode_data['bbcode_id']);
  87. }
  88. }
  89. }
  90. }
  91. $this->bbcode_bitfield = $bitfield->get_base64();
  92. }
  93. /**
  94. * Prepare some bbcodes for better parsing
  95. */
  96. function prepare_bbcodes()
  97. {
  98. // Ok, seems like users instead want the no-parsing of urls, smilies, etc. after and before and within quote tags being tagged as "not a bug".
  99. // Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you.
  100. /* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
  101. if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false)
  102. {
  103. $this->message = str_replace("\r\n", "\n", $this->message);
  104. // We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
  105. $this->message = preg_replace('#\[quote(=&quot;.*?&quot;)?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message);
  106. }
  107. */
  108. // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
  109. }
  110. /**
  111. * Init bbcode data for later parsing
  112. */
  113. function bbcode_init($allow_custom_bbcode = true)
  114. {
  115. global $phpbb_dispatcher;
  116. static $rowset;
  117. $bbcode_class = $this;
  118. // This array holds all bbcode data. BBCodes will be processed in this
  119. // order, so it is important to keep [code] in first position and
  120. // [quote] in second position.
  121. // To parse multiline URL we enable dotall option setting only for URL text
  122. // but not for link itself, thus [url][/url] is not affected.
  123. //
  124. // To perform custom validation in extension, use $this->validate_bbcode_by_extension()
  125. // method which accepts variable number of parameters
  126. $this->bbcodes = array(
  127. 'code' => array('bbcode_id' => BBCODE_ID_CODE, 'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#uis' => function ($match) use($bbcode_class)
  128. {
  129. return $bbcode_class->bbcode_code($match[1], $match[2]);
  130. }
  131. )),
  132. 'quote' => array('bbcode_id' => BBCODE_ID_QUOTE, 'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#uis' => function ($match) use($bbcode_class)
  133. {
  134. return $bbcode_class->bbcode_quote($match[0]);
  135. }
  136. )),
  137. 'attachment' => array('bbcode_id' => BBCODE_ID_ATTACH, 'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#uis' => function ($match) use($bbcode_class)
  138. {
  139. return $bbcode_class->bbcode_attachment($match[1], $match[2]);
  140. }
  141. )),
  142. 'b' => array('bbcode_id' => BBCODE_ID_B, 'regexp' => array('#\[b\](.*?)\[/b\]#uis' => function ($match) use($bbcode_class)
  143. {
  144. return $bbcode_class->bbcode_strong($match[1]);
  145. }
  146. )),
  147. 'i' => array('bbcode_id' => BBCODE_ID_I, 'regexp' => array('#\[i\](.*?)\[/i\]#uis' => function ($match) use($bbcode_class)
  148. {
  149. return $bbcode_class->bbcode_italic($match[1]);
  150. }
  151. )),
  152. 'url' => array('bbcode_id' => BBCODE_ID_URL, 'regexp' => array('#\[url(=(.*))?\](?(1)((?s).*(?-s))|(.*))\[/url\]#uiU' => function ($match) use($bbcode_class)
  153. {
  154. return $bbcode_class->validate_url($match[2], ($match[3]) ? $match[3] : $match[4]);
  155. }
  156. )),
  157. 'img' => array('bbcode_id' => BBCODE_ID_IMG, 'regexp' => array('#\[img\](.*)\[/img\]#uiU' => function ($match) use($bbcode_class)
  158. {
  159. return $bbcode_class->bbcode_img($match[1]);
  160. }
  161. )),
  162. 'size' => array('bbcode_id' => BBCODE_ID_SIZE, 'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#uis' => function ($match) use($bbcode_class)
  163. {
  164. return $bbcode_class->bbcode_size($match[1], $match[2]);
  165. }
  166. )),
  167. 'color' => array('bbcode_id' => BBCODE_ID_COLOR, 'regexp' => array('!\[color=(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!uis' => function ($match) use($bbcode_class)
  168. {
  169. return $bbcode_class->bbcode_color($match[1], $match[2]);
  170. }
  171. )),
  172. 'u' => array('bbcode_id' => BBCODE_ID_U, 'regexp' => array('#\[u\](.*?)\[/u\]#uis' => function ($match) use($bbcode_class)
  173. {
  174. return $bbcode_class->bbcode_underline($match[1]);
  175. }
  176. )),
  177. 'list' => array('bbcode_id' => BBCODE_ID_LIST, 'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#uis' => function ($match) use($bbcode_class)
  178. {
  179. return $bbcode_class->bbcode_parse_list($match[0]);
  180. }
  181. )),
  182. 'email' => array('bbcode_id' => BBCODE_ID_EMAIL, 'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#uis' => function ($match) use($bbcode_class)
  183. {
  184. return $bbcode_class->validate_email($match[1], $match[2]);
  185. }
  186. )),
  187. 'flash' => array('bbcode_id' => BBCODE_ID_FLASH, 'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ui' => function ($match) use($bbcode_class)
  188. {
  189. return $bbcode_class->bbcode_flash($match[1], $match[2], $match[3]);
  190. }
  191. ))
  192. );
  193. // Zero the parsed items array
  194. $this->parsed_items = array();
  195. foreach ($this->bbcodes as $tag => $bbcode_data)
  196. {
  197. $this->parsed_items[$tag] = 0;
  198. }
  199. if (!$allow_custom_bbcode)
  200. {
  201. return;
  202. }
  203. if (!is_array($rowset))
  204. {
  205. global $db;
  206. $rowset = array();
  207. $sql = 'SELECT *
  208. FROM ' . BBCODES_TABLE;
  209. $result = $db->sql_query($sql);
  210. while ($row = $db->sql_fetchrow($result))
  211. {
  212. $rowset[] = $row;
  213. }
  214. $db->sql_freeresult($result);
  215. }
  216. foreach ($rowset as $row)
  217. {
  218. $this->bbcodes[$row['bbcode_tag']] = array(
  219. 'bbcode_id' => (int) $row['bbcode_id'],
  220. 'regexp' => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
  221. );
  222. }
  223. $bbcodes = $this->bbcodes;
  224. /**
  225. * Event to modify the bbcode data for later parsing
  226. *
  227. * @event core.modify_bbcode_init
  228. * @var array bbcodes Array of bbcode data for use in parsing
  229. * @var array rowset Array of bbcode data from the database
  230. * @since 3.1.0-a3
  231. */
  232. $vars = array('bbcodes', 'rowset');
  233. extract($phpbb_dispatcher->trigger_event('core.modify_bbcode_init', compact($vars)));
  234. $this->bbcodes = $bbcodes;
  235. }
  236. /**
  237. * Making some pre-checks for bbcodes as well as increasing the number of parsed items
  238. */
  239. function check_bbcode($bbcode, &$in)
  240. {
  241. // when using the /e modifier, preg_replace slashes double-quotes but does not
  242. // seem to slash anything else
  243. $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
  244. // Trimming here to make sure no empty bbcodes are parsed accidently
  245. if (trim($in) == '')
  246. {
  247. return false;
  248. }
  249. $this->parsed_items[$bbcode]++;
  250. return true;
  251. }
  252. /**
  253. * Transform some characters in valid bbcodes
  254. */
  255. function bbcode_specialchars($text)
  256. {
  257. $str_from = array('<', '>', '[', ']', '.', ':');
  258. $str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');
  259. return str_replace($str_from, $str_to, $text);
  260. }
  261. /**
  262. * Parse size tag
  263. */
  264. function bbcode_size($stx, $in)
  265. {
  266. global $user, $config;
  267. if (!$this->check_bbcode('size', $in))
  268. {
  269. return $in;
  270. }
  271. if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
  272. {
  273. $this->warn_msg[] = $user->lang('MAX_FONT_SIZE_EXCEEDED', (int) $config['max_' . $this->mode . '_font_size']);
  274. return '[size=' . $stx . ']' . $in . '[/size]';
  275. }
  276. // Do not allow size=0
  277. if ($stx <= 0)
  278. {
  279. return '[size=' . $stx . ']' . $in . '[/size]';
  280. }
  281. return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
  282. }
  283. /**
  284. * Parse color tag
  285. */
  286. function bbcode_color($stx, $in)
  287. {
  288. if (!$this->check_bbcode('color', $in))
  289. {
  290. return $in;
  291. }
  292. return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
  293. }
  294. /**
  295. * Parse u tag
  296. */
  297. function bbcode_underline($in)
  298. {
  299. if (!$this->check_bbcode('u', $in))
  300. {
  301. return $in;
  302. }
  303. return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
  304. }
  305. /**
  306. * Parse b tag
  307. */
  308. function bbcode_strong($in)
  309. {
  310. if (!$this->check_bbcode('b', $in))
  311. {
  312. return $in;
  313. }
  314. return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
  315. }
  316. /**
  317. * Parse i tag
  318. */
  319. function bbcode_italic($in)
  320. {
  321. if (!$this->check_bbcode('i', $in))
  322. {
  323. return $in;
  324. }
  325. return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
  326. }
  327. /**
  328. * Parse img tag
  329. */
  330. function bbcode_img($in)
  331. {
  332. global $user, $config;
  333. if (!$this->check_bbcode('img', $in))
  334. {
  335. return $in;
  336. }
  337. $in = trim($in);
  338. $error = false;
  339. $in = str_replace(' ', '%20', $in);
  340. // Checking urls
  341. if (!preg_match('#^' . get_preg_expression('url') . '$#iu', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
  342. {
  343. return '[img]' . $in . '[/img]';
  344. }
  345. // Try to cope with a common user error... not specifying a protocol but only a subdomain
  346. if (!preg_match('#^[a-z0-9]+://#i', $in))
  347. {
  348. $in = 'http://' . $in;
  349. }
  350. if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
  351. {
  352. $imagesize = new \FastImageSize\FastImageSize();
  353. $size_info = $imagesize->getImageSize(htmlspecialchars_decode($in));
  354. if ($size_info === false)
  355. {
  356. $error = true;
  357. $this->warn_msg[] = $user->lang['UNABLE_GET_IMAGE_SIZE'];
  358. }
  359. else
  360. {
  361. if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $size_info['height'])
  362. {
  363. $error = true;
  364. $this->warn_msg[] = $user->lang('MAX_IMG_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);
  365. }
  366. if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $size_info['width'])
  367. {
  368. $error = true;
  369. $this->warn_msg[] = $user->lang('MAX_IMG_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);
  370. }
  371. }
  372. }
  373. if ($error || $this->path_in_domain($in))
  374. {
  375. return '[img]' . $in . '[/img]';
  376. }
  377. return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
  378. }
  379. /**
  380. * Parse flash tag
  381. */
  382. function bbcode_flash($width, $height, $in)
  383. {
  384. global $user, $config;
  385. if (!$this->check_bbcode('flash', $in))
  386. {
  387. return $in;
  388. }
  389. $in = trim($in);
  390. $error = false;
  391. // Do not allow 0-sizes generally being entered
  392. if ($width <= 0 || $height <= 0)
  393. {
  394. return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
  395. }
  396. $in = str_replace(' ', '%20', $in);
  397. // Make sure $in is a URL.
  398. if (!preg_match('#^' . get_preg_expression('url') . '$#iu', $in) &&
  399. !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
  400. {
  401. return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
  402. }
  403. // Apply the same size checks on flash files as on images
  404. if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
  405. {
  406. if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)
  407. {
  408. $error = true;
  409. $this->warn_msg[] = $user->lang('MAX_FLASH_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);
  410. }
  411. if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)
  412. {
  413. $error = true;
  414. $this->warn_msg[] = $user->lang('MAX_FLASH_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);
  415. }
  416. }
  417. if ($error || $this->path_in_domain($in))
  418. {
  419. return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
  420. }
  421. return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
  422. }
  423. /**
  424. * Parse inline attachments [ia]
  425. */
  426. function bbcode_attachment($stx, $in)
  427. {
  428. if (!$this->check_bbcode('attachment', $in))
  429. {
  430. return $in;
  431. }
  432. return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
  433. }
  434. /**
  435. * Parse code text from code tag
  436. * @access private
  437. */
  438. function bbcode_parse_code($stx, &$code)
  439. {
  440. switch (strtolower($stx))
  441. {
  442. case 'php':
  443. $remove_tags = false;
  444. $str_from = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;', '&#058;');
  445. $str_to = array('<', '>', '[', ']', '.', ':', ':');
  446. $code = str_replace($str_from, $str_to, $code);
  447. if (!preg_match('/\<\?.*?\?\>/is', $code))
  448. {
  449. $remove_tags = true;
  450. $code = "<?php $code ?>";
  451. }
  452. $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
  453. foreach ($conf as $ini_var)
  454. {
  455. @ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
  456. }
  457. // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
  458. $code = htmlspecialchars_decode($code);
  459. $code = highlight_string($code, true);
  460. $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
  461. $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');
  462. if ($remove_tags)
  463. {
  464. $str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
  465. $str_to[] = '';
  466. $str_from[] = '<span class="syntaxdefault">&lt;?php&nbsp;';
  467. $str_to[] = '<span class="syntaxdefault">';
  468. }
  469. $code = str_replace($str_from, $str_to, $code);
  470. $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
  471. if ($remove_tags)
  472. {
  473. $code = preg_replace('#(<span class="[a-z]+">)?\?&gt;(</span>)#', '$1&nbsp;$2', $code);
  474. }
  475. $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
  476. $code = preg_replace('#(?:\s++|&nbsp;)*+</span>$#u', '</span>', $code);
  477. // remove newline at the end
  478. if (!empty($code) && substr($code, -1) == "\n")
  479. {
  480. $code = substr($code, 0, -1);
  481. }
  482. return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
  483. break;
  484. default:
  485. return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
  486. break;
  487. }
  488. }
  489. /**
  490. * Parse code tag
  491. * Expects the argument to start right after the opening [code] tag and to end with [/code]
  492. */
  493. function bbcode_code($stx, $in)
  494. {
  495. if (!$this->check_bbcode('code', $in))
  496. {
  497. return $in;
  498. }
  499. // We remove the hardcoded elements from the code block here because it is not used in code blocks
  500. // Having it here saves us one preg_replace per message containing [code] blocks
  501. // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
  502. $htm_match = get_preg_expression('bbcode_htm');
  503. unset($htm_match[4], $htm_match[5]);
  504. $htm_replace = array('\1', '\1', '\2', '\1');
  505. $out = $code_block = '';
  506. $open = 1;
  507. while ($in)
  508. {
  509. // Determine position and tag length of next code block
  510. preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
  511. $pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
  512. $tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;
  513. // Determine position of ending code tag
  514. $pos2 = stripos($in, '[/code]');
  515. // Which is the next block, ending code or code block
  516. if ($pos !== false && $pos < $pos2)
  517. {
  518. // Open new block
  519. if (!$open)
  520. {
  521. $out .= substr($in, 0, $pos);
  522. $in = substr($in, $pos);
  523. $stx = (isset($buffer[3])) ? $buffer[3] : '';
  524. $code_block = '';
  525. }
  526. else
  527. {
  528. // Already opened block, just append to the current block
  529. $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
  530. $in = substr($in, $pos);
  531. }
  532. $in = substr($in, $tag_length);
  533. $open++;
  534. }
  535. else
  536. {
  537. // Close the block
  538. if ($open == 1)
  539. {
  540. $code_block .= substr($in, 0, $pos2);
  541. $code_block = preg_replace($htm_match, $htm_replace, $code_block);
  542. // Parse this code block
  543. $out .= $this->bbcode_parse_code($stx, $code_block);
  544. $code_block = '';
  545. $open--;
  546. }
  547. else if ($open)
  548. {
  549. // Close one open tag... add to the current code block
  550. $code_block .= substr($in, 0, $pos2 + 7);
  551. $open--;
  552. }
  553. else
  554. {
  555. // end code without opening code... will be always outside code block
  556. $out .= substr($in, 0, $pos2 + 7);
  557. }
  558. $in = substr($in, $pos2 + 7);
  559. }
  560. }
  561. // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
  562. if ($code_block)
  563. {
  564. $code_block = substr($code_block, 0, -7);
  565. $code_block = preg_replace($htm_match, $htm_replace, $code_block);
  566. $out .= $this->bbcode_parse_code($stx, $code_block);
  567. }
  568. return $out;
  569. }
  570. /**
  571. * Parse list bbcode
  572. * Expects the argument to start with a tag
  573. */
  574. function bbcode_parse_list($in)
  575. {
  576. if (!$this->check_bbcode('list', $in))
  577. {
  578. return $in;
  579. }
  580. // $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
  581. $tok = ']';
  582. $out = '[';
  583. // First character is [
  584. $in = substr($in, 1);
  585. $list_end_tags = $item_end_tags = array();
  586. do
  587. {
  588. $pos = strlen($in);
  589. for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
  590. {
  591. $tmp_pos = strpos($in, $tok[$i]);
  592. if ($tmp_pos !== false && $tmp_pos < $pos)
  593. {
  594. $pos = $tmp_pos;
  595. }
  596. }
  597. $buffer = substr($in, 0, $pos);
  598. $tok = $in[$pos];
  599. $in = substr($in, $pos + 1);
  600. if ($tok == ']')
  601. {
  602. // if $tok is ']' the buffer holds a tag
  603. if (strtolower($buffer) == '/list' && count($list_end_tags))
  604. {
  605. // valid [/list] tag, check nesting so that we don't hit false positives
  606. if (count($item_end_tags) && count($item_end_tags) >= count($list_end_tags))
  607. {
  608. // current li tag has not been closed
  609. $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
  610. }
  611. $out .= array_pop($list_end_tags) . ']';
  612. $tok = '[';
  613. }
  614. else if (preg_match('#^list(=[0-9a-z]+)?$#i', $buffer, $m))
  615. {
  616. // sub-list, add a closing tag
  617. if (empty($m[1]) || preg_match('/^=(?:disc|square|circle)$/i', $m[1]))
  618. {
  619. array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
  620. }
  621. else
  622. {
  623. array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
  624. }
  625. $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
  626. $tok = '[';
  627. }
  628. else
  629. {
  630. if (($buffer == '*' || substr($buffer, -2) == '[*') && count($list_end_tags))
  631. {
  632. // the buffer holds a bullet tag and we have a [list] tag open
  633. if (count($item_end_tags) >= count($list_end_tags))
  634. {
  635. if (substr($buffer, -2) == '[*')
  636. {
  637. $out .= substr($buffer, 0, -2) . '[';
  638. }
  639. // current li tag has not been closed
  640. if (preg_match('/\n\[$/', $out, $m))
  641. {
  642. $out = preg_replace('/\n\[$/', '[', $out);
  643. $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
  644. }
  645. else
  646. {
  647. $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
  648. }
  649. }
  650. else
  651. {
  652. $buffer = '*:' . $this->bbcode_uid;
  653. }
  654. $item_end_tags[] = '/*:m:' . $this->bbcode_uid;
  655. }
  656. else if ($buffer == '/*')
  657. {
  658. array_pop($item_end_tags);
  659. $buffer = '/*:' . $this->bbcode_uid;
  660. }
  661. $out .= $buffer . $tok;
  662. $tok = '[]';
  663. }
  664. }
  665. else
  666. {
  667. // Not within a tag, just add buffer to the return string
  668. $out .= $buffer . $tok;
  669. $tok = ($tok == '[') ? ']' : '[]';
  670. }
  671. }
  672. while ($in);
  673. // do we have some tags open? close them now
  674. if (count($item_end_tags))
  675. {
  676. $out .= '[' . implode('][', $item_end_tags) . ']';
  677. }
  678. if (count($list_end_tags))
  679. {
  680. $out .= '[' . implode('][', $list_end_tags) . ']';
  681. }
  682. return $out;
  683. }
  684. /**
  685. * Parse quote bbcode
  686. * Expects the argument to start with a tag
  687. */
  688. function bbcode_quote($in)
  689. {
  690. $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
  691. if (!$in)
  692. {
  693. return '';
  694. }
  695. // To let the parser not catch tokens within quote_username quotes we encode them before we start this...
  696. $in = preg_replace_callback('#quote=&quot;(.*?)&quot;\]#i', function ($match) {
  697. return 'quote=&quot;' . str_replace(array('[', ']', '\\\"'), array('&#91;', '&#93;', '\"'), $match[1]) . '&quot;]';
  698. }, $in);
  699. $tok = ']';
  700. $out = '[';
  701. $in = substr($in, 1);
  702. $close_tags = $error_ary = array();
  703. $buffer = '';
  704. do
  705. {
  706. $pos = strlen($in);
  707. for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
  708. {
  709. $tmp_pos = strpos($in, $tok[$i]);
  710. if ($tmp_pos !== false && $tmp_pos < $pos)
  711. {
  712. $pos = $tmp_pos;
  713. }
  714. }
  715. $buffer .= substr($in, 0, $pos);
  716. $tok = $in[$pos];
  717. $in = substr($in, $pos + 1);
  718. if ($tok == ']')
  719. {
  720. if (strtolower($buffer) == '/quote' && count($close_tags) && substr($out, -1, 1) == '[')
  721. {
  722. // we have found a closing tag
  723. $out .= array_pop($close_tags) . ']';
  724. $tok = '[';
  725. $buffer = '';
  726. /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
  727. * 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.
  728. * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
  729. if (!$in || $in[0] !== ' ')
  730. {
  731. $out .= ' ';
  732. }*/
  733. }
  734. else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
  735. {
  736. $this->parsed_items['quote']++;
  737. array_push($close_tags, '/quote:' . $this->bbcode_uid);
  738. if (isset($m[1]) && $m[1])
  739. {
  740. $username = str_replace(array('&#91;', '&#93;'), array('[', ']'), $m[1]);
  741. $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $username);
  742. $end_tags = array();
  743. $error = false;
  744. preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
  745. foreach ($tags[1] as $tag)
  746. {
  747. if ($tag[0] != '/')
  748. {
  749. $end_tags[] = '/' . $tag;
  750. }
  751. else
  752. {
  753. $end_tag = array_pop($end_tags);
  754. $error = ($end_tag != $tag) ? true : false;
  755. }
  756. }
  757. if ($error)
  758. {
  759. $username = $m[1];
  760. }
  761. $out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
  762. }
  763. else
  764. {
  765. $out .= 'quote:' . $this->bbcode_uid . ']';
  766. }
  767. $tok = '[';
  768. $buffer = '';
  769. }
  770. else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
  771. {
  772. // the buffer holds an invalid opening tag
  773. $buffer .= ']';
  774. }
  775. else
  776. {
  777. $out .= $buffer . $tok;
  778. $tok = '[]';
  779. $buffer = '';
  780. }
  781. }
  782. else
  783. {
  784. /**
  785. * Old quote code working fine, but having errors listed in bug #3572
  786. *
  787. * $out .= $buffer . $tok;
  788. * $tok = ($tok == '[') ? ']' : '[]';
  789. * $buffer = '';
  790. */
  791. $out .= $buffer . $tok;
  792. if ($tok == '[')
  793. {
  794. // Search the text for the next tok... if an ending quote comes first, then change tok to []
  795. $pos1 = stripos($in, '[/quote');
  796. // If the token ] comes first, we change it to ]
  797. $pos2 = strpos($in, ']');
  798. // If the token [ comes first, we change it to [
  799. $pos3 = strpos($in, '[');
  800. if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
  801. {
  802. $tok = '[]';
  803. }
  804. else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
  805. {
  806. $tok = '[';
  807. }
  808. else
  809. {
  810. $tok = ']';
  811. }
  812. }
  813. else
  814. {
  815. $tok = '[]';
  816. }
  817. $buffer = '';
  818. }
  819. }
  820. while ($in);
  821. $out .= $buffer;
  822. if (count($close_tags))
  823. {
  824. $out .= '[' . implode('][', $close_tags) . ']';
  825. }
  826. foreach ($error_ary as $error_msg)
  827. {
  828. $this->warn_msg[] = $error_msg;
  829. }
  830. return $out;
  831. }
  832. /**
  833. * Validate email
  834. */
  835. function validate_email($var1, $var2)
  836. {
  837. $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
  838. $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
  839. $txt = $var2;
  840. $email = ($var1) ? $var1 : $var2;
  841. $validated = true;
  842. if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
  843. {
  844. $validated = false;
  845. }
  846. if (!$validated)
  847. {
  848. return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
  849. }
  850. $this->parsed_items['email']++;
  851. if ($var1)
  852. {
  853. $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
  854. }
  855. else
  856. {
  857. $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
  858. }
  859. return $retval;
  860. }
  861. /**
  862. * Validate url
  863. *
  864. * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
  865. * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
  866. */
  867. function validate_url($var1, $var2)
  868. {
  869. $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
  870. $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
  871. $url = ($var1) ? $var1 : $var2;
  872. if ($var1 && !$var2)
  873. {
  874. $var2 = $var1;
  875. }
  876. if (!$url)
  877. {
  878. return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
  879. }
  880. $valid = false;
  881. $url = str_replace(' ', '%20', $url);
  882. // Checking urls
  883. if (preg_match('#^' . get_preg_expression('url') . '$#iu', $url) ||
  884. preg_match('#^' . get_preg_expression('www_url') . '$#iu', $url) ||
  885. preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#iu', $url))
  886. {
  887. $valid = true;
  888. }
  889. if ($valid)
  890. {
  891. $this->parsed_items['url']++;
  892. // if there is no scheme, then add http schema
  893. if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
  894. {
  895. $url = 'http://' . $url;
  896. }
  897. // Is this a link to somewhere inside this board? If so then remove the session id from the url
  898. if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
  899. {
  900. $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}&amp;/', '\1', $url);
  901. $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}$/', '', $url);
  902. $url = append_sid($url);
  903. }
  904. 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 . ']';
  905. }
  906. return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
  907. }
  908. /**
  909. * Check if url is pointing to this domain/script_path/php-file
  910. *
  911. * @param string $url the url to check
  912. * @return true if the url is pointing to this domain/script_path/php-file, false if not
  913. *
  914. * @access private
  915. */
  916. function path_in_domain($url)
  917. {
  918. global $config, $phpEx, $user;
  919. if ($config['force_server_vars'])
  920. {
  921. $check_path = !empty($config['script_path']) ? $config['script_path'] : '/';
  922. }
  923. else
  924. {
  925. $check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
  926. }
  927. // Is the user trying to link to a php file in this domain and script path?
  928. if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
  929. {
  930. $server_name = $user->host;
  931. // Forcing server vars is the only way to specify/override the protocol
  932. if ($config['force_server_vars'] || !$server_name)
  933. {
  934. $server_name = $config['server_name'];
  935. }
  936. // Check again in correct order...
  937. $pos_ext = strpos($url, ".{$phpEx}");
  938. $pos_path = strpos($url, $check_path);
  939. $pos_domain = strpos($url, $server_name);
  940. if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
  941. {
  942. // Ok, actually we allow linking to some files (this may be able to be extended in some way later...)
  943. if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0)
  944. {
  945. return false;
  946. }
  947. return true;
  948. }
  949. }
  950. return false;
  951. }
  952. }
  953. /**
  954. * Main message parser for posting, pm, etc. takes raw message
  955. * and parses it for attachments, bbcode and smilies
  956. */
  957. class parse_message extends bbcode_firstpass
  958. {
  959. var $attachment_data = array();
  960. var $filename_data = array();
  961. // Helps ironing out user error
  962. var $message_status = '';
  963. var $allow_img_bbcode = true;
  964. var $allow_flash_bbcode = true;
  965. var $allow_quote_bbcode = true;
  966. var $allow_url_bbcode = true;
  967. /**
  968. * The plupload object used for dealing with attachments
  969. * @var \phpbb\plupload\plupload
  970. */
  971. protected $plupload;
  972. /**
  973. * Init - give message here or manually
  974. */
  975. function __construct($message = '')
  976. {
  977. // Init BBCode UID
  978. $this->bbcode_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN);
  979. $this->message = $message;
  980. }
  981. /**
  982. * Parse Message
  983. */
  984. function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post')
  985. {
  986. global $config, $user, $phpbb_dispatcher, $phpbb_container;
  987. $this->mode = $mode;
  988. foreach (array('chars', 'smilies', 'urls', 'font_size', 'img_height', 'img_width') as $key)
  989. {
  990. if (!isset($config['max_' . $mode . '_' . $key]))
  991. {
  992. $config['max_' . $mode . '_' . $key] = 0;
  993. }
  994. }
  995. $this->allow_img_bbcode = $allow_img_bbcode;
  996. $this->allow_flash_bbcode = $allow_flash_bbcode;
  997. $this->allow_quote_bbcode = $allow_quote_bbcode;
  998. $this->allow_url_bbcode = $allow_url_bbcode;
  999. // If false, then $this->message won't be altered, the text will be returned instead.
  1000. if (!$update_this_message)
  1001. {
  1002. $tmp_message = $this->message;
  1003. $return_message = &$this->message;
  1004. }
  1005. if ($this->message_status == 'display')
  1006. {
  1007. $this->decode_message();
  1008. }
  1009. // Store message length...
  1010. $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message));
  1011. // Maximum message length check. 0 disables this check completely.
  1012. if ((int) $config['max_' . $mode . '_chars'] > 0 && $message_length > (int) $config['max_' . $mode . '_chars'])
  1013. {
  1014. $this->warn_msg[] = $user->lang('CHARS_' . strtoupper($mode) . '_CONTAINS', $message_length) . '<br />' . $user->lang('TOO_MANY_CHARS_LIMIT', (int) $config['max_' . $mode . '_chars']);
  1015. return (!$update_this_message) ? $return_message : $this->warn_msg;
  1016. }
  1017. // Minimum message length check for post only
  1018. if ($mode === 'post')
  1019. {
  1020. if (!$message_length || $message_length < (int) $config['min_post_chars'])
  1021. {
  1022. $this->warn_msg[] = (!$message_length) ? $user->lang['TOO_FEW_CHARS'] : ($user->lang('CHARS_POST_CONTAINS', $message_length) . '<br />' . $user->lang('TOO_FEW_CHARS_LIMIT', (int) $config['min_post_chars']));
  1023. return (!$update_this_message) ? $return_message : $this->warn_msg;
  1024. }
  1025. }
  1026. /**
  1027. * This event can be used for additional message checks/cleanup before parsing
  1028. *
  1029. * @event core.message_parser_check_message
  1030. * @var bool allow_bbcode Do we allow BBCodes
  1031. * @var bool allow_magic_url Do we allow magic urls
  1032. * @var bool allow_smilies Do we allow smilies
  1033. * @var bool allow_img_bbcode Do we allow image BBCode
  1034. * @var bool allow_flash_bbcode Do we allow flash BBCode
  1035. * @var bool allow_quote_bbcode Do we allow quote BBCode
  1036. * @var bool allow_url_bbcode Do we allow url BBCode
  1037. * @var bool update_this_message Do we alter the parsed message
  1038. * @var string mode Posting mode
  1039. * @var string message The message text to parse
  1040. * @var string bbcode_bitfield The bbcode_bitfield before parsing
  1041. * @var string bbcode_uid The bbcode_uid before parsing
  1042. * @var bool return Do we return after the event is triggered if $warn_msg is not empty
  1043. * @var array warn_msg Array of the warning messages
  1044. * @since 3.1.2-RC1
  1045. * @changed 3.1.3-RC1 Added vars $bbcode_bitfield and $bbcode_uid
  1046. */
  1047. $message = $this->message;
  1048. $warn_msg = $this->warn_msg;
  1049. $return = false;
  1050. $bbcode_bitfield = $this->bbcode_bitfield;
  1051. $bbcode_uid = $this->bbcode_uid;
  1052. $vars = array(
  1053. 'allow_bbcode',
  1054. 'allow_magic_url',
  1055. 'allow_smilies',
  1056. 'allow_img_bbcode',
  1057. 'allow_flash_bbcode',
  1058. 'allow_quote_bbcode',
  1059. 'allow_url_bbcode',
  1060. 'update_this_message',
  1061. 'mode',
  1062. 'message',
  1063. 'bbcode_bitfield',
  1064. 'bbcode_uid',
  1065. 'return',
  1066. 'warn_msg',
  1067. );
  1068. extract($phpbb_dispatcher->trigger_event('core.message_parser_check_message', compact($vars)));
  1069. $this->message = $message;
  1070. $this->warn_msg = $warn_msg;
  1071. $this->bbcode_bitfield = $bbcode_bitfield;
  1072. $this->bbcode_uid = $bbcode_uid;
  1073. if ($return && !empty($this->warn_msg))
  1074. {
  1075. return (!$update_this_message) ? $return_message : $this->warn_msg;
  1076. }
  1077. // Get the parser
  1078. $parser = $phpbb_container->get('text_formatter.parser');
  1079. // Set the parser's options
  1080. ($allow_bbcode) ? $parser->enable_bbcodes() : $parser->disable_bbcodes();
  1081. ($allow_magic_url) ? $parser->enable_magic_url() : $parser->disable_magic_url();
  1082. ($allow_smilies) ? $parser->enable_smilies() : $parser->disable_smilies();
  1083. ($allow_img_bbcode) ? $parser->enable_bbcode('img') : $parser->disable_bbcode('img');
  1084. ($allow_flash_bbcode) ? $parser->enable_bbcode('flash') : $parser->disable_bbcode('flash');
  1085. ($allow_quote_bbcode) ? $parser->enable_bbcode('quote') : $parser->disable_bbcode('quote');
  1086. ($allow_url_bbcode) ? $parser->enable_bbcode('url') : $parser->disable_bbcode('url');
  1087. // Set some config values
  1088. $parser->set_vars(array(
  1089. 'max_font_size' => $config['max_' . $this->mode . '_font_size'],
  1090. 'max_img_height' => $config['max_' . $this->mode . '_img_height'],
  1091. 'max_img_width' => $config['max_' . $this->mode . '_img_width'],
  1092. 'max_smilies' => $config['max_' . $this->mode . '_smilies'],
  1093. 'max_urls' => $config['max_' . $this->mode . '_urls']
  1094. ));
  1095. // Parse this message
  1096. $this->message = $parser->parse(htmlspecialchars_decode($this->message, ENT_QUOTES));
  1097. // Remove quotes that are nested too deep
  1098. if ($config['max_quote_depth'] > 0)
  1099. {
  1100. $this->remove_nested_quotes($config['max_quote_depth']);
  1101. }
  1102. // Check for "empty" message. We do not check here for maximum length, because bbcode, smilies, etc. can add to the length.
  1103. // The maximum length check happened before any parsings.
  1104. if ($mode === 'post' && utf8_clean_string($this->message) === '')
  1105. {
  1106. $this->warn_msg[] = $user->lang['TOO_FEW_CHARS'];
  1107. return (!$update_this_message) ? $return_message : $this->warn_msg;
  1108. }
  1109. // Remove quotes that are nested too deep
  1110. if ($config['max_quote_depth'] > 0)
  1111. {
  1112. $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode(
  1113. $this->message,
  1114. 'quote',
  1115. $config['max_quote_depth']
  1116. );
  1117. }
  1118. // Check for errors
  1119. $errors = $parser->get_errors();
  1120. if ($errors)
  1121. {
  1122. foreach ($errors as $i => $args)
  1123. {
  1124. // Translate each error with $user->lang()
  1125. $errors[$i] = call_user_func_array(array($user, 'lang'), $args);
  1126. }
  1127. $this->warn_msg = array_merge($this->warn_msg, $errors);
  1128. return (!$update_this_message) ? $return_message : $this->warn_msg;
  1129. }
  1130. if (!$update_this_message)
  1131. {
  1132. unset($this->message);
  1133. $this->message = $tmp_message;
  1134. return $return_message;
  1135. }
  1136. $this->message_status = 'parsed';
  1137. return false;
  1138. }
  1139. /**
  1140. * Formatting text for display
  1141. */
  1142. function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
  1143. {
  1144. global $phpbb_container, $phpbb_dispatcher;
  1145. // If false, then the parsed message get returned but internal message not processed.
  1146. if (!$update_this_message)
  1147. {
  1148. $tmp_message = $this->message;
  1149. $return_message = &$this->message;
  1150. }
  1151. $text = $this->message;
  1152. $uid = $this->bbcode_uid;
  1153. /**
  1154. * Event to modify the text before it is parsed
  1155. *
  1156. * @event core.modify_format_display_text_before
  1157. * @var string text The message text to parse
  1158. * @var string uid The bbcode uid
  1159. * @var bool allow_bbcode Do we allow bbcodes
  1160. * @var bool allow_magic_url Do we allow magic urls
  1161. * @var bool allow_smilies Do we allow smilies
  1162. * @var bool update_this_message Do we update the internal message
  1163. * with the parsed result
  1164. * @since 3.1.6-RC1
  1165. */
  1166. $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
  1167. extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_before', compact($vars)));
  1168. $this->message = $text;
  1169. $this->bbcode_uid = $uid;
  1170. unset($text, $uid);
  1171. // NOTE: message_status is unreliable for detecting unparsed text because some callers
  1172. // change $this->message without resetting $this->message_status to 'plain' so we
  1173. // inspect the message instead
  1174. //if ($this->message_status == 'plain')
  1175. if (!preg_match('/^<[rt][ >]/', $this->message))
  1176. {
  1177. // Force updating message - of course.
  1178. $this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_flash_bbcode, $this->allow_quote_bbcode, $this->allow_url_bbcode, true);
  1179. }
  1180. // There's a bug when previewing a topic with no poll, because the empty title of the poll
  1181. // gets parsed but $this->message still ends up empty. This fixes it, until a proper fix is
  1182. // devised
  1183. if ($this->message === '')
  1184. {
  1185. $this->message = $phpbb_container->get('text_formatter.parser')->parse($this->message);
  1186. }
  1187. $this->message = $phpbb_container->get('text_formatter.renderer')->render($this->message);
  1188. $text = $this->message;
  1189. $uid = $this->bbcode_uid;
  1190. /**
  1191. * Event to modify the text after it is parsed
  1192. *
  1193. * @event core.modify_format_display_text_after
  1194. * @var string text The message text to parse
  1195. * @var string uid The bbcode uid
  1196. * @var bool allow_bbcode Do we allow bbcodes
  1197. * @var bool allow_magic_url Do we allow magic urls
  1198. * @var bool allow_smilies Do we allow smilies
  1199. * @var bool update_this_message Do we update the internal message
  1200. * with the parsed result
  1201. * @since 3.1.0-a3
  1202. */
  1203. $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
  1204. extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_after', compact($vars)));
  1205. $this->message = $text;
  1206. $this->bbcode_uid = $uid;
  1207. if (!$update_this_message)
  1208. {
  1209. unset($this->message);
  1210. $this->message = $tmp_message;
  1211. return $return_message;
  1212. }
  1213. $this->message_status = 'display';
  1214. return false;
  1215. }
  1216. /**
  1217. * Decode message to be placed back into form box
  1218. */
  1219. function decode_message($custom_bbcode_uid = '', $update_this_message = true)
  1220. {
  1221. // If false, then the parsed message get returned but internal message not processed.
  1222. if (!$update_this_message)
  1223. {
  1224. $tmp_message = $this->message;
  1225. $return_message = &$this->message;
  1226. }
  1227. ($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);
  1228. if (!$update_this_message)
  1229. {
  1230. unset($this->message);
  1231. $this->message = $tmp_message;
  1232. return $return_message;
  1233. }
  1234. $this->message_status = 'plain';
  1235. return false;
  1236. }
  1237. /**
  1238. * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
  1239. * Cuts down displayed size of link if over 50 chars, turns absolute links
  1240. * into relative versions when the server/script path matches the link
  1241. */
  1242. function magic_url($server_url)
  1243. {
  1244. // We use the global make_clickable function
  1245. $this->message = make_clickable($this->message, $server_url);
  1246. }
  1247. /**
  1248. * Parse Smilies
  1249. */
  1250. function smilies($max_smilies = 0)
  1251. {
  1252. global $db, $user;
  1253. static $match;
  1254. static $replace;
  1255. // See if the static arrays have already been filled on an earlier invocation
  1256. if (!is_array($match))
  1257. {
  1258. $match = $replace = array();
  1259. // NOTE: obtain_* function? chaching the table contents?
  1260. // For now setting the ttl to 10 minutes
  1261. switch ($db->get_sql_layer())
  1262. {
  1263. case 'mssql_odbc':
  1264. case 'mssqlnative':
  1265. $sql = 'SELECT *
  1266. FROM ' . SMILIES_TABLE . '
  1267. ORDER BY LEN(code) DESC';
  1268. break;
  1269. // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
  1270. default:
  1271. $sql = 'SELECT *
  1272. FROM ' . SMILIES_TABLE . '
  1273. ORDER BY LENGTH(code) DESC';
  1274. break;
  1275. }
  1276. $result = $db->sql_query($sql, 600);
  1277. while ($row = $db->sql_fetchrow($result))
  1278. {
  1279. if (empty($row['code']))
  1280. {
  1281. continue;
  1282. }
  1283. // (assertion)
  1284. $match[] = preg_quote($row['code'], '#');
  1285. $replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
  1286. }
  1287. $db->sql_freeresult($result);
  1288. }
  1289. if (count($match))
  1290. {
  1291. if ($max_smilies)
  1292. {
  1293. // 'u' modifier has been added to correctly parse smilies within unicode strings
  1294. // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
  1295. $num_matches = preg_match_all('#(?<=^|[\n .])(?:' . implode('|', $match) . ')(?![^<>]*>)#u', $this->message, $matches);
  1296. unset($matches);
  1297. if ($num_matches !== false && $num_matches > $max_smilies)
  1298. {
  1299. $this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
  1300. return;
  1301. }
  1302. }
  1303. // Make sure the delimiter # is added in front and at the end of every element within $match
  1304. // 'u' modifier has been added to correctly parse smilies within unicode strings
  1305. // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
  1306. $this->message = trim(preg_replace(explode(chr(0), '#(?<=^|[\n .])' . implode('(?![^<>]*>)#u' . chr(0) . '#(?<=^|[\n .])', $match) . '(?![^<>]*>)#u'), $replace, $this->message));
  1307. }
  1308. }
  1309. /**
  1310. * Check attachment form token depending on submit type
  1311. *
  1312. * @param \phpbb\language\language $language Language
  1313. * @param \phpbb\request\request_interface $request Request
  1314. * @param string $form_name Form name for checking form key
  1315. *
  1316. * @return bool True if form token is not needed or valid, false if needed and invalid
  1317. */
  1318. function check_attachment_form_token(\phpbb\language\language $language, \phpbb\request\request_interface $request, $form_name)
  1319. {
  1320. $add_file = $request->is_set_post('add_file');
  1321. $delete_file = $request->is_set_post('delete_file');
  1322. if (($add_file || $delete_file) && !check_form_key($form_name))
  1323. {
  1324. $this->warn_msg[] = $language->lang('FORM_INVALID');
  1325. if ($request->is_ajax() && $this->plupload)
  1326. {
  1327. $this->plupload->emit_error(-400, 'FORM_INVALID');
  1328. }
  1329. return false;
  1330. }
  1331. return true;
  1332. }
  1333. /**
  1334. * Parse Attachments
  1335. */
  1336. function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
  1337. {
  1338. global $config, $auth, $user, $phpbb_root_path, $phpEx, $db, $request;
  1339. global $phpbb_container, $phpbb_dispatcher;
  1340. $error = array();
  1341. $num_attachments = count($this->attachment_data);
  1342. $this->filename_data['filecomment'] = $request->variable('filecomment', '', true);
  1343. $upload = $request->file($form_name);
  1344. $upload_file = (!empty($upload) && $upload['name'] !== 'none' && trim($upload['name']));
  1345. $add_file = (isset($_POST['add_file'])) ? true : false;
  1346. $delete_file = (isset($_POST['delete_file'])) ? true : false;
  1347. // First of all adjust comments if changed
  1348. $actual_comment_list = $request->variable('comment_list', array(''), true);
  1349. foreach ($actual_comment_list as $comment_key => $comment)
  1350. {
  1351. if (!isset($this->attachment_data[$comment_key]))
  1352. {
  1353. continue;
  1354. }
  1355. if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
  1356. {
  1357. $this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
  1358. }
  1359. }
  1360. $cfg = array();
  1361. $cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
  1362. $forum_id = ($is_message) ? 0 : $forum_id;
  1363. if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
  1364. {
  1365. if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
  1366. {
  1367. /** @var \phpbb\attachment\manager $attachment_manager */
  1368. $attachment_manager = $phpbb_container->get('attachment.manager');
  1369. $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message);
  1370. $error = $filedata['error'];
  1371. if ($filedata['post_attach'] && !count($error))
  1372. {
  1373. $sql_ary = array(
  1374. 'physical_filename' => $filedata['physical_filename'],
  1375. 'attach_comment' => $this->filename_data['filecomment'],
  1376. 'real_filename' => $filedata['real_filename'],
  1377. 'extension' => $filedata['extension'],
  1378. 'mimetype' => $filedata['mimetype'],
  1379. 'filesize' => $filedata['filesize'],
  1380. 'filetime' => $filedata['filetime'],
  1381. 'thumbnail' => $filedata['thumbnail'],
  1382. 'is_orphan' => 1,
  1383. 'in_message' => ($is_message) ? 1 : 0,
  1384. 'poster_id' => $user->data['user_id'],
  1385. );
  1386. /**
  1387. * Modify attachment sql array on submit
  1388. *
  1389. * @event core.modify_attachment_sql_ary_on_submit
  1390. * @var array sql_ary Array containing SQL data
  1391. * @since 3.2.6-RC1
  1392. */
  1393. $vars = array('sql_ary');
  1394. extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_submit', compact($vars)));
  1395. $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  1396. $new_entry = array(
  1397. 'attach_id' => $db->sql_nextid(),
  1398. 'is_orphan' => 1,
  1399. 'real_filename' => $filedata['real_filename'],
  1400. 'attach_comment'=> $this->filename_data['filecomment'],
  1401. 'filesize' => $filedata['filesize'],
  1402. );
  1403. $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
  1404. /**
  1405. * Modify attachment data on submit
  1406. *
  1407. * @event core.modify_attachment_data_on_submit
  1408. * @var array attachment_data Array containing attachment data
  1409. * @since 3.2.2-RC1
  1410. */
  1411. $attachment_data = $this->attachment_data;
  1412. $vars = array('attachment_data');
  1413. extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_submit', compact($vars)));
  1414. $this->attachment_data = $attachment_data;
  1415. unset($attachment_data);
  1416. $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) {
  1417. return '[attachment='.($match[1] + 1).']' . $match[2] . '[/attachment]';
  1418. }, $this->message);
  1419. $this->filename_data['filecomment'] = '';
  1420. // This Variable is set to false here, because Attachments are entered into the
  1421. // Database in two modes, one if the id_list is 0 and the second one if post_attach is true
  1422. // Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
  1423. // but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
  1424. //
  1425. // This is very relevant, because it could happen that the post got not submitted, but we do not
  1426. // know this circumstance here. We could be at the posting page or we could be redirected to the entered
  1427. // post. :)
  1428. $filedata['post_attach'] = false;
  1429. }
  1430. }
  1431. else
  1432. {
  1433. $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
  1434. }
  1435. }
  1436. if ($preview || $refresh || count($error))
  1437. {
  1438. if (isset($this->plupload) && $this->plupload->is_active())
  1439. {
  1440. $json_response = new \phpbb\json_response();
  1441. }
  1442. // Perform actions on temporary attachments
  1443. if ($delete_file)
  1444. {
  1445. include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
  1446. $index = array_keys($request->variable('delete_file', array(0 => 0)));
  1447. $index = (!empty($index)) ? $index[0] : false;
  1448. if ($index !== false && !empty

Large files files are truncated, but you can click here to view the full file