punbb/include/parser.php
changeset 7 98bbc533541c
child 10 98d80b672f3c
equal deleted inserted replaced
6:5e1f1e916419 7:98bbc533541c
       
     1 <?php
       
     2 /***********************************************************************
       
     3 
       
     4   Copyright (C) 2002-2008  PunBB.org
       
     5 
       
     6   This file is part of PunBB.
       
     7 
       
     8   PunBB is free software; you can redistribute it and/or modify it
       
     9   under the terms of the GNU General Public License as published
       
    10   by the Free Software Foundation; either version 2 of the License,
       
    11   or (at your option) any later version.
       
    12 
       
    13   PunBB is distributed in the hope that it will be useful, but
       
    14   WITHOUT ANY WARRANTY; without even the implied warranty of
       
    15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    16   GNU General Public License for more details.
       
    17 
       
    18   You should have received a copy of the GNU General Public License
       
    19   along with this program; if not, write to the Free Software
       
    20   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
       
    21   MA  02111-1307  USA
       
    22 
       
    23 ************************************************************************/
       
    24 
       
    25 
       
    26 // Make sure no one attempts to run this script "directly"
       
    27 if (!defined('PUN'))
       
    28 	exit;
       
    29 
       
    30 
       
    31 // Here you can add additional smilies if you like (please note that you must escape singlequote and backslash)
       
    32 $smiley_text = array(':)', '=)', ':|', '=|', ':(', '=(', ':D', '=D', ':o', ':O', ';)', ':/', ':P', ':lol:', ':mad:', ':rolleyes:', ':cool:');
       
    33 $smiley_img = array('smile.png', 'smile.png', 'neutral.png', 'neutral.png', 'sad.png', 'sad.png', 'big_smile.png', 'big_smile.png', 'yikes.png', 'yikes.png', 'wink.png', 'hmm.png', 'tongue.png', 'lol.png', 'mad.png', 'roll.png', 'cool.png');
       
    34 
       
    35 // Uncomment the next row if you add smilies that contain any of the characters &"'<>
       
    36 //$smiley_text = array_map('pun_htmlspecialchars', $smiley_text);
       
    37 
       
    38 
       
    39 //
       
    40 // Make sure all BBCodes are lower case and do a little cleanup
       
    41 //
       
    42 function preparse_bbcode($text, &$errors, $is_signature = false)
       
    43 {
       
    44 	// Change all simple BBCodes to lower case
       
    45 	$a = array('[B]', '[I]', '[U]', '[/B]', '[/I]', '[/U]');
       
    46 	$b = array('[b]', '[i]', '[u]', '[/b]', '[/i]', '[/u]');
       
    47 	$text = str_replace($a, $b, $text);
       
    48 
       
    49 	// Do the more complex BBCodes (also strip excessive whitespace and useless quotes)
       
    50 	$a = array( '#\[url=("|\'|)(.*?)\\1\]\s*#i',
       
    51 				'#\[url\]\s*#i',
       
    52 				'#\s*\[/url\]#i',
       
    53 				'#\[email=("|\'|)(.*?)\\1\]\s*#i',
       
    54 				'#\[email\]\s*#i',
       
    55 				'#\s*\[/email\]#i',
       
    56 				'#\[img\]\s*(.*?)\s*\[/img\]#is',
       
    57 				'#\[colou?r=("|\'|)(.*?)\\1\](.*?)\[/colou?r\]#is');
       
    58 
       
    59 	$b = array(	'[url=$2]',
       
    60 				'[url]',
       
    61 				'[/url]',
       
    62 				'[email=$2]',
       
    63 				'[email]',
       
    64 				'[/email]',
       
    65 				'[img]$1[/img]',
       
    66 				'[color=$2]$3[/color]');
       
    67 
       
    68 	if (!$is_signature)
       
    69 	{
       
    70 		// For non-signatures, we have to do the quote and code tags as well
       
    71 		$a[] = '#\[quote=(&quot;|"|\'|)(.*?)\\1\]\s*#i';
       
    72 		$a[] = '#\[quote\]\s*#i';
       
    73 		$a[] = '#\s*\[/quote\]\s*#i';
       
    74 		$a[] = '#\[code\][\r\n]*(.*?)\s*\[/code\]\s*#is';
       
    75 
       
    76 		$b[] = '[quote=$1$2$1]';
       
    77 		$b[] = '[quote]';
       
    78 		$b[] = '[/quote]'."\n";
       
    79 		$b[] = '[code]$1[/code]'."\n";
       
    80 	}
       
    81 
       
    82 	// Run this baby!
       
    83 	$text = preg_replace($a, $b, $text);
       
    84 
       
    85 	if (!$is_signature)
       
    86 	{
       
    87 		$overflow = check_tag_order($text, $error);
       
    88 
       
    89 		if ($error)
       
    90 			// A BBCode error was spotted in check_tag_order()
       
    91 			$errors[] = $error;
       
    92 		else if ($overflow)
       
    93 			// The quote depth level was too high, so we strip out the inner most quote(s)
       
    94 			$text = substr($text, 0, $overflow[0]).substr($text, $overflow[1], (strlen($text) - $overflow[0]));
       
    95 	}
       
    96 	else
       
    97 	{
       
    98 		global $lang_profile;
       
    99 
       
   100 		if (preg_match('#\[quote=(&quot;|"|\'|)(.*)\\1\]|\[quote\]|\[/quote\]|\[code\]|\[/code\]#i', $text))
       
   101 			message($lang_profile['Signature quote/code']);
       
   102 	}
       
   103 
       
   104 	return trim($text);
       
   105 }
       
   106 
       
   107 
       
   108 //
       
   109 // Parse text and make sure that [code] and [quote] syntax is correct
       
   110 //
       
   111 function check_tag_order($text, &$error)
       
   112 {
       
   113 	global $lang_common;
       
   114 
       
   115 	// The maximum allowed quote depth
       
   116 	$max_depth = 3;
       
   117 
       
   118 	$cur_index = 0;
       
   119 	$q_depth = 0;
       
   120 
       
   121 	while (true)
       
   122 	{
       
   123 		// Look for regular code and quote tags
       
   124 		$c_start = strpos($text, '[code]');
       
   125 		$c_end = strpos($text, '[/code]');
       
   126 		$q_start = strpos($text, '[quote]');
       
   127 		$q_end = strpos($text, '[/quote]');
       
   128 
       
   129 		// Look for [quote=username] style quote tags
       
   130 		if (preg_match('#\[quote=(&quot;|"|\'|)(.*)\\1\]#sU', $text, $matches))
       
   131 			$q2_start = strpos($text, $matches[0]);
       
   132 		else
       
   133 			$q2_start = 65536;
       
   134 
       
   135 		// Deal with strpos() returning false when the string is not found
       
   136 		// (65536 is one byte longer than the maximum post length)
       
   137 		if ($c_start === false) $c_start = 65536;
       
   138 		if ($c_end === false) $c_end = 65536;
       
   139 		if ($q_start === false) $q_start = 65536;
       
   140 		if ($q_end === false) $q_end = 65536;
       
   141 
       
   142 		// If none of the strings were found
       
   143 		if (min($c_start, $c_end, $q_start, $q_end, $q2_start) == 65536)
       
   144 			break;
       
   145 
       
   146 		// We are interested in the first quote (regardless of the type of quote)
       
   147 		$q3_start = ($q_start < $q2_start) ? $q_start : $q2_start;
       
   148 
       
   149 		// We found a [quote] or a [quote=username]
       
   150 		if ($q3_start < min($q_end, $c_start, $c_end))
       
   151 		{
       
   152 			$step = ($q_start < $q2_start) ? 7 : strlen($matches[0]);
       
   153 
       
   154 			$cur_index += $q3_start + $step;
       
   155 
       
   156 			// Did we reach $max_depth?
       
   157 			if ($q_depth == $max_depth)
       
   158 				$overflow_begin = $cur_index - $step;
       
   159 
       
   160 			++$q_depth;
       
   161 			$text = substr($text, $q3_start + $step);
       
   162 		}
       
   163 
       
   164 		// We found a [/quote]
       
   165 		else if ($q_end < min($q_start, $c_start, $c_end))
       
   166 		{
       
   167 			if ($q_depth == 0)
       
   168 			{
       
   169 				$error = $lang_common['BBCode error'].' '.$lang_common['BBCode error 1'];
       
   170 				return;
       
   171 			}
       
   172 
       
   173 			$q_depth--;
       
   174 			$cur_index += $q_end+8;
       
   175 
       
   176 			// Did we reach $max_depth?
       
   177 			if ($q_depth == $max_depth)
       
   178 				$overflow_end = $cur_index;
       
   179 
       
   180 			$text = substr($text, $q_end+8);
       
   181 		}
       
   182 
       
   183 		// We found a [code]
       
   184 		else if ($c_start < min($c_end, $q_start, $q_end))
       
   185 		{
       
   186 			// Make sure there's a [/code] and that any new [code] doesn't occur before the end tag
       
   187 			$tmp = strpos($text, '[/code]');
       
   188 			$tmp2 = strpos(substr($text, $c_start+6), '[code]');
       
   189 			if ($tmp2 !== false)
       
   190 				$tmp2 += $c_start+6;
       
   191 
       
   192 			if ($tmp === false || ($tmp2 !== false && $tmp2 < $tmp))
       
   193 			{
       
   194 				$error = $lang_common['BBCode error'].' '.$lang_common['BBCode error 2'];
       
   195 				return;
       
   196 			}
       
   197 			else
       
   198 				$text = substr($text, $tmp+7);
       
   199 
       
   200 			$cur_index += $tmp+7;
       
   201 		}
       
   202 
       
   203 		// We found a [/code] (this shouldn't happen since we handle both start and end tag in the if clause above)
       
   204 		else if ($c_end < min($c_start, $q_start, $q_end))
       
   205 		{
       
   206 			$error = $lang_common['BBCode error'].' '.$lang_common['BBCode error 3'];
       
   207 			return;
       
   208 		}
       
   209 	}
       
   210 
       
   211 	// If $q_depth <> 0 something is wrong with the quote syntax
       
   212 	if ($q_depth)
       
   213 	{
       
   214 		$error = $lang_common['BBCode error'].' '.$lang_common['BBCode error 4'];
       
   215 		return;
       
   216 	}
       
   217 	else if ($q_depth < 0)
       
   218 	{
       
   219 		$error = $lang_common['BBCode error'].' '.$lang_common['BBCode error 5'];
       
   220 		return;
       
   221 	}
       
   222 
       
   223 	// If the quote depth level was higher than $max_depth we return the index for the
       
   224 	// beginning and end of the part we should strip out
       
   225 	if (isset($overflow_begin))
       
   226 		return array($overflow_begin, $overflow_end);
       
   227 	else
       
   228 		return null;
       
   229 }
       
   230 
       
   231 
       
   232 //
       
   233 // Split text into chunks ($inside contains all text inside $start and $end, and $outside contains all text outside)
       
   234 //
       
   235 function split_text($text, $start, $end)
       
   236 {
       
   237 	global $pun_config;
       
   238 
       
   239 	$tokens = explode($start, $text);
       
   240 
       
   241 	$outside[] = $tokens[0];
       
   242 
       
   243 	$num_tokens = count($tokens);
       
   244 	for ($i = 1; $i < $num_tokens; ++$i)
       
   245 	{
       
   246 		$temp = explode($end, $tokens[$i]);
       
   247 		$inside[] = $temp[0];
       
   248 		$outside[] = $temp[1];
       
   249 	}
       
   250 
       
   251 	if ($pun_config['o_indent_num_spaces'] != 8 && $start == '[code]')
       
   252 	{
       
   253 		$spaces = str_repeat(' ', $pun_config['o_indent_num_spaces']);
       
   254 		$inside = str_replace("\t", $spaces, $inside);
       
   255 	}
       
   256 
       
   257 	return array($inside, $outside);
       
   258 }
       
   259 
       
   260 
       
   261 //
       
   262 // Truncate URL if longer than 55 characters (add http:// or ftp:// if missing)
       
   263 //
       
   264 function handle_url_tag($url, $link = '')
       
   265 {
       
   266 	global $pun_user;
       
   267 
       
   268 	$full_url = str_replace(array(' ', '\'', '`', '"'), array('%20', '', '', ''), $url);
       
   269 	if (strpos($url, 'www.') === 0)			// If it starts with www, we add http://
       
   270 		$full_url = 'http://'.$full_url;
       
   271 	else if (strpos($url, 'ftp.') === 0)	// Else if it starts with ftp, we add ftp://
       
   272 		$full_url = 'ftp://'.$full_url;
       
   273 	else if (!preg_match('#^([a-z0-9]{3,6})://#', $url, $bah)) 	// Else if it doesn't start with abcdef://, we add http://
       
   274 		$full_url = 'http://'.$full_url;
       
   275 
       
   276 	// Ok, not very pretty :-)
       
   277 	$link = ($link == '' || $link == $url) ? ((strlen($url) > 55) ? substr($url, 0 , 39).' &#133; '.substr($url, -10) : $url) : stripslashes($link);
       
   278 
       
   279 	return '<a href="'.$full_url.'">'.$link.'</a>';
       
   280 }
       
   281 
       
   282 
       
   283 //
       
   284 // Turns an URL from the [img] tag into an <img> tag or a <a href...> tag
       
   285 //
       
   286 function handle_img_tag($url, $is_signature = false)
       
   287 {
       
   288 	global $lang_common, $pun_config, $pun_user;
       
   289 
       
   290 	$img_tag = '<a href="'.$url.'">&lt;'.$lang_common['Image link'].'&gt;</a>';
       
   291 
       
   292 	if ($is_signature && $pun_user['show_img_sig'] != '0')
       
   293 		$img_tag = '<img class="sigimage" src="'.$url.'" alt="'.htmlspecialchars($url).'" />';
       
   294 	else if (!$is_signature && $pun_user['show_img'] != '0')
       
   295 		$img_tag = '<span class="postimg"><img src="'.$url.'" alt="'.htmlspecialchars($url).'" /></span>';
       
   296 
       
   297 	return $img_tag;
       
   298 }
       
   299 
       
   300 
       
   301 //
       
   302 // Convert BBCodes to their HTML equivalent
       
   303 //
       
   304 function do_bbcode($text)
       
   305 {
       
   306 	global $lang_common, $pun_user;
       
   307 
       
   308 	if (strpos($text, 'quote') !== false)
       
   309 	{
       
   310 		$text = str_replace('[quote]', '</p><div class="quotebox"><blockquote><p>', $text);
       
   311 		$text = preg_replace('#\[quote=(&quot;|"|\'|)(.*)\\1\]#seU', '"</p><div class=\"quotebox\"><cite>".str_replace(array(\'[\', \'\\"\'), array(\'&#91;\', \'"\'), \'$2\')." ".$lang_common[\'wrote\'].":</cite><blockquote><p>"', $text);
       
   312 		$text = preg_replace('#\[\/quote\]\s*#', '</p></blockquote></div><p>', $text);
       
   313 	}
       
   314 
       
   315 	$pattern = array('#\[b\](.*?)\[/b\]#s',
       
   316 					 '#\[i\](.*?)\[/i\]#s',
       
   317 					 '#\[u\](.*?)\[/u\]#s',
       
   318 					 '#\[url\]([^\[]*?)\[/url\]#e',
       
   319 					 '#\[url=([^\[]*?)\](.*?)\[/url\]#e',
       
   320 					 '#\[email\]([^\[]*?)\[/email\]#',
       
   321 					 '#\[email=([^\[]*?)\](.*?)\[/email\]#',
       
   322 					 '#\[color=([a-zA-Z]*|\#?[0-9a-fA-F]{6})](.*?)\[/color\]#s');
       
   323 
       
   324 	$replace = array('<strong>$1</strong>',
       
   325 					 '<em>$1</em>',
       
   326 					 '<em class="bbuline">$1</em>',
       
   327 					 'handle_url_tag(\'$1\')',
       
   328 					 'handle_url_tag(\'$1\', \'$2\')',
       
   329 					 '<a href="mailto:$1">$1</a>',
       
   330 					 '<a href="mailto:$1">$2</a>',
       
   331 					 '<span style="color: $1">$2</span>');
       
   332 
       
   333 	// This thing takes a while! :)
       
   334 	$text = preg_replace($pattern, $replace, $text);
       
   335 
       
   336 	return $text;
       
   337 }
       
   338 
       
   339 
       
   340 //
       
   341 // Make hyperlinks clickable
       
   342 //
       
   343 function do_clickable($text)
       
   344 {
       
   345 	global $pun_user;
       
   346 
       
   347 	$text = ' '.$text;
       
   348 
       
   349 	$text = preg_replace('#([\s\(\)])(https?|ftp|news){1}://([\w\-]+\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^"\s\(\)<\[]*)?)#ie', '\'$1\'.handle_url_tag(\'$2://$3\')', $text);
       
   350 	$text = preg_replace('#([\s\(\)])(www|ftp)\.(([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^"\s\(\)<\[]*)?)#ie', '\'$1\'.handle_url_tag(\'$2.$3\', \'$2.$3\')', $text);
       
   351 
       
   352 	return substr($text, 1);
       
   353 }
       
   354 
       
   355 
       
   356 //
       
   357 // Convert a series of smilies to images
       
   358 //
       
   359 function do_smilies($text)
       
   360 {
       
   361 	global $pun_config, $base_url, $smiley_text, $smiley_img;
       
   362 
       
   363 	$text = ' '.$text.' ';
       
   364 
       
   365 	$num_smilies = count($smiley_text);
       
   366 	for ($i = 0; $i < $num_smilies; ++$i)
       
   367 		$text = preg_replace("#(?<=.\W|\W.|^\W)".preg_quote($smiley_text[$i], '#')."(?=.\W|\W.|\W$)#m", '$1<img src="'.$base_url.'/img/smilies/'.$smiley_img[$i].'" width="15" height="15" alt="'.substr($smiley_img[$i], 0, strrpos($smiley_img[$i], '.')).'" />$2', $text);
       
   368 
       
   369 	return substr($text, 1, -1);
       
   370 }
       
   371 
       
   372 
       
   373 //
       
   374 // Parse message text
       
   375 //
       
   376 function parse_message($text, $hide_smilies)
       
   377 {
       
   378 	global $pun_config, $lang_common, $pun_user;
       
   379 
       
   380 	if ($pun_config['o_censoring'] == '1')
       
   381 		$text = censor_words($text);
       
   382 
       
   383 	// Convert applicable characters to HTML entities
       
   384 	$text = htmlspecialchars($text);
       
   385 
       
   386 	// If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched)
       
   387 	if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false)
       
   388 	{
       
   389 		list($inside, $outside) = split_text($text, '[code]', '[/code]');
       
   390 		$outside = array_map('ltrim', $outside);
       
   391 		$text = implode('<">', $outside);
       
   392 	}
       
   393 
       
   394 	if ($pun_config['o_make_links'] == '1')
       
   395 		$text = do_clickable($text);
       
   396 
       
   397 
       
   398 	if ($pun_config['o_smilies'] == '1' && $pun_user['show_smilies'] == '1' && $hide_smilies == '0')
       
   399 		$text = do_smilies($text);
       
   400 
       
   401 	if ($pun_config['p_message_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false)
       
   402 	{
       
   403 		$text = do_bbcode($text);
       
   404 
       
   405 		if ($pun_config['p_message_img_tag'] == '1')
       
   406 		{
       
   407 //			$text = preg_replace('#\[img\]((ht|f)tps?://)([^\s<"]*?)\.(jpg|jpeg|png|gif)\[/img\]#e', 'handle_img_tag(\'$1$3.$4\')', $text);
       
   408 			$text = preg_replace('#\[img\]((ht|f)tps?://)([^\s<"]*?)\[/img\]#e', 'handle_img_tag(\'$1$3\')', $text);
       
   409 		}
       
   410 	}
       
   411 
       
   412 	// Deal with newlines, tabs and multiple spaces
       
   413 	$pattern = array("\n", "\t", '  ', '  ');
       
   414 	$replace = array('<br />', '&nbsp; &nbsp; ', '&nbsp; ', ' &nbsp;');
       
   415 	$text = str_replace($pattern, $replace, $text);
       
   416 
       
   417 	// If we split up the message before we have to concatenate it together again (code tags)
       
   418 	if (isset($inside))
       
   419 	{
       
   420 		$outside = explode('<">', $text);
       
   421 		$text = '';
       
   422 
       
   423 		$num_tokens = count($outside);
       
   424 
       
   425 		for ($i = 0; $i < $num_tokens; ++$i)
       
   426 		{
       
   427 			$text .= $outside[$i];
       
   428 			if (isset($inside[$i]))
       
   429 				$text .= '</p><div class="codebox"><strong>'.$lang_common['Code'].':</strong><pre><code>'.$inside[$i].'</code></pre></div><p>';
       
   430 		}
       
   431 	}
       
   432 
       
   433 	// Add paragraph tag around post, but make sure there are no empty paragraphs
       
   434 	$text = preg_replace('#<br />\s*?<br />(?!\s*<br />)#i', "</p><p>", $text);
       
   435 	$text = str_replace('<p></p>', '', '<p>'.$text.'</p>');
       
   436 
       
   437 	return $text;
       
   438 }
       
   439 
       
   440 
       
   441 //
       
   442 // Parse signature text
       
   443 //
       
   444 function parse_signature($text)
       
   445 {
       
   446 	global $pun_config, $lang_common, $pun_user;
       
   447 
       
   448 	if ($pun_config['o_censoring'] == '1')
       
   449 		$text = censor_words($text);
       
   450 
       
   451 	$text = htmlspecialchars($text);
       
   452 
       
   453 	if ($pun_config['o_make_links'] == '1')
       
   454 		$text = do_clickable($text);
       
   455 
       
   456 	if ($pun_config['o_smilies_sig'] == '1' && $pun_user['show_smilies'] != '0')
       
   457 		$text = do_smilies($text);
       
   458 
       
   459 	if ($pun_config['p_sig_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false)
       
   460 	{
       
   461 		$text = do_bbcode($text);
       
   462 
       
   463 		if ($pun_config['p_sig_img_tag'] == '1')
       
   464 		{
       
   465 //			$text = preg_replace('#\[img\]((ht|f)tps?://)([^\s<"]*?)\.(jpg|jpeg|png|gif)\[/img\]#e', 'handle_img_tag(\'$1$3.$4\', true)', $text);
       
   466 			$text = preg_replace('#\[img\]((ht|f)tps?://)([^\s<"]*?)\[/img\]#e', 'handle_img_tag(\'$1$3\', true)', $text);
       
   467 		}
       
   468 	}
       
   469 
       
   470 	// Deal with newlines, tabs and multiple spaces
       
   471 	$pattern = array("\n", "\t", '  ', '  ');
       
   472 	$replace = array('<br />', '&nbsp; &nbsp; ', '&nbsp; ', ' &nbsp;');
       
   473 	$text = str_replace($pattern, $replace, $text);
       
   474 
       
   475 	return $text;
       
   476 }