phpBB
Statistics
| Revision:

root / trunk / phpBB / includes / message_parser.php

History | View | Annotate | Download (48.8 kB)

1
<?php
2
/**
3
*
4
* @package phpBB3
5
* @copyright (c) 2005 phpBB Group
6
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
7
*
8
*/
9
10
/**
11
* @ignore
12
*/
13
if (!defined('IN_PHPBB'))
14
{
15
        exit;
16
}
17
18
if (!class_exists('bbcode'))
19
{
20
        include($phpbb_root_path . 'includes/bbcode.' . $phpEx);
21
}
22
23
/**
24
* BBCODE FIRSTPASS
25
* BBCODE first pass class (functions for parsing messages for db storage)
26
* @package phpBB3
27
*/
28
class bbcode_firstpass extends bbcode
29
{
30
        var $message = '';
31
        var $warn_msg = array();
32
        var $parsed_items = array();
33
34
        /**
35
        * Parse BBCode
36
        */
37
        function parse_bbcode()
38
        {
39
                if (!$this->bbcodes)
40
                {
41
                        $this->bbcode_init();
42
                }
43
44
                global $user;
45
46
                $this->bbcode_bitfield = '';
47
                $bitfield = new bitfield();
48
49
                foreach ($this->bbcodes as $bbcode_name => $bbcode_data)
50
                {
51
                        if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
52
                        {
53
                                foreach ($bbcode_data['regexp'] as $regexp => $replacement)
54
                                {
55
                                        if (preg_match($regexp, $this->message))
56
                                        {
57
                                                $this->warn_msg[] = sprintf($user->lang['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']');
58
                                                continue;
59
                                        }
60
                                }
61
                        }
62
                        else
63
                        {
64
                                foreach ($bbcode_data['regexp'] as $regexp => $replacement)
65
                                {
66
                                        // The pattern gets compiled and cached by the PCRE extension,
67
                                        // it should not demand recompilation
68
                                        if (preg_match($regexp, $this->message))
69
                                        {
70
                                                $this->message = preg_replace($regexp, $replacement, $this->message);
71
                                                $bitfield->set($bbcode_data['bbcode_id']);
72
                                        }
73
                                }
74
                        }
75
                }
76
77
                $this->bbcode_bitfield = $bitfield->get_base64();
78
        }
79
80
        /**
81
        * Prepare some bbcodes for better parsing
82
        */
83
        function prepare_bbcodes()
84
        {
85
                // 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".
86
                // Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you.
87
88
                /* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
89
                if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false)
90
                {
91
                        $this->message = str_replace("\r\n", "\n", $this->message);
92
93
                        // We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
94
                        $this->message = preg_replace('#\[quote(=&quot;.*?&quot;)?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message);
95
                }
96
                */
97
98
                // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
99
        }
100
101
        /**
102
        * Init bbcode data for later parsing
103
        */
104
        function bbcode_init($allow_custom_bbcode = true)
105
        {
106
                static $rowset;
107
108
                // This array holds all bbcode data. BBCodes will be processed in this
109
                // order, so it is important to keep [code] in first position and
110
                // [quote] in second position.
111
                // To parse multiline URL we enable dotall option setting only for URL text
112
                // but not for link itself, thus [url][/url] is not affected.
113
                $this->bbcodes = array(
114
                        'code'                        => array('bbcode_id' => 8,        'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#uise' => "\$this->bbcode_code('\$1', '\$2')")),
115
                        'quote'                        => array('bbcode_id' => 0,        'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#uise' => "\$this->bbcode_quote('\$0')")),
116
                        'attachment'        => array('bbcode_id' => 12,        'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#uise' => "\$this->bbcode_attachment('\$1', '\$2')")),
117
                        'b'                                => array('bbcode_id' => 1,        'regexp' => array('#\[b\](.*?)\[/b\]#uise' => "\$this->bbcode_strong('\$1')")),
118
                        'i'                                => array('bbcode_id' => 2,        'regexp' => array('#\[i\](.*?)\[/i\]#uise' => "\$this->bbcode_italic('\$1')")),
119
                        'url'                        => array('bbcode_id' => 3,        'regexp' => array('#\[url(=(.*))?\](?(1)((?s).*(?-s))|(.*))\[/url\]#uiUe' => "\$this->validate_url('\$2', ('\$3') ? '\$3' : '\$4')")),
120
                        'img'                        => array('bbcode_id' => 4,        'regexp' => array('#\[img\](.*)\[/img\]#uiUe' => "\$this->bbcode_img('\$1')")),
121
                        'size'                        => array('bbcode_id' => 5,        'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#uise' => "\$this->bbcode_size('\$1', '\$2')")),
122
                        'color'                        => array('bbcode_id' => 6,        'regexp' => array('!\[color=(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!uise' => "\$this->bbcode_color('\$1', '\$2')")),
123
                        'u'                                => array('bbcode_id' => 7,        'regexp' => array('#\[u\](.*?)\[/u\]#uise' => "\$this->bbcode_underline('\$1')")),
124
                        'list'                        => array('bbcode_id' => 9,        'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#uise' => "\$this->bbcode_parse_list('\$0')")),
125
                        'email'                        => array('bbcode_id' => 10,        'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#uise' => "\$this->validate_email('\$1', '\$2')")),
126
                        'flash'                        => array('bbcode_id' => 11,        'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#uie' => "\$this->bbcode_flash('\$1', '\$2', '\$3')"))
127
                );
128
129
                // Zero the parsed items array
130
                $this->parsed_items = array();
131
132
                foreach ($this->bbcodes as $tag => $bbcode_data)
133
                {
134
                        $this->parsed_items[$tag] = 0;
135
                }
136
137
                if (!$allow_custom_bbcode)
138
                {
139
                        return;
140
                }
141
142
                if (!is_array($rowset))
143
                {
144
                        global $db;
145
                        $rowset = array();
146
147
                        $sql = 'SELECT *
148
                                FROM ' . BBCODES_TABLE;
149
                        $result = $db->sql_query($sql);
150
151
                        while ($row = $db->sql_fetchrow($result))
152
                        {
153
                                $rowset[] = $row;
154
                        }
155
                        $db->sql_freeresult($result);
156
                }
157
158
                foreach ($rowset as $row)
159
                {
160
                        $this->bbcodes[$row['bbcode_tag']] = array(
161
                                'bbcode_id'        => (int) $row['bbcode_id'],
162
                                'regexp'        => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
163
                        );
164
                }
165
        }
166
167
        /**
168
        * Making some pre-checks for bbcodes as well as increasing the number of parsed items
169
        */
170
        function check_bbcode($bbcode, &$in)
171
        {
172
                // when using the /e modifier, preg_replace slashes double-quotes but does not
173
                // seem to slash anything else
174
                $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));
175
176
                // Trimming here to make sure no empty bbcodes are parsed accidently
177
                if (trim($in) == '')
178
                {
179
                        return false;
180
                }
181
182
                $this->parsed_items[$bbcode]++;
183
184
                return true;
185
        }
186
187
        /**
188
        * Transform some characters in valid bbcodes
189
        */
190
        function bbcode_specialchars($text)
191
        {
192
                $str_from = array('<', '>', '[', ']', '.', ':');
193
                $str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');
194
195
                return str_replace($str_from, $str_to, $text);
196
        }
197
198
        /**
199
        * Parse size tag
200
        */
201
        function bbcode_size($stx, $in)
202
        {
203
                global $user, $config;
204
205
                if (!$this->check_bbcode('size', $in))
206
                {
207
                        return $in;
208
                }
209
210
                if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
211
                {
212
                        $this->warn_msg[] = $user->lang('MAX_FONT_SIZE_EXCEEDED', (int) $config['max_' . $this->mode . '_font_size']);
213
214
                        return '[size=' . $stx . ']' . $in . '[/size]';
215
                }
216
217
                // Do not allow size=0
218
                if ($stx <= 0)
219
                {
220
                        return '[size=' . $stx . ']' . $in . '[/size]';
221
                }
222
223
                return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
224
        }
225
226
        /**
227
        * Parse color tag
228
        */
229
        function bbcode_color($stx, $in)
230
        {
231
                if (!$this->check_bbcode('color', $in))
232
                {
233
                        return $in;
234
                }
235
236
                return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
237
        }
238
239
        /**
240
        * Parse u tag
241
        */
242
        function bbcode_underline($in)
243
        {
244
                if (!$this->check_bbcode('u', $in))
245
                {
246
                        return $in;
247
                }
248
249
                return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
250
        }
251
252
        /**
253
        * Parse b tag
254
        */
255
        function bbcode_strong($in)
256
        {
257
                if (!$this->check_bbcode('b', $in))
258
                {
259
                        return $in;
260
                }
261
262
                return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
263
        }
264
265
        /**
266
        * Parse i tag
267
        */
268
        function bbcode_italic($in)
269
        {
270
                if (!$this->check_bbcode('i', $in))
271
                {
272
                        return $in;
273
                }
274
275
                return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
276
        }
277
278
        /**
279
        * Parse img tag
280
        */
281
        function bbcode_img($in)
282
        {
283
                global $user, $config;
284
285
                if (!$this->check_bbcode('img', $in))
286
                {
287
                        return $in;
288
                }
289
290
                $in = trim($in);
291
                $error = false;
292
293
                $in = str_replace(' ', '%20', $in);
294
295
                // Checking urls
296
                if (!preg_match('#^' . get_preg_expression('url') . '$#i', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#i', $in))
297
                {
298
                        return '[img]' . $in . '[/img]';
299
                }
300
301
                // Try to cope with a common user error... not specifying a protocol but only a subdomain
302
                if (!preg_match('#^[a-z0-9]+://#i', $in))
303
                {
304
                        $in = 'http://' . $in;
305
                }
306
307
                if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
308
                {
309
                        $stats = @getimagesize(htmlspecialchars_decode($in));
310
311
                        if ($stats === false)
312
                        {
313
                                $error = true;
314
                                $this->warn_msg[] = $user->lang['UNABLE_GET_IMAGE_SIZE'];
315
                        }
316
                        else
317
                        {
318
                                if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $stats[1])
319
                                {
320
                                        $error = true;
321
                                        $this->warn_msg[] = $user->lang('MAX_IMG_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);
322
                                }
323
324
                                if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $stats[0])
325
                                {
326
                                        $error = true;
327
                                        $this->warn_msg[] = $user->lang('MAX_IMG_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);
328
                                }
329
                        }
330
                }
331
332
                if ($error || $this->path_in_domain($in))
333
                {
334
                        return '[img]' . $in . '[/img]';
335
                }
336
337
                return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
338
        }
339
340
        /**
341
        * Parse flash tag
342
        */
343
        function bbcode_flash($width, $height, $in)
344
        {
345
                global $user, $config;
346
347
                if (!$this->check_bbcode('flash', $in))
348
                {
349
                        return $in;
350
                }
351
352
                $in = trim($in);
353
                $error = false;
354
355
                // Do not allow 0-sizes generally being entered
356
                if ($width <= 0 || $height <= 0)
357
                {
358
                        return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
359
                }
360
361
                $in = str_replace(' ', '%20', $in);
362
363
                // Make sure $in is a URL.
364
                if (!preg_match('#^' . get_preg_expression('url') . '$#i', $in) &&
365
                        !preg_match('#^' . get_preg_expression('www_url') . '$#i', $in))
366
                {
367
                        return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
368
                }
369
370
                // Apply the same size checks on flash files as on images
371
                if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
372
                {
373
                        if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)
374
                        {
375
                                $error = true;
376
                                $this->warn_msg[] = $user->lang('MAX_FLASH_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);
377
                        }
378
379
                        if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)
380
                        {
381
                                $error = true;
382
                                $this->warn_msg[] = $user->lang('MAX_FLASH_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);
383
                        }
384
                }
385
386
                if ($error || $this->path_in_domain($in))
387
                {
388
                        return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
389
                }
390
391
                return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
392
        }
393
394
        /**
395
        * Parse inline attachments [ia]
396
        */
397
        function bbcode_attachment($stx, $in)
398
        {
399
                if (!$this->check_bbcode('attachment', $in))
400
                {
401
                        return $in;
402
                }
403
404
                return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
405
        }
406
407
        /**
408
        * Parse code text from code tag
409
        * @access private
410
        */
411
        function bbcode_parse_code($stx, &$code)
412
        {
413
                switch (strtolower($stx))
414
                {
415
                        case 'php':
416
417
                                $remove_tags = false;
418
419
                                $str_from = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;', '&#058;');
420
                                $str_to = array('<', '>', '[', ']', '.', ':', ':');
421
                                $code = str_replace($str_from, $str_to, $code);
422
423
                                if (!preg_match('/\<\?.*?\?\>/is', $code))
424
                                {
425
                                        $remove_tags = true;
426
                                        $code = "<?php $code ?>";
427
                                }
428
429
                                $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
430
                                foreach ($conf as $ini_var)
431
                                {
432
                                        @ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
433
                                }
434
435
                                // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
436
                                $code = htmlspecialchars_decode($code);
437
                                $code = highlight_string($code, true);
438
439
                                $str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
440
                                $str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');
441
442
                                if ($remove_tags)
443
                                {
444
                                        $str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
445
                                        $str_to[] = '';
446
                                        $str_from[] = '<span class="syntaxdefault">&lt;?php&nbsp;';
447
                                        $str_to[] = '<span class="syntaxdefault">';
448
                                }
449
450
                                $code = str_replace($str_from, $str_to, $code);
451
                                $code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);
452
453
                                if ($remove_tags)
454
                                {
455
                                        $code = preg_replace('#(<span class="[a-z]+">)?\?&gt;(</span>)#', '$1&nbsp;$2', $code);
456
                                }
457
458
                                $code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
459
                                $code = preg_replace('#(?:\s++|&nbsp;)*+</span>$#u', '</span>', $code);
460
461
                                // remove newline at the end
462
                                if (!empty($code) && substr($code, -1) == "\n")
463
                                {
464
                                        $code = substr($code, 0, -1);
465
                                }
466
467
                                return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
468
                        break;
469
470
                        default:
471
                                return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
472
                        break;
473
                }
474
        }
475
476
        /**
477
        * Parse code tag
478
        * Expects the argument to start right after the opening [code] tag and to end with [/code]
479
        */
480
        function bbcode_code($stx, $in)
481
        {
482
                if (!$this->check_bbcode('code', $in))
483
                {
484
                        return $in;
485
                }
486
487
                // We remove the hardcoded elements from the code block here because it is not used in code blocks
488
                // Having it here saves us one preg_replace per message containing [code] blocks
489
                // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
490
                $htm_match = get_preg_expression('bbcode_htm');
491
                unset($htm_match[4], $htm_match[5]);
492
                $htm_replace = array('\1', '\1', '\2', '\1');
493
494
                $out = $code_block = '';
495
                $open = 1;
496
497
                while ($in)
498
                {
499
                        // Determine position and tag length of next code block
500
                        preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
501
                        $pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
502
                        $tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;
503
504
                        // Determine position of ending code tag
505
                        $pos2 = stripos($in, '[/code]');
506
507
                        // Which is the next block, ending code or code block
508
                        if ($pos !== false && $pos < $pos2)
509
                        {
510
                                // Open new block
511
                                if (!$open)
512
                                {
513
                                        $out .= substr($in, 0, $pos);
514
                                        $in = substr($in, $pos);
515
                                        $stx = (isset($buffer[3])) ? $buffer[3] : '';
516
                                        $code_block = '';
517
                                }
518
                                else
519
                                {
520
                                        // Already opened block, just append to the current block
521
                                        $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
522
                                        $in = substr($in, $pos);
523
                                }
524
525
                                $in = substr($in, $tag_length);
526
                                $open++;
527
                        }
528
                        else
529
                        {
530
                                // Close the block
531
                                if ($open == 1)
532
                                {
533
                                        $code_block .= substr($in, 0, $pos2);
534
                                        $code_block = preg_replace($htm_match, $htm_replace, $code_block);
535
536
                                        // Parse this code block
537
                                        $out .= $this->bbcode_parse_code($stx, $code_block);
538
                                        $code_block = '';
539
                                        $open--;
540
                                }
541
                                else if ($open)
542
                                {
543
                                        // Close one open tag... add to the current code block
544
                                        $code_block .= substr($in, 0, $pos2 + 7);
545
                                        $open--;
546
                                }
547
                                else
548
                                {
549
                                        // end code without opening code... will be always outside code block
550
                                        $out .= substr($in, 0, $pos2 + 7);
551
                                }
552
553
                                $in = substr($in, $pos2 + 7);
554
                        }
555
                }
556
557
                // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
558
                if ($code_block)
559
                {
560
                        $code_block = substr($code_block, 0, -7);
561
                        $code_block = preg_replace($htm_match, $htm_replace, $code_block);
562
563
                        $out .= $this->bbcode_parse_code($stx, $code_block);
564
                }
565
566
                return $out;
567
        }
568
569
        /**
570
        * Parse list bbcode
571
        * Expects the argument to start with a tag
572
        */
573
        function bbcode_parse_list($in)
574
        {
575
                if (!$this->check_bbcode('list', $in))
576
                {
577
                        return $in;
578
                }
579
580
                // $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
581
                $tok = ']';
582
                $out = '[';
583
584
                // First character is [
585
                $in = substr($in, 1);
586
                $list_end_tags = $item_end_tags = array();
587
588
                do
589
                {
590
                        $pos = strlen($in);
591
592
                        for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
593
                        {
594
                                $tmp_pos = strpos($in, $tok[$i]);
595
596
                                if ($tmp_pos !== false && $tmp_pos < $pos)
597
                                {
598
                                        $pos = $tmp_pos;
599
                                }
600
                        }
601
602
                        $buffer = substr($in, 0, $pos);
603
                        $tok = $in[$pos];
604
605
                        $in = substr($in, $pos + 1);
606
607
                        if ($tok == ']')
608
                        {
609
                                // if $tok is ']' the buffer holds a tag
610
                                if (strtolower($buffer) == '/list' && sizeof($list_end_tags))
611
                                {
612
                                        // valid [/list] tag, check nesting so that we don't hit false positives
613
                                        if (sizeof($item_end_tags) && sizeof($item_end_tags) >= sizeof($list_end_tags))
614
                                        {
615
                                                // current li tag has not been closed
616
                                                $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
617
                                        }
618
619
                                        $out .= array_pop($list_end_tags) . ']';
620
                                        $tok = '[';
621
                                }
622
                                else if (preg_match('#^list(=[0-9a-z]+)?$#i', $buffer, $m))
623
                                {
624
                                        // sub-list, add a closing tag
625
                                        if (empty($m[1]) || preg_match('/^=(?:disc|square|circle)$/i', $m[1]))
626
                                        {
627
                                                array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
628
                                        }
629
                                        else
630
                                        {
631
                                                array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
632
                                        }
633
                                        $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
634
                                        $tok = '[';
635
                                }
636
                                else
637
                                {
638
                                        if (($buffer == '*' || substr($buffer, -2) == '[*') && sizeof($list_end_tags))
639
                                        {
640
                                                // the buffer holds a bullet tag and we have a [list] tag open
641
                                                if (sizeof($item_end_tags) >= sizeof($list_end_tags))
642
                                                {
643
                                                        if (substr($buffer, -2) == '[*')
644
                                                        {
645
                                                                $out .= substr($buffer, 0, -2) . '[';
646
                                                        }
647
                                                        // current li tag has not been closed
648
                                                        if (preg_match('/\n\[$/', $out, $m))
649
                                                        {
650
                                                                $out = preg_replace('/\n\[$/', '[', $out);
651
                                                                $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
652
                                                        }
653
                                                        else
654
                                                        {
655
                                                                $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
656
                                                        }
657
                                                }
658
                                                else
659
                                                {
660
                                                        $buffer = '*:' . $this->bbcode_uid;
661
                                                }
662
663
                                                $item_end_tags[] = '/*:m:' . $this->bbcode_uid;
664
                                        }
665
                                        else if ($buffer == '/*')
666
                                        {
667
                                                array_pop($item_end_tags);
668
                                                $buffer = '/*:' . $this->bbcode_uid;
669
                                        }
670
671
                                        $out .= $buffer . $tok;
672
                                        $tok = '[]';
673
                                }
674
                        }
675
                        else
676
                        {
677
                                // Not within a tag, just add buffer to the return string
678
                                $out .= $buffer . $tok;
679
                                $tok = ($tok == '[') ? ']' : '[]';
680
                        }
681
                }
682
                while ($in);
683
684
                // do we have some tags open? close them now
685
                if (sizeof($item_end_tags))
686
                {
687
                        $out .= '[' . implode('][', $item_end_tags) . ']';
688
                }
689
                if (sizeof($list_end_tags))
690
                {
691
                        $out .= '[' . implode('][', $list_end_tags) . ']';
692
                }
693
694
                return $out;
695
        }
696
697
        /**
698
        * Parse quote bbcode
699
        * Expects the argument to start with a tag
700
        */
701
        function bbcode_quote($in)
702
        {
703
                global $config, $user;
704
705
                /**
706
                * If you change this code, make sure the cases described within the following reports are still working:
707
                * #3572 - [quote="[test]test"]test [ test[/quote] - (correct: parsed)
708
                * #14667 - [quote]test[/quote] test ] and [ test [quote]test[/quote] (correct: parsed)
709
                * #14770 - [quote="["]test[/quote] (correct: parsed)
710
                * [quote="[i]test[/i]"]test[/quote] (correct: parsed)
711
                * [quote="[quote]test[/quote]"]test[/quote] (correct: parsed - Username displayed as [quote]test[/quote])
712
                * #20735 - [quote]test[/[/b]quote] test [/quote][/quote] test - (correct: quoted: "test[/[/b]quote] test" / non-quoted: "[/quote] test" - also failed if layout distorted)
713
                * #40565 - [quote="a"]a[/quote][quote="a]a[/quote] (correct: first quote tag parsed, second quote tag unparsed)
714
                */
715
716
                $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));
717
718
                if (!$in)
719
                {
720
                        return '';
721
                }
722
723
                // To let the parser not catch tokens within quote_username quotes we encode them before we start this...
724
                $in = preg_replace('#quote=&quot;(.*?)&quot;\]#ie', "'quote=&quot;' . str_replace(array('[', ']', '\\\"'), array('&#91;', '&#93;', '\"'), '\$1') . '&quot;]'", $in);
725
726
                $tok = ']';
727
                $out = '[';
728
729
                $in = substr($in, 1);
730
                $close_tags = $error_ary = array();
731
                $buffer = '';
732
733
                do
734
                {
735
                        $pos = strlen($in);
736
                        for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
737
                        {
738
                                $tmp_pos = strpos($in, $tok[$i]);
739
                                if ($tmp_pos !== false && $tmp_pos < $pos)
740
                                {
741
                                        $pos = $tmp_pos;
742
                                }
743
                        }
744
745
                        $buffer .= substr($in, 0, $pos);
746
                        $tok = $in[$pos];
747
                        $in = substr($in, $pos + 1);
748
749
                        if ($tok == ']')
750
                        {
751
                                if (strtolower($buffer) == '/quote' && sizeof($close_tags) && substr($out, -1, 1) == '[')
752
                                {
753
                                        // we have found a closing tag
754
                                        $out .= array_pop($close_tags) . ']';
755
                                        $tok = '[';
756
                                        $buffer = '';
757
758
                                        /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
759
                                        * 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.
760
                                        * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
761
                                        if (!$in || $in[0] !== ' ')
762
                                        {
763
                                                $out .= ' ';
764
                                        }*/
765
                                }
766
                                else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
767
                                {
768
                                        $this->parsed_items['quote']++;
769
770
                                        // the buffer holds a valid opening tag
771
                                        if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])
772
                                        {
773
                                                // there are too many nested quotes
774
                                                $error_ary['quote_depth'] = $user->lang('QUOTE_DEPTH_EXCEEDED', (int) $config['max_quote_depth']);
775
776
                                                $out .= $buffer . $tok;
777
                                                $tok = '[]';
778
                                                $buffer = '';
779
780
                                                continue;
781
                                        }
782
783
                                        array_push($close_tags, '/quote:' . $this->bbcode_uid);
784
785
                                        if (isset($m[1]) && $m[1])
786
                                        {
787
                                                $username = str_replace(array('&#91;', '&#93;'), array('[', ']'), $m[1]);
788
                                                $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $username);
789
790
                                                $end_tags = array();
791
                                                $error = false;
792
793
                                                preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
794
                                                foreach ($tags[1] as $tag)
795
                                                {
796
                                                        if ($tag[0] != '/')
797
                                                        {
798
                                                                $end_tags[] = '/' . $tag;
799
                                                        }
800
                                                        else
801
                                                        {
802
                                                                $end_tag = array_pop($end_tags);
803
                                                                $error = ($end_tag != $tag) ? true : false;
804
                                                        }
805
                                                }
806
807
                                                if ($error)
808
                                                {
809
                                                        $username = $m[1];
810
                                                }
811
812
                                                $out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
813
                                        }
814
                                        else
815
                                        {
816
                                                $out .= 'quote:' . $this->bbcode_uid . ']';
817
                                        }
818
819
                                        $tok = '[';
820
                                        $buffer = '';
821
                                }
822
                                else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
823
                                {
824
                                        // the buffer holds an invalid opening tag
825
                                        $buffer .= ']';
826
                                }
827
                                else
828
                                {
829
                                        $out .= $buffer . $tok;
830
                                        $tok = '[]';
831
                                        $buffer = '';
832
                                }
833
                        }
834
                        else
835
                        {
836
/**
837
*                                Old quote code working fine, but having errors listed in bug #3572
838
*
839
*                                $out .= $buffer . $tok;
840
*                                $tok = ($tok == '[') ? ']' : '[]';
841
*                                $buffer = '';
842
*/
843
844
                                $out .= $buffer . $tok;
845
846
                                if ($tok == '[')
847
                                {
848
                                        // Search the text for the next tok... if an ending quote comes first, then change tok to []
849
                                        $pos1 = stripos($in, '[/quote');
850
                                        // If the token ] comes first, we change it to ]
851
                                        $pos2 = strpos($in, ']');
852
                                        // If the token [ comes first, we change it to [
853
                                        $pos3 = strpos($in, '[');
854
855
                                        if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
856
                                        {
857
                                                $tok = '[]';
858
                                        }
859
                                        else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
860
                                        {
861
                                                $tok = '[';
862
                                        }
863
                                        else
864
                                        {
865
                                                $tok = ']';
866
                                        }
867
                                }
868
                                else
869
                                {
870
                                        $tok = '[]';
871
                                }
872
                                $buffer = '';
873
                        }
874
                }
875
                while ($in);
876
877
                $out .= $buffer;
878
879
                if (sizeof($close_tags))
880
                {
881
                        $out .= '[' . implode('][', $close_tags) . ']';
882
                }
883
884
                foreach ($error_ary as $error_msg)
885
                {
886
                        $this->warn_msg[] = $error_msg;
887
                }
888
889
                return $out;
890
        }
891
892
        /**
893
        * Validate email
894
        */
895
        function validate_email($var1, $var2)
896
        {
897
                $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
898
                $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
899
900
                $txt = $var2;
901
                $email = ($var1) ? $var1 : $var2;
902
903
                $validated = true;
904
905
                if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
906
                {
907
                        $validated = false;
908
                }
909
910
                if (!$validated)
911
                {
912
                        return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
913
                }
914
915
                $this->parsed_items['email']++;
916
917
                if ($var1)
918
                {
919
                        $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
920
                }
921
                else
922
                {
923
                        $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
924
                }
925
926
                return $retval;
927
        }
928
929
        /**
930
        * Validate url
931
        *
932
        * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
933
        * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
934
        */
935
        function validate_url($var1, $var2)
936
        {
937
                global $config;
938
939
                $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
940
                $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));
941
942
                $url = ($var1) ? $var1 : $var2;
943
944
                if ($var1 && !$var2)
945
                {
946
                        $var2 = $var1;
947
                }
948
949
                if (!$url)
950
                {
951
                        return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
952
                }
953
954
                $valid = false;
955
956
                $url = str_replace(' ', '%20', $url);
957
958
                // Checking urls
959
                if (preg_match('#^' . get_preg_expression('url') . '$#i', $url) ||
960
                        preg_match('#^' . get_preg_expression('www_url') . '$#i', $url) ||
961
                        preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#i', $url))
962
                {
963
                        $valid = true;
964
                }
965
966
                if ($valid)
967
                {
968
                        $this->parsed_items['url']++;
969
970
                        // if there is no scheme, then add http schema
971
                        if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
972
                        {
973
                                $url = 'http://' . $url;
974
                        }
975
976
                        // Is this a link to somewhere inside this board? If so then remove the session id from the url
977
                        if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
978
                        {
979
                                $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}&amp;/', '\1', $url);
980
                                $url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}$/', '', $url);
981
                                $url = append_sid($url);
982
                        }
983
984
                        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 . ']';
985
                }
986
987
                return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
988
        }
989
990
        /**
991
        * Check if url is pointing to this domain/script_path/php-file
992
        *
993
        * @param string $url the url to check
994
        * @return true if the url is pointing to this domain/script_path/php-file, false if not
995
        *
996
        * @access private
997
        */
998
        function path_in_domain($url)
999
        {
1000
                global $config, $phpEx, $user;
1001
1002
                if ($config['force_server_vars'])
1003
                {
1004
                        $check_path = $config['script_path'];
1005
                }
1006
                else
1007
                {
1008
                        $check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
1009
                }
1010
1011
                // Is the user trying to link to a php file in this domain and script path?
1012
                if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false)
1013
                {
1014
                        $server_name = $user->host;
1015
1016
                        // Forcing server vars is the only way to specify/override the protocol
1017
                        if ($config['force_server_vars'] || !$server_name)
1018
                        {
1019
                                $server_name = $config['server_name'];
1020
                        }
1021
1022
                        // Check again in correct order...
1023
                        $pos_ext = strpos($url, ".{$phpEx}");
1024
                        $pos_path = strpos($url, $check_path);
1025
                        $pos_domain = strpos($url, $server_name);
1026
1027
                        if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
1028
                        {
1029
                                // Ok, actually we allow linking to some files (this may be able to be extended in some way later...)
1030
                                if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0)
1031
                                {
1032
                                        return false;
1033
                                }
1034
1035
                                return true;
1036
                        }
1037
                }
1038
1039
                return false;
1040
        }
1041
}
1042
1043
/**
1044
* Main message parser for posting, pm, etc. takes raw message
1045
* and parses it for attachments, bbcode and smilies
1046
* @package phpBB3
1047
*/
1048
class parse_message extends bbcode_firstpass
1049
{
1050
        var $attachment_data = array();
1051
        var $filename_data = array();
1052
1053
        // Helps ironing out user error
1054
        var $message_status = '';
1055
1056
        var $allow_img_bbcode = true;
1057
        var $allow_flash_bbcode = true;
1058
        var $allow_quote_bbcode = true;
1059
        var $allow_url_bbcode = true;
1060
1061
        var $mode;
1062
1063
        /**
1064
        * Init - give message here or manually
1065
        */
1066
        function parse_message($message = '')
1067
        {
1068
                // Init BBCode UID
1069
                $this->bbcode_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN);
1070
                $this->message = $message;
1071
        }
1072
1073
        /**
1074
        * Parse Message
1075
        */
1076
        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')
1077
        {
1078
                global $config, $db, $user;
1079
1080
                $this->mode = $mode;
1081
1082
                foreach (array('chars', 'smilies', 'urls', 'font_size', 'img_height', 'img_width') as $key)
1083
                {
1084
                        if (!isset($config['max_' . $mode . '_' . $key]))
1085
                        {
1086
                                $config['max_' . $mode . '_' . $key] = 0;
1087
                        }
1088
                }
1089
1090
                $this->allow_img_bbcode = $allow_img_bbcode;
1091
                $this->allow_flash_bbcode = $allow_flash_bbcode;
1092
                $this->allow_quote_bbcode = $allow_quote_bbcode;
1093
                $this->allow_url_bbcode = $allow_url_bbcode;
1094
1095
                // If false, then $this->message won't be altered, the text will be returned instead.
1096
                if (!$update_this_message)
1097
                {
1098
                        $tmp_message = $this->message;
1099
                        $return_message = &$this->message;
1100
                }
1101
1102
                if ($this->message_status == 'display')
1103
                {
1104
                        $this->decode_message();
1105
                }
1106
1107
                // Do some general 'cleanup' first before processing message,
1108
                // e.g. remove excessive newlines(?), smilies(?)
1109
                $match = array('#(script|about|applet|activex|chrome):#i');
1110
                $replace = array("\\1&#058;");
1111
                $this->message = preg_replace($match, $replace, trim($this->message));
1112
1113
                // Store message length...
1114
                $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message));
1115
1116
                // Maximum message length check. 0 disables this check completely.
1117
                if ((int) $config['max_' . $mode . '_chars'] > 0 && $message_length > (int) $config['max_' . $mode . '_chars'])
1118
                {
1119
                        $this->warn_msg[] = $user->lang('TOO_MANY_CHARS_' . strtoupper($mode), $message_length, (int) $config['max_' . $mode . '_chars']);
1120
                        return (!$update_this_message) ? $return_message : $this->warn_msg;
1121
                }
1122
1123
                // Minimum message length check for post only
1124
                if ($mode === 'post')
1125
                {
1126
                        if (!$message_length || $message_length < (int) $config['min_post_chars'])
1127
                        {
1128
                                $this->warn_msg[] = (!$message_length) ? $user->lang['TOO_FEW_CHARS'] : $user->lang('TOO_FEW_CHARS_LIMIT', $message_length, (int) $config['min_post_chars']);
1129
                                return (!$update_this_message) ? $return_message : $this->warn_msg;
1130
                        }
1131
                }
1132
1133
                // Prepare BBcode (just prepares some tags for better parsing)
1134
                if ($allow_bbcode && strpos($this->message, '[') !== false)
1135
                {
1136
                        $this->bbcode_init();
1137
                        $disallow = array('img', 'flash', 'quote', 'url');
1138
                        foreach ($disallow as $bool)
1139
                        {
1140
                                if (!${'allow_' . $bool . '_bbcode'})
1141
                                {
1142
                                        $this->bbcodes[$bool]['disabled'] = true;
1143
                                }
1144
                        }
1145
1146
                        $this->prepare_bbcodes();
1147
                }
1148
1149
                // Parse smilies
1150
                if ($allow_smilies)
1151
                {
1152
                        $this->smilies($config['max_' . $mode . '_smilies']);
1153
                }
1154
1155
                $num_urls = 0;
1156
1157
                // Parse BBCode
1158
                if ($allow_bbcode && strpos($this->message, '[') !== false)
1159
                {
1160
                        $this->parse_bbcode();
1161
                        $num_urls += $this->parsed_items['url'];
1162
                }
1163
1164
                // Parse URL's
1165
                if ($allow_magic_url)
1166
                {
1167
                        $this->magic_url(generate_board_url());
1168
1169
                        if ($config['max_' . $mode . '_urls'])
1170
                        {
1171
                                $num_urls += preg_match_all('#\<!-- ([lmwe]) --\>.*?\<!-- \1 --\>#', $this->message, $matches);
1172
                        }
1173
                }
1174
1175
                // Check for "empty" message. We do not check here for maximum length, because bbcode, smilies, etc. can add to the length.
1176
                // The maximum length check happened before any parsings.
1177
                if ($mode === 'post' && utf8_clean_string($this->message) === '')
1178
                {
1179
                        $this->warn_msg[] = $user->lang['TOO_FEW_CHARS'];
1180
                        return (!$update_this_message) ? $return_message : $this->warn_msg;
1181
                }
1182
1183
                // Check number of links
1184
                if ($config['max_' . $mode . '_urls'] && $num_urls > $config['max_' . $mode . '_urls'])
1185
                {
1186
                        $this->warn_msg[] = sprintf($user->lang['TOO_MANY_URLS'], $config['max_' . $mode . '_urls']);
1187
                        return (!$update_this_message) ? $return_message : $this->warn_msg;
1188
                }
1189
1190
                if (!$update_this_message)
1191
                {
1192
                        unset($this->message);
1193
                        $this->message = $tmp_message;
1194
                        return $return_message;
1195
                }
1196
1197
                $this->message_status = 'parsed';
1198
                return false;
1199
        }
1200
1201
        /**
1202
        * Formatting text for display
1203
        */
1204
        function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
1205
        {
1206
                // If false, then the parsed message get returned but internal message not processed.
1207
                if (!$update_this_message)
1208
                {
1209
                        $tmp_message = $this->message;
1210
                        $return_message = &$this->message;
1211
                }
1212
1213
                if ($this->message_status == 'plain')
1214
                {
1215
                        // Force updating message - of course.
1216
                        $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);
1217
                }
1218
1219
                // Replace naughty words such as farty pants
1220
                $this->message = censor_text($this->message);
1221
1222
                // Parse BBcode
1223
                if ($allow_bbcode)
1224
                {
1225
                        $this->bbcode_cache_init();
1226
1227
                        // We are giving those parameters to be able to use the bbcode class on its own
1228
                        $this->bbcode_second_pass($this->message, $this->bbcode_uid);
1229
                }
1230
1231
                $this->message = bbcode_nl2br($this->message);
1232
                $this->message = smiley_text($this->message, !$allow_smilies);
1233
1234
                if (!$update_this_message)
1235
                {
1236
                        unset($this->message);
1237
                        $this->message = $tmp_message;
1238
                        return $return_message;
1239
                }
1240
1241
                $this->message_status = 'display';
1242
                return false;
1243
        }
1244
1245
        /**
1246
        * Decode message to be placed back into form box
1247
        */
1248
        function decode_message($custom_bbcode_uid = '', $update_this_message = true)
1249
        {
1250
                // If false, then the parsed message get returned but internal message not processed.
1251
                if (!$update_this_message)
1252
                {
1253
                        $tmp_message = $this->message;
1254
                        $return_message = &$this->message;
1255
                }
1256
1257
                ($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);
1258
1259
                if (!$update_this_message)
1260
                {
1261
                        unset($this->message);
1262
                        $this->message = $tmp_message;
1263
                        return $return_message;
1264
                }
1265
1266
                $this->message_status = 'plain';
1267
                return false;
1268
        }
1269
1270
        /**
1271
        * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
1272
        * Cuts down displayed size of link if over 50 chars, turns absolute links
1273
        * into relative versions when the server/script path matches the link
1274
        */
1275
        function magic_url($server_url)
1276
        {
1277
                // We use the global make_clickable function
1278
                $this->message = make_clickable($this->message, $server_url);
1279
        }
1280
1281
        /**
1282
        * Parse Smilies
1283
        */
1284
        function smilies($max_smilies = 0)
1285
        {
1286
                global $db, $user;
1287
                static $match;
1288
                static $replace;
1289
1290
                // See if the static arrays have already been filled on an earlier invocation
1291
                if (!is_array($match))
1292
                {
1293
                        $match = $replace = array();
1294
1295
                        // NOTE: obtain_* function? chaching the table contents?
1296
1297
                        // For now setting the ttl to 10 minutes
1298
                        switch ($db->sql_layer)
1299
                        {
1300
                                case 'mssql':
1301
                                case 'mssql_odbc':
1302
                                case 'mssqlnative':
1303
                                        $sql = 'SELECT *
1304
                                                FROM ' . SMILIES_TABLE . '
1305
                                                ORDER BY LEN(code) DESC';
1306
                                break;
1307
1308
                                case 'firebird':
1309
                                        $sql = 'SELECT *
1310
                                                FROM ' . SMILIES_TABLE . '
1311
                                                ORDER BY CHAR_LENGTH(code) DESC';
1312
                                break;
1313
1314
                                // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure...
1315
                                default:
1316
                                        $sql = 'SELECT *
1317
                                                FROM ' . SMILIES_TABLE . '
1318
                                                ORDER BY LENGTH(code) DESC';
1319
                                break;
1320
                        }
1321
                        $result = $db->sql_query($sql, 600);
1322
1323
                        while ($row = $db->sql_fetchrow($result))
1324
                        {
1325
                                if (empty($row['code']))
1326
                                {
1327
                                        continue;
1328
                                }
1329
1330
                                // (assertion)
1331
                                $match[] = preg_quote($row['code'], '#');
1332
                                $replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
1333
                        }
1334
                        $db->sql_freeresult($result);
1335
                }
1336
1337
                if (sizeof($match))
1338
                {
1339
                        if ($max_smilies)
1340
                        {
1341
                                // 'u' modifier has been added to correctly parse smilies within unicode strings
1342
                                // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
1343
                                $num_matches = preg_match_all('#(?<=^|[\n .])(?:' . implode('|', $match) . ')(?![^<>]*>)#u', $this->message, $matches);
1344
                                unset($matches);
1345
1346
                                if ($num_matches !== false && $num_matches > $max_smilies)
1347
                                {
1348
                                        $this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
1349
                                        return;
1350
                                }
1351
                        }
1352
1353
                        // Make sure the delimiter # is added in front and at the end of every element within $match
1354
                        // 'u' modifier has been added to correctly parse smilies within unicode strings
1355
                        // For details: http://tracker.phpbb.com/browse/PHPBB3-10117
1356
1357
                        $this->message = trim(preg_replace(explode(chr(0), '#(?<=^|[\n .])' . implode('(?![^<>]*>)#u' . chr(0) . '#(?<=^|[\n .])', $match) . '(?![^<>]*>)#u'), $replace, $this->message));
1358
                }
1359
        }
1360
1361
        /**
1362
        * Parse Attachments
1363
        */
1364
        function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
1365
        {
1366
                global $config, $auth, $user, $phpbb_root_path, $phpEx, $db;
1367
1368
                $error = array();
1369
1370
                $num_attachments = sizeof($this->attachment_data);
1371
                $this->filename_data['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
1372
                $upload_file = (isset($_FILES[$form_name]) && $_FILES[$form_name]['name'] != 'none' && trim($_FILES[$form_name]['name'])) ? true : false;
1373
1374
                $add_file                = (isset($_POST['add_file'])) ? true : false;
1375
                $delete_file        = (isset($_POST['delete_file'])) ? true : false;
1376
1377
                // First of all adjust comments if changed
1378
                $actual_comment_list = utf8_normalize_nfc(request_var('comment_list', array(''), true));
1379
1380
                foreach ($actual_comment_list as $comment_key => $comment)
1381
                {
1382
                        if (!isset($this->attachment_data[$comment_key]))
1383
                        {
1384
                                continue;
1385
                        }
1386
1387
                        if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
1388
                        {
1389
                                $this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
1390
                        }
1391
                }
1392
1393
                $cfg = array();
1394
                $cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
1395
                $forum_id = ($is_message) ? 0 : $forum_id;
1396
1397
                if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
1398
                {
1399
                        if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
1400
                        {
1401
                                $filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1402
                                $error = $filedata['error'];
1403
1404
                                if ($filedata['post_attach'] && !sizeof($error))
1405
                                {
1406
                                        $sql_ary = array(
1407
                                                'physical_filename'        => $filedata['physical_filename'],
1408
                                                'attach_comment'        => $this->filename_data['filecomment'],
1409
                                                'real_filename'                => $filedata['real_filename'],
1410
                                                'extension'                        => $filedata['extension'],
1411
                                                'mimetype'                        => $filedata['mimetype'],
1412
                                                'filesize'                        => $filedata['filesize'],
1413
                                                'filetime'                        => $filedata['filetime'],
1414
                                                'thumbnail'                        => $filedata['thumbnail'],
1415
                                                'is_orphan'                        => 1,
1416
                                                'in_message'                => ($is_message) ? 1 : 0,
1417
                                                'poster_id'                        => $user->data['user_id'],
1418
                                        );
1419
1420
                                        $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1421
1422
                                        $new_entry = array(
1423
                                                'attach_id'                => $db->sql_nextid(),
1424
                                                'is_orphan'                => 1,
1425
                                                'real_filename'        => $filedata['real_filename'],
1426
                                                'attach_comment'=> $this->filename_data['filecomment'],
1427
                                        );
1428
1429
                                        $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1430
                                        $this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);
1431
1432
                                        $this->filename_data['filecomment'] = '';
1433
1434
                                        // This Variable is set to false here, because Attachments are entered into the
1435
                                        // Database in two modes, one if the id_list is 0 and the second one if post_attach is true
1436
                                        // Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
1437
                                        // but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
1438
                                        //
1439
                                        // This is very relevant, because it could happen that the post got not submitted, but we do not
1440
                                        // know this circumstance here. We could be at the posting page or we could be redirected to the entered
1441
                                        // post. :)
1442
                                        $filedata['post_attach'] = false;
1443
                                }
1444
                        }
1445
                        else
1446
                        {
1447
                                $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
1448
                        }
1449
                }
1450
1451
                if ($preview || $refresh || sizeof($error))
1452
                {
1453
                        // Perform actions on temporary attachments
1454
                        if ($delete_file)
1455
                        {
1456
                                include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
1457
1458
                                $index = array_keys(request_var('delete_file', array(0 => 0)));
1459
                                $index = (!empty($index)) ? $index[0] : false;
1460
1461
                                if ($index !== false && !empty($this->attachment_data[$index]))
1462
                                {
1463
                                        // delete selected attachment
1464
                                        if ($this->attachment_data[$index]['is_orphan'])
1465
                                        {
1466
                                                $sql = 'SELECT attach_id, physical_filename, thumbnail
1467
                                                        FROM ' . ATTACHMENTS_TABLE . '
1468
                                                        WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id'] . '
1469
                                                                AND is_orphan = 1
1470
                                                                AND poster_id = ' . $user->data['user_id'];
1471
                                                $result = $db->sql_query($sql);
1472
                                                $row = $db->sql_fetchrow($result);
1473
                                                $db->sql_freeresult($result);
1474
1475
                                                if ($row)
1476
                                                {
1477
                                                        phpbb_unlink($row['physical_filename'], 'file');
1478
1479
                                                        if ($row['thumbnail'])
1480
                                                        {
1481
                                                                phpbb_unlink($row['physical_filename'], 'thumbnail');
1482
                                                        }
1483
1484
                                                        $db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE . ' WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id']);
1485
                                                }
1486
                                        }
1487
                                        else
1488
                                        {
1489
                                                delete_attachments('attach', array(intval($this->attachment_data[$index]['attach_id'])));
1490
                                        }
1491
1492
                                        unset($this->attachment_data[$index]);
1493
                                        $this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "(\\1 == \$index) ? '' : ((\\1 > \$index) ? '[attachment=' . (\\1 - 1) . ']\\2[/attachment]' : '\\0')", $this->message);
1494
1495
                                        // Reindex Array
1496
                                        $this->attachment_data = array_values($this->attachment_data);
1497
                                }
1498
                        }
1499
                        else if (($add_file || $preview) && $upload_file)
1500
                        {
1501
                                if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_', $forum_id))
1502
                                {
1503
                                        $filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
1504
                                        $error = array_merge($error, $filedata['error']);
1505
1506
                                        if (!sizeof($error))
1507
                                        {
1508
                                                $sql_ary = array(
1509
                                                        'physical_filename'        => $filedata['physical_filename'],
1510
                                                        'attach_comment'        => $this->filename_data['filecomment'],
1511
                                                        'real_filename'                => $filedata['real_filename'],
1512
                                                        'extension'                        => $filedata['extension'],
1513
                                                        'mimetype'                        => $filedata['mimetype'],
1514
                                                        'filesize'                        => $filedata['filesize'],
1515
                                                        'filetime'                        => $filedata['filetime'],
1516
                                                        'thumbnail'                        => $filedata['thumbnail'],
1517
                                                        'is_orphan'                        => 1,
1518
                                                        'in_message'                => ($is_message) ? 1 : 0,
1519
                                                        'poster_id'                        => $user->data['user_id'],
1520
                                                );
1521
1522
                                                $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1523
1524
                                                $new_entry = array(
1525
                                                        'attach_id'                => $db->sql_nextid(),
1526
                                                        'is_orphan'                => 1,
1527
                                                        'real_filename'        => $filedata['real_filename'],
1528
                                                        'attach_comment'=> $this->filename_data['filecomment'],
1529
                                                );
1530
1531
                                                $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
1532
                                                $this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);
1533
                                                $this->filename_data['filecomment'] = '';
1534
                                        }
1535
                                }
1536
                                else
1537
                                {
1538
                                        $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);
1539
                                }
1540
                        }
1541
                }
1542
1543
                foreach ($error as $error_msg)
1544
                {
1545
                        $this->warn_msg[] = $error_msg;
1546
                }
1547
        }
1548
1549
        /**
1550
        * Get Attachment Data
1551
        */
1552
        function get_submitted_attachment_data($check_user_id = false)
1553
        {
1554
                global $user, $db, $phpbb_root_path, $phpEx, $config;
1555
                global $request;
1556
1557
                $this->filename_data['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
1558
                $attachment_data = $request->variable('attachment_data', array(0 => array('' => '')), true, phpbb_request_interface::POST);
1559
                $this->attachment_data = array();
1560
1561
                $check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id;
1562
1563
                if (!sizeof($attachment_data))
1564
                {
1565
                        return;
1566
                }
1567
1568
                $not_orphan = $orphan = array();
1569
1570
                foreach ($attachment_data as $pos => $var_ary)
1571
                {
1572
                        if ($var_ary['is_orphan'])
1573
                        {
1574
                                $orphan[(int) $var_ary['attach_id']] = $pos;
1575
                        }
1576
                        else
1577
                        {
1578
                                $not_orphan[(int) $var_ary['attach_id']] = $pos;
1579
                        }
1580
                }
1581
1582
                // Regenerate already posted attachments
1583
                if (sizeof($not_orphan))
1584
                {
1585
                        // Get the attachment data, based on the poster id...
1586
                        $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
1587
                                FROM ' . ATTACHMENTS_TABLE . '
1588
                                WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '
1589
                                        AND poster_id = ' . $check_user_id;
1590
                        $result = $db->sql_query($sql);
1591
1592
                        while ($row = $db->sql_fetchrow($result))
1593
                        {
1594
                                $pos = $not_orphan[$row['attach_id']];
1595
                                $this->attachment_data[$pos] = $row;
1596
                                $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
1597
1598
                                unset($not_orphan[$row['attach_id']]);
1599
                        }
1600
                        $db->sql_freeresult($result);
1601
                }
1602
1603
                if (sizeof($not_orphan))
1604
                {
1605
                        trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1606
                }
1607
1608
                // Regenerate newly uploaded attachments
1609
                if (sizeof($orphan))
1610
                {
1611
                        $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
1612
                                FROM ' . ATTACHMENTS_TABLE . '
1613
                                WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '
1614
                                        AND poster_id = ' . $user->data['user_id'] . '
1615
                                        AND is_orphan = 1';
1616
                        $result = $db->sql_query($sql);
1617
1618
                        while ($row = $db->sql_fetchrow($result))
1619
                        {
1620
                                $pos = $orphan[$row['attach_id']];
1621
                                $this->attachment_data[$pos] = $row;
1622
                                $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment'];
1623
1624
                                unset($orphan[$row['attach_id']]);
1625
                        }
1626
                        $db->sql_freeresult($result);
1627
                }
1628
1629
                if (sizeof($orphan))
1630
                {
1631
                        trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
1632
                }
1633
1634
                ksort($this->attachment_data);
1635
        }
1636
1637
        /**
1638
        * Parse Poll
1639
        */
1640
        function parse_poll(&$poll)
1641
        {
1642
                global $auth, $user, $config;
1643
1644
                $poll_max_options = $poll['poll_max_options'];
1645
1646
                // Parse Poll Option text ;)
1647
                $tmp_message = $this->message;
1648
                $this->message = $poll['poll_option_text'];
1649
                $bbcode_bitfield = $this->bbcode_bitfield;
1650
1651
                $poll['poll_option_text'] = $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');
1652
1653
                $bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield));
1654
                $this->message = $tmp_message;
1655
1656
                // Parse Poll Title
1657
                $tmp_message = $this->message;
1658
                $this->message = $poll['poll_title'];
1659
                $this->bbcode_bitfield = $bbcode_bitfield;
1660
1661
                $poll['poll_options'] = explode("\n", trim($poll['poll_option_text']));
1662
                $poll['poll_options_size'] = sizeof($poll['poll_options']);
1663
1664
                if (!$poll['poll_title'] && $poll['poll_options_size'])
1665
                {
1666
                        $this->warn_msg[] = $user->lang['NO_POLL_TITLE'];
1667
                }
1668
                else
1669
                {
1670
                        if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)) > 100)
1671
                        {
1672
                                $this->warn_msg[] = $user->lang['POLL_TITLE_TOO_LONG'];
1673
                        }
1674
                        $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');
1675
                        if (strlen($poll['poll_title']) > 255)
1676
                        {
1677
                                $this->warn_msg[] = $user->lang['POLL_TITLE_COMP_TOO_LONG'];
1678
                        }
1679
                }
1680
1681
                $this->bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield));
1682
                $this->message = $tmp_message;
1683
                unset($tmp_message);
1684
1685
                if (sizeof($poll['poll_options']) == 1)
1686
                {
1687
                        $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS'];
1688
                }
1689
                else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
1690
                {
1691
                        $this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS'];
1692
                }
1693
                else if ($poll_max_options > $poll['poll_options_size'])
1694
                {
1695
                        $this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS'];
1696
                }
1697
1698
                $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']);
1699
        }
1700
}