[ Index ]

PHP Cross Reference of MyBB 1.8.28

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


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