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