Newer
Older
/**
* @file
* All the BBcode to HTML conversions are handled in this file.
*/
function _bbcode_filter_process(&$body, $settings) {
$quote_text = t('Quote:');
$quote_user = t('\\1 wrote:');
// Encode all script tags to prevent XSS html injection attacks
$body = preg_replace(array('#<script([^>]*)>#i', '#</script([^>]*)>#i'), array('<script\\1>', '</script\\1>'), $body);
// Find all [code] tags and check if they contain a newline. If we find a newline,
// that [code] should be rendered as a block, otherwise it will still be inline
$mode = $settings['bbcode_paragraph_breaks'];
$pre = array();
$i = 0;
if (preg_match_all('#\[code(?::\w+)?\](.*?)\[/code(?::\w+)?\]#si', $body, $code_tags, PREG_SET_ORDER)) {
foreach ($code_tags as $code_tag) {
$code_tag[1] = str_replace(array('<', '>'), array('<', '>'), $code_tag[1]);
if (strpos($code_tag[1], "\n") === FALSE) {
$body = str_replace($code_tag[0], '<code class="bb-code">' . $code_tag[1] . '</code>', $body);
}
elseif ($mode) {
// Strip preformatted code blocks from text during line break processing, replaced below
$body = str_replace($code_tag[0], "***pRe_sTrInG$i***", $body);
$pre[$i++] = '<pre class="bb-code-block">' . $code_tag[1] . '</pre>';
}
else {
$body = str_replace($code_tag[0], '<pre class="bb-code-block">' . $code_tag[1] . '</pre>', $body);
}
// Apply line and paragraph breaks (skipping preformatted code)
if ($mode) {
if ($mode == 1) { // Line breaks only (starting with PHP 4.0.5, nl2br() is XHTML compliant)
if ($mode == 2) { // Line and paragraph breaks (may not always be XHTML compliant)
$body = preg_replace("/(\r\n|\n|\r)/", "\n", $body);
$body = preg_replace("/\n\n+/", "\n\n", $body);
$parts = explode("\n\n", $body);
for ($i = 0; $i < sizeof($parts); $i++) {
// No linebreaks if paragraph starts with an HTML tag
if ( !preg_match('/^<.*>/', $parts[$i]) ) {
$parts[$i] = nl2br($parts[$i]);
}
// Some tags should not be in paragraph blocks
if ( !preg_match('/^(?:<|\[)(?:table|list|ol|ul|pre|select|form|blockquote|hr)/i', $parts[$i]) ) {
$parts[$i] = '<p>' . $parts[$i] . '</p>';
}
// Reinsert preformatted code blocks
foreach ($pre as $i => $code_tag) {
$body = str_replace("***pRe_sTrInG$i***", $code_tag, $body);
}
// Add closing tags to prevent users from disruping your site's HTML
// (required for nestable tags only: [list] and [quote])
preg_match_all('/\[quote/i', $body, $matches);
$opentags = count($matches['0']);
preg_match_all('/\[\/quote\]/i', $body, $matches);
$unclosed = $opentags - count($matches['0']);
for ($i = 0; $i < $unclosed; $i++) {
$body .= '[/quote]';
}
preg_match_all('/\[list/i', $body, $matches);
$opentags = count($matches['0']);
preg_match_all('/\[\/list\]/i', $body, $matches);
$unclosed = $opentags - count($matches['0']);
for ($i = 0; $i < $unclosed; $i++) {
$body .= '[/list]';
}
naudefj
committed
if (stristr($body, '[size=') !== FALSE) { // prevent useless processing
$arr = array(
'tag' => 'size',
'pattern' => '#\[\x07=([\d]+)(?::\w+)?\]([^\x07]*)\[/\x07(?::\w+)?\]#esi',
'replacement' => '"<span style=\"font-size:". _bbcode_size_val(\'$1\') ."\">". str_replace(\'\"\', \'"\', \'$2\') ."</span>"',
naudefj
committed
$body = _bbcode_replace_nest_tag($arr);
} // end processing for [size]
// begin processing for [color]
if (stristr($body, '[color=') !== FALSE) { // prevent useless processing
$arr = array(
'tag' => 'color',
'pattern' => '#\[\x07=([\#\w]+)(?::\w+)?\]([^\x07]*)\[/\x07(?::\w+)?\]#si',
naudefj
committed
'replacement' => '<span style="color:$1">$2</span>',
naudefj
committed
$body = _bbcode_replace_nest_tag($arr);
} // end processing for [color]
// begin processing for [font]
if (stristr($body, '[font=') !== FALSE) { // prevent useless processing
$arr = array(
'tag' => 'font',
'pattern' => '#\[\x07=([\w\s]+)(?::\w+)?\]([^\x07]*)\[/\x07(?::\w+)?\]#si',
naudefj
committed
'replacement' => '<span style="font-family:$1">$2</span>',
naudefj
committed
$body = _bbcode_replace_nest_tag($arr);
} // end processing for [font]
// begin processing for [list] and [*]
if (stristr($body, '[list') !== FALSE) { // prevent useless processing
$l_type = array(
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
NULL => array(
'style' => 'circle',
'tag' => 'ul',
),
'c' => array(
'style' => 'circle',
'tag' => 'ul',
),
'd' => array(
'style' => 'disc',
'tag' => 'ul',
),
's' => array(
'style' => 'square',
'tag' => 'ul',
),
'1' => array(
'style' => 'decimal',
'tag' => 'ol',
),
'a' => array(
'style' => 'lower-alpha',
'tag' => 'ol',
),
'A' => array(
'style' => 'upper-alpha',
'tag' => 'ol',
),
'i' => array(
'style' => 'lower-roman',
'tag' => 'ol',
),
'I' => array(
'style' => 'upper-roman',
'tag' => 'ol',
),
);
$body = preg_replace('#(\[[/]*)list(.*?\])#si', "$1\x07$2", $body);
// replace to <li> tags - [*]..[*]|[*]..[/list]
$body = preg_replace('#\[\*(?::\w+)?\]([^\x07]*?)(?=\s*?(\[\*(?::\w+)?\]|\[/\x07(?::\w+)?\]))#si', '<li>$1</li>', $body);
// add </li> tags to nested <li> - [/list]..[/list]
$body = preg_replace('#(\[/\x07(?::\w+)?\])(?=[^\x07]*?\[/\x07(?::\w+)?\])#si', '$1</li>', $body);
// add </li> tags to nested <li> - [/list]..[*]..[list]
$body = preg_replace('#(\[/\x07(?::\w+)?\])(?=[^\x07]*?\[\*(?::\w+)?\][^\x07]*?\[\x07.*(?::\w+)?\])#si', '$1</li>', $body);
// replace to <li> tags for nested <li> - [*]..[list]
$body = preg_replace('#\[\*(?::\w+)?\]([^\x07]*)?(?=\[\x07.*(?::\w+)?\])#si', '<li>$1', $body);
// replace to <ol>/<ul> and </ol>/</ul> tags
// It will be better to use &count and do-while, if php 5 or higher.
while (preg_match("#\[\x07[=]*((?-i)[cds1aAiI])*(?::\w+)?\]([^\x07]*)\[/\x07(?::\w+)?\]#si", $body)) {
$body = preg_replace("#\[\x07[=]*((?-i)[cds1aAiI])*(?::\w+)?\]([^\x07]*)\[/\x07(?::\w+)?\]#esi", '"<". $l_type[\'$1\']["tag"] ." class=\"bb-list\" style=\"list-style-type:". $l_type[\'$1\']["style"] .";\">". str_replace(\'\"\', \'"\', \'$2\') ."</". $l_type[\'$1\']["tag"] .">"', $body);
}
// remove <br /> tags
$body = preg_replace('#(<[/]*([uo]l|li).*>.*)<br />#i', '$1', $body);
} // end processing for [list] and [*]
// Implement [notag]
$body = preg_replace_callback('#\[notag(?::\w+)?\](.*?)\[/notag(?::\w+)?\]#si',
function ($matches) { return _bbcode_notag_tag($matches[1]); }, $body);
// PHP code blocks (syntax highlighted)
$body = preg_replace_callback('#\[php(?::\w+)?\](?:[\r\n])*(.*?)\[/php(?::\w+)?\]#si',
function ($matches) { return _bbcode_php_tag($matches[1]); }, $body);
// Headings and indexes - articles will almost always need them
$body = preg_replace_callback('#\[h([1-6])(?::\w+)?\](.*?)\[/h[1-6](?::\w+)?\]#si',
function ($matches) { return _bbcode_generate_heading($matches[1], $matches[2]); }, $body);
$body = preg_replace_callback('#\[index\s*/?\]#si',
function ($matches) { return _bbcode_generate_index($body); }, $body);
$body = preg_replace_callback('#\[index style=(ol|ul)\]#si',
function ($matches) { return _bbcode_generate_index($bosy, $matches[1]); }, $body);
$preg = array(
// Font, text and alignment
'#\[align=(\w+)(?::\w+)?\](.*?)\[/align(?::\w+)?\]#si' => '<span style="text-align:\\1">\\2</span>',
'#\[float=(left|right)(?::\w+)?\](.*?)\[/float(?::\w+)?\]#si' => '<span style="float:\\1">\\2</span>',
'#\[justify(?::\w+)?\](.*?)\[/justify(?::\w+)?\]#si' => '<div style="text-align:justify;">\\1</div>',
'#\[(b|strong)(?::\w+)?\](.*?)\[/(b|strong)(?::\w+)?\]#si' => '<span style="font-weight:bold">\\2</span>',
'#\[(i|em)(?::\w+)?\](.*?)\[/(i|em)(?::\w+)?\]#si' => '<span style="font-style:italic">\\2</span>',
'#\[u(?::\w+)?\](.*?)\[/u(?::\w+)?\]#si' => '<span style="text-decoration:underline">\\1</span>',
'#\[s(?::\w+)?\](.*?)\[/s(?::\w+)?\]#si' => '<s>\\1</s>',
'#\[sup(?::\w+)?\](.*?)\[/sup(?::\w+)?\]#si' => '<sup>\\1</sup>',
'#\[sub(?::\w+)?\](.*?)\[/sub(?::\w+)?\]#si' => '<sub>\\1</sub>',
'#\[center(?::\w+)?\](.*?)\[/center(?::\w+)?\]#si' => '<div style="text-align:center">\\1</div>',
'#\[left(?::\w+)?\](.*?)\[/left(?::\w+)?\]#si' => '<div style="text-align:left">\\1</div>',
'#\[right(?::\w+)?\](.*?)\[/right(?::\w+)?\]#si' => '<div style="text-align:right">\\1</div>',
// Links without a protocol, with a protocol, and with good looking text
'#\[url(?::\w+)?\]www\.([\w:;&,%+~!=@\/\.\-\#\?]+?)\[/url(?::\w+)?\]#si' => '<a href="http://www.\\1" class="bb-url">\\1</a>',
'#\[url(?::\w+)?\]([\w:;&,%+~!=@\/\.\-\#\?]+?)\[/url(?::\w+)?\]#si' => '<a href="\\1" class="bb-url">\\1</a>',
'#\[url=www\.([\w:;&,%+~!=@\/\.\-\#\?]+?)\](.*?)\[/url(?::\w+)?\]#si' => '<a href="http://www.\\1" class="bb-url">\\2</a>',
'#\[url="?\'?([\w:;&,%+~!=@\/\.\-\#\?]+?)"?\'?\](.*?)\[/url(?::\w+)?\]#si' => '<a href="\\1" class="bb-url">\\2</a>',
// Anchor tags for linking within documents
'#\[anchor=(\w+)(?::\w+)?\](.*?)\[/anchor(?::\w+)?\]#si' => '<a name="\\1">\\2</a>',
// Images without or with client-side sizing
'#\[img(?::\w+)?\]([\w:;&,~%+!=@\/\.\-\#\?]+)\[/img(?::\w+)?\]#si' => '<img src="\\1" alt="" class="bb-image" />',
'#\[img=(\d+)x(\d+)(?::\w+)?\]([\w:;&,~%+!=@\/\.\-\#\?]+)\[/img(?::\w+)?\]#si' => '<img width="\\1" height="\\2" alt="" src="\\3" class="bb-image" />',
'#\[img=([\w\s:;,\.\-\'\(\)]+)(?::\w+)?\]([\w:;&,~%+!=@\/\.\-\#\?]+)\[/img(?::\w+)?\]#si' => '<img alt="\\1" src="\\2" class="bb-image" />',
'#\[img align=(left|right|center)(?::\w+)?\]([\w:;&,~%+!=@\/\.\-\#\?]+)\[/img(?::\w+)?\]#si' => '<img src="\\2" alt="" align="\\1" class="bb-image" />',
naudefj
committed
// Flash animations and other special effects
'#\[flash=(\d+)x(\d+)(?::\w+)?\]([\w:;&,~%+!=@\/\.\-\#\?]+)\[/flash(?::\w+)?\]#si' => '<object type="application/x-shockwave-flash" data="\\3" width="\\1" height="\\2"><param name="movie" value="\\3" /></object>',
// Acronyms & abbreviations - show description when mouse moves over tag
'#\[acronym=([\w\s-,\.]+)(?::\w+)?\](.*?)\[/acronym(?::\w+)?\]#si' => '<acronym title="\\1">\\2</acronym>',
'#\[abbr=([\w\s-,\.]+)(?::\w+)?\](.*?)\[/abbr(?::\w+)?\]#si' => '<abbr title="\\1">\\2</abbr>',
// Quoting with or without specifying the source
'#\[quote(?::\w+)?\]#i' => '<div class="bb-quote">' . $quote_text . '<blockquote class="bb-quote-body">',
'#\[quote=(?:"|"|\')?(.*?)["\']?(?:"|"|\')?\]#i' => '<div class="bb-quote"><b>' . $quote_user . '</b><blockquote class="bb-quote-body">',
'#\[/quote(?::\w+)?\]#si' => '</blockquote></div>',
// Links to popular sites
'#\[google(?::\w+)?\]([\w\s-]+?)\[/google(?::\w+)?\]#si' => '<a href="http://www.google.com/search?q=\\1">\\1</a>',
'#\[wikipedia(?::\w+)?\]([\w\s-]+?)\[/wikipedia(?::\w+)?\]#si' => '<a href="http://www.wikipedia.org/wiki/\\1">\\1</a>',
naudefj
committed
'#\[youtube\]([0-9a-zA-Z_\-]+)\[/youtube\]#si' => '<iframe width="425" height="366" src="//www.youtube.com/embed/\\1" frameborder="0" allowfullscreen></iframe>',
'#\[table\](.+?)\[/table\]#si' => '<table class="bb-table">\\1</table>',
'#\[(row|r|tr)\](.+?)\[/(row|r|tr)\]#si' => '<tr>\\2</tr>',
'#\[(row|r|tr) color=([\#\w]+)\](.+?)\[/(row|r|tr)\]#si' => '<tr bgcolor=\\2>\\3</tr>',
'#\[(header|head|h)\](.+?)\[/(header|head|h)\]#si' => '<th>\\2</th>',
'#\[(col|c|td)\](.+?)\[/(col|c|td)\]#si' => '<td valign="top">\\2</td>',
'#\[indent\](.+?)\[\/indent\]#i' => '<div style="padding-left: 20px;">\\1</div>',
// Cleanup table output (td, th and tr tags)
'#<([\/]?)t([dhr])><br />#si' => '<\\1t\\2>',
'#<table(.+?)><br />#si' => '<table\\1>',
);
$body = preg_replace(array_keys($preg), array_values($preg), $body);
// Simple replacements (str_replace is faster than preg_replace)
$str = array(
// Horizontal delimiter
);
$body = str_replace(array_keys($str), array_values($str), $body);
// We cannot evaluate the variable in callback function because
// there is no way to pass the $format variable
$body = preg_replace_callback('#\[email(?::\w+)?\]([\w\.\-\+~@]+)\[/email(?::\w+)?\]#si', '_bbcode_encode_mailto', $body);
$body = preg_replace_callback('#\[email=(.*?)(?::\w+)?\](.*?)\[/email(?::\w+)?\]#si', '_bbcode_encode_mailto', $body);
array('#\[email(?::\w+)?\](.*?)\[/email(?::\w+)?\]#si', '#\[email=(.*?)(?::\w+)?\]([\w\s]+)\[/email(?::\w+)?\]#si'),
array('<a href="mailto:\\1" class="bb-email">\\1</a>', '<a href="mailto:\\1" class="bb-email">\\2</a>'),
$body);
}
// Turns web and e-mail addresses into clickable links
// pad with a space so we can match things at the start of the 1st line
$ret = ' ' . $body;
// padding to already filtered links
$ret = preg_replace('#(<a.+>)(.+</a>)#i', "$1\x07$2", $ret);
// matches an "xxx://yyyy" URL at the start of a line, or after a space.
// xxxx can only be alpha characters.
// yyyy is anything up to the first space, newline, comma, double quote or <
$ret = preg_replace('#(?<=^|[\t\r\n >\(\[\]\|])([a-z]+?://[\w\-]+\.([\w\-]+\.)*\w+(:[0-9]+)?(/[^ "\'\(\n\r\t<\)\[\]\|]*)?)((?<![,\.])|(?!\s))#i', '<a href="\1">\1</a>', $ret);
// matches a "www|ftp.xxxx.yyyy[/zzzz]" kinda lazy URL thing
// Must contain at least 2 dots. xxxx contains either alphanum, or "-"
// zzzz is optional.. will contain everything up to the first space, newline,
// comma, double quote or <.
$ret = preg_replace('#([\t\r\n >\(\[\|])(www|ftp)\.(([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^ \"\'\(\n\r\t<\)\[\]\|]*)?)#i', '\1<a href="http://\2.\3">\2.\3</a>', $ret);
// matches an email@domain type address at the start of a line, or after a space.
// Note: Only the followed chars are valid; alphanums, "-", "_" and or ".".
$ret = preg_replace_callback("#([\t\r\n ])([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i", '_bbcode_encode_mailto', $ret);
$ret = preg_replace('#([\t\r\n ])([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i', '\\1<a href="mailto:\\2@\\3">\\2@\\3</a>', $ret);
// Remove our padding
$ret = str_replace("\x07", '', $ret);
$body = substr($ret, 1);
}
$body = preg_replace('#<a([^>]+)>#i', '<a\\1 rel="nofollow">', $body);
}
function _bbcode_generate_heading($level, $text) {
$anchor = preg_replace('/([\s]+)/', '_', $text);
$anchor = preg_replace('/([\W]+)/', '', $anchor);
return '<h' . $level . '><a name="' . $anchor . '">' . $text . '</a></h' . $level . '>';
function _bbcode_generate_index($body, $tag = 'ol') {
$close_tags = 0;
if (preg_match_all('#\[h([1-6]).*?\](.*?)\[/h([1-6]).*?\]#si', $body, $head_tags, PREG_SET_ORDER)) {
foreach ($head_tags as $head_tag) {
if ($level == 0) {
$level = $head_tag[1];
}
$anchor = preg_replace('/([\s]+)/', '_', $head_tag[2]);
$index .= '<' . $tag . ">\n";
$index .= '<li><a href="#' . $anchor . '">' . $head_tag[2] . "</a>\n";
$close_tags++;
$level = $head_tag[1];
$index .= '<li><a href="#' . $anchor . '">' . $head_tag[2] . "</a>\n";
}
else {
$index .= '<li><a href="#' . $anchor . '">' . $head_tag[2] . "</a>\n";
$level = $head_tag[1];
}
}
}
while ($close_tags >= 0) {
$close_tags--;
}
return $index;
}
if (isset($matches[3])) {
$link = 'document.write(\'<a href="mailto:' . $matches[2] . '@' . $matches[3] . '">' . $matches[2] . '@' . $matches[3] . '</a>\');';
}
else {
$link = 'document.write(\'<a href="mailto:' . $matches[1] . '" class="bb-email">' . (isset($matches[2]) ? $matches[2] : $matches[1]) . '</a>\');';
$link = '<script type="text/javascript">eval(unescape(\'' . $js_encode . '\'))</script>';
if (isset($matches[3])) {
function _bbcode_notag_tag($text = NULL) {
return str_replace( array('[', ']', '@'), array('[', ']', '@'), stripslashes($text));
function _bbcode_php_tag($text = NULL) {
return '<pre>' . highlight_string( str_replace('<br />', '', stripslashes($text)), TRUE) . '</pre>';
naudefj
committed
function _bbcode_size_val($size) {
if ($size < 6)
return '6px';
elseif ($size <= 48)
return $size . 'px';
else
return $size . '%';
naudefj
committed
}
function _bbcode_replace_nest_tag($arr = NULL) {
$text = preg_replace('#(\[[/]*)' . $arr['tag'] . '(.*?\])#si', "$1\x07$2", $arr['text']);
naudefj
committed
// It will be better to use &count and do-while, if php 5 or higher.
while (preg_match($arr['pattern'], $text)) {
$text = preg_replace($arr['pattern'], $arr['replacement'], $text);
}
return $text;
}