[ Index ]

PHP Cross Reference of MyBB 1.8.40

title

Body

[close]

/inc/ -> class_parser.php (source)

   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("&lt;mybb-code&gt;\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', '&lt;$1$2$3&gt;', $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("#(&nbsp;)+(</?(?: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", "&amp;", $message); // fix & but allow unicode
 317          $message = str_replace("<","&lt;",$message);
 318          $message = str_replace(">","&gt;",$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'] = "&copy;";
 359  
 360              $standard_mycode['tm']['regex'] = "#\(tm\)#i";
 361              $standard_mycode['tm']['replacement'] = "&#153;";
 362  
 363              $standard_mycode['reg']['regex'] = "#\(r\)#i";
 364              $standard_mycode['reg']['replacement'] = "&reg;";
 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('$', '&#36;', $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                              "&amp$s" => "&amp$s",
 647                              "&lt$s" => "&lt$s",
 648                              "&gt$s" => "&gt$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=([\"']|&quot;|)(.*?)(?:\\1)(.*?)(?:[\"']|&quot;)?\](.*?)\[/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=(?:&quot;|\"|')?([0-9]+)[\"']?(?:&quot;|\"|')?#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("#(?:&quot;|\"|')? pid=(?:&quot;|\"|')?[0-9]+[\"']?(?:&quot;|\"|')?#i", '', $username);
 967              $delete_quote = false;
 968          }
 969  
 970          unset($match);
 971          preg_match("#dateline=(?:&quot;|\"|')?([0-9]+)(?:&quot;|\"|')?#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("#(?:&quot;|\"|')? dateline=(?:&quot;|\"|')?[0-9]+(?:&quot;|\"|')?#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('$', '&#36;', $code);
1066          $code = preg_replace('#\$([0-9])#', '\\\$\\1', $code);
1067          $code = str_replace('\\', '&#92;', $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("\\", '&#092;', $code);
1143          $code = str_replace('$', '&#36;', $code);
1144          $code = preg_replace("#&amp;\#([0-9]+);#si", "&#$1;", $code);
1145  
1146          if($added_open_tag)
1147          {
1148              $code = preg_replace("#<code><span style=\"color: \#([A-Z0-9]{6})\">&lt;\?php( |&nbsp;)(<br />|\n)#", "<code><span style=\"color: #$1\">", $code);
1149          }
1150  
1151          if($added_end_tag)
1152          {
1153              $code = str_replace("?&gt;</span></code>", "</span></code>", $code);
1154              // Wait a minute. It fails highlighting? Stupid highlighter.
1155              $code = str_replace("?&gt;</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("#&amp;\#([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", "&amp;", $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                      (?<=&amp;)|(?<=&lt;)|(?<=&gt;)|                # 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', '&#36;' => '%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  }


2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup Cross-referenced by PHPXref