- tackle some usability issues
[phpbb.git] / phpBB / includes / message_parser.php
blobb5090d3950e4e0a6bad71f7247411da0e2039592
1 <?php
2 /**
4 * @package phpBB3
5 * @version $Id$
6 * @copyright (c) 2005 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
9 */
11 /**
12 * @ignore
14 if (!defined('IN_PHPBB'))
16 exit;
19 if (!class_exists('bbcode'))
21 include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
24 /**
25 * BBCODE FIRSTPASS
26 * BBCODE first pass class (functions for parsing messages for db storage)
27 * @package phpBB3
29 class bbcode_firstpass extends bbcode
31 var $message = '';
32 var $warn_msg = array();
33 var $parsed_items = array();
35 /**
36 * Parse BBCode
38 function parse_bbcode()
40 if (!$this->bbcodes)
42 $this->bbcode_init();
45 global $user;
46 $this->bbcode_bitfield = 0;
48 $size = strlen($this->message);
49 foreach ($this->bbcodes as $bbcode_name => $bbcode_data)
51 if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
53 foreach ($bbcode_data['regexp'] as $regexp => $replacement)
55 if (preg_match($regexp, $this->message))
57 $this->warn_msg[] = $user->lang['UNAUTHORISED_BBCODE'] . '[' . $bbcode_name . ']';
58 continue;
62 else
64 foreach ($bbcode_data['regexp'] as $regexp => $replacement)
66 $this->message = preg_replace($regexp, $replacement, $this->message);
70 // Because we add bbcode_uid to all tags, the message length
71 // will increase whenever a tag is found
72 $new_size = strlen($this->message);
73 if ($size != $new_size)
75 $this->bbcode_bitfield |= (1 << $bbcode_data['bbcode_id']);
76 $size = $new_size;
81 /**
82 * Prepare some bbcodes for better parsing
84 function prepare_bbcodes()
86 // Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
87 if (strpos($this->message, '[quote') !== false)
89 $in = str_replace("\r\n", "\n", $this->message);
91 $this->message = preg_replace(array('#\[quote(=&quot;.*?&quot;)?\]([^\n])#is', '#([^\n])\[\/quote\]#is'), array("[quote\\1]\n\\2", "\\1\n[/quote]"), $this->message);
92 $this->message = preg_replace(array('#\[quote(=&quot;.*?&quot;)?\]([^\n])#is', '#([^\n])\[\/quote\]#is'), array("[quote\\1]\n\\2", "\\1\n[/quote]"), $this->message);
95 // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
98 /**
99 * Init bbcode data for later parsing
101 function bbcode_init()
103 static $rowset;
105 // This array holds all bbcode data. BBCodes will be processed in this
106 // order, so it is important to keep [code] in first position and
107 // [quote] in second position.
108 $this->bbcodes = array(
109 'code' => array('bbcode_id' => 8, 'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#ise' => "\$this->bbcode_code('\$1', '\$2')")),
110 'quote' => array('bbcode_id' => 0, 'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#ise' => "\$this->bbcode_quote('\$0')")),
111 'attachment' => array('bbcode_id' => 12, 'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#ise' => "\$this->bbcode_attachment('\$1', '\$2')")),
112 'b' => array('bbcode_id' => 1, 'regexp' => array('#\[b\](.*?)\[/b\]#ise' => "\$this->bbcode_strong('\$1')")),
113 'i' => array('bbcode_id' => 2, 'regexp' => array('#\[i\](.*?)\[/i\]#ise' => "\$this->bbcode_italic('\$1')")),
114 'url' => array('bbcode_id' => 3, 'regexp' => array('#\[url(=(.*))?\](.*)\[/url\]#iUe' => "\$this->validate_url('\$2', '\$3')")),
115 'img' => array('bbcode_id' => 4, 'regexp' => array('#\[img\](https?://)([a-z0-9\-\.,\?!%\*_:;~\\&$@/=\+]+)\[/img\]#ie' => "\$this->bbcode_img('\$1\$2')")),
116 'size' => array('bbcode_id' => 5, 'regexp' => array('#\[size=([\-\+]?[1-2]?[0-9])\](.*?)\[/size\]#ise' => "\$this->bbcode_size('\$1', '\$2')")),
117 'color' => array('bbcode_id' => 6, 'regexp' => array('!\[color=(#[0-9A-Fa-f]{6}|[a-z\-]+)\](.*?)\[/color\]!ise' => "\$this->bbcode_color('\$1', '\$2')")),
118 'u' => array('bbcode_id' => 7, 'regexp' => array('#\[u\](.*?)\[/u\]#ise' => "\$this->bbcode_underline('\$1')")),
119 'list' => array('bbcode_id' => 9, 'regexp' => array('#\[list(=[a-z|0-9|(?:disc|circle|square))]+)?\].*\[/list\]#ise' => "\$this->bbcode_parse_list('\$0')")),
120 'email' => array('bbcode_id' => 10, 'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#ise' => "\$this->validate_email('\$1', '\$2')")),
121 'flash' => array('bbcode_id' => 11, 'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ie' => "\$this->bbcode_flash('\$1', '\$2', '\$3')"))
124 // Zero the parsed items array
125 $this->parsed_items = array();
127 foreach ($this->bbcodes as $tag => $bbcode_data)
129 $this->parsed_items[$tag] = 0;
132 if (!is_array($rowset))
134 global $db;
135 $rowset = array();
137 $sql = 'SELECT *
138 FROM ' . BBCODES_TABLE;
139 $result = $db->sql_query($sql);
141 while ($row = $db->sql_fetchrow($result))
143 $rowset[] = $row;
145 $db->sql_freeresult($result);
148 foreach ($rowset as $row)
150 $this->bbcodes[$row['bbcode_tag']] = array(
151 'bbcode_id' => (int) $row['bbcode_id'],
152 'regexp' => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
158 * Making some pre-checks for bbcodes as well as increasing the number of parsed items
160 function check_bbcode($bbcode, &$in)
162 // when using the /e modifier, preg_replace slashes double-quotes but does not
163 // seem to slash anything else
164 $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
166 // Trimming here to make sure no empty bbcodes are parsed accidently
167 if (trim($in) == '')
169 return false;
172 $this->parsed_items[$bbcode]++;
174 return true;
178 * Transform some characters in valid bbcodes
180 function bbcode_specialchars($text)
182 $str_from = array('<', '>', '[', ']', '.', ':');
183 $str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');
185 return str_replace($str_from, $str_to, $text);
189 * Parse size tag
191 function bbcode_size($stx, $in)
193 global $user, $config;
195 if (!$this->check_bbcode('size', $in))
197 return '';
200 if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
202 $this->warn_msg[] = sprintf($user->lang['MAX_FONT_SIZE_EXCEEDED'], $config['max_' . $this->mode . '_font_size']);
205 return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
209 * Parse color tag
211 function bbcode_color($stx, $in)
213 if (!$this->check_bbcode('color', $in))
215 return '';
218 return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
222 * Parse u tag
224 function bbcode_underline($in)
226 if (!$this->check_bbcode('u', $in))
228 return '';
231 return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
235 * Parse b tag
237 function bbcode_strong($in)
239 if (!$this->check_bbcode('b', $in))
241 return '';
244 return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
248 * Parse i tag
250 function bbcode_italic($in)
252 if (!$this->check_bbcode('i', $in))
254 return '';
257 return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
261 * Parse img tag
263 function bbcode_img($in)
265 global $user, $config, $phpEx;
267 if (!$this->check_bbcode('img', $in))
269 return '';
272 $in = trim($in);
274 if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
276 $stats = @getimagesize($in);
278 if ($stats === false)
280 $this->warn_msg[] = $user->lang['UNABLE_GET_IMAGE_SIZE'];
282 else
284 if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $stats[1])
286 $this->warn_msg[] = sprintf($user->lang['MAX_IMG_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']);
289 if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $stats[0])
291 $this->warn_msg[] = sprintf($user->lang['MAX_IMG_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']);
296 if ($this->path_in_domain($in))
298 return '[img]' . $in . '[/img]';
301 return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
305 * Parse flash tag
307 function bbcode_flash($width, $height, $in)
309 global $user, $config, $phpEx;
311 if (!$this->check_bbcode('flash', $in))
313 return '';
316 $in = trim($in);
318 // Apply the same size checks on flash files as on images
319 if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
321 if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)
323 $this->warn_msg[] = sprintf($user->lang['MAX_FLASH_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']);
326 if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)
328 $this->warn_msg[] = sprintf($user->lang['MAX_FLASH_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']);
332 if ($this->path_in_domain($in))
334 return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
337 return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
341 * Parse inline attachments [ia]
343 function bbcode_attachment($stx, $in)
345 if (!$this->check_bbcode('attachment', $in))
347 return '';
350 return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
354 * Parse code tag
355 * Expects the argument to start right after the opening [code] tag and to end with [/code]
357 function bbcode_code($stx, $in)
359 if (!$this->check_bbcode('code', $in))
361 return '';
364 // We remove the hardcoded elements from the code block here because it is not used in code blocks
365 // Having it here saves us one preg_replace per message containing [code] blocks
366 // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
367 $htm_match = array(
368 '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
369 '#<!\-\- m \-\-><a href="(.*?)" target="_blank">.*?</a><!\-\- m \-\->#',
370 '#<!\-\- w \-\-><a href="http:\/\/(.*?)" target="_blank">.*?</a><!\-\- w \-\->#',
371 '#<!\-\- l \-\-><a href="(.*?)">.*?</a><!\-\- l \-\->#',
372 '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
373 '#&\#([0-9]+);#',
375 $htm_replace = array('\1', '\1', '\1', '\1', '\1', '&amp;#\1;');
377 $out = '';
381 $pos = stripos($in, '[/code]') + 7;
382 $code = substr($in, 0, $pos);
383 $in = substr($in, $pos);
385 // $code contains everything that was between code tags (including the ending tag) but we're trying to grab as much extra text as possible, as long as it does not contain open [code] tags
386 while ($in)
388 $pos = stripos($in, '[/code]') + 7;
389 $buffer = substr($in, 0, $pos);
391 if (preg_match('#\[code(?:=([a-z]+))?\]#i', $buffer))
393 break;
395 else
397 $in = substr($in, $pos);
398 $code .= $buffer;
402 $code = substr($code, 0, -7);
403 // $code = preg_replace('#^[\r\n]*(.*?)[\n\r\s\t]*$#s', '$1', $code);
404 $code = preg_replace($htm_match, $htm_replace, $code);
406 switch (strtolower($stx))
408 case 'php':
409 $code = trim($code);
411 $remove_tags = false;
412 $code = str_replace(array('&lt;', '&gt;'), array('<', '>'), $code);
414 if (!preg_match('/^\<\?.*?\?\>/is', $code))
416 $remove_tags = true;
417 $code = "<?php $code ?>";
420 $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
421 foreach ($conf as $ini_var)
423 ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
426 // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
427 $code = html_entity_decode($code);
428 $code = highlight_string($code, true);
430 $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
431 $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');
433 if ($remove_tags)
435 $str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
436 $str_to[] = '';
437 $str_from[] = '<span class="syntaxdefault">&lt;?php ';
438 $str_to[] = '<span class="syntaxdefault">';
441 $code = str_replace($str_from, $str_to, $code);
442 $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
444 if ($remove_tags)
446 $code = preg_replace('#(<span class="[a-z]+">)?\?&gt;</span>#', '', $code);
449 $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
450 $code = preg_replace('#(?:[\n\r\s\t]|&nbsp;)*</span>$#', '</span>', $code);
452 $out .= "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
453 break;
455 default:
456 $out .= '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
457 break;
460 if (preg_match('#(.*?)\[code(?:=([a-z]+))?\](.+)#is', $in, $m))
462 $out .= $m[1];
463 $stx = $m[2];
464 $in = $m[3];
467 while ($in);
469 return $out;
473 * Parse list bbcode
474 * Expects the argument to start with a tag
476 function bbcode_parse_list($in)
478 if (!$this->check_bbcode('list', $in))
480 return '';
483 $out = '[';
485 // Grab item_start with no item_end
486 $in = preg_replace('#\[\*\](.*?)(\[\/list\]|\[list(=?(?:[0-9]|[a-z]|))\]|\[\*\])#is', '[*:' . $this->bbcode_uid . ']\1[/*:m:' . $this->bbcode_uid . ']\2', $in);
488 // Grab them again as backreference
489 $in = preg_replace('#\[\*\](.*?)(\[\/list\]|\[list(=?(?:[0-9]|[a-z]|))\]|\[\*\])(^\[\/*\])#is', '[*:' . $this->bbcode_uid . ']\1[/*:m:' . $this->bbcode_uid . ']\2', $in);
491 // Grab end tag following start tag
492 $in = preg_replace('#\[\/\*:m:' . $this->bbcode_uid . '\](\n|)\[\*\]#is', '[/*:m:' . $this->bbcode_uid . '][*:' . $this->bbcode_uid . ']', $in);
494 // Replace end tag
495 $in = preg_replace('#\[\/\*\]#i', '[/*:' . $this->bbcode_uid . ']', $in);
497 // $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
498 $tok = ']';
499 $out = '[';
501 $in = substr($in, 1);
502 $list_end_tags = array();
506 $pos = strlen($in);
507 for ($i = 0; $i < strlen($tok); ++$i)
509 $tmp_pos = strpos($in, $tok{$i});
511 if ($tmp_pos !== false && $tmp_pos < $pos)
513 $pos = $tmp_pos;
517 $buffer = substr($in, 0, $pos);
518 $tok = $in{$pos};
520 $in = substr($in, $pos + 1);
522 if ($tok == ']')
524 // if $tok is ']' the buffer holds a tag
525 if ($buffer == '/list' && sizeof($list_end_tags))
527 $out .= array_pop($list_end_tags) . ']';
528 $tok = '[';
530 else if (preg_match('#list(=?(?:[0-9]|[a-z]|))#i', $buffer, $m))
532 // sub-list, add a closing tag
533 if (!$m[1] || preg_match('/^(disc|square|circle)$/i', $m[1]))
535 array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
537 else
539 array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
541 $out .= $buffer . ':' . $this->bbcode_uid . ']';
542 $tok = '[';
544 else
546 $out .= $buffer . $tok;
547 $tok = '[]';
550 else
552 // Not within a tag, just add buffer to the return string
553 $out .= $buffer . $tok;
554 $tok = ($tok == '[') ? ']' : '[]';
557 while ($in);
559 if (sizeof($list_end_tags))
561 $out .= '[' . implode('][', $list_end_tags) . ']';
564 return $out;
568 * Parse quote bbcode
569 * Expects the argument to start with a tag
571 function bbcode_quote($in)
573 global $config, $user;
575 $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
577 if (!$in)
579 return '';
582 $tok = ']';
583 $out = '[';
585 $in = substr($in, 1);
586 $close_tags = $error_ary = array();
587 $buffer = '';
591 $pos = strlen($in);
592 for ($i = 0; $i < strlen($tok); ++$i)
594 $tmp_pos = strpos($in, $tok{$i});
595 if ($tmp_pos !== false && $tmp_pos < $pos)
597 $pos = $tmp_pos;
601 $buffer .= substr($in, 0, $pos);
602 $tok = $in{$pos};
603 $in = substr($in, $pos + 1);
605 if ($tok == ']')
607 if ($buffer == '/quote' && sizeof($close_tags))
609 // we have found a closing tag
610 // Add space at the end of the closing tag to allow following urls/smilies to be parsed correctly
611 $out .= array_pop($close_tags) . '] ';
612 $tok = '[';
613 $buffer = '';
615 else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m))
617 $this->parsed_items['quote']++;
619 // the buffer holds a valid opening tag
620 if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])
622 // there are too many nested quotes
623 $error_ary['quote_depth'] = sprintf($user->lang['QUOTE_DEPTH_EXCEEDED'], $config['max_quote_depth']);
625 $out .= $buffer . $tok;
626 $tok = '[]';
627 $buffer = '';
629 continue;
632 array_push($close_tags, '/quote:' . $this->bbcode_uid);
634 if (isset($m[1]) && $m[1])
636 $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $m[1]);
637 $end_tags = array();
638 $error = false;
640 preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
641 foreach ($tags[1] as $tag)
643 if ($tag{0} != '/')
645 $end_tags[] = '/' . $tag;
647 else
649 $end_tag = array_pop($end_tags);
650 if ($end_tag != $tag)
652 $error = true;
654 else
656 $error = false;
661 if ($error)
663 $username = str_replace('[', '&#91;', str_replace(']', '&#93;', $m[1]));
666 $out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
668 else
670 $out .= 'quote:' . $this->bbcode_uid . ']';
673 $tok = '[';
674 $buffer = '';
676 else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
678 // the buffer holds an invalid opening tag
679 $buffer .= ']';
681 else
683 $out .= $buffer . $tok;
684 $tok = '[]';
685 $buffer = '';
688 else
690 $out .= $buffer . $tok;
691 $tok = ($tok == '[') ? ']' : '[]';
692 $buffer = '';
695 while ($in);
697 if (sizeof($close_tags))
699 $out .= '[' . implode('][', $close_tags) . ']';
702 foreach ($error_ary as $error_msg)
704 $this->warn_msg[] = $error_msg;
707 return $out;
711 * Validate email
713 function validate_email($var1, $var2)
715 $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
716 $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
718 $txt = $var2;
719 $email = ($var1) ? $var1 : $var2;
721 $validated = true;
723 if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
725 $validated = false;
728 if (!$validated)
730 return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
733 $this->parsed_items['email']++;
735 if ($var1)
737 $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
739 else
741 $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
744 return $retval;
748 * Validate url
750 function validate_url($var1, $var2)
752 global $config;
754 $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
755 $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
757 $url = ($var1) ? $var1 : $var2;
758 $valid = false;
760 if (!$url || ($var1 && !$var2))
762 return '';
765 // Checking urls
766 if (preg_match('#' . preg_quote(generate_board_url(), '#') . '/([^ \t\n\r<"\']+)#i', $url) ||
767 preg_match('#([\w]+?://.*?[^ \t\n\r<"\']*)#i', $url) ||
768 preg_match('#(www\.[\w\-]+\.[\w\-.\~]+(?:/[^ \t\n\r<"\']*)?)#i', $url))
770 $valid = true;
773 if ($valid)
775 $this->parsed_items['url']++;
777 if (!preg_match('#^[\w]+?://.*?#i', $url))
779 $url = 'http://' . $url;
782 // We take our test url and stick on the first bit of text we get to check if we are really at the domain. If so, lets go!
783 if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
785 $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}/', '\1', $url);
788 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 . ']';
791 return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
795 * Check if url is pointing to this domain/script_path/php-file
797 * @param string $url the url to check
798 * @return true if the url is pointing to this domain/script_path/php-file, false if not
800 * @access: private
802 function path_in_domain($url)
804 global $config, $phpEx, $user;
806 $check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
808 // Is the user trying to link to a php file in this domain and script path?
809 if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
811 $server_name = (!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME');
813 // Forcing server vars is the only way to specify/override the protocol
814 if ($config['force_server_vars'] || !$server_name)
816 $server_name = $config['server_name'];
819 // Check again in correct order...
820 $pos_ext = strpos($url, ".{$phpEx}");
821 $pos_path = strpos($url, $check_path);
822 $pos_domain = strpos($url, $server_name);
824 if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
826 return true;
830 return false;
835 * Main message parser for posting, pm, etc. takes raw message
836 * and parses it for attachments, bbcode and smilies
837 * @package phpBB3
839 class parse_message extends bbcode_firstpass
841 var $attachment_data = array();
842 var $filename_data = array();
844 // Helps ironing out user error
845 var $message_status = '';
847 var $allow_img_bbcode = true;
848 var $allow_flash_bbcode = true;
849 var $allow_quote_bbcode = true;
851 var $mode;
854 * Init - give message here or manually
856 function parse_message($message = '')
858 // Init BBCode UID
859 $this->bbcode_uid = substr(md5(time()), 0, BBCODE_UID_LEN);
861 if ($message)
863 $this->message = $message;
868 * Parse Message
870 function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $update_this_message = true, $mode = 'post')
872 global $config, $db, $user;
874 $mode = ($mode != 'post') ? 'sig' : 'post';
876 $this->mode = $mode;
878 $this->allow_img_bbcode = $allow_img_bbcode;
879 $this->allow_flash_bbcode = $allow_flash_bbcode;
880 $this->allow_quote_bbcode = $allow_quote_bbcode;
882 // If false, then $this->message won't be altered, the text will be returned instead.
883 if (!$update_this_message)
885 $tmp_message = $this->message;
886 $return_message = &$this->message;
889 if ($this->message_status == 'display')
891 $this->decode_message();
894 // Do some general 'cleanup' first before processing message,
895 // e.g. remove excessive newlines(?), smilies(?)
896 // Transform \r\n and \r into \n
897 $match = array('#\r\n?#', "#([\n][\s]+){3,}#", '#(script|about|applet|activex|chrome):#i');
898 $replace = array("\n", "\n\n", "\\1&#058;");
899 $this->message = preg_replace($match, $replace, trim($this->message));
901 // Message length check. -1 disables this check completely.
902 if ($config['max_' . $mode . '_chars'] != -1)
904 $msg_len = ($mode == 'post') ? strlen($this->message) : strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#is', ' ', $this->message));
906 if ((!$msg_len && $mode !== 'sig') || $config['max_' . $mode . '_chars'] && $msg_len > $config['max_' . $mode . '_chars'])
908 $this->warn_msg[] = (!$msg_len) ? $user->lang['TOO_FEW_CHARS'] : $user->lang['TOO_MANY_CHARS'];
909 return $this->warn_msg;
913 // Prepare BBcode (just prepares some tags for better parsing)
914 if ($allow_bbcode && strpos($this->message, '[') !== false)
916 $this->bbcode_init();
917 $disallow = array('img', 'flash', 'quote');
918 foreach ($disallow as $bool)
920 if (!${'allow_' . $bool . '_bbcode'})
922 $this->bbcodes[$bool]['disabled'] = true;
926 $this->prepare_bbcodes();
929 // Parse smilies
930 if ($allow_smilies)
932 $this->smilies($config['max_' . $mode . '_smilies']);
935 $num_urls = 0;
937 // Parse BBCode
938 if ($allow_bbcode && strpos($this->message, '[') !== false)
940 $this->parse_bbcode();
941 $num_urls += $this->parsed_items['url'];
944 // Parse URL's
945 if ($allow_magic_url)
947 $this->magic_url(generate_board_url());
949 if ($config['max_' . $mode . '_urls'])
951 $num_urls += preg_match_all('#\<!-- (l|m|w|e) --\>.*?\<!-- \1 --\>#', $this->message, $matches);
955 // Check number of links
956 if ($config['max_' . $mode . '_urls'] && $num_urls > $config['max_' . $mode . '_urls'])
958 $this->warn_msg[] = sprintf($user->lang['TOO_MANY_URLS'], $config['max_' . $mode . '_urls']);
959 return $this->warn_msg;
962 if (!$update_this_message)
964 unset($this->message);
965 $this->message = $tmp_message;
966 return $return_message;
969 $this->message_status = 'parsed';
970 return false;
974 * Formatting text for display
976 function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
978 // If false, then the parsed message get returned but internal message not processed.
979 if (!$update_this_message)
981 $tmp_message = $this->message;
982 $return_message = &$this->message;
985 if ($this->message_status == 'plain')
987 // Force updating message - of course.
988 $this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_flash_bbcode, $this->allow_quote_bbcode, true);
991 // Parse BBcode
992 if ($allow_bbcode)
994 $this->bbcode_cache_init();
996 // We are giving those parameters to be able to use the bbcode class on its own
997 $this->bbcode_second_pass($this->message, $this->bbcode_uid);
1000 $this->message = smiley_text($this->message, !$allow_smilies);
1002 // Replace naughty words such as farty pants
1003 $this->message = str_replace("\n", '<br />', censor_text($this->message));
1005 if (!$update_this_message)
1007 unset($this->message);
1008 $this->message = $tmp_message;
1009 return $return_message;
1012 $this->message_status = 'display';
1013 return false;
1017 * Decode message to be placed back into form box
1019 function decode_message($custom_bbcode_uid = '', $update_this_message = true)
1021 // If false, then the parsed message get returned but internal message not processed.
1022 if (!$update_this_message)
1024 $tmp_message = $this->message;
1025 $return_message = &$this->message;
1028 ($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);
1030 if (!$update_this_message)
1032 unset($this->message);
1033 $this->message = $tmp_message;
1034 return $return_message;
1037 $this->message_status = 'plain';
1038 return false;
1042 * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
1043 * Cuts down displayed size of link if over 50 chars, turns absolute links
1044 * into relative versions when the server/script path matches the link
1046 function magic_url($server_url)
1048 // We use the global make_clickable function
1049 $this->message = make_clickable($this->message, $server_url);
1053 * Parse Smilies
1055 function smilies($max_smilies = 0)
1057 global $db, $user, $phpbb_root_path;
1058 static $match;
1059 static $replace;
1061 // See if the static arrays have already been filled on an earlier invocation
1062 if (!is_array($match))
1064 $match = $replace = array();
1066 // NOTE: obtain_* function? chaching the table contents?
1068 // For now setting the ttl to 10 minutes
1069 switch (SQL_LAYER)
1071 case 'mssql':
1072 case 'mssql_odbc':
1073 $sql = 'SELECT *
1074 FROM ' . SMILIES_TABLE . '
1075 ORDER BY LEN(code) DESC';
1076 break;
1078 case 'firebird':
1079 $sql = 'SELECT *
1080 FROM ' . SMILIES_TABLE . '
1081 ORDER BY STRLEN(code) DESC';
1082 break;
1084 // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
1085 default:
1086 $sql = 'SELECT *
1087 FROM ' . SMILIES_TABLE . '
1088 ORDER BY LENGTH(code) DESC';
1089 break;
1091 $result = $db->sql_query($sql, 600);
1093 while ($row = $db->sql_fetchrow($result))
1095 // (assertion)
1096 $match[] = '#(?<=^|[\n ]|\.)' . preg_quote($row['code'], '#') . '#';
1097 $replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" border="0" alt="' . $row['emotion'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
1099 $db->sql_freeresult($result);
1102 if (sizeof($match))
1104 if ($max_smilies)
1106 $num_matches = preg_match_all('#' . str_replace('#', '', implode('|', $match)) . '#', $this->message, $matches);
1107 unset($matches);
1109 if ($num_matches !== false && $num_matches > $max_smilies)
1111 $this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
1112 return;
1115 $this->message = trim(preg_replace($match, $replace, $this->message));
1120 * Parse Attachments
1122 function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
1124 global $config, $auth, $user, $phpbb_root_path, $phpEx;
1126 $error = array();
1128 $num_attachments = sizeof($this->attachment_data);
1129 $this->filename_data['filecomment'] = request_var('filecomment', '', true);
1130 $upload_file = (isset($_FILES[$form_name]) && $_FILES[$form_name]['name'] != 'none' && trim($_FILES[$form_name]['name'])) ? true : false;
1132 $add_file = (isset($_POST['add_file'])) ? true : false;
1133 $delete_file = (isset($_POST['delete_file'])) ? true : false;
1134 $edit_comment = (isset($_POST['edit_comment'])) ? true : false;
1136 $cfg = array();
1137 $cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
1138 $forum_id = ($is_message) ? 0 : $forum_id;
1140 if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
1142 if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
1144 $filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1145 $error = $filedata['error'];
1147 if ($filedata['post_attach'] && !sizeof($error))
1149 $new_entry = array(
1150 'physical_filename' => $filedata['physical_filename'],
1151 'comment' => $this->filename_data['filecomment'],
1152 'real_filename' => $filedata['real_filename'],
1153 'extension' => $filedata['extension'],
1154 'mimetype' => $filedata['mimetype'],
1155 'filesize' => $filedata['filesize'],
1156 'filetime' => $filedata['filetime'],
1157 'attach_id' => 0,
1158 'thumbnail' => $filedata['thumbnail']
1161 $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1162 $this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);
1164 $this->filename_data['filecomment'] = '';
1166 // This Variable is set to false here, because Attachments are entered into the
1167 // Database in two modes, one if the id_list is 0 and the second one if post_attach is true
1168 // Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
1169 // but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
1171 // This is very relevant, because it could happen that the post got not submitted, but we do not
1172 // know this circumstance here. We could be at the posting page or we could be redirected to the entered
1173 // post. :)
1174 $filedata['post_attach'] = false;
1177 else
1179 $error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
1183 if ($preview || $refresh || sizeof($error))
1185 // Perform actions on temporary attachments
1186 if ($delete_file)
1188 include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1190 $index = (int) key($_POST['delete_file']);
1192 // delete selected attachment
1193 if (!$this->attachment_data[$index]['attach_id'])
1195 phpbb_unlink($this->attachment_data[$index]['physical_filename'], 'file');
1197 if ($this->attachment_data[$index]['thumbnail'])
1199 phpbb_unlink($this->attachment_data[$index]['physical_filename'], 'thumbnail');
1202 else
1204 delete_attachments('attach', array(intval($this->attachment_data[$index]['attach_id'])));
1207 unset($this->attachment_data[$index]);
1208 $this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "(\\1 == \$index) ? '' : ((\\1 > \$index) ? '[attachment=' . (\\1 - 1) . ']\\2[/attachment]' : '\\0')", $this->message);
1210 // Reindex Array
1211 $this->attachment_data = array_values($this->attachment_data);
1213 else if ($edit_comment || $add_file || $preview)
1215 if ($edit_comment)
1217 $actual_comment_list = request_var('comment_list', array(''), true);
1219 $edit_comment = request_var('edit_comment', array(0 => ''));
1220 $edit_comment = key($edit_comment);
1221 $this->attachment_data[$edit_comment]['comment'] = $actual_comment_list[$edit_comment];
1224 if (($add_file || $preview) && $upload_file)
1226 if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_'))
1228 $filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1229 $error = array_merge($error, $filedata['error']);
1231 if (!sizeof($error))
1233 $new_entry = array(
1234 'physical_filename' => $filedata['physical_filename'],
1235 'comment' => $this->filename_data['filecomment'],
1236 'real_filename' => $filedata['real_filename'],
1237 'extension' => $filedata['extension'],
1238 'mimetype' => $filedata['mimetype'],
1239 'filesize' => $filedata['filesize'],
1240 'filetime' => $filedata['filetime'],
1241 'attach_id' => 0,
1242 'thumbnail' => $filedata['thumbnail']
1245 $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1246 $this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);
1247 $this->filename_data['filecomment'] = '';
1250 else
1252 $error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
1258 foreach ($error as $error_msg)
1260 $this->warn_msg[] = $error_msg;
1265 * Get Attachment Data
1267 function get_submitted_attachment_data($check_user_id = false)
1269 global $user, $db, $phpbb_root_path, $phpEx, $config;
1271 $this->filename_data['filecomment'] = request_var('filecomment', '', true);
1272 $this->attachment_data = (isset($_POST['attachment_data'])) ? $_POST['attachment_data'] : array();
1274 $check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id;
1276 // Regenerate data array...
1277 $attach_ids = $filenames = array();
1279 foreach ($this->attachment_data as $pos => $var_ary)
1281 if ($var_ary['attach_id'])
1283 $attach_ids[(int) $this->attachment_data[$pos]['attach_id']] = $pos;
1285 else
1287 $filenames[$pos] = '';
1288 set_var($filenames[$pos], $this->attachment_data[$pos]['physical_filename'], 'string');
1289 $filenames[$pos] = basename($filenames[$pos]);
1293 $this->attachment_data = array();
1295 // Regenerate already posted attachments...
1296 if (sizeof($attach_ids))
1298 // Get the data from the attachments
1299 $sql = 'SELECT attach_id, physical_filename, real_filename, extension, mimetype, filesize, filetime, thumbnail
1300 FROM ' . ATTACHMENTS_TABLE . '
1301 WHERE attach_id IN (' . implode(', ', array_keys($attach_ids)) . ')
1302 AND poster_id = ' . $check_user_id;
1303 $result = $db->sql_query($sql);
1305 while ($row = $db->sql_fetchrow($result))
1307 if (isset($attach_ids[$row['attach_id']]))
1309 $pos = $attach_ids[$row['attach_id']];
1310 $this->attachment_data[$pos] = $row;
1311 set_var($this->attachment_data[$pos]['comment'], $_POST['attachment_data'][$pos]['comment'], 'string', true);
1313 unset($attach_ids[$row['attach_id']]);
1316 $db->sql_freeresult($result);
1318 if (sizeof($attach_ids))
1320 trigger_error($user->lang['NO_ACCESS_ATTACHMENT'], E_USER_ERROR);
1324 // Regenerate newly uploaded attachments
1325 if (sizeof($filenames))
1327 include_once($phpbb_root_path . 'includes/functions_upload.' . $phpEx);
1329 $sql = 'SELECT attach_id
1330 FROM ' . ATTACHMENTS_TABLE . "
1331 WHERE LOWER(physical_filename) IN ('" . implode("', '", array_map('strtolower', $filenames)) . "')";
1332 $result = $db->sql_query_limit($sql, 1);
1333 $row = $db->sql_fetchrow($result);
1334 $db->sql_freeresult($result);
1336 if ($row)
1338 trigger_error($user->lang['NO_ACCESS_ATTACHMENT'], E_USER_ERROR);
1341 foreach ($filenames as $pos => $physical_filename)
1343 $this->attachment_data[$pos] = array(
1344 'physical_filename' => $physical_filename,
1345 'extension' => strtolower(filespec::get_extension($phpbb_root_path . $config['upload_path'] . '/' . $physical_filename)),
1346 'filesize' => filespec::get_filesize($phpbb_root_path . $config['upload_path'] . '/' . $physical_filename),
1347 'attach_id' => 0,
1348 'thumbnail' => (file_exists($phpbb_root_path . $config['upload_path'] . '/thumb_' . $physical_filename)) ? 1 : 0,
1351 set_var($this->attachment_data[$pos]['comment'], $_POST['attachment_data'][$pos]['comment'], 'string', true);
1352 set_var($this->attachment_data[$pos]['real_filename'], $_POST['attachment_data'][$pos]['real_filename'], 'string', true);
1353 set_var($this->attachment_data[$pos]['filetime'], $_POST['attachment_data'][$pos]['filetime'], 'int');
1355 if (strpos($_POST['attachment_data'][$pos]['mimetype'], 'image/') !== false)
1357 set_var($this->attachment_data[$pos]['mimetype'], $_POST['attachment_data'][$pos]['mimetype'], 'string');
1359 else
1361 $this->attachment_data[$pos]['mimetype'] = filespec::get_mimetype($phpbb_root_path . $config['upload_path'] . '/' . $physical_filename);
1368 * Parse Poll
1370 function parse_poll(&$poll)
1372 global $auth, $user, $config;
1374 $poll_max_options = $poll['poll_max_options'];
1376 // Parse Poll Option text ;)
1377 $tmp_message = $this->message;
1378 $this->message = $poll['poll_option_text'];
1379 $bbcode_bitfield = $this->bbcode_bitfield;
1381 $poll['poll_option_text'] = $this->parse($poll['enable_bbcode'], $poll['enable_urls'], $poll['enable_smilies'], $poll['img_status'], false, false, false);
1383 $this->bbcode_bitfield |= $bbcode_bitfield;
1384 $this->message = $tmp_message;
1386 // Parse Poll Title
1387 $tmp_message = $this->message;
1388 $this->message = $poll['poll_title'];
1389 $bbcode_bitfield = $this->bbcode_bitfield;
1391 $poll['poll_title'] = $this->parse($poll['enable_bbcode'], $poll['enable_urls'], $poll['enable_smilies'], $poll['img_status'], false, false, false);
1393 $this->bbcode_bitfield |= $bbcode_bitfield;
1394 $this->message = $tmp_message;
1396 unset($tmp_message);
1398 $poll['poll_options'] = explode("\n", trim($poll['poll_option_text']));
1399 $poll['poll_options_size'] = sizeof($poll['poll_options']);
1401 if (sizeof($poll['poll_options']) == 1)
1403 $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS'];
1405 else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
1407 $this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS'];
1409 else if ($poll_max_options > $poll['poll_options_size'])
1411 $this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS'];
1414 if (!$poll['poll_title'] && $poll['poll_options_size'])
1416 $this->warn_msg[] = $user->lang['NO_POLL_TITLE'];
1419 $poll['poll_max_options'] = ($poll['poll_max_options'] < 1) ? 1 : (($poll['poll_max_options'] > $config['max_poll_options']) ? $config['max_poll_options'] : $poll['poll_max_options']);