[ Index ] |
PHP Cross Reference of MyBB 1.8.38 |
[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('#<code>\s*<span style="color: \#000000">\s*#i', "<code>", $code); 1119 $code = preg_replace("#</span>\s*</code>#", "</code>", $code); 1120 $code = preg_replace("#</span>(\r\n?|\n?)</code>#", "</span></code>", $code); 1121 $code = str_replace("\\", '\', $code); 1122 $code = str_replace('$', '$', $code); 1123 $code = preg_replace("#&\#([0-9]+);#si", "&#$1;", $code); 1124 1125 if($added_open_tag) 1126 { 1127 $code = preg_replace("#<code><span style=\"color: \#([A-Z0-9]{6})\"><\?php( | )(<br />?)#", "<code><span style=\"color: #$1\">", $code); 1128 } 1129 1130 if($added_end_tag) 1131 { 1132 $code = str_replace("?></span></code>", "</span></code>", $code); 1133 // Wait a minute. It fails highlighting? Stupid highlighter. 1134 $code = str_replace("?></code>", "</code>", $code); 1135 } 1136 1137 $code = preg_replace("#<span style=\"color: \#([A-Z0-9]{6})\"></span>#", "", $code); 1138 $code = str_replace("<code>", "<div dir=\"ltr\"><code>", $code); 1139 $code = str_replace("</code>", "</code></div>", $code); 1140 $code = preg_replace("# *$#", "", $code); 1141 1142 if($bare_return) 1143 { 1144 return $code; 1145 } 1146 1147 // Send back the code all nice and pretty 1148 eval("\$mycode_php = \"".$templates->get("mycode_php", 1, 0)."\";"); 1149 return $mycode_php; 1150 } 1151 1152 /** 1153 * Parses PHP code MyCode. 1154 * 1155 * @param array $matches Matches. 1156 * @return string The parsed message. 1157 */ 1158 function mycode_parse_php_callback($matches) 1159 { 1160 return $this->mycode_parse_php($matches[1], false, true); 1161 } 1162 1163 /** 1164 * Parses URL MyCode. 1165 * 1166 * @param string $url The URL to link to. 1167 * @param string $name The name of the link. 1168 * @return string The built-up link. 1169 */ 1170 function mycode_parse_url($url, $name="") 1171 { 1172 global $templates; 1173 if(!preg_match("#^[a-z0-9]+://#i", $url)) 1174 { 1175 $url = "http://".$url; 1176 } 1177 1178 if(!empty($this->options['allow_html'])) 1179 { 1180 $url = $this->parse_html($url); 1181 } 1182 1183 if(!$name) 1184 { 1185 $name = $url; 1186 } 1187 1188 if($name == $url && (!isset($this->options['shorten_urls']) || !empty($this->options['shorten_urls']))) 1189 { 1190 $name = htmlspecialchars_decode($name); 1191 if(my_strlen($name) > 55) 1192 { 1193 $name = my_substr($name , 0, 40).'...'.my_substr($name , -10); 1194 } 1195 $name = htmlspecialchars_uni($name); 1196 } 1197 1198 if(!empty($this->options['nofollow_on'])) 1199 { 1200 $rel = " rel=\"noopener nofollow\""; 1201 } 1202 else 1203 { 1204 $rel = " rel=\"noopener\""; 1205 } 1206 1207 // Fix some entities in URLs 1208 $url = $this->encode_url($url); 1209 $name = $this->parse_badwords(preg_replace("#&\#([0-9]+);#si", "&#$1;", $name)); // Fix & but allow unicode, filter bad words 1210 1211 eval("\$mycode_url = \"".$templates->get("mycode_url", 1, 0)."\";"); 1212 return $mycode_url; 1213 } 1214 1215 /** 1216 * Parses font MyCode. 1217 * 1218 * @param array $matches Matches. 1219 * @return string The HTML <span> tag with styled font. 1220 */ 1221 function mycode_parse_font_callback($matches) 1222 { 1223 // Replace any occurrence(s) of double quotes in fonts with single quotes. 1224 // A back-fix for double-quote-containing MyBB font tags in existing 1225 // posts prior to the client-side aspect of this fix for the 1226 // browser-independent SCEditor bug of issue #4182. 1227 $fonts = str_replace('"', "'", $matches[2]); 1228 1229 return "<span style=\"font-family: {$fonts};\" class=\"mycode_font\">{$matches[3]}</span>"; 1230 } 1231 1232 /** 1233 * Parses URL MyCode. 1234 * 1235 * @param array $matches Matches. 1236 * @return string The built-up link. 1237 */ 1238 function mycode_parse_url_callback1($matches) 1239 { 1240 if(!isset($matches[3])) 1241 { 1242 $matches[3] = ''; 1243 } 1244 return $this->mycode_parse_url($matches[1].$matches[2], $matches[3]); 1245 } 1246 1247 /** 1248 * Parses URL MyCode. 1249 * 1250 * @param array $matches Matches. 1251 * @return string The built-up link. 1252 */ 1253 function mycode_parse_url_callback2($matches) 1254 { 1255 if(!isset($matches[2])) 1256 { 1257 $matches[2] = ''; 1258 } 1259 return $this->mycode_parse_url($matches[1], $matches[2]); 1260 } 1261 1262 /** 1263 * Parses IMG MyCode. 1264 * 1265 * @param string $url The URL to the image 1266 * @param array $dimensions Optional array of dimensions 1267 * @param string $align 1268 * @return string 1269 */ 1270 function mycode_parse_img($url, $dimensions=array(), $align='') 1271 { 1272 global $lang, $templates; 1273 $url = trim($url); 1274 $url = str_replace("\n", "", $url); 1275 $url = str_replace("\r", "", $url); 1276 1277 if(!empty($this->options['allow_html'])) 1278 { 1279 $url = $this->parse_html($url); 1280 } 1281 1282 $css_align = ''; 1283 if($align == "right") 1284 { 1285 $css_align = ' style="float: right;"'; 1286 } 1287 else if($align == "left") 1288 { 1289 $css_align = ' style="float: left;"'; 1290 } 1291 1292 if($align) 1293 { 1294 $this->clear_needed = true; 1295 } 1296 1297 $alt = basename($url); 1298 $alt = htmlspecialchars_decode($alt); 1299 if(my_strlen($alt) > 55) 1300 { 1301 $alt = my_substr($alt, 0, 40).'...'.my_substr($alt, -10); 1302 } 1303 $alt = $this->encode_url($alt); 1304 $alt = preg_replace("#&(?!\#[0-9]+;)#si", "&", $alt); // fix & but allow unicode 1305 1306 $alt = $lang->sprintf($lang->posted_image, $alt); 1307 $width = $height = ''; 1308 if(isset($dimensions[0]) && $dimensions[0] > 0 && isset($dimensions[1]) && $dimensions[1] > 0) 1309 { 1310 $width = " width=\"{$dimensions[0]}\""; 1311 $height = " height=\"{$dimensions[1]}\""; 1312 } 1313 1314 $url = $this->encode_url($url); 1315 1316 eval("\$mycode_img = \"".$templates->get("mycode_img", 1, 0)."\";"); 1317 return $mycode_img; 1318 } 1319 1320 /** 1321 * Parses IMG MyCode. 1322 * 1323 * @param array $matches Matches. 1324 * @return string Image code. 1325 */ 1326 function mycode_parse_img_callback1($matches) 1327 { 1328 return $this->mycode_parse_img($matches[2]); 1329 } 1330 1331 /** 1332 * Parses IMG MyCode. 1333 * 1334 * @param array $matches Matches. 1335 * @return string Image code. 1336 */ 1337 function mycode_parse_img_callback2($matches) 1338 { 1339 return $this->mycode_parse_img($matches[4], array($matches[1], $matches[2])); 1340 } 1341 1342 /** 1343 * Parses IMG MyCode. 1344 * 1345 * @param array $matches Matches. 1346 * @return string Image code. 1347 */ 1348 function mycode_parse_img_callback3($matches) 1349 { 1350 return $this->mycode_parse_img($matches[3], array(), $matches[1]); 1351 } 1352 1353 /** 1354 * Parses IMG MyCode. 1355 * 1356 * @param array $matches Matches. 1357 * @return string Image code. 1358 */ 1359 function mycode_parse_img_callback4($matches) 1360 { 1361 return $this->mycode_parse_img($matches[5], array($matches[1], $matches[2]), $matches[3]); 1362 } 1363 1364 /** 1365 * Parses IMG MyCode disabled. 1366 * 1367 * @param string $url The URL to the image 1368 * @return string 1369 */ 1370 function mycode_parse_img_disabled($url) 1371 { 1372 global $lang; 1373 $url = trim($url); 1374 $url = str_replace("\n", "", $url); 1375 $url = str_replace("\r", "", $url); 1376 $url = str_replace("\'", "'", $url); 1377 1378 $image = $lang->sprintf($lang->posted_image, $this->mycode_parse_url($url)); 1379 return $image; 1380 } 1381 1382 /** 1383 * Parses IMG MyCode disabled. 1384 * 1385 * @param array $matches Matches. 1386 * @return string Image code. 1387 */ 1388 function mycode_parse_img_disabled_callback1($matches) 1389 { 1390 return $this->mycode_parse_img_disabled($matches[2]); 1391 } 1392 1393 /** 1394 * Parses IMG MyCode disabled. 1395 * 1396 * @param array $matches Matches. 1397 * @return string Image code. 1398 */ 1399 function mycode_parse_img_disabled_callback2($matches) 1400 { 1401 return $this->mycode_parse_img_disabled($matches[4]); 1402 } 1403 1404 /** 1405 * Parses IMG MyCode disabled. 1406 * 1407 * @param array $matches Matches. 1408 * @return string Image code. 1409 */ 1410 function mycode_parse_img_disabled_callback3($matches) 1411 { 1412 return $this->mycode_parse_img_disabled($matches[3]); 1413 } 1414 1415 /** 1416 * Parses IMG MyCode disabled. 1417 * 1418 * @param array $matches Matches. 1419 * @return string Image code. 1420 */ 1421 function mycode_parse_img_disabled_callback4($matches) 1422 { 1423 return $this->mycode_parse_img_disabled($matches[5]); 1424 } 1425 1426 /** 1427 * Parses email MyCode. 1428 * 1429 * @param string $email The email address to link to. 1430 * @param string $name The name for the link. 1431 * @return string The built-up email link. 1432 */ 1433 function mycode_parse_email($email, $name="") 1434 { 1435 global $templates; 1436 1437 if(!$name) 1438 { 1439 $name = $email; 1440 } 1441 1442 $email = $this->encode_url($email); 1443 1444 eval("\$mycode_email = \"".$templates->get("mycode_email", 1, 0)."\";"); 1445 return $mycode_email; 1446 } 1447 1448 /** 1449 * Parses email MyCode. 1450 * 1451 * @param array $matches Matches 1452 * @return string The built-up email link. 1453 */ 1454 function mycode_parse_email_callback($matches) 1455 { 1456 if(!isset($matches[2])) 1457 { 1458 $matches[2] = ''; 1459 } 1460 return $this->mycode_parse_email($matches[1], $matches[2]); 1461 } 1462 1463 /** 1464 * Parses video MyCode. 1465 * 1466 * @param string $video The video provider. 1467 * @param string $url The video to link to. 1468 * @return string The built-up video code. 1469 */ 1470 function mycode_parse_video($video, $url) 1471 { 1472 global $mybb, $templates; 1473 1474 if(empty($video) || empty($url)) 1475 { 1476 return "[video={$video}]{$url}[/video]"; 1477 } 1478 1479 // Check URL is a valid URL first, as `parse_url` doesn't check validity. 1480 if(false === filter_var($url, FILTER_VALIDATE_URL)) 1481 { 1482 return "[video={$video}]{$url}[/video]"; 1483 } 1484 1485 $parsed_url = @parse_url(urldecode($url)); 1486 if($parsed_url === false) 1487 { 1488 return "[video={$video}]{$url}[/video]"; 1489 } 1490 1491 $bbdomain = parse_url($mybb->settings['bburl'], PHP_URL_HOST); 1492 1493 $fragments = empty($parsed_url['fragment']) ? array() : explode("&", $parsed_url['fragment']); 1494 1495 if($video == "liveleak" && !empty($parsed_url['query'])) 1496 { 1497 // The query part can start with any alphabet, but set only 'i' to catch in index key later 1498 $parsed_url['query'] = "i".substr($parsed_url['query'], 1); 1499 } 1500 1501 $queries = empty($parsed_url['query']) ? array() : explode("&", $parsed_url['query']); 1502 1503 $input = array(); 1504 foreach($queries as $query) 1505 { 1506 $query_array = explode("=", $query); 1507 if(count($query_array) == 2) 1508 { 1509 list($key, $value) = $query_array; 1510 $key = str_replace("amp;", "", $key); 1511 $input[$key] = $value; 1512 } 1513 } 1514 1515 $path = empty($parsed_url['path']) ? array() : explode('/', $parsed_url['path']); 1516 1517 switch($video) 1518 { 1519 case "dailymotion": 1520 if(!empty($path[2])) 1521 { 1522 list($id) = explode('_', $path[2], 2); // http://www.dailymotion.com/video/fds123_title-goes-here 1523 } 1524 elseif(!empty($path[1])) 1525 { 1526 $id = $path[1]; // http://dai.ly/fds123 1527 } 1528 break; 1529 case "metacafe": 1530 if(!empty($path[2])) 1531 { 1532 $id = $path[2]; // http://www.metacafe.com/watch/fds123/title_goes_here/ 1533 } 1534 break; 1535 case "myspacetv": 1536 if(!empty($path[4])) 1537 { 1538 $id = $path[4]; // http://www.myspace.com/video/fds/fds/123 1539 } 1540 break; 1541 case "facebook": 1542 if(!empty($input['v'])) 1543 { 1544 $id = $input['v']; // http://www.facebook.com/video/video.php?v=123 1545 } 1546 elseif(!empty($path[3]) && substr($path[3], 0, 3) == 'vb.' && !empty($path[4])) 1547 { 1548 $id = $path[4]; // https://www.facebook.com/fds/videos/vb.123/123/ 1549 } 1550 elseif(!empty($path[3])) 1551 { 1552 $id = $path[3]; // https://www.facebook.com/fds/videos/123/ 1553 } 1554 break; 1555 case "mixer": 1556 if(!empty($path[1])) 1557 { 1558 $id = $path[1]; // https://mixer.com/streamer 1559 } 1560 break; 1561 case "liveleak": 1562 if(!empty($input['i'])) 1563 { 1564 $id = $input['i']; // http://www.liveleak.com/view?i=123 1565 } 1566 break; 1567 case "yahoo": 1568 if(!empty($path[2])) 1569 { 1570 $id = $path[2]; // http://xy.screen.yahoo.com/fds/fds-123.html 1571 } 1572 elseif(!empty($path[1])) 1573 { 1574 $id = $path[1]; // http://xy.screen.yahoo.com/fds-123.html 1575 } 1576 // Support for localized portals 1577 if(!empty($parsed_url['host'])) 1578 { 1579 $domain = explode('.', $parsed_url['host']); 1580 if($domain[0] != 'screen' && preg_match('#^([a-z-]+)$#', $domain[0])) 1581 { 1582 $local = "{$domain[0]}."; 1583 } 1584 else 1585 { 1586 $local = ''; 1587 } 1588 } 1589 break; 1590 case "vimeo": 1591 if(!empty($path[3])) 1592 { 1593 $id = $path[3]; // http://vimeo.com/fds/fds/fds123 1594 } 1595 elseif(!empty($path[1])) 1596 { 1597 $id = $path[1]; // http://vimeo.com/fds123 1598 } 1599 break; 1600 case "youtube": 1601 if(!empty($fragments[0])) 1602 { 1603 $id = str_replace('!v=', '', $fragments[0]); // http://www.youtube.com/watch#!v=fds123 1604 } 1605 elseif(!empty($input['v'])) 1606 { 1607 $id = $input['v']; // http://www.youtube.com/watch?v=fds123 1608 } 1609 elseif(!empty($path[1])) 1610 { 1611 $id = $path[1]; // http://www.youtu.be/fds123 1612 } 1613 break; 1614 case "twitch": 1615 if(count($path) >= 3 && $path[1] == 'videos') 1616 { 1617 // Direct video embed with URL like: https://www.twitch.tv/videos/179723472 1618 $id = 'video=v'.$path[2]; 1619 } 1620 elseif(count($path) >= 4 && $path[2] == 'v') 1621 { 1622 // Direct video embed with URL like: https://www.twitch.tv/waypoint/v/179723472 1623 $id = 'video=v'.$path[3]; 1624 } 1625 elseif(count($path) >= 2) 1626 { 1627 // Channel (livestream) embed with URL like: https://twitch.tv/waypoint 1628 $id = 'channel='.$path[1]; 1629 } 1630 break; 1631 default: 1632 return "[video={$video}]{$url}[/video]"; 1633 } 1634 1635 if(empty($id)) 1636 { 1637 return "[video={$video}]{$url}[/video]"; 1638 } 1639 1640 $id = $this->encode_url($id); 1641 1642 eval("\$video_code = \"".$templates->get("video_{$video}_embed", 1, 0)."\";"); 1643 return $video_code; 1644 } 1645 1646 /** 1647 * Parses video MyCode. 1648 * 1649 * @param array $matches Matches. 1650 * @return string The built-up video code. 1651 */ 1652 function mycode_parse_video_callback($matches) 1653 { 1654 return $this->mycode_parse_video($matches[1], $matches[2]); 1655 } 1656 1657 /** 1658 * Parses video MyCode disabled. 1659 * 1660 * @param string $url The URL to the video 1661 * @return string 1662 */ 1663 function mycode_parse_video_disabled($url) 1664 { 1665 global $lang; 1666 $url = trim($url); 1667 $url = str_replace("\n", "", $url); 1668 $url = str_replace("\r", "", $url); 1669 $url = str_replace("\'", "'", $url); 1670 1671 $video = $lang->sprintf($lang->posted_video, $this->mycode_parse_url($url)); 1672 return $video; 1673 } 1674 1675 /** 1676 * Parses video MyCode disabled. 1677 * 1678 * @param array $matches Matches. 1679 * @return string The built-up video code. 1680 */ 1681 function mycode_parse_video_disabled_callback($matches) 1682 { 1683 return $this->mycode_parse_video_disabled($matches[2]); 1684 } 1685 1686 /** 1687 * Parses URLs automatically. 1688 * 1689 * @param string $message The message to be parsed 1690 * @return string The parsed message. 1691 */ 1692 function mycode_auto_url($message) 1693 { 1694 // Links should end with slashes, numbers, characters and braces but not with dots, commas or question marks 1695 // Don't create links within existing links (handled up-front in the callback function). 1696 $message = preg_replace_callback( 1697 "~ 1698 <a\\s[^>]*>.*?</a>| # match and return existing links 1699 (?<=^|[\s\(\)\[\>]) # character preceding the link 1700 (?P<prefix> 1701 (?:http|https|ftp|news|irc|ircs|irc6)://| # scheme, or 1702 (?:www|ftp)\. # common subdomain 1703 ) 1704 (?P<link> 1705 (?:[^\/\"\s\<\[\.]+\.)*[\w]+ # host 1706 (?::[0-9]+)? # port 1707 (?:/(?:[^\"\s<\[&]|\[\]|&(?:amp|lt|gt);)*)? # path, query, fragment; exclude unencoded characters 1708 [\w\/\)] 1709 ) 1710 (?![^<>]*?>) # not followed by unopened > (within HTML tags) 1711 ~iusx", 1712 array($this, 'mycode_auto_url_callback'), 1713 $message 1714 ); 1715 1716 return $message; 1717 } 1718 1719 /** 1720 * Parses URLs automatically. 1721 * 1722 * @param array $matches Matches 1723 * @return string The parsed message. 1724 */ 1725 function mycode_auto_url_callback($matches=array()) 1726 { 1727 // If we matched a preexisting link (the part of the regexes in mycode_auto_url() before the pipe symbol), 1728 // then simply return it - we don't create links within existing links. 1729 if(count($matches) == 1) 1730 { 1731 return $matches[0]; 1732 } 1733 1734 $external = ''; 1735 // Allow links like http://en.wikipedia.org/wiki/PHP_(disambiguation) but detect mismatching braces 1736 while(my_substr($matches['link'], -1) == ')') 1737 { 1738 if(substr_count($matches['link'], ')') > substr_count($matches['link'], '(')) 1739 { 1740 $matches['link'] = my_substr($matches['link'], 0, -1); 1741 $external = ')'.$external; 1742 } 1743 else 1744 { 1745 break; 1746 } 1747 1748 // Example: ([...] http://en.wikipedia.org/Example_(disambiguation).) 1749 $last_char = my_substr($matches['link'], -1); 1750 while($last_char == '.' || $last_char == ',' || $last_char == '?' || $last_char == '!') 1751 { 1752 $matches['link'] = my_substr($matches['link'], 0, -1); 1753 $external = $last_char.$external; 1754 $last_char = my_substr($matches['link'], -1); 1755 } 1756 } 1757 $url = $matches['prefix'].$matches['link']; 1758 1759 return $this->mycode_parse_url($url, $url).$external; 1760 } 1761 1762 /** 1763 * Parses list MyCode. 1764 * 1765 * @param string $message The message to be parsed 1766 * @param string $type The list type 1767 * @return string The parsed message. 1768 */ 1769 function mycode_parse_list($message, $type="") 1770 { 1771 // No list elements? That's invalid HTML 1772 if(strpos($message, '[*]') === false) 1773 { 1774 $message = "[*]{$message}"; 1775 } 1776 1777 $message = preg_split("#[^\S\n\r]*\[\*\]\s*#", $message); 1778 if(isset($message[0]) && trim($message[0]) == '') 1779 { 1780 array_shift($message); 1781 } 1782 $message = '<li>'.implode("</li>\n<li>", $message)."</li>\n"; 1783 1784 if($type) 1785 { 1786 $list = "\n<ol type=\"$type\" class=\"mycode_list\">$message</ol>\n"; 1787 } 1788 else 1789 { 1790 $list = "<ul class=\"mycode_list\">$message</ul>\n"; 1791 } 1792 $list = preg_replace("#<(ol type=\"$type\"|ul)>\s*</li>#", "<$1>", $list); 1793 return $list; 1794 } 1795 1796 /** 1797 * Parses list MyCode. 1798 * 1799 * @param array $matches Matches 1800 * @return string The parsed message. 1801 */ 1802 function mycode_parse_list_callback($matches) 1803 { 1804 return $this->mycode_parse_list($matches[3], $matches[2]); 1805 } 1806 1807 /** 1808 * Prepares list MyCode by finding the matching list tags. 1809 * 1810 * @param array $matches Matches 1811 * @return string Temporary replacements. 1812 */ 1813 function mycode_prepare_list($matches) 1814 { 1815 // Append number to identify matching list tags 1816 if(strcasecmp($matches[1], '[/list]') == 0) 1817 { 1818 $count = array_pop($this->list_elements); 1819 if($count !== NULL) 1820 { 1821 return "[/list&{$count}]"; 1822 } 1823 else 1824 { 1825 // No open list tag... 1826 return $matches[0]; 1827 } 1828 } 1829 else 1830 { 1831 ++$this->list_count; 1832 $this->list_elements[] = $this->list_count; 1833 if(!empty($matches[2])) 1834 { 1835 return "[list{$matches[2]}&{$this->list_count}]"; 1836 } 1837 else 1838 { 1839 return "[list&{$this->list_count}]"; 1840 } 1841 } 1842 } 1843 1844 /** 1845 * Strips smilies from a string 1846 * 1847 * @param string $message The message for smilies to be stripped from 1848 * @return string The message with smilies stripped 1849 */ 1850 function strip_smilies($message) 1851 { 1852 if($this->smilies_cache == 0) 1853 { 1854 $this->cache_smilies(); 1855 } 1856 if(is_array($this->smilies_cache)) 1857 { 1858 $message = str_replace($this->smilies_cache, array_keys($this->smilies_cache), $message); 1859 } 1860 return $message; 1861 } 1862 1863 /** 1864 * Highlights a string 1865 * 1866 * @param string $message The message to be highligted 1867 * @param string $highlight The highlight keywords 1868 * @return string The message with highlight bbcodes 1869 */ 1870 function highlight_message($message, $highlight) 1871 { 1872 if(empty($this->highlight_cache)) 1873 { 1874 $this->highlight_cache = build_highlight_array($highlight); 1875 } 1876 1877 if(is_array($this->highlight_cache) && !empty($this->highlight_cache)) 1878 { 1879 $message = preg_replace(array_keys($this->highlight_cache), $this->highlight_cache, $message); 1880 } 1881 1882 return $message; 1883 } 1884 1885 /** 1886 * Parses message to plain text equivalents of MyCode. 1887 * 1888 * @param string $message The message to be parsed 1889 * @param array $options 1890 * @return string The parsed message. 1891 */ 1892 function text_parse_message($message, $options=array()) 1893 { 1894 global $plugins; 1895 1896 if(empty($this->options)) 1897 { 1898 $this->options = $options; 1899 } 1900 else 1901 { 1902 foreach($options as $option_name => $option_value) 1903 { 1904 $this->options[$option_name] = $option_value; 1905 } 1906 } 1907 1908 // Filter bad words if requested. 1909 if(!empty($this->options['filter_badwords'])) 1910 { 1911 $message = $this->parse_badwords($message); 1912 } 1913 1914 // Parse quotes first 1915 $message = $this->mycode_parse_quotes($message, true); 1916 1917 $message = preg_replace_callback("#\[php\](.*?)\[/php\](\r\n?|\n?)#is", array($this, 'mycode_parse_php_callback'), $message); 1918 $message = preg_replace_callback("#\[code\](.*?)\[/code\](\r\n?|\n?)#is", array($this, 'mycode_parse_code_callback'), $message); 1919 1920 $find = array( 1921 "#\[(b|u|i|s|url|email|color|img)\](.*?)\[/\\1\]#is", 1922 "#\[(email|color|size|font|align|video)=[^]]*\](.*?)\[/\\1\]#is", 1923 "#\[img=([1-9][0-9]*)x([1-9][0-9]*)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is", 1924 "#\[url=((?!javascript)[a-z]+?://)([^\r\n\"<]+?)\](.+?)\[/url\]#si", 1925 "#\[url=((?!javascript:)[^\r\n\"<&\(\)]+?)\](.+?)\[/url\]#si", 1926 "#\[attachment=([0-9]+?)\]#i", 1927 ); 1928 1929 $replace = array( 1930 "$2", 1931 "$2", 1932 "$4", 1933 "$3 ($1$2)", 1934 "$2 ($1)", 1935 "", 1936 ); 1937 1938 $messageBefore = ""; 1939 // The counter limit for this "for" loop is for defensive programming purpose only. It protects against infinite repetition. 1940 for($cnt = 1; $cnt < 20 && $message != $messageBefore; $cnt++) 1941 { 1942 $messageBefore = $message; 1943 $message = preg_replace($find, $replace, $messageBefore); 1944 } 1945 1946 // Replace "me" code and slaps if we have a username 1947 if(!empty($this->options['me_username'])) 1948 { 1949 global $lang; 1950 1951 $message = preg_replace('#(>|^|\r|\n)/me ([^\r\n<]*)#i', "\\1* {$this->options['me_username']} \\2", $message); 1952 $message = preg_replace('#(>|^|\r|\n)/slap ([^\r\n<]*)#i', "\\1* {$this->options['me_username']} {$lang->slaps} \\2 {$lang->with_trout}", $message); 1953 } 1954 1955 // Reset list cache 1956 $this->list_elements = array(); 1957 $this->list_count = 0; 1958 1959 // Find all lists 1960 $message = preg_replace_callback("#(\[list(=(a|A|i|I|1))?\]|\[/list\])#si", array($this, 'mycode_prepare_list'), $message); 1961 1962 // Replace all lists 1963 for($i = $this->list_count; $i > 0; $i--) 1964 { 1965 // Ignores missing end tags 1966 $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); 1967 } 1968 1969 // Run plugin hooks 1970 $message = $plugins->run_hooks("text_parse_message", $message); 1971 1972 return $message; 1973 } 1974 1975 /** 1976 * Replaces certain characters with their entities in a URL. 1977 * 1978 * @param string $url The URL to be escaped. 1979 * @return string The escaped URL. 1980 */ 1981 function encode_url($url) 1982 { 1983 $entities = array('$' => '%24', '$' => '%24', '^' => '%5E', '`' => '%60', '[' => '%5B', ']' => '%5D', '{' => '%7B', '}' => '%7D', '"' => '%22', '<' => '%3C', '>' => '%3E', ' ' => '%20'); 1984 1985 $url = str_replace(array_keys($entities), array_values($entities), $url); 1986 1987 return $url; 1988 } 1989 1990 /** 1991 * Determines whether the resulting HTML syntax is acceptable for output, 1992 * according to the parser's validation policy and HTML support. 1993 * 1994 * @param string $source The original MyCode. 1995 * @param string $output The output HTML code. 1996 * @return bool 1997 */ 1998 function output_allowed($source, $output) 1999 { 2000 if($this->output_validation_policy === self::VALIDATION_DISABLE || !empty($this->options['allow_html'])) 2001 { 2002 return true; 2003 } 2004 else 2005 { 2006 $output_valid = $this->validate_output($source, $output); 2007 2008 if($this->output_validation_policy === self::VALIDATION_REPORT_ONLY) 2009 { 2010 return true; 2011 } 2012 else 2013 { 2014 return $output_valid === true; 2015 } 2016 } 2017 } 2018 2019 /** 2020 * Validate HTML syntax and pass errors to the error handler. 2021 * 2022 * @param string $source The original MyCode. 2023 * @param string $output The output HTML code. 2024 * @return bool 2025 */ 2026 function validate_output($source, $output) 2027 { 2028 global $error_handler; 2029 2030 $ignored_error_codes = array( 2031 // entities may be broken through smilie parsing; cache_smilies() method workaround doesn't cover all entities 2032 'XML_ERR_INVALID_DEC_CHARREF' => 7, 2033 'XML_ERR_INVALID_CHAR' => 9, 2034 2035 'XML_ERR_UNDECLARED_ENTITY' => 26, // unrecognized HTML entities 2036 'XML_ERR_ATTRIBUTE_WITHOUT_VALUE' => 41, 2037 'XML_ERR_TAG_NAME_MISMATCH' => 76, // the parser may output tags closed in different levels and siblings 2038 ); 2039 2040 libxml_use_internal_errors(true); 2041 @libxml_disable_entity_loader(true); 2042 2043 simplexml_load_string('<root>'.$output.'</root>', 'SimpleXMLElement', 524288 /* LIBXML_PARSEHUGE */); 2044 2045 $errors = libxml_get_errors(); 2046 2047 libxml_use_internal_errors(false); 2048 2049 if( 2050 $errors && 2051 array_diff( 2052 array_column($errors, 'code'), 2053 $ignored_error_codes 2054 ) 2055 ) 2056 { 2057 $data = array( 2058 'sourceHtmlEntities' => htmlspecialchars_uni($source), 2059 'outputHtmlEntities' => htmlspecialchars_uni($output), 2060 'errors' => $errors, 2061 ); 2062 $error_message = "Parser output validation failed.\n"; 2063 $error_message .= var_export($data, true); 2064 2065 $error_handler->error(E_USER_WARNING, $error_message, __FILE__, __LINE__, false); 2066 2067 return false; 2068 } else { 2069 return true; 2070 } 2071 } 2072 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup | Cross-referenced by PHPXref |