Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 2
6.06% covered (danger)
6.06%
2 / 33
CRAP
43.10% covered (danger)
43.10%
325 / 754
bbcode_firstpass
0.00% covered (danger)
0.00%
0 / 1
5.00% covered (danger)
5.00%
1 / 20
1397.31
64.36% covered (warning)
64.36%
242 / 376
 parse_bbcode
0.00% covered (danger)
0.00%
0 / 1
13.15
68.42% covered (warning)
68.42%
13 / 19
 prepare_bbcodes
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 1
 bbcode_init
0.00% covered (danger)
0.00%
0 / 1
9.03
65.38% covered (warning)
65.38%
34 / 52
 check_bbcode
0.00% covered (danger)
0.00%
0 / 1
2.03
80.00% covered (warning)
80.00%
4 / 5
 bbcode_specialchars
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 bbcode_size
0.00% covered (danger)
0.00%
0 / 1
7.19
55.56% covered (warning)
55.56%
5 / 9
 bbcode_color
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 bbcode_underline
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 bbcode_strong
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 bbcode_italic
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 bbcode_img
0.00% covered (danger)
0.00%
0 / 1
7.60
76.92% covered (warning)
76.92%
10 / 13
 bbcode_flash
0.00% covered (danger)
0.00%
0 / 1
210.00
0.00% covered (danger)
0.00%
0 / 21
 bbcode_attachment
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 bbcode_parse_code
0.00% covered (danger)
0.00%
0 / 1
55.16
9.68% covered (danger)
9.68%
3 / 31
 bbcode_code
0.00% covered (danger)
0.00%
0 / 1
28.13
55.26% covered (warning)
55.26%
21 / 38
 bbcode_parse_list
0.00% covered (danger)
0.00%
0 / 1
27.57
81.63% covered (warning)
81.63%
40 / 49
 bbcode_quote
0.00% covered (danger)
0.00%
0 / 1
30.65
91.04% covered (success)
91.04%
61 / 67
 validate_email
0.00% covered (danger)
0.00%
0 / 1
6.10
85.71% covered (warning)
85.71%
12 / 14
 validate_url
0.00% covered (danger)
0.00%
0 / 1
24.47
65.22% covered (warning)
65.22%
15 / 23
 path_in_domain
0.00% covered (danger)
0.00%
0 / 1
16.39
68.75% covered (warning)
68.75%
11 / 16
parse_message
0.00% covered (danger)
0.00%
0 / 1
7.69% covered (danger)
7.69%
1 / 13
9590.94
21.96% covered (danger)
21.96%
83 / 378
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 parse
0.00% covered (danger)
0.00%
0 / 1
39.41
81.94% covered (warning)
81.94%
59 / 72
 format_display
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 28
 decode_message
0.00% covered (danger)
0.00%
0 / 1
20.00
0.00% covered (danger)
0.00%
0 / 10
 magic_url
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 2
 smilies
0.00% covered (danger)
0.00%
0 / 1
110.00
0.00% covered (danger)
0.00%
0 / 28
 check_attachment_form_token
0.00% covered (danger)
0.00%
0 / 1
42.00
0.00% covered (danger)
0.00%
0 / 8
 parse_attachments
0.00% covered (danger)
0.00%
0 / 1
2352.00
0.00% covered (danger)
0.00%
0 / 128
 get_submitted_attachment_data
0.00% covered (danger)
0.00%
0 / 1
132.00
0.00% covered (danger)
0.00%
0 / 39
 parse_poll
0.00% covered (danger)
0.00%
0 / 1
14.85
77.78% covered (warning)
77.78%
21 / 27
 remove_nested_quotes
0.00% covered (danger)
0.00%
0 / 1
56.00
0.00% covered (danger)
0.00%
0 / 25
 set_plupload
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 2
 validate_bbcode_by_extension
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 6
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
    exit;
}
if (!class_exists('bbcode'))
{
    // The following lines are for extensions which include message_parser.php
    // while $phpbb_root_path and $phpEx are out of the script scope
    // which may lead to the 'Undefined variable' and 'failed to open stream' errors
    if (!isset($phpbb_root_path))
    {
        global $phpbb_root_path;
    }
    if (!isset($phpEx))
    {
        global $phpEx;
    }
    include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
}
/**
* BBCODE FIRSTPASS
* BBCODE first pass class (functions for parsing messages for db storage)
*/
class bbcode_firstpass extends bbcode
{
    var $message = '';
    var $warn_msg = array();
    var $parsed_items = array();
    var $mode;
    /**
    * Parse BBCode
    */
    function parse_bbcode()
    {
        if (!$this->bbcodes)
        {
            $this->bbcode_init();
        }
        global $user;
        $this->bbcode_bitfield = '';
        $bitfield = new bitfield();
        foreach ($this->bbcodes as $bbcode_name => $bbcode_data)
        {
            if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
            {
                foreach ($bbcode_data['regexp'] as $regexp => $replacement)
                {
                    if (preg_match($regexp, $this->message))
                    {
                        $this->warn_msg[] = sprintf($user->lang['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']');
                        continue;
                    }
                }
            }
            else
            {
                foreach ($bbcode_data['regexp'] as $regexp => $replacement)
                {
                    // The pattern gets compiled and cached by the PCRE extension,
                    // it should not demand recompilation
                    if (preg_match($regexp, $this->message))
                    {
                        if (is_callable($replacement))
                        {
                            $this->message = preg_replace_callback($regexp, $replacement, $this->message);
                        }
                        else
                        {
                            $this->message = preg_replace($regexp, $replacement, $this->message);
                        }
                        $bitfield->set($bbcode_data['bbcode_id']);
                    }
                }
            }
        }
        $this->bbcode_bitfield = $bitfield->get_base64();
    }
    /**
    * Prepare some bbcodes for better parsing
    */
    function prepare_bbcodes()
    {
        // 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".
        // Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you.
        /* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
        if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false)
        {
            $this->message = str_replace("\r\n", "\n", $this->message);
            // We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
            $this->message = preg_replace('#\[quote(=&quot;.*?&quot;)?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message);
        }
        */
        // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
    }
    /**
    * Init bbcode data for later parsing
    */
    function bbcode_init($allow_custom_bbcode = true)
    {
        global $phpbb_dispatcher;
        static $rowset;
        $bbcode_class = $this;
        // This array holds all bbcode data. BBCodes will be processed in this
        // order, so it is important to keep [code] in first position and
        // [quote] in second position.
        // To parse multiline URL we enable dotall option setting only for URL text
        // but not for link itself, thus [url][/url] is not affected.
        //
        // To perform custom validation in extension, use $this->validate_bbcode_by_extension()
        // method which accepts variable number of parameters
        $this->bbcodes = array(
            'code'            => array('bbcode_id' => BBCODE_ID_CODE,    'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_code($match[1], $match[2]);
                }
            )),
            'quote'            => array('bbcode_id' => BBCODE_ID_QUOTE,    'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_quote($match[0]);
                }
            )),
            'attachment'    => array('bbcode_id' => BBCODE_ID_ATTACH,    'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_attachment($match[1], $match[2]);
                }
            )),
            'b'                => array('bbcode_id' => BBCODE_ID_B,    'regexp' => array('#\[b\](.*?)\[/b\]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_strong($match[1]);
                }
            )),
            'i'                => array('bbcode_id' => BBCODE_ID_I,    'regexp' => array('#\[i\](.*?)\[/i\]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_italic($match[1]);
                }
            )),
            'url'            => array('bbcode_id' => BBCODE_ID_URL,    'regexp' => array('#\[url(=(.*))?\](?(1)((?s).*(?-s))|(.*))\[/url\]#uiU' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->validate_url($match[2], ($match[3]) ? $match[3] : $match[4]);
                }
            )),
            'img'            => array('bbcode_id' => BBCODE_ID_IMG,    'regexp' => array('#\[img\](.*)\[/img\]#uiU' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_img($match[1]);
                }
            )),
            'size'            => array('bbcode_id' => BBCODE_ID_SIZE,    'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_size($match[1], $match[2]);
                }
            )),
            '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)
                {
                    return $bbcode_class->bbcode_color($match[1], $match[2]);
                }
            )),
            'u'                => array('bbcode_id' => BBCODE_ID_U,    'regexp' => array('#\[u\](.*?)\[/u\]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_underline($match[1]);
                }
            )),
            'list'            => array('bbcode_id' => BBCODE_ID_LIST,    'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_parse_list($match[0]);
                }
            )),
            'email'            => array('bbcode_id' => BBCODE_ID_EMAIL,    'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#uis' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->validate_email($match[1], $match[2]);
                }
            )),
            'flash'            => array('bbcode_id' => BBCODE_ID_FLASH,    'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ui' => function ($match) use($bbcode_class)
                {
                    return $bbcode_class->bbcode_flash($match[1], $match[2], $match[3]);
                }
            ))
        );
        // Zero the parsed items array
        $this->parsed_items = array();
        foreach ($this->bbcodes as $tag => $bbcode_data)
        {
            $this->parsed_items[$tag] = 0;
        }
        if (!$allow_custom_bbcode)
        {
            return;
        }
        if (!is_array($rowset))
        {
            global $db;
            $rowset = array();
            $sql = 'SELECT *
                FROM ' . BBCODES_TABLE;
            $result = $db->sql_query($sql);
            while ($row = $db->sql_fetchrow($result))
            {
                $rowset[] = $row;
            }
            $db->sql_freeresult($result);
        }
        foreach ($rowset as $row)
        {
            $this->bbcodes[$row['bbcode_tag']] = array(
                'bbcode_id'    => (int) $row['bbcode_id'],
                'regexp'    => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
            );
        }
        $bbcodes = $this->bbcodes;
        /**
        * Event to modify the bbcode data for later parsing
        *
        * @event core.modify_bbcode_init
        * @var array    bbcodes        Array of bbcode data for use in parsing
        * @var array    rowset        Array of bbcode data from the database
        * @since 3.1.0-a3
        */
        $vars = array('bbcodes', 'rowset');
        extract($phpbb_dispatcher->trigger_event('core.modify_bbcode_init', compact($vars)));
        $this->bbcodes = $bbcodes;
    }
    /**
    * Making some pre-checks for bbcodes as well as increasing the number of parsed items
    */
    function check_bbcode($bbcode, &$in)
    {
        // when using the /e modifier, preg_replace slashes double-quotes but does not
        // seem to slash anything else
        $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
        // Trimming here to make sure no empty bbcodes are parsed accidently
        if (trim($in) == '')
        {
            return false;
        }
        $this->parsed_items[$bbcode]++;
        return true;
    }
    /**
    * Transform some characters in valid bbcodes
    */
    function bbcode_specialchars($text)
    {
        $str_from = array('<', '>', '[', ']', '.', ':');
        $str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');
        return str_replace($str_from, $str_to, $text);
    }
    /**
    * Parse size tag
    */
    function bbcode_size($stx, $in)
    {
        global $user, $config;
        if (!$this->check_bbcode('size', $in))
        {
            return $in;
        }
        if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
        {
            $this->warn_msg[] = $user->lang('MAX_FONT_SIZE_EXCEEDED', (int) $config['max_' . $this->mode . '_font_size']);
            return '[size=' . $stx . ']' . $in . '[/size]';
        }
        // Do not allow size=0
        if ($stx <= 0)
        {
            return '[size=' . $stx . ']' . $in . '[/size]';
        }
        return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse color tag
    */
    function bbcode_color($stx, $in)
    {
        if (!$this->check_bbcode('color', $in))
        {
            return $in;
        }
        return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse u tag
    */
    function bbcode_underline($in)
    {
        if (!$this->check_bbcode('u', $in))
        {
            return $in;
        }
        return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse b tag
    */
    function bbcode_strong($in)
    {
        if (!$this->check_bbcode('b', $in))
        {
            return $in;
        }
        return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse i tag
    */
    function bbcode_italic($in)
    {
        if (!$this->check_bbcode('i', $in))
        {
            return $in;
        }
        return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse img tag
    */
    function bbcode_img($in)
    {
        global $user, $config;
        if (!$this->check_bbcode('img', $in))
        {
            return $in;
        }
        $in = trim($in);
        $error = false;
        $in = str_replace(' ', '%20', $in);
        // Checking urls
        if (!preg_match('#^' . get_preg_expression('url_http') . '$#iu', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
        {
            return '[img]' . $in . '[/img]';
        }
        // Try to cope with a common user error... not specifying a protocol but only a subdomain
        if (!preg_match('#^[a-z0-9]+://#i', $in))
        {
            $in = 'http://' . $in;
        }
        if ($error || $this->path_in_domain($in))
        {
            return '[img]' . $in . '[/img]';
        }
        return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse flash tag
    */
    function bbcode_flash($width, $height, $in)
    {
        global $user, $config;
        if (!$this->check_bbcode('flash', $in))
        {
            return $in;
        }
        $in = trim($in);
        $error = false;
        // Do not allow 0-sizes generally being entered
        if ($width <= 0 || $height <= 0)
        {
            return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
        }
        $in = str_replace(' ', '%20', $in);
        // Make sure $in is a URL.
        if (!preg_match('#^' . get_preg_expression('url') . '$#iu', $in) &&
            !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
        {
            return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
        }
        // Apply the same size checks on flash files as on images
        if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
        {
            if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)
            {
                $error = true;
                $this->warn_msg[] = $user->lang('MAX_FLASH_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);
            }
            if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)
            {
                $error = true;
                $this->warn_msg[] = $user->lang('MAX_FLASH_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);
            }
        }
        if ($error || $this->path_in_domain($in))
        {
            return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
        }
        return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse inline attachments [ia]
    */
    function bbcode_attachment($stx, $in)
    {
        if (!$this->check_bbcode('attachment', $in))
        {
            return $in;
        }
        return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
    }
    /**
    * Parse code text from code tag
    * @access private
    */
    function bbcode_parse_code($stx, &$code)
    {
        switch (strtolower($stx))
        {
            case 'php':
                $remove_tags = false;
                $str_from = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;', '&#058;');
                $str_to = array('<', '>', '[', ']', '.', ':', ':');
                $code = str_replace($str_from, $str_to, $code);
                if (!preg_match('/\<\?.*?\?\>/is', $code))
                {
                    $remove_tags = true;
                    $code = "<?php $code ?>";
                }
                $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
                foreach ($conf as $ini_var)
                {
                    @ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
                }
                // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
                $code = html_entity_decode($code, ENT_COMPAT);
                $code = highlight_string($code, true);
                $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
                $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');
                if ($remove_tags)
                {
                    $str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
                    $str_to[] = '';
                    $str_from[] = '<span class="syntaxdefault">&lt;?php&nbsp;';
                    $str_to[] = '<span class="syntaxdefault">';
                }
                $code = str_replace($str_from, $str_to, $code);
                $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
                if ($remove_tags)
                {
                    $code = preg_replace('#(<span class="[a-z]+">)?\?&gt;(</span>)#', '$1&nbsp;$2', $code);
                }
                $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
                $code = preg_replace('#(?:\s++|&nbsp;)*+</span>$#u', '</span>', $code);
                // remove newline at the end
                if (!empty($code) && substr($code, -1) == "\n")
                {
                    $code = substr($code, 0, -1);
                }
                return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
            break;
            default:
                return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
            break;
        }
    }
    /**
    * Parse code tag
    * Expects the argument to start right after the opening [code] tag and to end with [/code]
    */
    function bbcode_code($stx, $in)
    {
        if (!$this->check_bbcode('code', $in))
        {
            return $in;
        }
        // We remove the hardcoded elements from the code block here because it is not used in code blocks
        // Having it here saves us one preg_replace per message containing [code] blocks
        // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
        $htm_match = get_preg_expression('bbcode_htm');
        unset($htm_match[4], $htm_match[5]);
        $htm_replace = array('\1', '\1', '\2', '\1');
        $out = $code_block = '';
        $open = 1;
        while ($in)
        {
            // Determine position and tag length of next code block
            preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
            $pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
            $tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;
            // Determine position of ending code tag
            $pos2 = stripos($in, '[/code]');
            // Which is the next block, ending code or code block
            if ($pos !== false && $pos < $pos2)
            {
                // Open new block
                if (!$open)
                {
                    $out .= substr($in, 0, $pos);
                    $in = substr($in, $pos);
                    $stx = (isset($buffer[3])) ? $buffer[3] : '';
                    $code_block = '';
                }
                else
                {
                    // Already opened block, just append to the current block
                    $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
                    $in = substr($in, $pos);
                }
                $in = substr($in, $tag_length);
                $open++;
            }
            else
            {
                // Close the block
                if ($open == 1)
                {
                    $code_block .= substr($in, 0, $pos2);
                    $code_block = preg_replace($htm_match, $htm_replace, $code_block);
                    // Parse this code block
                    $out .= $this->bbcode_parse_code($stx, $code_block);
                    $code_block = '';
                    $open--;
                }
                else if ($open)
                {
                    // Close one open tag... add to the current code block
                    $code_block .= substr($in, 0, $pos2 + 7);
                    $open--;
                }
                else
                {
                    // end code without opening code... will be always outside code block
                    $out .= substr($in, 0, $pos2 + 7);
                }
                $in = substr($in, $pos2 + 7);
            }
        }
        // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
        if ($code_block)
        {
            $code_block = substr($code_block, 0, -7);
            $code_block = preg_replace($htm_match, $htm_replace, $code_block);
            $out .= $this->bbcode_parse_code($stx, $code_block);
        }
        return $out;
    }
    /**
    * Parse list bbcode
    * Expects the argument to start with a tag
    */
    function bbcode_parse_list($in)
    {
        if (!$this->check_bbcode('list', $in))
        {
            return $in;
        }
        // $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
        $tok = ']';
        $out = '[';
        // First character is [
        $in = substr($in, 1);
        $list_end_tags = $item_end_tags = array();
        do
        {
            $pos = strlen($in);
            for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
            {
                $tmp_pos = strpos($in, $tok[$i]);
                if ($tmp_pos !== false && $tmp_pos < $pos)
                {
                    $pos = $tmp_pos;
                }
            }
            $buffer = substr($in, 0, $pos);
            $tok = $in[$pos];
            $in = substr($in, $pos + 1);
            if ($tok == ']')
            {
                // if $tok is ']' the buffer holds a tag
                if (strtolower($buffer) == '/list' && count($list_end_tags))
                {
                    // valid [/list] tag, check nesting so that we don't hit false positives
                    if (count($item_end_tags) && count($item_end_tags) >= count($list_end_tags))
                    {
                        // current li tag has not been closed
                        $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
                    }
                    $out .= array_pop($list_end_tags) . ']';
                    $tok = '[';
                }
                else if (preg_match('#^list(=[0-9a-z]+)?$#i', $buffer, $m))
                {
                    // sub-list, add a closing tag
                    if (empty($m[1]) || preg_match('/^=(?:disc|square|circle)$/i', $m[1]))
                    {
                        array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
                    }
                    else
                    {
                        array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
                    }
                    $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
                    $tok = '[';
                }
                else
                {
                    if (($buffer == '*' || substr($buffer, -2) == '[*') && count($list_end_tags))
                    {
                        // the buffer holds a bullet tag and we have a [list] tag open
                        if (count($item_end_tags) >= count($list_end_tags))
                        {
                            if (substr($buffer, -2) == '[*')
                            {
                                $out .= substr($buffer, 0, -2) . '[';
                            }
                            // current li tag has not been closed
                            if (preg_match('/\n\[$/', $out, $m))
                            {
                                $out = preg_replace('/\n\[$/', '[', $out);
                                $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
                            }
                            else
                            {
                                $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
                            }
                        }
                        else
                        {
                            $buffer = '*:' . $this->bbcode_uid;
                        }
                        $item_end_tags[] = '/*:m:' . $this->bbcode_uid;
                    }
                    else if ($buffer == '/*')
                    {
                        array_pop($item_end_tags);
                        $buffer = '/*:' . $this->bbcode_uid;
                    }
                    $out .= $buffer . $tok;
                    $tok = '[]';
                }
            }
            else
            {
                // Not within a tag, just add buffer to the return string
                $out .= $buffer . $tok;
                $tok = ($tok == '[') ? ']' : '[]';
            }
        }
        while ($in);
        // do we have some tags open? close them now
        if (count($item_end_tags))
        {
            $out .= '[' . implode('][', $item_end_tags) . ']';
        }
        if (count($list_end_tags))
        {
            $out .= '[' . implode('][', $list_end_tags) . ']';
        }
        return $out;
    }
    /**
    * Parse quote bbcode
    * Expects the argument to start with a tag
    */
    function bbcode_quote($in)
    {
        $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
        if (!$in)
        {
            return '';
        }
        // To let the parser not catch tokens within quote_username quotes we encode them before we start this...
        $in = preg_replace_callback('#quote=&quot;(.*?)&quot;\]#i', function ($match) {
            return 'quote=&quot;' . str_replace(array('[', ']', '\\\"'), array('&#91;', '&#93;', '\"'), $match[1]) . '&quot;]';
        }, $in);
        $tok = ']';
        $out = '[';
        $in = substr($in, 1);
        $close_tags = $error_ary = array();
        $buffer = '';
        do
        {
            $pos = strlen($in);
            for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
            {
                $tmp_pos = strpos($in, $tok[$i]);
                if ($tmp_pos !== false && $tmp_pos < $pos)
                {
                    $pos = $tmp_pos;
                }
            }
            $buffer .= substr($in, 0, $pos);
            $tok = $in[$pos];
            $in = substr($in, $pos + 1);
            if ($tok == ']')
            {
                if (strtolower($buffer) == '/quote' && count($close_tags) && substr($out, -1, 1) == '[')
                {
                    // we have found a closing tag
                    $out .= array_pop($close_tags) . ']';
                    $tok = '[';
                    $buffer = '';
                    /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
                    * 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.
                    * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
                    if (!$in || $in[0] !== ' ')
                    {
                        $out .= ' ';
                    }*/
                }
                else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
                {
                    $this->parsed_items['quote']++;
                    array_push($close_tags, '/quote:' . $this->bbcode_uid);
                    if (isset($m[1]) && $m[1])
                    {
                        $username = str_replace(array('&#91;', '&#93;'), array('[', ']'), $m[1]);
                        $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $username);
                        $end_tags = array();
                        $error = false;
                        preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
                        foreach ($tags[1] as $tag)
                        {
                            if ($tag[0] != '/')
                            {
                                $end_tags[] = '/' . $tag;
                            }
                            else
                            {
                                $end_tag = array_pop($end_tags);
                                $error = ($end_tag != $tag) ? true : false;
                            }
                        }
                        if ($error)
                        {
                            $username = $m[1];
                        }
                        $out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
                    }
                    else
                    {
                        $out .= 'quote:' . $this->bbcode_uid . ']';
                    }
                    $tok = '[';
                    $buffer = '';
                }
                else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
                {
                    // the buffer holds an invalid opening tag
                    $buffer .= ']';
                }
                else
                {
                    $out .= $buffer . $tok;
                    $tok = '[]';
                    $buffer = '';
                }
            }
            else
            {
/**
*                Old quote code working fine, but having errors listed in bug #3572
*
*                $out .= $buffer . $tok;
*                $tok = ($tok == '[') ? ']' : '[]';
*                $buffer = '';
*/
                $out .= $buffer . $tok;
                if ($tok == '[')
                {
                    // Search the text for the next tok... if an ending quote comes first, then change tok to []
                    $pos1 = stripos($in, '[/quote');
                    // If the token ] comes first, we change it to ]
                    $pos2 = strpos($in, ']');
                    // If the token [ comes first, we change it to [
                    $pos3 = strpos($in, '[');
                    if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
                    {
                        $tok = '[]';
                    }
                    else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
                    {
                        $tok = '[';
                    }
                    else
                    {
                        $tok = ']';
                    }
                }
                else
                {
                    $tok = '[]';
                }
                $buffer = '';
            }
        }
        while ($in);
        $out .= $buffer;
        if (count($close_tags))
        {
            $out .= '[' . implode('][', $close_tags) . ']';
        }
        foreach ($error_ary as $error_msg)
        {
            $this->warn_msg[] = $error_msg;
        }
        return $out;
    }
    /**
    * Validate email
    */
    function validate_email($var1, $var2)
    {
        $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
        $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
        $txt = $var2;
        $email = ($var1) ? $var1 : $var2;
        $validated = true;
        if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
        {
            $validated = false;
        }
        if (!$validated)
        {
            return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
        }
        $this->parsed_items['email']++;
        if ($var1)
        {
            $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
        }
        else
        {
            $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
        }
        return $retval;
    }
    /**
    * Validate url
    *
    * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
    * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
    */
    function validate_url($var1, $var2)
    {
        $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
        $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
        $url = ($var1) ? $var1 : $var2;
        if ($var1 && !$var2)
        {
            $var2 = $var1;
        }
        if (!$url)
        {
            return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
        }
        $valid = false;
        $url = str_replace(' ', '%20', $url);
        // Checking urls
        if (preg_match('#^' . get_preg_expression('url') . '$#iu', $url) ||
            preg_match('#^' . get_preg_expression('www_url') . '$#iu', $url) ||
            preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#iu', $url))
        {
            $valid = true;
        }
        if ($valid)
        {
            $this->parsed_items['url']++;
            // if there is no scheme, then add http schema
            if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
            {
                $url = 'http://' . $url;
            }
            // Is this a link to somewhere inside this board? If so then remove the session id from the url
            if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
            {
                $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}&amp;/', '\1', $url);
                $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}$/', '', $url);
                $url = append_sid($url);
            }
            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 . ']';
        }
        return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
    }
    /**
    * Check if url is pointing to this domain/script_path/php-file
    *
    * @param string $url the url to check
    * @return true if the url is pointing to this domain/script_path/php-file, false if not
    *
    * @access private
    */
    function path_in_domain($url)
    {
        global $config, $phpEx, $user;
        if ($config['force_server_vars'])
        {
            $check_path = !empty($config['script_path']) ? $config['script_path'] : '/';
        }
        else
        {
            $check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
        }
        // Is the user trying to link to a php file in this domain and script path?
        if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
        {
            $server_name = $user->host;
            // Forcing server vars is the only way to specify/override the protocol
            if ($config['force_server_vars'] || !$server_name)
            {
                $server_name = $config['server_name'];
            }
            // Check again in correct order...
            $pos_ext = strpos($url, ".{$phpEx}");
            $pos_path = strpos($url, $check_path);
            $pos_domain = strpos($url, $server_name);
            if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
            {
                // Ok, actually we allow linking to some files (this may be able to be extended in some way later...)
                if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0)
                {
                    return false;
                }
                return true;
            }
        }
        return false;
    }
}
/**
* Main message parser for posting, pm, etc. takes raw message
* and parses it for attachments, bbcode and smilies
*/
class parse_message extends bbcode_firstpass
{
    var $attachment_data = array();
    var $filename_data = array();
    // Helps ironing out user error
    var $message_status = '';
    var $allow_img_bbcode = true;
    var $allow_flash_bbcode = true;
    var $allow_quote_bbcode = true;
    var $allow_url_bbcode = true;
    /**
    * The plupload object used for dealing with attachments
    * @var \phpbb\plupload\plupload
    */
    protected $plupload;
    /**
    * Init - give message here or manually
    */
    function __construct($message = '')
    {
        // Init BBCode UID
        $this->bbcode_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN);
        $this->message = $message;
    }
    /**
    * Parse Message
    */
    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')
    {
        global $config, $user, $phpbb_dispatcher, $phpbb_container;
        $this->mode = $mode;
        foreach (array('chars', 'smilies', 'urls', 'font_size', 'img_height', 'img_width') as $key)
        {
            if (!isset($config['max_' . $mode . '_' . $key]))
            {
                $config['max_' . $mode . '_' . $key] = 0;
            }
        }
        $this->allow_img_bbcode = $allow_img_bbcode;
        $this->allow_flash_bbcode = $allow_flash_bbcode;
        $this->allow_quote_bbcode = $allow_quote_bbcode;
        $this->allow_url_bbcode = $allow_url_bbcode;
        // If false, then $this->message won't be altered, the text will be returned instead.
        if (!$update_this_message)
        {
            $tmp_message = $this->message;
            $return_message = &$this->message;
        }
        if ($this->message_status == 'display')
        {
            $this->decode_message();
        }
        // Store message length...
        $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(?:=\S+?)?\]#ius', '', $this->message));
        // Maximum message length check. 0 disables this check completely.
        if ((int) $config['max_' . $mode . '_chars'] > 0 && $message_length > (int) $config['max_' . $mode . '_chars'])
        {
            $this->warn_msg[] = $user->lang('CHARS_' . strtoupper($mode) . '_CONTAINS', $message_length) . '<br />' . $user->lang('TOO_MANY_CHARS_LIMIT', (int) $config['max_' . $mode . '_chars']);
            return (!$update_this_message) ? $return_message : $this->warn_msg;
        }
        // Minimum message length check for post only
        if ($mode === 'post')
        {
            if (!$message_length || $message_length < (int) $config['min_post_chars'])
            {
                $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']));
                return (!$update_this_message) ? $return_message : $this->warn_msg;
            }
        }
        /**
        * This event can be used for additional message checks/cleanup before parsing
        *
        * @event core.message_parser_check_message
        * @var bool        allow_bbcode            Do we allow BBCodes
        * @var bool        allow_magic_url            Do we allow magic urls
        * @var bool        allow_smilies            Do we allow smilies
        * @var bool        allow_img_bbcode        Do we allow image BBCode
        * @var bool        allow_flash_bbcode        Do we allow flash BBCode
        * @var bool        allow_quote_bbcode        Do we allow quote BBCode
        * @var bool        allow_url_bbcode        Do we allow url BBCode
        * @var bool        update_this_message        Do we alter the parsed message
        * @var string    mode                    Posting mode
        * @var string    message                    The message text to parse
        * @var string    bbcode_bitfield            The bbcode_bitfield before parsing
        * @var string    bbcode_uid                The bbcode_uid before parsing
        * @var bool        return                    Do we return after the event is triggered if $warn_msg is not empty
        * @var array    warn_msg                Array of the warning messages
        * @since 3.1.2-RC1
        * @changed 3.1.3-RC1 Added vars $bbcode_bitfield and $bbcode_uid
        */
        $message = $this->message;
        $warn_msg = $this->warn_msg;
        $return = false;
        $bbcode_bitfield = $this->bbcode_bitfield;
        $bbcode_uid = $this->bbcode_uid;
        $vars = array(
            'allow_bbcode',
            'allow_magic_url',
            'allow_smilies',
            'allow_img_bbcode',
            'allow_flash_bbcode',
            'allow_quote_bbcode',
            'allow_url_bbcode',
            'update_this_message',
            'mode',
            'message',
            'bbcode_bitfield',
            'bbcode_uid',
            'return',
            'warn_msg',
        );
        extract($phpbb_dispatcher->trigger_event('core.message_parser_check_message', compact($vars)));
        $this->message = $message;
        $this->warn_msg = $warn_msg;
        $this->bbcode_bitfield = $bbcode_bitfield;
        $this->bbcode_uid = $bbcode_uid;
        if ($return && !empty($this->warn_msg))
        {
            return (!$update_this_message) ? $return_message : $this->warn_msg;
        }
        // Get the parser
        $parser = $phpbb_container->get('text_formatter.parser');
        // Set the parser's options
        ($allow_bbcode)       ? $parser->enable_bbcodes()       : $parser->disable_bbcodes();
        ($allow_magic_url)    ? $parser->enable_magic_url()     : $parser->disable_magic_url();
        ($allow_smilies)      ? $parser->enable_smilies()       : $parser->disable_smilies();
        ($allow_img_bbcode)   ? $parser->enable_bbcode('img')   : $parser->disable_bbcode('img');
        ($allow_flash_bbcode) ? $parser->enable_bbcode('flash') : $parser->disable_bbcode('flash');
        ($allow_quote_bbcode) ? $parser->enable_bbcode('quote') : $parser->disable_bbcode('quote');
        ($allow_url_bbcode)   ? $parser->enable_bbcode('url')   : $parser->disable_bbcode('url');
        // Set some config values
        $parser->set_vars(array(
            'max_font_size'  => $config['max_' . $this->mode . '_font_size'],
            'max_img_height' => $config['max_' . $this->mode . '_img_height'],
            'max_img_width'  => $config['max_' . $this->mode . '_img_width'],
            'max_smilies'    => $config['max_' . $this->mode . '_smilies'],
            'max_urls'       => $config['max_' . $this->mode . '_urls']
        ));
        // Parse this message
        $this->message = $parser->parse(html_entity_decode($this->message, ENT_QUOTES));
        // Remove quotes that are nested too deep
        if ($config['max_quote_depth'] > 0)
        {
            $this->remove_nested_quotes($config['max_quote_depth']);
        }
        // Check for "empty" message. We do not check here for maximum length, because bbcode, smilies, etc. can add to the length.
        // The maximum length check happened before any parsings.
        if ($mode === 'post' && utf8_clean_string($this->message) === '')
        {
            $this->warn_msg[] = $user->lang['TOO_FEW_CHARS'];
            return (!$update_this_message) ? $return_message : $this->warn_msg;
        }
        // Remove quotes that are nested too deep
        if ($config['max_quote_depth'] > 0)
        {
            $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode(
                $this->message,
                'quote',
                $config['max_quote_depth']
            );
        }
        // Check for errors
        $errors = $parser->get_errors();
        if ($errors)
        {
            foreach ($errors as $i => $args)
            {
                // Translate each error with $user->lang()
                $errors[$i] = call_user_func_array(array($user, 'lang'), $args);
            }
            $this->warn_msg = array_merge($this->warn_msg, $errors);
            return (!$update_this_message) ? $return_message : $this->warn_msg;
        }
        if (!$update_this_message)
        {
            unset($this->message);
            $this->message = $tmp_message;
            return $return_message;
        }
        $this->message_status = 'parsed';
        return false;
    }
    /**
    * Formatting text for display
    */
    function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
    {
        global $phpbb_container, $phpbb_dispatcher;
        // If false, then the parsed message get returned but internal message not processed.
        if (!$update_this_message)
        {
            $tmp_message = $this->message;
            $return_message = &$this->message;
        }
        $text = $this->message;
        $uid = $this->bbcode_uid;
        /**
        * Event to modify the text before it is parsed
        *
        * @event core.modify_format_display_text_before
        * @var string    text                The message text to parse
        * @var string    uid                    The bbcode uid
        * @var bool        allow_bbcode        Do we allow bbcodes
        * @var bool        allow_magic_url        Do we allow magic urls
        * @var bool        allow_smilies        Do we allow smilies
        * @var bool        update_this_message    Do we update the internal message
        *                                    with the parsed result
        * @since 3.1.6-RC1
        */
        $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
        extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_before', compact($vars)));
        $this->message = $text;
        $this->bbcode_uid = $uid;
        unset($text, $uid);
        // NOTE: message_status is unreliable for detecting unparsed text because some callers
        //       change $this->message without resetting $this->message_status to 'plain' so we
        //       inspect the message instead
        //if ($this->message_status == 'plain')
        if (!preg_match('/^<[rt][ >]/', $this->message))
        {
            // Force updating message - of course.
            $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);
        }
        // There's a bug when previewing a topic with no poll, because the empty title of the poll
        // gets parsed but $this->message still ends up empty. This fixes it, until a proper fix is
        // devised
        if ($this->message === '')
        {
            $this->message = $phpbb_container->get('text_formatter.parser')->parse($this->message);
        }
        $this->message = $phpbb_container->get('text_formatter.renderer')->render($this->message);
        $text = $this->message;
        $uid = $this->bbcode_uid;
        /**
        * Event to modify the text after it is parsed
        *
        * @event core.modify_format_display_text_after
        * @var string    text                The message text to parse
        * @var string    uid                    The bbcode uid
        * @var bool        allow_bbcode        Do we allow bbcodes
        * @var bool        allow_magic_url        Do we allow magic urls
        * @var bool        allow_smilies        Do we allow smilies
        * @var bool        update_this_message    Do we update the internal message
        *                                    with the parsed result
        * @since 3.1.0-a3
        */
        $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
        extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_after', compact($vars)));
        $this->message = $text;
        $this->bbcode_uid = $uid;
        if (!$update_this_message)
        {
            unset($this->message);
            $this->message = $tmp_message;
            return $return_message;
        }
        $this->message_status = 'display';
        return false;
    }
    /**
    * Decode message to be placed back into form box
    */
    function decode_message($custom_bbcode_uid = '', $update_this_message = true)
    {
        // If false, then the parsed message get returned but internal message not processed.
        if (!$update_this_message)
        {
            $tmp_message = $this->message;
            $return_message = &$this->message;
        }
        ($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);
        if (!$update_this_message)
        {
            unset($this->message);
            $this->message = $tmp_message;
            return $return_message;
        }
        $this->message_status = 'plain';
        return false;
    }
    /**
    * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
    * Cuts down displayed size of link if over 50 chars, turns absolute links
    * into relative versions when the server/script path matches the link
    */
    function magic_url($server_url)
    {
        // We use the global make_clickable function
        $this->message = make_clickable($this->message, $server_url);
    }
    /**
    * Parse Smilies
    */
    function smilies($max_smilies = 0)
    {
        global $db, $user;
        static $match;
        static $replace;
        // See if the static arrays have already been filled on an earlier invocation
        if (!is_array($match))
        {
            $match = $replace = array();
            // NOTE: obtain_* function? chaching the table contents?
            // For now setting the ttl to 10 minutes
            switch ($db->get_sql_layer())
            {
                case 'mssql_odbc':
                case 'mssqlnative':
                    $sql = 'SELECT *
                        FROM ' . SMILIES_TABLE . '
                        ORDER BY LEN(code) DESC';
                break;
                // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
                default:
                    $sql = 'SELECT *
                        FROM ' . SMILIES_TABLE . '
                        ORDER BY LENGTH(code) DESC';
                break;
            }
            $result = $db->sql_query($sql, 600);
            while ($row = $db->sql_fetchrow($result))
            {
                if (empty($row['code']))
                {
                    continue;
                }
                // (assertion)
                $match[] = preg_quote($row['code'], '#');
                $replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
            }
            $db->sql_freeresult($result);
        }
        if (count($match))
        {
            if ($max_smilies)
            {
                // 'u' modifier has been added to correctly parse smilies within unicode strings
                // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
                $num_matches = preg_match_all('#(?<=^|[\n .])(?:' . implode('|', $match) . ')(?![^<>]*>)#u', $this->message, $matches);
                unset($matches);
                if ($num_matches !== false && $num_matches > $max_smilies)
                {
                    $this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
                    return;
                }
            }
            // Make sure the delimiter # is added in front and at the end of every element within $match
            // 'u' modifier has been added to correctly parse smilies within unicode strings
            // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
            $this->message = trim(preg_replace(explode(chr(0), '#(?<=^|[\n .])' . implode('(?![^<>]*>)#u' . chr(0) . '#(?<=^|[\n .])', $match) . '(?![^<>]*>)#u'), $replace, $this->message));
        }
    }
    /**
     * Check attachment form token depending on submit type
     *
     * @param \phpbb\language\language $language Language
     * @param \phpbb\request\request_interface $request Request
     * @param string $form_name Form name for checking form key
     *
     * @return bool True if form token is not needed or valid, false if needed and invalid
     */
    function check_attachment_form_token(\phpbb\language\language $language, \phpbb\request\request_interface $request, $form_name)
    {
        $add_file = $request->is_set_post('add_file');
        $delete_file = $request->is_set_post('delete_file');
        if (($add_file || $delete_file) && !check_form_key($form_name))
        {
            $this->warn_msg[] = $language->lang('FORM_INVALID');
            if ($request->is_ajax() && $this->plupload)
            {
                $this->plupload->emit_error(-400, 'FORM_INVALID');
            }
            return false;
        }
        return true;
    }
    /**
    * Parse Attachments
    */
    function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
    {
        global $config, $auth, $user, $phpbb_root_path, $phpEx, $db, $request;
        global $phpbb_container, $phpbb_dispatcher;
        $error = array();
        $num_attachments = count($this->attachment_data);
        $this->filename_data['filecomment'] = $request->variable('filecomment', '', true);
        $upload = $request->file($form_name);
        $upload_file = (!empty($upload) && $upload['name'] !== 'none' && trim($upload['name']));
        $add_file        = (isset($_POST['add_file'])) ? true : false;
        $delete_file    = (isset($_POST['delete_file'])) ? true : false;
        // First of all adjust comments if changed
        $actual_comment_list = $request->variable('comment_list', array(''), true);
        foreach ($actual_comment_list as $comment_key => $comment)
        {
            if (!isset($this->attachment_data[$comment_key]))
            {
                continue;
            }
            if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
            {
                $this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
            }
        }
        $cfg = array();
        $cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
        $forum_id = ($is_message) ? 0 : $forum_id;
        if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
        {
            if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
            {
                /** @var \phpbb\attachment\manager $attachment_manager */
                $attachment_manager = $phpbb_container->get('attachment.manager');
                $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message);
                $error = $filedata['error'];
                if ($filedata['post_attach'] && !count($error))
                {
                    $sql_ary = array(
                        'physical_filename'    => $filedata['physical_filename'],
                        'attach_comment'    => $this->filename_data['filecomment'],
                        'real_filename'        => $filedata['real_filename'],
                        'extension'            => $filedata['extension'],
                        'mimetype'            => $filedata['mimetype'],
                        'filesize'            => $filedata['filesize'],
                        'filetime'            => $filedata['filetime'],
                        'thumbnail'            => $filedata['thumbnail'],
                        'is_orphan'            => 1,
                        'in_message'        => ($is_message) ? 1 : 0,
                        'poster_id'            => $user->data['user_id'],
                    );
                    /**
                    * Modify attachment sql array on submit
                    *
                    * @event core.modify_attachment_sql_ary_on_submit
                    * @var    array    sql_ary        Array containing SQL data
                    * @since 3.2.6-RC1
                    */
                    $vars = array('sql_ary');
                    extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_submit', compact($vars)));
                    $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
                    $new_entry = array(
                        'attach_id'        => $db->sql_nextid(),
                        'is_orphan'        => 1,
                        'real_filename'    => $filedata['real_filename'],
                        'attach_comment'=> $this->filename_data['filecomment'],
                        'filesize'        => $filedata['filesize'],
                    );
                    $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
                    /**
                    * Modify attachment data on submit
                    *
                    * @event core.modify_attachment_data_on_submit
                    * @var    array    attachment_data        Array containing attachment data
                    * @since 3.2.2-RC1
                    */
                    $attachment_data = $this->attachment_data;
                    $vars = array('attachment_data');
                    extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_submit', compact($vars)));
                    $this->attachment_data = $attachment_data;
                    unset($attachment_data);
                    $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) {
                        return '[attachment='.($match[1] + 1).']' . $match[2] . '[/attachment]';
                    }, $this->message);
                    $this->filename_data['filecomment'] = '';
                    // This Variable is set to false here, because Attachments are entered into the
                    // Database in two modes, one if the id_list is 0 and the second one if post_attach is true
                    // Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
                    // but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
                    //
                    // This is very relevant, because it could happen that the post got not submitted, but we do not
                    // know this circumstance here. We could be at the posting page or we could be redirected to the entered
                    // post. :)
                    $filedata['post_attach'] = false;
                }
            }
            else
            {
                $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
            }
        }
        if ($preview || $refresh || count($error))
        {
            if (isset($this->plupload) && $this->plupload->is_active())
            {
                $json_response = new \phpbb\json_response();
            }
            // Perform actions on temporary attachments
            if ($delete_file)
            {
                include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
                $index = array_keys($request->variable('delete_file', array(0 => 0)));
                $index = (!empty($index)) ? $index[0] : false;
                if ($index !== false && !empty($this->attachment_data[$index]))
                {
                    /** @var \phpbb\attachment\manager $attachment_manager */
                    $attachment_manager = $phpbb_container->get('attachment.manager');
                    // delete selected attachment
                    if ($this->attachment_data[$index]['is_orphan'])
                    {
                        $sql = 'SELECT attach_id, physical_filename, thumbnail
                            FROM ' . ATTACHMENTS_TABLE . '
                            WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id'] . '
                                AND is_orphan = 1
                                AND poster_id = ' . $user->data['user_id'];
                        $result = $db->sql_query($sql);
                        $row = $db->sql_fetchrow($result);
                        $db->sql_freeresult($result);
                        if ($row)
                        {
                            $attachment_manager->unlink($row['physical_filename'], 'file');
                            if ($row['thumbnail'])
                            {
                                $attachment_manager->unlink($row['physical_filename'], 'thumbnail');
                            }
                            $db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE . ' WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id']);
                        }
                    }
                    else
                    {
                        $attachment_manager->delete('attach', $this->attachment_data[$index]['attach_id']);
                    }
                    unset($this->attachment_data[$index]);
                    $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) use($index) {
                        return ($match[1] == $index) ? '' : (($match[1] > $index) ? '[attachment=' . ($match[1] - 1) . ']' . $match[2] . '[/attachment]' : $match[0]);
                    }, $this->message);
                    // Reindex Array
                    $this->attachment_data = array_values($this->attachment_data);
                    if (isset($this->plupload) && $this->plupload->is_active())
                    {
                        $json_response->send($this->attachment_data);
                    }
                }
            }
            else if (($add_file || $preview) && $upload_file)
            {
                if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_', $forum_id))
                {
                    /** @var \phpbb\attachment\manager $attachment_manager */
                    $attachment_manager = $phpbb_container->get('attachment.manager');
                    $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message);
                    $error = array_merge($error, $filedata['error']);
                    if (!count($error))
                    {
                        $sql_ary = array(
                            'physical_filename'    => $filedata['physical_filename'],
                            'attach_comment'    => $this->filename_data['filecomment'],
                            'real_filename'        => $filedata['real_filename'],
                            'extension'            => $filedata['extension'],
                            'mimetype'            => $filedata['mimetype'],
                            'filesize'            => $filedata['filesize'],
                            'filetime'            => $filedata['filetime'],
                            'thumbnail'            => $filedata['thumbnail'],
                            'is_orphan'            => 1,
                            'in_message'        => ($is_message) ? 1 : 0,
                            'poster_id'            => $user->data['user_id'],
                        );
                        /**
                        * Modify attachment sql array on upload
                        *
                        * @event core.modify_attachment_sql_ary_on_upload
                        * @var    array    sql_ary        Array containing SQL data
                        * @since 3.2.6-RC1
                        */
                        $vars = array('sql_ary');
                        extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_upload', compact($vars)));
                        $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
                        $new_entry = array(
                            'attach_id'        => $db->sql_nextid(),
                            'is_orphan'        => 1,
                            'real_filename'    => $filedata['real_filename'],
                            'attach_comment'=> $this->filename_data['filecomment'],
                            'filesize'        => $filedata['filesize'],
                        );
                        $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
                        /**
                        * Modify attachment data on upload
                        *
                        * @event core.modify_attachment_data_on_upload
                        * @var    array    attachment_data        Array containing attachment data
                        * @since 3.2.2-RC1
                        */
                        $attachment_data = $this->attachment_data;
                        $vars = array('attachment_data');
                        extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_upload', compact($vars)));
                        $this->attachment_data = $attachment_data;
                        unset($attachment_data);
                        $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) {
                            return '[attachment=' . ($match[1] + 1) . ']' . $match[2] . '[/attachment]';
                        }, $this->message);
                        $this->filename_data['filecomment'] = '';
                        if (isset($this->plupload) && $this->plupload->is_active())
                        {
                            $download_url = append_sid("{$phpbb_root_path}download/file.{$phpEx}", 'mode=view&amp;id=' . $new_entry['attach_id']);
                            // Send the client the attachment data to maintain state
                            $json_response->send(array('data' => $this->attachment_data, 'download_url' => $download_url));
                        }
                    }
                }
                else
                {
                    $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
                }
                if (!empty($error) && isset($this->plupload) && $this->plupload->is_active())
                {
                    // If this is a plupload (and thus ajax) request, give the
                    // client the first error we have
                    $json_response->send(array(
                        'jsonrpc' => '2.0',
                        'id' => 'id',
                        'error' => array(
                            'code' => 105,
                            'message' => current($error),
                        ),
                    ));
                }
            }
        }
        foreach ($error as $error_msg)
        {
            $this->warn_msg[] = $error_msg;
        }
    }
    /**
    * Get Attachment Data
    */
    function get_submitted_attachment_data($check_user_id = false)
    {
        global $user, $db;
        global $request;
        $this->filename_data['filecomment'] = $request->variable('filecomment', '', true);
        $attachment_data = $request->variable('attachment_data', array(0 => array('' => '')), true, \phpbb\request\request_interface::POST);
        $this->attachment_data = array();
        $check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id;
        if (!count($attachment_data))
        {
            return;
        }
        $not_orphan = $orphan = array();
        foreach ($attachment_data as $pos => $var_ary)
        {
            if ($var_ary['is_orphan'])
            {
                $orphan[(int) $var_ary['attach_id']] = $pos;
            }
            else
            {
                $not_orphan[(int) $var_ary['attach_id']] = $pos;
            }
        }
        // Regenerate already posted attachments
        if (count($not_orphan))
        {
            // Get the attachment data, based on the poster id...
            $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize
                FROM ' . ATTACHMENTS_TABLE . '
                WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '
                    AND poster_id = ' . $check_user_id;
            $result = $db->sql_query($sql);
            while ($row = $db->sql_fetchrow($result))
            {
                $pos = $not_orphan[$row['attach_id']];
                $this->attachment_data[$pos] = $row;
                $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
                unset($not_orphan[$row['attach_id']]);
            }
            $db->sql_freeresult($result);
        }
        if (count($not_orphan))
        {
            trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
        }
        // Regenerate newly uploaded attachments
        if (count($orphan))
        {
            $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize
                FROM ' . ATTACHMENTS_TABLE . '
                WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '
                    AND poster_id = ' . $user->data['user_id'] . '
                    AND is_orphan = 1';
            $result = $db->sql_query($sql);
            while ($row = $db->sql_fetchrow($result))
            {
                $pos = $orphan[$row['attach_id']];
                $this->attachment_data[$pos] = $row;
                $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
                unset($orphan[$row['attach_id']]);
            }
            $db->sql_freeresult($result);
        }
        if (count($orphan))
        {
            trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
        }
        ksort($this->attachment_data);
    }
    /**
    * Parse Poll
    */
    function parse_poll(&$poll)
    {
        global $user, $config;
        $poll_max_options = $poll['poll_max_options'];
        // Parse Poll Option text
        $tmp_message = $this->message;
        $poll['poll_options'] = preg_split('/\s*?\n\s*/', trim($poll['poll_option_text']));
        $poll['poll_options_size'] = count($poll['poll_options']);
        foreach ($poll['poll_options'] as &$poll_option)
        {
            $this->message = $poll_option;
            $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');
        }
        unset($poll_option);
        $poll['poll_option_text'] = implode("\n", $poll['poll_options']);
        // Parse Poll Title
        $this->message = $poll['poll_title'];
        if (!$poll['poll_title'] && $poll['poll_options_size'])
        {
            $this->warn_msg[] = $user->lang['NO_POLL_TITLE'];
        }
        else
        {
            if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)) > 100)
            {
                $this->warn_msg[] = $user->lang['POLL_TITLE_TOO_LONG'];
            }
            $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');
            if (strlen($poll['poll_title']) > 255)
            {
                $this->warn_msg[] = $user->lang['POLL_TITLE_COMP_TOO_LONG'];
            }
        }
        if (count($poll['poll_options']) == 1)
        {
            $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS'];
        }
        else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
        {
            $this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS'];
        }
        else if ($poll_max_options > $poll['poll_options_size'])
        {
            $this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS'];
        }
        $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']);
        $this->message = $tmp_message;
    }
    /**
    * Remove nested quotes at given depth in current parsed message
    *
    * @param  integer $max_depth Depth limit
    * @return null
    */
    public function remove_nested_quotes($max_depth)
    {
        global $phpbb_container;
        if (preg_match('#^<[rt][ >]#', $this->message))
        {
            $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode(
                $this->message,
                'quote',
                $max_depth
            );
            return;
        }
        // Capture all [quote] and [/quote] tags
        preg_match_all('(\\[/?quote(?:=&quot;(.*?)&quot;)?:' . $this->bbcode_uid . '\\])', $this->message, $matches, PREG_OFFSET_CAPTURE);
        // Iterate over the quote tags to mark the ranges that must be removed
        $depth = 0;
        $ranges = array();
        $start_pos = 0;
        foreach ($matches[0] as $match)
        {
            if ($match[0][1] === '/')
            {
                --$depth;
                if ($depth == $max_depth)
                {
                    $end_pos = $match[1] + strlen($match[0]);
                    $length = $end_pos - $start_pos;
                    $ranges[] = array($start_pos, $length);
                }
            }
            else
            {
                ++$depth;
                if ($depth == $max_depth + 1)
                {
                    $start_pos = $match[1];
                }
            }
        }
        foreach (array_reverse($ranges) as $range)
        {
            list($start_pos, $length) = $range;
            $this->message = substr_replace($this->message, '', $start_pos, $length);
        }
    }
    /**
    * Setter function for passing the plupload object
    *
    * @param \phpbb\plupload\plupload $plupload The plupload object
    *
    * @return null
    */
    public function set_plupload(\phpbb\plupload\plupload $plupload)
    {
        $this->plupload = $plupload;
    }
    /**
    * Function to perform custom bbcode validation by extensions
    * can be used in bbcode_init() to assign regexp replacement
    * Example: 'regexp' => array('#\[b\](.*?)\[/b\]#uise' => "\$this->validate_bbcode_by_extension('\$1')")
    *
    * Accepts variable number of parameters
    *
    * @return mixed Validation result
    */
    public function validate_bbcode_by_extension()
    {
        global $phpbb_dispatcher;
        $return = false;
        $params_array = func_get_args();
        /**
        * Event to validate bbcode with the custom validating methods
        * provided by extensions
        *
        * @event core.validate_bbcode_by_extension
        * @var array    params_array    Array with the function parameters
        * @var mixed    return            Validation result to return
        *
        * @since 3.1.5-RC1
        */
        $vars = array('params_array', 'return');
        extract($phpbb_dispatcher->trigger_event('core.validate_bbcode_by_extension', compact($vars)));
        return $return;
    }
}