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