[ Index ]

PHP Cross Reference of MyBB 1.8.32

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 $mybb, $templates;
1473  
1474          if(empty($video) || empty($url))
1475          {
1476              return "[video={$video}]{$url}[/video]";
1477          }
1478  
1479          // Check URL is a valid URL first, as `parse_url` doesn't check validity.
1480          if(false === filter_var($url, FILTER_VALIDATE_URL))
1481          {
1482              return "[video={$video}]{$url}[/video]";
1483          }
1484  
1485          $parsed_url = @parse_url(urldecode($url));
1486          if($parsed_url === false)
1487          {
1488              return "[video={$video}]{$url}[/video]";
1489          }
1490  
1491          $bbdomain = parse_url($mybb->settings['bburl'], PHP_URL_HOST);
1492  
1493          $fragments = array();
1494          if($parsed_url['fragment'])
1495          {
1496              $fragments = explode("&", $parsed_url['fragment']);
1497          }
1498  
1499          if($video == "liveleak")
1500          {
1501              // The query part can start with any alphabet, but set only 'i' to catch in index key later
1502              $parsed_url['query'] = "i".substr($parsed_url['query'], 1);
1503          }
1504  
1505          $queries = explode("&", $parsed_url['query']);
1506  
1507          $input = array();
1508          foreach($queries as $query)
1509          {
1510              list($key, $value) = explode("=", $query);
1511              $key = str_replace("amp;", "", $key);
1512              $input[$key] = $value;
1513          }
1514  
1515          $path = explode('/', $parsed_url['path']);
1516  
1517          switch($video)
1518          {
1519              case "dailymotion":
1520                  if(isset($path[2]))
1521                  {
1522                      list($id) = explode('_', $path[2], 2); // http://www.dailymotion.com/video/fds123_title-goes-here
1523                  }
1524                  else
1525                  {
1526                      $id = $path[1]; // http://dai.ly/fds123
1527                  }
1528                  break;
1529              case "metacafe":
1530                  $id = $path[2]; // http://www.metacafe.com/watch/fds123/title_goes_here/
1531                  $title = htmlspecialchars_uni($path[3]);
1532                  break;
1533              case "myspacetv":
1534                  $id = $path[4]; // http://www.myspace.com/video/fds/fds/123
1535                  break;
1536              case "facebook":
1537                  if(isset($input['v']))
1538                  {
1539                      $id = $input['v']; // http://www.facebook.com/video/video.php?v=123
1540                  }
1541                  elseif(substr($path[3], 0, 3) == 'vb.')
1542                  {
1543                      $id = $path[4]; // https://www.facebook.com/fds/videos/vb.123/123/
1544                  }
1545                  else
1546                  {
1547                      $id = $path[3]; // https://www.facebook.com/fds/videos/123/
1548                  }
1549                  break;
1550              case "mixer":
1551                  $id = $path[1]; // https://mixer.com/streamer
1552                  break;
1553              case "liveleak":
1554                  $id = $input['i']; // http://www.liveleak.com/view?i=123
1555                  break;
1556              case "yahoo":
1557                  if(isset($path[2]))
1558                  {
1559                      $id = $path[2]; // http://xy.screen.yahoo.com/fds/fds-123.html
1560                  }
1561                  else
1562                  {
1563                      $id = $path[1]; // http://xy.screen.yahoo.com/fds-123.html
1564                  }
1565                  // Support for localized portals
1566                  $domain = explode('.', $parsed_url['host']);
1567                  if($domain[0] != 'screen' && preg_match('#^([a-z-]+)$#', $domain[0]))
1568                  {
1569                      $local = "{$domain[0]}.";
1570                  }
1571                  else
1572                  {
1573                      $local = '';
1574                  }
1575                  break;
1576              case "vimeo":
1577                  if(isset($path[3]))
1578                  {
1579                      $id = $path[3]; // http://vimeo.com/fds/fds/fds123
1580                  }
1581                  else
1582                  {
1583                      $id = $path[1]; // http://vimeo.com/fds123
1584                  }
1585                  break;
1586              case "youtube":
1587                  if($fragments[0])
1588                  {
1589                      $id = str_replace('!v=', '', $fragments[0]); // http://www.youtube.com/watch#!v=fds123
1590                  }
1591                  elseif($input['v'])
1592                  {
1593                      $id = $input['v']; // http://www.youtube.com/watch?v=fds123
1594                  }
1595                  else
1596                  {
1597                      $id = $path[1]; // http://www.youtu.be/fds123
1598                  }
1599                  break;
1600              case "twitch":
1601                  if(count($path) >= 3 && $path[1] == 'videos')
1602                  {
1603                      // Direct video embed with URL like: https://www.twitch.tv/videos/179723472
1604                      $id = 'video=v'.$path[2];
1605                  }
1606                  elseif(count($path) >= 4 && $path[2] == 'v')
1607                  {
1608                      // Direct video embed with URL like: https://www.twitch.tv/waypoint/v/179723472
1609                      $id = 'video=v'.$path[3];
1610                  }
1611                  elseif(count($path) >= 2)
1612                  {
1613                      // Channel (livestream) embed with URL like: https://twitch.tv/waypoint
1614                      $id = 'channel='.$path[1];
1615                  }
1616                  break;
1617              default:
1618                  return "[video={$video}]{$url}[/video]";
1619          }
1620  
1621          if(empty($id))
1622          {
1623              return "[video={$video}]{$url}[/video]";
1624          }
1625  
1626          $id = $this->encode_url($id);
1627  
1628          eval("\$video_code = \"".$templates->get("video_{$video}_embed", 1, 0)."\";");
1629          return $video_code;
1630      }
1631  
1632      /**
1633      * Parses video MyCode.
1634      *
1635      * @param array $matches Matches.
1636      * @return string The built-up video code.
1637      */
1638  	function mycode_parse_video_callback($matches)
1639      {
1640          return $this->mycode_parse_video($matches[1], $matches[2]);
1641      }
1642  
1643      /**
1644       * Parses video MyCode disabled.
1645       *
1646       * @param string $url The URL to the video
1647       * @return string
1648       */
1649  	function mycode_parse_video_disabled($url)
1650      {
1651          global $lang;
1652          $url = trim($url);
1653          $url = str_replace("\n", "", $url);
1654          $url = str_replace("\r", "", $url);
1655          $url = str_replace("\'", "'", $url);
1656  
1657          $video = $lang->sprintf($lang->posted_video, $this->mycode_parse_url($url));
1658          return $video;
1659      }
1660  
1661      /**
1662      * Parses video MyCode disabled.
1663      *
1664      * @param array $matches Matches.
1665      * @return string The built-up video code.
1666      */
1667  	function mycode_parse_video_disabled_callback($matches)
1668      {
1669          return $this->mycode_parse_video_disabled($matches[2]);
1670      }
1671  
1672      /**
1673      * Parses URLs automatically.
1674      *
1675      * @param string $message The message to be parsed
1676      * @return string The parsed message.
1677      */
1678  	function mycode_auto_url($message)
1679      {
1680          // Links should end with slashes, numbers, characters and braces but not with dots, commas or question marks
1681          // Don't create links within existing links (handled up-front in the callback function).
1682          $message = preg_replace_callback(
1683              "~
1684                  <a\\s[^>]*>.*?</a>|                                # match and return existing links
1685                  (?<=^|[\s\(\)\[\>])                                # character preceding the link
1686                  (?P<prefix>
1687                      (?:http|https|ftp|news|irc|ircs|irc6)://|    # scheme, or
1688                      (?:www|ftp)\.                                # common subdomain
1689                  )
1690                  (?P<link>
1691                      (?:[^\/\"\s\<\[\.]+\.)*[\w]+                # host
1692                      (?::[0-9]+)?                                # port
1693                      (?:/(?:[^\"\s<\[&]|\[\]|&(?:amp|lt|gt);)*)?    # path, query, fragment; exclude unencoded characters
1694                      [\w\/\)]
1695                  )
1696                  (?![^<>]*?>)                                    # not followed by unopened > (within HTML tags)
1697              ~iusx",
1698              array($this, 'mycode_auto_url_callback'),
1699              $message
1700          );
1701  
1702          return $message;
1703      }
1704  
1705      /**
1706      * Parses URLs automatically.
1707      *
1708      * @param array $matches Matches
1709      * @return string The parsed message.
1710      */
1711  	function mycode_auto_url_callback($matches=array())
1712      {
1713          // If we matched a preexisting link (the part of the regexes in mycode_auto_url() before the pipe symbol),
1714          // then simply return it - we don't create links within existing links.
1715          if(count($matches) == 1)
1716          {
1717              return $matches[0];
1718          }
1719  
1720          $external = '';
1721          // Allow links like http://en.wikipedia.org/wiki/PHP_(disambiguation) but detect mismatching braces
1722          while(my_substr($matches['link'], -1) == ')')
1723          {
1724              if(substr_count($matches['link'], ')') > substr_count($matches['link'], '('))
1725              {
1726                  $matches['link'] = my_substr($matches['link'], 0, -1);
1727                  $external = ')'.$external;
1728              }
1729              else
1730              {
1731                  break;
1732              }
1733  
1734              // Example: ([...] http://en.wikipedia.org/Example_(disambiguation).)
1735              $last_char = my_substr($matches['link'], -1);
1736              while($last_char == '.' || $last_char == ',' || $last_char == '?' || $last_char == '!')
1737              {
1738                  $matches[4] = my_substr($matches['link'], 0, -1);
1739                  $external = $last_char.$external;
1740                  $last_char = my_substr($matches['link'], -1);
1741              }
1742          }
1743          $url = $matches['prefix'].$matches['link'];
1744  
1745          return $this->mycode_parse_url($url, $url).$external;
1746      }
1747  
1748      /**
1749      * Parses list MyCode.
1750      *
1751      * @param string $message The message to be parsed
1752      * @param string $type The list type
1753      * @return string The parsed message.
1754      */
1755  	function mycode_parse_list($message, $type="")
1756      {
1757          // No list elements? That's invalid HTML
1758          if(strpos($message, '[*]') === false)
1759          {
1760              $message = "[*]{$message}";
1761          }
1762  
1763          $message = preg_split("#[^\S\n\r]*\[\*\]\s*#", $message);
1764          if(isset($message[0]) && trim($message[0]) == '')
1765          {
1766              array_shift($message);
1767          }
1768          $message = '<li>'.implode("</li>\n<li>", $message)."</li>\n";
1769  
1770          if($type)
1771          {
1772              $list = "\n<ol type=\"$type\" class=\"mycode_list\">$message</ol>\n";
1773          }
1774          else
1775          {
1776              $list = "<ul class=\"mycode_list\">$message</ul>\n";
1777          }
1778          $list = preg_replace("#<(ol type=\"$type\"|ul)>\s*</li>#", "<$1>", $list);
1779          return $list;
1780      }
1781  
1782      /**
1783      * Parses list MyCode.
1784      *
1785      * @param array $matches Matches
1786      * @return string The parsed message.
1787      */
1788  	function mycode_parse_list_callback($matches)
1789      {
1790          return $this->mycode_parse_list($matches[3], $matches[2]);
1791      }
1792  
1793      /**
1794      * Prepares list MyCode by finding the matching list tags.
1795      *
1796      * @param array $matches Matches
1797      * @return string Temporary replacements.
1798      */
1799  	function mycode_prepare_list($matches)
1800      {
1801          // Append number to identify matching list tags
1802          if(strcasecmp($matches[1], '[/list]') == 0)
1803          {
1804              $count = array_pop($this->list_elements);
1805              if($count !== NULL)
1806              {
1807                  return "[/list&{$count}]";
1808              }
1809              else
1810              {
1811                  // No open list tag...
1812                  return $matches[0];
1813              }
1814          }
1815          else
1816          {
1817              ++$this->list_count;
1818              $this->list_elements[] = $this->list_count;
1819              if(!empty($matches[2]))
1820              {
1821                  return "[list{$matches[2]}&{$this->list_count}]";
1822              }
1823              else
1824              {
1825                  return "[list&{$this->list_count}]";
1826              }
1827          }
1828      }
1829  
1830      /**
1831       * Strips smilies from a string
1832       *
1833       * @param string $message The message for smilies to be stripped from
1834       * @return string The message with smilies stripped
1835       */
1836  	function strip_smilies($message)
1837      {
1838          if($this->smilies_cache == 0)
1839          {
1840              $this->cache_smilies();
1841          }
1842          if(is_array($this->smilies_cache))
1843          {
1844              $message = str_replace($this->smilies_cache, array_keys($this->smilies_cache), $message);
1845          }
1846          return $message;
1847      }
1848  
1849      /**
1850       * Highlights a string
1851       *
1852       * @param string $message The message to be highligted
1853       * @param string $highlight The highlight keywords
1854       * @return string The message with highlight bbcodes
1855       */
1856  	function highlight_message($message, $highlight)
1857      {
1858          if(empty($this->highlight_cache))
1859          {
1860              $this->highlight_cache = build_highlight_array($highlight);
1861          }
1862  
1863          if(is_array($this->highlight_cache) && !empty($this->highlight_cache))
1864          {
1865              $message = preg_replace(array_keys($this->highlight_cache), $this->highlight_cache, $message);
1866          }
1867  
1868          return $message;
1869      }
1870  
1871      /**
1872       * Parses message to plain text equivalents of MyCode.
1873       *
1874       * @param string $message The message to be parsed
1875       * @param array $options
1876       * @return string The parsed message.
1877       */
1878  	function text_parse_message($message, $options=array())
1879      {
1880          global $plugins;
1881  
1882          if(empty($this->options))
1883          {
1884              $this->options = $options;
1885          }
1886          else
1887          {
1888              foreach($options as $option_name => $option_value)
1889              {
1890                  $this->options[$option_name] = $option_value;
1891              }
1892          }
1893  
1894          // Filter bad words if requested.
1895          if(!empty($this->options['filter_badwords']))
1896          {
1897              $message = $this->parse_badwords($message);
1898          }
1899  
1900          // Parse quotes first
1901          $message = $this->mycode_parse_quotes($message, true);
1902  
1903          $message = preg_replace_callback("#\[php\](.*?)\[/php\](\r\n?|\n?)#is", array($this, 'mycode_parse_php_callback'), $message);
1904          $message = preg_replace_callback("#\[code\](.*?)\[/code\](\r\n?|\n?)#is", array($this, 'mycode_parse_code_callback'), $message);
1905  
1906          $find = array(
1907              "#\[(b|u|i|s|url|email|color|img)\](.*?)\[/\\1\]#is",
1908              "#\[(email|color|size|font|align|video)=[^]]*\](.*?)\[/\\1\]#is",
1909              "#\[img=([1-9][0-9]*)x([1-9][0-9]*)\](\r\n?|\n?)(https?://([^<>\"']+?))\[/img\]#is",
1910              "#\[url=((?!javascript)[a-z]+?://)([^\r\n\"<]+?)\](.+?)\[/url\]#si",
1911              "#\[url=((?!javascript:)[^\r\n\"<&\(\)]+?)\](.+?)\[/url\]#si",
1912              "#\[attachment=([0-9]+?)\]#i",
1913          );
1914  
1915          $replace = array(
1916              "$2",
1917              "$2",
1918              "$4",
1919              "$3 ($1$2)",
1920              "$2 ($1)",
1921              "",
1922          );
1923          
1924          $messageBefore = "";
1925          // The counter limit for this "for" loop is for defensive programming purpose only. It protects against infinite repetition. 
1926          for($cnt = 1; $cnt < 20 && $message != $messageBefore; $cnt++)
1927          {
1928              $messageBefore = $message;
1929              $message = preg_replace($find, $replace, $messageBefore);
1930          }
1931  
1932          // Replace "me" code and slaps if we have a username
1933          if(!empty($this->options['me_username']))
1934          {
1935              global $lang;
1936  
1937              $message = preg_replace('#(>|^|\r|\n)/me ([^\r\n<]*)#i', "\\1* {$this->options['me_username']} \\2", $message);
1938              $message = preg_replace('#(>|^|\r|\n)/slap ([^\r\n<]*)#i', "\\1* {$this->options['me_username']} {$lang->slaps} \\2 {$lang->with_trout}", $message);
1939          }
1940  
1941          // Reset list cache
1942          $this->list_elements = array();
1943          $this->list_count = 0;
1944  
1945          // Find all lists
1946          $message = preg_replace_callback("#(\[list(=(a|A|i|I|1))?\]|\[/list\])#si", array($this, 'mycode_prepare_list'), $message);
1947  
1948          // Replace all lists
1949          for($i = $this->list_count; $i > 0; $i--)
1950          {
1951              // Ignores missing end tags
1952              $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);
1953          }
1954  
1955          // Run plugin hooks
1956          $message = $plugins->run_hooks("text_parse_message", $message);
1957  
1958          return $message;
1959      }
1960  
1961      /**
1962       * Replaces certain characters with their entities in a URL.
1963       *
1964       * @param string $url The URL to be escaped.
1965       * @return string The escaped URL.
1966       */
1967  	function encode_url($url)
1968      {
1969          $entities = array('$' => '%24', '&#36;' => '%24', '^' => '%5E', '`' => '%60', '[' => '%5B', ']' => '%5D', '{' => '%7B', '}' => '%7D', '"' => '%22', '<' => '%3C', '>' => '%3E', ' ' => '%20');
1970  
1971          $url = str_replace(array_keys($entities), array_values($entities), $url);
1972  
1973          return $url;
1974      }
1975  
1976      /**
1977       * Determines whether the resulting HTML syntax is acceptable for output,
1978       * according to the parser's validation policy and HTML support.
1979       *
1980       * @param string $source The original MyCode.
1981       * @param string $output The output HTML code.
1982       * @return bool
1983       */
1984  	function output_allowed($source, $output)
1985      {
1986          if($this->output_validation_policy === self::VALIDATION_DISABLE || !empty($this->options['allow_html']))
1987          {
1988              return true;
1989          }
1990          else
1991          {
1992              $output_valid = $this->validate_output($source, $output);
1993  
1994              if($this->output_validation_policy === self::VALIDATION_REPORT_ONLY)
1995              {
1996                  return true;
1997              }
1998              else
1999              {
2000                  return $output_valid === true;
2001              }
2002          }
2003      }
2004  
2005      /**
2006       * Validate HTML syntax and pass errors to the error handler.
2007       *
2008       * @param string $source The original MyCode.
2009       * @param string $output The output HTML code.
2010       * @return bool
2011       */
2012  	function validate_output($source, $output)
2013      {
2014          global $error_handler;
2015  
2016          $ignored_error_codes = array(
2017              // entities may be broken through smilie parsing; cache_smilies() method workaround doesn't cover all entities
2018              'XML_ERR_INVALID_DEC_CHARREF' => 7,
2019              'XML_ERR_INVALID_CHAR' => 9,
2020  
2021              'XML_ERR_UNDECLARED_ENTITY' => 26, // unrecognized HTML entities
2022              'XML_ERR_ATTRIBUTE_WITHOUT_VALUE' => 41,
2023              'XML_ERR_TAG_NAME_MISMATCH' => 76, // the parser may output tags closed in different levels and siblings
2024          );
2025  
2026          libxml_use_internal_errors(true);
2027          @libxml_disable_entity_loader(true);
2028  
2029          simplexml_load_string('<root>'.$output.'</root>', 'SimpleXMLElement', 524288 /* LIBXML_PARSEHUGE */);
2030  
2031          $errors = libxml_get_errors();
2032  
2033          libxml_use_internal_errors(false);
2034  
2035          if(
2036              $errors &&
2037              array_diff(
2038                  array_column($errors, 'code'),
2039                  $ignored_error_codes
2040              )
2041          )
2042          {
2043              $data = array(
2044                  'sourceHtmlEntities' => htmlspecialchars_uni($source),
2045                  'outputHtmlEntities' => htmlspecialchars_uni($output),
2046                  'errors' => $errors,
2047              );
2048              $error_message = "Parser output validation failed.\n";
2049              $error_message .= var_export($data, true);
2050  
2051              $error_handler->error(E_USER_WARNING, $error_message, __FILE__, __LINE__, false);
2052  
2053              return false;
2054          } else {
2055              return true;
2056          }
2057      }
2058  }


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