6 * @copyright (c) 2005 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
14 if (!defined('IN_PHPBB'))
19 if (!class_exists('bbcode'))
21 include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
26 * BBCODE first pass class (functions for parsing messages for db storage)
29 class bbcode_firstpass
extends bbcode
32 var $warn_msg = array();
33 var $parsed_items = array();
38 function parse_bbcode()
47 $this->bbcode_bitfield
= '';
48 $bitfield = new bitfield();
50 foreach ($this->bbcodes
as $bbcode_name => $bbcode_data)
52 if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
54 foreach ($bbcode_data['regexp'] as $regexp => $replacement)
56 if (preg_match($regexp, $this->message
))
58 $this->warn_msg
[] = sprintf($user->lang
['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']');
65 foreach ($bbcode_data['regexp'] as $regexp => $replacement)
67 // The pattern gets compiled and cached by the PCRE extension,
68 // it should not demand recompilation
69 if (preg_match($regexp, $this->message
))
71 $this->message
= preg_replace($regexp, $replacement, $this->message
);
72 $bitfield->set($bbcode_data['bbcode_id']);
78 $this->bbcode_bitfield
= $bitfield->get_base64();
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 && strpos($this->message
, '[/quote]') !== false)
89 $this->message
= str_replace("\r\n", "\n", $this->message
);
91 // We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
92 $this->message
= preg_replace('#\[quote(=".*?")?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message
);
95 // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
99 * Init bbcode data for later parsing
101 function bbcode_init()
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(?:="(.*?)")?\](.+)\[/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=([\-\+]?\d+)\](.*?)\[/size\]#ise' => "\$this->bbcode_size('\$1', '\$2')")),
117 'color' => array('bbcode_id' => 6, 'regexp' => array('!\[color=(#[0-9a-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-z0-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))
138 FROM ' . BBCODES_TABLE
;
139 $result = $db->sql_query($sql);
141 while ($row = $db->sql_fetchrow($result))
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
172 $this->parsed_items
[$bbcode]++
;
178 * Transform some characters in valid bbcodes
180 function bbcode_specialchars($text)
182 $str_from = array('<', '>', '[', ']', '.', ':');
183 $str_to = array('<', '>', '[', ']', '.', ':');
185 return str_replace($str_from, $str_to, $text);
191 function bbcode_size($stx, $in)
193 global $user, $config;
195 if (!$this->check_bbcode('size', $in))
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']);
204 return '[size=' . $stx . ']' . $in . '[/size]';
207 return '[size=' . $stx . ':' . $this->bbcode_uid
. ']' . $in . '[/size:' . $this->bbcode_uid
. ']';
213 function bbcode_color($stx, $in)
215 if (!$this->check_bbcode('color', $in))
220 return '[color=' . $stx . ':' . $this->bbcode_uid
. ']' . $in . '[/color:' . $this->bbcode_uid
. ']';
226 function bbcode_underline($in)
228 if (!$this->check_bbcode('u', $in))
233 return '[u:' . $this->bbcode_uid
. ']' . $in . '[/u:' . $this->bbcode_uid
. ']';
239 function bbcode_strong($in)
241 if (!$this->check_bbcode('b', $in))
246 return '[b:' . $this->bbcode_uid
. ']' . $in . '[/b:' . $this->bbcode_uid
. ']';
252 function bbcode_italic($in)
254 if (!$this->check_bbcode('i', $in))
259 return '[i:' . $this->bbcode_uid
. ']' . $in . '[/i:' . $this->bbcode_uid
. ']';
265 function bbcode_img($in)
267 global $user, $config;
269 if (!$this->check_bbcode('img', $in))
277 if ($config['max_' . $this->mode
. '_img_height'] ||
$config['max_' . $this->mode
. '_img_width'])
279 $stats = getimagesize($in);
281 if ($stats === false)
284 $this->warn_msg
[] = $user->lang
['UNABLE_GET_IMAGE_SIZE'];
288 if ($config['max_' . $this->mode
. '_img_height'] && $config['max_' . $this->mode
. '_img_height'] < $stats[1])
291 $this->warn_msg
[] = sprintf($user->lang
['MAX_IMG_HEIGHT_EXCEEDED'], $config['max_' . $this->mode
. '_img_height']);
294 if ($config['max_' . $this->mode
. '_img_width'] && $config['max_' . $this->mode
. '_img_width'] < $stats[0])
297 $this->warn_msg
[] = sprintf($user->lang
['MAX_IMG_WIDTH_EXCEEDED'], $config['max_' . $this->mode
. '_img_width']);
302 if ($error ||
$this->path_in_domain($in))
304 return '[img]' . $in . '[/img]';
307 return '[img:' . $this->bbcode_uid
. ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid
. ']';
313 function bbcode_flash($width, $height, $in)
315 global $user, $config;
317 if (!$this->check_bbcode('flash', $in))
325 // Apply the same size checks on flash files as on images
326 if ($config['max_' . $this->mode
. '_img_height'] ||
$config['max_' . $this->mode
. '_img_width'])
328 if ($config['max_' . $this->mode
. '_img_height'] && $config['max_' . $this->mode
. '_img_height'] < $height)
331 $this->warn_msg
[] = sprintf($user->lang
['MAX_FLASH_HEIGHT_EXCEEDED'], $config['max_' . $this->mode
. '_img_height']);
334 if ($config['max_' . $this->mode
. '_img_width'] && $config['max_' . $this->mode
. '_img_width'] < $width)
337 $this->warn_msg
[] = sprintf($user->lang
['MAX_FLASH_WIDTH_EXCEEDED'], $config['max_' . $this->mode
. '_img_width']);
341 if ($error ||
$this->path_in_domain($in))
343 return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
346 return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid
. ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid
. ']';
350 * Parse inline attachments [ia]
352 function bbcode_attachment($stx, $in)
354 if (!$this->check_bbcode('attachment', $in))
359 return '[attachment=' . $stx . ':' . $this->bbcode_uid
. ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid
. ']';
363 * Parse code text from code tag
366 function bbcode_parse_code($stx, $code)
368 switch (strtolower($stx))
372 $remove_tags = false;
373 $code = str_replace(array('<', '>'), array('<', '>'), $code);
375 if (!preg_match('/\<\?.*?\?\>/is', $code))
378 $code = "<?php $code ?>";
381 $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
382 foreach ($conf as $ini_var)
384 @ini_set
($ini_var, str_replace('highlight.', 'syntax', $ini_var));
387 // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
388 $code = htmlspecialchars_decode($code);
389 $code = highlight_string($code, true);
391 $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
392 $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '[', ']', '.', ':');
396 $str_from[] = '<span class="syntaxdefault"><?php </span>';
398 $str_from[] = '<span class="syntaxdefault"><?php ';
399 $str_to[] = '<span class="syntaxdefault">';
402 $code = str_replace($str_from, $str_to, $code);
403 $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
407 $code = preg_replace('#(<span class="[a-z]+">)?\?>(</span>)#', '$1 $2', $code);
410 $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
411 $code = preg_replace('#(?:[\n\r\s\t]| )*</span>$#u', '</span>', $code);
413 // remove newline at the end
414 if (!empty($code) && $code[strlen($code) - 1] == "\n")
416 $code = substr($code, 0, -1);
419 return "[code=$stx:" . $this->bbcode_uid
. ']' . $code . '[/code:' . $this->bbcode_uid
. ']';
423 return '[code:' . $this->bbcode_uid
. ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid
. ']';
430 * Expects the argument to start right after the opening [code] tag and to end with [/code]
432 function bbcode_code($stx, $in)
434 if (!$this->check_bbcode('code', $in))
439 // We remove the hardcoded elements from the code block here because it is not used in code blocks
440 // Having it here saves us one preg_replace per message containing [code] blocks
441 // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
442 $htm_match = get_preg_expression('bbcode_htm');
443 unset($htm_match[4], $htm_match[5]);
444 $htm_replace = array('\1', '\1', '\2', '\1');
446 $in = preg_replace($htm_match, $htm_replace, $in);
447 $out = $code_block = '';
452 // Determine position and tag length of next code block
453 preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
454 $pos = (isset($buffer[1])) ?
strlen($buffer[1]) : false;
455 $tag_length = (isset($buffer[2])) ?
strlen($buffer[2]) : false;
457 // Determine position of ending code tag
458 $pos2 = stripos($in, '[/code]');
460 // Which is the next block, ending code or code block
461 if ($pos !== false && $pos < $pos2)
466 $out .= substr($in, 0, $pos);
467 $in = substr($in, $pos);
468 $stx = (isset($buffer[3])) ?
$buffer[3] : '';
473 // Already opened block, just append to the current block
474 $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ?
$buffer[2] : '');
475 $in = substr($in, $pos);
478 $in = substr($in, $tag_length);
486 $code_block .= substr($in, 0, $pos2);
488 // Parse this code block
489 $out .= $this->bbcode_parse_code($stx, $code_block);
495 // Close one open tag... add to the current code block
496 $code_block .= substr($in, 0, $pos2 +
7);
501 // end code without opening code... will be always outside code block
502 $out .= substr($in, 0, $pos2 +
7);
505 $in = substr($in, $pos2 +
7);
509 // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
512 $code_block = substr($code_block, 0, -7);
513 $out .= $this->bbcode_parse_code($stx, $code_block);
521 * Expects the argument to start with a tag
523 function bbcode_parse_list($in)
525 if (!$this->check_bbcode('list', $in))
530 // $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
534 // First character is [
535 $in = substr($in, 1);
536 $list_end_tags = $item_end_tags = array();
542 for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++
$i)
544 $tmp_pos = strpos($in, $tok[$i]);
546 if ($tmp_pos !== false && $tmp_pos < $pos)
552 $buffer = substr($in, 0, $pos);
555 $in = substr($in, $pos +
1);
559 // if $tok is ']' the buffer holds a tag
560 if (strtolower($buffer) == '/list' && sizeof($list_end_tags))
562 // valid [/list] tag, check nesting so that we don't hit false positives
563 if (sizeof($item_end_tags) && sizeof($item_end_tags) >= sizeof($list_end_tags))
565 // current li tag has not been closed
566 $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
569 $out .= array_pop($list_end_tags) . ']';
572 else if (preg_match('#^list(=[0-9a-z])?$#i', $buffer, $m))
574 // sub-list, add a closing tag
575 if (empty($m[1]) ||
preg_match('/^(?:disc|square|circle)$/i', $m[1]))
577 array_push($list_end_tags, '/list:u:' . $this->bbcode_uid
);
581 array_push($list_end_tags, '/list:o:' . $this->bbcode_uid
);
583 $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid
. ']';
588 if (($buffer == '*' ||
substr($buffer, -2) == '[*') && sizeof($list_end_tags))
590 // the buffer holds a bullet tag and we have a [list] tag open
591 if (sizeof($item_end_tags) >= sizeof($list_end_tags))
593 if (substr($buffer, -2) == '[*')
595 $out .= substr($buffer, 0, -2) . '[';
597 // current li tag has not been closed
598 if (preg_match('/\n\[$/', $out, $m))
600 $out = preg_replace('/\n\[$/', '[', $out);
601 $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid
;
605 $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid
;
610 $buffer = '*:' . $this->bbcode_uid
;
613 $item_end_tags[] = '/*:m:' . $this->bbcode_uid
;
615 else if ($buffer == '/*')
617 array_pop($item_end_tags);
618 $buffer = '/*:' . $this->bbcode_uid
;
621 $out .= $buffer . $tok;
627 // Not within a tag, just add buffer to the return string
628 $out .= $buffer . $tok;
629 $tok = ($tok == '[') ?
']' : '[]';
634 // do we have some tags open? close them now
635 if (sizeof($item_end_tags))
637 $out .= '[' . implode('][', $item_end_tags) . ']';
639 if (sizeof($list_end_tags))
641 $out .= '[' . implode('][', $list_end_tags) . ']';
649 * Expects the argument to start with a tag
651 function bbcode_quote($in)
653 global $config, $user;
655 $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
665 $in = substr($in, 1);
666 $close_tags = $error_ary = array();
672 for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++
$i)
674 $tmp_pos = strpos($in, $tok[$i]);
675 if ($tmp_pos !== false && $tmp_pos < $pos)
681 $buffer .= substr($in, 0, $pos);
683 $in = substr($in, $pos +
1);
687 if ($buffer == '/quote' && sizeof($close_tags))
689 // we have found a closing tag
690 $out .= array_pop($close_tags) . ']';
694 /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
695 * 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.
696 * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
697 if (!$in || $in[0] !== ' ')
702 else if (preg_match('#^quote(?:="(.*?)")?$#is', $buffer, $m))
704 $this->parsed_items
['quote']++
;
706 // the buffer holds a valid opening tag
707 if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])
709 // there are too many nested quotes
710 $error_ary['quote_depth'] = sprintf($user->lang
['QUOTE_DEPTH_EXCEEDED'], $config['max_quote_depth']);
712 $out .= $buffer . $tok;
719 array_push($close_tags, '/quote:' . $this->bbcode_uid
);
721 if (isset($m[1]) && $m[1])
723 $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '[$1', $m[1]);
727 preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
728 foreach ($tags[1] as $tag)
732 $end_tags[] = '/' . $tag;
736 $end_tag = array_pop($end_tags);
737 $error = ($end_tag != $tag) ?
true : false;
743 $username = str_replace('[', '[', str_replace(']', ']', $m[1]));
746 $out .= 'quote="' . $username . '":' . $this->bbcode_uid
. ']';
750 $out .= 'quote:' . $this->bbcode_uid
. ']';
756 else if (preg_match('#^quote="(.*?)#is', $buffer, $m))
758 // the buffer holds an invalid opening tag
763 $out .= $buffer . $tok;
771 * Old quote code working fine, but having errors listed in bug #3572
773 * $out .= $buffer . $tok;
774 * $tok = ($tok == '[') ? ']' : '[]';
778 $out .= $buffer . $tok;
782 // Search the text for the next tok... if an ending quote comes first, then change tok to []
783 $pos1 = strpos($in, '[/quote');
784 $pos2 = strpos($in, ']');
786 if ($pos1 !== false && ($pos2 === false ||
$pos1 < $pos2))
804 if (sizeof($close_tags))
806 $out .= '[' . implode('][', $close_tags) . ']';
809 foreach ($error_ary as $error_msg)
811 $this->warn_msg
[] = $error_msg;
820 function validate_email($var1, $var2)
822 $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
823 $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
826 $email = ($var1) ?
$var1 : $var2;
830 if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
837 return '[email' . (($var1) ?
"=$var1" : '') . ']' . $var2 . '[/email]';
840 $this->parsed_items
['email']++
;
844 $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid
. ']' . $txt . '[/email:' . $this->bbcode_uid
. ']';
848 $retval = '[email:' . $this->bbcode_uid
. ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid
. ']';
857 * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
858 * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
860 function validate_url($var1, $var2)
864 $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
865 $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
867 $url = ($var1) ?
$var1 : $var2;
869 if (!$url ||
($var1 && !$var2))
876 $url = str_replace(' ', '%20', $url);
879 if (preg_match('#^' . get_preg_expression('url') . '$#i', $url) ||
880 preg_match('#^' . get_preg_expression('www_url') . '$#i', $url) ||
881 preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#i', $url))
888 $this->parsed_items
['url']++
;
890 // if there is no scheme, then add http schema
891 if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
893 $url = 'http://' . $url;
896 // Is this a link to somewhere inside this board? If so then remove the session id from the url
897 if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
899 $url = preg_replace('/(&|\?)sid=[0-9a-f]{32}&/', '\1', $url);
900 $url = preg_replace('/(&|\?)sid=[0-9a-f]{32}$/', '', $url);
901 $url = append_sid($url);
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
. ']';
907 return '[url' . (($var1) ?
'=' . $var1 : '') . ']' . $var2 . '[/url]';
911 * Check if url is pointing to this domain/script_path/php-file
913 * @param string $url the url to check
914 * @return true if the url is pointing to this domain/script_path/php-file, false if not
918 function path_in_domain($url)
920 global $config, $phpEx, $user;
922 if ($config['force_server_vars'])
924 $check_path = $config['script_path'];
928 $check_path = ($user->page
['root_script_path'] != '/') ?
substr($user->page
['root_script_path'], 0, -1) : '/';
931 // Is the user trying to link to a php file in this domain and script path?
932 if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
934 $server_name = (!empty($_SERVER['SERVER_NAME'])) ?
$_SERVER['SERVER_NAME'] : getenv('SERVER_NAME');
936 // Forcing server vars is the only way to specify/override the protocol
937 if ($config['force_server_vars'] ||
!$server_name)
939 $server_name = $config['server_name'];
942 // Check again in correct order...
943 $pos_ext = strpos($url, ".{$phpEx}");
944 $pos_path = strpos($url, $check_path);
945 $pos_domain = strpos($url, $server_name);
947 if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
958 * Main message parser for posting, pm, etc. takes raw message
959 * and parses it for attachments, bbcode and smilies
962 class parse_message
extends bbcode_firstpass
964 var $attachment_data = array();
965 var $filename_data = array();
967 // Helps ironing out user error
968 var $message_status = '';
970 var $allow_img_bbcode = true;
971 var $allow_flash_bbcode = true;
972 var $allow_quote_bbcode = true;
973 var $allow_url_bbcode = true;
978 * Init - give message here or manually
980 function parse_message($message = '')
983 $this->bbcode_uid
= substr(md5(time()), 0, BBCODE_UID_LEN
);
987 $this->message
= $message;
994 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')
996 global $config, $db, $user;
998 $mode = ($mode != 'post') ?
'sig' : 'post';
1000 $this->mode
= $mode;
1002 $this->allow_img_bbcode
= $allow_img_bbcode;
1003 $this->allow_flash_bbcode
= $allow_flash_bbcode;
1004 $this->allow_quote_bbcode
= $allow_quote_bbcode;
1005 $this->allow_url_bbcode
= $allow_url_bbcode;
1007 // If false, then $this->message won't be altered, the text will be returned instead.
1008 if (!$update_this_message)
1010 $tmp_message = $this->message
;
1011 $return_message = &$this->message
;
1014 if ($this->message_status
== 'display')
1016 $this->decode_message();
1019 // Do some general 'cleanup' first before processing message,
1020 // e.g. remove excessive newlines(?), smilies(?)
1021 $match = array('#(script|about|applet|activex|chrome):#i');
1022 $replace = array("\\1:");
1023 $this->message
= preg_replace($match, $replace, trim($this->message
));
1025 // Message length check. -1 disables this check completely.
1026 if ($config['max_' . $mode . '_chars'] != -1)
1028 $msg_len = ($mode == 'post') ?
utf8_strlen($this->message
) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message
));
1030 if ((!$msg_len && $mode !== 'sig') ||
$config['max_' . $mode . '_chars'] && $msg_len > $config['max_' . $mode . '_chars'])
1032 $this->warn_msg
[] = (!$msg_len) ?
$user->lang
['TOO_FEW_CHARS'] : $user->lang
['TOO_MANY_CHARS'];
1033 return $this->warn_msg
;
1037 // Prepare BBcode (just prepares some tags for better parsing)
1038 if ($allow_bbcode && strpos($this->message
, '[') !== false)
1040 $this->bbcode_init();
1041 $disallow = array('img', 'flash', 'quote', 'url');
1042 foreach ($disallow as $bool)
1044 if (!$
{'allow_' . $bool . '_bbcode'})
1046 $this->bbcodes
[$bool]['disabled'] = true;
1050 $this->prepare_bbcodes();
1056 $this->smilies($config['max_' . $mode . '_smilies']);
1062 if ($allow_bbcode && strpos($this->message
, '[') !== false)
1064 $this->parse_bbcode();
1065 $num_urls +
= $this->parsed_items
['url'];
1069 if ($allow_magic_url)
1071 $this->magic_url(generate_board_url());
1073 if ($config['max_' . $mode . '_urls'])
1075 $num_urls +
= preg_match_all('#\<!-- ([lmwe]) --\>.*?\<!-- \1 --\>#', $this->message
, $matches);
1079 // Check number of links
1080 if ($config['max_' . $mode . '_urls'] && $num_urls > $config['max_' . $mode . '_urls'])
1082 $this->warn_msg
[] = sprintf($user->lang
['TOO_MANY_URLS'], $config['max_' . $mode . '_urls']);
1083 return $this->warn_msg
;
1086 if (!$update_this_message)
1088 unset($this->message
);
1089 $this->message
= $tmp_message;
1090 return $return_message;
1093 $this->message_status
= 'parsed';
1098 * Formatting text for display
1100 function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
1102 // If false, then the parsed message get returned but internal message not processed.
1103 if (!$update_this_message)
1105 $tmp_message = $this->message
;
1106 $return_message = &$this->message
;
1109 if ($this->message_status
== 'plain')
1111 // Force updating message - of course.
1112 $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);
1118 $this->bbcode_cache_init();
1120 // We are giving those parameters to be able to use the bbcode class on its own
1121 $this->bbcode_second_pass($this->message
, $this->bbcode_uid
);
1124 $this->message
= smiley_text($this->message
, !$allow_smilies);
1126 // Replace naughty words such as farty pants
1127 $this->message
= str_replace("\n", '<br />', censor_text($this->message
));
1129 if (!$update_this_message)
1131 unset($this->message
);
1132 $this->message
= $tmp_message;
1133 return $return_message;
1136 $this->message_status
= 'display';
1141 * Decode message to be placed back into form box
1143 function decode_message($custom_bbcode_uid = '', $update_this_message = true)
1145 // If false, then the parsed message get returned but internal message not processed.
1146 if (!$update_this_message)
1148 $tmp_message = $this->message
;
1149 $return_message = &$this->message
;
1152 ($custom_bbcode_uid) ?
decode_message($this->message
, $custom_bbcode_uid) : decode_message($this->message
, $this->bbcode_uid
);
1154 if (!$update_this_message)
1156 unset($this->message
);
1157 $this->message
= $tmp_message;
1158 return $return_message;
1161 $this->message_status
= 'plain';
1166 * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
1167 * Cuts down displayed size of link if over 50 chars, turns absolute links
1168 * into relative versions when the server/script path matches the link
1170 function magic_url($server_url)
1172 // We use the global make_clickable function
1173 $this->message
= make_clickable($this->message
, $server_url);
1179 function smilies($max_smilies = 0)
1185 // See if the static arrays have already been filled on an earlier invocation
1186 if (!is_array($match))
1188 $match = $replace = array();
1190 // NOTE: obtain_* function? chaching the table contents?
1192 // For now setting the ttl to 10 minutes
1193 switch ($db->sql_layer
)
1198 FROM ' . SMILIES_TABLE
. '
1199 ORDER BY LEN(code) DESC';
1204 FROM ' . SMILIES_TABLE
. '
1205 ORDER BY CHAR_LENGTH(code) DESC';
1208 // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
1211 FROM ' . SMILIES_TABLE
. '
1212 ORDER BY LENGTH(code) DESC';
1215 $result = $db->sql_query($sql, 600);
1217 while ($row = $db->sql_fetchrow($result))
1220 $match[] = '#(?<=^|[\n .])' . preg_quote($row['code'], '#') . '(?![^<>]*>)#';
1221 $replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
1223 $db->sql_freeresult($result);
1231 foreach ($match as $key => $smilie)
1233 if ($small_count = preg_match_all($smilie, $this->message
, $array))
1235 $count +
= $small_count;
1236 if ($count > $max_smilies)
1238 $this->warn_msg
[] = sprintf($user->lang
['TOO_MANY_SMILIES'], $max_smilies);
1242 $this->message
= preg_replace($smilie, $replace[$key], $this->message
);
1244 $this->message
= trim($this->message
);
1248 $this->message
= trim(preg_replace($match, $replace, $this->message
));
1256 function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
1258 global $config, $auth, $user, $phpbb_root_path, $phpEx, $db;
1262 $num_attachments = sizeof($this->attachment_data
);
1263 $this->filename_data
['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
1264 $upload_file = (isset($_FILES[$form_name]) && $_FILES[$form_name]['name'] != 'none' && trim($_FILES[$form_name]['name'])) ?
true : false;
1266 $add_file = (isset($_POST['add_file'])) ?
true : false;
1267 $delete_file = (isset($_POST['delete_file'])) ?
true : false;
1269 // First of all adjust comments if changed
1270 $actual_comment_list = utf8_normalize_nfc(request_var('comment_list', array(''), true));
1272 foreach ($actual_comment_list as $comment_key => $comment)
1274 if (!isset($this->attachment_data
[$comment_key]))
1279 if ($this->attachment_data
[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
1281 $this->attachment_data
[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
1286 $cfg['max_attachments'] = ($is_message) ?
$config['max_attachments_pm'] : $config['max_attachments'];
1287 $forum_id = ($is_message) ?
0 : $forum_id;
1289 if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
1291 if ($num_attachments < $cfg['max_attachments'] ||
$auth->acl_get('a_') ||
$auth->acl_get('m_', $forum_id))
1293 $filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1294 $error = $filedata['error'];
1296 if ($filedata['post_attach'] && !sizeof($error))
1299 'physical_filename' => $filedata['physical_filename'],
1300 'attach_comment' => $this->filename_data
['filecomment'],
1301 'real_filename' => $filedata['real_filename'],
1302 'extension' => $filedata['extension'],
1303 'mimetype' => $filedata['mimetype'],
1304 'filesize' => $filedata['filesize'],
1305 'filetime' => $filedata['filetime'],
1306 'thumbnail' => $filedata['thumbnail'],
1308 'in_message' => ($is_message) ?
1 : 0,
1309 'poster_id' => $user->data
['user_id'],
1312 $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE
. ' ' . $db->sql_build_array('INSERT', $sql_ary));
1315 'attach_id' => $db->sql_nextid(),
1317 'real_filename' => $filedata['real_filename'],
1318 'attach_comment'=> $this->filename_data
['filecomment'],
1321 $this->attachment_data
= array_merge(array(0 => $new_entry), $this->attachment_data
);
1322 $this->message
= preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message
);
1324 $this->filename_data
['filecomment'] = '';
1326 // This Variable is set to false here, because Attachments are entered into the
1327 // Database in two modes, one if the id_list is 0 and the second one if post_attach is true
1328 // Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
1329 // but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
1331 // This is very relevant, because it could happen that the post got not submitted, but we do not
1332 // know this circumstance here. We could be at the posting page or we could be redirected to the entered
1334 $filedata['post_attach'] = false;
1339 $error[] = sprintf($user->lang
['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
1343 if ($preview ||
$refresh ||
sizeof($error))
1345 // Perform actions on temporary attachments
1348 include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1350 $index = (int) key($_POST['delete_file']);
1352 if (!empty($this->attachment_data
[$index]))
1354 // delete selected attachment
1355 if ($this->attachment_data
[$index]['is_orphan'])
1357 $sql = 'SELECT attach_id, physical_filename, thumbnail
1358 FROM ' . ATTACHMENTS_TABLE
. '
1359 WHERE attach_id = ' . (int) $this->attachment_data
[$index]['attach_id'] . '
1361 AND poster_id = ' . $user->data
['user_id'];
1362 $result = $db->sql_query($sql);
1363 $row = $db->sql_fetchrow($result);
1364 $db->sql_freeresult($result);
1368 phpbb_unlink($row['physical_filename'], 'file');
1370 if ($row['thumbnail'])
1372 phpbb_unlink($row['physical_filename'], 'thumbnail');
1375 $db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE
. ' WHERE attach_id = ' . (int) $this->attachment_data
[$index]['attach_id']);
1380 delete_attachments('attach', array(intval($this->attachment_data
[$index]['attach_id'])));
1383 unset($this->attachment_data
[$index]);
1384 $this->message
= preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "(\\1 == \$index) ? '' : ((\\1 > \$index) ? '[attachment=' . (\\1 - 1) . ']\\2[/attachment]' : '\\0')", $this->message
);
1387 $this->attachment_data
= array_values($this->attachment_data
);
1390 else if (($add_file ||
$preview) && $upload_file)
1392 if ($num_attachments < $cfg['max_attachments'] ||
$auth->acl_gets('m_', 'a_', $forum_id))
1394 $filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1395 $error = array_merge($error, $filedata['error']);
1397 if (!sizeof($error))
1400 'physical_filename' => $filedata['physical_filename'],
1401 'attach_comment' => $this->filename_data
['filecomment'],
1402 'real_filename' => $filedata['real_filename'],
1403 'extension' => $filedata['extension'],
1404 'mimetype' => $filedata['mimetype'],
1405 'filesize' => $filedata['filesize'],
1406 'filetime' => $filedata['filetime'],
1407 'thumbnail' => $filedata['thumbnail'],
1409 'in_message' => ($is_message) ?
1 : 0,
1410 'poster_id' => $user->data
['user_id'],
1413 $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE
. ' ' . $db->sql_build_array('INSERT', $sql_ary));
1416 'attach_id' => $db->sql_nextid(),
1418 'real_filename' => $filedata['real_filename'],
1419 'attach_comment'=> $this->filename_data
['filecomment'],
1422 $this->attachment_data
= array_merge(array(0 => $new_entry), $this->attachment_data
);
1423 $this->message
= preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message
);
1424 $this->filename_data
['filecomment'] = '';
1429 $error[] = sprintf($user->lang
['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
1434 foreach ($error as $error_msg)
1436 $this->warn_msg
[] = $error_msg;
1441 * Get Attachment Data
1443 function get_submitted_attachment_data($check_user_id = false)
1445 global $user, $db, $phpbb_root_path, $phpEx, $config;
1447 $this->filename_data
['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
1448 $attachment_data = (isset($_POST['attachment_data'])) ?
$_POST['attachment_data'] : array();
1449 $this->attachment_data
= array();
1451 $check_user_id = ($check_user_id === false) ?
$user->data
['user_id'] : $check_user_id;
1453 if (!sizeof($attachment_data))
1458 $not_orphan = $orphan = array();
1460 foreach ($attachment_data as $pos => $var_ary)
1462 if ($var_ary['is_orphan'])
1464 $orphan[(int) $var_ary['attach_id']] = $pos;
1468 $not_orphan[(int) $var_ary['attach_id']] = $pos;
1472 // Regenerate already posted attachments
1473 if (sizeof($not_orphan))
1475 // Get the attachment data, based on the poster id...
1476 $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
1477 FROM ' . ATTACHMENTS_TABLE
. '
1478 WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '
1479 AND poster_id = ' . $check_user_id;
1480 $result = $db->sql_query($sql);
1482 while ($row = $db->sql_fetchrow($result))
1484 $pos = $not_orphan[$row['attach_id']];
1485 $this->attachment_data
[$pos] = $row;
1486 set_var($this->attachment_data
[$pos]['attach_comment'], $_POST['attachment_data'][$pos]['attach_comment'], 'string', true);
1488 unset($not_orphan[$row['attach_id']]);
1490 $db->sql_freeresult($result);
1493 if (sizeof($not_orphan))
1495 trigger_error($user->lang
['NO_ACCESS_ATTACHMENT'], E_USER_ERROR
);
1498 // Regenerate newly uploaded attachments
1499 if (sizeof($orphan))
1501 $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
1502 FROM ' . ATTACHMENTS_TABLE
. '
1503 WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '
1504 AND poster_id = ' . $user->data
['user_id'] . '
1506 $result = $db->sql_query($sql);
1508 while ($row = $db->sql_fetchrow($result))
1510 $pos = $orphan[$row['attach_id']];
1511 $this->attachment_data
[$pos] = $row;
1512 set_var($this->attachment_data
[$pos]['attach_comment'], $_POST['attachment_data'][$pos]['attach_comment'], 'string', true);
1514 unset($orphan[$row['attach_id']]);
1516 $db->sql_freeresult($result);
1519 if (sizeof($orphan))
1521 trigger_error($user->lang
['NO_ACCESS_ATTACHMENT'], E_USER_ERROR
);
1524 ksort($this->attachment_data
);
1530 function parse_poll(&$poll)
1532 global $auth, $user, $config;
1534 $poll_max_options = $poll['poll_max_options'];
1536 // Parse Poll Option text ;)
1537 $tmp_message = $this->message
;
1538 $this->message
= $poll['poll_option_text'];
1541 $poll['poll_option_text'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ?
$poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false);
1543 $this->message
= $tmp_message;
1546 $tmp_message = $this->message
;
1547 $this->message
= $poll['poll_title'];
1549 $poll['poll_options'] = explode("\n", trim($poll['poll_option_text']));
1550 $poll['poll_options_size'] = sizeof($poll['poll_options']);
1552 if (!$poll['poll_title'] && $poll['poll_options_size'])
1554 $this->warn_msg
[] = $user->lang
['NO_POLL_TITLE'];
1558 if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message
)) > 100)
1560 $this->warn_msg
[] = $user->lang
['POLL_TITLE_TOO_LONG'];
1562 $poll['poll_title'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ?
$poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false);
1563 if (strlen($poll['poll_title']) > 255)
1565 $this->warn_msg
[] = $user->lang
['POLL_TITLE_COMP_TOO_LONG'];
1569 $this->message
= $tmp_message;
1571 unset($tmp_message);
1573 if (sizeof($poll['poll_options']) == 1)
1575 $this->warn_msg
[] = $user->lang
['TOO_FEW_POLL_OPTIONS'];
1577 else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
1579 $this->warn_msg
[] = $user->lang
['TOO_MANY_POLL_OPTIONS'];
1581 else if ($poll_max_options > $poll['poll_options_size'])
1583 $this->warn_msg
[] = $user->lang
['TOO_MANY_USER_OPTIONS'];
1586 $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']);