do nothing (*correction ... throw an error ... quick change of mind!), leave tag unprocessed ... also need size limit checks on img/flash tags ... probably warrants some discussion)
*/
// case-insensitive strpos() - needed for some functions
if (!function_exists('stripos'))
{
function stripos($haystack, $needle)
{
if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
{
return strpos($haystack, $m[0]);
}
return FALSE;
}
}
// Main message parser for posting, pm, etc. takes raw message
// and parses it for attachments, html, bbcode and smilies
class parse_message
{
var $bbcode_tpl = null;
var $message_mode = 0; // MSG_POST/MSG_PM
var $bbcode_uid = '';
var $bbcode_bitfield = 0;
var $bbcode_array = array();
var $message = '';
var $disabled_bbcodes = array();
var $attachment_data = array();
var $filename_data = array();
function parse_message($message_type)
{
$this->message_mode = $message_type;
$this->bbcode_uid = substr(md5(time()), 0, BBCODE_UID_LEN);
}
function parse($html, $bbcode, $url, $smilies, $bbcode_img = TRUE, $bbcode_flash = TRUE)
{
global $config, $db, $user;
$warn_msg = '';
// Do some general 'cleanup' first before processing message,
// e.g. remove excessive newlines(?), smilies(?)
$match = array('#sid=[a-z0-9]*?&?#', "#([\r\n][\s]+){3,}#");
$replace = array('', "\n\n");
$this->message = trim(preg_replace($match, $replace, $this->message));
// Message length check
if (!strlen($this->message) || (intval($config['max_post_chars']) && strlen($this->message) > intval($config['max_post_chars'])))
{
$warn_msg .= (($warn_msg != '') ? '
' : '') . (!strlen($this->message)) ? $user->lang['TOO_FEW_CHARS'] : $user->lang['TOO_MANY_CHARS'];
}
// Smiley check
if (intval($config['max_post_smilies']) && $smilies)
{
$sql = "SELECT code
FROM " . SMILIES_TABLE;
$result = $db->sql_query($sql);
$match = 0;
while ($row = $db->sql_fetchrow($result))
{
if (preg_match_all('#('. preg_quote($row['code'], '#') . ')#', $this->message, $matches))
{
$match++;
}
if ($match > intval($config['max_post_smilies']))
{
$warn_msg .= (($warn_msg != '') ? '
' : '') . $user->lang['TOO_MANY_SMILIES'];
break;
}
}
$db->sql_freeresult($result);
unset($matches);
}
if ($warn_msg)
{
return $warn_msg;
}
$warn_msg .= (($warn_msg != '') ? '
' : '') . $this->html($html);
if ($bbcode)
{
if (!$bbcode_img)
{
$this->disabled_bbcodes['img'] = TRUE;
}
if (!$bbcode_flash)
{
$this->disabled_bbcodes['flash'] = TRUE;
}
$warn_msg .= (($warn_msg != '') ? '
' : '') . $this->bbcode();
}
$warn_msg .= (($warn_msg != '') ? '
' : '') . $this->emoticons($smilies);
$warn_msg .= (($warn_msg != '') ? '
' : '') . $this->magic_url($url);
return $warn_msg;
}
function html($html)
{
global $config;
$this->message = str_replace(array('<', '>'), array('<', '>'), $this->message);
if ($html)
{
// If $html is true then "allowed_tags" are converted back from entity
// form, others remain
$allowed_tags = split(',', $config['allow_html_tags']);
if (sizeof($allowed_tags))
{
$this->message = preg_replace('#<(\/?)(' . str_replace('*', '.*?', implode('|', $allowed_tags)) . ')>#is', '<\1\2>', $this->message);
}
}
return;
}
function bbcode()
{
$this->bbcode_init();
$this->bbcode_bitfield = 0;
$size = strlen($this->message);
foreach ($this->bbcode_array as $bbcode_id => $row)
{
$parse = FALSE;
foreach ($row as $regex => $replacement)
{
$this->message = preg_replace($regex, $replacement, $this->message);
}
// Since we add bbcode_uid to all tags, the message length will increase whenever a tag is found
$new_size = strlen($this->message);
if ($size != $new_size)
{
$this->bbcode_bitfield += pow(2, $bbcode_id);
$size = $new_size;
}
}
}
function bbcode_init()
{
static $rowset;
// Always parse [code] first
// [quote] moved to the second position
$this->bbcode_array = array(
8 => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#ise' => "\$this->bbcode_code('\\1', '\\2')"),
0 => array('#\[quote(?:="(.*?)")\](.+)\[/quote\]#ise'=> "\$this->bbcode_quote('\\0')"),
// TODO: validation regexp
11 => array('#\[flash\](.*?)\[/flash\]#i' => '[flash:' . $this->bbcode_uid . ']\1[/flash:' . $this->bbcode_uid . ']'),
10 => array('#\[email(=.*?)?\](.*?)\[/email\]#ise' => "\$this->validate_email('\\1', '\\2')"),
9 => array('#\[list(=[a-z|0-1]+)?\].*\[/list\]#ise' => "\$this->bbcode_list('\\0')"),
7 => array('#\[u\](.*?)\[/u\]#is' => '[u:' . $this->bbcode_uid . ']\1[/u:' . $this->bbcode_uid . ']'),
6 => array('!\[color=(#[0-9A-F]{6}|[a-z\-]+)\](.*?)\[/color\]!is'
=> '[color=\1:' . $this->bbcode_uid . ']\2[/color:' . $this->bbcode_uid . ']'),
5 => array('#\[size=([\-\+]?[1-2]?[0-9])\](.*?)\[/size\]#is'
=> '[size=\1:' . $this->bbcode_uid . ']\2[/size:' . $this->bbcode_uid . ']'),
4 => array('#\[img\](https?://)([a-z0-9\-\.,\?!%\*_:;~\\&$@/=\+]+)\[/img\]#i'
=> '[img:' . $this->bbcode_uid . ']\1\2[/img:' . $this->bbcode_uid . ']'),
3 => array('#\[url=?(.*?)?\](.*?)\[/url\]#ise' => "\$this->validate_url('\\1', '\\2')"),
2 => array('#\[i\](.*?)\[/i\]#is' => '[i:' . $this->bbcode_uid . ']\1[/i:' . $this->bbcode_uid . ']'),
1 => array('#\[b\](.*?)\[/b\]#is' => '[b:' . $this->bbcode_uid . ']\1[/b:' . $this->bbcode_uid . ']')
);
if (!empty($this->disabled_bbcodes['img']))
{
unset($this->bbcode_array[4]);
}
if (!empty($this->disabled_bbcodes['flash']))
{
unset($this->bbcode_array[11]);
}
/**************
if (!isset($rowset))
{
global $db;
$rowset = array();
$result = $db->sql_query('SELECT bbcode_id, first_pass_regexp, first_pass_replacement FROM ' . BBCODES_TABLE);
while ($row = $db->sql_fetchrow($result))
{
$rowset[] = $row;
}
}
foreach ($rowset as $row)
{
$this->bbcode_array[intval($row['bbcode_id'])] = array($row['first_pass_regexp'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replacement']));
}
**************/
}
function bbcode_quote_username($username)
{
echo "
"; if (!$username) { return ''; } // Will do some stuff at some point (will hopefully prevent from breaking out of quotes) $username = stripslashes($username); return '="' . $username . '"'; } // Expects the argument to start right after the opening [code] tag and to end with [/code] function bbcode_code($stx, $in) { // if I remember correctly, preg_replace() will slash passed vars $in = str_replace("\r\n", "\n", stripslashes($in)); $out = ''; do { $pos = stripos($in, '[/code]') + 7; $code = substr($in, 0, $pos); $in = substr($in, $pos); // $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 while ($in) { $pos = stripos($in, '[/code]') + 7; $buffer = substr($in, 0, $pos); if (preg_match('#\[code(?:=([a-z]+))?\]#i', $buffer)) { break; } else { $in = substr($in, $pos); $code .= $buffer; } } $code = substr($code, 0, -7); $code = preg_replace('#^[\r\n]*(.*?)[\n\r\s\t]*$#s', '\1', $code); switch (strtolower($stx)) { case 'php': $remove_tags = FALSE; if (!preg_match('/\<\?.*?\?\>/is', $code)) { $remove_tags = TRUE; $code = ""; } ob_start(); highlight_string($code); $code = ob_get_contents(); ob_end_clean(); if ($remove_tags) { $code = preg_replace('!^
processing $username
[\n\r\s\t]*[\n\r\s\t]*()<\?php (.*)\?>[\n\r\s\t]*()[\n\r\s\t]*
[\n\r\s\t]*!is', '\1\2\3', $code);
}
else
{
$code = preg_replace('!^[\n\r\s\t]*[\n\r\s\t]*(.*)[\n\r\s\t]*
[\n\r\s\t]*!is', '\1', $code);
}
$str_from = array('[', ']', '.');
$str_to = array('[', ']', '.');
$out .= "[code=$stx:" . $this->bbcode_uid . ']' . str_replace($str_from, $str_to, $code) . '[/code:' . $this->bbcode_uid . ']';
break;
default:
$str_from = array('<', '>', '[', ']', '.');
$str_to = array('<', '>', '[', ']', '.');
$out .= '[code:' . $this->bbcode_uid . ']' . str_replace($str_from, $str_to, $code) . '[/code:' . $this->bbcode_uid . ']';
}
if (preg_match('#(.*?)\[code(?:=[a-z]+)?\](.+)#is', $in, $m))
{
$out .= $m[1];
$in = $m[2];
}
}
while ($in);
return $out;
}
// Expects the argument to start with a tag
function bbcode_list($in)
{
// $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
$tok = ']';
$out = '[';
$in = substr(stripslashes($in), 1);
$close_tags = array();
do
{
$pos = strlen($in);
for ($i = 0; $i < strlen($tok); ++$i)
{
$tmp_pos = strpos($in, $tok{$i});
if ($tmp_pos !== FALSE && $tmp_pos < $pos)
{
$pos = $tmp_pos;
}
}
$buffer = substr($in, 0, $pos);
$tok = $in{$pos};
$in = substr($in, $pos + 1);
if ($tok == ']')
{
// if $tok is ']' the buffer holds a tag
if ($buffer == '/list' && count($close_tags))
{
// valid [/list] tag
$tag = array_pop($close_tags);
$out .= $tag . ']';
$tok = '[';
}
elseif (preg_match('#list(=?(?:[0-9]|[a-z]|))#i', $buffer, $m))
{
// sub-list, add a closing tag
array_push($close_tags, (($m[1]) ? '/list:o:' . $this->bbcode_uid : '/list:u:' . $this->bbcode_uid));
$out .= $buffer . ':' . $this->bbcode_uid . ']';
$tok = '[';
}
else
{
if ($buffer == '*' && count($close_tags))
{
// the buffer holds a bullet tag and we have a [list] tag open
$buffer = '*:' . $this->bbcode_uid;
}
$out .= $buffer . $tok;
$tok = '[]';
}
}
else
{
// Not within a tag, just add buffer to the return string
$out .= $buffer . $tok;
$tok = ($tok == '[') ? ']' : '[]';
}
}
while ($in);
// do we have some tags open? close them now
if (count($close_tags))
{
$out .= '[' . implode('][', $close_tags) . ']';
}
return $out;
}
// Expects the argument to start with a tag
function bbcode_quote($in)
{
$tok = ']';
$out = '[';
$in = substr(stripslashes($in), 1);
$close_tags = array();
$buffer = '';
do
{
$pos = strlen($in);
for ($i = 0; $i < strlen($tok); ++$i)
{
$tmp_pos = strpos($in, $tok{$i});
if ($tmp_pos !== FALSE && $tmp_pos < $pos)
{
$pos = $tmp_pos;
}
}
$buffer .= substr($in, 0, $pos);
$tok = $in{$pos};
$in = substr($in, $pos + 1);
if ($tok == ']')
{
if ($buffer == '/quote' && count($close_tags))
{
// we have found a closing tag
$tag = array_pop($close_tags);
$out .= $tag . ']';
$tok = '[';
$buffer = '';
}
elseif (preg_match('#^quote(?:="(.*?)")?$#is', $buffer, $m))
{
// the buffer holds a valid opening tag
array_push($close_tags, '/quote:' . $this->bbcode_uid);
if (!empty($m[1]))
{
// here we can check for valid bbcode pairs or whatever
$username = $m[1];
$out .= 'quote="' . $username . '":' . $this->bbcode_uid . ']';
}
else
{
$out .= 'quote:' . $this->bbcode_uid . ']';
}
$tok = '[';
$buffer = '';
}
elseif (preg_match('#^quote="(.*?)#is', $buffer, $m))
{
// the buffer holds an invalid opening tag
$buffer .= ']';
}
else
{
$out .= $buffer . $tok;
$tok = '[]';
$buffer = '';
}
}
else
{
$out .= $buffer . $tok;
$tok = ($tok == '[') ? ']' : '[]';
$buffer = '';
}
}
while ($in);
if (count($close_tags))
{
$out .= '[' . implode('][', $close_tags) . ']';
}
return $out;
}
function validate_email($var1, $var2)
{
$var1 = stripslashes($var1);
$var2 = stripslashes($var2);
$retval = '[email' . $var1 . ':' . $this->bbcode_uid . ']' . $var2 . '[/email:' . $this->bbcode_uid . ']';
return $retval;
}
function validate_url($var1, $var2)
{
$url = (empty($var1)) ? stripslashes($var2) : stripslashes($var1);
// Put validation regexps here
$valid = FALSE;
if (preg_match('#^http(s?)://#i', $url))
{
$valid = TRUE;
}
if ($valid)
{
return (empty($var1)) ? '[url:' . $this->bbcode_uid . ']' . $url . '[/url:' . $this->bbcode_uid . ']' : "[url=$url:" . $this->bbcode_uid . ']' . stripslashes($var2) . '[/url:' . $this->bbcode_uid . ']';
}
return '[url' . $var1 . ']' . stripslashes($var2) . '[/url]';
}
// Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
// Cuts down displayed size of link if over 50 chars, turns absolute links
// into relative versions when the server/script path matches the link
function magic_url($url)
{
global $config;
if ($url)
{
$server_protocol = ( $config['cookie_secure'] ) ? 'https://' : 'http://';
$server_port = ( $config['server_port'] <> 80 ) ? ':' . trim($config['server_port']) . '/' : '/';
$match = array();
$replace = array();
// relative urls for this board
$match[] = '#' . $server_protocol . trim($config['server_name']) . $server_port . preg_replace('/^\/?(.*?)(\/)?$/', '\1', trim($config['script_path'])) . '/([^ \t\n\r <"\']+)#i';
$replace[] = '\1';
// matches a xxxx://aaaaa.bbb.cccc. ...
$match[] = '#(^|[\n ])([\w]+?://.*?[^ \t\n\r<"]*)#ie';
$replace[] = "'\\1' . ((strlen('\\2') > 55) ? substr('\\2', 0, 39) . ' ... ' . substr('\\2', -10) : '\\2') . ''";
// matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing
$match[] = '#(^|[\n ])(www\.[\w\-]+\.[\w\-.\~]+(?:/[^ \t\n\r<"]*)?)#ie';
$replace[] = "'\\1' . ((strlen('\\2') > 55) ? substr(str_replace(' ', '%20', '\\2'), 0, 39) . ' ... ' . substr('\\2', -10) : '\\2') . ''";
// matches an email@domain type address at the start of a line, or after a space.
$match[] = '#(^|[\n ])([a-z0-9&\-_.]+?@[\w\-]+\.([\w\-\.]+\.)?[\w]+)#ie';
$replace[] = "'\\1' . ((strlen('\\2') > 55) ? substr('\\2', 0, 39) . ' ... ' . substr('\\2', -10) : '\\2') . ''";
$this->message = preg_replace($match, $replace, $this->message);
}
}
function emoticons($smile)
{
global $db, $user;
$sql = "SELECT *
FROM " . SMILIES_TABLE;
$result = $db->sql_query($sql);
if ($row = $db->sql_fetchrow($result))
{
$match = $replace = array();
do
{
$match[] = "#(?<=.\W|\W.|^\W)" . preg_quote($row['code'], '#') . "(?=.\W|\W.|\W$)#";
$replace[] = '