[ Index ] |
PHP Cross Reference of MyBB 1.8.39 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * MyBB 1.8 4 * Copyright 2014 MyBB Group, All Rights Reserved 5 * 6 * Website: http://www.mybb.com 7 * License: http://www.mybb.com/about/license 8 * 9 */ 10 11 /* 12 options = array( 13 allow_html 14 allow_smilies 15 allow_mycode 16 allow_auto_url 17 nl2br 18 filter_badwords 19 me_username 20 shorten_urls 21 highlight 22 filter_cdata 23 ) 24 */ 25 26 class postParser 27 { 28 /** 29 * Internal cache of MyCode. 30 * 31 * @access public 32 * @var mixed 33 */ 34 public $mycode_cache = 0; 35 36 /** 37 * Internal cache of smilies 38 * 39 * @access public 40 * @var mixed 41 */ 42 public $smilies_cache = 0; 43 44 /** 45 * Internal cache of badwords filters 46 * 47 * @access public 48 * @var mixed 49 */ 50 public $badwords_cache = 0; 51 52 /** 53 * Base URL for smilies 54 * 55 * @access public 56 * @var string 57 */ 58 public $base_url; 59 60 /** 61 * Parsed Highlights cache 62 * 63 * @access public 64 * @var array 65 */ 66 public $highlight_cache = array(); 67 68 /** 69 * Options for this parsed message 70 * 71 * @access public 72 * @var array 73 */ 74 public $options; 75 76 /** 77 * Internal cache for nested lists 78 * 79 * @access public 80 * @var array 81 */ 82 public $list_elements; 83 84 /** 85 * Internal counter for nested lists 86 * 87 * @access public 88 * @var int 89 */ 90 public $list_count; 91 92 /** 93 * Whether or not should a <br /> with clear: both be added at the end of the parsed message 94 * 95 * @access public 96 * @var boolean 97 */ 98 public $clear_needed = false; 99 100 /** 101 * Don't validate parser output 102 */ 103 const VALIDATION_DISABLE = 0; 104 105 /** 106 * Validate parser output and log errors 107 */ 108 const VALIDATION_REPORT_ONLY = 1; 109 110 /** 111 * Validate parser output, log errors, and block output on failure 112 */ 113 const VALIDATION_REQUIRE = 2; 114 115 /** 116 * Whether to validate the parser's HTML output when `allow_html` is disabled. 117 * Validation errors will be logged/sent/displayed according to board settings. 118 * 119 * @access public 120 * @var self::VALIDATION_* 121 */ 122 public $output_validation_policy = self::VALIDATION_REQUIRE; 123 124 /** 125 * Parses a message with the specified options. 126 * 127 * @param string $message The message to be parsed. 128 * @param array $options Array of yes/no options 129 * @return string The parsed message. 130 */ 131 function parse_message($message, $options=array()) 132 { 133 global $plugins, $mybb; 134 135 $original_message = $message; 136 137 $this->clear_needed = false; 138 139 // Set base URL for parsing smilies 140 $this->base_url = $mybb->settings['bburl']; 141 142 if($this->base_url != "") 143 { 144 if(my_substr($this->base_url, my_strlen($this->base_url) -1) != "/") 145 { 146 $this->base_url = $this->base_url."/"; 147 } 148 } 149 150 // Set the options 151 $this->options = $options; 152 153 $message = $plugins->run_hooks("parse_message_start", $message); 154 155 // Get rid of carriage returns for they are the workings of the devil 156 $message = str_replace("\r", "", $message); 157 158 // Filter bad words if requested. 159 if(!empty($this->options['filter_badwords'])) 160 { 161 $message = $this->parse_badwords($message); 162 } 163 164 // Filter CDATA tags if requested (syndication.php). 165 if(!empty($this->options['filter_cdata'])) 166 { 167 $message = $this->parse_cdata($message); 168 } 169 170 // If MyCode needs to be replaced, first filter out [code] and [php] tags. 171 $code_matches = array(); 172 if(!empty($this->options['allow_mycode']) && $mybb->settings['allowcodemycode'] == 1) 173 { 174 // This code is reserved and could break codes 175 $message = str_replace("<mybb-code>\n", "<mybb_code>\n", $message); 176 177 preg_match_all("#\[(code|php)\](.*?)(\[/\\1\])+(\r\n?|\n?)#si", $message, $code_matches, PREG_SET_ORDER); 178 foreach($code_matches as $point => $part) 179 { 180 if(isset($part[3])) 181 { 182 $part[1] = "[".$part[1]."]"; 183 $code_matches[$point][2] = substr_replace($part[0], "", strrpos($part[0], $part[3]), strlen($part[3])); 184 $code_matches[$point][2] = substr_replace($code_matches[$point][2], "", strpos($code_matches[$point][2], $part[1]), strlen($part[1])); 185 } 186 } 187 $message = preg_replace("#\[(code|php)\](.*?)(\[/\\1\])+(\r\n?|\n?)#si", "<mybb-code>\n", $message); 188 } 189 190 if(empty($this->options['allow_html'])) 191 { 192 $message = $this->parse_html($message); 193 $message = str_replace("<mybb-code>\n", "<mybb-code>\n", $message); 194 } 195 else 196 { 197 // Replace base, meta,script and style tags in our post - these are > dangerous < 198 $message = preg_replace('#<(/?)(base|meta|script|style)([^>]*)>#i', '<$1$2$3>', $message); 199 $message = $this->fix_javascript($message); 200 201 $find = array("<br />\n", "<br>\n"); 202 $replace = array("\n", "\n"); 203 $message = str_replace($find, $replace, $message); 204 } 205 206 $message = $plugins->run_hooks("parse_message_htmlsanitized", $message); 207 208 // Replace "me" code and slaps if we have a username 209 if(!empty($this->options['me_username']) && $mybb->settings['allowmemycode'] == 1) 210 { 211 global $lang; 212 213 $message = preg_replace('#(>|^|\r|\n)/me ([^\r\n<]*)#i', "\\1<span style=\"color: red;\" class=\"mycode_me\">* {$this->options['me_username']} \\2</span>", $message); 214 $message = preg_replace('#(>|^|\r|\n)/slap ([^\r\n<]*)#i', "\\1<span style=\"color: red;\" class=\"mycode_slap\">* {$this->options['me_username']} {$lang->slaps} \\2 {$lang->with_trout}</span>", $message); 215 } 216 217 $message = $plugins->run_hooks("parse_message_me_mycode", $message); 218 219 // If we can, parse smilies 220 if(!empty($this->options['allow_smilies'])) 221 { 222 $message = $this->parse_smilies($message, $this->options['allow_html']); 223 } 224 225 // Replace MyCode if requested. 226 if(!empty($this->options['allow_mycode'])) 227 { 228 $message = $this->parse_mycode($message); 229 } 230 231 // Filter url codes, if disabled. 232 if($mybb->settings['allowlinkmycode'] != 1) 233 { 234 $message = preg_replace("#\[(\/)?url{1}(.*?)\]#i", "", $message); 235 } 236 237 // Parse Highlights 238 if(!empty($this->options['highlight'])) 239 { 240 $message = $this->highlight_message($message, $this->options['highlight']); 241 } 242 243 // Run plugin hooks 244 $message = $plugins->run_hooks("parse_message", $message); 245 246 if(!empty($this->options['allow_mycode'])) 247 { 248 // Now that we're done, if we split up any code tags, parse them and glue it all back together 249 if(count($code_matches) > 0) 250 { 251 foreach($code_matches as $text) 252 { 253 if(my_strtolower($text[1]) == "code") 254 { 255 // Fix up HTML inside the code tags so it is clean 256 $text[2] = $this->parse_html($text[2]); 257 258 $code = $this->mycode_parse_code($text[2]); 259 } 260 elseif(my_strtolower($text[1]) == "php") 261 { 262 $code = $this->mycode_parse_php($text[2]); 263 } 264 $message = preg_replace("#\<mybb-code>\n?#", $code, $message, 1); 265 } 266 } 267 } 268 269 if(!isset($this->options['nl2br']) || $this->options['nl2br'] != 0) 270 { 271 $message = nl2br($message); 272 // Fix up new lines and block level elements 273 $message = preg_replace("#(</?(?:html|head|body|div|p|form|table|thead|tbody|tfoot|tr|td|th|ul|ol|li|div|p|blockquote|cite|hr)[^>]*>)\s*<br />#i", "$1", $message); 274 $message = preg_replace("#( )+(</?(?:html|head|body|div|p|form|table|thead|tbody|tfoot|tr|td|th|ul|ol|li|div|p|blockquote|cite|hr)[^>]*>)#i", "$2", $message); 275 } 276 277 if($this->clear_needed) 278 { 279 $message .= '<br class="clear" />'; 280 } 281 282 $message = $plugins->run_hooks("parse_message_end", $message); 283 284 if ($this->output_allowed($original_message, $message) === true) 285 { 286 return $message; 287 } 288 else 289 { 290 return ''; 291 } 292 } 293 294 /** 295 * Converts HTML in a message to their specific entities whilst allowing unicode characters. 296 * 297 * @param string $message The message to be parsed. 298 * @return string The formatted message. 299 */ 300 function parse_html($message) 301 { 302 $message = preg_replace("#&(?!\#[0-9]+;)#si", "&", $message); // fix & but allow unicode 303 $message = str_replace("<","<",$message); 304 $message = str_replace(">",">",$message); 305 return $message; 306 } 307 308 /** 309 * Generates a cache of MyCode, both standard and custom. 310 * 311 * @access private 312 */ 313 function cache_mycode() 314 { 315 global $cache, $lang, $mybb; 316 $this->mycode_cache = array(); 317 318 $standard_mycode = $callback_mycode = $nestable_mycode = $nestable_callback_mycode = array(); 319 $standard_count = $callback_count = $nestable_count = $nestable_callback_count = 0; 320 321 if($mybb->settings['allowbasicmycode'] == 1) 322 { 323 $standard_mycode['b']['regex'] = "#\[b\](.*?)\[/b\]#si"; 324 $standard_mycode['b']['replacement'] = "<span style=\"font-weight: bold;\" class=\"mycode_b\">$1</span>"; 325 326 $standard_mycode['u']['regex'] = "#\[u\](.*?)\[/u\]#si"; 327 $standard_mycode['u']['replacement'] = "<span style=\"text-decoration: underline;\" class=\"mycode_u\">$1</span>"; 328 329 $standard_mycode['i']['regex'] = "#\[i\](.*?)\[/i\]#si"; 330 $standard_mycode['i']['replacement'] = "<span style=\"font-style: italic;\" class=\"mycode_i\">$1</span>"; 331 332 $standard_mycode['s']['regex'] = "#\[s\](.*?)\[/s\]#si"; 333 $standard_mycode['s']['replacement'] = "<span style=\"text-decoration: line-through;\" class=\"mycode_s\">$1</span>"; 334 335 $standard_mycode['hr']['regex'] = "#\[hr\]#si"; 336 $standard_mycode['hr']['replacement'] = "<hr class=\"mycode_hr\" />"; 337 338 ++$standard_count; 339 } 340 341 if($mybb->settings['allowsymbolmycode'] == 1) 342 { 343 $standard_mycode['copy']['regex'] = "#\(c\)#i"; 344 $standard_mycode['copy']['replacement'] = "©"; 345 346 $standard_mycode['tm']['regex'] = "#\(tm\)#i"; 347 $standard_mycode['tm']['replacement'] = "™"; 348 349 $standard_mycode['reg']['regex'] = "#\(r\)#i"; 350 $standard_mycode['reg']['replacement'] = "®"; 351 352 ++$standard_count; 353 } 354 355 if($mybb->settings['allowlinkmycode'] == 1) 356 { 357 $callback_mycode['url_simple']['regex'] = "#\[url\]((?!javascript)[a-z]+?://)([^\r\n\"<]+?)\[/url\]#si"; 358 $callback_mycode['url_simple']['replacement'] = array($this, 'mycode_parse_url_callback1'); 359 360 $callback_mycode['url_simple2']['regex'] = "#\[url\]((?!javascript:)[^\r\n\"<]+?)\[/url\]#i"; 361 $callback_mycode['url_simple2']['replacement'] = array($this, 'mycode_parse_url_callback2'); 362 363 $callback_mycode['url_complex']['regex'] = "#\[url=((?!javascript)[a-z]+?://)([^\r\n\"<]+?)\](.+?)\[/url\]#si"; 364 $callback_mycode['url_complex']['replacement'] = array($this, 'mycode_parse_url_callback1'); 365 366 $callback_mycode['url_complex2']['regex'] = "#\[url=((?!javascript:)[^\r\n\"<]+?)\](.+?)\[/url\]#si"; 367 $callback_mycode['url_complex2']['replacement'] = array($this, 'mycode_parse_url_callback2'); 368 369 ++$callback_count; 370 } 371 372 if($mybb->settings['allowemailmycode'] == 1) 373 { 374 $callback_mycode['email_simple']['regex'] = "#\[email\]((?:[a-zA-Z0-9-_\+\.]+?)@[a-zA-Z0-9-]+\.[a-zA-Z0-9\.-]+(?:\?.*?)?)\[/email\]#i"; 375 $callback_mycode['email_simple']['replacement'] = array($this, 'mycode_parse_email_callback'); 376 377 $callback_mycode['email_complex']['regex'] = "#\[email=((?:[a-zA-Z0-9-_\+\.]+?)@[a-zA-Z0-9-]+\.[a-zA-Z0-9\.-]+(?:\?.*?)?)\](.*?)\[/email\]#i"; 378 $callback_mycode['email_complex']['replacement'] = array($this, 'mycode_parse_email_callback'); 379 380 ++$callback_count; 381 } 382 383 if($mybb->settings['allowcolormycode'] == 1) 384 { 385 $nestable_mycode['color']['regex'] = "#\[color=([a-zA-Z]*|\#?[\da-fA-F]{3}|\#?[\da-fA-F]{6})](.*?)\[/color\]#si"; 386 $nestable_mycode['color']['replacement'] = "<span style=\"color: $1;\" class=\"mycode_color\">$2</span>"; 387 388 ++$nestable_count; 389 } 390 391 if($mybb->settings['allowsizemycode'] == 1) 392 { 393 $nestable_mycode['size']['regex'] = "#\[size=(xx-small|x-small|small|medium|large|x-large|xx-large)\](.*?)\[/size\]#si"; 394 $nestable_mycode['size']['replacement'] = "<span style=\"font-size: $1;\" class=\"mycode_size\">$2</span>"; 395 396 $callback_mycode['size_int']['regex'] = "#\[size=([0-9\+\-]+?)\](.*?)\[/size\]#si"; 397 $callback_mycode['size_int']['replacement'] = array($this, 'mycode_handle_size_callback'); 398 399 ++$nestable_count; 400 ++$callback_count; 401 } 402 403 if($mybb->settings['allowalignmycode'] == 1) 404 { 405 $nestable_mycode['align']['regex'] = "#\[align=(left|center|right|justify)\](.*?)\[/align\]#si"; 406 $nestable_mycode['align']['replacement'] = "<div style=\"text-align: $1;\" class=\"mycode_align\">$2</div>"; 407 408 ++$nestable_count; 409 } 410 411 if($mybb->settings['allowfontmycode'] == 1) 412 { 413 $nestable_callback_mycode['font']['regex'] = "#\[font=\\s*(\"?)([a-z0-9 ,\-_'\"]+)\\1\\s*\](.*?)\[/font\]#si"; 414 $nestable_callback_mycode['font']['replacement'] = array($this, 'mycode_parse_font_callback'); 415 416 ++$nestable_callback_count; 417 } 418 419 $custom_mycode = $cache->read("mycode"); 420 421 // If there is custom MyCode, load it. 422 if(is_array($custom_mycode)) 423 { 424 foreach($custom_mycode as $key => $mycode) 425 { 426 $mycode['regex'] = str_replace("\x0", "", $mycode['regex']); 427 $custom_mycode[$key]['regex'] = "#".$mycode['regex']."#si"; 428 429 ++$standard_count; 430 } 431 $mycode = array_merge($standard_mycode, $custom_mycode); 432 } 433 else 434 { 435 $mycode = $standard_mycode; 436 } 437 438 // Assign the MyCode to the cache. 439 foreach($mycode as $code) 440 { 441 $this->mycode_cache['standard']['find'][] = $code['regex']; 442 $this->mycode_cache['standard']['replacement'][] = $code['replacement']; 443 } 444 445 // Assign the nestable MyCode to the cache. 446 foreach($nestable_mycode as $code) 447 { 448 $this->mycode_cache['nestable'][] = array('find' => $code['regex'], 'replacement' => $code['replacement']); 449 } 450 451 // Assign the callback MyCode to the cache. 452 foreach($callback_mycode as $code) 453 { 454 $this->mycode_cache['callback'][] = array('find' => $code['regex'], 'replacement' => $code['replacement']); 455 } 456 457 // Assign the nestable callback MyCode to the cache. 458 foreach($nestable_callback_mycode as $code) 459 { 460 $this->mycode_cache['nestable_callback'][] = array('find' => $code['regex'], 'replacement' => $code['replacement']); 461 } 462 463 $this->mycode_cache['standard_count'] = $standard_count; 464 $this->mycode_cache['callback_count'] = $callback_count; 465 $this->mycode_cache['nestable_count'] = $nestable_count; 466 $this->mycode_cache['nestable_callback_count'] = $nestable_callback_count; 467 } 468 469 /** 470 * Parses MyCode tags in a specific message with the specified options. 471 * 472 * @param string $message The message to be parsed. 473 * @param array $options Array of options in yes/no format. Options are allow_imgcode. 474 * @return string The parsed message. 475 */ 476 function parse_mycode($message, $options=array()) 477 { 478 global $lang, $mybb; 479 480 if(empty($this->options)) 481 { 482 $this->options = $options; 483 } 484 485 // Cache the MyCode globally if needed. 486 if($this->mycode_cache == 0) 487 { 488 $this->cache_mycode(); 489 } 490 491 // Parse quotes first 492 $message = $this->mycode_parse_quotes($message); 493 494 // Convert images when allowed. 495 if(!empty($this->options['allow_imgcode'])) 496 { 497 $message = preg_replace_callback("#\[img\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_callback1'), $message); 498 $message = preg_replace_callback("#\[img=([1-9][0-9]*)x([1-9][0-9]*)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_callback2'), $message); 499 $message = preg_replace_callback("#\[img align=(left|right)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_callback3'), $message); 500 $message = preg_replace_callback("#\[img=([1-9][0-9]*)x([1-9][0-9]*) align=(left|right)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_callback4'), $message); 501 } 502 else 503 { 504 $message = preg_replace_callback("#\[img\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_disabled_callback1'), $message); 505 $message = preg_replace_callback("#\[img=([1-9][0-9]*)x([1-9][0-9]*)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_disabled_callback2'), $message); 506 $message = preg_replace_callback("#\[img align=(left|right)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_disabled_callback3'), $message); 507 $message = preg_replace_callback("#\[img=([1-9][0-9]*)x([1-9][0-9]*) align=(left|right)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", array($this, 'mycode_parse_img_disabled_callback4'), $message); 508 } 509 510 // Convert videos when allow. 511 if(!empty($this->options['allow_videocode'])) 512 { 513 $message = preg_replace_callback("#\[video=(.*?)\](.*?)\[/video\]#i", array($this, 'mycode_parse_video_callback'), $message); 514 } 515 else 516 { 517 $message = preg_replace_callback("#\[video=(.*?)\](.*?)\[/video\]#i", array($this, 'mycode_parse_video_disabled_callback'), $message); 518 } 519 520 $message = str_replace('$', '$', $message); 521 522 // Replace the rest 523 if($this->mycode_cache['standard_count'] > 0) 524 { 525 $message = preg_replace($this->mycode_cache['standard']['find'], $this->mycode_cache['standard']['replacement'], $message); 526 } 527 528 if($this->mycode_cache['callback_count'] > 0) 529 { 530 foreach($this->mycode_cache['callback'] as $replace) 531 { 532 $message = preg_replace_callback($replace['find'], $replace['replacement'], $message); 533 } 534 } 535 536 // Replace the nestable mycode's 537 if($this->mycode_cache['nestable_count'] > 0) 538 { 539 foreach($this->mycode_cache['nestable'] as $mycode) 540 { 541 while(preg_match($mycode['find'], $message)) 542 { 543 $message = preg_replace($mycode['find'], $mycode['replacement'], $message); 544 } 545 } 546 } 547 548 // Replace the nestable callback mycodes 549 if($this->mycode_cache['nestable_callback_count'] > 0) 550 { 551 foreach($this->mycode_cache['nestable_callback'] as $replace) 552 { 553 while(preg_match($replace['find'], $message)) 554 { 555 $message_org = $message; 556 $message = preg_replace_callback($replace['find'], $replace['replacement'], $message); 557 if ($message_org == $message) 558 { 559 break; 560 } 561 } 562 } 563 } 564 565 // Reset list cache 566 if($mybb->settings['allowlistmycode'] == 1) 567 { 568 $this->list_elements = array(); 569 $this->list_count = 0; 570 571 // Find all lists 572 $message = preg_replace_callback("#(\[list(=(a|A|i|I|1))?\]|\[/list\])#si", array($this, 'mycode_prepare_list'), $message); 573 574 // Replace all lists 575 for($i = $this->list_count; $i > 0; $i--) 576 { 577 // Ignores missing end tags 578 $message = preg_replace_callback("#\s?\[list(=(a|A|i|I|1))?&{$i}\](.*?)(\[/list&{$i}\]|$)(\r\n?|\n?)#si", array($this, 'mycode_parse_list_callback'), $message, 1); 579 } 580 } 581 582 if( 583 (!isset($this->options['allow_auto_url']) || $this->options['allow_auto_url'] == 1) && 584 $mybb->settings['allowautourl'] == 1 585 ) 586 { 587 $message = $this->mycode_auto_url($message); 588 } 589 590 return $message; 591 } 592 593 /** 594 * Generates a cache of smilies 595 * 596 * @access private 597 */ 598 function cache_smilies() 599 { 600 global $cache, $mybb, $theme, $templates; 601 $this->smilies_cache = array(); 602 603 $smilies = $cache->read("smilies"); 604 if(is_array($smilies)) 605 { 606 $extra_class = $onclick = ''; 607 foreach($smilies as $sid => $smilie) 608 { 609 if(isset($theme['imgdir'])) 610 { 611 $imgdir = $theme['imgdir']; 612 } 613 else 614 { 615 $imgdir = ''; 616 } 617 618 $smilie['find'] = explode("\n", $smilie['find']); 619 $smilie['image'] = str_replace("{theme}", $imgdir, $smilie['image']); 620 $smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image'])); 621 $smilie['name'] = htmlspecialchars_uni($smilie['name']); 622 623 foreach($smilie['find'] as $s) 624 { 625 $s = $this->parse_html($s); 626 eval("\$smilie_template = \"".$templates->get("smilie", 1, 0)."\";"); 627 $this->smilies_cache[$s] = $smilie_template; 628 // workaround for smilies starting with ; 629 if($s[0] == ";") 630 { 631 $this->smilies_cache += array( 632 "&$s" => "&$s", 633 "<$s" => "<$s", 634 ">$s" => ">$s", 635 ); 636 } 637 } 638 } 639 } 640 } 641 642 /** 643 * Parses smilie code in the specified message. 644 * 645 * @param string $message $message The message being parsed. 646 * @param int $allow_html not used 647 * @return string The parsed message. 648 */ 649 function parse_smilies($message, $allow_html=0) 650 { 651 if($this->smilies_cache == 0) 652 { 653 $this->cache_smilies(); 654 } 655 656 // No smilies? 657 if(!count($this->smilies_cache)) 658 { 659 return $message; 660 } 661 662 // First we take out any of the tags we don't want parsed between (url= etc) 663 preg_match_all("#\[(url(=[^\]]*)?\]|quote=([^\]]*)?\])|(http|ftp)(s|)://[^\s]*#i", $message, $bad_matches, PREG_PATTERN_ORDER); 664 if(count($bad_matches[0]) > 0) 665 { 666 $message = preg_replace("#\[(url(=[^\]]*)?\]|quote=([^\]]*)?\])|(http|ftp)(s|)://[^\s]*#si", "<mybb-bad-sm>", $message); 667 } 668 669 $message = strtr($message, $this->smilies_cache); 670 671 // If we matched any tags previously, swap them back in 672 if(count($bad_matches[0]) > 0) 673 { 674 $message = explode("<mybb-bad-sm>", $message); 675 $i = 0; 676 foreach($bad_matches[0] as $match) 677 { 678 $message[$i] .= $match; 679 $i++; 680 } 681 $message = implode("", $message); 682 } 683 684 return $message; 685 } 686 687 /** 688 * Generates a cache of badwords filters. 689 * 690 * @access private 691 */ 692 function cache_badwords() 693 { 694 global $cache; 695 $this->badwords_cache = array(); 696 $this->badwords_cache = $cache->read("badwords"); 697 } 698 699 /** 700 * Parses a list of filtered/badwords in the specified message. 701 * 702 * @param string $message The message to be parsed. 703 * @param array $options Array of parser options in yes/no format. 704 * @return string The parsed message. 705 */ 706 function parse_badwords($message, $options=array()) 707 { 708 if(empty($this->options)) 709 { 710 $this->options = $options; 711 } 712 713 if($this->badwords_cache == 0) 714 { 715 $this->cache_badwords(); 716 } 717 if(is_array($this->badwords_cache)) 718 { 719 reset($this->badwords_cache); 720 foreach($this->badwords_cache as $bid => $badword) 721 { 722 if(!$badword['replacement']) 723 { 724 $badword['replacement'] = "*****"; 725 } 726 727 if(!$badword['regex']) 728 { 729 $badword['badword'] = $this->generate_regex($badword['badword']); 730 } 731 732 $message = preg_replace('#'.$badword['badword'].'#is', $badword['replacement'], $message); 733 } 734 } 735 if(!empty($this->options['strip_tags'])) 736 { 737 $message = strip_tags($message); 738 } 739 return $message; 740 } 741 742 /** 743 * Generates REGEX patterns based on user defined badword string. 744 * 745 * @param string $badword The word defined to replace. 746 * @return string The regex pattern to match the word or null on error. 747 */ 748 function generate_regex($bad_word = "") 749 { 750 if($bad_word == "") 751 { 752 return; 753 } 754 755 // Neutralize escape character, regex operators, multiple adjacent wildcards and generate pattern 756 $ptrn = array('/\\\\/', '/([\[\^\$\.\|\?\(\)\{\}]{1})/', '/\*\++/', '/\++\*/', '/\*+/'); 757 $rplc = array('\\\\\\\\','\\\\$1}', '*', '*', '[^\s\n]*'); 758 $bad_word = preg_replace($ptrn, $rplc, $bad_word); 759 760 // Count + and generate pattern 761 $bad_word = explode('+', $bad_word); 762 $trap = ""; 763 $plus = 0; 764 foreach($bad_word as $bad_piece) 765 { 766 if($bad_piece) 767 { 768 $trap .= $plus ? '[^\s\n]{'.$plus.'}'.$bad_piece : $bad_piece; 769 $plus = 1; 770 } 771 else 772 { 773 $plus++; 774 } 775 } 776 777 // Handle trailing + 778 if($plus > 1) 779 { 780 $trap .= '[^\s\n]{'.($plus-1).'}'; 781 } 782 783 return '\b'.$trap.'\b'; 784 } 785 786 /** 787 * Resolves nested CDATA tags in the specified message. 788 * 789 * @param string $message The message to be parsed. 790 * @return string The parsed message. 791 */ 792 function parse_cdata($message) 793 { 794 $message = str_replace(']]>', ']]]]><![CDATA[>', $message); 795 796 return $message; 797 } 798 799 /** 800 * Attempts to move any javascript references in the specified message. 801 * 802 * @param string The message to be parsed. 803 * @return string The parsed message. 804 */ 805 function fix_javascript($message) 806 { 807 $js_array = array( 808 "#(&\#(0*)106;?|&\#(0*)74;?|&\#x(0*)4a;?|&\#x(0*)6a;?|j)((&\#(0*)97;?|&\#(0*)65;?|a)(&\#(0*)118;?|&\#(0*)86;?|v)(&\#(0*)97;?|&\#(0*)65;?|a)(\s)?(&\#(0*)115;?|&\#(0*)83;?|s)(&\#(0*)99;?|&\#(0*)67;?|c)(&\#(0*)114;?|&\#(0*)82;?|r)(&\#(0*)105;?|&\#(0*)73;?|i)(&\#112;?|&\#(0*)80;?|p)(&\#(0*)116;?|&\#(0*)84;?|t)(&\#(0*)58;?|\:))#i", 809 "#([\s\"']on)([a-z]+\s*=)#i", 810 ); 811 812 // Add invisible white space 813 $message = preg_replace($js_array, "$1\xE2\x80\x8C$2$6", $message); 814 815 return $message; 816 } 817 818 /** 819 * Handles fontsize. 820 * 821 * @param int $size The original size. 822 * @param string $text The text within a size tag. 823 * @return string The parsed text. 824 */ 825 function mycode_handle_size($size, $text) 826 { 827 global $templates; 828 829 $size = (int)$size; 830 831 if($size < 1) 832 { 833 $size = 1; 834 } 835 836 if($size > 50) 837 { 838 $size = 50; 839 } 840 841 $text = str_replace("\'", "'", $text); 842 843 eval("\$mycode_size = \"".$templates->get("mycode_size_int", 1, 0)."\";"); 844 return $mycode_size; 845 } 846 847 /** 848 * Handles fontsize. 849 * 850 * @param array $matches Matches. 851 * @return string The parsed text. 852 */ 853 function mycode_handle_size_callback($matches) 854 { 855 return $this->mycode_handle_size($matches[1], $matches[2]); 856 } 857 858 /** 859 * Parses quote MyCode. 860 * 861 * @param string $message The message to be parsed 862 * @param boolean $text_only Are we formatting as text? 863 * @return string The parsed message. 864 */ 865 function mycode_parse_quotes($message, $text_only=false) 866 { 867 global $lang, $templates, $theme, $mybb; 868 869 // Assign pattern and replace values. 870 $pattern = "#\[quote\](.*?)\[\/quote\](\r\n?|\n?)#si"; 871 $pattern_callback = "#\[quote=([\"']|"|)(.*?)(?:\\1)(.*?)(?:[\"']|")?\](.*?)\[/quote\](\r\n?|\n?)#si"; 872 873 if($text_only == false) 874 { 875 $replace = "<blockquote class=\"mycode_quote\"><cite>$lang->quote</cite>$1</blockquote>\n"; 876 $replace_callback = array($this, 'mycode_parse_post_quotes_callback1'); 877 } 878 else 879 { 880 $replace = empty($this->options['signature_parse']) ? "\n{$lang->quote}\n--\n$1\n--\n" : "$1"; 881 $replace_callback = array($this, 'mycode_parse_post_quotes_callback2'); 882 } 883 884 do 885 { 886 // preg_replace has erased the message? Restore it... 887 $previous_message = $message; 888 $message = preg_replace($pattern, $replace, $message, -1, $count); 889 $message = preg_replace_callback($pattern_callback, $replace_callback, $message, -1, $count_callback); 890 if(!$message) 891 { 892 $message = $previous_message; 893 break; 894 } 895 } while($count || $count_callback); 896 897 if($text_only == false) 898 { 899 $find = array( 900 "#(\r\n*|\n*)<\/cite>(\r\n*|\n*)#", 901 "#(\r\n*|\n*)<\/blockquote>#" 902 ); 903 904 $replace = array( 905 "</cite><br />", 906 "</blockquote>" 907 ); 908 $message = preg_replace($find, $replace, $message); 909 } 910 return $message; 911 } 912 913 /** 914 * Parses quotes with post id and/or dateline. 915 * 916 * @param string $message The message to be parsed 917 * @param string $username The username to be parsed 918 * @param boolean $text_only Are we formatting as text? 919 * @return string The parsed message. 920 */ 921 function mycode_parse_post_quotes($message, $username, $text_only=false) 922 { 923 global $lang, $templates, $theme, $mybb; 924 925 $linkback = $date = ""; 926 927 $message = trim($message); 928 $message = preg_replace("#(^<br(\s?)(\/?)>|<br(\s?)(\/?)>$)#i", "", $message); 929 930 if(!$message) 931 { 932 return ''; 933 } 934 935 $username .= "'"; 936 $delete_quote = true; 937 938 preg_match("#pid=(?:"|\"|')?([0-9]+)[\"']?(?:"|\"|')?#i", $username, $match); 939 if(isset($match[1]) && (int)$match[1]) 940 { 941 $pid = (int)$match[1]; 942 $url = $mybb->settings['bburl']."/".get_post_link($pid)."#pid$pid"; 943 if(defined("IN_ARCHIVE")) 944 { 945 $linkback = " <a href=\"{$url}\">[ -> ]</a>"; 946 } 947 else 948 { 949 eval("\$linkback = \" ".$templates->get("postbit_gotopost", 1, 0)."\";"); 950 } 951 952 $username = preg_replace("#(?:"|\"|')? pid=(?:"|\"|')?[0-9]+[\"']?(?:"|\"|')?#i", '', $username); 953 $delete_quote = false; 954 } 955 956 unset($match); 957 preg_match("#dateline=(?:"|\"|')?([0-9]+)(?:"|\"|')?#i", $username, $match); 958 if(isset($match[1]) && (int)$match[1]) 959 { 960 if($match[1] < TIME_NOW) 961 { 962 if($text_only) 963 { 964 $postdate = my_date('normal', (int)$match[1]); 965 } 966 else 967 { 968 $postdate = my_date('relative', (int)$match[1]); 969 } 970 $date = " ({$postdate})"; 971 } 972 $username = preg_replace("#(?:"|\"|')? dateline=(?:"|\"|')?[0-9]+(?:"|\"|')?#i", '', $username); 973 $delete_quote = false; 974 } 975 976 if($delete_quote) 977 { 978 $username = my_substr($username, 0, my_strlen($username)-1, true); 979 } 980 981 if(!empty($this->options['allow_html'])) 982 { 983 $username = htmlspecialchars_uni($username); 984 } 985 986 if($text_only) 987 { 988 return "\n{$username} {$lang->wrote}{$date}\n--\n{$message}\n--\n"; 989 } 990 else 991 { 992 $span = ""; 993 if(!$delete_quote) 994 { 995 $span = "<span>{$date}</span>"; 996 } 997 998 eval("\$mycode_quote = \"".$templates->get("mycode_quote_post", 1, 0)."\";"); 999 return $mycode_quote; 1000 } 1001 } 1002 1003 /** 1004 * Parses quotes with post id and/or dateline. 1005 * 1006 * @param array $matches Matches. 1007 * @return string The parsed message. 1008 */ 1009 function mycode_parse_post_quotes_callback1($matches) 1010 { 1011 return $this->mycode_parse_post_quotes($matches[4],$matches[2].$matches[3]); 1012 } 1013 1014 /** 1015 * Parses quotes with post id and/or dateline. 1016 * 1017 * @param array $matches Matches. 1018 * @return string The parsed message. 1019 */ 1020 function mycode_parse_post_quotes_callback2($matches) 1021 { 1022 return $this->mycode_parse_post_quotes($matches[4],$matches[2].$matches[3], true); 1023 } 1024 1025 /** 1026 * Parses code MyCode. 1027 * 1028 * @param string $code The message to be parsed 1029 * @param boolean $text_only Are we formatting as text? 1030 * @return string The parsed message. 1031 */ 1032 function mycode_parse_code($code, $text_only=false) 1033 { 1034 global $lang, $templates; 1035 1036 if($text_only == true) 1037 { 1038 return empty($this->options['signature_parse']) ? "\n{$lang->code}\n--\n{$code}\n--\n" : $code; 1039 } 1040 1041 // Clean the string before parsing. 1042 $code = preg_replace('#^(\t*)(\n|\r|\0|\x0B| )*#', '\\1', $code); 1043 $code = rtrim($code); 1044 $original = preg_replace('#^\t*#', '', $code); 1045 1046 if(empty($original)) 1047 { 1048 return; 1049 } 1050 1051 $code = str_replace('$', '$', $code); 1052 $code = preg_replace('#\$([0-9])#', '\\\$\\1', $code); 1053 $code = str_replace('\\', '\', $code); 1054 $code = str_replace("\t", ' ', $code); 1055 $code = str_replace(" ", ' ', $code); 1056 1057 eval("\$mycode_code = \"".$templates->get("mycode_code", 1, 0)."\";"); 1058 return $mycode_code; 1059 } 1060 1061 /** 1062 * Parses code MyCode. 1063 * 1064 * @param array $matches Matches. 1065 * @return string The parsed message. 1066 */ 1067 function mycode_parse_code_callback($matches) 1068 { 1069 return $this->mycode_parse_code($matches[1], true); 1070 } 1071 1072 /** 1073 * Parses PHP code MyCode. 1074 * 1075 * @param string $str The message to be parsed 1076 * @param boolean $bare_return Whether or not it should return it as pre-wrapped in a div or not. 1077 * @param boolean $text_only Are we formatting as text? 1078 * @return string The parsed message. 1079 */ 1080 function mycode_parse_php($str, $bare_return = false, $text_only = false) 1081 { 1082 global $lang, $templates; 1083 1084 if($text_only == true) 1085 { 1086 return empty($this->options['signature_parse']) ? "\n{$lang->php_code}\n--\n{$str}\n--\n" : $str; 1087 } 1088 1089 // Clean the string before parsing except tab spaces. 1090 $str = preg_replace('#^(\t*)(\n|\r|\0|\x0B| )*#', '\\1', $str); 1091 $str = rtrim($str); 1092 1093 $original = preg_replace('#^\t*#', '', $str); 1094 1095 if(empty($original)) 1096 { 1097 return; 1098 } 1099 1100 // See if open and close tags are provided. 1101 $added_open_tag = false; 1102 if(!preg_match("#^\s*<\?#si", $str)) 1103 { 1104 $added_open_tag = true; 1105 $str = "<?php \n".$str; 1106 } 1107 1108 $added_end_tag = false; 1109 if(!preg_match("#\?>\s*$#si", $str)) 1110 { 1111 $added_end_tag = true; 1112 $str = $str." \n?>"; 1113 } 1114 1115 $code = @highlight_string($str, true); 1116 1117 // Do the actual replacing. 1118 $code = preg_replace('#<pre><code style="color: \#000000">#i', "<code>", $code); 1119 $code = preg_replace('#<code>\s*<span style="color: \#000000">\s*#i', "<code>", $code); 1120 $code = preg_replace("#</span>\s*</code>#", "</code>", $code); 1121 $code = preg_replace("#</code>\s*</pre>#", "</code>", $code); 1122 $code = preg_replace("#</span>(\r\n?|\n?)</code>#", "</span></code>", $code); 1123 $code = str_replace("\\", '\', $code); 1124 $code = str_replace('$', '$', $code); 1125 $code = preg_replace("#&\#([0-9]+);#si", "&#$1;", $code); 1126 1127 if($added_open_tag) 1128 { 1129 $code = preg_replace("#<code><span style=\"color: \#([A-Z0-9]{6})\"><\?php( | )(<br />|\n)#", "<code><span style=\"color: #$1\">", $code); 1130 } 1131 1132 if($added_end_tag) 1133 { 1134 $code = str_replace("?></span></code>", "</span></code>", $code); 1135 // Wait a minute. It fails highlighting? Stupid highlighter. 1136 $code = str_replace("?></code>", "</code>", $code); 1137 } 1138 1139 $code = preg_replace("#<span style=\"color: \#([A-Z0-9]{6})\"></span>#", "", $code); 1140 $code = str_replace("<code>", "<div dir=\"ltr\"><code>", $code); 1141 $code = str_replace("</code>", "</code></div>", $code); 1142 $code = preg_replace("# *$#", "", $code); 1143 1144 if($bare_return) 1145 { 1146 return $code; 1147 } 1148 1149 // Send back the code all nice and pretty 1150 eval("\$mycode_php = \"".$templates->get("mycode_php", 1, 0)."\";"); 1151 return $mycode_php; 1152 } 1153 1154 /** 1155 * Parses PHP code MyCode. 1156 * 1157 * @param array $matches Matches. 1158 * @return string The parsed message. 1159 */ 1160 function mycode_parse_php_callback($matches) 1161 { 1162 return $this->mycode_parse_php($matches[1], false, true); 1163 } 1164 1165 /** 1166 * Parses URL MyCode. 1167 * 1168 * @param string $url The URL to link to. 1169 * @param string $name The name of the link. 1170 * @return string The built-up link. 1171 */ 1172 function mycode_parse_url($url, $name="") 1173 { 1174 global $templates; 1175 if(!preg_match("#^[a-z0-9]+://#i", $url)) 1176 { 1177 $url = "http://".$url; 1178 } 1179 1180 if(!empty($this->options['allow_html'])) 1181 { 1182 $url = $this->parse_html($url); 1183 } 1184 1185 if(!$name) 1186 { 1187 $name = $url; 1188 } 1189 1190 if($name == $url && (!isset($this->options['shorten_urls']) || !empty($this->options['shorten_urls']))) 1191 { 1192 $name = htmlspecialchars_decode($name); 1193 if(my_strlen($name) > 55) 1194 { 1195 $name = my_substr($name , 0, 40).'...'.my_substr($name , -10); 1196 } 1197 $name = htmlspecialchars_uni($name); 1198 } 1199 1200 if(!empty($this->options['nofollow_on'])) 1201 { 1202 $rel = " rel=\"noopener nofollow\""; 1203 } 1204 else 1205 { 1206 $rel = " rel=\"noopener\""; 1207 } 1208 1209 // Fix some entities in URLs 1210 $url = $this->encode_url($url); 1211 $name = $this->parse_badwords(preg_replace("#&\#([0-9]+);#si", "&#$1;", $name)); // Fix & but allow unicode, filter bad words 1212 1213 eval("\$mycode_url = \"".$templates->get("mycode_url", 1, 0)."\";"); 1214 return $mycode_url; 1215 } 1216 1217 /** 1218 * Parses font MyCode. 1219 * 1220 * @param array $matches Matches. 1221 * @return string The HTML <span> tag with styled font. 1222 */ 1223 function mycode_parse_font_callback($matches) 1224 { 1225 // Replace any occurrence(s) of double quotes in fonts with single quotes. 1226 // A back-fix for double-quote-containing MyBB font tags in existing 1227 // posts prior to the client-side aspect of this fix for the 1228 // browser-independent SCEditor bug of issue #4182. 1229 $fonts = str_replace('"', "'", $matches[2]); 1230 1231 return "<span style=\"font-family: {$fonts};\" class=\"mycode_font\">{$matches[3]}</span>"; 1232 } 1233 1234 /** 1235 * Parses URL MyCode. 1236 * 1237 * @param array $matches Matches. 1238 * @return string The built-up link. 1239 */ 1240 function mycode_parse_url_callback1($matches) 1241 { 1242 if(!isset($matches[3])) 1243 { 1244 $matches[3] = ''; 1245 } 1246 return $this->mycode_parse_url($matches[1].$matches[2], $matches[3]); 1247 } 1248 1249 /** 1250 * Parses URL MyCode. 1251 * 1252 * @param array $matches Matches. 1253 * @return string The built-up link. 1254 */ 1255 function mycode_parse_url_callback2($matches) 1256 { 1257 if(!isset($matches[2])) 1258 { 1259 $matches[2] = ''; 1260 } 1261 return $this->mycode_parse_url($matches[1], $matches[2]); 1262 } 1263 1264 /** 1265 * Parses IMG MyCode. 1266 * 1267 * @param string $url The URL to the image 1268 * @param array $dimensions Optional array of dimensions 1269 * @param string $align 1270 * @return string 1271 */ 1272 function mycode_parse_img($url, $dimensions=array(), $align='') 1273 { 1274 global $lang, $templates; 1275 $url = trim($url); 1276 $url = str_replace("\n", "", $url); 1277 $url = str_replace("\r", "", $url); 1278 1279 if(!empty($this->options['allow_html'])) 1280 { 1281 $url = $this->parse_html($url); 1282 } 1283 1284 $css_align = ''; 1285 if($align == "right") 1286 { 1287 $css_align = ' style="float: right;"'; 1288 } 1289 else if($align == "left") 1290 { 1291 $css_align = ' style="float: left;"'; 1292 } 1293 1294 if($align) 1295 { 1296 $this->clear_needed = true; 1297 } 1298 1299 $alt = basename($url); 1300 $alt = htmlspecialchars_decode($alt); 1301 if(my_strlen($alt) > 55) 1302 { 1303 $alt = my_substr($alt, 0, 40).'...'.my_substr($alt, -10); 1304 } 1305 $alt = $this->encode_url($alt); 1306 $alt = preg_replace("#&(?!\#[0-9]+;)#si", "&", $alt); // fix & but allow unicode 1307 1308 $alt = $lang->sprintf($lang->posted_image, $alt); 1309 $width = $height = ''; 1310 if(isset($dimensions[0]) && $dimensions[0] > 0 && isset($dimensions[1]) && $dimensions[1] > 0) 1311 { 1312 $width = " width=\"{$dimensions[0]}\""; 1313 $height = " height=\"{$dimensions[1]}\""; 1314 } 1315 1316 $url = $this->encode_url($url); 1317 1318 eval("\$mycode_img = \"".$templates->get("mycode_img", 1, 0)."\";"); 1319 return $mycode_img; 1320 } 1321 1322 /** 1323 * Parses IMG MyCode. 1324 * 1325 * @param array $matches Matches. 1326 * @return string Image code. 1327 */ 1328 function mycode_parse_img_callback1($matches) 1329 { 1330 return $this->mycode_parse_img($matches[2]); 1331 } 1332 1333 /** 1334 * Parses IMG MyCode. 1335 * 1336 * @param array $matches Matches. 1337 * @return string Image code. 1338 */ 1339 function mycode_parse_img_callback2($matches) 1340 { 1341 return $this->mycode_parse_img($matches[4], array($matches[1], $matches[2])); 1342 } 1343 1344 /** 1345 * Parses IMG MyCode. 1346 * 1347 * @param array $matches Matches. 1348 * @return string Image code. 1349 */ 1350 function mycode_parse_img_callback3($matches) 1351 { 1352 return $this->mycode_parse_img($matches[3], array(), $matches[1]); 1353 } 1354 1355 /** 1356 * Parses IMG MyCode. 1357 * 1358 * @param array $matches Matches. 1359 * @return string Image code. 1360 */ 1361 function mycode_parse_img_callback4($matches) 1362 { 1363 return $this->mycode_parse_img($matches[5], array($matches[1], $matches[2]), $matches[3]); 1364 } 1365 1366 /** 1367 * Parses IMG MyCode disabled. 1368 * 1369 * @param string $url The URL to the image 1370 * @return string 1371 */ 1372 function mycode_parse_img_disabled($url) 1373 { 1374 global $lang; 1375 $url = trim($url); 1376 $url = str_replace("\n", "", $url); 1377 $url = str_replace("\r", "", $url); 1378 $url = str_replace("\'", "'", $url); 1379 1380 $image = $lang->sprintf($lang->posted_image, $this->mycode_parse_url($url)); 1381 return $image; 1382 } 1383 1384 /** 1385 * Parses IMG MyCode disabled. 1386 * 1387 * @param array $matches Matches. 1388 * @return string Image code. 1389 */ 1390 function mycode_parse_img_disabled_callback1($matches) 1391 { 1392 return $this->mycode_parse_img_disabled($matches[2]); 1393 } 1394 1395 /** 1396 * Parses IMG MyCode disabled. 1397 * 1398 * @param array $matches Matches. 1399 * @return string Image code. 1400 */ 1401 function mycode_parse_img_disabled_callback2($matches) 1402 { 1403 return $this->mycode_parse_img_disabled($matches[4]); 1404 } 1405 1406 /** 1407 * Parses IMG MyCode disabled. 1408 * 1409 * @param array $matches Matches. 1410 * @return string Image code. 1411 */ 1412 function mycode_parse_img_disabled_callback3($matches) 1413 { 1414 return $this->mycode_parse_img_disabled($matches[3]); 1415 } 1416 1417 /** 1418 * Parses IMG MyCode disabled. 1419 * 1420 * @param array $matches Matches. 1421 * @return string Image code. 1422 */ 1423 function mycode_parse_img_disabled_callback4($matches) 1424 { 1425 return $this->mycode_parse_img_disabled($matches[5]); 1426 } 1427 1428 /** 1429 * Parses email MyCode. 1430 * 1431 * @param string $email The email address to link to. 1432 * @param string $name The name for the link. 1433 * @return string The built-up email link. 1434 */ 1435 function mycode_parse_email($email, $name="") 1436 { 1437 global $templates; 1438 1439 if(!$name) 1440 { 1441 $name = $email; 1442 } 1443 1444 $email = $this->encode_url($email); 1445 1446 eval("\$mycode_email = \"".$templates->get("mycode_email", 1, 0)."\";"); 1447 return $mycode_email; 1448 } 1449 1450 /** 1451 * Parses email MyCode. 1452 * 1453 * @param array $matches Matches 1454 * @return string The built-up email link. 1455 */ 1456 function mycode_parse_email_callback($matches) 1457 { 1458 if(!isset($matches[2])) 1459 { 1460 $matches[2] = ''; 1461 } 1462 return $this->mycode_parse_email($matches[1], $matches[2]); 1463 } 1464 1465 /** 1466 * Parses video MyCode. 1467 * 1468 * @param string $video The video provider. 1469 * @param string $url The video to link to. 1470 * @return string The built-up video code. 1471 */ 1472 function mycode_parse_video($video, $url) 1473 { 1474 global $mybb, $templates; 1475 1476 if(empty($video) || empty($url)) 1477 { 1478 return "[video={$video}]{$url}[/video]"; 1479 } 1480 1481 // Check URL is a valid URL first, as `parse_url` doesn't check validity. 1482 if(false === filter_var($url, FILTER_VALIDATE_URL)) 1483 { 1484 return "[video={$video}]{$url}[/video]"; 1485 } 1486 1487 $parsed_url = @parse_url(urldecode($url)); 1488 if($parsed_url === false) 1489 { 1490 return "[video={$video}]{$url}[/video]"; 1491 } 1492 1493 $bbdomain = parse_url($mybb->settings['bburl'], PHP_URL_HOST); 1494 1495 $fragments = empty($parsed_url['fragment']) ? array() : explode("&", $parsed_url['fragment']); 1496 1497 if($video == "liveleak" && !empty($parsed_url['query'])) 1498 { 1499 // The query part can start with any alphabet, but set only 'i' to catch in index key later 1500 $parsed_url['query'] = "i".substr($parsed_url['query'], 1); 1501 } 1502 1503 $queries = empty($parsed_url['query']) ? array() : explode("&", $parsed_url['query']); 1504 1505 $input = array(); 1506 foreach($queries as $query) 1507 { 1508 $query_array = explode("=", $query); 1509 if(count($query_array) == 2) 1510 { 1511 list($key, $value) = $query_array; 1512 $key = str_replace("amp;", "", $key); 1513 $input[$key] = $value; 1514 } 1515 } 1516 1517 $path = empty($parsed_url['path']) ? array() : explode('/', $parsed_url['path']); 1518 1519 switch($video) 1520 { 1521 case "dailymotion": 1522 if(!empty($path[2])) 1523 { 1524 list($id) = explode('_', $path[2], 2); // http://www.dailymotion.com/video/fds123_title-goes-here 1525 } 1526 elseif(!empty($path[1])) 1527 { 1528 $id = $path[1]; // http://dai.ly/fds123 1529 } 1530 break; 1531 case "metacafe": 1532 if(!empty($path[2])) 1533 { 1534 $id = $path[2]; // http://www.metacafe.com/watch/fds123/title_goes_here/ 1535 } 1536 break; 1537 case "myspacetv": 1538 if(!empty($path[4])) 1539 { 1540 $id = $path[4]; // http://www.myspace.com/video/fds/fds/123 1541 } 1542 break; 1543 case "facebook": 1544 if(!empty($input['v'])) 1545 { 1546 $id = $input['v']; // http://www.facebook.com/video/video.php?v=123 1547 } 1548 elseif(!empty($path[3]) && substr($path[3], 0, 3) == 'vb.' && !empty($path[4])) 1549 { 1550 $id = $path[4]; // https://www.facebook.com/fds/videos/vb.123/123/ 1551 } 1552 elseif(!empty($path[3])) 1553 { 1554 $id = $path[3]; // https://www.facebook.com/fds/videos/123/ 1555 } 1556 break; 1557 case "mixer": 1558 if(!empty($path[1])) 1559 { 1560 $id = $path[1]; // https://mixer.com/streamer 1561 } 1562 break; 1563 case "liveleak": 1564 if(!empty($input['i'])) 1565 { 1566 $id = $input['i']; // http://www.liveleak.com/view?i=123 1567 } 1568 break; 1569 case "yahoo": 1570 if(!empty($path[2])) 1571 { 1572 $id = $path[2]; // http://xy.screen.yahoo.com/fds/fds-123.html 1573 } 1574 elseif(!empty($path[1])) 1575 { 1576 $id = $path[1]; // http://xy.screen.yahoo.com/fds-123.html 1577 } 1578 // Support for localized portals 1579 if(!empty($parsed_url['host'])) 1580 { 1581 $domain = explode('.', $parsed_url['host']); 1582 if($domain[0] != 'screen' && preg_match('#^([a-z-]+)$#', $domain[0])) 1583 { 1584 $local = "{$domain[0]}."; 1585 } 1586 else 1587 { 1588 $local = ''; 1589 } 1590 } 1591 break; 1592 case "vimeo": 1593 if(!empty($path[3])) 1594 { 1595 $id = $path[3]; // http://vimeo.com/fds/fds/fds123 1596 } 1597 elseif(!empty($path[1])) 1598 { 1599 $id = $path[1]; // http://vimeo.com/fds123 1600 } 1601 break; 1602 case "youtube": 1603 if(!empty($fragments[0])) 1604 { 1605 $id = str_replace('!v=', '', $fragments[0]); // http://www.youtube.com/watch#!v=fds123 1606 } 1607 elseif(!empty($input['v'])) 1608 { 1609 $id = $input['v']; // http://www.youtube.com/watch?v=fds123 1610 } 1611 elseif(!empty($path[1])) 1612 { 1613 $id = $path[1]; // http://www.youtu.be/fds123 1614 } 1615 break; 1616 case "twitch": 1617 if(count($path) >= 3 && $path[1] == 'videos') 1618 { 1619 // Direct video embed with URL like: https://www.twitch.tv/videos/179723472 1620 $id = 'video=v'.$path[2]; 1621 } 1622 elseif(count($path) >= 4 && $path[2] == 'v') 1623 { 1624 // Direct video embed with URL like: https://www.twitch.tv/waypoint/v/179723472 1625 $id = 'video=v'.$path[3]; 1626 } 1627 elseif(count($path) >= 2) 1628 { 1629 // Channel (livestream) embed with URL like: https://twitch.tv/waypoint 1630 $id = 'channel='.$path[1]; 1631 } 1632 break; 1633 default: 1634 return "[video={$video}]{$url}[/video]"; 1635 } 1636 1637 if(empty($id)) 1638 { 1639 return "[video={$video}]{$url}[/video]"; 1640 } 1641 1642 $id = $this->encode_url($id); 1643 1644 eval("\$video_code = \"".$templates->get("video_{$video}_embed", 1, 0)."\";"); 1645 return $video_code; 1646 } 1647 1648 /** 1649 * Parses video MyCode. 1650 * 1651 * @param array $matches Matches. 1652 * @return string The built-up video code. 1653 */ 1654 function mycode_parse_video_callback($matches) 1655 { 1656 return $this->mycode_parse_video($matches[1], $matches[2]); 1657 } 1658 1659 /** 1660 * Parses video MyCode disabled. 1661 * 1662 * @param string $url The URL to the video 1663 * @return string 1664 */ 1665 function mycode_parse_video_disabled($url) 1666 { 1667 global $lang; 1668 $url = trim($url); 1669 $url = str_replace("\n", "", $url); 1670 $url = str_replace("\r", "", $url); 1671 $url = str_replace("\'", "'", $url); 1672 1673 $video = $lang->sprintf($lang->posted_video, $this->mycode_parse_url($url)); 1674 return $video; 1675 } 1676 1677 /** 1678 * Parses video MyCode disabled. 1679 * 1680 * @param array $matches Matches. 1681 * @return string The built-up video code. 1682 */ 1683 function mycode_parse_video_disabled_callback($matches) 1684 { 1685 return $this->mycode_parse_video_disabled($matches[2]); 1686 } 1687 1688 /** 1689 * Parses URLs automatically. 1690 * 1691 * @param string $message The message to be parsed 1692 * @return string The parsed message. 1693 */ 1694 function mycode_auto_url($message) 1695 { 1696 /* 1697 * Don't create links: 1698 * - within existing links (any <a> HTML tag must be returned as-is) 1699 * - within HTML tags (must not be followed by a > character without a matching < after the link) 1700 * 1701 * Don't include: 1702 * - common punctuation characters around the link 1703 * - braces that likely constitute punctuation around the particular link (handled in the callback function) 1704 * - partial HTML entities (https://github.com/mybb/mybb/issues/4303) 1705 */ 1706 $message = preg_replace_callback( 1707 "~ 1708 <a\\s[^>]*>.*?</a>| # match and return existing links 1709 (?<=^|[\s\(\)\[\>]) # character preceding the link 1710 (?P<prefix> 1711 (?:http|https|ftp|news|irc|ircs|irc6)://| # scheme, or 1712 (?:www|ftp)\. # common subdomain 1713 ) 1714 (?P<link> 1715 (?: 1716 \[[0-9a-fA-F:]+(?:%[0-9a-zA-Z._-]+)?\]| # IPv6 address with optional zone 1717 (?:\d{1,3}\.){3}\d{1,3}| # IPv4 address 1718 (?:[^\"\s<>\[\]:/?&#.]+\.)*[\w-]+ # domain name 1719 ) 1720 (?::[0-9]+)? # optional port number 1721 (?:/[^\"\s<>\[\]?&#]*)? # optional path 1722 (?:\?(?:[^\"\s<>\[\]?#]|\[\])*)? # optional query 1723 (?:\#[^\"\s<>\[\]]*)? # optional fragment 1724 ) 1725 (?: 1726 (?<=&)|(?<=<)|(?<=>)| # allow trailing entities 1727 (?<![.,:`'\"?!])(?<!&) # exclude other trailing punctuation 1728 ) 1729 (?![^<>]*?>) # not followed by unopened > (within HTML tags) 1730 ~iusx", 1731 array($this, 'mycode_auto_url_callback'), 1732 $message 1733 ); 1734 1735 return $message; 1736 } 1737 1738 /** 1739 * Parses URLs automatically. 1740 * 1741 * @param array $matches Matches 1742 * @return string The parsed message. 1743 */ 1744 function mycode_auto_url_callback($matches=array()) 1745 { 1746 // If we matched a preexisting link (the part of the regexes in mycode_auto_url() before the pipe symbol), 1747 // then simply return it - we don't create links within existing links. 1748 if(count($matches) == 1) 1749 { 1750 return $matches[0]; 1751 } 1752 1753 $external = ''; 1754 // Allow links like http://en.wikipedia.org/wiki/PHP_(disambiguation) but detect mismatching braces 1755 while(my_substr($matches['link'], -1) == ')') 1756 { 1757 if(substr_count($matches['link'], ')') > substr_count($matches['link'], '(')) 1758 { 1759 $matches['link'] = my_substr($matches['link'], 0, -1); 1760 $external = ')'.$external; 1761 } 1762 else 1763 { 1764 break; 1765 } 1766 1767 // Example: ([...] http://en.wikipedia.org/Example_(disambiguation).) 1768 $last_char = my_substr($matches['link'], -1); 1769 while($last_char == '.' || $last_char == ',' || $last_char == '?' || $last_char == '!') 1770 { 1771 $matches['link'] = my_substr($matches['link'], 0, -1); 1772 $external = $last_char.$external; 1773 $last_char = my_substr($matches['link'], -1); 1774 } 1775 } 1776 $url = $matches['prefix'].$matches['link']; 1777 1778 return $this->mycode_parse_url($url, $url).$external; 1779 } 1780 1781 /** 1782 * Parses list MyCode. 1783 * 1784 * @param string $message The message to be parsed 1785 * @param string $type The list type 1786 * @return string The parsed message. 1787 */ 1788 function mycode_parse_list($message, $type="") 1789 { 1790 // No list elements? That's invalid HTML 1791 if(strpos($message, '[*]') === false) 1792 { 1793 $message = "[*]{$message}"; 1794 } 1795 1796 $message = preg_split("#[^\S\n\r]*\[\*\]\s*#", $message); 1797 if(isset($message[0]) && trim($message[0]) == '') 1798 { 1799 array_shift($message); 1800 } 1801 $message = '<li>'.implode("</li>\n<li>", $message)."</li>\n"; 1802 1803 if($type) 1804 { 1805 $list = "\n<ol type=\"$type\" class=\"mycode_list\">$message</ol>\n"; 1806 } 1807 else 1808 { 1809 $list = "<ul class=\"mycode_list\">$message</ul>\n"; 1810 } 1811 $list = preg_replace("#<(ol type=\"$type\"|ul)>\s*</li>#", "<$1>", $list); 1812 return $list; 1813 } 1814 1815 /** 1816 * Parses list MyCode. 1817 * 1818 * @param array $matches Matches 1819 * @return string The parsed message. 1820 */ 1821 function mycode_parse_list_callback($matches) 1822 { 1823 return $this->mycode_parse_list($matches[3], $matches[2]); 1824 } 1825 1826 /** 1827 * Prepares list MyCode by finding the matching list tags. 1828 * 1829 * @param array $matches Matches 1830 * @return string Temporary replacements. 1831 */ 1832 function mycode_prepare_list($matches) 1833 { 1834 // Append number to identify matching list tags 1835 if(strcasecmp($matches[1], '[/list]') == 0) 1836 { 1837 $count = array_pop($this->list_elements); 1838 if($count !== NULL) 1839 { 1840 return "[/list&{$count}]"; 1841 } 1842 else 1843 { 1844 // No open list tag... 1845 return $matches[0]; 1846 } 1847 } 1848 else 1849 { 1850 ++$this->list_count; 1851 $this->list_elements[] = $this->list_count; 1852 if(!empty($matches[2])) 1853 { 1854 return "[list{$matches[2]}&{$this->list_count}]"; 1855 } 1856 else 1857 { 1858 return "[list&{$this->list_count}]"; 1859 } 1860 } 1861 } 1862 1863 /** 1864 * Strips smilies from a string 1865 * 1866 * @param string $message The message for smilies to be stripped from 1867 * @return string The message with smilies stripped 1868 */ 1869 function strip_smilies($message) 1870 { 1871 if($this->smilies_cache == 0) 1872 { 1873 $this->cache_smilies(); 1874 } 1875 if(is_array($this->smilies_cache)) 1876 { 1877 $message = str_replace($this->smilies_cache, array_keys($this->smilies_cache), $message); 1878 } 1879 return $message; 1880 } 1881 1882 /** 1883 * Highlights a string 1884 * 1885 * @param string $message The message to be highligted 1886 * @param string $highlight The highlight keywords 1887 * @return string The message with highlight bbcodes 1888 */ 1889 function highlight_message($message, $highlight) 1890 { 1891 if(empty($this->highlight_cache)) 1892 { 1893 $this->highlight_cache = build_highlight_array($highlight); 1894 } 1895 1896 if(is_array($this->highlight_cache) && !empty($this->highlight_cache)) 1897 { 1898 $message = preg_replace(array_keys($this->highlight_cache), $this->highlight_cache, $message); 1899 } 1900 1901 return $message; 1902 } 1903 1904 /** 1905 * Parses message to plain text equivalents of MyCode. 1906 * 1907 * @param string $message The message to be parsed 1908 * @param array $options 1909 * @return string The parsed message. 1910 */ 1911 function text_parse_message($message, $options=array()) 1912 { 1913 global $plugins; 1914 1915 if(empty($this->options)) 1916 { 1917 $this->options = $options; 1918 } 1919 else 1920 { 1921 foreach($options as $option_name => $option_value) 1922 { 1923 $this->options[$option_name] = $option_value; 1924 } 1925 } 1926 1927 // Filter bad words if requested. 1928 if(!empty($this->options['filter_badwords'])) 1929 { 1930 $message = $this->parse_badwords($message); 1931 } 1932 1933 // Parse quotes first 1934 $message = $this->mycode_parse_quotes($message, true); 1935 1936 $message = preg_replace_callback("#\[php\](.*?)\[/php\](\r\n?|\n?)#is", array($this, 'mycode_parse_php_callback'), $message); 1937 $message = preg_replace_callback("#\[code\](.*?)\[/code\](\r\n?|\n?)#is", array($this, 'mycode_parse_code_callback'), $message); 1938 1939 $find = array( 1940 "#\[(b|u|i|s|url|email|color|img)\](.*?)\[/\\1\]#is", 1941 "#\[(email|color|size|font|align|video)=[^]]*\](.*?)\[/\\1\]#is", 1942 "#\[img=([1-9][0-9]*)x([1-9][0-9]*)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", 1943 "#\[url=((?!javascript)[a-z]+?://)([^\r\n\"<]+?)\](.+?)\[/url\]#si", 1944 "#\[url=((?!javascript:)[^\r\n\"<&\(\)]+?)\](.+?)\[/url\]#si", 1945 "#\[attachment=([0-9]+?)\]#i", 1946 ); 1947 1948 $replace = array( 1949 "$2", 1950 "$2", 1951 "$4", 1952 "$3 ($1$2)", 1953 "$2 ($1)", 1954 "", 1955 ); 1956 1957 $messageBefore = ""; 1958 // The counter limit for this "for" loop is for defensive programming purpose only. It protects against infinite repetition. 1959 for($cnt = 1; $cnt < 20 && $message != $messageBefore; $cnt++) 1960 { 1961 $messageBefore = $message; 1962 $message = preg_replace($find, $replace, $messageBefore); 1963 } 1964 1965 // Replace "me" code and slaps if we have a username 1966 if(!empty($this->options['me_username'])) 1967 { 1968 global $lang; 1969 1970 $message = preg_replace('#(>|^|\r|\n)/me ([^\r\n<]*)#i', "\\1* {$this->options['me_username']} \\2", $message); 1971 $message = preg_replace('#(>|^|\r|\n)/slap ([^\r\n<]*)#i', "\\1* {$this->options['me_username']} {$lang->slaps} \\2 {$lang->with_trout}", $message); 1972 } 1973 1974 // Reset list cache 1975 $this->list_elements = array(); 1976 $this->list_count = 0; 1977 1978 // Find all lists 1979 $message = preg_replace_callback("#(\[list(=(a|A|i|I|1))?\]|\[/list\])#si", array($this, 'mycode_prepare_list'), $message); 1980 1981 // Replace all lists 1982 for($i = $this->list_count; $i > 0; $i--) 1983 { 1984 // Ignores missing end tags 1985 $message = preg_replace_callback("#\s?\[list(=(a|A|i|I|1))?&{$i}\](.*?)(\[/list&{$i}\]|$)(\r\n?|\n?)#si", array($this, 'mycode_parse_list_callback'), $message, 1); 1986 } 1987 1988 // Run plugin hooks 1989 $message = $plugins->run_hooks("text_parse_message", $message); 1990 1991 return $message; 1992 } 1993 1994 /** 1995 * Replaces certain characters with their entities in a URL. 1996 * 1997 * @param string $url The URL to be escaped. 1998 * @return string The escaped URL. 1999 */ 2000 function encode_url($url) 2001 { 2002 $entities = array('$' => '%24', '$' => '%24', '^' => '%5E', '`' => '%60', '[' => '%5B', ']' => '%5D', '{' => '%7B', '}' => '%7D', '"' => '%22', '<' => '%3C', '>' => '%3E', ' ' => '%20'); 2003 2004 $url = str_replace(array_keys($entities), array_values($entities), $url); 2005 2006 return $url; 2007 } 2008 2009 /** 2010 * Determines whether the resulting HTML syntax is acceptable for output, 2011 * according to the parser's validation policy and HTML support. 2012 * 2013 * @param string $source The original MyCode. 2014 * @param string $output The output HTML code. 2015 * @return bool 2016 */ 2017 function output_allowed($source, $output) 2018 { 2019 if($this->output_validation_policy === self::VALIDATION_DISABLE || !empty($this->options['allow_html'])) 2020 { 2021 return true; 2022 } 2023 else 2024 { 2025 $output_valid = $this->validate_output($source, $output); 2026 2027 if($this->output_validation_policy === self::VALIDATION_REPORT_ONLY) 2028 { 2029 return true; 2030 } 2031 else 2032 { 2033 return $output_valid === true; 2034 } 2035 } 2036 } 2037 2038 /** 2039 * Validate HTML syntax and pass errors to the error handler. 2040 * 2041 * @param string $source The original MyCode. 2042 * @param string $output The output HTML code. 2043 * @return bool 2044 */ 2045 function validate_output($source, $output) 2046 { 2047 global $error_handler; 2048 2049 $ignored_error_codes = array( 2050 // entities may be broken through smilie parsing; cache_smilies() method workaround doesn't cover all entities 2051 'XML_ERR_INVALID_DEC_CHARREF' => 7, 2052 'XML_ERR_INVALID_CHAR' => 9, 2053 2054 'XML_ERR_UNDECLARED_ENTITY' => 26, // unrecognized HTML entities 2055 'XML_ERR_ATTRIBUTE_WITHOUT_VALUE' => 41, 2056 'XML_ERR_TAG_NAME_MISMATCH' => 76, // the parser may output tags closed in different levels and siblings 2057 ); 2058 2059 libxml_use_internal_errors(true); 2060 @libxml_disable_entity_loader(true); 2061 2062 simplexml_load_string('<root>'.$output.'</root>', 'SimpleXMLElement', 524288 /* LIBXML_PARSEHUGE */); 2063 2064 $errors = libxml_get_errors(); 2065 2066 libxml_use_internal_errors(false); 2067 2068 if( 2069 $errors && 2070 array_diff( 2071 array_column($errors, 'code'), 2072 $ignored_error_codes 2073 ) 2074 ) 2075 { 2076 $data = array( 2077 'sourceHtmlEntities' => htmlspecialchars_uni($source), 2078 'outputHtmlEntities' => htmlspecialchars_uni($output), 2079 'errors' => $errors, 2080 ); 2081 $error_message = "Parser output validation failed.\n"; 2082 $error_message .= var_export($data, true); 2083 2084 $error_handler->error(E_USER_WARNING, $error_message, __FILE__, __LINE__, false); 2085 2086 return false; 2087 } else { 2088 return true; 2089 } 2090 } 2091 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup | Cross-referenced by PHPXref |