diff options
Diffstat (limited to 'phpBB/includes/functions.php')
-rw-r--r-- | phpBB/includes/functions.php | 2326 |
1 files changed, 1344 insertions, 982 deletions
diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index eef4ade4e7..689a682de3 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2,9 +2,8 @@ /** * * @package phpBB3 -* @version $Id$ * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 * */ @@ -17,123 +16,82 @@ if (!defined('IN_PHPBB')) } // Common global functions - /** -* set_var -* -* Set variable, used by {@link request_var the request_var function} +* Casts a variable to the given type. * -* @access private +* @deprecated */ function set_var(&$result, $var, $type, $multibyte = false) { - settype($var, $type); - $result = $var; - - if ($type == 'string') - { - $result = trim(htmlspecialchars(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result), ENT_COMPAT, 'UTF-8')); - - if (!empty($result)) - { - // Make sure multibyte characters are wellformed - if ($multibyte) - { - if (!preg_match('/^./u', $result)) - { - $result = ''; - } - } - else - { - // no multibyte, allow only ASCII (0-127) - $result = preg_replace('/[\x80-\xFF]/', '?', $result); - } - } - - $result = (STRIP) ? stripslashes($result) : $result; - } + // no need for dependency injection here, if you have the object, call the method yourself! + $type_cast_helper = new \phpbb\request\type_cast_helper(); + $type_cast_helper->set_var($result, $var, $type, $multibyte); } /** -* request_var +* Wrapper function of \phpbb\request\request::variable which exists for backwards compatability. +* See {@link \phpbb\request\request_interface::variable \phpbb\request\request_interface::variable} for +* documentation of this function's use. * -* Used to get passed variable +* @deprecated +* @param mixed $var_name The form variable's name from which data shall be retrieved. +* If the value is an array this may be an array of indizes which will give +* direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") +* then specifying array("var", 1) as the name will return "a". +* If you pass an instance of {@link \phpbb\request\request_interface phpbb_request_interface} +* as this parameter it will overwrite the current request class instance. If you do +* not do so, it will create its own instance (but leave superglobals enabled). +* @param mixed $default A default value that is returned if the variable was not set. +* This function will always return a value of the same type as the default. +* @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters +* Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks +* @param bool $cookie This param is mapped to \phpbb\request\request_interface::COOKIE as the last param for +* \phpbb\request\request_interface::variable for backwards compatability reasons. +* @param \phpbb\request\request_interface|null|false If an instance of \phpbb\request\request_interface is given the instance is stored in +* a static variable and used for all further calls where this parameters is null. Until +* the function is called with an instance it automatically creates a new \phpbb\request\request +* instance on every call. By passing false this per-call instantiation can be restored +* after having passed in a \phpbb\request\request_interface instance. +* +* @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the +* the same as that of $default. If the variable is not set $default is returned. */ -function request_var($var_name, $default, $multibyte = false, $cookie = false) +function request_var($var_name, $default, $multibyte = false, $cookie = false, $request = null) { - if (!$cookie && isset($_COOKIE[$var_name])) - { - if (!isset($_GET[$var_name]) && !isset($_POST[$var_name])) - { - return (is_array($default)) ? array() : $default; - } - $_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name]; - } + // This is all just an ugly hack to add "Dependency Injection" to a function + // the only real code is the function call which maps this function to a method. + static $static_request = null; - $super_global = ($cookie) ? '_COOKIE' : '_REQUEST'; - if (!isset($GLOBALS[$super_global][$var_name]) || is_array($GLOBALS[$super_global][$var_name]) != is_array($default)) + if ($request instanceof \phpbb\request\request_interface) { - return (is_array($default)) ? array() : $default; - } + $static_request = $request; - $var = $GLOBALS[$super_global][$var_name]; - if (!is_array($default)) - { - $type = gettype($default); - } - else - { - list($key_type, $type) = each($default); - $type = gettype($type); - $key_type = gettype($key_type); - if ($type == 'array') + if (empty($var_name)) { - reset($default); - $default = current($default); - list($sub_key_type, $sub_type) = each($default); - $sub_type = gettype($sub_type); - $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type; - $sub_key_type = gettype($sub_key_type); + return; } } - - if (is_array($var)) + else if ($request === false) { - $_var = $var; - $var = array(); + $static_request = null; - foreach ($_var as $k => $v) + if (empty($var_name)) { - set_var($k, $k, $key_type); - if ($type == 'array' && is_array($v)) - { - foreach ($v as $_k => $_v) - { - if (is_array($_v)) - { - $_v = null; - } - set_var($_k, $_k, $sub_key_type, $multibyte); - set_var($var[$k][$_k], $_v, $sub_type, $multibyte); - } - } - else - { - if ($type == 'array' || is_array($v)) - { - $v = null; - } - set_var($var[$k], $v, $type, $multibyte); - } + return; } } - else + + $tmp_request = $static_request; + + // no request class set, create a temporary one ourselves to keep backwards compatability + if ($tmp_request === null) { - set_var($var, $var, $type, $multibyte); + // false param: enable super globals, so the created request class does not + // make super globals inaccessible everywhere outside this function. + $tmp_request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false); } - return $var; + return $tmp_request->variable($var_name, $default, $multibyte, ($cookie) ? \phpbb\request\request_interface::COOKIE : \phpbb\request\request_interface::REQUEST); } /** @@ -149,31 +107,24 @@ function request_var($var_name, $default, $multibyte = false, $cookie = false) * efficiently cached. * * @return null +* +* @deprecated */ -function set_config($config_name, $config_value, $is_dynamic = false) +function set_config($config_name, $config_value, $is_dynamic = false, \phpbb\config\config $set_config = null) { - global $db, $cache, $config; + static $config = null; - $sql = 'UPDATE ' . CONFIG_TABLE . " - SET config_value = '" . $db->sql_escape($config_value) . "' - WHERE config_name = '" . $db->sql_escape($config_name) . "'"; - $db->sql_query($sql); - - if (!$db->sql_affectedrows() && !isset($config[$config_name])) + if ($set_config !== null) { - $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array( - 'config_name' => $config_name, - 'config_value' => $config_value, - 'is_dynamic' => ($is_dynamic) ? 1 : 0)); - $db->sql_query($sql); - } - - $config[$config_name] = $config_value; + $config = $set_config; - if (!$is_dynamic) - { - $cache->destroy('config'); + if (empty($config_name)) + { + return; + } } + + $config->set($config_name, $config_value, !$is_dynamic); } /** @@ -186,35 +137,24 @@ function set_config($config_name, $config_value, $is_dynamic = false) * efficiently cached. * * @return null +* +* @deprecated */ -function set_config_count($config_name, $increment, $is_dynamic = false) +function set_config_count($config_name, $increment, $is_dynamic = false, \phpbb\config\config $set_config = null) { - global $db, $cache; + static $config = null; - switch ($db->sql_layer) + if ($set_config !== null) { - case 'firebird': - // Precision must be from 1 to 18 - $sql_update = 'CAST(CAST(config_value as DECIMAL(18, 0)) + ' . (int) $increment . ' as VARCHAR(255))'; - break; + $config = $set_config; - case 'postgres': - // Need to cast to text first for PostgreSQL 7.x - $sql_update = 'CAST(CAST(config_value::text as DECIMAL(255, 0)) + ' . (int) $increment . ' as VARCHAR(255))'; - break; - - // MySQL, SQlite, mssql, mssql_odbc, oracle - default: - $sql_update = 'config_value + ' . (int) $increment; - break; + if (empty($config_name)) + { + return; + } } - $db->sql_query('UPDATE ' . CONFIG_TABLE . ' SET config_value = ' . $sql_update . " WHERE config_name = '" . $db->sql_escape($config_name) . "'"); - - if (!$is_dynamic) - { - $cache->destroy('config'); - } + $config->increment($config_name, $increment, !$is_dynamic); } /** @@ -428,73 +368,27 @@ function still_on_time($extra_time = 15) } /** +* Hash the password * -* @version Version 0.1 / slightly modified for phpBB 3.0.x (using $H$ as hash type identifier) -* -* Portable PHP password hashing framework. -* -* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in -* the public domain. -* -* There's absolutely no warranty. -* -* The homepage URL for this framework is: -* -* http://www.openwall.com/phpass/ -* -* Please be sure to update the Version line if you edit this file in any way. -* It is suggested that you leave the main version number intact, but indicate -* your project name (after the slash) and add your own revision information. -* -* Please do not change the "private" password hashing method implemented in -* here, thereby making your hashes incompatible. However, if you must, please -* change the hash type identifier (the "$P$") to something different. -* -* Obviously, since this code is in the public domain, the above are not -* requirements (there can be none), but merely suggestions. +* @deprecated 3.1.0-a2 (To be removed: 3.3.0) * +* @param string $password Password to be hashed * -* Hash the password +* @return string|bool Password hash or false if something went wrong during hashing */ function phpbb_hash($password) { - $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - - $random_state = unique_id(); - $random = ''; - $count = 6; - - if (($fh = @fopen('/dev/urandom', 'rb'))) - { - $random = fread($fh, $count); - fclose($fh); - } + global $phpbb_container; - if (strlen($random) < $count) - { - $random = ''; - - for ($i = 0; $i < $count; $i += 16) - { - $random_state = md5(unique_id() . $random_state); - $random .= pack('H*', md5($random_state)); - } - $random = substr($random, 0, $count); - } - - $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64); - - if (strlen($hash) == 34) - { - return $hash; - } - - return md5($password); + $passwords_manager = $phpbb_container->get('passwords.manager'); + return $passwords_manager->hash($password); } /** * Check for correct password * +* @deprecated 3.1.0-a2 (To be removed: 3.3.0) +* * @param string $password The password in plain text * @param string $hash The stored password hash * @@ -502,142 +396,10 @@ function phpbb_hash($password) */ function phpbb_check_hash($password, $hash) { - if (strlen($password) > 4096) - { - // If the password is too huge, we will simply reject it - // and not let the server try to hash it. - return false; - } - - $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - if (strlen($hash) == 34) - { - return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false; - } + global $phpbb_container; - return (md5($password) === $hash) ? true : false; -} - -/** -* Generate salt for hash generation -*/ -function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6) -{ - if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) - { - $iteration_count_log2 = 8; - } - - $output = '$H$'; - $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)]; - $output .= _hash_encode64($input, 6, $itoa64); - - return $output; -} - -/** -* Encode hash -*/ -function _hash_encode64($input, $count, &$itoa64) -{ - $output = ''; - $i = 0; - - do - { - $value = ord($input[$i++]); - $output .= $itoa64[$value & 0x3f]; - - if ($i < $count) - { - $value |= ord($input[$i]) << 8; - } - - $output .= $itoa64[($value >> 6) & 0x3f]; - - if ($i++ >= $count) - { - break; - } - - if ($i < $count) - { - $value |= ord($input[$i]) << 16; - } - - $output .= $itoa64[($value >> 12) & 0x3f]; - - if ($i++ >= $count) - { - break; - } - - $output .= $itoa64[($value >> 18) & 0x3f]; - } - while ($i < $count); - - return $output; -} - -/** -* The crypt function/replacement -*/ -function _hash_crypt_private($password, $setting, &$itoa64) -{ - $output = '*'; - - // Check for correct hash - if (substr($setting, 0, 3) != '$H$' && substr($setting, 0, 3) != '$P$') - { - return $output; - } - - $count_log2 = strpos($itoa64, $setting[3]); - - if ($count_log2 < 7 || $count_log2 > 30) - { - return $output; - } - - $count = 1 << $count_log2; - $salt = substr($setting, 4, 8); - - if (strlen($salt) != 8) - { - return $output; - } - - /** - * We're kind of forced to use MD5 here since it's the only - * cryptographic primitive available in all versions of PHP - * currently in use. To implement our own low-level crypto - * in PHP would result in much worse performance and - * consequently in lower iteration counts and hashes that are - * quicker to crack (by non-PHP code). - */ - if (PHP_VERSION >= 5) - { - $hash = md5($salt . $password, true); - do - { - $hash = md5($hash . $password, true); - } - while (--$count); - } - else - { - $hash = pack('H*', md5($salt . $password)); - do - { - $hash = pack('H*', md5($hash . $password)); - } - while (--$count); - } - - $output = substr($setting, 0, 12); - $output .= _hash_encode64($hash, 16, $itoa64); - - return $output; + $passwords_manager = $phpbb_container->get('passwords.manager'); + return $passwords_manager->check($password, $hash); } /** @@ -915,102 +677,13 @@ function phpbb_is_writable($file) } } -// Compatibility functions - -if (!function_exists('array_combine')) -{ - /** - * A wrapper for the PHP5 function array_combine() - * @param array $keys contains keys for the resulting array - * @param array $values contains values for the resulting array - * - * @return Returns an array by using the values from the keys array as keys and the - * values from the values array as the corresponding values. Returns false if the - * number of elements for each array isn't equal or if the arrays are empty. - */ - function array_combine($keys, $values) - { - $keys = array_values($keys); - $values = array_values($values); - - $n = sizeof($keys); - $m = sizeof($values); - if (!$n || !$m || ($n != $m)) - { - return false; - } - - $combined = array(); - for ($i = 0; $i < $n; $i++) - { - $combined[$keys[$i]] = $values[$i]; - } - return $combined; - } -} - -if (!function_exists('str_split')) -{ - /** - * A wrapper for the PHP5 function str_split() - * @param array $string contains the string to be converted - * @param array $split_length contains the length of each chunk - * - * @return Converts a string to an array. If the optional split_length parameter is specified, - * the returned array will be broken down into chunks with each being split_length in length, - * otherwise each chunk will be one character in length. FALSE is returned if split_length is - * less than 1. If the split_length length exceeds the length of string, the entire string is - * returned as the first (and only) array element. - */ - function str_split($string, $split_length = 1) - { - if ($split_length < 1) - { - return false; - } - else if ($split_length >= strlen($string)) - { - return array($string); - } - else - { - preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches); - return $matches[0]; - } - } -} - -if (!function_exists('stripos')) -{ - /** - * A wrapper for the PHP5 function stripos - * Find position of first occurrence of a case-insensitive string - * - * @param string $haystack is the string to search in - * @param string $needle is the string to search for - * - * @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive. - * Note that the needle may be a string of one or more characters. - * If needle is not found, stripos() will return boolean FALSE. - */ - function stripos($haystack, $needle) - { - if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m)) - { - return strpos($haystack, $m[0]); - } - - return false; - } -} - /** * Checks if a path ($path) is absolute or relative * * @param string $path Path to check absoluteness of * @return boolean */ -function is_absolute($path) +function phpbb_is_absolute($path) { return (isset($path[0]) && $path[0] == '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false; } @@ -1023,6 +696,8 @@ function is_absolute($path) */ function phpbb_own_realpath($path) { + global $request; + // Now to perform funky shizzle // Switch to use UNIX slashes @@ -1030,7 +705,7 @@ function phpbb_own_realpath($path) $path_prefix = ''; // Determine what sort of path we have - if (is_absolute($path)) + if (phpbb_is_absolute($path)) { $absolute = true; @@ -1066,11 +741,12 @@ function phpbb_own_realpath($path) $path_prefix = ''; } } - else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME'])) + else if ($request->server('SCRIPT_FILENAME')) { // Warning: If chdir() has been used this will lie! // Warning: This has some problems sometime (CLI can create them easily) - $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path; + $filename = htmlspecialchars_decode($request->server('SCRIPT_FILENAME')); + $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($filename)) . '/' . $path; $absolute = true; $path_prefix = ''; } @@ -1212,43 +888,41 @@ else /** * Eliminates useless . and .. components from specified path. * +* Deprecated, use filesystem class instead +* * @param string $path Path to clean * @return string Cleaned path +* +* @deprecated */ function phpbb_clean_path($path) { - $exploded = explode('/', $path); - $filtered = array(); - foreach ($exploded as $part) + global $phpbb_path_helper, $phpbb_container; + + if (!$phpbb_path_helper && $phpbb_container) { - if ($part === '.' && !empty($filtered)) + $phpbb_path_helper = $phpbb_container->get('path_helper'); + } + else if (!$phpbb_path_helper) + { + // The container is not yet loaded, use a new instance + if (!class_exists('\phpbb\path_helper')) { - continue; + global $phpbb_root_path, $phpEx; + require($phpbb_root_path . 'phpbb/path_helper.' . $phpEx); } - if ($part === '..' && !empty($filtered) && $filtered[sizeof($filtered) - 1] !== '..') - { - array_pop($filtered); - } - else - { - $filtered[] = $part; - } + $phpbb_path_helper = new phpbb\path_helper( + new phpbb\symfony_request( + new phpbb\request\request() + ), + new phpbb\filesystem(), + $phpbb_root_path, + $phpEx + ); } - $path = implode('/', $filtered); - return $path; -} -if (!function_exists('htmlspecialchars_decode')) -{ - /** - * A wrapper for htmlspecialchars_decode - * @ignore - */ - function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT) - { - return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style))); - } + return $phpbb_path_helper->clean_path($path); } // functions used for building option fields @@ -1302,32 +976,203 @@ function style_select($default = '', $all = false) } /** +* Format the timezone offset with hours and minutes +* +* @param int $tz_offset Timezone offset in seconds +* @return string Normalized offset string: -7200 => -02:00 +* 16200 => +04:30 +*/ +function phpbb_format_timezone_offset($tz_offset) +{ + $sign = ($tz_offset < 0) ? '-' : '+'; + $time_offset = abs($tz_offset); + + $offset_seconds = $time_offset % 3600; + $offset_minutes = $offset_seconds / 60; + $offset_hours = ($time_offset - $offset_seconds) / 3600; + + $offset_string = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes); + return $offset_string; +} + +/** +* Compares two time zone labels. +* Arranges them in increasing order by timezone offset. +* Places UTC before other timezones in the same offset. +*/ +function phpbb_tz_select_compare($a, $b) +{ + $a_sign = $a[3]; + $b_sign = $b[3]; + if ($a_sign != $b_sign) + { + return $a_sign == '-' ? -1 : 1; + } + + $a_offset = substr($a, 4, 5); + $b_offset = substr($b, 4, 5); + if ($a_offset == $b_offset) + { + $a_name = substr($a, 12); + $b_name = substr($b, 12); + if ($a_name == $b_name) + { + return 0; + } + else if ($a_name == 'UTC') + { + return -1; + } + else if ($b_name == 'UTC') + { + return 1; + } + else + { + return $a_name < $b_name ? -1 : 1; + } + } + else + { + if ($a_sign == '-') + { + return $a_offset > $b_offset ? -1 : 1; + } + else + { + return $a_offset < $b_offset ? -1 : 1; + } + } +} + +/** +* Return list of timezone identifiers +* We also add the selected timezone if we can create an object with it. +* DateTimeZone::listIdentifiers seems to not add all identifiers to the list, +* because some are only kept for backward compatible reasons. If the user has +* a deprecated value, we add it here, so it can still be kept. Once the user +* changed his value, there is no way back to deprecated values. +* +* @param string $selected_timezone Additional timezone that shall +* be added to the list of identiers +* @return array DateTimeZone::listIdentifiers and additional +* selected_timezone if it is a valid timezone. +*/ +function phpbb_get_timezone_identifiers($selected_timezone) +{ + $timezones = DateTimeZone::listIdentifiers(); + + if (!in_array($selected_timezone, $timezones)) + { + try + { + // Add valid timezones that are currently selected but not returned + // by DateTimeZone::listIdentifiers + $validate_timezone = new DateTimeZone($selected_timezone); + $timezones[] = $selected_timezone; + } + catch (Exception $e) + { + } + } + + return $timezones; +} + +/** * Pick a timezone +* +* @param string $default A timezone to select +* @param boolean $truncate Shall we truncate the options text +* +* @return string Returns the options for timezone selector only +* +* @deprecated */ function tz_select($default = '', $truncate = false) { global $user; - $tz_select = ''; - foreach ($user->lang['tz_zones'] as $offset => $zone) + $timezone_select = phpbb_timezone_select($user, $default, $truncate); + return $timezone_select['tz_select']; +} + +/** +* Options to pick a timezone and date/time +* +* @param \phpbb\user $user Object of the current user +* @param string $default A timezone to select +* @param boolean $truncate Shall we truncate the options text +* +* @return array Returns an array, also containing the options for the time selector. +*/ +function phpbb_timezone_select($user, $default = '', $truncate = false) +{ + static $timezones; + + $default_offset = ''; + if (!isset($timezones)) { - if ($truncate) + $unsorted_timezones = phpbb_get_timezone_identifiers($default); + + $timezones = array(); + foreach ($unsorted_timezones as $timezone) { - $zone_trunc = truncate_string($zone, 50, 255, false, '...'); + $tz = new DateTimeZone($timezone); + $dt = new \phpbb\datetime($user, 'now', $tz); + $offset = $dt->getOffset(); + $current_time = $dt->format($user->lang['DATETIME_FORMAT'], true); + $offset_string = phpbb_format_timezone_offset($offset); + $timezones['GMT' . $offset_string . ' - ' . $timezone] = array( + 'tz' => $timezone, + 'offest' => 'GMT' . $offset_string, + 'current' => $current_time, + ); + if ($timezone === $default) + { + $default_offset = 'GMT' . $offset_string; + } } - else + unset($unsorted_timezones); + + uksort($timezones, 'phpbb_tz_select_compare'); + } + + $tz_select = $tz_dates = $opt_group = ''; + + foreach ($timezones as $timezone) + { + if ($opt_group != $timezone['offest']) + { + $tz_select .= ($opt_group) ? '</optgroup>' : ''; + $tz_select .= '<optgroup label="' . $timezone['offest'] . ' - ' . $timezone['current'] . '">'; + $opt_group = $timezone['offest']; + + $selected = ($default_offset == $timezone['offest']) ? ' selected="selected"' : ''; + $tz_dates .= '<option value="' . $timezone['offest'] . ' - ' . $timezone['current'] . '"' . $selected . '>' . $timezone['offest'] . ' - ' . $timezone['current'] . '</option>'; + } + + $label = $timezone['tz']; + if (isset($user->lang['timezones'][$label])) { - $zone_trunc = $zone; + $label = $user->lang['timezones'][$label]; } + $title = $timezone['offest'] . ' - ' . $label; - if (is_numeric($offset)) + if ($truncate) { - $selected = ($offset == $default) ? ' selected="selected"' : ''; - $tz_select .= '<option title="' . $zone . '" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>'; + $label = truncate_string($label, 50, 255, false, '...'); } + + $selected = ($timezone['tz'] === $default) ? ' selected="selected"' : ''; + $tz_select .= '<option title="' . $title . '" value="' . $timezone['tz'] . '"' . $selected . '>' . $label . '</option>'; } + $tz_select .= '</optgroup>'; - return $tz_select; + return array( + 'tz_select' => $tz_select, + 'tz_dates' => $tz_dates, + ); } // Functions handling topic/post tracking/marking @@ -1336,41 +1181,77 @@ function tz_select($default = '', $truncate = false) * Marks a topic/forum as read * Marks a topic as posted to * +* @param string $mode (all, topics, topic, post) +* @param int|bool $forum_id Used in all, topics, and topic mode +* @param int|bool $topic_id Used in topic and post mode +* @param int $post_time 0 means current time(), otherwise to set a specific mark time * @param int $user_id can only be used with $mode == 'post' */ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0) { global $db, $user, $config; + global $request, $phpbb_container; + + $post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time; if ($mode == 'all') { if ($forum_id === false || !sizeof($forum_id)) { + // Mark all forums read (index page) + + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + // Mark all topic notifications read for this user + $phpbb_notifications->mark_notifications_read(array( + 'topic', + 'quote', + 'bookmark', + 'post', + 'approve_topic', + 'approve_post', + ), false, $user->data['user_id'], $post_time); + if ($config['load_db_lastread'] && $user->data['is_registered']) { // Mark all forums read (index page) - $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}"); - $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}"); - $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}"); + $tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE); + foreach ($tables as $table) + { + $sql = 'DELETE FROM ' . $table . " + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time"; + $db->sql_query($sql); + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND user_lastmark < $post_time"; + $db->sql_query($sql); } else if ($config['load_anon_lastread'] || $user->data['is_registered']) { - $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : ''; + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); unset($tracking_topics['tf']); unset($tracking_topics['t']); unset($tracking_topics['f']); - $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36); + $tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36); - $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000); - $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics); + $user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000); + $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), \phpbb\request\request_interface::COOKIE); unset($tracking_topics); if ($user->data['is_registered']) { - $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}"); + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND user_lastmark < $post_time"; + $db->sql_query($sql); } } } @@ -1385,6 +1266,32 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $forum_id = array($forum_id); } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications_read_by_parent(array( + 'topic', + 'approve_topic', + ), $forum_id, $user->data['user_id'], $post_time); + + // Mark all post/quote notifications read for this user in this forum + $topic_ids = array(); + $sql = 'SELECT topic_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id); + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + $topic_ids[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + $phpbb_notifications->mark_notifications_read_by_parent(array( + 'quote', + 'bookmark', + 'post', + 'approve_post', + ), $topic_ids, $user->data['user_id'], $post_time); + // Add 0 to forums array to mark global announcements correctly // $forum_id[] = 0; @@ -1392,6 +1299,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ { $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time AND " . $db->sql_in_set('forum_id', $forum_id); $db->sql_query($sql); @@ -1410,9 +1318,10 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ if (sizeof($sql_update)) { - $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . ' - SET mark_time = ' . time() . " + $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . " + SET mark_time = $post_time WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time AND " . $db->sql_in_set('forum_id', $sql_update); $db->sql_query($sql); } @@ -1425,7 +1334,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $sql_ary[] = array( 'user_id' => (int) $user->data['user_id'], 'forum_id' => (int) $f_id, - 'mark_time' => time() + 'mark_time' => $post_time, ); } @@ -1434,7 +1343,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ } else if ($config['load_anon_lastread'] || $user->data['is_registered']) { - $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : ''; + $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); $tracking = ($tracking) ? tracking_unserialize($tracking) : array(); foreach ($forum_id as $f_id) @@ -1456,7 +1365,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ unset($tracking['f'][$f_id]); } - $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36); + $tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36); } if (isset($tracking['tf']) && empty($tracking['tf'])) @@ -1464,8 +1373,8 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ unset($tracking['tf']); } - $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000); - $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking); + $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000); + $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE); unset($tracking); } @@ -1479,11 +1388,27 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ return; } + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + // Mark post notifications read for this user in this topic + $phpbb_notifications->mark_notifications_read(array( + 'topic', + 'approve_topic', + ), $topic_id, $user->data['user_id'], $post_time); + + $phpbb_notifications->mark_notifications_read_by_parent(array( + 'quote', + 'bookmark', + 'post', + 'approve_post', + ), $topic_id, $user->data['user_id'], $post_time); + if ($config['load_db_lastread'] && $user->data['is_registered']) { - $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . ' - SET mark_time = ' . (($post_time) ? $post_time : time()) . " + $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . " + SET mark_time = $post_time WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time AND topic_id = $topic_id"; $db->sql_query($sql); @@ -1496,7 +1421,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ 'user_id' => (int) $user->data['user_id'], 'topic_id' => (int) $topic_id, 'forum_id' => (int) $forum_id, - 'mark_time' => ($post_time) ? (int) $post_time : time(), + 'mark_time' => $post_time, ); $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); @@ -1506,7 +1431,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ } else if ($config['load_anon_lastread'] || $user->data['is_registered']) { - $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : ''; + $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); $tracking = ($tracking) ? tracking_unserialize($tracking) : array(); $topic_id36 = base_convert($topic_id, 10, 36); @@ -1516,12 +1441,11 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $tracking['tf'][$forum_id][$topic_id36] = true; } - $post_time = ($post_time) ? $post_time : time(); $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36); // If the cookie grows larger than 10000 characters we will remove the smallest value // This can result in old topics being unread - but most of the time it should be accurate... - if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000) + if (strlen($request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE)) > 10000) { //echo 'Cookie grown too large' . print_r($tracking, true); @@ -1552,7 +1476,12 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ if ($user->data['is_registered']) { $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10)); - $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}"); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time"; + $db->sql_query($sql); } else { @@ -1560,8 +1489,8 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ } } - $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000); - $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking); + $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000); + $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE); } return; @@ -1582,7 +1511,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $ $sql_ary = array( 'user_id' => (int) $use_user_id, 'topic_id' => (int) $topic_id, - 'topic_posted' => 1 + 'topic_posted' => 1, ); $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); @@ -1622,35 +1551,6 @@ function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $ { $mark_time = array(); - // Get global announcement info - if ($global_announce_list && sizeof($global_announce_list)) - { - if (!isset($forum_mark_time[0])) - { - global $db; - - $sql = 'SELECT mark_time - FROM ' . FORUMS_TRACK_TABLE . " - WHERE user_id = {$user->data['user_id']} - AND forum_id = 0"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if ($row) - { - $mark_time[0] = $row['mark_time']; - } - } - else - { - if ($forum_mark_time[0] !== false) - { - $mark_time[0] = $forum_mark_time[0]; - } - } - } - if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false) { $mark_time[$forum_id] = $forum_mark_time[$forum_id]; @@ -1660,14 +1560,7 @@ function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $ foreach ($topic_ids as $topic_id) { - if ($global_announce_list && isset($global_announce_list[$topic_id])) - { - $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark; - } - else - { - $last_read[$topic_id] = $user_lastmark; - } + $last_read[$topic_id] = $user_lastmark; } } @@ -1679,7 +1572,7 @@ function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $ */ function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false) { - global $config, $user; + global $config, $user, $request; $last_read = array(); @@ -1711,8 +1604,7 @@ function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_lis $sql = 'SELECT forum_id, mark_time FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']} - AND forum_id " . - (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id"); + AND forum_id = $forum_id"; $result = $db->sql_query($sql); $mark_time = array(); @@ -1726,14 +1618,7 @@ function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_lis foreach ($topic_ids as $topic_id) { - if ($global_announce_list && isset($global_announce_list[$topic_id])) - { - $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark; - } - else - { - $last_read[$topic_id] = $user_lastmark; - } + $last_read[$topic_id] = $user_lastmark; } } } @@ -1743,7 +1628,7 @@ function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_lis if (!isset($tracking_topics) || !sizeof($tracking_topics)) { - $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : ''; + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); } @@ -1771,13 +1656,6 @@ function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_lis if (sizeof($topic_ids)) { $mark_time = array(); - if ($global_announce_list && sizeof($global_announce_list)) - { - if (isset($tracking_topics['f'][0])) - { - $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate']; - } - } if (isset($tracking_topics['f'][$forum_id])) { @@ -1788,14 +1666,7 @@ function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_lis foreach ($topic_ids as $topic_id) { - if ($global_announce_list && isset($global_announce_list[$topic_id])) - { - $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark; - } - else - { - $last_read[$topic_id] = $user_lastmark; - } + $last_read[$topic_id] = $user_lastmark; } } } @@ -1943,7 +1814,7 @@ function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $s */ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false) { - global $db, $tracking_topics, $user, $config, $auth; + global $db, $tracking_topics, $user, $config, $auth, $request, $phpbb_container; // Determine the users last forum mark time if not given. if ($mark_time_forum === false) @@ -1954,7 +1825,7 @@ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_ti } else if ($config['load_anon_lastread'] || $user->data['is_registered']) { - $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : ''; + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); if (!$user->data['is_registered']) @@ -1968,7 +1839,7 @@ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_ti // Handle update of unapproved topics info. // Only update for moderators having m_approve permission for the forum. - $sql_update_unapproved = ($auth->acl_get('m_approve', $forum_id)) ? '': 'AND t.topic_approved = 1'; + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); // Check the forum for any left unread topics. // If there are none, we mark the forum as read. @@ -1988,8 +1859,8 @@ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_ti AND tt.user_id = ' . $user->data['user_id'] . ') WHERE t.forum_id = ' . $forum_id . ' AND t.topic_last_post_time > ' . $mark_time_forum . ' - AND t.topic_moved_id = 0 ' . - $sql_update_unapproved . ' + AND t.topic_moved_id = 0 + AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . ' AND (tt.topic_id IS NULL OR tt.mark_time < t.topic_last_post_time)'; $result = $db->sql_query_limit($sql, 1); @@ -2013,8 +1884,8 @@ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_ti FROM ' . TOPICS_TABLE . ' t WHERE t.forum_id = ' . $forum_id . ' AND t.topic_last_post_time > ' . $mark_time_forum . ' - AND t.topic_moved_id = 0 ' . - $sql_update_unapproved; + AND t.topic_moved_id = 0 + AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.'); $result = $db->sql_query($sql); $check_forum = $tracking_topics['tf'][$forum_id]; @@ -2168,111 +2039,6 @@ function tracking_unserialize($string, $max_depth = 3) return $level; } -// Pagination functions - -/** -* Pagination routine, generates page number sequence -* tpl_prefix is for using different pagination blocks at one page -*/ -function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '') -{ - global $template, $user; - - // Make sure $per_page is a valid value - $per_page = ($per_page <= 0) ? 1 : $per_page; - - $seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>'; - $total_pages = ceil($num_items / $per_page); - - if ($total_pages == 1 || !$num_items) - { - return false; - } - - $on_page = floor($start_item / $per_page) + 1; - $url_delim = (strpos($base_url, '?') === false) ? '?' : ((strpos($base_url, '?') === strlen($base_url) - 1) ? '' : '&'); - - $page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>'; - - if ($total_pages > 5) - { - $start_cnt = min(max(1, $on_page - 4), $total_pages - 5); - $end_cnt = max(min($total_pages, $on_page + 4), 6); - - $page_string .= ($start_cnt > 1) ? '<span class="page-dots"> ... </span>' : $seperator; - - for ($i = $start_cnt + 1; $i < $end_cnt; $i++) - { - $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>'; - if ($i < $end_cnt - 1) - { - $page_string .= $seperator; - } - } - - $page_string .= ($end_cnt < $total_pages) ? '<span class="page-dots"> ... </span>' : $seperator; - } - else - { - $page_string .= $seperator; - - for ($i = 2; $i < $total_pages; $i++) - { - $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>'; - if ($i < $total_pages) - { - $page_string .= $seperator; - } - } - } - - $page_string .= ($on_page == $total_pages) ? '<strong>' . $total_pages . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($total_pages - 1) * $per_page) . '">' . $total_pages . '</a>'; - - if ($add_prevnext_text) - { - if ($on_page != 1) - { - $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a> ' . $page_string; - } - - if ($on_page != $total_pages) - { - $page_string .= ' <a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>'; - } - } - - $template->assign_vars(array( - $tpl_prefix . 'BASE_URL' => $base_url, - 'A_' . $tpl_prefix . 'BASE_URL' => addslashes($base_url), - $tpl_prefix . 'PER_PAGE' => $per_page, - - $tpl_prefix . 'PREVIOUS_PAGE' => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page), - $tpl_prefix . 'NEXT_PAGE' => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page), - $tpl_prefix . 'TOTAL_PAGES' => $total_pages, - )); - - return $page_string; -} - -/** -* Return current page (pagination) -*/ -function on_page($num_items, $per_page, $start) -{ - global $template, $user; - - // Make sure $per_page is a valid value - $per_page = ($per_page <= 0) ? 1 : $per_page; - - $on_page = floor($start / $per_page) + 1; - - $template->assign_vars(array( - 'ON_PAGE' => $on_page) - ); - - return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1)); -} - // Server functions (building urls, redirecting...) /** @@ -2295,7 +2061,8 @@ function on_page($num_items, $per_page, $start) */ function append_sid($url, $params = false, $is_amp = true, $session_id = false) { - global $_SID, $_EXTRA_URL, $phpbb_hook; + global $_SID, $_EXTRA_URL, $phpbb_hook, $phpbb_path_helper; + global $phpbb_dispatcher; if ($params === '' || (is_array($params) && empty($params))) { @@ -2303,6 +2070,45 @@ function append_sid($url, $params = false, $is_amp = true, $session_id = false) $params = false; } + // Update the root path with the correct relative web path + if ($phpbb_path_helper instanceof \phpbb\path_helper) + { + $url = $phpbb_path_helper->update_web_root_path($url); + } + + $append_sid_overwrite = false; + + /** + * This event can either supplement or override the append_sid() function + * + * To override this function, the event must set $append_sid_overwrite to + * the new URL value, which will be returned following the event + * + * @event core.append_sid + * @var string url The url the session id needs + * to be appended to (can have + * params) + * @var mixed params String or array of additional + * url parameters + * @var bool is_amp Is url using & (true) or + * & (false) + * @var bool|string session_id Possibility to use a custom + * session id (string) instead of + * the global one (false) + * @var bool|string append_sid_overwrite Overwrite function (string + * URL) or not (false) + * @since 3.1-A1 + */ + $vars = array('url', 'params', 'is_amp', 'session_id', 'append_sid_overwrite'); + extract($phpbb_dispatcher->trigger_event('core.append_sid', compact($vars))); + + if ($append_sid_overwrite) + { + return $append_sid_overwrite; + } + + // The following hook remains for backwards compatibility, though use of + // the event above is preferred. // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately. // They could mimic most of what is within this function if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id)) @@ -2404,10 +2210,10 @@ function append_sid($url, $params = false, $is_amp = true, $session_id = false) */ function generate_board_url($without_script_path = false) { - global $config, $user; + global $config, $user, $request; $server_name = $user->host; - $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT'); + $server_port = $request->server('SERVER_PORT', 0); // Forcing server vars is the only way to specify/override the protocol if ($config['force_server_vars'] || !$server_name) @@ -2423,7 +2229,7 @@ function generate_board_url($without_script_path = false) else { // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection - $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0; + $cookie_secure = $request->is_secure() ? 1 : 0; $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name; $script_path = $user->page['root_script_path']; @@ -2462,7 +2268,7 @@ function generate_board_url($without_script_path = false) */ function redirect($url, $return = false, $disable_cd_check = false) { - global $db, $cache, $config, $user, $phpbb_root_path; + global $db, $cache, $config, $user, $phpbb_root_path, $phpbb_filesystem, $phpbb_path_helper, $phpEx; $failover_flag = false; @@ -2505,84 +2311,40 @@ function redirect($url, $return = false, $disable_cd_check = false) // Relative uri $pathinfo = pathinfo($url); - if (!$disable_cd_check && !file_exists($pathinfo['dirname'] . '/')) + // Is the uri pointing to the current directory? + if ($pathinfo['dirname'] == '.') { - $url = str_replace('../', '', $url); - $pathinfo = pathinfo($url); + $url = str_replace('./', '', $url); - if (!file_exists($pathinfo['dirname'] . '/')) + // Strip / from the beginning + if ($url && substr($url, 0, 1) == '/') { - // fallback to "last known user page" - // at least this way we know the user does not leave the phpBB root - $url = generate_board_url() . '/' . $user->page['page']; - $failover_flag = true; + $url = substr($url, 1); } } - if (!$failover_flag) - { - // Is the uri pointing to the current directory? - if ($pathinfo['dirname'] == '.') - { - $url = str_replace('./', '', $url); + $url = $phpbb_path_helper->remove_web_root_path($url); - // Strip / from the beginning - if ($url && substr($url, 0, 1) == '/') - { - $url = substr($url, 1); - } - - if ($user->page['page_dir']) - { - $url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url; - } - else - { - $url = generate_board_url() . '/' . $url; - } - } - else - { - // Used ./ before, but $phpbb_root_path is working better with urls within another root path - $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path))); - $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname']))); - $intersection = array_intersect_assoc($root_dirs, $page_dirs); - - $root_dirs = array_diff_assoc($root_dirs, $intersection); - $page_dirs = array_diff_assoc($page_dirs, $intersection); - - $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs); - - // Strip / from the end - if ($dir && substr($dir, -1, 1) == '/') - { - $dir = substr($dir, 0, -1); - } - - // Strip / from the beginning - if ($dir && substr($dir, 0, 1) == '/') - { - $dir = substr($dir, 1); - } + if ($user->page['page_dir']) + { + $url = $user->page['page_dir'] . '/' . $url; + } - $url = str_replace($pathinfo['dirname'] . '/', '', $url); + $url = generate_board_url() . '/' . $url; + } - // Strip / from the beginning - if (substr($url, 0, 1) == '/') - { - $url = substr($url, 1); - } + // Clean URL and check if we go outside the forum directory + $url = $phpbb_path_helper->clean_url($url); - $url = (!empty($dir) ? $dir . '/' : '') . $url; - $url = generate_board_url() . '/' . $url; - } - } + if (!$disable_cd_check && strpos($url, generate_board_url(true)) === false) + { + trigger_error('INSECURE_REDIRECT', E_USER_ERROR); } // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2 if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false) { - trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR); + trigger_error('INSECURE_REDIRECT', E_USER_ERROR); } // Now, also check the protocol and for a valid url the last time... @@ -2591,7 +2353,7 @@ function redirect($url, $return = false, $disable_cd_check = false) if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols)) { - trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR); + trigger_error('INSECURE_REDIRECT', E_USER_ERROR); } if ($return) @@ -2604,10 +2366,10 @@ function redirect($url, $return = false, $disable_cd_check = false) { header('Refresh: 0; URL=' . $url); - echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; - echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">'; + echo '<!DOCTYPE html>'; + echo '<html dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '">'; echo '<head>'; - echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />'; + echo '<meta charset="utf-8">'; echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&', $url) . '" />'; echo '<title>' . $user->lang['REDIRECT'] . '</title>'; echo '</head>'; @@ -2659,8 +2421,22 @@ function build_url($strip_vars = false) { global $user, $phpbb_root_path; + $page = $user->page['page']; + + // We need to be cautious here. + // On some situations, the redirect path is an absolute URL, sometimes a relative path + // For a relative path, let's prefix it with $phpbb_root_path to point to the correct location, + // else we use the URL directly. + $url_parts = parse_url($page); + + // URL + if ($url_parts === false || empty($url_parts['scheme']) || empty($url_parts['host'])) + { + $page = $phpbb_root_path . $page; + } + // Append SID - $redirect = append_sid($user->page['page'], false, false); + $redirect = append_sid($page, false, false); // Add delimiter if not there... if (strpos($redirect, '?') === false) @@ -2715,19 +2491,7 @@ function build_url($strip_vars = false) $redirect .= ($query) ? '?' . $query : ''; } - // We need to be cautious here. - // On some situations, the redirect path is an absolute URL, sometimes a relative path - // For a relative path, let's prefix it with $phpbb_root_path to point to the correct location, - // else we use the URL directly. - $url_parts = @parse_url($redirect); - - // URL - if ($url_parts !== false && !empty($url_parts['scheme']) && !empty($url_parts['host'])) - { - return str_replace('&', '&', $redirect); - } - - return $phpbb_root_path . str_replace('&', '&', $redirect); + return str_replace('&', '&', $redirect); } /** @@ -2740,15 +2504,25 @@ function build_url($strip_vars = false) */ function meta_refresh($time, $url, $disable_cd_check = false) { - global $template; + global $template, $refresh_data, $request; - $url = redirect($url, true, $disable_cd_check); - $url = str_replace('&', '&', $url); + if ($request->is_ajax()) + { + $refresh_data = array( + 'time' => $time, + 'url' => str_replace('&', '&', $url) + ); + } + else + { + $url = redirect($url, true, $disable_cd_check); + $url = str_replace('&', '&', $url); - // For XHTML compatibility we change back & to & - $template->assign_vars(array( - 'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />') - ); + // For XHTML compatibility we change back & to & + $template->assign_vars(array( + 'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />') + ); + } return $url; } @@ -2782,18 +2556,35 @@ function send_status_line($code, $message) } else { - if (!empty($_SERVER['SERVER_PROTOCOL'])) - { - $version = $_SERVER['SERVER_PROTOCOL']; - } - else - { - $version = 'HTTP/1.0'; - } + $version = phpbb_request_http_version(); header("$version $code $message", true, $code); } } +/** +* Returns the HTTP version used in the current request. +* +* Handles the case of being called before $request is present, +* in which case it falls back to the $_SERVER superglobal. +* +* @return string HTTP version +*/ +function phpbb_request_http_version() +{ + global $request; + + if ($request && $request->server('SERVER_PROTOCOL')) + { + return $request->server('SERVER_PROTOCOL'); + } + else if (isset($_SERVER['SERVER_PROTOCOL'])) + { + return $_SERVER['SERVER_PROTOCOL']; + } + + return 'HTTP/1.0'; +} + //Form validation @@ -2909,23 +2700,15 @@ function check_form_key($form_name, $timespan = false, $return_page = '', $trigg */ function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '') { - global $user, $template, $db; - global $phpEx, $phpbb_root_path; + global $user, $template, $db, $request; + global $phpEx, $phpbb_root_path, $request; if (isset($_POST['cancel'])) { return false; } - $confirm = false; - if (isset($_POST['confirm'])) - { - // language frontier - if ($_POST['confirm'] === $user->lang['YES']) - { - $confirm = true; - } - } + $confirm = ($user->lang['YES'] === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST)); if ($check && $confirm) { @@ -2990,13 +2773,30 @@ function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_bo 'YES_VALUE' => $user->lang['YES'], 'S_CONFIRM_ACTION' => $u_action, - 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields) - ); + 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields, + 'S_AJAX_REQUEST' => $request->is_ajax(), + )); $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "' WHERE user_id = " . $user->data['user_id']; $db->sql_query($sql); + + if ($request->is_ajax()) + { + $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id; + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_BODY' => $template->assign_display('body'), + 'MESSAGE_TITLE' => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title], + 'MESSAGE_TEXT' => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'], + + 'YES_VALUE' => $user->lang['YES'], + 'S_CONFIRM_ACTION' => str_replace('&', '&', $u_action), //inefficient, rewrite whole function + 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields + )); + } + if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) { adm_page_footer(); @@ -3013,8 +2813,9 @@ function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_bo function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true) { global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config; + global $request, $phpbb_container; - if (!class_exists('phpbb_captcha_factory')) + if (!class_exists('phpbb_captcha_factory', false)) { include($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx); } @@ -3039,7 +2840,7 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa trigger_error('NO_AUTH_ADMIN'); } - if (isset($_POST['login'])) + if ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external')) { // Get credential if ($admin) @@ -3055,16 +2856,16 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa trigger_error('NO_AUTH_ADMIN'); } - $password = request_var('password_' . $credential, '', true); + $password = $request->untrimmed_variable('password_' . $credential, '', true); } else { - $password = request_var('password', '', true); + $password = $request->untrimmed_variable('password', '', true); } $username = request_var('username', '', true); - $autologin = (!empty($_POST['autologin'])) ? true : false; - $viewonline = (!empty($_POST['viewonline'])) ? 0 : 1; + $autologin = $request->is_set_post('autologin'); + $viewonline = (int) !$request->is_set_post('viewonline'); $admin = ($admin) ? 1 : 0; $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline; @@ -3114,8 +2915,7 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa return; } - $redirect = meta_refresh(3, $redirect); - trigger_error($message . '<br /><br />' . sprintf($l_redirect, '<a href="' . $redirect . '">', '</a>')); + redirect($redirect); } // Something failed, determine what... @@ -3181,6 +2981,29 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa $s_hidden_fields['credential'] = $credential; } + $auth_provider = $phpbb_container->get('auth.provider.' . $config['auth_method']); + + $auth_provider_data = $auth_provider->get_login_data(); + if ($auth_provider_data) + { + if (isset($auth_provider_data['VARS'])) + { + $template->assign_vars($auth_provider_data['VARS']); + } + + if (isset($auth_provider_data['BLOCK_VAR_NAME'])) + { + foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars) + { + $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars); + } + } + + $template->assign_vars(array( + 'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'], + )); + } + $s_hidden_fields = build_hidden_fields($s_hidden_fields); $template->assign_vars(array( @@ -3217,9 +3040,9 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa */ function login_forum_box($forum_data) { - global $db, $config, $user, $template, $phpEx; + global $db, $phpbb_container, $request, $template, $user; - $password = request_var('password', '', true); + $password = $request->variable('password', '', true); $sql = 'SELECT forum_id FROM ' . FORUMS_ACCESS_TABLE . ' @@ -3260,7 +3083,9 @@ function login_forum_box($forum_data) } $db->sql_freeresult($result); - if (phpbb_check_hash($password, $forum_data['forum_password'])) + $passwords_manager = $phpbb_container->get('passwords.manager'); + + if ($passwords_manager->check($password, $forum_data['forum_password'])) { $sql_ary = array( 'forum_id' => (int) $forum_data['forum_id'], @@ -3389,79 +3214,59 @@ function parse_cfg_file($filename, $lines = false) $parsed_items[$key] = $value; } - - if (isset($parsed_items['inherit_from']) && isset($parsed_items['name']) && $parsed_items['inherit_from'] == $parsed_items['name']) + + if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name']) { - unset($parsed_items['inherit_from']); + unset($parsed_items['parent']); } return $parsed_items; } /** -* Add log event +* Add log entry +* +* @param string $mode The mode defines which log_type is used and from which log the entry is retrieved +* @param int $forum_id Mode 'mod' ONLY: forum id of the related item, NOT INCLUDED otherwise +* @param int $topic_id Mode 'mod' ONLY: topic id of the related item, NOT INCLUDED otherwise +* @param int $reportee_id Mode 'user' ONLY: user id of the reportee, NOT INCLUDED otherwise +* @param string $log_operation Name of the operation +* @param array $additional_data More arguments can be added, depending on the log_type +* +* @return int|bool Returns the log_id, if the entry was added to the database, false otherwise. +* +* @deprecated Use $phpbb_log->add() instead */ function add_log() { - global $db, $user; - - // In phpBB 3.1.x i want to have logging in a class to be able to control it - // For now, we need a quite hakish approach to circumvent logging for some actions - // @todo implement cleanly - if (!empty($GLOBALS['skip_add_log'])) - { - return false; - } + global $phpbb_log, $user; $args = func_get_args(); + $mode = array_shift($args); - $mode = array_shift($args); - $reportee_id = ($mode == 'user') ? intval(array_shift($args)) : ''; - $forum_id = ($mode == 'mod') ? intval(array_shift($args)) : ''; - $topic_id = ($mode == 'mod') ? intval(array_shift($args)) : ''; - $action = array_shift($args); - $data = (!sizeof($args)) ? '' : serialize($args); - - $sql_ary = array( - 'user_id' => (empty($user->data)) ? ANONYMOUS : $user->data['user_id'], - 'log_ip' => $user->ip, - 'log_time' => time(), - 'log_operation' => $action, - 'log_data' => $data, - ); - + // This looks kind of dirty, but add_log has some additional data before the log_operation + $additional_data = array(); switch ($mode) { case 'admin': - $sql_ary['log_type'] = LOG_ADMIN; + case 'critical': break; - case 'mod': - $sql_ary += array( - 'log_type' => LOG_MOD, - 'forum_id' => $forum_id, - 'topic_id' => $topic_id - ); + $additional_data['forum_id'] = array_shift($args); + $additional_data['topic_id'] = array_shift($args); break; - case 'user': - $sql_ary += array( - 'log_type' => LOG_USERS, - 'reportee_id' => $reportee_id - ); - break; - - case 'critical': - $sql_ary['log_type'] = LOG_CRITICAL; + $additional_data['reportee_id'] = array_shift($args); break; - - default: - return false; } - $db->sql_query('INSERT INTO ' . LOG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + $log_operation = array_shift($args); + $additional_data = array_merge($additional_data, $args); - return $db->sql_nextid(); + $user_id = (empty($user->data)) ? ANONYMOUS : $user->data['user_id']; + $user_ip = (empty($user->ip)) ? '' : $user->ip; + + return $phpbb_log->add($mode, $user_id, $user_ip, $log_operation, time(), $additional_data); } /** @@ -3584,18 +3389,10 @@ function get_preg_expression($mode) */ function get_censor_preg_expression($word, $use_unicode = true) { - static $unicode_support = null; - - // Check whether PHP version supports unicode properties - if (is_null($unicode_support)) - { - $unicode_support = ((version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>='))) && @preg_match('/\p{L}/u', 'a') !== false) ? true : false; - } - // Unescape the asterisk to simplify further conversions $word = str_replace('\*', '*', preg_quote($word, '#')); - if ($use_unicode && $unicode_support) + if ($use_unicode && phpbb_pcre_utf8_support()) { // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes $word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word); @@ -3647,6 +3444,188 @@ function short_ipv6($ip, $length) } /** +* Normalises an internet protocol address, +* also checks whether the specified address is valid. +* +* IPv4 addresses are returned 'as is'. +* +* IPv6 addresses are normalised according to +* A Recommendation for IPv6 Address Text Representation +* http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07 +* +* @param string $address IP address +* +* @return mixed false if specified address is not valid, +* string otherwise +* +* @author bantu +*/ +function phpbb_ip_normalise($address) +{ + $address = trim($address); + + if (empty($address) || !is_string($address)) + { + return false; + } + + if (preg_match(get_preg_expression('ipv4'), $address)) + { + return $address; + } + + return phpbb_inet_ntop(phpbb_inet_pton($address)); +} + +/** +* Wrapper for inet_ntop() +* +* Converts a packed internet address to a human readable representation +* inet_ntop() is supported by PHP since 5.1.0, since 5.3.0 also on Windows. +* +* @param string $in_addr A 32bit IPv4, or 128bit IPv6 address. +* +* @return mixed false on failure, +* string otherwise +* +* @author APTX +*/ +function phpbb_inet_ntop($in_addr) +{ + $in_addr = bin2hex($in_addr); + + switch (strlen($in_addr)) + { + case 8: + return implode('.', array_map('hexdec', str_split($in_addr, 2))); + + case 32: + if (substr($in_addr, 0, 24) === '00000000000000000000ffff') + { + return phpbb_inet_ntop(pack('H*', substr($in_addr, 24))); + } + + $parts = str_split($in_addr, 4); + $parts = preg_replace('/^0+(?!$)/', '', $parts); + $ret = implode(':', $parts); + + $matches = array(); + preg_match_all('/(?<=:|^)(?::?0){2,}/', $ret, $matches, PREG_OFFSET_CAPTURE); + $matches = $matches[0]; + + if (empty($matches)) + { + return $ret; + } + + $longest_match = ''; + $longest_match_offset = 0; + foreach ($matches as $match) + { + if (strlen($match[0]) > strlen($longest_match)) + { + $longest_match = $match[0]; + $longest_match_offset = $match[1]; + } + } + + $ret = substr_replace($ret, '', $longest_match_offset, strlen($longest_match)); + + if ($longest_match_offset == strlen($ret)) + { + $ret .= ':'; + } + + if ($longest_match_offset == 0) + { + $ret = ':' . $ret; + } + + return $ret; + + default: + return false; + } +} + +/** +* Wrapper for inet_pton() +* +* Converts a human readable IP address to its packed in_addr representation +* inet_pton() is supported by PHP since 5.1.0, since 5.3.0 also on Windows. +* +* @param string $address A human readable IPv4 or IPv6 address. +* +* @return mixed false if address is invalid, +* in_addr representation of the given address otherwise (string) +* +* @author APTX +*/ +function phpbb_inet_pton($address) +{ + $ret = ''; + if (preg_match(get_preg_expression('ipv4'), $address)) + { + foreach (explode('.', $address) as $part) + { + $ret .= ($part <= 0xF ? '0' : '') . dechex($part); + } + + return pack('H*', $ret); + } + + if (preg_match(get_preg_expression('ipv6'), $address)) + { + $parts = explode(':', $address); + $missing_parts = 8 - sizeof($parts) + 1; + + if (substr($address, 0, 2) === '::') + { + ++$missing_parts; + } + + if (substr($address, -2) === '::') + { + ++$missing_parts; + } + + $embedded_ipv4 = false; + $last_part = end($parts); + + if (preg_match(get_preg_expression('ipv4'), $last_part)) + { + $parts[sizeof($parts) - 1] = ''; + $last_part = phpbb_inet_pton($last_part); + $embedded_ipv4 = true; + --$missing_parts; + } + + foreach ($parts as $i => $part) + { + if (strlen($part)) + { + $ret .= str_pad($part, 4, '0', STR_PAD_LEFT); + } + else if ($i && $i < sizeof($parts) - 1) + { + $ret .= str_repeat('0000', $missing_parts); + } + } + + $ret = pack('H*', $ret); + + if ($embedded_ipv4) + { + $ret .= $last_part; + } + + return $ret; + } + + return false; +} + +/** * Wrapper for php's checkdnsrr function. * * @param string $host Fully-Qualified Domain Name @@ -3843,11 +3822,11 @@ function phpbb_checkdnsrr($host, $type = 'MX') // Handler, header and footer /** -* Error and message handler, call with trigger_error if reqd +* Error and message handler, call with trigger_error if read */ function msg_handler($errno, $msg_text, $errfile, $errline) { - global $cache, $db, $auth, $template, $config, $user; + global $cache, $db, $auth, $template, $config, $user, $request; global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text; // Do not display notices if we suppress them via @ @@ -3932,12 +3911,12 @@ function msg_handler($errno, $msg_text, $errfile, $errline) $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace; } - if (defined('IN_INSTALL') || defined('DEBUG_EXTRA') || isset($auth) && $auth->acl_get('a_')) + if (defined('IN_INSTALL') || defined('DEBUG') || isset($auth) && $auth->acl_get('a_')) { $msg_text = $log_text; } - if ((defined('DEBUG') || defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db)) + if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db)) { // let's avoid loops $db->sql_return_on_error(true); @@ -3952,10 +3931,10 @@ function msg_handler($errno, $msg_text, $errfile, $errline) // Try to not call the adm page data... - echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; - echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">'; + echo '<!DOCTYPE html>'; + echo '<html dir="ltr">'; echo '<head>'; - echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />'; + echo '<meta charset="utf-8">'; echo '<title>' . $msg_title . '</title>'; echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n"; echo '* { margin: 0; padding: 0; } html { font-size: 100%; height: 100%; margin-bottom: 1px; background-color: #E4EDF0; } body { font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; color: #536482; background: #E4EDF0; font-size: 62.5%; margin: 0; } '; @@ -4046,6 +4025,20 @@ function msg_handler($errno, $msg_text, $errfile, $errline) 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false) ); + if ($request->is_ajax()) + { + global $refresh_data; + + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $msg_title, + 'MESSAGE_TEXT' => $msg_text, + 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false, + 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false, + 'REFRESH_DATA' => (!empty($refresh_data)) ? $refresh_data : null + )); + } + // We do not want the cron script to be called on error messages define('IN_CRON', true); @@ -4260,59 +4253,26 @@ function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum' } else if ($config['load_online_guests']) { - $l_online = ($online_users['guests_online'] === 1) ? $user->lang['BROWSING_' . $item_caps . '_GUEST'] : $user->lang['BROWSING_' . $item_caps . '_GUESTS']; - $online_userlist = sprintf($l_online, $online_userlist, $online_users['guests_online']); + $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist); } else { $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist); } // Build online listing - $vars_online = array( - 'ONLINE' => array('total_online', 'l_t_user_s', 0), - 'REG' => array('visible_online', 'l_r_user_s', !$config['load_online_guests']), - 'HIDDEN' => array('hidden_online', 'l_h_user_s', $config['load_online_guests']), - 'GUEST' => array('guests_online', 'l_g_user_s', 0) - ); + $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']); + $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']); - foreach ($vars_online as $l_prefix => $var_ary) + if ($config['load_online_guests']) { - if ($var_ary[2]) - { - $l_suffix = '_AND'; - } - else - { - $l_suffix = ''; - } - switch ($online_users[$var_ary[0]]) - { - case 0: - ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL' . $l_suffix]; - break; - - case 1: - ${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL' . $l_suffix]; - break; - - default: - ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL' . $l_suffix]; - break; - } + $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']); + $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online); } - unset($vars_online); - - $l_online_users = sprintf($l_t_user_s, $online_users['total_online']); - $l_online_users .= sprintf($l_r_user_s, $online_users['visible_online']); - $l_online_users .= sprintf($l_h_user_s, $online_users['hidden_online']); - - if ($config['load_online_guests']) + else { - $l_online_users .= sprintf($l_g_user_s, $online_users['guests_online']); + $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online); } - - return array( 'online_userlist' => $online_userlist, 'l_online_users' => $l_online_users, @@ -4355,6 +4315,178 @@ function phpbb_optionset($bit, $set, $data) } /** +* Determine which plural form we should use. +* For some languages this is not as simple as for English. +* +* @param $rule int ID of the plural rule we want to use, see http://wiki.phpbb.com/Plural_Rules#Plural_Rules +* @param $number int|float The number we want to get the plural case for. Float numbers are floored. +* @return int The plural-case we need to use for the number plural-rule combination +*/ +function phpbb_get_plural_form($rule, $number) +{ + $number = (int) $number; + + if ($rule > 15 || $rule < 0) + { + trigger_error('INVALID_PLURAL_RULE'); + } + + /** + * The following plural rules are based on a list published by the Mozilla Developer Network + * https://developer.mozilla.org/en/Localization_and_Plurals + */ + switch ($rule) + { + case 0: + /** + * Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao + * 1 - everything: 0, 1, 2, ... + */ + return 1; + + case 1: + /** + * Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan) + * 1 - 1 + * 2 - everything else: 0, 2, 3, ... + */ + return ($number == 1) ? 1 : 2; + + case 2: + /** + * Families: Romanic (French, Brazilian Portuguese) + * 1 - 0, 1 + * 2 - everything else: 2, 3, ... + */ + return (($number == 0) || ($number == 1)) ? 1 : 2; + + case 3: + /** + * Families: Baltic (Latvian) + * 1 - 0 + * 2 - ends in 1, not 11: 1, 21, ... 101, 121, ... + * 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ... + */ + return ($number == 0) ? 1 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 2 : 3); + + case 4: + /** + * Families: Celtic (Scottish Gaelic) + * 1 - is 1 or 11: 1, 11 + * 2 - is 2 or 12: 2, 12 + * 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19 + * 4 - everything else: 0, 20, 21, ... + */ + return ($number == 1 || $number == 11) ? 1 : (($number == 2 || $number == 12) ? 2 : (($number >= 3 && $number <= 19) ? 3 : 4)); + + case 5: + /** + * Families: Romanic (Romanian) + * 1 - 1 + * 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ... + * 3 - everything else: 20, 21, ... + */ + return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 2 : 3); + + case 6: + /** + * Families: Baltic (Lithuanian) + * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... + * 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ... + * 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ... + */ + return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 < 2) || (($number % 100 >= 10) && ($number % 100 < 20))) ? 2 : 3); + + case 7: + /** + * Families: Slavic (Croatian, Serbian, Russian, Ukrainian) + * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... + * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... + * 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ... + */ + return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 2 : 3); + + case 8: + /** + * Families: Slavic (Slovak, Czech) + * 1 - 1 + * 2 - 2, 3, 4 + * 3 - everything else: 0, 5, 6, 7, ... + */ + return ($number == 1) ? 1 : ((($number >= 2) && ($number <= 4)) ? 2 : 3); + + case 9: + /** + * Families: Slavic (Polish) + * 1 - 1 + * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ... + * 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ... + */ + return ($number == 1) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 2 : 3); + + case 10: + /** + * Families: Slavic (Slovenian, Sorbian) + * 1 - ends in 01: 1, 101, 201, ... + * 2 - ends in 02: 2, 102, 202, ... + * 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ... + * 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ... + */ + return ($number % 100 == 1) ? 1 : (($number % 100 == 2) ? 2 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 3 : 4)); + + case 11: + /** + * Families: Celtic (Irish Gaeilge) + * 1 - 1 + * 2 - 2 + * 3 - is 3-6: 3, 4, 5, 6 + * 4 - is 7-10: 7, 8, 9, 10 + * 5 - everything else: 0, 11, 12, ... + */ + return ($number == 1) ? 1 : (($number == 2) ? 2 : (($number >= 3 && $number <= 6) ? 3 : (($number >= 7 && $number <= 10) ? 4 : 5))); + + case 12: + /** + * Families: Semitic (Arabic) + * 1 - 1 + * 2 - 2 + * 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ... + * 4 - ends in 11-99: 11, ... 99, 111, 112, ... + * 5 - everything else: 100, 101, 102, 200, 201, 202, ... + * 6 - 0 + */ + return ($number == 1) ? 1 : (($number == 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : (($number != 0) ? 5 : 6)))); + + case 13: + /** + * Families: Semitic (Maltese) + * 1 - 1 + * 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ... + * 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ... + * 4 - everything else: 20, 21, ... + */ + return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 2 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 3 : 4)); + + case 14: + /** + * Families: Slavic (Macedonian) + * 1 - ends in 1: 1, 11, 21, ... + * 2 - ends in 2: 2, 12, 22, ... + * 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ... + */ + return ($number % 10 == 1) ? 1 : (($number % 10 == 2) ? 2 : 3); + + case 15: + /** + * Families: Icelandic + * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ... + * 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ... + */ + return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2; + } +} + +/** * Login using http authenticate. * * @param array $param Parameter array, see $param_defaults array. @@ -4363,7 +4495,7 @@ function phpbb_optionset($bit, $set, $data) */ function phpbb_http_login($param) { - global $auth, $user; + global $auth, $user, $request; global $config; $param_defaults = array( @@ -4403,9 +4535,9 @@ function phpbb_http_login($param) $username = null; foreach ($username_keys as $k) { - if (isset($_SERVER[$k])) + if ($request->is_set($k, \phpbb\request\request_interface::SERVER)) { - $username = $_SERVER[$k]; + $username = htmlspecialchars_decode($request->server($k)); break; } } @@ -4413,9 +4545,9 @@ function phpbb_http_login($param) $password = null; foreach ($password_keys as $k) { - if (isset($_SERVER[$k])) + if ($request->is_set($k, \phpbb\request\request_interface::SERVER)) { - $password = $_SERVER[$k]; + $password = htmlspecialchars_decode($request->server($k)); break; } } @@ -4458,11 +4590,107 @@ function phpbb_http_login($param) } /** +* Escapes and quotes a string for use as an HTML/XML attribute value. +* +* This is a port of Python xml.sax.saxutils quoteattr. +* +* The function will attempt to choose a quote character in such a way as to +* avoid escaping quotes in the string. If this is not possible the string will +* be wrapped in double quotes and double quotes will be escaped. +* +* @param string $data The string to be escaped +* @param array $entities Associative array of additional entities to be escaped +* @return string Escaped and quoted string +*/ +function phpbb_quoteattr($data, $entities = null) +{ + $data = str_replace('&', '&', $data); + $data = str_replace('>', '>', $data); + $data = str_replace('<', '<', $data); + + $data = str_replace("\n", ' ', $data); + $data = str_replace("\r", ' ', $data); + $data = str_replace("\t", '	', $data); + + if (!empty($entities)) + { + $data = str_replace(array_keys($entities), array_values($entities), $data); + } + + if (strpos($data, '"') !== false) + { + if (strpos($data, "'") !== false) + { + $data = '"' . str_replace('"', '"', $data) . '"'; + } + else + { + $data = "'" . $data . "'"; + } + } + else + { + $data = '"' . $data . '"'; + } + + return $data; +} + +/** +* Converts query string (GET) parameters in request into hidden fields. +* +* Useful for forwarding GET parameters when submitting forms with GET method. +* +* It is possible to omit some of the GET parameters, which is useful if +* they are specified in the form being submitted. +* +* sid is always omitted. +* +* @param \phpbb\request\request $request Request object +* @param array $exclude A list of variable names that should not be forwarded +* @return string HTML with hidden fields +*/ +function phpbb_build_hidden_fields_for_query_params($request, $exclude = null) +{ + $names = $request->variable_names(\phpbb\request\request_interface::GET); + $hidden = ''; + foreach ($names as $name) + { + // Sessions are dealt with elsewhere, omit sid always + if ($name == 'sid') + { + continue; + } + + // Omit any additional parameters requested + if (!empty($exclude) && in_array($name, $exclude)) + { + continue; + } + + $escaped_name = phpbb_quoteattr($name); + + // Note: we might retrieve the variable from POST or cookies + // here. To avoid exposing cookies, skip variables that are + // overwritten somewhere other than GET entirely. + $value = $request->variable($name, '', true); + $get_value = $request->variable($name, '', true, \phpbb\request\request_interface::GET); + if ($value === $get_value) + { + $escaped_value = phpbb_quoteattr($value); + $hidden .= "<input type='hidden' name=$escaped_name value=$escaped_value />"; + } + } + return $hidden; +} + +/** * Generate page header */ function page_header($page_title = '', $display_online_list = true, $item_id = 0, $item = 'forum') { global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path; + global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path; if (defined('HEADER_INC')) { @@ -4471,6 +4699,31 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 define('HEADER_INC', true); + // A listener can set this variable to `true` when it overrides this function + $page_header_override = false; + + /** + * Execute code and/or overwrite page_header() + * + * @event core.page_header + * @var string page_title Page title + * @var bool display_online_list Do we display online users list + * @var string item Restrict online users to a certain + * session item, e.g. forum for + * session_forum_id + * @var int item_id Restrict online users to item id + * @var bool page_header_override Shall we return instead of running + * the rest of page_header() + * @since 3.1-A1 + */ + $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override'); + extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars))); + + if ($page_header_override) + { + return; + } + // gzip_compression if ($config['gzip_compress']) { @@ -4533,23 +4786,18 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 set_config('record_online_date', time(), true); } - $l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date'], false, true)); + $l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true)); - $l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES'; - $l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']); + $l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']); } - $l_privmsgs_text = $l_privmsgs_text_unread = ''; $s_privmsg_new = false; - // Obtain number of new private messages if user is logged in + // Check for new private messages if user is logged in if (!empty($user->data['is_registered'])) { if ($user->data['user_new_privmsg']) { - $l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS']; - $l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']); - if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit']) { $sql = 'UPDATE ' . USERS_TABLE . ' @@ -4566,17 +4814,8 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 } else { - $l_privmsgs_text = $user->lang['NO_NEW_PM']; $s_privmsg_new = false; } - - $l_privmsgs_text_unread = ''; - - if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg']) - { - $l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS']; - $l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']); - } } $forum_id = request_var('f', 0); @@ -4597,10 +4836,12 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 // Determine board url - we may need it later $board_url = generate_board_url() . '/'; - $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $phpbb_root_path; - - // Which timezone? - $tz = ($user->data['user_id'] != ANONYMOUS) ? strval(doubleval($user->data['user_timezone'])) : strval(doubleval($config['board_timezone'])); + // This path is sent with the base template paths in the assign_vars() + // call below. We need to correct it in case we are accessing from a + // controller because the web paths will be incorrect otherwise. + $phpbb_path_helper = $phpbb_container->get('path_helper'); + $corrected_path = $phpbb_path_helper->get_web_root_path(); + $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path; // Send a proper content-language to the output $user_lang = $user->lang['USER_LANG']; @@ -4624,6 +4865,34 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 } } + $dt = new \phpbb\datetime($user, 'now', $user->timezone); + $timezone_offset = 'GMT' . phpbb_format_timezone_offset($dt->getOffset()); + $timezone_name = $user->timezone->getName(); + if (isset($user->lang['timezones'][$timezone_name])) + { + $timezone_name = $user->lang['timezones'][$timezone_name]; + } + + // Output the notifications + $notifications = false; + if ($config['load_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE) + { + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $notifications = $phpbb_notifications->load_notifications(array( + 'all_unread' => true, + 'limit' => 5, + )); + + foreach ($notifications['notifications'] as $notification) + { + $template->assign_block_vars('notifications', $notification->prepare_for_display()); + } + } + + $hidden_fields_for_jumpbox = phpbb_build_hidden_fields_for_query_params($request, array('f')); + $notification_mark_hash = generate_link_hash('mark_all_notifications_read'); + // The following assigns all _common_ variables that may be used at any point in a template. $template->assign_vars(array( 'SITENAME' => $config['sitename'], @@ -4636,8 +4905,15 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'TOTAL_USERS_ONLINE' => $l_online_users, 'LOGGED_IN_USER_LIST' => $online_userlist, 'RECORD_USERS' => $l_online_record, - 'PRIVATE_MESSAGE_INFO' => $l_privmsgs_text, - 'PRIVATE_MESSAGE_INFO_UNREAD' => $l_privmsgs_text_unread, + 'PRIVATE_MESSAGE_COUNT' => (!empty($user->data['user_unread_privmsg'])) ? $user->data['user_unread_privmsg'] : 0, + 'HIDDEN_FIELDS_FOR_JUMPBOX' => $hidden_fields_for_jumpbox, + + 'UNREAD_NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '', + 'NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '', + 'U_VIEW_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'), + 'U_MARK_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_list&mark=all&token=' . $notification_mark_hash), + 'U_NOTIFICATION_SETTINGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_options'), + 'S_NOTIFICATIONS_DISPLAY' => $config['load_notifications'], 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'], 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'], @@ -4646,22 +4922,22 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'SID' => $SID, '_SID' => $_SID, 'SESSION_ID' => $user->session_id, - 'ROOT_PATH' => $phpbb_root_path, + 'ROOT_PATH' => $web_path, 'BOARD_URL' => $board_url, 'L_LOGIN_LOGOUT' => $l_login_logout, - 'L_INDEX' => $user->lang['FORUM_INDEX'], + 'L_INDEX' => ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['FORUM_INDEX'], + 'L_SITE_HOME' => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'], 'L_ONLINE_EXPLAIN' => $l_online_time, 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), 'U_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), - 'U_POPUP_PM' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=popup'), - 'UA_POPUP_PM' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=popup')), 'U_MEMBERLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"), 'U_VIEWONLINE' => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '', 'U_LOGIN_LOGOUT' => $u_login_logout, 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"), 'U_SEARCH' => append_sid("{$phpbb_root_path}search.$phpEx"), + 'U_SITE_HOME' => $config['site_home_url'], 'U_REGISTER' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'), 'U_PROFILE' => append_sid("{$phpbb_root_path}ucp.$phpEx"), 'U_MODCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id), @@ -4683,7 +4959,6 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false, 'S_REGISTERED_USER' => (!empty($user->data['is_registered'])) ? true : false, 'S_IS_BOT' => (!empty($user->data['is_bot'])) ? true : false, - 'S_USER_PM_POPUP' => $user->optionget('popuppm'), 'S_USER_LANG' => $user_lang, 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'], 'S_USERNAME' => $user->data['username'], @@ -4691,7 +4966,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right', 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left', 'S_CONTENT_ENCODING' => 'UTF-8', - 'S_TIMEZONE' => ($user->data['user_dst'] || ($user->data['user_id'] == ANONYMOUS && $config['board_dst'])) ? sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], $user->lang['tz']['dst']) : sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], ''), + 'S_TIMEZONE' => sprintf($user->lang['ALL_TIMES'], $timezone_offset, $timezone_name), 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0, 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1), 'S_DISPLAY_PM' => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false, @@ -4701,7 +4976,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'S_FORUM_ID' => $forum_id, 'S_TOPIC_ID' => $topic_id, - 'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("index.$phpEx", false, true, $user->session_id)), + 'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id)), 'S_LOGIN_REDIRECT' => build_hidden_fields(array('redirect' => build_url())), 'S_ENABLE_FEEDS' => ($config['feed_enable']) ? true : false, @@ -4715,11 +4990,11 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'S_SEARCH_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields), - 'T_THEME_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme', - 'T_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template', - 'T_SUPER_TEMPLATE_PATH' => (isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? "{$web_path}styles/" . rawurlencode($user->theme['template_inherit_path']) . '/template' : "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template', - 'T_IMAGESET_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset', - 'T_IMAGESET_LANG_PATH' => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset/' . $user->lang_name, + 'T_ASSETS_VERSION' => $config['assets_version'], + 'T_ASSETS_PATH' => "{$web_path}assets", + 'T_THEME_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme', + 'T_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template', + 'T_SUPER_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template', 'T_IMAGES_PATH' => "{$web_path}images/", 'T_SMILIES_PATH' => "{$web_path}{$config['smilies_path']}/", 'T_AVATAR_PATH' => "{$web_path}{$config['avatar_path']}/", @@ -4727,14 +5002,15 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'T_ICONS_PATH' => "{$web_path}{$config['icons_path']}/", 'T_RANKS_PATH' => "{$web_path}{$config['ranks_path']}/", 'T_UPLOAD_PATH' => "{$web_path}{$config['upload_path']}/", - 'T_STYLESHEET_LINK' => (!$user->theme['theme_storedb']) ? "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme/stylesheet.css' : append_sid("{$phpbb_root_path}style.$phpEx", 'id=' . $user->theme['style_id'] . '&lang=' . $user->lang_name), - 'T_STYLESHEET_NAME' => $user->theme['theme_name'], - - 'T_THEME_NAME' => rawurlencode($user->theme['theme_path']), - 'T_TEMPLATE_NAME' => rawurlencode($user->theme['template_path']), - 'T_SUPER_TEMPLATE_NAME' => rawurlencode((isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? $user->theme['template_inherit_path'] : $user->theme['template_path']), - 'T_IMAGESET_NAME' => rawurlencode($user->theme['imageset_path']), - 'T_IMAGESET_LANG_NAME' => $user->data['user_lang'], + 'T_STYLESHEET_LINK' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet.css?assets_version=' . $config['assets_version'], + 'T_STYLESHEET_LANG_LINK' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'], + 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery.js?assets_version=" . $config['assets_version'], + 'S_ALLOW_CDN' => !empty($config['allow_cdn']), + + 'T_THEME_NAME' => rawurlencode($user->style['style_path']), + 'T_THEME_LANG_NAME' => $user->data['user_lang'], + 'T_TEMPLATE_NAME' => $user->style['style_path'], + 'T_SUPER_TEMPLATE_NAME' => rawurlencode((isset($user->style['style_parent_tree']) && $user->style['style_parent_tree']) ? $user->style['style_parent_tree'] : $user->style['style_path']), 'T_IMAGES' => 'images', 'T_SMILIES' => $config['smilies_path'], 'T_AVATAR' => $config['avatar_path'], @@ -4744,8 +5020,6 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'T_UPLOAD' => $config['upload_path'], 'SITE_LOGO_IMG' => $user->img('site_logo'), - - 'A_COOKIE_SETTINGS' => addslashes('; path=' . $config['cookie_path'] . ((!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']) . ((!$config['cookie_secure']) ? '' : '; secure')), )); // application/xhtml+xml not used because of IE @@ -4766,10 +5040,35 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 /** * Generate page footer +* +* @param bool $run_cron Whether or not to run the cron +* @param bool $display_template Whether or not to display the template +* @param bool $exit_handler Whether or not to run the exit_handler() */ -function page_footer($run_cron = true) +function page_footer($run_cron = true, $display_template = true, $exit_handler = true) { global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx; + global $request, $phpbb_dispatcher, $phpbb_admin_path; + + // A listener can set this variable to `true` when it overrides this function + $page_footer_override = false; + + /** + * Execute code and/or overwrite page_footer() + * + * @event core.page_footer + * @var bool run_cron Shall we run cron tasks + * @var bool page_footer_override Shall we return instead of running + * the rest of page_footer() + * @since 3.1-A1 + */ + $vars = array('run_cron', 'page_footer_override'); + extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars))); + + if ($page_footer_override) + { + return; + } // Output page creation time if (defined('DEBUG')) @@ -4777,24 +5076,22 @@ function page_footer($run_cron = true) $mtime = explode(' ', microtime()); $totaltime = $mtime[0] + $mtime[1] - $starttime; - if (!empty($_REQUEST['explain']) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report')) + if ($request->variable('explain', false) && $auth->acl_get('a_') && defined('DEBUG') && method_exists($db, 'sql_report')) { $db->sql_report('display'); } $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime); - if ($auth->acl_get('a_') && defined('DEBUG_EXTRA')) + if ($auth->acl_get('a_') && defined('DEBUG')) { - if (function_exists('memory_get_usage')) + if (function_exists('memory_get_peak_usage')) { - if ($memory_usage = memory_get_usage()) + if ($memory_usage = memory_get_peak_usage()) { - global $base_memory_usage; - $memory_usage -= $base_memory_usage; $memory_usage = get_formatted_filesize($memory_usage); - $debug_output .= ' | Memory Usage: ' . $memory_usage; + $debug_output .= ' | Peak Memory Usage: ' . $memory_usage; } } @@ -4807,12 +5104,12 @@ function page_footer($run_cron = true) 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '', 'CREDIT_LINE' => $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'), - 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '') + 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id) : '') ); // Call cron-type script $call_cron = false; - if (!defined('IN_CRON') && $run_cron && !$config['board_disable'] && !$user->data['is_bot']) + if (!defined('IN_CRON') && !$config['use_system_cron'] && $run_cron && !$config['board_disable'] && !$user->data['is_bot']) { $call_cron = true; $time_now = (!empty($user->time_now) && is_int($user->time_now)) ? $user->time_now : time(); @@ -4833,47 +5130,27 @@ function page_footer($run_cron = true) // Call cron job? if ($call_cron) { - $cron_type = ''; + global $cron; + $task = $cron->find_one_ready_task(); - if ($time_now - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists($phpbb_root_path . 'cache/queue.' . $phpEx)) - { - // Process email queue - $cron_type = 'queue'; - } - else if (method_exists($cache, 'tidy') && $time_now - $config['cache_gc'] > $config['cache_last_gc']) - { - // Tidy the cache - $cron_type = 'tidy_cache'; - } - else if ($config['warnings_expire_days'] && ($time_now - $config['warnings_gc'] > $config['warnings_last_gc'])) + if ($task) { - $cron_type = 'tidy_warnings'; - } - else if ($time_now - $config['database_gc'] > $config['database_last_gc']) - { - // Tidy the database - $cron_type = 'tidy_database'; - } - else if ($time_now - $config['search_gc'] > $config['search_last_gc']) - { - // Tidy the search - $cron_type = 'tidy_search'; - } - else if ($time_now - $config['session_gc'] > $config['session_last_gc']) - { - $cron_type = 'tidy_sessions'; - } - - if ($cron_type) - { - $template->assign_var('RUN_CRON_TASK', '<img src="' . append_sid($phpbb_root_path . 'cron.' . $phpEx, 'cron_type=' . $cron_type) . '" width="1" height="1" alt="cron" />'); + $url = $task->get_url(); + $template->assign_var('RUN_CRON_TASK', '<img src="' . $url . '" width="1" height="1" alt="cron" />'); } } - $template->display('body'); + if ($display_template) + { + $template->display('body'); + } garbage_collection(); - exit_handler(); + + if ($exit_handler) + { + exit_handler(); + } } /** @@ -4883,6 +5160,18 @@ function page_footer($run_cron = true) function garbage_collection() { global $cache, $db; + global $phpbb_dispatcher; + + if (!empty($phpbb_dispatcher)) + { + /** + * Unload some objects, to free some memory, before we finish our task + * + * @event core.garbage_collection + * @since 3.1-A1 + */ + $phpbb_dispatcher->dispatch('core.garbage_collection'); + } // Unload cache, must be done before the DB connection if closed if (!empty($cache)) @@ -4922,7 +5211,7 @@ function exit_handler() } /** -* Handler for init calls in phpBB. This function is called in user::setup(); +* Handler for init calls in phpBB. This function is called in \phpbb\user::setup(); * This function supports hooks. */ function phpbb_user_session_handler() @@ -4940,4 +5229,77 @@ function phpbb_user_session_handler() return; } -?>
\ No newline at end of file +/** +* Check if PCRE has UTF-8 support +* PHP may not be linked with the bundled PCRE lib and instead with an older version +* +* @return bool Returns true if PCRE (the regular expressions library) supports UTF-8 encoding +*/ +function phpbb_pcre_utf8_support() +{ + static $utf8_pcre_properties = null; + if (is_null($utf8_pcre_properties)) + { + $utf8_pcre_properties = (@preg_match('/\p{L}/u', 'a') !== false); + } + return $utf8_pcre_properties; +} + +/** +* Casts a numeric string $input to an appropriate numeric type (i.e. integer or float) +* +* @param string $input A numeric string. +* +* @return int|float Integer $input if $input fits integer, +* float $input otherwise. +*/ +function phpbb_to_numeric($input) +{ + return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; +} + +/** +* Convert either 3.0 dbms or 3.1 db driver class name to 3.1 db driver class name. +* +* If $dbms is a valid 3.1 db driver class name, returns it unchanged. +* Otherwise prepends phpbb\db\driver\ to the dbms to convert a 3.0 dbms +* to 3.1 db driver class name. +* +* @param string $dbms dbms parameter +* @return db driver class +*/ +function phpbb_convert_30_dbms_to_31($dbms) +{ + // Note: this check is done first because mysqli extension + // supplies a mysqli class, and class_exists($dbms) would return + // true for mysqli class. + // However, per the docblock any valid 3.1 driver name should be + // recognized by this function, and have priority over 3.0 dbms. + if (class_exists('phpbb\db\driver\\' . $dbms)) + { + return 'phpbb\db\driver\\' . $dbms; + } + + if (class_exists($dbms)) + { + // Additionally we could check that $dbms extends phpbb\db\driver\driver. + // http://php.net/manual/en/class.reflectionclass.php + // Beware of possible performance issues: + // http://stackoverflow.com/questions/294582/php-5-reflection-api-performance + // We could check for interface implementation in all paths or + // only when we do not prepend phpbb\db\driver\. + + /* + $reflection = new \ReflectionClass($dbms); + + if ($reflection->isSubclassOf('phpbb\db\driver\driver')) + { + return $dbms; + } + */ + + return $dbms; + } + + throw new \RuntimeException("You have specified an invalid dbms driver: $dbms"); +} |