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