Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

message_parser.php

Zuletzt modifiziert: 02.04.2025, 15:01 - Dateigröße: 59.84 KiB


0001  <?php
0002  /**
0003  *
0004  * This file is part of the phpBB Forum Software package.
0005  *
0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007  * @license GNU General Public License, version 2 (GPL-2.0)
0008  *
0009  * For full copyright and license information, please see
0010  * the docs/CREDITS.txt file.
0011  *
0012  */
0013   
0014  /**
0015  * @ignore
0016  */
0017  if (!defined('IN_PHPBB'))
0018  {
0019      exit;
0020  }
0021   
0022  if (!class_exists('bbcode'))
0023  {
0024      // The following lines are for extensions which include message_parser.php
0025      // while $phpbb_root_path and $phpEx are out of the script scope
0026      // which may lead to the 'Undefined variable' and 'failed to open stream' errors
0027      if (!isset($phpbb_root_path))
0028      {
0029          global $phpbb_root_path;
0030      }
0031   
0032      if (!isset($phpEx))
0033      {
0034          global $phpEx;
0035      }
0036   
0037      include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
0038  }
0039   
0040  /**
0041  * BBCODE FIRSTPASS
0042  * BBCODE first pass class (functions for parsing messages for db storage)
0043  */
0044  class bbcode_firstpass extends bbcode
0045  {
0046      var $message = '';
0047      var $warn_msg = array();
0048      var $parsed_items = array();
0049      var $mode;
0050   
0051      /**
0052      * Parse BBCode
0053      */
0054      function parse_bbcode()
0055      {
0056          if (!$this->bbcodes)
0057          {
0058              $this->bbcode_init();
0059          }
0060   
0061          global $user;
0062   
0063          $this->bbcode_bitfield = '';
0064          $bitfield = new bitfield();
0065   
0066          foreach ($this->bbcodes as $bbcode_name => $bbcode_data)
0067          {
0068              if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
0069              {
0070                  foreach ($bbcode_data['regexp'] as $regexp => $replacement)
0071                  {
0072                      if (preg_match($regexp, $this->message))
0073                      {
0074                          $this->warn_msg[] = sprintf($user->lang['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']');
0075                          continue;
0076                      }
0077                  }
0078              }
0079              else
0080              {
0081                  foreach ($bbcode_data['regexp'] as $regexp => $replacement)
0082                  {
0083                      // The pattern gets compiled and cached by the PCRE extension,
0084                      // it should not demand recompilation
0085                      if (preg_match($regexp, $this->message))
0086                      {
0087                          if (is_callable($replacement))
0088                          {
0089                              $this->message = preg_replace_callback($regexp, $replacement, $this->message);
0090                          }
0091                          else
0092                          {
0093                              $this->message = preg_replace($regexp, $replacement, $this->message);
0094                          }
0095                          $bitfield->set($bbcode_data['bbcode_id']);
0096                      }
0097                  }
0098              }
0099          }
0100   
0101          $this->bbcode_bitfield = $bitfield->get_base64();
0102      }
0103   
0104      /**
0105      * Prepare some bbcodes for better parsing
0106      */
0107      function prepare_bbcodes()
0108      {
0109          // Ok, seems like users instead want the no-parsing of urls, smilies, etc. after and before and within quote tags being tagged as "not a bug".
0110          // Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you.
0111   
0112          /* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
0113          if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false)
0114          {
0115              $this->message = str_replace("\r\n", "\n", $this->message);
0116   
0117              // We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
0118              $this->message = preg_replace('#\[quote(=&quot;.*?&quot;)?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message);
0119          }
0120          */
0121   
0122          // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
0123      }
0124   
0125      /**
0126      * Init bbcode data for later parsing
0127      */
0128      function bbcode_init($allow_custom_bbcode = true)
0129      {
0130          global $phpbb_dispatcher;
0131   
0132          static $rowset;
0133   
0134          $bbcode_class = $this;
0135   
0136          // This array holds all bbcode data. BBCodes will be processed in this
0137          // order, so it is important to keep [code] in first position and
0138          // [quote] in second position.
0139          // To parse multiline URL we enable dotall option setting only for URL text
0140          // but not for link itself, thus [url][/url] is not affected.
0141          //
0142          // To perform custom validation in extension, use $this->validate_bbcode_by_extension()
0143          // method which accepts variable number of parameters
0144          $this->bbcodes = array(
0145              'code'            => array('bbcode_id' => BBCODE_ID_CODE,    'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#uis' => function ($match) use($bbcode_class)
0146                  {
0147                      return $bbcode_class->bbcode_code($match[1], $match[2]);
0148                  }
0149              )),
0150              'quote'            => array('bbcode_id' => BBCODE_ID_QUOTE,    'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#uis' => function ($match) use($bbcode_class)
0151                  {
0152                      return $bbcode_class->bbcode_quote($match[0]);
0153                  }
0154              )),
0155              'attachment'    => array('bbcode_id' => BBCODE_ID_ATTACH,    'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#uis' => function ($match) use($bbcode_class)
0156                  {
0157                      return $bbcode_class->bbcode_attachment($match[1], $match[2]);
0158                  }
0159              )),
0160              'b'                => array('bbcode_id' => BBCODE_ID_B,    'regexp' => array('#\[b\](.*?)\[/b\]#uis' => function ($match) use($bbcode_class)
0161                  {
0162                      return $bbcode_class->bbcode_strong($match[1]);
0163                  }
0164              )),
0165              'i'                => array('bbcode_id' => BBCODE_ID_I,    'regexp' => array('#\[i\](.*?)\[/i\]#uis' => function ($match) use($bbcode_class)
0166                  {
0167                      return $bbcode_class->bbcode_italic($match[1]);
0168                  }
0169              )),
0170              'url'            => array('bbcode_id' => BBCODE_ID_URL,    'regexp' => array('#\[url(=(.*))?\](?(1)((?s).*(?-s))|(.*))\[/url\]#uiU' => function ($match) use($bbcode_class)
0171                  {
0172                      return $bbcode_class->validate_url($match[2], ($match[3]) ? $match[3] : $match[4]);
0173                  }
0174              )),
0175              'img'            => array('bbcode_id' => BBCODE_ID_IMG,    'regexp' => array('#\[img\](.*)\[/img\]#uiU' => function ($match) use($bbcode_class)
0176                  {
0177                      return $bbcode_class->bbcode_img($match[1]);
0178                  }
0179              )),
0180              'size'            => array('bbcode_id' => BBCODE_ID_SIZE,    'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#uis' => function ($match) use($bbcode_class)
0181                  {
0182                      return $bbcode_class->bbcode_size($match[1], $match[2]);
0183                  }
0184              )),
0185              'color'            => array('bbcode_id' => BBCODE_ID_COLOR,    'regexp' => array('!\[color=(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!uis' => function ($match) use($bbcode_class)
0186                  {
0187                      return $bbcode_class->bbcode_color($match[1], $match[2]);
0188                  }
0189              )),
0190              'u'                => array('bbcode_id' => BBCODE_ID_U,    'regexp' => array('#\[u\](.*?)\[/u\]#uis' => function ($match) use($bbcode_class)
0191                  {
0192                      return $bbcode_class->bbcode_underline($match[1]);
0193                  }
0194              )),
0195              'list'            => array('bbcode_id' => BBCODE_ID_LIST,    'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#uis' => function ($match) use($bbcode_class)
0196                  {
0197                      return $bbcode_class->bbcode_parse_list($match[0]);
0198                  }
0199              )),
0200              'email'            => array('bbcode_id' => BBCODE_ID_EMAIL,    'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#uis' => function ($match) use($bbcode_class)
0201                  {
0202                      return $bbcode_class->validate_email($match[1], $match[2]);
0203                  }
0204              )),
0205              'flash'            => array('bbcode_id' => BBCODE_ID_FLASH,    'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ui' => function ($match) use($bbcode_class)
0206                  {
0207                      return $bbcode_class->bbcode_flash($match[1], $match[2], $match[3]);
0208                  }
0209              ))
0210          );
0211   
0212          // Zero the parsed items array
0213          $this->parsed_items = array();
0214   
0215          foreach ($this->bbcodes as $tag => $bbcode_data)
0216          {
0217              $this->parsed_items[$tag] = 0;
0218          }
0219   
0220          if (!$allow_custom_bbcode)
0221          {
0222              return;
0223          }
0224   
0225          if (!is_array($rowset))
0226          {
0227              global $db;
0228              $rowset = array();
0229   
0230              $sql = 'SELECT *
0231                  FROM ' . BBCODES_TABLE;
0232              $result = $db->sql_query($sql);
0233   
0234              while ($row = $db->sql_fetchrow($result))
0235              {
0236                  $rowset[] = $row;
0237              }
0238              $db->sql_freeresult($result);
0239          }
0240   
0241          foreach ($rowset as $row)
0242          {
0243              $this->bbcodes[$row['bbcode_tag']] = array(
0244                  'bbcode_id'    => (int) $row['bbcode_id'],
0245                  'regexp'    => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
0246              );
0247          }
0248   
0249          $bbcodes = $this->bbcodes;
0250   
0251          /**
0252          * Event to modify the bbcode data for later parsing
0253          *
0254          * @event core.modify_bbcode_init
0255          * @var array    bbcodes        Array of bbcode data for use in parsing
0256          * @var array    rowset        Array of bbcode data from the database
0257          * @since 3.1.0-a3
0258          */
0259          $vars = array('bbcodes', 'rowset');
0260          extract($phpbb_dispatcher->trigger_event('core.modify_bbcode_init', compact($vars)));
0261   
0262          $this->bbcodes = $bbcodes;
0263      }
0264   
0265      /**
0266      * Making some pre-checks for bbcodes as well as increasing the number of parsed items
0267      */
0268      function check_bbcode($bbcode, &$in)
0269      {
0270          // when using the /e modifier, preg_replace slashes double-quotes but does not
0271          // seem to slash anything else
0272          $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
0273   
0274          // Trimming here to make sure no empty bbcodes are parsed accidently
0275          if (trim($in) == '')
0276          {
0277              return false;
0278          }
0279   
0280          $this->parsed_items[$bbcode]++;
0281   
0282          return true;
0283      }
0284   
0285      /**
0286      * Transform some characters in valid bbcodes
0287      */
0288      function bbcode_specialchars($text)
0289      {
0290          $str_from = array('<', '>', '[', ']', '.', ':');
0291          $str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');
0292   
0293          return str_replace($str_from, $str_to, $text);
0294      }
0295   
0296      /**
0297      * Parse size tag
0298      */
0299      function bbcode_size($stx, $in)
0300      {
0301          global $user, $config;
0302   
0303          if (!$this->check_bbcode('size', $in))
0304          {
0305              return $in;
0306          }
0307   
0308          if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
0309          {
0310              $this->warn_msg[] = $user->lang('MAX_FONT_SIZE_EXCEEDED', (int) $config['max_' . $this->mode . '_font_size']);
0311   
0312              return '[size=' . $stx . ']' . $in . '[/size]';
0313          }
0314   
0315          // Do not allow size=0
0316          if ($stx <= 0)
0317          {
0318              return '[size=' . $stx . ']' . $in . '[/size]';
0319          }
0320   
0321          return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
0322      }
0323   
0324      /**
0325      * Parse color tag
0326      */
0327      function bbcode_color($stx, $in)
0328      {
0329          if (!$this->check_bbcode('color', $in))
0330          {
0331              return $in;
0332          }
0333   
0334          return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
0335      }
0336   
0337      /**
0338      * Parse u tag
0339      */
0340      function bbcode_underline($in)
0341      {
0342          if (!$this->check_bbcode('u', $in))
0343          {
0344              return $in;
0345          }
0346   
0347          return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
0348      }
0349   
0350      /**
0351      * Parse b tag
0352      */
0353      function bbcode_strong($in)
0354      {
0355          if (!$this->check_bbcode('b', $in))
0356          {
0357              return $in;
0358          }
0359   
0360          return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
0361      }
0362   
0363      /**
0364      * Parse i tag
0365      */
0366      function bbcode_italic($in)
0367      {
0368          if (!$this->check_bbcode('i', $in))
0369          {
0370              return $in;
0371          }
0372   
0373          return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
0374      }
0375   
0376      /**
0377      * Parse img tag
0378      */
0379      function bbcode_img($in)
0380      {
0381          global $user, $config;
0382   
0383          if (!$this->check_bbcode('img', $in))
0384          {
0385              return $in;
0386          }
0387   
0388          $in = trim($in);
0389          $error = false;
0390   
0391          $in = str_replace(' ', '%20', $in);
0392   
0393          // Checking urls
0394          if (!preg_match('#^' . get_preg_expression('url_http') . '$#iu', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
0395          {
0396              return '[img]' . $in . '[/img]';
0397          }
0398   
0399          // Try to cope with a common user error... not specifying a protocol but only a subdomain
0400          if (!preg_match('#^[a-z0-9]+://#i', $in))
0401          {
0402              $in = 'http://' . $in;
0403          }
0404   
0405          if ($error || $this->path_in_domain($in))
0406          {
0407              return '[img]' . $in . '[/img]';
0408          }
0409   
0410          return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
0411      }
0412   
0413      /**
0414      * Parse flash tag
0415      */
0416      function bbcode_flash($width, $height, $in)
0417      {
0418          global $user, $config;
0419   
0420          if (!$this->check_bbcode('flash', $in))
0421          {
0422              return $in;
0423          }
0424   
0425          $in = trim($in);
0426          $error = false;
0427   
0428          // Do not allow 0-sizes generally being entered
0429          if ($width <= 0 || $height <= 0)
0430          {
0431              return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
0432          }
0433   
0434          $in = str_replace(' ', '%20', $in);
0435   
0436          // Make sure $in is a URL.
0437          if (!preg_match('#^' . get_preg_expression('url') . '$#iu', $in) &&
0438              !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
0439          {
0440              return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
0441          }
0442   
0443          // Apply the same size checks on flash files as on images
0444          if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
0445          {
0446              if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)
0447              {
0448                  $error = true;
0449                  $this->warn_msg[] = $user->lang('MAX_FLASH_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);
0450              }
0451   
0452              if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)
0453              {
0454                  $error = true;
0455                  $this->warn_msg[] = $user->lang('MAX_FLASH_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);
0456              }
0457          }
0458   
0459          if ($error || $this->path_in_domain($in))
0460          {
0461              return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
0462          }
0463   
0464          return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
0465      }
0466   
0467      /**
0468      * Parse inline attachments [ia]
0469      */
0470      function bbcode_attachment($stx, $in)
0471      {
0472          if (!$this->check_bbcode('attachment', $in))
0473          {
0474              return $in;
0475          }
0476   
0477          return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
0478      }
0479   
0480      /**
0481      * Parse code text from code tag
0482      * @access private
0483      */
0484      function bbcode_parse_code($stx, &$code)
0485      {
0486          switch (strtolower($stx))
0487          {
0488              case 'php':
0489   
0490                  $remove_tags = false;
0491   
0492                  $str_from = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;', '&#058;');
0493                  $str_to = array('<', '>', '[', ']', '.', ':', ':');
0494                  $code = str_replace($str_from, $str_to, $code);
0495   
0496                  if (!preg_match('/\<\?.*?\?\>/is', $code))
0497                  {
0498                      $remove_tags = true;
0499                      $code = "<?php $code ?>";
0500                  }
0501   
0502                  $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
0503                  foreach ($conf as $ini_var)
0504                  {
0505                      @ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
0506                  }
0507   
0508                  // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
0509                  $code = html_entity_decode($code, ENT_COMPAT);
0510                  $code = highlight_string($code, true);
0511   
0512                  $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
0513                  $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');
0514   
0515                  if ($remove_tags)
0516                  {
0517                      $str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
0518                      $str_to[] = '';
0519                      $str_from[] = '<span class="syntaxdefault">&lt;?php&nbsp;';
0520                      $str_to[] = '<span class="syntaxdefault">';
0521                  }
0522   
0523                  $code = str_replace($str_from, $str_to, $code);
0524                  $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
0525   
0526                  if ($remove_tags)
0527                  {
0528                      $code = preg_replace('#(<span class="[a-z]+">)?\?&gt;(</span>)#', '$1&nbsp;$2', $code);
0529                  }
0530   
0531                  $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
0532                  $code = preg_replace('#(?:\s++|&nbsp;)*+</span>$#u', '</span>', $code);
0533   
0534                  // remove newline at the end
0535                  if (!empty($code) && substr($code, -1) == "\n")
0536                  {
0537                      $code = substr($code, 0, -1);
0538                  }
0539   
0540                  return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
0541              break;
0542   
0543              default:
0544                  return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
0545              break;
0546          }
0547      }
0548   
0549      /**
0550      * Parse code tag
0551      * Expects the argument to start right after the opening [code] tag and to end with [/code]
0552      */
0553      function bbcode_code($stx, $in)
0554      {
0555          if (!$this->check_bbcode('code', $in))
0556          {
0557              return $in;
0558          }
0559   
0560          // We remove the hardcoded elements from the code block here because it is not used in code blocks
0561          // Having it here saves us one preg_replace per message containing [code] blocks
0562          // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
0563          $htm_match = get_preg_expression('bbcode_htm');
0564          unset($htm_match[4], $htm_match[5]);
0565          $htm_replace = array('\1', '\1', '\2', '\1');
0566   
0567          $out = $code_block = '';
0568          $open = 1;
0569   
0570          while ($in)
0571          {
0572              // Determine position and tag length of next code block
0573              preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
0574              $pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
0575              $tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;
0576   
0577              // Determine position of ending code tag
0578              $pos2 = stripos($in, '[/code]');
0579   
0580              // Which is the next block, ending code or code block
0581              if ($pos !== false && $pos < $pos2)
0582              {
0583                  // Open new block
0584                  if (!$open)
0585                  {
0586                      $out .= substr($in, 0, $pos);
0587                      $in = substr($in, $pos);
0588                      $stx = (isset($buffer[3])) ? $buffer[3] : '';
0589                      $code_block = '';
0590                  }
0591                  else
0592                  {
0593                      // Already opened block, just append to the current block
0594                      $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
0595                      $in = substr($in, $pos);
0596                  }
0597   
0598                  $in = substr($in, $tag_length);
0599                  $open++;
0600              }
0601              else
0602              {
0603                  // Close the block
0604                  if ($open == 1)
0605                  {
0606                      $code_block .= substr($in, 0, $pos2);
0607                      $code_block = preg_replace($htm_match, $htm_replace, $code_block);
0608   
0609                      // Parse this code block
0610                      $out .= $this->bbcode_parse_code($stx, $code_block);
0611                      $code_block = '';
0612                      $open--;
0613                  }
0614                  else if ($open)
0615                  {
0616                      // Close one open tag... add to the current code block
0617                      $code_block .= substr($in, 0, $pos2 + 7);
0618                      $open--;
0619                  }
0620                  else
0621                  {
0622                      // end code without opening code... will be always outside code block
0623                      $out .= substr($in, 0, $pos2 + 7);
0624                  }
0625   
0626                  $in = substr($in, $pos2 + 7);
0627              }
0628          }
0629   
0630          // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
0631          if ($code_block)
0632          {
0633              $code_block = substr($code_block, 0, -7);
0634              $code_block = preg_replace($htm_match, $htm_replace, $code_block);
0635   
0636              $out .= $this->bbcode_parse_code($stx, $code_block);
0637          }
0638   
0639          return $out;
0640      }
0641   
0642      /**
0643      * Parse list bbcode
0644      * Expects the argument to start with a tag
0645      */
0646      function bbcode_parse_list($in)
0647      {
0648          if (!$this->check_bbcode('list', $in))
0649          {
0650              return $in;
0651          }
0652   
0653          // $tok holds characters to stop at. Since the string starts with a '[' we'll get everything up to the first ']' which should be the opening [list] tag
0654          $tok = ']';
0655          $out = '[';
0656   
0657          // First character is [
0658          $in = substr($in, 1);
0659          $list_end_tags = $item_end_tags = array();
0660   
0661          do
0662          {
0663              $pos = strlen($in);
0664   
0665              for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
0666              {
0667                  $tmp_pos = strpos($in, $tok[$i]);
0668   
0669                  if ($tmp_pos !== false && $tmp_pos < $pos)
0670                  {
0671                      $pos = $tmp_pos;
0672                  }
0673              }
0674   
0675              $buffer = substr($in, 0, $pos);
0676              $tok = $in[$pos];
0677   
0678              $in = substr($in, $pos + 1);
0679   
0680              if ($tok == ']')
0681              {
0682                  // if $tok is ']' the buffer holds a tag
0683                  if (strtolower($buffer) == '/list' && count($list_end_tags))
0684                  {
0685                      // valid [/list] tag, check nesting so that we don't hit false positives
0686                      if (count($item_end_tags) && count($item_end_tags) >= count($list_end_tags))
0687                      {
0688                          // current li tag has not been closed
0689                          $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
0690                      }
0691   
0692                      $out .= array_pop($list_end_tags) . ']';
0693                      $tok = '[';
0694                  }
0695                  else if (preg_match('#^list(=[0-9a-z]+)?$#i', $buffer, $m))
0696                  {
0697                      // sub-list, add a closing tag
0698                      if (empty($m[1]) || preg_match('/^=(?:disc|square|circle)$/i', $m[1]))
0699                      {
0700                          array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
0701                      }
0702                      else
0703                      {
0704                          array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
0705                      }
0706                      $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
0707                      $tok = '[';
0708                  }
0709                  else
0710                  {
0711                      if (($buffer == '*' || substr($buffer, -2) == '[*') && count($list_end_tags))
0712                      {
0713                          // the buffer holds a bullet tag and we have a [list] tag open
0714                          if (count($item_end_tags) >= count($list_end_tags))
0715                          {
0716                              if (substr($buffer, -2) == '[*')
0717                              {
0718                                  $out .= substr($buffer, 0, -2) . '[';
0719                              }
0720                              // current li tag has not been closed
0721                              if (preg_match('/\n\[$/', $out, $m))
0722                              {
0723                                  $out = preg_replace('/\n\[$/', '[', $out);
0724                                  $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
0725                              }
0726                              else
0727                              {
0728                                  $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
0729                              }
0730                          }
0731                          else
0732                          {
0733                              $buffer = '*:' . $this->bbcode_uid;
0734                          }
0735   
0736                          $item_end_tags[] = '/*:m:' . $this->bbcode_uid;
0737                      }
0738                      else if ($buffer == '/*')
0739                      {
0740                          array_pop($item_end_tags);
0741                          $buffer = '/*:' . $this->bbcode_uid;
0742                      }
0743   
0744                      $out .= $buffer . $tok;
0745                      $tok = '[]';
0746                  }
0747              }
0748              else
0749              {
0750                  // Not within a tag, just add buffer to the return string
0751                  $out .= $buffer . $tok;
0752                  $tok = ($tok == '[') ? ']' : '[]';
0753              }
0754          }
0755          while ($in);
0756   
0757          // do we have some tags open? close them now
0758          if (count($item_end_tags))
0759          {
0760              $out .= '[' . implode('][', $item_end_tags) . ']';
0761          }
0762          if (count($list_end_tags))
0763          {
0764              $out .= '[' . implode('][', $list_end_tags) . ']';
0765          }
0766   
0767          return $out;
0768      }
0769   
0770      /**
0771      * Parse quote bbcode
0772      * Expects the argument to start with a tag
0773      */
0774      function bbcode_quote($in)
0775      {
0776          $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
0777   
0778          if (!$in)
0779          {
0780              return '';
0781          }
0782   
0783          // To let the parser not catch tokens within quote_username quotes we encode them before we start this...
0784          $in = preg_replace_callback('#quote=&quot;(.*?)&quot;\]#i', function ($match) {
0785              return 'quote=&quot;' . str_replace(array('[', ']', '\\\"'), array('&#91;', '&#93;', '\"'), $match[1]) . '&quot;]';
0786          }, $in);
0787   
0788          $tok = ']';
0789          $out = '[';
0790   
0791          $in = substr($in, 1);
0792          $close_tags = $error_ary = array();
0793          $buffer = '';
0794   
0795          do
0796          {
0797              $pos = strlen($in);
0798              for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
0799              {
0800                  $tmp_pos = strpos($in, $tok[$i]);
0801                  if ($tmp_pos !== false && $tmp_pos < $pos)
0802                  {
0803                      $pos = $tmp_pos;
0804                  }
0805              }
0806   
0807              $buffer .= substr($in, 0, $pos);
0808              $tok = $in[$pos];
0809              $in = substr($in, $pos + 1);
0810   
0811              if ($tok == ']')
0812              {
0813                  if (strtolower($buffer) == '/quote' && count($close_tags) && substr($out, -1, 1) == '[')
0814                  {
0815                      // we have found a closing tag
0816                      $out .= array_pop($close_tags) . ']';
0817                      $tok = '[';
0818                      $buffer = '';
0819   
0820                      /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
0821                      * Do not try to think for the user. :/ Do not parse urls/smilies if there is no space - is the same as with other bbcodes too.
0822                      * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
0823                      if (!$in || $in[0] !== ' ')
0824                      {
0825                          $out .= ' ';
0826                      }*/
0827                  }
0828                  else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
0829                  {
0830                      $this->parsed_items['quote']++;
0831                      array_push($close_tags, '/quote:' . $this->bbcode_uid);
0832   
0833                      if (isset($m[1]) && $m[1])
0834                      {
0835                          $username = str_replace(array('&#91;', '&#93;'), array('[', ']'), $m[1]);
0836                          $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $username);
0837   
0838                          $end_tags = array();
0839                          $error = false;
0840   
0841                          preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
0842                          foreach ($tags[1] as $tag)
0843                          {
0844                              if ($tag[0] != '/')
0845                              {
0846                                  $end_tags[] = '/' . $tag;
0847                              }
0848                              else
0849                              {
0850                                  $end_tag = array_pop($end_tags);
0851                                  $error = ($end_tag != $tag) ? true : false;
0852                              }
0853                          }
0854   
0855                          if ($error)
0856                          {
0857                              $username = $m[1];
0858                          }
0859   
0860                          $out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
0861                      }
0862                      else
0863                      {
0864                          $out .= 'quote:' . $this->bbcode_uid . ']';
0865                      }
0866   
0867                      $tok = '[';
0868                      $buffer = '';
0869                  }
0870                  else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
0871                  {
0872                      // the buffer holds an invalid opening tag
0873                      $buffer .= ']';
0874                  }
0875                  else
0876                  {
0877                      $out .= $buffer . $tok;
0878                      $tok = '[]';
0879                      $buffer = '';
0880                  }
0881              }
0882              else
0883              {
0884  /**
0885  *                Old quote code working fine, but having errors listed in bug #3572
0886  *
0887  *                $out .= $buffer . $tok;
0888  *                $tok = ($tok == '[') ? ']' : '[]';
0889  *                $buffer = '';
0890  */
0891   
0892                  $out .= $buffer . $tok;
0893   
0894                  if ($tok == '[')
0895                  {
0896                      // Search the text for the next tok... if an ending quote comes first, then change tok to []
0897                      $pos1 = stripos($in, '[/quote');
0898                      // If the token ] comes first, we change it to ]
0899                      $pos2 = strpos($in, ']');
0900                      // If the token [ comes first, we change it to [
0901                      $pos3 = strpos($in, '[');
0902   
0903                      if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
0904                      {
0905                          $tok = '[]';
0906                      }
0907                      else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
0908                      {
0909                          $tok = '[';
0910                      }
0911                      else
0912                      {
0913                          $tok = ']';
0914                      }
0915                  }
0916                  else
0917                  {
0918                      $tok = '[]';
0919                  }
0920                  $buffer = '';
0921              }
0922          }
0923          while ($in);
0924   
0925          $out .= $buffer;
0926   
0927          if (count($close_tags))
0928          {
0929              $out .= '[' . implode('][', $close_tags) . ']';
0930          }
0931   
0932          foreach ($error_ary as $error_msg)
0933          {
0934              $this->warn_msg[] = $error_msg;
0935          }
0936   
0937          return $out;
0938      }
0939   
0940      /**
0941      * Validate email
0942      */
0943      function validate_email($var1, $var2)
0944      {
0945          $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
0946          $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
0947   
0948          $txt = $var2;
0949          $email = ($var1) ? $var1 : $var2;
0950   
0951          $validated = true;
0952   
0953          if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
0954          {
0955              $validated = false;
0956          }
0957   
0958          if (!$validated)
0959          {
0960              return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
0961          }
0962   
0963          $this->parsed_items['email']++;
0964   
0965          if ($var1)
0966          {
0967              $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
0968          }
0969          else
0970          {
0971              $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
0972          }
0973   
0974          return $retval;
0975      }
0976   
0977      /**
0978      * Validate url
0979      *
0980      * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
0981      * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
0982      */
0983      function validate_url($var1, $var2)
0984      {
0985          $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
0986          $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
0987   
0988          $url = ($var1) ? $var1 : $var2;
0989   
0990          if ($var1 && !$var2)
0991          {
0992              $var2 = $var1;
0993          }
0994   
0995          if (!$url)
0996          {
0997              return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
0998          }
0999   
1000          $valid = false;
1001   
1002          $url = str_replace(' ', '%20', $url);
1003   
1004          // Checking urls
1005          if (preg_match('#^' . get_preg_expression('url') . '$#iu', $url) ||
1006              preg_match('#^' . get_preg_expression('www_url') . '$#iu', $url) ||
1007              preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#iu', $url))
1008          {
1009              $valid = true;
1010          }
1011   
1012          if ($valid)
1013          {
1014              $this->parsed_items['url']++;
1015   
1016              // if there is no scheme, then add http schema
1017              if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
1018              {
1019                  $url = 'http://' . $url;
1020              }
1021   
1022              // Is this a link to somewhere inside this board? If so then remove the session id from the url
1023              if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
1024              {
1025                  $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}&amp;/', '\1', $url);
1026                  $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}$/', '', $url);
1027                  $url = append_sid($url);
1028              }
1029   
1030              return ($var1) ? '[url=' . $this->bbcode_specialchars($url) . ':' . $this->bbcode_uid . ']' . $var2 . '[/url:' . $this->bbcode_uid . ']' : '[url:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($url) . '[/url:' . $this->bbcode_uid . ']';
1031          }
1032   
1033          return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
1034      }
1035   
1036      /**
1037      * Check if url is pointing to this domain/script_path/php-file
1038      *
1039      * @param string $url the url to check
1040      * @return true if the url is pointing to this domain/script_path/php-file, false if not
1041      *
1042      * @access private
1043      */
1044      function path_in_domain($url)
1045      {
1046          global $config, $phpEx, $user;
1047   
1048          if ($config['force_server_vars'])
1049          {
1050              $check_path = !empty($config['script_path']) ? $config['script_path'] : '/';
1051          }
1052          else
1053          {
1054              $check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
1055          }
1056   
1057          // Is the user trying to link to a php file in this domain and script path?
1058          if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
1059          {
1060              $server_name = $user->host;
1061   
1062              // Forcing server vars is the only way to specify/override the protocol
1063              if ($config['force_server_vars'] || !$server_name)
1064              {
1065                  $server_name = $config['server_name'];
1066              }
1067   
1068              // Check again in correct order...
1069              $pos_ext = strpos($url, ".{$phpEx}");
1070              $pos_path = strpos($url, $check_path);
1071              $pos_domain = strpos($url, $server_name);
1072   
1073              if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
1074              {
1075                  // Ok, actually we allow linking to some files (this may be able to be extended in some way later...)
1076                  if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0)
1077                  {
1078                      return false;
1079                  }
1080   
1081                  return true;
1082              }
1083          }
1084   
1085          return false;
1086      }
1087  }
1088   
1089  /**
1090  * Main message parser for posting, pm, etc. takes raw message
1091  * and parses it for attachments, bbcode and smilies
1092  */
1093  class parse_message extends bbcode_firstpass
1094  {
1095      var $attachment_data = array();
1096      var $filename_data = array();
1097   
1098      // Helps ironing out user error
1099      var $message_status = '';
1100   
1101      var $allow_img_bbcode = true;
1102      var $allow_flash_bbcode = true;
1103      var $allow_quote_bbcode = true;
1104      var $allow_url_bbcode = true;
1105   
1106      /**
1107      * The plupload object used for dealing with attachments
1108      * @var \phpbb\plupload\plupload
1109      */
1110      protected $plupload;
1111   
1112      /**
1113      * Init - give message here or manually
1114      */
1115      function __construct($message = '')
1116      {
1117          // Init BBCode UID
1118          $this->bbcode_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN);
1119          $this->message = $message;
1120      }
1121   
1122      /**
1123      * Parse Message
1124      */
1125      function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post')
1126      {
1127          global $config, $user, $phpbb_dispatcher, $phpbb_container;
1128   
1129          $this->mode = $mode;
1130   
1131          foreach (array('chars', 'smilies', 'urls', 'font_size', 'img_height', 'img_width') as $key)
1132          {
1133              if (!isset($config['max_' . $mode . '_' . $key]))
1134              {
1135                  $config['max_' . $mode . '_' . $key] = 0;
1136              }
1137          }
1138   
1139          $this->allow_img_bbcode = $allow_img_bbcode;
1140          $this->allow_flash_bbcode = $allow_flash_bbcode;
1141          $this->allow_quote_bbcode = $allow_quote_bbcode;
1142          $this->allow_url_bbcode = $allow_url_bbcode;
1143   
1144          // If false, then $this->message won't be altered, the text will be returned instead.
1145          if (!$update_this_message)
1146          {
1147              $tmp_message = $this->message;
1148              $return_message = &$this->message;
1149          }
1150   
1151          if ($this->message_status == 'display')
1152          {
1153              $this->decode_message();
1154          }
1155   
1156          // Store message length...
1157          $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(?:=\S+?)?\]#ius', '', $this->message));
1158   
1159          // Maximum message length check. 0 disables this check completely.
1160          if ((int) $config['max_' . $mode . '_chars'] > 0 && $message_length > (int) $config['max_' . $mode . '_chars'])
1161          {
1162              $this->warn_msg[] = $user->lang('CHARS_' . strtoupper($mode) . '_CONTAINS', $message_length) . '<br />' . $user->lang('TOO_MANY_CHARS_LIMIT', (int) $config['max_' . $mode . '_chars']);
1163              return (!$update_this_message) ? $return_message : $this->warn_msg;
1164          }
1165   
1166          // Minimum message length check for post only
1167          if ($mode === 'post')
1168          {
1169              if (!$message_length || $message_length < (int) $config['min_post_chars'])
1170              {
1171                  $this->warn_msg[] = (!$message_length) ? $user->lang['TOO_FEW_CHARS'] : ($user->lang('CHARS_POST_CONTAINS', $message_length) . '<br />' . $user->lang('TOO_FEW_CHARS_LIMIT', (int) $config['min_post_chars']));
1172                  return (!$update_this_message) ? $return_message : $this->warn_msg;
1173              }
1174          }
1175   
1176          /**
1177          * This event can be used for additional message checks/cleanup before parsing
1178          *
1179          * @event core.message_parser_check_message
1180          * @var bool        allow_bbcode            Do we allow BBCodes
1181          * @var bool        allow_magic_url            Do we allow magic urls
1182          * @var bool        allow_smilies            Do we allow smilies
1183          * @var bool        allow_img_bbcode        Do we allow image BBCode
1184          * @var bool        allow_flash_bbcode        Do we allow flash BBCode
1185          * @var bool        allow_quote_bbcode        Do we allow quote BBCode
1186          * @var bool        allow_url_bbcode        Do we allow url BBCode
1187          * @var bool        update_this_message        Do we alter the parsed message
1188          * @var string    mode                    Posting mode
1189          * @var string    message                    The message text to parse
1190          * @var string    bbcode_bitfield            The bbcode_bitfield before parsing
1191          * @var string    bbcode_uid                The bbcode_uid before parsing
1192          * @var bool        return                    Do we return after the event is triggered if $warn_msg is not empty
1193          * @var array    warn_msg                Array of the warning messages
1194          * @since 3.1.2-RC1
1195          * @changed 3.1.3-RC1 Added vars $bbcode_bitfield and $bbcode_uid
1196          */
1197          $message = $this->message;
1198          $warn_msg = $this->warn_msg;
1199          $return = false;
1200          $bbcode_bitfield = $this->bbcode_bitfield;
1201          $bbcode_uid = $this->bbcode_uid;
1202          $vars = array(
1203              'allow_bbcode',
1204              'allow_magic_url',
1205              'allow_smilies',
1206              'allow_img_bbcode',
1207              'allow_flash_bbcode',
1208              'allow_quote_bbcode',
1209              'allow_url_bbcode',
1210              'update_this_message',
1211              'mode',
1212              'message',
1213              'bbcode_bitfield',
1214              'bbcode_uid',
1215              'return',
1216              'warn_msg',
1217          );
1218          extract($phpbb_dispatcher->trigger_event('core.message_parser_check_message', compact($vars)));
1219          $this->message = $message;
1220          $this->warn_msg = $warn_msg;
1221          $this->bbcode_bitfield = $bbcode_bitfield;
1222          $this->bbcode_uid = $bbcode_uid;
1223          if ($return && !empty($this->warn_msg))
1224          {
1225              return (!$update_this_message) ? $return_message : $this->warn_msg;
1226          }
1227   
1228          // Get the parser
1229          $parser = $phpbb_container->get('text_formatter.parser');
1230   
1231          // Set the parser's options
1232          ($allow_bbcode)       ? $parser->enable_bbcodes()       : $parser->disable_bbcodes();
1233          ($allow_magic_url)    ? $parser->enable_magic_url()     : $parser->disable_magic_url();
1234          ($allow_smilies)      ? $parser->enable_smilies()       : $parser->disable_smilies();
1235          ($allow_img_bbcode)   ? $parser->enable_bbcode('img')   : $parser->disable_bbcode('img');
1236          ($allow_flash_bbcode) ? $parser->enable_bbcode('flash') : $parser->disable_bbcode('flash');
1237          ($allow_quote_bbcode) ? $parser->enable_bbcode('quote') : $parser->disable_bbcode('quote');
1238          ($allow_url_bbcode)   ? $parser->enable_bbcode('url')   : $parser->disable_bbcode('url');
1239   
1240          // Set some config values
1241          $parser->set_vars(array(
1242              'max_font_size'  => $config['max_' . $this->mode . '_font_size'],
1243              'max_img_height' => $config['max_' . $this->mode . '_img_height'],
1244              'max_img_width'  => $config['max_' . $this->mode . '_img_width'],
1245              'max_smilies'    => $config['max_' . $this->mode . '_smilies'],
1246              'max_urls'       => $config['max_' . $this->mode . '_urls']
1247          ));
1248   
1249          // Parse this message
1250          $this->message = $parser->parse(html_entity_decode($this->message, ENT_QUOTES));
1251   
1252          // Remove quotes that are nested too deep
1253          if ($config['max_quote_depth'] > 0)
1254          {
1255              $this->remove_nested_quotes($config['max_quote_depth']);
1256          }
1257   
1258          // Check for "empty" message. We do not check here for maximum length, because bbcode, smilies, etc. can add to the length.
1259          // The maximum length check happened before any parsings.
1260          if ($mode === 'post' && utf8_clean_string($this->message) === '')
1261          {
1262              $this->warn_msg[] = $user->lang['TOO_FEW_CHARS'];
1263              return (!$update_this_message) ? $return_message : $this->warn_msg;
1264          }
1265   
1266          // Remove quotes that are nested too deep
1267          if ($config['max_quote_depth'] > 0)
1268          {
1269              $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode(
1270                  $this->message,
1271                  'quote',
1272                  $config['max_quote_depth']
1273              );
1274          }
1275   
1276          // Check for errors
1277          $errors = $parser->get_errors();
1278          if ($errors)
1279          {
1280              foreach ($errors as $i => $args)
1281              {
1282                  // Translate each error with $user->lang()
1283                  $errors[$i] = call_user_func_array(array($user, 'lang'), $args);
1284              }
1285              $this->warn_msg = array_merge($this->warn_msg, $errors);
1286   
1287              return (!$update_this_message) ? $return_message : $this->warn_msg;
1288          }
1289   
1290          if (!$update_this_message)
1291          {
1292              unset($this->message);
1293              $this->message = $tmp_message;
1294              return $return_message;
1295          }
1296   
1297          $this->message_status = 'parsed';
1298          return false;
1299      }
1300   
1301      /**
1302      * Formatting text for display
1303      */
1304      function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
1305      {
1306          global $phpbb_container, $phpbb_dispatcher;
1307   
1308          // If false, then the parsed message get returned but internal message not processed.
1309          if (!$update_this_message)
1310          {
1311              $tmp_message = $this->message;
1312              $return_message = &$this->message;
1313          }
1314   
1315          $text = $this->message;
1316          $uid = $this->bbcode_uid;
1317   
1318          /**
1319          * Event to modify the text before it is parsed
1320          *
1321          * @event core.modify_format_display_text_before
1322          * @var string    text                The message text to parse
1323          * @var string    uid                    The bbcode uid
1324          * @var bool        allow_bbcode        Do we allow bbcodes
1325          * @var bool        allow_magic_url        Do we allow magic urls
1326          * @var bool        allow_smilies        Do we allow smilies
1327          * @var bool        update_this_message    Do we update the internal message
1328          *                                    with the parsed result
1329          * @since 3.1.6-RC1
1330          */
1331          $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
1332          extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_before', compact($vars)));
1333   
1334          $this->message = $text;
1335          $this->bbcode_uid = $uid;
1336          unset($text, $uid);
1337   
1338          // NOTE: message_status is unreliable for detecting unparsed text because some callers
1339          //       change $this->message without resetting $this->message_status to 'plain' so we
1340          //       inspect the message instead
1341          //if ($this->message_status == 'plain')
1342          if (!preg_match('/^<[rt][ >]/', $this->message))
1343          {
1344              // Force updating message - of course.
1345              $this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_flash_bbcode, $this->allow_quote_bbcode, $this->allow_url_bbcode, true);
1346          }
1347   
1348          // There's a bug when previewing a topic with no poll, because the empty title of the poll
1349          // gets parsed but $this->message still ends up empty. This fixes it, until a proper fix is
1350          // devised
1351          if ($this->message === '')
1352          {
1353              $this->message = $phpbb_container->get('text_formatter.parser')->parse($this->message);
1354          }
1355   
1356          $this->message = $phpbb_container->get('text_formatter.renderer')->render($this->message);
1357   
1358          $text = $this->message;
1359          $uid = $this->bbcode_uid;
1360   
1361          /**
1362          * Event to modify the text after it is parsed
1363          *
1364          * @event core.modify_format_display_text_after
1365          * @var string    text                The message text to parse
1366          * @var string    uid                    The bbcode uid
1367          * @var bool        allow_bbcode        Do we allow bbcodes
1368          * @var bool        allow_magic_url        Do we allow magic urls
1369          * @var bool        allow_smilies        Do we allow smilies
1370          * @var bool        update_this_message    Do we update the internal message
1371          *                                    with the parsed result
1372          * @since 3.1.0-a3
1373          */
1374          $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
1375          extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_after', compact($vars)));
1376   
1377          $this->message = $text;
1378          $this->bbcode_uid = $uid;
1379   
1380          if (!$update_this_message)
1381          {
1382              unset($this->message);
1383              $this->message = $tmp_message;
1384              return $return_message;
1385          }
1386   
1387          $this->message_status = 'display';
1388          return false;
1389      }
1390   
1391      /**
1392      * Decode message to be placed back into form box
1393      */
1394      function decode_message($custom_bbcode_uid = '', $update_this_message = true)
1395      {
1396          // If false, then the parsed message get returned but internal message not processed.
1397          if (!$update_this_message)
1398          {
1399              $tmp_message = $this->message;
1400              $return_message = &$this->message;
1401          }
1402   
1403          ($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);
1404   
1405          if (!$update_this_message)
1406          {
1407              unset($this->message);
1408              $this->message = $tmp_message;
1409              return $return_message;
1410          }
1411   
1412          $this->message_status = 'plain';
1413          return false;
1414      }
1415   
1416      /**
1417      * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
1418      * Cuts down displayed size of link if over 50 chars, turns absolute links
1419      * into relative versions when the server/script path matches the link
1420      */
1421      function magic_url($server_url)
1422      {
1423          // We use the global make_clickable function
1424          $this->message = make_clickable($this->message, $server_url);
1425      }
1426   
1427      /**
1428      * Parse Smilies
1429      */
1430      function smilies($max_smilies = 0)
1431      {
1432          global $db, $user;
1433          static $match;
1434          static $replace;
1435   
1436          // See if the static arrays have already been filled on an earlier invocation
1437          if (!is_array($match))
1438          {
1439              $match = $replace = array();
1440   
1441              // NOTE: obtain_* function? chaching the table contents?
1442   
1443              // For now setting the ttl to 10 minutes
1444              switch ($db->get_sql_layer())
1445              {
1446                  case 'mssql_odbc':
1447                  case 'mssqlnative':
1448                      $sql = 'SELECT *
1449                          FROM ' . SMILIES_TABLE . '
1450                          ORDER BY LEN(code) DESC';
1451                  break;
1452   
1453                  // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
1454                  default:
1455                      $sql = 'SELECT *
1456                          FROM ' . SMILIES_TABLE . '
1457                          ORDER BY LENGTH(code) DESC';
1458                  break;
1459              }
1460              $result = $db->sql_query($sql, 600);
1461   
1462              while ($row = $db->sql_fetchrow($result))
1463              {
1464                  if (empty($row['code']))
1465                  {
1466                      continue;
1467                  }
1468   
1469                  // (assertion)
1470                  $match[] = preg_quote($row['code'], '#');
1471                  $replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
1472              }
1473              $db->sql_freeresult($result);
1474          }
1475   
1476          if (count($match))
1477          {
1478              if ($max_smilies)
1479              {
1480                  // 'u' modifier has been added to correctly parse smilies within unicode strings
1481                  // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
1482                  $num_matches = preg_match_all('#(?<=^|[\n .])(?:' . implode('|', $match) . ')(?![^<>]*>)#u', $this->message, $matches);
1483                  unset($matches);
1484   
1485                  if ($num_matches !== false && $num_matches > $max_smilies)
1486                  {
1487                      $this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
1488                      return;
1489                  }
1490              }
1491   
1492              // Make sure the delimiter # is added in front and at the end of every element within $match
1493              // 'u' modifier has been added to correctly parse smilies within unicode strings
1494              // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
1495   
1496              $this->message = trim(preg_replace(explode(chr(0), '#(?<=^|[\n .])' . implode('(?![^<>]*>)#u' . chr(0) . '#(?<=^|[\n .])', $match) . '(?![^<>]*>)#u'), $replace, $this->message));
1497          }
1498      }
1499   
1500      /**
1501       * Check attachment form token depending on submit type
1502       *
1503       * @param \phpbb\language\language $language Language
1504       * @param \phpbb\request\request_interface $request Request
1505       * @param string $form_name Form name for checking form key
1506       *
1507       * @return bool True if form token is not needed or valid, false if needed and invalid
1508       */
1509      function check_attachment_form_token(\phpbb\language\language $language, \phpbb\request\request_interface $request, $form_name)
1510      {
1511          $add_file = $request->is_set_post('add_file');
1512          $delete_file = $request->is_set_post('delete_file');
1513   
1514          if (($add_file || $delete_file) && !check_form_key($form_name))
1515          {
1516              $this->warn_msg[] = $language->lang('FORM_INVALID');
1517   
1518              if ($request->is_ajax() && $this->plupload)
1519              {
1520                  $this->plupload->emit_error(-400, 'FORM_INVALID');
1521              }
1522   
1523              return false;
1524          }
1525   
1526          return true;
1527      }
1528   
1529      /**
1530      * Parse Attachments
1531      */
1532      function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
1533      {
1534          global $config, $auth, $user, $phpbb_root_path, $phpEx, $db, $request;
1535          global $phpbb_container, $phpbb_dispatcher;
1536   
1537          $error = array();
1538   
1539          $num_attachments = count($this->attachment_data);
1540          $this->filename_data['filecomment'] = $request->variable('filecomment', '', true);
1541          $upload = $request->file($form_name);
1542          $upload_file = (!empty($upload) && $upload['name'] !== 'none' && trim($upload['name']));
1543   
1544          $add_file        = (isset($_POST['add_file'])) ? true : false;
1545          $delete_file    = (isset($_POST['delete_file'])) ? true : false;
1546   
1547          // First of all adjust comments if changed
1548          $actual_comment_list = $request->variable('comment_list', array(''), true);
1549   
1550          foreach ($actual_comment_list as $comment_key => $comment)
1551          {
1552              if (!isset($this->attachment_data[$comment_key]))
1553              {
1554                  continue;
1555              }
1556   
1557              if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
1558              {
1559                  $this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
1560              }
1561          }
1562   
1563          $cfg = array();
1564          $cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
1565          $forum_id = ($is_message) ? 0 : $forum_id;
1566   
1567          if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
1568          {
1569              if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
1570              {
1571                  /** @var \phpbb\attachment\manager $attachment_manager */
1572                  $attachment_manager = $phpbb_container->get('attachment.manager');
1573                  $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message);
1574                  $error = $filedata['error'];
1575   
1576                  if ($filedata['post_attach'] && !count($error))
1577                  {
1578                      $sql_ary = array(
1579                          'physical_filename'    => $filedata['physical_filename'],
1580                          'attach_comment'    => $this->filename_data['filecomment'],
1581                          'real_filename'        => $filedata['real_filename'],
1582                          'extension'            => $filedata['extension'],
1583                          'mimetype'            => $filedata['mimetype'],
1584                          'filesize'            => $filedata['filesize'],
1585                          'filetime'            => $filedata['filetime'],
1586                          'thumbnail'            => $filedata['thumbnail'],
1587                          'is_orphan'            => 1,
1588                          'in_message'        => ($is_message) ? 1 : 0,
1589                          'poster_id'            => $user->data['user_id'],
1590                      );
1591   
1592                      /**
1593                      * Modify attachment sql array on submit
1594                      *
1595                      * @event core.modify_attachment_sql_ary_on_submit
1596                      * @var    array    sql_ary        Array containing SQL data
1597                      * @since 3.2.6-RC1
1598                      */
1599                      $vars = array('sql_ary');
1600                      extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_submit', compact($vars)));
1601   
1602                      $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1603   
1604                      $new_entry = array(
1605                          'attach_id'        => $db->sql_nextid(),
1606                          'is_orphan'        => 1,
1607                          'real_filename'    => $filedata['real_filename'],
1608                          'attach_comment'=> $this->filename_data['filecomment'],
1609                          'filesize'        => $filedata['filesize'],
1610                      );
1611   
1612                      $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1613   
1614                      /**
1615                      * Modify attachment data on submit
1616                      *
1617                      * @event core.modify_attachment_data_on_submit
1618                      * @var    array    attachment_data        Array containing attachment data
1619                      * @since 3.2.2-RC1
1620                      */
1621                      $attachment_data = $this->attachment_data;
1622                      $vars = array('attachment_data');
1623                      extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_submit', compact($vars)));
1624                      $this->attachment_data = $attachment_data;
1625                      unset($attachment_data);
1626   
1627                      $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) {
1628                          return '[attachment='.($match[1] + 1).']' . $match[2] . '[/attachment]';
1629                      }, $this->message);
1630   
1631                      $this->filename_data['filecomment'] = '';
1632   
1633                      // This Variable is set to false here, because Attachments are entered into the
1634                      // Database in two modes, one if the id_list is 0 and the second one if post_attach is true
1635                      // Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
1636                      // but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
1637                      //
1638                      // This is very relevant, because it could happen that the post got not submitted, but we do not
1639                      // know this circumstance here. We could be at the posting page or we could be redirected to the entered
1640                      // post. :)
1641                      $filedata['post_attach'] = false;
1642                  }
1643              }
1644              else
1645              {
1646                  $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
1647              }
1648          }
1649   
1650          if ($preview || $refresh || count($error))
1651          {
1652              if (isset($this->plupload) && $this->plupload->is_active())
1653              {
1654                  $json_response = new \phpbb\json_response();
1655              }
1656   
1657              // Perform actions on temporary attachments
1658              if ($delete_file)
1659              {
1660                  include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1661   
1662                  $index = array_keys($request->variable('delete_file', array(0 => 0)));
1663                  $index = (!empty($index)) ? $index[0] : false;
1664   
1665                  if ($index !== false && !empty($this->attachment_data[$index]))
1666                  {
1667                      /** @var \phpbb\attachment\manager $attachment_manager */
1668                      $attachment_manager = $phpbb_container->get('attachment.manager');
1669   
1670                      // delete selected attachment
1671                      if ($this->attachment_data[$index]['is_orphan'])
1672                      {
1673                          $sql = 'SELECT attach_id, physical_filename, thumbnail
1674                              FROM ' . ATTACHMENTS_TABLE . '
1675                              WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id'] . '
1676                                  AND is_orphan = 1
1677                                  AND poster_id = ' . $user->data['user_id'];
1678                          $result = $db->sql_query($sql);
1679                          $row = $db->sql_fetchrow($result);
1680                          $db->sql_freeresult($result);
1681   
1682                          if ($row)
1683                          {
1684                              $attachment_manager->unlink($row['physical_filename'], 'file');
1685   
1686                              if ($row['thumbnail'])
1687                              {
1688                                  $attachment_manager->unlink($row['physical_filename'], 'thumbnail');
1689                              }
1690   
1691                              $db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE . ' WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id']);
1692                          }
1693                      }
1694                      else
1695                      {
1696                          $attachment_manager->delete('attach', $this->attachment_data[$index]['attach_id']);
1697                      }
1698   
1699                      unset($this->attachment_data[$index]);
1700                      $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) use($index) {
1701                          return ($match[1] == $index) ? '' : (($match[1] > $index) ? '[attachment=' . ($match[1] - 1) . ']' . $match[2] . '[/attachment]' : $match[0]);
1702                      }, $this->message);
1703   
1704                      // Reindex Array
1705                      $this->attachment_data = array_values($this->attachment_data);
1706                      if (isset($this->plupload) && $this->plupload->is_active())
1707                      {
1708                          $json_response->send($this->attachment_data);
1709                      }
1710                  }
1711              }
1712              else if (($add_file || $preview) && $upload_file)
1713              {
1714                  if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_', $forum_id))
1715                  {
1716                      /** @var \phpbb\attachment\manager $attachment_manager */
1717                      $attachment_manager = $phpbb_container->get('attachment.manager');
1718                      $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message);
1719                      $error = array_merge($error, $filedata['error']);
1720   
1721                      if (!count($error))
1722                      {
1723                          $sql_ary = array(
1724                              'physical_filename'    => $filedata['physical_filename'],
1725                              'attach_comment'    => $this->filename_data['filecomment'],
1726                              'real_filename'        => $filedata['real_filename'],
1727                              'extension'            => $filedata['extension'],
1728                              'mimetype'            => $filedata['mimetype'],
1729                              'filesize'            => $filedata['filesize'],
1730                              'filetime'            => $filedata['filetime'],
1731                              'thumbnail'            => $filedata['thumbnail'],
1732                              'is_orphan'            => 1,
1733                              'in_message'        => ($is_message) ? 1 : 0,
1734                              'poster_id'            => $user->data['user_id'],
1735                          );
1736   
1737                          /**
1738                          * Modify attachment sql array on upload
1739                          *
1740                          * @event core.modify_attachment_sql_ary_on_upload
1741                          * @var    array    sql_ary        Array containing SQL data
1742                          * @since 3.2.6-RC1
1743                          */
1744                          $vars = array('sql_ary');
1745                          extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_upload', compact($vars)));
1746   
1747                          $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1748   
1749                          $new_entry = array(
1750                              'attach_id'        => $db->sql_nextid(),
1751                              'is_orphan'        => 1,
1752                              'real_filename'    => $filedata['real_filename'],
1753                              'attach_comment'=> $this->filename_data['filecomment'],
1754                              'filesize'        => $filedata['filesize'],
1755                          );
1756   
1757                          $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1758   
1759                          /**
1760                          * Modify attachment data on upload
1761                          *
1762                          * @event core.modify_attachment_data_on_upload
1763                          * @var    array    attachment_data        Array containing attachment data
1764                          * @since 3.2.2-RC1
1765                          */
1766                          $attachment_data = $this->attachment_data;
1767                          $vars = array('attachment_data');
1768                          extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_upload', compact($vars)));
1769                          $this->attachment_data = $attachment_data;
1770                          unset($attachment_data);
1771   
1772                          $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) {
1773                              return '[attachment=' . ($match[1] + 1) . ']' . $match[2] . '[/attachment]';
1774                          }, $this->message);
1775                          $this->filename_data['filecomment'] = '';
1776   
1777                          if (isset($this->plupload) && $this->plupload->is_active())
1778                          {
1779                              $download_url = append_sid("{$phpbb_root_path}download/file.{$phpEx}", 'mode=view&amp;id=' . $new_entry['attach_id']);
1780   
1781                              // Send the client the attachment data to maintain state
1782                              $json_response->send(array('data' => $this->attachment_data, 'download_url' => $download_url));
1783                          }
1784                      }
1785                  }
1786                  else
1787                  {
1788                      $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
1789                  }
1790   
1791                  if (!empty($error) && isset($this->plupload) && $this->plupload->is_active())
1792                  {
1793                      // If this is a plupload (and thus ajax) request, give the
1794                      // client the first error we have
1795                      $json_response->send(array(
1796                          'jsonrpc' => '2.0',
1797                          'id' => 'id',
1798                          'error' => array(
1799                              'code' => 105,
1800                              'message' => current($error),
1801                          ),
1802                      ));
1803                  }
1804              }
1805          }
1806   
1807          foreach ($error as $error_msg)
1808          {
1809              $this->warn_msg[] = $error_msg;
1810          }
1811      }
1812   
1813      /**
1814      * Get Attachment Data
1815      */
1816      function get_submitted_attachment_data($check_user_id = false)
1817      {
1818          global $user, $db;
1819          global $request;
1820   
1821          $this->filename_data['filecomment'] = $request->variable('filecomment', '', true);
1822          $attachment_data = $request->variable('attachment_data', array(0 => array('' => '')), true, \phpbb\request\request_interface::POST);
1823          $this->attachment_data = array();
1824   
1825          $check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id;
1826   
1827          if (!count($attachment_data))
1828          {
1829              return;
1830          }
1831   
1832          $not_orphan = $orphan = array();
1833   
1834          foreach ($attachment_data as $pos => $var_ary)
1835          {
1836              if ($var_ary['is_orphan'])
1837              {
1838                  $orphan[(int) $var_ary['attach_id']] = $pos;
1839              }
1840              else
1841              {
1842                  $not_orphan[(int) $var_ary['attach_id']] = $pos;
1843              }
1844          }
1845   
1846          // Regenerate already posted attachments
1847          if (count($not_orphan))
1848          {
1849              // Get the attachment data, based on the poster id...
1850              $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize
1851                  FROM ' . ATTACHMENTS_TABLE . '
1852                  WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '
1853                      AND poster_id = ' . $check_user_id;
1854              $result = $db->sql_query($sql);
1855   
1856              while ($row = $db->sql_fetchrow($result))
1857              {
1858                  $pos = $not_orphan[$row['attach_id']];
1859                  $this->attachment_data[$pos] = $row;
1860                  $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
1861   
1862                  unset($not_orphan[$row['attach_id']]);
1863              }
1864              $db->sql_freeresult($result);
1865          }
1866   
1867          if (count($not_orphan))
1868          {
1869              trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1870          }
1871   
1872          // Regenerate newly uploaded attachments
1873          if (count($orphan))
1874          {
1875              $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize
1876                  FROM ' . ATTACHMENTS_TABLE . '
1877                  WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '
1878                      AND poster_id = ' . $user->data['user_id'] . '
1879                      AND is_orphan = 1';
1880              $result = $db->sql_query($sql);
1881   
1882              while ($row = $db->sql_fetchrow($result))
1883              {
1884                  $pos = $orphan[$row['attach_id']];
1885                  $this->attachment_data[$pos] = $row;
1886                  $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
1887   
1888                  unset($orphan[$row['attach_id']]);
1889              }
1890              $db->sql_freeresult($result);
1891          }
1892   
1893          if (count($orphan))
1894          {
1895              trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1896          }
1897   
1898          ksort($this->attachment_data);
1899      }
1900   
1901      /**
1902      * Parse Poll
1903      */
1904      function parse_poll(&$poll)
1905      {
1906          global $user, $config;
1907   
1908          $poll_max_options = $poll['poll_max_options'];
1909   
1910          // Parse Poll Option text
1911          $tmp_message = $this->message;
1912   
1913          $poll['poll_options'] = preg_split('/\s*?\n\s*/', trim($poll['poll_option_text']));
1914          $poll['poll_options_size'] = count($poll['poll_options']);
1915   
1916          foreach ($poll['poll_options'] as &$poll_option)
1917          {
1918              $this->message = $poll_option;
1919              $poll_option = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll');
1920          }
1921          unset($poll_option);
1922          $poll['poll_option_text'] = implode("\n", $poll['poll_options']);
1923   
1924          // Parse Poll Title
1925          $this->message = $poll['poll_title'];
1926          if (!$poll['poll_title'] && $poll['poll_options_size'])
1927          {
1928              $this->warn_msg[] = $user->lang['NO_POLL_TITLE'];
1929          }
1930          else
1931          {
1932              if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)) > 100)
1933              {
1934                  $this->warn_msg[] = $user->lang['POLL_TITLE_TOO_LONG'];
1935              }
1936              $poll['poll_title'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll');
1937              if (strlen($poll['poll_title']) > 255)
1938              {
1939                  $this->warn_msg[] = $user->lang['POLL_TITLE_COMP_TOO_LONG'];
1940              }
1941          }
1942   
1943          if (count($poll['poll_options']) == 1)
1944          {
1945              $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS'];
1946          }
1947          else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
1948          {
1949              $this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS'];
1950          }
1951          else if ($poll_max_options > $poll['poll_options_size'])
1952          {
1953              $this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS'];
1954          }
1955   
1956          $poll['poll_max_options'] = ($poll['poll_max_options'] < 1) ? 1 : (($poll['poll_max_options'] > $config['max_poll_options']) ? $config['max_poll_options'] : $poll['poll_max_options']);
1957   
1958          $this->message = $tmp_message;
1959      }
1960   
1961      /**
1962      * Remove nested quotes at given depth in current parsed message
1963      *
1964      * @param  integer $max_depth Depth limit
1965      * @return null
1966      */
1967      public function remove_nested_quotes($max_depth)
1968      {
1969          global $phpbb_container;
1970   
1971          if (preg_match('#^<[rt][ >]#', $this->message))
1972          {
1973              $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode(
1974                  $this->message,
1975                  'quote',
1976                  $max_depth
1977              );
1978   
1979              return;
1980          }
1981   
1982          // Capture all [quote] and [/quote] tags
1983          preg_match_all('(\\[/?quote(?:=&quot;(.*?)&quot;)?:' . $this->bbcode_uid . '\\])', $this->message, $matches, PREG_OFFSET_CAPTURE);
1984   
1985          // Iterate over the quote tags to mark the ranges that must be removed
1986          $depth = 0;
1987          $ranges = array();
1988          $start_pos = 0;
1989          foreach ($matches[0] as $match)
1990          {
1991              if ($match[0][1] === '/')
1992              {
1993                  --$depth;
1994                  if ($depth == $max_depth)
1995                  {
1996                      $end_pos = $match[1] + strlen($match[0]);
1997                      $length = $end_pos - $start_pos;
1998                      $ranges[] = array($start_pos, $length);
1999                  }
2000              }
2001              else
2002              {
2003                  ++$depth;
2004                  if ($depth == $max_depth + 1)
2005                  {
2006                      $start_pos = $match[1];
2007                  }
2008              }
2009          }
2010   
2011          foreach (array_reverse($ranges) as $range)
2012          {
2013              list($start_pos, $length) = $range;
2014              $this->message = substr_replace($this->message, '', $start_pos, $length);
2015          }
2016      }
2017   
2018      /**
2019      * Setter function for passing the plupload object
2020      *
2021      * @param \phpbb\plupload\plupload $plupload The plupload object
2022      *
2023      * @return null
2024      */
2025      public function set_plupload(\phpbb\plupload\plupload $plupload)
2026      {
2027          $this->plupload = $plupload;
2028      }
2029   
2030      /**
2031      * Function to perform custom bbcode validation by extensions
2032      * can be used in bbcode_init() to assign regexp replacement
2033      * Example: 'regexp' => array('#\[b\](.*?)\[/b\]#uise' => "\$this->validate_bbcode_by_extension('\$1')")
2034      *
2035      * Accepts variable number of parameters
2036      *
2037      * @return mixed Validation result
2038      */
2039      public function validate_bbcode_by_extension()
2040      {
2041          global $phpbb_dispatcher;
2042   
2043          $return = false;
2044          $params_array = func_get_args();
2045   
2046          /**
2047          * Event to validate bbcode with the custom validating methods
2048          * provided by extensions
2049          *
2050          * @event core.validate_bbcode_by_extension
2051          * @var array    params_array    Array with the function parameters
2052          * @var mixed    return            Validation result to return
2053          *
2054          * @since 3.1.5-RC1
2055          */
2056          $vars = array('params_array', 'return');
2057          extract($phpbb_dispatcher->trigger_event('core.validate_bbcode_by_extension', compact($vars)));
2058   
2059          return $return;
2060      }
2061  }
2062