[ Index ]

PHP Cross Reference of MyBB 1.8.37

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


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