[ Index ]

PHP Cross Reference of MyBB 1.8.27

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


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