diff options
author | Igor Wiedler <igor@wiedler.ch> | 2012-07-21 00:37:48 +0200 |
---|---|---|
committer | Igor Wiedler <igor@wiedler.ch> | 2012-07-21 00:37:48 +0200 |
commit | 0e2a30a27b92a851221be489370217b9c7bf8e07 (patch) | |
tree | 78e6a55815d2dffbc07b101beffe17c98a8642f5 /phpBB/includes | |
parent | df16bd1c49e6e970b147f15e752825dd3186fb87 (diff) | |
parent | 841ea0e494504400c798faa6cc860dd1179e1004 (diff) | |
download | forums-0e2a30a27b92a851221be489370217b9c7bf8e07.tar forums-0e2a30a27b92a851221be489370217b9c7bf8e07.tar.gz forums-0e2a30a27b92a851221be489370217b9c7bf8e07.tar.bz2 forums-0e2a30a27b92a851221be489370217b9c7bf8e07.tar.xz forums-0e2a30a27b92a851221be489370217b9c7bf8e07.zip |
Merge branch 'develop' into feature/avatars
* develop: (201 commits)
[feature/new-tz-handling] Don't use global user but make it a parameter
[feature/new-tz-handling] Fix size of suggestion button in chrome
[feature/new-tz-handling] Fall back to UTC, if the timezone is invalid
[feature/new-tz-handling] Add previous selected value to validation if valid
[feature/new-tz-handling] Display suggestion when a different value is selected
[ticket/10998] Add border-radius to forum rules block - prosilver
[feature/new-tz-handling] Remove additional marking of selected items
[feature/new-tz-handling] Move update helper function to new class
[feature/new-tz-handling] Fix unit test
[feature/new-tz-handling] Delete old variable which is not used anymore
[feature/new-tz-handling] Rename $user->tz back to $user->timezone
[feature/pagination-as-list] New parameter for name of start var
[feature/pagination-as-list] Updates for nils comments
[feature/pagination-as-list] Rename and deprecate functions
[feature/pagination-as-list] Various fixes and improvements
[ticket/10968] Render pagination within the template
[feature/new-tz-handling] Remove "timezone might be numeric"
[feature/new-tz-handling] Add function to update the timezone
[feature/new-tz-handling] Correctly update user and board timezones on update
[ticket/10996] Use correct DBMS name in Travis config for PostgreSQL
...
Diffstat (limited to 'phpBB/includes')
40 files changed, 1736 insertions, 275 deletions
diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index abe304c282..3433d7e2cd 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -1222,12 +1222,14 @@ class acp_attachments } $db->sql_freeresult($result); + $base_url = $this->u_action . "&$u_sort_param"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_files, $attachments_per_page, $start); + $template->assign_vars(array( 'TOTAL_FILES' => $num_files, 'TOTAL_SIZE' => get_formatted_filesize($total_size), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param", $num_files, $attachments_per_page, $start, true), - 'S_ON_PAGE' => on_page($num_files, $attachments_per_page, $start), + 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $num_files, $attachments_per_page, $start), 'S_LIMIT_DAYS' => $s_limit_days, 'S_SORT_KEY' => $s_sort_key, 'S_SORT_DIR' => $s_sort_dir) diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index d537885ef1..575d05933f 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -57,8 +57,7 @@ class acp_board 'board_disable_msg' => false, 'default_lang' => array('lang' => 'DEFAULT_LANGUAGE', 'validate' => 'lang', 'type' => 'select', 'function' => 'language_select', 'params' => array('{CONFIG_VALUE}'), 'explain' => false), 'default_dateformat' => array('lang' => 'DEFAULT_DATE_FORMAT', 'validate' => 'string', 'type' => 'custom', 'method' => 'dateformat_select', 'explain' => true), - 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'string', 'type' => 'select', 'function' => 'tz_select', 'params' => array('{CONFIG_VALUE}', 1), 'explain' => true), - 'board_dst' => array('lang' => 'SYSTEM_DST', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'timezone', 'type' => 'custom', 'method' => 'timezone_select', 'explain' => true), 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => false), 'override_user_style' => array('lang' => 'OVERRIDE_STYLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), @@ -897,6 +896,18 @@ class acp_board '<br /><br /><input class="button2" type="submit" id="' . $key . '_enable" name="' . $key . '_enable" value="' . $user->lang['ALLOW_QUICK_REPLY_BUTTON'] . '" />'; } + /** + * Select guest timezone + */ + function timezone_select($value, $key) + { + global $user; + + $timezone_select = phpbb_timezone_select($user, $value, true); + $timezone_select['tz_select']; + + return '<select name="config[' . $key . ']" id="' . $key . '">' . $timezone_select['tz_select'] . '</select>'; + } /** * Select default dateformat @@ -907,10 +918,14 @@ class acp_board // Let the format_date function operate with the acp values $old_tz = $user->timezone; - $old_dst = $user->dst; - - $user->timezone = $config['board_timezone'] * 3600; - $user->dst = $config['board_dst'] * 3600; + try + { + $user->timezone = new DateTimeZone($config['board_timezone']); + } + catch (Exception $e) + { + // If the board timezone is invalid, we just use the users timezone. + } $dateformat_options = ''; @@ -930,7 +945,6 @@ class acp_board // Reset users date options $user->timezone = $old_tz; - $user->dst = $old_dst; return "<select name=\"dateoptions\" id=\"dateoptions\" onchange=\"if (this.value == 'custom') { document.getElementById('" . addslashes($key) . "').value = '" . addslashes($value) . "'; } else { document.getElementById('" . addslashes($key) . "').value = this.value; }\">$dateformat_options</select> <input type=\"text\" name=\"config[$key]\" id=\"$key\" value=\"$value\" maxlength=\"30\" />"; diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index d4a3e40d93..befb12cabe 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -684,13 +684,15 @@ class acp_groups $s_action_options .= '<option value="' . $option . '">' . $user->lang['GROUP_' . $lang] . '</option>'; } + $base_url = $this->u_action . "&action=$action&g=$group_id"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total_members, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_LIST' => true, 'S_GROUP_SPECIAL' => ($group_row['group_type'] == GROUP_SPECIAL) ? true : false, 'S_ACTION_OPTIONS' => $s_action_options, - 'S_ON_PAGE' => on_page($total_members, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&action=$action&g=$group_id", $total_members, $config['topics_per_page'], $start, true), + 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $total_members, $config['topics_per_page'], $start), 'GROUP_NAME' => ($group_row['group_type'] == GROUP_SPECIAL) ? $user->lang['G_' . $group_row['group_name']] : $group_row['group_name'], 'U_ACTION' => $this->u_action . "&g=$group_id", diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index bfe17c5007..b7be92d477 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -927,10 +927,8 @@ class acp_icons } } $db->sql_freeresult($result); - - $template->assign_var('PAGINATION', - generate_pagination($this->u_action, $item_count, $config['smilies_per_page'], $pagination_start, true) - ); + + phpbb_generate_template_pagination($template, $this->u_action, 'pagination', 'start', $item_count, $config['smilies_per_page'], $pagination_start); } /** diff --git a/phpBB/includes/acp/acp_inactive.php b/phpBB/includes/acp/acp_inactive.php index 78d6a0b2f3..1e23c2e6cf 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -136,6 +136,8 @@ class acp_inactive add_log('admin', 'LOG_USER_ACTIVE', $row['username']); add_log('user', $row['user_id'], 'LOG_USER_ACTIVE_USER'); } + + trigger_error(sprintf($user->lang['LOG_INACTIVE_ACTIVATE'], implode($user->lang['COMMA_SEPARATOR'], $user_affected) . ' ' . adm_back_link($this->u_action))); } // For activate we really need to redirect, else a refresh can result in users being deactivated again @@ -159,6 +161,8 @@ class acp_inactive } add_log('admin', 'LOG_INACTIVE_' . strtoupper($action), implode(', ', $user_affected)); + + trigger_error(sprintf($user->lang['LOG_INACTIVE_DELETE'], implode($user->lang['COMMA_SEPARATOR'], $user_affected) . ' ' . adm_back_link($this->u_action))); } else { @@ -230,7 +234,8 @@ class acp_inactive $db->sql_query($sql); add_log('admin', 'LOG_INACTIVE_REMIND', implode(', ', $usernames)); - unset($usernames); + + trigger_error(sprintf($user->lang['LOG_INACTIVE_REMIND'], implode($user->lang['COMMA_SEPARATOR'], $usernames) . ' ' . adm_back_link($this->u_action))); } $db->sql_freeresult($result); @@ -283,6 +288,9 @@ class acp_inactive $option_ary += array('remind' => 'REMIND'); } + $base_url = $this->u_action . "&$u_sort_param&users_per_page=$per_page"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $inactive_count, $per_page, $start); + $template->assign_vars(array( 'S_INACTIVE_USERS' => true, 'S_INACTIVE_OPTIONS' => build_select($option_ary), @@ -290,8 +298,8 @@ class acp_inactive 'S_LIMIT_DAYS' => $s_limit_days, 'S_SORT_KEY' => $s_sort_key, 'S_SORT_DIR' => $s_sort_dir, - 'S_ON_PAGE' => on_page($inactive_count, $per_page, $start), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param&users_per_page=$per_page", $inactive_count, $per_page, $start, true), + 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $inactive_count, $per_page, $start), + 'USERS_PER_PAGE' => $per_page, 'U_ACTION' => $this->u_action . "&$u_sort_param&users_per_page=$per_page&start=$start", diff --git a/phpBB/includes/acp/acp_logs.php b/phpBB/includes/acp/acp_logs.php index 6b67175220..4538633d6c 100644 --- a/phpBB/includes/acp/acp_logs.php +++ b/phpBB/includes/acp/acp_logs.php @@ -129,13 +129,15 @@ class acp_logs $log_count = 0; $start = view_log($mode, $log_data, $log_count, $config['topics_per_page'], $start, $forum_id, 0, 0, $sql_where, $sql_sort, $keywords); + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'L_TITLE' => $l_title, 'L_EXPLAIN' => $l_title_explain, 'U_ACTION' => $this->u_action . "&$u_sort_param$keywords_param&start=$start", - 'S_ON_PAGE' => on_page($log_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start, true), + 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), 'S_LIMIT_DAYS' => $s_limit_days, 'S_SORT_KEY' => $s_sort_key, diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 08763882b3..c9cf814ec4 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -1123,10 +1123,12 @@ class acp_users $log_count = 0; $start = view_log('user', $log_data, $log_count, $config['topics_per_page'], $start, 0, 0, $user_id, $sql_where, $sql_sort); + $base_url = $this->u_action . "&u=$user_id&$u_sort_param"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_FEEDBACK' => true, - 'S_ON_PAGE' => on_page($log_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&u=$user_id&$u_sort_param", $log_count, $config['topics_per_page'], $start, true), + 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), 'S_LIMIT_DAYS' => $s_limit_days, 'S_SORT_KEY' => $s_sort_key, @@ -1461,9 +1463,8 @@ class acp_users $data = array( 'dateformat' => utf8_normalize_nfc(request_var('dateformat', $user_row['user_dateformat'], true)), 'lang' => basename(request_var('lang', $user_row['user_lang'])), - 'tz' => request_var('tz', (float) $user_row['user_timezone']), + 'tz' => request_var('tz', $user_row['user_timezone']), 'style' => request_var('style', $user_row['user_style']), - 'dst' => request_var('dst', $user_row['user_dst']), 'viewemail' => request_var('viewemail', $user_row['user_allow_viewemail']), 'massemail' => request_var('massemail', $user_row['user_allow_massemail']), 'hideonline' => request_var('hideonline', !$user_row['user_allow_viewonline']), @@ -1498,7 +1499,7 @@ class acp_users $error = validate_data($data, array( 'dateformat' => array('string', false, 1, 30), 'lang' => array('match', false, '#^[a-z_\-]{2,}$#i'), - 'tz' => array('num', false, -14, 14), + 'tz' => array('timezone'), 'topic_sk' => array('string', false, 1, 1), 'topic_sd' => array('string', false, 1, 1), @@ -1534,7 +1535,6 @@ class acp_users 'user_notify_type' => $data['notifymethod'], 'user_notify_pm' => $data['notifypm'], - 'user_dst' => $data['dst'], 'user_dateformat' => $data['dateformat'], 'user_lang' => $data['lang'], 'user_timezone' => $data['tz'], @@ -1644,6 +1644,7 @@ class acp_users ${'s_sort_' . $sort_option . '_dir'} .= '</select>'; } + $timezone_selects = phpbb_timezone_select($user, $data['tz'], true); $template->assign_vars(array( 'S_PREFS' => true, 'S_JABBER_DISABLED' => ($config['jab_enable'] && $user_row['user_jabber'] && @extension_loaded('xml')) ? false : true, @@ -1657,7 +1658,6 @@ class acp_users 'NOTIFY_BOTH' => ($data['notifymethod'] == NOTIFY_BOTH) ? true : false, 'NOTIFY_PM' => $data['notifypm'], 'POPUP_PM' => $data['popuppm'], - 'DST' => $data['dst'], 'BBCODE' => $data['bbcode'], 'SMILIES' => $data['smilies'], 'ATTACH_SIG' => $data['sig'], @@ -1684,7 +1684,8 @@ class acp_users 'S_LANG_OPTIONS' => language_select($data['lang']), 'S_STYLE_OPTIONS' => style_select($data['style']), - 'S_TZ_OPTIONS' => tz_select($data['tz'], true), + 'S_TZ_OPTIONS' => $timezone_selects['tz_select'], + 'S_TZ_DATE_OPTIONS' => $timezone_selects['tz_dates'], ) ); @@ -2097,14 +2098,15 @@ class acp_users } $db->sql_freeresult($result); + $base_url = $this->u_action . "&u=$user_id&sk=$sort_key&sd=$sort_dir"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_ATTACHMENTS' => true, - 'S_ON_PAGE' => on_page($num_attachments, $config['topics_per_page'], $start), + 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $num_attachments, $config['topics_per_page'], $start), 'S_SORT_KEY' => $s_sort_key, 'S_SORT_DIR' => $s_sort_dir, - - 'PAGINATION' => generate_pagination($this->u_action . "&u=$user_id&sk=$sort_key&sd=$sort_dir", $num_attachments, $config['topics_per_page'], $start, true)) - ); + )); break; diff --git a/phpBB/includes/datetime.php b/phpBB/includes/datetime.php new file mode 100644 index 0000000000..b3462ddf67 --- /dev/null +++ b/phpBB/includes/datetime.php @@ -0,0 +1,158 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +*/ + +/** +* phpBB custom extensions to the PHP DateTime class +* This handles the relative formats phpBB employs +*/ +class phpbb_datetime extends DateTime +{ + /** + * String used to wrap the date segment which should be replaced by today/tomorrow/yesterday + */ + const RELATIVE_WRAPPER = '|'; + + /** + * @var user User who is the context for this DateTime instance + */ + protected $user; + + /** + * @var array Date formats are preprocessed by phpBB, to save constant recalculation they are cached. + */ + static protected $format_cache = array(); + + /** + * Constructs a new instance of phpbb_datetime, expanded to include an argument to inject + * the user context and modify the timezone to the users selected timezone if one is not set. + * + * @param string $time String in a format accepted by strtotime(). + * @param DateTimeZone $timezone Time zone of the time. + * @param user User object for context. + */ + public function __construct($user, $time = 'now', DateTimeZone $timezone = null) + { + $this->user = $user; + $timezone = $timezone ?: $this->user->timezone; + + parent::__construct($time, $timezone); + } + + /** + * Formats the current date time into the specified format + * + * @param string $format Optional format to use for output, defaults to users chosen format + * @param boolean $force_absolute Force output of a non relative date + * @return string Formatted date time + */ + public function format($format = '', $force_absolute = false) + { + $format = $format ? $format : $this->user->date_format; + $format = self::format_cache($format, $this->user); + $relative = ($format['is_short'] && !$force_absolute); + $now = new self($this->user, 'now', $this->user->timezone); + + $timestamp = $this->getTimestamp(); + $now_ts = $now->getTimeStamp(); + + $delta = $now_ts - $timestamp; + + if ($relative) + { + /* + * Check the delta is less than or equal to 1 hour + * and the delta not more than a minute in the past + * and the delta is either greater than -5 seconds or timestamp + * and current time are of the same minute (they must be in the same hour already) + * finally check that relative dates are supported by the language pack + */ + if ($delta <= 3600 && $delta > -60 && + ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60)) + && isset($this->user->lang['datetime']['AGO'])) + { + return $this->user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); + } + else + { + $midnight = clone $now; + $midnight->setTime(0, 0, 0); + + $midnight = $midnight->getTimestamp(); + + $day = false; + + if ($timestamp > $midnight + 86400) + { + $day = 'TOMORROW'; + } + else if ($timestamp > $midnight) + { + $day = 'TODAY'; + } + else if ($timestamp > $midnight - 86400) + { + $day = 'YESTERDAY'; + } + + if ($day !== false) + { + // Format using the short formatting and finally swap out the relative token placeholder with the correct value + return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); + } + } + } + + return strtr(parent::format($format['format_long']), $format['lang']); + } + + /** + * Magic method to convert DateTime object to string + * + * @return Formatted date time, according to the users default settings. + */ + public function __toString() + { + return $this->format(); + } + + /** + * Pre-processes the specified date format + * + * @param string $format Output format + * @param user $user User object to use for localisation + * @return array Processed date format + */ + static protected function format_cache($format, $user) + { + $lang = $user->lang_name; + + if (!isset(self::$format_cache[$lang])) + { + self::$format_cache[$lang] = array(); + } + + if (!isset(self::$format_cache[$lang][$format])) + { + // Is the user requesting a friendly date format (i.e. 'Today 12:42')? + self::$format_cache[$lang][$format] = array( + 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, + 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), + 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), + 'lang' => $user->lang['datetime'], + ); + + // Short representation of month in format? Some languages use different terms for the long and short format of May + if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) + { + self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short']; + } + } + + return self::$format_cache[$lang][$format]; + } +} diff --git a/phpBB/includes/db/dbal.php b/phpBB/includes/db/dbal.php index cf54d455f7..159703d3be 100644 --- a/phpBB/includes/db/dbal.php +++ b/phpBB/includes/db/dbal.php @@ -284,6 +284,37 @@ class dbal } /** + * Build a case expression + * + * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! + * + * @param string $condition The condition which must be true, to use action_true rather then action_else + * @param string $action_true SQL expression that is used, if the condition is true + * @param string $action_else SQL expression that is used, if the condition is false, optional + * @return string CASE expression including the condition and statements + */ + public function sql_case($condition, $action_true, $action_false = false) + { + $sql_case = 'CASE WHEN ' . $condition; + $sql_case .= ' THEN ' . $action_true; + $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; + $sql_case .= ' END'; + return $sql_case; + } + + /** + * Build a concatenated expression + * + * @param string $expr1 Base SQL expression where we append the second one + * @param string $expr2 SQL expression that is appended to the first expression + * @return string Concatenated string + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' || ' . $expr2; + } + + /** * Returns whether results of a query need to be buffered to run a transaction while iterating over them. * * @return bool Whether buffering is required. diff --git a/phpBB/includes/db/mssql.php b/phpBB/includes/db/mssql.php index abeabc389f..fb044b492f 100644 --- a/phpBB/includes/db/mssql.php +++ b/phpBB/includes/db/mssql.php @@ -92,6 +92,14 @@ class dbal_mssql extends dbal } /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** * SQL Transaction * @access private */ diff --git a/phpBB/includes/db/mssql_odbc.php b/phpBB/includes/db/mssql_odbc.php index 6e24f4e9e8..64fa9634d1 100644 --- a/phpBB/includes/db/mssql_odbc.php +++ b/phpBB/includes/db/mssql_odbc.php @@ -110,6 +110,14 @@ class dbal_mssql_odbc extends dbal } /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** * SQL Transaction * @access private */ diff --git a/phpBB/includes/db/mssqlnative.php b/phpBB/includes/db/mssqlnative.php index 8a4503f111..1f37d54ecb 100644 --- a/phpBB/includes/db/mssqlnative.php +++ b/phpBB/includes/db/mssqlnative.php @@ -260,6 +260,14 @@ class dbal_mssqlnative extends dbal /** * {@inheritDoc} */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** + * {@inheritDoc} + */ function sql_buffer_nested_transactions() { return true; @@ -435,7 +443,7 @@ class dbal_mssqlnative extends dbal unset($row['line2'], $row['line3']); } } - return $row; + return (sizeof($row)) ? $row : false; } /** diff --git a/phpBB/includes/db/mysql.php b/phpBB/includes/db/mysql.php index eb38e3e913..8d1f805870 100644 --- a/phpBB/includes/db/mysql.php +++ b/phpBB/includes/db/mysql.php @@ -120,6 +120,14 @@ class dbal_mysql extends dbal } /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; + } + + /** * SQL Transaction * @access private */ diff --git a/phpBB/includes/db/mysqli.php b/phpBB/includes/db/mysqli.php index 4210a58002..e07cd35e24 100644 --- a/phpBB/includes/db/mysqli.php +++ b/phpBB/includes/db/mysqli.php @@ -123,6 +123,14 @@ class dbal_mysqli extends dbal } /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; + } + + /** * SQL Transaction * @access private */ diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index e40df93194..e5b721b1f5 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1069,32 +1069,209 @@ 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; + } + } + 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>'; + } + + if (isset($user->lang['timezones'][$timezone['tz']])) + { + $title = $label = $user->lang['timezones'][$timezone['tz']]; } else { - $zone_trunc = $zone; + // No label, we'll figure one out + $bits = explode('/', str_replace('_', ' ', $timezone['tz'])); + + $label = implode(' - ', $bits); + $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 @@ -1881,105 +2058,152 @@ function tracking_unserialize($string, $max_depth = 3) // Pagination functions /** -* Pagination routine, generates page number sequence -* tpl_prefix is for using different pagination blocks at one page +* Generate template rendered pagination +* Allows full control of rendering of pagination with the template +* +* @param object $template the template object +* @param string $base_url is url prepended to all links generated within the function +* @param string $block_var_name is the name assigned to the pagination data block within the template (example: <!-- BEGIN pagination -->) +* @param string $start_name is the name of the parameter containing the first item of the given page (example: start=20) +* @param int $num_items the total number of items, posts, etc., used to determine the number of pages to produce +* @param int $per_page the number of items, posts, etc. to display per page, used to determine the number of pages to produce +* @param int $start_item the item which should be considered currently active, used to determine the page we're on +* @param bool $reverse_count determines whether we weight display of the list towards the start (false) or end (true) of the list +* @param bool $ignore_on_page decides whether we enable an active (unlinked) item, used primarily for embedded lists +* @return null */ -function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '') +function phpbb_generate_template_pagination($template, $base_url, $block_var_name, $start_name, $num_items, $per_page, $start_item = 1, $reverse_count = false, $ignore_on_page = false) { - global $template, $user; - // Make sure $per_page is a valid value $per_page = ($per_page <= 0) ? 1 : $per_page; - - $separator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>'; $total_pages = ceil($num_items / $per_page); if ($total_pages == 1 || !$num_items) { - return false; + return; } $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) + + if ($reverse_count) { - $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>' : $separator; - - 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 .= $separator; - } - } - - $page_string .= ($end_cnt < $total_pages) ? '<span class="page-dots"> ... </span>' : $separator; + $start_page = ($total_pages > 5) ? $total_pages - 4 : 1; + $end_page = $total_pages; } else { - $page_string .= $separator; + // What we're doing here is calculating what the "start" and "end" pages should be. We + // do this by assuming pagination is "centered" around the currently active page with + // the three previous and three next page links displayed. Anything more than that and + // we display the ellipsis, likewise anything less. + // + // $start_page is the page at which we start creating the list. When we have five or less + // pages we start at page 1 since there will be no ellipsis displayed. Anymore than that + // and we calculate the start based on the active page. This is the min/max calculation. + // First (max) would we end up starting on a page less than 1? Next (min) would we end + // up starting so close to the end that we'd not display our minimum number of pages. + // + // $end_page is the last page in the list to display. Like $start_page we use a min/max to + // determine this number. Again at most five pages? Then just display them all. More than + // five and we first (min) determine whether we'd end up listing more pages than exist. + // We then (max) ensure we're displaying the minimum number of pages. + $start_page = ($total_pages > 5) ? min(max(1, $on_page - 3), $total_pages - 4) : 1; + $end_page = ($total_pages > 5) ? max(min($total_pages, $on_page + 3), 5) : $total_pages; + } + + if ($on_page != $total_pages) + { + $template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => '', + 'PAGE_URL' => $base_url . $url_delim . $start_name . '=' . ($on_page * $per_page), + 'S_IS_CURRENT' => false, + 'S_IS_PREV' => false, + 'S_IS_NEXT' => true, + 'S_IS_ELLIPSIS' => false, + )); + } + + // This do...while exists purely to negate the need for start and end assign_block_vars, i.e. + // to display the first and last page in the list plus any ellipsis. We use this loop to jump + // around a little within the list depending on where we're starting (and ending). + $at_page = 1; + do + { + $page_url = $base_url . (($at_page == 1) ? '' : $url_delim . $start_name . '=' . (($at_page - 1) * $per_page)); + + // We decide whether to display the ellipsis during the loop. The ellipsis is always + // displayed as either the second or penultimate item in the list. So are we at either + // of those points and of course do we even need to display it, i.e. is the list starting + // on at least page 3 and ending three pages before the final item. + $template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => $at_page, + 'PAGE_URL' => $page_url, + 'S_IS_CURRENT' => (!$ignore_on_page && $at_page == $on_page), + 'S_IS_NEXT' => false, + 'S_IS_PREV' => false, + 'S_IS_ELLIPSIS' => ($at_page == 2 && $start_page > 2) || ($at_page == $total_pages - 1 && $end_page < $total_pages - 1), + )); - for ($i = 2; $i < $total_pages; $i++) + // We may need to jump around in the list depending on whether we have or need to display + // the ellipsis. Are we on page 2 and are we more than one page away from the start + // of the list? Yes? Then we jump to the start of the list. Likewise are we at the end of + // the list and are there more than two pages left in total? Yes? Then jump to the penultimate + // page (so we can display the ellipsis next pass). Else, increment the counter and keep + // going + if ($at_page == 2 && $at_page < $start_page - 1) { - $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 .= $separator; - } + $at_page = $start_page; } - } - - $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) + else if ($at_page == $end_page && $end_page < $total_pages - 1) { - $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a> ' . $page_string; + $at_page = $total_pages - 1; } - - if ($on_page != $total_pages) + else { - $page_string .= ' <a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>'; + $at_page++; } } + while ($at_page <= $total_pages); - $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, - $tpl_prefix . 'CURRENT_PAGE' => $on_page, - )); - - return $page_string; + if ($on_page != 1) + { + $template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => '', + 'PAGE_URL' => $base_url . $url_delim . $start_name . '=' . (($on_page - 2) * $per_page), + 'S_IS_CURRENT' => false, + 'S_IS_PREV' => true, + 'S_IS_NEXT' => false, + 'S_IS_ELLIPSIS' => false, + )); + } } /** -* Return current page (pagination) +* Return current page +* This function also sets certain specific template variables +* +* @param object $template the template object +* @param object $user the user object +* @param string $base_url the base url used to call this page, used by Javascript for popup jump to page +* @param int $num_items the total number of items, posts, topics, etc. +* @param int $per_page the number of items, posts, etc. per page +* @param int $start the item which should be considered currently active, used to determine the page we're on +* @return null */ -function on_page($num_items, $per_page, $start) +function phpbb_on_page($template, $user, $base_url, $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) - ); + 'PER_PAGE' => $per_page, + 'ON_PAGE' => $on_page, + + 'A_BASE_URL' => addslashes($base_url), + )); return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1)); } @@ -4654,9 +4878,6 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 $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'])); - // Send a proper content-language to the output $user_lang = $user->lang['USER_LANG']; if (strpos($user_lang, '-x-') !== false) @@ -4679,6 +4900,14 @@ 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]; + } + // The following assigns all _common_ variables that may be used at any point in a template. $template->assign_vars(array( 'SITENAME' => $config['sitename'], @@ -4746,7 +4975,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, diff --git a/phpBB/includes/functions_convert.php b/phpBB/includes/functions_convert.php index e9ec153c50..ac791e0d9b 100644 --- a/phpBB/includes/functions_convert.php +++ b/phpBB/includes/functions_convert.php @@ -1884,7 +1884,7 @@ function add_bots() 'user_email' => '', 'user_lang' => $config['default_lang'], 'user_style' => 1, - 'user_timezone' => 0, + 'user_timezone' => 'UTC', 'user_allow_massemail' => 0, ); diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index fbc71e78d0..320c74d4c8 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -640,48 +640,6 @@ function get_forum_parents(&$forum_data) } /** -* Generate topic pagination -*/ -function topic_generate_pagination($replies, $url) -{ - global $config, $user; - - // Make sure $per_page is a valid value - $per_page = ($config['posts_per_page'] <= 0) ? 1 : $config['posts_per_page']; - - if (($replies + 1) > $per_page) - { - $total_pages = ceil(($replies + 1) / $per_page); - $pagination = ''; - - $times = 1; - for ($j = 0; $j < $replies + 1; $j += $per_page) - { - $pagination .= '<a href="' . $url . ($j == 0 ? '' : '&start=' . $j) . '">' . $times . '</a>'; - if ($times == 1 && $total_pages > 5) - { - $pagination .= '<span class="page-dots"> ... </span>'; - - // Display the last three pages - $times = $total_pages - 3; - $j += ($total_pages - 4) * $per_page; - } - else if ($times < $total_pages) - { - $pagination .= '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>'; - } - $times++; - } - } - else - { - $pagination = ''; - } - - return $pagination; -} - -/** * Obtain list of moderators of each forum */ function get_moderators(&$forum_moderators, $forum_id = false) @@ -1221,7 +1179,9 @@ function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id, if ($can_watch) { $s_watching['link'] = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&" . (($is_watching) ? 'unwatch' : 'watch') . "=$mode&start=$start&hash=" . generate_link_hash("{$mode}_$match_id")); + $s_watching['link_toggle'] = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&" . ((!$is_watching) ? 'unwatch' : 'watch') . "=$mode&start=$start&hash=" . generate_link_hash("{$mode}_$match_id")); $s_watching['title'] = $user->lang[(($is_watching) ? 'STOP' : 'START') . '_WATCHING_' . strtoupper($mode)]; + $s_watching['title_toggle'] = $user->lang[((!$is_watching) ? 'STOP' : 'START') . '_WATCHING_' . strtoupper($mode)]; $s_watching['is_watching'] = $is_watching; } diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 50af8fe019..46541acd44 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -463,17 +463,21 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix, } /** -* Removes comments from schema files +* Removes "/* style" as well as "# style" comments from $input. +* +* @param string $input Input string +* +* @return string Input string with comments removed */ -function remove_comments($sql) +function phpbb_remove_comments($input) { // Remove /* */ comments (http://ostermiller.org/findcomment.html) - $sql = preg_replace('#/\*(.|[\r\n])*?\*/#', "\n", $sql); + $input = preg_replace('#/\*(.|[\r\n])*?\*/#', "\n", $input); // Remove # style comments - $sql = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql)); + $input = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $input)); - return $sql; + return $input; } /** diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index c549f99091..6c21b0f412 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -61,10 +61,7 @@ function generate_smilies($mode, $forum_id) 'body' => 'posting_smilies.html') ); - $template->assign_var('PAGINATION', - generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&f=' . $forum_id), - $smiley_count, $config['smilies_per_page'], $start, true) - ); + generate_pagination(append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&f=' . $forum_id), $smiley_count, $config['smilies_per_page'], $start); } $display_link = false; diff --git a/phpBB/includes/functions_profile_fields.php b/phpBB/includes/functions_profile_fields.php index 3399334f94..1c15ef897f 100644 --- a/phpBB/includes/functions_profile_fields.php +++ b/phpBB/includes/functions_profile_fields.php @@ -554,9 +554,12 @@ class custom_profile else if ($day && $month && $year) { global $user; - // Date should display as the same date for every user regardless of timezone, so remove offset - // to compensate for the offset added by phpbb_user::format_date() - return $user->format_date(gmmktime(0, 0, 0, $month, $day, $year) - ($user->timezone + $user->dst), $user->lang['DATE_FORMAT'], true); + // Date should display as the same date for every user regardless of timezone + + return $user->create_datetime() + ->setDate($year, $month, $day) + ->setTime(0, 0, 0) + ->format($user->lang['DATE_FORMAT'], true); } return $value; diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index f70e20e616..d4c6b42cf4 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -151,7 +151,7 @@ class filespec */ function is_image() { - return (strpos($this->mimetype, 'image/') !== false) ? true : false; + return (strpos($this->mimetype, 'image/') === 0); } /** diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index b2acf43b2a..5759473b65 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -197,7 +197,6 @@ function user_add($user_row, $cp_data = false) 'user_lastpost_time' => 0, 'user_lastpage' => '', 'user_posts' => 0, - 'user_dst' => (int) $config['board_dst'], 'user_colour' => '', 'user_occ' => '', 'user_interests' => '', @@ -677,8 +676,10 @@ function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_exclude, $ban_reas if (sizeof($ban_other) == 3 && ((int)$ban_other[0] < 9999) && (strlen($ban_other[0]) == 4) && (strlen($ban_other[1]) == 2) && (strlen($ban_other[2]) == 2)) { - $time_offset = (isset($user->timezone) && isset($user->dst)) ? (int) $user->timezone + (int) $user->dst : 0; - $ban_end = max($current_time, gmmktime(0, 0, 0, (int)$ban_other[1], (int)$ban_other[2], (int)$ban_other[0]) - $time_offset); + $ban_end = max($current_time, $user->create_datetime() + ->setDate((int) $ban_other[0], (int) $ban_other[1], (int) $ban_other[2]) + ->setTime(0, 0, 0) + ->getTimestamp() + $user->timezone->getOffset(new DateTime('UTC'))); } else { @@ -1247,10 +1248,21 @@ function validate_data($data, $val_ary) $function = array_shift($validate); array_unshift($validate, $data[$var]); - if ($result = call_user_func_array('validate_' . $function, $validate)) + if (function_exists('phpbb_validate_' . $function)) { - // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted. - $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var); + if ($result = call_user_func_array('phpbb_validate_' . $function, $validate)) + { + // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted. + $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var); + } + } + else + { + if ($result = call_user_func_array('validate_' . $function, $validate)) + { + // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted. + $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var); + } } } } @@ -1396,6 +1408,22 @@ function validate_language_iso_name($lang_iso) } /** +* Validate Timezone Name +* +* Tests whether a timezone name is valid +* +* @param string $timezone The timezone string to test +* +* @return bool|string Either false if validation succeeded or +* a string which will be used as the error message +* (with the variable name appended) +*/ +function phpbb_validate_timezone($timezone) +{ + return (in_array($timezone, phpbb_get_timezone_identifiers($timezone))) ? false : 'TIMEZONE_INVALID'; +} + +/** * Check to see if the username has been taken, or if it is disallowed. * Also checks if it includes the " character, which we don't allow in usernames. * Used for registering, changing names, and posting anonymously with a username diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index fec1edc872..4518e7b7cf 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -101,6 +101,9 @@ function mcp_forum_view($id, $mode, $action, $forum_info) $forum_topics = ($total == -1) ? $forum_info['forum_topics'] : $total; $limit_time_sql = ($sort_days) ? 'AND t.topic_last_post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $base_url = $url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''); + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $forum_topics, $topics_per_page, $start); + $template->assign_vars(array( 'ACTION' => $action, 'FORUM_NAME' => $forum_info['forum_name'], @@ -129,8 +132,7 @@ function mcp_forum_view($id, $mode, $action, $forum_info) 'S_MCP_ACTION' => $url . "&i=$id&forum_action=$action&mode=$mode&start=$start" . (($merge_select) ? $selected_ids : ''), - 'PAGINATION' => generate_pagination($url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''), $forum_topics, $topics_per_page, $start), - 'PAGE_NUMBER' => on_page($forum_topics, $topics_per_page, $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $forum_topics, $topics_per_page, $start), 'TOTAL_TOPICS' => $user->lang('VIEW_FORUM_TOPICS', (int) $forum_topics), )); diff --git a/phpBB/includes/mcp/mcp_logs.php b/phpBB/includes/mcp/mcp_logs.php index 848bad40a3..c1724b20d9 100644 --- a/phpBB/includes/mcp/mcp_logs.php +++ b/phpBB/includes/mcp/mcp_logs.php @@ -171,10 +171,12 @@ class mcp_logs $log_count = 0; $start = view_log('mod', $log_data, $log_count, $config['topics_per_page'], $start, $forum_list, $topic_id, 0, $sql_where, $sql_sort, $keywords); + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( - 'PAGE_NUMBER' => on_page($log_count, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), 'TOTAL' => $user->lang('TOTAL_LOGS', (int) $log_count), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start), 'L_TITLE' => $user->lang['MCP_LOGS'], diff --git a/phpBB/includes/mcp/mcp_notes.php b/phpBB/includes/mcp/mcp_notes.php index fe5be5dc0b..2d05438d70 100644 --- a/phpBB/includes/mcp/mcp_notes.php +++ b/phpBB/includes/mcp/mcp_notes.php @@ -215,6 +215,9 @@ class mcp_notes } } + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'U_POST_ACTION' => $this->u_action, 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, @@ -225,8 +228,7 @@ class mcp_notes 'L_TITLE' => $user->lang['MCP_NOTES_USER'], - 'PAGE_NUMBER' => on_page($log_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&$u_sort_param$keywords_param", $log_count, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $log_count), 'RANK_TITLE' => $rank_title, diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index d242929a80..41e912fff9 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -297,7 +297,10 @@ class mcp_pm_reports } } } - + + $base_url = $this->u_action . "&st=$sort_days&sk=$sort_key&sd=$sort_dir"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); + // Now display the page $template->assign_vars(array( 'L_EXPLAIN' => ($mode == 'pm_reports') ? $user->lang['MCP_PM_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_PM_REPORTS_CLOSED_EXPLAIN'], @@ -307,8 +310,7 @@ class mcp_pm_reports 'S_MCP_ACTION' => $this->u_action, 'S_CLOSED' => ($mode == 'pm_reports_closed') ? true : false, - 'PAGINATION' => generate_pagination($this->u_action . "&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start), - 'PAGE_NUMBER' => on_page($total, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $total, $config['topics_per_page'], $start), 'TOTAL' => $total, 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $total), ) diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 4d720a435c..b44685b8a3 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -419,6 +419,9 @@ class mcp_queue } unset($rowset, $forum_names); + $base_url = $this->u_action . "&f=$forum_id&st=$sort_days&sk=$sort_key&sd=$sort_dir"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); + // Now display the page $template->assign_vars(array( 'L_DISPLAY_ITEMS' => ($mode == 'unapproved_posts') ? $user->lang['DISPLAY_POSTS'] : $user->lang['DISPLAY_TOPICS'], @@ -430,8 +433,7 @@ class mcp_queue 'S_MCP_ACTION' => build_url(array('t', 'f', 'sd', 'st', 'sk')), 'S_TOPICS' => ($mode == 'unapproved_posts') ? false : true, - 'PAGINATION' => generate_pagination($this->u_action . "&f=$forum_id&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start), - 'PAGE_NUMBER' => on_page($total, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $total, $config['topics_per_page'], $start), 'TOPIC_ID' => $topic_id, 'TOTAL' => $user->lang((($mode == 'unapproved_posts') ? 'VIEW_TOPIC_POSTS' : 'VIEW_FORUM_TOPICS'), (int) $total), )); diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 69c6a4cfff..2890cd56e2 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -411,6 +411,9 @@ class mcp_reports unset($report_ids, $row); } + $base_url = $this->u_action . "&f=$forum_id&t=$topic_id&st=$sort_days&sk=$sort_key&sd=$sort_dir"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); + // Now display the page $template->assign_vars(array( 'L_EXPLAIN' => ($mode == 'reports') ? $user->lang['MCP_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_REPORTS_CLOSED_EXPLAIN'], @@ -421,8 +424,7 @@ class mcp_reports 'S_FORUM_OPTIONS' => $forum_options, 'S_CLOSED' => ($mode == 'reports_closed') ? true : false, - 'PAGINATION' => generate_pagination($this->u_action . "&f=$forum_id&t=$topic_id&st=$sort_days&sk=$sort_key&sd=$sort_dir", $total, $config['topics_per_page'], $start), - 'PAGE_NUMBER' => on_page($total, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $total, $config['topics_per_page'], $start), 'TOPIC_ID' => $topic_id, 'TOTAL' => $total, 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $total), diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index d4ba89b04c..e39e553ab6 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -306,6 +306,12 @@ function mcp_topic_view($id, $mode, $action) 'post_ids' => $post_id_list, )); + $base_url = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&t={$topic_info['topic_id']}&mode=$mode&action=$action&to_topic_id=$to_topic_id&posts_per_page=$posts_per_page&st=$sort_days&sk=$sort_key&sd=$sort_dir"); + if ($posts_per_page) + { + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $posts_per_page, $start); + } + $template->assign_vars(array( 'TOPIC_TITLE' => $topic_info['topic_title'], 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_info['forum_id'] . '&t=' . $topic_info['topic_id']), @@ -344,8 +350,7 @@ function mcp_topic_view($id, $mode, $action) 'RETURN_TOPIC' => sprintf($user->lang['RETURN_TOPIC'], '<a href="' . append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f={$topic_info['forum_id']}&t={$topic_info['topic_id']}&start=$start") . '">', '</a>'), 'RETURN_FORUM' => sprintf($user->lang['RETURN_FORUM'], '<a href="' . append_sid("{$phpbb_root_path}viewforum.$phpEx", "f={$topic_info['forum_id']}&start=$start") . '">', '</a>'), - 'PAGE_NUMBER' => on_page($total, $posts_per_page, $start), - 'PAGINATION' => (!$posts_per_page) ? '' : generate_pagination(append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&t={$topic_info['topic_id']}&mode=$mode&action=$action&to_topic_id=$to_topic_id&posts_per_page=$posts_per_page&st=$sort_days&sk=$sort_key&sd=$sort_dir"), $total, $posts_per_page, $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $total, $posts_per_page, $start), 'TOTAL_POSTS' => $user->lang('VIEW_TOPIC_POSTS', (int) $total), )); } diff --git a/phpBB/includes/mcp/mcp_warn.php b/phpBB/includes/mcp/mcp_warn.php index 718d324aa0..e8b0fd9ff2 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -175,6 +175,9 @@ class mcp_warn )); } + $base_url = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=warn&mode=list&st=$st&sk=$sk&sd=$sd"); + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $user_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'U_POST_ACTION' => $this->u_action, 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, @@ -182,8 +185,7 @@ class mcp_warn 'S_SELECT_SORT_KEY' => $s_sort_key, 'S_SELECT_SORT_DAYS' => $s_limit_days, - 'PAGE_NUMBER' => on_page($user_count, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination(append_sid("{$phpbb_root_path}mcp.$phpEx", "i=warn&mode=list&st=$st&sk=$sk&sd=$sd"), $user_count, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $user_count, $config['topics_per_page'], $start), 'TOTAL_USERS' => $user->lang('LIST_USERS', (int) $user_count), )); } diff --git a/phpBB/includes/questionnaire/questionnaire.php b/phpBB/includes/questionnaire/questionnaire.php index 46a743d7e9..5cb441d536 100644 --- a/phpBB/includes/questionnaire/questionnaire.php +++ b/phpBB/includes/questionnaire/questionnaire.php @@ -304,7 +304,6 @@ class phpbb_questionnaire_phpbb_data_provider 'avatar_max_width' => true, 'avatar_min_height' => true, 'avatar_min_width' => true, - 'board_dst' => true, 'board_email_form' => true, 'board_hide_emails' => true, 'board_timezone' => true, diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php new file mode 100644 index 0000000000..84ce674564 --- /dev/null +++ b/phpBB/includes/search/fulltext_postgres.php @@ -0,0 +1,856 @@ +<?php +/** +* +* @package search +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* fulltext_postgres +* Fulltext search for PostgreSQL +* @package search +*/ +class phpbb_search_fulltext_postgres extends phpbb_search_base +{ + private $stats = array(); + private $split_words = array(); + private $tsearch_usable = false; + private $version; + private $tsearch_query; + private $phrase_search = false; + public $search_query; + public $common_words = array(); + public $word_length = array(); + + /** + * Constructor + * Creates a new phpbb_search_fulltext_postgres, which is used as a search backend. + * + * @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false + */ + public function __construct(&$error) + { + global $db, $config; + + $this->word_length = array('min' => $config['fulltext_postgres_min_word_len'], 'max' => $config['fulltext_postgres_max_word_len']); + + + if ($db->sql_layer == 'postgres') + { + $pgsql_version = explode(',', substr($db->sql_server_info(), 10)); + $this->version = trim($pgsql_version[0]); + if (version_compare($this->version, '8.3', '>=')) + { + $this->tsearch_usable = true; + } + } + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + * + * @access public + */ + public function get_name() + { + return 'PostgreSQL Fulltext'; + } + + /** + * Returns if phrase search is supported or not + * + * @return bool + * + * @access public + */ + public function supports_phrase_search() + { + return $this->phrase_search; + } + + /** + * Checks for correct PostgreSQL version and stores min/max word length in the config + * + * @return string|bool Language key of the error/incompatiblity occured + * + * @access public + */ + function init() + { + global $db, $user; + + if ($db->sql_layer != 'postgres') + { + return $user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE']; + } + + if (!$this->tsearch_usable) + { + return $user->lang['FULLTEXT_POSTGRES_TS_NOT_USABLE']; + } + + return false; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string &$keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return bool false if no valid keywords were found and otherwise true + * + * @access public + */ + function split_keywords(&$keywords, $terms) + { + global $config; + + if ($terms == 'all') + { + $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#\+#', '#-#', '#\|#'); + $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); + + $keywords = preg_replace($match, $replace, $keywords); + } + + // Filter out as above + $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + + // Split words + $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; + + foreach ($this->split_words as $i => $word) + { + $clean_word = preg_replace('#^[+\-|"]#', '', $word); + + // check word length + $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); + if (($clean_len < $config['fulltext_postgres_min_word_len']) || ($clean_len > $config['fulltext_postgres_max_word_len'])) + { + $this->common_words[] = $word; + unset($this->split_words[$i]); + } + } + + if ($terms == 'any') + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) + { + $word = substr($word, 1); + } + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . $word . ' '; + } + } + else + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if (strpos($word, '+') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&' . substr($word, 1) . ' '; + } + elseif (strpos($word, '-') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&!' . substr($word, 1) . ' '; + } + elseif (strpos($word, '|') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . substr($word, 1) . ' '; + } + else + { + $this->search_query .= '+' . $word . ' '; + $this->tsearch_query .= '&' . $word . ' '; + } + } + } + + $this->tsearch_query = substr($this->tsearch_query, 1); + $this->search_query = utf8_htmlspecialchars($this->search_query); + + if ($this->search_query) + { + $this->split_words = array_values($this->split_words); + sort($this->split_words); + return true; + } + return false; + } + + /** + * Turns text into an array of words + * @param string $text contains post text/subject + * + * @access public + */ + function split_message($text) + { + global $config; + + // Split words + $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); + $text = $matches[1]; + + // remove too short or too long words + $text = array_values($text); + for ($i = 0, $n = sizeof($text); $i < $n; $i++) + { + $text[$i] = trim($text[$i]); + if (utf8_strlen($text[$i]) < $config['fulltext_postgres_min_word_len'] || utf8_strlen($text[$i]) > $config['fulltext_postgres_max_word_len']) + { + unset($text[$i]); + } + } + + return array_values($text); + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first. + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param array $m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + * + * @access public + */ + function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + { + global $config, $db; + + // No keywords? No posts. + if (!$this->search_query) + { + return false; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + implode(', ', $this->split_words), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + implode(',', $m_approve_fid_ary), + implode(',', $author_ary) + ))); + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $join_topic = true; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $sql_match = 'p.post_subject'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + case 'msgonly': + $sql_match = 'p.post_text'; + $sql_match_where = ''; + break; + + case 'firstpost': + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + default: + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ''; + break; + } + + if (!sizeof($m_approve_fid_ary)) + { + $m_approve_fid_sql = ' AND p.post_approved = 1'; + } + else if ($m_approve_fid_ary === array(-1)) + { + $m_approve_fid_sql = ''; + } + else + { + $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; + } + + $sql_select = ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id'; + $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; + $field = ($type == 'posts') ? 'post_id' : 'topic_id'; + $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')'; + + if (sizeof($author_ary) && $author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else if (sizeof($author_ary)) + { + $sql_author = ' AND ' . $db->sql_in_set('p.poster_id', $author_ary); + } + else + { + $sql_author = ''; + } + + $sql_where_options = $sql_sort_join; + $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; + $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; + $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_where_options .= $m_approve_fid_sql; + $sql_where_options .= $sql_author; + $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_where_options .= $sql_match_where; + + $tmp_sql_match = array(); + foreach (explode(',', $sql_match) as $sql_match_column) + { + $tmp_sql_match[] = "to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', '" . $db->sql_escape($this->tsearch_query) . "')"; + } + + $sql = "SELECT $sql_select + FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p + WHERE (" . implode(' OR ', $tmp_sql_match) . ") + $sql_where_options + ORDER BY $sql_sort"; + $result = $db->sql_query_limit($sql, $config['search_block_size'], $start); + + while ($row = $db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + + if (!sizeof($id_ary)) + { + return false; + } + + // if the total result count is not cached yet, retrieve it from the db + if (!$result_count) + { + $result_count = sizeof ($id_ary); + + if (!$result_count) + { + return false; + } + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param array $m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + * + * @access public + */ + function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + { + global $config, $db; + + // No author? No posts. + if (!sizeof($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + implode(',', $m_approve_fid_ary), + implode(',', $author_ary), + $author_name, + ))); + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + // Create some display specific sql strings + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $db->sql_in_set('p.poster_id', $author_ary); + } + $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + if (!sizeof($m_approve_fid_ary)) + { + $m_approve_fid_sql = ' AND p.post_approved = 1'; + } + else if ($m_approve_fid_ary == array(-1)) + { + $m_approve_fid_sql = ''; + } + else + { + $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; + } + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT p.post_id + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT t.topic_id + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, $sort_by_sql[$sort_key] + ORDER BY $sql_sort"; + $field = 'topic_id'; + } + + // Only read one block of posts from the db and then cache it + $result = $db->sql_query_limit($sql, $config['search_block_size'], $start); + + while ($row = $db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $db->sql_freeresult($result); + + // retrieve the total result count if needed + if (!$result_count) + { + $result_count = sizeof ($id_ary); + + if (!$result_count) + { + return false; + } + } + + if (sizeof($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $result_count; + } + return false; + } + + /** + * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated. + * + * @param string $mode contains the post mode: edit, post, reply, quote ... + * @param int $post_id contains the post id of the post to index + * @param string $message contains the post text of the post + * @param string $subject contains the subject of the post to index + * @param int $poster_id contains the user id of the poster + * @param int $forum_id contains the forum id of parent forum of the post + * + * @access public + */ + function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + global $db; + + // Split old and new post/subject to obtain array of words + $split_text = $this->split_message($message); + $split_title = ($subject) ? $this->split_message($subject) : array(); + + $words = array_unique(array_merge($split_text, $split_title)); + + unset($split_text); + unset($split_title); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache($words, array($poster_id)); + + unset($words); + } + + /** + * Destroy cached results, that might be outdated after deleting a post + * + * @access public + */ + function index_remove($post_ids, $author_ids, $forum_ids) + { + $this->destroy_cache(array(), $author_ids); + } + + /** + * Destroy old cache entries + * + * @access public + */ + function tidy() + { + global $db, $config; + + // destroy too old cached search results + $this->destroy_cache(array()); + + set_config('search_last_gc', time(), true); + } + + /** + * Create fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + * + * @access public + */ + function create_index($acp_module, $u_action) + { + global $db, $config; + + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + if (!isset($this->stats['post_subject'])) + { + $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_subject))"); + } + + if (!isset($this->stats['post_text'])) + { + $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_text))"); + } + + $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Drop fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + * + * @access public + */ + function delete_index($acp_module, $u_action) + { + global $db; + + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + if (isset($this->stats['post_subject'])) + { + $db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); + } + + if (isset($this->stats['post_text'])) + { + $db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); + } + + $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Returns true if both FULLTEXT indexes exist + * + * @access public + */ + function index_created() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return (isset($this->stats['post_text']) && isset($this->stats['post_subject'])) ? true : false; + } + + /** + * Returns an associative array containing information about the indexes + * + * @access public + */ + function index_stats() + { + global $user; + + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $user->lang['FULLTEXT_POSTGRES_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + /** + * Computes the stats and store them in the $this->stats associative array + * + * @access private + */ + function get_stats() + { + global $db, $config; + + if ($db->sql_layer != 'postgres') + { + $this->stats = array(); + return; + } + + $sql = "SELECT c2.relname, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS indexdef + FROM pg_catalog.pg_class c1, pg_catalog.pg_index i, pg_catalog.pg_class c2 + WHERE c1.relname = '" . POSTS_TABLE . "' + AND pg_catalog.pg_table_is_visible(c1.oid) + AND c1.oid = i.indrelid + AND i.indexrelid = c2.oid"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + // deal with older PostgreSQL versions which didn't use Index_type + if (strpos($row['indexdef'], 'to_tsvector') !== false) + { + if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') + { + $this->stats['post_text'] = $row; + } + else if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') + { + $this->stats['post_subject'] = $row; + } + } + } + $db->sql_freeresult($result); + + $this->stats['total_posts'] = $config['num_posts']; + } + + /** + * Display various options that can be configured for the backend from the acp + * + * @return associative array containing template and config variables + * + * @access public + */ + function acp() + { + global $user, $config, $db; + + $tpl = ' + <dl> + <dt><label>' . $user->lang['FULLTEXT_POSTGRES_VERSION_CHECK'] . '</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN'] . '</span></dt> + <dd>' . (($this->tsearch_usable) ? $user->lang['YES'] : $user->lang['NO']) . ' (PostgreSQL ' . $this->version . ')</dd> + </dl> + <dl> + <dt><label>' . $user->lang['FULLTEXT_POSTGRES_TS_NAME'] . '</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '</span></dt> + <dd><select name="config[fulltext_postgres_ts_name]">'; + + if ($db->sql_layer == 'postgres' && $this->tsearch_usable) + { + $sql = 'SELECT cfgname AS ts_name + FROM pg_ts_config'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $tpl .= '<option value="' . $row['ts_name'] . '"' . ($row['ts_name'] === $config['fulltext_postgres_ts_name'] ? ' selected="selected"' : '') . '>' . $row['ts_name'] . '</option>'; + } + $db->sql_freeresult($result); + } + else + { + $tpl .= '<option value="' . $config['fulltext_postgres_ts_name'] . '" selected="selected">' . $config['fulltext_postgres_ts_name'] . '</option>'; + } + + $tpl .= '</select></dd> + </dl> + <dl> + <dt><label for="fulltext_postgres_min_word_len">' . $user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN'] . ':</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN'] . '</span></dt> + <dd><input id="fulltext_postgres_min_word_len" type="text" size="3" maxlength="3" name="config[fulltext_postgres_min_word_len]" value="' . (int) $config['fulltext_postgres_min_word_len'] . '" /></dd> + </dl> + <dl> + <dt><label for="fulltext_postgres_max_word_len">' . $user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN'] . ':</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN'] . '</span></dt> + <dd><input id="fulltext_postgres_max_word_len" type="text" size="3" maxlength="3" name="config[fulltext_postgres_max_word_len]" value="' . (int) $config['fulltext_postgres_max_word_len'] . '" /></dd> + </dl> + '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array('fulltext_postgres_ts_name' => 'string', 'fulltext_postgres_min_word_len' => 'integer:0:255', 'fulltext_postgres_max_word_len' => 'integer:0:255') + ); + } +} diff --git a/phpBB/includes/ucp/ucp_attachments.php b/phpBB/includes/ucp/ucp_attachments.php index 836185f105..e4c351709b 100644 --- a/phpBB/includes/ucp/ucp_attachments.php +++ b/phpBB/includes/ucp/ucp_attachments.php @@ -170,9 +170,11 @@ class ucp_attachments } $db->sql_freeresult($result); + $base_url = $this->u_action . "&sk=$sort_key&sd=$sort_dir"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); + $template->assign_vars(array( - 'PAGE_NUMBER' => on_page($num_attachments, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&sk=$sort_key&sd=$sort_dir", $num_attachments, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $num_attachments, $config['topics_per_page'], $start), 'TOTAL_ATTACHMENTS' => $num_attachments, 'L_TITLE' => $user->lang['UCP_ATTACHMENTS'], diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php index 98bdf17d3f..029deef212 100644 --- a/phpBB/includes/ucp/ucp_groups.php +++ b/phpBB/includes/ucp/ucp_groups.php @@ -844,11 +844,13 @@ class ucp_groups $s_action_options .= '<option value="' . $option . '">' . $user->lang['GROUP_' . $lang] . '</option>'; } + $base_url = $this->u_action . "&action=$action&g=$group_id"; + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total_members, $config['topics_per_page'], $start); + $template->assign_vars(array( 'S_LIST' => true, 'S_ACTION_OPTIONS' => $s_action_options, - 'S_ON_PAGE' => on_page($total_members, $config['topics_per_page'], $start), - 'PAGINATION' => generate_pagination($this->u_action . "&action=$action&g=$group_id", $total_members, $config['topics_per_page'], $start), + 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $total_members, $config['topics_per_page'], $start), 'U_ACTION' => $this->u_action . "&g=$group_id", 'S_UCP_ACTION' => $this->u_action . "&g=$group_id", diff --git a/phpBB/includes/ucp/ucp_main.php b/phpBB/includes/ucp/ucp_main.php index 00b7b55f27..f21ea2471b 100644 --- a/phpBB/includes/ucp/ucp_main.php +++ b/phpBB/includes/ucp/ucp_main.php @@ -670,9 +670,10 @@ class ucp_main if ($topics_count) { + phpbb_generate_template_pagination($template, $this->u_action, 'pagination', 'start', $topics_count, $config['topics_per_page'], $start); + $template->assign_vars(array( - 'PAGINATION' => generate_pagination($this->u_action, $topics_count, $config['topics_per_page'], $start), - 'PAGE_NUMBER' => on_page($topics_count, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $this->u_action, $topics_count, $config['topics_per_page'], $start), 'TOTAL_TOPICS' => $user->lang('VIEW_FORUM_TOPICS', (int) $topics_count), )); } @@ -813,7 +814,6 @@ class ucp_main 'S_DELETED_TOPIC' => (!$row['topic_id']) ? true : false, - 'PAGINATION' => topic_generate_pagination($replies, append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . "&t=$topic_id")), 'REPLIES' => $replies, 'VIEWS' => $row['topic_views'], 'TOPIC_TITLE' => censor_text($row['topic_title']), @@ -837,6 +837,8 @@ class ucp_main 'U_VIEW_TOPIC' => $view_topic_url, 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), )); + + phpbb_generate_template_pagination($template, append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . "&t=$topic_id"), 'topicrow.pagination', 'start', $replies + 1, $config['posts_per_page'], 1, true, true); } } } diff --git a/phpBB/includes/ucp/ucp_pm_viewfolder.php b/phpBB/includes/ucp/ucp_pm_viewfolder.php index 8b1cd419f4..dc77587452 100644 --- a/phpBB/includes/ucp/ucp_pm_viewfolder.php +++ b/phpBB/includes/ucp/ucp_pm_viewfolder.php @@ -451,9 +451,11 @@ function get_pm_from($folder_id, $folder, $user_id) $sql_limit_time = ''; } + $base_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=view&action=view_folder&f=$folder_id&$u_sort_param"); + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $pm_count, $config['topics_per_page'], $start); + $template->assign_vars(array( - 'PAGINATION' => generate_pagination(append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=view&action=view_folder&f=$folder_id&$u_sort_param"), $pm_count, $config['topics_per_page'], $start), - 'PAGE_NUMBER' => on_page($pm_count, $config['topics_per_page'], $start), + 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $pm_count, $config['topics_per_page'], $start), 'TOTAL_MESSAGES' => $user->lang('VIEW_PM_MESSAGES', (int) $pm_count), 'POST_IMG' => (!$auth->acl_get('u_sendpm')) ? $user->img('button_topic_locked', 'POST_PM_LOCKED') : $user->img('button_pm_new', 'POST_NEW_PM'), diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 0c9f20f266..2228bc7931 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -41,9 +41,8 @@ class ucp_prefs 'dateformat' => request_var('dateformat', $user->data['user_dateformat'], true), 'lang' => basename(request_var('lang', $user->data['user_lang'])), 'style' => request_var('style', (int) $user->data['user_style']), - 'tz' => request_var('tz', (float) $user->data['user_timezone']), + 'tz' => request_var('tz', $user->data['user_timezone']), - 'dst' => request_var('dst', (bool) $user->data['user_dst']), 'viewemail' => request_var('viewemail', (bool) $user->data['user_allow_viewemail']), 'massemail' => request_var('massemail', (bool) $user->data['user_allow_massemail']), 'hideonline' => request_var('hideonline', (bool) !$user->data['user_allow_viewonline']), @@ -72,7 +71,7 @@ class ucp_prefs $error = validate_data($data, array( 'dateformat' => array('string', false, 1, 30), 'lang' => array('language_iso_name'), - 'tz' => array('num', false, -14, 14), + 'tz' => array('timezone'), )); if (!check_form_key('ucp_prefs_personal')) @@ -93,7 +92,6 @@ class ucp_prefs 'user_notify_pm' => $data['notifypm'], 'user_options' => $user->data['user_options'], - 'user_dst' => $data['dst'], 'user_dateformat' => $data['dateformat'], 'user_lang' => $data['lang'], 'user_timezone' => $data['tz'], @@ -133,6 +131,7 @@ class ucp_prefs } $dateformat_options .= '>' . $user->lang['CUSTOM_DATEFORMAT'] . '</option>'; + $timezone_selects = phpbb_timezone_select($user, $data['tz'], true); $template->assign_vars(array( 'ERROR' => (sizeof($error)) ? implode('<br />', $error) : '', @@ -145,7 +144,6 @@ class ucp_prefs 'S_HIDE_ONLINE' => $data['hideonline'], 'S_NOTIFY_PM' => $data['notifypm'], 'S_POPUP_PM' => $data['popuppm'], - 'S_DST' => $data['dst'], 'DATE_FORMAT' => $data['dateformat'], 'A_DATE_FORMAT' => addslashes($data['dateformat']), @@ -156,7 +154,8 @@ class ucp_prefs 'S_LANG_OPTIONS' => language_select($data['lang']), 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : style_select($data['style']), - 'S_TZ_OPTIONS' => tz_select($data['tz'], true), + 'S_TZ_OPTIONS' => $timezone_selects['tz_select'], + 'S_TZ_DATE_OPTIONS' => $timezone_selects['tz_dates'], 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) ); diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 5d85029e62..6ce53a79ab 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -100,7 +100,7 @@ class ucp_register 'username' => utf8_normalize_nfc(request_var('username', '', true)), 'email' => strtolower(request_var('email', '')), 'lang' => $user->lang_name, - 'tz' => request_var('tz', (float) $config['board_timezone']), + 'tz' => request_var('tz', $config['board_timezone']), )); } @@ -120,7 +120,10 @@ class ucp_register if ($coppa === false && $config['coppa_enable']) { $now = getdate(); - $coppa_birthday = $user->format_date(mktime($now['hours'] + $user->data['user_dst'], $now['minutes'], $now['seconds'], $now['mon'], $now['mday'] - 1, $now['year'] - 13), $user->lang['DATE_FORMAT']); + $coppa_birthday = $user->create_datetime() + ->setDate($now['year'] - 13, $now['mon'], $now['mday'] - 1) + ->setTime(0, 0, 0) + ->format($user->lang['DATE_FORMAT'], true); unset($now); $template->assign_vars(array( @@ -163,7 +166,6 @@ class ucp_register $captcha->init(CONFIRM_REG); } - $is_dst = $config['board_dst']; $timezone = $config['board_timezone']; $data = array( @@ -172,7 +174,7 @@ class ucp_register 'password_confirm' => request_var('password_confirm', '', true), 'email' => strtolower(request_var('email', '')), 'lang' => basename(request_var('lang', $user->lang_name)), - 'tz' => request_var('tz', (float) $timezone), + 'tz' => request_var('tz', $timezone), ); // Check and initialize some variables if needed @@ -189,7 +191,7 @@ class ucp_register 'email' => array( array('string', false, 6, 60), array('email')), - 'tz' => array('num', false, -14, 14), + 'tz' => array('timezone'), 'lang' => array('language_iso_name'), )); @@ -279,8 +281,7 @@ class ucp_register 'user_password' => phpbb_hash($data['new_password']), 'user_email' => $data['email'], 'group_id' => (int) $group_id, - 'user_timezone' => (float) $data['tz'], - 'user_dst' => $is_dst, + 'user_timezone' => $data['tz'], 'user_lang' => $data['lang'], 'user_type' => $user_type, 'user_actkey' => $user_actkey, @@ -441,6 +442,7 @@ class ucp_register break; } + $timezone_selects = phpbb_timezone_select($user, $data['tz'], true); $template->assign_vars(array( 'ERROR' => (sizeof($error)) ? implode('<br />', $error) : '', 'USERNAME' => $data['username'], @@ -453,7 +455,8 @@ class ucp_register 'L_PASSWORD_EXPLAIN' => $user->lang($config['pass_complex'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_pass_chars']), $user->lang('CHARACTERS', (int) $config['max_pass_chars'])), 'S_LANG_OPTIONS' => language_select($data['lang']), - 'S_TZ_OPTIONS' => tz_select($data['tz']), + 'S_TZ_OPTIONS' => $timezone_selects['tz_select'], + 'S_TZ_DATE_OPTIONS' => $timezone_selects['tz_dates'], 'S_CONFIRM_REFRESH' => ($config['enable_confirm'] && $config['confirm_refresh']) ? true : false, 'S_REGISTRATION' => true, 'S_COPPA' => $coppa, diff --git a/phpBB/includes/update_helpers.php b/phpBB/includes/update_helpers.php new file mode 100644 index 0000000000..8e8e9670b0 --- /dev/null +++ b/phpBB/includes/update_helpers.php @@ -0,0 +1,112 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +*/ + +/** +* phpBB Update Helpers +*/ +class phpbb_update_helpers +{ + /** + * Determine the new timezone for a given phpBB 3.0 timezone and + * "Daylight Saving Time" option + * + * @param $timezone float Users timezone in 3.0 + * @param $dst int Users daylight saving time + * @return string Users new php Timezone which is used since 3.1 + */ + function convert_phpbb30_timezone($timezone, $dst) + { + $offset = $timezone + $dst; + + switch ($timezone) + { + case '-12': + return 'Etc/GMT' . $offset; //'[UTC - 12] Baker Island Time' + case '-11': + return 'Etc/GMT' . $offset; //'[UTC - 11] Niue Time, Samoa Standard Time' + case '-10': + return 'Etc/GMT' . $offset; //'[UTC - 10] Hawaii-Aleutian Standard Time, Cook Island Time' + case '-9.5': + return 'Pacific/Marquesas'; //'[UTC - 9:30] Marquesas Islands Time' + case '-9': + return 'Etc/GMT' . $offset; //'[UTC - 9] Alaska Standard Time, Gambier Island Time' + case '-8': + return 'Etc/GMT' . $offset; //'[UTC - 8] Pacific Standard Time' + case '-7': + return 'Etc/GMT' . $offset; //'[UTC - 7] Mountain Standard Time' + case '-6': + return 'Etc/GMT' . $offset; //'[UTC - 6] Central Standard Time' + case '-5': + return 'Etc/GMT' . $offset; //'[UTC - 5] Eastern Standard Time' + case '-4.5': + return 'America/Caracas'; //'[UTC - 4:30] Venezuelan Standard Time' + case '-4': + return 'Etc/GMT' . $offset; //'[UTC - 4] Atlantic Standard Time' + case '-3.5': + return 'America/St_Johns'; //'[UTC - 3:30] Newfoundland Standard Time' + case '-3': + return 'Etc/GMT' . $offset; //'[UTC - 3] Amazon Standard Time, Central Greenland Time' + case '-2': + return 'Etc/GMT' . $offset; //'[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time' + case '-1': + return 'Etc/GMT' . $offset; //'[UTC - 1] Azores Standard Time, Cape Verde Time, Eastern Greenland Time' + case '0': + return (!$dst) ? 'UTC' : 'Etc/GMT+1'; //'[UTC] Western European Time, Greenwich Mean Time' + case '1': + return 'Etc/GMT+' . $offset; //'[UTC + 1] Central European Time, West African Time' + case '2': + return 'Etc/GMT+' . $offset; //'[UTC + 2] Eastern European Time, Central African Time' + case '3': + return 'Etc/GMT+' . $offset; //'[UTC + 3] Moscow Standard Time, Eastern African Time' + case '3.5': + return 'Asia/Tehran'; //'[UTC + 3:30] Iran Standard Time' + case '4': + return 'Etc/GMT+' . $offset; //'[UTC + 4] Gulf Standard Time, Samara Standard Time' + case '4.5': + return 'Asia/Kabul'; //'[UTC + 4:30] Afghanistan Time' + case '5': + return 'Etc/GMT+' . $offset; //'[UTC + 5] Pakistan Standard Time, Yekaterinburg Standard Time' + case '5.5': + return 'Asia/Kolkata'; //'[UTC + 5:30] Indian Standard Time, Sri Lanka Time' + case '5.75': + return 'Asia/Kathmandu'; //'[UTC + 5:45] Nepal Time' + case '6': + return 'Etc/GMT+' . $offset; //'[UTC + 6] Bangladesh Time, Bhutan Time, Novosibirsk Standard Time' + case '6.5': + return 'Indian/Cocos'; //'[UTC + 6:30] Cocos Islands Time, Myanmar Time' + case '7': + return 'Etc/GMT+' . $offset; //'[UTC + 7] Indochina Time, Krasnoyarsk Standard Time' + case '8': + return 'Etc/GMT+' . $offset; //'[UTC + 8] Chinese Standard Time, Australian Western Standard Time, Irkutsk Standard Time' + case '8.75': + return 'Australia/Eucla'; //'[UTC + 8:45] Southeastern Western Australia Standard Time' + case '9': + return 'Etc/GMT+' . $offset; //'[UTC + 9] Japan Standard Time, Korea Standard Time, Chita Standard Time' + case '9.5': + return 'Australia/ACT'; //'[UTC + 9:30] Australian Central Standard Time' + case '10': + return 'Etc/GMT+' . $offset; //'[UTC + 10] Australian Eastern Standard Time, Vladivostok Standard Time' + case '10.5': + return 'Australia/Lord_Howe'; //'[UTC + 10:30] Lord Howe Standard Time' + case '11': + return 'Etc/GMT+' . $offset; //'[UTC + 11] Solomon Island Time, Magadan Standard Time' + case '11.5': + return 'Pacific/Norfolk'; //'[UTC + 11:30] Norfolk Island Time' + case '12': + return 'Etc/GMT+12'; //'[UTC + 12] New Zealand Time, Fiji Time, Kamchatka Standard Time' + case '12.75': + return 'Pacific/Chatham'; //'[UTC + 12:45] Chatham Islands Time' + case '13': + return 'Pacific/Tongatapu'; //'[UTC + 13] Tonga Time, Phoenix Islands Time' + case '14': + return 'Pacific/Kiritimati'; //'[UTC + 14] Line Island Time' + default: + return 'UTC'; + } + } +} diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index cf9e6b9994..fcbfaaddfa 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -29,8 +29,11 @@ class phpbb_user extends phpbb_session var $help = array(); var $style = array(); var $date_format; - var $timezone; - var $dst; + + /** + * DateTimeZone object holding the timezone of the user + */ + public $timezone; var $lang_name = false; var $lang_id = false; @@ -79,15 +82,13 @@ class phpbb_user extends phpbb_session $this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); $this->date_format = $this->data['user_dateformat']; - $this->timezone = $this->data['user_timezone'] * 3600; - $this->dst = $this->data['user_dst'] * 3600; + $user_timezone = $this->data['user_timezone']; } else { $this->lang_name = basename($config['default_lang']); $this->date_format = $config['default_dateformat']; - $this->timezone = $config['board_timezone'] * 3600; - $this->dst = $config['board_dst'] * 3600; + $user_timezone = $config['board_timezone']; /** * If a guest user is surfing, we try to guess his/her language first by obtaining the browser language @@ -126,6 +127,16 @@ class phpbb_user extends phpbb_session */ } + try + { + $this->timezone = new DateTimeZone($user_timezone); + } + catch (Exception $e) + { + // If the timezone the user has selected is invalid, we fall back to UTC. + $this->timezone = new DateTimeZone('UTC'); + } + // We include common language file here to not load it every time a custom language file is included $lang = &$this->lang; @@ -612,70 +623,46 @@ class phpbb_user extends phpbb_session */ function format_date($gmepoch, $format = false, $forcedate = false) { - static $midnight; - static $date_cache; - - $format = (!$format) ? $this->date_format : $format; - $now = time(); - $delta = $now - $gmepoch; - - if (!isset($date_cache[$format])) - { - // Is the user requesting a friendly date format (i.e. 'Today 12:42')? - $date_cache[$format] = array( - 'is_short' => strpos($format, '|'), - 'format_short' => substr($format, 0, strpos($format, '|')) . '||' . substr(strrchr($format, '|'), 1), - 'format_long' => str_replace('|', '', $format), - 'lang' => $this->lang['datetime'], - ); - - // Short representation of month in format? Some languages use different terms for the long and short format of May - if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) - { - $date_cache[$format]['lang']['May'] = $this->lang['datetime']['May_short']; - } - } + static $utc; - // Zone offset - $zone_offset = $this->timezone + $this->dst; - - // Show date < 1 hour ago as 'xx min ago' but not greater than 60 seconds in the future - // A small tolerence is given for times in the future but in the same minute are displayed as '< than a minute ago' - if ($delta < 3600 && $delta > -60 && ($delta >= -5 || (($now / 60) % 60) == (($gmepoch / 60) % 60)) && $date_cache[$format]['is_short'] !== false && !$forcedate && isset($this->lang['datetime']['AGO'])) + if (!isset($utc)) { - return $this->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); + $utc = new DateTimeZone('UTC'); } - if (!$midnight) - { - list($d, $m, $y) = explode(' ', gmdate('j n Y', time() + $zone_offset)); - $midnight = gmmktime(0, 0, 0, $m, $d, $y) - $zone_offset; - } - - if ($date_cache[$format]['is_short'] !== false && !$forcedate && !($gmepoch < $midnight - 86400 || $gmepoch > $midnight + 172800)) - { - $day = false; + $time = new phpbb_datetime($this, "@$gmepoch", $utc); + $time->setTimezone($this->timezone); - if ($gmepoch > $midnight + 86400) - { - $day = 'TOMORROW'; - } - else if ($gmepoch > $midnight) - { - $day = 'TODAY'; - } - else if ($gmepoch > $midnight - 86400) - { - $day = 'YESTERDAY'; - } + return $time->format($format, $forcedate); + } - if ($day !== false) - { - return str_replace('||', $this->lang['datetime'][$day], strtr(@gmdate($date_cache[$format]['format_short'], $gmepoch + $zone_offset), $date_cache[$format]['lang'])); - } - } + /** + * Create a phpbb_datetime object in the context of the current user + * + * @since 3.1 + * @param string $time String in a format accepted by strtotime(). + * @param DateTimeZone $timezone Time zone of the time. + * @return phpbb_datetime Date time object linked to the current users locale + */ + public function create_datetime($time = 'now', DateTimeZone $timezone = null) + { + $timezone = $timezone ?: $this->timezone; + return new phpbb_datetime($this, $time, $timezone); + } - return strtr(@gmdate($date_cache[$format]['format_long'], $gmepoch + $zone_offset), $date_cache[$format]['lang']); + /** + * Get the UNIX timestamp for a datetime in the users timezone, so we can store it in the database. + * + * @param string $format Format of the entered date/time + * @param string $time Date/time with the timezone applied + * @param DateTimeZone $timezone Timezone of the date/time, falls back to timezone of current user + * @return int Returns the unix timestamp + */ + public function get_timestamp_from_format($format, $time, DateTimeZone $timezone = null) + { + $timezone = $timezone ?: $this->timezone; + $date = DateTime::createFromFormat($format, $time, $timezone); + return ($date !== false) ? $date->format('U') : false; } /** |