merge? merge.
[phpbb.git] / phpBB / includes / functions.php
blob7eba95ed044fe1499f9f78767d5ef23eb2f11e35
1 <?php
2 /**
4 * @package phpBB3
5 * @version $Id$
6 * @copyright (c) 2005 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
9 */
11 /**
12 * @ignore
14 if (!defined('IN_PHPBB'))
16 exit;
19 // Common global functions
21 /**
22 * set_var
24 * Set variable, used by {@link request_var the request_var function}
26 * @access private
28 function set_var(&$result, $var, $type, $multibyte = false)
30 settype($var, $type);
31 $result = $var;
33 if ($type == 'string')
35 $result = trim(htmlspecialchars(str_replace(array("\r\n", "\r"), array("\n", "\n"), $result), ENT_COMPAT, 'UTF-8'));
37 if (!empty($result))
39 // Make sure multibyte characters are wellformed
40 if ($multibyte)
42 if (!preg_match('/^./u', $result))
44 $result = '';
47 else
49 // no multibyte, allow only ASCII (0-127)
50 $result = preg_replace('/[\x80-\xFF]/', '?', $result);
54 $result = (STRIP) ? stripslashes($result) : $result;
58 /**
59 * Central type safe input handling function.
60 * All variables in GET or POST requests should be retrieved through this
61 * function to maximise security.
63 * @param string $var_name The name of the variable from the form that is
64 * to be retrieved.
65 * @param mixed $default A default value that is returned if the variable
66 * was not set. This function will always return a
67 * a value of the same type as the default.
68 * @param bool $multibyte If $default is a string this paramater has to be
69 * true if the variable may contain any UTF-8 characters
70 * Default is fault, causing all bytes outside the ASCII
71 * range (0-127) to be replaced with question marks
72 * @param bool $cookie True if the variable shall be retrieved from $_COOKIE
73 * instead of $_REQUEST. False by default.
74 * @return mixed The value of $_REQUEST[$var_name] run through
75 * {@link set_var set_var} to ensure that the type is the
76 * the same as that of $default. If the variable is not set
77 * $default is returned.
79 function request_var($var_name, $default, $multibyte = false, $cookie = false)
81 if (!$cookie && isset($_COOKIE[$var_name]))
83 if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
85 return (is_array($default)) ? array() : $default;
87 $_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
90 if (!isset($_REQUEST[$var_name]) || (is_array($_REQUEST[$var_name]) && !is_array($default)) || (is_array($default) && !is_array($_REQUEST[$var_name])))
92 return (is_array($default)) ? array() : $default;
95 $var = $_REQUEST[$var_name];
96 if (!is_array($default))
98 $type = gettype($default);
100 else
102 list($key_type, $type) = each($default);
103 $type = gettype($type);
104 $key_type = gettype($key_type);
105 if ($type == 'array')
107 reset($default);
108 $default = current($default);
109 list($sub_key_type, $sub_type) = each($default);
110 $sub_type = gettype($sub_type);
111 $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
112 $sub_key_type = gettype($sub_key_type);
116 if (is_array($var))
118 $_var = $var;
119 $var = array();
121 foreach ($_var as $k => $v)
123 set_var($k, $k, $key_type);
124 if ($type == 'array' && is_array($v))
126 foreach ($v as $_k => $_v)
128 if (is_array($_v))
130 $_v = null;
132 set_var($_k, $_k, $sub_key_type);
133 set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
136 else
138 if ($type == 'array' || is_array($v))
140 $v = null;
142 set_var($var[$k], $v, $type, $multibyte);
146 else
148 set_var($var, $var, $type, $multibyte);
151 return $var;
155 * Checks whether a certain variable was sent via POST.
156 * To make sure that a request was sent using POST you should call this function
157 * on at least one variable. The function will perform referrer validation
158 * as an additional measure against CSRF.
160 * @param string $name The name of the form variable which should have a
161 * _p suffix to indicate the check in the code that
162 * creates the form too.
163 * @return bool True if the variable was set in a POST request,
164 * false otherwise.
166 function isset_post($name)
169 * @todo validate referrer
171 return isset($_POST[$name]);
175 * Set config value. Creates missing config entry.
177 function set_config($config_name, $config_value, $is_dynamic = false)
179 global $db, $cache, $config;
181 $sql = 'UPDATE ' . CONFIG_TABLE . "
182 SET config_value = '" . $db->sql_escape($config_value) . "'
183 WHERE config_name = '" . $db->sql_escape($config_name) . "'";
184 $db->sql_query($sql);
186 if (!$db->sql_affectedrows() && !isset($config[$config_name]))
188 $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
189 'config_name' => $config_name,
190 'config_value' => $config_value,
191 'is_dynamic' => ($is_dynamic) ? 1 : 0));
192 $db->sql_query($sql);
195 $config[$config_name] = $config_value;
197 if (!$is_dynamic)
199 $cache->destroy('config');
204 * Generates an alphanumeric random string of given length
206 function gen_rand_string($num_chars = 8)
208 $rand_str = unique_id();
209 $rand_str = str_replace('0', 'Z', strtoupper(base_convert($rand_str, 16, 35)));
211 return substr($rand_str, 0, $num_chars);
215 * Return unique id
216 * @param string $extra additional entropy
218 function unique_id($extra = 'c')
220 static $dss_seeded = false;
221 global $config;
223 $val = $config['rand_seed'] . microtime();
224 $val = md5($val);
225 $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
227 if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
229 set_config('rand_seed', $config['rand_seed'], true);
230 set_config('rand_seed_last_update', time(), true);
231 $dss_seeded = true;
234 return substr($val, 4, 16);
238 * Return formatted string for filesizes
240 function get_formatted_filesize($bytes, $add_size_lang = true)
242 global $user;
244 if ($bytes >= pow(2, 20))
246 return ($add_size_lang) ? round($bytes / 1024 / 1024, 2) . ' ' . $user->lang['MIB'] : round($bytes / 1024 / 1024, 2);
249 if ($bytes >= pow(2, 10))
251 return ($add_size_lang) ? round($bytes / 1024, 2) . ' ' . $user->lang['KIB'] : round($bytes / 1024, 2);
254 return ($add_size_lang) ? ($bytes) . ' ' . $user->lang['BYTES'] : ($bytes);
258 * Determine whether we are approaching the maximum execution time. Should be called once
259 * at the beginning of the script in which it's used.
260 * @return bool Either true if the maximum execution time is nearly reached, or false
261 * if some time is still left.
263 function still_on_time($extra_time = 15)
265 static $max_execution_time, $start_time;
267 $time = explode(' ', microtime());
268 $current_time = $time[0] + $time[1];
270 if (empty($max_execution_time))
272 $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
274 // If zero, then set to something higher to not let the user catch the ten seconds barrier.
275 if ($max_execution_time === 0)
277 $max_execution_time = 50 + $extra_time;
280 $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
282 // For debugging purposes
283 // $max_execution_time = 10;
285 global $starttime;
286 $start_time = (empty($starttime)) ? $current_time : $starttime;
289 return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
294 * @version Version 0.1 / $Id$
296 * Portable PHP password hashing framework.
298 * Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
299 * the public domain.
301 * There's absolutely no warranty.
303 * The homepage URL for this framework is:
305 * http://www.openwall.com/phpass/
307 * Please be sure to update the Version line if you edit this file in any way.
308 * It is suggested that you leave the main version number intact, but indicate
309 * your project name (after the slash) and add your own revision information.
311 * Please do not change the "private" password hashing method implemented in
312 * here, thereby making your hashes incompatible. However, if you must, please
313 * change the hash type identifier (the "$P$") to something different.
315 * Obviously, since this code is in the public domain, the above are not
316 * requirements (there can be none), but merely suggestions.
319 * Hash the password
321 function phpbb_hash($password)
323 $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
325 $random_state = unique_id();
326 $random = '';
327 $count = 6;
329 if (($fh = @fopen('/dev/urandom', 'rb')))
331 $random = fread($fh, $count);
332 fclose($fh);
335 if (strlen($random) < $count)
337 $random = '';
339 for ($i = 0; $i < $count; $i += 16)
341 $random_state = md5(unique_id() . $random_state);
342 $random .= pack('H*', md5($random_state));
344 $random = substr($random, 0, $count);
347 $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);
349 if (strlen($hash) == 34)
351 return $hash;
354 return md5($password);
358 * Check for correct password
360 function phpbb_check_hash($password, $hash)
362 $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
363 if (strlen($hash) == 34)
365 return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
368 return (md5($password) === $hash) ? true : false;
372 * Generate salt for hash generation
374 function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
376 if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
378 $iteration_count_log2 = 8;
381 $output = '$H$';
382 $output .= $itoa64[min($iteration_count_log2 + 5, 30)];
383 $output .= _hash_encode64($input, 6, $itoa64);
385 return $output;
389 * Encode hash
391 function _hash_encode64($input, $count, &$itoa64)
393 $output = '';
394 $i = 0;
398 $value = ord($input[$i++]);
399 $output .= $itoa64[$value & 0x3f];
401 if ($i < $count)
403 $value |= ord($input[$i]) << 8;
406 $output .= $itoa64[($value >> 6) & 0x3f];
408 if ($i++ >= $count)
410 break;
413 if ($i < $count)
415 $value |= ord($input[$i]) << 16;
418 $output .= $itoa64[($value >> 12) & 0x3f];
420 if ($i++ >= $count)
422 break;
425 $output .= $itoa64[($value >> 18) & 0x3f];
427 while ($i < $count);
429 return $output;
433 * The crypt function/replacement
435 function _hash_crypt_private($password, $setting, &$itoa64)
437 $output = '*';
439 // Check for correct hash
440 if (substr($setting, 0, 3) != '$H$')
442 return $output;
445 $count_log2 = strpos($itoa64, $setting[3]);
447 if ($count_log2 < 7 || $count_log2 > 30)
449 return $output;
452 $count = 1 << $count_log2;
453 $salt = substr($setting, 4, 8);
455 if (strlen($salt) != 8)
457 return $output;
461 * We're kind of forced to use MD5 here since it's the only
462 * cryptographic primitive available in all versions of PHP
463 * currently in use. To implement our own low-level crypto
464 * in PHP would result in much worse performance and
465 * consequently in lower iteration counts and hashes that are
466 * quicker to crack (by non-PHP code).
468 $hash = md5($salt . $password, true);
471 $hash = md5($hash . $password, true);
473 while (--$count);
475 $output = substr($setting, 0, 12);
476 $output .= _hash_encode64($hash, 16, $itoa64);
478 return $output;
482 * Checks if a path ($path) is absolute or relative
484 * @param string $path Path to check absoluteness of
485 * @return boolean
487 function is_absolute($path)
489 return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:/#i', $path))) ? true : false;
493 * @author Chris Smith <chris@project-minerva.org>
494 * @copyright 2006 Project Minerva Team
495 * @param string $path The path which we should attempt to resolve.
496 * @return mixed
498 function phpbb_own_realpath($path)
500 // Now to perform funky shizzle
502 // Switch to use UNIX slashes
503 $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
504 $path_prefix = '';
506 // Determine what sort of path we have
507 if (is_absolute($path))
509 $absolute = true;
511 if ($path[0] == '/')
513 // Absolute path, *NIX style
514 $path_prefix = '';
516 else
518 // Absolute path, Windows style
519 // Remove the drive letter and colon
520 $path_prefix = $path[0] . ':';
521 $path = substr($path, 2);
524 else
526 // Relative Path
527 // Prepend the current working directory
528 if (function_exists('getcwd'))
530 // This is the best method, hopefully it is enabled!
531 $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
532 $absolute = true;
533 if (preg_match('#^[a-z]:#i', $path))
535 $path_prefix = $path[0] . ':';
536 $path = substr($path, 2);
538 else
540 $path_prefix = '';
543 else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
545 // Warning: If chdir() has been used this will lie!
546 // Warning: This has some problems sometime (CLI can create them easily)
547 $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
548 $absolute = true;
549 $path_prefix = '';
551 else
553 // We have no way of getting the absolute path, just run on using relative ones.
554 $absolute = false;
555 $path_prefix = '.';
559 // Remove any repeated slashes
560 $path = preg_replace('#/{2,}#', '/', $path);
562 // Remove the slashes from the start and end of the path
563 $path = trim($path, '/');
565 // Break the string into little bits for us to nibble on
566 $bits = explode('/', $path);
568 // Remove any . in the path, renumber array for the loop below
569 $bits = array_values(array_diff($bits, array('.')));
571 // Lets get looping, run over and resolve any .. (up directory)
572 for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
574 // @todo Optimise
575 if ($bits[$i] == '..' )
577 if (isset($bits[$i - 1]))
579 if ($bits[$i - 1] != '..')
581 // We found a .. and we are able to traverse upwards, lets do it!
582 unset($bits[$i]);
583 unset($bits[$i - 1]);
584 $i -= 2;
585 $max -= 2;
586 $bits = array_values($bits);
589 else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
591 // We have an absolute path trying to descend above the root of the filesystem
592 // ... Error!
593 return false;
598 // Prepend the path prefix
599 array_unshift($bits, $path_prefix);
601 $resolved = '';
603 $max = sizeof($bits) - 1;
605 // Check if we are able to resolve symlinks, Windows cannot.
606 $symlink_resolve = (function_exists('readlink')) ? true : false;
608 foreach ($bits as $i => $bit)
610 if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
612 // Path Exists
613 if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
615 // Resolved a symlink.
616 $resolved = $link . (($i == $max) ? '' : '/');
617 continue;
620 else
622 // Something doesn't exist here!
623 // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
624 // return false;
626 $resolved .= $bit . (($i == $max) ? '' : '/');
629 // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
630 // because we must be inside that basedir, the question is where...
631 // @internal The slash in is_dir() gets around an open_basedir restriction
632 if (!@file_exists($resolved) || (!is_dir($resolved . '/') && !is_file($resolved)))
634 return false;
637 // Put the slashes back to the native operating systems slashes
638 $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
640 // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
641 if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
643 return substr($resolved, 0, -1);
646 return $resolved; // We got here, in the end!
649 if (!function_exists('realpath'))
652 * A wrapper for realpath
653 * @ignore
655 function phpbb_realpath($path)
657 return phpbb_own_realpath($path);
660 else
663 * A wrapper for realpath
665 function phpbb_realpath($path)
667 $realpath = realpath($path);
669 // Strangely there are provider not disabling realpath but returning strange values. :o
670 // We at least try to cope with them.
671 if ($realpath === $path || $realpath === false)
673 return phpbb_own_realpath($path);
676 // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
677 if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
679 $realpath = substr($realpath, 0, -1);
682 return $realpath;
686 // functions used for building option fields
689 * Pick a language, any language ...
691 function language_select($default = '')
693 global $db;
695 $sql = 'SELECT lang_iso, lang_local_name
696 FROM ' . LANG_TABLE . '
697 ORDER BY lang_english_name';
698 $result = $db->sql_query($sql);
700 $lang_options = '';
701 while ($row = $db->sql_fetchrow($result))
703 $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
704 $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
706 $db->sql_freeresult($result);
708 return $lang_options;
712 * Pick a template/theme combo,
714 function style_select($default = '', $all = false)
716 global $db;
718 $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
719 $sql = 'SELECT style_id, style_name
720 FROM ' . STYLES_TABLE . "
721 $sql_where
722 ORDER BY style_name";
723 $result = $db->sql_query($sql);
725 $style_options = '';
726 while ($row = $db->sql_fetchrow($result))
728 $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
729 $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
731 $db->sql_freeresult($result);
733 return $style_options;
737 * Pick a timezone
739 function tz_select($default = '', $truncate = false)
741 global $user;
743 $tz_select = '';
744 foreach ($user->lang['tz_zones'] as $offset => $zone)
746 if ($truncate)
748 $zone_trunc = truncate_string($zone, 50, 255, false, '...');
750 else
752 $zone_trunc = $zone;
755 if (is_numeric($offset))
757 $selected = ($offset == $default) ? ' selected="selected"' : '';
758 $tz_select .= '<option title="'.$zone.'" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
762 return $tz_select;
765 // Functions handling topic/post tracking/marking
768 * Marks a topic/forum as read
769 * Marks a topic as posted to
771 * @param int $user_id can only be used with $mode == 'post'
773 function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
775 global $db, $user, $config;
777 if ($mode == 'all')
779 if ($forum_id === false || !sizeof($forum_id))
781 if ($config['load_db_lastread'] && $user->data['is_registered'])
783 // Mark all forums read (index page)
784 $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
785 $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
786 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
788 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
790 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
791 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
793 unset($tracking_topics['tf']);
794 unset($tracking_topics['t']);
795 unset($tracking_topics['f']);
796 $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
798 $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
799 $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics);
801 unset($tracking_topics);
803 if ($user->data['is_registered'])
805 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
810 return;
812 else if ($mode == 'topics')
814 // Mark all topics in forums read
815 if (!is_array($forum_id))
817 $forum_id = array($forum_id);
820 // Add 0 to forums array to mark global announcements correctly
821 $forum_id[] = 0;
823 if ($config['load_db_lastread'] && $user->data['is_registered'])
825 $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
826 WHERE user_id = {$user->data['user_id']}
827 AND " . $db->sql_in_set('forum_id', $forum_id);
828 $db->sql_query($sql);
830 $sql = 'SELECT forum_id
831 FROM ' . FORUMS_TRACK_TABLE . "
832 WHERE user_id = {$user->data['user_id']}
833 AND " . $db->sql_in_set('forum_id', $forum_id);
834 $result = $db->sql_query($sql);
836 $sql_update = array();
837 while ($row = $db->sql_fetchrow($result))
839 $sql_update[] = $row['forum_id'];
841 $db->sql_freeresult($result);
843 if (sizeof($sql_update))
845 $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
846 SET mark_time = ' . time() . "
847 WHERE user_id = {$user->data['user_id']}
848 AND " . $db->sql_in_set('forum_id', $sql_update);
849 $db->sql_query($sql);
852 if ($sql_insert = array_diff($forum_id, $sql_update))
854 $sql_ary = array();
855 foreach ($sql_insert as $f_id)
857 $sql_ary[] = array(
858 'user_id' => (int) $user->data['user_id'],
859 'forum_id' => (int) $f_id,
860 'mark_time' => time()
864 $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
867 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
869 $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
870 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
872 foreach ($forum_id as $f_id)
874 $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
876 if (isset($tracking['tf'][$f_id]))
878 unset($tracking['tf'][$f_id]);
881 foreach ($topic_ids36 as $topic_id36)
883 unset($tracking['t'][$topic_id36]);
886 if (isset($tracking['f'][$f_id]))
888 unset($tracking['f'][$f_id]);
891 $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
894 if (isset($tracking['tf']) && empty($tracking['tf']))
896 unset($tracking['tf']);
899 $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
900 $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
902 unset($tracking);
905 return;
907 else if ($mode == 'topic')
909 if ($topic_id === false || $forum_id === false)
911 return;
914 if ($config['load_db_lastread'] && $user->data['is_registered'])
916 $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
917 SET mark_time = ' . (($post_time) ? $post_time : time()) . "
918 WHERE user_id = {$user->data['user_id']}
919 AND topic_id = $topic_id";
920 $db->sql_query($sql);
922 // insert row
923 if (!$db->sql_affectedrows())
925 $db->sql_return_on_error(true);
927 $sql_ary = array(
928 'user_id' => (int) $user->data['user_id'],
929 'topic_id' => (int) $topic_id,
930 'forum_id' => (int) $forum_id,
931 'mark_time' => ($post_time) ? (int) $post_time : time(),
934 $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
936 $db->sql_return_on_error(false);
939 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
941 $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
942 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
944 $topic_id36 = base_convert($topic_id, 10, 36);
946 if (!isset($tracking['t'][$topic_id36]))
948 $tracking['tf'][$forum_id][$topic_id36] = true;
951 $post_time = ($post_time) ? $post_time : time();
952 $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
954 // If the cookie grows larger than 10000 characters we will remove the smallest value
955 // This can result in old topics being unread - but most of the time it should be accurate...
956 if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
958 //echo 'Cookie grown too large' . print_r($tracking, true);
960 // We get the ten most minimum stored time offsets and its associated topic ids
961 $time_keys = array();
962 for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
964 $min_value = min($tracking['t']);
965 $m_tkey = array_search($min_value, $tracking['t']);
966 unset($tracking['t'][$m_tkey]);
968 $time_keys[$m_tkey] = $min_value;
971 // Now remove the topic ids from the array...
972 foreach ($tracking['tf'] as $f_id => $topic_id_ary)
974 foreach ($time_keys as $m_tkey => $min_value)
976 if (isset($topic_id_ary[$m_tkey]))
978 $tracking['f'][$f_id] = $min_value;
979 unset($tracking['tf'][$f_id][$m_tkey]);
984 if ($user->data['is_registered'])
986 $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
987 $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
989 else
991 $tracking['l'] = max($time_keys);
995 $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
996 $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
999 return;
1001 else if ($mode == 'post')
1003 if ($topic_id === false)
1005 return;
1008 $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
1010 if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
1012 $db->sql_return_on_error(true);
1014 $sql_ary = array(
1015 'user_id' => (int) $use_user_id,
1016 'topic_id' => (int) $topic_id,
1017 'topic_posted' => 1
1020 $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1022 $db->sql_return_on_error(false);
1025 return;
1030 * Get topic tracking info by using already fetched info
1032 function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
1034 global $config, $user;
1036 $last_read = array();
1038 if (!is_array($topic_ids))
1040 $topic_ids = array($topic_ids);
1043 foreach ($topic_ids as $topic_id)
1045 if (!empty($rowset[$topic_id]['mark_time']))
1047 $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
1051 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1053 if (sizeof($topic_ids))
1055 $mark_time = array();
1057 // Get global announcement info
1058 if ($global_announce_list && sizeof($global_announce_list))
1060 if (!isset($forum_mark_time[0]))
1062 global $db;
1064 $sql = 'SELECT mark_time
1065 FROM ' . FORUMS_TRACK_TABLE . "
1066 WHERE user_id = {$user->data['user_id']}
1067 AND forum_id = 0";
1068 $result = $db->sql_query($sql);
1069 $row = $db->sql_fetchrow($result);
1070 $db->sql_freeresult($result);
1072 if ($row)
1074 $mark_time[0] = $row['mark_time'];
1077 else
1079 if ($forum_mark_time[0] !== false)
1081 $mark_time[0] = $forum_mark_time[0];
1086 if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
1088 $mark_time[$forum_id] = $forum_mark_time[$forum_id];
1091 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1093 foreach ($topic_ids as $topic_id)
1095 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1097 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1099 else
1101 $last_read[$topic_id] = $user_lastmark;
1106 return $last_read;
1110 * Get topic tracking info from db (for cookie based tracking only this function is used)
1112 function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
1114 global $config, $user;
1116 $last_read = array();
1118 if (!is_array($topic_ids))
1120 $topic_ids = array($topic_ids);
1123 if ($config['load_db_lastread'] && $user->data['is_registered'])
1125 global $db;
1127 $sql = 'SELECT topic_id, mark_time
1128 FROM ' . TOPICS_TRACK_TABLE . "
1129 WHERE user_id = {$user->data['user_id']}
1130 AND " . $db->sql_in_set('topic_id', $topic_ids);
1131 $result = $db->sql_query($sql);
1133 while ($row = $db->sql_fetchrow($result))
1135 $last_read[$row['topic_id']] = $row['mark_time'];
1137 $db->sql_freeresult($result);
1139 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1141 if (sizeof($topic_ids))
1143 $sql = 'SELECT forum_id, mark_time
1144 FROM ' . FORUMS_TRACK_TABLE . "
1145 WHERE user_id = {$user->data['user_id']}
1146 AND forum_id " .
1147 (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
1148 $result = $db->sql_query($sql);
1150 $mark_time = array();
1151 while ($row = $db->sql_fetchrow($result))
1153 $mark_time[$row['forum_id']] = $row['mark_time'];
1155 $db->sql_freeresult($result);
1157 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1159 foreach ($topic_ids as $topic_id)
1161 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1163 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1165 else
1167 $last_read[$topic_id] = $user_lastmark;
1172 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1174 global $tracking_topics;
1176 if (!isset($tracking_topics) || !sizeof($tracking_topics))
1178 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1179 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1182 if (!$user->data['is_registered'])
1184 $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1186 else
1188 $user_lastmark = $user->data['user_lastmark'];
1191 foreach ($topic_ids as $topic_id)
1193 $topic_id36 = base_convert($topic_id, 10, 36);
1195 if (isset($tracking_topics['t'][$topic_id36]))
1197 $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1201 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1203 if (sizeof($topic_ids))
1205 $mark_time = array();
1206 if ($global_announce_list && sizeof($global_announce_list))
1208 if (isset($tracking_topics['f'][0]))
1210 $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
1214 if (isset($tracking_topics['f'][$forum_id]))
1216 $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1219 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1221 foreach ($topic_ids as $topic_id)
1223 if ($global_announce_list && isset($global_announce_list[$topic_id]))
1225 $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1227 else
1229 $last_read[$topic_id] = $user_lastmark;
1235 return $last_read;
1239 * Check for read forums and update topic tracking info accordingly
1241 * @param int $forum_id the forum id to check
1242 * @param int $forum_last_post_time the forums last post time
1243 * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1244 * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1246 * @return true if complete forum got marked read, else false.
1248 function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1250 global $db, $tracking_topics, $user, $config;
1252 // Determine the users last forum mark time if not given.
1253 if ($mark_time_forum === false)
1255 if ($config['load_db_lastread'] && $user->data['is_registered'])
1257 $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1259 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1261 $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1262 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1264 if (!$user->data['is_registered'])
1266 $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1269 $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'];
1273 // Check the forum for any left unread topics.
1274 // If there are none, we mark the forum as read.
1275 if ($config['load_db_lastread'] && $user->data['is_registered'])
1277 if ($mark_time_forum >= $forum_last_post_time)
1279 // We do not need to mark read, this happened before. Therefore setting this to true
1280 $row = true;
1282 else
1284 $sql = 'SELECT t.forum_id FROM ' . TOPICS_TABLE . ' t
1285 LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id'] . ')
1286 WHERE t.forum_id = ' . $forum_id . '
1287 AND t.topic_last_post_time > ' . $mark_time_forum . '
1288 AND t.topic_moved_id = 0
1289 AND (tt.topic_id IS NULL OR tt.mark_time < t.topic_last_post_time)
1290 GROUP BY t.forum_id';
1291 $result = $db->sql_query_limit($sql, 1);
1292 $row = $db->sql_fetchrow($result);
1293 $db->sql_freeresult($result);
1296 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1298 // Get information from cookie
1299 $row = false;
1301 if (!isset($tracking_topics['tf'][$forum_id]))
1303 // We do not need to mark read, this happened before. Therefore setting this to true
1304 $row = true;
1306 else
1308 $sql = 'SELECT topic_id
1309 FROM ' . TOPICS_TABLE . '
1310 WHERE forum_id = ' . $forum_id . '
1311 AND topic_last_post_time > ' . $mark_time_forum . '
1312 AND topic_moved_id = 0';
1313 $result = $db->sql_query($sql);
1315 $check_forum = $tracking_topics['tf'][$forum_id];
1316 $unread = false;
1318 while ($row = $db->sql_fetchrow($result))
1320 if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
1322 $unread = true;
1323 break;
1326 $db->sql_freeresult($result);
1328 $row = $unread;
1331 else
1333 $row = true;
1336 if (!$row)
1338 markread('topics', $forum_id);
1339 return true;
1342 return false;
1346 * Transform an array into a serialized format
1348 function tracking_serialize($input)
1350 $out = '';
1351 foreach ($input as $key => $value)
1353 if (is_array($value))
1355 $out .= $key . ':(' . tracking_serialize($value) . ');';
1357 else
1359 $out .= $key . ':' . $value . ';';
1362 return $out;
1366 * Transform a serialized array into an actual array
1368 function tracking_unserialize($string, $max_depth = 3)
1370 $n = strlen($string);
1371 if ($n > 10010)
1373 die('Invalid data supplied');
1375 $data = $stack = array();
1376 $key = '';
1377 $mode = 0;
1378 $level = &$data;
1379 for ($i = 0; $i < $n; ++$i)
1381 switch ($mode)
1383 case 0:
1384 switch ($string[$i])
1386 case ':':
1387 $level[$key] = 0;
1388 $mode = 1;
1389 break;
1390 case ')':
1391 unset($level);
1392 $level = array_pop($stack);
1393 $mode = 3;
1394 break;
1395 default:
1396 $key .= $string[$i];
1398 break;
1400 case 1:
1401 switch ($string[$i])
1403 case '(':
1404 if (sizeof($stack) >= $max_depth)
1406 die('Invalid data supplied');
1408 $stack[] = &$level;
1409 $level[$key] = array();
1410 $level = &$level[$key];
1411 $key = '';
1412 $mode = 0;
1413 break;
1414 default:
1415 $level[$key] = $string[$i];
1416 $mode = 2;
1417 break;
1419 break;
1421 case 2:
1422 switch ($string[$i])
1424 case ')':
1425 unset($level);
1426 $level = array_pop($stack);
1427 $mode = 3;
1428 break;
1429 case ';':
1430 $key = '';
1431 $mode = 0;
1432 break;
1433 default:
1434 $level[$key] .= $string[$i];
1435 break;
1437 break;
1439 case 3:
1440 switch ($string[$i])
1442 case ')':
1443 unset($level);
1444 $level = array_pop($stack);
1445 break;
1446 case ';':
1447 $key = '';
1448 $mode = 0;
1449 break;
1450 default:
1451 die('Invalid data supplied');
1452 break;
1454 break;
1458 if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
1460 die('Invalid data supplied');
1463 return $level;
1466 // Pagination functions
1469 * Pagination routine, generates page number sequence
1470 * tpl_prefix is for using different pagination blocks at one page
1472 function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '')
1474 global $template, $user;
1476 // Make sure $per_page is a valid value
1477 $per_page = ($per_page <= 0) ? 1 : $per_page;
1479 $seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>';
1480 $total_pages = ceil($num_items / $per_page);
1482 if ($total_pages == 1 || !$num_items)
1484 return false;
1487 $on_page = floor($start_item / $per_page) + 1;
1488 $url_delim = (strpos($base_url, '?') === false) ? '?' : '&amp;';
1490 $page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>';
1492 if ($total_pages > 5)
1494 $start_cnt = min(max(1, $on_page - 4), $total_pages - 5);
1495 $end_cnt = max(min($total_pages, $on_page + 4), 6);
1497 $page_string .= ($start_cnt > 1) ? ' ... ' : $seperator;
1499 for ($i = $start_cnt + 1; $i < $end_cnt; $i++)
1501 $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
1502 if ($i < $end_cnt - 1)
1504 $page_string .= $seperator;
1508 $page_string .= ($end_cnt < $total_pages) ? ' ... ' : $seperator;
1510 else
1512 $page_string .= $seperator;
1514 for ($i = 2; $i < $total_pages; $i++)
1516 $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
1517 if ($i < $total_pages)
1519 $page_string .= $seperator;
1524 $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>';
1526 if ($add_prevnext_text)
1528 if ($on_page != 1)
1530 $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a>&nbsp;&nbsp;' . $page_string;
1533 if ($on_page != $total_pages)
1535 $page_string .= '&nbsp;&nbsp;<a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>';
1539 $template->assign_vars(array(
1540 $tpl_prefix . 'BASE_URL' => $base_url,
1541 'A_' . $tpl_prefix . 'BASE_URL' => addslashes($base_url),
1542 $tpl_prefix . 'PER_PAGE' => $per_page,
1544 $tpl_prefix . 'PREVIOUS_PAGE' => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page),
1545 $tpl_prefix . 'NEXT_PAGE' => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page),
1546 $tpl_prefix . 'TOTAL_PAGES' => $total_pages,
1549 return $page_string;
1553 * Return current page (pagination)
1555 function on_page($num_items, $per_page, $start)
1557 global $template, $user;
1559 // Make sure $per_page is a valid value
1560 $per_page = ($per_page <= 0) ? 1 : $per_page;
1562 $on_page = floor($start / $per_page) + 1;
1564 $template->assign_vars(array(
1565 'ON_PAGE' => $on_page)
1568 return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1));
1571 // Server functions (building urls, redirecting...)
1574 * Append session id to url.
1575 * This function supports hooks.
1577 * @param string $url The url the session id needs to be appended to (can have params)
1578 * @param mixed $params String or array of additional url parameters
1579 * @param bool $is_amp Is url using &amp; (true) or & (false)
1580 * @param string $session_id Possibility to use a custom session id instead of the global one
1582 * Examples:
1583 * <code>
1584 * append_sid(PHPBB_ROOT_PATH . 'viewtopic.' . PHP_EXT . '?t=1&amp;f=2');
1585 * append_sid(PHPBB_ROOT_PATH . 'viewtopic.' . PHP_EXT, 't=1&amp;f=2');
1586 * append_sid('viewtopic', 't=1&amp;f=2'); // short notation of the above example
1587 * append_sid('viewtopic', 't=1&f=2', false);
1588 * append_sid('viewtopic', array('t' => 1, 'f' => 2));
1589 * </code>
1592 function append_sid($url, $params = false, $is_amp = true, $session_id = false)
1594 global $_SID, $_EXTRA_URL, $phpbb_hook;
1595 static $parsed_urls = array();
1597 // Adjust internal url before calling the hook - we are able to just leave out any path and extension.
1598 // In this case the root path and extension are added before going through this function.
1599 if (isset($parsed_urls[$url]))
1601 $url = $parsed_urls[$url];
1603 else
1605 if (strpos($url, '.' . PHP_EXT) === false && $url[0] != '.' && $url[0] != '/')
1607 $parsed_urls[$url] = $url = PHPBB_ROOT_PATH . $url . '.' . PHP_EXT;
1611 if (empty($params))
1613 $params = false;
1616 // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropiatly.
1617 // They could mimick most of what is within this function
1618 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
1620 if ($phpbb_hook->hook_return(__FUNCTION__))
1622 return $phpbb_hook->hook_return_result(__FUNCTION__);
1626 // Assign sid if session id is not specified
1627 if ($session_id === false)
1629 $session_id = $_SID;
1632 $amp_delim = ($is_amp) ? '&amp;' : '&';
1633 $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
1635 // Appending custom url parameter?
1636 $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
1638 $anchor = '';
1639 if (strpos($url, '#') !== false)
1641 list($url, $anchor) = explode('#', $url, 2);
1642 $anchor = '#' . $anchor;
1644 else if (!is_array($params) && strpos($params, '#') !== false)
1646 list($params, $anchor) = explode('#', $params, 2);
1647 $anchor = '#' . $anchor;
1650 // Use the short variant if possible ;)
1651 if ($params === false)
1653 // Append session id
1654 if (!$session_id)
1656 return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
1658 else
1660 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
1664 // Build string if parameters are specified as array
1665 if (is_array($params))
1667 $output = array();
1669 foreach ($params as $key => $item)
1671 if ($item === NULL)
1673 continue;
1676 if ($key == '#')
1678 $anchor = '#' . $item;
1679 continue;
1682 $output[] = $key . '=' . $item;
1685 $params = implode($amp_delim, $output);
1688 // Append session id and parameters (even if they are empty)
1689 // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
1690 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
1694 * Generate board url (example: http://www.example.com/phpBB)
1695 * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
1697 function generate_board_url($without_script_path = false)
1699 global $config, $user;
1701 $server_name = $user->host;
1702 $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
1704 // Forcing server vars is the only way to specify/override the protocol
1705 if ($config['force_server_vars'] || !$server_name)
1707 $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
1708 $server_name = $config['server_name'];
1709 $server_port = (int) $config['server_port'];
1710 $script_path = $config['script_path'];
1712 $url = $server_protocol . $server_name;
1714 else
1716 // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
1717 $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
1718 $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
1720 $script_path = $user->page['root_script_path'];
1723 if ($server_port && (($config['cookie_secure'] && $server_port <> 443) || (!$config['cookie_secure'] && $server_port <> 80)))
1725 // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
1726 if (strpos($server_name, ':') === false)
1728 $url .= ':' . $server_port;
1732 if (!$without_script_path)
1734 $url .= $script_path;
1737 // Strip / from the end
1738 if (substr($url, -1, 1) == '/')
1740 $url = substr($url, 0, -1);
1743 return $url;
1747 * Redirects the user to another page then exits the script nicely
1748 * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
1750 function redirect($url, $return = false)
1752 global $db, $cache, $config, $user;
1754 if (empty($user->lang))
1756 $user->add_lang('common');
1759 if (!$return)
1761 garbage_collection();
1764 // Make sure no &amp;'s are in, this will break the redirect
1765 $url = str_replace('&amp;', '&', $url);
1767 // Determine which type of redirect we need to handle...
1768 $url_parts = parse_url($url);
1770 if ($url_parts === false)
1772 // Malformed url, redirect to current page...
1773 $url = generate_board_url() . '/' . $user->page['page'];
1775 else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
1777 // Attention: only able to redirect within the same domain (yourdomain.com -> www.yourdomain.com will not work)
1778 if ($url_parts['host'] !== $user->host)
1780 $url = generate_board_url();
1783 else if ($url[0] == '/')
1785 // Absolute uri, prepend direct url...
1786 $url = generate_board_url(true) . $url;
1788 else
1790 // Relative uri
1791 $pathinfo = pathinfo($url);
1793 // Is the uri pointing to the current directory?
1794 if ($pathinfo['dirname'] == '.')
1796 $url = str_replace('./', '', $url);
1798 // Strip / from the beginning
1799 if ($url && substr($url, 0, 1) == '/')
1801 $url = substr($url, 1);
1804 if ($user->page['page_dir'])
1806 $url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url;
1808 else
1810 $url = generate_board_url() . '/' . $url;
1813 else
1815 // Used ./ before, but PHPBB_ROOT_PATH is working better with urls within another root path
1816 $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath(PHPBB_ROOT_PATH)));
1817 $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname'])));
1818 $intersection = array_intersect_assoc($root_dirs, $page_dirs);
1820 $root_dirs = array_diff_assoc($root_dirs, $intersection);
1821 $page_dirs = array_diff_assoc($page_dirs, $intersection);
1823 $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
1825 // Strip / from the end
1826 if ($dir && substr($dir, -1, 1) == '/')
1828 $dir = substr($dir, 0, -1);
1831 // Strip / from the beginning
1832 if ($dir && substr($dir, 0, 1) == '/')
1834 $dir = substr($dir, 1);
1837 $url = str_replace($pathinfo['dirname'] . '/', '', $url);
1839 // Strip / from the beginning
1840 if (substr($url, 0, 1) == '/')
1842 $url = substr($url, 1);
1845 $url = $dir . '/' . $url;
1846 $url = generate_board_url() . '/' . $url;
1850 // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
1851 if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
1853 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
1856 // Now, also check the protocol and for a valid url the last time...
1857 $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
1858 $url_parts = parse_url($url);
1860 if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
1862 trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
1865 if ($return)
1867 return $url;
1870 // Redirect via an HTML form for PITA webservers
1871 if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
1873 header('Refresh: 0; URL=' . $url);
1875 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1876 echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">';
1877 echo '<head>';
1878 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
1879 echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
1880 echo '<title>' . $user->lang['REDIRECT'] . '</title>';
1881 echo '</head>';
1882 echo '<body>';
1883 echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
1884 echo '</body>';
1885 echo '</html>';
1887 exit;
1890 // Behave as per HTTP/1.1 spec for others
1891 header('Location: ' . $url);
1892 exit;
1896 * Re-Apply session id after page reloads
1898 function reapply_sid($url)
1900 if ($url === 'index.' . PHP_EXT)
1902 return append_sid('index.' . PHP_EXT);
1904 else if ($url === PHPBB_ROOT_PATH . 'index.' . PHP_EXT)
1906 return append_sid('index');
1909 // Remove previously added sid
1910 if (strpos($url, '?sid=') !== false)
1912 $url = preg_replace('/(\?)sid=[a-z0-9]+(&amp;|&)?/', '\1', $url);
1914 else if (strpos($url, '&sid=') !== false)
1916 $url = preg_replace('/&sid=[a-z0-9]+(&)?/', '\1', $url);
1918 else if (strpos($url, '&amp;sid=') !== false)
1920 $url = preg_replace('/&amp;sid=[a-z0-9]+(&amp;)?/', '\1', $url);
1923 return append_sid($url);
1927 * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
1929 function build_url($strip_vars = false)
1931 global $user;
1933 // Append SID
1934 $redirect = append_sid($user->page['page'], false, false);
1936 // Add delimiter if not there...
1937 if (strpos($redirect, '?') === false)
1939 $redirect .= '?';
1942 // Strip vars...
1943 if ($strip_vars !== false && strpos($redirect, '?') !== false)
1945 if (!is_array($strip_vars))
1947 $strip_vars = array($strip_vars);
1950 $query = $_query = array();
1952 $args = substr($redirect, strpos($redirect, '?') + 1);
1953 $args = ($args) ? explode('&', $args) : array();
1954 $redirect = substr($redirect, 0, strpos($redirect, '?'));
1956 foreach ($args as $argument)
1958 $arguments = explode('=', $argument);
1959 $key = $arguments[0];
1960 unset($arguments[0]);
1962 $query[$key] = implode('=', $arguments);
1965 // Strip the vars off
1966 foreach ($strip_vars as $strip)
1968 if (isset($query[$strip]))
1970 unset($query[$strip]);
1974 // Glue the remaining parts together... already urlencoded
1975 foreach ($query as $key => $value)
1977 $_query[] = $key . '=' . $value;
1979 $query = implode('&', $_query);
1981 $redirect .= ($query) ? '?' . $query : '';
1984 return PHPBB_ROOT_PATH . str_replace('&', '&amp;', $redirect);
1988 * Meta refresh assignment
1990 function meta_refresh($time, $url)
1992 global $template;
1994 $url = redirect($url, true);
1995 $url = str_replace('&', '&amp;', $url);
1997 // For XHTML compatibility we change back & to &amp;
1998 $template->assign_vars(array(
1999 'META' => '<meta http-equiv="refresh" content="' . $time . ';url=' . $url . '" />')
2002 return $url;
2005 //Form validation
2008 * Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2009 * @param string $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2011 function add_form_key($form_name)
2013 global $config, $template, $user;
2014 $now = time();
2015 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2016 $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2018 $s_fields = build_hidden_fields(array(
2019 'creation_time' => $now,
2020 'form_token' => $token,
2022 $template->assign_vars(array(
2023 'S_FORM_TOKEN' => $s_fields,
2028 * Check the form key. Required for all altering actions not secured by confirm_box
2029 * @param string $form_name The name of the form; has to match the name used in add_form_key, otherwise no restrictions apply
2030 * @param int $timespan The maximum acceptable age for a submitted form in seconds. Defaults to the config setting.
2031 * @param string $return_page The address for the return link
2032 * @param bool $trigger If true, the function will triger an error when encountering an invalid form
2034 function check_form_key($form_name, $timespan = false, $return_page = '', $trigger = false)
2036 global $config, $user;
2038 if ($timespan === false)
2040 // we enforce a minimum value of half a minute here.
2041 $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2044 if (isset($_POST['creation_time']) && isset($_POST['form_token']))
2046 $creation_time = abs(request_var('creation_time', 0));
2047 $token = request_var('form_token', '');
2049 $diff = (time() - $creation_time);
2051 if (($diff <= $timespan) || $timespan === -1)
2053 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2055 $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2056 if ($key === $token)
2058 return true;
2062 if ($trigger)
2064 trigger_error($user->lang['FORM_INVALID'] . $return_page);
2066 return false;
2069 // Message/Login boxes
2072 * Build Confirm box
2073 * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2074 * @param string $title Title/Message used for confirm box.
2075 * message text is _CONFIRM appended to title.
2076 * If title cannot be found in user->lang a default one is displayed
2077 * If title_CONFIRM cannot be found in user->lang the text given is used.
2078 * @param string $hidden Hidden variables
2079 * @param string $html_body Template used for confirm box
2080 * @param string $u_action Custom form action
2082 function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2084 global $user, $template, $db;
2086 if (isset($_POST['cancel']))
2088 return false;
2091 $confirm = false;
2092 if (isset($_POST['confirm']))
2094 // language frontier
2095 if ($_POST['confirm'] === $user->lang['YES'])
2097 $confirm = true;
2101 if ($check && $confirm)
2103 $user_id = request_var('user_id', 0);
2104 $session_id = request_var('sess', '');
2105 $confirm_key = request_var('confirm_key', '');
2107 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'])
2109 return false;
2112 // Reset user_last_confirm_key
2113 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2114 WHERE user_id = " . $user->data['user_id'];
2115 $db->sql_query($sql);
2117 return true;
2119 else if ($check)
2121 return false;
2124 $s_hidden_fields = build_hidden_fields(array(
2125 'user_id' => $user->data['user_id'],
2126 'sess' => $user->session_id,
2127 'sid' => $user->session_id)
2130 // generate activation key
2131 $confirm_key = gen_rand_string(10);
2133 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2135 adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2137 else
2139 page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2142 $template->set_filenames(array(
2143 'body' => $html_body)
2146 // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2147 if (request_var('confirm_key', ''))
2149 // This should not occur, therefore we cancel the operation to safe the user
2150 return false;
2153 // re-add sid / transform & to &amp; for user->page (user->page is always using &)
2154 $use_page = ($u_action) ? PHPBB_ROOT_PATH . $u_action : PHPBB_ROOT_PATH . str_replace('&', '&amp;', $user->page['page']);
2155 $u_action = reapply_sid($use_page);
2156 $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2158 $template->assign_vars(array(
2159 'MESSAGE_TITLE' => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2160 'MESSAGE_TEXT' => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2162 'YES_VALUE' => $user->lang['YES'],
2163 'S_CONFIRM_ACTION' => $u_action,
2164 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields)
2167 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2168 WHERE user_id = " . $user->data['user_id'];
2169 $db->sql_query($sql);
2171 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2173 adm_page_footer();
2175 else
2177 page_footer();
2182 * Generate login box or verify password
2184 function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2186 global $db, $user, $template, $auth, $config;
2189 $err = '';
2191 // Make sure user->setup() has been called
2192 if (empty($user->lang))
2194 $user->setup();
2197 if (defined('ADMIN_START'))
2199 // Set custom template for admin area
2200 $template->set_custom_template(PHPBB_ADMIN_PATH . 'style', 'admin');
2201 $template->assign_var('T_TEMPLATE_PATH', PHPBB_ADMIN_PATH . 'style');
2203 // the acp template is never stored in the database
2204 $user->theme['template_storedb'] = false;
2207 // Print out error if user tries to authenticate as an administrator without having the privileges...
2208 if ($admin && !$auth->acl_get('a_'))
2210 // Not authd
2211 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2212 if ($user->data['is_registered'])
2214 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2216 trigger_error('NO_AUTH_ADMIN');
2219 if (isset($_POST['login']))
2221 // Get credential
2222 if ($admin)
2224 $credential = request_var('credential', '');
2226 if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
2228 if ($user->data['is_registered'])
2230 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2232 trigger_error('NO_AUTH_ADMIN');
2235 $password = request_var('password_' . $credential, '', true);
2237 else
2239 $password = request_var('password', '', true);
2242 $username = request_var('username', '', true);
2243 $autologin = (!empty($_POST['autologin'])) ? true : false;
2244 $viewonline = (!empty($_POST['viewonline'])) ? 0 : 1;
2245 $admin = ($admin) ? 1 : 0;
2246 $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
2248 // Check if the supplied username is equal to the one stored within the database if re-authenticating
2249 if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
2251 // We log the attempt to use a different username...
2252 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2253 trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
2256 // If authentication is successful we redirect user to previous page
2257 $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
2259 // If admin authentication and login, we will log if it was a success or not...
2260 // We also break the operation on the first non-success login - it could be argued that the user already knows
2261 if ($admin)
2263 if ($result['status'] == LOGIN_SUCCESS)
2265 add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
2267 else
2269 // Only log the failed attempt if a real user tried to.
2270 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2271 if ($user->data['is_registered'])
2273 add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2278 // The result parameter is always an array, holding the relevant information...
2279 if ($result['status'] == LOGIN_SUCCESS)
2281 $redirect = request_var('redirect', PHPBB_ROOT_PATH . 'index.' . PHP_EXT);
2282 $message = ($l_success) ? $l_success : $user->lang['LOGIN_REDIRECT'];
2283 $l_redirect = ($admin) ? $user->lang['PROCEED_TO_ACP'] : (($redirect === PHPBB_ROOT_PATH . 'index.' . PHP_EXT || $redirect === "index." . PHP_EXT) ? $user->lang['RETURN_INDEX'] : $user->lang['RETURN_PAGE']);
2285 // append/replace SID (may change during the session for AOL users)
2286 $redirect = reapply_sid($redirect);
2288 // Special case... the user is effectively banned, but we allow founders to login
2289 if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
2291 return;
2294 $redirect = meta_refresh(3, $redirect);
2295 trigger_error($message . '<br /><br />' . sprintf($l_redirect, '<a href="' . $redirect . '">', '</a>'));
2298 // Something failed, determine what...
2299 if ($result['status'] == LOGIN_BREAK)
2301 trigger_error($result['error_msg']);
2304 // Special cases... determine
2305 switch ($result['status'])
2307 case LOGIN_ERROR_ATTEMPTS:
2309 // Show confirm image
2310 $sql = 'DELETE FROM ' . CONFIRM_TABLE . "
2311 WHERE session_id = '" . $db->sql_escape($user->session_id) . "'
2312 AND confirm_type = " . CONFIRM_LOGIN;
2313 $db->sql_query($sql);
2315 // Generate code
2316 $code = gen_rand_string(mt_rand(5, 8));
2317 $confirm_id = md5(unique_id($user->ip));
2318 $seed = hexdec(substr(unique_id(), 4, 10));
2320 // compute $seed % 0x7fffffff
2321 $seed -= 0x7fffffff * floor($seed / 0x7fffffff);
2323 $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array(
2324 'confirm_id' => (string) $confirm_id,
2325 'session_id' => (string) $user->session_id,
2326 'confirm_type' => (int) CONFIRM_LOGIN,
2327 'code' => (string) $code,
2328 'seed' => (int) $seed)
2330 $db->sql_query($sql);
2332 $template->assign_vars(array(
2333 'S_CONFIRM_CODE' => true,
2334 'CONFIRM_ID' => $confirm_id,
2335 'CONFIRM_IMAGE' => '<img src="' . append_sid('ucp', 'mode=confirm&amp;id=' . $confirm_id . '&amp;type=' . CONFIRM_LOGIN) . '" alt="" title="" />',
2336 'L_LOGIN_CONFIRM_EXPLAIN' => sprintf($user->lang['LOGIN_CONFIRM_EXPLAIN'], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>'),
2339 $err = $user->lang[$result['error_msg']];
2341 break;
2343 case LOGIN_ERROR_PASSWORD_CONVERT:
2344 $err = sprintf(
2345 $user->lang[$result['error_msg']],
2346 ($config['email_enable']) ? '<a href="' . append_sid('ucp', 'mode=sendpassword') . '">' : '',
2347 ($config['email_enable']) ? '</a>' : '',
2348 ($config['board_contact']) ? '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">' : '',
2349 ($config['board_contact']) ? '</a>' : ''
2351 break;
2353 // Username, password, etc...
2354 default:
2355 $err = $user->lang[$result['error_msg']];
2357 // Assign admin contact to some error messages
2358 if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
2360 $err = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>');
2363 break;
2367 if (!$redirect)
2369 // We just use what the session code determined...
2370 // If we are not within the admin directory we use the page dir...
2371 $redirect = '';
2373 if (!$admin && !defined('ADMIN_START'))
2375 $redirect .= ($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '';
2378 $redirect .= $user->page['page_name'] . (($user->page['query_string']) ? '?' . htmlspecialchars($user->page['query_string']) : '');
2381 // Assign credential for username/password pair
2382 $credential = ($admin) ? md5(unique_id()) : false;
2384 $s_hidden_fields = array(
2385 'redirect' => $redirect,
2386 'sid' => $user->session_id,
2389 if ($admin)
2391 $s_hidden_fields['credential'] = $credential;
2394 $s_hidden_fields = build_hidden_fields($s_hidden_fields);
2396 $template->assign_vars(array(
2397 'LOGIN_ERROR' => $err,
2398 'LOGIN_EXPLAIN' => $l_explain,
2400 'U_SEND_PASSWORD' => ($config['email_enable']) ? append_sid('ucp', 'mode=sendpassword') : '',
2401 'U_RESEND_ACTIVATION' => ($config['require_activation'] != USER_ACTIVATION_NONE && $config['email_enable']) ? append_sid('ucp', 'mode=resend_act') : '',
2402 'U_TERMS_USE' => append_sid('ucp', 'mode=terms'),
2403 'U_PRIVACY' => append_sid('ucp', 'mode=privacy'),
2405 'S_DISPLAY_FULL_LOGIN' => ($s_display) ? true : false,
2406 'S_LOGIN_ACTION' => (!$admin && !defined('ADMIN_START')) ? append_sid('ucp', 'mode=login') : append_sid(PHPBB_ADMIN_PATH . 'index.' . PHP_EXT, false, true, $user->session_id),
2407 'S_HIDDEN_FIELDS' => $s_hidden_fields,
2409 'S_ADMIN_AUTH' => $admin,
2410 'S_ACP_LOGIN' => defined('ADMIN_START'),
2411 'USERNAME' => ($admin) ? $user->data['username'] : '',
2413 'USERNAME_CREDENTIAL' => 'username',
2414 'PASSWORD_CREDENTIAL' => ($admin) ? 'password_' . $credential : 'password',
2417 if (defined('ADMIN_START'))
2419 $template->set_filenames(array(
2420 'body' => 'acp_login.html')
2422 $template->assign_block_vars('t_block1', array(
2423 'L_TITLE' => $user->lang['LOGIN'],
2424 'S_SELECTED' => true,
2425 'U_TITLE' => '',
2427 adm_page_header($user->lang['LOGIN'], false);
2429 else
2431 $template->set_filenames(array(
2432 'body' => 'login_body.html')
2434 page_header($user->lang['LOGIN'], false);
2437 make_jumpbox(append_sid('viewforum'));
2438 if (defined('ADMIN_START') && isset($user->data['session_admin']) && $user->data['session_admin'])
2440 adm_page_footer();
2442 else
2444 page_footer();
2450 * Generate forum login box
2452 function login_forum_box($forum_data)
2454 global $db, $config, $user, $template;
2456 $password = request_var('password', '', true);
2458 $sql = 'SELECT forum_id
2459 FROM ' . FORUMS_ACCESS_TABLE . '
2460 WHERE forum_id = ' . $forum_data['forum_id'] . '
2461 AND user_id = ' . $user->data['user_id'] . "
2462 AND session_id = '" . $db->sql_escape($user->session_id) . "'";
2463 $result = $db->sql_query($sql);
2464 $row = $db->sql_fetchrow($result);
2465 $db->sql_freeresult($result);
2467 if ($row)
2469 return true;
2472 if ($password)
2474 // Remove expired authorised sessions
2475 $sql = 'SELECT f.session_id
2476 FROM ' . FORUMS_ACCESS_TABLE . ' f
2477 LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
2478 WHERE s.session_id IS NULL';
2479 $result = $db->sql_query($sql);
2481 if ($row = $db->sql_fetchrow($result))
2483 $sql_in = array();
2486 $sql_in[] = (string) $row['session_id'];
2488 while ($row = $db->sql_fetchrow($result));
2490 // Remove expired sessions
2491 $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
2492 WHERE ' . $db->sql_in_set('session_id', $sql_in);
2493 $db->sql_query($sql);
2495 $db->sql_freeresult($result);
2497 if (phpbb_check_hash($password, $forum_data['forum_password']))
2499 $sql_ary = array(
2500 'forum_id' => (int) $forum_data['forum_id'],
2501 'user_id' => (int) $user->data['user_id'],
2502 'session_id' => (string) $user->session_id,
2505 $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
2507 return true;
2510 $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
2513 page_header($user->lang['LOGIN']);
2515 $template->assign_vars(array(
2516 'S_HIDDEN_FIELDS' => build_hidden_fields(array('f' => $forum_data['forum_id'])))
2519 $template->set_filenames(array(
2520 'body' => 'login_forum.html')
2523 page_footer();
2526 // Little helpers
2529 * Little helper for the build_hidden_fields function
2531 function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
2533 $hidden_fields = '';
2535 if (!is_array($value))
2537 $value = ($stripslashes) ? stripslashes($value) : $value;
2538 $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
2540 $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
2542 else
2544 foreach ($value as $_key => $_value)
2546 $_key = ($stripslashes) ? stripslashes($_key) : $_key;
2547 $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
2549 $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
2553 return $hidden_fields;
2557 * Build simple hidden fields from array
2559 * @param array $field_ary an array of values to build the hidden field from
2560 * @param bool $specialchar if true, keys and values get specialchared
2561 * @param bool $stripslashes if true, keys and values get stripslashed
2563 * @return string the hidden fields
2565 function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
2567 $s_hidden_fields = '';
2569 foreach ($field_ary as $name => $vars)
2571 $name = ($stripslashes) ? stripslashes($name) : $name;
2572 $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
2574 $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
2577 return $s_hidden_fields;
2581 * Parse cfg file
2583 function parse_cfg_file($filename, $lines = false)
2585 $parsed_items = array();
2587 if ($lines === false)
2589 $lines = file($filename);
2592 foreach ($lines as $line)
2594 $line = trim($line);
2596 if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
2598 continue;
2601 // Determine first occurrence, since in values the equal sign is allowed
2602 $key = strtolower(trim(substr($line, 0, $delim_pos)));
2603 $value = trim(substr($line, $delim_pos + 1));
2605 if (in_array($value, array('off', 'false', '0')))
2607 $value = false;
2609 else if (in_array($value, array('on', 'true', '1')))
2611 $value = true;
2613 else if (!trim($value))
2615 $value = '';
2617 else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
2619 $value = substr($value, 1, sizeof($value)-2);
2622 $parsed_items[$key] = $value;
2625 return $parsed_items;
2629 * Add log event
2631 function add_log()
2633 global $db, $user;
2635 $args = func_get_args();
2637 $mode = array_shift($args);
2638 $reportee_id = ($mode == 'user') ? intval(array_shift($args)) : '';
2639 $forum_id = ($mode == 'mod') ? intval(array_shift($args)) : '';
2640 $topic_id = ($mode == 'mod') ? intval(array_shift($args)) : '';
2641 $action = array_shift($args);
2642 $data = (!sizeof($args)) ? '' : serialize($args);
2644 $sql_ary = array(
2645 'user_id' => (empty($user->data)) ? ANONYMOUS : $user->data['user_id'],
2646 'log_ip' => $user->ip,
2647 'log_time' => time(),
2648 'log_operation' => $action,
2649 'log_data' => $data,
2652 switch ($mode)
2654 case 'admin':
2655 $sql_ary['log_type'] = LOG_ADMIN;
2656 break;
2658 case 'mod':
2659 $sql_ary += array(
2660 'log_type' => LOG_MOD,
2661 'forum_id' => $forum_id,
2662 'topic_id' => $topic_id
2664 break;
2666 case 'user':
2667 $sql_ary += array(
2668 'log_type' => LOG_USERS,
2669 'reportee_id' => $reportee_id
2671 break;
2673 case 'critical':
2674 $sql_ary['log_type'] = LOG_CRITICAL;
2675 break;
2677 default:
2678 return false;
2681 $db->sql_query('INSERT INTO ' . LOG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
2683 return $db->sql_nextid();
2687 * Return a nicely formatted backtrace (parts from the php manual by diz at ysagoon dot com)
2689 function get_backtrace()
2691 $output = '<div style="font-family: monospace;">';
2692 $backtrace = debug_backtrace();
2693 $path = phpbb_realpath(PHPBB_ROOT_PATH);
2695 foreach ($backtrace as $number => $trace)
2697 // We skip the first one, because it only shows this file/function
2698 if ($number == 0)
2700 continue;
2703 // Strip the current directory from path
2704 if (empty($trace['file']))
2706 $trace['file'] = '';
2708 else
2710 $trace['file'] = str_replace(array($path, '\\'), array('', '/'), $trace['file']);
2711 $trace['file'] = substr($trace['file'], 1);
2713 $args = array();
2715 // If include/require/include_once is not called, do not show arguments - they may contain sensible information
2716 if (!in_array($trace['function'], array('include', 'require', 'include_once')))
2718 unset($trace['args']);
2720 else
2722 // Path...
2723 if (!empty($trace['args'][0]))
2725 $argument = htmlspecialchars($trace['args'][0]);
2726 $argument = str_replace(array($path, '\\'), array('', '/'), $argument);
2727 $argument = substr($argument, 1);
2728 $args[] = "'{$argument}'";
2732 $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
2733 $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
2735 $output .= '<br />';
2736 $output .= '<b>FILE:</b> ' . htmlspecialchars($trace['file']) . '<br />';
2737 $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
2739 $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']) . '(' . ((sizeof($args)) ? implode(', ', $args) : '') . ')<br />';
2741 $output .= '</div>';
2742 return $output;
2746 * This function returns a regular expression pattern for commonly used expressions
2747 * Use with / as delimiter for email mode and # for url modes
2748 * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
2750 function get_preg_expression($mode)
2752 switch ($mode)
2754 case 'email':
2755 return '(?:[a-z0-9\'\.\-_\+\|]++|&amp;)+@[a-z0-9\-]+\.(?:[a-z0-9\-]+\.)*[a-z]+';
2756 break;
2758 case 'bbcode_htm':
2759 return array(
2760 '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
2761 '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
2762 '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
2763 '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
2764 '#<!\-\- .*? \-\->#s',
2765 '#<.*?>#s',
2767 break;
2769 // Whoa these look impressive!
2770 // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
2771 // can be found in the develop directory
2772 case 'ipv4':
2773 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])$#';
2774 break;
2776 case 'ipv6':
2777 return '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){5}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:))$#i';
2778 break;
2780 case 'url':
2781 case 'url_inline':
2782 $inline = ($mode == 'url') ? ')' : '';
2783 $scheme = ($mode == 'url') ? '[a-z\d+\-.]' : '[a-z\d+]'; // avoid automatic parsing of "word" in "last word.http://..."
2784 // generated with regex generation file in the develop folder
2785 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})*)?";
2786 break;
2788 case 'www_url':
2789 case 'www_url_inline':
2790 $inline = ($mode == 'www_url') ? ')' : '';
2791 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})*)?";
2792 break;
2794 case 'relative_url':
2795 case 'relative_url_inline':
2796 $inline = ($mode == 'relative_url') ? ')' : '';
2797 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})*)?";
2798 break;
2801 return '';
2805 * Returns the first block of the specified IPv6 address and as many additional
2806 * ones as specified in the length paramater.
2807 * If length is zero, then an empty string is returned.
2808 * If length is greater than 3 the complete IP will be returned
2810 function short_ipv6($ip, $length)
2812 if ($length < 1)
2814 return '';
2817 // extend IPv6 addresses
2818 $blocks = substr_count($ip, ':') + 1;
2819 if ($blocks < 9)
2821 $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
2823 if ($ip[0] == ':')
2825 $ip = '0000' . $ip;
2827 if ($length < 4)
2829 $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
2832 return $ip;
2836 * Wrapper for php's checkdnsrr function.
2838 * The windows failover is from the php manual
2839 * Please make sure to check the return value for === true and === false, since NULL could
2840 * be returned too.
2842 * @return true if entry found, false if not, NULL if this function is not supported by this environment
2844 function phpbb_checkdnsrr($host, $type = '')
2846 $type = (!$type) ? 'MX' : $type;
2848 if (DIRECTORY_SEPARATOR == '\\')
2850 if (!function_exists('exec'))
2852 return NULL;
2855 // @exec('nslookup -retry=1 -timout=1 -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host), $output);
2856 @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host), $output);
2858 // If output is empty, the nslookup failed
2859 if (empty($output))
2861 return NULL;
2864 foreach ($output as $line)
2866 if (!trim($line))
2868 continue;
2871 // Valid records begin with host name:
2872 if (strpos($line, $host) === 0)
2874 return true;
2878 return false;
2880 else if (function_exists('checkdnsrr'))
2882 return (checkdnsrr($host, $type)) ? true : false;
2885 return NULL;
2888 // Handler, header and footer
2891 * Error and message handler, call with trigger_error if reqd
2893 function msg_handler($errno, $msg_text, $errfile, $errline)
2895 global $cache, $db, $auth, $template, $config, $user;
2896 global $msg_title, $msg_long_text;
2898 // Do not display notices if we suppress them via @
2899 if (error_reporting() == 0)
2901 return;
2904 // Message handler is stripping text. In case we need it, we are possible to define long text...
2905 if (isset($msg_long_text) && $msg_long_text && !$msg_text)
2907 $msg_text = $msg_long_text;
2910 switch ($errno)
2912 case E_NOTICE:
2913 case E_WARNING:
2914 case E_STRICT:
2916 // Check the error reporting level and return if the error level does not match
2917 // If DEBUG is defined the default level is E_ALL
2918 if (($errno & ((defined('DEBUG')) ? E_ALL | E_STRICT : error_reporting())) == 0)
2920 return;
2923 if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
2925 // flush the content, else we get a white page if output buffering is on
2926 if ((int) @ini_get('output_buffering') === 1 || strtolower(@ini_get('output_buffering')) === 'on')
2928 @ob_flush();
2931 // Another quick fix for those having gzip compression enabled, but do not flush if the coder wants to catch "something". ;)
2932 if ($config['gzip_compress'])
2934 if (@extension_loaded('zlib') && !headers_sent() && !ob_get_level())
2936 @ob_flush();
2940 // remove complete path to installation, with the risk of changing backslashes meant to be there
2941 $errfile = str_replace(array(phpbb_realpath(PHPBB_ROOT_PATH), '\\'), array('', '/'), $errfile);
2942 $msg_text = str_replace(array(phpbb_realpath(PHPBB_ROOT_PATH), '\\'), array('', '/'), $msg_text);
2944 echo '<b>[phpBB Debug] PHP Notice</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
2947 return;
2949 break;
2951 case E_RECOVERABLE_ERROR:
2952 case E_USER_ERROR:
2954 if (!empty($user) && !empty($user->lang))
2956 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
2957 $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
2959 $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . PHPBB_ROOT_PATH . '">', '</a>');
2960 $l_notify = '';
2962 if (!empty($config['board_contact']))
2964 $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
2967 else
2969 $msg_title = 'General Error';
2970 $l_return_index = '<a href="' . PHPBB_ROOT_PATH . '">Return to index page</a>';
2971 $l_notify = '';
2973 if (!empty($config['board_contact']))
2975 $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
2979 garbage_collection();
2981 // Try to not call the adm page data...
2983 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
2984 echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">';
2985 echo '<head>';
2986 echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
2987 echo '<title>' . $msg_title . '</title>';
2988 echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
2989 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; } ';
2990 echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
2991 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; } ';
2992 echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px #A9B8C2; } ';
2993 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; } ';
2994 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; } ';
2995 echo "\n" . '/* ]]> */' . "\n";
2996 echo '</style>';
2997 echo '</head>';
2998 echo '<body id="errorpage">';
2999 echo '<div id="wrap">';
3000 echo ' <div id="page-header">';
3001 echo ' ' . $l_return_index;
3002 echo ' </div>';
3003 echo ' <div id="acp">';
3004 echo ' <div class="panel">';
3005 echo ' <div id="content">';
3006 echo ' <h1>' . $msg_title . '</h1>';
3008 echo ' <div>' . $msg_text . '</div>';
3010 echo $l_notify;
3012 echo ' </div>';
3013 echo ' </div>';
3014 echo ' </div>';
3015 echo ' <div id="page-footer">';
3016 echo ' Powered by phpBB &copy; 2000, 2002, 2005, 2007 <a href="http://www.phpbb.com/">phpBB Group</a>';
3017 echo ' </div>';
3018 echo '</div>';
3019 echo '</body>';
3020 echo '</html>';
3022 exit_handler();
3023 break;
3025 case E_USER_WARNING:
3026 case E_USER_NOTICE:
3028 define('IN_ERROR_HANDLER', true);
3030 if (empty($user->data))
3032 $user->session_begin();
3035 // We re-init the auth array to get correct results on login/logout
3036 $auth->acl($user->data);
3038 if (empty($user->lang))
3040 $user->setup();
3043 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3044 $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3046 if (!defined('HEADER_INC'))
3048 if (defined('ADMIN_START') || (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']))
3050 adm_page_header($msg_title);
3052 else
3054 page_header($msg_title);
3058 $template->set_filenames(array(
3059 'body' => 'message_body.html')
3062 $template->assign_vars(array(
3063 'MESSAGE_TITLE' => $msg_title,
3064 'MESSAGE_TEXT' => $msg_text,
3065 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false,
3066 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false)
3069 // We do not want the cron script to be called on error messages
3070 define('IN_CRON', true);
3072 if (defined('ADMIN_START') || (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']))
3074 adm_page_footer();
3076 else
3078 page_footer();
3081 exit_handler();
3082 break;
3085 // If we notice an error not handled here we pass this back to PHP by returning false
3086 // This may not work for all php versions
3087 return false;
3091 * Generate page header
3093 function page_header($page_title = '', $display_online_list = true)
3095 global $db, $config, $template, $SID, $_SID, $user, $auth;
3097 if (defined('HEADER_INC'))
3099 return;
3102 define('HEADER_INC', true);
3104 // gzip_compression
3105 if ($config['gzip_compress'])
3107 if (@extension_loaded('zlib') && !headers_sent())
3109 ob_start('ob_gzhandler');
3113 // Generate logged in/logged out status
3114 if ($user->data['user_id'] != ANONYMOUS)
3116 $u_login_logout = append_sid('ucp', 'mode=logout', true, $user->session_id);
3117 $l_login_logout = sprintf($user->lang['LOGOUT_USER'], $user->data['username']);
3119 else
3121 $u_login_logout = append_sid('ucp', 'mode=login');
3122 $l_login_logout = $user->lang['LOGIN'];
3125 // Last visit date/time
3126 $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
3128 // Get users online list ... if required
3129 $l_online_users = $online_userlist = $l_online_record = '';
3131 if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
3133 $logged_visible_online = $logged_hidden_online = $guests_online = $prev_user_id = 0;
3134 $prev_session_ip = $reading_sql = '';
3136 if (!empty($_REQUEST['f']))
3138 $f = request_var('f', 0);
3140 $reading_sql = ' AND s.session_page ' . $db->sql_like_expression("{$db->any_char}_f_={$f}x{$db->any_char}");
3143 // Get number of online guests
3144 if (!$config['load_online_guests'])
3146 if ($db->count_distinct)
3148 $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
3149 FROM ' . SESSIONS_TABLE . ' s
3150 WHERE s.session_user_id = ' . ANONYMOUS . '
3151 AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
3152 $reading_sql;
3154 else
3156 $sql = 'SELECT COUNT(session_ip) as num_guests
3157 FROM (
3158 SELECT DISTINCT s.session_ip
3159 FROM ' . SESSIONS_TABLE . ' s
3160 WHERE s.session_user_id = ' . ANONYMOUS . '
3161 AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
3162 $reading_sql .
3163 ')';
3165 $result = $db->sql_query($sql);
3166 $guests_online = (int) $db->sql_fetchfield('num_guests');
3167 $db->sql_freeresult($result);
3170 $sql = 'SELECT u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour, s.session_ip, s.session_viewonline
3171 FROM ' . USERS_TABLE . ' u, ' . SESSIONS_TABLE . ' s
3172 WHERE s.session_time >= ' . (time() - (intval($config['load_online_time']) * 60)) .
3173 $reading_sql .
3174 ((!$config['load_online_guests']) ? ' AND s.session_user_id <> ' . ANONYMOUS : '') . '
3175 AND u.user_id = s.session_user_id
3176 ORDER BY u.username_clean ASC, s.session_ip ASC';
3177 $result = $db->sql_query($sql);
3179 $prev_user_id = false;
3181 while ($row = $db->sql_fetchrow($result))
3183 // User is logged in and therefore not a guest
3184 if ($row['user_id'] != ANONYMOUS)
3186 // Skip multiple sessions for one user
3187 if ($row['user_id'] != $prev_user_id)
3189 if ($row['session_viewonline'])
3191 $logged_visible_online++;
3193 else
3195 $row['username'] = '<em>' . $row['username'] . '</em>';
3196 $logged_hidden_online++;
3199 if (($row['session_viewonline']) || $auth->acl_get('u_viewonline'))
3201 $user_online_link = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
3202 $online_userlist .= ($online_userlist != '') ? ', ' . $user_online_link : $user_online_link;
3206 $prev_user_id = $row['user_id'];
3208 else
3210 // Skip multiple sessions for one user
3211 if ($row['session_ip'] != $prev_session_ip)
3213 $guests_online++;
3217 $prev_session_ip = $row['session_ip'];
3219 $db->sql_freeresult($result);
3221 if (!$online_userlist)
3223 $online_userlist = $user->lang['NO_ONLINE_USERS'];
3226 if (empty($_REQUEST['f']))
3228 $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
3230 else
3232 $l_online = ($guests_online == 1) ? $user->lang['BROWSING_FORUM_GUEST'] : $user->lang['BROWSING_FORUM_GUESTS'];
3233 $online_userlist = sprintf($l_online, $online_userlist, $guests_online);
3236 $total_online_users = $logged_visible_online + $logged_hidden_online + $guests_online;
3238 if ($total_online_users > $config['record_online_users'])
3240 set_config('record_online_users', $total_online_users, true);
3241 set_config('record_online_date', time(), true);
3244 // Build online listing
3245 $vars_online = array(
3246 'ONLINE' => array('total_online_users', 'l_t_user_s'),
3247 'REG' => array('logged_visible_online', 'l_r_user_s'),
3248 'HIDDEN' => array('logged_hidden_online', 'l_h_user_s'),
3249 'GUEST' => array('guests_online', 'l_g_user_s')
3252 foreach ($vars_online as $l_prefix => $var_ary)
3254 switch (${$var_ary[0]})
3256 case 0:
3257 ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL'];
3258 break;
3260 case 1:
3261 ${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL'];
3262 break;
3264 default:
3265 ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL'];
3266 break;
3269 unset($vars_online);
3271 $l_online_users = sprintf($l_t_user_s, $total_online_users);
3272 $l_online_users .= sprintf($l_r_user_s, $logged_visible_online);
3273 $l_online_users .= sprintf($l_h_user_s, $logged_hidden_online);
3274 $l_online_users .= sprintf($l_g_user_s, $guests_online);
3276 $l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date']));
3278 $l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES';
3279 $l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']);
3281 else
3283 $l_online_time = '';
3286 $l_privmsgs_text = $l_privmsgs_text_unread = '';
3287 $s_privmsg_new = false;
3289 // Obtain number of new private messages if user is logged in
3290 if (isset($user->data['is_registered']) && $user->data['is_registered'])
3292 if ($user->data['user_new_privmsg'])
3294 $l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS'];
3295 $l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']);
3297 if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
3299 $sql = 'UPDATE ' . USERS_TABLE . '
3300 SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
3301 WHERE user_id = ' . $user->data['user_id'];
3302 $db->sql_query($sql);
3304 $s_privmsg_new = true;
3306 else
3308 $s_privmsg_new = false;
3311 else
3313 $l_privmsgs_text = $user->lang['NO_NEW_PM'];
3314 $s_privmsg_new = false;
3317 $l_privmsgs_text_unread = '';
3319 if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg'])
3321 $l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS'];
3322 $l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']);
3326 // Which timezone?
3327 $tz = ($user->data['user_id'] != ANONYMOUS) ? strval(doubleval($user->data['user_timezone'])) : strval(doubleval($config['board_timezone']));
3329 // Send a proper content-language to the output
3330 $user_lang = $user->lang['USER_LANG'];
3331 if (strpos($user_lang, '-x-') !== false)
3333 $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
3336 // The following assigns all _common_ variables that may be used at any point in a template.
3337 $template->assign_vars(array(
3338 'SITENAME' => $config['sitename'],
3339 'SITE_DESCRIPTION' => $config['site_desc'],
3340 'PAGE_TITLE' => $page_title,
3341 'SCRIPT_NAME' => str_replace('.' . PHP_EXT, '', $user->page['page_name']),
3342 'LAST_VISIT_DATE' => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
3343 'LAST_VISIT_YOU' => $s_last_visit,
3344 'CURRENT_TIME' => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
3345 'TOTAL_USERS_ONLINE' => $l_online_users,
3346 'LOGGED_IN_USER_LIST' => $online_userlist,
3347 'RECORD_USERS' => $l_online_record,
3348 'PRIVATE_MESSAGE_INFO' => $l_privmsgs_text,
3349 'PRIVATE_MESSAGE_INFO_UNREAD' => $l_privmsgs_text_unread,
3351 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'],
3352 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'],
3354 'SID' => $SID,
3355 '_SID' => $_SID,
3356 'SESSION_ID' => $user->session_id,
3357 'ROOT_PATH' => PHPBB_ROOT_PATH,
3359 'L_LOGIN_LOGOUT' => $l_login_logout,
3360 'L_INDEX' => $user->lang['FORUM_INDEX'],
3361 'L_ONLINE_EXPLAIN' => $l_online_time,
3363 'U_PRIVATEMSGS' => append_sid('ucp', 'i=pm&amp;folder=inbox'),
3364 'U_RETURN_INBOX' => append_sid('ucp', 'i=pm&amp;folder=inbox'),
3365 'U_POPUP_PM' => append_sid('ucp', 'i=pm&amp;mode=popup'),
3366 'UA_POPUP_PM' => addslashes(append_sid('ucp', 'i=pm&amp;mode=popup')),
3367 'U_MEMBERLIST' => append_sid('memberlist'),
3368 'U_VIEWONLINE' => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid('viewonline') : '',
3369 'U_LOGIN_LOGOUT' => $u_login_logout,
3370 'U_INDEX' => append_sid('index'),
3371 'U_SEARCH' => append_sid('search'),
3372 'U_REGISTER' => append_sid('ucp', 'mode=register'),
3373 'U_PROFILE' => append_sid('ucp'),
3374 'U_MODCP' => append_sid('mcp', false, true, $user->session_id),
3375 'U_FAQ' => append_sid('faq'),
3376 'U_SEARCH_SELF' => append_sid('search', 'search_id=egosearch'),
3377 'U_SEARCH_NEW' => append_sid('search', 'search_id=newposts'),
3378 'U_SEARCH_UNANSWERED' => append_sid('search', 'search_id=unanswered'),
3379 'U_SEARCH_ACTIVE_TOPICS'=> append_sid('search', 'search_id=active_topics'),
3380 'U_DELETE_COOKIES' => append_sid('ucp', 'mode=delete_cookies'),
3381 'U_TEAM' => append_sid('memberlist', 'mode=leaders'),
3382 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid('ucp', 'mode=restore_perm') : '',
3384 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false,
3385 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false,
3386 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false,
3387 'S_REGISTERED_USER' => $user->data['is_registered'],
3388 'S_IS_BOT' => $user->data['is_bot'],
3389 'S_IN_SEARCH' => false,
3390 'S_VIEWTOPIC' => false,
3391 'S_VIEWFORUM' => false,
3392 'S_USER_PM_POPUP' => $user->optionget('popuppm'),
3393 'S_USER_LANG' => $user_lang,
3394 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
3395 'S_USERNAME' => $user->data['username'],
3396 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'],
3397 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
3398 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
3399 'S_CONTENT_ENCODING' => 'UTF-8',
3400 '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], ''),
3401 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0,
3402 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
3403 'S_DISPLAY_PM' => ($config['allow_privmsg'] && $user->data['is_registered'] && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
3404 'S_DISPLAY_MEMBERLIST' => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
3405 'S_NEW_PM' => ($s_privmsg_new) ? 1 : 0,
3406 'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
3408 'T_THEME_PATH' => PHPBB_ROOT_PATH . 'styles/' . $user->theme['theme_path'] . '/theme',
3409 'T_TEMPLATE_PATH' => PHPBB_ROOT_PATH . 'styles/' . $user->theme['template_path'] . '/template',
3410 'T_IMAGESET_PATH' => PHPBB_ROOT_PATH . 'styles/' . $user->theme['imageset_path'] . '/imageset',
3411 'T_IMAGESET_LANG_PATH' => PHPBB_ROOT_PATH . 'styles/' . $user->theme['imageset_path'] . '/imageset/' . $user->data['user_lang'],
3412 'T_IMAGES_PATH' => PHPBB_ROOT_PATH . 'images/',
3413 'T_SMILIES_PATH' => PHPBB_ROOT_PATH . $config['smilies_path'] . '/',
3414 'T_AVATAR_PATH' => PHPBB_ROOT_PATH . $config['avatar_path'] . '/',
3415 'T_AVATAR_GALLERY_PATH' => PHPBB_ROOT_PATH . $config['avatar_gallery_path'] . '/',
3416 'T_ICONS_PATH' => PHPBB_ROOT_PATH . $config['icons_path'] . '/',
3417 'T_RANKS_PATH' => PHPBB_ROOT_PATH . $config['ranks_path'] . '/',
3418 'T_UPLOAD_PATH' => PHPBB_ROOT_PATH . $config['upload_path'] . '/',
3419 'T_STYLESHEET_LINK' => (!$user->theme['theme_storedb']) ? PHPBB_ROOT_PATH . 'styles/' . $user->theme['theme_path'] . '/theme/stylesheet.css' : PHPBB_ROOT_PATH . 'style.' . PHP_EXT . "?sid=$user->session_id&amp;id=" . $user->theme['style_id'] . '&amp;lang=' . $user->data['user_lang'], //PHPBB_ROOT_PATH . "store/{$user->theme['theme_id']}_{$user->theme['imageset_id']}_{$user->lang_name}.css"
3420 'T_STYLESHEET_NAME' => $user->theme['theme_name'],
3422 'SITE_LOGO_IMG' => $user->img('site_logo'))
3425 // application/xhtml+xml not used because of IE
3426 header('Content-type: text/html; charset=UTF-8');
3428 header('Cache-Control: private, no-cache="set-cookie"');
3429 header('Expires: 0');
3430 header('Pragma: no-cache');
3432 return;
3436 * Generate page footer
3438 function page_footer($run_cron = true)
3440 global $db, $config, $template, $user, $auth, $cache, $starttime;
3442 // Output page creation time
3443 if (defined('DEBUG'))
3445 $mtime = explode(' ', microtime());
3446 $totaltime = $mtime[0] + $mtime[1] - $starttime;
3448 if (!empty($_REQUEST['explain']) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report'))
3450 $db->sql_report('display');
3453 $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress']) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime);
3455 if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))
3457 if (function_exists('memory_get_usage'))
3459 if ($memory_usage = memory_get_usage())
3461 global $base_memory_usage;
3462 $memory_usage -= $base_memory_usage;
3463 $memory_usage = get_formatted_filesize($memory_usage);
3465 $debug_output .= ' | Memory Usage: ' . $memory_usage;
3469 $debug_output .= ' | <a href="' . build_url() . '&amp;explain=1">Explain</a>';
3473 $template->assign_vars(array(
3474 'DEBUG_OUTPUT' => (defined('DEBUG')) ? $debug_output : '',
3475 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
3477 'U_ACP' => ($auth->acl_get('a_') && $user->data['is_registered']) ? append_sid(CONFIG_ADM_FOLDER . '/index', false, true, $user->session_id) : '')
3480 // Call cron-type script
3481 if (!defined('IN_CRON') && $run_cron && !$config['board_disable'])
3483 $cron_type = '';
3485 if (time() - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists(PHPBB_ROOT_PATH . 'cache/queue.' . PHP_EXT))
3487 // Process email queue
3488 $cron_type = 'queue';
3490 else if (method_exists($cache, 'tidy') && time() - $config['cache_gc'] > $config['cache_last_gc'])
3492 // Tidy the cache
3493 $cron_type = 'tidy_cache';
3495 else if (time() - $config['warnings_gc'] > $config['warnings_last_gc'])
3497 $cron_type = 'tidy_warnings';
3499 else if (time() - $config['database_gc'] > $config['database_last_gc'])
3501 // Tidy the database
3502 $cron_type = 'tidy_database';
3504 else if (time() - $config['search_gc'] > $config['search_last_gc'])
3506 // Tidy the search
3507 $cron_type = 'tidy_search';
3509 else if (time() - $config['session_gc'] > $config['session_last_gc'])
3511 $cron_type = 'tidy_sessions';
3514 if ($cron_type)
3516 $template->assign_var('RUN_CRON_TASK', '<img src="' . append_sid('cron', 'cron_type=' . $cron_type) . '" width="1" height="1" alt="cron" />');
3520 $template->display('body');
3522 garbage_collection();
3523 exit_handler();
3527 * Closing the cache object and the database
3528 * Cool function name, eh? We might want to add operations to it later
3530 function garbage_collection()
3532 global $cache, $db;
3534 // Unload cache, must be done before the DB connection if closed
3535 if (!empty($cache))
3537 $cache->unload();
3540 // Close our DB connection.
3541 if (!empty($db))
3543 $db->sql_close();
3548 * Handler for exit calls in phpBB.
3549 * This function supports hooks.
3551 * Note: This function is called after the template has been outputted.
3553 function exit_handler()
3555 global $phpbb_hook, $config;
3557 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
3559 if ($phpbb_hook->hook_return(__FUNCTION__))
3561 return $phpbb_hook->hook_return_result(__FUNCTION__);
3565 // As a pre-caution... some setups display a blank page if the flush() is not there.
3566 (!$config['gzip_compress']) ? @flush() : @ob_flush();
3568 exit;
3572 * Handler for init calls in phpBB. This function is called in user::setup();
3573 * This function supports hooks.
3575 function phpbb_user_session_handler()
3577 global $phpbb_hook;
3579 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
3581 if ($phpbb_hook->hook_return(__FUNCTION__))
3583 return $phpbb_hook->hook_return_result(__FUNCTION__);
3587 return;