1<?php 2/** 3 * Loads various functions used to parse posts. 4 * 5 * @copyright (C) 2008-2012 PunBB, partially based on code (C) 2008-2009 FluxBB.org 6 * @license http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 7 * @package PunBB 8 */ 9 10 11// Make sure no one attempts to run this script "directly" 12if (!defined('FORUM')) 13 exit; 14 15// Load the IDNA class for international url handling 16if (defined('FORUM_SUPPORT_PCRE_UNICODE') && defined('FORUM_ENABLE_IDNA')) 17{ 18 require FORUM_ROOT.'include/idna/idna_convert.class.php'; 19} 20 21 22// Here you can add additional smilies if you like (please note that you must escape singlequote and backslash) 23$smilies = array(':)' => 'smile.png', '=)' => 'smile.png', ':|' => 'neutral.png', '=|' => 'neutral.png', ':(' => 'sad.png', '=(' => 'sad.png', ':D' => 'big_smile.png', '=D' => 'big_smile.png', ':o' => 'yikes.png', ':O' => 'yikes.png', ';)' => 'wink.png', ':/' => 'hmm.png', ':P' => 'tongue.png', ':p' => 'tongue.png', ':lol:' => 'lol.png', ':mad:' => 'mad.png', ':rolleyes:' => 'roll.png', ':cool:' => 'cool.png'); 24 25($hook = get_hook('ps_start')) ? eval($hook) : null; 26 27 28// 29// Make sure all BBCodes are lower case and do a little cleanup 30// 31function preparse_bbcode($text, &$errors, $is_signature = false) 32{ 33 global $forum_config; 34 35 $return = ($hook = get_hook('ps_preparse_bbcode_start')) ? eval($hook) : null; 36 if ($return !== null) 37 return $return; 38 39 if ($is_signature) 40 { 41 global $lang_profile; 42 43 if (preg_match('#\[quote(=("|"|\'|)(.*)\\1)?\]|\[/quote\]|\[code\]|\[/code\]|\[list(=([1a\*]))?\]|\[/list\]#i', $text)) 44 $errors[] = $lang_profile['Signature quote/code/list']; 45 } 46 47 if ($forum_config['p_sig_bbcode'] == '1' && $is_signature || $forum_config['p_message_bbcode'] == '1' && !$is_signature) 48 { 49 // If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched) 50 if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false) 51 { 52 list($inside, $outside) = split_text($text, '[code]', '[/code]', $errors); 53 $text = implode("\x1", $outside); 54 } 55 56 // Tidy up lists 57 $pattern_callback = '%\[list(?:=([1a*]))?+\]((?:(?>.*?(?=\[list(?:=[1a*])?+\]|\[/list\]))|(?R))*)\[/list\]%is'; 58 $text = preg_replace_callback($pattern_callback, function($matches, $errors) { 59 return preparse_list_tag($matches[2], $matches[1], $errors); 60 }, $text); 61 $text = str_replace('*'."\0".']', '*]', $text); 62 63 if ($forum_config['o_make_links'] == '1') 64 { 65 $text = do_clickable($text, defined('FORUM_SUPPORT_PCRE_UNICODE')); 66 } 67 68 // If we split up the message before we have to concatenate it together again (code tags) 69 if (isset($inside)) 70 { 71 $outside = explode("\x1", $text); 72 $text = ''; 73 74 $num_tokens = count($outside); 75 76 for ($i = 0; $i < $num_tokens; ++$i) 77 { 78 $text .= $outside[$i]; 79 if (isset($inside[$i])) 80 $text .= '[code]'.$inside[$i].'[/code]'; 81 } 82 } 83 84 $temp_text = false; 85 if (empty($errors)) 86 $temp_text = preparse_tags($text, $errors, $is_signature); 87 88 if ($temp_text !== false) 89 $text = $temp_text; 90 91 // Remove empty tags 92 while ($new_text = preg_replace('/\[(b|u|i|h|colou?r|quote|code|img|url|email|list)(?:\=[^\]]*)?\]\[\/\1\]/', '', $text)) 93 { 94 if ($new_text != $text) 95 $text = $new_text; 96 else 97 break; 98 } 99 100 } 101 102 $return = ($hook = get_hook('ps_preparse_bbcode_end')) ? eval($hook) : null; 103 if ($return !== null) 104 return $return; 105 106 return forum_trim($text); 107} 108 109 110// 111// Check the structure of bbcode tags and fix simple mistakes where possible 112// 113function preparse_tags($text, &$errors, $is_signature = false) 114{ 115 global $lang_common, $forum_config; 116 117 // Start off by making some arrays of bbcode tags and what we need to do with each one 118 119 // List of all the tags 120 $tags = array('quote', 'code', 'b', 'i', 'u', 'color', 'colour', 'url', 'email', 'img', 'list', '*', 'h'); 121 // List of tags that we need to check are open (You could not put b,i,u in here then illegal nesting like [b][i][/b][/i] would be allowed) 122 $tags_opened = $tags; 123 // and tags we need to check are closed (the same as above, added it just in case) 124 $tags_closed = $tags; 125 // Tags we can nest and the depth they can be nested to (only quotes ) 126 $tags_nested = array('quote' => $forum_config['o_quote_depth'], 'list' => 5, '*' => 5); 127 // Tags to ignore the contents of completely (just code) 128 $tags_ignore = array('code'); 129 // Block tags, block tags can only go within another block tag, they cannot be in a normal tag 130 $tags_block = array('quote', 'code', 'list', 'h', '*'); 131 // Inline tags, we do not allow new lines in these 132 $tags_inline = array('b', 'i', 'u', 'color', 'colour', 'h'); 133 // Tags we trim interior space 134 $tags_trim = array('img'); 135 // Tags we remove quotes from the argument 136 $tags_quotes = array('url', 'email', 'img'); 137 // Tags we limit bbcode in 138 $tags_limit_bbcode = array( 139 '*' => array('b', 'i', 'u', 'color', 'colour', 'url', 'email', 'list', 'img'), 140 'list' => array('*'), 141 'url' => array('b', 'i', 'u', 'color', 'colour', 'img'), 142 'email' => array(), 143 'img' => array() 144 ); 145 // Tags we can automatically fix bad nesting 146 $tags_fix = array('quote', 'b', 'i', 'u', 'color', 'colour', 'url', 'email', 'h'); 147 148 $return = ($hook = get_hook('ps_preparse_tags_start')) ? eval($hook) : null; 149 if ($return !== null) 150 return $return; 151 152 $split_text = preg_split("/(\[[\*a-zA-Z0-9-\/]*?(?:=.*?)?\])/", $text, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); 153 154 $open_tags = array('post'); 155 $open_args = array(''); 156 $opened_tag = 0; 157 $new_text = ''; 158 $current_ignore = ''; 159 $current_nest = ''; 160 $current_depth = array(); 161 $limit_bbcode = $tags; 162 163 foreach ($split_text as $current) 164 { 165 if ($current == '') 166 continue; 167 168 // Are we dealing with a tag? 169 if (substr($current, 0, 1) != '[' || substr($current, -1, 1) != ']') 170 { 171 // Its not a bbcode tag so we put it on the end and continue 172 173 // If we are nested too deeply don't add to the end 174 if ($current_nest) 175 continue; 176 177 $current = str_replace("\r\n", "\n", $current); 178 $current = str_replace("\r", "\n", $current); 179 if (in_array($open_tags[$opened_tag], $tags_inline) && strpos($current, "\n") !== false) 180 { 181 // Deal with new lines 182 $split_current = preg_split("/(\n\n+)/", $current, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); 183 $current = ''; 184 185 if (!forum_trim($split_current[0], "\n")) // the first part is a linebreak so we need to handle any open tags first 186 array_unshift($split_current, ''); 187 188 for ($i = 1; $i < count($split_current); $i += 2) 189 { 190 $temp_opened = array(); 191 $temp_opened_arg = array(); 192 $temp = $split_current[$i - 1]; 193 194 while (!empty($open_tags)) 195 { 196 $temp_tag = array_pop($open_tags); 197 $temp_arg = array_pop($open_args); 198 199 if (in_array($temp_tag , $tags_inline)) 200 { 201 array_push($temp_opened, $temp_tag); 202 array_push($temp_opened_arg, $temp_arg); 203 $temp .= '[/'.$temp_tag.']'; 204 } 205 else 206 { 207 array_push($open_tags, $temp_tag); 208 array_push($open_args, $temp_arg); 209 break; 210 } 211 } 212 $current .= $temp.$split_current[$i]; 213 $temp = ''; 214 while (!empty($temp_opened)) 215 { 216 $temp_tag = array_pop($temp_opened); 217 $temp_arg = array_pop($temp_opened_arg); 218 if (empty($temp_arg)) 219 $temp .= '['.$temp_tag.']'; 220 else 221 $temp .= '['.$temp_tag.'='.$temp_arg.']'; 222 array_push($open_tags, $temp_tag); 223 array_push($open_args, $temp_arg); 224 } 225 $current .= $temp; 226 } 227 228 if (array_key_exists($i - 1, $split_current)) 229 $current .= $split_current[$i - 1]; 230 } 231 232 if (in_array($open_tags[$opened_tag], $tags_trim)) 233 $new_text .= forum_trim($current); 234 else 235 $new_text .= $current; 236 237 continue; 238 } 239 240 // Get the name of the tag 241 $current_arg = ''; 242 if (strpos($current, '/') === 1) 243 { 244 $current_tag = substr($current, 2, -1); 245 } 246 else if (strpos($current, '=') === false) 247 { 248 $current_tag = substr($current, 1, -1); 249 } 250 else 251 { 252 $current_tag = substr($current, 1, strpos($current, '=')-1); 253 $current_arg = substr($current, strpos($current, '=')+1, -1); 254 } 255 $current_tag = strtolower($current_tag); 256 257 // Is the tag defined? 258 if (!in_array($current_tag, $tags)) 259 { 260 // Its not a bbcode tag so we put it on the end and continue 261 if (!$current_nest) 262 $new_text .= $current; 263 264 continue; 265 } 266 267 // We definitely have a bbcode tag. 268 269 // Make the tag string lower case 270 if ($equalpos = strpos($current,'=')) 271 { 272 // We have an argument for the tag which we don't want to make lowercase 273 if (strlen(substr($current, $equalpos)) == 2) 274 { 275 // Empty tag argument 276 $errors[] = sprintf($lang_common['BBCode error 6'], $current_tag); 277 return false; 278 } 279 $current = strtolower(substr($current, 0, $equalpos)).substr($current, $equalpos); 280 } 281 else 282 $current = strtolower($current); 283 284 //This is if we are currently in a tag which escapes other bbcode such as code 285 if ($current_ignore) 286 { 287 if ('[/'.$current_ignore.']' == $current) 288 { 289 // We've finished the ignored section 290 $current = '[/'.$current_tag.']'; 291 $current_ignore = ''; 292 } 293 294 $new_text .= $current; 295 296 continue; 297 } 298 299 if ($current_nest) 300 { 301 // We are currently too deeply nested so lets see if we are closing the tag or not. 302 if ($current_tag != $current_nest) 303 continue; 304 305 if (substr($current, 1, 1) == '/') 306 $current_depth[$current_nest]--; 307 else 308 $current_depth[$current_nest]++; 309 310 if ($current_depth[$current_nest] <= $tags_nested[$current_nest]) 311 $current_nest = ''; 312 313 continue; 314 } 315 316 // Check the current tag is allowed here 317 if (!in_array($current_tag, $limit_bbcode) && $current_tag != $open_tags[$opened_tag]) 318 { 319 $errors[] = sprintf($lang_common['BBCode error 3'], $current_tag, $open_tags[$opened_tag]); 320 return false; 321 } 322 323 if (substr($current, 1, 1) == '/') 324 { 325 //This is if we are closing a tag 326 327 if ($opened_tag == 0 || !in_array($current_tag, $open_tags)) 328 { 329 //We tried to close a tag which is not open 330 if (in_array($current_tag, $tags_opened)) 331 { 332 $errors[] = sprintf($lang_common['BBCode error 1'], $current_tag); 333 return false; 334 } 335 } 336 else 337 { 338 // Check nesting 339 while (true) 340 { 341 // Nesting is ok 342 if ($open_tags[$opened_tag] == $current_tag) 343 { 344 array_pop($open_tags); 345 array_pop($open_args); 346 $opened_tag--; 347 break; 348 } 349 350 // Nesting isn't ok, try to fix it 351 if (in_array($open_tags[$opened_tag], $tags_closed) && in_array($current_tag, $tags_closed)) 352 { 353 if (in_array($current_tag, $open_tags)) 354 { 355 $temp_opened = array(); 356 $temp_opened_arg = array(); 357 $temp = ''; 358 while (!empty($open_tags)) 359 { 360 $temp_tag = array_pop($open_tags); 361 $temp_arg = array_pop($open_args); 362 363 if (!in_array($temp_tag, $tags_fix)) 364 { 365 // We couldn't fix nesting 366 $errors[] = sprintf($lang_common['BBCode error 5'], $temp_tag); 367 return false; 368 } 369 array_push($temp_opened, $temp_tag); 370 array_push($temp_opened_arg, $temp_arg); 371 372 if ($temp_tag == $current_tag) 373 break; 374 else 375 $temp .= '[/'.$temp_tag.']'; 376 } 377 $current = $temp.$current; 378 $temp = ''; 379 array_pop($temp_opened); 380 array_pop($temp_opened_arg); 381 382 while (!empty($temp_opened)) 383 { 384 $temp_tag = array_pop($temp_opened); 385 $temp_arg = array_pop($temp_opened_arg); 386 if (empty($temp_arg)) 387 $temp .= '['.$temp_tag.']'; 388 else 389 $temp .= '['.$temp_tag.'='.$temp_arg.']'; 390 array_push($open_tags, $temp_tag); 391 array_push($open_args, $temp_arg); 392 } 393 $current .= $temp; 394 $opened_tag--; 395 break; 396 } 397 else 398 { 399 // We couldn't fix nesting 400 $errors[] = sprintf($lang_common['BBCode error 1'], $current_tag); 401 return false; 402 } 403 } 404 else if (in_array($open_tags[$opened_tag], $tags_closed)) 405 break; 406 else 407 { 408 array_pop($open_tags); 409 array_pop($open_args); 410 $opened_tag--; 411 } 412 } 413 } 414 415 if (in_array($current_tag, array_keys($tags_nested))) 416 { 417 if (isset($current_depth[$current_tag])) 418 $current_depth[$current_tag]--; 419 } 420 421 if (in_array($open_tags[$opened_tag], array_keys($tags_limit_bbcode))) 422 $limit_bbcode = $tags_limit_bbcode[$open_tags[$opened_tag]]; 423 else 424 $limit_bbcode = $tags; 425 $new_text .= $current; 426 427 continue; 428 } 429 else 430 { 431 // We are opening a tag 432 if (in_array($current_tag, array_keys($tags_limit_bbcode))) 433 $limit_bbcode = $tags_limit_bbcode[$current_tag]; 434 else 435 $limit_bbcode = $tags; 436 437 if (in_array($current_tag, $tags_block) && !in_array($open_tags[$opened_tag], $tags_block) && $opened_tag != 0) 438 { 439 // We tried to open a block tag within a non-block tag 440 $errors[] = sprintf($lang_common['BBCode error 3'], $current_tag, $open_tags[$opened_tag]); 441 return false; 442 } 443 444 if (in_array($current_tag, $tags_ignore)) 445 { 446 // Its an ignore tag so we don't need to worry about whats inside it, 447 $current_ignore = $current_tag; 448 $new_text .= $current; 449 continue; 450 } 451 452 // Deal with nested tags 453 if (in_array($current_tag, $open_tags) && !in_array($current_tag, array_keys($tags_nested))) 454 { 455 // We nested a tag we shouldn't 456 $errors[] = sprintf($lang_common['BBCode error 4'], $current_tag); 457 return false; 458 } 459 else if (in_array($current_tag, array_keys($tags_nested))) 460 { 461 // We are allowed to nest this tag 462 463 if (isset($current_depth[$current_tag])) 464 $current_depth[$current_tag]++; 465 else 466 $current_depth[$current_tag] = 1; 467 468 // See if we are nested too deep 469 if ($current_depth[$current_tag] > $tags_nested[$current_tag]) 470 { 471 $current_nest = $current_tag; 472 continue; 473 } 474 } 475 476 // Remove quotes from arguments for certain tags 477 if (strpos($current, '=') !== false && in_array($current_tag, $tags_quotes)) 478 { 479 $current = preg_replace('#\['.$current_tag.'=("|\'|)(.*?)\\1\]\s*#i', '['.$current_tag.'=$2]', $current); 480 } 481 482 if (in_array($current_tag, array_keys($tags_limit_bbcode))) 483 $limit_bbcode = $tags_limit_bbcode[$current_tag]; 484 485 $open_tags[] = $current_tag; 486 $open_args[] = $current_arg; 487 $opened_tag++; 488 $new_text .= $current; 489 continue; 490 } 491 } 492 493 // Check we closed all the tags we needed to 494 foreach ($tags_closed as $check) 495 { 496 if (in_array($check, $open_tags)) 497 { 498 // We left an important tag open 499 $errors[] = sprintf($lang_common['BBCode error 5'], $check); 500 return false; 501 } 502 } 503 504 if ($current_ignore) 505 { 506 // We left an ignore tag open 507 $errors[] = sprintf($lang_common['BBCode error 5'], $current_ignore); 508 return false; 509 } 510 511 $return = ($hook = get_hook('ps_preparse_tags_end')) ? eval($hook) : null; 512 if ($return !== null) 513 return $return; 514 515 return $new_text; 516} 517 518 519// 520// Preparse the contents of [list] bbcode 521// 522function preparse_list_tag($content, $type = '*', &$errors) 523{ 524 global $lang_common; 525 526 if (strlen($type) != 1) 527 $type = '*'; 528 529 if (strpos($content,'[list') !== false) 530 { 531 $pattern_callback = '%\[list(?:=([1a*]))?+\]((?:(?>.*?(?=\[list(?:=[1a*])?+\]|\[/list\]))|(?R))*)\[/list\]%is'; 532 $content = preg_replace_callback($pattern_callback, $callback = function($matches, $errors) { 533 return preparse_list_tag($matches[2], $matches[1], $errors); 534 }, $content); 535 } 536 537 $items = explode('[*]', str_replace('\"', '"', $content)); 538 539 $content = ''; 540 foreach ($items as $item) 541 { 542 if (forum_trim($item) != '') 543 $content .= '[*'."\0".']'.str_replace('[/*]', '', forum_trim($item)).'[/*'."\0".']'."\n"; 544 } 545 546 return '[list='.$type.']'."\n".$content.'[/list]'; 547} 548 549 550// 551// Split text into chunks ($inside contains all text inside $start and $end, and $outside contains all text outside) 552// 553function split_text($text, $start, $end, &$errors, $retab = true) 554{ 555 global $forum_config, $lang_common; 556 557 $tokens = explode($start, $text); 558 559 $outside[] = $tokens[0]; 560 561 $num_tokens = count($tokens); 562 for ($i = 1; $i < $num_tokens; ++$i) 563 { 564 $temp = explode($end, $tokens[$i]); 565 566 if (count($temp) != 2) 567 { 568 $errors[] = $lang_common['BBCode code problem']; 569 return array(null, array($text)); 570 } 571 $inside[] = $temp[0]; 572 $outside[] = $temp[1]; 573 } 574 575 if ($forum_config['o_indent_num_spaces'] != 8 && $retab) 576 { 577 $spaces = str_repeat(' ', $forum_config['o_indent_num_spaces']); 578 $inside = str_replace("\t", $spaces, $inside); 579 } 580 581 return array($inside, $outside); 582} 583 584 585// 586// Truncate URL if longer than 55 characters (add http:// or ftp:// if missing) 587// 588function handle_url_tag($url, $link = '', $bbcode = false) 589{ 590 $return = ($hook = get_hook('ps_handle_url_tag_start')) ? eval($hook) : null; 591 if ($return !== null) 592 return $return; 593 594 $full_url = str_replace(array(' ', '\'', '`', '"'), array('%20', '', '', ''), $url); 595 if (strpos($url, 'www.') === 0) // If it starts with www, we add http:// 596 $full_url = 'http://'.$full_url; 597 else if (strpos($url, 'ftp.') === 0) // Else if it starts with ftp, we add ftp:// 598 $full_url = 'ftp://'.$full_url; 599 else if (!preg_match('#^([a-z0-9]{3,6})://#', $url)) // Else if it doesn't start with abcdef://, we add http:// 600 $full_url = 'http://'.$full_url; 601 602 if (defined('FORUM_SUPPORT_PCRE_UNICODE') && defined('FORUM_ENABLE_IDNA')) 603 { 604 static $idn; 605 static $cached_encoded_urls = null; 606 607 if (is_null($cached_encoded_urls)) 608 $cached_encoded_urls = array(); 609 610 // Check in cache 611 $cache_key = md5($full_url); 612 if (isset($cached_encoded_urls[$cache_key])) 613 $full_url = $cached_encoded_urls[$cache_key]; 614 else 615 { 616 if (!isset($idn)) 617 { 618 $idn = new idna_convert(); 619 $idn->set_parameter('encoding', 'utf8'); 620 $idn->set_parameter('strict', false); 621 } 622 623 $full_url = $idn->encode($full_url); 624 $cached_encoded_urls[$cache_key] = $full_url; 625 } 626 } 627 628 // Ok, not very pretty :-) 629 if (!$bbcode) 630 { 631 if (defined('FORUM_SUPPORT_PCRE_UNICODE') && defined('FORUM_ENABLE_IDNA')) 632 { 633 $link_name = ($link == '' || $link == $url) ? $url : $link; 634 if (preg_match('!^(https?|ftp|news){1}'.preg_quote('://xn--', '!').'!', $link_name)) 635 { 636 $link = $idn->decode($link_name); 637 } 638 } 639 640 $link = ($link == '' || $link == $url) ? ((utf8_strlen($url) > 55) ? utf8_substr($url, 0 , 39).' … '.utf8_substr($url, -10) : $url) : stripslashes($link); 641 } 642 643 $return = ($hook = get_hook('ps_handle_url_tag_end')) ? eval($hook) : null; 644 if ($return !== null) 645 return $return; 646 647 if ($bbcode) 648 { 649 if (defined('FORUM_SUPPORT_PCRE_UNICODE') && defined('FORUM_ENABLE_IDNA')) 650 { 651 if (preg_match('!^(https?|ftp|news){1}'.preg_quote('://xn--', '!').'!', $link)) 652 { 653 $link = $idn->decode($link); 654 } 655 } 656 657 if ($full_url == $link) 658 return '[url]'.$link.'[/url]'; 659 else 660 return '[url='.$full_url.']'.$link.'[/url]'; 661 } 662 else 663 return '<a href="'.$full_url.'">'.$link.'</a>'; 664} 665 666 667// 668// Callback for handle_url_tag 669// 670function callback_handle_url_nobb($reg) 671{ 672 return handle_url_tag($reg[1], (isset($reg[2]) ? $reg[2] : ''), false); 673} 674 675// 676// Callback for handle_url_tag 677// 678function callback_handle_url_bb($reg) 679{ 680 return handle_url_tag($reg[1], (isset($reg[2]) ? $reg[2] : ''), true); 681} 682 683 684// 685// Turns an URL from the [img] tag into an <img> tag or a <a href...> tag 686// 687function handle_img_tag($url, $is_signature = false, $alt = null) 688{ 689 global $lang_common, $forum_user; 690 691 $return = ($hook = get_hook('ps_handle_img_tag_start')) ? eval($hook) : null; 692 if ($return !== null) 693 return $return; 694 695 if ($alt == null) 696 $alt = $url; 697 698 $img_tag = '<a href="'.$url.'"><'.$lang_common['Image link'].'></a>'; 699 700 if ($is_signature && $forum_user['show_img_sig'] != '0') 701 $img_tag = '<img class="sigimage" src="'.$url.'" alt="'.forum_htmlencode($alt).'" />'; 702 else if (!$is_signature && $forum_user['show_img'] != '0') 703 $img_tag = '<span class="postimg"><img src="'.$url.'" alt="'.forum_htmlencode($alt).'" /></span>'; 704 705 $return = ($hook = get_hook('ps_handle_img_tag_end')) ? eval($hook) : null; 706 if ($return !== null) 707 return $return; 708 709 return $img_tag; 710} 711 712 713// 714// Parse the contents of [list] bbcode 715// 716function handle_list_tag($content, $type = '*') 717{ 718 if (strlen($type) != 1) 719 $type = '*'; 720 721 if (strpos($content,'[list') !== false) 722 { 723 $pattern_callback = '%\[list(?:=([1a*]))?+\]((?:(?>.*?(?=\[list(?:=[1a*])?+\]|\[/list\]))|(?R))*)\[/list\]%is'; 724 $content = preg_replace_callback($pattern_callback, function($matches) { 725 return handle_list_tag($matches[2], $matches[1]); 726 }, $content); 727 } 728 729 $content = preg_replace('#\s*\[\*\](.*?)\[/\*\]\s*#s', '<li><p>$1</p></li>', forum_trim($content)); 730 731 if ($type == '*') 732 $content = '<ul>'.$content.'</ul>'; 733 else 734 if ($type == 'a') 735 $content = '<ol class="alpha">'.$content.'</ol>'; 736 else 737 $content = '<ol class="decimal">'.$content.'</ol>'; 738 739 return '</p>'.$content.'<p>'; 740} 741 742 743// 744// Convert BBCodes to their HTML equivalent 745// 746function do_bbcode($text, $is_signature = false) 747{ 748 global $lang_common, $forum_user, $forum_config; 749 750 $return = ($hook = get_hook('ps_do_bbcode_start')) ? eval($hook) : null; 751 if ($return !== null) 752 return $return; 753 754 if (strpos($text, '[quote') !== false) 755 { 756 $text = preg_replace_callback( 757 '#\[quote=(&\#039;|"|"|\'|)(.*?)\\1\]#', function($matches) { 758global $lang_common; 759return '</p><div class="quotebox"><cite>'.str_replace(array('[', '"'), array('[', '"'), $matches[2])." ".$lang_common['wrote'].":</cite><blockquote><p>"; 760}, 761$text); 762 $text = preg_replace('#\[quote\]\s*#', '</p><div class="quotebox"><blockquote><p>', $text); 763 $text = preg_replace('#\s*\[\/quote\]#S', '</p></blockquote></div><p>', $text); 764 } 765 766 if (!$is_signature) 767 { 768 $pattern_callback[] = '%\[list(?:=([1a*]))?+\]((?:(?>.*?(?=\[list(?:=[1a*])?+\]|\[/list\]))|(?R))*)\[/list\]%is'; 769 $replace_callback[] = 'handle_list_tag($matches[2], $matches[1])'; 770 } 771 772 $pattern[] = '#\[email\]([^\[]*?)\[/email\]#'; 773 $pattern[] = '#\[email=([^\[]+?)\](.*?)\[/email\]#'; 774 775 $replace[] = '<a href=\"mailto:$matches[1]\">$matches[1]</a>'; 776 $replace[] = '<a href=\"mailto:$matches[1]\">$matches[2]</a>'; 777 778 $pattern[] = '#\[b\](.*?)\[/b\]#ms'; 779 $pattern[] = '#\[i\](.*?)\[/i\]#ms'; 780 $pattern[] = '#\[u\](.*?)\[/u\]#ms'; 781 $pattern[] = '#\[colou?r=([a-zA-Z]{3,20}|\#[0-9a-fA-F]{6}|\#[0-9a-fA-F]{3})](.*?)\[/colou?r\]#ms'; 782 $pattern[] = '#\[h\](.*?)\[/h\]#ms'; 783 784 $replace[] = '<strong>$matches[1]</strong>'; 785 $replace[] = '<em>$matches[1]</em>'; 786 $replace[] = '<span class=\"bbu\">$matches[1]</span>'; 787 $replace[] = '<span style=\"color: $matches[1]\">$matches[2]</span>'; 788 $replace[] = '</p><h5>$matches[1]</h5><p>'; 789 790 if (($is_signature && $forum_config['p_sig_img_tag'] == '1') || (!$is_signature && $forum_config['p_message_img_tag'] == '1')) 791 { 792 $pattern[] = '#\[img\]((ht|f)tps?://)([^\s<"]*?)\[/img\]#'; 793 $pattern[] = '#\[img=([^\[]*?)\]((ht|f)tps?://)([^\s<"]*?)\[/img\]#'; 794 if ($is_signature) 795 { 796 $replace[] = '".handle_img_tag($matches[1].$matches[3], true)."'; 797 $replace[] = '".handle_img_tag($matches[2].$matches[4], true, $matches[1])."'; 798 } 799 else 800 { 801 $replace[] = '".handle_img_tag($matches[1].$matches[3], false)."'; 802 $replace[] = '".handle_img_tag($matches[2].$matches[4], false, $matches[1])."'; 803 } 804 } 805 806 $text = preg_replace_callback('#\[url\]([^\[]*?)\[/url\]#', 'callback_handle_url_nobb', $text); 807 $text = preg_replace_callback('#\[url=([^\[]+?)\](.*?)\[/url\]#', 'callback_handle_url_nobb', $text); 808 809 $return = ($hook = get_hook('ps_do_bbcode_replace')) ? eval($hook) : null; 810 if ($return !== null) 811 return $return; 812 813 $count = count($pattern); 814 for ($i = 0; $i < $count; $i++) { 815 $text = preg_replace_callback($pattern[$i], function($matches) use ($replace, $i) { 816 return eval('return "'.$replace[$i].'";'); 817 }, $text); 818 } 819 820 $count = count($pattern_callback); 821 for ($i = 0; $i < $count; $i++) { 822 $text = preg_replace_callback($pattern_callback[$i], function($matches) use ($replace_callback, $i) { 823 return eval('return '.$replace_callback[$i].';'); 824 }, $text); 825 } 826 $return = ($hook = get_hook('ps_do_bbcode_end')) ? eval($hook) : null; 827 if ($return !== null) 828 return $return; 829 830 return $text; 831} 832 833 834// 835// Make hyperlinks clickable 836// 837function do_clickable($text, $unicode = FALSE) 838{ 839 $text = ' '.$text; 840 841 if ($unicode) 842 { 843 // Round 1 844 $text = preg_replace_callback( 845 '#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(https?|ftp|news){1}://([\p{Nd}\p{L}\-]+\.([\p{Nd}\p{L}\-]+\.)*[\p{Nd}\p{L}\-]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-]?)?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#iu', 846 function($matches) { 847 for($i = 1; $i <= 12; $i++) { 848 $matches[$i] = isset($matches[$i]) ? $matches[$i]:''; 849 } 850 return stripslashes($matches[1].$matches[2].$matches[3].$matches[4]). 851 handle_url_tag($matches[5].'://'.$matches[6], $matches[5].'://'.$matches[6], true). 852 stripslashes($matches[4].$matches[10].$matches[11].$matches[12]); 853 }, $text); 854 855 // Round 2 856 $text = preg_replace_callback( 857 '#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(www|ftp)\.(([\p{Nd}\p{L}\-]+\.)*[\p{Nd}\p{L}\-]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-])?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#iu', 858 function($matches) { 859 for($i = 1; $i <= 12; $i++) { 860 $matches[$i] = isset($matches[$i]) ? $matches[$i] : ''; 861 } 862 return stripslashes($matches[1].$matches[2].$matches[3].$matches[4]). 863 handle_url_tag($matches[5].'.'.$matches[6], $matches[5].'.'.$matches[6], true). 864 stripslashes($matches[4].$matches[10].$matches[11].$matches[12]); 865 }, $text); 866 } 867 else 868 { 869 // Round 1 870 $text = preg_replace_callback( 871 '#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(https?|ftp|news){1}://([\w\-]+\.([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-]?)?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#i', 872 function($matches) { 873 for($i = 1; $i <= 12; $i++) { 874 $matches[$i] = isset($matches[$i]) ? $matches[$i] : ''; 875 } 876 return stripslashes($matches[1].$matches[2].$matches[3].$matches[4]). 877 handle_url_tag($matches[5].'://'.$matches[6], $matches[5].'://'.$matches[6], true). 878 stripslashes($matches[4].$matches[10].$matches[11].$matches[12]); 879 }, $text); 880 881 // Round 2 882 $text = preg_replace_callback( 883 '#(?<=[\s\]\)])(<)?(\[)?(\()?([\'"]?)(www|ftp)\.(([\w\-]+\.)*[\w]+(:[0-9]+)?(/[^\s\[]*[^\s.,?!\[;:-])?)\4(?(3)(\)))(?(2)(\]))(?(1)(>))(?![^\s]*\[/(?:url|img)\])#i', 884 function($matches) { 885 for($i = 1; $i <= 12; $i++) { 886 $matches[$i] = isset($matches[$i]) ? $matches[$i]:''; 887 } 888 return stripslashes($matches[1].$matches[2].$matches[3].$matches[4]). 889 handle_url_tag($matches[5].'.'.$matches[6], $matches[5].'.'.$matches[6], true). 890 stripslashes($matches[4].$matches[10].$matches[11].$matches[12]); 891 }, $text); 892 } 893 894 return substr($text, 1); 895} 896 897 898// 899// Convert a series of smilies to images 900// 901function do_smilies($text) 902{ 903 global $forum_config, $base_url, $smilies; 904 905 $return = ($hook = get_hook('ps_do_smilies_start')) ? eval($hook) : null; 906 if ($return !== null) 907 return $return; 908 909 $text = ' '.$text.' '; 910 911 foreach ($smilies as $smiley_text => $smiley_img) 912 { 913 if (strpos($text, $smiley_text) !== false) 914 $text = preg_replace("#(?<=[>\s])".preg_quote($smiley_text, '#')."(?=\W)#m", '<img src="'.$base_url.'/img/smilies/'.$smiley_img.'" width="15" height="15" alt="'.substr($smiley_img, 0, strrpos($smiley_img, '.')).'" />', $text); 915 } 916 917 $return = ($hook = get_hook('ps_do_smilies_end')) ? eval($hook) : null; 918 919 return substr($text, 1, -1); 920} 921 922 923// 924// Parse message text 925// 926function parse_message($text, $hide_smilies) 927{ 928 global $forum_config, $lang_common, $forum_user; 929 930 $return = ($hook = get_hook('ps_parse_message_start')) ? eval($hook) : null; 931 if ($return !== null) 932 return $return; 933 934 if ($forum_config['o_censoring'] == '1') 935 $text = censor_words($text); 936 937 $return = ($hook = get_hook('ps_parse_message_post_censor')) ? eval($hook) : null; 938 if ($return !== null) 939 return $return; 940 941 // Convert applicable characters to HTML entities 942 $text = forum_htmlencode($text); 943 944 $return = ($hook = get_hook('ps_parse_message_pre_split')) ? eval($hook) : null; 945 if ($return !== null) 946 return $return; 947 948 // If the message contains a code tag we have to split it up (text within [code][/code] shouldn't be touched) 949 if (strpos($text, '[code]') !== false && strpos($text, '[/code]') !== false) 950 { 951 list($inside, $outside) = split_text($text, '[code]', '[/code]', $errors); 952 $text = implode("\x1", $outside); 953 } 954 955 $return = ($hook = get_hook('ps_parse_message_post_split')) ? eval($hook) : null; 956 if ($return !== null) 957 return $return; 958 959 if ($forum_config['p_message_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false) 960 $text = do_bbcode($text); 961 962 if ($forum_config['o_smilies'] == '1' && $forum_user['show_smilies'] == '1' && $hide_smilies == '0') 963 $text = do_smilies($text); 964 965 $return = ($hook = get_hook('ps_parse_message_bbcode')) ? eval($hook) : null; 966 if ($return !== null) 967 return $return; 968 969 // Deal with newlines, tabs and multiple spaces 970 $pattern = array("\n", "\t", ' ', ' '); 971 $replace = array('<br />', ' ', ' ', ' '); 972 $text = str_replace($pattern, $replace, $text); 973 974 $return = ($hook = get_hook('ps_parse_message_pre_merge')) ? eval($hook) : null; 975 if ($return !== null) 976 return $return; 977 978 // If we split up the message before we have to concatenate it together again (code tags) 979 if (isset($inside)) 980 { 981 $outside = explode("\x1", $text); 982 $text = ''; 983 984 $num_tokens = count($outside); 985 986 for ($i = 0; $i < $num_tokens; ++$i) 987 { 988 $text .= $outside[$i]; 989 if (isset($inside[$i])) 990 $text .= '</p><div class="codebox"><pre><code>'.forum_trim($inside[$i], "\n\r").'</code></pre></div><p>'; 991 } 992 } 993 994 $return = ($hook = get_hook('ps_parse_message_post_merge')) ? eval($hook) : null; 995 if ($return !== null) 996 return $return; 997 998 // Add paragraph tag around post, but make sure there are no empty paragraphs 999 $text = preg_replace('#<br />\s*?<br />((\s*<br />)*)#i', "</p>$1<p>", $text); 1000 $text = str_replace('<p><br />', '<p>', $text); 1001 $text = str_replace('<p></p>', '', '<p>'.$text.'</p>'); 1002 1003 $return = ($hook = get_hook('ps_parse_message_end')) ? eval($hook) : null; 1004 if ($return !== null) 1005 return $return; 1006 1007 return $text; 1008} 1009 1010 1011// 1012// Parse signature text 1013// 1014function parse_signature($text) 1015{ 1016 global $forum_config, $lang_common, $forum_user; 1017 1018 $return = ($hook = get_hook('ps_parse_signature_start')) ? eval($hook) : null; 1019 if ($return !== null) 1020 return $return; 1021 1022 if ($forum_config['o_censoring'] == '1') 1023 $text = censor_words($text); 1024 1025 $return = ($hook = get_hook('ps_parse_signature_post_censor')) ? eval($hook) : null; 1026 if ($return !== null) 1027 return $return; 1028 1029 // Convert applicable characters to HTML entities 1030 $text = forum_htmlencode($text); 1031 1032 $return = ($hook = get_hook('ps_parse_signature_pre_bbcode')) ? eval($hook) : null; 1033 if ($return !== null) 1034 return $return; 1035 1036 if ($forum_config['p_sig_bbcode'] == '1' && strpos($text, '[') !== false && strpos($text, ']') !== false) 1037 $text = do_bbcode($text, true); 1038 1039 if ($forum_config['o_smilies_sig'] == '1' && $forum_user['show_smilies'] == '1') 1040 $text = do_smilies($text); 1041 1042 $return = ($hook = get_hook('ps_parse_signature_post_bbcode')) ? eval($hook) : null; 1043 if ($return !== null) 1044 return $return; 1045 1046 // Deal with newlines, tabs and multiple spaces 1047 $pattern = array("\n", "\t", ' ', ' '); 1048 $replace = array('<br />', ' ', ' ', ' '); 1049 $text = str_replace($pattern, $replace, $text); 1050 1051 $return = ($hook = get_hook('ps_parse_signature_end')) ? eval($hook) : null; 1052 if ($return !== null) 1053 return $return; 1054 1055 return $text; 1056} 1057 1058define('FORUM_PARSER_LOADED', 1); 1059