Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
46.11% covered (danger)
46.11%
373 / 809
6.25% covered (danger)
6.25%
2 / 32
CRAP
0.00% covered (danger)
0.00%
0 / 2
bbcode_firstpass
71.10% covered (warning)
71.10%
278 / 391
5.26% covered (danger)
5.26%
1 / 19
693.11
0.00% covered (danger)
0.00%
0 / 1
 parse_bbcode
66.67% covered (warning)
66.67%
12 / 18
0.00% covered (danger)
0.00%
0 / 1
13.70
 prepare_bbcodes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bbcode_init
79.55% covered (warning)
79.55%
70 / 88
0.00% covered (danger)
0.00%
0 / 1
7.42
 check_bbcode
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 bbcode_specialchars
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 bbcode_size
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
7.19
 bbcode_color
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 bbcode_underline
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 bbcode_strong
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 bbcode_italic
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 bbcode_img
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
7.60
 bbcode_attachment
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 bbcode_parse_code
9.68% covered (danger)
9.68%
3 / 31
0.00% covered (danger)
0.00%
0 / 1
68.69
 bbcode_code
55.26% covered (warning)
55.26%
21 / 38
0.00% covered (danger)
0.00%
0 / 1
28.13
 bbcode_parse_list
81.63% covered (warning)
81.63%
40 / 49
0.00% covered (danger)
0.00%
0 / 1
26.28
 bbcode_quote
91.18% covered (success)
91.18%
62 / 68
0.00% covered (danger)
0.00%
0 / 1
29.58
 validate_email
85.71% covered (warning)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
6.10
 validate_url
65.22% covered (warning)
65.22%
15 / 23
0.00% covered (danger)
0.00%
0 / 1
24.47
 path_in_domain
68.75% covered (warning)
68.75%
11 / 16
0.00% covered (danger)
0.00%
0 / 1
16.39
parse_message
22.73% covered (danger)
22.73%
95 / 418
7.69% covered (danger)
7.69%
1 / 13
9314.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 parse
83.72% covered (warning)
83.72%
72 / 86
0.00% covered (danger)
0.00%
0 / 1
36.42
 format_display
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
30
 decode_message
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 magic_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 smilies
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
132
 check_attachment_form_token
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 parse_attachments
0.00% covered (danger)
0.00%
0 / 150
0.00% covered (danger)
0.00%
0 / 1
2352
 get_submitted_attachment_data
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
132
 parse_poll
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
15.08
 remove_nested_quotes
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
56
 set_plupload
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validate_bbcode_by_extension
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14/**
15* @ignore
16*/
17if (!defined('IN_PHPBB'))
18{
19    exit;
20}
21
22if (!class_exists('bbcode'))
23{
24    // The following lines are for extensions which include message_parser.php
25    // while $phpbb_root_path and $phpEx are out of the script scope
26    // which may lead to the 'Undefined variable' and 'failed to open stream' errors
27    if (!isset($phpbb_root_path))
28    {
29        global $phpbb_root_path;
30    }
31
32    if (!isset($phpEx))
33    {
34        global $phpEx;
35    }
36
37    include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
38}
39
40/**
41* BBCODE FIRSTPASS
42* BBCODE first pass class (functions for parsing messages for db storage)
43*/
44class bbcode_firstpass extends bbcode
45{
46    var $message = '';
47    var $warn_msg = array();
48    var $parsed_items = array();
49    var $mode;
50
51    /**
52    * Parse BBCode
53    */
54    function parse_bbcode()
55    {
56        if (!$this->bbcodes)
57        {
58            $this->bbcode_init();
59        }
60
61        global $user;
62
63        $this->bbcode_bitfield = '';
64        $bitfield = new bitfield();
65
66        foreach ($this->bbcodes as $bbcode_name => $bbcode_data)
67        {
68            if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
69            {
70                foreach ($bbcode_data['regexp'] as $regexp => $replacement)
71                {
72                    if (preg_match($regexp, $this->message))
73                    {
74                        $this->warn_msg[] = sprintf($user->lang['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']');
75                        continue;
76                    }
77                }
78            }
79            else
80            {
81                foreach ($bbcode_data['regexp'] as $regexp => $replacement)
82                {
83                    // The pattern gets compiled and cached by the PCRE extension,
84                    // it should not demand recompilation
85                    if (preg_match($regexp, $this->message))
86                    {
87                        if (is_callable($replacement))
88                        {
89                            $this->message = preg_replace_callback($regexp, $replacement, $this->message);
90                        }
91                        else
92                        {
93                            $this->message = preg_replace($regexp, $replacement, $this->message);
94                        }
95                        $bitfield->set($bbcode_data['bbcode_id']);
96                    }
97                }
98            }
99        }
100
101        $this->bbcode_bitfield = $bitfield->get_base64();
102    }
103
104    /**
105    * Prepare some bbcodes for better parsing
106    */
107    function prepare_bbcodes()
108    {
109        // 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".
110        // Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you.
111
112        /* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
113        if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false)
114        {
115            $this->message = str_replace("\r\n", "\n", $this->message);
116
117            // We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
118            $this->message = preg_replace('#\[quote(=&quot;.*?&quot;)?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message);
119        }
120        */
121
122        // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
123    }
124
125    /**
126    * Init bbcode data for later parsing
127    */
128    function bbcode_init($allow_custom_bbcode = true)
129    {
130        global $phpbb_dispatcher;
131
132        static $rowset;
133
134        $bbcode_class = $this;
135
136        // This array holds all bbcode data. BBCodes will be processed in this
137        // order, so it is important to keep [code] in first position and
138        // [quote] in second position.
139        // To parse multiline URL we enable dotall option setting only for URL text
140        // but not for link itself, thus [url][/url] is not affected.
141        //
142        // To perform custom validation in extension, use $this->validate_bbcode_by_extension()
143        // method which accepts variable number of parameters
144        $this->bbcodes = array(
145            'code'            => array('bbcode_id' => BBCODE_ID_CODE,    'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#uis' => function ($match) use($bbcode_class)
146                {
147                    return $bbcode_class->bbcode_code($match[1], $match[2]);
148                }
149            )),
150            'quote'            => array('bbcode_id' => BBCODE_ID_QUOTE,    'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#uis' => function ($match) use($bbcode_class)
151                {
152                    return $bbcode_class->bbcode_quote($match[0]);
153                }
154            )),
155            'attachment'    => array('bbcode_id' => BBCODE_ID_ATTACH,    'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#uis' => function ($match) use($bbcode_class)
156                {
157                    return $bbcode_class->bbcode_attachment($match[1], $match[2]);
158                }
159            )),
160            'b'                => array('bbcode_id' => BBCODE_ID_B,    'regexp' => array('#\[b\](.*?)\[/b\]#uis' => function ($match) use($bbcode_class)
161                {
162                    return $bbcode_class->bbcode_strong($match[1]);
163                }
164            )),
165            'i'                => array('bbcode_id' => BBCODE_ID_I,    'regexp' => array('#\[i\](.*?)\[/i\]#uis' => function ($match) use($bbcode_class)
166                {
167                    return $bbcode_class->bbcode_italic($match[1]);
168                }
169            )),
170            'url'            => array('bbcode_id' => BBCODE_ID_URL,    'regexp' => array('#\[url(=(.*))?\](?(1)((?s).*(?-s))|(.*))\[/url\]#uiU' => function ($match) use($bbcode_class)
171                {
172                    return $bbcode_class->validate_url($match[2], ($match[3]) ? $match[3] : $match[4]);
173                }
174            )),
175            'img'            => array('bbcode_id' => BBCODE_ID_IMG,    'regexp' => array('#\[img\](.*)\[/img\]#uiU' => function ($match) use($bbcode_class)
176                {
177                    return $bbcode_class->bbcode_img($match[1]);
178                }
179            )),
180            'size'            => array('bbcode_id' => BBCODE_ID_SIZE,    'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#uis' => function ($match) use($bbcode_class)
181                {
182                    return $bbcode_class->bbcode_size($match[1], $match[2]);
183                }
184            )),
185            '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)
186                {
187                    return $bbcode_class->bbcode_color($match[1], $match[2]);
188                }
189            )),
190            'u'                => array('bbcode_id' => BBCODE_ID_U,    'regexp' => array('#\[u\](.*?)\[/u\]#uis' => function ($match) use($bbcode_class)
191                {
192                    return $bbcode_class->bbcode_underline($match[1]);
193                }
194            )),
195            'list'            => array('bbcode_id' => BBCODE_ID_LIST,    'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#uis' => function ($match) use($bbcode_class)
196                {
197                    return $bbcode_class->bbcode_parse_list($match[0]);
198                }
199            )),
200            'email'            => array('bbcode_id' => BBCODE_ID_EMAIL,    'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#uis' => function ($match) use($bbcode_class)
201                {
202                    return $bbcode_class->validate_email($match[1], $match[2]);
203                }
204            )),
205        );
206
207        // Zero the parsed items array
208        $this->parsed_items = array();
209
210        foreach ($this->bbcodes as $tag => $bbcode_data)
211        {
212            $this->parsed_items[$tag] = 0;
213        }
214
215        if (!$allow_custom_bbcode)
216        {
217            return;
218        }
219
220        if (!is_array($rowset))
221        {
222            global $db;
223            $rowset = array();
224
225            $sql = 'SELECT *
226                FROM ' . BBCODES_TABLE;
227            $result = $db->sql_query($sql);
228
229            while ($row = $db->sql_fetchrow($result))
230            {
231                $rowset[] = $row;
232            }
233            $db->sql_freeresult($result);
234        }
235
236        foreach ($rowset as $row)
237        {
238            $this->bbcodes[$row['bbcode_tag']] = array(
239                'bbcode_id'    => (int) $row['bbcode_id'],
240                'regexp'    => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
241            );
242        }
243
244        $bbcodes = $this->bbcodes;
245
246        /**
247        * Event to modify the bbcode data for later parsing
248        *
249        * @event core.modify_bbcode_init
250        * @var array    bbcodes        Array of bbcode data for use in parsing
251        * @var array    rowset        Array of bbcode data from the database
252        * @since 3.1.0-a3
253        */
254        $vars = array('bbcodes', 'rowset');
255        extract($phpbb_dispatcher->trigger_event('core.modify_bbcode_init', compact($vars)));
256
257        $this->bbcodes = $bbcodes;
258    }
259
260    /**
261    * Making some pre-checks for bbcodes as well as increasing the number of parsed items
262    */
263    function check_bbcode($bbcode, &$in)
264    {
265        // when using the /e modifier, preg_replace slashes double-quotes but does not
266        // seem to slash anything else
267        $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
268
269        // Trimming here to make sure no empty bbcodes are parsed accidently
270        if (trim($in) == '')
271        {
272            return false;
273        }
274
275        $this->parsed_items[$bbcode]++;
276
277        return true;
278    }
279
280    /**
281    * Transform some characters in valid bbcodes
282    */
283    function bbcode_specialchars($text)
284    {
285        $str_from = array('<', '>', '[', ']', '.', ':');
286        $str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');
287
288        return str_replace($str_from, $str_to, $text);
289    }
290
291    /**
292    * Parse size tag
293    */
294    function bbcode_size($stx, $in)
295    {
296        global $user, $config;
297
298        if (!$this->check_bbcode('size', $in))
299        {
300            return $in;
301        }
302
303        if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
304        {
305            $this->warn_msg[] = $user->lang('MAX_FONT_SIZE_EXCEEDED', (int) $config['max_' . $this->mode . '_font_size']);
306
307            return '[size=' . $stx . ']' . $in . '[/size]';
308        }
309
310        // Do not allow size=0
311        if ($stx <= 0)
312        {
313            return '[size=' . $stx . ']' . $in . '[/size]';
314        }
315
316        return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
317    }
318
319    /**
320    * Parse color tag
321    */
322    function bbcode_color($stx, $in)
323    {
324        if (!$this->check_bbcode('color', $in))
325        {
326            return $in;
327        }
328
329        return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
330    }
331
332    /**
333    * Parse u tag
334    */
335    function bbcode_underline($in)
336    {
337        if (!$this->check_bbcode('u', $in))
338        {
339            return $in;
340        }
341
342        return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
343    }
344
345    /**
346    * Parse b tag
347    */
348    function bbcode_strong($in)
349    {
350        if (!$this->check_bbcode('b', $in))
351        {
352            return $in;
353        }
354
355        return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
356    }
357
358    /**
359    * Parse i tag
360    */
361    function bbcode_italic($in)
362    {
363        if (!$this->check_bbcode('i', $in))
364        {
365            return $in;
366        }
367
368        return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
369    }
370
371    /**
372    * Parse img tag
373    */
374    function bbcode_img($in)
375    {
376        global $user, $config;
377
378        if (!$this->check_bbcode('img', $in))
379        {
380            return $in;
381        }
382
383        $in = trim($in);
384        $error = false;
385
386        $in = str_replace(' ', '%20', $in);
387
388        // Checking urls
389        if (!preg_match('#^' . get_preg_expression('url_http') . '$#iu', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in))
390        {
391            return '[img]' . $in . '[/img]';
392        }
393
394        // Try to cope with a common user error... not specifying a protocol but only a subdomain
395        if (!preg_match('#^[a-z0-9]+://#i', $in))
396        {
397            $in = 'http://' . $in;
398        }
399
400        if ($error || $this->path_in_domain($in))
401        {
402            return '[img]' . $in . '[/img]';
403        }
404
405        return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
406    }
407
408    /**
409    * Parse inline attachments [ia]
410    */
411    function bbcode_attachment($stx, $in)
412    {
413        if (!$this->check_bbcode('attachment', $in))
414        {
415            return $in;
416        }
417
418        return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
419    }
420
421    /**
422    * Parse code text from code tag
423    * @access private
424    */
425    function bbcode_parse_code($stx, &$code)
426    {
427        switch (strtolower($stx))
428        {
429            case 'php':
430
431                $remove_tags = false;
432
433                $str_from = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;', '&#058;');
434                $str_to = array('<', '>', '[', ']', '.', ':', ':');
435                $code = str_replace($str_from, $str_to, $code);
436
437                if (!preg_match('/\<\?.*?\?\>/is', $code))
438                {
439                    $remove_tags = true;
440                    $code = "<?php $code ?>";
441                }
442
443                $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
444                foreach ($conf as $ini_var)
445                {
446                    @ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
447                }
448
449                // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
450                $code = html_entity_decode($code, ENT_COMPAT);
451                $code = highlight_string($code, true);
452
453                $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
454                $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');
455
456                if ($remove_tags)
457                {
458                    $str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
459                    $str_to[] = '';
460                    $str_from[] = '<span class="syntaxdefault">&lt;?php&nbsp;';
461                    $str_to[] = '<span class="syntaxdefault">';
462                }
463
464                $code = str_replace($str_from, $str_to, $code);
465                $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
466
467                if ($remove_tags)
468                {
469                    $code = preg_replace('#(<span class="[a-z]+">)?\?&gt;(</span>)#', '$1&nbsp;$2', $code);
470                }
471
472                $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
473                $code = preg_replace('#(?:\s++|&nbsp;)*+</span>$#u', '</span>', $code);
474
475                // remove newline at the end
476                if (!empty($code) && substr($code, -1) == "\n")
477                {
478                    $code = substr($code, 0, -1);
479                }
480
481                return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
482            break;
483
484            default:
485                return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
486            break;
487        }
488    }
489
490    /**
491    * Parse code tag
492    * Expects the argument to start right after the opening [code] tag and to end with [/code]
493    */
494    function bbcode_code($stx, $in)
495    {
496        if (!$this->check_bbcode('code', $in))
497        {
498            return $in;
499        }
500
501        // We remove the hardcoded elements from the code block here because it is not used in code blocks
502        // Having it here saves us one preg_replace per message containing [code] blocks
503        // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
504        $htm_match = get_preg_expression('bbcode_htm');
505        unset($htm_match[4], $htm_match[5]);
506        $htm_replace = array('\1', '\1', '\2', '\1');
507
508        $out = $code_block = '';
509        $open = 1;
510
511        while ($in)
512        {
513            // Determine position and tag length of next code block
514            preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
515            $pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
516            $tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;
517
518            // Determine position of ending code tag
519            $pos2 = stripos($in, '[/code]');
520
521            // Which is the next block, ending code or code block
522            if ($pos !== false && $pos < $pos2)
523            {
524                // Open new block
525                if (!$open)
526                {
527                    $out .= substr($in, 0, $pos);
528                    $in = substr($in, $pos);
529                    $stx = (isset($buffer[3])) ? $buffer[3] : '';
530                    $code_block = '';
531                }
532                else
533                {
534                    // Already opened block, just append to the current block
535                    $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
536                    $in = substr($in, $pos);
537                }
538
539                $in = substr($in, $tag_length);
540                $open++;
541            }
542            else
543            {
544                // Close the block
545                if ($open == 1)
546                {
547                    $code_block .= substr($in, 0, $pos2);
548                    $code_block = preg_replace($htm_match, $htm_replace, $code_block);
549
550                    // Parse this code block
551                    $out .= $this->bbcode_parse_code($stx, $code_block);
552                    $code_block = '';
553                    $open--;
554                }
555                else if ($open)
556                {
557                    // Close one open tag... add to the current code block
558                    $code_block .= substr($in, 0, $pos2 + 7);
559                    $open--;
560                }
561                else
562                {
563                    // end code without opening code... will be always outside code block
564                    $out .= substr($in, 0, $pos2 + 7);
565                }
566
567                $in = substr($in, $pos2 + 7);
568            }
569        }
570
571        // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
572        if ($code_block)
573        {
574            $code_block = substr($code_block, 0, -7);
575            $code_block = preg_replace($htm_match, $htm_replace, $code_block);
576
577            $out .= $this->bbcode_parse_code($stx, $code_block);
578        }
579
580        return $out;
581    }
582
583    /**
584    * Parse list bbcode
585    * Expects the argument to start with a tag
586    */
587    function bbcode_parse_list($in)
588    {
589        if (!$this->check_bbcode('list', $in))
590        {
591            return $in;
592        }
593
594        // $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
595        $tok = ']';
596        $out = '[';
597
598        // First character is [
599        $in = substr($in, 1);
600        $list_end_tags = $item_end_tags = array();
601
602        do
603        {
604            $pos = strlen($in);
605
606            for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
607            {
608                $tmp_pos = strpos($in, $tok[$i]);
609
610                if ($tmp_pos !== false && $tmp_pos < $pos)
611                {
612                    $pos = $tmp_pos;
613                }
614            }
615
616            $buffer = substr($in, 0, $pos);
617            $tok = $in[$pos];
618
619            $in = substr($in, $pos + 1);
620
621            if ($tok == ']')
622            {
623                // if $tok is ']' the buffer holds a tag
624                if (strtolower($buffer) == '/list' && count($list_end_tags))
625                {
626                    // valid [/list] tag, check nesting so that we don't hit false positives
627                    if (count($item_end_tags) && count($item_end_tags) >= count($list_end_tags))
628                    {
629                        // current li tag has not been closed
630                        $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
631                    }
632
633                    $out .= array_pop($list_end_tags) . ']';
634                    $tok = '[';
635                }
636                else if (preg_match('#^list(=[0-9a-z]+)?$#i', $buffer, $m))
637                {
638                    // sub-list, add a closing tag
639                    if (empty($m[1]) || preg_match('/^=(?:disc|square|circle)$/i', $m[1]))
640                    {
641                        array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
642                    }
643                    else
644                    {
645                        array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
646                    }
647                    $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
648                    $tok = '[';
649                }
650                else
651                {
652                    if (($buffer == '*' || substr($buffer, -2) == '[*') && count($list_end_tags))
653                    {
654                        // the buffer holds a bullet tag and we have a [list] tag open
655                        if (count($item_end_tags) >= count($list_end_tags))
656                        {
657                            if (substr($buffer, -2) == '[*')
658                            {
659                                $out .= substr($buffer, 0, -2) . '[';
660                            }
661                            // current li tag has not been closed
662                            if (preg_match('/\n\[$/', $out, $m))
663                            {
664                                $out = preg_replace('/\n\[$/', '[', $out);
665                                $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
666                            }
667                            else
668                            {
669                                $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
670                            }
671                        }
672                        else
673                        {
674                            $buffer = '*:' . $this->bbcode_uid;
675                        }
676
677                        $item_end_tags[] = '/*:m:' . $this->bbcode_uid;
678                    }
679                    else if ($buffer == '/*')
680                    {
681                        array_pop($item_end_tags);
682                        $buffer = '/*:' . $this->bbcode_uid;
683                    }
684
685                    $out .= $buffer . $tok;
686                    $tok = '[]';
687                }
688            }
689            else
690            {
691                // Not within a tag, just add buffer to the return string
692                $out .= $buffer . $tok;
693                $tok = ($tok == '[') ? ']' : '[]';
694            }
695        }
696        while ($in);
697
698        // do we have some tags open? close them now
699        if (count($item_end_tags))
700        {
701            $out .= '[' . implode('][', $item_end_tags) . ']';
702        }
703        if (count($list_end_tags))
704        {
705            $out .= '[' . implode('][', $list_end_tags) . ']';
706        }
707
708        return $out;
709    }
710
711    /**
712    * Parse quote bbcode
713    * Expects the argument to start with a tag
714    */
715    function bbcode_quote($in)
716    {
717        $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
718
719        if (!$in)
720        {
721            return '';
722        }
723
724        // To let the parser not catch tokens within quote_username quotes we encode them before we start this...
725        $in = preg_replace_callback('#quote=&quot;(.*?)&quot;\]#i', function ($match) {
726            return 'quote=&quot;' . str_replace(array('[', ']', '\\\"'), array('&#91;', '&#93;', '\"'), $match[1]) . '&quot;]';
727        }, $in);
728
729        $tok = ']';
730        $out = '[';
731
732        $in = substr($in, 1);
733        $close_tags = $error_ary = array();
734        $buffer = '';
735
736        do
737        {
738            $pos = strlen($in);
739            for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
740            {
741                $tmp_pos = strpos($in, $tok[$i]);
742                if ($tmp_pos !== false && $tmp_pos < $pos)
743                {
744                    $pos = $tmp_pos;
745                }
746            }
747
748            $buffer .= substr($in, 0, $pos);
749            $tok = $in[$pos];
750            $in = substr($in, $pos + 1);
751
752            if ($tok == ']')
753            {
754                if (strtolower($buffer) == '/quote' && count($close_tags) && substr($out, -1, 1) == '[')
755                {
756                    // we have found a closing tag
757                    $out .= array_pop($close_tags) . ']';
758                    $tok = '[';
759                    $buffer = '';
760
761                    /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
762                    * 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.
763                    * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
764                    if (!$in || $in[0] !== ' ')
765                    {
766                        $out .= ' ';
767                    }*/
768                }
769                else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
770                {
771                    $this->parsed_items['quote']++;
772                    array_push($close_tags, '/quote:' . $this->bbcode_uid);
773
774                    if (isset($m[1]) && $m[1])
775                    {
776                        $username = str_replace(array('&#91;', '&#93;'), array('[', ']'), $m[1]);
777                        $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $username);
778
779                        $end_tags = array();
780                        $error = false;
781
782                        preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
783                        foreach ($tags[1] as $tag)
784                        {
785                            if ($tag[0] != '/')
786                            {
787                                $end_tags[] = '/' . $tag;
788                            }
789                            else
790                            {
791                                $end_tag = array_pop($end_tags);
792                                $error = ($end_tag != $tag) ? true : false;
793                            }
794                        }
795
796                        if ($error)
797                        {
798                            $username = $m[1];
799                        }
800
801                        $out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
802                    }
803                    else
804                    {
805                        $out .= 'quote:' . $this->bbcode_uid . ']';
806                    }
807
808                    $tok = '[';
809                    $buffer = '';
810                }
811                else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
812                {
813                    // the buffer holds an invalid opening tag
814                    $buffer .= ']';
815                }
816                else
817                {
818                    $out .= $buffer . $tok;
819                    $tok = '[]';
820                    $buffer = '';
821                }
822            }
823            else
824            {
825/**
826*                Old quote code working fine, but having errors listed in bug #3572
827*
828*                $out .= $buffer . $tok;
829*                $tok = ($tok == '[') ? ']' : '[]';
830*                $buffer = '';
831*/
832
833                $out .= $buffer . $tok;
834
835                if ($tok == '[')
836                {
837                    // Search the text for the next tok... if an ending quote comes first, then change tok to []
838                    $pos1 = stripos($in, '[/quote');
839                    // If the token ] comes first, we change it to ]
840                    $pos2 = strpos($in, ']');
841                    // If the token [ comes first, we change it to [
842                    $pos3 = strpos($in, '[');
843
844                    if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
845                    {
846                        $tok = '[]';
847                    }
848                    else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
849                    {
850                        $tok = '[';
851                    }
852                    else
853                    {
854                        $tok = ']';
855                    }
856                }
857                else
858                {
859                    $tok = '[]';
860                }
861                $buffer = '';
862            }
863        }
864        while ($in);
865
866        $out .= $buffer;
867
868        if (count($close_tags))
869        {
870            $out .= '[' . implode('][', $close_tags) . ']';
871        }
872
873        foreach ($error_ary as $error_msg)
874        {
875            $this->warn_msg[] = $error_msg;
876        }
877
878        return $out;
879    }
880
881    /**
882    * Validate email
883    */
884    function validate_email($var1, $var2)
885    {
886        $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
887        $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
888
889        $txt = $var2;
890        $email = ($var1) ? $var1 : $var2;
891
892        $validated = true;
893
894        if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
895        {
896            $validated = false;
897        }
898
899        if (!$validated)
900        {
901            return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
902        }
903
904        $this->parsed_items['email']++;
905
906        if ($var1)
907        {
908            $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
909        }
910        else
911        {
912            $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
913        }
914
915        return $retval;
916    }
917
918    /**
919    * Validate url
920    *
921    * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
922    * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
923    */
924    function validate_url($var1, $var2)
925    {
926        $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
927        $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
928
929        $url = ($var1) ? $var1 : $var2;
930
931        if ($var1 && !$var2)
932        {
933            $var2 = $var1;
934        }
935
936        if (!$url)
937        {
938            return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
939        }
940
941        $valid = false;
942
943        $url = str_replace(' ', '%20', $url);
944
945        // Checking urls
946        if (preg_match('#^' . get_preg_expression('url') . '$#iu', $url) ||
947            preg_match('#^' . get_preg_expression('www_url') . '$#iu', $url) ||
948            preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#iu', $url))
949        {
950            $valid = true;
951        }
952
953        if ($valid)
954        {
955            $this->parsed_items['url']++;
956
957            // if there is no scheme, then add http schema
958            if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
959            {
960                $url = 'http://' . $url;
961            }
962
963            // Is this a link to somewhere inside this board? If so then remove the session id from the url
964            if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
965            {
966                $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}&amp;/', '\1', $url);
967                $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}$/', '', $url);
968                $url = append_sid($url);
969            }
970
971            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 . ']';
972        }
973
974        return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
975    }
976
977    /**
978    * Check if url is pointing to this domain/script_path/php-file
979    *
980    * @param string $url the url to check
981    * @return true if the url is pointing to this domain/script_path/php-file, false if not
982    *
983    * @access private
984    */
985    function path_in_domain($url)
986    {
987        global $config, $phpEx, $user;
988
989        if ($config['force_server_vars'])
990        {
991            $check_path = !empty($config['script_path']) ? $config['script_path'] : '/';
992        }
993        else
994        {
995            $check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
996        }
997
998        // Is the user trying to link to a php file in this domain and script path?
999        if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
1000        {
1001            $server_name = $user->host;
1002
1003            // Forcing server vars is the only way to specify/override the protocol
1004            if ($config['force_server_vars'] || !$server_name)
1005            {
1006                $server_name = $config['server_name'];
1007            }
1008
1009            // Check again in correct order...
1010            $pos_ext = strpos($url, ".{$phpEx}");
1011            $pos_path = strpos($url, $check_path);
1012            $pos_domain = strpos($url, $server_name);
1013
1014            if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
1015            {
1016                // Ok, actually we allow linking to some files (this may be able to be extended in some way later...)
1017                // @deprecated
1018                if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0)
1019                {
1020                    return false;
1021                }
1022
1023                return true;
1024            }
1025        }
1026
1027        return false;
1028    }
1029}
1030
1031/**
1032* Main message parser for posting, pm, etc. takes raw message
1033* and parses it for attachments, bbcode and smilies
1034*/
1035class parse_message extends bbcode_firstpass
1036{
1037    var $attachment_data = array();
1038    var $filename_data = array();
1039
1040    // Helps ironing out user error
1041    var $message_status = '';
1042
1043    var $allow_img_bbcode = true;
1044    var $allow_quote_bbcode = true;
1045    var $allow_url_bbcode = true;
1046
1047    /**
1048    * The plupload object used for dealing with attachments
1049    * @var \phpbb\plupload\plupload
1050    */
1051    protected $plupload;
1052
1053    /**
1054    * Init - give message here or manually
1055    */
1056    function __construct($message = '')
1057    {
1058        // Init BBCode UID
1059        $unique_id = preg_replace('/[^0-9a-f]/', '', unique_id());
1060        $this->bbcode_uid = substr(base_convert($unique_id, 16, 36), 0, BBCODE_UID_LEN);
1061        $this->message = $message;
1062    }
1063
1064    /**
1065    * Parse Message
1066    */
1067    function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post')
1068    {
1069        global $config, $user, $phpbb_dispatcher, $phpbb_container;
1070
1071        $this->mode = $mode;
1072
1073        foreach (array('chars', 'smilies', 'urls', 'font_size', 'img_height', 'img_width') as $key)
1074        {
1075            if (!isset($config['max_' . $mode . '_' . $key]))
1076            {
1077                $config['max_' . $mode . '_' . $key] = 0;
1078            }
1079        }
1080
1081        $this->allow_img_bbcode = $allow_img_bbcode;
1082        $this->allow_quote_bbcode = $allow_quote_bbcode;
1083        $this->allow_url_bbcode = $allow_url_bbcode;
1084
1085        // If false, then $this->message won't be altered, the text will be returned instead.
1086        if (!$update_this_message)
1087        {
1088            $tmp_message = $this->message;
1089            $return_message = &$this->message;
1090        }
1091
1092        if ($this->message_status == 'display')
1093        {
1094            $this->decode_message();
1095        }
1096
1097        // Store message length...
1098        $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(?:=\S+?)?\]#ius', '', $this->message));
1099
1100        // Maximum message length check. 0 disables this check completely.
1101        if ((int) $config['max_' . $mode . '_chars'] > 0 && $message_length > (int) $config['max_' . $mode . '_chars'])
1102        {
1103            $this->warn_msg[] = $user->lang('CHARS_' . strtoupper($mode) . '_CONTAINS', $message_length) . '<br />' . $user->lang('TOO_MANY_CHARS_LIMIT', (int) $config['max_' . $mode . '_chars']);
1104            return (!$update_this_message) ? $return_message : $this->warn_msg;
1105        }
1106
1107        // Minimum message length check for post only
1108        if ($mode === 'post')
1109        {
1110            if (!$message_length || $message_length < (int) $config['min_post_chars'])
1111            {
1112                $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']));
1113                return (!$update_this_message) ? $return_message : $this->warn_msg;
1114            }
1115        }
1116
1117        /**
1118        * This event can be used for additional message checks/cleanup before parsing
1119        *
1120        * @event core.message_parser_check_message
1121        * @var bool        allow_bbcode            Do we allow BBCodes
1122        * @var bool        allow_magic_url            Do we allow magic urls
1123        * @var bool        allow_smilies            Do we allow smilies
1124        * @var bool        allow_img_bbcode        Do we allow image BBCode
1125        * @var bool        allow_quote_bbcode        Do we allow quote BBCode
1126        * @var bool        allow_url_bbcode        Do we allow url BBCode
1127        * @var bool        update_this_message        Do we alter the parsed message
1128        * @var string    mode                    Posting mode
1129        * @var string    message                    The message text to parse
1130        * @var string    bbcode_bitfield            The bbcode_bitfield before parsing
1131        * @var string    bbcode_uid                The bbcode_uid before parsing
1132        * @var bool        return                    Do we return after the event is triggered if $warn_msg is not empty
1133        * @var array    warn_msg                Array of the warning messages
1134        * @since 3.1.2-RC1
1135        * @changed 3.1.3-RC1 Added vars $bbcode_bitfield and $bbcode_uid
1136        * @changed 4.0.0-a1 Removed $allow_flash_bbcode
1137        */
1138        $message = $this->message;
1139        $warn_msg = $this->warn_msg;
1140        $return = false;
1141        $bbcode_bitfield = $this->bbcode_bitfield;
1142        $bbcode_uid = $this->bbcode_uid;
1143        $vars = array(
1144            'allow_bbcode',
1145            'allow_magic_url',
1146            'allow_smilies',
1147            'allow_img_bbcode',
1148            'allow_quote_bbcode',
1149            'allow_url_bbcode',
1150            'update_this_message',
1151            'mode',
1152            'message',
1153            'bbcode_bitfield',
1154            'bbcode_uid',
1155            'return',
1156            'warn_msg',
1157        );
1158        extract($phpbb_dispatcher->trigger_event('core.message_parser_check_message', compact($vars)));
1159        $this->message = $message;
1160        $this->warn_msg = $warn_msg;
1161        $this->bbcode_bitfield = $bbcode_bitfield;
1162        $this->bbcode_uid = $bbcode_uid;
1163        if ($return && !empty($this->warn_msg))
1164        {
1165            return (!$update_this_message) ? $return_message : $this->warn_msg;
1166        }
1167
1168        // Get the parser
1169        $parser = $phpbb_container->get('text_formatter.parser');
1170
1171        // Set the parser's options
1172        ($allow_bbcode)       ? $parser->enable_bbcodes()       : $parser->disable_bbcodes();
1173        ($allow_magic_url)    ? $parser->enable_magic_url()     : $parser->disable_magic_url();
1174        ($allow_smilies)      ? $parser->enable_smilies()       : $parser->disable_smilies();
1175        ($allow_img_bbcode)   ? $parser->enable_bbcode('img')   : $parser->disable_bbcode('img');
1176        ($allow_quote_bbcode) ? $parser->enable_bbcode('quote') : $parser->disable_bbcode('quote');
1177        ($allow_url_bbcode)   ? $parser->enable_bbcode('url')   : $parser->disable_bbcode('url');
1178
1179        // Set some config values
1180        $parser->set_vars(array(
1181            'max_font_size'  => $config['max_' . $this->mode . '_font_size'],
1182            'max_img_height' => $config['max_' . $this->mode . '_img_height'],
1183            'max_img_width'  => $config['max_' . $this->mode . '_img_width'],
1184            'max_smilies'    => $config['max_' . $this->mode . '_smilies'],
1185            'max_urls'       => $config['max_' . $this->mode . '_urls']
1186        ));
1187
1188        // Parse this message
1189        $this->message = $parser->parse(html_entity_decode($this->message, ENT_QUOTES));
1190
1191        // Remove quotes that are nested too deep
1192        if ($config['max_quote_depth'] > 0)
1193        {
1194            $this->remove_nested_quotes($config['max_quote_depth']);
1195        }
1196
1197        // Check for "empty" message. We do not check here for maximum length, because bbcode, smilies, etc. can add to the length.
1198        // The maximum length check happened before any parsings.
1199        if ($mode === 'post' && utf8_clean_string($this->message) === '')
1200        {
1201            $this->warn_msg[] = $user->lang['TOO_FEW_CHARS'];
1202            return (!$update_this_message) ? $return_message : $this->warn_msg;
1203        }
1204
1205        // Remove quotes that are nested too deep
1206        if ($config['max_quote_depth'] > 0)
1207        {
1208            $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode(
1209                $this->message,
1210                'quote',
1211                $config['max_quote_depth']
1212            );
1213        }
1214
1215        // Check for errors
1216        $errors = $parser->get_errors();
1217        if ($errors)
1218        {
1219            foreach ($errors as $i => $args)
1220            {
1221                // Translate each error with $user->lang()
1222                $errors[$i] = call_user_func_array(array($user, 'lang'), $args);
1223            }
1224            $this->warn_msg = array_merge($this->warn_msg, $errors);
1225
1226            return (!$update_this_message) ? $return_message : $this->warn_msg;
1227        }
1228
1229        if (!$update_this_message)
1230        {
1231            unset($this->message);
1232            $this->message = $tmp_message;
1233            return $return_message;
1234        }
1235
1236        $this->message_status = 'parsed';
1237        return false;
1238    }
1239
1240    /**
1241    * Formatting text for display
1242    */
1243    function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
1244    {
1245        global $phpbb_container, $phpbb_dispatcher;
1246
1247        // If false, then the parsed message get returned but internal message not processed.
1248        if (!$update_this_message)
1249        {
1250            $tmp_message = $this->message;
1251            $return_message = &$this->message;
1252        }
1253
1254        $text = $this->message;
1255        $uid = $this->bbcode_uid;
1256
1257        /**
1258        * Event to modify the text before it is parsed
1259        *
1260        * @event core.modify_format_display_text_before
1261        * @var string    text                The message text to parse
1262        * @var string    uid                    The bbcode uid
1263        * @var bool        allow_bbcode        Do we allow bbcodes
1264        * @var bool        allow_magic_url        Do we allow magic urls
1265        * @var bool        allow_smilies        Do we allow smilies
1266        * @var bool        update_this_message    Do we update the internal message
1267        *                                    with the parsed result
1268        * @since 3.1.6-RC1
1269        */
1270        $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
1271        extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_before', compact($vars)));
1272
1273        $this->message = $text;
1274        $this->bbcode_uid = $uid;
1275        unset($text, $uid);
1276
1277        // NOTE: message_status is unreliable for detecting unparsed text because some callers
1278        //       change $this->message without resetting $this->message_status to 'plain' so we
1279        //       inspect the message instead
1280        //if ($this->message_status == 'plain')
1281        if (!preg_match('/^<[rt][ >]/', $this->message))
1282        {
1283            // Force updating message - of course.
1284            $this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_quote_bbcode, $this->allow_url_bbcode, true);
1285        }
1286
1287        // There's a bug when previewing a topic with no poll, because the empty title of the poll
1288        // gets parsed but $this->message still ends up empty. This fixes it, until a proper fix is
1289        // devised
1290        if ($this->message === '')
1291        {
1292            $this->message = $phpbb_container->get('text_formatter.parser')->parse($this->message);
1293        }
1294
1295        $this->message = $phpbb_container->get('text_formatter.renderer')->render($this->message);
1296
1297        $text = $this->message;
1298        $uid = $this->bbcode_uid;
1299
1300        /**
1301        * Event to modify the text after it is parsed
1302        *
1303        * @event core.modify_format_display_text_after
1304        * @var string    text                The message text to parse
1305        * @var string    uid                    The bbcode uid
1306        * @var bool        allow_bbcode        Do we allow bbcodes
1307        * @var bool        allow_magic_url        Do we allow magic urls
1308        * @var bool        allow_smilies        Do we allow smilies
1309        * @var bool        update_this_message    Do we update the internal message
1310        *                                    with the parsed result
1311        * @since 3.1.0-a3
1312        */
1313        $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message');
1314        extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_after', compact($vars)));
1315
1316        $this->message = $text;
1317        $this->bbcode_uid = $uid;
1318
1319        if (!$update_this_message)
1320        {
1321            unset($this->message);
1322            $this->message = $tmp_message;
1323            return $return_message;
1324        }
1325
1326        $this->message_status = 'display';
1327        return false;
1328    }
1329
1330    /**
1331    * Decode message to be placed back into form box
1332    */
1333    function decode_message($custom_bbcode_uid = '', $update_this_message = true)
1334    {
1335        // If false, then the parsed message get returned but internal message not processed.
1336        if (!$update_this_message)
1337        {
1338            $tmp_message = $this->message;
1339            $return_message = &$this->message;
1340        }
1341
1342        ($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);
1343
1344        if (!$update_this_message)
1345        {
1346            unset($this->message);
1347            $this->message = $tmp_message;
1348            return $return_message;
1349        }
1350
1351        $this->message_status = 'plain';
1352        return false;
1353    }
1354
1355    /**
1356    * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
1357    * Cuts down displayed size of link if over 50 chars, turns absolute links
1358    * into relative versions when the server/script path matches the link
1359    */
1360    function magic_url($server_url)
1361    {
1362        // We use the global make_clickable function
1363        $this->message = make_clickable($this->message, $server_url);
1364    }
1365
1366    /**
1367    * Parse Smilies
1368    */
1369    function smilies($max_smilies = 0)
1370    {
1371        global $db, $user;
1372        static $match;
1373        static $replace;
1374
1375        // See if the static arrays have already been filled on an earlier invocation
1376        if (!is_array($match))
1377        {
1378            $match = $replace = array();
1379
1380            // NOTE: obtain_* function? chaching the table contents?
1381
1382            // For now setting the ttl to 10 minutes
1383            switch ($db->get_sql_layer())
1384            {
1385                case 'mssql_odbc':
1386                case 'mssqlnative':
1387                    $sql = 'SELECT *
1388                        FROM ' . SMILIES_TABLE . '
1389                        ORDER BY LEN(code) DESC';
1390                break;
1391
1392                // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
1393                default:
1394                    $sql = 'SELECT *
1395                        FROM ' . SMILIES_TABLE . '
1396                        ORDER BY LENGTH(code) DESC';
1397                break;
1398            }
1399            $result = $db->sql_query($sql, 600);
1400
1401            while ($row = $db->sql_fetchrow($result))
1402            {
1403                if (empty($row['code']))
1404                {
1405                    continue;
1406                }
1407
1408                // (assertion)
1409                $match[] = preg_quote($row['code'], '#');
1410                $replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
1411            }
1412            $db->sql_freeresult($result);
1413        }
1414
1415        if (count($match))
1416        {
1417            if ($max_smilies)
1418            {
1419                // 'u' modifier has been added to correctly parse smilies within unicode strings
1420                // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
1421                $num_matches = preg_match_all('#(?<=^|[\n .])(?:' . implode('|', $match) . ')(?![^<>]*>)#u', $this->message, $matches);
1422                unset($matches);
1423
1424                if ($num_matches !== false && $num_matches > $max_smilies)
1425                {
1426                    $this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
1427                    return;
1428                }
1429            }
1430
1431            // Make sure the delimiter # is added in front and at the end of every element within $match
1432            // 'u' modifier has been added to correctly parse smilies within unicode strings
1433            // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
1434
1435            $this->message = trim(preg_replace(explode(chr(0), '#(?<=^|[\n .])' . implode('(?![^<>]*>)#u' . chr(0) . '#(?<=^|[\n .])', $match) . '(?![^<>]*>)#u'), $replace, $this->message));
1436        }
1437    }
1438
1439    /**
1440     * Check attachment form token depending on submit type
1441     *
1442     * @param \phpbb\language\language $language Language
1443     * @param \phpbb\request\request_interface $request Request
1444     * @param string $form_name Form name for checking form key
1445     *
1446     * @return bool True if form token is not needed or valid, false if needed and invalid
1447     */
1448    function check_attachment_form_token(\phpbb\language\language $language, \phpbb\request\request_interface $request, $form_name)
1449    {
1450        $add_file = $request->is_set_post('add_file');
1451        $delete_file = $request->is_set_post('delete_file');
1452
1453        if (($add_file || $delete_file) && !check_form_key($form_name))
1454        {
1455            $this->warn_msg[] = $language->lang('FORM_INVALID');
1456
1457            if ($request->is_ajax() && $this->plupload)
1458            {
1459                $this->plupload->emit_error(-400, 'FORM_INVALID');
1460            }
1461
1462            return false;
1463        }
1464
1465        return true;
1466    }
1467
1468    /**
1469    * Parse Attachments
1470    */
1471    function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
1472    {
1473        global $config, $auth, $user, $phpbb_root_path, $phpEx, $db, $request;
1474        global $phpbb_container, $phpbb_dispatcher;
1475
1476        $controller_helper = $phpbb_container->get('controller.helper');
1477
1478        $error = array();
1479
1480        $num_attachments = count($this->attachment_data);
1481        $this->filename_data['filecomment'] = $request->variable('filecomment', '', true);
1482        $upload = $request->file($form_name);
1483        $upload_file = (!empty($upload) && $upload['name'] !== 'none' && trim($upload['name']));
1484
1485        $add_file        = (isset($_POST['add_file'])) ? true : false;
1486        $delete_file    = (isset($_POST['delete_file'])) ? true : false;
1487
1488        // First of all adjust comments if changed
1489        $actual_comment_list = $request->variable('comment_list', array(''), true);
1490
1491        foreach ($actual_comment_list as $comment_key => $comment)
1492        {
1493            if (!isset($this->attachment_data[$comment_key]))
1494            {
1495                continue;
1496            }
1497
1498            if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
1499            {
1500                $this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
1501            }
1502        }
1503
1504        $cfg = array();
1505        $cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
1506        $forum_id = ($is_message) ? 0 : $forum_id;
1507
1508        if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
1509        {
1510            if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
1511            {
1512                /** @var \phpbb\attachment\manager $attachment_manager */
1513                $attachment_manager = $phpbb_container->get('attachment.manager');
1514                $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message);
1515                $error = $filedata['error'];
1516
1517                if ($filedata['post_attach'] && !count($error))
1518                {
1519                    $sql_ary = array(
1520                        'physical_filename'    => $filedata['physical_filename'],
1521                        'attach_comment'    => $this->filename_data['filecomment'],
1522                        'real_filename'        => $filedata['real_filename'],
1523                        'extension'            => $filedata['extension'],
1524                        'mimetype'            => $filedata['mimetype'],
1525                        'filesize'            => $filedata['filesize'],
1526                        'filetime'            => $filedata['filetime'],
1527                        'thumbnail'            => $filedata['thumbnail'],
1528                        'is_orphan'            => 1,
1529                        'in_message'        => ($is_message) ? 1 : 0,
1530                        'poster_id'            => $user->data['user_id'],
1531                    );
1532
1533                    /**
1534                    * Modify attachment sql array on submit
1535                    *
1536                    * @event core.modify_attachment_sql_ary_on_submit
1537                    * @var    array    sql_ary        Array containing SQL data
1538                    * @since 3.2.6-RC1
1539                    */
1540                    $vars = array('sql_ary');
1541                    extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_submit', compact($vars)));
1542
1543                    $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1544
1545                    $new_entry = array(
1546                        'attach_id'        => $db->sql_nextid(),
1547                        'is_orphan'        => 1,
1548                        'real_filename'    => $filedata['real_filename'],
1549                        'attach_comment'=> $this->filename_data['filecomment'],
1550                        'filesize'        => $filedata['filesize'],
1551                    );
1552
1553                    $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1554
1555                    /**
1556                    * Modify attachment data on submit
1557                    *
1558                    * @event core.modify_attachment_data_on_submit
1559                    * @var    array    attachment_data        Array containing attachment data
1560                    * @since 3.2.2-RC1
1561                    */
1562                    $attachment_data = $this->attachment_data;
1563                    $vars = array('attachment_data');
1564                    extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_submit', compact($vars)));
1565                    $this->attachment_data = $attachment_data;
1566                    unset($attachment_data);
1567
1568                    $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) {
1569                        return '[attachment='.($match[1] + 1).']' . $match[2] . '[/attachment]';
1570                    }, $this->message);
1571
1572                    $this->filename_data['filecomment'] = '';
1573
1574                    // This Variable is set to false here, because Attachments are entered into the
1575                    // Database in two modes, one if the id_list is 0 and the second one if post_attach is true
1576                    // Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
1577                    // but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
1578                    //
1579                    // This is very relevant, because it could happen that the post got not submitted, but we do not
1580                    // know this circumstance here. We could be at the posting page or we could be redirected to the entered
1581                    // post. :)
1582                    $filedata['post_attach'] = false;
1583                }
1584            }
1585            else
1586            {
1587                $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
1588            }
1589        }
1590
1591        if ($preview || $refresh || count($error))
1592        {
1593            if (isset($this->plupload) && $this->plupload->is_active())
1594            {
1595                $json_response = new \phpbb\json_response();
1596            }
1597
1598            // Perform actions on temporary attachments
1599            if ($delete_file)
1600            {
1601                include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1602
1603                $index = array_keys($request->variable('delete_file', array(0 => 0)));
1604                $index = (!empty($index)) ? $index[0] : false;
1605
1606                if ($index !== false && !empty($this->attachment_data[$index]))
1607                {
1608                    /** @var \phpbb\attachment\manager $attachment_manager */
1609                    $attachment_manager = $phpbb_container->get('attachment.manager');
1610
1611                    // delete selected attachment
1612                    if ($this->attachment_data[$index]['is_orphan'])
1613                    {
1614                        $sql = 'SELECT attach_id, physical_filename, thumbnail
1615                            FROM ' . ATTACHMENTS_TABLE . '
1616                            WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id'] . '
1617                                AND is_orphan = 1
1618                                AND poster_id = ' . $user->data['user_id'];
1619                        $result = $db->sql_query($sql);
1620                        $row = $db->sql_fetchrow($result);
1621                        $db->sql_freeresult($result);
1622
1623                        if ($row)
1624                        {
1625                            $attachment_manager->unlink($row['physical_filename'], 'file');
1626
1627                            if ($row['thumbnail'])
1628                            {
1629                                $attachment_manager->unlink($row['physical_filename'], 'thumbnail');
1630                            }
1631
1632                            $db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE . ' WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id']);
1633                        }
1634                    }
1635                    else
1636                    {
1637                        $attachment_manager->delete('attach', $this->attachment_data[$index]['attach_id']);
1638                    }
1639
1640                    unset($this->attachment_data[$index]);
1641                    $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) use($index) {
1642                        return ($match[1] == $index) ? '' : (($match[1] > $index) ? '[attachment=' . ($match[1] - 1) . ']' . $match[2] . '[/attachment]' : $match[0]);
1643                    }, $this->message);
1644
1645                    // Reindex Array
1646                    $this->attachment_data = array_values($this->attachment_data);
1647                    if (isset($this->plupload) && $this->plupload->is_active())
1648                    {
1649                        $json_response->send($this->attachment_data);
1650                    }
1651                }
1652            }
1653            else if (($add_file || $preview) && $upload_file)
1654            {
1655                if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_', $forum_id))
1656                {
1657                    /** @var \phpbb\attachment\manager $attachment_manager */
1658                    $attachment_manager = $phpbb_container->get('attachment.manager');
1659                    $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message);
1660                    $error = array_merge($error, $filedata['error']);
1661
1662                    if (!count($error))
1663                    {
1664                        $sql_ary = array(
1665                            'physical_filename'    => $filedata['physical_filename'],
1666                            'attach_comment'    => $this->filename_data['filecomment'],
1667                            'real_filename'        => $filedata['real_filename'],
1668                            'extension'            => $filedata['extension'],
1669                            'mimetype'            => $filedata['mimetype'],
1670                            'filesize'            => $filedata['filesize'],
1671                            'filetime'            => $filedata['filetime'],
1672                            'thumbnail'            => $filedata['thumbnail'],
1673                            'is_orphan'            => 1,
1674                            'in_message'        => ($is_message) ? 1 : 0,
1675                            'poster_id'            => $user->data['user_id'],
1676                        );
1677
1678                        /**
1679                        * Modify attachment sql array on upload
1680                        *
1681                        * @event core.modify_attachment_sql_ary_on_upload
1682                        * @var    array    sql_ary        Array containing SQL data
1683                        * @since 3.2.6-RC1
1684                        */
1685                        $vars = array('sql_ary');
1686                        extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_upload', compact($vars)));
1687
1688                        $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1689
1690                        $new_entry = array(
1691                            'attach_id'        => $db->sql_nextid(),
1692                            'is_orphan'        => 1,
1693                            'real_filename'    => $filedata['real_filename'],
1694                            'attach_comment'=> $this->filename_data['filecomment'],
1695                            'filesize'        => $filedata['filesize'],
1696                        );
1697
1698                        $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1699
1700                        /**
1701                        * Modify attachment data on upload
1702                        *
1703                        * @event core.modify_attachment_data_on_upload
1704                        * @var    array    attachment_data        Array containing attachment data
1705                        * @since 3.2.2-RC1
1706                        */
1707                        $attachment_data = $this->attachment_data;
1708                        $vars = array('attachment_data');
1709                        extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_upload', compact($vars)));
1710                        $this->attachment_data = $attachment_data;
1711                        unset($attachment_data);
1712
1713                        $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) {
1714                            return '[attachment=' . ($match[1] + 1) . ']' . $match[2] . '[/attachment]';
1715                        }, $this->message);
1716                        $this->filename_data['filecomment'] = '';
1717
1718                        if (isset($this->plupload) && $this->plupload->is_active())
1719                        {
1720                            $download_url = $controller_helper->route(
1721                                'phpbb_storage_attachment',
1722                                [
1723                                    'id'        => (int) $new_entry['attach_id'],
1724                                    'filename'    => $new_entry['real_filename'],
1725                                ]
1726                            );
1727
1728                            // Send the client the attachment data to maintain state
1729                            $json_response->send(array('data' => $this->attachment_data, 'download_url' => $download_url));
1730                        }
1731                    }
1732                }
1733                else
1734                {
1735                    $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
1736                }
1737
1738                if (!empty($error) && isset($this->plupload) && $this->plupload->is_active())
1739                {
1740                    // If this is a plupload (and thus ajax) request, give the
1741                    // client the first error we have
1742                    $json_response->send(array(
1743                        'jsonrpc' => '2.0',
1744                        'id' => 'id',
1745                        'error' => array(
1746                            'code' => 105,
1747                            'message' => current($error),
1748                        ),
1749                    ));
1750                }
1751            }
1752        }
1753
1754        foreach ($error as $error_msg)
1755        {
1756            $this->warn_msg[] = $error_msg;
1757        }
1758    }
1759
1760    /**
1761    * Get Attachment Data
1762    */
1763    function get_submitted_attachment_data($check_user_id = false)
1764    {
1765        global $user, $db;
1766        global $request;
1767
1768        $this->filename_data['filecomment'] = $request->variable('filecomment', '', true);
1769        $attachment_data = $request->variable('attachment_data', array(0 => array('' => '')), true, \phpbb\request\request_interface::POST);
1770        $this->attachment_data = array();
1771
1772        $check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id;
1773
1774        if (!count($attachment_data))
1775        {
1776            return;
1777        }
1778
1779        $not_orphan = $orphan = array();
1780
1781        foreach ($attachment_data as $pos => $var_ary)
1782        {
1783            if ($var_ary['is_orphan'])
1784            {
1785                $orphan[(int) $var_ary['attach_id']] = $pos;
1786            }
1787            else
1788            {
1789                $not_orphan[(int) $var_ary['attach_id']] = $pos;
1790            }
1791        }
1792
1793        // Regenerate already posted attachments
1794        if (count($not_orphan))
1795        {
1796            // Get the attachment data, based on the poster id...
1797            $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize
1798                FROM ' . ATTACHMENTS_TABLE . '
1799                WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '
1800                    AND poster_id = ' . $check_user_id;
1801            $result = $db->sql_query($sql);
1802
1803            while ($row = $db->sql_fetchrow($result))
1804            {
1805                $pos = $not_orphan[$row['attach_id']];
1806                $this->attachment_data[$pos] = $row;
1807                $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
1808
1809                unset($not_orphan[$row['attach_id']]);
1810            }
1811            $db->sql_freeresult($result);
1812        }
1813
1814        if (count($not_orphan))
1815        {
1816            trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1817        }
1818
1819        // Regenerate newly uploaded attachments
1820        if (count($orphan))
1821        {
1822            $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize
1823                FROM ' . ATTACHMENTS_TABLE . '
1824                WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '
1825                    AND poster_id = ' . $user->data['user_id'] . '
1826                    AND is_orphan = 1';
1827            $result = $db->sql_query($sql);
1828
1829            while ($row = $db->sql_fetchrow($result))
1830            {
1831                $pos = $orphan[$row['attach_id']];
1832                $this->attachment_data[$pos] = $row;
1833                $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
1834
1835                unset($orphan[$row['attach_id']]);
1836            }
1837            $db->sql_freeresult($result);
1838        }
1839
1840        if (count($orphan))
1841        {
1842            trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1843        }
1844
1845        ksort($this->attachment_data);
1846    }
1847
1848    /**
1849    * Parse Poll
1850    */
1851    function parse_poll(&$poll)
1852    {
1853        global $user, $config;
1854
1855        $poll_max_options = $poll['poll_max_options'];
1856
1857        // Parse Poll Option text
1858        $tmp_message = $this->message;
1859
1860        $poll['poll_options'] = preg_split('/\s*?\n\s*/', trim($poll['poll_option_text']));
1861        $poll['poll_options_size'] = count($poll['poll_options']);
1862
1863        foreach ($poll['poll_options'] as &$poll_option)
1864        {
1865            $this->message = $poll_option;
1866            $poll_option = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, $config['allow_post_links'], false, 'poll');
1867        }
1868        unset($poll_option);
1869        $poll['poll_option_text'] = implode("\n", $poll['poll_options']);
1870
1871        // Parse Poll Title
1872        $this->message = $poll['poll_title'];
1873        if (!$poll['poll_title'] && $poll['poll_options_size'])
1874        {
1875            $this->warn_msg[] = $user->lang['NO_POLL_TITLE'];
1876        }
1877        else
1878        {
1879            if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)) > 100)
1880            {
1881                $this->warn_msg[] = $user->lang['POLL_TITLE_TOO_LONG'];
1882            }
1883            $poll['poll_title'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, $config['allow_post_links'], false, 'poll');
1884            if (strlen($poll['poll_title']) > 255)
1885            {
1886                $this->warn_msg[] = $user->lang['POLL_TITLE_COMP_TOO_LONG'];
1887            }
1888        }
1889
1890        if (count($poll['poll_options']) == 1)
1891        {
1892            $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS'];
1893        }
1894        else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
1895        {
1896            $this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS'];
1897        }
1898        else if ($poll_max_options > $poll['poll_options_size'])
1899        {
1900            $this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS'];
1901        }
1902
1903        $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']);
1904
1905        $this->message = $tmp_message;
1906    }
1907
1908    /**
1909    * Remove nested quotes at given depth in current parsed message
1910    *
1911    * @param  integer $max_depth Depth limit
1912    * @return void
1913    */
1914    public function remove_nested_quotes($max_depth)
1915    {
1916        global $phpbb_container;
1917
1918        if (preg_match('#^<[rt][ >]#', $this->message))
1919        {
1920            $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode(
1921                $this->message,
1922                'quote',
1923                $max_depth
1924            );
1925
1926            return;
1927        }
1928
1929        // Capture all [quote] and [/quote] tags
1930        preg_match_all('(\\[/?quote(?:=&quot;(.*?)&quot;)?:' . $this->bbcode_uid . '\\])', $this->message, $matches, PREG_OFFSET_CAPTURE);
1931
1932        // Iterate over the quote tags to mark the ranges that must be removed
1933        $depth = 0;
1934        $ranges = array();
1935        $start_pos = 0;
1936        foreach ($matches[0] as $match)
1937        {
1938            if ($match[0][1] === '/')
1939            {
1940                --$depth;
1941                if ($depth == $max_depth)
1942                {
1943                    $end_pos = $match[1] + strlen($match[0]);
1944                    $length = $end_pos - $start_pos;
1945                    $ranges[] = array($start_pos, $length);
1946                }
1947            }
1948            else
1949            {
1950                ++$depth;
1951                if ($depth == $max_depth + 1)
1952                {
1953                    $start_pos = $match[1];
1954                }
1955            }
1956        }
1957
1958        foreach (array_reverse($ranges) as $range)
1959        {
1960            list($start_pos, $length) = $range;
1961            $this->message = substr_replace($this->message, '', $start_pos, $length);
1962        }
1963    }
1964
1965    /**
1966    * Setter function for passing the plupload object
1967    *
1968    * @param \phpbb\plupload\plupload $plupload The plupload object
1969    *
1970    * @return null
1971    */
1972    public function set_plupload(\phpbb\plupload\plupload $plupload)
1973    {
1974        $this->plupload = $plupload;
1975    }
1976
1977    /**
1978    * Function to perform custom bbcode validation by extensions
1979    * can be used in bbcode_init() to assign regexp replacement
1980    * Example: 'regexp' => array('#\[b\](.*?)\[/b\]#uise' => "\$this->validate_bbcode_by_extension('\$1')")
1981    *
1982    * Accepts variable number of parameters
1983    *
1984    * @return mixed Validation result
1985    */
1986    public function validate_bbcode_by_extension()
1987    {
1988        global $phpbb_dispatcher;
1989
1990        $return = false;
1991        $params_array = func_get_args();
1992
1993        /**
1994        * Event to validate bbcode with the custom validating methods
1995        * provided by extensions
1996        *
1997        * @event core.validate_bbcode_by_extension
1998        * @var array    params_array    Array with the function parameters
1999        * @var mixed    return            Validation result to return
2000        *
2001        * @since 3.1.5-RC1
2002        */
2003        $vars = array('params_array', 'return');
2004        extract($phpbb_dispatcher->trigger_event('core.validate_bbcode_by_extension', compact($vars)));
2005
2006        return $return;
2007    }
2008}