5
* @version $Id: message_parser.php,v 1.218 2007/11/29 14:09:32 acydburn Exp $
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
// 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".
87
// Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you.
89
/* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
90
if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false)
92
$this->message = str_replace("\r\n", "\n", $this->message);
94
// We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
95
$this->message = preg_replace('#\[quote(=".*?")?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message);
99
// Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
103
* Init bbcode data for later parsing
105
function bbcode_init()
109
// This array holds all bbcode data. BBCodes will be processed in this
110
// order, so it is important to keep [code] in first position and
111
// [quote] in second position.
112
$this->bbcodes = array(
113
'code' => array('bbcode_id' => 8, 'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#ise' => "\$this->bbcode_code('\$1', '\$2')")),
114
'quote' => array('bbcode_id' => 0, 'regexp' => array('#\[quote(?:="(.*?)")?\](.+)\[/quote\]#ise' => "\$this->bbcode_quote('\$0')")),
115
'attachment' => array('bbcode_id' => 12, 'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#ise' => "\$this->bbcode_attachment('\$1', '\$2')")),
116
'b' => array('bbcode_id' => 1, 'regexp' => array('#\[b\](.*?)\[/b\]#ise' => "\$this->bbcode_strong('\$1')")),
117
'i' => array('bbcode_id' => 2, 'regexp' => array('#\[i\](.*?)\[/i\]#ise' => "\$this->bbcode_italic('\$1')")),
118
'url' => array('bbcode_id' => 3, 'regexp' => array('#\[url(=(.*))?\](.*)\[/url\]#iUe' => "\$this->validate_url('\$2', '\$3')")),
119
'img' => array('bbcode_id' => 4, 'regexp' => array('#\[img\](.*)\[/img\]#iUe' => "\$this->bbcode_img('\$1')")),
120
'size' => array('bbcode_id' => 5, 'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#ise' => "\$this->bbcode_size('\$1', '\$2')")),
121
'color' => array('bbcode_id' => 6, 'regexp' => array('!\[color=(#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!ise' => "\$this->bbcode_color('\$1', '\$2')")),
122
'u' => array('bbcode_id' => 7, 'regexp' => array('#\[u\](.*?)\[/u\]#ise' => "\$this->bbcode_underline('\$1')")),
123
'list' => array('bbcode_id' => 9, 'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#ise' => "\$this->bbcode_parse_list('\$0')")),
124
'email' => array('bbcode_id' => 10, 'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#ise' => "\$this->validate_email('\$1', '\$2')")),
125
'flash' => array('bbcode_id' => 11, 'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ie' => "\$this->bbcode_flash('\$1', '\$2', '\$3')"))
128
// Zero the parsed items array
129
$this->parsed_items = array();
131
foreach ($this->bbcodes as $tag => $bbcode_data)
133
$this->parsed_items[$tag] = 0;
136
if (!is_array($rowset))
142
FROM ' . BBCODES_TABLE;
143
$result = $db->sql_query($sql);
145
while ($row = $db->sql_fetchrow($result))
149
$db->sql_freeresult($result);
152
foreach ($rowset as $row)
154
$this->bbcodes[$row['bbcode_tag']] = array(
155
'bbcode_id' => (int) $row['bbcode_id'],
156
'regexp' => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
162
* Making some pre-checks for bbcodes as well as increasing the number of parsed items
164
function check_bbcode($bbcode, &$in)
166
// when using the /e modifier, preg_replace slashes double-quotes but does not
167
// seem to slash anything else
168
$in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
170
// Trimming here to make sure no empty bbcodes are parsed accidently
176
$this->parsed_items[$bbcode]++;
182
* Transform some characters in valid bbcodes
184
function bbcode_specialchars($text)
186
$str_from = array('<', '>', '[', ']', '.', ':');
187
$str_to = array('<', '>', '[', ']', '.', ':');
189
return str_replace($str_from, $str_to, $text);
195
function bbcode_size($stx, $in)
197
global $user, $config;
199
if (!$this->check_bbcode('size', $in))
204
if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
206
$this->warn_msg[] = sprintf($user->lang['MAX_FONT_SIZE_EXCEEDED'], $config['max_' . $this->mode . '_font_size']);
208
return '[size=' . $stx . ']' . $in . '[/size]';
211
// Do not allow size=0
214
return '[size=' . $stx . ']' . $in . '[/size]';
217
return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
223
function bbcode_color($stx, $in)
225
if (!$this->check_bbcode('color', $in))
230
return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
236
function bbcode_underline($in)
238
if (!$this->check_bbcode('u', $in))
243
return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
249
function bbcode_strong($in)
251
if (!$this->check_bbcode('b', $in))
256
return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
262
function bbcode_italic($in)
264
if (!$this->check_bbcode('i', $in))
269
return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
275
function bbcode_img($in)
277
global $user, $config;
279
if (!$this->check_bbcode('img', $in))
287
$in = str_replace(' ', '%20', $in);
290
if (!preg_match('#^' . get_preg_expression('url') . '$#i', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#i', $in))
292
return '[img]' . $in . '[/img]';
295
// Try to cope with a common user error... not specifying a protocol but only a subdomain
296
if (!preg_match('#^[a-z0-9]+://#i', $in))
298
$in = 'http://' . $in;
301
if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
303
$stats = @getimagesize($in);
305
if ($stats === false)
308
$this->warn_msg[] = $user->lang['UNABLE_GET_IMAGE_SIZE'];
312
if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $stats[1])
315
$this->warn_msg[] = sprintf($user->lang['MAX_IMG_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']);
318
if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $stats[0])
321
$this->warn_msg[] = sprintf($user->lang['MAX_IMG_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']);
326
if ($error || $this->path_in_domain($in))
328
return '[img]' . $in . '[/img]';
331
return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
337
function bbcode_flash($width, $height, $in)
339
global $user, $config;
341
if (!$this->check_bbcode('flash', $in))
349
// Apply the same size checks on flash files as on images
350
if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
352
if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)
355
$this->warn_msg[] = sprintf($user->lang['MAX_FLASH_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']);
358
if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)
361
$this->warn_msg[] = sprintf($user->lang['MAX_FLASH_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']);
365
if ($error || $this->path_in_domain($in))
367
return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
370
return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
374
* Parse inline attachments [ia]
376
function bbcode_attachment($stx, $in)
378
if (!$this->check_bbcode('attachment', $in))
383
return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
387
* Parse code text from code tag
390
function bbcode_parse_code($stx, &$code)
392
switch (strtolower($stx))
396
$remove_tags = false;
397
$code = str_replace(array('<', '>'), array('<', '>'), $code);
399
if (!preg_match('/\<\?.*?\?\>/is', $code))
402
$code = "<?php $code ?>";
405
$conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
406
foreach ($conf as $ini_var)
408
@ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
411
// Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
412
$code = htmlspecialchars_decode($code);
413
$code = highlight_string($code, true);
415
$str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
416
$str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '[', ']', '.', ':');
420
$str_from[] = '<span class="syntaxdefault"><?php </span>';
422
$str_from[] = '<span class="syntaxdefault"><?php ';
423
$str_to[] = '<span class="syntaxdefault">';
426
$code = str_replace($str_from, $str_to, $code);
427
$code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
431
$code = preg_replace('#(<span class="[a-z]+">)?\?>(</span>)#', '$1 $2', $code);
434
$code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
435
$code = preg_replace('#(?:\s++| )*+</span>$#u', '</span>', $code);
437
// remove newline at the end
438
if (!empty($code) && substr($code, -1) == "\n")
440
$code = substr($code, 0, -1);
443
return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
447
return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
454
* Expects the argument to start right after the opening [code] tag and to end with [/code]
456
function bbcode_code($stx, $in)
458
if (!$this->check_bbcode('code', $in))
463
// We remove the hardcoded elements from the code block here because it is not used in code blocks
464
// Having it here saves us one preg_replace per message containing [code] blocks
465
// Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
466
$htm_match = get_preg_expression('bbcode_htm');
467
unset($htm_match[4], $htm_match[5]);
468
$htm_replace = array('\1', '\1', '\2', '\1');
470
$out = $code_block = '';
475
// Determine position and tag length of next code block
476
preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
477
$pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
478
$tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;
480
// Determine position of ending code tag
481
$pos2 = stripos($in, '[/code]');
483
// Which is the next block, ending code or code block
484
if ($pos !== false && $pos < $pos2)
489
$out .= substr($in, 0, $pos);
490
$in = substr($in, $pos);
491
$stx = (isset($buffer[3])) ? $buffer[3] : '';
496
// Already opened block, just append to the current block
497
$code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
498
$in = substr($in, $pos);
501
$in = substr($in, $tag_length);
509
$code_block .= substr($in, 0, $pos2);
510
$code_block = preg_replace($htm_match, $htm_replace, $code_block);
512
// Parse this code block
513
$out .= $this->bbcode_parse_code($stx, $code_block);
519
// Close one open tag... add to the current code block
520
$code_block .= substr($in, 0, $pos2 + 7);
525
// end code without opening code... will be always outside code block
526
$out .= substr($in, 0, $pos2 + 7);
529
$in = substr($in, $pos2 + 7);
533
// if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
536
$code_block = substr($code_block, 0, -7);
537
$code_block = preg_replace($htm_match, $htm_replace, $code_block);
539
$out .= $this->bbcode_parse_code($stx, $code_block);
547
* Expects the argument to start with a tag
549
function bbcode_parse_list($in)
551
if (!$this->check_bbcode('list', $in))
556
// $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
560
// First character is [
561
$in = substr($in, 1);
562
$list_end_tags = $item_end_tags = array();
568
for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
570
$tmp_pos = strpos($in, $tok[$i]);
572
if ($tmp_pos !== false && $tmp_pos < $pos)
578
$buffer = substr($in, 0, $pos);
581
$in = substr($in, $pos + 1);
585
// if $tok is ']' the buffer holds a tag
586
if (strtolower($buffer) == '/list' && sizeof($list_end_tags))
588
// valid [/list] tag, check nesting so that we don't hit false positives
589
if (sizeof($item_end_tags) && sizeof($item_end_tags) >= sizeof($list_end_tags))
591
// current li tag has not been closed
592
$out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
595
$out .= array_pop($list_end_tags) . ']';
598
else if (preg_match('#^list(=[0-9a-z])?$#i', $buffer, $m))
600
// sub-list, add a closing tag
601
if (empty($m[1]) || preg_match('/^(?:disc|square|circle)$/i', $m[1]))
603
array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
607
array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
609
$out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
614
if (($buffer == '*' || substr($buffer, -2) == '[*') && sizeof($list_end_tags))
616
// the buffer holds a bullet tag and we have a [list] tag open
617
if (sizeof($item_end_tags) >= sizeof($list_end_tags))
619
if (substr($buffer, -2) == '[*')
621
$out .= substr($buffer, 0, -2) . '[';
623
// current li tag has not been closed
624
if (preg_match('/\n\[$/', $out, $m))
626
$out = preg_replace('/\n\[$/', '[', $out);
627
$buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
631
$buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
636
$buffer = '*:' . $this->bbcode_uid;
639
$item_end_tags[] = '/*:m:' . $this->bbcode_uid;
641
else if ($buffer == '/*')
643
array_pop($item_end_tags);
644
$buffer = '/*:' . $this->bbcode_uid;
647
$out .= $buffer . $tok;
653
// Not within a tag, just add buffer to the return string
654
$out .= $buffer . $tok;
655
$tok = ($tok == '[') ? ']' : '[]';
660
// do we have some tags open? close them now
661
if (sizeof($item_end_tags))
663
$out .= '[' . implode('][', $item_end_tags) . ']';
665
if (sizeof($list_end_tags))
667
$out .= '[' . implode('][', $list_end_tags) . ']';
675
* Expects the argument to start with a tag
677
function bbcode_quote($in)
679
global $config, $user;
682
* If you change this code, make sure the cases described within the following reports are still working:
683
* #3572 - [quote="[test]test"]test [ test[/quote] - (correct: parsed)
684
* #14667 - [quote]test[/quote] test ] and [ test [quote]test[/quote] (correct: parsed)
685
* #14770 - [quote="["]test[/quote] (correct: parsed)
686
* [quote="[i]test[/i]"]test[/quote] (correct: parsed)
687
* [quote="[quote]test[/quote]"]test[/quote] (correct: NOT parsed)
690
$in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
697
// To let the parser not catch tokens within quote_username quotes we encode them before we start this...
698
$in = preg_replace('#quote="(.*?)"\]#ie', "'quote="' . str_replace(array('[', ']'), array('[', ']'), '\$1') . '"]'", $in);
703
$in = substr($in, 1);
704
$close_tags = $error_ary = array();
710
for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
712
$tmp_pos = strpos($in, $tok[$i]);
713
if ($tmp_pos !== false && $tmp_pos < $pos)
719
$buffer .= substr($in, 0, $pos);
721
$in = substr($in, $pos + 1);
725
if (strtolower($buffer) == '/quote' && sizeof($close_tags) && substr($out, -1, 1) == '[')
727
// we have found a closing tag
728
$out .= array_pop($close_tags) . ']';
732
/* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
733
* 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.
734
* Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
735
if (!$in || $in[0] !== ' ')
740
else if (preg_match('#^quote(?:="(.*?)")?$#is', $buffer, $m))
742
$this->parsed_items['quote']++;
744
// the buffer holds a valid opening tag
745
if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])
747
// there are too many nested quotes
748
$error_ary['quote_depth'] = sprintf($user->lang['QUOTE_DEPTH_EXCEEDED'], $config['max_quote_depth']);
750
$out .= $buffer . $tok;
757
array_push($close_tags, '/quote:' . $this->bbcode_uid);
759
if (isset($m[1]) && $m[1])
761
$username = str_replace(array('[', ']'), array('[', ']'), $m[1]);
762
$username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '[$1', $username);
767
preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
768
foreach ($tags[1] as $tag)
772
$end_tags[] = '/' . $tag;
776
$end_tag = array_pop($end_tags);
777
$error = ($end_tag != $tag) ? true : false;
786
$out .= 'quote="' . $username . '":' . $this->bbcode_uid . ']';
790
$out .= 'quote:' . $this->bbcode_uid . ']';
796
else if (preg_match('#^quote="(.*?)#is', $buffer, $m))
798
// the buffer holds an invalid opening tag
803
$out .= $buffer . $tok;
811
* Old quote code working fine, but having errors listed in bug #3572
813
* $out .= $buffer . $tok;
814
* $tok = ($tok == '[') ? ']' : '[]';
818
$out .= $buffer . $tok;
822
// Search the text for the next tok... if an ending quote comes first, then change tok to []
823
$pos1 = stripos($in, '[/quote');
824
// If the token ] comes first, we change it to ]
825
$pos2 = strpos($in, ']');
826
// If the token [ comes first, we change it to [
827
$pos3 = strpos($in, '[');
829
if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
833
else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
851
if (sizeof($close_tags))
853
$out .= '[' . implode('][', $close_tags) . ']';
856
foreach ($error_ary as $error_msg)
858
$this->warn_msg[] = $error_msg;
867
function validate_email($var1, $var2)
869
$var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
870
$var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
873
$email = ($var1) ? $var1 : $var2;
877
if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
884
return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
887
$this->parsed_items['email']++;
891
$retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
895
$retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
904
* @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
905
* @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
907
function validate_url($var1, $var2)
911
$var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
912
$var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
914
$url = ($var1) ? $var1 : $var2;
916
if (!$url || ($var1 && !$var2))
923
$url = str_replace(' ', '%20', $url);
926
if (preg_match('#^' . get_preg_expression('url') . '$#i', $url) ||
927
preg_match('#^' . get_preg_expression('www_url') . '$#i', $url) ||
928
preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#i', $url))
935
$this->parsed_items['url']++;
937
// if there is no scheme, then add http schema
938
if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
940
$url = 'http://' . $url;
943
// Is this a link to somewhere inside this board? If so then remove the session id from the url
944
if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
946
$url = preg_replace('/(&|\?)sid=[0-9a-f]{32}&/', '\1', $url);
947
$url = preg_replace('/(&|\?)sid=[0-9a-f]{32}$/', '', $url);
948
$url = append_sid($url);
951
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 . ']';
954
return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
958
* Check if url is pointing to this domain/script_path/php-file
960
* @param string $url the url to check
961
* @return true if the url is pointing to this domain/script_path/php-file, false if not
965
function path_in_domain($url)
967
global $config, $phpEx, $user;
969
if ($config['force_server_vars'])
971
$check_path = $config['script_path'];
975
$check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
978
// Is the user trying to link to a php file in this domain and script path?
979
if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
981
$server_name = (!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME');
983
// Forcing server vars is the only way to specify/override the protocol
984
if ($config['force_server_vars'] || !$server_name)
986
$server_name = $config['server_name'];
989
// Check again in correct order...
990
$pos_ext = strpos($url, ".{$phpEx}");
991
$pos_path = strpos($url, $check_path);
992
$pos_domain = strpos($url, $server_name);
994
if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
996
// Ok, actually we allow linking to some files (this may be able to be extended in some way later...)
997
if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0)
1011
* Main message parser for posting, pm, etc. takes raw message
1012
* and parses it for attachments, bbcode and smilies
1015
class parse_message extends bbcode_firstpass
1017
var $attachment_data = array();
1018
var $filename_data = array();
1020
// Helps ironing out user error
1021
var $message_status = '';
1023
var $allow_img_bbcode = true;
1024
var $allow_flash_bbcode = true;
1025
var $allow_quote_bbcode = true;
1026
var $allow_url_bbcode = true;
1031
* Init - give message here or manually
1033
function parse_message($message = '')
1036
$this->bbcode_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN);
1040
$this->message = $message;
1047
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')
1049
global $config, $db, $user;
1051
$mode = ($mode != 'post') ? 'sig' : 'post';
1053
$this->mode = $mode;
1055
$this->allow_img_bbcode = $allow_img_bbcode;
1056
$this->allow_flash_bbcode = $allow_flash_bbcode;
1057
$this->allow_quote_bbcode = $allow_quote_bbcode;
1058
$this->allow_url_bbcode = $allow_url_bbcode;
1060
// If false, then $this->message won't be altered, the text will be returned instead.
1061
if (!$update_this_message)
1063
$tmp_message = $this->message;
1064
$return_message = &$this->message;
1067
if ($this->message_status == 'display')
1069
$this->decode_message();
1072
// Do some general 'cleanup' first before processing message,
1073
// e.g. remove excessive newlines(?), smilies(?)
1074
$match = array('#(script|about|applet|activex|chrome):#i');
1075
$replace = array("\\1:");
1076
$this->message = preg_replace($match, $replace, trim($this->message));
1078
// Message length check. 0 disables this check completely.
1079
if ($config['max_' . $mode . '_chars'] > 0)
1081
$msg_len = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message));
1083
if ((!$msg_len && $mode !== 'sig') || $config['max_' . $mode . '_chars'] && $msg_len > $config['max_' . $mode . '_chars'])
1085
$this->warn_msg[] = (!$msg_len) ? $user->lang['TOO_FEW_CHARS'] : sprintf($user->lang['TOO_MANY_CHARS_' . strtoupper($mode)], $msg_len, $config['max_' . $mode . '_chars']);
1086
return $this->warn_msg;
1090
// Check for "empty" message
1091
if ($mode !== 'sig' && !utf8_clean_string($this->message))
1093
$this->warn_msg[] = $user->lang['TOO_FEW_CHARS'];
1094
return $this->warn_msg;
1097
// Prepare BBcode (just prepares some tags for better parsing)
1098
if ($allow_bbcode && strpos($this->message, '[') !== false)
1100
$this->bbcode_init();
1101
$disallow = array('img', 'flash', 'quote', 'url');
1102
foreach ($disallow as $bool)
1104
if (!${'allow_' . $bool . '_bbcode'})
1106
$this->bbcodes[$bool]['disabled'] = true;
1110
$this->prepare_bbcodes();
1116
$this->smilies($config['max_' . $mode . '_smilies']);
1122
if ($allow_bbcode && strpos($this->message, '[') !== false)
1124
$this->parse_bbcode();
1125
$num_urls += $this->parsed_items['url'];
1129
if ($allow_magic_url)
1131
$this->magic_url(generate_board_url());
1133
if ($config['max_' . $mode . '_urls'])
1135
$num_urls += preg_match_all('#\<!-- ([lmwe]) --\>.*?\<!-- \1 --\>#', $this->message, $matches);
1139
// Check number of links
1140
if ($config['max_' . $mode . '_urls'] && $num_urls > $config['max_' . $mode . '_urls'])
1142
$this->warn_msg[] = sprintf($user->lang['TOO_MANY_URLS'], $config['max_' . $mode . '_urls']);
1143
return $this->warn_msg;
1146
if (!$update_this_message)
1148
unset($this->message);
1149
$this->message = $tmp_message;
1150
return $return_message;
1153
$this->message_status = 'parsed';
1158
* Formatting text for display
1160
function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
1162
// If false, then the parsed message get returned but internal message not processed.
1163
if (!$update_this_message)
1165
$tmp_message = $this->message;
1166
$return_message = &$this->message;
1169
if ($this->message_status == 'plain')
1171
// Force updating message - of course.
1172
$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);
1175
// Replace naughty words such as farty pants
1176
$this->message = censor_text($this->message);
1181
$this->bbcode_cache_init();
1183
// We are giving those parameters to be able to use the bbcode class on its own
1184
$this->bbcode_second_pass($this->message, $this->bbcode_uid);
1187
$this->message = bbcode_nl2br($this->message);
1188
$this->message = smiley_text($this->message, !$allow_smilies);
1190
if (!$update_this_message)
1192
unset($this->message);
1193
$this->message = $tmp_message;
1194
return $return_message;
1197
$this->message_status = 'display';
1202
* Decode message to be placed back into form box
1204
function decode_message($custom_bbcode_uid = '', $update_this_message = true)
1206
// If false, then the parsed message get returned but internal message not processed.
1207
if (!$update_this_message)
1209
$tmp_message = $this->message;
1210
$return_message = &$this->message;
1213
($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);
1215
if (!$update_this_message)
1217
unset($this->message);
1218
$this->message = $tmp_message;
1219
return $return_message;
1222
$this->message_status = 'plain';
1227
* Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
1228
* Cuts down displayed size of link if over 50 chars, turns absolute links
1229
* into relative versions when the server/script path matches the link
1231
function magic_url($server_url)
1233
// We use the global make_clickable function
1234
$this->message = make_clickable($this->message, $server_url);
1240
function smilies($max_smilies = 0)
1246
// See if the static arrays have already been filled on an earlier invocation
1247
if (!is_array($match))
1249
$match = $replace = array();
1251
// NOTE: obtain_* function? chaching the table contents?
1253
// For now setting the ttl to 10 minutes
1254
switch ($db->sql_layer)
1259
FROM ' . SMILIES_TABLE . '
1260
ORDER BY LEN(code) DESC';
1265
FROM ' . SMILIES_TABLE . '
1266
ORDER BY CHAR_LENGTH(code) DESC';
1269
// LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
1272
FROM ' . SMILIES_TABLE . '
1273
ORDER BY LENGTH(code) DESC';
1276
$result = $db->sql_query($sql, 600);
1278
while ($row = $db->sql_fetchrow($result))
1280
if (empty($row['code']))
1286
$match[] = '(?<=^|[\n .])' . preg_quote($row['code'], '#') . '(?![^<>]*>)';
1287
$replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
1289
$db->sql_freeresult($result);
1296
$num_matches = preg_match_all('#' . implode('|', $match) . '#', $this->message, $matches);
1299
if ($num_matches !== false && $num_matches > $max_smilies)
1301
$this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
1306
// Make sure the delimiter # is added in front and at the end of every element within $match
1307
$this->message = trim(preg_replace(explode(chr(0), '#' . implode('#' . chr(0) . '#', $match) . '#'), $replace, $this->message));
1314
function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
1316
global $config, $auth, $user, $phpbb_root_path, $phpEx, $db;
1320
$num_attachments = sizeof($this->attachment_data);
1321
$this->filename_data['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
1322
$upload_file = (isset($_FILES[$form_name]) && $_FILES[$form_name]['name'] != 'none' && trim($_FILES[$form_name]['name'])) ? true : false;
1324
$add_file = (isset($_POST['add_file'])) ? true : false;
1325
$delete_file = (isset($_POST['delete_file'])) ? true : false;
1327
// First of all adjust comments if changed
1328
$actual_comment_list = utf8_normalize_nfc(request_var('comment_list', array(''), true));
1330
foreach ($actual_comment_list as $comment_key => $comment)
1332
if (!isset($this->attachment_data[$comment_key]))
1337
if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
1339
$this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
1344
$cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
1345
$forum_id = ($is_message) ? 0 : $forum_id;
1347
if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
1349
if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
1351
$filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1352
$error = $filedata['error'];
1354
if ($filedata['post_attach'] && !sizeof($error))
1357
'physical_filename' => $filedata['physical_filename'],
1358
'attach_comment' => $this->filename_data['filecomment'],
1359
'real_filename' => $filedata['real_filename'],
1360
'extension' => $filedata['extension'],
1361
'mimetype' => $filedata['mimetype'],
1362
'filesize' => $filedata['filesize'],
1363
'filetime' => $filedata['filetime'],
1364
'thumbnail' => $filedata['thumbnail'],
1366
'in_message' => ($is_message) ? 1 : 0,
1367
'poster_id' => $user->data['user_id'],
1370
$db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1373
'attach_id' => $db->sql_nextid(),
1375
'real_filename' => $filedata['real_filename'],
1376
'attach_comment'=> $this->filename_data['filecomment'],
1379
$this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1380
$this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);
1382
$this->filename_data['filecomment'] = '';
1384
// This Variable is set to false here, because Attachments are entered into the
1385
// Database in two modes, one if the id_list is 0 and the second one if post_attach is true
1386
// Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
1387
// but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
1389
// This is very relevant, because it could happen that the post got not submitted, but we do not
1390
// know this circumstance here. We could be at the posting page or we could be redirected to the entered
1392
$filedata['post_attach'] = false;
1397
$error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
1401
if ($preview || $refresh || sizeof($error))
1403
// Perform actions on temporary attachments
1406
include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1408
$index = array_keys(request_var('delete_file', array(0 => 0)));
1409
$index = (!empty($index)) ? $index[0] : false;
1411
if ($index !== false && !empty($this->attachment_data[$index]))
1413
// delete selected attachment
1414
if ($this->attachment_data[$index]['is_orphan'])
1416
$sql = 'SELECT attach_id, physical_filename, thumbnail
1417
FROM ' . ATTACHMENTS_TABLE . '
1418
WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id'] . '
1420
AND poster_id = ' . $user->data['user_id'];
1421
$result = $db->sql_query($sql);
1422
$row = $db->sql_fetchrow($result);
1423
$db->sql_freeresult($result);
1427
phpbb_unlink($row['physical_filename'], 'file');
1429
if ($row['thumbnail'])
1431
phpbb_unlink($row['physical_filename'], 'thumbnail');
1434
$db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE . ' WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id']);
1439
delete_attachments('attach', array(intval($this->attachment_data[$index]['attach_id'])));
1442
unset($this->attachment_data[$index]);
1443
$this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "(\\1 == \$index) ? '' : ((\\1 > \$index) ? '[attachment=' . (\\1 - 1) . ']\\2[/attachment]' : '\\0')", $this->message);
1446
$this->attachment_data = array_values($this->attachment_data);
1449
else if (($add_file || $preview) && $upload_file)
1451
if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_', $forum_id))
1453
$filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1454
$error = array_merge($error, $filedata['error']);
1456
if (!sizeof($error))
1459
'physical_filename' => $filedata['physical_filename'],
1460
'attach_comment' => $this->filename_data['filecomment'],
1461
'real_filename' => $filedata['real_filename'],
1462
'extension' => $filedata['extension'],
1463
'mimetype' => $filedata['mimetype'],
1464
'filesize' => $filedata['filesize'],
1465
'filetime' => $filedata['filetime'],
1466
'thumbnail' => $filedata['thumbnail'],
1468
'in_message' => ($is_message) ? 1 : 0,
1469
'poster_id' => $user->data['user_id'],
1472
$db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1475
'attach_id' => $db->sql_nextid(),
1477
'real_filename' => $filedata['real_filename'],
1478
'attach_comment'=> $this->filename_data['filecomment'],
1481
$this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1482
$this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);
1483
$this->filename_data['filecomment'] = '';
1488
$error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
1493
foreach ($error as $error_msg)
1495
$this->warn_msg[] = $error_msg;
1500
* Get Attachment Data
1502
function get_submitted_attachment_data($check_user_id = false)
1504
global $user, $db, $phpbb_root_path, $phpEx, $config;
1506
$this->filename_data['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
1507
$attachment_data = (isset($_POST['attachment_data'])) ? $_POST['attachment_data'] : array();
1508
$this->attachment_data = array();
1510
$check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id;
1512
if (!sizeof($attachment_data))
1517
$not_orphan = $orphan = array();
1519
foreach ($attachment_data as $pos => $var_ary)
1521
if ($var_ary['is_orphan'])
1523
$orphan[(int) $var_ary['attach_id']] = $pos;
1527
$not_orphan[(int) $var_ary['attach_id']] = $pos;
1531
// Regenerate already posted attachments
1532
if (sizeof($not_orphan))
1534
// Get the attachment data, based on the poster id...
1535
$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
1536
FROM ' . ATTACHMENTS_TABLE . '
1537
WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '
1538
AND poster_id = ' . $check_user_id;
1539
$result = $db->sql_query($sql);
1541
while ($row = $db->sql_fetchrow($result))
1543
$pos = $not_orphan[$row['attach_id']];
1544
$this->attachment_data[$pos] = $row;
1545
set_var($this->attachment_data[$pos]['attach_comment'], $_POST['attachment_data'][$pos]['attach_comment'], 'string', true);
1547
unset($not_orphan[$row['attach_id']]);
1549
$db->sql_freeresult($result);
1552
if (sizeof($not_orphan))
1554
trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1557
// Regenerate newly uploaded attachments
1558
if (sizeof($orphan))
1560
$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
1561
FROM ' . ATTACHMENTS_TABLE . '
1562
WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '
1563
AND poster_id = ' . $user->data['user_id'] . '
1565
$result = $db->sql_query($sql);
1567
while ($row = $db->sql_fetchrow($result))
1569
$pos = $orphan[$row['attach_id']];
1570
$this->attachment_data[$pos] = $row;
1571
set_var($this->attachment_data[$pos]['attach_comment'], $_POST['attachment_data'][$pos]['attach_comment'], 'string', true);
1573
unset($orphan[$row['attach_id']]);
1575
$db->sql_freeresult($result);
1578
if (sizeof($orphan))
1580
trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1583
ksort($this->attachment_data);
1589
function parse_poll(&$poll)
1591
global $auth, $user, $config;
1593
$poll_max_options = $poll['poll_max_options'];
1595
// Parse Poll Option text ;)
1596
$tmp_message = $this->message;
1597
$this->message = $poll['poll_option_text'];
1598
$bbcode_bitfield = $this->bbcode_bitfield;
1601
$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);
1603
$bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield));
1604
$this->message = $tmp_message;
1607
$tmp_message = $this->message;
1608
$this->message = $poll['poll_title'];
1609
$this->bbcode_bitfield = $bbcode_bitfield;
1611
$poll['poll_options'] = explode("\n", trim($poll['poll_option_text']));
1612
$poll['poll_options_size'] = sizeof($poll['poll_options']);
1614
if (!$poll['poll_title'] && $poll['poll_options_size'])
1616
$this->warn_msg[] = $user->lang['NO_POLL_TITLE'];
1620
if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)) > 100)
1622
$this->warn_msg[] = $user->lang['POLL_TITLE_TOO_LONG'];
1624
$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);
1625
if (strlen($poll['poll_title']) > 255)
1627
$this->warn_msg[] = $user->lang['POLL_TITLE_COMP_TOO_LONG'];
1631
$this->bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield));
1632
$this->message = $tmp_message;
1633
unset($tmp_message);
1635
if (sizeof($poll['poll_options']) == 1)
1637
$this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS'];
1639
else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
1641
$this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS'];
1643
else if ($poll_max_options > $poll['poll_options_size'])
1645
$this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS'];
1648
$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']);
b'\\ No newline at end of file'