aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/includes/classes
diff options
context:
space:
mode:
Diffstat (limited to 'phpBB/includes/classes')
-rw-r--r--phpBB/includes/classes/acl.php1041
-rw-r--r--phpBB/includes/classes/cache.php422
-rw-r--r--phpBB/includes/classes/session.php2187
-rw-r--r--phpBB/includes/classes/template.php536
-rw-r--r--phpBB/includes/classes/template_compile.php869
5 files changed, 5055 insertions, 0 deletions
diff --git a/phpBB/includes/classes/acl.php b/phpBB/includes/classes/acl.php
new file mode 100644
index 0000000000..e7fa0f2674
--- /dev/null
+++ b/phpBB/includes/classes/acl.php
@@ -0,0 +1,1041 @@
+<?php
+/**
+*
+* @package phpBB3
+* @version $Id$
+* @copyright (c) 2005 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Permission/Auth class
+* @package phpBB3
+*/
+class auth
+{
+ private $acl = array();
+ private $cache = array();
+ public $acl_options = array();
+ private $acl_forum_ids = false;
+
+ /**
+ * Init permissions
+ */
+ function acl(array &$userdata)
+ {
+ global $db, $cache;
+
+ $this->acl = $this->cache = $this->acl_options = array();
+ $this->acl_forum_ids = false;
+
+ if (($this->acl_options = $cache->get('_acl_options')) === false)
+ {
+ $sql = 'SELECT auth_option_id, auth_option, is_global, is_local
+ FROM ' . ACL_OPTIONS_TABLE . '
+ ORDER BY auth_option_id';
+ $result = $db->sql_query($sql);
+
+ $global = $local = 0;
+ $this->acl_options = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ if ($row['is_global'])
+ {
+ $this->acl_options['global'][$row['auth_option']] = $global++;
+ }
+
+ if ($row['is_local'])
+ {
+ $this->acl_options['local'][$row['auth_option']] = $local++;
+ }
+
+ $this->acl_options['id'][$row['auth_option']] = (int) $row['auth_option_id'];
+ $this->acl_options['option'][(int) $row['auth_option_id']] = $row['auth_option'];
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('_acl_options', $this->acl_options);
+ $this->acl_cache($userdata);
+ }
+ else if (!trim($userdata['user_permissions']))
+ {
+ $this->acl_cache($userdata);
+ }
+
+ // Fill ACL array
+ $this->_fill_acl($userdata['user_permissions']);
+
+ // Verify bitstring length with options provided...
+ $renew = false;
+ $global_length = sizeof($this->acl_options['global']);
+ $local_length = sizeof($this->acl_options['local']);
+
+ // Specify comparing length (bitstring is padded to 31 bits)
+ $global_length = ($global_length % 31) ? ($global_length - ($global_length % 31) + 31) : $global_length;
+ $local_length = ($local_length % 31) ? ($local_length - ($local_length % 31) + 31) : $local_length;
+
+ // You thought we are finished now? Noooo... now compare them.
+ foreach ($this->acl as $forum_id => $bitstring)
+ {
+ if (($forum_id && strlen($bitstring) != $local_length) || (!$forum_id && strlen($bitstring) != $global_length))
+ {
+ $renew = true;
+ break;
+ }
+ }
+
+ // If a bitstring within the list does not match the options, we have a user with incorrect permissions set and need to renew them
+ if ($renew)
+ {
+ $this->acl_cache($userdata);
+ $this->_fill_acl($userdata['user_permissions']);
+ }
+
+ return;
+ }
+
+ /**
+ * Fill ACL array with relevant bitstrings from user_permissions column
+ * @access private
+ */
+ private function _fill_acl($user_permissions)
+ {
+ $this->acl = array();
+ $user_permissions = explode("\n", $user_permissions);
+
+ foreach ($user_permissions as $f => $seq)
+ {
+ if ($seq)
+ {
+ $i = 0;
+
+ if (!isset($this->acl[$f]))
+ {
+ $this->acl[$f] = '';
+ }
+
+ while ($subseq = substr($seq, $i, 6))
+ {
+ // We put the original bitstring into the acl array
+ $this->acl[$f] .= str_pad(base_convert($subseq, 36, 2), 31, 0, STR_PAD_LEFT);
+ $i += 6;
+ }
+ }
+ }
+ }
+
+ /**
+ * Look up an option
+ * if the option is prefixed with !, then the result becomes negated
+ *
+ * If a forum id is specified the local option will be combined with a global option if one exist.
+ * If a forum id is not specified, only the global option will be checked.
+ */
+ public function acl_get($opt, $f = 0)
+ {
+ $negate = false;
+
+ if (strpos($opt, '!') === 0)
+ {
+ $negate = true;
+ $opt = substr($opt, 1);
+ }
+
+ // @todo: use the ref technique to reduce opcode generation
+ if (!isset($this->cache[$f][$opt]))
+ {
+ // We combine the global/local option with an OR because some options are global and local.
+ // If the user has the global permission the local one is true too and vice versa
+ $this->cache[$f][$opt] = false;
+
+ // Is this option a global permission setting?
+ if (isset($this->acl_options['global'][$opt]))
+ {
+ if (isset($this->acl[0]))
+ {
+ $this->cache[$f][$opt] = $this->acl[0][$this->acl_options['global'][$opt]];
+ }
+ }
+
+ // Is this option a local permission setting?
+ // But if we check for a global option only, we won't combine the options...
+ if ($f != 0 && isset($this->acl_options['local'][$opt]))
+ {
+ if (isset($this->acl[$f]) && isset($this->acl[$f][$this->acl_options['local'][$opt]]))
+ {
+ $this->cache[$f][$opt] |= $this->acl[$f][$this->acl_options['local'][$opt]];
+ }
+ }
+ }
+
+ // Founder always has all global options set to true...
+ return $negate xor $this->cache[$f][$opt];
+ }
+
+ /**
+ * Get forums with the specified permission setting
+ * if the option is prefixed with !, then the result becomes negated
+ *
+ * @param bool $clean set to true if only values needs to be returned which are set/unset
+ */
+ public function acl_getf($opt, $clean = false)
+ {
+ $acl_f = array();
+ $negate = false;
+
+ if (strpos($opt, '!') === 0)
+ {
+ $negate = true;
+ $opt = substr($opt, 1);
+ }
+
+ // If we retrieve a list of forums not having permissions in, we need to get every forum_id
+ if ($negate)
+ {
+ if ($this->acl_forum_ids === false)
+ {
+ global $db;
+
+ $sql = 'SELECT forum_id
+ FROM ' . FORUMS_TABLE;
+
+ if (sizeof($this->acl))
+ {
+ $sql .= ' WHERE ' . $db->sql_in_set('forum_id', array_keys($this->acl), true);
+ }
+ $result = $db->sql_query($sql);
+
+ $this->acl_forum_ids = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $this->acl_forum_ids[] = $row['forum_id'];
+ }
+ $db->sql_freeresult($result);
+ }
+ }
+
+ if (isset($this->acl_options['local'][$opt]))
+ {
+ foreach ($this->acl as $f => $bitstring)
+ {
+ // Skip global settings
+ if (!$f)
+ {
+ continue;
+ }
+
+ $allowed = (!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt];
+
+ if (!$clean)
+ {
+ $acl_f[$f][$opt] = $negate xor $allowed;
+ }
+ else if ($negate xor $allowed)
+ {
+ $acl_f[$f][$opt] = 1;
+ }
+ }
+ }
+
+ // If we get forum_ids not having this permission, we need to fill the remaining parts
+ if ($negate && sizeof($this->acl_forum_ids))
+ {
+ foreach ($this->acl_forum_ids as $f)
+ {
+ $acl_f[$f][$opt] = 1;
+ }
+ }
+
+ return $acl_f;
+ }
+
+ /**
+ * Get local permission state for any forum.
+ *
+ * Returns true if user has the permission in one or more forums, false if in no forum.
+ * If global option is checked it returns the global state (same as acl_get($opt))
+ * Local option has precedence...
+ */
+ public function acl_getf_global($opt)
+ {
+ if (is_array($opt))
+ {
+ // evaluates to true as soon as acl_getf_global is true for one option
+ foreach ($opt as $check_option)
+ {
+ if ($this->acl_getf_global($check_option))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (isset($this->acl_options['local'][$opt]))
+ {
+ foreach ($this->acl as $f => $bitstring)
+ {
+ // Skip global settings
+ if (!$f)
+ {
+ continue;
+ }
+
+ // as soon as the user has any permission we're done so return true
+ if ((!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt])
+ {
+ return true;
+ }
+ }
+ }
+ else if (isset($this->acl_options['global'][$opt]))
+ {
+ return $this->acl_get($opt);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get permission settings (more than one)
+ */
+ public function acl_gets()
+ {
+ $args = func_get_args();
+ $f = array_pop($args);
+
+ if (!is_numeric($f))
+ {
+ $args[] = $f;
+ $f = 0;
+ }
+
+ // alternate syntax: acl_gets(array('m_', 'a_'), $forum_id)
+ if (is_array($args[0]))
+ {
+ $args = $args[0];
+ }
+
+ $acl = 0;
+ foreach ($args as $opt)
+ {
+ $acl |= $this->acl_get($opt, $f);
+ }
+
+ return $acl;
+ }
+
+ /**
+ * Get permission listing based on user_id/options/forum_ids
+ */
+ public function acl_get_list($user_id = false, $opts = false, $forum_id = false)
+ {
+ if ($user_id !== false && !is_array($user_id) && $opts === false && $forum_id === false)
+ {
+ $hold_ary = array($user_id => $this->acl_raw_data_single_user($user_id));
+ }
+ else
+ {
+ $hold_ary = $this->acl_raw_data($user_id, $opts, $forum_id);
+ }
+
+ $auth_ary = array();
+ foreach ($hold_ary as $user_id => $forum_ary)
+ {
+ foreach ($forum_ary as $forum_id => $auth_option_ary)
+ {
+ foreach ($auth_option_ary as $auth_option => $auth_setting)
+ {
+ if ($auth_setting)
+ {
+ $auth_ary[$forum_id][$auth_option][] = $user_id;
+ }
+ }
+ }
+ }
+
+ return $auth_ary;
+ }
+
+ /**
+ * Cache data to user_permissions row
+ */
+ public function acl_cache(array &$userdata)
+ {
+ global $db;
+
+ // Empty user_permissions
+ $userdata['user_permissions'] = '';
+
+ $hold_ary = $this->acl_raw_data_single_user($userdata['user_id']);
+
+ // Key 0 in $hold_ary are global options, all others are forum_ids
+
+ // If this user is founder we're going to force fill the admin options ...
+ if ($userdata['user_type'] == USER_FOUNDER)
+ {
+ foreach ($this->acl_options['global'] as $opt => $id)
+ {
+ if (strpos($opt, 'a_') === 0)
+ {
+ $hold_ary[0][$this->acl_options['id'][$opt]] = ACL_YES;
+ }
+ }
+ }
+
+ $hold_str = $this->build_bitstring($hold_ary);
+
+ if ($hold_str)
+ {
+ $userdata['user_permissions'] = $hold_str;
+
+ $sql = 'UPDATE ' . USERS_TABLE . "
+ SET user_permissions = '" . $db->sql_escape($userdata['user_permissions']) . "',
+ user_perm_from = 0
+ WHERE user_id = " . $userdata['user_id'];
+ $db->sql_query($sql);
+ }
+
+ return;
+ }
+
+ /**
+ * Build bitstring from permission set
+ */
+ protected function build_bitstring(&$hold_ary)
+ {
+ $hold_str = '';
+
+ if (sizeof($hold_ary))
+ {
+ ksort($hold_ary);
+
+ $last_f = 0;
+
+ foreach ($hold_ary as $f => $auth_ary)
+ {
+ $ary_key = (!$f) ? 'global' : 'local';
+
+ $bitstring = array();
+ foreach ($this->acl_options[$ary_key] as $opt => $id)
+ {
+ if (isset($auth_ary[$this->acl_options['id'][$opt]]))
+ {
+ $bitstring[$id] = $auth_ary[$this->acl_options['id'][$opt]];
+
+ $option_key = substr($opt, 0, strpos($opt, '_') + 1);
+
+ // If one option is allowed, the global permission for this option has to be allowed too
+ // example: if the user has the a_ permission this means he has one or more a_* permissions
+ if ($auth_ary[$this->acl_options['id'][$opt]] == ACL_YES && (!isset($bitstring[$this->acl_options[$ary_key][$option_key]]) || $bitstring[$this->acl_options[$ary_key][$option_key]] == ACL_NEVER))
+ {
+ $bitstring[$this->acl_options[$ary_key][$option_key]] = ACL_YES;
+ }
+ }
+ else
+ {
+ $bitstring[$id] = ACL_NEVER;
+ }
+ }
+
+ // Now this bitstring defines the permission setting for the current forum $f (or global setting)
+ $bitstring = implode('', $bitstring);
+
+ // The line number indicates the id, therefore we have to add empty lines for those ids not present
+ $hold_str .= str_repeat("\n", $f - $last_f);
+
+ // Convert bitstring for storage - we do not use binary/bytes because PHP's string functions are not fully binary safe
+ for ($i = 0, $bit_length = strlen($bitstring); $i < $bit_length; $i += 31)
+ {
+ $hold_str .= str_pad(base_convert(str_pad(substr($bitstring, $i, 31), 31, 0, STR_PAD_RIGHT), 2, 36), 6, 0, STR_PAD_LEFT);
+ }
+
+ $last_f = $f;
+ }
+ unset($bitstring);
+
+ $hold_str = rtrim($hold_str);
+ }
+
+ return $hold_str;
+ }
+
+ /**
+ * Clear one or all users cached permission settings
+ */
+ public function acl_clear_prefetch($user_id = false)
+ {
+ global $db, $cache;
+
+ // Rebuild options cache
+ $cache->destroy('_role_cache');
+
+ $sql = 'SELECT *
+ FROM ' . ACL_ROLES_DATA_TABLE . '
+ ORDER BY role_id ASC';
+ $result = $db->sql_query($sql);
+
+ $this->role_cache = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting'];
+ }
+ $db->sql_freeresult($result);
+
+ foreach ($this->role_cache as $role_id => $role_options)
+ {
+ $this->role_cache[$role_id] = serialize($role_options);
+ }
+
+ $cache->put('_role_cache', $this->role_cache);
+
+ // Now empty user permissions
+ $where_sql = '';
+
+ if ($user_id !== false)
+ {
+ $user_id = (!is_array($user_id)) ? $user_id = array((int) $user_id) : array_map('intval', $user_id);
+ $where_sql = ' WHERE ' . $db->sql_in_set('user_id', $user_id);
+ }
+
+ $sql = 'UPDATE ' . USERS_TABLE . "
+ SET user_permissions = '',
+ user_perm_from = 0
+ $where_sql";
+ $db->sql_query($sql);
+
+ return;
+ }
+
+ /**
+ * Get assigned roles
+ * @todo: protected or public?
+ */
+ public function acl_role_data($user_type, $role_type, $ug_id = false, $forum_id = false)
+ {
+ global $db;
+
+ $roles = array();
+
+ $sql_id = ($user_type == 'user') ? 'user_id' : 'group_id';
+
+ $sql_ug = ($ug_id !== false) ? ((!is_array($ug_id)) ? "AND a.$sql_id = $ug_id" : 'AND ' . $db->sql_in_set("a.$sql_id", $ug_id)) : '';
+ $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? "AND a.forum_id = $forum_id" : 'AND ' . $db->sql_in_set('a.forum_id', $forum_id)) : '';
+
+ // Grab assigned roles...
+ $sql = 'SELECT a.auth_role_id, a.' . $sql_id . ', a.forum_id
+ FROM ' . (($user_type == 'user') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE) . ' a, ' . ACL_ROLES_TABLE . " r
+ WHERE a.auth_role_id = r.role_id
+ AND r.role_type = '" . $db->sql_escape($role_type) . "'
+ $sql_ug
+ $sql_forum
+ ORDER BY r.role_order ASC";
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $roles[$row[$sql_id]][$row['forum_id']] = $row['auth_role_id'];
+ }
+ $db->sql_freeresult($result);
+
+ return $roles;
+ }
+
+ /**
+ * Get raw acl data based on user/option/forum
+ * @todo: protected or public?
+ */
+ public function acl_raw_data($user_id = false, $opts = false, $forum_id = false)
+ {
+ global $db;
+
+ $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : '';
+ $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : '';
+
+ $sql_opts = $sql_opts_select = $sql_opts_from = '';
+ $hold_ary = array();
+
+ if ($opts !== false)
+ {
+ $sql_opts_select = ', ao.auth_option';
+ $sql_opts_from = ', ' . ACL_OPTIONS_TABLE . ' ao';
+ $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts);
+ }
+
+ $sql_ary = array();
+
+ // Grab non-role settings - user-specific
+ $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . '
+ FROM ' . ACL_USERS_TABLE . ' a' . $sql_opts_from . '
+ WHERE a.auth_role_id = 0 ' .
+ (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') .
+ (($sql_user) ? 'AND a.' . $sql_user : '') . "
+ $sql_forum
+ $sql_opts";
+
+ // Now the role settings - user-specific
+ $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . '
+ FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . '
+ WHERE a.auth_role_id = r.role_id ' .
+ (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') .
+ (($sql_user) ? 'AND a.' . $sql_user : '') . "
+ $sql_forum
+ $sql_opts";
+
+ foreach ($sql_ary as $sql)
+ {
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']];
+ $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting'];
+ }
+ $db->sql_freeresult($result);
+ }
+
+ $sql_ary = array();
+
+ // Now grab group settings - non-role specific...
+ $sql_ary[] = 'SELECT ug.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . '
+ FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug' . $sql_opts_from . '
+ WHERE a.auth_role_id = 0 ' .
+ (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . '
+ AND a.group_id = ug.group_id
+ AND ug.user_pending = 0
+ ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . "
+ $sql_forum
+ $sql_opts";
+
+ // Now grab group settings - role specific...
+ $sql_ary[] = 'SELECT ug.user_id, a.forum_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . '
+ FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . '
+ WHERE a.auth_role_id = r.role_id ' .
+ (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . '
+ AND a.group_id = ug.group_id
+ AND ug.user_pending = 0
+ ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . "
+ $sql_forum
+ $sql_opts";
+
+ foreach ($sql_ary as $sql)
+ {
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']];
+
+ // @todo: use the ref technique to reduce opcode generation
+ if (!isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) || (isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) && $hold_ary[$row['user_id']][$row['forum_id']][$option] != ACL_NEVER))
+ {
+ $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting'];
+
+ // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again)
+ if ($row['auth_setting'] == ACL_NEVER)
+ {
+ $flag = substr($option, 0, strpos($option, '_') + 1);
+
+ if (isset($hold_ary[$row['user_id']][$row['forum_id']][$flag]) && $hold_ary[$row['user_id']][$row['forum_id']][$flag] == ACL_YES)
+ {
+ unset($hold_ary[$row['user_id']][$row['forum_id']][$flag]);
+
+/* if (in_array(ACL_YES, $hold_ary[$row['user_id']][$row['forum_id']]))
+ {
+ $hold_ary[$row['user_id']][$row['forum_id']][$flag] = ACL_YES;
+ }
+*/
+ }
+ }
+ }
+ }
+ $db->sql_freeresult($result);
+ }
+
+ return $hold_ary;
+ }
+
+ /**
+ * Get raw user based permission settings
+ */
+ public function acl_user_raw_data($user_id = false, $opts = false, $forum_id = false)
+ {
+ global $db;
+
+ $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : '';
+ $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : '';
+
+ $sql_opts = '';
+ $hold_ary = $sql_ary = array();
+
+ if ($opts !== false)
+ {
+ $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts);
+ }
+
+ // Grab user settings - non-role specific...
+ $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option
+ FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao
+ WHERE a.auth_role_id = 0
+ AND a.auth_option_id = ao.auth_option_id ' .
+ (($sql_user) ? 'AND a.' . $sql_user : '') . "
+ $sql_forum
+ $sql_opts
+ ORDER BY a.forum_id, ao.auth_option";
+
+ // Now the role settings - user-specific
+ $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id, ao.auth_option
+ FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao
+ WHERE a.auth_role_id = r.role_id
+ AND r.auth_option_id = ao.auth_option_id ' .
+ (($sql_user) ? 'AND a.' . $sql_user : '') . "
+ $sql_forum
+ $sql_opts
+ ORDER BY a.forum_id, ao.auth_option";
+
+ foreach ($sql_ary as $sql)
+ {
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $hold_ary[$row['user_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting'];
+ }
+ $db->sql_freeresult($result);
+ }
+
+ return $hold_ary;
+ }
+
+ /**
+ * Get raw group based permission settings
+ */
+ public function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false)
+ {
+ global $db;
+
+ $sql_group = ($group_id !== false) ? ((!is_array($group_id)) ? 'group_id = ' . (int) $group_id : $db->sql_in_set('group_id', array_map('intval', $group_id))) : '';
+ $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : '';
+
+ $sql_opts = '';
+ $hold_ary = $sql_ary = array();
+
+ if ($opts !== false)
+ {
+ $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts);
+ }
+
+ // Grab group settings - non-role specific...
+ $sql_ary[] = 'SELECT a.group_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option
+ FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao
+ WHERE a.auth_role_id = 0
+ AND a.auth_option_id = ao.auth_option_id ' .
+ (($sql_group) ? 'AND a.' . $sql_group : '') . "
+ $sql_forum
+ $sql_opts
+ ORDER BY a.forum_id, ao.auth_option";
+
+ // Now grab group settings - role specific...
+ $sql_ary[] = 'SELECT a.group_id, a.forum_id, r.auth_setting, r.auth_option_id, ao.auth_option
+ FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao
+ WHERE a.auth_role_id = r.role_id
+ AND r.auth_option_id = ao.auth_option_id ' .
+ (($sql_group) ? 'AND a.' . $sql_group : '') . "
+ $sql_forum
+ $sql_opts
+ ORDER BY a.forum_id, ao.auth_option";
+
+ foreach ($sql_ary as $sql)
+ {
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $hold_ary[$row['group_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting'];
+ }
+ $db->sql_freeresult($result);
+ }
+
+ return $hold_ary;
+ }
+
+ /**
+ * Get raw acl data based on user for caching user_permissions
+ * This function returns the same data as acl_raw_data(), but without the user id as the first key within the array.
+ */
+ public function acl_raw_data_single_user($user_id)
+ {
+ global $db, $cache;
+
+ // Check if the role-cache is there
+ if (($this->role_cache = $cache->get('_role_cache')) === false)
+ {
+ $this->role_cache = array();
+
+ // We pre-fetch roles
+ $sql = 'SELECT *
+ FROM ' . ACL_ROLES_DATA_TABLE . '
+ ORDER BY role_id ASC';
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting'];
+ }
+ $db->sql_freeresult($result);
+
+ foreach ($this->role_cache as $role_id => $role_options)
+ {
+ $this->role_cache[$role_id] = serialize($role_options);
+ }
+
+ $cache->put('_role_cache', $this->role_cache);
+ }
+
+ $hold_ary = array();
+
+ // Grab user-specific permission settings
+ $sql = 'SELECT forum_id, auth_option_id, auth_role_id, auth_setting
+ FROM ' . ACL_USERS_TABLE . '
+ WHERE user_id = ' . $user_id;
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ // If a role is assigned, assign all options included within this role. Else, only set this one option.
+ if ($row['auth_role_id'])
+ {
+ $hold_ary[$row['forum_id']] = (empty($hold_ary[$row['forum_id']])) ? unserialize($this->role_cache[$row['auth_role_id']]) : $hold_ary[$row['forum_id']] + unserialize($this->role_cache[$row['auth_role_id']]);
+ }
+ else
+ {
+ $hold_ary[$row['forum_id']][$row['auth_option_id']] = $row['auth_setting'];
+ }
+ }
+ $db->sql_freeresult($result);
+
+ // Now grab group-specific permission settings
+ $sql = 'SELECT a.forum_id, a.auth_option_id, a.auth_role_id, a.auth_setting
+ FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug
+ WHERE a.group_id = ug.group_id
+ AND ug.user_pending = 0
+ AND ug.user_id = ' . $user_id;
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ if (!$row['auth_role_id'])
+ {
+ $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $row['auth_option_id'], $row['auth_setting']);
+ }
+ else if (!empty($this->role_cache[$row['auth_role_id']]))
+ {
+ foreach (unserialize($this->role_cache[$row['auth_role_id']]) as $option_id => $setting)
+ {
+ $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $option_id, $setting);
+ }
+ }
+ }
+ $db->sql_freeresult($result);
+
+ return $hold_ary;
+ }
+
+ /**
+ * Private function snippet for setting a specific piece of the hold_ary
+ */
+ private function _set_group_hold_ary(&$hold_ary, $option_id, $setting)
+ {
+ if (!isset($hold_ary[$option_id]) || (isset($hold_ary[$option_id]) && $hold_ary[$option_id] != ACL_NEVER))
+ {
+ $hold_ary[$option_id] = $setting;
+
+ // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again)
+ if ($setting == ACL_NEVER)
+ {
+ $flag = substr($this->acl_options['option'][$option_id], 0, strpos($this->acl_options['option'][$option_id], '_') + 1);
+ $flag = (int) $this->acl_options['id'][$flag];
+
+ if (isset($hold_ary[$flag]) && $hold_ary[$flag] == ACL_YES)
+ {
+ unset($hold_ary[$flag]);
+
+/* This is uncommented, because i suspect this being slightly wrong due to mixed permission classes being possible
+ if (in_array(ACL_YES, $hold_ary))
+ {
+ $hold_ary[$flag] = ACL_YES;
+ }*/
+ }
+ }
+ }
+ }
+
+ /**
+ * Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him.
+ */
+ public function login($username, $password, $autologin = false, $viewonline = 1, $admin = 0)
+ {
+ global $config, $db, $user;
+
+ $method = trim(basename($config['auth_method']));
+ include_once(PHPBB_ROOT_PATH . 'includes/auth/auth_' . $method . '.' . PHP_EXT);
+
+ $method = 'login_' . $method;
+ if (function_exists($method))
+ {
+ $login = $method($username, $password);
+
+ // If the auth module wants us to create an empty profile do so and then treat the status as LOGIN_SUCCESS
+ if ($login['status'] == LOGIN_SUCCESS_CREATE_PROFILE)
+ {
+ // we are going to use the user_add function so include functions_user.php if it wasn't defined yet
+ if (!function_exists('user_add'))
+ {
+ include(PHPBB_ROOT_PATH . 'includes/functions_user.' . PHP_EXT);
+ }
+
+ user_add($login['user_row'], (isset($login['cp_data'])) ? $login['cp_data'] : false);
+
+ $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type
+ FROM ' . USERS_TABLE . "
+ WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'";
+ $result = $db->sql_query($sql);
+ $row = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+
+ if (!$row)
+ {
+ return array(
+ 'status' => LOGIN_ERROR_EXTERNAL_AUTH,
+ 'error_msg' => 'AUTH_NO_PROFILE_CREATED',
+ 'user_row' => array('user_id' => ANONYMOUS),
+ );
+ }
+
+ $login = array(
+ 'status' => LOGIN_SUCCESS,
+ 'error_msg' => false,
+ 'user_row' => $row,
+ );
+ }
+
+ // If login succeeded, we will log the user in... else we pass the login array through...
+ if ($login['status'] == LOGIN_SUCCESS)
+ {
+ $old_session_id = $user->session_id;
+
+ if ($admin)
+ {
+ global $SID, $_SID;
+
+ $cookie_expire = time() - 31536000;
+ $user->set_cookie('u', '', $cookie_expire);
+ $user->set_cookie('sid', '', $cookie_expire);
+ unset($cookie_expire);
+
+ $SID = '?sid=';
+ $user->session_id = $_SID = '';
+ }
+
+ $result = $user->session_create($login['user_row']['user_id'], $admin, $autologin, $viewonline);
+
+ // Successful session creation
+ if ($result === true)
+ {
+ // If admin re-authentication we remove the old session entry because a new one has been created...
+ if ($admin)
+ {
+ // the login array is used because the user ids do not differ for re-authentication
+ $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
+ WHERE session_id = '" . $db->sql_escape($old_session_id) . "'
+ AND session_user_id = {$login['user_row']['user_id']}";
+ $db->sql_query($sql);
+ }
+
+ return array(
+ 'status' => LOGIN_SUCCESS,
+ 'error_msg' => false,
+ 'user_row' => $login['user_row'],
+ );
+ }
+
+ return array(
+ 'status' => LOGIN_BREAK,
+ 'error_msg' => $result,
+ 'user_row' => $login['user_row'],
+ );
+ }
+
+ return $login;
+ }
+
+ trigger_error('Authentication method not found', E_USER_ERROR);
+ }
+
+ /**
+ * Fill auth_option statement for later querying based on the supplied options
+ */
+ private function build_auth_option_statement($key, $auth_options, &$sql_opts)
+ {
+ global $db;
+
+ if (!is_array($auth_options))
+ {
+ if (strpos($auth_options, '%') !== false)
+ {
+ $sql_opts = "AND $key " . $db->sql_like_expression(str_replace('%', $db->any_char, $auth_options));
+ }
+ else
+ {
+ $sql_opts = "AND $key = '" . $db->sql_escape($auth_options) . "'";
+ }
+ }
+ else
+ {
+ $is_like_expression = false;
+
+ foreach ($auth_options as $option)
+ {
+ if (strpos($option, '%') !== false)
+ {
+ $is_like_expression = true;
+ }
+ }
+
+ if (!$is_like_expression)
+ {
+ $sql_opts = 'AND ' . $db->sql_in_set($key, $auth_options);
+ }
+ else
+ {
+ $sql = array();
+
+ foreach ($auth_options as $option)
+ {
+ if (strpos($option, '%') !== false)
+ {
+ $sql[] = $key . ' ' . $db->sql_like_expression(str_replace('%', $db->any_char, $option));
+ }
+ else
+ {
+ $sql[] = $key . " = '" . $db->sql_escape($option) . "'";
+ }
+ }
+
+ $sql_opts = 'AND (' . implode(' OR ', $sql) . ')';
+ }
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/phpBB/includes/classes/cache.php b/phpBB/includes/classes/cache.php
new file mode 100644
index 0000000000..f537b52b3f
--- /dev/null
+++ b/phpBB/includes/classes/cache.php
@@ -0,0 +1,422 @@
+<?php
+/**
+*
+* @package acm
+* @version $Id$
+* @copyright (c) 2005 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Class for grabbing/handling cached entries, extends acm_file or acm_db depending on the setup
+* @package acm
+*/
+class cache
+{
+ /**
+ * Get config values
+ */
+ public static function obtain_config()
+ {
+ global $db, $cache;
+
+ if (($config = $cache->get('config')) !== false)
+ {
+ $sql = 'SELECT config_name, config_value
+ FROM ' . CONFIG_TABLE . '
+ WHERE is_dynamic = 1';
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $config[$row['config_name']] = $row['config_value'];
+ }
+ $db->sql_freeresult($result);
+ }
+ else
+ {
+ $config = $cached_config = array();
+
+ $sql = 'SELECT config_name, config_value, is_dynamic
+ FROM ' . CONFIG_TABLE;
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ if (!$row['is_dynamic'])
+ {
+ $cached_config[$row['config_name']] = $row['config_value'];
+ }
+
+ $config[$row['config_name']] = $row['config_value'];
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('config', $cached_config);
+ }
+
+ return $config;
+ }
+
+ /**
+ * Obtain list of naughty words and build preg style replacement arrays for use by the
+ * calling script
+ */
+ public static function obtain_word_list()
+ {
+ global $cache;
+
+ if (($censors = $cache->get('_word_censors')) === false)
+ {
+ global $db;
+
+ $sql = 'SELECT word, replacement
+ FROM ' . WORDS_TABLE;
+ $result = $db->sql_query($sql);
+
+ $censors = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $censors['match'][] = '#(?<!\w)(' . str_replace('\*', '\w*?', preg_quote($row['word'], '#')) . ')(?!\w)#i';
+ $censors['replace'][] = $row['replacement'];
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('_word_censors', $censors);
+ }
+
+ return $censors;
+ }
+
+ /**
+ * Obtain currently listed icons
+ */
+ public static function obtain_icons()
+ {
+ global $cache;
+
+ if (($icons = $cache->get('_icons')) === false)
+ {
+ global $db;
+
+ // Topic icons
+ $sql = 'SELECT *
+ FROM ' . ICONS_TABLE . '
+ ORDER BY icons_order';
+ $result = $db->sql_query($sql);
+
+ $icons = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $icons[$row['icons_id']]['img'] = $row['icons_url'];
+ $icons[$row['icons_id']]['width'] = (int) $row['icons_width'];
+ $icons[$row['icons_id']]['height'] = (int) $row['icons_height'];
+ $icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting'];
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('_icons', $icons);
+ }
+
+ return $icons;
+ }
+
+ /**
+ * Obtain ranks
+ */
+ public static function obtain_ranks()
+ {
+ global $cache;
+
+ if (($ranks = $cache->get('_ranks')) === false)
+ {
+ global $db;
+
+ $sql = 'SELECT *
+ FROM ' . RANKS_TABLE . '
+ ORDER BY rank_min DESC';
+ $result = $db->sql_query($sql);
+
+ $ranks = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ if ($row['rank_special'])
+ {
+ $ranks['special'][$row['rank_id']] = array(
+ 'rank_title' => $row['rank_title'],
+ 'rank_image' => $row['rank_image']
+ );
+ }
+ else
+ {
+ $ranks['normal'][] = array(
+ 'rank_title' => $row['rank_title'],
+ 'rank_min' => $row['rank_min'],
+ 'rank_image' => $row['rank_image']
+ );
+ }
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('_ranks', $ranks);
+ }
+
+ return $ranks;
+ }
+
+ /**
+ * Obtain allowed extensions
+ *
+ * @param mixed $forum_id If false then check for private messaging, if int then check for forum id. If true, then only return extension informations.
+ *
+ * @return array allowed extensions array.
+ */
+ public static function obtain_attach_extensions($forum_id)
+ {
+ global $cache;
+
+ if (($extensions = $cache->get('_extensions')) === false)
+ {
+ global $db;
+
+ $extensions = array(
+ '_allowed_post' => array(),
+ '_allowed_pm' => array(),
+ );
+
+ // The rule is to only allow those extensions defined. ;)
+ $sql = 'SELECT e.extension, g.*
+ FROM ' . EXTENSIONS_TABLE . ' e, ' . EXTENSION_GROUPS_TABLE . ' g
+ WHERE e.group_id = g.group_id
+ AND (g.allow_group = 1 OR g.allow_in_pm = 1)';
+ $result = $db->sql_query($sql);
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $extension = strtolower(trim($row['extension']));
+
+ $extensions[$extension] = array(
+ 'display_cat' => (int) $row['cat_id'],
+ 'download_mode' => (int) $row['download_mode'],
+ 'upload_icon' => trim($row['upload_icon']),
+ 'max_filesize' => (int) $row['max_filesize'],
+ 'allow_group' => $row['allow_group'],
+ 'allow_in_pm' => $row['allow_in_pm'],
+ );
+
+ $allowed_forums = ($row['allowed_forums']) ? unserialize(trim($row['allowed_forums'])) : array();
+
+ // Store allowed extensions forum wise
+ if ($row['allow_group'])
+ {
+ $extensions['_allowed_post'][$extension] = (!sizeof($allowed_forums)) ? 0 : $allowed_forums;
+ }
+
+ if ($row['allow_in_pm'])
+ {
+ $extensions['_allowed_pm'][$extension] = 0;
+ }
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('_extensions', $extensions);
+ }
+
+ // Forum post
+ if ($forum_id === false)
+ {
+ // We are checking for private messages, therefore we only need to get the pm extensions...
+ $return = array('_allowed_' => array());
+
+ foreach ($extensions['_allowed_pm'] as $extension => $check)
+ {
+ $return['_allowed_'][$extension] = 0;
+ $return[$extension] = $extensions[$extension];
+ }
+
+ $extensions = $return;
+ }
+ else if ($forum_id === true)
+ {
+ return $extensions;
+ }
+ else
+ {
+ $forum_id = (int) $forum_id;
+ $return = array('_allowed_' => array());
+
+ foreach ($extensions['_allowed_post'] as $extension => $check)
+ {
+ // Check for allowed forums
+ if (is_array($check))
+ {
+ $allowed = (!in_array($forum_id, $check)) ? false : true;
+ }
+ else
+ {
+ $allowed = true;
+ }
+
+ if ($allowed)
+ {
+ $return['_allowed_'][$extension] = 0;
+ $return[$extension] = $extensions[$extension];
+ }
+ }
+
+ $extensions = $return;
+ }
+
+ if (!isset($extensions['_allowed_']))
+ {
+ $extensions['_allowed_'] = array();
+ }
+
+ return $extensions;
+ }
+
+ /**
+ * Obtain active bots
+ */
+ public static function obtain_bots()
+ {
+ global $cache;
+
+ if (($bots = $cache->get('_bots')) === false)
+ {
+ global $db;
+
+ $sql = 'SELECT user_id, bot_agent, bot_ip
+ FROM ' . BOTS_TABLE . '
+ WHERE bot_active = 1
+ ORDER BY ' . $db->sql_function('length_varchar', 'bot_agent') . 'DESC';
+ $result = $db->sql_query($sql);
+
+ $bots = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $bots[] = $row;
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('_bots', $bots);
+ }
+
+ return $bots;
+ }
+
+ /**
+ * Obtain cfg file data
+ *
+ * @param array $theme An array containing the path to the item
+ *
+ * @param string $item The specific item to get: 'theme', 'template', or 'imageset'
+ *
+ */
+ public static function obtain_cfg_item($theme, $item = 'theme')
+ {
+ global $config, $cache;
+
+ $parsed_array = $cache->get('_cfg_' . $item . '_' . $theme[$item . '_path']);
+
+ if ($parsed_array === false)
+ {
+ $parsed_array = array();
+ }
+
+ $reparse = false;
+ $filename = PHPBB_ROOT_PATH . 'styles/' . $theme[$item . '_path'] . '/' . $item . '/' . $item . '.cfg';
+
+ if (!file_exists($filename))
+ {
+ return $parsed_array;
+ }
+
+ if (!isset($parsed_array['filetime']) || (($config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime'])))
+ {
+ $reparse = true;
+ }
+
+ // Re-parse cfg file
+ if ($reparse)
+ {
+ $parsed_array = parse_cfg_file($filename);
+ $parsed_array['filetime'] = @filemtime($filename);
+
+ $cache->put('_cfg_' . $item . '_' . $theme[$item . '_path'], $parsed_array);
+ }
+
+ return $parsed_array;
+ }
+
+ /**
+ * Obtain disallowed usernames
+ */
+ public static function obtain_disallowed_usernames()
+ {
+ global $cache;
+
+ if (($usernames = $cache->get('_disallowed_usernames')) === false)
+ {
+ global $db;
+
+ $sql = 'SELECT disallow_username
+ FROM ' . DISALLOW_TABLE;
+ $result = $db->sql_query($sql);
+
+ $usernames = array();
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $usernames[] = str_replace('%', '.*?', preg_quote(utf8_clean_string($row['disallow_username']), '#'));
+ }
+ $db->sql_freeresult($result);
+
+ $cache->put('_disallowed_usernames', $usernames);
+ }
+
+ return $usernames;
+ }
+
+ /**
+ * Obtain hooks...
+ */
+ public static function obtain_hooks()
+ {
+ global $cache;
+
+ if (($hook_files = $cache->get('_hooks')) === false)
+ {
+ $hook_files = array();
+
+ // Now search for hooks...
+ $dh = @opendir(PHPBB_ROOT_PATH . 'includes/hooks/');
+
+ if ($dh)
+ {
+ while (($file = readdir($dh)) !== false)
+ {
+ if (strpos($file, 'hook_') === 0 && substr($file, -(strlen(PHP_EXT) + 1)) === '.' . PHP_EXT)
+ {
+ $hook_files[] = substr($file, 0, -(strlen(PHP_EXT) + 1));
+ }
+ }
+ closedir($dh);
+ }
+
+ $cache->put('_hooks', $hook_files);
+ }
+
+ return $hook_files;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/phpBB/includes/classes/session.php b/phpBB/includes/classes/session.php
new file mode 100644
index 0000000000..7e7fbfb8ac
--- /dev/null
+++ b/phpBB/includes/classes/session.php
@@ -0,0 +1,2187 @@
+<?php
+/**
+*
+* @package phpBB3
+* @version $Id$
+* @copyright (c) 2005 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Session class
+* @package phpBB3
+*/
+class session
+{
+ var $cookie_data = array();
+ var $page = array();
+ var $data = array();
+ var $browser = '';
+ var $forwarded_for = '';
+ var $host = '';
+ var $session_id = '';
+ var $ip = '';
+ var $load = 0;
+ var $time_now = 0;
+ var $update_session_page = true;
+
+ /**
+ * Extract current session page
+ *
+ * @param string $root_path current root path (phpbb_root_path)
+ */
+ public static function extract_current_page($root_path)
+ {
+ $page_array = array();
+
+ // First of all, get the request uri...
+ $script_name = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : getenv('PHP_SELF');
+ $args = (!empty($_SERVER['QUERY_STRING'])) ? explode('&', $_SERVER['QUERY_STRING']) : explode('&', getenv('QUERY_STRING'));
+
+ // If we are unable to get the script name we use REQUEST_URI as a failover and note it within the page array for easier support...
+ if (!$script_name)
+ {
+ $script_name = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : getenv('REQUEST_URI');
+ $script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name;
+ $page_array['failover'] = 1;
+ }
+
+ // Replace backslashes and doubled slashes (could happen on some proxy setups)
+ $script_name = str_replace(array('\\', '//'), '/', $script_name);
+
+ // Now, remove the sid and let us get a clean query string...
+ $use_args = array();
+
+ // Since some browser do not encode correctly we need to do this with some "special" characters...
+ // " -> %22, ' => %27, < -> %3C, > -> %3E
+ $find = array('"', "'", '<', '>');
+ $replace = array('%22', '%27', '%3C', '%3E');
+
+ foreach ($args as $argument)
+ {
+ if (strpos($argument, 'sid=') === 0)
+ {
+ continue;
+ }
+
+ $use_args[] = str_replace($find, $replace, $argument);
+ }
+ unset($args);
+
+ // The following examples given are for an request uri of {path to the phpbb directory}/adm/index.php?i=10&b=2
+
+ // The current query string
+ $query_string = trim(implode('&', $use_args));
+
+ // basenamed page name (for example: index.php)
+ $page_name = basename($script_name);
+ $page_name = urlencode(htmlspecialchars($page_name));
+
+ // current directory within the phpBB root (for example: adm)
+ $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($root_path)));
+ $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath('./')));
+ $intersection = array_intersect_assoc($root_dirs, $page_dirs);
+
+ $root_dirs = array_diff_assoc($root_dirs, $intersection);
+ $page_dirs = array_diff_assoc($page_dirs, $intersection);
+
+ $page_dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
+
+ if ($page_dir && substr($page_dir, -1, 1) == '/')
+ {
+ $page_dir = substr($page_dir, 0, -1);
+ }
+
+ // Current page from phpBB root (for example: adm/index.php?i=10&b=2)
+ $page = (($page_dir) ? $page_dir . '/' : '') . $page_name . (($query_string) ? "?$query_string" : '');
+
+ // The script path from the webroot to the current directory (for example: /phpBB3/adm/) : always prefixed with / and ends in /
+ $script_path = trim(str_replace('\\', '/', dirname($script_name)));
+
+ // The script path from the webroot to the phpBB root (for example: /phpBB3/)
+ $script_dirs = explode('/', $script_path);
+ array_splice($script_dirs, -sizeof($page_dirs));
+ $root_script_path = implode('/', $script_dirs) . (sizeof($root_dirs) ? '/' . implode('/', $root_dirs) : '');
+
+ // We are on the base level (phpBB root == webroot), lets adjust the variables a bit...
+ if (!$root_script_path)
+ {
+ $root_script_path = ($page_dir) ? str_replace($page_dir, '', $script_path) : $script_path;
+ }
+
+ $script_path .= (substr($script_path, -1, 1) == '/') ? '' : '/';
+ $root_script_path .= (substr($root_script_path, -1, 1) == '/') ? '' : '/';
+
+ $page_array += array(
+ 'page_name' => $page_name,
+ 'page_dir' => $page_dir,
+
+ 'query_string' => $query_string,
+ 'script_path' => str_replace(' ', '%20', htmlspecialchars($script_path)),
+ 'root_script_path' => str_replace(' ', '%20', htmlspecialchars($root_script_path)),
+
+ 'page' => $page
+ );
+
+ return $page_array;
+ }
+
+ /**
+ * Get valid hostname/port. HTTP_HOST is used, SERVER_NAME if HTTP_HOST not present.
+ */
+ function extract_current_hostname()
+ {
+ global $config;
+
+ // Get hostname
+ $host = (!empty($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : ((!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME'));
+
+ // Should be a string and lowered
+ $host = (string) strtolower($host);
+
+ // If host is equal the cookie domain or the server name (if config is set), then we assume it is valid
+ if ((isset($config['cookie_domain']) && $host === $config['cookie_domain']) || (isset($config['server_name']) && $host === $config['server_name']))
+ {
+ return $host;
+ }
+
+ // Is the host actually a IP? If so, we use the IP... (IPv4)
+ if (long2ip(ip2long($host)) === $host)
+ {
+ return $host;
+ }
+
+ // Now return the hostname (this also removes any port definition). The http:// is prepended to construct a valid URL, hosts never have a scheme assigned
+ $host = @parse_url('http://' . $host, PHP_URL_HOST);
+
+ // Remove any portions not removed by parse_url (#)
+ $host = str_replace('#', '', $host);
+
+ // If, by any means, the host is now empty, we will use a "best approach" way to guess one
+ if (empty($host))
+ {
+ if (!empty($config['server_name']))
+ {
+ $host = $config['server_name'];
+ }
+ else if (!empty($config['cookie_domain']))
+ {
+ $host = (strpos($config['cookie_domain'], '.') === 0) ? substr($config['cookie_domain'], 1) : $config['cookie_domain'];
+ }
+ else
+ {
+ // Set to OS hostname or localhost
+ $host = (function_exists('php_uname')) ? php_uname('n') : 'localhost';
+ }
+ }
+
+ // It may be still no valid host, but for sure only a hostname (we may further expand on the cookie domain... if set)
+ return $host;
+ }
+
+ /**
+ * Start session management
+ *
+ * This is where all session activity begins. We gather various pieces of
+ * information from the client and server. We test to see if a session already
+ * exists. If it does, fine and dandy. If it doesn't we'll go on to create a
+ * new one ... pretty logical heh? We also examine the system load (if we're
+ * running on a system which makes such information readily available) and
+ * halt if it's above an admin definable limit.
+ *
+ * @param bool $update_session_page if true the session page gets updated.
+ * This can be set to circumvent certain scripts to update the users last visited page.
+ */
+ function session_begin($update_session_page = true)
+ {
+ global $SID, $_SID, $_EXTRA_URL, $db, $config;
+
+ // Give us some basic information
+ $this->time_now = time();
+ $this->cookie_data = array('u' => 0, 'k' => '');
+ $this->update_session_page = $update_session_page;
+ $this->browser = (!empty($_SERVER['HTTP_USER_AGENT'])) ? htmlspecialchars((string) $_SERVER['HTTP_USER_AGENT']) : '';
+ $this->referer = (!empty($_SERVER['HTTP_REFERER'])) ? htmlspecialchars((string) $_SERVER['HTTP_REFERER']) : '';
+ $this->forwarded_for = (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) ? (string) $_SERVER['HTTP_X_FORWARDED_FOR'] : '';
+
+ $this->host = self::extract_current_hostname();
+ $this->page = self::extract_current_page(PHPBB_ROOT_PATH);
+
+ // if the forwarded for header shall be checked we have to validate its contents
+ if ($config['forwarded_for_check'])
+ {
+ $this->forwarded_for = preg_replace('#, +#', ', ', $this->forwarded_for);
+
+ // split the list of IPs
+ $ips = explode(', ', $this->forwarded_for);
+ foreach ($ips as $ip)
+ {
+ // check IPv4 first, the IPv6 is hopefully only going to be used very seldomly
+ if (!empty($ip) && !preg_match(get_preg_expression('ipv4'), $ip) && !preg_match(get_preg_expression('ipv6'), $ip))
+ {
+ // contains invalid data, don't use the forwarded for header
+ $this->forwarded_for = '';
+ break;
+ }
+ }
+ }
+ else
+ {
+ $this->forwarded_for = '';
+ }
+
+ // Add forum to the page for tracking online users - also adding a "x" to the end to properly identify the number
+ $forum = request_var('f', 0);
+ $this->page['page'] .= ($forum) ? ((strpos($this->page['page'], '?') !== false) ? '&' : '?') . '_f_=' . $forum . 'x' : '';
+
+ if (request::is_set($config['cookie_name'] . '_sid', request::COOKIE) || request::is_set($config['cookie_name'] . '_u', request::COOKIE))
+ {
+ $this->cookie_data['u'] = request_var($config['cookie_name'] . '_u', 0, false, true);
+ $this->cookie_data['k'] = request_var($config['cookie_name'] . '_k', '', false, true);
+ $this->session_id = request_var($config['cookie_name'] . '_sid', '', false, true);
+
+ $SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid=';
+ $_SID = (defined('NEED_SID')) ? $this->session_id : '';
+
+ if (empty($this->session_id))
+ {
+ $this->session_id = $_SID = request_var('sid', '');
+ $SID = '?sid=' . $this->session_id;
+ $this->cookie_data = array('u' => 0, 'k' => '');
+ }
+ }
+ else
+ {
+ $this->session_id = $_SID = request_var('sid', '');
+ $SID = '?sid=' . $this->session_id;
+ }
+
+ $_EXTRA_URL = array();
+
+ // Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests
+ // it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip.
+ $this->ip = (!empty($_SERVER['REMOTE_ADDR'])) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '';
+ $this->load = false;
+
+ // Load limit check (if applicable)
+ if ($config['limit_load'] || $config['limit_search_load'])
+ {
+ if ((function_exists('sys_getloadavg') && $load = sys_getloadavg()) || ($load = explode(' ', @file_get_contents('/proc/loadavg'))))
+ {
+ $this->load = array_slice($load, 0, 1);
+ $this->load = floatval($this->load[0]);
+ }
+ else
+ {
+ set_config('limit_load', '0');
+ set_config('limit_search_load', '0');
+ }
+ }
+
+ // Is session_id is set or session_id is set and matches the url param if required
+ if (!empty($this->session_id) && (!defined('NEED_SID') || $this->session_id === request::variable('sid', '', false, request::GET)))
+ {
+ $sql = 'SELECT u.*, s.*
+ FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u
+ WHERE s.session_id = '" . $db->sql_escape($this->session_id) . "'
+ AND u.user_id = s.session_user_id";
+ $result = $db->sql_query($sql);
+ $this->data = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+
+ // Did the session exist in the DB?
+ if (isset($this->data['user_id']))
+ {
+ // Validate IP length according to admin ... enforces an IP
+ // check on bots if admin requires this
+// $quadcheck = ($config['ip_check_bot'] && $this->data['user_type'] & USER_BOT) ? 4 : $config['ip_check'];
+
+ if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false)
+ {
+ $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']);
+ $u_ip = short_ipv6($this->ip, $config['ip_check']);
+ }
+ else
+ {
+ $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check']));
+ $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check']));
+ }
+
+ $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : '';
+ $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : '';
+
+ $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : '';
+ $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : '';
+
+ // referer checks
+ $check_referer_path = $config['referer_validation'] == REFERER_VALIDATE_PATH;
+ $referer_valid = true;
+ // we assume HEAD and TRACE to be foul play and thus only whitelist GET
+ if ($config['referer_validation'] && isset($_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD']) !== 'get')
+ {
+ $referer_valid = $this->validate_referer($check_referer_path);
+ }
+
+
+ if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid)
+ {
+ $session_expired = false;
+
+ // Check whether the session is still valid if we have one
+ $method = basename(trim($config['auth_method']));
+ include_once(PHPBB_ROOT_PATH . 'includes/auth/auth_' . $method . '.' . PHP_EXT);
+
+ $method = 'validate_session_' . $method;
+ if (function_exists($method))
+ {
+ if (!$method($this->data))
+ {
+ $session_expired = true;
+ }
+ }
+
+ if (!$session_expired)
+ {
+ // Check the session length timeframe if autologin is not enabled.
+ // Else check the autologin length... and also removing those having autologin enabled but no longer allowed board-wide.
+ if (!$this->data['session_autologin'])
+ {
+ if ($this->data['session_time'] < $this->time_now - ($config['session_length'] + 60))
+ {
+ $session_expired = true;
+ }
+ }
+ else if (!$config['allow_autologin'] || ($config['max_autologin_time'] && $this->data['session_time'] < $this->time_now - (86400 * (int) $config['max_autologin_time']) + 60))
+ {
+ $session_expired = true;
+ }
+ }
+
+ if (!$session_expired)
+ {
+ // Only update session DB a minute or so after last update or if page changes
+ if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page']))
+ {
+ $sql_ary = array('session_time' => $this->time_now);
+
+ if ($this->update_session_page)
+ {
+ $sql_ary['session_page'] = substr($this->page['page'], 0, 199);
+ }
+
+ $db->sql_return_on_error(true);
+
+ $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
+ WHERE session_id = '" . $db->sql_escape($this->session_id) . "'";
+ $result = $db->sql_query($sql);
+
+ $db->sql_return_on_error(false);
+
+ // If the database is not yet updated, there will be an error due to the session_forum_id
+ // @todo REMOVE for 3.0.2
+ if ($result === false)
+ {
+ unset($sql_ary['session_forum_id']);
+
+ $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
+ WHERE session_id = '" . $db->sql_escape($this->session_id) . "'";
+ $db->sql_query($sql);
+ }
+ }
+
+ $this->data['is_registered'] = ($this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false;
+ $this->data['is_bot'] = (!$this->data['is_registered'] && $this->data['user_id'] != ANONYMOUS) ? true : false;
+ $this->data['user_lang'] = basename($this->data['user_lang']);
+
+ return true;
+ }
+ }
+ else
+ {
+ // Added logging temporarly to help debug bugs...
+ if (defined('DEBUG_EXTRA') && $this->data['user_id'] != ANONYMOUS)
+ {
+ if ($referer_valid)
+ {
+ add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, htmlspecialchars($u_forwarded_for), htmlspecialchars($s_forwarded_for));
+ }
+ else
+ {
+ add_log('critical', 'LOG_REFERER_INVALID', $this->referer);
+ }
+ }
+ }
+ }
+ }
+
+ // If we reach here then no (valid) session exists. So we'll create a new one
+ return $this->session_create();
+ }
+
+ /**
+ * Create a new session
+ *
+ * If upon trying to start a session we discover there is nothing existing we
+ * jump here. Additionally this method is called directly during login to regenerate
+ * the session for the specific user. In this method we carry out a number of tasks;
+ * garbage collection, (search)bot checking, banned user comparison. Basically
+ * though this method will result in a new session for a specific user.
+ */
+ function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true)
+ {
+ global $SID, $_SID, $db, $config, $cache;
+
+ $this->data = array();
+
+ /* Garbage collection ... remove old sessions updating user information
+ // if necessary. It means (potentially) 11 queries but only infrequently
+ if ($this->time_now > $config['session_last_gc'] + $config['session_gc'])
+ {
+ $this->session_gc();
+ }*/
+
+ // Do we allow autologin on this board? No? Then override anything
+ // that may be requested here
+ if (!$config['allow_autologin'])
+ {
+ $this->cookie_data['k'] = $persist_login = false;
+ }
+
+ /**
+ * Here we do a bot check, oh er saucy! No, not that kind of bot
+ * check. We loop through the list of bots defined by the admin and
+ * see if we have any useragent and/or IP matches. If we do, this is a
+ * bot, act accordingly
+ */
+ $bot = false;
+ $active_bots = cache::obtain_bots();
+
+ foreach ($active_bots as $row)
+ {
+ if ($row['bot_agent'] && preg_match('#' . str_replace('\*', '.*?', preg_quote($row['bot_agent'], '#')) . '#i', $this->browser))
+ {
+ $bot = $row['user_id'];
+ }
+
+ // If ip is supplied, we will make sure the ip is matching too...
+ if ($row['bot_ip'] && ($bot || !$row['bot_agent']))
+ {
+ // Set bot to false, then we only have to set it to true if it is matching
+ $bot = false;
+
+ foreach (explode(',', $row['bot_ip']) as $bot_ip)
+ {
+ if (strpos($this->ip, $bot_ip) === 0)
+ {
+ $bot = (int) $row['user_id'];
+ break;
+ }
+ }
+ }
+
+ if ($bot)
+ {
+ break;
+ }
+ }
+
+ $method = basename(trim($config['auth_method']));
+ include_once(PHPBB_ROOT_PATH . 'includes/auth/auth_' . $method . '.' . PHP_EXT);
+
+ $method = 'autologin_' . $method;
+ if (function_exists($method))
+ {
+ $this->data = $method();
+
+ if (sizeof($this->data))
+ {
+ $this->cookie_data['k'] = '';
+ $this->cookie_data['u'] = $this->data['user_id'];
+ }
+ }
+
+ // If we're presented with an autologin key we'll join against it.
+ // Else if we've been passed a user_id we'll grab data based on that
+ if (isset($this->cookie_data['k']) && $this->cookie_data['k'] && $this->cookie_data['u'] && !sizeof($this->data))
+ {
+ $sql = 'SELECT u.*
+ FROM ' . USERS_TABLE . ' u, ' . SESSIONS_KEYS_TABLE . ' k
+ WHERE u.user_id = ' . (int) $this->cookie_data['u'] . '
+ AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ")
+ AND k.user_id = u.user_id
+ AND k.key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'";
+ $result = $db->sql_query($sql);
+ $this->data = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+ $bot = false;
+ }
+ else if ($user_id !== false && !sizeof($this->data))
+ {
+ $this->cookie_data['k'] = '';
+ $this->cookie_data['u'] = $user_id;
+
+ $sql = 'SELECT *
+ FROM ' . USERS_TABLE . '
+ WHERE user_id = ' . (int) $this->cookie_data['u'] . '
+ AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')';
+ $result = $db->sql_query($sql);
+ $this->data = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+ $bot = false;
+ }
+
+ // If no data was returned one or more of the following occurred:
+ // Key didn't match one in the DB
+ // User does not exist
+ // User is inactive
+ // User is bot
+ if (!sizeof($this->data) || !is_array($this->data))
+ {
+ $this->cookie_data['k'] = '';
+ $this->cookie_data['u'] = ($bot) ? $bot : ANONYMOUS;
+
+ if (!$bot)
+ {
+ $sql = 'SELECT *
+ FROM ' . USERS_TABLE . '
+ WHERE user_id = ' . (int) $this->cookie_data['u'];
+ }
+ else
+ {
+ // We give bots always the same session if it is not yet expired.
+ $sql = 'SELECT u.*, s.*
+ FROM ' . USERS_TABLE . ' u
+ LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id)
+ WHERE u.user_id = ' . (int) $bot;
+ }
+
+ $result = $db->sql_query($sql);
+ $this->data = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+ }
+
+ if ($this->data['user_id'] != ANONYMOUS && !$bot)
+ {
+ $this->data['session_last_visit'] = (isset($this->data['session_time']) && $this->data['session_time']) ? $this->data['session_time'] : (($this->data['user_lastvisit']) ? $this->data['user_lastvisit'] : time());
+ }
+ else
+ {
+ $this->data['session_last_visit'] = $this->time_now;
+ }
+
+ // Force user id to be integer...
+ $this->data['user_id'] = (int) $this->data['user_id'];
+
+ // At this stage we should have a filled data array, defined cookie u and k data.
+ // data array should contain recent session info if we're a real user and a recent
+ // session exists in which case session_id will also be set
+
+ // Is user banned? Are they excluded? Won't return on ban, exists within method
+ if ($this->data['user_type'] != USER_FOUNDER)
+ {
+ if (!$config['forwarded_for_check'])
+ {
+ $this->check_ban($this->data['user_id'], $this->ip);
+ }
+ else
+ {
+ $ips = explode(', ', $this->forwarded_for);
+ $ips[] = $this->ip;
+ $this->check_ban($this->data['user_id'], $ips);
+ }
+ }
+
+ $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false;
+ $this->data['is_bot'] = ($bot) ? true : false;
+
+ // If our friend is a bot, we re-assign a previously assigned session
+ if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id'])
+ {
+ // Only assign the current session if the ip, browser and forwarded_for match...
+ if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false)
+ {
+ $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']);
+ $u_ip = short_ipv6($this->ip, $config['ip_check']);
+ }
+ else
+ {
+ $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check']));
+ $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check']));
+ }
+
+ $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : '';
+ $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : '';
+
+ $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : '';
+ $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : '';
+
+ if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for)
+ {
+ $this->session_id = $this->data['session_id'];
+
+ // Only update session DB a minute or so after last update or if page changes
+ if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page']))
+ {
+ $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now;
+
+ $sql_ary = array('session_time' => $this->time_now, 'session_last_visit' => $this->time_now, 'session_admin' => 0);
+
+ if ($this->update_session_page)
+ {
+ $sql_ary['session_page'] = substr($this->page['page'], 0, 199);
+ }
+
+ $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
+ WHERE session_id = '" . $db->sql_escape($this->session_id) . "'";
+ $db->sql_query($sql);
+
+ // Update the last visit time
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET user_lastvisit = ' . (int) $this->data['session_time'] . '
+ WHERE user_id = ' . (int) $this->data['user_id'];
+ $db->sql_query($sql);
+ }
+
+ $SID = '?sid=';
+ $_SID = '';
+ return true;
+ }
+ else
+ {
+ // If the ip and browser does not match make sure we only have one bot assigned to one session
+ $db->sql_query('DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $this->data['user_id']);
+ }
+ }
+
+ $session_autologin = (($this->cookie_data['k'] || $persist_login) && $this->data['is_registered']) ? true : false;
+ $set_admin = ($set_admin && $this->data['is_registered']) ? true : false;
+
+ // Create or update the session
+ $sql_ary = array(
+ 'session_user_id' => (int) $this->data['user_id'],
+ 'session_start' => (int) $this->time_now,
+ 'session_last_visit' => (int) $this->data['session_last_visit'],
+ 'session_time' => (int) $this->time_now,
+ 'session_browser' => (string) trim(substr($this->browser, 0, 149)),
+ 'session_forwarded_for' => (string) $this->forwarded_for,
+ 'session_ip' => (string) $this->ip,
+ 'session_autologin' => ($session_autologin) ? 1 : 0,
+ 'session_admin' => ($set_admin) ? 1 : 0,
+ 'session_viewonline' => ($viewonline) ? 1 : 0,
+ );
+
+ if ($this->update_session_page)
+ {
+ $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199);
+ }
+
+ $db->sql_return_on_error(true);
+
+ $sql = 'DELETE
+ FROM ' . SESSIONS_TABLE . '
+ WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\'
+ AND session_user_id = ' . ANONYMOUS;
+
+ if (!defined('IN_ERROR_HANDLER') && (!$this->session_id || !$db->sql_query($sql) || !$db->sql_affectedrows()))
+ {
+ // Limit new sessions in 1 minute period (if required)
+ if (empty($this->data['session_time']) && $config['active_sessions'])
+ {
+ $sql = 'SELECT COUNT(session_id) AS sessions
+ FROM ' . SESSIONS_TABLE . '
+ WHERE session_time >= ' . ($this->time_now - 60);
+ $result = $db->sql_query($sql);
+ $row = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+
+ if ((int) $row['sessions'] > (int) $config['active_sessions'])
+ {
+ header('HTTP/1.1 503 Service Unavailable');
+ trigger_error('BOARD_UNAVAILABLE');
+ }
+ }
+ }
+
+ $this->session_id = $this->data['session_id'] = md5(unique_id());
+
+ $sql_ary['session_id'] = (string) $this->session_id;
+ $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199);
+
+ $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
+ $db->sql_query($sql);
+
+ $db->sql_return_on_error(false);
+
+ // Regenerate autologin/persistent login key
+ if ($session_autologin)
+ {
+ $this->set_login_key();
+ }
+
+ // refresh data
+ $SID = '?sid=' . $this->session_id;
+ $_SID = $this->session_id;
+ $this->data = array_merge($this->data, $sql_ary);
+
+ if (!$bot)
+ {
+ $cookie_expire = $this->time_now + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000);
+
+ $this->set_cookie('u', $this->cookie_data['u'], $cookie_expire);
+ $this->set_cookie('k', $this->cookie_data['k'], $cookie_expire);
+ $this->set_cookie('sid', $this->session_id, $cookie_expire);
+
+ unset($cookie_expire);
+
+ $sql = 'SELECT COUNT(session_id) AS sessions
+ FROM ' . SESSIONS_TABLE . '
+ WHERE session_user_id = ' . (int) $this->data['user_id'] . '
+ AND session_time >= ' . (int) ($this->time_now - (max($config['session_length'], $config['form_token_lifetime'])));
+ $result = $db->sql_query($sql);
+ $row = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+
+ if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt']))
+ {
+ $this->data['user_form_salt'] = unique_id();
+ // Update the form key
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\'
+ WHERE user_id = ' . (int) $this->data['user_id'];
+ $db->sql_query($sql);
+ }
+ }
+ else
+ {
+ $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now;
+
+ // Update the last visit time
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET user_lastvisit = ' . (int) $this->data['session_time'] . '
+ WHERE user_id = ' . (int) $this->data['user_id'];
+ $db->sql_query($sql);
+
+ $SID = '?sid=';
+ $_SID = '';
+ }
+
+ return true;
+ }
+
+ /**
+ * Kills a session
+ *
+ * This method does what it says on the tin. It will delete a pre-existing session.
+ * It resets cookie information (destroying any autologin key within that cookie data)
+ * and update the users information from the relevant session data. It will then
+ * grab guest user information.
+ */
+ function session_kill($new_session = true)
+ {
+ global $SID, $_SID, $db, $config;
+
+ $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
+ WHERE session_id = '" . $db->sql_escape($this->session_id) . "'
+ AND session_user_id = " . (int) $this->data['user_id'];
+ $db->sql_query($sql);
+
+ // Allow connecting logout with external auth method logout
+ $method = basename(trim($config['auth_method']));
+ include_once(PHPBB_ROOT_PATH . 'includes/auth/auth_' . $method . '.' . PHP_EXT);
+
+ $method = 'logout_' . $method;
+ if (function_exists($method))
+ {
+ $method($this->data, $new_session);
+ }
+
+ if ($this->data['user_id'] != ANONYMOUS)
+ {
+ // Delete existing session, update last visit info first!
+ if (!isset($this->data['session_time']))
+ {
+ $this->data['session_time'] = time();
+ }
+
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET user_lastvisit = ' . (int) $this->data['session_time'] . '
+ WHERE user_id = ' . (int) $this->data['user_id'];
+ $db->sql_query($sql);
+
+ if ($this->cookie_data['k'])
+ {
+ $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
+ WHERE user_id = ' . (int) $this->data['user_id'] . "
+ AND key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'";
+ $db->sql_query($sql);
+ }
+
+ // Reset the data array
+ $this->data = array();
+
+ $sql = 'SELECT *
+ FROM ' . USERS_TABLE . '
+ WHERE user_id = ' . ANONYMOUS;
+ $result = $db->sql_query($sql);
+ $this->data = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+ }
+
+ $cookie_expire = $this->time_now - 31536000;
+ $this->set_cookie('u', '', $cookie_expire);
+ $this->set_cookie('k', '', $cookie_expire);
+ $this->set_cookie('sid', '', $cookie_expire);
+ unset($cookie_expire);
+
+ $SID = '?sid=';
+ $this->session_id = $_SID = '';
+
+ // To make sure a valid session is created we create one for the anonymous user
+ if ($new_session)
+ {
+ $this->session_create(ANONYMOUS);
+ }
+
+ return true;
+ }
+
+ /**
+ * Session garbage collection
+ *
+ * This looks a lot more complex than it really is. Effectively we are
+ * deleting any sessions older than an admin definable limit. Due to the
+ * way in which we maintain session data we have to ensure we update user
+ * data before those sessions are destroyed. In addition this method
+ * removes autologin key information that is older than an admin defined
+ * limit.
+ */
+ function session_gc()
+ {
+ global $db, $config;
+
+ $batch_size = 10;
+
+ if (!$this->time_now)
+ {
+ $this->time_now = time();
+ }
+
+ // Firstly, delete guest sessions
+ $sql = 'DELETE FROM ' . SESSIONS_TABLE . '
+ WHERE session_user_id = ' . ANONYMOUS . '
+ AND session_time < ' . (int) ($this->time_now - $config['session_length']);
+ $db->sql_query($sql);
+
+ // Get expired sessions, only most recent for each user
+ $sql = 'SELECT session_user_id, session_page, MAX(session_time) AS recent_time
+ FROM ' . SESSIONS_TABLE . '
+ WHERE session_time < ' . ($this->time_now - $config['session_length']) . '
+ GROUP BY session_user_id, session_page';
+ $result = $db->sql_query_limit($sql, $batch_size);
+
+ $del_user_id = array();
+ $del_sessions = 0;
+
+ while ($row = $db->sql_fetchrow($result))
+ {
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "'
+ WHERE user_id = " . (int) $row['session_user_id'];
+ $db->sql_query($sql);
+
+ $del_user_id[] = (int) $row['session_user_id'];
+ $del_sessions++;
+ }
+ $db->sql_freeresult($result);
+
+ if (sizeof($del_user_id))
+ {
+ // Delete expired sessions
+ $sql = 'DELETE FROM ' . SESSIONS_TABLE . '
+ WHERE ' . $db->sql_in_set('session_user_id', $del_user_id) . '
+ AND session_time < ' . ($this->time_now - $config['session_length']);
+ $db->sql_query($sql);
+ }
+
+ if ($del_sessions < $batch_size)
+ {
+ // Less than 10 users, update gc timer ... else we want gc
+ // called again to delete other sessions
+ set_config('session_last_gc', $this->time_now, true);
+
+ if ($config['max_autologin_time'])
+ {
+ $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
+ WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time']));
+ $db->sql_query($sql);
+ }
+
+ // only called from CRON; should be a safe workaround until the infrastructure gets going
+ if (!class_exists('captcha_factory'))
+ {
+ include(PHPBB_ROOT_PATH . "includes/captcha/captcha_factory." . PHP_EXT);
+ }
+ captcha_factory::garbage_collect($config['captcha_plugin']);
+ }
+
+ return;
+ }
+
+
+ /**
+ * Sets a cookie
+ *
+ * Sets a cookie of the given name with the specified data for the given length of time. If no time is specified, a session cookie will be set.
+ *
+ * @param string $name Name of the cookie, will be automatically prefixed with the phpBB cookie name. track becomes [cookie_name]_track then.
+ * @param string $cookiedata The data to hold within the cookie
+ * @param int $cookietime The expiration time as UNIX timestamp. If 0 is provided, a session cookie is set.
+ */
+ function set_cookie($name, $cookiedata, $cookietime)
+ {
+ global $config;
+
+ $name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata);
+ $expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime);
+ $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain'];
+
+ header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . '; HttpOnly', false);
+ }
+
+ /**
+ * Check for banned user
+ *
+ * Checks whether the supplied user is banned by id, ip or email. If no parameters
+ * are passed to the method pre-existing session data is used. If $return is false
+ * this routine does not return on finding a banned user, it outputs a relevant
+ * message and stops execution.
+ *
+ * @param string|array $user_ips Can contain a string with one IP or an array of multiple IPs
+ */
+ function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false)
+ {
+ global $config, $db;
+
+ if (defined('IN_CHECK_BAN'))
+ {
+ return;
+ }
+
+ $banned = false;
+ $cache_ttl = 3600;
+ $where_sql = array();
+
+ $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end
+ FROM ' . BANLIST_TABLE . '
+ WHERE ';
+
+ // Determine which entries to check, only return those
+ if ($user_email === false)
+ {
+ $where_sql[] = "ban_email = ''";
+ }
+
+ if ($user_ips === false)
+ {
+ $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)";
+ }
+
+ if ($user_id === false)
+ {
+ $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)';
+ }
+ else
+ {
+ $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0;
+ $_sql = '(ban_userid = ' . $user_id;
+
+ if ($user_email !== false)
+ {
+ $_sql .= " OR ban_email <> ''";
+ }
+
+ if ($user_ips !== false)
+ {
+ $_sql .= " OR ban_ip <> ''";
+ }
+
+ $_sql .= ')';
+
+ $where_sql[] = $_sql;
+ }
+
+ $sql .= (sizeof($where_sql)) ? implode(' AND ', $where_sql) : '';
+ $result = $db->sql_query($sql, $cache_ttl);
+
+ $ban_triggered_by = 'user';
+ while ($row = $db->sql_fetchrow($result))
+ {
+ if ($row['ban_end'] && $row['ban_end'] < time())
+ {
+ continue;
+ }
+
+ $ip_banned = false;
+ if (!empty($row['ban_ip']))
+ {
+ if (!is_array($user_ips))
+ {
+ $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips);
+ }
+ else
+ {
+ foreach ($user_ips as $user_ip)
+ {
+ if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip))
+ {
+ $ip_banned = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) ||
+ $ip_banned ||
+ (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email)))
+ {
+ if (!empty($row['ban_exclude']))
+ {
+ $banned = false;
+ break;
+ }
+ else
+ {
+ $banned = true;
+ $ban_row = $row;
+
+ if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id)
+ {
+ $ban_triggered_by = 'user';
+ }
+ else if ($ip_banned)
+ {
+ $ban_triggered_by = 'ip';
+ }
+ else
+ {
+ $ban_triggered_by = 'email';
+ }
+
+ // Don't break. Check if there is an exclude rule for this user
+ }
+ }
+ }
+ $db->sql_freeresult($result);
+
+ if ($banned && !$return)
+ {
+ global $template;
+
+ // If the session is empty we need to create a valid one...
+ if (empty($this->session_id))
+ {
+ // This seems to be no longer needed? - #14971
+// $this->session_create(ANONYMOUS);
+ }
+
+ // Initiate environment ... since it won't be set at this stage
+ $this->setup();
+
+ // Logout the user, banned users are unable to use the normal 'logout' link
+ if ($this->data['user_id'] != ANONYMOUS)
+ {
+ $this->session_kill();
+ }
+
+ // We show a login box here to allow founders accessing the board if banned by IP
+ if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS)
+ {
+ $this->setup('ucp');
+ $this->data['is_registered'] = $this->data['is_bot'] = false;
+
+ // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again.
+ define('IN_CHECK_BAN', 1);
+
+ login_box('index.' . PHP_EXT);
+
+ // The false here is needed, else the user is able to circumvent the ban.
+ $this->session_kill(false);
+ }
+
+ // Ok, we catch the case of an empty session id for the anonymous user...
+ // This can happen if the user is logging in, banned by username and the login_box() being called "again".
+ if (empty($this->session_id) && defined('IN_CHECK_BAN'))
+ {
+ $this->session_create(ANONYMOUS);
+ }
+
+
+ // Determine which message to output
+ $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : '';
+ $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM';
+
+ $message = sprintf($this->lang[$message], $till_date, '<a href="mailto:' . $config['board_contact'] . '">', '</a>');
+ $message .= ($ban_row['ban_give_reason']) ? '<br /><br />' . sprintf($this->lang['BOARD_BAN_REASON'], $ban_row['ban_give_reason']) : '';
+ $message .= '<br /><br /><em>' . $this->lang['BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)] . '</em>';
+
+ // To circumvent session_begin returning a valid value and the check_ban() not called on second page view, we kill the session again
+ $this->session_kill(false);
+
+ // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page
+ if (defined('IN_CRON'))
+ {
+ garbage_collection();
+ exit_handler();
+ exit;
+ }
+
+ trigger_error($message);
+ }
+
+ return ($banned && $ban_row['ban_give_reason']) ? $ban_row['ban_give_reason'] : $banned;
+ }
+
+ /**
+ * Check if ip is blacklisted
+ * This should be called only where absolutly necessary
+ *
+ * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups)
+ *
+ * @author satmd (from the php manual)
+ * @param string $mode register/post - spamcop for example is ommitted for posting
+ * @return false if ip is not blacklisted, else an array([checked server], [lookup])
+ */
+ function check_dnsbl($mode, $ip = false)
+ {
+ if ($ip === false)
+ {
+ $ip = $this->ip;
+ }
+
+ $dnsbl_check = array(
+ 'sbl-xbl.spamhaus.org' => 'http://www.spamhaus.org/query/bl?ip=',
+ );
+
+ if ($mode == 'register')
+ {
+ $dnsbl_check['bl.spamcop.net'] = 'http://spamcop.net/bl.shtml?';
+ }
+
+ if ($ip)
+ {
+ $quads = explode('.', $ip);
+ $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0];
+
+ // Need to be listed on all servers...
+ $listed = true;
+ $info = array();
+
+ foreach ($dnsbl_check as $dnsbl => $lookup)
+ {
+ if (phpbb_checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true)
+ {
+ $info = array($dnsbl, $lookup . $ip);
+ }
+ else
+ {
+ $listed = false;
+ }
+ }
+
+ if ($listed)
+ {
+ return $info;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if URI is blacklisted
+ * This should be called only where absolutly necessary, for example on the submitted website field
+ * This function is not in use at the moment and is only included for testing purposes, it may not work at all!
+ * This means it is untested at the moment and therefore commented out
+ *
+ * @param string $uri URI to check
+ * @return true if uri is on blacklist, else false. Only blacklist is checked (~zero FP), no grey lists
+ function check_uribl($uri)
+ {
+ // Normally parse_url() is not intended to parse uris
+ // We need to get the top-level domain name anyway... change.
+ $uri = parse_url($uri);
+
+ if ($uri === false || empty($uri['host']))
+ {
+ return false;
+ }
+
+ $uri = trim($uri['host']);
+
+ if ($uri)
+ {
+ // One problem here... the return parameter for the "windows" method is different from what
+ // we expect... this may render this check useless...
+ if (phpbb_checkdnsrr($uri . '.multi.uribl.com.', 'A') === true)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ */
+
+ /**
+ * Set/Update a persistent login key
+ *
+ * This method creates or updates a persistent session key. When a user makes
+ * use of persistent (formerly auto-) logins a key is generated and stored in the
+ * DB. When they revisit with the same key it's automatically updated in both the
+ * DB and cookie. Multiple keys may exist for each user representing different
+ * browsers or locations. As with _any_ non-secure-socket no passphrase login this
+ * remains vulnerable to exploit.
+ */
+ function set_login_key($user_id = false, $key = false, $user_ip = false)
+ {
+ global $config, $db;
+
+ $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id;
+ $user_ip = ($user_ip === false) ? $this->ip : $user_ip;
+ $key = ($key === false) ? (($this->cookie_data['k']) ? $this->cookie_data['k'] : false) : $key;
+
+ $key_id = unique_id(hexdec(substr($this->session_id, 0, 8)));
+
+ $sql_ary = array(
+ 'key_id' => (string) md5($key_id),
+ 'last_ip' => (string) $this->ip,
+ 'last_login' => (int) time()
+ );
+
+ if (!$key)
+ {
+ $sql_ary += array(
+ 'user_id' => (int) $user_id
+ );
+ }
+
+ if ($key)
+ {
+ $sql = 'UPDATE ' . SESSIONS_KEYS_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE user_id = ' . (int) $user_id . "
+ AND key_id = '" . $db->sql_escape(md5($key)) . "'";
+ }
+ else
+ {
+ $sql = 'INSERT INTO ' . SESSIONS_KEYS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);
+ }
+ $db->sql_query($sql);
+
+ $this->cookie_data['k'] = $key_id;
+
+ return false;
+ }
+
+ /**
+ * Reset all login keys for the specified user
+ *
+ * This method removes all current login keys for a specified (or the current)
+ * user. It will be called on password change to render old keys unusable
+ */
+ function reset_login_keys($user_id = false)
+ {
+ global $config, $db;
+
+ $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id;
+
+ $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . '
+ WHERE user_id = ' . (int) $user_id;
+ $db->sql_query($sql);
+
+ // Let's also clear any current sessions for the specified user_id
+ // If it's the current user then we'll leave this session intact
+ $sql_where = 'session_user_id = ' . (int) $user_id;
+ $sql_where .= ($user_id === $this->data['user_id']) ? " AND session_id <> '" . $db->sql_escape($this->session_id) . "'" : '';
+
+ $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
+ WHERE $sql_where";
+ $db->sql_query($sql);
+
+ // We're changing the password of the current user and they have a key
+ // Lets regenerate it to be safe
+ if ($user_id === $this->data['user_id'] && $this->cookie_data['k'])
+ {
+ $this->set_login_key($user_id);
+ }
+ }
+
+
+ /**
+ * Check if the request originated from the same page.
+ * @param bool $check_script_path If true, the path will be checked as well
+ */
+ function validate_referer($check_script_path = false)
+ {
+ // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason)
+ if (empty($this->referer) || empty($this->host))
+ {
+ return true;
+ }
+
+ $host = htmlspecialchars($this->host);
+ $ref = substr($this->referer, strpos($this->referer, '://') + 3);
+
+ if (!(stripos($ref, $host) === 0))
+ {
+ return false;
+ }
+ else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '')
+ {
+ $ref = substr($ref, strlen($host));
+ $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
+
+ if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0)
+ {
+ $ref = substr($ref, strlen(":$server_port"));
+ }
+
+ if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ function unset_admin()
+ {
+ global $db;
+ $sql = 'UPDATE ' . SESSIONS_TABLE . '
+ SET session_admin = 0
+ WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\'';
+ $db->sql_query($sql);
+ }
+}
+
+
+/**
+* Base user class
+*
+* This is the overarching class which contains (through session extend)
+* all methods utilised for user functionality during a session.
+*
+* @package phpBB3
+*/
+class user extends session
+{
+ var $lang = array();
+ var $help = array();
+ var $theme = array();
+ var $date_format;
+ var $timezone;
+ var $dst;
+
+ var $lang_name = false;
+ var $lang_id = false;
+ var $lang_path;
+ var $img_lang;
+ var $img_array = array();
+
+ // Able to add new option (id 7)
+ var $keyoptions = array('viewimg' => 0, 'viewflash' => 1, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'popuppm' => 10);
+ var $keyvalues = array();
+
+ /**
+ * Constructor to set the lang path
+ */
+ public function __construct()
+ {
+ $this->lang_path = PHPBB_ROOT_PATH . 'language/';
+ }
+
+ /**
+ * Function to set custom language path (able to use directory outside of phpBB)
+ *
+ * @param string $lang_path New language path used.
+ * @access public
+ */
+ function set_custom_lang_path($lang_path)
+ {
+ $this->lang_path = $lang_path;
+
+ if (substr($this->lang_path, -1) != '/')
+ {
+ $this->lang_path .= '/';
+ }
+ }
+
+ /**
+ * Setup basic user-specific items (style, language, ...)
+ */
+ function setup($lang_set = false, $style = false)
+ {
+ global $db, $template, $config, $auth, $cache;
+
+ if ($this->data['user_id'] != ANONYMOUS)
+ {
+ $this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common." . PHP_EXT)) ? $this->data['user_lang'] : basename($config['default_lang']);
+ $this->date_format = $this->data['user_dateformat'];
+ $this->timezone = $this->data['user_timezone'] * 3600;
+ $this->dst = $this->data['user_dst'] * 3600;
+ }
+ else
+ {
+ $this->lang_name = basename($config['default_lang']);
+ $this->date_format = $config['default_dateformat'];
+ $this->timezone = $config['board_timezone'] * 3600;
+ $this->dst = $config['board_dst'] * 3600;
+
+ /**
+ * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language
+ * If re-enabled we need to make sure only those languages installed are checked
+ * Commented out so we do not loose the code.
+
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+ {
+ $accept_lang_ary = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+
+ foreach ($accept_lang_ary as $accept_lang)
+ {
+ // Set correct format ... guess full xx_YY form
+ $accept_lang = substr($accept_lang, 0, 2) . '_' . strtoupper(substr($accept_lang, 3, 2));
+ $accept_lang = basename($accept_lang);
+
+ if (file_exists($this->lang_path . $accept_lang . "/common." . PHP_EXT))
+ {
+ $this->lang_name = $config['default_lang'] = $accept_lang;
+ break;
+ }
+ else
+ {
+ // No match on xx_YY so try xx
+ $accept_lang = substr($accept_lang, 0, 2);
+ $accept_lang = basename($accept_lang);
+
+ if (file_exists($this->lang_path . $accept_lang . "/common." . PHP_EXT))
+ {
+ $this->lang_name = $config['default_lang'] = $accept_lang;
+ break;
+ }
+ }
+ }
+ }
+ */
+ }
+
+ // We include common language file here to not load it every time a custom language file is included
+ $lang = &$this->lang;
+
+ if ((@include $this->lang_path . $this->lang_name . "/common." . PHP_EXT) === false)
+ {
+ die('Language file ' . $this->lang_path . $this->lang_name . "/common." . PHP_EXT . " couldn't be opened.");
+ }
+
+ $this->add_lang($lang_set);
+ unset($lang_set);
+
+ if (request::variable('style', false, false, request::GET) && $auth->acl_get('a_styles'))
+ {
+ global $SID, $_EXTRA_URL;
+
+ $style = request_var('style', 0);
+ $SID .= '&amp;style=' . $style;
+ $_EXTRA_URL = array('style=' . $style);
+ }
+ else
+ {
+ // Set up style
+ $style = ($style) ? $style : ((!$config['override_user_style']) ? $this->data['user_style'] : $config['default_style']);
+ }
+
+ $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, c.theme_path, c.theme_name, c.theme_storedb, c.theme_id, i.imageset_path, i.imageset_id, i.imageset_name
+ FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . " i
+ WHERE s.style_id = $style
+ AND t.template_id = s.template_id
+ AND c.theme_id = s.theme_id
+ AND i.imageset_id = s.imageset_id";
+ $result = $db->sql_query($sql, 3600);
+ $this->theme = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+
+ // User has wrong style
+ if (!$this->theme && $style == $this->data['user_style'])
+ {
+ $style = $this->data['user_style'] = $config['default_style'];
+
+ $sql = 'UPDATE ' . USERS_TABLE . "
+ SET user_style = $style
+ WHERE user_id = {$this->data['user_id']}";
+ $db->sql_query($sql);
+
+ $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, c.theme_path, c.theme_name, c.theme_storedb, c.theme_id, i.imageset_path, i.imageset_id, i.imageset_name
+ FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . " i
+ WHERE s.style_id = $style
+ AND t.template_id = s.template_id
+ AND c.theme_id = s.theme_id
+ AND i.imageset_id = s.imageset_id";
+ $result = $db->sql_query($sql, 3600);
+ $this->theme = $db->sql_fetchrow($result);
+ $db->sql_freeresult($result);
+ }
+
+ if (!$this->theme)
+ {
+ trigger_error('Could not get style data', E_USER_ERROR);
+ }
+
+ // Now parse the cfg file and cache it,
+ // we are only interested in the theme configuration for now
+ $parsed_items = cache::obtain_cfg_item($this->theme, 'theme');
+
+ $check_for = array(
+ 'parse_css_file' => (int) 0,
+ 'pagination_sep' => (string) ', '
+ );
+
+ foreach ($check_for as $key => $default_value)
+ {
+ $this->theme[$key] = (isset($parsed_items[$key])) ? $parsed_items[$key] : $default_value;
+ settype($this->theme[$key], gettype($default_value));
+
+ if (is_string($default_value))
+ {
+ $this->theme[$key] = htmlspecialchars($this->theme[$key]);
+ }
+ }
+
+ // If the style author specified the theme needs to be cached
+ // (because of the used paths and variables) than make sure it is the case.
+ // For example, if the theme uses language-specific images it needs to be stored in db.
+ if (!$this->theme['theme_storedb'] && $this->theme['parse_css_file'])
+ {
+ $this->theme['theme_storedb'] = 1;
+
+ $stylesheet = file_get_contents(PHPBB_ROOT_PATH . "styles/{$this->theme['theme_path']}/theme/stylesheet.css");
+ // Match CSS imports
+ $matches = array();
+ preg_match_all('/@import url\(["\'](.*)["\']\);/i', $stylesheet, $matches);
+
+ if (sizeof($matches))
+ {
+ $content = '';
+ foreach ($matches[0] as $idx => $match)
+ {
+ if ($content = @file_get_contents(PHPBB_ROOT_PATH . "styles/{$this->theme['theme_path']}/theme/" . $matches[1][$idx]))
+ {
+ $content = trim($content);
+ }
+ else
+ {
+ $content = '';
+ }
+ $stylesheet = str_replace($match, $content, $stylesheet);
+ }
+ unset($content);
+ }
+
+ $stylesheet = str_replace('./', 'styles/' . $this->theme['theme_path'] . '/theme/', $stylesheet);
+
+ $sql_ary = array(
+ 'theme_data' => $stylesheet,
+ 'theme_mtime' => time(),
+ 'theme_storedb' => 1
+ );
+
+ $sql = 'UPDATE ' . STYLES_THEME_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE theme_id = ' . $this->theme['theme_id'];
+ $db->sql_query($sql);
+
+ unset($sql_ary);
+ }
+
+ $template->set_template();
+
+ $this->img_lang = (file_exists(PHPBB_ROOT_PATH . 'styles/' . $this->theme['imageset_path'] . '/imageset/' . $this->lang_name)) ? $this->lang_name : $config['default_lang'];
+
+ $sql = 'SELECT image_name, image_filename, image_lang, image_height, image_width
+ FROM ' . STYLES_IMAGESET_DATA_TABLE . '
+ WHERE imageset_id = ' . $this->theme['imageset_id'] . "
+ AND image_filename <> ''
+ AND image_lang IN ('" . $db->sql_escape($this->img_lang) . "', '')";
+ $result = $db->sql_query($sql, 3600);
+
+ $localised_images = false;
+ while ($row = $db->sql_fetchrow($result))
+ {
+ if ($row['image_lang'])
+ {
+ $localised_images = true;
+ }
+
+ $row['image_filename'] = rawurlencode($row['image_filename']);
+ $this->img_array[$row['image_name']] = $row;
+ }
+ $db->sql_freeresult($result);
+
+ // there were no localised images, try to refresh the localised imageset for the user's language
+ if (!$localised_images)
+ {
+ // Attention: this code ignores the image definition list from acp_styles and just takes everything
+ // that the config file contains
+ $sql_ary = array();
+
+ $db->sql_transaction('begin');
+
+ $sql = 'DELETE FROM ' . STYLES_IMAGESET_DATA_TABLE . '
+ WHERE imageset_id = ' . $this->theme['imageset_id'] . '
+ AND image_lang = \'' . $db->sql_escape($this->img_lang) . '\'';
+ $result = $db->sql_query($sql);
+
+ if (@file_exists(PHPBB_ROOT_PATH . "styles/{$this->theme['imageset_path']}/imageset/{$this->img_lang}/imageset.cfg"))
+ {
+ $cfg_data_imageset_data = parse_cfg_file(PHPBB_ROOT_PATH . "styles/{$this->theme['imageset_path']}/imageset/{$this->img_lang}/imageset.cfg");
+ foreach ($cfg_data_imageset_data as $image_name => $value)
+ {
+ if (strpos($value, '*') !== false)
+ {
+ if (substr($value, -1, 1) === '*')
+ {
+ list($image_filename, $image_height) = explode('*', $value);
+ $image_width = 0;
+ }
+ else
+ {
+ list($image_filename, $image_height, $image_width) = explode('*', $value);
+ }
+ }
+ else
+ {
+ $image_filename = $value;
+ $image_height = $image_width = 0;
+ }
+
+ if (strpos($image_name, 'img_') === 0 && $image_filename)
+ {
+ $image_name = substr($image_name, 4);
+ $sql_ary[] = array(
+ 'image_name' => (string) $image_name,
+ 'image_filename' => (string) $image_filename,
+ 'image_height' => (int) $image_height,
+ 'image_width' => (int) $image_width,
+ 'imageset_id' => (int) $this->theme['imageset_id'],
+ 'image_lang' => (string) $this->img_lang,
+ );
+ }
+ }
+ }
+
+ if (sizeof($sql_ary))
+ {
+ $db->sql_multi_insert(STYLES_IMAGESET_DATA_TABLE, $sql_ary);
+ $db->sql_transaction('commit');
+ $cache->destroy('sql', STYLES_IMAGESET_DATA_TABLE);
+
+ add_log('admin', 'LOG_IMAGESET_LANG_REFRESHED', $this->theme['imageset_name'], $this->img_lang);
+ }
+ else
+ {
+ $db->sql_transaction('commit');
+ add_log('admin', 'LOG_IMAGESET_LANG_MISSING', $this->theme['imageset_name'], $this->img_lang);
+ }
+ }
+
+ // Call phpbb_user_session_handler() in case external application want to "bend" some variables or replace classes...
+ // After calling it we continue script execution...
+ phpbb_user_session_handler();
+
+ // If this function got called from the error handler we are finished here.
+ if (defined('IN_ERROR_HANDLER'))
+ {
+ return;
+ }
+
+ // Disable board if the install/ directory is still present
+ // For the brave development army we do not care about this, else we need to comment out this everytime we develop locally
+ if (!defined('DEBUG_EXTRA') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists(PHPBB_ROOT_PATH . 'install'))
+ {
+ // Adjust the message slightly according to the permissions
+ if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))
+ {
+ $message = 'REMOVE_INSTALL';
+ }
+ else
+ {
+ $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE';
+ }
+ trigger_error($message);
+ }
+
+ // Is board disabled and user not an admin or moderator?
+ if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_'))
+ {
+ header('HTTP/1.1 503 Service Unavailable');
+
+ $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE';
+ trigger_error($message);
+ }
+
+ // Is load exceeded?
+ if ($config['limit_load'] && $this->load !== false)
+ {
+ if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN'))
+ {
+ // Set board disabled to true to let the admins/mods get the proper notification
+ $config['board_disable'] = '1';
+
+ if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_'))
+ {
+ header('HTTP/1.1 503 Service Unavailable');
+ trigger_error('BOARD_UNAVAILABLE');
+ }
+ }
+ }
+
+ if (isset($this->data['session_viewonline']))
+ {
+ // Make sure the user is able to hide his session
+ if (!$this->data['session_viewonline'])
+ {
+ // Reset online status if not allowed to hide the session...
+ if (!$auth->acl_get('u_hideonline'))
+ {
+ $sql = 'UPDATE ' . SESSIONS_TABLE . '
+ SET session_viewonline = 1
+ WHERE session_user_id = ' . $this->data['user_id'];
+ $db->sql_query($sql);
+ $this->data['session_viewonline'] = 1;
+ }
+ }
+ else if (!$this->data['user_allow_viewonline'])
+ {
+ // the user wants to hide and is allowed to -> cloaking device on.
+ if ($auth->acl_get('u_hideonline'))
+ {
+ $sql = 'UPDATE ' . SESSIONS_TABLE . '
+ SET session_viewonline = 0
+ WHERE session_user_id = ' . $this->data['user_id'];
+ $db->sql_query($sql);
+ $this->data['session_viewonline'] = 0;
+ }
+ }
+ }
+
+
+ // Does the user need to change their password? If so, redirect to the
+ // ucp profile reg_details page ... of course do not redirect if we're already in the ucp
+ if (!defined('IN_ADMIN') && !defined('ADMIN_START') && $config['chg_passforce'] && $this->data['is_registered'] && $auth->acl_get('u_chgpasswd') && $this->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400))
+ {
+ if (strpos($this->page['query_string'], 'mode=reg_details') === false && $this->page['page_name'] != 'ucp.' . PHP_EXT)
+ {
+ redirect(append_sid('ucp', 'i=profile&amp;mode=reg_details'));
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * More advanced language substitution
+ * Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms.
+ * Params are the language key and the parameters to be substituted.
+ * This function/functionality is inspired by SHS` and Ashe.
+ *
+ * Example call: <samp>$user->lang('NUM_POSTS_IN_QUEUE', 1);</samp>
+ */
+ function lang()
+ {
+ $args = func_get_args();
+ $key = $args[0];
+
+ if (is_array($key))
+ {
+ $lang = &$this->lang[array_shift($key)];
+
+ foreach ($key as $_key)
+ {
+ $lang = &$lang[$_key];
+ }
+ }
+ else
+ {
+ $lang = &$this->lang[$key];
+ }
+
+ // Return if language string does not exist
+ if (!isset($lang) || (!is_string($lang) && !is_array($lang)))
+ {
+ return $key;
+ }
+
+ // If the language entry is a string, we simply mimic sprintf() behaviour
+ if (is_string($lang))
+ {
+ if (sizeof($args) == 1)
+ {
+ return $lang;
+ }
+
+ // Replace key with language entry and simply pass along...
+ $args[0] = $lang;
+ return call_user_func_array('sprintf', $args);
+ }
+
+ // It is an array... now handle different nullar/singular/plural forms
+ $key_found = false;
+
+ // We now get the first number passed and will select the key based upon this number
+ for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++)
+ {
+ if (is_int($args[$i]))
+ {
+ $numbers = array_keys($lang);
+
+ foreach ($numbers as $num)
+ {
+ if ($num > $args[$i])
+ {
+ break;
+ }
+
+ $key_found = $num;
+ }
+ }
+ }
+
+ // Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form)
+ if ($key_found === false)
+ {
+ $numbers = array_keys($lang);
+ $key_found = end($numbers);
+ }
+
+ // Use the language string we determined and pass it to sprintf()
+ $args[0] = $lang[$key_found];
+ return call_user_func_array('sprintf', $args);
+ }
+
+ /**
+ * Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion)
+ *
+ * @param mixed $lang_set specifies the language entries to include
+ * @param bool $use_db internal variable for recursion, do not use
+ * @param bool $use_help internal variable for recursion, do not use
+ *
+ * Examples:
+ * <code>
+ * $lang_set = array('posting', 'help' => 'faq');
+ * $lang_set = array('posting', 'viewtopic', 'help' => array('bbcode', 'faq'))
+ * $lang_set = array(array('posting', 'viewtopic'), 'help' => array('bbcode', 'faq'))
+ * $lang_set = 'posting'
+ * $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting'))
+ * </code>
+ */
+ function add_lang($lang_set, $use_db = false, $use_help = false)
+ {
+ if (is_array($lang_set))
+ {
+ foreach ($lang_set as $key => $lang_file)
+ {
+ // Please do not delete this line.
+ // We have to force the type here, else [array] language inclusion will not work
+ $key = (string) $key;
+
+ if ($key == 'db')
+ {
+ $this->add_lang($lang_file, true, $use_help);
+ }
+ else if ($key == 'help')
+ {
+ $this->add_lang($lang_file, $use_db, true);
+ }
+ else if (!is_array($lang_file))
+ {
+ $this->set_lang($this->lang, $this->help, $lang_file, $use_db, $use_help);
+ }
+ else
+ {
+ $this->add_lang($lang_file, $use_db, $use_help);
+ }
+ }
+ unset($lang_set);
+ }
+ else if ($lang_set)
+ {
+ $this->set_lang($this->lang, $this->help, $lang_set, $use_db, $use_help);
+ }
+ }
+
+ /**
+ * Set language entry (called by add_lang)
+ * @access private
+ */
+ function set_lang(&$lang, &$help, $lang_file, $use_db = false, $use_help = false)
+ {
+ // Make sure the language name is set (if the user setup did not happen it is not set)
+ if (!$this->lang_name)
+ {
+ global $config;
+ $this->lang_name = basename($config['default_lang']);
+ }
+
+ // $lang == $this->lang
+ // $help == $this->help
+ // - add appropriate variables here, name them as they are used within the language file...
+ if (!$use_db)
+ {
+ if ($use_help && strpos($lang_file, '/') !== false)
+ {
+ $language_filename = $this->lang_path . $this->lang_name . '/' . substr($lang_file, 0, stripos($lang_file, '/') + 1) . 'help_' . substr($lang_file, stripos($lang_file, '/') + 1) . '.' . PHP_EXT;
+ }
+ else
+ {
+ $language_filename = $this->lang_path . $this->lang_name . '/' . (($use_help) ? 'help_' : '') . $lang_file . '.' . PHP_EXT;
+ }
+
+ if ((@include $language_filename) === false)
+ {
+ trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR);
+ }
+ }
+ else if ($use_db)
+ {
+ // Get Database Language Strings
+ // Put them into $lang if nothing is prefixed, put them into $help if help: is prefixed
+ // For example: help:faq, posting
+ }
+ }
+
+ /**
+ * Format user date
+ *
+ * @param int $gmepoch unix timestamp
+ * @param string $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i.
+ * @param bool $forcedate force non-relative date format.
+ *
+ * @return mixed translated date
+ */
+ function format_date($gmepoch, $format = false, $forcedate = false)
+ {
+ static $midnight;
+ static $date_cache;
+
+ $format = (!$format) ? $this->date_format : $format;
+ $now = time();
+ $delta = $now - $gmepoch;
+
+ if (!isset($date_cache[$format]))
+ {
+ // Is the user requesting a friendly date format (i.e. 'Today 12:42')?
+ $date_cache[$format] = array(
+ 'is_short' => strpos($format, '|'),
+ 'zone_offset' => $this->timezone + $this->dst,
+ 'format_short' => substr($format, 0, strpos($format, '|')) . '||' . substr(strrchr($format, '|'), 1),
+ 'format_long' => str_replace('|', '', $format),
+ 'lang' => $this->lang['datetime'],
+ );
+
+ // Short representation of month in format? Some languages use different terms for the long and short format of May
+ if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false))
+ {
+ $date_cache[$format]['lang']['May'] = $this->lang['datetime']['May_short'];
+ }
+ }
+
+ // Show date <= 1 hour ago as 'xx min ago'
+ // A small tolerence is given for times in the future and times in the future but in the same minute are displayed as '< than a minute ago'
+ if ($delta <= 3600 && ($delta >= -5 || (($now / 60) % 60) == (($gmepoch / 60) % 60)) && $date_cache[$format]['is_short'] !== false && !$forcedate && isset($this->lang['datetime']['AGO']))
+ {
+ return $this->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60)));
+ }
+
+ if (!$midnight)
+ {
+ list($d, $m, $y) = explode(' ', gmdate('j n Y', time() + $date_cache[$format]['zone_offset']));
+ $midnight = gmmktime(0, 0, 0, $m, $d, $y) - $date_cache[$format]['zone_offset'];
+ }
+
+ if ($date_cache[$format]['is_short'] !== false && !$forcedate)
+ {
+ $day = false;
+
+ if ($gmepoch > $midnight + 86400)
+ {
+ $day = 'TOMORROW';
+ }
+ else if ($gmepoch > $midnight)
+ {
+ $day = 'TODAY';
+ }
+ else if ($gmepoch > $midnight - 86400)
+ {
+ $day = 'YESTERDAY';
+ }
+
+ if ($day !== false)
+ {
+ return str_replace('||', $this->lang['datetime'][$day], strtr(@gmdate($date_cache[$format]['format_short'], $gmepoch + $date_cache[$format]['zone_offset']), $date_cache[$format]['lang']));
+ }
+ }
+
+ return strtr(@gmdate($date_cache[$format]['format_long'], $gmepoch + $date_cache[$format]['zone_offset']), $date_cache[$format]['lang']);
+ }
+
+ /**
+ * Get language id currently used by the user
+ */
+ function get_iso_lang_id()
+ {
+ global $config, $db;
+
+ if (!empty($this->lang_id))
+ {
+ return $this->lang_id;
+ }
+
+ if (!$this->lang_name)
+ {
+ $this->lang_name = $config['default_lang'];
+ }
+
+ $sql = 'SELECT lang_id
+ FROM ' . LANG_TABLE . "
+ WHERE lang_iso = '" . $db->sql_escape($this->lang_name) . "'";
+ $result = $db->sql_query($sql);
+ $this->lang_id = (int) $db->sql_fetchfield('lang_id');
+ $db->sql_freeresult($result);
+
+ return $this->lang_id;
+ }
+
+ /**
+ * Get users profile fields
+ */
+ function get_profile_fields($user_id)
+ {
+ global $db;
+
+ if (isset($this->profile_fields))
+ {
+ return;
+ }
+
+ $sql = 'SELECT *
+ FROM ' . PROFILE_FIELDS_DATA_TABLE . "
+ WHERE user_id = $user_id";
+ $result = $db->sql_query_limit($sql, 1);
+ $this->profile_fields = (!($row = $db->sql_fetchrow($result))) ? array() : $row;
+ $db->sql_freeresult($result);
+ }
+
+ /**
+ * Specify/Get image
+ */
+ function img($img, $alt = '', $type = 'full_tag', $width = false)
+ {
+ static $imgs;
+
+ $img_data = &$imgs[$img];
+
+ if (empty($img_data))
+ {
+ if (!isset($this->img_array[$img]))
+ {
+ // Do not fill the image to let designers decide what to do if the image is empty
+ $img_data = '';
+ return $img_data;
+ }
+
+ $img_data['src'] = PHPBB_ROOT_PATH . 'styles/' . $this->theme['imageset_path'] . '/imageset/' . ($this->img_array[$img]['image_lang'] ? $this->img_array[$img]['image_lang'] .'/' : '') . $this->img_array[$img]['image_filename'];
+ $img_data['width'] = $this->img_array[$img]['image_width'];
+ $img_data['height'] = $this->img_array[$img]['image_height'];
+ }
+
+ $alt = (!empty($this->lang[$alt])) ? $this->lang[$alt] : $alt;
+
+ switch ($type)
+ {
+ case 'src':
+ return $img_data['src'];
+ break;
+
+ case 'width':
+ return ($width === false) ? $img_data['width'] : $width;
+ break;
+
+ case 'height':
+ return $img_data['height'];
+ break;
+
+ default:
+ $use_width = ($width === false) ? $img_data['width'] : $width;
+
+ return '<img src="' . $img_data['src'] . '"' . (($use_width) ? ' width="' . $use_width . '"' : '') . (($img_data['height']) ? ' height="' . $img_data['height'] . '"' : '') . ' alt="' . $alt . '" title="' . $alt . '" />';
+ break;
+ }
+ }
+
+ /**
+ * Get option bit field from user options
+ */
+ function optionget($key, $data = false)
+ {
+ if (!isset($this->keyvalues[$key]))
+ {
+ $var = ($data) ? $data : $this->data['user_options'];
+ $this->keyvalues[$key] = ($var & 1 << $this->keyoptions[$key]) ? true : false;
+ }
+
+ return $this->keyvalues[$key];
+ }
+
+ /**
+ * Set option bit field for user options
+ */
+ function optionset($key, $value, $data = false)
+ {
+ $var = ($data) ? $data : $this->data['user_options'];
+
+ if ($value && !($var & 1 << $this->keyoptions[$key]))
+ {
+ $var += 1 << $this->keyoptions[$key];
+ }
+ else if (!$value && ($var & 1 << $this->keyoptions[$key]))
+ {
+ $var -= 1 << $this->keyoptions[$key];
+ }
+ else
+ {
+ return ($data) ? $var : false;
+ }
+
+ if (!$data)
+ {
+ $this->data['user_options'] = $var;
+ return true;
+ }
+ else
+ {
+ return $var;
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/phpBB/includes/classes/template.php b/phpBB/includes/classes/template.php
new file mode 100644
index 0000000000..a49fd75eb3
--- /dev/null
+++ b/phpBB/includes/classes/template.php
@@ -0,0 +1,536 @@
+<?php
+/**
+*
+* @package phpBB3
+* @version $Id$
+* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Base Template class.
+* @package phpBB3
+*/
+class template
+{
+ /**
+ * variable that holds all the data we'll be substituting into
+ * the compiled templates. Takes form:
+ * --> $this->_tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value
+ * if it's a root-level variable, it'll be like this:
+ * --> $this->_tpldata[.][0][varname] == value
+ * @var array
+ */
+ private $_tpldata = array('.' => array(0 => array()));
+
+ /**
+ * @var array Reference to template->_tpldata['.'][0]
+ */
+ private $_rootref;
+
+ /**
+ * @var string Root dir for template.
+ */
+ private $root = '';
+
+ /**
+ * @var string Path of the cache directory for the template
+ */
+ public $cachepath = '';
+
+ /**
+ * @var array Hash of handle => file path pairs
+ */
+ public $files = array();
+
+ /**
+ * @var array Hash of handle => filename pairs
+ */
+ public $filename = array();
+
+ /**
+ * Set template location
+ * @access public
+ */
+ public function set_template()
+ {
+ global $user;
+
+ if (file_exists(PHPBB_ROOT_PATH . 'styles/' . $user->theme['template_path'] . '/template'))
+ {
+ $this->root = PHPBB_ROOT_PATH . 'styles/' . $user->theme['template_path'] . '/template';
+ $this->cachepath = PHPBB_ROOT_PATH . 'cache/tpl_' . $user->theme['template_path'] . '_';
+ }
+ else
+ {
+ trigger_error('Template path could not be found: styles/' . $user->theme['template_path'] . '/template', E_USER_ERROR);
+ }
+
+ $this->_rootref = &$this->_tpldata['.'][0];
+ }
+
+ /**
+ * Set custom template location (able to use directory outside of phpBB)
+ * @access public
+ * @param string $template_path Path to template directory
+ * @param string $template_name Name of template
+ */
+ public function set_custom_template($template_path, $template_name)
+ {
+ $this->root = $template_path;
+ $this->cachepath = PHPBB_ROOT_PATH . 'cache/ctpl_' . str_replace('_', '-', $template_name) . '_';
+ }
+
+ /**
+ * Sets the template filenames for handles. $filename_array
+ * @access public
+ * @param array $filname_array Should be a hash of handle => filename pairs.
+ */
+ public function set_filenames(array $filename_array)
+ {
+ foreach ($filename_array as $handle => $filename)
+ {
+ if (empty($filename))
+ {
+ trigger_error("template->set_filenames: Empty filename specified for $handle", E_USER_ERROR);
+ }
+
+ $this->filename[$handle] = $filename;
+ $this->files[$handle] = $this->root . '/' . $filename;
+ }
+
+ return true;
+ }
+
+ /**
+ * Destroy template data set
+ * @access public
+ */
+ public function __destruct()
+ {
+ $this->_tpldata = array('.' => array(0 => array()));
+ }
+
+ /**
+ * Reset/empty complete block
+ * @access public
+ * @param string $blockname Name of block to destroy
+ */
+ public function destroy_block_vars($blockname)
+ {
+ if (strpos($blockname, '.') !== false)
+ {
+ // Nested block.
+ $blocks = explode('.', $blockname);
+ $blockcount = sizeof($blocks) - 1;
+
+ $str = &$this->_tpldata;
+ for ($i = 0; $i < $blockcount; $i++)
+ {
+ $str = &$str[$blocks[$i]];
+ $str = &$str[sizeof($str) - 1];
+ }
+
+ unset($str[$blocks[$blockcount]]);
+ }
+ else
+ {
+ // Top-level block.
+ unset($this->_tpldata[$blockname]);
+ }
+ }
+
+ /**
+ * Display handle
+ * @access public
+ * @param string $handle Handle to display
+ * @param bool $include_once Allow multiple inclusions
+ * @return bool True on success, false on failure
+ */
+ public function display($handle, $include_once = true)
+ {
+ global $user, $phpbb_hook;
+
+ if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, __FUNCTION__), $handle, $include_once))
+ {
+ if ($phpbb_hook->hook_return(array(__CLASS__, __FUNCTION__)))
+ {
+ return $phpbb_hook->hook_return_result(array(__CLASS__, __FUNCTION__));
+ }
+ }
+
+/* if (defined('IN_ERROR_HANDLER'))
+ {
+ if ((E_NOTICE & error_reporting()) == E_NOTICE)
+ {
+ //error_reporting(error_reporting() ^ E_NOTICE);
+ }
+ }*/
+
+ $_tpldata = &$this->_tpldata;
+ $_rootref = &$this->_rootref;
+ $_lang = &$user->lang;
+
+ // These _are_ used the included files.
+ $_tpldata; $_rootref; $_lang;
+
+ if (($filename = $this->_tpl_load($handle)) !== false)
+ {
+ ($include_once) ? include_once($filename) : include($filename);
+ }
+ else if (($code = $this->_tpl_eval($handle)) !== false)
+ {
+ $code = ' ?> ' . $code . ' <?php ';
+ eval($code);
+ }
+ else
+ {
+ // if we could not eval AND the file exists, something horrific has occured
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Display the handle and assign the output to a template variable or return the compiled result.
+ * @access public
+ * @param string $handle Handle to operate on
+ * @param string $template_var Template variable to assign compiled handle to
+ * @param bool $return_content If true return compiled handle, otherwise assign to $template_var
+ * @param bool $include_once Allow multiple inclusions of the file
+ * @return bool|string If $return_content is true return string of the compiled handle, otherwise return true
+ */
+ public function assign_display($handle, $template_var = '', $return_content = true, $include_once = false)
+ {
+ ob_start();
+ $this->display($handle, $include_once);
+ $contents = ob_get_clean();
+
+ if ($return_content)
+ {
+ return $contents;
+ }
+
+ $this->assign_var($template_var, $contents);
+
+ return true;
+ }
+
+ /**
+ * Load a compiled template if possible, if not, recompile it
+ * @access private
+ * @param string $handle Handle of the template to load
+ * @return string|bool Return filename on success otherwise false
+ * @uses template_compile is used to compile uncached templates
+ */
+ private function _tpl_load($handle)
+ {
+ global $config;
+
+ $filename = $this->cachepath . str_replace('/', '.', $this->filename[$handle]) . '.' . PHP_EXT;
+
+ $recompile = (!file_exists($filename) || @filesize($filename) === 0 || ($config['load_tplcompile'] && @filemtime($filename) < filemtime($this->files[$handle]))) ? true : false;
+
+ // Recompile page if the original template is newer, otherwise load the compiled version
+ if ($recompile)
+ {
+ if (!class_exists('template_compile'))
+ {
+ include(PHPBB_ROOT_PATH . 'includes/functions_template.' . PHP_EXT);
+ }
+
+ $compile = new template_compile($this);
+
+ // If we don't have a file assigned to this handle, die.
+ if (!isset($this->files[$handle]))
+ {
+ trigger_error("template->_tpl_load(): No file specified for handle $handle", E_USER_ERROR);
+ }
+
+ if ($compile->_tpl_load_file($handle) === false)
+ {
+ return false;
+ }
+ }
+
+ return $filename;
+ }
+
+ /**
+ * This code should only run when some high level error prevents us from writing to the cache.
+ * @access private
+ * @param string $handle Template handle to compile
+ * @return string|bool Return compiled code on success otherwise false
+ * @uses template_compile is used to compile template
+ */
+ private function _tpl_eval($handle)
+ {
+ if (!class_exists('template_compile'))
+ {
+ include(PHPBB_ROOT_PATH . 'includes/functions_template.' . PHP_EXT);
+ }
+
+ $compile = new template_compile($this);
+
+ // If we don't have a file assigned to this handle, die.
+ if (!isset($this->files[$handle]))
+ {
+ trigger_error("template->_tpl_eval(): No file specified for handle $handle", E_USER_ERROR);
+ }
+
+ if (($code = $compile->_tpl_gen_src($handle)) === false)
+ {
+ return false;
+ }
+
+ return $code;
+ }
+
+ /**
+ * Assign key variable pairs from an array
+ * @access public
+ * @param array $vararray A hash of variable name => value pairs
+ */
+ public function assign_vars(array $vararray)
+ {
+ foreach ($vararray as $key => $val)
+ {
+ $this->_rootref[$key] = $val;
+ }
+ }
+
+ /**
+ * Assign a single variable to a single key
+ * @access public
+ * @param string $varname Variable name
+ * @param string $varval Value to assign to variable
+ */
+ public function assign_var($varname, $varval)
+ {
+ $this->_rootref[$varname] = $varval;
+ }
+
+ /**
+ * Assign key variable pairs from an array to a specified block
+ * @access public
+ * @param string $blockname Name of block to assign $vararray to
+ * @param array $vararray A hash of variable name => value pairs
+ */
+ public function assign_block_vars($blockname, array $vararray)
+ {
+ if (strpos($blockname, '.') !== false)
+ {
+ // Nested block.
+ $blocks = explode('.', $blockname);
+ $blockcount = sizeof($blocks) - 1;
+
+ $str = &$this->_tpldata;
+ for ($i = 0; $i < $blockcount; $i++)
+ {
+ $str = &$str[$blocks[$i]];
+ $str = &$str[sizeof($str) - 1];
+ }
+
+ // Now we add the block that we're actually assigning to.
+ // We're adding a new iteration to this block with the given
+ // variable assignments.
+ $str[$blocks[$blockcount]][] = $vararray;
+ }
+ else
+ {
+ // Top-level block.
+
+ // Add a new iteration to this block with the variable assignments we were given.
+ $this->_tpldata[$blockname][] = $vararray;
+ }
+ }
+
+ /**
+ * Change already assigned key variable pair (one-dimensional - single loop entry)
+ *
+ * An example of how to use this function:
+ *
+ * @param string $blockname the blockname, for example 'loop'
+ * @param array $vararray the var array to insert/add or merge
+ * @param mixed $key Key to search for
+ *
+ * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position]
+ *
+ * int: Position [the position to change or insert at directly given]
+ *
+ * If key is false the position is set to 0
+ * If key is true the position is set to the last entry
+ *
+ * @param string $mode Mode to execute (valid modes are 'insert' and 'change')
+ *
+ * If insert, the vararray is inserted at the given position (position counting from zero).
+ * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value).
+ *
+ * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array)
+ * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars)
+ *
+ * @return bool false on error, true on success
+ * @access public
+ */
+ public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert')
+ {
+ if (strpos($blockname, '.') !== false)
+ {
+ // Nested block.
+ $blocks = explode('.', $blockname);
+ $blockcount = sizeof($blocks) - 1;
+
+ $block = &$this->_tpldata;
+ for ($i = 0; $i < $blockcount; $i++)
+ {
+ if (($pos = strpos($blocks[$i], '[')) !== false)
+ {
+ $name = substr($blocks[$i], 0, $pos);
+
+ if (strpos($blocks[$i], '[]') === $pos)
+ {
+ $index = sizeof($block[$name]) - 1;
+ }
+ else
+ {
+ $index = min((int) substr($blocks[$i], $pos + 1, -1), sizeof($block[$name]) - 1);
+ }
+ }
+ else
+ {
+ $name = $blocks[$i];
+ $index = sizeof($block[$name]) - 1;
+ }
+ $block = &$block[$name];
+ $block = &$block[$index];
+ }
+
+ $block = &$block[$blocks[$i]]; // Traverse the last block
+ }
+ else
+ {
+ // Top-level block.
+ $block = &$this->_tpldata[$blockname];
+ }
+
+ // Change key to zero (change first position) if false and to last position if true
+ if ($key === false || $key === true)
+ {
+ $key = ($key === false) ? 0 : sizeof($block);
+ }
+
+ // Get correct position if array given
+ if (is_array($key))
+ {
+ // Search array to get correct position
+ list($search_key, $search_value) = @each($key);
+
+ $key = NULL;
+ foreach ($block as $i => $val_ary)
+ {
+ if ($val_ary[$search_key] === $search_value)
+ {
+ $key = $i;
+ break;
+ }
+ }
+
+ // key/value pair not found
+ if ($key === NULL)
+ {
+ return false;
+ }
+ }
+
+ // Insert Block
+ if ($mode == 'insert')
+ {
+ // Re-position template blocks
+ for ($i = sizeof($block); $i > $key; $i--)
+ {
+ $block[$i] = $block[$i-1];
+ }
+
+ // Insert vararray at given position
+ $block[$key] = $vararray;
+
+ return true;
+ }
+
+ // Which block to change?
+ if ($mode == 'change')
+ {
+ if ($key == sizeof($block))
+ {
+ $key--;
+ }
+
+ $block[$key] = array_merge($block[$key], $vararray);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Include a separate template
+ * @access private
+ * @param string $filename Template filename to include
+ * @param bool $include True to include the file, false to just load it
+ * @uses template_compile is used to compile uncached templates
+ */
+ private function _tpl_include($filename, $include = true)
+ {
+ $handle = $filename;
+ $this->filename[$handle] = $filename;
+ $this->files[$handle] = $this->root . '/' . $filename;
+
+ $filename = $this->_tpl_load($handle);
+
+ if ($include)
+ {
+ global $user;
+
+ $_tpldata = &$this->_tpldata;
+ $_rootref = &$this->_rootref;
+ $_lang = &$user->lang;
+
+ // These _are_ used the included files.
+ $_tpldata; $_rootref; $_lang;
+
+ if ($filename)
+ {
+ include($filename);
+ return;
+ }
+ else
+ {
+ if (!class_exists('template_compile'))
+ {
+ include(PHPBB_ROOT_PATH . 'includes/functions_template.' . PHP_EXT);
+ }
+
+ $compile = new template_compile($this);
+
+ if (($code = $compile->_tpl_gen_src($handle)) !== false)
+ {
+ $code = ' ?> ' . $code . ' <?php ';
+ eval($code);
+ }
+ }
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/phpBB/includes/classes/template_compile.php b/phpBB/includes/classes/template_compile.php
new file mode 100644
index 0000000000..471ca5eca3
--- /dev/null
+++ b/phpBB/includes/classes/template_compile.php
@@ -0,0 +1,869 @@
+<?php
+/**
+*
+* @package phpBB3
+* @version $Id$
+* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+ * The template filter that does the actual compilation
+ * @see template_compile
+ * @package phpBB3
+ *
+ */
+class template_filter extends php_user_filter
+{
+ /**
+ * @var string Replaceable tokens regex
+ */
+ private $regex = '~<!-- ([A-Z][A-Z_0-9]+)(?: (.*?) ?)?-->|{((?:[a-z][a-z_0-9]+\.)*\\$?[A-Z][A-Z_0-9]+)}~';
+
+ /**
+ * @var array
+ */
+ private $block_names = array();
+
+ /**
+ * @var array
+ */
+ private $block_else_level = array();
+
+ /**
+ * @var string
+ */
+ private $chunk;
+
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ $written = false;
+
+ while ($bucket = stream_bucket_make_writeable($in))
+ {
+ $consumed += $bucket->datalen;
+
+ $data = $this->chunk . $bucket->data;
+ $last_nl = strrpos($data, "\n");
+ $this->chunk = substr($data, $last_nl);
+ $data = substr($data, 0, $last_nl);
+
+ if (!strlen($data))
+ {
+ continue;
+ }
+
+ $written = true;
+
+ $bucket->data = $this->compile($data);
+ $bucket->datalen = strlen($bucket->data);
+ stream_bucket_append($out, $bucket);
+ }
+
+ if ($closing && strlen($this->chunk))
+ {
+ $written = true;
+ $bucket = stream_bucket_new($this->stream, $this->compile($this->chunk));
+ stream_bucket_append($out, $bucket);
+ }
+
+ return $written ? PSFS_PASS_ON : PSFS_FEED_ME;
+ }
+
+ public function onCreate()
+ {
+ $this->chunk = '';
+ return true;
+ }
+
+ private function compile($data)
+ {
+ $data = preg_replace('#<(?:[\\?%]|script)#s', '<?php echo\'\\0\';?>', $data);
+ return str_replace('?><?php', '', preg_replace_callback($this->regex, array($this, 'replace'), $data));
+ }
+
+ private function replace($matches)
+ {
+ global $config;
+
+ if (isset($matches[3]))
+ {
+ return $this->compile_var_tags($matches[0]);
+ }
+
+ switch ($matches[1])
+ {
+ case 'BEGIN':
+ $this->block_else_level[] = false;
+ return '<?php ' . $this->compile_tag_block($matches[2]) . ' ?>';
+ break;
+
+ case 'BEGINELSE':
+ $this->block_else_level[sizeof($this->block_else_level) - 1] = true;
+ return '<?php }} else { ?>';
+ break;
+
+ case 'END':
+ array_pop($this->block_names);
+ return '<?php ' . ((array_pop($this->block_else_level)) ? '}' : '}}') . ' ?>';
+ break;
+
+ case 'IF':
+ return '<?php ' . $this->compile_tag_if($matches[2], false) . ' ?>';
+ break;
+
+ case 'ELSE':
+ return '<?php } else { ?>';
+ break;
+
+ case 'ELSEIF':
+ return '<?php ' . $this->compile_tag_if($matches[2], true) . ' ?>';
+ break;
+
+ case 'ENDIF':
+ return '<?php } ?>';
+ break;
+
+ case 'DEFINE':
+ return '<?php ' . $this->compile_tag_define($matches[2], true) . ' ?>';
+ break;
+
+ case 'UNDEFINE':
+ return '<?php ' . $this->compile_tag_define($matches[2], false) . ' ?>';
+ break;
+
+ case 'INCLUDE':
+ return '<?php ' . $this->compile_tag_include($matches[2]) . ' ?>';
+ break;
+
+ case 'INCLUDEPHP':
+ return ($config['tpl_allow_php']) ? '<?php ' . $this->compile_tag_include_php($matches[2]) . ' ?>' : '';
+ break;
+
+ case 'PHP':
+ return ($config['tpl_allow_php']) ? '<?php ' : '<!-- ';
+ break;
+
+ case 'ENDPHP':
+ return ($config['tpl_allow_php']) ? ' ?>' : ' -->';
+ break;
+
+ default:
+ return $matches[0];
+ break;
+
+ }
+ return '';
+ }
+
+ /**
+ * Compile variables
+ * @access private
+ */
+ private function compile_var_tags(&$text_blocks)
+ {
+ // change template varrefs into PHP varrefs
+ $varrefs = array();
+
+ // This one will handle varrefs WITH namespaces
+ preg_match_all('#\{((?:[a-z0-9\-_]+\.)+)(\$)?([A-Z0-9\-_]+)\}#', $text_blocks, $varrefs, PREG_SET_ORDER);
+
+ foreach ($varrefs as $var_val)
+ {
+ $namespace = $var_val[1];
+ $varname = $var_val[3];
+ $new = $this->generate_block_varref($namespace, $varname, true, $var_val[2]);
+
+ $text_blocks = str_replace($var_val[0], $new, $text_blocks);
+ }
+
+ // This will handle the remaining root-level varrefs
+ // transform vars prefixed by L_ into their language variable pendant if nothing is set within the tpldata array
+ if (strpos($text_blocks, '{L_') !== false)
+ {
+ $text_blocks = preg_replace('#\{L_([a-z0-9\-_]*)\}#is', "<?php echo ((isset(\$_rootref['L_\\1'])) ? \$_rootref['L_\\1'] : ((isset(\$_lang['\\1'])) ? \$_lang['\\1'] : '{ \\1 }')); ?>", $text_blocks);
+ }
+
+ // Handle addslashed language variables prefixed with LA_
+ // If a template variable already exist, it will be used in favor of it...
+ if (strpos($text_blocks, '{LA_') !== false)
+ {
+ $text_blocks = preg_replace('#\{LA_([a-z0-9\-_]*)\}#is', "<?php echo ((isset(\$_rootref['LA_\\1'])) ? \$_rootref['LA_\\1'] : ((isset(\$_rootref['L_\\1'])) ? addslashes(\$_rootref['L_\\1']) : ((isset(\$_lang['\\1'])) ? addslashes(\$_lang['\\1']) : '{ \\1 }'))); ?>", $text_blocks);
+ }
+
+ // Handle remaining varrefs
+ $text_blocks = preg_replace('#\{([a-z0-9\-_]*)\}#is', "<?php echo (isset(\$_rootref['\\1'])) ? \$_rootref['\\1'] : ''; ?>", $text_blocks);
+ $text_blocks = preg_replace('#\{\$([a-z0-9\-_]*)\}#is', "<?php echo (isset(\$_tpldata['DEFINE']['.']['\\1'])) ? \$_tpldata['DEFINE']['.']['\\1'] : ''; ?>", $text_blocks);
+
+ return $text_blocks;
+ }
+
+ /**
+ * Compile blocks
+ * @access private
+ */
+ private function compile_tag_block($tag_args)
+ {
+ $no_nesting = false;
+
+ // Is the designer wanting to call another loop in a loop?
+ // <!-- BEGIN loop -->
+ // <!-- BEGIN !loop2 -->
+ // <!-- END !loop2 -->
+ // <!-- END loop -->
+ // 'loop2' is actually on the same nesting level as 'loop' you assign
+ // variables to it with template->assign_block_vars('loop2', array(...))
+ if (strpos($tag_args, '!') === 0)
+ {
+ // Count the number if ! occurrences (not allowed in vars)
+ $no_nesting = substr_count($tag_args, '!');
+ $tag_args = substr($tag_args, $no_nesting);
+ }
+
+ // Allow for control of looping (indexes start from zero):
+ // foo(2) : Will start the loop on the 3rd entry
+ // foo(-2) : Will start the loop two entries from the end
+ // foo(3,4) : Will start the loop on the fourth entry and end it on the fifth
+ // foo(3,-4) : Will start the loop on the fourth entry and end it four from last
+ $match = array();
+ if (preg_match('#^([^()]*)\(([\-\d]+)(?:,([\-\d]+))?\)$#', $tag_args, $match))
+ {
+ $tag_args = $match[1];
+
+ if ($match[2] < 0)
+ {
+ $loop_start = '($_' . $tag_args . '_count ' . $match[2] . ' < 0 ? 0 : $_' . $tag_args . '_count ' . $match[2] . ')';
+ }
+ else
+ {
+ $loop_start = '($_' . $tag_args . '_count < ' . $match[2] . ' ? $_' . $tag_args . '_count : ' . $match[2] . ')';
+ }
+
+ if (strlen($match[3]) < 1 || $match[3] == -1)
+ {
+ $loop_end = '$_' . $tag_args . '_count';
+ }
+ else if ($match[3] >= 0)
+ {
+ $loop_end = '(' . ($match[3] + 1) . ' > $_' . $tag_args . '_count ? $_' . $tag_args . '_count : ' . ($match[3] + 1) . ')';
+ }
+ else //if ($match[3] < -1)
+ {
+ $loop_end = '$_' . $tag_args . '_count' . ($match[3] + 1);
+ }
+ }
+ else
+ {
+ $loop_start = 0;
+ $loop_end = '$_' . $tag_args . '_count';
+ }
+
+ $tag_template_php = '';
+ array_push($this->block_names, $tag_args);
+
+ if ($no_nesting !== false)
+ {
+ // We need to implode $no_nesting times from the end...
+ $block = array_slice($this->block_names, -$no_nesting);
+ }
+ else
+ {
+ $block = $this->block_names;
+ }
+
+ if (sizeof($block) < 2)
+ {
+ // Block is not nested.
+ $tag_template_php = '$_' . $tag_args . "_count = (isset(\$_tpldata['$tag_args'])) ? sizeof(\$_tpldata['$tag_args']) : 0;";
+ $varref = "\$_tpldata['$tag_args']";
+ }
+ else
+ {
+ // This block is nested.
+ // Generate a namespace string for this block.
+ $namespace = implode('.', $block);
+
+ // Get a reference to the data array for this block that depends on the
+ // current indices of all parent blocks.
+ $varref = $this->generate_block_data_ref($namespace, false);
+
+ // Create the for loop code to iterate over this block.
+ $tag_template_php = '$_' . $tag_args . '_count = (isset(' . $varref . ')) ? sizeof(' . $varref . ') : 0;';
+ }
+
+ $tag_template_php .= 'if ($_' . $tag_args . '_count) {';
+
+ /**
+ * The following uses foreach for iteration instead of a for loop, foreach is faster but requires PHP to make a copy of the contents of the array which uses more memory
+ * <code>
+ * if (!$offset)
+ * {
+ * $tag_template_php .= 'foreach (' . $varref . ' as $_' . $tag_args . '_i => $_' . $tag_args . '_val){';
+ * }
+ * </code>
+ */
+
+ $tag_template_php .= 'for ($_' . $tag_args . '_i = ' . $loop_start . '; $_' . $tag_args . '_i < ' . $loop_end . '; ++$_' . $tag_args . '_i){';
+ $tag_template_php .= '$_'. $tag_args . '_val = &' . $varref . '[$_'. $tag_args. '_i];';
+
+ return $tag_template_php;
+ }
+
+ /**
+ * Compile a general expression - much of this is from Smarty with
+ * some adaptions for our block level methods
+ * @access private
+ */
+ private function compile_expression($tag_args)
+ {
+ $match = array();
+ preg_match_all('/(?:
+ "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" |
+ \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' |
+ [(),] |
+ [^\s(),]+)/x', $tag_args, $match);
+
+ $tokens = $match[0];
+ $is_arg_stack = array();
+
+ for ($i = 0, $size = sizeof($tokens); $i < $size; $i++)
+ {
+ $token = &$tokens[$i];
+
+ switch ($token)
+ {
+ case '!==':
+ case '===':
+ case '<<':
+ case '>>':
+ case '|':
+ case '^':
+ case '&':
+ case '~':
+ case ')':
+ case ',':
+ case '+':
+ case '-':
+ case '*':
+ case '/':
+ case '@':
+ break;
+
+ case '==':
+ case 'eq':
+ $token = '==';
+ break;
+
+ case '!=':
+ case '<>':
+ case 'ne':
+ case 'neq':
+ $token = '!=';
+ break;
+
+ case '<':
+ case 'lt':
+ $token = '<';
+ break;
+
+ case '<=':
+ case 'le':
+ case 'lte':
+ $token = '<=';
+ break;
+
+ case '>':
+ case 'gt':
+ $token = '>';
+ break;
+
+ case '>=':
+ case 'ge':
+ case 'gte':
+ $token = '>=';
+ break;
+
+ case '&&':
+ case 'and':
+ $token = '&&';
+ break;
+
+ case '||':
+ case 'or':
+ $token = '||';
+ break;
+
+ case '!':
+ case 'not':
+ $token = '!';
+ break;
+
+ case '%':
+ case 'mod':
+ $token = '%';
+ break;
+
+ case '(':
+ array_push($is_arg_stack, $i);
+ break;
+
+ case 'is':
+ $is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1;
+ $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
+
+ $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
+
+ array_splice($tokens, $is_arg_start, sizeof($tokens), $new_tokens);
+
+ $i = $is_arg_start;
+
+ // no break
+
+ default:
+ $varrefs = array();
+ if (preg_match('#^((?:[a-z0-9\-_]+\.)+)?(\$)?(?=[A-Z])([A-Z0-9\-_]+)#s', $token, $varrefs))
+ {
+ if (!empty($varrefs[1]))
+ {
+ $namespace = substr($varrefs[1], 0, -1);
+ $namespace = (strpos($namespace, '.') === false) ? $namespace : strrchr($namespace, '.');
+
+ // S_ROW_COUNT is deceptive, it returns the current row number not the number of rows
+ // hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM
+ switch ($varrefs[3])
+ {
+ case 'S_ROW_NUM':
+ case 'S_ROW_COUNT':
+ $token = "\$_${namespace}_i";
+ break;
+
+ case 'S_NUM_ROWS':
+ $token = "\$_${namespace}_count";
+ break;
+
+ case 'S_FIRST_ROW':
+ $token = "(\$_${namespace}_i == 0)";
+ break;
+
+ case 'S_LAST_ROW':
+ $token = "(\$_${namespace}_i == \$_${namespace}_count - 1)";
+ break;
+
+ case 'S_BLOCK_NAME':
+ $token = "'$namespace'";
+ break;
+
+ default:
+ $token = $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[2]) . '[\'' . $varrefs[3] . '\']';
+ break;
+ }
+ }
+ else
+ {
+ $token = ($varrefs[2]) ? '$_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[3] . '\']' : '$_rootref[\'' . $varrefs[3] . '\']';
+ }
+ }
+ else if (preg_match('#^\.((?:[a-z0-9\-_]+\.?)+)$#s', $token, $varrefs))
+ {
+ // Allow checking if loops are set with .loopname
+ // It is also possible to check the loop count by doing <!-- IF .loopname > 1 --> for example
+ $blocks = explode('.', $varrefs[1]);
+
+ // If the block is nested, we have a reference that we can grab.
+ // If the block is not nested, we just go and grab the block from _tpldata
+ if (sizeof($blocks) > 1)
+ {
+ $block = array_pop($blocks);
+ $namespace = implode('.', $blocks);
+ $varref = $this->generate_block_data_ref($namespace, true);
+
+ // Add the block reference for the last child.
+ $varref .= "['" . $block . "']";
+ }
+ else
+ {
+ $varref = '$_tpldata';
+
+ // Add the block reference for the last child.
+ $varref .= "['" . $blocks[0] . "']";
+ }
+ $token = "isset($varref) && sizeof($varref)";
+ }
+
+ break;
+ }
+ }
+
+ return $tokens;
+ }
+
+
+ private function compile_tag_if($tag_args, $elseif)
+ {
+ $tokens = $this->compile_expression($tag_args);
+ return (($elseif) ? '} else if (' : 'if (') . (implode(' ', $tokens) . ') { ');
+ }
+
+ /**
+ * Compile DEFINE tags
+ * @access private
+ */
+ private function compile_tag_define($tag_args, $op)
+ {
+ $match = array();
+ preg_match('#^((?:[a-z0-9\-_]+\.)+)?\$(?=[A-Z])([A-Z0-9_\-]*)(?: = (.*?))?$#', $tag_args, $match);
+
+ if (empty($match[2]) || (!isset($match[3]) && $op))
+ {
+ return '';
+ }
+
+ if (!$op)
+ {
+ return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ');';
+ }
+
+ $parsed_statement = implode(' ', $this->compile_expression($match[3]));
+
+ return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ' = ' . $parsed_statement . ';';
+ }
+
+ /**
+ * Compile INCLUDE tag
+ * @access private
+ */
+ private function compile_tag_include($tag_args)
+ {
+ return "\$this->_tpl_include('$tag_args');";
+ }
+
+ /**
+ * Compile INCLUDE_PHP tag
+ * @access private
+ */
+ private function compile_tag_include_php($tag_args)
+ {
+ return "include('" . $tag_args . "');";
+ }
+
+ /**
+ * parse expression
+ * This is from Smarty
+ * @access private
+ */
+ private function _parse_is_expr($is_arg, $tokens)
+ {
+ $expr_end = 0;
+ $negate_expr = false;
+
+ if (($first_token = array_shift($tokens)) == 'not')
+ {
+ $negate_expr = true;
+ $expr_type = array_shift($tokens);
+ }
+ else
+ {
+ $expr_type = $first_token;
+ }
+
+ switch ($expr_type)
+ {
+ case 'even':
+ if (@$tokens[$expr_end] == 'by')
+ {
+ $expr_end++;
+ $expr_arg = $tokens[$expr_end++];
+ $expr = "!(($is_arg / $expr_arg) & 1)";
+ }
+ else
+ {
+ $expr = "!($is_arg & 1)";
+ }
+ break;
+
+ case 'odd':
+ if (@$tokens[$expr_end] == 'by')
+ {
+ $expr_end++;
+ $expr_arg = $tokens[$expr_end++];
+ $expr = "(($is_arg / $expr_arg) & 1)";
+ }
+ else
+ {
+ $expr = "($is_arg & 1)";
+ }
+ break;
+
+ case 'div':
+ if (@$tokens[$expr_end] == 'by')
+ {
+ $expr_end++;
+ $expr_arg = $tokens[$expr_end++];
+ $expr = "!($is_arg % $expr_arg)";
+ }
+ break;
+ }
+
+ if ($negate_expr)
+ {
+ $expr = "!($expr)";
+ }
+
+ array_splice($tokens, 0, $expr_end, $expr);
+
+ return $tokens;
+ }
+
+ /**
+ * Generates a reference to the given variable inside the given (possibly nested)
+ * block namespace. This is a string of the form:
+ * ' . $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . '
+ * It's ready to be inserted into an "echo" line in one of the templates.
+ *
+ * @access private
+ * @param string $namespace Namespace to access (expects a trailing "." on the namespace)
+ * @param string $varname Variable name to use
+ * @param bool $echo If true return an echo statement, otherwise a reference to the internal variable
+ * @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable
+ * @return string Code to access variable or echo it if $echo is true
+ */
+ private function generate_block_varref($namespace, $varname, $echo = true, $defop = false)
+ {
+ // Strip the trailing period.
+ $namespace = substr($namespace, 0, -1);
+
+ $expr = true;
+
+ // S_ROW_COUNT is deceptive, it returns the current row number not the number of rows
+ // hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM
+ switch ($varname)
+ {
+ case 'S_ROW_NUM':
+ case 'S_ROW_COUNT':
+ $varref = "\$_${namespace}_i";
+ break;
+
+ case 'S_NUM_ROWS':
+ $varref = "\$_${namespace}_count";
+ break;
+
+ case 'S_FIRST_ROW':
+ $varref = "(\$_${namespace}_i == 0)";
+ break;
+
+ case 'S_LAST_ROW':
+ $varref = "(\$_${namespace}_i == \$_${namespace}_count - 1)";
+ break;
+
+ case 'S_BLOCK_NAME':
+ $varref = "'$namespace'";
+ break;
+
+ default:
+ // Get a reference to the data block for this namespace.
+ $varref = $this->generate_block_data_ref($namespace, true, $defop);
+ // Prepend the necessary code to stick this in an echo line.
+
+ // Append the variable reference.
+ $varref .= "['$varname']";
+
+ $expr = false;
+ break;
+ }
+ // @todo Test the !$expr more
+ $varref = ($echo) ? "<?php echo $varref; ?>" : (($expr || isset($varref)) ? $varref : '');
+
+ return $varref;
+ }
+
+ /**
+ * Generates a reference to the array of data values for the given
+ * (possibly nested) block namespace. This is a string of the form:
+ * $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN']
+ *
+ * @access private
+ * @param string $blockname Block to access (does not expect a trailing "." on the blockname)
+ * @param bool $include_last_iterator If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above.
+ * @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable
+ * @return string Code to access variable
+ */
+ private function generate_block_data_ref($blockname, $include_last_iterator, $defop = false)
+ {
+ // Get an array of the blocks involved.
+ $blocks = explode('.', $blockname);
+ $blockcount = sizeof($blocks) - 1;
+
+ // DEFINE is not an element of any referenced variable, we must use _tpldata to access it
+ if ($defop)
+ {
+ $varref = '$_tpldata[\'DEFINE\']';
+ // Build up the string with everything but the last child.
+ for ($i = 0; $i < $blockcount; $i++)
+ {
+ $varref .= "['" . $blocks[$i] . "'][\$_" . $blocks[$i] . '_i]';
+ }
+ // Add the block reference for the last child.
+ $varref .= "['" . $blocks[$blockcount] . "']";
+ // Add the iterator for the last child if requried.
+ if ($include_last_iterator)
+ {
+ $varref .= '[$_' . $blocks[$blockcount] . '_i]';
+ }
+ return $varref;
+ }
+ else if ($include_last_iterator)
+ {
+ return '$_'. $blocks[$blockcount] . '_val';
+ }
+ else
+ {
+ return '$_'. $blocks[$blockcount - 1] . '_val[\''. $blocks[$blockcount]. '\']';
+ }
+ }
+}
+
+stream_filter_register('template', 'template_filter');
+
+/**
+* Extension of template class - Functions needed for compiling templates only.
+*
+* psoTFX, phpBB Development Team - Completion of file caching, decompilation
+* routines and implementation of conditionals/keywords and associated changes
+*
+* The interface was inspired by PHPLib templates, and the template file (formats are
+* quite similar)
+*
+* The keyword/conditional implementation is currently based on sections of code from
+* the Smarty templating engine (c) 2001 ispi of Lincoln, Inc. which is released
+* (on its own and in whole) under the LGPL. Section 3 of the LGPL states that any code
+* derived from an LGPL application may be relicenced under the GPL, this applies
+* to this source
+*
+* DEFINE directive inspired by a request by Cyberalien
+*
+* @package phpBB3
+* @uses template_filter As a PHP stream filter to perform compilation of templates
+*/
+class template_compile
+{
+ /**
+ * @var template Reference to the {@link template template} object performing compilation
+ */
+ private $template;
+
+ /**
+ * Constructor
+ * @param template $template {@link template Template} object performing compilation
+ */
+ function __construct(template $template)
+ {
+ $this->template = $template;
+ }
+
+ /**
+ * Load template source from file
+ * @access public
+ * @param string $handle Template handle we wish to load
+ * @return bool Return true on success otherwise false
+ */
+ public function _tpl_load_file($handle)
+ {
+ // Try and open template for read
+ if (!file_exists($this->template->files[$handle]))
+ {
+ trigger_error("template->_tpl_load_file(): File {$this->template->files[$handle]} does not exist or is empty", E_USER_ERROR);
+ }
+
+ // Actually compile the code now.
+ return $this->compile_write($handle, $this->template->files[$handle]);
+ }
+
+ /**
+ * Load template source from file
+ * @access public
+ * @param string $handle Template handle we wish to compile
+ * @return string|bool Return compiled code on successful compilation otherwise false
+ */
+ public function _tpl_gen_src($handle)
+ {
+ // Try and open template for read
+ if (!file_exists($this->template->files[$handle]))
+ {
+ trigger_error("template->_tpl_load_file(): File {$this->template->files[$handle]} does not exist or is empty", E_USER_ERROR);
+ }
+
+ // Actually compile the code now.
+ return $this->compile_gen($this->template->files[$handle]);
+ }
+
+ /**
+ * Write compiled file to cache directory
+ * @access private
+ * @param string $handle Template handle to compile
+ * @param string $source_file Source template file
+ * @return bool Return true on success otherwise false
+ */
+ private function compile_write($handle, $source_file)
+ {
+ $filename = $this->template->cachepath . str_replace('/', '.', $this->template->filename[$handle]) . '.' . PHP_EXT;
+
+ $source_handle = @fopen($source_file, 'rb');
+ $destination_handle = @fopen($filename, 'wb');
+
+ if (!$source_handle || !$destination_handle)
+ {
+ return false;
+ }
+
+ @flock($destination_handle, LOCK_EX);
+
+ stream_filter_append($source_handle, 'template');
+ stream_copy_to_stream($source_handle, $destination_handle);
+
+ @fclose($source_handle);
+ @flock($destination_handle, LOCK_UN);
+ @fclose($destination_handle);
+
+ phpbb_chmod($filename, CHMOD_WRITE);
+
+ clearstatcache();
+
+ return true;
+ }
+
+ /**
+ * Generate source for eval()
+ * @access private
+ * @param string $source_file Source template file
+ * @return string|bool Return compiled code on successful compilation otherwise false
+ */
+ private function compile_gen($source_file)
+ {
+ $source_handle = @fopen($source_file, 'rb');
+ $destination_handle = @fopen('php://temp' ,'r+b');
+
+ if (!$source_handle || !$destination_handle)
+ {
+ return false;
+ }
+
+ stream_filter_append($source_handle, 'template');
+ stream_copy_to_stream($source_handle, $destination_handle);
+
+ @fclose($source_handle);
+
+ rewind($destination_handle);
+ return stream_get_contents($destination_handle);
+ }
+}
+
+?> \ No newline at end of file