phpBB
Statistics
| Revision:

root / branches / phpBB-3_0_0 / phpBB / includes / message_parser.php

History | View | Annotate | Download (48.8 kB)

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