~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: functions.php,v 1.647 2007/12/10 18:35:28 kellanved 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
// Common global functions
20
21
/**
22
* set_var
23
*
24
* Set variable, used by {@link request_var the request_var function}
25
*
26
* @access private
27
*/
28
function set_var(&$result, $var, $type, $multibyte = false)
29
{
30
	settype($var, $type);
31
	$result = $var;
32
33
	if ($type == 'string')
34
	{
35
		$result = trim(htmlspecialchars(str_replace(array("\r\n", "\r"), array("\n", "\n"), $result), ENT_COMPAT, 'UTF-8'));
36
37
		if (!empty($result))
38
		{
39
			// Make sure multibyte characters are wellformed
40
			if ($multibyte)
41
			{
42
				if (!preg_match('/^./u', $result))
43
				{
44
					$result = '';
45
				}
46
			}
47
			else
48
			{
49
				// no multibyte, allow only ASCII (0-127)
50
				$result = preg_replace('/[\x80-\xFF]/', '?', $result);
51
			}
52
		}
53
54
		$result = (STRIP) ? stripslashes($result) : $result;
55
	}
56
}
57
58
/**
59
* request_var
60
*
61
* Used to get passed variable
62
*/
63
function request_var($var_name, $default, $multibyte = false, $cookie = false)
64
{
65
	if (!$cookie && isset($_COOKIE[$var_name]))
66
	{
67
		if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
68
		{
69
			return (is_array($default)) ? array() : $default;
70
		}
71
		$_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
72
	}
73
74
	if (!isset($_REQUEST[$var_name]) || (is_array($_REQUEST[$var_name]) && !is_array($default)) || (is_array($default) && !is_array($_REQUEST[$var_name])))
75
	{
76
		return (is_array($default)) ? array() : $default;
77
	}
78
79
	$var = $_REQUEST[$var_name];
80
	if (!is_array($default))
81
	{
82
		$type = gettype($default);
83
	}
84
	else
85
	{
86
		list($key_type, $type) = each($default);
87
		$type = gettype($type);
88
		$key_type = gettype($key_type);
89
		if ($type == 'array')
90
		{
91
			reset($default);
92
			$default = current($default);
93
			list($sub_key_type, $sub_type) = each($default);
94
			$sub_type = gettype($sub_type);
95
			$sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
96
			$sub_key_type = gettype($sub_key_type);
97
		}
98
	}
99
100
	if (is_array($var))
101
	{
102
		$_var = $var;
103
		$var = array();
104
105
		foreach ($_var as $k => $v)
106
		{
107
			set_var($k, $k, $key_type);
108
			if ($type == 'array' && is_array($v))
109
			{
110
				foreach ($v as $_k => $_v)
111
				{
112
					if (is_array($_v))
113
					{
114
						$_v = null;
115
					}
116
					set_var($_k, $_k, $sub_key_type);
117
					set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
118
				}
119
			}
120
			else
121
			{
122
				if ($type == 'array' || is_array($v))
123
				{
124
					$v = null;
125
				}
126
				set_var($var[$k], $v, $type, $multibyte);
127
			}
128
		}
129
	}
130
	else
131
	{
132
		set_var($var, $var, $type, $multibyte);
133
	}
134
135
	return $var;
136
}
137
138
/**
139
* Set config value. Creates missing config entry.
140
*/
141
function set_config($config_name, $config_value, $is_dynamic = false)
142
{
143
	global $db, $cache, $config;
144
145
	$sql = 'UPDATE ' . CONFIG_TABLE . "
146
		SET config_value = '" . $db->sql_escape($config_value) . "'
147
		WHERE config_name = '" . $db->sql_escape($config_name) . "'";
148
	$db->sql_query($sql);
149
150
	if (!$db->sql_affectedrows() && !isset($config[$config_name]))
151
	{
152
		$sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
153
			'config_name'	=> $config_name,
154
			'config_value'	=> $config_value,
155
			'is_dynamic'	=> ($is_dynamic) ? 1 : 0));
156
		$db->sql_query($sql);
157
	}
158
159
	$config[$config_name] = $config_value;
160
161
	if (!$is_dynamic)
162
	{
163
		$cache->destroy('config');
164
	}
165
}
166
167
/**
168
* Generates an alphanumeric random string of given length
169
*/
170
function gen_rand_string($num_chars = 8)
171
{
172
	$rand_str = unique_id();
173
	$rand_str = str_replace('0', 'Z', strtoupper(base_convert($rand_str, 16, 35)));
174
175
	return substr($rand_str, 0, $num_chars);
176
}
177
178
/**
179
* Return unique id
180
* @param string $extra additional entropy
181
*/
182
function unique_id($extra = 'c')
183
{
184
	static $dss_seeded = false;
185
	global $config;
186
187
	$val = $config['rand_seed'] . microtime();
188
	$val = md5($val);
189
	$config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
190
191
	if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
192
	{
193
		set_config('rand_seed', $config['rand_seed'], true);
194
		set_config('rand_seed_last_update', time(), true);
195
		$dss_seeded = true;
196
	}
197
198
	return substr($val, 4, 16);
199
}
200
201
/**
202
* Determine whether we are approaching the maximum execution time. Should be called once
203
* at the beginning of the script in which it's used.
204
* @return	bool	Either true if the maximum execution time is nearly reached, or false
205
*					if some time is still left.
206
*/
207
function still_on_time($extra_time = 15)
208
{
209
	static $max_execution_time, $start_time;
210
211
	$time = explode(' ', microtime());
212
	$current_time = $time[0] + $time[1];
213
214
	if (empty($max_execution_time))
215
	{
216
		$max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
217
218
		// If zero, then set to something higher to not let the user catch the ten seconds barrier.
219
		if ($max_execution_time === 0)
220
		{
221
			$max_execution_time = 50 + $extra_time;
222
		}
223
224
		$max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
225
226
		// For debugging purposes
227
		// $max_execution_time = 10;
228
229
		global $starttime;
230
		$start_time = (empty($starttime)) ? $current_time : $starttime;
231
	}
232
233
	return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
234
}
235
236
/**
237
*
238
* @version Version 0.1 / $Id: functions.php,v 1.647 2007/12/10 18:35:28 kellanved Exp $
239
*
240
* Portable PHP password hashing framework.
241
*
242
* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
243
* the public domain.
244
*
245
* There's absolutely no warranty.
246
*
247
* The homepage URL for this framework is:
248
*
249
*	http://www.openwall.com/phpass/
250
*
251
* Please be sure to update the Version line if you edit this file in any way.
252
* It is suggested that you leave the main version number intact, but indicate
253
* your project name (after the slash) and add your own revision information.
254
*
255
* Please do not change the "private" password hashing method implemented in
256
* here, thereby making your hashes incompatible.  However, if you must, please
257
* change the hash type identifier (the "$P$") to something different.
258
*
259
* Obviously, since this code is in the public domain, the above are not
260
* requirements (there can be none), but merely suggestions.
261
*
262
*
263
* Hash the password
264
*/
265
function phpbb_hash($password)
266
{
267
	$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
268
269
	$random_state = unique_id();
270
	$random = '';
271
	$count = 6;
272
273
	if (($fh = @fopen('/dev/urandom', 'rb')))
274
	{
275
		$random = fread($fh, $count);
276
		fclose($fh);
277
	}
278
279
	if (strlen($random) < $count)
280
	{
281
		$random = '';
282
283
		for ($i = 0; $i < $count; $i += 16)
284
		{
285
			$random_state = md5(unique_id() . $random_state);
286
			$random .= pack('H*', md5($random_state));
287
		}
288
		$random = substr($random, 0, $count);
289
	}
290
	
291
	$hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);
292
293
	if (strlen($hash) == 34)
294
	{
295
		return $hash;
296
	}
297
298
	return md5($password);
299
}
300
301
/**
302
* Check for correct password
303
*/
304
function phpbb_check_hash($password, $hash)
305
{
306
	$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
307
	if (strlen($hash) == 34)
308
	{
309
		return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
310
	}
311
312
	return (md5($password) === $hash) ? true : false;
313
}
314
315
/**
316
* Generate salt for hash generation
317
*/
318
function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
319
{
320
	if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
321
	{
322
		$iteration_count_log2 = 8;
323
	}
324
325
	$output = '$H$';
326
	$output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
327
	$output .= _hash_encode64($input, 6, $itoa64);
328
329
	return $output;
330
}
331
332
/**
333
* Encode hash
334
*/
335
function _hash_encode64($input, $count, &$itoa64)
336
{
337
	$output = '';
338
	$i = 0;
339
340
	do
341
	{
342
		$value = ord($input[$i++]);
343
		$output .= $itoa64[$value & 0x3f];
344
345
		if ($i < $count)
346
		{
347
			$value |= ord($input[$i]) << 8;
348
		}
349
350
		$output .= $itoa64[($value >> 6) & 0x3f];
351
352
		if ($i++ >= $count)
353
		{
354
			break;
355
		}
356
357
		if ($i < $count)
358
		{
359
			$value |= ord($input[$i]) << 16;
360
		}
361
362
		$output .= $itoa64[($value >> 12) & 0x3f];
363
		
364
		if ($i++ >= $count)
365
		{
366
			break;
367
		}
368
369
		$output .= $itoa64[($value >> 18) & 0x3f];
370
	}
371
	while ($i < $count);
372
373
	return $output;
374
}
375
376
/**
377
* The crypt function/replacement
378
*/
379
function _hash_crypt_private($password, $setting, &$itoa64)
380
{
381
	$output = '*';
382
383
	// Check for correct hash
384
	if (substr($setting, 0, 3) != '$H$')
385
	{
386
		return $output;
387
	}
388
389
	$count_log2 = strpos($itoa64, $setting[3]);
390
391
	if ($count_log2 < 7 || $count_log2 > 30)
392
	{
393
		return $output;
394
	}
395
396
	$count = 1 << $count_log2;
397
	$salt = substr($setting, 4, 8);
398
399
	if (strlen($salt) != 8)
400
	{
401
		return $output;
402
	}
403
404
	/**
405
	* We're kind of forced to use MD5 here since it's the only
406
	* cryptographic primitive available in all versions of PHP
407
	* currently in use.  To implement our own low-level crypto
408
	* in PHP would result in much worse performance and
409
	* consequently in lower iteration counts and hashes that are
410
	* quicker to crack (by non-PHP code).
411
	*/
412
	if (PHP_VERSION >= 5)
413
	{
414
		$hash = md5($salt . $password, true);
415
		do
416
		{
417
			$hash = md5($hash . $password, true);
418
		}
419
		while (--$count);
420
	}
421
	else
422
	{
423
		$hash = pack('H*', md5($salt . $password));
424
		do
425
		{
426
			$hash = pack('H*', md5($hash . $password));
427
		}
428
		while (--$count);
429
	}
430
431
	$output = substr($setting, 0, 12);
432
	$output .= _hash_encode64($hash, 16, $itoa64);
433
434
	return $output;
435
}
436
437
// Compatibility functions
438
439
if (!function_exists('array_combine'))
440
{
441
	/**
442
	* A wrapper for the PHP5 function array_combine()
443
	* @param array $keys contains keys for the resulting array
444
	* @param array $values contains values for the resulting array
445
	*
446
	* @return Returns an array by using the values from the keys array as keys and the
447
	* 	values from the values array as the corresponding values. Returns false if the
448
	* 	number of elements for each array isn't equal or if the arrays are empty.
449
	*/
450
	function array_combine($keys, $values)
451
	{
452
		$keys = array_values($keys);
453
		$values = array_values($values);
454
455
		$n = sizeof($keys);
456
		$m = sizeof($values);
457
		if (!$n || !$m || ($n != $m))
458
		{
459
			return false;
460
		}
461
462
		$combined = array();
463
		for ($i = 0; $i < $n; $i++)
464
		{
465
			$combined[$keys[$i]] = $values[$i];
466
		}
467
		return $combined;
468
	}
469
}
470
471
if (!function_exists('str_split'))
472
{
473
	/**
474
	* A wrapper for the PHP5 function str_split()
475
	* @param array $string contains the string to be converted
476
	* @param array $split_length contains the length of each chunk
477
	*
478
	* @return  Converts a string to an array. If the optional split_length parameter is specified,
479
	*  	the returned array will be broken down into chunks with each being split_length in length,
480
	*  	otherwise each chunk will be one character in length. FALSE is returned if split_length is
481
	*  	less than 1. If the split_length length exceeds the length of string, the entire string is
482
	*  	returned as the first (and only) array element.
483
	*/
484
	function str_split($string, $split_length = 1)
485
	{
486
		if ($split_length < 1)
487
		{
488
			return false;
489
		}
490
		else if ($split_length >= strlen($string))
491
		{
492
			return array($string);
493
		}
494
		else
495
		{
496
			preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches);
497
			return $matches[0];
498
		}
499
	}
500
}
501
502
if (!function_exists('stripos'))
503
{
504
	/**
505
	* A wrapper for the PHP5 function stripos
506
	* Find position of first occurrence of a case-insensitive string
507
	*
508
	* @param string $haystack is the string to search in
509
	* @param string $needle is the string to search for
510
	*
511
	* @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive.
512
	* Note that the needle may be a string of one or more characters.
513
	* If needle is not found, stripos() will return boolean FALSE.
514
	*/
515
	function stripos($haystack, $needle)
516
	{
517
		if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
518
		{
519
			return strpos($haystack, $m[0]);
520
		}
521
522
		return false;
523
	}
524
}
525
526
if (!function_exists('realpath'))
527
{
528
	/**
529
	* Checks if a path ($path) is absolute or relative
530
	*
531
	* @param string $path Path to check absoluteness of
532
	* @return boolean
533
	*/
534
	function is_absolute($path)
535
	{
536
		return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:/#i', $path))) ? true : false;
537
	}
538
539
	/**
540
	* @author Chris Smith <chris@project-minerva.org>
541
	* @copyright 2006 Project Minerva Team
542
	* @param string $path The path which we should attempt to resolve.
543
	* @return mixed
544
	*/
545
	function phpbb_realpath($path)
546
	{
547
		// Now to perform funky shizzle
548
549
		// Switch to use UNIX slashes
550
		$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
551
		$path_prefix = '';
552
553
		// Determine what sort of path we have
554
		if (is_absolute($path))
555
		{
556
			$absolute = true;
557
558
			if ($path[0] == '/')
559
			{
560
				// Absolute path, *NIX style
561
				$path_prefix = '';
562
			}
563
			else
564
			{
565
				// Absolute path, Windows style
566
				// Remove the drive letter and colon
567
				$path_prefix = $path[0] . ':';
568
				$path = substr($path, 2);
569
			}
570
		}
571
		else
572
		{
573
			// Relative Path
574
			// Prepend the current working directory
575
			if (function_exists('getcwd'))
576
			{
577
				// This is the best method, hopefully it is enabled!
578
				$path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
579
				$absolute = true;
580
				if (preg_match('#^[a-z]:#i', $path))
581
				{
582
					$path_prefix = $path[0] . ':';
583
					$path = substr($path, 2);
584
				}
585
				else
586
				{
587
					$path_prefix = '';
588
				}
589
			}
590
			else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
591
			{
592
				// Warning: If chdir() has been used this will lie!
593
				// Warning: This has some problems sometime (CLI can create them easily)
594
				$path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
595
				$absolute = true;
596
				$path_prefix = '';
597
			}
598
			else
599
			{
600
				// We have no way of getting the absolute path, just run on using relative ones.
601
				$absolute = false;
602
				$path_prefix = '.';
603
			}
604
		}
605
606
		// Remove any repeated slashes
607
		$path = preg_replace('#/{2,}#', '/', $path);
608
609
		// Remove the slashes from the start and end of the path
610
		$path = trim($path, '/');
611
612
		// Break the string into little bits for us to nibble on
613
		$bits = explode('/', $path);
614
615
		// Remove any . in the path, renumber array for the loop below
616
		$bits = array_values(array_diff($bits, array('.')));
617
618
		// Lets get looping, run over and resolve any .. (up directory)
619
		for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
620
		{
621
			// @todo Optimise
622
			if ($bits[$i] == '..' )
623
			{
624
				if (isset($bits[$i - 1]))
625
				{
626
					if ($bits[$i - 1] != '..')
627
					{
628
						// We found a .. and we are able to traverse upwards, lets do it!
629
						unset($bits[$i]);
630
						unset($bits[$i - 1]);
631
						$i -= 2;
632
						$max -= 2;
633
						$bits = array_values($bits);
634
					}
635
				}
636
				else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
637
				{
638
					// We have an absolute path trying to descend above the root of the filesystem
639
					// ... Error!
640
					return false;
641
				}
642
			}
643
		}
644
645
		// Prepend the path prefix
646
		array_unshift($bits, $path_prefix);
647
648
		$resolved = '';
649
650
		$max = sizeof($bits) - 1;
651
652
		// Check if we are able to resolve symlinks, Windows cannot.
653
		$symlink_resolve = (function_exists('readlink')) ? true : false;
654
655
		foreach ($bits as $i => $bit)
656
		{
657
			if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
658
			{
659
				// Path Exists
660
				if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
661
				{
662
					// Resolved a symlink.
663
					$resolved = $link . (($i == $max) ? '' : '/');
664
					continue;
665
				}
666
			}
667
			else
668
			{
669
				// Something doesn't exist here!
670
				// This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
671
				// return false;
672
			}
673
			$resolved .= $bit . (($i == $max) ? '' : '/');
674
		}
675
676
		// @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
677
		// because we must be inside that basedir, the question is where...
678
		// @internal The slash in is_dir() gets around an open_basedir restriction
679
		if (!@file_exists($resolved) || (!is_dir($resolved . '/') && !is_file($resolved)))
680
		{
681
			return false;
682
		}
683
684
		// Put the slashes back to the native operating systems slashes
685
		$resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
686
687
		// Check for DIRECTORY_SEPARATOR at the end (and remove it!)
688
		if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
689
		{
690
			return substr($resolved, 0, -1);
691
		}
692
693
		return $resolved; // We got here, in the end!
694
	}
695
}
696
else
697
{
698
	/**
699
	* A wrapper for realpath
700
	* @ignore
701
	*/
702
	function phpbb_realpath($path)
703
	{
704
		$path = realpath($path);
705
706
		// Check for DIRECTORY_SEPARATOR at the end (and remove it!)
707
		if (substr($path, -1) == DIRECTORY_SEPARATOR)
708
		{
709
			return substr($path, 0, -1);
710
		}
711
712
		return $path;
713
	}
714
}
715
716
if (!function_exists('htmlspecialchars_decode'))
717
{
718
	/**
719
	* A wrapper for htmlspecialchars_decode
720
	* @ignore
721
	*/
722
	function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
723
	{
724
		return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
725
	}
726
}
727
728
// functions used for building option fields
729
730
/**
731
* Pick a language, any language ...
732
*/
733
function language_select($default = '')
734
{
735
	global $db;
736
737
	$sql = 'SELECT lang_iso, lang_local_name
738
		FROM ' . LANG_TABLE . '
739
		ORDER BY lang_english_name';
740
	$result = $db->sql_query($sql);
741
742
	$lang_options = '';
743
	while ($row = $db->sql_fetchrow($result))
744
	{
745
		$selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
746
		$lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
747
	}
748
	$db->sql_freeresult($result);
749
750
	return $lang_options;
751
}
752
753
/**
754
* Pick a template/theme combo,
755
*/
756
function style_select($default = '', $all = false)
757
{
758
	global $db;
759
760
	$sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
761
	$sql = 'SELECT style_id, style_name
762
		FROM ' . STYLES_TABLE . "
763
		$sql_where
764
		ORDER BY style_name";
765
	$result = $db->sql_query($sql);
766
767
	$style_options = '';
768
	while ($row = $db->sql_fetchrow($result))
769
	{
770
		$selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
771
		$style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
772
	}
773
	$db->sql_freeresult($result);
774
775
	return $style_options;
776
}
777
778
/**
779
* Pick a timezone
780
*/
781
function tz_select($default = '', $truncate = false)
782
{
783
	global $user;
784
785
	$tz_select = '';
786
	foreach ($user->lang['tz_zones'] as $offset => $zone)
787
	{
788
		if ($truncate)
789
		{
790
			$zone_trunc = truncate_string($zone, 50, false, '...');
791
		}
792
		else
793
		{
794
			$zone_trunc = $zone;
795
		}
796
797
		if (is_numeric($offset))
798
		{
799
			$selected = ($offset == $default) ? ' selected="selected"' : '';
800
			$tz_select .= '<option title="'.$zone.'" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
801
		}
802
	}
803
804
	return $tz_select;
805
}
806
807
// Functions handling topic/post tracking/marking
808
809
/**
810
* Marks a topic/forum as read
811
* Marks a topic as posted to
812
*
813
* @param int $user_id can only be used with $mode == 'post'
814
*/
815
function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
816
{
817
	global $db, $user, $config;
818
819
	if ($mode == 'all')
820
	{
821
		if ($forum_id === false || !sizeof($forum_id))
822
		{
823
			if ($config['load_db_lastread'] && $user->data['is_registered'])
824
			{
825
				// Mark all forums read (index page)
826
				$db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
827
				$db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
828
				$db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
829
			}
830
			else if ($config['load_anon_lastread'] || $user->data['is_registered'])
831
			{
832
				$tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
833
				$tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
834
835
				unset($tracking_topics['tf']);
836
				unset($tracking_topics['t']);
837
				unset($tracking_topics['f']);
838
				$tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
839
	
840
				$user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
841
				$_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics);
842
843
				unset($tracking_topics);
844
845
				if ($user->data['is_registered'])
846
				{
847
					$db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
848
				}
849
			}
850
		}
851
852
		return;
853
	}
854
	else if ($mode == 'topics')
855
	{
856
		// Mark all topics in forums read
857
		if (!is_array($forum_id))
858
		{
859
			$forum_id = array($forum_id);
860
		}
861
862
		// Add 0 to forums array to mark global announcements correctly
863
		$forum_id[] = 0;
864
865
		if ($config['load_db_lastread'] && $user->data['is_registered'])
866
		{
867
			$sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
868
				WHERE user_id = {$user->data['user_id']}
869
					AND " . $db->sql_in_set('forum_id', $forum_id);
870
			$db->sql_query($sql);
871
872
			$sql = 'SELECT forum_id
873
				FROM ' . FORUMS_TRACK_TABLE . "
874
				WHERE user_id = {$user->data['user_id']}
875
					AND " . $db->sql_in_set('forum_id', $forum_id);
876
			$result = $db->sql_query($sql);
877
878
			$sql_update = array();
879
			while ($row = $db->sql_fetchrow($result))
880
			{
881
				$sql_update[] = $row['forum_id'];
882
			}
883
			$db->sql_freeresult($result);
884
885
			if (sizeof($sql_update))
886
			{
887
				$sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
888
					SET mark_time = ' . time() . "
889
					WHERE user_id = {$user->data['user_id']}
890
						AND " . $db->sql_in_set('forum_id', $sql_update);
891
				$db->sql_query($sql);
892
			}
893
894
			if ($sql_insert = array_diff($forum_id, $sql_update))
895
			{
896
				$sql_ary = array();
897
				foreach ($sql_insert as $f_id)
898
				{
899
					$sql_ary[] = array(
900
						'user_id'	=> (int) $user->data['user_id'],
901
						'forum_id'	=> (int) $f_id,
902
						'mark_time'	=> time()
903
					);
904
				}
905
906
				$db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
907
			}
908
		}
909
		else if ($config['load_anon_lastread'] || $user->data['is_registered'])
910
		{
911
			$tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
912
			$tracking = ($tracking) ? tracking_unserialize($tracking) : array();
913
914
			foreach ($forum_id as $f_id)
915
			{
916
				$topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
917
918
				if (isset($tracking['tf'][$f_id]))
919
				{
920
					unset($tracking['tf'][$f_id]);
921
				}
922
923
				foreach ($topic_ids36 as $topic_id36)
924
				{
925
					unset($tracking['t'][$topic_id36]);
926
				}
927
928
				if (isset($tracking['f'][$f_id]))
929
				{
930
					unset($tracking['f'][$f_id]);
931
				}
932
933
				$tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
934
			}
935
936
			if (isset($tracking['tf']) && empty($tracking['tf']))
937
			{
938
				unset($tracking['tf']);
939
			}
940
941
			$user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
942
			$_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
943
944
			unset($tracking);
945
		}
946
947
		return;
948
	}
949
	else if ($mode == 'topic')
950
	{
951
		if ($topic_id === false || $forum_id === false)
952
		{
953
			return;
954
		}
955
956
		if ($config['load_db_lastread'] && $user->data['is_registered'])
957
		{
958
			$sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
959
				SET mark_time = ' . (($post_time) ? $post_time : time()) . "
960
				WHERE user_id = {$user->data['user_id']}
961
					AND topic_id = $topic_id";
962
			$db->sql_query($sql);
963
964
			// insert row
965
			if (!$db->sql_affectedrows())
966
			{
967
				$db->sql_return_on_error(true);
968
969
				$sql_ary = array(
970
					'user_id'		=> (int) $user->data['user_id'],
971
					'topic_id'		=> (int) $topic_id,
972
					'forum_id'		=> (int) $forum_id,
973
					'mark_time'		=> ($post_time) ? (int) $post_time : time(),
974
				);
975
976
				$db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
977
978
				$db->sql_return_on_error(false);
979
			}
980
		}
981
		else if ($config['load_anon_lastread'] || $user->data['is_registered'])
982
		{
983
			$tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
984
			$tracking = ($tracking) ? tracking_unserialize($tracking) : array();
985
986
			$topic_id36 = base_convert($topic_id, 10, 36);
987
988
			if (!isset($tracking['t'][$topic_id36]))
989
			{
990
				$tracking['tf'][$forum_id][$topic_id36] = true;
991
			}
992
993
			$post_time = ($post_time) ? $post_time : time();
994
			$tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
995
996
			// If the cookie grows larger than 10000 characters we will remove the smallest value
997
			// This can result in old topics being unread - but most of the time it should be accurate...
998
			if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
999
			{
1000
				//echo 'Cookie grown too large' . print_r($tracking, true);
1001
1002
				// We get the ten most minimum stored time offsets and its associated topic ids
1003
				$time_keys = array();
1004
				for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
1005
				{
1006
					$min_value = min($tracking['t']);
1007
					$m_tkey = array_search($min_value, $tracking['t']);
1008
					unset($tracking['t'][$m_tkey]);
1009
1010
					$time_keys[$m_tkey] = $min_value;
1011
				}
1012
1013
				// Now remove the topic ids from the array...
1014
				foreach ($tracking['tf'] as $f_id => $topic_id_ary)
1015
				{
1016
					foreach ($time_keys as $m_tkey => $min_value)
1017
					{
1018
						if (isset($topic_id_ary[$m_tkey]))
1019
						{
1020
							$tracking['f'][$f_id] = $min_value;
1021
							unset($tracking['tf'][$f_id][$m_tkey]);
1022
						}
1023
					}
1024
				}
1025
1026
				if ($user->data['is_registered'])
1027
				{
1028
					$user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
1029
					$db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
1030
				}
1031
				else
1032
				{
1033
					$tracking['l'] = max($time_keys);
1034
				}
1035
			}
1036
1037
			$user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
1038
			$_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
1039
		}
1040
1041
		return;
1042
	}
1043
	else if ($mode == 'post')
1044
	{
1045
		if ($topic_id === false)
1046
		{
1047
			return;
1048
		}
1049
1050
		$use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
1051
1052
		if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
1053
		{
1054
			$db->sql_return_on_error(true);
1055
1056
			$sql_ary = array(
1057
				'user_id'		=> (int) $use_user_id,
1058
				'topic_id'		=> (int) $topic_id,
1059
				'topic_posted'	=> 1
1060
			);
1061
1062
			$db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1063
1064
			$db->sql_return_on_error(false);
1065
		}
1066
1067
		return;
1068
	}
1069
}
1070
1071
/**
1072
* Get topic tracking info by using already fetched info
1073
*/
1074
function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
1075
{
1076
	global $config, $user;
1077
1078
	$last_read = array();
1079
1080
	if (!is_array($topic_ids))
1081
	{
1082
		$topic_ids = array($topic_ids);
1083
	}
1084
1085
	foreach ($topic_ids as $topic_id)
1086
	{
1087
		if (!empty($rowset[$topic_id]['mark_time']))
1088
		{
1089
			$last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
1090
		}
1091
	}
1092
1093
	$topic_ids = array_diff($topic_ids, array_keys($last_read));
1094
1095
	if (sizeof($topic_ids))
1096
	{
1097
		$mark_time = array();
1098
1099
		// Get global announcement info
1100
		if ($global_announce_list && sizeof($global_announce_list))
1101
		{
1102
			if (!isset($forum_mark_time[0]))
1103
			{
1104
				global $db;
1105
1106
				$sql = 'SELECT mark_time
1107
					FROM ' . FORUMS_TRACK_TABLE . "
1108
					WHERE user_id = {$user->data['user_id']}
1109
						AND forum_id = 0";
1110
				$result = $db->sql_query($sql);
1111
				$row = $db->sql_fetchrow($result);
1112
				$db->sql_freeresult($result);
1113
1114
				if ($row)
1115
				{
1116
					$mark_time[0] = $row['mark_time'];
1117
				}
1118
			}
1119
			else
1120
			{
1121
				if ($forum_mark_time[0] !== false)
1122
				{
1123
					$mark_time[0] = $forum_mark_time[0];
1124
				}
1125
			}
1126
		}
1127
1128
		if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
1129
		{
1130
			$mark_time[$forum_id] = $forum_mark_time[$forum_id];
1131
		}
1132
			
1133
		$user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1134
1135
		foreach ($topic_ids as $topic_id)
1136
		{
1137
			if ($global_announce_list && isset($global_announce_list[$topic_id]))
1138
			{
1139
				$last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1140
			}
1141
			else
1142
			{
1143
				$last_read[$topic_id] = $user_lastmark;
1144
			}
1145
		}
1146
	}
1147
1148
	return $last_read;
1149
}
1150
1151
/**
1152
* Get topic tracking info from db (for cookie based tracking only this function is used)
1153
*/
1154
function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
1155
{
1156
	global $config, $user;
1157
1158
	$last_read = array();
1159
1160
	if (!is_array($topic_ids))
1161
	{
1162
		$topic_ids = array($topic_ids);
1163
	}
1164
1165
	if ($config['load_db_lastread'] && $user->data['is_registered'])
1166
	{
1167
		global $db;
1168
1169
		$sql = 'SELECT topic_id, mark_time
1170
			FROM ' . TOPICS_TRACK_TABLE . "
1171
			WHERE user_id = {$user->data['user_id']}
1172
				AND " . $db->sql_in_set('topic_id', $topic_ids);
1173
		$result = $db->sql_query($sql);
1174
1175
		while ($row = $db->sql_fetchrow($result))
1176
		{
1177
			$last_read[$row['topic_id']] = $row['mark_time'];
1178
		}
1179
		$db->sql_freeresult($result);
1180
	
1181
		$topic_ids = array_diff($topic_ids, array_keys($last_read));
1182
1183
		if (sizeof($topic_ids))
1184
		{
1185
			$sql = 'SELECT forum_id, mark_time
1186
				FROM ' . FORUMS_TRACK_TABLE . "
1187
				WHERE user_id = {$user->data['user_id']}
1188
					AND forum_id " .
1189
					(($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
1190
			$result = $db->sql_query($sql);
1191
		
1192
			$mark_time = array();
1193
			while ($row = $db->sql_fetchrow($result))
1194
			{
1195
				$mark_time[$row['forum_id']] = $row['mark_time'];
1196
			}
1197
			$db->sql_freeresult($result);
1198
1199
			$user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1200
1201
			foreach ($topic_ids as $topic_id)
1202
			{
1203
				if ($global_announce_list && isset($global_announce_list[$topic_id]))
1204
				{
1205
					$last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1206
				}
1207
				else
1208
				{
1209
					$last_read[$topic_id] = $user_lastmark;
1210
				}
1211
			}
1212
		}
1213
	}
1214
	else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1215
	{
1216
		global $tracking_topics;
1217
1218
		if (!isset($tracking_topics) || !sizeof($tracking_topics))
1219
		{
1220
			$tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1221
			$tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1222
		}
1223
1224
		if (!$user->data['is_registered'])
1225
		{
1226
			$user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1227
		}
1228
		else
1229
		{
1230
			$user_lastmark = $user->data['user_lastmark'];
1231
		}
1232
1233
		foreach ($topic_ids as $topic_id)
1234
		{
1235
			$topic_id36 = base_convert($topic_id, 10, 36);
1236
1237
			if (isset($tracking_topics['t'][$topic_id36]))
1238
			{
1239
				$last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1240
			}
1241
		}
1242
1243
		$topic_ids = array_diff($topic_ids, array_keys($last_read));
1244
1245
		if (sizeof($topic_ids))
1246
		{
1247
			$mark_time = array();
1248
			if ($global_announce_list && sizeof($global_announce_list))
1249
			{
1250
				if (isset($tracking_topics['f'][0]))
1251
				{
1252
					$mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
1253
				}
1254
			}
1255
1256
			if (isset($tracking_topics['f'][$forum_id]))
1257
			{
1258
				$mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1259
			}
1260
1261
			$user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1262
1263
			foreach ($topic_ids as $topic_id)
1264
			{
1265
				if ($global_announce_list && isset($global_announce_list[$topic_id]))
1266
				{
1267
					$last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1268
				}
1269
				else
1270
				{
1271
					$last_read[$topic_id] = $user_lastmark;
1272
				}
1273
			}
1274
		}
1275
	}
1276
1277
	return $last_read;
1278
}
1279
1280
/**
1281
* Check for read forums and update topic tracking info accordingly
1282
*
1283
* @param int $forum_id the forum id to check
1284
* @param int $forum_last_post_time the forums last post time
1285
* @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1286
* @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1287
*
1288
* @return true if complete forum got marked read, else false.
1289
*/
1290
function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1291
{
1292
	global $db, $tracking_topics, $user, $config;
1293
1294
	// Determine the users last forum mark time if not given.
1295
	if ($mark_time_forum === false)
1296
	{
1297
		if ($config['load_db_lastread'] && $user->data['is_registered'])
1298
		{
1299
			$mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1300
		}
1301
		else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1302
		{
1303
			$tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1304
			$tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1305
1306
			if (!$user->data['is_registered'])
1307
			{
1308
				$user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1309
			}
1310
1311
			$mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark'];
1312
		}
1313
	}
1314
1315
	// Check the forum for any left unread topics.
1316
	// If there are none, we mark the forum as read.
1317
	if ($config['load_db_lastread'] && $user->data['is_registered'])
1318
	{
1319
		if ($mark_time_forum >= $forum_last_post_time)
1320
		{
1321
			// We do not need to mark read, this happened before. Therefore setting this to true
1322
			$row = true;
1323
		}
1324
		else
1325
		{
1326
			$sql = 'SELECT t.forum_id FROM ' . TOPICS_TABLE . ' t
1327
				LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id'] . ')
1328
				WHERE t.forum_id = ' . $forum_id . '
1329
					AND t.topic_last_post_time > ' . $mark_time_forum . '
1330
					AND t.topic_moved_id = 0
1331
					AND (tt.topic_id IS NULL OR tt.mark_time < t.topic_last_post_time)
1332
				GROUP BY t.forum_id';
1333
			$result = $db->sql_query_limit($sql, 1);
1334
			$row = $db->sql_fetchrow($result);
1335
			$db->sql_freeresult($result);
1336
		}
1337
	}
1338
	else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1339
	{
1340
		// Get information from cookie
1341
		$row = false;
1342
1343
		if (!isset($tracking_topics['tf'][$forum_id]))
1344
		{
1345
			// We do not need to mark read, this happened before. Therefore setting this to true
1346
			$row = true;
1347
		}
1348
		else
1349
		{
1350
			$sql = 'SELECT topic_id
1351
				FROM ' . TOPICS_TABLE . '
1352
				WHERE forum_id = ' . $forum_id . '
1353
					AND topic_last_post_time > ' . $mark_time_forum . '
1354
					AND topic_moved_id = 0';
1355
			$result = $db->sql_query($sql);
1356
1357
			$check_forum = $tracking_topics['tf'][$forum_id];
1358
			$unread = false;
1359
1360
			while ($row = $db->sql_fetchrow($result))
1361
			{
1362
				if (!in_array(base_convert($row['topic_id'], 10, 36), array_keys($check_forum)))
1363
				{
1364
					$unread = true;
1365
					break;
1366
				}
1367
			}
1368
			$db->sql_freeresult($result);
1369
1370
			$row = $unread;
1371
		}
1372
	}
1373
	else
1374
	{
1375
		$row = true;
1376
	}
1377
1378
	if (!$row)
1379
	{
1380
		markread('topics', $forum_id);
1381
		return true;
1382
	}
1383
1384
	return false;
1385
}
1386
1387
/**
1388
* Transform an array into a serialized format
1389
*/
1390
function tracking_serialize($input)
1391
{
1392
	$out = '';
1393
	foreach ($input as $key => $value)
1394
	{
1395
		if (is_array($value))
1396
		{
1397
			$out .= $key . ':(' . tracking_serialize($value) . ');';
1398
		}
1399
		else
1400
		{
1401
			$out .= $key . ':' . $value . ';';
1402
		}
1403
	}
1404
	return $out;
1405
}
1406
1407
/**
1408
* Transform a serialized array into an actual array
1409
*/
1410
function tracking_unserialize($string, $max_depth = 3)
1411
{
1412
	$n = strlen($string);
1413
	if ($n > 10010)
1414
	{
1415
		die('Invalid data supplied');
1416
	}
1417
	$data = $stack = array();
1418
	$key = '';
1419
	$mode = 0;
1420
	$level = &$data;
1421
	for ($i = 0; $i < $n; ++$i)
1422
	{
1423
		switch ($mode)
1424
		{
1425
			case 0:
1426
				switch ($string[$i])
1427
				{
1428
					case ':':
1429
						$level[$key] = 0;
1430
						$mode = 1;
1431
					break;
1432
					case ')':
1433
						unset($level);
1434
						$level = array_pop($stack);
1435
						$mode = 3;
1436
					break;
1437
					default:
1438
						$key .= $string[$i];
1439
				}
1440
			break;
1441
1442
			case 1:
1443
				switch ($string[$i])
1444
				{
1445
					case '(':
1446
						if (sizeof($stack) >= $max_depth)
1447
						{
1448
							die('Invalid data supplied');
1449
						}
1450
						$stack[] = &$level;
1451
						$level[$key] = array();
1452
						$level = &$level[$key];
1453
						$key = '';
1454
						$mode = 0;
1455
					break;
1456
					default:
1457
						$level[$key] = $string[$i];
1458
						$mode = 2;
1459
					break;
1460
				}
1461
			break;
1462
			
1463
			case 2:
1464
				switch ($string[$i])
1465
				{
1466
					case ')':
1467
						unset($level);
1468
						$level = array_pop($stack);
1469
						$mode = 3;
1470
					break;
1471
					case ';':
1472
						$key = '';
1473
						$mode = 0;
1474
					break;
1475
					default:
1476
						$level[$key] .= $string[$i];
1477
					break;
1478
				}
1479
			break;
1480
			
1481
			case 3:
1482
				switch ($string[$i])
1483
				{
1484
					case ')':
1485
						unset($level);
1486
						$level = array_pop($stack);
1487
					break;
1488
					case ';':
1489
						$key = '';
1490
						$mode = 0;
1491
					break;
1492
					default:
1493
						die('Invalid data supplied');
1494
					break;
1495
				}
1496
			break;
1497
		}
1498
	}
1499
1500
	if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
1501
	{
1502
		die('Invalid data supplied');
1503
	}
1504
	
1505
	return $level;
1506
}
1507
1508
// Pagination functions
1509
1510
/**
1511
* Pagination routine, generates page number sequence
1512
* tpl_prefix is for using different pagination blocks at one page
1513
*/
1514
function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '')
1515
{
1516
	global $template, $user;
1517
1518
	// Make sure $per_page is a valid value
1519
	$per_page = ($per_page <= 0) ? 1 : $per_page;
1520
1521
	$seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>';
1522
	$total_pages = ceil($num_items / $per_page);
1523
1524
	if ($total_pages == 1 || !$num_items)
1525
	{
1526
		return false;
1527
	}
1528
1529
	$on_page = floor($start_item / $per_page) + 1;
1530
	$url_delim = (strpos($base_url, '?') === false) ? '?' : '&amp;';
1531
1532
	$page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>';
1533
1534
	if ($total_pages > 5)
1535
	{
1536
		$start_cnt = min(max(1, $on_page - 4), $total_pages - 5);
1537
		$end_cnt = max(min($total_pages, $on_page + 4), 6);
1538
1539
		$page_string .= ($start_cnt > 1) ? ' ... ' : $seperator;
1540
1541
		for ($i = $start_cnt + 1; $i < $end_cnt; $i++)
1542
		{
1543
			$page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
1544
			if ($i < $end_cnt - 1)
1545
			{
1546
				$page_string .= $seperator;
1547
			}
1548
		}
1549
1550
		$page_string .= ($end_cnt < $total_pages) ? ' ... ' : $seperator;
1551
	}
1552
	else
1553
	{
1554
		$page_string .= $seperator;
1555
1556
		for ($i = 2; $i < $total_pages; $i++)
1557
		{
1558
			$page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
1559
			if ($i < $total_pages)
1560
			{
1561
				$page_string .= $seperator;
1562
			}
1563
		}
1564
	}
1565
1566
	$page_string .= ($on_page == $total_pages) ? '<strong>' . $total_pages . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($total_pages - 1) * $per_page) . '">' . $total_pages . '</a>';
1567
1568
	if ($add_prevnext_text)
1569
	{
1570
		if ($on_page != 1)
1571
		{
1572
			$page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a>&nbsp;&nbsp;' . $page_string;
1573
		}
1574
1575
		if ($on_page != $total_pages)
1576
		{
1577
			$page_string .= '&nbsp;&nbsp;<a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>';
1578
		}
1579
	}
1580
1581
	$template->assign_vars(array(
1582
		$tpl_prefix . 'BASE_URL'		=> $base_url,
1583
		'A_' . $tpl_prefix . 'BASE_URL'	=> addslashes($base_url),
1584
		$tpl_prefix . 'PER_PAGE'		=> $per_page,
1585
1586
		$tpl_prefix . 'PREVIOUS_PAGE'	=> ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page),
1587
		$tpl_prefix . 'NEXT_PAGE'		=> ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page),
1588
		$tpl_prefix . 'TOTAL_PAGES'		=> $total_pages,
1589
	));
1590
1591
	return $page_string;
1592
}
1593
1594
/**
1595
* Return current page (pagination)
1596
*/
1597
function on_page($num_items, $per_page, $start)
1598
{
1599
	global $template, $user;
1600
1601
	// Make sure $per_page is a valid value
1602
	$per_page = ($per_page <= 0) ? 1 : $per_page;
1603
1604
	$on_page = floor($start / $per_page) + 1;
1605
1606
	$template->assign_vars(array(
1607
		'ON_PAGE'		=> $on_page)
1608
	);
1609
1610
	return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1));
1611
}
1612
1613
// Server functions (building urls, redirecting...)
1614
1615
/**
1616
* Append session id to url.
1617
* This function supports hooks.
1618
*
1619
* @param string $url The url the session id needs to be appended to (can have params)
1620
* @param mixed $params String or array of additional url parameters
1621
* @param bool $is_amp Is url using &amp; (true) or & (false)
1622
* @param string $session_id Possibility to use a custom session id instead of the global one
1623
*
1624
* Examples:
1625
* <code>
1626
* append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&amp;f=2");
1627
* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&amp;f=2');
1628
* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
1629
* append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
1630
* </code>
1631
*
1632
*/
1633
function append_sid($url, $params = false, $is_amp = true, $session_id = false)
1634
{
1635
	global $_SID, $_EXTRA_URL, $phpbb_hook;
1636
1637
	// Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropiatly.
1638
	// They could mimick most of what is within this function
1639
	if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
1640
	{
1641
		if ($phpbb_hook->hook_return(__FUNCTION__))
1642
		{
1643
			return $phpbb_hook->hook_return_result(__FUNCTION__);
1644
		}
1645
	}
1646
1647
	// Assign sid if session id is not specified
1648
	if ($session_id === false)
1649
	{
1650
		$session_id = $_SID;
1651
	}
1652
1653
	$amp_delim = ($is_amp) ? '&amp;' : '&';
1654
	$url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
1655
1656
	// Appending custom url parameter?
1657
	$append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
1658
1659
	$anchor = '';
1660
	if (strpos($url, '#') !== false)
1661
	{
1662
		list($url, $anchor) = explode('#', $url, 2);
1663
		$anchor = '#' . $anchor;
1664
	}
1665
	else if (!is_array($params) && strpos($params, '#') !== false)
1666
	{
1667
		list($params, $anchor) = explode('#', $params, 2);
1668
		$anchor = '#' . $anchor;
1669
	}
1670
1671
	// Use the short variant if possible ;)
1672
	if ($params === false)
1673
	{
1674
		// Append session id
1675
		if (!$session_id)
1676
		{
1677
			return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
1678
		}
1679
		else
1680
		{
1681
			return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
1682
		}
1683
	}
1684
1685
	// Build string if parameters are specified as array
1686
	if (is_array($params))
1687
	{
1688
		$output = array();
1689
1690
		foreach ($params as $key => $item)
1691
		{
1692
			if ($item === NULL)
1693
			{
1694
				continue;
1695
			}
1696
1697
			if ($key == '#')
1698
			{
1699
				$anchor = '#' . $item;
1700
				continue;
1701
			}
1702
1703
			$output[] = $key . '=' . $item;
1704
		}
1705
1706
		$params = implode($amp_delim, $output);
1707
	}
1708
1709
	// Append session id and parameters (even if they are empty)
1710
	// If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
1711
	return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
1712
}
1713
1714
/**
1715
* Generate board url (example: http://www.example.com/phpBB)
1716
* @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
1717
*/
1718
function generate_board_url($without_script_path = false)
1719
{
1720
	global $config, $user;
1721
1722
	$server_name = (!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME');
1723
	$server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
1724
1725
	// Forcing server vars is the only way to specify/override the protocol
1726
	if ($config['force_server_vars'] || !$server_name)
1727
	{
1728
		$server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
1729
		$server_name = $config['server_name'];
1730
		$server_port = (int) $config['server_port'];
1731
		$script_path = $config['script_path'];
1732
1733
		$url = $server_protocol . $server_name;
1734
	}
1735
	else
1736
	{
1737
		// Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
1738
		$cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
1739
		$url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
1740
1741
		$script_path = $user->page['root_script_path'];
1742
	}
1743
1744
	if ($server_port && (($config['cookie_secure'] && $server_port <> 443) || (!$config['cookie_secure'] && $server_port <> 80)))
1745
	{
1746
		$url .= ':' . $server_port;
1747
	}
1748
1749
	if (!$without_script_path)
1750
	{
1751
		$url .= $script_path;
1752
	}
1753
1754
	// Strip / from the end
1755
	if (substr($url, -1, 1) == '/')
1756
	{
1757
		$url = substr($url, 0, -1);
1758
	}
1759
1760
	return $url;
1761
}
1762
1763
/**
1764
* Redirects the user to another page then exits the script nicely
1765
*/
1766
function redirect($url, $return = false)
1767
{
1768
	global $db, $cache, $config, $user, $phpbb_root_path;
1769
1770
	if (empty($user->lang))
1771
	{
1772
		$user->add_lang('common');
1773
	}
1774
1775
	if (!$return)
1776
	{
1777
		garbage_collection();
1778
	}
1779
1780
	// Make sure no &amp;'s are in, this will break the redirect
1781
	$url = str_replace('&amp;', '&', $url);
1782
1783
	// Determine which type of redirect we need to handle...
1784
	$url_parts = parse_url($url);
1785
1786
	if ($url_parts === false)
1787
	{
1788
		// Malformed url, redirect to current page...
1789
		$url = generate_board_url() . '/' . $user->page['page'];
1790
	}
1791
	else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
1792
	{
1793
		// Full URL
1794
	}
1795
	else if ($url[0] == '/')
1796
	{
1797
		// Absolute uri, prepend direct url...
1798
		$url = generate_board_url(true) . $url;
1799
	}
1800
	else
1801
	{
1802
		// Relative uri
1803
		$pathinfo = pathinfo($url);
1804
1805
		// Is the uri pointing to the current directory?
1806
		if ($pathinfo['dirname'] == '.')
1807
		{
1808
			$url = str_replace('./', '', $url);
1809
1810
			// Strip / from the beginning
1811
			if ($url && substr($url, 0, 1) == '/')
1812
			{
1813
				$url = substr($url, 1);
1814
			}
1815
1816
			if ($user->page['page_dir'])
1817
			{
1818
				$url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url;
1819
			}
1820
			else
1821
			{
1822
				$url = generate_board_url() . '/' . $url;
1823
			}
1824
		}
1825
		else
1826
		{
1827
			// Used ./ before, but $phpbb_root_path is working better with urls within another root path
1828
			$root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path)));
1829
			$page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname'])));
1830
			$intersection = array_intersect_assoc($root_dirs, $page_dirs);
1831
1832
			$root_dirs = array_diff_assoc($root_dirs, $intersection);
1833
			$page_dirs = array_diff_assoc($page_dirs, $intersection);
1834
1835
			$dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
1836
1837
			// Strip / from the end
1838
			if ($dir && substr($dir, -1, 1) == '/')
1839
			{
1840
				$dir = substr($dir, 0, -1);
1841
			}
1842
1843
			// Strip / from the beginning
1844
			if ($dir && substr($dir, 0, 1) == '/')
1845
			{
1846
				$dir = substr($dir, 1);
1847
			}
1848
1849
			$url = str_replace($pathinfo['dirname'] . '/', '', $url);
1850
1851
			// Strip / from the beginning
1852
			if (substr($url, 0, 1) == '/')
1853
			{
1854
				$url = substr($url, 1);
1855
			}
1856
1857
			$url = $dir . '/' . $url;
1858
			$url = generate_board_url() . '/' . $url;
1859
		}
1860
	}
1861
1862
	// Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
1863
	if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
1864
	{
1865
		trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
1866
	}
1867
1868
	// Now, also check the protocol and for a valid url the last time...
1869
	$allowed_protocols = array('http', 'https', 'ftp', 'ftps');
1870
	$url_parts = parse_url($url);
1871
1872
	if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
1873
	{
1874
		trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
1875
	}
1876
1877
	if ($return)
1878
	{
1879
		return $url;
1880
	}
1881
1882
	// Redirect via an HTML form for PITA webservers
1883
	if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
1884
	{
1885
		header('Refresh: 0; URL=' . $url);
1886
1887
		echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1888
		echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">';
1889
		echo '<head>';
1890
		echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
1891
		echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
1892
		echo '<title>' . $user->lang['REDIRECT'] . '</title>';
1893
		echo '</head>';
1894
		echo '<body>';
1895
		echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
1896
		echo '</body>';
1897
		echo '</html>';
1898
1899
		exit;
1900
	}
1901
1902
	// Behave as per HTTP/1.1 spec for others
1903
	header('Location: ' . $url);
1904
	exit;
1905
}
1906
1907
/**
1908
* Re-Apply session id after page reloads
1909
*/
1910
function reapply_sid($url)
1911
{
1912
	global $phpEx, $phpbb_root_path;
1913
1914
	if ($url === "index.$phpEx")
1915
	{
1916
		return append_sid("index.$phpEx");
1917
	}
1918
	else if ($url === "{$phpbb_root_path}index.$phpEx")
1919
	{
1920
		return append_sid("{$phpbb_root_path}index.$phpEx");
1921
	}
1922
1923
	// Remove previously added sid
1924
	if (strpos($url, '?sid=') !== false)
1925
	{
1926
		$url = preg_replace('/(\?)sid=[a-z0-9]+(&amp;|&)?/', '\1', $url);
1927
	}
1928
	else if (strpos($url, '&sid=') !== false)
1929
	{
1930
		$url = preg_replace('/&sid=[a-z0-9]+(&)?/', '\1', $url);
1931
	}
1932
	else if (strpos($url, '&amp;sid=') !== false)
1933
	{
1934
		$url = preg_replace('/&amp;sid=[a-z0-9]+(&amp;)?/', '\1', $url);
1935
	}
1936
1937
	return append_sid($url);
1938
}
1939
1940
/**
1941
* Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
1942
*/
1943
function build_url($strip_vars = false)
1944
{
1945
	global $user, $phpbb_root_path;
1946
1947
	// Append SID
1948
	$redirect = append_sid($user->page['page'], false, false);
1949
1950
	// Add delimiter if not there...
1951
	if (strpos($redirect, '?') === false)
1952
	{
1953
		$redirect .= '?';
1954
	}
1955
1956
	// Strip vars...
1957
	if ($strip_vars !== false && strpos($redirect, '?') !== false)
1958
	{
1959
		if (!is_array($strip_vars))
1960
		{
1961
			$strip_vars = array($strip_vars);
1962
		}
1963
1964
		$query = $_query = array();
1965
1966
		$args = substr($redirect, strpos($redirect, '?') + 1);
1967
		$args = ($args) ? explode('&', $args) : array();
1968
		$redirect = substr($redirect, 0, strpos($redirect, '?'));
1969
1970
		foreach ($args as $argument)
1971
		{
1972
			$arguments = explode('=', $argument);
1973
			$key = $arguments[0];
1974
			unset($arguments[0]);
1975
1976
			$query[$key] = implode('=', $arguments);
1977
		}
1978
1979
		// Strip the vars off
1980
		foreach ($strip_vars as $strip)
1981
		{
1982
			if (isset($query[$strip]))
1983
			{
1984
				unset($query[$strip]);
1985
			}
1986
		}
1987
	
1988
		// Glue the remaining parts together... already urlencoded
1989
		foreach ($query as $key => $value)
1990
		{
1991
			$_query[] = $key . '=' . $value;
1992
		}
1993
		$query = implode('&', $_query);
1994
1995
		$redirect .= ($query) ? '?' . $query : '';
1996
	}
1997
1998
	return $phpbb_root_path . str_replace('&', '&amp;', $redirect);
1999
}
2000
2001
/**
2002
* Meta refresh assignment
2003
*/
2004
function meta_refresh($time, $url)
2005
{
2006
	global $template;
2007
2008
	$url = redirect($url, true);
2009
2010
	// For XHTML compatibility we change back & to &amp;
2011
	$template->assign_vars(array(
2012
		'META' => '<meta http-equiv="refresh" content="' . $time . ';url=' . str_replace('&', '&amp;', $url) . '" />')
2013
	);
2014
}
2015
2016
//Form validation
2017
2018
/**
2019
* Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2020
* @param string  $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2021
*/
2022
function add_form_key($form_name)
2023
{
2024
	global $config, $template, $user;
2025
	$now = time();
2026
	$token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2027
	$token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2028
2029
	$s_fields = build_hidden_fields(array(
2030
			'creation_time' => $now,
2031
			'form_token'	=> $token,
2032
	));
2033
	$template->assign_vars(array(
2034
			'S_FORM_TOKEN'	=> $s_fields,
2035
	));
2036
}
2037
2038
/**
2039
* Check the form key. Required for all altering actions not secured by confirm_box
2040
* @param string  $form_name The name of the form; has to match the name used in add_form_key, otherwise no restrictions apply
2041
* @param int $timespan The maximum acceptable age for a submitted form in seconds. Defaults to the config setting.
2042
* @param string $return_page The address for the return link
2043
* @param bool $trigger If true, the function will triger an error when encountering an invalid form
2044
* @param int $minimum_time The minimum acceptable age for a submitted form in seconds
2045
*/
2046
function check_form_key($form_name, $timespan = false, $return_page = '', $trigger = false, $minimum_time = false)
2047
{
2048
	global $config, $user;
2049
2050
	if ($timespan === false)
2051
	{
2052
		// we enforce a minimum value of half a minute here.
2053
		$timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2054
	}
2055
	if ($minimum_time === false)
2056
	{
2057
		$minimum_time = (int) $config['form_token_mintime'];
2058
	}
2059
	
2060
	if (isset($_POST['creation_time']) && isset($_POST['form_token']))
2061
	{
2062
		$creation_time	= abs(request_var('creation_time', 0));
2063
		$token = request_var('form_token', '');
2064
2065
		$diff = (time() - $creation_time);
2066
2067
		if (($diff >= $minimum_time) && (($diff <= $timespan) || $timespan == -1))
2068
		{
2069
			$token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2070
			
2071
			$key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2072
			if ($key === $token)
2073
			{
2074
				return true;
2075
			}
2076
		}
2077
	}
2078
	if ($trigger)
2079
	{
2080
		trigger_error($user->lang['FORM_INVALID'] . $return_page);
2081
	}
2082
	return false;
2083
}
2084
2085
// Message/Login boxes
2086
2087
/**
2088
* Build Confirm box
2089
* @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2090
* @param string $title Title/Message used for confirm box.
2091
*		message text is _CONFIRM appended to title.
2092
*		If title cannot be found in user->lang a default one is displayed
2093
*		If title_CONFIRM cannot be found in user->lang the text given is used.
2094
* @param string $hidden Hidden variables
2095
* @param string $html_body Template used for confirm box
2096
* @param string $u_action Custom form action
2097
*/
2098
function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2099
{
2100
	global $user, $template, $db;
2101
	global $phpEx, $phpbb_root_path;
2102
2103
	if (isset($_POST['cancel']))
2104
	{
2105
		return false;
2106
	}
2107
2108
	$confirm = false;
2109
	if (isset($_POST['confirm']))
2110
	{
2111
		// language frontier
2112
		if ($_POST['confirm'] === $user->lang['YES'])
2113
		{
2114
			$confirm = true;
2115
		}
2116
	}
2117
2118
	if ($check && $confirm)
2119
	{
2120
		$user_id = request_var('user_id', 0);
2121
		$session_id = request_var('sess', '');
2122
		$confirm_key = request_var('confirm_key', '');
2123
2124
		if ($user_id != $user->data['user_id'] || $session_id != $user->session_id || !$confirm_key || !$user->data['user_last_confirm_key'] || $confirm_key != $user->data['user_last_confirm_key'])
2125
		{
2126
			return false;
2127
		}
2128
2129
		// Reset user_last_confirm_key
2130
		$sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2131
			WHERE user_id = " . $user->data['user_id'];
2132
		$db->sql_query($sql);
2133
2134
		return true;
2135
	}
2136
	else if ($check)
2137
	{
2138
		return false;
2139
	}
2140
2141
	$s_hidden_fields = build_hidden_fields(array(
2142
		'user_id'	=> $user->data['user_id'],
2143
		'sess'		=> $user->session_id,
2144
		'sid'		=> $user->session_id)
2145
	);
2146
2147
	// generate activation key
2148
	$confirm_key = gen_rand_string(10);
2149
2150
	if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2151
	{
2152
		adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2153
	}
2154
	else
2155
	{
2156
		page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2157
	}
2158
2159
	$template->set_filenames(array(
2160
		'body' => $html_body)
2161
	);
2162
2163
	// If activation key already exist, we better do not re-use the key (something very strange is going on...)
2164
	if (request_var('confirm_key', ''))
2165
	{
2166
		// This should not occur, therefore we cancel the operation to safe the user
2167
		return false;
2168
	}
2169
2170
	// re-add sid / transform & to &amp; for user->page (user->page is always using &)
2171
	$use_page = ($u_action) ? $phpbb_root_path . $u_action : $phpbb_root_path . str_replace('&', '&amp;', $user->page['page']);
2172
	$u_action = reapply_sid($use_page);
2173
	$u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2174
2175
	$template->assign_vars(array(
2176
		'MESSAGE_TITLE'		=> (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2177
		'MESSAGE_TEXT'		=> (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2178
2179
		'YES_VALUE'			=> $user->lang['YES'],
2180
		'S_CONFIRM_ACTION'	=> $u_action,
2181
		'S_HIDDEN_FIELDS'	=> $hidden . $s_hidden_fields)
2182
	);
2183
2184
	$sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2185
		WHERE user_id = " . $user->data['user_id'];
2186
	$db->sql_query($sql);
2187
2188
	if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2189
	{
2190
		adm_page_footer();
2191
	}
2192
	else
2193
	{
2194
		page_footer();
2195
	}
2196
}
2197
2198
/**
2199
* Generate login box or verify password
2200
*/
2201
function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2202
{
2203
	global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
2204
2205
	$err = '';
2206
2207
	// Make sure user->setup() has been called
2208
	if (empty($user->lang))
2209
	{
2210
		$user->setup();
2211
	}
2212
2213
	// Print out error if user tries to authenticate as an administrator without having the privileges...
2214
	if ($admin && !$auth->acl_get('a_'))
2215
	{
2216
		// Not authd
2217
		// anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2218
		if ($user->data['is_registered'])
2219
		{
2220
			add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2221
		}
2222
		trigger_error('NO_AUTH_ADMIN');
2223
	}
2224
2225
	if (isset($_POST['login']))
2226
	{
2227
		// Get credential
2228
		if ($admin)
2229
		{
2230
			$credential = request_var('credential', '');
2231
2232
			if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
2233
			{
2234
				if ($user->data['is_registered'])
2235
				{
2236
					add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2237
				}
2238
				trigger_error('NO_AUTH_ADMIN');
2239
			}
2240
2241
			$password	= request_var('password_' . $credential, '', true);
2242
		}
2243
		else
2244
		{
2245
			$password	= request_var('password', '', true);
2246
		}
2247
2248
		$username	= request_var('username', '', true);
2249
		$autologin	= (!empty($_POST['autologin'])) ? true : false;
2250
		$viewonline = (!empty($_POST['viewonline'])) ? 0 : 1;
2251
		$admin 		= ($admin) ? 1 : 0;
2252
		$viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
2253
2254
		// Check if the supplied username is equal to the one stored within the database if re-authenticating
2255
		if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
2256
		{
2257
			// We log the attempt to use a different username...
2258
			add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2259
			trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
2260
		}
2261
2262
		// If authentication is successful we redirect user to previous page
2263
		$result = $auth->login($username, $password, $autologin, $viewonline, $admin);
2264
2265
		// If admin authentication and login, we will log if it was a success or not...
2266
		// We also break the operation on the first non-success login - it could be argued that the user already knows
2267
		if ($admin)
2268
		{
2269
			if ($result['status'] == LOGIN_SUCCESS)
2270
			{
2271
				add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
2272
			}
2273
			else
2274
			{
2275
				// Only log the failed attempt if a real user tried to.
2276
				// anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2277
				if ($user->data['is_registered'])
2278
				{
2279
					add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2280
				}
2281
			}
2282
		}
2283
2284
		// The result parameter is always an array, holding the relevant information...
2285
		if ($result['status'] == LOGIN_SUCCESS)
2286
		{
2287
			$redirect = request_var('redirect', "{$phpbb_root_path}index.$phpEx");
2288
			$message = ($l_success) ? $l_success : $user->lang['LOGIN_REDIRECT'];
2289
			$l_redirect = ($admin) ? $user->lang['PROCEED_TO_ACP'] : (($redirect === "{$phpbb_root_path}index.$phpEx" || $redirect === "index.$phpEx") ? $user->lang['RETURN_INDEX'] : $user->lang['RETURN_PAGE']);
2290
2291
			// append/replace SID (may change during the session for AOL users)
2292
			$redirect = reapply_sid($redirect);
2293
2294
			// Special case... the user is effectively banned, but we allow founders to login
2295
			if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
2296
			{
2297
				return;
2298
			}
2299
2300
			meta_refresh(3, $redirect);
2301
			trigger_error($message . '<br /><br />' . sprintf($l_redirect, '<a href="' . $redirect . '">', '</a>'));
2302
		}
2303
2304
		// Something failed, determine what...
2305
		if ($result['status'] == LOGIN_BREAK)
2306
		{
2307
			trigger_error($result['error_msg'], E_USER_ERROR);
2308
		}
2309
2310
		// Special cases... determine
2311
		switch ($result['status'])
2312
		{
2313
			case LOGIN_ERROR_ATTEMPTS:
2314
2315
				// Show confirm image
2316
				$sql = 'DELETE FROM ' . CONFIRM_TABLE . "
2317
					WHERE session_id = '" . $db->sql_escape($user->session_id) . "'
2318
						AND confirm_type = " . CONFIRM_LOGIN;
2319
				$db->sql_query($sql);
2320
2321
				// Generate code
2322
				$code = gen_rand_string(mt_rand(5, 8));
2323
				$confirm_id = md5(unique_id($user->ip));
2324
				$seed = hexdec(substr(unique_id(), 4, 10));
2325
2326
				// compute $seed % 0x7fffffff
2327
				$seed -= 0x7fffffff * floor($seed / 0x7fffffff);
2328
2329
				$sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array(
2330
					'confirm_id'	=> (string) $confirm_id,
2331
					'session_id'	=> (string) $user->session_id,
2332
					'confirm_type'	=> (int) CONFIRM_LOGIN,
2333
					'code'			=> (string) $code,
2334
					'seed'			=> (int) $seed)
2335
				);
2336
				$db->sql_query($sql);
2337
2338
				$template->assign_vars(array(
2339
					'S_CONFIRM_CODE'			=> true,
2340
					'CONFIRM_ID'				=> $confirm_id,
2341
					'CONFIRM_IMAGE'				=> '<img src="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=confirm&amp;id=' . $confirm_id . '&amp;type=' . CONFIRM_LOGIN) . '" alt="" title="" />',
2342
					'L_LOGIN_CONFIRM_EXPLAIN'	=> sprintf($user->lang['LOGIN_CONFIRM_EXPLAIN'], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>'),
2343
				));
2344
2345
				$err = $user->lang[$result['error_msg']];
2346
2347
			break;
2348
2349
			case LOGIN_ERROR_PASSWORD_CONVERT:
2350
				$err = sprintf(
2351
					$user->lang[$result['error_msg']],
2352
					($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
2353
					($config['email_enable']) ? '</a>' : '',
2354
					($config['board_contact']) ? '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">' : '',
2355
					($config['board_contact']) ? '</a>' : ''
2356
				);
2357
			break;
2358
2359
			// Username, password, etc...
2360
			default:
2361
				$err = $user->lang[$result['error_msg']];
2362
2363
				// Assign admin contact to some error messages
2364
				if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
2365
				{
2366
					$err = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>');
2367
				}
2368
				
2369
			break;
2370
		}
2371
	}
2372
2373
	if (!$redirect)
2374
	{
2375
		// We just use what the session code determined...
2376
		// If we are not within the admin directory we use the page dir...
2377
		$redirect = '';
2378
2379
		if (!$admin)
2380
		{
2381
			$redirect .= ($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '';
2382
		}
2383
2384
		$redirect .= $user->page['page_name'] . (($user->page['query_string']) ? '?' . htmlspecialchars($user->page['query_string']) : '');
2385
	}
2386
2387
	// Assign credential for username/password pair
2388
	$credential = ($admin) ? md5(unique_id()) : false;
2389
2390
	$s_hidden_fields = array(
2391
		'redirect'	=> $redirect,
2392
		'sid'		=> $user->session_id,
2393
	);
2394
2395
	if ($admin)
2396
	{
2397
		$s_hidden_fields['credential'] = $credential;
2398
	}
2399
2400
	$s_hidden_fields = build_hidden_fields($s_hidden_fields);
2401
2402
	$template->assign_vars(array(
2403
		'LOGIN_ERROR'		=> $err,
2404
		'LOGIN_EXPLAIN'		=> $l_explain,
2405
2406
		'U_SEND_PASSWORD' 		=> ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',
2407
		'U_RESEND_ACTIVATION'	=> ($config['require_activation'] != USER_ACTIVATION_NONE && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
2408
		'U_TERMS_USE'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
2409
		'U_PRIVACY'				=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
2410
2411
		'S_DISPLAY_FULL_LOGIN'	=> ($s_display) ? true : false,
2412
		'S_LOGIN_ACTION'		=> (!$admin) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("index.$phpEx", false, true, $user->session_id), // Needs to stay index.$phpEx because we are within the admin directory
2413
		'S_HIDDEN_FIELDS' 		=> $s_hidden_fields,
2414
2415
		'S_ADMIN_AUTH'			=> $admin,
2416
		'USERNAME'				=> ($admin) ? $user->data['username'] : '',
2417
2418
		'USERNAME_CREDENTIAL'	=> 'username',
2419
		'PASSWORD_CREDENTIAL'	=> ($admin) ? 'password_' . $credential : 'password',
2420
	));
2421
2422
	page_header($user->lang['LOGIN']);
2423
2424
	$template->set_filenames(array(
2425
		'body' => 'login_body.html')
2426
	);
2427
	make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
2428
2429
	page_footer();
2430
}
2431
2432
/**
2433
* Generate forum login box
2434
*/
2435
function login_forum_box($forum_data)
2436
{
2437
	global $db, $config, $user, $template, $phpEx;
2438
2439
	$password = request_var('password', '', true);
2440
2441
	$sql = 'SELECT forum_id
2442
		FROM ' . FORUMS_ACCESS_TABLE . '
2443
		WHERE forum_id = ' . $forum_data['forum_id'] . '
2444
			AND user_id = ' . $user->data['user_id'] . "
2445
			AND session_id = '" . $db->sql_escape($user->session_id) . "'";
2446
	$result = $db->sql_query($sql);
2447
	$row = $db->sql_fetchrow($result);
2448
	$db->sql_freeresult($result);
2449
2450
	if ($row)
2451
	{
2452
		return true;
2453
	}
2454
2455
	if ($password)
2456
	{
2457
		// Remove expired authorised sessions
2458
		$sql = 'SELECT f.session_id
2459
			FROM ' . FORUMS_ACCESS_TABLE . ' f
2460
			LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
2461
			WHERE s.session_id IS NULL';
2462
		$result = $db->sql_query($sql);
2463
2464
		if ($row = $db->sql_fetchrow($result))
2465
		{
2466
			$sql_in = array();
2467
			do
2468
			{
2469
				$sql_in[] = (string) $row['session_id'];
2470
			}
2471
			while ($row = $db->sql_fetchrow($result));
2472
2473
			// Remove expired sessions
2474
			$sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
2475
				WHERE ' . $db->sql_in_set('session_id', $sql_in);
2476
			$db->sql_query($sql);
2477
		}
2478
		$db->sql_freeresult($result);
2479
2480
		if (phpbb_check_hash($password, $forum_data['forum_password']))
2481
		{
2482
			$sql_ary = array(
2483
				'forum_id'		=> (int) $forum_data['forum_id'],
2484
				'user_id'		=> (int) $user->data['user_id'],
2485
				'session_id'	=> (string) $user->session_id,
2486
			);
2487
2488
			$db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
2489
2490
			return true;
2491
		}
2492
2493
		$template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
2494
	}
2495
2496
	page_header($user->lang['LOGIN']);
2497
2498
	$template->assign_vars(array(
2499
		'S_HIDDEN_FIELDS'		=> build_hidden_fields(array('f' => $forum_data['forum_id'])))
2500
	);
2501
2502
	$template->set_filenames(array(
2503
		'body' => 'login_forum.html')
2504
	);
2505
	
2506
	page_footer();
2507
}
2508
2509
// Little helpers
2510
2511
/**
2512
* Little helper for the build_hidden_fields function
2513
*/
2514
function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
2515
{
2516
	$hidden_fields = '';
2517
2518
	if (!is_array($value))
2519
	{
2520
		$value = ($stripslashes) ? stripslashes($value) : $value;
2521
		$value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
2522
2523
		$hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
2524
	}
2525
	else
2526
	{
2527
		foreach ($value as $_key => $_value)
2528
		{
2529
			$_key = ($stripslashes) ? stripslashes($_key) : $_key;
2530
			$_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
2531
2532
			$hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
2533
		}
2534
	}
2535
2536
	return $hidden_fields;
2537
}
2538
2539
/**
2540
* Build simple hidden fields from array
2541
*
2542
* @param array $field_ary an array of values to build the hidden field from
2543
* @param bool $specialchar if true, keys and values get specialchared
2544
* @param bool $stripslashes if true, keys and values get stripslashed
2545
*
2546
* @return string the hidden fields
2547
*/
2548
function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
2549
{
2550
	$s_hidden_fields = '';
2551
2552
	foreach ($field_ary as $name => $vars)
2553
	{
2554
		$name = ($stripslashes) ? stripslashes($name) : $name;
2555
		$name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
2556
2557
		$s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
2558
	}
2559
2560
	return $s_hidden_fields;
2561
}
2562
2563
/**
2564
* Parse cfg file
2565
*/
2566
function parse_cfg_file($filename, $lines = false)
2567
{
2568
	$parsed_items = array();
2569
2570
	if ($lines === false)
2571
	{
2572
		$lines = file($filename);
2573
	}
2574
2575
	foreach ($lines as $line)
2576
	{
2577
		$line = trim($line);
2578
2579
		if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
2580
		{
2581
			continue;
2582
		}
2583
2584
		// Determine first occurrence, since in values the equal sign is allowed
2585
		$key = strtolower(trim(substr($line, 0, $delim_pos)));
2586
		$value = trim(substr($line, $delim_pos + 1));
2587
2588
		if (in_array($value, array('off', 'false', '0')))
2589
		{
2590
			$value = false;
2591
		}
2592
		else if (in_array($value, array('on', 'true', '1')))
2593
		{
2594
			$value = true;
2595
		}
2596
		else if (!trim($value))
2597
		{
2598
			$value = '';
2599
		}
2600
		else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
2601
		{
2602
			$value = substr($value, 1, sizeof($value)-2);
2603
		}
2604
	
2605
		$parsed_items[$key] = $value;
2606
	}
2607
	
2608
	return $parsed_items;
2609
}
2610
2611
/**
2612
* Add log event
2613
*/
2614
function add_log()
2615
{
2616
	global $db, $user;
2617
2618
	$args = func_get_args();
2619
2620
	$mode			= array_shift($args);
2621
	$reportee_id	= ($mode == 'user') ? intval(array_shift($args)) : '';
2622
	$forum_id		= ($mode == 'mod') ? intval(array_shift($args)) : '';
2623
	$topic_id		= ($mode == 'mod') ? intval(array_shift($args)) : '';
2624
	$action			= array_shift($args);
2625
	$data			= (!sizeof($args)) ? '' : serialize($args);
2626
2627
	$sql_ary = array(
2628
		'user_id'		=> (empty($user->data)) ? ANONYMOUS : $user->data['user_id'],
2629
		'log_ip'		=> $user->ip,
2630
		'log_time'		=> time(),
2631
		'log_operation'	=> $action,
2632
		'log_data'		=> $data,
2633
	);
2634
	
2635
	switch ($mode)
2636
	{
2637
		case 'admin':
2638
			$sql_ary['log_type'] = LOG_ADMIN;
2639
		break;
2640
		
2641
		case 'mod':
2642
			$sql_ary += array(
2643
				'log_type'	=> LOG_MOD,
2644
				'forum_id'	=> $forum_id,
2645
				'topic_id'	=> $topic_id
2646
			);
2647
		break;
2648
2649
		case 'user':
2650
			$sql_ary += array(
2651
				'log_type'		=> LOG_USERS,
2652
				'reportee_id'	=> $reportee_id
2653
			);
2654
		break;
2655
2656
		case 'critical':
2657
			$sql_ary['log_type'] = LOG_CRITICAL;
2658
		break;
2659
		
2660
		default:
2661
			return false;
2662
	}
2663
2664
	$db->sql_query('INSERT INTO ' . LOG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
2665
2666
	return $db->sql_nextid();
2667
}
2668
2669
/**
2670
* Return a nicely formatted backtrace (parts from the php manual by diz at ysagoon dot com)
2671
*/
2672
function get_backtrace()
2673
{
2674
	global $phpbb_root_path;
2675
2676
	$output = '<div style="font-family: monospace;">';
2677
	$backtrace = debug_backtrace();
2678
	$path = phpbb_realpath($phpbb_root_path);
2679
2680
	foreach ($backtrace as $number => $trace)
2681
	{
2682
		// We skip the first one, because it only shows this file/function
2683
		if ($number == 0)
2684
		{
2685
			continue;
2686
		}
2687
2688
		// Strip the current directory from path
2689
		if (empty($trace['file']))
2690
		{
2691
			$trace['file'] = '';
2692
		}
2693
		else
2694
		{
2695
			$trace['file'] = str_replace(array($path, '\\'), array('', '/'), $trace['file']);
2696
			$trace['file'] = substr($trace['file'], 1);
2697
		}
2698
		$args = array();
2699
2700
		// If include/require/include_once is not called, do not show arguments - they may contain sensible information
2701
		if (!in_array($trace['function'], array('include', 'require', 'include_once')))
2702
		{
2703
			unset($trace['args']);
2704
		}
2705
		else
2706
		{
2707
			// Path...
2708
			if (!empty($trace['args'][0]))
2709
			{
2710
				$argument = htmlspecialchars($trace['args'][0]);
2711
				$argument = str_replace(array($path, '\\'), array('', '/'), $argument);
2712
				$argument = substr($argument, 1);
2713
				$args[] = "'{$argument}'";
2714
			}
2715
		}
2716
2717
		$trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
2718
		$trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
2719
2720
		$output .= '<br />';
2721
		$output .= '<b>FILE:</b> ' . htmlspecialchars($trace['file']) . '<br />';
2722
		$output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
2723
2724
		$output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']) . '(' . ((sizeof($args)) ? implode(', ', $args) : '') . ')<br />';
2725
	}
2726
	$output .= '</div>';
2727
	return $output;
2728
}
2729
2730
/**
2731
* This function returns a regular expression pattern for commonly used expressions
2732
* Use with / as delimiter for email mode and # for url modes
2733
* mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
2734
*/
2735
function get_preg_expression($mode)
2736
{
2737
	switch ($mode)
2738
	{
2739
		case 'email':
2740
			return '[a-z0-9&\'\.\-_\+]+@[a-z0-9\-]+\.([a-z0-9\-]+\.)*[a-z]+';
2741
		break;
2742
2743
		case 'bbcode_htm':
2744
			return array(
2745
				'#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
2746
				'#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
2747
				'#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
2748
				'#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
2749
				'#<!\-\- .*? \-\->#s',
2750
				'#<.*?>#s',
2751
			);
2752
		break;
2753
2754
		// Whoa these look impressive!
2755
		// The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
2756
		// can be found in the develop directory
2757
		case 'ipv4':
2758
			return '#^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$#';
2759
		break;
2760
2761
		case 'ipv6':
2762
			return '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){5}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:))$#i';
2763
		break;
2764
2765
		case 'url':
2766
		case 'url_inline':
2767
			$inline = ($mode == 'url') ? ')' : '';
2768
			$scheme = ($mode == 'url') ? '[a-z\d+\-.]' : '[a-z\d+]'; // avoid automatic parsing of "word" in "last word.http://..."
2769
			// generated with regex generation file in the develop folder
2770
			return "[a-z]$scheme*:/{2}(?:(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2771
		break;
2772
2773
		case 'www_url':
2774
		case 'www_url_inline':
2775
			$inline = ($mode == 'www_url') ? ')' : '';
2776
			return "www\.(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2777
		break;
2778
2779
		case 'relative_url':
2780
		case 'relative_url_inline':
2781
			$inline = ($mode == 'relative_url') ? ')' : '';
2782
			return "(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2783
		break;
2784
	}
2785
2786
	return '';
2787
}
2788
2789
/**
2790
* Returns the first block of the specified IPv6 address and as many additional
2791
* ones as specified in the length paramater.
2792
* If length is zero, then an empty string is returned.
2793
* If length is greater than 3 the complete IP will be returned
2794
*/
2795
function short_ipv6($ip, $length)
2796
{
2797
	if ($length < 1)
2798
	{
2799
		return '';
2800
	}
2801
2802
	// extend IPv6 addresses
2803
	$blocks = substr_count($ip, ':') + 1;
2804
	if ($blocks < 9)
2805
	{
2806
		$ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
2807
	}
2808
	if ($ip[0] == ':')
2809
	{
2810
		$ip = '0000' . $ip;
2811
	}
2812
	if ($length < 4)
2813
	{
2814
		$ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
2815
	}
2816
2817
	return $ip;
2818
}
2819
2820
/**
2821
* Wrapper for php's checkdnsrr function.
2822
*
2823
* The windows failover is from the php manual
2824
* Please make sure to check the return value for === true and === false, since NULL could
2825
* be returned too.
2826
*
2827
* @return true if entry found, false if not, NULL if this function is not supported by this environment
2828
*/
2829
function phpbb_checkdnsrr($host, $type = '')
2830
{
2831
	$type = (!$type) ? 'MX' : $type;
2832
2833
	if (DIRECTORY_SEPARATOR == '\\')
2834
	{
2835
		if (!function_exists('exec'))
2836
		{
2837
			return NULL;
2838
		}
2839
2840
		// @exec('nslookup -retry=1 -timout=1 -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host), $output);
2841
		@exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host), $output);
2842
2843
		// If output is empty, the nslookup failed
2844
		if (empty($output))
2845
		{
2846
			return NULL;
2847
		}
2848
2849
		foreach ($output as $line)
2850
		{
2851
			if (!trim($line))
2852
			{
2853
				continue;
2854
			}
2855
2856
			// Valid records begin with host name:
2857
			if (strpos($line, $host) === 0)
2858
			{
2859
				return true;
2860
			}
2861
		}
2862
2863
		return false;
2864
	}
2865
	else if (function_exists('checkdnsrr'))
2866
	{
2867
		return (checkdnsrr($host, $type)) ? true : false;
2868
	}
2869
2870
	return NULL;
2871
}
2872
2873
// Handler, header and footer
2874
2875
/**
2876
* Error and message handler, call with trigger_error if reqd
2877
*/
2878
function msg_handler($errno, $msg_text, $errfile, $errline)
2879
{
2880
	global $cache, $db, $auth, $template, $config, $user;
2881
	global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;
2882
2883
	// Do not display notices if we suppress them via @
2884
	if (error_reporting() == 0)
2885
	{
2886
		return;
2887
	}
2888
2889
	// Message handler is stripping text. In case we need it, we are possible to define long text...
2890
	if (isset($msg_long_text) && $msg_long_text && !$msg_text)
2891
	{
2892
		$msg_text = $msg_long_text;
2893
	}
2894
2895
	switch ($errno)
2896
	{
2897
		case E_NOTICE:
2898
		case E_WARNING:
2899
2900
			// Check the error reporting level and return if the error level does not match
2901
			// If DEBUG is defined the default level is E_ALL
2902
			if (($errno & ((defined('DEBUG')) ? E_ALL : error_reporting())) == 0)
2903
			{
2904
				return;
2905
			}
2906
2907
			if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
2908
			{
2909
				// flush the content, else we get a white page if output buffering is on
2910
				if ($config['gzip_compress'])
2911
				{
2912
					if (@extension_loaded('zlib') && !headers_sent())
2913
					{
2914
						ob_flush();
2915
					}
2916
				}
2917
2918
				// remove complete path to installation, with the risk of changing backslashes meant to be there
2919
				$errfile = str_replace(array(phpbb_realpath($phpbb_root_path), '\\'), array('', '/'), $errfile);
2920
				$msg_text = str_replace(array(phpbb_realpath($phpbb_root_path), '\\'), array('', '/'), $msg_text);
2921
2922
				echo '<b>[phpBB Debug] PHP Notice</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
2923
			}
2924
2925
			return;
2926
2927
		break;
2928
2929
		case E_USER_ERROR:
2930
2931
			if (!empty($user) && !empty($user->lang))
2932
			{
2933
				$msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
2934
				$msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
2935
2936
				$l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
2937
				$l_notify = '';
2938
2939
				if (!empty($config['board_contact']))
2940
				{
2941
					$l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
2942
				}
2943
			}
2944
			else
2945
			{
2946
				$msg_title = 'General Error';
2947
				$l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
2948
				$l_notify = '';
2949
2950
				if (!empty($config['board_contact']))
2951
				{
2952
					$l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
2953
				}
2954
			}
2955
2956
			garbage_collection();
2957
2958
			// Try to not call the adm page data...
2959
2960
			echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
2961
			echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">';
2962
			echo '<head>';
2963
			echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
2964
			echo '<title>' . $msg_title . '</title>';
2965
			echo '<style type="text/css">' . "\n" . '<!--' . "\n";
2966
			echo '* { margin: 0; padding: 0; } html { font-size: 100%; height: 100%; margin-bottom: 1px; background-color: #E4EDF0; } body { font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; color: #536482; background: #E4EDF0; font-size: 62.5%; margin: 0; } ';
2967
			echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
2968
			echo '#wrap { padding: 0 20px 15px 20px; min-width: 615px; } #page-header { text-align: right; height: 40px; } #page-footer { clear: both; font-size: 1em; text-align: center; } ';
2969
			echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px  #A9B8C2; } ';
2970
			echo '#errorpage #page-header a { font-weight: bold; line-height: 6em; } #errorpage #content { padding: 10px; } #errorpage #content h1 { line-height: 1.2em; margin-bottom: 0; color: #DF075C; } ';
2971
			echo '#errorpage #content div { margin-top: 20px; margin-bottom: 5px; border-bottom: 1px solid #CCCCCC; padding-bottom: 5px; color: #333333; font: bold 1.2em "Lucida Grande", Arial, Helvetica, sans-serif; text-decoration: none; line-height: 120%; text-align: left; } ';
2972
			echo "\n" . '//-->' . "\n";
2973
			echo '</style>';
2974
			echo '</head>';
2975
			echo '<body id="errorpage">';
2976
			echo '<div id="wrap">';
2977
			echo '	<div id="page-header">';
2978
			echo '		' . $l_return_index;
2979
			echo '	</div>';
2980
			echo '	<div id="acp">';
2981
			echo '	<div class="panel">';
2982
			echo '		<div id="content">';
2983
			echo '			<h1>' . $msg_title . '</h1>';
2984
			
2985
			echo '			<div>' . $msg_text . '</div>';
2986
			
2987
			echo $l_notify;
2988
2989
			echo '		</div>';
2990
			echo '	</div>';
2991
			echo '	</div>';
2992
			echo '	<div id="page-footer">';
2993
			echo '		Powered by phpBB &copy; 2000, 2002, 2005, 2007 <a href="http://www.phpbb.com/">phpBB Group</a>';
2994
			echo '	</div>';
2995
			echo '</div>';
2996
			echo '</body>';
2997
			echo '</html>';
2998
			
2999
			exit_handler();
3000
		break;
3001
3002
		case E_USER_WARNING:
3003
		case E_USER_NOTICE:
3004
3005
			define('IN_ERROR_HANDLER', true);
3006
3007
			if (empty($user->data))
3008
			{
3009
				$user->session_begin();
3010
			}
3011
3012
			// We re-init the auth array to get correct results on login/logout
3013
			$auth->acl($user->data);
3014
3015
			if (empty($user->lang))
3016
			{
3017
				$user->setup();
3018
			}
3019
3020
			$msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3021
			$msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3022
3023
			if (!defined('HEADER_INC'))
3024
			{
3025
				if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3026
				{
3027
					adm_page_header($msg_title);
3028
				}
3029
				else
3030
				{
3031
					page_header($msg_title);
3032
				}
3033
			}
3034
3035
			$template->set_filenames(array(
3036
				'body' => 'message_body.html')
3037
			);
3038
3039
			$template->assign_vars(array(
3040
				'MESSAGE_TITLE'		=> $msg_title,
3041
				'MESSAGE_TEXT'		=> $msg_text,
3042
				'S_USER_WARNING'	=> ($errno == E_USER_WARNING) ? true : false,
3043
				'S_USER_NOTICE'		=> ($errno == E_USER_NOTICE) ? true : false)
3044
			);
3045
3046
			// We do not want the cron script to be called on error messages
3047
			define('IN_CRON', true);
3048
			
3049
			if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3050
			{
3051
				adm_page_footer();
3052
			}
3053
			else
3054
			{
3055
				page_footer();
3056
			}
3057
3058
			exit_handler();
3059
		break;
3060
	}
3061
3062
	// If we notice an error not handled here we pass this back to PHP by returning false
3063
	// This may not work for all php versions
3064
	return false;
3065
}
3066
3067
/**
3068
* Generate page header
3069
*/
3070
function page_header($page_title = '', $display_online_list = true)
3071
{
3072
	global $db, $config, $template, $SID, $_SID, $user, $auth, $phpEx, $phpbb_root_path;
3073
3074
	if (defined('HEADER_INC'))
3075
	{
3076
		return;
3077
	}
3078
	
3079
	define('HEADER_INC', true);
3080
3081
	// gzip_compression
3082
	if ($config['gzip_compress'])
3083
	{
3084
		if (@extension_loaded('zlib') && !headers_sent())
3085
		{
3086
			ob_start('ob_gzhandler');
3087
		}
3088
	}
3089
3090
	// Generate logged in/logged out status
3091
	if ($user->data['user_id'] != ANONYMOUS)
3092
	{
3093
		$u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
3094
		$l_login_logout = sprintf($user->lang['LOGOUT_USER'], $user->data['username']);
3095
	}
3096
	else
3097
	{
3098
		$u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login');
3099
		$l_login_logout = $user->lang['LOGIN'];
3100
	}
3101
3102
	// Last visit date/time
3103
	$s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
3104
3105
	// Get users online list ... if required
3106
	$l_online_users = $online_userlist = $l_online_record = '';
3107
3108
	if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
3109
	{
3110
		$logged_visible_online = $logged_hidden_online = $guests_online = $prev_user_id = 0;
3111
		$prev_session_ip = $reading_sql = '';
3112
3113
		if (!empty($_REQUEST['f']))
3114
		{
3115
			$f = request_var('f', 0);
3116
3117
			$reading_sql = ' AND s.session_page ' . $db->sql_like_expression("{$db->any_char}_f_={$f}x{$db->any_char}");
3118
		}
3119
3120
		// Get number of online guests
3121
		if (!$config['load_online_guests'])
3122
		{
3123
			if ($db->sql_layer === 'sqlite')
3124
			{
3125
				$sql = 'SELECT COUNT(session_ip) as num_guests
3126
					FROM (
3127
						SELECT DISTINCT s.session_ip
3128
							FROM ' . SESSIONS_TABLE . ' s
3129
							WHERE s.session_user_id = ' . ANONYMOUS . '
3130
								AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
3131
								$reading_sql .
3132
					')';
3133
			}
3134
			else
3135
			{
3136
				$sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
3137
					FROM ' . SESSIONS_TABLE . ' s
3138
					WHERE s.session_user_id = ' . ANONYMOUS . '
3139
						AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
3140
					$reading_sql;
3141
			}
3142
			$result = $db->sql_query($sql);
3143
			$guests_online = (int) $db->sql_fetchfield('num_guests');
3144
			$db->sql_freeresult($result);
3145
		}
3146
3147
		$sql = 'SELECT u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour, s.session_ip, s.session_viewonline
3148
			FROM ' . USERS_TABLE . ' u, ' . SESSIONS_TABLE . ' s
3149
			WHERE s.session_time >= ' . (time() - (intval($config['load_online_time']) * 60)) .
3150
				$reading_sql .
3151
				((!$config['load_online_guests']) ? ' AND s.session_user_id <> ' . ANONYMOUS : '') . '
3152
				AND u.user_id = s.session_user_id
3153
			ORDER BY u.username_clean ASC, s.session_ip ASC';
3154
		$result = $db->sql_query($sql);
3155
3156
		while ($row = $db->sql_fetchrow($result))
3157
		{
3158
			// User is logged in and therefore not a guest
3159
			if ($row['user_id'] != ANONYMOUS)
3160
			{
3161
				// Skip multiple sessions for one user
3162
				if ($row['user_id'] != $prev_user_id)
3163
				{
3164
					if ($row['session_viewonline'])
3165
					{
3166
						$logged_visible_online++;
3167
					}
3168
					else
3169
					{
3170
						$row['username'] = '<em>' . $row['username'] . '</em>';
3171
						$logged_hidden_online++;
3172
					}
3173
3174
					if (($row['session_viewonline']) || $auth->acl_get('u_viewonline'))
3175
					{
3176
						$user_online_link = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
3177
						$online_userlist .= ($online_userlist != '') ? ', ' . $user_online_link : $user_online_link;
3178
					}
3179
				}
3180
3181
				$prev_user_id = $row['user_id'];
3182
			}
3183
			else
3184
			{
3185
				// Skip multiple sessions for one user
3186
				if ($row['session_ip'] != $prev_session_ip)
3187
				{
3188
					$guests_online++;
3189
				}
3190
			}
3191
3192
			$prev_session_ip = $row['session_ip'];
3193
		}
3194
		$db->sql_freeresult($result);
3195
3196
		if (!$online_userlist)
3197
		{
3198
			$online_userlist = $user->lang['NO_ONLINE_USERS'];
3199
		}
3200
3201
		if (empty($_REQUEST['f']))
3202
		{
3203
			$online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
3204
		}
3205
		else
3206
		{
3207
			$l_online = ($guests_online == 1) ? $user->lang['BROWSING_FORUM_GUEST'] : $user->lang['BROWSING_FORUM_GUESTS'];
3208
			$online_userlist = sprintf($l_online, $online_userlist, $guests_online);
3209
		}
3210
3211
		$total_online_users = $logged_visible_online + $logged_hidden_online + $guests_online;
3212
3213
		if ($total_online_users > $config['record_online_users'])
3214
		{
3215
			set_config('record_online_users', $total_online_users, true);
3216
			set_config('record_online_date', time(), true);
3217
		}
3218
3219
		// Build online listing
3220
		$vars_online = array(
3221
			'ONLINE'	=> array('total_online_users', 'l_t_user_s'),
3222
			'REG'		=> array('logged_visible_online', 'l_r_user_s'),
3223
			'HIDDEN'	=> array('logged_hidden_online', 'l_h_user_s'),
3224
			'GUEST'		=> array('guests_online', 'l_g_user_s')
3225
		);
3226
3227
		foreach ($vars_online as $l_prefix => $var_ary)
3228
		{
3229
			switch (${$var_ary[0]})
3230
			{
3231
				case 0:
3232
					${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL'];
3233
				break;
3234
3235
				case 1:
3236
					${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL'];
3237
				break;
3238
3239
				default:
3240
					${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL'];
3241
				break;
3242
			}
3243
		}
3244
		unset($vars_online);
3245
3246
		$l_online_users = sprintf($l_t_user_s, $total_online_users);
3247
		$l_online_users .= sprintf($l_r_user_s, $logged_visible_online);
3248
		$l_online_users .= sprintf($l_h_user_s, $logged_hidden_online);
3249
		$l_online_users .= sprintf($l_g_user_s, $guests_online);
3250
3251
		$l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date']));
3252
3253
		$l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES';
3254
		$l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']);
3255
	}
3256
	else
3257
	{
3258
		$l_online_time = '';
3259
	}
3260
3261
	$l_privmsgs_text = $l_privmsgs_text_unread = '';
3262
	$s_privmsg_new = false;
3263
3264
	// Obtain number of new private messages if user is logged in
3265
	if (isset($user->data['is_registered']) && $user->data['is_registered'])
3266
	{
3267
		if ($user->data['user_new_privmsg'])
3268
		{
3269
			$l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS'];
3270
			$l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']);
3271
3272
			if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
3273
			{
3274
				$sql = 'UPDATE ' . USERS_TABLE . '
3275
					SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
3276
					WHERE user_id = ' . $user->data['user_id'];
3277
				$db->sql_query($sql);
3278
3279
				$s_privmsg_new = true;
3280
			}
3281
			else
3282
			{
3283
				$s_privmsg_new = false;
3284
			}
3285
		}
3286
		else
3287
		{
3288
			$l_privmsgs_text = $user->lang['NO_NEW_PM'];
3289
			$s_privmsg_new = false;
3290
		}
3291
3292
		$l_privmsgs_text_unread = '';
3293
3294
		if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg'])
3295
		{
3296
			$l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS'];
3297
			$l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']);
3298
		}
3299
	}
3300
3301
	// Which timezone?
3302
	$tz = ($user->data['user_id'] != ANONYMOUS) ? strval(doubleval($user->data['user_timezone'])) : strval(doubleval($config['board_timezone']));
3303
	
3304
	// The following assigns all _common_ variables that may be used at any point in a template.
3305
	$template->assign_vars(array(
3306
		'SITENAME'						=> $config['sitename'],
3307
		'SITE_DESCRIPTION'				=> $config['site_desc'],
3308
		'PAGE_TITLE'					=> $page_title,
3309
		'SCRIPT_NAME'					=> str_replace('.' . $phpEx, '', $user->page['page_name']),
3310
		'LAST_VISIT_DATE'				=> sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
3311
		'LAST_VISIT_YOU'				=> $s_last_visit,
3312
		'CURRENT_TIME'					=> sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
3313
		'TOTAL_USERS_ONLINE'			=> $l_online_users,
3314
		'LOGGED_IN_USER_LIST'			=> $online_userlist,
3315
		'RECORD_USERS'					=> $l_online_record,
3316
		'PRIVATE_MESSAGE_INFO'			=> $l_privmsgs_text,
3317
		'PRIVATE_MESSAGE_INFO_UNREAD'	=> $l_privmsgs_text_unread,
3318
3319
		'S_USER_NEW_PRIVMSG'			=> $user->data['user_new_privmsg'],
3320
		'S_USER_UNREAD_PRIVMSG'			=> $user->data['user_unread_privmsg'],
3321
3322
		'SID'				=> $SID,
3323
		'_SID'				=> $_SID,
3324
		'SESSION_ID'		=> $user->session_id,
3325
		'ROOT_PATH'			=> $phpbb_root_path,
3326
3327
		'L_LOGIN_LOGOUT'	=> $l_login_logout,
3328
		'L_INDEX'			=> $user->lang['FORUM_INDEX'],
3329
		'L_ONLINE_EXPLAIN'	=> $l_online_time,
3330
3331
		'U_PRIVATEMSGS'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
3332
		'U_RETURN_INBOX'		=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
3333
		'U_POPUP_PM'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=popup'),
3334
		'UA_POPUP_PM'			=> addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=popup')),
3335
		'U_MEMBERLIST'			=> append_sid("{$phpbb_root_path}memberlist.$phpEx"),
3336
		'U_MEMBERSLIST'			=> append_sid("{$phpbb_root_path}memberlist.$phpEx"),
3337
		'U_VIEWONLINE'			=> ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
3338
		'U_LOGIN_LOGOUT'		=> $u_login_logout,
3339
		'U_INDEX'				=> append_sid("{$phpbb_root_path}index.$phpEx"),
3340
		'U_SEARCH'				=> append_sid("{$phpbb_root_path}search.$phpEx"),
3341
		'U_REGISTER'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
3342
		'U_PROFILE'				=> append_sid("{$phpbb_root_path}ucp.$phpEx"),
3343
		'U_MODCP'				=> append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
3344
		'U_FAQ'					=> append_sid("{$phpbb_root_path}faq.$phpEx"),
3345
		'U_SEARCH_SELF'			=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
3346
		'U_SEARCH_NEW'			=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
3347
		'U_SEARCH_UNANSWERED'	=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
3348
		'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
3349
		'U_DELETE_COOKIES'		=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'),
3350
		'U_TEAM'				=> append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=leaders'),
3351
		'U_RESTORE_PERMISSIONS'	=> ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
3352
3353
		'S_USER_LOGGED_IN'		=> ($user->data['user_id'] != ANONYMOUS) ? true : false,
3354
		'S_AUTOLOGIN_ENABLED'	=> ($config['allow_autologin']) ? true : false,
3355
		'S_BOARD_DISABLED'		=> ($config['board_disable']) ? true : false,
3356
		'S_REGISTERED_USER'		=> $user->data['is_registered'],
3357
		'S_IS_BOT'				=> $user->data['is_bot'],
3358
		'S_USER_PM_POPUP'		=> $user->optionget('popuppm'),
3359
		'S_USER_LANG'			=> $user->lang['USER_LANG'],
3360
		'S_USER_BROWSER'		=> (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
3361
		'S_USERNAME'			=> $user->data['username'],
3362
		'S_CONTENT_DIRECTION'	=> $user->lang['DIRECTION'],
3363
		'S_CONTENT_FLOW_BEGIN'	=> ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
3364
		'S_CONTENT_FLOW_END'	=> ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
3365
		'S_CONTENT_ENCODING'	=> 'UTF-8',
3366
		'S_TIMEZONE'			=> ($user->data['user_dst'] || ($user->data['user_id'] == ANONYMOUS && $config['board_dst'])) ? sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], $user->lang['tz']['dst']) : sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], ''),
3367
		'S_DISPLAY_ONLINE_LIST'	=> ($l_online_time) ? 1 : 0,
3368
		'S_DISPLAY_SEARCH'		=> (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
3369
		'S_DISPLAY_PM'			=> ($config['allow_privmsg'] && $user->data['is_registered'] && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
3370
		'S_DISPLAY_MEMBERLIST'	=> (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
3371
		'S_NEW_PM'				=> ($s_privmsg_new) ? 1 : 0,
3372
3373
		'T_THEME_PATH'			=> "{$phpbb_root_path}styles/" . $user->theme['theme_path'] . '/theme',
3374
		'T_TEMPLATE_PATH'		=> "{$phpbb_root_path}styles/" . $user->theme['template_path'] . '/template',
3375
		'T_IMAGESET_PATH'		=> "{$phpbb_root_path}styles/" . $user->theme['imageset_path'] . '/imageset',
3376
		'T_IMAGESET_LANG_PATH'	=> "{$phpbb_root_path}styles/" . $user->theme['imageset_path'] . '/imageset/' . $user->data['user_lang'],
3377
		'T_IMAGES_PATH'			=> "{$phpbb_root_path}images/",
3378
		'T_SMILIES_PATH'		=> "{$phpbb_root_path}{$config['smilies_path']}/",
3379
		'T_AVATAR_PATH'			=> "{$phpbb_root_path}{$config['avatar_path']}/",
3380
		'T_AVATAR_GALLERY_PATH'	=> "{$phpbb_root_path}{$config['avatar_gallery_path']}/",
3381
		'T_ICONS_PATH'			=> "{$phpbb_root_path}{$config['icons_path']}/",
3382
		'T_RANKS_PATH'			=> "{$phpbb_root_path}{$config['ranks_path']}/",
3383
		'T_UPLOAD_PATH'			=> "{$phpbb_root_path}{$config['upload_path']}/",
3384
		'T_STYLESHEET_LINK'		=> (!$user->theme['theme_storedb']) ? "{$phpbb_root_path}styles/" . $user->theme['theme_path'] . '/theme/stylesheet.css' : "{$phpbb_root_path}style.$phpEx?sid=$user->session_id&amp;id=" . $user->theme['style_id'] . '&amp;lang=' . $user->data['user_lang'],
3385
		'T_STYLESHEET_NAME'		=> $user->theme['theme_name'],
3386
3387
		'SITE_LOGO_IMG'			=> $user->img('site_logo'))
3388
	);
3389
3390
	// application/xhtml+xml not used because of IE
3391
	header('Content-type: text/html; charset=UTF-8');
3392
3393
	header('Cache-Control: private, no-cache="set-cookie"');
3394
	header('Expires: 0');
3395
	header('Pragma: no-cache');
3396
3397
	return;
3398
}
3399
3400
/**
3401
* Generate page footer
3402
*/
3403
function page_footer($run_cron = true)
3404
{
3405
	global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
3406
3407
	// Output page creation time
3408
	if (defined('DEBUG'))
3409
	{
3410
		$mtime = explode(' ', microtime());
3411
		$totaltime = $mtime[0] + $mtime[1] - $starttime;
3412
3413
		if (!empty($_REQUEST['explain']) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report'))
3414
		{
3415
			$db->sql_report('display');
3416
		}
3417
3418
		$debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress']) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime);
3419
3420
		if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))
3421
		{
3422
			if (function_exists('memory_get_usage'))
3423
			{
3424
				if ($memory_usage = memory_get_usage())
3425
				{
3426
					global $base_memory_usage;
3427
					$memory_usage -= $base_memory_usage;
3428
					$memory_usage = ($memory_usage >= 1048576) ? round((round($memory_usage / 1048576 * 100) / 100), 2) . ' ' . $user->lang['MB'] : (($memory_usage >= 1024) ? round((round($memory_usage / 1024 * 100) / 100), 2) . ' ' . $user->lang['KB'] : $memory_usage . ' ' . $user->lang['BYTES']);
3429
3430
					$debug_output .= ' | Memory Usage: ' . $memory_usage;
3431
				}
3432
			}
3433
3434
			$debug_output .= ' | <a href="' . build_url() . '&amp;explain=1">Explain</a>';
3435
		}
3436
	}
3437
3438
	$template->assign_vars(array(
3439
		'DEBUG_OUTPUT'			=> (defined('DEBUG')) ? $debug_output : '',
3440
		'TRANSLATION_INFO'		=> (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
3441
3442
		'U_ACP' => ($auth->acl_get('a_') && $user->data['is_registered']) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '')
3443
	);
3444
3445
	// Call cron-type script
3446
	if (!defined('IN_CRON') && $run_cron && !$config['board_disable'])
3447
	{
3448
		$cron_type = '';
3449
	
3450
		if (time() - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists($phpbb_root_path . 'cache/queue.' . $phpEx))
3451
		{
3452
			// Process email queue
3453
			$cron_type = 'queue';
3454
		}
3455
		else if (method_exists($cache, 'tidy') && time() - $config['cache_gc'] > $config['cache_last_gc'])
3456
		{
3457
			// Tidy the cache
3458
			$cron_type = 'tidy_cache';
3459
		}
3460
		else if (time() - $config['warnings_gc'] > $config['warnings_last_gc'])
3461
		{
3462
			$cron_type = 'tidy_warnings';
3463
		}
3464
		else if (time() - $config['database_gc'] > $config['database_last_gc'])
3465
		{
3466
			// Tidy the database
3467
			$cron_type = 'tidy_database';
3468
		}
3469
		else if (time() - $config['search_gc'] > $config['search_last_gc'])
3470
		{
3471
			// Tidy the search
3472
			$cron_type = 'tidy_search';
3473
		}
3474
		else if (time() - $config['session_gc'] > $config['session_last_gc'])
3475
		{
3476
			$cron_type = 'tidy_sessions';
3477
		}
3478
3479
		if ($cron_type)
3480
		{
3481
			$template->assign_var('RUN_CRON_TASK', '<img src="' . append_sid($phpbb_root_path . 'cron.' . $phpEx, 'cron_type=' . $cron_type) . '" width="1" height="1" alt="cron" />');
3482
		}
3483
	}
3484
3485
	$template->display('body');
3486
3487
	garbage_collection();
3488
	exit_handler();
3489
}
3490
3491
/**
3492
* Closing the cache object and the database
3493
* Cool function name, eh? We might want to add operations to it later
3494
*/
3495
function garbage_collection()
3496
{
3497
	global $cache, $db;
3498
3499
	// Unload cache, must be done before the DB connection if closed
3500
	if (!empty($cache))
3501
	{
3502
		$cache->unload();
3503
	}
3504
3505
	// Close our DB connection.
3506
	if (!empty($db))
3507
	{
3508
		$db->sql_close();
3509
	}
3510
}
3511
3512
/**
3513
* Handler for exit calls in phpBB.
3514
* This function supports hooks.
3515
*
3516
* Note: This function is called after the template has been outputted.
3517
*/
3518
function exit_handler()
3519
{
3520
	global $phpbb_hook;
3521
3522
	if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
3523
	{
3524
		if ($phpbb_hook->hook_return(__FUNCTION__))
3525
		{
3526
			return $phpbb_hook->hook_return_result(__FUNCTION__);
3527
		}
3528
	}
3529
3530
	exit;
3531
}
3532
3533
/**
3534
* Handler for init calls in phpBB. This function is called in user::setup();
3535
* This function supports hooks.
3536
*/
3537
function phpbb_user_session_handler()
3538
{
3539
	global $phpbb_hook;
3540
3541
	if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
3542
	{
3543
		if ($phpbb_hook->hook_return(__FUNCTION__))
3544
		{
3545
			return $phpbb_hook->hook_return_result(__FUNCTION__);
3546
		}
3547
	}
3548
3549
	return;
3550
}
3551
3552
?>