~azzar1/unity/add-show-desktop-key

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