· 9 years ago · Dec 18, 2016, 03:52 PM
1<?php
2/**
3*
4* @package phpBB3
5* @version $Id$
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*/
14if (!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*/
28function 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", "\0"), 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*/
63function 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 $super_global = ($cookie) ? '_COOKIE' : '_REQUEST';
75 if (!isset($GLOBALS[$super_global][$var_name]) || is_array($GLOBALS[$super_global][$var_name]) != is_array($default))
76 {
77 return (is_array($default)) ? array() : $default;
78 }
79
80 $var = $GLOBALS[$super_global][$var_name];
81 if (!is_array($default))
82 {
83 $type = gettype($default);
84 }
85 else
86 {
87 list($key_type, $type) = each($default);
88 $type = gettype($type);
89 $key_type = gettype($key_type);
90 if ($type == 'array')
91 {
92 reset($default);
93 $default = current($default);
94 list($sub_key_type, $sub_type) = each($default);
95 $sub_type = gettype($sub_type);
96 $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
97 $sub_key_type = gettype($sub_key_type);
98 }
99 }
100
101 if (is_array($var))
102 {
103 $_var = $var;
104 $var = array();
105
106 foreach ($_var as $k => $v)
107 {
108 set_var($k, $k, $key_type);
109 if ($type == 'array' && is_array($v))
110 {
111 foreach ($v as $_k => $_v)
112 {
113 if (is_array($_v))
114 {
115 $_v = null;
116 }
117 set_var($_k, $_k, $sub_key_type, $multibyte);
118 set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
119 }
120 }
121 else
122 {
123 if ($type == 'array' || is_array($v))
124 {
125 $v = null;
126 }
127 set_var($var[$k], $v, $type, $multibyte);
128 }
129 }
130 }
131 else
132 {
133 set_var($var, $var, $type, $multibyte);
134 }
135
136 return $var;
137}
138
139/**
140* Sets a configuration option's value.
141*
142* Please note that this function does not update the is_dynamic value for
143* an already existing config option.
144*
145* @param string $config_name The configuration option's name
146* @param string $config_value New configuration value
147* @param bool $is_dynamic Whether this variable should be cached (false) or
148* if it changes too frequently (true) to be
149* efficiently cached.
150*
151* @return null
152*/
153function set_config($config_name, $config_value, $is_dynamic = false)
154{
155 global $db, $cache, $config;
156
157 $sql = 'UPDATE ' . CONFIG_TABLE . "
158 SET config_value = '" . $db->sql_escape($config_value) . "'
159 WHERE config_name = '" . $db->sql_escape($config_name) . "'";
160 $db->sql_query($sql);
161
162 if (!$db->sql_affectedrows() && !isset($config[$config_name]))
163 {
164 $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
165 'config_name' => $config_name,
166 'config_value' => $config_value,
167 'is_dynamic' => ($is_dynamic) ? 1 : 0));
168 $db->sql_query($sql);
169 }
170
171 $config[$config_name] = $config_value;
172
173 if (!$is_dynamic)
174 {
175 $cache->destroy('config');
176 }
177}
178
179/**
180* Increments an integer config value directly in the database.
181*
182* @param string $config_name The configuration option's name
183* @param int $increment Amount to increment by
184* @param bool $is_dynamic Whether this variable should be cached (false) or
185* if it changes too frequently (true) to be
186* efficiently cached.
187*
188* @return null
189*/
190function set_config_count($config_name, $increment, $is_dynamic = false)
191{
192 global $db, $cache;
193
194 switch ($db->sql_layer)
195 {
196 case 'firebird':
197 // Precision must be from 1 to 18
198 $sql_update = 'CAST(CAST(config_value as DECIMAL(18, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
199 break;
200
201 case 'postgres':
202 // Need to cast to text first for PostgreSQL 7.x
203 $sql_update = 'CAST(CAST(config_value::text as DECIMAL(255, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
204 break;
205
206 // MySQL, SQlite, mssql, mssql_odbc, oracle
207 default:
208 $sql_update = 'config_value + ' . (int) $increment;
209 break;
210 }
211
212 $db->sql_query('UPDATE ' . CONFIG_TABLE . ' SET config_value = ' . $sql_update . " WHERE config_name = '" . $db->sql_escape($config_name) . "'");
213
214 if (!$is_dynamic)
215 {
216 $cache->destroy('config');
217 }
218}
219
220/**
221* Generates an alphanumeric random string of given length
222*
223* @return string
224*/
225function gen_rand_string($num_chars = 8)
226{
227 // [a, z] + [0, 9] = 36
228 return substr(strtoupper(base_convert(unique_id(), 16, 36)), 0, $num_chars);
229}
230
231/**
232* Generates a user-friendly alphanumeric random string of given length
233* We remove 0 and O so users cannot confuse those in passwords etc.
234*
235* @return string
236*/
237function gen_rand_string_friendly($num_chars = 8)
238{
239 $rand_str = unique_id();
240
241 // Remove Z and Y from the base_convert(), replace 0 with Z and O with Y
242 // [a, z] + [0, 9] - {z, y} = [a, z] + [0, 9] - {0, o} = 34
243 $rand_str = str_replace(array('0', 'O'), array('Z', 'Y'), strtoupper(base_convert($rand_str, 16, 34)));
244
245 return substr($rand_str, 0, $num_chars);
246}
247
248/**
249* Return unique id
250* @param string $extra additional entropy
251*/
252function unique_id($extra = 'c')
253{
254 static $dss_seeded = false;
255 global $config;
256
257 $val = $config['rand_seed'] . microtime();
258 $val = md5($val);
259 $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
260
261 if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
262 {
263 set_config('rand_seed_last_update', time(), true);
264 set_config('rand_seed', $config['rand_seed'], true);
265 $dss_seeded = true;
266 }
267
268 return substr($val, 4, 16);
269}
270
271/**
272* Wrapper for mt_rand() which allows swapping $min and $max parameters.
273*
274* PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
275* (since PHP 5.3.4, see http://bugs.php.net/46587)
276*
277* @param int $min Lowest value to be returned
278* @param int $max Highest value to be returned
279*
280* @return int Random integer between $min and $max (or $max and $min)
281*/
282function phpbb_mt_rand($min, $max)
283{
284 return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
285}
286
287/**
288* Wrapper for getdate() which returns the equivalent array for UTC timestamps.
289*
290* @param int $time Unix timestamp (optional)
291*
292* @return array Returns an associative array of information related to the timestamp.
293* See http://www.php.net/manual/en/function.getdate.php
294*/
295function phpbb_gmgetdate($time = false)
296{
297 if ($time === false)
298 {
299 $time = time();
300 }
301
302 // getdate() interprets timestamps in local time.
303 // What follows uses the fact that getdate() and
304 // date('Z') balance each other out.
305 return getdate($time - date('Z'));
306}
307
308/**
309* Return formatted string for filesizes
310*
311* @param mixed $value filesize in bytes
312* (non-negative number; int, float or string)
313* @param bool $string_only true if language string should be returned
314* @param array $allowed_units only allow these units (data array indexes)
315*
316* @return mixed data array if $string_only is false
317* @author bantu
318*/
319function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
320{
321 global $user;
322
323 $available_units = array(
324 'tb' => array(
325 'min' => 1099511627776, // pow(2, 40)
326 'index' => 4,
327 'si_unit' => 'TB',
328 'iec_unit' => 'TIB',
329 ),
330 'gb' => array(
331 'min' => 1073741824, // pow(2, 30)
332 'index' => 3,
333 'si_unit' => 'GB',
334 'iec_unit' => 'GIB',
335 ),
336 'mb' => array(
337 'min' => 1048576, // pow(2, 20)
338 'index' => 2,
339 'si_unit' => 'MB',
340 'iec_unit' => 'MIB',
341 ),
342 'kb' => array(
343 'min' => 1024, // pow(2, 10)
344 'index' => 1,
345 'si_unit' => 'KB',
346 'iec_unit' => 'KIB',
347 ),
348 'b' => array(
349 'min' => 0,
350 'index' => 0,
351 'si_unit' => 'BYTES', // Language index
352 'iec_unit' => 'BYTES', // Language index
353 ),
354 );
355
356 foreach ($available_units as $si_identifier => $unit_info)
357 {
358 if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
359 {
360 continue;
361 }
362
363 if ($value >= $unit_info['min'])
364 {
365 $unit_info['si_identifier'] = $si_identifier;
366
367 break;
368 }
369 }
370 unset($available_units);
371
372 for ($i = 0; $i < $unit_info['index']; $i++)
373 {
374 $value /= 1024;
375 }
376 $value = round($value, 2);
377
378 // Lookup units in language dictionary
379 $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
380 $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
381
382 // Default to IEC
383 $unit_info['unit'] = $unit_info['iec_unit'];
384
385 if (!$string_only)
386 {
387 $unit_info['value'] = $value;
388
389 return $unit_info;
390 }
391
392 return $value . ' ' . $unit_info['unit'];
393}
394
395/**
396* Determine whether we are approaching the maximum execution time. Should be called once
397* at the beginning of the script in which it's used.
398* @return bool Either true if the maximum execution time is nearly reached, or false
399* if some time is still left.
400*/
401function still_on_time($extra_time = 15)
402{
403 static $max_execution_time, $start_time;
404
405 $time = explode(' ', microtime());
406 $current_time = $time[0] + $time[1];
407
408 if (empty($max_execution_time))
409 {
410 $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
411
412 // If zero, then set to something higher to not let the user catch the ten seconds barrier.
413 if ($max_execution_time === 0)
414 {
415 $max_execution_time = 50 + $extra_time;
416 }
417
418 $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
419
420 // For debugging purposes
421 // $max_execution_time = 10;
422
423 global $starttime;
424 $start_time = (empty($starttime)) ? $current_time : $starttime;
425 }
426
427 return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
428}
429
430/**
431*
432* @version Version 0.1 / slightly modified for phpBB 3.0.x (using $H$ as hash type identifier)
433*
434* Portable PHP password hashing framework.
435*
436* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
437* the public domain.
438*
439* There's absolutely no warranty.
440*
441* The homepage URL for this framework is:
442*
443* http://www.openwall.com/phpass/
444*
445* Please be sure to update the Version line if you edit this file in any way.
446* It is suggested that you leave the main version number intact, but indicate
447* your project name (after the slash) and add your own revision information.
448*
449* Please do not change the "private" password hashing method implemented in
450* here, thereby making your hashes incompatible. However, if you must, please
451* change the hash type identifier (the "$P$") to something different.
452*
453* Obviously, since this code is in the public domain, the above are not
454* requirements (there can be none), but merely suggestions.
455*
456*
457* Hash the password
458*/
459function phpbb_hash($password)
460{
461 $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
462
463 $random_state = unique_id();
464 $random = '';
465 $count = 6;
466
467 if (($fh = @fopen('/dev/urandom', 'rb')))
468 {
469 $random = fread($fh, $count);
470 fclose($fh);
471 }
472
473 if (strlen($random) < $count)
474 {
475 $random = '';
476
477 for ($i = 0; $i < $count; $i += 16)
478 {
479 $random_state = md5(unique_id() . $random_state);
480 $random .= pack('H*', md5($random_state));
481 }
482 $random = substr($random, 0, $count);
483 }
484
485 $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);
486
487 if (strlen($hash) == 34)
488 {
489 return $hash;
490 }
491
492 return md5($password);
493}
494
495/**
496* Check for correct password
497*
498* @param string $password The password in plain text
499* @param string $hash The stored password hash
500*
501* @return bool Returns true if the password is correct, false if not.
502*/
503function phpbb_check_hash($password, $hash)
504{
505 if (strlen($password) > 4096)
506 {
507 // If the password is too huge, we will simply reject it
508 // and not let the server try to hash it.
509 return false;
510 }
511
512 $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
513 if (strlen($hash) == 34)
514 {
515 return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
516 }
517
518 return (md5($password) === $hash) ? true : false;
519}
520
521/**
522* Generate salt for hash generation
523*/
524function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
525{
526 if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
527 {
528 $iteration_count_log2 = 8;
529 }
530
531 $output = '$H$';
532 $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
533 $output .= _hash_encode64($input, 6, $itoa64);
534
535 return $output;
536}
537
538/**
539* Encode hash
540*/
541function _hash_encode64($input, $count, &$itoa64)
542{
543 $output = '';
544 $i = 0;
545
546 do
547 {
548 $value = ord($input[$i++]);
549 $output .= $itoa64[$value & 0x3f];
550
551 if ($i < $count)
552 {
553 $value |= ord($input[$i]) << 8;
554 }
555
556 $output .= $itoa64[($value >> 6) & 0x3f];
557
558 if ($i++ >= $count)
559 {
560 break;
561 }
562
563 if ($i < $count)
564 {
565 $value |= ord($input[$i]) << 16;
566 }
567
568 $output .= $itoa64[($value >> 12) & 0x3f];
569
570 if ($i++ >= $count)
571 {
572 break;
573 }
574
575 $output .= $itoa64[($value >> 18) & 0x3f];
576 }
577 while ($i < $count);
578
579 return $output;
580}
581
582/**
583* The crypt function/replacement
584*/
585function _hash_crypt_private($password, $setting, &$itoa64)
586{
587 $output = '*';
588
589 // Check for correct hash
590 if (substr($setting, 0, 3) != '$H$' && substr($setting, 0, 3) != '$P$')
591 {
592 return $output;
593 }
594
595 $count_log2 = strpos($itoa64, $setting[3]);
596
597 if ($count_log2 < 7 || $count_log2 > 30)
598 {
599 return $output;
600 }
601
602 $count = 1 << $count_log2;
603 $salt = substr($setting, 4, 8);
604
605 if (strlen($salt) != 8)
606 {
607 return $output;
608 }
609
610 /**
611 * We're kind of forced to use MD5 here since it's the only
612 * cryptographic primitive available in all versions of PHP
613 * currently in use. To implement our own low-level crypto
614 * in PHP would result in much worse performance and
615 * consequently in lower iteration counts and hashes that are
616 * quicker to crack (by non-PHP code).
617 */
618 if (PHP_VERSION >= 5)
619 {
620 $hash = md5($salt . $password, true);
621 do
622 {
623 $hash = md5($hash . $password, true);
624 }
625 while (--$count);
626 }
627 else
628 {
629 $hash = pack('H*', md5($salt . $password));
630 do
631 {
632 $hash = pack('H*', md5($hash . $password));
633 }
634 while (--$count);
635 }
636
637 $output = substr($setting, 0, 12);
638 $output .= _hash_encode64($hash, 16, $itoa64);
639
640 return $output;
641}
642
643/**
644* Hashes an email address to a big integer
645*
646* @param string $email Email address
647*
648* @return string Unsigned Big Integer
649*/
650function phpbb_email_hash($email)
651{
652 return sprintf('%u', crc32(strtolower($email))) . strlen($email);
653}
654
655/**
656* Wrapper for version_compare() that allows using uppercase A and B
657* for alpha and beta releases.
658*
659* See http://www.php.net/manual/en/function.version-compare.php
660*
661* @param string $version1 First version number
662* @param string $version2 Second version number
663* @param string $operator Comparison operator (optional)
664*
665* @return mixed Boolean (true, false) if comparison operator is specified.
666* Integer (-1, 0, 1) otherwise.
667*/
668function phpbb_version_compare($version1, $version2, $operator = null)
669{
670 $version1 = strtolower($version1);
671 $version2 = strtolower($version2);
672
673 if (is_null($operator))
674 {
675 return version_compare($version1, $version2);
676 }
677 else
678 {
679 return version_compare($version1, $version2, $operator);
680 }
681}
682
683/**
684* Global function for chmodding directories and files for internal use
685*
686* This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions.
687* The function determines owner and group from common.php file and sets the same to the provided file.
688* The function uses bit fields to build the permissions.
689* The function sets the appropiate execute bit on directories.
690*
691* Supported constants representing bit fields are:
692*
693* CHMOD_ALL - all permissions (7)
694* CHMOD_READ - read permission (4)
695* CHMOD_WRITE - write permission (2)
696* CHMOD_EXECUTE - execute permission (1)
697*
698* NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions.
699*
700* @param string $filename The file/directory to be chmodded
701* @param int $perms Permissions to set
702*
703* @return bool true on success, otherwise false
704* @author faw, phpBB Group
705*/
706function phpbb_chmod($filename, $perms = CHMOD_READ)
707{
708 static $_chmod_info;
709
710 // Return if the file no longer exists.
711 if (!file_exists($filename))
712 {
713 return false;
714 }
715
716 // Determine some common vars
717 if (empty($_chmod_info))
718 {
719 if (!function_exists('fileowner') || !function_exists('filegroup'))
720 {
721 // No need to further determine owner/group - it is unknown
722 $_chmod_info['process'] = false;
723 }
724 else
725 {
726 global $phpbb_root_path, $phpEx;
727
728 // Determine owner/group of common.php file and the filename we want to change here
729 $common_php_owner = @fileowner($phpbb_root_path . 'common.' . $phpEx);
730 $common_php_group = @filegroup($phpbb_root_path . 'common.' . $phpEx);
731
732 // And the owner and the groups PHP is running under.
733 $php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false;
734 $php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false;
735
736 // If we are unable to get owner/group, then do not try to set them by guessing
737 if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group)
738 {
739 $_chmod_info['process'] = false;
740 }
741 else
742 {
743 $_chmod_info = array(
744 'process' => true,
745 'common_owner' => $common_php_owner,
746 'common_group' => $common_php_group,
747 'php_uid' => $php_uid,
748 'php_gids' => $php_gids,
749 );
750 }
751 }
752 }
753
754 if ($_chmod_info['process'])
755 {
756 $file_uid = @fileowner($filename);
757 $file_gid = @filegroup($filename);
758
759 // Change owner
760 if (@chown($filename, $_chmod_info['common_owner']))
761 {
762 clearstatcache();
763 $file_uid = @fileowner($filename);
764 }
765
766 // Change group
767 if (@chgrp($filename, $_chmod_info['common_group']))
768 {
769 clearstatcache();
770 $file_gid = @filegroup($filename);
771 }
772
773 // If the file_uid/gid now match the one from common.php we can process further, else we are not able to change something
774 if ($file_uid != $_chmod_info['common_owner'] || $file_gid != $_chmod_info['common_group'])
775 {
776 $_chmod_info['process'] = false;
777 }
778 }
779
780 // Still able to process?
781 if ($_chmod_info['process'])
782 {
783 if ($file_uid == $_chmod_info['php_uid'])
784 {
785 $php = 'owner';
786 }
787 else if (in_array($file_gid, $_chmod_info['php_gids']))
788 {
789 $php = 'group';
790 }
791 else
792 {
793 // Since we are setting the everyone bit anyway, no need to do expensive operations
794 $_chmod_info['process'] = false;
795 }
796 }
797
798 // We are not able to determine or change something
799 if (!$_chmod_info['process'])
800 {
801 $php = 'other';
802 }
803
804 // Owner always has read/write permission
805 $owner = CHMOD_READ | CHMOD_WRITE;
806 if (is_dir($filename))
807 {
808 $owner |= CHMOD_EXECUTE;
809
810 // Only add execute bit to the permission if the dir needs to be readable
811 if ($perms & CHMOD_READ)
812 {
813 $perms |= CHMOD_EXECUTE;
814 }
815 }
816
817 switch ($php)
818 {
819 case 'owner':
820 $result = @chmod($filename, ($owner << 6) + (0 << 3) + (0 << 0));
821
822 clearstatcache();
823
824 if (is_readable($filename) && phpbb_is_writable($filename))
825 {
826 break;
827 }
828
829 case 'group':
830 $result = @chmod($filename, ($owner << 6) + ($perms << 3) + (0 << 0));
831
832 clearstatcache();
833
834 if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
835 {
836 break;
837 }
838
839 case 'other':
840 $result = @chmod($filename, ($owner << 6) + ($perms << 3) + ($perms << 0));
841
842 clearstatcache();
843
844 if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
845 {
846 break;
847 }
848
849 default:
850 return false;
851 break;
852 }
853
854 return $result;
855}
856
857/**
858* Test if a file/directory is writable
859*
860* This function calls the native is_writable() when not running under
861* Windows and it is not disabled.
862*
863* @param string $file Path to perform write test on
864* @return bool True when the path is writable, otherwise false.
865*/
866function phpbb_is_writable($file)
867{
868 if (strtolower(substr(PHP_OS, 0, 3)) === 'win' || !function_exists('is_writable'))
869 {
870 if (file_exists($file))
871 {
872 // Canonicalise path to absolute path
873 $file = phpbb_realpath($file);
874
875 if (is_dir($file))
876 {
877 // Test directory by creating a file inside the directory
878 $result = @tempnam($file, 'i_w');
879
880 if (is_string($result) && file_exists($result))
881 {
882 unlink($result);
883
884 // Ensure the file is actually in the directory (returned realpathed)
885 return (strpos($result, $file) === 0) ? true : false;
886 }
887 }
888 else
889 {
890 $handle = @fopen($file, 'r+');
891
892 if (is_resource($handle))
893 {
894 fclose($handle);
895 return true;
896 }
897 }
898 }
899 else
900 {
901 // file does not exist test if we can write to the directory
902 $dir = dirname($file);
903
904 if (file_exists($dir) && is_dir($dir) && phpbb_is_writable($dir))
905 {
906 return true;
907 }
908 }
909
910 return false;
911 }
912 else
913 {
914 return is_writable($file);
915 }
916}
917
918// Compatibility functions
919
920if (!function_exists('array_combine'))
921{
922 /**
923 * A wrapper for the PHP5 function array_combine()
924 * @param array $keys contains keys for the resulting array
925 * @param array $values contains values for the resulting array
926 *
927 * @return Returns an array by using the values from the keys array as keys and the
928 * values from the values array as the corresponding values. Returns false if the
929 * number of elements for each array isn't equal or if the arrays are empty.
930 */
931 function array_combine($keys, $values)
932 {
933 $keys = array_values($keys);
934 $values = array_values($values);
935
936 $n = sizeof($keys);
937 $m = sizeof($values);
938 if (!$n || !$m || ($n != $m))
939 {
940 return false;
941 }
942
943 $combined = array();
944 for ($i = 0; $i < $n; $i++)
945 {
946 $combined[$keys[$i]] = $values[$i];
947 }
948 return $combined;
949 }
950}
951
952if (!function_exists('str_split'))
953{
954 /**
955 * A wrapper for the PHP5 function str_split()
956 * @param array $string contains the string to be converted
957 * @param array $split_length contains the length of each chunk
958 *
959 * @return Converts a string to an array. If the optional split_length parameter is specified,
960 * the returned array will be broken down into chunks with each being split_length in length,
961 * otherwise each chunk will be one character in length. FALSE is returned if split_length is
962 * less than 1. If the split_length length exceeds the length of string, the entire string is
963 * returned as the first (and only) array element.
964 */
965 function str_split($string, $split_length = 1)
966 {
967 if ($split_length < 1)
968 {
969 return false;
970 }
971 else if ($split_length >= strlen($string))
972 {
973 return array($string);
974 }
975 else
976 {
977 preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches);
978 return $matches[0];
979 }
980 }
981}
982
983if (!function_exists('stripos'))
984{
985 /**
986 * A wrapper for the PHP5 function stripos
987 * Find position of first occurrence of a case-insensitive string
988 *
989 * @param string $haystack is the string to search in
990 * @param string $needle is the string to search for
991 *
992 * @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive.
993 * Note that the needle may be a string of one or more characters.
994 * If needle is not found, stripos() will return boolean FALSE.
995 */
996 function stripos($haystack, $needle)
997 {
998 if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
999 {
1000 return strpos($haystack, $m[0]);
1001 }
1002
1003 return false;
1004 }
1005}
1006
1007/**
1008* Checks if a path ($path) is absolute or relative
1009*
1010* @param string $path Path to check absoluteness of
1011* @return boolean
1012*/
1013function is_absolute($path)
1014{
1015 return (isset($path[0]) && $path[0] == '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false;
1016}
1017
1018/**
1019* @author Chris Smith <chris@project-minerva.org>
1020* @copyright 2006 Project Minerva Team
1021* @param string $path The path which we should attempt to resolve.
1022* @return mixed
1023*/
1024function phpbb_own_realpath($path)
1025{
1026 // Now to perform funky shizzle
1027
1028 // Switch to use UNIX slashes
1029 $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
1030 $path_prefix = '';
1031
1032 // Determine what sort of path we have
1033 if (is_absolute($path))
1034 {
1035 $absolute = true;
1036
1037 if ($path[0] == '/')
1038 {
1039 // Absolute path, *NIX style
1040 $path_prefix = '';
1041 }
1042 else
1043 {
1044 // Absolute path, Windows style
1045 // Remove the drive letter and colon
1046 $path_prefix = $path[0] . ':';
1047 $path = substr($path, 2);
1048 }
1049 }
1050 else
1051 {
1052 // Relative Path
1053 // Prepend the current working directory
1054 if (function_exists('getcwd'))
1055 {
1056 // This is the best method, hopefully it is enabled!
1057 $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
1058 $absolute = true;
1059 if (preg_match('#^[a-z]:#i', $path))
1060 {
1061 $path_prefix = $path[0] . ':';
1062 $path = substr($path, 2);
1063 }
1064 else
1065 {
1066 $path_prefix = '';
1067 }
1068 }
1069 else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
1070 {
1071 // Warning: If chdir() has been used this will lie!
1072 // Warning: This has some problems sometime (CLI can create them easily)
1073 $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
1074 $absolute = true;
1075 $path_prefix = '';
1076 }
1077 else
1078 {
1079 // We have no way of getting the absolute path, just run on using relative ones.
1080 $absolute = false;
1081 $path_prefix = '.';
1082 }
1083 }
1084
1085 // Remove any repeated slashes
1086 $path = preg_replace('#/{2,}#', '/', $path);
1087
1088 // Remove the slashes from the start and end of the path
1089 $path = trim($path, '/');
1090
1091 // Break the string into little bits for us to nibble on
1092 $bits = explode('/', $path);
1093
1094 // Remove any . in the path, renumber array for the loop below
1095 $bits = array_values(array_diff($bits, array('.')));
1096
1097 // Lets get looping, run over and resolve any .. (up directory)
1098 for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
1099 {
1100 // @todo Optimise
1101 if ($bits[$i] == '..' )
1102 {
1103 if (isset($bits[$i - 1]))
1104 {
1105 if ($bits[$i - 1] != '..')
1106 {
1107 // We found a .. and we are able to traverse upwards, lets do it!
1108 unset($bits[$i]);
1109 unset($bits[$i - 1]);
1110 $i -= 2;
1111 $max -= 2;
1112 $bits = array_values($bits);
1113 }
1114 }
1115 else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
1116 {
1117 // We have an absolute path trying to descend above the root of the filesystem
1118 // ... Error!
1119 return false;
1120 }
1121 }
1122 }
1123
1124 // Prepend the path prefix
1125 array_unshift($bits, $path_prefix);
1126
1127 $resolved = '';
1128
1129 $max = sizeof($bits) - 1;
1130
1131 // Check if we are able to resolve symlinks, Windows cannot.
1132 $symlink_resolve = (function_exists('readlink')) ? true : false;
1133
1134 foreach ($bits as $i => $bit)
1135 {
1136 if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
1137 {
1138 // Path Exists
1139 if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
1140 {
1141 // Resolved a symlink.
1142 $resolved = $link . (($i == $max) ? '' : '/');
1143 continue;
1144 }
1145 }
1146 else
1147 {
1148 // Something doesn't exist here!
1149 // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
1150 // return false;
1151 }
1152 $resolved .= $bit . (($i == $max) ? '' : '/');
1153 }
1154
1155 // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
1156 // because we must be inside that basedir, the question is where...
1157 // @internal The slash in is_dir() gets around an open_basedir restriction
1158 if (!@file_exists($resolved) || (!@is_dir($resolved . '/') && !is_file($resolved)))
1159 {
1160 return false;
1161 }
1162
1163 // Put the slashes back to the native operating systems slashes
1164 $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
1165
1166 // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
1167 if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
1168 {
1169 return substr($resolved, 0, -1);
1170 }
1171
1172 return $resolved; // We got here, in the end!
1173}
1174
1175if (!function_exists('realpath'))
1176{
1177 /**
1178 * A wrapper for realpath
1179 * @ignore
1180 */
1181 function phpbb_realpath($path)
1182 {
1183 return phpbb_own_realpath($path);
1184 }
1185}
1186else
1187{
1188 /**
1189 * A wrapper for realpath
1190 */
1191 function phpbb_realpath($path)
1192 {
1193 $realpath = realpath($path);
1194
1195 // Strangely there are provider not disabling realpath but returning strange values. :o
1196 // We at least try to cope with them.
1197 if ($realpath === $path || $realpath === false)
1198 {
1199 return phpbb_own_realpath($path);
1200 }
1201
1202 // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
1203 if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
1204 {
1205 $realpath = substr($realpath, 0, -1);
1206 }
1207
1208 return $realpath;
1209 }
1210}
1211
1212/**
1213* Eliminates useless . and .. components from specified path.
1214*
1215* @param string $path Path to clean
1216* @return string Cleaned path
1217*/
1218function phpbb_clean_path($path)
1219{
1220 $exploded = explode('/', $path);
1221 $filtered = array();
1222 foreach ($exploded as $part)
1223 {
1224 if ($part === '.' && !empty($filtered))
1225 {
1226 continue;
1227 }
1228
1229 if ($part === '..' && !empty($filtered) && $filtered[sizeof($filtered) - 1] !== '..')
1230 {
1231 array_pop($filtered);
1232 }
1233 else
1234 {
1235 $filtered[] = $part;
1236 }
1237 }
1238 $path = implode('/', $filtered);
1239 return $path;
1240}
1241
1242if (!function_exists('htmlspecialchars_decode'))
1243{
1244 /**
1245 * A wrapper for htmlspecialchars_decode
1246 * @ignore
1247 */
1248 function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
1249 {
1250 return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
1251 }
1252}
1253
1254// functions used for building option fields
1255
1256/**
1257* Pick a language, any language ...
1258*/
1259function language_select($default = '')
1260{
1261 global $db;
1262
1263 $sql = 'SELECT lang_iso, lang_local_name
1264 FROM ' . LANG_TABLE . '
1265 ORDER BY lang_english_name';
1266 $result = $db->sql_query($sql);
1267
1268 $lang_options = '';
1269 while ($row = $db->sql_fetchrow($result))
1270 {
1271 $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
1272 $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
1273 }
1274 $db->sql_freeresult($result);
1275
1276 return $lang_options;
1277}
1278
1279/**
1280* Pick a template/theme combo,
1281*/
1282function style_select($default = '', $all = false)
1283{
1284 global $db;
1285
1286 $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
1287 $sql = 'SELECT style_id, style_name
1288 FROM ' . STYLES_TABLE . "
1289 $sql_where
1290 ORDER BY style_name";
1291 $result = $db->sql_query($sql);
1292
1293 $style_options = '';
1294 while ($row = $db->sql_fetchrow($result))
1295 {
1296 $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
1297 $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
1298 }
1299 $db->sql_freeresult($result);
1300
1301 return $style_options;
1302}
1303
1304/**
1305* Pick a timezone
1306*/
1307function tz_select($default = '', $truncate = false)
1308{
1309 global $user;
1310
1311 $tz_select = '';
1312 foreach ($user->lang['tz_zones'] as $offset => $zone)
1313 {
1314 if ($truncate)
1315 {
1316 $zone_trunc = truncate_string($zone, 50, 255, false, '...');
1317 }
1318 else
1319 {
1320 $zone_trunc = $zone;
1321 }
1322
1323 if (is_numeric($offset))
1324 {
1325 $selected = ($offset == $default) ? ' selected="selected"' : '';
1326 $tz_select .= '<option title="' . $zone . '" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
1327 }
1328 }
1329
1330 return $tz_select;
1331}
1332
1333// Functions handling topic/post tracking/marking
1334
1335/**
1336* Marks a topic/forum as read
1337* Marks a topic as posted to
1338*
1339* @param int $user_id can only be used with $mode == 'post'
1340*/
1341function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
1342{
1343 global $db, $user, $config;
1344
1345 if ($mode == 'all')
1346 {
1347 if ($forum_id === false || !sizeof($forum_id))
1348 {
1349 if ($config['load_db_lastread'] && $user->data['is_registered'])
1350 {
1351 // Mark all forums read (index page)
1352 $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
1353 $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
1354 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
1355 }
1356 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1357 {
1358 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1359 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1360
1361 unset($tracking_topics['tf']);
1362 unset($tracking_topics['t']);
1363 unset($tracking_topics['f']);
1364 $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
1365
1366 $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
1367 $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics);
1368
1369 unset($tracking_topics);
1370
1371 if ($user->data['is_registered'])
1372 {
1373 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
1374 }
1375 }
1376 }
1377
1378 return;
1379 }
1380 else if ($mode == 'topics')
1381 {
1382 // Mark all topics in forums read
1383 if (!is_array($forum_id))
1384 {
1385 $forum_id = array($forum_id);
1386 }
1387
1388 // Add 0 to forums array to mark global announcements correctly
1389 // $forum_id[] = 0;
1390
1391 if ($config['load_db_lastread'] && $user->data['is_registered'])
1392 {
1393 $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
1394 WHERE user_id = {$user->data['user_id']}
1395 AND " . $db->sql_in_set('forum_id', $forum_id);
1396 $db->sql_query($sql);
1397
1398 $sql = 'SELECT forum_id
1399 FROM ' . FORUMS_TRACK_TABLE . "
1400 WHERE user_id = {$user->data['user_id']}
1401 AND " . $db->sql_in_set('forum_id', $forum_id);
1402 $result = $db->sql_query($sql);
1403
1404 $sql_update = array();
1405 while ($row = $db->sql_fetchrow($result))
1406 {
1407 $sql_update[] = (int) $row['forum_id'];
1408 }
1409 $db->sql_freeresult($result);
1410
1411 if (sizeof($sql_update))
1412 {
1413 $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
1414 SET mark_time = ' . time() . "
1415 WHERE user_id = {$user->data['user_id']}
1416 AND " . $db->sql_in_set('forum_id', $sql_update);
1417 $db->sql_query($sql);
1418 }
1419
1420 if ($sql_insert = array_diff($forum_id, $sql_update))
1421 {
1422 $sql_ary = array();
1423 foreach ($sql_insert as $f_id)
1424 {
1425 $sql_ary[] = array(
1426 'user_id' => (int) $user->data['user_id'],
1427 'forum_id' => (int) $f_id,
1428 'mark_time' => time()
1429 );
1430 }
1431
1432 $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
1433 }
1434 }
1435 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1436 {
1437 $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1438 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1439
1440 foreach ($forum_id as $f_id)
1441 {
1442 $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
1443
1444 if (isset($tracking['tf'][$f_id]))
1445 {
1446 unset($tracking['tf'][$f_id]);
1447 }
1448
1449 foreach ($topic_ids36 as $topic_id36)
1450 {
1451 unset($tracking['t'][$topic_id36]);
1452 }
1453
1454 if (isset($tracking['f'][$f_id]))
1455 {
1456 unset($tracking['f'][$f_id]);
1457 }
1458
1459 $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
1460 }
1461
1462 if (isset($tracking['tf']) && empty($tracking['tf']))
1463 {
1464 unset($tracking['tf']);
1465 }
1466
1467 $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
1468 $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
1469
1470 unset($tracking);
1471 }
1472
1473 return;
1474 }
1475 else if ($mode == 'topic')
1476 {
1477 if ($topic_id === false || $forum_id === false)
1478 {
1479 return;
1480 }
1481
1482 if ($config['load_db_lastread'] && $user->data['is_registered'])
1483 {
1484 $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
1485 SET mark_time = ' . (($post_time) ? $post_time : time()) . "
1486 WHERE user_id = {$user->data['user_id']}
1487 AND topic_id = $topic_id";
1488 $db->sql_query($sql);
1489
1490 // insert row
1491 if (!$db->sql_affectedrows())
1492 {
1493 $db->sql_return_on_error(true);
1494
1495 $sql_ary = array(
1496 'user_id' => (int) $user->data['user_id'],
1497 'topic_id' => (int) $topic_id,
1498 'forum_id' => (int) $forum_id,
1499 'mark_time' => ($post_time) ? (int) $post_time : time(),
1500 );
1501
1502 $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1503
1504 $db->sql_return_on_error(false);
1505 }
1506 }
1507 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1508 {
1509 $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1510 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1511
1512 $topic_id36 = base_convert($topic_id, 10, 36);
1513
1514 if (!isset($tracking['t'][$topic_id36]))
1515 {
1516 $tracking['tf'][$forum_id][$topic_id36] = true;
1517 }
1518
1519 $post_time = ($post_time) ? $post_time : time();
1520 $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
1521
1522 // If the cookie grows larger than 10000 characters we will remove the smallest value
1523 // This can result in old topics being unread - but most of the time it should be accurate...
1524 if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
1525 {
1526 //echo 'Cookie grown too large' . print_r($tracking, true);
1527
1528 // We get the ten most minimum stored time offsets and its associated topic ids
1529 $time_keys = array();
1530 for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
1531 {
1532 $min_value = min($tracking['t']);
1533 $m_tkey = array_search($min_value, $tracking['t']);
1534 unset($tracking['t'][$m_tkey]);
1535
1536 $time_keys[$m_tkey] = $min_value;
1537 }
1538
1539 // Now remove the topic ids from the array...
1540 foreach ($tracking['tf'] as $f_id => $topic_id_ary)
1541 {
1542 foreach ($time_keys as $m_tkey => $min_value)
1543 {
1544 if (isset($topic_id_ary[$m_tkey]))
1545 {
1546 $tracking['f'][$f_id] = $min_value;
1547 unset($tracking['tf'][$f_id][$m_tkey]);
1548 }
1549 }
1550 }
1551
1552 if ($user->data['is_registered'])
1553 {
1554 $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
1555 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
1556 }
1557 else
1558 {
1559 $tracking['l'] = max($time_keys);
1560 }
1561 }
1562
1563 $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
1564 $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
1565 }
1566
1567 return;
1568 }
1569 else if ($mode == 'post')
1570 {
1571 if ($topic_id === false)
1572 {
1573 return;
1574 }
1575
1576 $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
1577
1578 if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
1579 {
1580 $db->sql_return_on_error(true);
1581
1582 $sql_ary = array(
1583 'user_id' => (int) $use_user_id,
1584 'topic_id' => (int) $topic_id,
1585 'topic_posted' => 1
1586 );
1587
1588 $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1589
1590 $db->sql_return_on_error(false);
1591 }
1592
1593 return;
1594 }
1595}
1596
1597/**
1598* Get topic tracking info by using already fetched info
1599*/
1600function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
1601{
1602 global $config, $user;
1603
1604 $last_read = array();
1605
1606 if (!is_array($topic_ids))
1607 {
1608 $topic_ids = array($topic_ids);
1609 }
1610
1611 foreach ($topic_ids as $topic_id)
1612 {
1613 if (!empty($rowset[$topic_id]['mark_time']))
1614 {
1615 $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
1616 }
1617 }
1618
1619 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1620
1621 if (sizeof($topic_ids))
1622 {
1623 $mark_time = array();
1624
1625 // Get global announcement info
1626 if ($global_announce_list && sizeof($global_announce_list))
1627 {
1628 if (!isset($forum_mark_time[0]))
1629 {
1630 global $db;
1631
1632 $sql = 'SELECT mark_time
1633 FROM ' . FORUMS_TRACK_TABLE . "
1634 WHERE user_id = {$user->data['user_id']}
1635 AND forum_id = 0";
1636 $result = $db->sql_query($sql);
1637 $row = $db->sql_fetchrow($result);
1638 $db->sql_freeresult($result);
1639
1640 if ($row)
1641 {
1642 $mark_time[0] = $row['mark_time'];
1643 }
1644 }
1645 else
1646 {
1647 if ($forum_mark_time[0] !== false)
1648 {
1649 $mark_time[0] = $forum_mark_time[0];
1650 }
1651 }
1652 }
1653
1654 if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
1655 {
1656 $mark_time[$forum_id] = $forum_mark_time[$forum_id];
1657 }
1658
1659 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1660
1661 foreach ($topic_ids as $topic_id)
1662 {
1663 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1664 {
1665 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1666 }
1667 else
1668 {
1669 $last_read[$topic_id] = $user_lastmark;
1670 }
1671 }
1672 }
1673
1674 return $last_read;
1675}
1676
1677/**
1678* Get topic tracking info from db (for cookie based tracking only this function is used)
1679*/
1680function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
1681{
1682 global $config, $user;
1683
1684 $last_read = array();
1685
1686 if (!is_array($topic_ids))
1687 {
1688 $topic_ids = array($topic_ids);
1689 }
1690
1691 if ($config['load_db_lastread'] && $user->data['is_registered'])
1692 {
1693 global $db;
1694
1695 $sql = 'SELECT topic_id, mark_time
1696 FROM ' . TOPICS_TRACK_TABLE . "
1697 WHERE user_id = {$user->data['user_id']}
1698 AND " . $db->sql_in_set('topic_id', $topic_ids);
1699 $result = $db->sql_query($sql);
1700
1701 while ($row = $db->sql_fetchrow($result))
1702 {
1703 $last_read[$row['topic_id']] = $row['mark_time'];
1704 }
1705 $db->sql_freeresult($result);
1706
1707 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1708
1709 if (sizeof($topic_ids))
1710 {
1711 $sql = 'SELECT forum_id, mark_time
1712 FROM ' . FORUMS_TRACK_TABLE . "
1713 WHERE user_id = {$user->data['user_id']}
1714 AND forum_id " .
1715 (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
1716 $result = $db->sql_query($sql);
1717
1718 $mark_time = array();
1719 while ($row = $db->sql_fetchrow($result))
1720 {
1721 $mark_time[$row['forum_id']] = $row['mark_time'];
1722 }
1723 $db->sql_freeresult($result);
1724
1725 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1726
1727 foreach ($topic_ids as $topic_id)
1728 {
1729 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1730 {
1731 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1732 }
1733 else
1734 {
1735 $last_read[$topic_id] = $user_lastmark;
1736 }
1737 }
1738 }
1739 }
1740 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1741 {
1742 global $tracking_topics;
1743
1744 if (!isset($tracking_topics) || !sizeof($tracking_topics))
1745 {
1746 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1747 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1748 }
1749
1750 if (!$user->data['is_registered'])
1751 {
1752 $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1753 }
1754 else
1755 {
1756 $user_lastmark = $user->data['user_lastmark'];
1757 }
1758
1759 foreach ($topic_ids as $topic_id)
1760 {
1761 $topic_id36 = base_convert($topic_id, 10, 36);
1762
1763 if (isset($tracking_topics['t'][$topic_id36]))
1764 {
1765 $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1766 }
1767 }
1768
1769 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1770
1771 if (sizeof($topic_ids))
1772 {
1773 $mark_time = array();
1774 if ($global_announce_list && sizeof($global_announce_list))
1775 {
1776 if (isset($tracking_topics['f'][0]))
1777 {
1778 $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
1779 }
1780 }
1781
1782 if (isset($tracking_topics['f'][$forum_id]))
1783 {
1784 $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1785 }
1786
1787 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1788
1789 foreach ($topic_ids as $topic_id)
1790 {
1791 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1792 {
1793 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1794 }
1795 else
1796 {
1797 $last_read[$topic_id] = $user_lastmark;
1798 }
1799 }
1800 }
1801 }
1802
1803 return $last_read;
1804}
1805
1806/**
1807* Get list of unread topics
1808*
1809* @param int $user_id User ID (or false for current user)
1810* @param string $sql_extra Extra WHERE SQL statement
1811* @param string $sql_sort ORDER BY SQL sorting statement
1812* @param string $sql_limit Limits the size of unread topics list, 0 for unlimited query
1813* @param string $sql_limit_offset Sets the offset of the first row to search, 0 to search from the start
1814*
1815* @return array[int][int] Topic ids as keys, mark_time of topic as value
1816*/
1817function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
1818{
1819 global $config, $db, $user;
1820
1821 $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
1822
1823 // Data array we're going to return
1824 $unread_topics = array();
1825
1826 if (empty($sql_sort))
1827 {
1828 $sql_sort = 'ORDER BY t.topic_last_post_time DESC';
1829 }
1830
1831 if ($config['load_db_lastread'] && $user->data['is_registered'])
1832 {
1833 // Get list of the unread topics
1834 $last_mark = (int) $user->data['user_lastmark'];
1835
1836 $sql_array = array(
1837 'SELECT' => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
1838
1839 'FROM' => array(TOPICS_TABLE => 't'),
1840
1841 'LEFT_JOIN' => array(
1842 array(
1843 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'),
1844 'ON' => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
1845 ),
1846 array(
1847 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'),
1848 'ON' => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
1849 ),
1850 ),
1851
1852 'WHERE' => "
1853 t.topic_last_post_time > $last_mark AND
1854 (
1855 (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
1856 (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
1857 (tt.mark_time IS NULL AND ft.mark_time IS NULL)
1858 )
1859 $sql_extra
1860 $sql_sort",
1861 );
1862
1863 $sql = $db->sql_build_query('SELECT', $sql_array);
1864 $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1865
1866 while ($row = $db->sql_fetchrow($result))
1867 {
1868 $topic_id = (int) $row['topic_id'];
1869 $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
1870 }
1871 $db->sql_freeresult($result);
1872 }
1873 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1874 {
1875 global $tracking_topics;
1876
1877 if (empty($tracking_topics))
1878 {
1879 $tracking_topics = request_var($config['cookie_name'] . '_track', '', false, true);
1880 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1881 }
1882
1883 if (!$user->data['is_registered'])
1884 {
1885 $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1886 }
1887 else
1888 {
1889 $user_lastmark = (int) $user->data['user_lastmark'];
1890 }
1891
1892 $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
1893 FROM ' . TOPICS_TABLE . ' t
1894 WHERE t.topic_last_post_time > ' . $user_lastmark . "
1895 $sql_extra
1896 $sql_sort";
1897 $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1898
1899 while ($row = $db->sql_fetchrow($result))
1900 {
1901 $forum_id = (int) $row['forum_id'];
1902 $topic_id = (int) $row['topic_id'];
1903 $topic_id36 = base_convert($topic_id, 10, 36);
1904
1905 if (isset($tracking_topics['t'][$topic_id36]))
1906 {
1907 $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1908
1909 if ($row['topic_last_post_time'] > $last_read)
1910 {
1911 $unread_topics[$topic_id] = $last_read;
1912 }
1913 }
1914 else if (isset($tracking_topics['f'][$forum_id]))
1915 {
1916 $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1917
1918 if ($row['topic_last_post_time'] > $mark_time)
1919 {
1920 $unread_topics[$topic_id] = $mark_time;
1921 }
1922 }
1923 else
1924 {
1925 $unread_topics[$topic_id] = $user_lastmark;
1926 }
1927 }
1928 $db->sql_freeresult($result);
1929 }
1930
1931 return $unread_topics;
1932}
1933
1934/**
1935* Check for read forums and update topic tracking info accordingly
1936*
1937* @param int $forum_id the forum id to check
1938* @param int $forum_last_post_time the forums last post time
1939* @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1940* @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1941*
1942* @return true if complete forum got marked read, else false.
1943*/
1944function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1945{
1946 global $db, $tracking_topics, $user, $config, $auth;
1947
1948 // Determine the users last forum mark time if not given.
1949 if ($mark_time_forum === false)
1950 {
1951 if ($config['load_db_lastread'] && $user->data['is_registered'])
1952 {
1953 $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1954 }
1955 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1956 {
1957 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1958 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1959
1960 if (!$user->data['is_registered'])
1961 {
1962 $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1963 }
1964
1965 $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'];
1966 }
1967 }
1968
1969 // Handle update of unapproved topics info.
1970 // Only update for moderators having m_approve permission for the forum.
1971 $sql_update_unapproved = ($auth->acl_get('m_approve', $forum_id)) ? '': 'AND t.topic_approved = 1';
1972
1973 // Check the forum for any left unread topics.
1974 // If there are none, we mark the forum as read.
1975 if ($config['load_db_lastread'] && $user->data['is_registered'])
1976 {
1977 if ($mark_time_forum >= $forum_last_post_time)
1978 {
1979 // We do not need to mark read, this happened before. Therefore setting this to true
1980 $row = true;
1981 }
1982 else
1983 {
1984 $sql = 'SELECT t.forum_id
1985 FROM ' . TOPICS_TABLE . ' t
1986 LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt
1987 ON (tt.topic_id = t.topic_id
1988 AND tt.user_id = ' . $user->data['user_id'] . ')
1989 WHERE t.forum_id = ' . $forum_id . '
1990 AND t.topic_last_post_time > ' . $mark_time_forum . '
1991 AND t.topic_moved_id = 0 ' .
1992 $sql_update_unapproved . '
1993 AND (tt.topic_id IS NULL
1994 OR tt.mark_time < t.topic_last_post_time)';
1995 $result = $db->sql_query_limit($sql, 1);
1996 $row = $db->sql_fetchrow($result);
1997 $db->sql_freeresult($result);
1998 }
1999 }
2000 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
2001 {
2002 // Get information from cookie
2003 $row = false;
2004
2005 if (!isset($tracking_topics['tf'][$forum_id]))
2006 {
2007 // We do not need to mark read, this happened before. Therefore setting this to true
2008 $row = true;
2009 }
2010 else
2011 {
2012 $sql = 'SELECT t.topic_id
2013 FROM ' . TOPICS_TABLE . ' t
2014 WHERE t.forum_id = ' . $forum_id . '
2015 AND t.topic_last_post_time > ' . $mark_time_forum . '
2016 AND t.topic_moved_id = 0 ' .
2017 $sql_update_unapproved;
2018 $result = $db->sql_query($sql);
2019
2020 $check_forum = $tracking_topics['tf'][$forum_id];
2021 $unread = false;
2022
2023 while ($row = $db->sql_fetchrow($result))
2024 {
2025 if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
2026 {
2027 $unread = true;
2028 break;
2029 }
2030 }
2031 $db->sql_freeresult($result);
2032
2033 $row = $unread;
2034 }
2035 }
2036 else
2037 {
2038 $row = true;
2039 }
2040
2041 if (!$row)
2042 {
2043 markread('topics', $forum_id);
2044 return true;
2045 }
2046
2047 return false;
2048}
2049
2050/**
2051* Transform an array into a serialized format
2052*/
2053function tracking_serialize($input)
2054{
2055 $out = '';
2056 foreach ($input as $key => $value)
2057 {
2058 if (is_array($value))
2059 {
2060 $out .= $key . ':(' . tracking_serialize($value) . ');';
2061 }
2062 else
2063 {
2064 $out .= $key . ':' . $value . ';';
2065 }
2066 }
2067 return $out;
2068}
2069
2070/**
2071* Transform a serialized array into an actual array
2072*/
2073function tracking_unserialize($string, $max_depth = 3)
2074{
2075 $n = strlen($string);
2076 if ($n > 10010)
2077 {
2078 die('Invalid data supplied');
2079 }
2080 $data = $stack = array();
2081 $key = '';
2082 $mode = 0;
2083 $level = &$data;
2084 for ($i = 0; $i < $n; ++$i)
2085 {
2086 switch ($mode)
2087 {
2088 case 0:
2089 switch ($string[$i])
2090 {
2091 case ':':
2092 $level[$key] = 0;
2093 $mode = 1;
2094 break;
2095 case ')':
2096 unset($level);
2097 $level = array_pop($stack);
2098 $mode = 3;
2099 break;
2100 default:
2101 $key .= $string[$i];
2102 }
2103 break;
2104
2105 case 1:
2106 switch ($string[$i])
2107 {
2108 case '(':
2109 if (sizeof($stack) >= $max_depth)
2110 {
2111 die('Invalid data supplied');
2112 }
2113 $stack[] = &$level;
2114 $level[$key] = array();
2115 $level = &$level[$key];
2116 $key = '';
2117 $mode = 0;
2118 break;
2119 default:
2120 $level[$key] = $string[$i];
2121 $mode = 2;
2122 break;
2123 }
2124 break;
2125
2126 case 2:
2127 switch ($string[$i])
2128 {
2129 case ')':
2130 unset($level);
2131 $level = array_pop($stack);
2132 $mode = 3;
2133 break;
2134 case ';':
2135 $key = '';
2136 $mode = 0;
2137 break;
2138 default:
2139 $level[$key] .= $string[$i];
2140 break;
2141 }
2142 break;
2143
2144 case 3:
2145 switch ($string[$i])
2146 {
2147 case ')':
2148 unset($level);
2149 $level = array_pop($stack);
2150 break;
2151 case ';':
2152 $key = '';
2153 $mode = 0;
2154 break;
2155 default:
2156 die('Invalid data supplied');
2157 break;
2158 }
2159 break;
2160 }
2161 }
2162
2163 if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
2164 {
2165 die('Invalid data supplied');
2166 }
2167
2168 return $level;
2169}
2170
2171// Pagination functions
2172
2173/**
2174* Pagination routine, generates page number sequence
2175* tpl_prefix is for using different pagination blocks at one page
2176*/
2177function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '')
2178{
2179 global $template, $user;
2180
2181 // Make sure $per_page is a valid value
2182 $per_page = ($per_page <= 0) ? 1 : $per_page;
2183
2184 $seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>';
2185 $total_pages = ceil($num_items / $per_page);
2186
2187 if ($total_pages == 1 || !$num_items)
2188 {
2189 return false;
2190 }
2191
2192 $on_page = floor($start_item / $per_page) + 1;
2193 $url_delim = (strpos($base_url, '?') === false) ? '?' : ((strpos($base_url, '?') === strlen($base_url) - 1) ? '' : '&');
2194
2195 $page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>';
2196
2197 if ($total_pages > 5)
2198 {
2199 $start_cnt = min(max(1, $on_page - 4), $total_pages - 5);
2200 $end_cnt = max(min($total_pages, $on_page + 4), 6);
2201
2202 $page_string .= ($start_cnt > 1) ? '<span class="page-dots"> ... </span>' : $seperator;
2203
2204 for ($i = $start_cnt + 1; $i < $end_cnt; $i++)
2205 {
2206 $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
2207 if ($i < $end_cnt - 1)
2208 {
2209 $page_string .= $seperator;
2210 }
2211 }
2212
2213 $page_string .= ($end_cnt < $total_pages) ? '<span class="page-dots"> ... </span>' : $seperator;
2214 }
2215 else
2216 {
2217 $page_string .= $seperator;
2218
2219 for ($i = 2; $i < $total_pages; $i++)
2220 {
2221 $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
2222 if ($i < $total_pages)
2223 {
2224 $page_string .= $seperator;
2225 }
2226 }
2227 }
2228
2229 $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>';
2230
2231 if ($add_prevnext_text)
2232 {
2233 if ($on_page != 1)
2234 {
2235 $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a> ' . $page_string;
2236 }
2237
2238 if ($on_page != $total_pages)
2239 {
2240 $page_string .= ' <a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>';
2241 }
2242 }
2243
2244 $template->assign_vars(array(
2245 $tpl_prefix . 'BASE_URL' => $base_url,
2246 'A_' . $tpl_prefix . 'BASE_URL' => addslashes($base_url),
2247 $tpl_prefix . 'PER_PAGE' => $per_page,
2248
2249 $tpl_prefix . 'PREVIOUS_PAGE' => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page),
2250 $tpl_prefix . 'NEXT_PAGE' => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page),
2251 $tpl_prefix . 'TOTAL_PAGES' => $total_pages,
2252 ));
2253
2254 return $page_string;
2255}
2256
2257/**
2258* Return current page (pagination)
2259*/
2260function on_page($num_items, $per_page, $start)
2261{
2262 global $template, $user;
2263
2264 // Make sure $per_page is a valid value
2265 $per_page = ($per_page <= 0) ? 1 : $per_page;
2266
2267 $on_page = floor($start / $per_page) + 1;
2268
2269 $template->assign_vars(array(
2270 'ON_PAGE' => $on_page)
2271 );
2272
2273 return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1));
2274}
2275
2276// Server functions (building urls, redirecting...)
2277
2278/**
2279* Append session id to url.
2280* This function supports hooks.
2281*
2282* @param string $url The url the session id needs to be appended to (can have params)
2283* @param mixed $params String or array of additional url parameters
2284* @param bool $is_amp Is url using & (true) or & (false)
2285* @param string $session_id Possibility to use a custom session id instead of the global one
2286*
2287* Examples:
2288* <code>
2289* append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&f=2");
2290* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2');
2291* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
2292* append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
2293* </code>
2294*
2295*/
2296function append_sid($url, $params = false, $is_amp = true, $session_id = false)
2297{
2298 global $_SID, $_EXTRA_URL, $phpbb_hook;
2299
2300 if ($params === '' || (is_array($params) && empty($params)))
2301 {
2302 // Do not append the ? if the param-list is empty anyway.
2303 $params = false;
2304 }
2305
2306 // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately.
2307 // They could mimic most of what is within this function
2308 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
2309 {
2310 if ($phpbb_hook->hook_return(__FUNCTION__))
2311 {
2312 return $phpbb_hook->hook_return_result(__FUNCTION__);
2313 }
2314 }
2315
2316 $params_is_array = is_array($params);
2317
2318 // Get anchor
2319 $anchor = '';
2320 if (strpos($url, '#') !== false)
2321 {
2322 list($url, $anchor) = explode('#', $url, 2);
2323 $anchor = '#' . $anchor;
2324 }
2325 else if (!$params_is_array && strpos($params, '#') !== false)
2326 {
2327 list($params, $anchor) = explode('#', $params, 2);
2328 $anchor = '#' . $anchor;
2329 }
2330
2331 // Handle really simple cases quickly
2332 if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
2333 {
2334 if ($params === false)
2335 {
2336 return $url;
2337 }
2338
2339 $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&' : '&');
2340 return $url . ($params !== false ? $url_delim. $params : '');
2341 }
2342
2343 // Assign sid if session id is not specified
2344 if ($session_id === false)
2345 {
2346 $session_id = $_SID;
2347 }
2348
2349 $amp_delim = ($is_amp) ? '&' : '&';
2350 $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
2351
2352 // Appending custom url parameter?
2353 $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
2354
2355 // Use the short variant if possible ;)
2356 if ($params === false)
2357 {
2358 // Append session id
2359 if (!$session_id)
2360 {
2361 return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
2362 }
2363 else
2364 {
2365 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
2366 }
2367 }
2368
2369 // Build string if parameters are specified as array
2370 if (is_array($params))
2371 {
2372 $output = array();
2373
2374 foreach ($params as $key => $item)
2375 {
2376 if ($item === NULL)
2377 {
2378 continue;
2379 }
2380
2381 if ($key == '#')
2382 {
2383 $anchor = '#' . $item;
2384 continue;
2385 }
2386
2387 $output[] = $key . '=' . $item;
2388 }
2389
2390 $params = implode($amp_delim, $output);
2391 }
2392
2393 // Append session id and parameters (even if they are empty)
2394 // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
2395 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
2396}
2397
2398/**
2399* Generate board url (example: http://www.example.com/phpBB)
2400*
2401* @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
2402*
2403* @return string the generated board url
2404*/
2405function generate_board_url($without_script_path = false)
2406{
2407 global $config, $user;
2408
2409 $server_name = $user->host;
2410 $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
2411
2412 // Forcing server vars is the only way to specify/override the protocol
2413 if ($config['force_server_vars'] || !$server_name)
2414 {
2415 $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
2416 $server_name = $config['server_name'];
2417 $server_port = (int) $config['server_port'];
2418 $script_path = $config['script_path'];
2419
2420 $url = $server_protocol . $server_name;
2421 $cookie_secure = $config['cookie_secure'];
2422 }
2423 else
2424 {
2425 // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
2426 $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
2427 $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
2428
2429 $script_path = $user->page['root_script_path'];
2430 }
2431
2432 if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
2433 {
2434 // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
2435 if (strpos($server_name, ':') === false)
2436 {
2437 $url .= ':' . $server_port;
2438 }
2439 }
2440
2441 if (!$without_script_path)
2442 {
2443 $url .= $script_path;
2444 }
2445
2446 // Strip / from the end
2447 if (substr($url, -1, 1) == '/')
2448 {
2449 $url = substr($url, 0, -1);
2450 }
2451
2452 return $url;
2453}
2454
2455/**
2456* Redirects the user to another page then exits the script nicely
2457* This function is intended for urls within the board. It's not meant to redirect to cross-domains.
2458*
2459* @param string $url The url to redirect to
2460* @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
2461* @param bool $disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
2462*/
2463function redirect($url, $return = false, $disable_cd_check = false)
2464{
2465 global $db, $cache, $config, $user, $phpbb_root_path;
2466
2467 $failover_flag = false;
2468
2469 if (empty($user->lang))
2470 {
2471 $user->add_lang('common');
2472 }
2473
2474 if (!$return)
2475 {
2476 garbage_collection();
2477 }
2478
2479 // Make sure no &'s are in, this will break the redirect
2480 $url = str_replace('&', '&', $url);
2481
2482 // Determine which type of redirect we need to handle...
2483 $url_parts = @parse_url($url);
2484
2485 if ($url_parts === false)
2486 {
2487 // Malformed url, redirect to current page...
2488 $url = generate_board_url() . '/' . $user->page['page'];
2489 }
2490 else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
2491 {
2492 // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
2493 if (!$disable_cd_check && $url_parts['host'] !== $user->host)
2494 {
2495 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2496 }
2497 }
2498 else if ($url[0] == '/')
2499 {
2500 // Absolute uri, prepend direct url...
2501 $url = generate_board_url(true) . $url;
2502 }
2503 else
2504 {
2505 // Relative uri
2506 $pathinfo = pathinfo($url);
2507
2508 if (!$disable_cd_check && !file_exists($pathinfo['dirname'] . '/'))
2509 {
2510 $url = str_replace('../', '', $url);
2511 $pathinfo = pathinfo($url);
2512
2513 if (!file_exists($pathinfo['dirname'] . '/'))
2514 {
2515 // fallback to "last known user page"
2516 // at least this way we know the user does not leave the phpBB root
2517 $url = generate_board_url() . '/' . $user->page['page'];
2518 $failover_flag = true;
2519 }
2520 }
2521
2522 if (!$failover_flag)
2523 {
2524 // Is the uri pointing to the current directory?
2525 if ($pathinfo['dirname'] == '.')
2526 {
2527 $url = str_replace('./', '', $url);
2528
2529 // Strip / from the beginning
2530 if ($url && substr($url, 0, 1) == '/')
2531 {
2532 $url = substr($url, 1);
2533 }
2534
2535 if ($user->page['page_dir'])
2536 {
2537 $url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url;
2538 }
2539 else
2540 {
2541 $url = generate_board_url() . '/' . $url;
2542 }
2543 }
2544 else
2545 {
2546 // Used ./ before, but $phpbb_root_path is working better with urls within another root path
2547 $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path)));
2548 $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname'])));
2549 $intersection = array_intersect_assoc($root_dirs, $page_dirs);
2550
2551 $root_dirs = array_diff_assoc($root_dirs, $intersection);
2552 $page_dirs = array_diff_assoc($page_dirs, $intersection);
2553
2554 $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
2555
2556 // Strip / from the end
2557 if ($dir && substr($dir, -1, 1) == '/')
2558 {
2559 $dir = substr($dir, 0, -1);
2560 }
2561
2562 // Strip / from the beginning
2563 if ($dir && substr($dir, 0, 1) == '/')
2564 {
2565 $dir = substr($dir, 1);
2566 }
2567
2568 $url = str_replace($pathinfo['dirname'] . '/', '', $url);
2569
2570 // Strip / from the beginning
2571 if (substr($url, 0, 1) == '/')
2572 {
2573 $url = substr($url, 1);
2574 }
2575
2576 $url = (!empty($dir) ? $dir . '/' : '') . $url;
2577 $url = generate_board_url() . '/' . $url;
2578 }
2579 }
2580 }
2581
2582 // Make sure we don't redirect to external URLs
2583 if (!$disable_cd_check && strpos($url, generate_board_url(true) . '/') !== 0)
2584 {
2585 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2586 }
2587
2588 // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
2589 if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
2590 {
2591 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2592 }
2593
2594 // Now, also check the protocol and for a valid url the last time...
2595 $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
2596 $url_parts = parse_url($url);
2597
2598 if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
2599 {
2600 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2601 }
2602
2603 if ($return)
2604 {
2605 return $url;
2606 }
2607
2608 // Redirect via an HTML form for PITA webservers
2609 if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
2610 {
2611 header('Refresh: 0; URL=' . $url);
2612
2613 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
2614 echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">';
2615 echo '<head>';
2616 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
2617 echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&', $url) . '" />';
2618 echo '<title>' . $user->lang['REDIRECT'] . '</title>';
2619 echo '</head>';
2620 echo '<body>';
2621 echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&', $url) . '">', '</a>') . '</div>';
2622 echo '</body>';
2623 echo '</html>';
2624
2625 exit;
2626 }
2627
2628 // Behave as per HTTP/1.1 spec for others
2629 header('Location: ' . $url);
2630 exit;
2631}
2632
2633/**
2634* Re-Apply session id after page reloads
2635*/
2636function reapply_sid($url)
2637{
2638 global $phpEx, $phpbb_root_path;
2639
2640 if ($url === "index.$phpEx")
2641 {
2642 return append_sid("index.$phpEx");
2643 }
2644 else if ($url === "{$phpbb_root_path}index.$phpEx")
2645 {
2646 return append_sid("{$phpbb_root_path}index.$phpEx");
2647 }
2648
2649 // Remove previously added sid
2650 if (strpos($url, 'sid=') !== false)
2651 {
2652 // All kind of links
2653 $url = preg_replace('/(\?)?(&|&)?sid=[a-z0-9]+/', '', $url);
2654 // if the sid was the first param, make the old second as first ones
2655 $url = preg_replace("/$phpEx(&|&)+?/", "$phpEx?", $url);
2656 }
2657
2658 return append_sid($url);
2659}
2660
2661/**
2662* Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
2663*/
2664function build_url($strip_vars = false)
2665{
2666 global $user, $phpbb_root_path;
2667
2668 // Append SID
2669 $redirect = append_sid($user->page['page'], false, false);
2670
2671 // Add delimiter if not there...
2672 if (strpos($redirect, '?') === false)
2673 {
2674 $redirect .= '?';
2675 }
2676
2677 // Strip vars...
2678 if ($strip_vars !== false && strpos($redirect, '?') !== false)
2679 {
2680 if (!is_array($strip_vars))
2681 {
2682 $strip_vars = array($strip_vars);
2683 }
2684
2685 $query = $_query = array();
2686
2687 $args = substr($redirect, strpos($redirect, '?') + 1);
2688 $args = ($args) ? explode('&', $args) : array();
2689 $redirect = substr($redirect, 0, strpos($redirect, '?'));
2690
2691 foreach ($args as $argument)
2692 {
2693 $arguments = explode('=', $argument);
2694 $key = $arguments[0];
2695 unset($arguments[0]);
2696
2697 if ($key === '')
2698 {
2699 continue;
2700 }
2701
2702 $query[$key] = implode('=', $arguments);
2703 }
2704
2705 // Strip the vars off
2706 foreach ($strip_vars as $strip)
2707 {
2708 if (isset($query[$strip]))
2709 {
2710 unset($query[$strip]);
2711 }
2712 }
2713
2714 // Glue the remaining parts together... already urlencoded
2715 foreach ($query as $key => $value)
2716 {
2717 $_query[] = $key . '=' . $value;
2718 }
2719 $query = implode('&', $_query);
2720
2721 $redirect .= ($query) ? '?' . $query : '';
2722 }
2723
2724 // We need to be cautious here.
2725 // On some situations, the redirect path is an absolute URL, sometimes a relative path
2726 // For a relative path, let's prefix it with $phpbb_root_path to point to the correct location,
2727 // else we use the URL directly.
2728 $url_parts = @parse_url($redirect);
2729
2730 // URL
2731 if ($url_parts !== false && !empty($url_parts['scheme']) && !empty($url_parts['host']))
2732 {
2733 return str_replace('&', '&', $redirect);
2734 }
2735
2736 return $phpbb_root_path . str_replace('&', '&', $redirect);
2737}
2738
2739/**
2740* Meta refresh assignment
2741* Adds META template variable with meta http tag.
2742*
2743* @param int $time Time in seconds for meta refresh tag
2744* @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
2745* @param bool $disable_cd_check If true, meta_refresh() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
2746*/
2747function meta_refresh($time, $url, $disable_cd_check = false)
2748{
2749 global $template;
2750
2751 $url = redirect($url, true, $disable_cd_check);
2752 $url = str_replace('&', '&', $url);
2753
2754 // For XHTML compatibility we change back & to &
2755 $template->assign_vars(array(
2756 'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />')
2757 );
2758
2759 return $url;
2760}
2761
2762/**
2763* Outputs correct status line header.
2764*
2765* Depending on php sapi one of the two following forms is used:
2766*
2767* Status: 404 Not Found
2768*
2769* HTTP/1.x 404 Not Found
2770*
2771* HTTP version is taken from HTTP_VERSION environment variable,
2772* and defaults to 1.0.
2773*
2774* Sample usage:
2775*
2776* send_status_line(404, 'Not Found');
2777*
2778* @param int $code HTTP status code
2779* @param string $message Message for the status code
2780* @return null
2781*/
2782function send_status_line($code, $message)
2783{
2784 if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')
2785 {
2786 // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though
2787 header("Status: $code $message", true, $code);
2788 }
2789 else
2790 {
2791 if (!empty($_SERVER['SERVER_PROTOCOL']) && is_string($_SERVER['SERVER_PROTOCOL']) && preg_match('#^HTTP/[0-9]\.[0-9]$#', $_SERVER['SERVER_PROTOCOL']))
2792 {
2793 $version = $_SERVER['SERVER_PROTOCOL'];
2794 }
2795 else
2796 {
2797 $version = 'HTTP/1.0';
2798 }
2799 header("$version $code $message", true, $code);
2800 }
2801}
2802
2803//Form validation
2804
2805
2806/**
2807* Add a secret hash for use in links/GET requests
2808* @param string $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
2809* @return string the hash
2810
2811*/
2812function generate_link_hash($link_name)
2813{
2814 global $user;
2815
2816 if (!isset($user->data["hash_$link_name"]))
2817 {
2818 $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
2819 }
2820
2821 return $user->data["hash_$link_name"];
2822}
2823
2824
2825/**
2826* checks a link hash - for GET requests
2827* @param string $token the submitted token
2828* @param string $link_name The name of the link
2829* @return boolean true if all is fine
2830*/
2831function check_link_hash($token, $link_name)
2832{
2833 return $token === generate_link_hash($link_name);
2834}
2835
2836/**
2837* Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2838* @param string $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2839*/
2840function add_form_key($form_name)
2841{
2842 global $config, $template, $user;
2843
2844 $now = time();
2845 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2846 $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2847
2848 $s_fields = build_hidden_fields(array(
2849 'creation_time' => $now,
2850 'form_token' => $token,
2851 ));
2852
2853 $template->assign_vars(array(
2854 'S_FORM_TOKEN' => $s_fields,
2855 ));
2856}
2857
2858/**
2859* Check the form key. Required for all altering actions not secured by confirm_box
2860* @param string $form_name The name of the form; has to match the name used in add_form_key, otherwise no restrictions apply
2861* @param int $timespan The maximum acceptable age for a submitted form in seconds. Defaults to the config setting.
2862* @param string $return_page The address for the return link
2863* @param bool $trigger If true, the function will triger an error when encountering an invalid form
2864*/
2865function check_form_key($form_name, $timespan = false, $return_page = '', $trigger = false)
2866{
2867 global $config, $user;
2868
2869 if ($timespan === false)
2870 {
2871 // we enforce a minimum value of half a minute here.
2872 $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2873 }
2874
2875 if (isset($_POST['creation_time']) && isset($_POST['form_token']))
2876 {
2877 $creation_time = abs(request_var('creation_time', 0));
2878 $token = request_var('form_token', '');
2879
2880 $diff = time() - $creation_time;
2881
2882 // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
2883 if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))
2884 {
2885 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2886 $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2887
2888 if ($key === $token)
2889 {
2890 return true;
2891 }
2892 }
2893 }
2894
2895 if ($trigger)
2896 {
2897 trigger_error($user->lang['FORM_INVALID'] . $return_page);
2898 }
2899
2900 return false;
2901}
2902
2903// Message/Login boxes
2904
2905/**
2906* Build Confirm box
2907* @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2908* @param string $title Title/Message used for confirm box.
2909* message text is _CONFIRM appended to title.
2910* If title cannot be found in user->lang a default one is displayed
2911* If title_CONFIRM cannot be found in user->lang the text given is used.
2912* @param string $hidden Hidden variables
2913* @param string $html_body Template used for confirm box
2914* @param string $u_action Custom form action
2915*/
2916function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2917{
2918 global $user, $template, $db;
2919 global $phpEx, $phpbb_root_path;
2920
2921 if (isset($_POST['cancel']))
2922 {
2923 return false;
2924 }
2925
2926 $confirm = false;
2927 if (isset($_POST['confirm']))
2928 {
2929 // language frontier
2930 if ($_POST['confirm'] === $user->lang['YES'])
2931 {
2932 $confirm = true;
2933 }
2934 }
2935
2936 if ($check && $confirm)
2937 {
2938 $user_id = request_var('confirm_uid', 0);
2939 $session_id = request_var('sess', '');
2940 $confirm_key = request_var('confirm_key', '');
2941
2942 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'])
2943 {
2944 return false;
2945 }
2946
2947 // Reset user_last_confirm_key
2948 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2949 WHERE user_id = " . $user->data['user_id'];
2950 $db->sql_query($sql);
2951
2952 return true;
2953 }
2954 else if ($check)
2955 {
2956 return false;
2957 }
2958
2959 $s_hidden_fields = build_hidden_fields(array(
2960 'confirm_uid' => $user->data['user_id'],
2961 'sess' => $user->session_id,
2962 'sid' => $user->session_id,
2963 ));
2964
2965 // generate activation key
2966 $confirm_key = gen_rand_string(10);
2967
2968 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2969 {
2970 adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2971 }
2972 else
2973 {
2974 page_header(((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]), false);
2975 }
2976
2977 $template->set_filenames(array(
2978 'body' => $html_body)
2979 );
2980
2981 // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2982 if (request_var('confirm_key', ''))
2983 {
2984 // This should not occur, therefore we cancel the operation to safe the user
2985 return false;
2986 }
2987
2988 // re-add sid / transform & to & for user->page (user->page is always using &)
2989 $use_page = ($u_action) ? $phpbb_root_path . $u_action : $phpbb_root_path . str_replace('&', '&', $user->page['page']);
2990 $u_action = reapply_sid($use_page);
2991 $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&') . 'confirm_key=' . $confirm_key;
2992
2993 $template->assign_vars(array(
2994 'MESSAGE_TITLE' => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2995 'MESSAGE_TEXT' => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2996
2997 'YES_VALUE' => $user->lang['YES'],
2998 'S_CONFIRM_ACTION' => $u_action,
2999 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields)
3000 );
3001
3002 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
3003 WHERE user_id = " . $user->data['user_id'];
3004 $db->sql_query($sql);
3005
3006 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3007 {
3008 adm_page_footer();
3009 }
3010 else
3011 {
3012 page_footer();
3013 }
3014}
3015
3016/**
3017* Generate login box or verify password
3018*/
3019function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
3020{
3021 global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
3022
3023 if (!class_exists('phpbb_captcha_factory'))
3024 {
3025 include($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx);
3026 }
3027
3028 $err = '';
3029
3030 // Make sure user->setup() has been called
3031 if (empty($user->lang))
3032 {
3033 $user->setup();
3034 }
3035
3036 // Print out error if user tries to authenticate as an administrator without having the privileges...
3037 if ($admin && !$auth->acl_get('a_'))
3038 {
3039 // Not authd
3040 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
3041 if ($user->data['is_registered'])
3042 {
3043 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3044 }
3045 trigger_error('NO_AUTH_ADMIN');
3046 }
3047
3048 if (isset($_POST['login']))
3049 {
3050 // Get credential
3051 if ($admin)
3052 {
3053 $credential = request_var('credential', '');
3054
3055 if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
3056 {
3057 if ($user->data['is_registered'])
3058 {
3059 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3060 }
3061 trigger_error('NO_AUTH_ADMIN');
3062 }
3063
3064 $password = request_var('password_' . $credential, '', true);
3065 }
3066 else
3067 {
3068 $password = request_var('password', '', true);
3069 }
3070
3071 $username = request_var('username', '', true);
3072 $autologin = (!empty($_POST['autologin'])) ? true : false;
3073 $viewonline = (!empty($_POST['viewonline'])) ? 0 : 1;
3074 $admin = ($admin) ? 1 : 0;
3075 $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
3076
3077 // Check if the supplied username is equal to the one stored within the database if re-authenticating
3078 if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
3079 {
3080 // We log the attempt to use a different username...
3081 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3082 trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
3083 }
3084
3085 // If authentication is successful we redirect user to previous page
3086 $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
3087
3088 // If admin authentication and login, we will log if it was a success or not...
3089 // We also break the operation on the first non-success login - it could be argued that the user already knows
3090 if ($admin)
3091 {
3092 if ($result['status'] == LOGIN_SUCCESS)
3093 {
3094 add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
3095 }
3096 else
3097 {
3098 // Only log the failed attempt if a real user tried to.
3099 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
3100 if ($user->data['is_registered'])
3101 {
3102 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3103 }
3104 }
3105 }
3106
3107 // The result parameter is always an array, holding the relevant information...
3108 if ($result['status'] == LOGIN_SUCCESS)
3109 {
3110 $redirect = request_var('redirect', "{$phpbb_root_path}index.$phpEx");
3111 $message = ($l_success) ? $l_success : $user->lang['LOGIN_REDIRECT'];
3112 $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']);
3113
3114 // append/replace SID (may change during the session for AOL users)
3115 $redirect = reapply_sid($redirect);
3116
3117 // Special case... the user is effectively banned, but we allow founders to login
3118 if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
3119 {
3120 return;
3121 }
3122
3123 $redirect = meta_refresh(3, $redirect);
3124 trigger_error($message . '<br /><br />' . sprintf($l_redirect, '<a href="' . $redirect . '">', '</a>'));
3125 }
3126
3127 // Something failed, determine what...
3128 if ($result['status'] == LOGIN_BREAK)
3129 {
3130 trigger_error($result['error_msg']);
3131 }
3132
3133 // Special cases... determine
3134 switch ($result['status'])
3135 {
3136 case LOGIN_ERROR_ATTEMPTS:
3137
3138 $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']);
3139 $captcha->init(CONFIRM_LOGIN);
3140 // $captcha->reset();
3141
3142 $template->assign_vars(array(
3143 'CAPTCHA_TEMPLATE' => $captcha->get_template(),
3144 ));
3145
3146 $err = $user->lang[$result['error_msg']];
3147 break;
3148
3149 case LOGIN_ERROR_PASSWORD_CONVERT:
3150 $err = sprintf(
3151 $user->lang[$result['error_msg']],
3152 ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
3153 ($config['email_enable']) ? '</a>' : '',
3154 ($config['board_contact']) ? '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">' : '',
3155 ($config['board_contact']) ? '</a>' : ''
3156 );
3157 break;
3158
3159 // Username, password, etc...
3160 default:
3161 $err = $user->lang[$result['error_msg']];
3162
3163 // Assign admin contact to some error messages
3164 if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
3165 {
3166 $err = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>');
3167 }
3168
3169 break;
3170 }
3171 }
3172
3173 // Assign credential for username/password pair
3174 $credential = ($admin) ? md5(unique_id()) : false;
3175
3176 $s_hidden_fields = array(
3177 'sid' => $user->session_id,
3178 );
3179
3180 if ($redirect)
3181 {
3182 $s_hidden_fields['redirect'] = $redirect;
3183 }
3184
3185 if ($admin)
3186 {
3187 $s_hidden_fields['credential'] = $credential;
3188 }
3189
3190 $s_hidden_fields = build_hidden_fields($s_hidden_fields);
3191
3192 $template->assign_vars(array(
3193 'LOGIN_ERROR' => $err,
3194 'LOGIN_EXPLAIN' => $l_explain,
3195
3196 'U_SEND_PASSWORD' => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',
3197 'U_RESEND_ACTIVATION' => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
3198 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
3199 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
3200
3201 'S_DISPLAY_FULL_LOGIN' => ($s_display) ? true : false,
3202 'S_HIDDEN_FIELDS' => $s_hidden_fields,
3203
3204 'S_ADMIN_AUTH' => $admin,
3205 'USERNAME' => ($admin) ? $user->data['username'] : '',
3206
3207 'USERNAME_CREDENTIAL' => 'username',
3208 'PASSWORD_CREDENTIAL' => ($admin) ? 'password_' . $credential : 'password',
3209 ));
3210
3211 page_header($user->lang['LOGIN'], false);
3212
3213 $template->set_filenames(array(
3214 'body' => 'login_body.html')
3215 );
3216 make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
3217
3218 page_footer();
3219}
3220
3221/**
3222* Generate forum login box
3223*/
3224function login_forum_box($forum_data)
3225{
3226 global $db, $config, $user, $template, $phpEx;
3227
3228 $password = request_var('password', '', true);
3229
3230 $sql = 'SELECT forum_id
3231 FROM ' . FORUMS_ACCESS_TABLE . '
3232 WHERE forum_id = ' . $forum_data['forum_id'] . '
3233 AND user_id = ' . $user->data['user_id'] . "
3234 AND session_id = '" . $db->sql_escape($user->session_id) . "'";
3235 $result = $db->sql_query($sql);
3236 $row = $db->sql_fetchrow($result);
3237 $db->sql_freeresult($result);
3238
3239 if ($row)
3240 {
3241 return true;
3242 }
3243
3244 if ($password)
3245 {
3246 // Remove expired authorised sessions
3247 $sql = 'SELECT f.session_id
3248 FROM ' . FORUMS_ACCESS_TABLE . ' f
3249 LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
3250 WHERE s.session_id IS NULL';
3251 $result = $db->sql_query($sql);
3252
3253 if ($row = $db->sql_fetchrow($result))
3254 {
3255 $sql_in = array();
3256 do
3257 {
3258 $sql_in[] = (string) $row['session_id'];
3259 }
3260 while ($row = $db->sql_fetchrow($result));
3261
3262 // Remove expired sessions
3263 $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
3264 WHERE ' . $db->sql_in_set('session_id', $sql_in);
3265 $db->sql_query($sql);
3266 }
3267 $db->sql_freeresult($result);
3268
3269 if (phpbb_check_hash($password, $forum_data['forum_password']))
3270 {
3271 $sql_ary = array(
3272 'forum_id' => (int) $forum_data['forum_id'],
3273 'user_id' => (int) $user->data['user_id'],
3274 'session_id' => (string) $user->session_id,
3275 );
3276
3277 $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3278
3279 return true;
3280 }
3281
3282 $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
3283 }
3284
3285 page_header($user->lang['LOGIN'], false);
3286
3287 $template->assign_vars(array(
3288 'FORUM_NAME' => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '',
3289 'S_LOGIN_ACTION' => build_url(array('f')),
3290 'S_HIDDEN_FIELDS' => build_hidden_fields(array('f' => $forum_data['forum_id'])))
3291 );
3292
3293 $template->set_filenames(array(
3294 'body' => 'login_forum.html')
3295 );
3296
3297 page_footer();
3298}
3299
3300// Little helpers
3301
3302/**
3303* Little helper for the build_hidden_fields function
3304*/
3305function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
3306{
3307 $hidden_fields = '';
3308
3309 if (!is_array($value))
3310 {
3311 $value = ($stripslashes) ? stripslashes($value) : $value;
3312 $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
3313
3314 $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
3315 }
3316 else
3317 {
3318 foreach ($value as $_key => $_value)
3319 {
3320 $_key = ($stripslashes) ? stripslashes($_key) : $_key;
3321 $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
3322
3323 $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
3324 }
3325 }
3326
3327 return $hidden_fields;
3328}
3329
3330/**
3331* Build simple hidden fields from array
3332*
3333* @param array $field_ary an array of values to build the hidden field from
3334* @param bool $specialchar if true, keys and values get specialchared
3335* @param bool $stripslashes if true, keys and values get stripslashed
3336*
3337* @return string the hidden fields
3338*/
3339function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
3340{
3341 $s_hidden_fields = '';
3342
3343 foreach ($field_ary as $name => $vars)
3344 {
3345 $name = ($stripslashes) ? stripslashes($name) : $name;
3346 $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
3347
3348 $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
3349 }
3350
3351 return $s_hidden_fields;
3352}
3353
3354/**
3355* Parse cfg file
3356*/
3357function parse_cfg_file($filename, $lines = false)
3358{
3359 $parsed_items = array();
3360
3361 if ($lines === false)
3362 {
3363 $lines = file($filename);
3364 }
3365
3366 foreach ($lines as $line)
3367 {
3368 $line = trim($line);
3369
3370 if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
3371 {
3372 continue;
3373 }
3374
3375 // Determine first occurrence, since in values the equal sign is allowed
3376 $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))));
3377 $value = trim(substr($line, $delim_pos + 1));
3378
3379 if (in_array($value, array('off', 'false', '0')))
3380 {
3381 $value = false;
3382 }
3383 else if (in_array($value, array('on', 'true', '1')))
3384 {
3385 $value = true;
3386 }
3387 else if (!trim($value))
3388 {
3389 $value = '';
3390 }
3391 else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
3392 {
3393 $value = htmlspecialchars(substr($value, 1, sizeof($value)-2));
3394 }
3395 else
3396 {
3397 $value = htmlspecialchars($value);
3398 }
3399
3400 $parsed_items[$key] = $value;
3401 }
3402
3403 if (isset($parsed_items['inherit_from']) && isset($parsed_items['name']) && $parsed_items['inherit_from'] == $parsed_items['name'])
3404 {
3405 unset($parsed_items['inherit_from']);
3406 }
3407
3408 return $parsed_items;
3409}
3410
3411/**
3412* Add log event
3413*/
3414function add_log()
3415{
3416 global $db, $user;
3417
3418 // In phpBB 3.1.x i want to have logging in a class to be able to control it
3419 // For now, we need a quite hakish approach to circumvent logging for some actions
3420 // @todo implement cleanly
3421 if (!empty($GLOBALS['skip_add_log']))
3422 {
3423 return false;
3424 }
3425
3426 $args = func_get_args();
3427
3428 $mode = array_shift($args);
3429 $reportee_id = ($mode == 'user') ? intval(array_shift($args)) : '';
3430 $forum_id = ($mode == 'mod') ? intval(array_shift($args)) : '';
3431 $topic_id = ($mode == 'mod') ? intval(array_shift($args)) : '';
3432 $action = array_shift($args);
3433 $data = (!sizeof($args)) ? '' : serialize($args);
3434
3435 $sql_ary = array(
3436 'user_id' => (empty($user->data)) ? ANONYMOUS : $user->data['user_id'],
3437 'log_ip' => $user->ip,
3438 'log_time' => time(),
3439 'log_operation' => $action,
3440 'log_data' => $data,
3441 );
3442
3443 switch ($mode)
3444 {
3445 case 'admin':
3446 $sql_ary['log_type'] = LOG_ADMIN;
3447 break;
3448
3449 case 'mod':
3450 $sql_ary += array(
3451 'log_type' => LOG_MOD,
3452 'forum_id' => $forum_id,
3453 'topic_id' => $topic_id
3454 );
3455 break;
3456
3457 case 'user':
3458 $sql_ary += array(
3459 'log_type' => LOG_USERS,
3460 'reportee_id' => $reportee_id
3461 );
3462 break;
3463
3464 case 'critical':
3465 $sql_ary['log_type'] = LOG_CRITICAL;
3466 break;
3467
3468 default:
3469 return false;
3470 }
3471
3472 $db->sql_query('INSERT INTO ' . LOG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3473
3474 return $db->sql_nextid();
3475}
3476
3477/**
3478* Return a nicely formatted backtrace.
3479*
3480* Turns the array returned by debug_backtrace() into HTML markup.
3481* Also filters out absolute paths to phpBB root.
3482*
3483* @return string HTML markup
3484*/
3485function get_backtrace()
3486{
3487 $output = '<div style="font-family: monospace;">';
3488 $backtrace = debug_backtrace();
3489
3490 // We skip the first one, because it only shows this file/function
3491 unset($backtrace[0]);
3492
3493 foreach ($backtrace as $trace)
3494 {
3495 // Strip the current directory from path
3496 $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']));
3497 $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
3498
3499 // Only show function arguments for include etc.
3500 // Other parameters may contain sensible information
3501 $argument = '';
3502 if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
3503 {
3504 $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]));
3505 }
3506
3507 $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
3508 $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
3509
3510 $output .= '<br />';
3511 $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
3512 $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
3513
3514 $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']);
3515 $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
3516 }
3517 $output .= '</div>';
3518 return $output;
3519}
3520
3521/**
3522* This function returns a regular expression pattern for commonly used expressions
3523* Use with / as delimiter for email mode and # for url modes
3524* mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
3525*/
3526function get_preg_expression($mode)
3527{
3528 switch ($mode)
3529 {
3530 case 'email':
3531 // Regex written by James Watts and Francisco Jose Martin Moreno
3532 // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
3533 return '([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&)+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)';
3534 break;
3535
3536 case 'bbcode_htm':
3537 return array(
3538 '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
3539 '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
3540 '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
3541 '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
3542 '#<!\-\- .*? \-\->#s',
3543 '#<.*?>#s',
3544 );
3545 break;
3546
3547 // Whoa these look impressive!
3548 // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
3549 // can be found in the develop directory
3550 case 'ipv4':
3551 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])$#';
3552 break;
3553
3554 case 'ipv6':
3555 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}:){0,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';
3556 break;
3557
3558 case 'url':
3559 case 'url_inline':
3560 $inline = ($mode == 'url') ? ')' : '';
3561 $scheme = ($mode == 'url') ? '[a-z\d+\-.]' : '[a-z\d+]'; // avoid automatic parsing of "word" in "last word.http://..."
3562 // generated with regex generation file in the develop folder
3563 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})*)?";
3564 break;
3565
3566 case 'www_url':
3567 case 'www_url_inline':
3568 $inline = ($mode == 'www_url') ? ')' : '';
3569 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})*)?";
3570 break;
3571
3572 case 'relative_url':
3573 case 'relative_url_inline':
3574 $inline = ($mode == 'relative_url') ? ')' : '';
3575 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})*)?";
3576 break;
3577
3578 case 'table_prefix':
3579 return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
3580 break;
3581 }
3582
3583 return '';
3584}
3585
3586/**
3587* Generate regexp for naughty words censoring
3588* Depends on whether installed PHP version supports unicode properties
3589*
3590* @param string $word word template to be replaced
3591* @param bool $use_unicode whether or not to take advantage of PCRE supporting unicode
3592*
3593* @return string $preg_expr regex to use with word censor
3594*/
3595function get_censor_preg_expression($word, $use_unicode = true)
3596{
3597 static $unicode_support = null;
3598
3599 // Check whether PHP version supports unicode properties
3600 if (is_null($unicode_support))
3601 {
3602 $unicode_support = ((version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>='))) && @preg_match('/\p{L}/u', 'a') !== false) ? true : false;
3603 }
3604
3605 // Unescape the asterisk to simplify further conversions
3606 $word = str_replace('\*', '*', preg_quote($word, '#'));
3607
3608 if ($use_unicode && $unicode_support)
3609 {
3610 // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
3611 $word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word);
3612
3613 // Generate the final substitution
3614 $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
3615 }
3616 else
3617 {
3618 // Replace the asterisk inside the pattern, at the start and at the end of it with regexes
3619 $word = preg_replace(array('#(?<=\S)\*+(?=\S)#iu', '#^\*+#', '#\*+$#'), array('(\x20*?\S*?)', '\S*?', '\S*?'), $word);
3620
3621 // Generate the final substitution
3622 $preg_expr = '#(?<!\S)(' . $word . ')(?!\S)#iu';
3623 }
3624
3625 return $preg_expr;
3626}
3627
3628/**
3629* Returns the first block of the specified IPv6 address and as many additional
3630* ones as specified in the length paramater.
3631* If length is zero, then an empty string is returned.
3632* If length is greater than 3 the complete IP will be returned
3633*/
3634function short_ipv6($ip, $length)
3635{
3636 if ($length < 1)
3637 {
3638 return '';
3639 }
3640
3641 // extend IPv6 addresses
3642 $blocks = substr_count($ip, ':') + 1;
3643 if ($blocks < 9)
3644 {
3645 $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
3646 }
3647 if ($ip[0] == ':')
3648 {
3649 $ip = '0000' . $ip;
3650 }
3651 if ($length < 4)
3652 {
3653 $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
3654 }
3655
3656 return $ip;
3657}
3658
3659/**
3660* Wrapper for php's checkdnsrr function.
3661*
3662* @param string $host Fully-Qualified Domain Name
3663* @param string $type Resource record type to lookup
3664* Supported types are: MX (default), A, AAAA, NS, TXT, CNAME
3665* Other types may work or may not work
3666*
3667* @return mixed true if entry found,
3668* false if entry not found,
3669* null if this function is not supported by this environment
3670*
3671* Since null can also be returned, you probably want to compare the result
3672* with === true or === false,
3673*
3674* @author bantu
3675*/
3676function phpbb_checkdnsrr($host, $type = 'MX')
3677{
3678 // The dot indicates to search the DNS root (helps those having DNS prefixes on the same domain)
3679 if (substr($host, -1) == '.')
3680 {
3681 $host_fqdn = $host;
3682 $host = substr($host, 0, -1);
3683 }
3684 else
3685 {
3686 $host_fqdn = $host . '.';
3687 }
3688 // $host has format some.host.example.com
3689 // $host_fqdn has format some.host.example.com.
3690
3691 // If we're looking for an A record we can use gethostbyname()
3692 if ($type == 'A' && function_exists('gethostbyname'))
3693 {
3694 return (@gethostbyname($host_fqdn) == $host_fqdn) ? false : true;
3695 }
3696
3697 // checkdnsrr() is available on Windows since PHP 5.3,
3698 // but until 5.3.3 it only works for MX records
3699 // See: http://bugs.php.net/bug.php?id=51844
3700
3701 // Call checkdnsrr() if
3702 // we're looking for an MX record or
3703 // we're not on Windows or
3704 // we're running a PHP version where #51844 has been fixed
3705
3706 // checkdnsrr() supports AAAA since 5.0.0
3707 // checkdnsrr() supports TXT since 5.2.4
3708 if (
3709 ($type == 'MX' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.3', '>=')) &&
3710 ($type != 'AAAA' || version_compare(PHP_VERSION, '5.0.0', '>=')) &&
3711 ($type != 'TXT' || version_compare(PHP_VERSION, '5.2.4', '>=')) &&
3712 function_exists('checkdnsrr')
3713 )
3714 {
3715 return checkdnsrr($host_fqdn, $type);
3716 }
3717
3718 // dns_get_record() is available since PHP 5; since PHP 5.3 also on Windows,
3719 // but on Windows it does not work reliable for AAAA records before PHP 5.3.1
3720
3721 // Call dns_get_record() if
3722 // we're not looking for an AAAA record or
3723 // we're not on Windows or
3724 // we're running a PHP version where AAAA lookups work reliable
3725 if (
3726 ($type != 'AAAA' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.1', '>=')) &&
3727 function_exists('dns_get_record')
3728 )
3729 {
3730 // dns_get_record() expects an integer as second parameter
3731 // We have to convert the string $type to the corresponding integer constant.
3732 $type_constant = 'DNS_' . $type;
3733 $type_param = (defined($type_constant)) ? constant($type_constant) : DNS_ANY;
3734
3735 // dns_get_record() might throw E_WARNING and return false for records that do not exist
3736 $resultset = @dns_get_record($host_fqdn, $type_param);
3737
3738 if (empty($resultset) || !is_array($resultset))
3739 {
3740 return false;
3741 }
3742 else if ($type_param == DNS_ANY)
3743 {
3744 // $resultset is a non-empty array
3745 return true;
3746 }
3747
3748 foreach ($resultset as $result)
3749 {
3750 if (
3751 isset($result['host']) && $result['host'] == $host &&
3752 isset($result['type']) && $result['type'] == $type
3753 )
3754 {
3755 return true;
3756 }
3757 }
3758
3759 return false;
3760 }
3761
3762 // If we're on Windows we can still try to call nslookup via exec() as a last resort
3763 if (DIRECTORY_SEPARATOR == '\\' && function_exists('exec'))
3764 {
3765 @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host_fqdn), $output);
3766
3767 // If output is empty, the nslookup failed
3768 if (empty($output))
3769 {
3770 return NULL;
3771 }
3772
3773 foreach ($output as $line)
3774 {
3775 $line = trim($line);
3776
3777 if (empty($line))
3778 {
3779 continue;
3780 }
3781
3782 // Squash tabs and multiple whitespaces to a single whitespace.
3783 $line = preg_replace('/\s+/', ' ', $line);
3784
3785 switch ($type)
3786 {
3787 case 'MX':
3788 if (stripos($line, "$host MX") === 0)
3789 {
3790 return true;
3791 }
3792 break;
3793
3794 case 'NS':
3795 if (stripos($line, "$host nameserver") === 0)
3796 {
3797 return true;
3798 }
3799 break;
3800
3801 case 'TXT':
3802 if (stripos($line, "$host text") === 0)
3803 {
3804 return true;
3805 }
3806 break;
3807
3808 case 'CNAME':
3809 if (stripos($line, "$host canonical name") === 0)
3810 {
3811 return true;
3812 }
3813 break;
3814
3815 default:
3816 case 'AAAA':
3817 // AAAA records returned by nslookup on Windows XP/2003 have this format.
3818 // Later Windows versions use the A record format below for AAAA records.
3819 if (stripos($line, "$host AAAA IPv6 address") === 0)
3820 {
3821 return true;
3822 }
3823 // No break
3824
3825 case 'A':
3826 if (!empty($host_matches))
3827 {
3828 // Second line
3829 if (stripos($line, "Address: ") === 0)
3830 {
3831 return true;
3832 }
3833 else
3834 {
3835 $host_matches = false;
3836 }
3837 }
3838 else if (stripos($line, "Name: $host") === 0)
3839 {
3840 // First line
3841 $host_matches = true;
3842 }
3843 break;
3844 }
3845 }
3846
3847 return false;
3848 }
3849
3850 return NULL;
3851}
3852
3853// Handler, header and footer
3854
3855/**
3856* Error and message handler, call with trigger_error if reqd
3857*/
3858function msg_handler($errno, $msg_text, $errfile, $errline)
3859{
3860 global $cache, $db, $auth, $template, $config, $user;
3861 global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;
3862
3863 // Do not display notices if we suppress them via @
3864 if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
3865 {
3866 return;
3867 }
3868
3869 // Message handler is stripping text. In case we need it, we are possible to define long text...
3870 if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3871 {
3872 $msg_text = $msg_long_text;
3873 }
3874
3875 if (!defined('E_DEPRECATED'))
3876 {
3877 define('E_DEPRECATED', 8192);
3878 }
3879
3880 switch ($errno)
3881 {
3882 case E_NOTICE:
3883 case E_WARNING:
3884
3885 // Check the error reporting level and return if the error level does not match
3886 // If DEBUG is defined the default level is E_ALL
3887 if (($errno & ((defined('DEBUG')) ? E_ALL : error_reporting())) == 0)
3888 {
3889 return;
3890 }
3891
3892 if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3893 {
3894 $errfile = phpbb_filter_root_path($errfile);
3895 $msg_text = phpbb_filter_root_path($msg_text);
3896 $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
3897 echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3898
3899 // we are writing an image - the user won't see the debug, so let's place it in the log
3900 if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
3901 {
3902 add_log('critical', 'LOG_IMAGE_GENERATION_ERROR', $errfile, $errline, $msg_text);
3903 }
3904 // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
3905 }
3906
3907 return;
3908
3909 break;
3910
3911 case E_USER_ERROR:
3912
3913 if (!empty($user) && !empty($user->lang))
3914 {
3915 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3916 $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3917
3918 $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3919 $l_notify = '';
3920
3921 if (!empty($config['board_contact']))
3922 {
3923 $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3924 }
3925 }
3926 else
3927 {
3928 $msg_title = 'General Error';
3929 $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3930 $l_notify = '';
3931
3932 if (!empty($config['board_contact']))
3933 {
3934 $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3935 }
3936 }
3937
3938 $log_text = $msg_text;
3939 $backtrace = get_backtrace();
3940 if ($backtrace)
3941 {
3942 $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
3943 }
3944
3945 if (defined('IN_INSTALL') || defined('DEBUG_EXTRA') || isset($auth) && $auth->acl_get('a_'))
3946 {
3947 $msg_text = $log_text;
3948 }
3949
3950 if ((defined('DEBUG') || defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
3951 {
3952 // let's avoid loops
3953 $db->sql_return_on_error(true);
3954 add_log('critical', 'LOG_GENERAL_ERROR', $msg_title, $log_text);
3955 $db->sql_return_on_error(false);
3956 }
3957
3958 // Do not send 200 OK, but service unavailable on errors
3959 send_status_line(503, 'Service Unavailable');
3960
3961 garbage_collection();
3962
3963 // Try to not call the adm page data...
3964
3965 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
3966 echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">';
3967 echo '<head>';
3968 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
3969 echo '<title>' . $msg_title . '</title>';
3970 echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
3971 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; } ';
3972 echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
3973 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; } ';
3974 echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px #A9B8C2; } ';
3975 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; } ';
3976 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; } ';
3977 echo "\n" . '/* ]]> */' . "\n";
3978 echo '</style>';
3979 echo '</head>';
3980 echo '<body id="errorpage">';
3981 echo '<div id="wrap">';
3982 echo ' <div id="page-header">';
3983 echo ' ' . $l_return_index;
3984 echo ' </div>';
3985 echo ' <div id="acp">';
3986 echo ' <div class="panel">';
3987 echo ' <div id="content">';
3988 echo ' <h1>' . $msg_title . '</h1>';
3989
3990 echo ' <div>' . $msg_text . '</div>';
3991
3992 echo $l_notify;
3993
3994 echo ' </div>';
3995 echo ' </div>';
3996 echo ' </div>';
3997 echo ' <div id="page-footer">';
3998 echo ' Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group';
3999 echo ' </div>';
4000 echo '</div>';
4001 echo '</body>';
4002 echo '</html>';
4003
4004 exit_handler();
4005
4006 // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
4007 exit;
4008 break;
4009
4010 case E_USER_WARNING:
4011 case E_USER_NOTICE:
4012
4013 define('IN_ERROR_HANDLER', true);
4014
4015 if (empty($user->data))
4016 {
4017 $user->session_begin();
4018 }
4019
4020 // We re-init the auth array to get correct results on login/logout
4021 $auth->acl($user->data);
4022
4023 if (empty($user->lang))
4024 {
4025 $user->setup();
4026 }
4027
4028 if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
4029 {
4030 send_status_line(404, 'Not Found');
4031 }
4032
4033 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
4034 $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
4035
4036 if (!defined('HEADER_INC'))
4037 {
4038 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
4039 {
4040 adm_page_header($msg_title);
4041 }
4042 else
4043 {
4044 page_header($msg_title, false);
4045 }
4046 }
4047
4048 $template->set_filenames(array(
4049 'body' => 'message_body.html')
4050 );
4051
4052 $template->assign_vars(array(
4053 'MESSAGE_TITLE' => $msg_title,
4054 'MESSAGE_TEXT' => $msg_text,
4055 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false,
4056 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false)
4057 );
4058
4059 // We do not want the cron script to be called on error messages
4060 define('IN_CRON', true);
4061
4062 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
4063 {
4064 adm_page_footer();
4065 }
4066 else
4067 {
4068 page_footer();
4069 }
4070
4071 exit_handler();
4072 break;
4073
4074 // PHP4 compatibility
4075 case E_DEPRECATED:
4076 return true;
4077 break;
4078 }
4079
4080 // If we notice an error not handled here we pass this back to PHP by returning false
4081 // This may not work for all php versions
4082 return false;
4083}
4084
4085/**
4086* Removes absolute path to phpBB root directory from error messages
4087* and converts backslashes to forward slashes.
4088*
4089* @param string $errfile Absolute file path
4090* (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
4091* Please note that if $errfile is outside of the phpBB root,
4092* the root path will not be found and can not be filtered.
4093* @return string Relative file path
4094* (e.g. /includes/functions.php)
4095*/
4096function phpbb_filter_root_path($errfile)
4097{
4098 static $root_path;
4099
4100 if (empty($root_path))
4101 {
4102 $root_path = phpbb_realpath(dirname(__FILE__) . '/../');
4103 }
4104
4105 return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
4106}
4107
4108/**
4109* Queries the session table to get information about online guests
4110* @param int $item_id Limits the search to the item with this id
4111* @param string $item The name of the item which is stored in the session table as session_{$item}_id
4112* @return int The number of active distinct guest sessions
4113*/
4114function obtain_guest_count($item_id = 0, $item = 'forum')
4115{
4116 global $db, $config;
4117
4118 if ($item_id)
4119 {
4120 $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4121 }
4122 else
4123 {
4124 $reading_sql = '';
4125 }
4126 $time = (time() - (intval($config['load_online_time']) * 60));
4127
4128 // Get number of online guests
4129
4130 if ($db->sql_layer === 'sqlite')
4131 {
4132 $sql = 'SELECT COUNT(session_ip) as num_guests
4133 FROM (
4134 SELECT DISTINCT s.session_ip
4135 FROM ' . SESSIONS_TABLE . ' s
4136 WHERE s.session_user_id = ' . ANONYMOUS . '
4137 AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4138 $reading_sql .
4139 ')';
4140 }
4141 else
4142 {
4143 $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
4144 FROM ' . SESSIONS_TABLE . ' s
4145 WHERE s.session_user_id = ' . ANONYMOUS . '
4146 AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4147 $reading_sql;
4148 }
4149 $result = $db->sql_query($sql);
4150 $guests_online = (int) $db->sql_fetchfield('num_guests');
4151 $db->sql_freeresult($result);
4152
4153 return $guests_online;
4154}
4155
4156/**
4157* Queries the session table to get information about online users
4158* @param int $item_id Limits the search to the item with this id
4159* @param string $item The name of the item which is stored in the session table as session_{$item}_id
4160* @return array An array containing the ids of online, hidden and visible users, as well as statistical info
4161*/
4162function obtain_users_online($item_id = 0, $item = 'forum')
4163{
4164 global $db, $config, $user;
4165
4166 $reading_sql = '';
4167 if ($item_id !== 0)
4168 {
4169 $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4170 }
4171
4172 $online_users = array(
4173 'online_users' => array(),
4174 'hidden_users' => array(),
4175 'total_online' => 0,
4176 'visible_online' => 0,
4177 'hidden_online' => 0,
4178 'guests_online' => 0,
4179 );
4180
4181 if ($config['load_online_guests'])
4182 {
4183 $online_users['guests_online'] = obtain_guest_count($item_id, $item);
4184 }
4185
4186 // a little discrete magic to cache this for 30 seconds
4187 $time = (time() - (intval($config['load_online_time']) * 60));
4188
4189 $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
4190 FROM ' . SESSIONS_TABLE . ' s
4191 WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
4192 $reading_sql .
4193 ' AND s.session_user_id <> ' . ANONYMOUS;
4194 $result = $db->sql_query($sql);
4195
4196 while ($row = $db->sql_fetchrow($result))
4197 {
4198 // Skip multiple sessions for one user
4199 if (!isset($online_users['online_users'][$row['session_user_id']]))
4200 {
4201 $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4202 if ($row['session_viewonline'])
4203 {
4204 $online_users['visible_online']++;
4205 }
4206 else
4207 {
4208 $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4209 $online_users['hidden_online']++;
4210 }
4211 }
4212 }
4213 $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
4214 $db->sql_freeresult($result);
4215
4216 return $online_users;
4217}
4218
4219/**
4220* Uses the result of obtain_users_online to generate a localized, readable representation.
4221* @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
4222* @param int $item_id Indicate that the data is limited to one item and not global
4223* @param string $item The name of the item which is stored in the session table as session_{$item}_id
4224* @return array An array containing the string for output to the template
4225*/
4226function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
4227{
4228 global $config, $db, $user, $auth;
4229
4230 $user_online_link = $online_userlist = '';
4231 // Need caps version of $item for language-strings
4232 $item_caps = strtoupper($item);
4233
4234 if (sizeof($online_users['online_users']))
4235 {
4236 $sql = 'SELECT username, username_clean, user_id, user_type, user_allow_viewonline, user_colour
4237 FROM ' . USERS_TABLE . '
4238 WHERE ' . $db->sql_in_set('user_id', $online_users['online_users']) . '
4239 ORDER BY username_clean ASC';
4240 $result = $db->sql_query($sql);
4241
4242 while ($row = $db->sql_fetchrow($result))
4243 {
4244 // User is logged in and therefore not a guest
4245 if ($row['user_id'] != ANONYMOUS)
4246 {
4247 if (isset($online_users['hidden_users'][$row['user_id']]))
4248 {
4249 $row['username'] = '<em>' . $row['username'] . '</em>';
4250 }
4251
4252 if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline'))
4253 {
4254 $user_online_link = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
4255 $online_userlist .= ($online_userlist != '') ? ', ' . $user_online_link : $user_online_link;
4256 }
4257 }
4258 }
4259 $db->sql_freeresult($result);
4260 }
4261
4262 if (!$online_userlist)
4263 {
4264 $online_userlist = $user->lang['NO_ONLINE_USERS'];
4265 }
4266
4267 if ($item_id === 0)
4268 {
4269 $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
4270 }
4271 else if ($config['load_online_guests'])
4272 {
4273 $l_online = ($online_users['guests_online'] === 1) ? $user->lang['BROWSING_' . $item_caps . '_GUEST'] : $user->lang['BROWSING_' . $item_caps . '_GUESTS'];
4274 $online_userlist = sprintf($l_online, $online_userlist, $online_users['guests_online']);
4275 }
4276 else
4277 {
4278 $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
4279 }
4280 // Build online listing
4281 $vars_online = array(
4282 'ONLINE' => array('total_online', 'l_t_user_s', 0),
4283 'REG' => array('visible_online', 'l_r_user_s', !$config['load_online_guests']),
4284 'HIDDEN' => array('hidden_online', 'l_h_user_s', $config['load_online_guests']),
4285 'GUEST' => array('guests_online', 'l_g_user_s', 0)
4286 );
4287
4288 foreach ($vars_online as $l_prefix => $var_ary)
4289 {
4290 if ($var_ary[2])
4291 {
4292 $l_suffix = '_AND';
4293 }
4294 else
4295 {
4296 $l_suffix = '';
4297 }
4298 switch ($online_users[$var_ary[0]])
4299 {
4300 case 0:
4301 ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL' . $l_suffix];
4302 break;
4303
4304 case 1:
4305 ${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL' . $l_suffix];
4306 break;
4307
4308 default:
4309 ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL' . $l_suffix];
4310 break;
4311 }
4312 }
4313 unset($vars_online);
4314
4315 $l_online_users = sprintf($l_t_user_s, $online_users['total_online']);
4316 $l_online_users .= sprintf($l_r_user_s, $online_users['visible_online']);
4317 $l_online_users .= sprintf($l_h_user_s, $online_users['hidden_online']);
4318
4319 if ($config['load_online_guests'])
4320 {
4321 $l_online_users .= sprintf($l_g_user_s, $online_users['guests_online']);
4322 }
4323
4324
4325
4326 return array(
4327 'online_userlist' => $online_userlist,
4328 'l_online_users' => $l_online_users,
4329 );
4330}
4331
4332/**
4333* Get option bitfield from custom data
4334*
4335* @param int $bit The bit/value to get
4336* @param int $data Current bitfield to check
4337* @return bool Returns true if value of constant is set in bitfield, else false
4338*/
4339function phpbb_optionget($bit, $data)
4340{
4341 return ($data & 1 << (int) $bit) ? true : false;
4342}
4343
4344/**
4345* Set option bitfield
4346*
4347* @param int $bit The bit/value to set/unset
4348* @param bool $set True if option should be set, false if option should be unset.
4349* @param int $data Current bitfield to change
4350*
4351* @return int The new bitfield
4352*/
4353function phpbb_optionset($bit, $set, $data)
4354{
4355 if ($set && !($data & 1 << $bit))
4356 {
4357 $data += 1 << $bit;
4358 }
4359 else if (!$set && ($data & 1 << $bit))
4360 {
4361 $data -= 1 << $bit;
4362 }
4363
4364 return $data;
4365}
4366
4367/**
4368* Login using http authenticate.
4369*
4370* @param array $param Parameter array, see $param_defaults array.
4371*
4372* @return null
4373*/
4374function phpbb_http_login($param)
4375{
4376 global $auth, $user;
4377 global $config;
4378
4379 $param_defaults = array(
4380 'auth_message' => '',
4381
4382 'autologin' => false,
4383 'viewonline' => true,
4384 'admin' => false,
4385 );
4386
4387 // Overwrite default values with passed values
4388 $param = array_merge($param_defaults, $param);
4389
4390 // User is already logged in
4391 // We will not overwrite his session
4392 if (!empty($user->data['is_registered']))
4393 {
4394 return;
4395 }
4396
4397 // $_SERVER keys to check
4398 $username_keys = array(
4399 'PHP_AUTH_USER',
4400 'Authorization',
4401 'REMOTE_USER', 'REDIRECT_REMOTE_USER',
4402 'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION',
4403 'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION',
4404 'AUTH_USER',
4405 );
4406
4407 $password_keys = array(
4408 'PHP_AUTH_PW',
4409 'REMOTE_PASSWORD',
4410 'AUTH_PASSWORD',
4411 );
4412
4413 $username = null;
4414 foreach ($username_keys as $k)
4415 {
4416 if (isset($_SERVER[$k]))
4417 {
4418 $username = $_SERVER[$k];
4419 break;
4420 }
4421 }
4422
4423 $password = null;
4424 foreach ($password_keys as $k)
4425 {
4426 if (isset($_SERVER[$k]))
4427 {
4428 $password = $_SERVER[$k];
4429 break;
4430 }
4431 }
4432
4433 // Decode encoded information (IIS, CGI, FastCGI etc.)
4434 if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0)
4435 {
4436 list($username, $password) = explode(':', base64_decode(substr($username, 6)), 2);
4437 }
4438
4439 if (!is_null($username) && !is_null($password))
4440 {
4441 set_var($username, $username, 'string', true);
4442 set_var($password, $password, 'string', true);
4443
4444 $auth_result = $auth->login($username, $password, $param['autologin'], $param['viewonline'], $param['admin']);
4445
4446 if ($auth_result['status'] == LOGIN_SUCCESS)
4447 {
4448 return;
4449 }
4450 else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS)
4451 {
4452 send_status_line(401, 'Unauthorized');
4453
4454 trigger_error('NOT_AUTHORISED');
4455 }
4456 }
4457
4458 // Prepend sitename to auth_message
4459 $param['auth_message'] = ($param['auth_message'] === '') ? $config['sitename'] : $config['sitename'] . ' - ' . $param['auth_message'];
4460
4461 // We should probably filter out non-ASCII characters - RFC2616
4462 $param['auth_message'] = preg_replace('/[\x80-\xFF]/', '?', $param['auth_message']);
4463
4464 header('WWW-Authenticate: Basic realm="' . $param['auth_message'] . '"');
4465 send_status_line(401, 'Unauthorized');
4466
4467 trigger_error('NOT_AUTHORISED');
4468}
4469
4470/**
4471* Generate page header
4472*/
4473function page_header($page_title = '', $display_online_list = true, $item_id = 0, $item = 'forum')
4474{
4475 global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
4476
4477 if (defined('HEADER_INC'))
4478 {
4479 return;
4480 }
4481
4482 define('HEADER_INC', true);
4483
4484 // gzip_compression
4485 if ($config['gzip_compress'])
4486 {
4487 // to avoid partially compressed output resulting in blank pages in
4488 // the browser or error messages, compression is disabled in a few cases:
4489 //
4490 // 1) if headers have already been sent, this indicates plaintext output
4491 // has been started so further content must not be compressed
4492 // 2) the length of the current output buffer is non-zero. This means
4493 // there is already some uncompressed content in this output buffer
4494 // so further output must not be compressed
4495 // 3) if more than one level of output buffering is used because we
4496 // cannot test all output buffer level content lengths. One level
4497 // could be caused by php.ini output_buffering. Anything
4498 // beyond that is manual, so the code wrapping phpBB in output buffering
4499 // can easily compress the output itself.
4500 //
4501 if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
4502 {
4503 ob_start('ob_gzhandler');
4504 }
4505 }
4506
4507 // Generate logged in/logged out status
4508 if ($user->data['user_id'] != ANONYMOUS)
4509 {
4510 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
4511 $l_login_logout = sprintf($user->lang['LOGOUT_USER'], $user->data['username']);
4512 }
4513 else
4514 {
4515 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login');
4516 $l_login_logout = $user->lang['LOGIN'];
4517 }
4518
4519 // Last visit date/time
4520 $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
4521
4522 // Get users online list ... if required
4523 $l_online_users = $online_userlist = $l_online_record = $l_online_time = '';
4524
4525 if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
4526 {
4527 /**
4528 * Load online data:
4529 * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id.
4530 */
4531 $item_id = max($item_id, 0);
4532
4533 $online_users = obtain_users_online($item_id, $item);
4534 $user_online_strings = obtain_users_online_string($online_users, $item_id, $item);
4535
4536 $l_online_users = $user_online_strings['l_online_users'];
4537 $online_userlist = $user_online_strings['online_userlist'];
4538 $total_online_users = $online_users['total_online'];
4539
4540 if ($total_online_users > $config['record_online_users'])
4541 {
4542 set_config('record_online_users', $total_online_users, true);
4543 set_config('record_online_date', time(), true);
4544 }
4545
4546 $l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date'], false, true));
4547
4548 $l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES';
4549 $l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']);
4550 }
4551
4552 $l_privmsgs_text = $l_privmsgs_text_unread = '';
4553 $s_privmsg_new = false;
4554
4555 // Obtain number of new private messages if user is logged in
4556 if (!empty($user->data['is_registered']))
4557 {
4558 if ($user->data['user_new_privmsg'])
4559 {
4560 $l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS'];
4561 $l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']);
4562
4563 if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
4564 {
4565 $sql = 'UPDATE ' . USERS_TABLE . '
4566 SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
4567 WHERE user_id = ' . $user->data['user_id'];
4568 $db->sql_query($sql);
4569
4570 $s_privmsg_new = true;
4571 }
4572 else
4573 {
4574 $s_privmsg_new = false;
4575 }
4576 }
4577 else
4578 {
4579 $l_privmsgs_text = $user->lang['NO_NEW_PM'];
4580 $s_privmsg_new = false;
4581 }
4582
4583 $l_privmsgs_text_unread = '';
4584
4585 if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg'])
4586 {
4587 $l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS'];
4588 $l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']);
4589 }
4590 }
4591
4592 $forum_id = request_var('f', 0);
4593 $topic_id = request_var('t', 0);
4594
4595 $s_feed_news = false;
4596
4597 // Get option for news
4598 if ($config['feed_enable'])
4599 {
4600 $sql = 'SELECT forum_id
4601 FROM ' . FORUMS_TABLE . '
4602 WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
4603 $result = $db->sql_query_limit($sql, 1, 0, 600);
4604 $s_feed_news = (int) $db->sql_fetchfield('forum_id');
4605 $db->sql_freeresult($result);
4606 }
4607
4608 // Determine board url - we may need it later
4609 $board_url = generate_board_url() . '/';
4610 $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $phpbb_root_path;
4611
4612 // Which timezone?
4613 $tz = ($user->data['user_id'] != ANONYMOUS) ? strval(doubleval($user->data['user_timezone'])) : strval(doubleval($config['board_timezone']));
4614
4615 // Send a proper content-language to the output
4616 $user_lang = $user->lang['USER_LANG'];
4617 if (strpos($user_lang, '-x-') !== false)
4618 {
4619 $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
4620 }
4621
4622 $s_search_hidden_fields = array();
4623 if ($_SID)
4624 {
4625 $s_search_hidden_fields['sid'] = $_SID;
4626 }
4627
4628 if (!empty($_EXTRA_URL))
4629 {
4630 foreach ($_EXTRA_URL as $url_param)
4631 {
4632 $url_param = explode('=', $url_param, 2);
4633 $s_search_hidden_fields[$url_param[0]] = $url_param[1];
4634 }
4635 }
4636 $user->add_lang(array('mods/lang_portal', 'mods/additional_blocks'));
4637 if (!function_exists('obtain_portal_config'))
4638 {
4639 include($phpbb_root_path . 'portal/includes/functions.' . $phpEx);
4640 }
4641 if(sql_table_exists(PORTAL_CONFIG_TABLE) == true)
4642 {
4643 $portal_config = obtain_portal_config();
4644 }
4645 // The following assigns all _common_ variables that may be used at any point in a template.
4646 $template->assign_vars(array(
4647 'SITENAME' => $config['sitename'],
4648 'SITE_DESCRIPTION' => $config['site_desc'],
4649 'PAGE_TITLE' => $page_title,
4650 'SCRIPT_NAME' => str_replace('.' . $phpEx, '', $user->page['page_name']),
4651 'LAST_VISIT_DATE' => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
4652 'LAST_VISIT_YOU' => $s_last_visit,
4653 'CURRENT_TIME' => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
4654 'TOTAL_USERS_ONLINE' => $l_online_users,
4655 'LOGGED_IN_USER_LIST' => $online_userlist,
4656 'RECORD_USERS' => $l_online_record,
4657 'PRIVATE_MESSAGE_INFO' => $l_privmsgs_text,
4658 'PRIVATE_MESSAGE_INFO_UNREAD' => $l_privmsgs_text_unread,
4659
4660 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'],
4661 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'],
4662 'S_USER_NEW' => $user->data['user_new'],
4663
4664 'SID' => $SID,
4665 '_SID' => $_SID,
4666 'SESSION_ID' => $user->session_id,
4667 'ROOT_PATH' => $phpbb_root_path,
4668 'BOARD_URL' => $board_url,
4669
4670 'L_LOGIN_LOGOUT' => $l_login_logout,
4671 'L_INDEX' => $user->lang['FORUM_INDEX'],
4672 'L_ONLINE_EXPLAIN' => $l_online_time,
4673 'U_PORTAL' => (isset($portal_config['portal_enable']) && $portal_config['portal_enable'] == true) ? append_sid("{$phpbb_root_path}portal.$phpEx") : '',
4674 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'),
4675 'U_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'),
4676 'U_POPUP_PM' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=popup'),
4677 'UA_POPUP_PM' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=popup')),
4678 'U_MEMBERLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
4679 'U_VIEWONLINE' => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
4680 'U_LOGIN_LOGOUT' => $u_login_logout,
4681 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"),
4682 'U_SEARCH' => append_sid("{$phpbb_root_path}search.$phpEx"),
4683 'U_REGISTER' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
4684 'U_PROFILE' => append_sid("{$phpbb_root_path}ucp.$phpEx"),
4685 'U_MODCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
4686 'U_FAQ' => append_sid("{$phpbb_root_path}faq.$phpEx"),
4687 'U_SEARCH_SELF' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
4688 'U_SEARCH_NEW' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
4689 'U_SEARCH_UNANSWERED' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
4690 'U_SEARCH_UNREAD' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'),
4691 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
4692 'U_DELETE_COOKIES' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'),
4693 'U_TEAM' => ($user->data['user_id'] != ANONYMOUS && !$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=leaders'),
4694 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
4695 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
4696 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
4697 'U_FEED' => generate_board_url() . "/feed.$phpEx",
4698
4699 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false,
4700 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false,
4701 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false,
4702 'S_REGISTERED_USER' => (!empty($user->data['is_registered'])) ? true : false,
4703 'S_IS_BOT' => (!empty($user->data['is_bot'])) ? true : false,
4704 'S_USER_PM_POPUP' => $user->optionget('popuppm'),
4705 'S_USER_LANG' => $user_lang,
4706 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
4707 'S_USERNAME' => $user->data['username'],
4708 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'],
4709 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
4710 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
4711 'S_CONTENT_ENCODING' => 'UTF-8',
4712 '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], ''),
4713 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0,
4714 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
4715 'S_DISPLAY_PM' => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
4716 'S_DISPLAY_MEMBERLIST' => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
4717 'S_NEW_PM' => ($s_privmsg_new) ? 1 : 0,
4718 'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
4719 'S_FORUM_ID' => $forum_id,
4720 'S_TOPIC_ID' => $topic_id,
4721
4722 'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("index.$phpEx", false, true, $user->session_id)),
4723 'S_LOGIN_REDIRECT' => build_hidden_fields(array('redirect' => build_url())),
4724
4725 'S_ENABLE_FEEDS' => ($config['feed_enable']) ? true : false,
4726 'S_ENABLE_FEEDS_OVERALL' => ($config['feed_overall']) ? true : false,
4727 'S_ENABLE_FEEDS_FORUMS' => ($config['feed_overall_forums']) ? true : false,
4728 'S_ENABLE_FEEDS_TOPICS' => ($config['feed_topics_new']) ? true : false,
4729 'S_ENABLE_FEEDS_TOPICS_ACTIVE' => ($config['feed_topics_active']) ? true : false,
4730 'S_ENABLE_FEEDS_NEWS' => ($s_feed_news) ? true : false,
4731
4732 'S_LOAD_UNREADS' => ($config['load_unreads_search'] && ($config['load_anon_lastread'] || $user->data['is_registered'])) ? true : false,
4733
4734 'S_SEARCH_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields),
4735
4736 'T_THEME_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme',
4737 'T_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template',
4738 'T_SUPER_TEMPLATE_PATH' => (isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? "{$web_path}styles/" . rawurlencode($user->theme['template_inherit_path']) . '/template' : "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template',
4739 'T_IMAGESET_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset',
4740 'T_IMAGESET_LANG_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset/' . $user->lang_name,
4741 'T_IMAGES_PATH' => "{$web_path}images/",
4742 'T_SMILIES_PATH' => "{$web_path}{$config['smilies_path']}/",
4743 'T_AVATAR_PATH' => "{$web_path}{$config['avatar_path']}/",
4744 'T_AVATAR_GALLERY_PATH' => "{$web_path}{$config['avatar_gallery_path']}/",
4745 'T_ICONS_PATH' => "{$web_path}{$config['icons_path']}/",
4746 'T_RANKS_PATH' => "{$web_path}{$config['ranks_path']}/",
4747 'T_UPLOAD_PATH' => "{$web_path}{$config['upload_path']}/",
4748 'T_STYLESHEET_LINK' => (!$user->theme['theme_storedb']) ? "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme/stylesheet.css' : append_sid("{$phpbb_root_path}style.$phpEx", 'id=' . $user->theme['style_id'] . '&lang=' . $user->lang_name),
4749 'T_STYLESHEET_NAME' => $user->theme['theme_name'],
4750
4751 'T_THEME_NAME' => rawurlencode($user->theme['theme_path']),
4752 'T_TEMPLATE_NAME' => rawurlencode($user->theme['template_path']),
4753 'T_SUPER_TEMPLATE_NAME' => rawurlencode((isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? $user->theme['template_inherit_path'] : $user->theme['template_path']),
4754 'T_IMAGESET_NAME' => rawurlencode($user->theme['imageset_path']),
4755 'T_IMAGESET_LANG_NAME' => $user->data['user_lang'],
4756 'T_IMAGES' => 'images',
4757 'T_SMILIES' => $config['smilies_path'],
4758 'T_AVATAR' => $config['avatar_path'],
4759 'T_AVATAR_GALLERY' => $config['avatar_gallery_path'],
4760 'T_ICONS' => $config['icons_path'],
4761 'T_RANKS' => $config['ranks_path'],
4762 'T_UPLOAD' => $config['upload_path'],
4763
4764 'SITE_LOGO_IMG' => $user->img('site_logo'),
4765
4766 'A_COOKIE_SETTINGS' => addslashes('; path=' . $config['cookie_path'] . ((!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']) . ((!$config['cookie_secure']) ? '' : '; secure')),
4767 ));
4768
4769 // application/xhtml+xml not used because of IE
4770 header('Content-type: text/html; charset=UTF-8');
4771
4772 header('Cache-Control: private, no-cache="set-cookie"');
4773 header('Expires: 0');
4774 header('Pragma: no-cache');
4775
4776 if (!empty($user->data['is_bot']))
4777 {
4778 // Let reverse proxies know we detected a bot.
4779 header('X-PHPBB-IS-BOT: yes');
4780 }
4781
4782 return;
4783}
4784
4785/**
4786* Generate page footer
4787*/
4788function page_footer($run_cron = true)
4789{
4790 global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
4791
4792 // Output page creation time
4793 if (defined('DEBUG'))
4794 {
4795 $mtime = explode(' ', microtime());
4796 $totaltime = $mtime[0] + $mtime[1] - $starttime;
4797
4798 if (!empty($_REQUEST['explain']) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report'))
4799 {
4800 $db->sql_report('display');
4801 }
4802
4803 $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime);
4804
4805 if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))
4806 {
4807 if (function_exists('memory_get_usage'))
4808 {
4809 if ($memory_usage = memory_get_usage())
4810 {
4811 global $base_memory_usage;
4812 $memory_usage -= $base_memory_usage;
4813 $memory_usage = get_formatted_filesize($memory_usage);
4814
4815 $debug_output .= ' | Memory Usage: ' . $memory_usage;
4816 }
4817 }
4818
4819 $debug_output .= ' | <a href="' . build_url() . '&explain=1">Explain</a>';
4820 }
4821 }
4822
4823 $template->assign_vars(array(
4824 'DEBUG_OUTPUT' => (defined('DEBUG')) ? $debug_output : '',
4825 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
4826 'CREDIT_LINE' => $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'),
4827
4828 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '')
4829 );
4830
4831 // Call cron-type script
4832 $call_cron = false;
4833 if (!defined('IN_CRON') && $run_cron && !$config['board_disable'] && !$user->data['is_bot'])
4834 {
4835 $call_cron = true;
4836 $time_now = (!empty($user->time_now) && is_int($user->time_now)) ? $user->time_now : time();
4837
4838 // Any old lock present?
4839 if (!empty($config['cron_lock']))
4840 {
4841 $cron_time = explode(' ', $config['cron_lock']);
4842
4843 // If 1 hour lock is present we do not call cron.php
4844 if ($cron_time[0] + 3600 >= $time_now)
4845 {
4846 $call_cron = false;
4847 }
4848 }
4849 }
4850
4851 // Call cron job?
4852 if ($call_cron)
4853 {
4854 $cron_type = '';
4855
4856 if ($time_now - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists($phpbb_root_path . 'cache/queue.' . $phpEx))
4857 {
4858 // Process email queue
4859 $cron_type = 'queue';
4860 }
4861 else if (method_exists($cache, 'tidy') && $time_now - $config['cache_gc'] > $config['cache_last_gc'])
4862 {
4863 // Tidy the cache
4864 $cron_type = 'tidy_cache';
4865 }
4866 else if ($config['warnings_expire_days'] && ($time_now - $config['warnings_gc'] > $config['warnings_last_gc']))
4867 {
4868 $cron_type = 'tidy_warnings';
4869 }
4870 else if ($time_now - $config['database_gc'] > $config['database_last_gc'])
4871 {
4872 // Tidy the database
4873 $cron_type = 'tidy_database';
4874 }
4875 else if ($time_now - $config['search_gc'] > $config['search_last_gc'])
4876 {
4877 // Tidy the search
4878 $cron_type = 'tidy_search';
4879 }
4880 else if ($time_now - $config['session_gc'] > $config['session_last_gc'])
4881 {
4882 $cron_type = 'tidy_sessions';
4883 }
4884
4885 if ($cron_type)
4886 {
4887 $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" />');
4888 }
4889 }
4890
4891 $template->display('body');
4892
4893 garbage_collection();
4894 exit_handler();
4895}
4896
4897/**
4898* Closing the cache object and the database
4899* Cool function name, eh? We might want to add operations to it later
4900*/
4901function garbage_collection()
4902{
4903 global $cache, $db;
4904
4905 // Unload cache, must be done before the DB connection if closed
4906 if (!empty($cache))
4907 {
4908 $cache->unload();
4909 }
4910
4911 // Close our DB connection.
4912 if (!empty($db))
4913 {
4914 $db->sql_close();
4915 }
4916}
4917
4918/**
4919* Handler for exit calls in phpBB.
4920* This function supports hooks.
4921*
4922* Note: This function is called after the template has been outputted.
4923*/
4924function exit_handler()
4925{
4926 global $phpbb_hook, $config;
4927
4928 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4929 {
4930 if ($phpbb_hook->hook_return(__FUNCTION__))
4931 {
4932 return $phpbb_hook->hook_return_result(__FUNCTION__);
4933 }
4934 }
4935
4936 // As a pre-caution... some setups display a blank page if the flush() is not there.
4937 (ob_get_level() > 0) ? @ob_flush() : @flush();
4938
4939 exit;
4940}
4941
4942/**
4943* Handler for init calls in phpBB. This function is called in user::setup();
4944* This function supports hooks.
4945*/
4946function phpbb_user_session_handler()
4947{
4948 global $phpbb_hook;
4949
4950 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4951 {
4952 if ($phpbb_hook->hook_return(__FUNCTION__))
4953 {
4954 return $phpbb_hook->hook_return_result(__FUNCTION__);
4955 }
4956 }
4957
4958 return;
4959}
4960
4961?>