diff options
Diffstat (limited to 'phpBB/includes')
246 files changed, 18021 insertions, 9046 deletions
diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index c62fefae46..9d6c2d5de1 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -99,7 +98,7 @@ class acp_attachments  				}  				$db->sql_freeresult($result); -				$l_legend_cat_images = $user->lang['SETTINGS_CAT_IMAGES'] . ' [' . $user->lang['ASSIGNED_GROUP'] . ': ' . ((!empty($s_assigned_groups[ATTACHMENT_CATEGORY_IMAGE])) ? implode(', ', $s_assigned_groups[ATTACHMENT_CATEGORY_IMAGE]) : $user->lang['NO_EXT_GROUP']) . ']'; +				$l_legend_cat_images = $user->lang['SETTINGS_CAT_IMAGES'] . ' [' . $user->lang['ASSIGNED_GROUP'] . ': ' . ((!empty($s_assigned_groups[ATTACHMENT_CATEGORY_IMAGE])) ? implode($user->lang['COMMA_SEPARATOR'], $s_assigned_groups[ATTACHMENT_CATEGORY_IMAGE]) : $user->lang['NO_EXT_GROUP']) . ']';  				$display_vars = array(  					'title'	=> 'ACP_ATTACHMENT_SETTINGS', @@ -918,7 +917,7 @@ class acp_attachments  						$db->sql_query($sql);  						add_log('admin', 'LOG_ATTACH_ORPHAN_DEL', implode(', ', $delete_files)); -						$notify[] = sprintf($user->lang['LOG_ATTACH_ORPHAN_DEL'], implode(', ', $delete_files)); +						$notify[] = sprintf($user->lang['LOG_ATTACH_ORPHAN_DEL'], implode($user->lang['COMMA_SEPARATOR'], $delete_files));  					}  					$upload_list = array(); @@ -1075,7 +1074,7 @@ class acp_attachments  								$error[] = $user->lang['FILES_GONE'];  							}  							add_log('admin', 'LOG_ATTACHMENTS_DELETED', implode(', ', $deleted_filenames)); -							$notify[] = sprintf($user->lang['LOG_ATTACHMENTS_DELETED'], implode(', ', $deleted_filenames)); +							$notify[] = sprintf($user->lang['LOG_ATTACHMENTS_DELETED'], implode($user->lang['COMMA_SEPARATOR'], $deleted_filenames));  						}  						else  						{ @@ -1159,12 +1158,12 @@ class acp_attachments  				// Issue warning message if files stats are inaccurate  				if (($num_files != $num_files_real) || ($total_size != $total_size_real))  				{ -					$error[] = sprintf($user->lang['FILES_STATS_WRONG'], $num_files_real, get_formatted_filesize($total_size_real)); +					$error[] = $user->lang('FILES_STATS_WRONG', (int) $num_files_real, get_formatted_filesize($total_size_real));  					$template->assign_vars(array(  						'S_ACTION_OPTIONS'	=> ($auth->acl_get('a_board')) ? true : false,  						'U_ACTION'			=> $this->u_action,) -					);					 +					);  				}  				// Make sure $start is set to the last page if it exceeds the amount @@ -1223,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) @@ -1244,8 +1245,7 @@ class acp_attachments  					$row['extension'] = strtolower(trim((string) $row['extension']));  					$comment = ($row['attach_comment'] && !$row['in_message']) ? str_replace(array("\n", "\r"), array('<br />', "\n"), $row['attach_comment']) : '';  					$display_cat = $extensions[$row['extension']]['display_cat']; -					$l_downloaded_viewed = ($display_cat == ATTACHMENT_CATEGORY_NONE) ? 'DOWNLOAD_COUNT' : 'VIEWED_COUNT'; -					$l_download_count = (!isset($row['download_count']) || (int) $row['download_count'] == 0) ? $user->lang[$l_downloaded_viewed . '_NONE'] : (((int) $row['download_count'] == 1) ? sprintf($user->lang[$l_downloaded_viewed], $row['download_count']) : sprintf($user->lang[$l_downloaded_viewed . 'S'], $row['download_count'])); +					$l_downloaded_viewed = ($display_cat == ATTACHMENT_CATEGORY_NONE) ? 'DOWNLOAD_COUNTS' : 'VIEWED_COUNTS';  					$template->assign_block_vars('attachments', array(  						'ATTACHMENT_POSTER'	=> get_username_string('full', (int) $row['poster_id'], (string) $row['username'], (string) $row['user_colour'], (string) $row['username']), @@ -1261,7 +1261,7 @@ class acp_attachments  						'TOPIC_ID'			=> (int) $row['topic_id'],  						'POST_IDS'			=> (!empty($post_ids[$row['attach_id']])) ? (int) $post_ids[$row['attach_id']] : '', -						'L_DOWNLOAD_COUNT'	=> $l_download_count, +						'L_DOWNLOAD_COUNT'	=> $user->lang($l_downloaded_viewed, (int) $row['download_count']),  						'S_IN_MESSAGE'		=> (bool) $row['in_message'], diff --git a/phpBB/includes/acp/acp_ban.php b/phpBB/includes/acp/acp_ban.php index 00e68367fd..3ed9c225f5 100644 --- a/phpBB/includes/acp/acp_ban.php +++ b/phpBB/includes/acp/acp_ban.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -98,7 +97,7 @@ class acp_ban  			break;  		} -		$this->display_ban_options($mode); +		self::display_ban_options($mode);  		$template->assign_vars(array(  			'L_TITLE'				=> $this->page_title, @@ -119,7 +118,7 @@ class acp_ban  	/**  	* Display ban options  	*/ -	function display_ban_options($mode) +	static public function display_ban_options($mode)  	{  		global $user, $db, $template; @@ -175,12 +174,21 @@ class acp_ban  		}  		$result = $db->sql_query($sql); -		$banned_options = ''; +		$banned_options = $excluded_options = array();  		$ban_length = $ban_reasons = $ban_give_reasons = array();  		while ($row = $db->sql_fetchrow($result))  		{ -			$banned_options .= '<option' . (($row['ban_exclude']) ? ' class="sep"' : '') . ' value="' . $row['ban_id'] . '">' . $row[$field] . '</option>'; +			$option = '<option value="' . $row['ban_id'] . '">' . $row[$field] . '</option>'; + +			if ($row['ban_exclude']) +			{ +				$excluded_options[] = $option; +			} +			else +			{ +				$banned_options[] = $option; +			}  			$time_length = ($row['ban_end']) ? ($row['ban_end'] - $row['ban_start']) / 60 : 0; @@ -241,10 +249,25 @@ class acp_ban  			}  		} +		$options = ''; +		if ($excluded_options) +		{ +			$options .= '<optgroup label="' . $user->lang['OPTIONS_EXCLUDED'] . '">'; +			$options .= implode('', $excluded_options); +			$options .= '</optgroup>'; +		} + +		if ($banned_options) +		{ +			$options .= '<optgroup label="' . $user->lang['OPTIONS_BANNED'] . '">'; +			$options .= implode('', $banned_options); +			$options .= '</optgroup>'; +		} +  		$template->assign_vars(array(  			'S_BAN_END_OPTIONS'	=> $ban_end_options, -			'S_BANNED_OPTIONS'	=> ($banned_options) ? true : false, -			'BANNED_OPTIONS'	=> $banned_options) -		); +			'S_BANNED_OPTIONS'	=> ($banned_options || $excluded_options) ? true : false, +			'BANNED_OPTIONS'	=> $options, +		));  	}  } diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index ecbaff6274..e537d7a8b9 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -25,7 +24,7 @@ class acp_bbcodes  	function main($id, $mode)  	{ -		global $db, $user, $auth, $template, $cache; +		global $db, $user, $auth, $template, $cache, $request;  		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx;  		$user->add_lang('acp/posting'); @@ -273,6 +272,18 @@ class acp_bbcodes  						$db->sql_query('DELETE FROM ' . BBCODES_TABLE . " WHERE bbcode_id = $bbcode_id");  						$cache->destroy('sql', BBCODES_TABLE);  						add_log('admin', 'LOG_BBCODE_DELETE', $row['bbcode_tag']); +						 +						if ($request->is_ajax()) +						{ +							$json_response = new phpbb_json_response; +							$json_response->send(array( +								'MESSAGE_TITLE'	=> $user->lang['INFORMATION'], +								'MESSAGE_TEXT'	=> $user->lang['BBCODE_DELETED'], +								'REFRESH_DATA'	=> array( +									'time'	=> 3 +								) +							)); +						}  					}  					else  					{ @@ -317,7 +328,7 @@ class acp_bbcodes  		$bbcode_tpl = trim($bbcode_tpl);  		$utf8 = strpos($bbcode_match, 'INTTEXT') !== false; -		$utf8_pcre_properties = pcre_utf8_support(); +		$utf8_pcre_properties = phpbb_pcre_utf8_support();  		$fp_match = preg_quote($bbcode_match, '!');  		$fp_replace = preg_replace('#^\[(.*?)\]#', '[$1:$uid]', $bbcode_match); diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 6821073749..322e1c55d8 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  * @todo add cron intervals to server settings? (database_gc, queue_interval, session_gc, search_gc, cache_gc, warnings_gc)  */ @@ -54,12 +53,13 @@ class acp_board  						'legend1'				=> 'ACP_BOARD_SETTINGS',  						'sitename'				=> array('lang' => 'SITE_NAME',				'validate' => 'string',	'type' => 'text:40:255', 'explain' => false),  						'site_desc'				=> array('lang' => 'SITE_DESC',				'validate' => 'string',	'type' => 'text:40:255', 'explain' => false), +						'site_home_url'			=> array('lang' => 'SITE_HOME_URL',			'validate' => 'string',	'type' => 'text:40:255', 'explain' => true), +						'site_home_text'		=> array('lang' => 'SITE_HOME_TEXT',		'validate' => 'string',	'type' => 'text:40:255', 'explain' => true),  						'board_disable'			=> array('lang' => 'DISABLE_BOARD',			'validate' => 'bool',	'type' => 'custom', 'method' => 'board_disable', 'explain' => true),  						'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), @@ -89,6 +89,7 @@ class acp_board  						'allow_nocensors'		=> array('lang' => 'ALLOW_NO_CENSORS',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true),  						'allow_bookmarks'		=> array('lang' => 'ALLOW_BOOKMARKS',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true),  						'allow_birthdays'		=> array('lang' => 'ALLOW_BIRTHDAYS',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true), +						'display_last_subject'	=> array('lang' => 'DISPLAY_LAST_SUBJECT',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true),  						'allow_quick_reply'		=> array('lang' => 'ALLOW_QUICK_REPLY',		'validate' => 'bool',	'type' => 'custom', 'method' => 'quick_reply', 'explain' => true),  						'legend2'				=> 'ACP_LOAD_SETTINGS', @@ -96,6 +97,7 @@ class acp_board  						'load_moderators'		=> array('lang' => 'YES_MODERATORS',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false),  						'load_jumpbox'			=> array('lang' => 'YES_JUMPBOX',			'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false),  						'load_cpf_memberlist'	=> array('lang' => 'LOAD_CPF_MEMBERLIST',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false), +						'load_cpf_pm'			=> array('lang' => 'LOAD_CPF_PM',			'validate' => 'bool',	'type' => 'radio:yes_no', 'explain'	=> false),  						'load_cpf_viewprofile'	=> array('lang' => 'LOAD_CPF_VIEWPROFILE',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false),  						'load_cpf_viewtopic'	=> array('lang' => 'LOAD_CPF_VIEWTOPIC',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false), @@ -188,7 +190,7 @@ class acp_board  						'hot_threshold'			=> array('lang' => 'HOT_THRESHOLD',			'validate' => 'int:0',		'type' => 'text:3:4', 'explain' => true),  						'max_poll_options'		=> array('lang' => 'MAX_POLL_OPTIONS',		'validate' => 'int:2:127',	'type' => 'text:4:4', 'explain' => false),  						'max_post_chars'		=> array('lang' => 'CHAR_LIMIT',			'validate' => 'int:0',		'type' => 'text:4:6', 'explain' => true), -						'min_post_chars'		=> array('lang' => 'MIN_CHAR_LIMIT',		'validate' => 'int:0',		'type' => 'text:4:6', 'explain' => true), +						'min_post_chars'		=> array('lang' => 'MIN_CHAR_LIMIT',		'validate' => 'int:1',		'type' => 'text:4:6', 'explain' => true),  						'max_post_smilies'		=> array('lang' => 'SMILIES_LIMIT',			'validate' => 'int:0',		'type' => 'text:4:4', 'explain' => true),  						'max_post_urls'			=> array('lang' => 'MAX_POST_URLS',			'validate' => 'int:0',		'type' => 'text:5:4', 'explain' => true),  						'max_post_font_size'	=> array('lang' => 'MAX_POST_FONT_SIZE',	'validate' => 'int:0',		'type' => 'text:5:4', 'explain' => true, 'append' => ' %'), @@ -234,7 +236,7 @@ class acp_board  						'max_name_chars'		=> array('lang' => 'USERNAME_LENGTH', 'validate' => 'int:8:180', 'type' => false, 'method' => false, 'explain' => false,),  						'max_pass_chars'		=> array('lang' => 'PASSWORD_LENGTH', 'validate' => 'int:8:255', 'type' => false, 'method' => false, 'explain' => false,), -						'require_activation'	=> array('lang' => 'ACC_ACTIVATION',	'validate' => 'int',	'type' => 'custom', 'method' => 'select_acc_activation', 'explain' => true), +						'require_activation'	=> array('lang' => 'ACC_ACTIVATION',	'validate' => 'int',	'type' => 'select', 'method' => 'select_acc_activation', 'explain' => true),  						'new_member_post_limit'	=> array('lang' => 'NEW_MEMBER_POST_LIMIT', 'validate' => 'int:0:255', 'type' => 'text:4:4', 'explain' => true, 'append' => ' ' . $user->lang['POSTS']),  						'new_member_group_default'=> array('lang' => 'NEW_MEMBER_GROUP_DEFAULT', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true),  						'min_name_chars'		=> array('lang' => 'USERNAME_LENGTH',	'validate' => 'int:1',	'type' => 'custom:5:180', 'method' => 'username_length', 'explain' => true), @@ -323,10 +325,12 @@ class acp_board  						'load_moderators'		=> array('lang' => 'YES_MODERATORS',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false),  						'load_jumpbox'			=> array('lang' => 'YES_JUMPBOX',			'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false),  						'load_user_activity'	=> array('lang' => 'LOAD_USER_ACTIVITY',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true), -						'load_tplcompile'		=> array('lang' => 'RECOMPILE_STYLES',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true), +						'load_tplcompile'		=> array('lang' => 'RECOMPILE_STYLES',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true), +						'load_jquery_cdn'		=> array('lang' => 'LOAD_JQUERY_CDN',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true),  						'legend3'				=> 'CUSTOM_PROFILE_FIELDS',  						'load_cpf_memberlist'	=> array('lang' => 'LOAD_CPF_MEMBERLIST',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false), +						'load_cpf_pm'			=> array('lang' => 'LOAD_CPF_PM',			'validate' => 'bool',	'type' => 'radio:yes_no', 'explain'	=> false),  						'load_cpf_viewprofile'	=> array('lang' => 'LOAD_CPF_VIEWPROFILE',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false),  						'load_cpf_viewtopic'	=> array('lang' => 'LOAD_CPF_VIEWTOPIC',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => false), @@ -384,9 +388,14 @@ class acp_board  						'referer_validation'	=> array('lang' => 'REFERER_VALID',		'validate' => 'int:0:3','type' => 'custom', 'method' => 'select_ref_check', 'explain' => true),  						'check_dnsbl'			=> array('lang' => 'CHECK_DNSBL',			'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true),  						'email_check_mx'		=> array('lang' => 'EMAIL_CHECK_MX',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true), +						'max_pass_chars'		=> array('lang' => 'PASSWORD_LENGTH', 'validate' => 'int:8:255', 'type' => false, 'method' => false, 'explain' => false,), +						'min_pass_chars'		=> array('lang' => 'PASSWORD_LENGTH',	'validate' => 'int:1',	'type' => 'custom', 'method' => 'password_length', 'explain' => true),  						'pass_complex'			=> array('lang' => 'PASSWORD_TYPE',			'validate' => 'string',	'type' => 'select', 'method' => 'select_password_chars', 'explain' => true),  						'chg_passforce'			=> array('lang' => 'FORCE_PASS_CHANGE',		'validate' => 'int:0',	'type' => 'text:3:3', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']),  						'max_login_attempts'	=> array('lang' => 'MAX_LOGIN_ATTEMPTS',	'validate' => 'int:0',	'type' => 'text:3:3', 'explain' => true), +						'ip_login_limit_max'	=> array('lang' => 'IP_LOGIN_LIMIT_MAX',	'validate' => 'int:0',	'type' => 'text:3:3', 'explain' => true), +						'ip_login_limit_time'	=> array('lang' => 'IP_LOGIN_LIMIT_TIME',	'validate' => 'int:0',	'type' => 'text:5:5', 'explain' => true, 'append' => ' ' . $user->lang['SECONDS']), +						'ip_login_limit_use_forwarded'	=> array('lang' => 'IP_LOGIN_LIMIT_USE_FORWARDED',	'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true),  						'tpl_allow_php'			=> array('lang' => 'TPL_ALLOW_PHP',			'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true),  						'form_token_lifetime'	=> array('lang' => 'FORM_TIME_MAX',			'validate' => 'int:-1',	'type' => 'text:5:5', 'explain' => true, 'append' => ' ' . $user->lang['SECONDS']),  						'form_token_sid_guests'	=> array('lang' => 'FORM_SID_GUESTS',		'validate' => 'bool',	'type' => 'radio:yes_no', 'explain' => true), @@ -766,17 +775,28 @@ class acp_board  	/**  	* Select account activation method  	*/ -	function select_acc_activation($value, $key = '') +	function select_acc_activation($selected_value, $value)  	{  		global $user, $config; -		$radio_ary = array(USER_ACTIVATION_DISABLE => 'ACC_DISABLE', USER_ACTIVATION_NONE => 'ACC_NONE'); +		$act_ary = array( +		  'ACC_DISABLE' => USER_ACTIVATION_DISABLE, +		  'ACC_NONE' => USER_ACTIVATION_NONE, +		);  		if ($config['email_enable'])  		{ -			$radio_ary += array(USER_ACTIVATION_SELF => 'ACC_USER', USER_ACTIVATION_ADMIN => 'ACC_ADMIN'); +			$act_ary['ACC_USER'] = USER_ACTIVATION_SELF; +			$act_ary['ACC_ADMIN'] = USER_ACTIVATION_ADMIN; +		}		 +		$act_options = ''; + +		foreach ($act_ary as $key => $value) +		{ +			$selected = ($selected_value == $value) ? ' selected="selected"' : ''; +			$act_options .= '<option value="' . $value . '"' . $selected . '>' . $user->lang[$key] . '</option>';  		} -		return h_radio('config[require_activation]', $radio_ary, $value, $key); +		return $act_options;  	}  	/** @@ -878,6 +898,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 @@ -888,10 +920,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 = ''; @@ -911,7 +947,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_bots.php b/phpBB/includes/acp/acp_bots.php index d253064e81..b9dd6664f4 100644 --- a/phpBB/includes/acp/acp_bots.php +++ b/phpBB/includes/acp/acp_bots.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -25,7 +24,7 @@ class acp_bots  	function main($id, $mode)  	{ -		global $config, $db, $user, $auth, $template, $cache; +		global $config, $db, $user, $auth, $template, $cache, $request;  		global $phpbb_root_path, $phpbb_admin_path, $phpEx, $table_prefix;  		$action = request_var('action', ''); @@ -353,6 +352,14 @@ class acp_bots  			break;  		} +		 +		if ($request->is_ajax() && ($action == 'activate' || $action == 'deactivate')) +		{ +			$json_response = new phpbb_json_response; +			$json_response->send(array( +				'text'	=> $user->lang['BOT_' . (($action == 'activate') ? 'DE' : '') . 'ACTIVATE'], +			)); +		}  		$s_options = '';  		$_options = array('activate' => 'BOT_ACTIVATE', 'deactivate' => 'BOT_DEACTIVATE', 'delete' => 'DELETE'); diff --git a/phpBB/includes/acp/acp_captcha.php b/phpBB/includes/acp/acp_captcha.php index 8116fce6f0..c7c64ae56b 100644 --- a/phpBB/includes/acp/acp_captcha.php +++ b/phpBB/includes/acp/acp_captcha.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  */  /** @@ -30,7 +29,8 @@ class acp_captcha  		$user->add_lang('acp/board');  		include($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx); -		$captchas = phpbb_captcha_factory::get_captcha_types(); +		$factory = new phpbb_captcha_factory(); +		$captchas = $factory->get_captcha_types();  		$selected = request_var('select_captcha', $config['captcha_plugin']);  		$selected = (isset($captchas['available'][$selected]) || isset($captchas['unavailable'][$selected])) ? $selected : $config['captcha_plugin']; @@ -46,7 +46,7 @@ class acp_captcha  		// Delegate  		if ($configure)  		{ -			$config_captcha =& phpbb_captcha_factory::get_instance($selected); +			$config_captcha = phpbb_captcha_factory::get_instance($selected);  			$config_captcha->acp_page($id, $this);  		}  		else @@ -78,11 +78,11 @@ class acp_captcha  					// sanity check  					if (isset($captchas['available'][$selected]))  					{ -						$old_captcha =& phpbb_captcha_factory::get_instance($config['captcha_plugin']); +						$old_captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']);  						$old_captcha->uninstall();  						set_config('captcha_plugin', $selected); -						$new_captcha =& phpbb_captcha_factory::get_instance($config['captcha_plugin']); +						$new_captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']);  						$new_captcha->install();  						add_log('admin', 'LOG_CONFIG_VISUAL'); @@ -96,7 +96,7 @@ class acp_captcha  			}  			else if ($submit)  			{ -				trigger_error($user->lang['FORM_INVALID'] . adm_back_link(), E_USER_WARNING); +				trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);  			}  			else  			{ @@ -104,16 +104,16 @@ class acp_captcha  				foreach ($captchas['available'] as $value => $title)  				{  					$current = ($selected !== false && $value == $selected) ? ' selected="selected"' : ''; -					$captcha_select .= '<option value="' . $value . '"' . $current . '>' . $user->lang[$title] . '</option>'; +					$captcha_select .= '<option value="' . $value . '"' . $current . '>' . $user->lang($title) . '</option>';  				}  				foreach ($captchas['unavailable'] as $value => $title)  				{  					$current = ($selected !== false && $value == $selected) ? ' selected="selected"' : ''; -					$captcha_select .= '<option value="' . $value . '"' . $current . ' class="disabled-option">' . $user->lang[$title] . '</option>'; +					$captcha_select .= '<option value="' . $value . '"' . $current . ' class="disabled-option">' . $user->lang($title) . '</option>';  				} -				$demo_captcha =& phpbb_captcha_factory::get_instance($selected); +				$demo_captcha = phpbb_captcha_factory::get_instance($selected);  				foreach ($config_vars as $config_var => $options)  				{ @@ -136,7 +136,7 @@ class acp_captcha  	{  		global $db, $user, $config; -		$captcha =& phpbb_captcha_factory::get_instance($selected); +		$captcha = phpbb_captcha_factory::get_instance($selected);  		$captcha->init(CONFIRM_REG);  		$captcha->execute_demo(); diff --git a/phpBB/includes/acp/acp_database.php b/phpBB/includes/acp/acp_database.php index 96542986d3..ebcbd28a87 100644 --- a/phpBB/includes/acp/acp_database.php +++ b/phpBB/includes/acp/acp_database.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -21,6 +20,7 @@ if (!defined('IN_PHPBB'))  */  class acp_database  { +	var $db_tools;  	var $u_action;  	function main($id, $mode) @@ -28,6 +28,12 @@ class acp_database  		global $cache, $db, $user, $auth, $template, $table_prefix;  		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; +		if (!class_exists('phpbb_db_tools')) +		{ +			require($phpbb_root_path . 'includes/db/db_tools.' . $phpEx); +		} +		$this->db_tools = new phpbb_db_tools($db); +  		$user->add_lang('acp/database');  		$this->tpl_name = 'acp_database'; @@ -50,7 +56,7 @@ class acp_database  				{  					case 'download':  						$type	= request_var('type', ''); -						$table	= request_var('table', array('')); +						$table	= array_intersect($this->db_tools->sql_list_tables(), request_var('table', array('')));  						$format	= request_var('method', '');  						$where	= request_var('where', ''); @@ -173,8 +179,7 @@ class acp_database  					break;  					default: -						include($phpbb_root_path . 'includes/functions_install.' . $phpEx); -						$tables = get_tables($db); +						$tables = $this->db_tools->sql_list_tables();  						asort($tables);  						foreach ($tables as $table_name)  						{ @@ -221,6 +226,7 @@ class acp_database  					case 'submit':  						$delete = request_var('delete', '');  						$file = request_var('file', ''); +						$download = request_var('download', '');  						if (!preg_match('#^backup_\d{10,}_[a-z\d]{16}\.(sql(?:\.(?:gz|bz2))?)$#', $file, $matches))  						{ @@ -247,10 +253,8 @@ class acp_database  								confirm_box(false, $user->lang['DELETE_SELECTED_BACKUP'], build_hidden_fields(array('delete' => $delete, 'file' => $file)));  							}  						} -						else +						else if ($download || confirm_box(true))  						{ -							$download = request_var('download', ''); -  							if ($download)  							{  								$name = $matches[0]; @@ -411,6 +415,10 @@ class acp_database  							trigger_error($user->lang['RESTORE_SUCCESS'] . adm_back_link($this->u_action));  							break;  						} +						else if (!$download) +						{ +							confirm_box(false, $user->lang['RESTORE_SELECTED_BACKUP'], build_hidden_fields(array('file' => $file))); +						}  					default:  						$methods = array('sql'); @@ -486,6 +494,8 @@ class base_extractor  	function base_extractor($download = false, $store = false, $format, $filename, $time)  	{ +		global $request; +  		$this->download = $download;  		$this->store = $store;  		$this->time = $time; @@ -530,7 +540,7 @@ class base_extractor  				break;  				case 'gzip': -					if ((isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) && strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'msie') === false) +					if (strpos($request->header('Accept-Encoding'), 'gzip') !== false && strpos(strtolower($request->header('User-Agent')), 'msie') === false)  					{  						ob_start('ob_gzhandler');  					} @@ -1580,7 +1590,7 @@ class mssql_extractor extends base_extractor  		}  		$this->flush($sql_data);  	} -	 +  	function write_data_mssqlnative($table_name)  	{  		global $db; @@ -1606,7 +1616,7 @@ class mssql_extractor extends base_extractor  		$row = new result_mssqlnative($result_fields);  		$i_num_fields = $row->num_fields(); -		 +  		for ($i = 0; $i < $i_num_fields; $i++)  		{  			$ary_type[$i] = $row->field_type($i); @@ -1619,7 +1629,7 @@ class mssql_extractor extends base_extractor  			WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1";  		$result2 = $db->sql_query($sql);  		$row2 = $db->sql_fetchrow($result2); -		 +  		if (!empty($row2['has_identity']))  		{  			$sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; @@ -1683,8 +1693,8 @@ class mssql_extractor extends base_extractor  			$sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n";  		}  		$this->flush($sql_data); -	}	 -	 +	} +  	function write_data_odbc($table_name)  	{  		global $db; diff --git a/phpBB/includes/acp/acp_disallow.php b/phpBB/includes/acp/acp_disallow.php index f29e902910..f613fa325d 100644 --- a/phpBB/includes/acp/acp_disallow.php +++ b/phpBB/includes/acp/acp_disallow.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index b4755d3984..c9d149b6d7 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -136,8 +135,9 @@ class acp_email  				$i = $j = 0; -				// Send with BCC, no more than 50 recipients for one mail (to not exceed the limit) -				$max_chunk_size = 50; +				// Send with BCC +				// Maximum number of bcc recipients +				$max_chunk_size = (int) $config['email_max_chunk_size'];  				$email_list = array();  				$old_lang = $row['user_lang'];  				$old_notify_type = $row['user_notify_type']; @@ -194,10 +194,7 @@ class acp_email  					$messenger->template('admin_send_email', $used_lang); -					$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -					$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -					$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -					$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +					$messenger->anti_abuse_headers($config, $user);  					$messenger->subject(htmlspecialchars_decode($subject));  					$messenger->set_mail_priority($priority); diff --git a/phpBB/includes/acp/acp_extensions.php b/phpBB/includes/acp/acp_extensions.php new file mode 100644 index 0000000000..a0bcf62ecc --- /dev/null +++ b/phpBB/includes/acp/acp_extensions.php @@ -0,0 +1,303 @@ +<?php +/** +* +* @package acp +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* @package acp +*/ +class acp_extensions +{ +	var $u_action; + +	private $db; +	private $config; +	private $template; +	private $user; + +	function main() +	{ +		// Start the page +		global $config, $user, $template, $request, $phpbb_extension_manager, $db, $phpbb_root_path, $phpEx; + +		$this->db = $db; +		$this->config = $config; +		$this->template = $template; +		$this->user = $user; + +		$user->add_lang(array('install', 'acp/extensions')); + +		$this->page_title = 'ACP_EXTENSIONS'; + +		$action = $request->variable('action', 'list'); +		$ext_name = $request->variable('ext_name', ''); + +		// Cancel action +		if ($request->is_set_post('cancel')) +		{ +			$action = 'list'; +			$ext_name = ''; +		} + +		// If they've specified an extension, let's load the metadata manager and validate it. +		if ($ext_name) +		{ +			$md_manager = new phpbb_extension_metadata_manager($ext_name, $db, $phpbb_extension_manager, $phpbb_root_path, ".$phpEx", $template, $config); + +			try +			{ +				$md_manager->get_metadata('all'); +			} +			catch(phpbb_extension_exception $e) +			{ +				trigger_error($e); +			} +		} + +		// What are we doing? +		switch ($action) +		{ +			case 'list': +			default: +				$this->list_enabled_exts($phpbb_extension_manager); +				$this->list_disabled_exts($phpbb_extension_manager); +				$this->list_available_exts($phpbb_extension_manager); + +				$this->tpl_name = 'acp_ext_list'; +			break; + +			case 'enable_pre': +				if (!$md_manager->validate_enable()) +				{ +					trigger_error($user->lang['EXTENSION_NOT_AVAILABLE'] . adm_back_link($this->u_action)); +				} + +				if ($phpbb_extension_manager->enabled($ext_name)) +				{ +					redirect($this->u_action); +				} + +				$this->tpl_name = 'acp_ext_enable'; + +				$template->assign_vars(array( +					'PRE'		=> true, +					'U_ENABLE'	=> $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name), +				)); +			break; + +			case 'enable': +				if (!$md_manager->validate_enable()) +				{ +					trigger_error($user->lang['EXTENSION_NOT_AVAILABLE'] . adm_back_link($this->u_action)); +				} + +				if ($phpbb_extension_manager->enable_step($ext_name)) +				{ +					$template->assign_var('S_NEXT_STEP', true); + +					meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name)); +				} + +				$this->tpl_name = 'acp_ext_enable'; + +				$template->assign_vars(array( +					'U_RETURN'	=> $this->u_action . '&action=list', +				)); +			break; + +			case 'disable_pre': +				if (!$phpbb_extension_manager->enabled($ext_name)) +				{ +					redirect($this->u_action); +				} + +				$this->tpl_name = 'acp_ext_disable'; + +				$template->assign_vars(array( +					'PRE'		=> true, +					'U_DISABLE'	=> $this->u_action . '&action=disable&ext_name=' . urlencode($ext_name), +				)); +			break; + +			case 'disable': +				if ($phpbb_extension_manager->disable_step($ext_name)) +				{ +					$template->assign_var('S_NEXT_STEP', true); + +					meta_refresh(0, $this->u_action . '&action=disable&ext_name=' . urlencode($ext_name)); +				} + +				$this->tpl_name = 'acp_ext_disable'; + +				$template->assign_vars(array( +					'U_RETURN'	=> $this->u_action . '&action=list', +				)); +			break; + +			case 'purge_pre': +				$this->tpl_name = 'acp_ext_purge'; + +				$template->assign_vars(array( +					'PRE'		=> true, +					'U_PURGE'	=> $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name), +				)); +			break; + +			case 'purge': +				if ($phpbb_extension_manager->purge_step($ext_name)) +				{ +					$template->assign_var('S_NEXT_STEP', true); + +					meta_refresh(0, $this->u_action . '&action=purge&ext_name=' . urlencode($ext_name)); +				} + +				$this->tpl_name = 'acp_ext_purge'; + +				$template->assign_vars(array( +					'U_RETURN'	=> $this->u_action . '&action=list', +				)); +			break; + +			case 'details': +				// Output it to the template +				$md_manager->output_template_data(); + +				$template->assign_var('U_BACK', $this->u_action . '&action=list'); + +				$this->tpl_name = 'acp_ext_details'; +			break; +		} +	} + +	/** +	 * Lists all the enabled extensions and dumps to the template +	 * +	 * @param  $phpbb_extension_manager     An instance of the extension manager +	 * @return null +	 */ +	public function list_enabled_exts(phpbb_extension_manager $phpbb_extension_manager) +	{ +		foreach ($phpbb_extension_manager->all_enabled() as $name => $location) +		{ +			$md_manager = $phpbb_extension_manager->create_extension_metadata_manager($name, $this->template); + +			try +			{ +				$this->template->assign_block_vars('enabled', array( +					'META_DISPLAY_NAME'		=> $md_manager->get_metadata('display-name'), + +					'U_DETAILS'		=> $this->u_action . '&action=details&ext_name=' . urlencode($name), +				)); + +				$this->output_actions('enabled', array( +					'DISABLE'		=> $this->u_action . '&action=disable_pre&ext_name=' . urlencode($name), +					'PURGE'			=> $this->u_action . '&action=purge_pre&ext_name=' . urlencode($name), +				)); +			} +			catch(phpbb_extension_exception $e) +			{ +				$this->template->assign_block_vars('disabled', array( +					'META_DISPLAY_NAME'		=> $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), +				)); +			} +		} +	} + +	/** +	 * Lists all the disabled extensions and dumps to the template +	 * +	 * @param  $phpbb_extension_manager     An instance of the extension manager +	 * @return null +	 */ +	public function list_disabled_exts(phpbb_extension_manager $phpbb_extension_manager) +	{ +		foreach ($phpbb_extension_manager->all_disabled() as $name => $location) +		{ +			$md_manager = $phpbb_extension_manager->create_extension_metadata_manager($name, $this->template); + +			try +			{ +				$this->template->assign_block_vars('disabled', array( +					'META_DISPLAY_NAME'		=> $md_manager->get_metadata('display-name'), + +					'U_DETAILS'		=> $this->u_action . '&action=details&ext_name=' . urlencode($name), +				)); + +				$this->output_actions('disabled', array( +					'ENABLE'		=> $this->u_action . '&action=enable_pre&ext_name=' . urlencode($name), +					'PURGE'			=> $this->u_action . '&action=purge_pre&ext_name=' . urlencode($name), +				)); +			} +			catch(phpbb_extension_exception $e) +			{ +				$this->template->assign_block_vars('disabled', array( +					'META_DISPLAY_NAME'		=> $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), +				)); +			} +		} +	} + +	/** +	 * Lists all the available extensions and dumps to the template +	 * +	 * @param  $phpbb_extension_manager     An instance of the extension manager +	 * @return null +	 */ +	public function list_available_exts(phpbb_extension_manager $phpbb_extension_manager) +	{ +		$uninstalled = array_diff_key($phpbb_extension_manager->all_available(), $phpbb_extension_manager->all_configured()); + +		foreach ($uninstalled as $name => $location) +		{ +			$md_manager = $phpbb_extension_manager->create_extension_metadata_manager($name, $this->template); + +			try +			{ +				$this->template->assign_block_vars('disabled', array( +					'META_DISPLAY_NAME'		=> $md_manager->get_metadata('display-name'), + +					'U_DETAILS'		=> $this->u_action . '&action=details&ext_name=' . urlencode($name), +				)); + +				$this->output_actions('disabled', array( +					'ENABLE'		=> $this->u_action . '&action=enable_pre&ext_name=' . urlencode($name), +				)); +			} +			catch(phpbb_extension_exception $e) +			{ +				$this->template->assign_block_vars('disabled', array( +					'META_DISPLAY_NAME'		=> $this->user->lang('EXTENSION_INVALID_LIST', $name, $e), +				)); +			} +		} +	} + +	/** +	* Output actions to a block +	* +	* @param string $block +	* @param array $actions +	*/ +	private function output_actions($block, $actions) +	{ +		foreach ($actions as $lang => $url) +		{ +			$this->template->assign_block_vars($block . '.actions', array( +				'L_ACTION'		=> $this->user->lang($lang), +				'U_ACTION'		=> $url, +			)); +		} +	} +} diff --git a/phpBB/includes/acp/acp_forums.php b/phpBB/includes/acp/acp_forums.php index 4d605e1b82..c6dbf5eb9c 100644 --- a/phpBB/includes/acp/acp_forums.php +++ b/phpBB/includes/acp/acp_forums.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -26,7 +25,7 @@ class acp_forums  	function main($id, $mode)  	{ -		global $db, $user, $auth, $template, $cache; +		global $db, $user, $auth, $template, $cache, $request, $phpbb_dispatcher;  		global $config, $phpbb_admin_path, $phpbb_root_path, $phpEx;  		$user->add_lang('acp/forums'); @@ -151,6 +150,17 @@ class acp_forums  						'forum_password_unset'	=> request_var('forum_password_unset', false),  					); +					/** +					* Request forum data and operate on it (parse texts, etc.) +					* +					* @event core.acp_manage_forums_request_data +					* @var	string	action		Type of the action: add|edit +					* @var	array	forum_data	Array with new forum data +					* @since 3.1-A1 +					*/ +					$vars = array('action', 'forum_data'); +					extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_request_data', compact($vars))); +  					// On add, add empty forum_options... else do not consider it (not updating it)  					if ($action == 'add')  					{ @@ -212,15 +222,11 @@ class acp_forums  						$message = ($action == 'add') ? $user->lang['FORUM_CREATED'] : $user->lang['FORUM_UPDATED']; -						// Redirect to permissions -						if ($auth->acl_get('a_fauth') && !$copied_permissions) -						{ -							$message .= '<br /><br />' . sprintf($user->lang['REDIRECT_ACL'], '<a href="' . append_sid("{$phpbb_admin_path}index.$phpEx", 'i=permissions' . $acl_url) . '">', '</a>'); -						} -  						// redirect directly to permission settings screen if authed  						if ($action == 'add' && !$copied_permissions && $auth->acl_get('a_fauth'))  						{ +							$message .= '<br /><br />' . sprintf($user->lang['REDIRECT_ACL'], '<a href="' . append_sid("{$phpbb_admin_path}index.$phpEx", 'i=permissions' . $acl_url) . '">', '</a>'); +  							meta_refresh(4, append_sid("{$phpbb_admin_path}index.$phpEx", 'i=permissions' . $acl_url));  						} @@ -260,6 +266,12 @@ class acp_forums  					add_log('admin', 'LOG_FORUM_' . strtoupper($action), $row['forum_name'], $move_forum_name);  					$cache->destroy('sql', FORUMS_TABLE);  				} +				 +				if ($request->is_ajax()) +				{ +					$json_response = new phpbb_json_response; +					$json_response->send(array('success' => ($move_forum_name !== false))); +				}  			break; @@ -385,6 +397,9 @@ class acp_forums  					$forum_data['forum_flags'] += (request_var('enable_quick_reply', false)) ? FORUM_FLAG_QUICK_REPLY : 0;  				} +				// Initialise $row, so we always have it in the event +				$row = array(); +  				// Show form to create/modify a forum  				if ($action == 'edit')  				{ @@ -452,6 +467,24 @@ class acp_forums  					}  				} +				/** +				* Initialise data before we display the add/edit form +				* +				* @event core.acp_manage_forums_initialise_data +				* @var	string	action		Type of the action: add|edit +				* @var	bool	update		Do we display the form only +				*							or did the user press submit +				* @var	int		forum_id	When editing: the forum id, +				*							when creating: the parent forum id +				* @var	array	row			Array with current forum data +				*							empty when creating new forum +				* @var	array	forum_data	Array with new forum data +				* @var	string	parents_list	List of parent options +				* @since 3.1-A1 +				*/ +				$vars = array('action', 'update', 'forum_id', 'row', 'forum_data', 'parents_list'); +				extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_initialise_data', compact($vars))); +  				$forum_rules_data = array(  					'text'			=> $forum_data['forum_rules'],  					'allow_bbcode'	=> true, @@ -581,7 +614,7 @@ class acp_forums  					$errors[] = $user->lang['FORUM_PASSWORD_OLD'];  				} -				$template->assign_vars(array( +				$template_data = array(  					'S_EDIT_FORUM'		=> true,  					'S_ERROR'			=> (sizeof($errors)) ? true : false,  					'S_PARENT_ID'		=> $this->parent_id, @@ -646,7 +679,31 @@ class acp_forums  					'S_ENABLE_POST_REVIEW'		=> ($forum_data['forum_flags'] & FORUM_FLAG_POST_REVIEW) ? true : false,  					'S_ENABLE_QUICK_REPLY'		=> ($forum_data['forum_flags'] & FORUM_FLAG_QUICK_REPLY) ? true : false,  					'S_CAN_COPY_PERMISSIONS'	=> ($action != 'edit' || empty($forum_id) || ($auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth'))) ? true : false, -				)); +				); + +				/** +				* Modify forum template data before we display the form +				* +				* @event core.acp_manage_forums_display_form +				* @var	string	action		Type of the action: add|edit +				* @var	bool	update		Do we display the form only +				*							or did the user press submit +				* @var	int		forum_id	When editing: the forum id, +				*							when creating: the parent forum id +				* @var	array	row			Array with current forum data +				*							empty when creating new forum +				* @var	array	forum_data	Array with new forum data +				* @var	string	parents_list	List of parent options +				* @var	array	errors		Array of errors, if you add errors +				*					ensure to update the template variables +				*					S_ERROR and ERROR_MSG to display it +				* @var	array	template_data	Array with new forum data +				* @since 3.1-A1 +				*/ +				$vars = array('action', 'update', 'forum_id', 'row', 'forum_data', 'parents_list', 'errors', 'template_data'); +				extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_display_form', compact($vars))); + +				$template->assign_vars($template_data);  				return; @@ -871,11 +928,23 @@ class acp_forums  	*/  	function update_forum_data(&$forum_data)  	{ -		global $db, $user, $cache, $phpbb_root_path; +		global $db, $user, $cache, $phpbb_root_path, $phpbb_dispatcher;  		$errors = array(); -		if (!$forum_data['forum_name']) +		/** +		* Validate the forum data before we create/update the forum +		* +		* @event core.acp_manage_forums_validate_data +		* @var	array	forum_data	Array with new forum data +		* @var	array	errors		Array of errors, should be strings and not +		*							language key. +		* @since 3.1-A1 +		*/ +		$vars = array('forum_data', 'errors'); +		extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_validate_data', compact($vars))); + +		if ($forum_data['forum_name'] == '')  		{  			$errors[] = $user->lang['FORUM_NAME_EMPTY'];  		} @@ -967,7 +1036,22 @@ class acp_forums  		}  		unset($forum_data_sql['forum_password_unset']); -		if (!isset($forum_data_sql['forum_id'])) +		/** +		* Remove invalid values from forum_data_sql that should not be updated +		* +		* @event core.acp_manage_forums_update_data_before +		* @var	array	forum_data		Array with forum data +		* @var	array	forum_data_sql	Array with data we are going to update +		*						If forum_data_sql[forum_id] is set, we update +		*						that forum, otherwise a new one is created. +		* @since 3.1-A1 +		*/ +		$vars = array('forum_data', 'forum_data_sql'); +		extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_update_data_before', compact($vars))); + +		$is_new_forum = !isset($forum_data_sql['forum_id']); + +		if ($is_new_forum)  		{  			// no forum_id means we're creating a new forum  			unset($forum_data_sql['type_action']); @@ -1238,6 +1322,22 @@ class acp_forums  			add_log('admin', 'LOG_FORUM_EDIT', $forum_data['forum_name']);  		} +		/** +		* Event after a forum was updated or created +		* +		* @event core.acp_manage_forums_update_data_after +		* @var	array	forum_data		Array with forum data +		* @var	array	forum_data_sql	Array with data we updated +		* @var	bool	is_new_forum	Did we create a forum or update one +		*								If you want to overwrite this value, +		*								ensure to set forum_data_sql[forum_id] +		* @var	array	errors		Array of errors, should be strings and not +		*							language key. +		* @since 3.1-A1 +		*/ +		$vars = array('forum_data', 'forum_data_sql', 'is_new_forum', 'errors'); +		extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_update_data_after', compact($vars))); +  		return $errors;  	} @@ -1246,7 +1346,7 @@ class acp_forums  	*/  	function move_forum($from_id, $to_id)  	{ -		global $db, $user; +		global $db, $user, $phpbb_dispatcher;  		$to_data = $moved_ids = $errors = array(); @@ -1258,10 +1358,30 @@ class acp_forums  			if ($to_data['forum_type'] == FORUM_LINK)  			{  				$errors[] = $user->lang['PARENT_IS_LINK_FORUM']; -				return $errors;  			}  		} +		/** +		* Event when we move all children of one forum to another +		* +		* This event may be triggered, when a forum is deleted +		* +		* @event core.acp_manage_forums_move_children +		* @var	int		from_id		If of the current parent forum +		* @var	int		to_id		If of the new parent forum +		* @var	array	errors		Array of errors, should be strings and not +		*							language key. +		* @since 3.1-A1 +		*/ +		$vars = array('from_id', 'to_id', 'errors'); +		extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_move_children', compact($vars))); + +		// Return if there were errors +		if (!empty($errors)) +		{ +			return $errors; +		} +  		$moved_forums = get_forum_branch($from_id, 'children', 'descending');  		$from_data = $moved_forums[0];  		$diff = sizeof($moved_forums) * 2; @@ -1341,7 +1461,30 @@ class acp_forums  	*/  	function move_forum_content($from_id, $to_id, $sync = true)  	{ -		global $db; +		global $db, $phpbb_dispatcher; + +		$errors = array(); + +		/** +		* Event when we move content from one forum to another +		* +		* @event core.acp_manage_forums_move_children +		* @var	int		from_id		If of the current parent forum +		* @var	int		to_id		If of the new parent forum +		* @var	bool	sync		Shall we sync the "to"-forum's data +		* @var	array	errors		Array of errors, should be strings and not +		*							language key. If this array is not empty, +		*							The content will not be moved. +		* @since 3.1-A1 +		*/ +		$vars = array('from_id', 'to_id', 'sync', 'errors'); +		extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_move_content', compact($vars))); + +		// Return if there were errors +		if (!empty($errors)) +		{ +			return $errors; +		}  		$table_ary = array(LOG_TABLE, POSTS_TABLE, TOPICS_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php index dde556c19e..9621407211 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -371,7 +370,7 @@ class acp_groups  						{  							if ($data['width'] > $config['avatar_max_width'] || $data['height'] > $config['avatar_max_height'])  							{ -								$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $data['width'], $data['height']); +								$error[] = phpbb_avatar_error_wrong_size($data['width'], $data['height']);  							}  						} @@ -381,7 +380,7 @@ class acp_groups  							{  								if ($data['width'] < $config['avatar_min_width'] || $data['height'] < $config['avatar_min_height'])  								{ -									$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $data['width'], $data['height']); +									$error[] = phpbb_avatar_error_wrong_size($data['width'], $data['height']);  								}  							}  						} @@ -415,6 +414,9 @@ class acp_groups  						// Only set the rank, colour, etc. if it's changed or if we're adding a new  						// group. This prevents existing group members being updated if no changes  						// were made. +						// However there are some attributes that need to be set everytime, +						// otherwise the group gets removed from the feature. +						$set_attributes = array('legend', 'teampage');  						$group_attributes = array();  						$test_variables = array( @@ -435,7 +437,7 @@ class acp_groups  						foreach ($test_variables as $test => $type)  						{ -							if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test])) +							if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || in_array($test, $set_attributes)))  							{  								settype($submit_ary[$test], $type);  								$group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test]; @@ -624,7 +626,7 @@ class acp_groups  					'U_BACK'			=> $u_back,  					'U_SWATCH'			=> append_sid("{$phpbb_admin_path}swatch.$phpEx", 'form=settings&name=group_colour'),  					'U_ACTION'			=> "{$this->u_action}&action=$action&g=$group_id", -					'L_AVATAR_EXPLAIN'	=> sprintf($user->lang['AVATAR_EXPLAIN'], $config['avatar_max_width'], $config['avatar_max_height'], round($config['avatar_filesize'] / 1024)), +					'L_AVATAR_EXPLAIN'	=> phpbb_avatar_explanation_string(),  				));  				return; @@ -680,13 +682,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", @@ -832,7 +836,7 @@ class acp_groups  			case 'set_config_teampage':  				set_config('teampage_forums', request_var('teampage_forums', 0)); -				set_config('teampage_multiple', request_var('teampage_multiple', 0)); +				set_config('teampage_memberships', request_var('teampage_memberships', 0));  			break;  			case 'add': @@ -913,11 +917,11 @@ class acp_groups  			'U_ACTION_LEGEND' => $this->u_action . '&field=legend',  			'U_ACTION_TEAMPAGE' => $this->u_action . '&field=teampage', -			'S_GROUP_SELECT_LEGEND' => $s_group_select_legend, -			'S_GROUP_SELECT_TEAMPAGE' => $s_group_select_teampage, -			'DISPLAY_FORUMS' => ($config['teampage_forums']) ? true : false, -			'DISPLAY_MULTIPLE' => ($config['teampage_multiple']) ? true : false, -			'LEGEND_SORT_GROUPNAME' => ($config['legend_sort_groupname']) ? true : false, +			'S_GROUP_SELECT_LEGEND'		=> $s_group_select_legend, +			'S_GROUP_SELECT_TEAMPAGE'	=> $s_group_select_teampage, +			'DISPLAY_FORUMS'			=> ($config['teampage_forums']) ? true : false, +			'DISPLAY_MEMBERSHIPS'		=> $config['teampage_memberships'], +			'LEGEND_SORT_GROUPNAME'		=> ($config['legend_sort_groupname']) ? true : false,  		));  	}  } diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index c848039c66..db4b4263b0 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -379,7 +378,7 @@ class acp_icons  					if ($smiley_count + $addable_smileys_count > SMILEY_LIMIT)  					{ -						trigger_error(sprintf($user->lang['TOO_MANY_SMILIES'], SMILEY_LIMIT) . adm_back_link($this->u_action), E_USER_WARNING); +						trigger_error($user->lang('TOO_MANY_SMILIES', SMILEY_LIMIT) . adm_back_link($this->u_action), E_USER_WARNING);  					}  				} @@ -487,21 +486,7 @@ class acp_icons  				$cache->destroy('_icons');  				$cache->destroy('sql', $table); -				$level = E_USER_NOTICE; -				switch ($icons_updated) -				{ -					case 0: -						$suc_lang = "{$lang}_NONE"; -						$level = E_USER_WARNING; -						break; - -					case 1: -						$suc_lang = "{$lang}_ONE"; -						break; - -					default: -						$suc_lang = $lang; -				} +				$level = ($icons_updated) ? E_USER_NOTICE : E_USER_WARNING;  				$errormsgs = '';  				foreach ($errors as $img => $error)  				{ @@ -509,11 +494,11 @@ class acp_icons  				}  				if ($action == 'modify')  				{ -					trigger_error($user->lang[$suc_lang . '_EDITED'] . $errormsgs . adm_back_link($this->u_action), $level); +					trigger_error($user->lang($lang . '_EDITED', $icons_updated) . $errormsgs . adm_back_link($this->u_action), $level);  				}  				else  				{ -					trigger_error($user->lang[$suc_lang . '_ADDED'] . $errormsgs . adm_back_link($this->u_action), $level); +					trigger_error($user->lang($lang . '_ADDED', $icons_updated) . $errormsgs . adm_back_link($this->u_action), $level);  				}  			break; @@ -599,7 +584,7 @@ class acp_icons  						$smiley_count = $this->item_count($table);  						if ($smiley_count + sizeof($pak_ary) > SMILEY_LIMIT)  						{ -							trigger_error(sprintf($user->lang['TOO_MANY_SMILIES'], SMILEY_LIMIT) . adm_back_link($this->u_action), E_USER_WARNING); +							trigger_error($user->lang('TOO_MANY_SMILIES', SMILEY_LIMIT) . adm_back_link($this->u_action), E_USER_WARNING);  						}  					} @@ -797,6 +782,18 @@ class acp_icons  					$cache->destroy('_icons');  					$cache->destroy('sql', $table); +					 +					if ($request->is_ajax()) +					{ +						$json_response = new phpbb_json_response; +						$json_response->send(array( +							'MESSAGE_TITLE'	=> $user->lang['INFORMATION'], +							'MESSAGE_TEXT'	=> $notice, +							'REFRESH_DATA'	=> array( +								'time'	=> 3 +							) +						)); +					}  				}  				else  				{ @@ -931,9 +928,7 @@ 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 833727084f..e61115f681 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -118,10 +117,7 @@ class acp_inactive  								$messenger->to($row['user_email'], $row['username']); -								$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -								$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -								$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -								$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +								$messenger->anti_abuse_headers($config, $user);  								$messenger->assign_vars(array(  									'USERNAME'	=> htmlspecialchars_decode($row['username'])) @@ -140,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 @@ -160,6 +158,8 @@ class acp_inactive  							user_delete('retain', $mark, true);  							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  						{ @@ -206,10 +206,7 @@ class acp_inactive  							$messenger->to($row['user_email'], $row['username']);  							$messenger->im($row['user_jabber'], $row['username']); -							$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -							$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -							$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -							$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +							$messenger->anti_abuse_headers($config, $user);  							$messenger->assign_vars(array(  								'USERNAME'		=> htmlspecialchars_decode($row['username']), @@ -234,7 +231,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); @@ -287,6 +285,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), @@ -294,11 +295,10 @@ 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 . '&start=' . $start, +			'U_ACTION'		=> $this->u_action . "&$u_sort_param&users_per_page=$per_page&start=$start",  		));  		$this->tpl_name = 'acp_inactive'; diff --git a/phpBB/includes/acp/acp_jabber.php b/phpBB/includes/acp/acp_jabber.php index f010c5a5fa..96371075d6 100644 --- a/phpBB/includes/acp/acp_jabber.php +++ b/phpBB/includes/acp/acp_jabber.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  * @todo Check/enter/update transport info  */ diff --git a/phpBB/includes/acp/acp_language.php b/phpBB/includes/acp/acp_language.php index 8df8e5c8e5..2be1ccfc41 100644 --- a/phpBB/includes/acp/acp_language.php +++ b/phpBB/includes/acp/acp_language.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -101,11 +100,25 @@ class acp_language  			switch ($method)  			{  				case 'ftp': -					$transfer = new ftp(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); +					$transfer = new ftp( +						request_var('host', ''), +						request_var('username', ''), +						htmlspecialchars_decode($request->untrimmed_variable('password', '')), +						request_var('root_path', ''), +						request_var('port', ''), +						request_var('timeout', '') +					);  				break;  				case 'ftp_fsock': -					$transfer = new ftp_fsock(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); +					$transfer = new ftp_fsock( +						request_var('host', ''), +						request_var('username', ''), +						htmlspecialchars_decode($request->untrimmed_variable('password', '')), +						request_var('root_path', ''), +						request_var('port', ''), +						request_var('timeout', '') +					);  				break;  				default: @@ -405,7 +418,14 @@ class acp_language  						trigger_error($user->lang['INVALID_UPLOAD_METHOD'], E_USER_ERROR);  					} -					$transfer = new $method(request_var('host', ''), request_var('username', ''), request_var('password', ''), request_var('root_path', ''), request_var('port', ''), request_var('timeout', '')); +					$transfer = new $method( +						request_var('host', ''), +						request_var('username', ''), +						htmlspecialchars_decode($request->untrimmed_variable('password', '')), +						request_var('root_path', ''), +						request_var('port', ''), +						request_var('timeout', '') +					);  					if (($result = $transfer->open_session()) !== true)  					{ @@ -797,11 +817,6 @@ class acp_language  					$sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' WHERE lang_id = ' . $lang_id;  					$db->sql_query($sql); -					$sql = 'DELETE FROM ' . STYLES_IMAGESET_DATA_TABLE . " WHERE image_lang = '" . $db->sql_escape($row['lang_iso']) . "'"; -					$result = $db->sql_query($sql); - -					$cache->destroy('sql', STYLES_IMAGESET_DATA_TABLE); -  					add_log('admin', 'LOG_LANGUAGE_PACK_DELETED', $row['lang_english_name']);  					trigger_error(sprintf($user->lang['LANGUAGE_PACK_DELETED'], $row['lang_english_name']) . adm_back_link($this->u_action)); @@ -866,66 +881,6 @@ class acp_language  				$db->sql_query('INSERT INTO ' . LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));  				$lang_id = $db->sql_nextid(); -				$valid_localized = array( -					'icon_back_top', 'icon_contact_aim', 'icon_contact_email', 'icon_contact_icq', 'icon_contact_jabber', 'icon_contact_msnm', 'icon_contact_pm', 'icon_contact_yahoo', 'icon_contact_www', 'icon_post_delete', 'icon_post_edit', 'icon_post_info', 'icon_post_quote', 'icon_post_report', 'icon_user_online', 'icon_user_offline', 'icon_user_profile', 'icon_user_search', 'icon_user_warn', 'button_pm_forward', 'button_pm_new', 'button_pm_reply', 'button_topic_locked', 'button_topic_new', 'button_topic_reply', -				); - -				$sql_ary = array(); - -				$sql = 'SELECT * -					FROM ' . STYLES_IMAGESET_TABLE; -				$result = $db->sql_query($sql); -				while ($imageset_row = $db->sql_fetchrow($result)) -				{ -					if (@file_exists("{$phpbb_root_path}styles/{$imageset_row['imageset_path']}/imageset/{$lang_pack['iso']}/imageset.cfg")) -					{ -						$cfg_data_imageset_data = parse_cfg_file("{$phpbb_root_path}styles/{$imageset_row['imageset_path']}/imageset/{$lang_pack['iso']}/imageset.cfg"); -						foreach ($cfg_data_imageset_data as $image_name => $value) -						{ -							if (strpos($value, '*') !== false) -							{ -								if (substr($value, -1, 1) === '*') -								{ -									list($image_filename, $image_height) = explode('*', $value); -									$image_width = 0; -								} -								else -								{ -									list($image_filename, $image_height, $image_width) = explode('*', $value); -								} -							} -							else -							{ -								$image_filename = $value; -								$image_height = $image_width = 0; -							} - -							if (strpos($image_name, 'img_') === 0 && $image_filename) -							{ -								$image_name = substr($image_name, 4); -								if (in_array($image_name, $valid_localized)) -								{ -									$sql_ary[] = array( -										'image_name'		=> (string) $image_name, -										'image_filename'	=> (string) $image_filename, -										'image_height'		=> (int) $image_height, -										'image_width'		=> (int) $image_width, -										'imageset_id'		=> (int) $imageset_row['imageset_id'], -										'image_lang'		=> (string) $lang_pack['iso'], -									); -								} -							} -						} -					} -				} -				$db->sql_freeresult($result); - -				if (sizeof($sql_ary)) -				{ -					$db->sql_multi_insert(STYLES_IMAGESET_DATA_TABLE, $sql_ary); -					$cache->destroy('sql', STYLES_IMAGESET_DATA_TABLE); -				} -  				// Now let's copy the default language entries for custom profile fields for this new language - makes admin's life easier.  				$sql = 'SELECT lang_id  					FROM ' . LANG_TABLE . " @@ -934,6 +889,9 @@ class acp_language  				$default_lang_id = (int) $db->sql_fetchfield('lang_id');  				$db->sql_freeresult($result); +				// We want to notify the admin that custom profile fields need to be updated for the new language. +				$notify_cpf_update = false; +  				// From the mysql documentation:  				// Prior to MySQL 4.0.14, the target table of the INSERT statement cannot appear in the FROM clause of the SELECT part of the query. This limitation is lifted in 4.0.14.  				// Due to this we stay on the safe side if we do the insertion "the manual way" @@ -947,6 +905,7 @@ class acp_language  				{  					$row['lang_id'] = $lang_id;  					$db->sql_query('INSERT INTO ' . PROFILE_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $row)); +					$notify_cpf_update = true;  				}  				$db->sql_freeresult($result); @@ -959,12 +918,15 @@ class acp_language  				{  					$row['lang_id'] = $lang_id;  					$db->sql_query('INSERT INTO ' . PROFILE_FIELDS_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $row)); +					$notify_cpf_update = true;  				}  				$db->sql_freeresult($result);  				add_log('admin', 'LOG_LANGUAGE_PACK_INSTALLED', $lang_pack['name']); -				trigger_error(sprintf($user->lang['LANGUAGE_PACK_INSTALLED'], $lang_pack['name']) . adm_back_link($this->u_action)); +				$message = sprintf($user->lang['LANGUAGE_PACK_INSTALLED'], $lang_pack['name']); +				$message .= ($notify_cpf_update) ? '<br /><br />' . $user->lang['LANGUAGE_PACK_CPF_UPDATE'] : ''; +				trigger_error($message . adm_back_link($this->u_action));  			break; @@ -1194,10 +1156,9 @@ class acp_language  * {FILENAME} [{LANG_NAME}]  *  * @package language -* @version $' . 'Id: ' . '$  * @copyright (c) ' . date('Y') . ' phpBB Group  * @author {CHANGED} - {AUTHOR} -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/acp_logs.php b/phpBB/includes/acp/acp_logs.php index 90c1a10649..d86521532c 100644 --- a/phpBB/includes/acp/acp_logs.php +++ b/phpBB/includes/acp/acp_logs.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -128,15 +127,17 @@ class acp_logs  		// Grab log data  		$log_data = array();  		$log_count = 0; -		view_log($mode, $log_data, $log_count, $config['topics_per_page'], $start, $forum_id, 0, 0, $sql_where, $sql_sort, $keywords); +		$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_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_main.php b/phpBB/includes/acp/acp_main.php index ac375838fd..eb613535bf 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -25,7 +24,7 @@ class acp_main  	function main($id, $mode)  	{ -		global $config, $db, $user, $auth, $template; +		global $config, $db, $user, $auth, $template, $request;  		global $phpbb_root_path, $phpbb_admin_path, $phpEx;  		// Show restore permissions notice @@ -130,6 +129,11 @@ class acp_main  						set_config('record_online_users', 1, true);  						set_config('record_online_date', time(), true);  						add_log('admin', 'LOG_RESET_ONLINE'); +						 +						if ($request->is_ajax()) +						{ +							trigger_error('RESET_ONLINE_SUCCESS'); +						}  					break;  					case 'stats': @@ -180,6 +184,11 @@ class acp_main  						update_last_username();  						add_log('admin', 'LOG_RESYNC_STATS'); +						 +						if ($request->is_ajax()) +						{ +							trigger_error('RESYNC_STATS_SUCCESS'); +						}  					break;  					case 'user': @@ -201,7 +210,7 @@ class acp_main  						// No maximum post id? :o  						if (!$max_post_id)  						{ -							$sql = 'SELECT MAX(post_id) +							$sql = 'SELECT MAX(post_id) as max_post_id  								FROM ' . POSTS_TABLE;  							$result = $db->sql_query($sql);  							$max_post_id = (int) $db->sql_fetchfield('max_post_id'); @@ -242,7 +251,11 @@ class acp_main  						}  						add_log('admin', 'LOG_RESYNC_POSTCOUNTS'); - +						 +						if ($request->is_ajax()) +						{ +							trigger_error('RESYNC_POSTCOUNTS_SUCCESS'); +						}  					break;  					case 'date': @@ -253,6 +266,11 @@ class acp_main  						set_config('board_startdate', time() - 1);  						add_log('admin', 'LOG_RESET_DATE'); +						 +						if ($request->is_ajax()) +						{ +							trigger_error('RESET_DATE_SUCCESS'); +						}  					break;  					case 'db_track': @@ -328,14 +346,14 @@ class acp_main  						}  						add_log('admin', 'LOG_RESYNC_POST_MARKING'); -					break; - -					case 'purge_cache': -						if ((int) $user->data['user_type'] !== USER_FOUNDER) +						 +						if ($request->is_ajax())  						{ -							trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); +							trigger_error('RESYNC_POST_MARKING_SUCCESS');  						} +					break; +					case 'purge_cache':  						global $cache;  						$cache->purge(); @@ -344,6 +362,11 @@ class acp_main  						cache_moderators();  						add_log('admin', 'LOG_PURGE_CACHE'); +						 +						if ($request->is_ajax()) +						{ +							trigger_error('PURGE_CACHE_SUCCESS'); +						}  					break;  					case 'purge_sessions': @@ -390,6 +413,11 @@ class acp_main  						$db->sql_query($sql);  						add_log('admin', 'LOG_PURGE_SESSIONS'); +						 +						if ($request->is_ajax()) +						{ +							trigger_error('PURGE_SESSIONS_SUCCESS'); +						}  					break;  				}  			} @@ -398,11 +426,11 @@ class acp_main  		// Version check  		$user->add_lang('install'); -		if ($auth->acl_get('a_server') && version_compare(PHP_VERSION, '5.2.0', '<')) +		if ($auth->acl_get('a_server') && version_compare(PHP_VERSION, '5.3.2', '<'))  		{  			$template->assign_vars(array(  				'S_PHP_VERSION_OLD'	=> true, -				'L_PHP_VERSION_OLD'	=> sprintf($user->lang['PHP_VERSION_OLD'], '<a href="http://www.phpbb.com/community/viewtopic.php?f=14&t=1958605">', '</a>'), +				'L_PHP_VERSION_OLD'	=> sprintf($user->lang['PHP_VERSION_OLD'], '<a href="http://www.phpbb.com/community/viewtopic.php?f=14&t=2152375">', '</a>'),  			));  		} @@ -415,11 +443,8 @@ class acp_main  		{  			$latest_version_info = explode("\n", $latest_version_info); -			$latest_version = str_replace('rc', 'RC', strtolower(trim($latest_version_info[0]))); -			$current_version = str_replace('rc', 'RC', strtolower($config['version'])); -  			$template->assign_vars(array( -				'S_VERSION_UP_TO_DATE'	=> version_compare($current_version, $latest_version, '<') ? false : true, +				'S_VERSION_UP_TO_DATE'	=> phpbb_version_compare(trim($latest_version_info[0]), $config['version'], '<='),  			));  		} @@ -521,7 +546,7 @@ class acp_main  			'U_ADMIN_LOG'		=> append_sid("{$phpbb_admin_path}index.$phpEx", 'i=logs&mode=admin'),  			'U_INACTIVE_USERS'	=> append_sid("{$phpbb_admin_path}index.$phpEx", 'i=inactive&mode=list'),  			'U_VERSIONCHECK'	=> append_sid("{$phpbb_admin_path}index.$phpEx", 'i=update&mode=version_check'), -			'U_VERSIONCHECK_FORCE'	=> append_sid("{$phpbb_admin_path}index.$phpEx", 'i=1&versioncheck_force=1'), +			'U_VERSIONCHECK_FORCE'	=> append_sid("{$phpbb_admin_path}index.$phpEx", 'versioncheck_force=1'),  			'S_ACTION_OPTIONS'	=> ($auth->acl_get('a_board')) ? true : false,  			'S_FOUNDER'			=> ($user->data['user_type'] == USER_FOUNDER) ? true : false, @@ -603,6 +628,17 @@ class acp_main  			$template->assign_var('S_WRITABLE_CONFIG', (bool) (@fileperms($phpbb_root_path . 'config.' . $phpEx) & 0x0002));  		} +		if (extension_loaded('mbstring')) +		{ +			$template->assign_vars(array( +				'S_MBSTRING_LOADED'						=> true, +				'S_MBSTRING_FUNC_OVERLOAD_FAIL'			=> (intval(@ini_get('mbstring.func_overload')) & (MB_OVERLOAD_MAIL | MB_OVERLOAD_STRING)), +				'S_MBSTRING_ENCODING_TRANSLATION_FAIL'	=> (@ini_get('mbstring.encoding_translation') != 0), +				'S_MBSTRING_HTTP_INPUT_FAIL'			=> (@ini_get('mbstring.http_input') != 'pass'), +				'S_MBSTRING_HTTP_OUTPUT_FAIL'			=> (@ini_get('mbstring.http_output') != 'pass'), +			)); +		} +  		// Fill dbms version if not yet filled  		if (empty($config['dbms_version']))  		{ diff --git a/phpBB/includes/acp/acp_modules.php b/phpBB/includes/acp/acp_modules.php index 52033b590c..8528dc91c4 100644 --- a/phpBB/includes/acp/acp_modules.php +++ b/phpBB/includes/acp/acp_modules.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -37,7 +36,7 @@ class acp_modules  	function main($id, $mode)  	{ -		global $db, $user, $auth, $template, $module; +		global $db, $user, $auth, $template, $module, $request;  		global $config, $phpbb_admin_path, $phpbb_root_path, $phpEx;  		// Set a global define for modules we might include (the author is able to prevent execution of code by checking this constant) @@ -111,7 +110,7 @@ class acp_modules  				}  			break; -			 +  			case 'enable':  			case 'disable':  				if (!$module_id) @@ -170,7 +169,7 @@ class acp_modules  					add_log('admin', 'LOG_MODULE_' . strtoupper($action), $this->lang_name($row['module_langname']), $move_module_name);  					$this->remove_cache_file();  				} -		 +  			break;  			case 'quickadd': @@ -207,7 +206,7 @@ class acp_modules  						if (!sizeof($errors))  						{  							$this->remove_cache_file(); -	 +  							trigger_error($user->lang['MODULE_ADDED'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id));  						}  					} @@ -231,7 +230,7 @@ class acp_modules  				{  					trigger_error($user->lang['NO_MODULE_ID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING);  				} -				 +  				$module_row = $this->get_module_row($module_id);  			// no break @@ -250,7 +249,7 @@ class acp_modules  						'module_auth'		=> '',  					);  				} -				 +  				$module_data = array();  				$module_data['module_basename'] = request_var('module_basename', (string) $module_row['module_basename']); @@ -295,7 +294,7 @@ class acp_modules  					if (!sizeof($errors))  					{  						$this->remove_cache_file(); -	 +  						trigger_error((($action == 'add') ? $user->lang['MODULE_ADDED'] : $user->lang['MODULE_EDITED']) . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id));  					}  				} @@ -316,7 +315,7 @@ class acp_modules  					}  					// Name options -					$s_name_options .= '<option value="' . $option . '"' . (($option == $module_data['module_basename']) ? ' selected="selected"' : '') . '>' . $this->lang_name($values['title']) . ' [' . $this->module_class . '_' . $option . ']</option>'; +					$s_name_options .= '<option value="' . $option . '"' . (($option == $module_data['module_basename']) ? ' selected="selected"' : '') . '>' . $this->lang_name($values['title']) . ' [' . $option . ']</option>';  					$template->assign_block_vars('m_names', array('NAME' => $option, 'A_NAME' => addslashes($option))); @@ -327,7 +326,7 @@ class acp_modules  						{  							$s_mode_options .= '<option value="' . $m_mode . '"' . (($m_mode == $module_data['module_mode']) ? ' selected="selected"' : '') . '>' . $this->lang_name($m_values['title']) . '</option>';  						} -						 +  						$template->assign_block_vars('m_names.modes', array(  							'OPTION'		=> $m_mode,  							'VALUE'			=> $this->lang_name($m_values['title']), @@ -336,7 +335,7 @@ class acp_modules  						);  					}  				} -				 +  				$s_cat_option = '<option value="0"' . (($module_data['parent_id'] == 0) ? ' selected="selected"' : '') . '>' . $user->lang['NO_PARENT'] . '</option>';  				$template->assign_vars(array_merge(array( @@ -349,7 +348,7 @@ class acp_modules  					'U_EDIT_ACTION'		=> $this->u_action . '&parent_id=' . $this->parent_id,  					'L_TITLE'			=> $user->lang[strtoupper($action) . '_MODULE'], -					 +  					'MODULENAME'		=> $this->lang_name($module_data['module_langname']),  					'ACTION'			=> $action,  					'MODULE_ID'			=> $module_id, @@ -374,6 +373,15 @@ class acp_modules  		// Default management page  		if (sizeof($errors))  		{ +			if ($request->is_ajax()) +			{ +				$json_response = new phpbb_json_response; +				$json_response->send(array( +					'MESSAGE_TITLE'	=> $user->lang('ERROR'), +					'MESSAGE_TEXT'	=> implode('<br />', $errors), +				)); +			} +  			$template->assign_vars(array(  				'S_ERROR'	=> true,  				'ERROR_MSG'	=> implode('<br />', $errors)) @@ -480,7 +488,7 @@ class acp_modules  		foreach ($module_infos as $option => $values)  		{  			// Name options -			$s_install_options .= '<optgroup label="' . $this->lang_name($values['title']) . ' [' . $this->module_class . '_' . $option . ']">'; +			$s_install_options .= '<optgroup label="' . $this->lang_name($values['title']) . ' [' . $option . ']">';  			// Build module modes  			foreach ($values['modes'] as $m_mode => $m_values) @@ -516,7 +524,7 @@ class acp_modules  		$result = $db->sql_query($sql);  		$row = $db->sql_fetchrow($result);  		$db->sql_freeresult($result); -		 +  		if (!$row)  		{  			trigger_error($user->lang['NO_MODULE'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); @@ -524,14 +532,14 @@ class acp_modules  		return $row;  	} -	 +  	/**  	* Get available module information from module files  	*/  	function get_module_infos($module = '', $module_class = false)  	{  		global $phpbb_root_path, $phpEx; -		 +  		$module_class = ($module_class === false) ? $this->module_class : $module_class;  		$directory = $phpbb_root_path . 'includes/' . $module_class . '/info/'; @@ -539,57 +547,72 @@ class acp_modules  		if (!$module)  		{ -			$dh = @opendir($directory); +			global $phpbb_extension_manager; -			if (!$dh) -			{ -				return $fileinfo; -			} +			$finder = $phpbb_extension_manager->get_finder(); + +			$modules = $finder +				->extension_suffix('_module') +				->extension_directory("/$module_class") +				->core_path("includes/$module_class/info/") +				->core_prefix($module_class . '_') +				->get_classes(); -			while (($file = readdir($dh)) !== false) +			foreach ($modules as $module)  			{ -				// Is module? -				if (preg_match('/^' . $module_class . '_.+\.' . $phpEx . '$/', $file)) -				{ -					$class = str_replace(".$phpEx", '', $file) . '_info'; +				$info_class = preg_replace('/_module$/', '_info', $module); -					if (!class_exists($class)) +				// If the class does not exist it might be following the old +				// format. phpbb_acp_info_acp_foo needs to be turned into +				// acp_foo_info and the respective file has to be included +				// manually because it does not support auto loading +				if (!class_exists($info_class)) +				{ +					$info_class = str_replace("phpbb_{$module_class}_info_", '', $module) . '_info'; +					if (file_exists($directory . $info_class . '.' . $phpEx))  					{ -						include($directory . $file); +						include($directory . $info_class . '.' . $phpEx);  					} +				} -					// Get module title tag -					if (class_exists($class)) -					{ -						$c_class = new $class(); -						$module_info = $c_class->module(); -						$fileinfo[str_replace($module_class . '_', '', $module_info['filename'])] = $module_info; -					} +				if (class_exists($info_class)) +				{ +					$info = new $info_class(); +					$module_info = $info->module(); + +					$main_class = (isset($module_info['filename'])) ? $module_info['filename'] : $module; + +					$fileinfo[$main_class] = $module_info;  				}  			} -			closedir($dh);  			ksort($fileinfo);  		}  		else  		{ -			$filename = $module_class . '_' . basename($module); -			$class = $module_class . '_' . basename($module) . '_info'; +			$info_class = preg_replace('/_module$/', '_info', $module); -			if (!class_exists($class)) +			if (!class_exists($info_class))  			{ -				include($directory . $filename . '.' . $phpEx); +				if (file_exists($directory . $module . '.' . $phpEx)) +				{ +					include($directory . $module . '.' . $phpEx); +				} +				$info_class = $module . '_info';  			}  			// Get module title tag -			if (class_exists($class)) +			if (class_exists($info_class))  			{ -				$c_class = new $class(); -				$module_info = $c_class->module(); -				$fileinfo[str_replace($module_class . '_', '', $module_info['filename'])] = $module_info; +				$info = new $info_class(); +				$module_info = $info->module(); + +				$main_class = (isset($module_info['filename'])) ? $module_info['filename'] : $module; + +				$fileinfo[$main_class] = $module_info;  			}  		} -		 +  		return $fileinfo;  	} @@ -721,7 +744,7 @@ class acp_modules  		// Sanitise for future path use, it's escaped as appropriate for queries  		$p_class = str_replace(array('.', '/', '\\'), '', basename($this->module_class)); -		 +  		$cache->destroy('_modules_' . $p_class);  		// Additionally remove sql cache diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index 3fe562b25d..004187af84 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php index 0708ed3e28..dd071074de 100644 --- a/phpBB/includes/acp/acp_permissions.php +++ b/phpBB/includes/acp/acp_permissions.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -498,7 +497,7 @@ class acp_permissions  				$template->assign_vars(array(  					'S_FORUM_NAMES'		=> (sizeof($forum_names)) ? true : false, -					'FORUM_NAMES'		=> implode(', ', $forum_names)) +					'FORUM_NAMES'		=> implode($user->lang['COMMA_SEPARATOR'], $forum_names))  				);  			} @@ -1106,7 +1105,7 @@ class acp_permissions  		{  			if ($user_id != $user->data['user_id'])  			{ -				$auth2 = new auth(); +				$auth2 = new phpbb_auth();  				$auth2->acl($userdata);  				$auth_setting = $auth2->acl_get($permission);  			} diff --git a/phpBB/includes/acp/acp_php_info.php b/phpBB/includes/acp/acp_php_info.php index 03561f3e30..125b77529f 100644 --- a/phpBB/includes/acp/acp_php_info.php +++ b/phpBB/includes/acp/acp_php_info.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -67,6 +66,9 @@ class acp_php_info  		$output = preg_replace('#<img border="0"#i', '<img', $output);  		$output = str_replace(array('class="e"', 'class="v"', 'class="h"', '<hr />', '<font', '</font>'), array('class="row1"', 'class="row2"', '', '', '<span', '</span>'), $output); +		// Fix invalid anchor names (eg "module_Zend Optimizer") +		$output = preg_replace_callback('#<a name="([^"]+)">#', array($this, 'remove_spaces'), $output); +  		if (empty($output))  		{  			trigger_error('NO_PHPINFO_AVAILABLE', E_USER_WARNING); @@ -79,4 +81,9 @@ class acp_php_info  		$template->assign_var('PHPINFO', $output);  	} +	 +	function remove_spaces($matches) +	{ +		return '<a name="' . str_replace(' ', '_', $matches[1]) . '">'; +	}  } diff --git a/phpBB/includes/acp/acp_profile.php b/phpBB/includes/acp/acp_profile.php index ca6a5f04d2..849160f1fa 100644 --- a/phpBB/includes/acp/acp_profile.php +++ b/phpBB/includes/acp/acp_profile.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -243,6 +242,15 @@ class acp_profile  				$db->sql_freeresult($result);  				add_log('admin', 'LOG_PROFILE_FIELD_ACTIVATE', $field_ident); + +				if ($request->is_ajax()) +				{ +					$json_response = new phpbb_json_response(); +					$json_response->send(array( +						'text'	=> $user->lang('DEACTIVATE'), +					)); +				} +  				trigger_error($user->lang['PROFILE_FIELD_ACTIVATED'] . adm_back_link($this->u_action));  			break; @@ -267,7 +275,16 @@ class acp_profile  				$field_ident = (string) $db->sql_fetchfield('field_ident');  				$db->sql_freeresult($result); +				if ($request->is_ajax()) +				{ +					$json_response = new phpbb_json_response(); +					$json_response->send(array( +						'text'	=> $user->lang('ACTIVATE'), +					)); +				} +  				add_log('admin', 'LOG_PROFILE_FIELD_DEACTIVATE', $field_ident); +  				trigger_error($user->lang['PROFILE_FIELD_DEACTIVATED'] . adm_back_link($this->u_action));  			break; @@ -366,10 +383,12 @@ class acp_profile  					$field_row = array_merge($default_values[$field_type], array(  						'field_ident'		=> str_replace(' ', '_', utf8_clean_string(request_var('field_ident', '', true))),  						'field_required'	=> 0, +						'field_show_novalue'=> 0,  						'field_hide'		=> 0,  						'field_show_profile'=> 0,  						'field_no_view'		=> 0,  						'field_show_on_reg'	=> 0, +						'field_show_on_pm'	=> 0,  						'field_show_on_vt'	=> 0,  						'lang_name'			=> utf8_normalize_nfc(request_var('field_ident', '', true)),  						'lang_explain'		=> '', @@ -381,7 +400,7 @@ class acp_profile  				// $exclude contains the data we gather in each step  				$exclude = array( -					1	=> array('field_ident', 'lang_name', 'lang_explain', 'field_option_none', 'field_show_on_reg', 'field_show_on_vt', 'field_required', 'field_hide', 'field_show_profile', 'field_no_view'), +					1	=> array('field_ident', 'lang_name', 'lang_explain', 'field_option_none', 'field_show_on_reg', 'field_show_on_pm', 'field_show_on_vt', 'field_required', 'field_show_novalue', 'field_hide', 'field_show_profile', 'field_no_view'),  					2	=> array('field_length', 'field_maxlen', 'field_minlen', 'field_validation', 'field_novalue', 'field_default_value'),  					3	=> array('l_lang_name', 'l_lang_explain', 'l_lang_default_value', 'l_lang_options')  				); @@ -406,7 +425,9 @@ class acp_profile  				// Visibility Options...  				$visibility_ary = array(  					'field_required', +					'field_show_novalue',  					'field_show_on_reg', +					'field_show_on_pm',  					'field_show_on_vt',  					'field_show_profile',  					'field_hide', @@ -507,11 +528,34 @@ class acp_profile  							}  						}  					} -					/* else if ($field_type == FIELD_BOOL && $key == 'field_default_value') +					else if ($field_type == FIELD_BOOL && $key == 'field_default_value')  					{ -						// Get the number of options if this key is 'field_maxlen' -						$var = request_var('field_default_value', 0); -					}*/ +						// 'field_length' == 1 defines radio buttons. Possible values are 1 or 2 only. +						// 'field_length' == 2 defines checkbox. Possible values are 0 or 1 only. +						// If we switch the type on step 2, we have to adjust field value. +						// 1 is a common value for the checkbox and radio buttons. + +						// Adjust unchecked checkbox value. +						// If we return or save settings from 2nd/3rd page +						// and the checkbox is unchecked, set the value to 0. +						if (isset($_REQUEST['step']) && !isset($_REQUEST[$key])) +						{ +							$var = 0; +						} + +						// If we switch to the checkbox type but former radio buttons value was 2, +						// which is not the case for the checkbox, set it to 0 (unchecked). +						if ($cp->vars['field_length'] == 2 && $var == 2) +						{ +							$var = 0; +						} +						// If we switch to the radio buttons but the former checkbox value was 0, +						// which is not the case for the radio buttons, set it to 0. +						else if ($cp->vars['field_length'] == 1 && $var == 0) +						{ +							$var = 2; +						} +					}  					else if ($field_type == FIELD_INT && $key == 'field_default_value')  					{  						// Permit an empty string @@ -679,6 +723,10 @@ class acp_profile  						{  							$_new_key_ary[$key] = utf8_normalize_nfc(request_var($key, array(array('')), true));  						} +						else if ($field_type == FIELD_BOOL && $key == 'field_default_value') +						{ +							$_new_key_ary[$key] =  request_var($key, $cp->vars[$key]); +						}  						else  						{  							if (!isset($_REQUEST[$key])) @@ -733,7 +781,9 @@ class acp_profile  						$template->assign_vars(array(  							'S_STEP_ONE'		=> true,  							'S_FIELD_REQUIRED'	=> ($cp->vars['field_required']) ? true : false, +							'S_FIELD_SHOW_NOVALUE'=> ($cp->vars['field_show_novalue']) ? true : false,  							'S_SHOW_ON_REG'		=> ($cp->vars['field_show_on_reg']) ? true : false, +							'S_SHOW_ON_PM'		=> ($cp->vars['field_show_on_pm']) ? true : false,  							'S_SHOW_ON_VT'		=> ($cp->vars['field_show_on_vt']) ? true : false,  							'S_FIELD_HIDE'		=> ($cp->vars['field_hide']) ? true : false,  							'S_SHOW_PROFILE'	=> ($cp->vars['field_show_profile']) ? true : false, @@ -1049,7 +1099,9 @@ class acp_profile  			'field_default_value'	=> $cp->vars['field_default_value'],  			'field_validation'		=> $cp->vars['field_validation'],  			'field_required'		=> $cp->vars['field_required'], +			'field_show_novalue'	=> $cp->vars['field_show_novalue'],  			'field_show_on_reg'		=> $cp->vars['field_show_on_reg'], +			'field_show_on_pm'		=> $cp->vars['field_show_on_pm'],  			'field_show_on_vt'		=> $cp->vars['field_show_on_vt'],  			'field_hide'			=> $cp->vars['field_hide'],  			'field_show_profile'	=> $cp->vars['field_show_profile'], diff --git a/phpBB/includes/acp/acp_prune.php b/phpBB/includes/acp/acp_prune.php index bb6e1a9a2a..a5dc02849a 100644 --- a/phpBB/includes/acp/acp_prune.php +++ b/phpBB/includes/acp/acp_prune.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/acp_ranks.php b/phpBB/includes/acp/acp_ranks.php index 5c05afbfad..d9ed5b17f1 100644 --- a/phpBB/includes/acp/acp_ranks.php +++ b/phpBB/includes/acp/acp_ranks.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -25,7 +24,7 @@ class acp_ranks  	function main($id, $mode)  	{ -		global $db, $user, $auth, $template, $cache; +		global $db, $user, $auth, $template, $cache, $request;  		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx;  		$user->add_lang('acp/posting'); @@ -52,7 +51,7 @@ class acp_ranks  				}  				$rank_title = utf8_normalize_nfc(request_var('title', '', true));  				$special_rank = request_var('special_rank', 0); -				$min_posts = ($special_rank) ? 0 : request_var('min_posts', 0); +				$min_posts = ($special_rank) ? 0 : max(0, request_var('min_posts', 0));  				$rank_image = request_var('rank_image', '');  				// The rank image has to be a jpg, gif or png @@ -123,6 +122,18 @@ class acp_ranks  					$cache->destroy('_ranks');  					add_log('admin', 'LOG_RANK_REMOVED', $rank_title); +					 +					if ($request->is_ajax()) +					{ +						$json_response = new phpbb_json_response; +						$json_response->send(array( +							'MESSAGE_TITLE'	=> $user->lang['INFORMATION'], +							'MESSAGE_TEXT'	=> $user->lang['RANK_REMOVED'], +							'REFRESH_DATA'	=> array( +								'time'	=> 3 +							) +						)); +					}  				}  				else  				{ @@ -199,7 +210,7 @@ class acp_ranks  					'RANK_TITLE'		=> (isset($ranks['rank_title'])) ? $ranks['rank_title'] : '',  					'S_FILENAME_LIST'	=> $filename_list,  					'RANK_IMAGE'		=> ($edit_img) ? $phpbb_root_path . $config['ranks_path'] . '/' . $edit_img : $phpbb_admin_path . 'images/spacer.gif', -					'S_SPECIAL_RANK'	=> (!isset($ranks['rank_special']) || $ranks['rank_special']) ? true : false, +					'S_SPECIAL_RANK'	=> (isset($ranks['rank_special']) && $ranks['rank_special']) ? true : false,  					'MIN_POSTS'			=> (isset($ranks['rank_min']) && !$ranks['rank_special']) ? $ranks['rank_min'] : 0)  				); diff --git a/phpBB/includes/acp/acp_reasons.php b/phpBB/includes/acp/acp_reasons.php index 799fcf4c3f..71e9108c2c 100644 --- a/phpBB/includes/acp/acp_reasons.php +++ b/phpBB/includes/acp/acp_reasons.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -114,7 +113,7 @@ class acp_reasons  							$result = $db->sql_query($sql);  							$max_order = (int) $db->sql_fetchfield('max_reason_order');  							$db->sql_freeresult($result); -							 +  							$sql_ary = array(  								'reason_title'			=> (string) $reason_row['reason_title'],  								'reason_description'	=> (string) $reason_row['reason_description'], @@ -172,14 +171,14 @@ class acp_reasons  					'U_ACTION'		=> $this->u_action . "&id=$reason_id&action=$action",  					'U_BACK'		=> $this->u_action,  					'ERROR_MSG'		=> (sizeof($error)) ? implode('<br />', $error) : '', -					 +  					'REASON_TITLE'			=> $reason_row['reason_title'],  					'REASON_DESCRIPTION'	=> $reason_row['reason_description'],  					'TRANSLATED_TITLE'		=> ($translated) ? $user->lang['report_reasons']['TITLE'][strtoupper($reason_row['reason_title'])] : '',  					'TRANSLATED_DESCRIPTION'=> ($translated) ? $user->lang['report_reasons']['DESCRIPTION'][strtoupper($reason_row['reason_title'])] : '', -					'S_AVAILABLE_TITLES'	=> implode(', ', array_map('htmlspecialchars', array_keys($user->lang['report_reasons']['TITLE']))), +					'S_AVAILABLE_TITLES'	=> implode($user->lang['COMMA_SEPARATOR'], array_map('htmlspecialchars', array_keys($user->lang['report_reasons']['TITLE']))),  					'S_EDIT_REASON'			=> true,  					'S_TRANSLATED'			=> $translated,  					'S_ERROR'				=> (sizeof($error)) ? true : false, @@ -304,7 +303,7 @@ class acp_reasons  			do  			{  				++$order; -				 +  				if ($row['reason_order'] != $order)  				{  					$sql = 'UPDATE ' . REPORTS_REASONS_TABLE . " diff --git a/phpBB/includes/acp/acp_search.php b/phpBB/includes/acp/acp_search.php index a3061ae2da..6618e2c3f9 100644 --- a/phpBB/includes/acp/acp_search.php +++ b/phpBB/includes/acp/acp_search.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -77,7 +76,8 @@ class acp_search  				continue;  			} -			$name = ucfirst(strtolower(str_replace('_', ' ', $type))); +			$name = $search->get_name(); +  			$selected = ($config['search_type'] == $type) ? ' selected="selected"' : '';  			$search_options .= '<option value="' . $type . '"' . $selected . '>' . $name . '</option>'; @@ -275,7 +275,7 @@ class acp_search  			{  				trigger_error($error . adm_back_link($this->u_action), E_USER_WARNING);  			} -			$name = ucfirst(strtolower(str_replace('_', ' ', $this->state[0]))); +			$name = $this->search->get_name();  			$action = &$this->state[1]; @@ -337,7 +337,7 @@ class acp_search  							$totaltime = $mtime[0] + $mtime[1] - $starttime;  							$rows_per_second = $row_count / $totaltime;  							meta_refresh(1, append_sid($this->u_action . '&action=delete&skip_rows=' . $post_counter)); -							trigger_error(sprintf($user->lang['SEARCH_INDEX_DELETE_REDIRECT'], $post_counter, $row_count, $rows_per_second)); +							trigger_error($user->lang('SEARCH_INDEX_DELETE_REDIRECT', (int) $row_count, $post_counter, $rows_per_second));  						}  					} @@ -427,7 +427,7 @@ class acp_search  							$totaltime = $mtime[0] + $mtime[1] - $starttime;  							$rows_per_second = $row_count / $totaltime;  							meta_refresh(1, append_sid($this->u_action . '&action=create&skip_rows=' . $post_counter)); -							trigger_error(sprintf($user->lang['SEARCH_INDEX_CREATE_REDIRECT'], $post_counter, $row_count, $rows_per_second)); +							trigger_error($user->lang('SEARCH_INDEX_CREATE_REDIRECT', (int) $row_count, $post_counter) . $user->lang('SEARCH_INDEX_CREATE_REDIRECT_RATE', $rows_per_second));  						}  					} @@ -454,7 +454,7 @@ class acp_search  				continue;  			} -			$name = ucfirst(strtolower(str_replace('_', ' ', $type))); +			$name = $search->get_name();  			$data = array();  			if (method_exists($search, 'index_stats')) @@ -553,27 +553,15 @@ class acp_search  	function get_search_types()  	{ -		global $phpbb_root_path, $phpEx; - -		$search_types = array(); - -		$dp = @opendir($phpbb_root_path . 'includes/search'); - -		if ($dp) -		{ -			while (($file = readdir($dp)) !== false) -			{ -				if ((preg_match('#\.' . $phpEx . '$#', $file)) && ($file != "search.$phpEx")) -				{ -					$search_types[] = preg_replace('#^(.*?)\.' . $phpEx . '$#', '\1', $file); -				} -			} -			closedir($dp); +		global $phpbb_root_path, $phpEx, $phpbb_extension_manager; -			sort($search_types); -		} +		$finder = $phpbb_extension_manager->get_finder(); -		return $search_types; +		return $finder +			->extension_suffix('_backend') +			->extension_directory('/search') +			->core_path('includes/search/') +			->get_classes();  	}  	function get_max_post_id() @@ -608,24 +596,16 @@ class acp_search  	*/  	function init_search($type, &$search, &$error)  	{ -		global $phpbb_root_path, $phpEx, $user; - -		if (!preg_match('#^\w+$#', $type) || !file_exists("{$phpbb_root_path}includes/search/$type.$phpEx")) -		{ -			$error = $user->lang['NO_SUCH_SEARCH_MODULE']; -			return $error; -		} - -		include_once("{$phpbb_root_path}includes/search/$type.$phpEx"); +		global $phpbb_root_path, $phpEx, $user, $auth, $config, $db; -		if (!class_exists($type)) +		if (!class_exists($type) || !method_exists($type, 'keyword_search'))  		{  			$error = $user->lang['NO_SUCH_SEARCH_MODULE'];  			return $error;  		}  		$error = false; -		$search = new $type($error); +		$search = new $type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user);  		return $error;  	} diff --git a/phpBB/includes/acp/acp_send_statistics.php b/phpBB/includes/acp/acp_send_statistics.php index aef67a8b1a..4421fbef18 100644 --- a/phpBB/includes/acp/acp_send_statistics.php +++ b/phpBB/includes/acp/acp_send_statistics.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -16,8 +15,6 @@ if (!defined('IN_PHPBB'))  	exit;  } -include($phpbb_root_path . 'includes/questionnaire/questionnaire.' . $phpEx); -  /**  * @package acp  */ @@ -27,7 +24,9 @@ class acp_send_statistics  	function main($id, $mode)  	{ -		global $config, $template, $phpbb_admin_path, $phpEx; +		global $config, $template, $phpbb_admin_path, $phpbb_root_path, $phpEx; + +		include($phpbb_root_path . 'includes/questionnaire/questionnaire.' . $phpEx);  		$collect_url = "http://www.phpbb.com/stats/receive_stats.php"; diff --git a/phpBB/includes/acp/acp_styles.php b/phpBB/includes/acp/acp_styles.php index e32a4d2310..db77825ae7 100644 --- a/phpBB/includes/acp/acp_styles.php +++ b/phpBB/includes/acp/acp_styles.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -21,3928 +20,1285 @@ if (!defined('IN_PHPBB'))  */  class acp_styles  { -	var $u_action; - -	var $style_cfg; -	var $template_cfg; -	var $theme_cfg; -	var $imageset_cfg; -	var $imageset_keys; - -	function main($id, $mode) +	public $u_action; + +	protected $u_base_action; +	protected $s_hidden_fields; +	protected $mode; +	protected $styles_path; +	protected $styles_path_absolute = 'styles'; +	protected $default_style = 0; + +	protected $db; +	protected $user; +	protected $template; +	protected $request; +	protected $cache; +	protected $auth; +	protected $phpbb_root_path; +	protected $php_ext; + +	public function main($id, $mode)  	{ -		global $db, $user, $auth, $template, $cache; -		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; - -		// Hardcoded template bitfield to add for new templates -		$bitfield = new bitfield(); -		$bitfield->set(0); -		$bitfield->set(1); -		$bitfield->set(2); -		$bitfield->set(3); -		$bitfield->set(4); -		$bitfield->set(8); -		$bitfield->set(9); -		$bitfield->set(11); -		$bitfield->set(12); -		$this->template_bitfield = $bitfield->get_base64(); -		unset($bitfield); +		global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config; + +		$this->db = $db; +		$this->user = $user; +		$this->template = $template; +		$this->request = $request; +		$this->cache = $cache; +		$this->auth = $auth; +		$this->config = $config; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $phpEx; + +		$this->default_style = $config['default_style']; +		$this->styles_path = $this->phpbb_root_path . $this->styles_path_absolute . '/'; + +		$this->u_base_action = append_sid("{$phpbb_admin_path}index.{$this->php_ext}", "i={$id}"); +		$this->s_hidden_fields = array( +			'mode'		=> $mode, +		); -		$user->add_lang('acp/styles'); +		$this->user->add_lang('acp/styles');  		$this->tpl_name = 'acp_styles';  		$this->page_title = 'ACP_CAT_STYLES'; +		$this->mode = $mode; + +		$action = $this->request->variable('action', ''); +		$post_actions = array('install', 'activate', 'deactivate', 'uninstall'); +		foreach ($post_actions as $key) +		{ +			if (isset($_POST[$key])) +			{ +				$action = $key; +			} +		} +		if ($action != '') +		{ +			$this->s_hidden_fields['action'] = $action; +		} -		$action = request_var('action', ''); -		$action = (isset($_POST['add'])) ? 'add' : $action; -		$style_id = request_var('id', 0); - -		// Fill the configuration variables -		$this->style_cfg = $this->template_cfg = $this->theme_cfg = $this->imageset_cfg = ' -# -# phpBB {MODE} configuration file -# -# @package phpBB3 -# @copyright (c) 2005 phpBB Group -# @license http://opensource.org/licenses/gpl-license.php GNU Public License -# -# -# At the left is the name, please do not change this -# At the right the value is entered -# For on/off options the valid values are on, off, 1, 0, true and false -# -# Values get trimmed, if you want to add a space in front or at the end of -# the value, then enclose the value with single or double quotes. -# Single and double quotes do not need to be escaped. -# -# - -# General Information about this {MODE} -name = {NAME} -copyright = {COPYRIGHT} -version = {VERSION} -'; - -		$this->theme_cfg .= ' -# Some configuration options - -# -# You have to turn this option on if you want to use the -# path template variables ({T_IMAGESET_PATH} for example) within -# your css file. -# This is mostly the case if you want to use language specific -# images within your css file. -# -parse_css_file = {PARSE_CSS_FILE} -'; - -		$this->template_cfg .= ' -# Some configuration options - -# -# You can use this function to inherit templates from another template. -# The template of the given name has to be installed. -# Templates cannot inherit from inheriting templates. -#'; - -		$this->imageset_keys = array( -			'logos' => array( -				'site_logo', -			), -			'buttons'	=> array( -				'icon_back_top', 'icon_contact_aim', 'icon_contact_email', 'icon_contact_icq', 'icon_contact_jabber', 'icon_contact_msnm', 'icon_contact_pm', 'icon_contact_yahoo', 'icon_contact_www', 'icon_post_delete', 'icon_post_edit', 'icon_post_info', 'icon_post_quote', 'icon_post_report', 'icon_user_online', 'icon_user_offline', 'icon_user_profile', 'icon_user_search', 'icon_user_warn', 'button_pm_forward', 'button_pm_new', 'button_pm_reply', 'button_topic_locked', 'button_topic_new', 'button_topic_reply', -			), -			'icons'		=> array( -				'icon_post_target', 'icon_post_target_unread', 'icon_topic_attach', 'icon_topic_latest', 'icon_topic_newest', 'icon_topic_reported', 'icon_topic_unapproved', 'icon_friend', 'icon_foe', -			), -			'forums'	=> array( -				'forum_link', 'forum_read', 'forum_read_locked', 'forum_read_subforum', 'forum_unread', 'forum_unread_locked', 'forum_unread_subforum', 'subforum_read', 'subforum_unread' -			), -			'folders'	=> array( -				'topic_moved', 'topic_read', 'topic_read_mine', 'topic_read_hot', 'topic_read_hot_mine', 'topic_read_locked', 'topic_read_locked_mine', 'topic_unread', 'topic_unread_mine', 'topic_unread_hot', 'topic_unread_hot_mine', 'topic_unread_locked', 'topic_unread_locked_mine', 'sticky_read', 'sticky_read_mine', 'sticky_read_locked', 'sticky_read_locked_mine', 'sticky_unread', 'sticky_unread_mine', 'sticky_unread_locked', 'sticky_unread_locked_mine', 'announce_read', 'announce_read_mine', 'announce_read_locked', 'announce_read_locked_mine', 'announce_unread', 'announce_unread_mine', 'announce_unread_locked', 'announce_unread_locked_mine', 'global_read', 'global_read_mine', 'global_read_locked', 'global_read_locked_mine', 'global_unread', 'global_unread_mine', 'global_unread_locked', 'global_unread_locked_mine', 'pm_read', 'pm_unread', -			), -			'polls'		=> array( -				'poll_left', 'poll_center', 'poll_right', -			), -			'ui'		=> array( -				'upload_bar', -			), -			'user'		=> array( -				'user_icon1', 'user_icon2', 'user_icon3', 'user_icon4', 'user_icon5', 'user_icon6', 'user_icon7', 'user_icon8', 'user_icon9', 'user_icon10', -			), +		$this->template->assign_vars(array( +			'U_ACTION'			=> $this->u_base_action, +			'S_HIDDEN_FIELDS'	=> build_hidden_fields($this->s_hidden_fields) +			)  		); -		// Execute overall actions +		// Execute actions  		switch ($action)  		{ -			case 'delete': -				if ($style_id) -				{ -					$this->remove($mode, $style_id); -					return; -				} -			break; - -			case 'export': -				if ($style_id) -				{ -					$this->export($mode, $style_id); -					return; -				} -			break; -  			case 'install': -				$this->install($mode); +				$this->action_install();  				return; -			break; - -			case 'add': -				$this->add($mode); +			case 'uninstall': +				$this->action_uninstall(); +				return; +			case 'activate': +				$this->action_activate(); +				return; +			case 'deactivate': +				$this->action_deactivate();  				return; -			break; -  			case 'details': -				if ($style_id) -				{ -					$this->details($mode, $style_id); -					return; -				} -			break; - -			case 'edit': -				if ($style_id) -				{ -					switch ($mode) -					{ -						case 'imageset': -							return $this->edit_imageset($style_id); -						case 'template': -							return $this->edit_template($style_id); -						case 'theme': -							return $this->edit_theme($style_id); -					} -				} -			break; - -			case 'cache': -				if ($style_id) -				{ -					switch ($mode) -					{ -						case 'template': -							return $this->template_cache($style_id); -					} -				} -			break; -		} - -		switch ($mode) -		{ -			case 'style': - -				switch ($action) -				{ -					case 'activate': -					case 'deactivate': - -						if ($style_id == $config['default_style']) -						{ -							trigger_error($user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING); -						} - -						if (($action == 'deactivate' && confirm_box(true)) || $action == 'activate') -						{ -							$sql = 'UPDATE ' . STYLES_TABLE . ' -								SET style_active = ' . (($action == 'activate') ? 1 : 0) . ' -								WHERE style_id = ' . $style_id; -							$db->sql_query($sql); - -							// Set style to default for any member using deactivated style -							if ($action == 'deactivate') -							{ -								$sql = 'UPDATE ' . USERS_TABLE . ' -									SET user_style = ' . $config['default_style'] . " -									WHERE user_style = $style_id"; -								$db->sql_query($sql); - -								$sql = 'UPDATE ' . FORUMS_TABLE . ' -									SET forum_style = 0 -									WHERE forum_style = ' . $style_id; -								$db->sql_query($sql); -							} -						} -						else if ($action == 'deactivate') -						{ -							$s_hidden_fields = array( -								'i'			=> $id, -								'mode'		=> $mode, -								'action'	=> $action, -								'style_id'	=> $style_id, -							); -							confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields($s_hidden_fields)); -						} -					break; -				} - -				$this->frontend('style', array('details'), array('export', 'delete')); -			break; - -			case 'template': - -				switch ($action) -				{ -					// Refresh template data stored in db and clear cache -					case 'refresh': - -						$sql = 'SELECT * -							FROM ' . STYLES_TEMPLATE_TABLE . " -							WHERE template_id = $style_id"; -						$result = $db->sql_query($sql); -						$template_row = $db->sql_fetchrow($result); -						$db->sql_freeresult($result); - -						if (!$template_row) -						{ -							trigger_error($user->lang['NO_TEMPLATE'] . adm_back_link($this->u_action), E_USER_WARNING); -						} - -						if (confirm_box(true)) -						{ -							$template_refreshed = ''; - -							// Only refresh database if the template is stored in the database -							if ($template_row['template_storedb'] && file_exists("{$phpbb_root_path}styles/{$template_row['template_path']}/template/")) -							{ -								$filelist = array('' => array()); - -								$sql = 'SELECT template_filename, template_mtime -									FROM ' . STYLES_TEMPLATE_DATA_TABLE . " -									WHERE template_id = $style_id"; -								$result = $db->sql_query($sql); - -								while ($row = $db->sql_fetchrow($result)) -								{ -//									if (@filemtime("{$phpbb_root_path}styles/{$template_row['template_path']}/template/" . $row['template_filename']) > $row['template_mtime']) -//									{ -										// get folder info from the filename -										if (($slash_pos = strrpos($row['template_filename'], '/')) === false) -										{ -											$filelist[''][] = $row['template_filename']; -										} -										else -										{ -											$filelist[substr($row['template_filename'], 0, $slash_pos + 1)][] = substr($row['template_filename'], $slash_pos + 1, strlen($row['template_filename']) - $slash_pos - 1); -										} -//									} -								} -								$db->sql_freeresult($result); - -								$this->store_templates('update', $style_id, $template_row['template_path'], $filelist); -								unset($filelist); - -								$template_refreshed = $user->lang['TEMPLATE_REFRESHED'] . '<br />'; -								add_log('admin', 'LOG_TEMPLATE_REFRESHED', $template_row['template_name']); -							} - -							$this->clear_template_cache($template_row); - -							trigger_error($template_refreshed . $user->lang['TEMPLATE_CACHE_CLEARED'] . adm_back_link($this->u_action)); -						} -						else -						{ -							confirm_box(false, ($template_row['template_storedb']) ? $user->lang['CONFIRM_TEMPLATE_REFRESH'] : $user->lang['CONFIRM_TEMPLATE_CLEAR_CACHE'], build_hidden_fields(array( -								'i'			=> $id, -								'mode'		=> $mode, -								'action'	=> $action, -								'id'		=> $style_id -							))); -						} - -					break; -				} - -				$this->frontend('template', array('edit', 'cache', 'details'), array('refresh', 'export', 'delete')); -			break; - -			case 'theme': - -				switch ($action) -				{ -					// Refresh theme data stored in the database -					case 'refresh': - -						$sql = 'SELECT * -							FROM ' . STYLES_THEME_TABLE . " -							WHERE theme_id = $style_id"; -						$result = $db->sql_query($sql); -						$theme_row = $db->sql_fetchrow($result); -						$db->sql_freeresult($result); - -						if (!$theme_row) -						{ -							trigger_error($user->lang['NO_THEME'] . adm_back_link($this->u_action), E_USER_WARNING); -						} - -						if (!$theme_row['theme_storedb']) -						{ -							trigger_error($user->lang['THEME_ERR_REFRESH_FS'] . adm_back_link($this->u_action), E_USER_WARNING); -						} - -						if (confirm_box(true)) -						{ -							if ($theme_row['theme_storedb'] && file_exists("{$phpbb_root_path}styles/{$theme_row['theme_path']}/theme/stylesheet.css")) -							{ -								// Save CSS contents -								$sql_ary = array( -									'theme_mtime'	=> (int) filemtime("{$phpbb_root_path}styles/{$theme_row['theme_path']}/theme/stylesheet.css"), -									'theme_data'	=> $this->db_theme_data($theme_row) -								); - -								$sql = 'UPDATE ' . STYLES_THEME_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " -									WHERE theme_id = $style_id"; -								$db->sql_query($sql); - -								$cache->destroy('sql', STYLES_THEME_TABLE); - -								add_log('admin', 'LOG_THEME_REFRESHED', $theme_row['theme_name']); -								trigger_error($user->lang['THEME_REFRESHED'] . adm_back_link($this->u_action)); -							} -						} -						else -						{ -							confirm_box(false, $user->lang['CONFIRM_THEME_REFRESH'], build_hidden_fields(array( -								'i'			=> $id, -								'mode'		=> $mode, -								'action'	=> $action, -								'id'		=> $style_id -							))); -						} -					break; -				} - -				$this->frontend('theme', array('edit', 'details'), array('refresh', 'export', 'delete')); -			break; - -			case 'imageset': - -				switch ($action) -				{ -					case 'refresh': - -						$sql = 'SELECT * -							FROM ' . STYLES_IMAGESET_TABLE . " -							WHERE imageset_id = $style_id"; -						$result = $db->sql_query($sql); -						$imageset_row = $db->sql_fetchrow($result); -						$db->sql_freeresult($result); - -						if (!$imageset_row) -						{ -							trigger_error($user->lang['NO_IMAGESET'] . adm_back_link($this->u_action), E_USER_WARNING); -						} - -						if (confirm_box(true)) -						{ -							$sql_ary = array(); - -							$imageset_definitions = array(); -							foreach ($this->imageset_keys as $topic => $key_array) -							{ -								$imageset_definitions = array_merge($imageset_definitions, $key_array); -							} - -							$cfg_data_imageset = parse_cfg_file("{$phpbb_root_path}styles/{$imageset_row['imageset_path']}/imageset/imageset.cfg"); - -							$db->sql_transaction('begin'); - -							$sql = 'DELETE FROM ' . STYLES_IMAGESET_DATA_TABLE . ' -								WHERE imageset_id = ' . $style_id; -							$result = $db->sql_query($sql); - -							foreach ($cfg_data_imageset as $image_name => $value) -							{ -								if (strpos($value, '*') !== false) -								{ -									if (substr($value, -1, 1) === '*') -									{ -										list($image_filename, $image_height) = explode('*', $value); -										$image_width = 0; -									} -									else -									{ -										list($image_filename, $image_height, $image_width) = explode('*', $value); -									} -								} -								else -								{ -									$image_filename = $value; -									$image_height = $image_width = 0; -								} - -								if (strpos($image_name, 'img_') === 0 && $image_filename) -								{ -									$image_name = substr($image_name, 4); -									if (in_array($image_name, $imageset_definitions)) -									{ -										$sql_ary[] = array( -											'image_name'		=> (string) $image_name, -											'image_filename'	=> (string) $image_filename, -											'image_height'		=> (int) $image_height, -											'image_width'		=> (int) $image_width, -											'imageset_id'		=> (int) $style_id, -											'image_lang'		=> '', -										); -									} -								} -							} - -							$sql = 'SELECT lang_dir -								FROM ' . LANG_TABLE; -							$result = $db->sql_query($sql); - -							while ($row = $db->sql_fetchrow($result)) -							{ -								if (@file_exists("{$phpbb_root_path}styles/{$imageset_row['imageset_path']}/imageset/{$row['lang_dir']}/imageset.cfg")) -								{ -									$cfg_data_imageset_data = parse_cfg_file("{$phpbb_root_path}styles/{$imageset_row['imageset_path']}/imageset/{$row['lang_dir']}/imageset.cfg"); -									foreach ($cfg_data_imageset_data as $image_name => $value) -									{ -										if (strpos($value, '*') !== false) -										{ -											if (substr($value, -1, 1) === '*') -											{ -												list($image_filename, $image_height) = explode('*', $value); -												$image_width = 0; -											} -											else -											{ -												list($image_filename, $image_height, $image_width) = explode('*', $value); -											} -										} -										else -										{ -											$image_filename = $value; -											$image_height = $image_width = 0; -										} - -										if (strpos($image_name, 'img_') === 0 && $image_filename) -										{ -											$image_name = substr($image_name, 4); -											if (in_array($image_name, $imageset_definitions)) -											{ -												$sql_ary[] = array( -													'image_name'		=> (string) $image_name, -													'image_filename'	=> (string) $image_filename, -													'image_height'		=> (int) $image_height, -													'image_width'		=> (int) $image_width, -													'imageset_id'		=> (int) $style_id, -													'image_lang'		=> (string) $row['lang_dir'], -												); -											} -										} -									} -								} -							} -							$db->sql_freeresult($result); - -							$db->sql_multi_insert(STYLES_IMAGESET_DATA_TABLE, $sql_ary); - -							$db->sql_transaction('commit'); - -							$cache->destroy('sql', STYLES_IMAGESET_DATA_TABLE); - -							add_log('admin', 'LOG_IMAGESET_REFRESHED', $imageset_row['imageset_name']); -							trigger_error($user->lang['IMAGESET_REFRESHED'] . adm_back_link($this->u_action)); -						} -						else -						{ -							confirm_box(false, $user->lang['CONFIRM_IMAGESET_REFRESH'], build_hidden_fields(array( -								'i'			=> $id, -								'mode'		=> $mode, -								'action'	=> $action, -								'id'		=> $style_id -							))); -						} -					break; -				} - -				$this->frontend('imageset', array('edit', 'details'), array('refresh', 'export', 'delete')); -			break; +				$this->action_details(); +				return; +			default: +				$this->frontend();  		}  	}  	/** -	* Build Frontend with supplied options +	* Main page  	*/ -	function frontend($mode, $options, $actions) +	protected function frontend()  	{ -		global $user, $template, $db, $config, $phpbb_root_path, $phpEx; - -		$sql_from = ''; -		$style_count = array(); - -		switch ($mode) +		// Check mode +		switch ($this->mode)  		{  			case 'style': -				$sql_from = STYLES_TABLE; - -				$sql = 'SELECT user_style, COUNT(user_style) AS style_count -					FROM ' . USERS_TABLE . ' -					GROUP BY user_style'; -				$result = $db->sql_query($sql); - -				while ($row = $db->sql_fetchrow($result)) -				{ -					$style_count[$row['user_style']] = $row['style_count']; -				} -				$db->sql_freeresult($result); - -			break; - -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -			break; - -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; - -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -			break; -		} - -		$l_prefix = strtoupper($mode); - -		$this->page_title = 'ACP_' . $l_prefix . 'S'; - -		$template->assign_vars(array( -			'S_FRONTEND'		=> true, -			'S_STYLE'			=> ($mode == 'style') ? true : false, - -			'L_TITLE'			=> $user->lang[$this->page_title], -			'L_EXPLAIN'			=> $user->lang[$this->page_title . '_EXPLAIN'], -			'L_NAME'			=> $user->lang[$l_prefix . '_NAME'], -			'L_INSTALLED'		=> $user->lang['INSTALLED_' . $l_prefix], -			'L_UNINSTALLED'		=> $user->lang['UNINSTALLED_' . $l_prefix], -			'L_NO_UNINSTALLED'	=> $user->lang['NO_UNINSTALLED_' . $l_prefix], -			'L_CREATE'			=> $user->lang['CREATE_' . $l_prefix], - -			'U_ACTION'			=> $this->u_action, -			) -		); - -		$sql = "SELECT * -			FROM $sql_from"; -		$result = $db->sql_query($sql); - -		$installed = array(); - -		$basis_options = '<option class="sep" value="">' . $user->lang['OPTIONAL_BASIS'] . '</option>'; -		while ($row = $db->sql_fetchrow($result)) -		{ -			$installed[] = $row[$mode . '_name']; -			$basis_options .= '<option value="' . $row[$mode . '_id'] . '">' . $row[$mode . '_name'] . '</option>'; - -			$stylevis = ($mode == 'style' && !$row['style_active']) ? 'activate' : 'deactivate'; - -			$s_options = array(); -			foreach ($options as $option) -			{ -				$s_options[] = '<a href="' . $this->u_action . "&action=$option&id=" . $row[$mode . '_id'] . '">' . $user->lang[strtoupper($option)] . '</a>'; -			} - -			$s_actions = array(); -			foreach ($actions as $option) -			{ -				$s_actions[] = '<a href="' . $this->u_action . "&action=$option&id=" . $row[$mode . '_id'] . '">' . $user->lang[strtoupper($option)] . '</a>'; -			} - -			$template->assign_block_vars('installed', array( -				'S_DEFAULT_STYLE'		=> ($mode == 'style' && $row['style_id'] == $config['default_style']) ? true : false, -				'U_EDIT'				=> $this->u_action . '&action=' . (($mode == 'style') ? 'details' : 'edit') . '&id=' . $row[$mode . '_id'], -				'U_STYLE_ACT_DEACT'		=> $this->u_action . '&action=' . $stylevis . '&id=' . $row[$mode . '_id'], -				'L_STYLE_ACT_DEACT'		=> $user->lang['STYLE_' . strtoupper($stylevis)], -				'S_OPTIONS'				=> implode(' | ', $s_options), -				'S_ACTIONS'				=> implode(' | ', $s_actions), -				'U_PREVIEW'				=> ($mode == 'style') ? append_sid("{$phpbb_root_path}index.$phpEx", "$mode=" . $row[$mode . '_id']) : '', - -				'NAME'					=> $row[$mode . '_name'], -				'STYLE_COUNT'			=> ($mode == 'style' && isset($style_count[$row['style_id']])) ? $style_count[$row['style_id']] : 0, -				) -			); -		} -		$db->sql_freeresult($result); - -		// Grab uninstalled items -		$new_ary = $cfg = array(); - -		$dp = @opendir("{$phpbb_root_path}styles"); - -		if ($dp) -		{ -			while (($file = readdir($dp)) !== false) -			{ -				if ($file[0] == '.' || !is_dir($phpbb_root_path . 'styles/' . $file)) -				{ -					continue; -				} - -				$subpath = ($mode != 'style') ? "$mode/" : ''; -				if (file_exists("{$phpbb_root_path}styles/$file/$subpath$mode.cfg")) -				{ -					if ($cfg = file("{$phpbb_root_path}styles/$file/$subpath$mode.cfg")) -					{ -						$items = parse_cfg_file('', $cfg); -						$name = (isset($items['name'])) ? trim($items['name']) : false; - -						if ($name && !in_array($name, $installed)) -						{ -							$new_ary[] = array( -								'path'		=> $file, -								'name'		=> $name, -								'copyright'	=> $items['copyright'], -							); -						} -					} -				} -			} -			closedir($dp); -		} - -		unset($installed); - -		if (sizeof($new_ary)) -		{ -			foreach ($new_ary as $cfg) -			{ -				$template->assign_block_vars('uninstalled', array( -					'NAME'			=> $cfg['name'], -					'COPYRIGHT'		=> $cfg['copyright'], -					'U_INSTALL'		=> $this->u_action . '&action=install&path=' . urlencode($cfg['path'])) -				); -			} +				$this->welcome_message('ACP_STYLES', 'ACP_STYLES_EXPLAIN'); +				$this->show_installed(); +				return; +			case 'install': +				$this->welcome_message('INSTALL_STYLES', 'INSTALL_STYLES_EXPLAIN'); +				$this->show_available(); +				return; +			case 'cache': +				$this->action_cache(); +				return;  		} -		unset($new_ary); - -		$template->assign_vars(array( -			'S_BASIS_OPTIONS'		=> $basis_options) -		); - +		trigger_error($this->user->lang['NO_MODE'] . adm_back_link($this->u_action), E_USER_WARNING);  	}  	/** -	* Provides a template editor which allows saving changes to template files on the filesystem or in the database. -	* -	* @param int $template_id specifies which template set is being edited +	* Purge cache  	*/ -	function edit_template($template_id) +	protected function action_cache()  	{ -		global $phpbb_root_path, $phpEx, $config, $db, $cache, $user, $template, $safe_mode; - -		if (defined('PHPBB_DISABLE_ACP_EDITOR')) -		{ -			trigger_error($user->lang['EDITOR_DISABLED'] . adm_back_link($this->u_action)); -		} +		$this->cache->purge(); -		$this->page_title = 'EDIT_TEMPLATE'; +		// Clear permissions +		$this->auth->acl_clear_prefetch(); +		cache_moderators(); -		$filelist = $filelist_cats = array(); +		add_log('admin', 'LOG_PURGE_CACHE'); -		$template_data	= utf8_normalize_nfc(request_var('template_data', '', true)); -		$template_data	= htmlspecialchars_decode($template_data); -		$template_file	= utf8_normalize_nfc(request_var('template_file', '', true)); -		$text_rows		= max(5, min(999, request_var('text_rows', 20))); -		$save_changes	= (isset($_POST['save'])) ? true : false; - -		// make sure template_file path doesn't go upwards -		$template_file = preg_replace('#\.{2,}#', '.', $template_file); - -		// Retrieve some information about the template -		$sql = 'SELECT template_storedb, template_path, template_name -			FROM ' . STYLES_TEMPLATE_TABLE . " -			WHERE template_id = $template_id"; -		$result = $db->sql_query($sql); -		$template_info = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); +		trigger_error($this->user->lang['PURGED_CACHE'] . adm_back_link($this->u_base_action), E_USER_NOTICE); +	} -		if (!$template_info) -		{ -			trigger_error($user->lang['NO_TEMPLATE'] . adm_back_link($this->u_action), E_USER_WARNING); -		} +	/** +	* Install style(s) +	*/ +	protected function action_install() +	{ +		// Get list of styles to install +		$dirs = $this->request_vars('dir', '', true); -		if ($save_changes && !check_form_key('acp_styles')) -		{ -			trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); -		} -		else if (!$save_changes) -		{ -			add_form_key('acp_styles'); -		} +		// Get list of styles that can be installed +		$styles = $this->find_available(false); -		// save changes to the template if the user submitted any -		if ($save_changes && $template_file) +		// Install each style +		$messages = array(); +		$installed_names = array(); +		$installed_dirs = array(); +		$last_installed = false; +		foreach ($dirs as $dir)  		{ -			// Get the filesystem location of the current file -			$file = "{$phpbb_root_path}styles/{$template_info['template_path']}/template/$template_file"; -			$additional = ''; - -			// If the template is stored on the filesystem try to write the file else store it in the database -			if (!$safe_mode && !$template_info['template_storedb'] && file_exists($file) && phpbb_is_writable($file)) +			$found = false; +			foreach ($styles as &$style)  			{ -				if (!($fp = @fopen($file, 'wb'))) +				// Check if: +				// 1. Directory matches directory we are looking for +				// 2. Style is not installed yet +				// 3. Style with same name or directory hasn't been installed already within this function +				if ($style['style_path'] == $dir && empty($style['_installed']) && !in_array($style['style_path'], $installed_dirs) && !in_array($style['style_name'], $installed_names))  				{ -					// File exists and is writeable, but still not able to be written to -					trigger_error(sprintf($user->lang['TEMPLATE_FILE_NOT_WRITABLE'], htmlspecialchars($template_file)) . adm_back_link($this->u_action), E_USER_WARNING); +					// Install style +					$style['style_active'] = 1; +					$style['style_id'] = $this->install_style($style); +					$style['_installed'] = true; +					$found = true; +					$last_installed = $style['style_id']; +					$installed_names[] = $style['style_name']; +					$installed_dirs[] = $style['style_path']; +					$messages[] = sprintf($this->user->lang['STYLE_INSTALLED'], htmlspecialchars($style['style_name']));  				} -				fwrite($fp, $template_data); -				fclose($fp);  			} -			else +			if (!$found)  			{ -				$db->sql_transaction('begin'); - -				// If it's not stored in the db yet, then update the template setting and store all template files in the db -				if (!$template_info['template_storedb']) -				{ -					if ($super = $this->get_super('template', $template_id)) -					{ -						$this->store_in_db('template', $super['template_id']); -					} -					else -					{ -						$this->store_in_db('template', $template_id); -					} - -					add_log('admin', 'LOG_TEMPLATE_EDIT_DETAILS', $template_info['template_name']); -					$additional .= '<br />' . $user->lang['EDIT_TEMPLATE_STORED_DB']; -				} - -				// Update the template_data table entry for this template file -				$sql = 'UPDATE ' . STYLES_TEMPLATE_DATA_TABLE . " -					SET template_data = '" . $db->sql_escape($template_data) . "', template_mtime = " . time() . " -					WHERE template_id = $template_id -						AND template_filename = '" . $db->sql_escape($template_file) . "'"; -				$db->sql_query($sql); - -				$db->sql_transaction('commit'); +				$messages[] = sprintf($this->user->lang['STYLE_NOT_INSTALLED'], htmlspecialchars($dir));  			} - -			// destroy the cached version of the template (filename without extension) -			$this->clear_template_cache($template_info, array(substr($template_file, 0, -5))); - -			$cache->destroy('sql', STYLES_TABLE); - -			add_log('admin', 'LOG_TEMPLATE_EDIT', $template_info['template_name'], $template_file); -			trigger_error($user->lang['TEMPLATE_FILE_UPDATED'] . $additional . adm_back_link($this->u_action . "&action=edit&id=$template_id&text_rows=$text_rows&template_file=$template_file"));  		} -		// Generate a category array containing template filenames -		if (!$template_info['template_storedb']) +		// Show message +		if (!count($messages))  		{ -			$template_path = "{$phpbb_root_path}styles/{$template_info['template_path']}/template"; - -			$filelist = filelist($template_path, '', 'html'); -			$filelist[''] = array_diff($filelist[''], array('bbcode.html')); - -			if ($template_file) -			{ -				if (!file_exists($template_path . "/$template_file") || !($template_data = file_get_contents($template_path . "/$template_file"))) -				{ -					trigger_error($user->lang['NO_TEMPLATE'] . adm_back_link($this->u_action), E_USER_WARNING); -				} -			} -		} -		else -		{ -			$sql = 'SELECT * -				FROM ' . STYLES_TEMPLATE_DATA_TABLE . " -				WHERE template_id = $template_id"; -			$result = $db->sql_query($sql); - -			$filelist = array('' => array()); -			while ($row = $db->sql_fetchrow($result)) -			{ -				$file_info = pathinfo($row['template_filename']); - -				if (($file_info['basename'] != 'bbcode') && ($file_info['extension'] == 'html')) -				{ -					if (($file_info['dirname'] == '.') || empty($file_info['dirname'])) -					{ -						$filelist[''][] = $row['template_filename']; -					} -					else -					{ -						$filelist[$file_info['dirname'] . '/'][] = $file_info['basename']; -					} -				} - -				if ($row['template_filename'] == $template_file) -				{ -					$template_data = $row['template_data']; -				} -			} -			$db->sql_freeresult($result); -			unset($file_info); +			trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);  		} +		$message = implode('<br />', $messages); +		$message .= '<br /><br />' . sprintf($this->user->lang['STYLE_INSTALLED_RETURN_STYLES'], $this->u_base_action . '&mode=style'); +		$message .= '<br /><br />' . sprintf($this->user->lang['STYLE_INSTALLED_RETURN_UNINSTALLED'], $this->u_base_action . '&mode=install'); +		trigger_error($message, E_USER_NOTICE); +	} -		if (empty($filelist[''])) -		{ -			trigger_error($user->lang['NO_TEMPLATE'] . adm_back_link($this->u_action), E_USER_WARNING); -		} +	/** +	* Confirm styles removal +	*/ +	protected function action_uninstall() +	{ +		// Get list of styles to uninstall +		$ids = $this->request_vars('id', 0, true); -		// Now create the categories -		$filelist_cats[''] = array(); -		foreach ($filelist as $pathfile => $file_ary) +		// Check if confirmation box was submitted +		if (confirm_box(true))  		{ -			// Use the directory name as category name -			if (!empty($pathfile)) -			{ -				$filelist_cats[$pathfile] = array(); -				foreach ($file_ary as $file) -				{ -					$filelist_cats[$pathfile][$pathfile . $file] = $file; -				} -			} -			// or if it's in the main category use the word before the first underscore to group files -			else -			{ -				$cats = array(); -				foreach ($file_ary as $file) -				{ -					$cats[] = substr($file, 0, strpos($file, '_')); -					$filelist_cats[substr($file, 0, strpos($file, '_'))][$file] = $file; -				} - -				$cats = array_values(array_unique($cats)); - -				// we don't need any single element categories so put them into the misc '' category -				for ($i = 0, $n = sizeof($cats); $i < $n; $i++) -				{ -					if (sizeof($filelist_cats[$cats[$i]]) == 1 && $cats[$i] !== '') -					{ -						$filelist_cats[''][key($filelist_cats[$cats[$i]])] = current($filelist_cats[$cats[$i]]); -						unset($filelist_cats[$cats[$i]]); -					} -				} -				unset($cats); -			} +			// Uninstall +			$this->action_uninstall_confirmed($ids, $this->request->variable('confirm_delete_files', false)); +			return;  		} -		unset($filelist); - -		// Generate list of categorised template files -		$tpl_options = ''; -		ksort($filelist_cats); -		foreach ($filelist_cats as $category => $tpl_ary) -		{ -			ksort($tpl_ary); -			if (!empty($category)) -			{ -				$tpl_options .= '<option class="sep" value="">' . $category . '</option>'; -			} - -			foreach ($tpl_ary as $filename => $file) -			{ -				$selected = ($template_file == $filename) ? ' selected="selected"' : ''; -				$tpl_options .= '<option value="' . $filename . '"' . $selected . '>' . $file . '</option>'; -			} -		} +		// Confirm box +		$s_hidden = build_hidden_fields(array( +			'action'	=> 'uninstall', +			'ids'		=> $ids +		)); +		$this->template->assign_var('S_CONFIRM_DELETE', true); +		confirm_box(false, $this->user->lang['CONFIRM_UNINSTALL_STYLES'], $s_hidden, 'acp_styles.html'); -		$template->assign_vars(array( -			'S_EDIT_TEMPLATE'	=> true, -			'S_HIDDEN_FIELDS'	=> build_hidden_fields(array('template_file' => $template_file)), -			'S_TEMPLATES'		=> $tpl_options, - -			'U_ACTION'			=> $this->u_action . "&action=edit&id=$template_id&text_rows=$text_rows", -			'U_BACK'			=> $this->u_action, - -			'L_EDIT'			=> $user->lang['EDIT_TEMPLATE'], -			'L_EDIT_EXPLAIN'	=> $user->lang['EDIT_TEMPLATE_EXPLAIN'], -			'L_EDITOR'			=> $user->lang['TEMPLATE_EDITOR'], -			'L_EDITOR_HEIGHT'	=> $user->lang['TEMPLATE_EDITOR_HEIGHT'], -			'L_FILE'			=> $user->lang['TEMPLATE_FILE'], -			'L_SELECT'			=> $user->lang['SELECT_TEMPLATE'], -			'L_SELECTED'		=> $user->lang['SELECTED_TEMPLATE'], -			'L_SELECTED_FILE'	=> $user->lang['SELECTED_TEMPLATE_FILE'], - -			'SELECTED_TEMPLATE'	=> $template_info['template_name'], -			'TEMPLATE_FILE'		=> $template_file, -			'TEMPLATE_DATA'		=> utf8_htmlspecialchars($template_data), -			'TEXT_ROWS'			=> $text_rows) -		); +		// Canceled - show styles list +		$this->frontend();  	}  	/** -	* Allows the admin to view cached versions of template files and clear single template cache files +	* Uninstall styles(s)  	* -	* @param int $template_id specifies which template's cache is shown +	* @param array $ids List of style IDs +	* @param bool $delete_files If true, script will attempt to remove files for selected styles  	*/ -	function template_cache($template_id) +	protected function action_uninstall_confirmed($ids, $delete_files)  	{ -		global $phpbb_root_path, $phpEx, $config, $db, $cache, $user, $template; +		$default = $this->default_style; +		$uninstalled = array(); +		$messages = array(); -		$source		= str_replace('/', '.', request_var('source', '')); -		$file_ary	= array_diff(request_var('delete', array('')), array('')); -		$submit		= isset($_POST['submit']) ? true : false; - -		$sql = 'SELECT * -			FROM ' . STYLES_TEMPLATE_TABLE . " -			WHERE template_id = $template_id"; -		$result = $db->sql_query($sql); -		$template_row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		if (!$template_row) +		// Check styles list +		foreach ($ids as $id)  		{ -			trigger_error($user->lang['NO_TEMPLATE'] . adm_back_link($this->u_action), E_USER_WARNING); -		} - -		// User wants to delete one or more files ... -		if ($submit && $file_ary) -		{ -			$this->clear_template_cache($template_row, $file_ary); -			trigger_error($user->lang['TEMPLATE_CACHE_CLEARED'] . adm_back_link($this->u_action . "&action=cache&id=$template_id")); -		} - -		$cache_prefix = 'tpl_' . str_replace('_', '-', $template_row['template_path']); - -		// Someone wants to see the cached source ... so we'll highlight it, -		// add line numbers and indent it appropriately. This could be nasty -		// on larger source files ... -		if ($source && file_exists("{$phpbb_root_path}cache/{$cache_prefix}_$source.html.$phpEx")) -		{ -			adm_page_header($user->lang['TEMPLATE_CACHE']); - -			$template->set_filenames(array( -				'body'	=> 'viewsource.html') -			); - -			$template->assign_vars(array( -				'FILENAME'	=> str_replace('.', '/', $source) . '.html') -			); - -			$code = str_replace(array("\r\n", "\r"), array("\n", "\n"), file_get_contents("{$phpbb_root_path}cache/{$cache_prefix}_$source.html.$phpEx")); - -			$conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string'); -			foreach ($conf as $ini_var) +			if (!$id)  			{ -				@ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var)); +				trigger_error($this->user->lang['INVALID_STYLE_ID'] . adm_back_link($this->u_action), E_USER_WARNING);  			} - -			$marker = 'MARKER' . time(); -			$code = highlight_string(str_replace("\n", $marker, $code), true); -			$code = str_replace($marker, "\n", $code); -			$str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':'); -			$str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '[', ']', '.', ':'); - -			$code = str_replace($str_from, $str_to, $code); -			$code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#ism', '$1$2$3', $code); -			$code = substr($code, strlen('<span class="syntaxhtml">')); -			$code = substr($code, 0, -1 * strlen('</ span>')); -			$code = explode("\n", $code); - -			foreach ($code as $key => $line) +			if ($id == $default)  			{ -				$template->assign_block_vars('source', array( -					'LINENUM'	=> $key + 1, -					'LINE'		=> preg_replace('#([^ ;]) ([^ &])#', '$1 $2', $line)) -				); -				unset($code[$key]); +				trigger_error($this->user->lang['UNINSTALL_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING);  			} - -			adm_page_footer(); +			$uninstalled[$id] = false;  		} -		$filemtime = array(); -		if ($template_row['template_storedb']) -		{ -			$ids = array(); -			if (isset($template_row['template_inherits_id']) && $template_row['template_inherits_id']) -			{ -				$ids[] = $template_row['template_inherits_id']; -			} -			$ids[] = $template_row['template_id']; - -			$filemtime 			= array(); -			$file_template_db	= array(); - -			foreach ($ids as $id) -			{ -				$sql = 'SELECT template_filename, template_mtime -					FROM ' . STYLES_TEMPLATE_DATA_TABLE . " -					WHERE template_id = $id"; -				$result = $db->sql_query($sql); - -				while ($row = $db->sql_fetchrow($result)) -				{ -					$filemtime[$row['template_filename']] = $row['template_mtime']; -					$file_template_db[$row['template_filename']] = $id; -				} -				$db->sql_freeresult($result); -			} -		} +		// Order by reversed style_id, so parent styles would be removed after child styles +		// This way parent and child styles can be removed in same function call +		$sql = 'SELECT * +			FROM ' . STYLES_TABLE . ' +			WHERE style_id IN (' . implode(', ', $ids) . ') +			ORDER BY style_id DESC'; +		$result = $this->db->sql_query($sql); -		// Get a list of cached template files and then retrieve additional information about them -		$file_ary = $this->template_cache_filelist($template_row['template_path']); +		$rows = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); -		foreach ($file_ary as $file) +		// Uinstall each style +		$uninstalled = array(); +		foreach ($rows as $style)  		{ -			$file		= str_replace('/', '.', $file); - -			// perform some dirty guessing to get the path right. -			// We assume that three dots in a row were '../' -			$tpl_file	= str_replace('.', '/', $file); -			$tpl_file	= str_replace('///', '../', $tpl_file); +			$result = $this->uninstall_style($style, $delete_files); -			$filename = "{$cache_prefix}_$file.html.$phpEx"; - -			if (!file_exists("{$phpbb_root_path}cache/$filename")) +			if (is_string($result))  			{ +				$messages[] = $result;  				continue;  			} +			$messages[] = sprintf($this->user->lang['STYLE_UNINSTALLED'], $style['style_name']); +			$uninstalled[] = $style['style_name']; -			$file_tpl = "{$phpbb_root_path}styles/{$template_row['template_path']}/template/$tpl_file.html"; -			$inherited = false; - -			if (isset($template_row['template_inherits_id']) && $template_row['template_inherits_id']) +			// Attempt to delete files +			if ($delete_files)  			{ -				if (!$template_row['template_storedb']) -				{ -					if (!file_exists($file_tpl)) -					{ -						$file_tpl = "{$phpbb_root_path}styles/{$template_row['template_inherit_path']}/template/$tpl_file.html"; -						$inherited = true; -					} -				} -				else -				{ -					if ($file_template_db[$file . '.html'] == $template_row['template_inherits_id']) -					{ -						$file_tpl = "{$phpbb_root_path}styles/{$template_row['template_inherit_path']}/template/$tpl_file.html"; -						$inherited = true; -					} -				} -			} - -			// Correct the filename if it is stored in database and the file is in a subfolder. -			if ($template_row['template_storedb']) -			{ -				$file = str_replace('.', '/', $file); +				$messages[] = sprintf($this->user->lang[$this->delete_style_files($style['style_path']) ? 'DELETE_STYLE_FILES_SUCCESS' : 'DELETE_STYLE_FILES_FAILED'], $style['style_name']);  			} +		} -			$template->assign_block_vars('file', array( -				'U_VIEWSOURCE'	=> $this->u_action . "&action=cache&id=$template_id&source=$file", +		if (empty($messages)) +		{ +			// Nothing to uninstall? +			trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); +		} -				'CACHED'		=> $user->format_date(filemtime("{$phpbb_root_path}cache/$filename")), -				'FILENAME'		=> $file, -				'FILENAME_PATH'	=> $file_tpl, -				'FILESIZE'		=> get_formatted_filesize(filesize("{$phpbb_root_path}cache/$filename")), -				'MODIFIED'		=> $user->format_date((!$template_row['template_storedb']) ? filemtime($file_tpl) : $filemtime[$file . '.html'])) -			); +		// Log action +		if (count($uninstalled)) +		{ +			add_log('admin', 'LOG_STYLE_DELETE', implode(', ', $uninstalled));  		} -		unset($filemtime); -		$template->assign_vars(array( -			'S_CACHE'			=> true, -			'S_TEMPLATE'		=> true, +		// Clear cache +		$this->cache->purge(); -			'U_ACTION'			=> $this->u_action . "&action=cache&id=$template_id", -			'U_BACK'			=> $this->u_action) -		); +		// Show message +		trigger_error(implode('<br />', $messages) . adm_back_link($this->u_action), E_USER_NOTICE);  	}  	/** -	* Provides a css editor and a basic easier to use stylesheet editing tool for less experienced (or lazy) users -	* -	* @param int $theme_id specifies which theme is being edited +	* Activate styles  	*/ -	function edit_theme($theme_id) +	protected function action_activate()  	{ -		global $phpbb_root_path, $phpEx, $config, $db, $cache, $user, $template, $safe_mode; - -		$this->page_title = 'EDIT_THEME'; - -		$filelist = $filelist_cats = array(); - -		$theme_data		= utf8_normalize_nfc(request_var('template_data', '', true)); -		$theme_data		= htmlspecialchars_decode($theme_data); -		$theme_file		= utf8_normalize_nfc(request_var('template_file', '', true)); -		$text_rows		= max(5, min(999, request_var('text_rows', 20))); -		$save_changes	= (isset($_POST['save'])) ? true : false; - -		// make sure theme_file path doesn't go upwards -		$theme_file = str_replace('..', '.', $theme_file); - -		// Retrieve some information about the theme -		$sql = 'SELECT theme_storedb, theme_path, theme_name, theme_data -			FROM ' . STYLES_THEME_TABLE . " -			WHERE theme_id = $theme_id"; -		$result = $db->sql_query($sql); +		// Get list of styles to activate +		$ids = $this->request_vars('id', 0, true); -		if (!($theme_info = $db->sql_fetchrow($result))) -		{ -			trigger_error($user->lang['NO_THEME'] . adm_back_link($this->u_action), E_USER_WARNING); -		} -		$db->sql_freeresult($result); +		// Activate styles +		$sql = 'UPDATE ' . STYLES_TABLE . ' +			SET style_active = 1 +			WHERE style_id IN (' . implode(', ', $ids) . ')'; +		$this->db->sql_query($sql); -		// save changes to the theme if the user submitted any -		if ($save_changes) -		{ -			// Get the filesystem location of the current file -			$file = "{$phpbb_root_path}styles/{$theme_info['theme_path']}/theme/$theme_file"; -			$additional = ''; -			$message = $user->lang['THEME_UPDATED']; +		// Purge cache +		$this->cache->destroy('sql', STYLES_TABLE); -			// If the theme is stored on the filesystem try to write the file else store it in the database -			if (!$safe_mode && !$theme_info['theme_storedb'] && file_exists($file) && phpbb_is_writable($file)) -			{ -				if (!($fp = @fopen($file, 'wb'))) -				{ -					trigger_error($user->lang['NO_THEME'] . adm_back_link($this->u_action), E_USER_WARNING); -				} -				fwrite($fp, $theme_data); -				fclose($fp); -			} -			else -			{ -				// Write stylesheet to db -				$sql_ary = array( -					'theme_mtime'		=> time(), -					'theme_storedb'		=> 1, -					'theme_data'		=> $this->db_theme_data($theme_info, $theme_data), -				); -				$sql = 'UPDATE ' . STYLES_THEME_TABLE . ' -					SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' -					WHERE theme_id = ' . $theme_id; -				$db->sql_query($sql); - -				$cache->destroy('sql', STYLES_THEME_TABLE); - -				// notify the user if the theme was not stored in the db before his modification -				if (!$theme_info['theme_storedb']) -				{ -					add_log('admin', 'LOG_THEME_EDIT_DETAILS', $theme_info['theme_name']); -					$message .= '<br />' . $user->lang['EDIT_THEME_STORED_DB']; -				} -			} -			$cache->destroy('sql', STYLES_THEME_TABLE); -			add_log('admin', (!$theme_info['theme_storedb']) ? 'LOG_THEME_EDIT_FILE' : 'LOG_THEME_EDIT', $theme_info['theme_name'], (!$theme_info['theme_storedb']) ? $theme_file : ''); - -			trigger_error($message . adm_back_link($this->u_action . "&action=edit&id=$theme_id&template_file=$theme_file&text_rows=$text_rows")); -		} - -		// Generate a category array containing theme filenames -		if (!$theme_info['theme_storedb']) -		{ -			$theme_path = "{$phpbb_root_path}styles/{$theme_info['theme_path']}/theme"; - -			$filelist = filelist($theme_path, '', 'css'); +		// Show styles list +		$this->frontend(); +	} -			if ($theme_file) -			{ -				if (!file_exists($theme_path . "/$theme_file") || !($theme_data = file_get_contents($theme_path . "/$theme_file"))) -				{ -					trigger_error($user->lang['NO_THEME'] . adm_back_link($this->u_action), E_USER_WARNING); -				} -			} -		} -		else -		{ -			$theme_data = &$theme_info['theme_data']; -		} +	/** +	* Deactivate styles +	*/ +	protected function action_deactivate() +	{ +		// Get list of styles to deactivate +		$ids = $this->request_vars('id', 0, true); -		// Now create the categories -		$filelist_cats[''] = array(); -		foreach ($filelist as $pathfile => $file_ary) +		// Check for default style +		foreach ($ids as $id)  		{ -			// Use the directory name as category name -			if (!empty($pathfile)) -			{ -				$filelist_cats[$pathfile] = array(); -				foreach ($file_ary as $file) -				{ -					$filelist_cats[$pathfile][$pathfile . $file] = $file; -				} -			} -			// or if it's in the main category use the word before the first underscore to group files -			else +			if ($id == $this->default_style)  			{ -				$cats = array(); -				foreach ($file_ary as $file) -				{ -					$cats[] = substr($file, 0, strpos($file, '_')); -					$filelist_cats[substr($file, 0, strpos($file, '_'))][$file] = $file; -				} - -				$cats = array_values(array_unique($cats)); - -				// we don't need any single element categories so put them into the misc '' category -				for ($i = 0, $n = sizeof($cats); $i < $n; $i++) -				{ -					if (sizeof($filelist_cats[$cats[$i]]) == 1 && $cats[$i] !== '') -					{ -						$filelist_cats[''][key($filelist_cats[$cats[$i]])] = current($filelist_cats[$cats[$i]]); -						unset($filelist_cats[$cats[$i]]); -					} -				} -				unset($cats); +				trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING);  			}  		} -		unset($filelist); -		// Generate list of categorised theme files -		$tpl_options = ''; -		ksort($filelist_cats); -		foreach ($filelist_cats as $category => $tpl_ary) -		{ -			ksort($tpl_ary); +		// Reset default style for users who use selected styles +		$sql = 'UPDATE ' . USERS_TABLE . ' +			SET user_style = 0 +			WHERE user_style IN (' . implode(', ', $ids) . ')'; +		$this->db->sql_query($sql); -			if (!empty($category)) -			{ -				$tpl_options .= '<option class="sep" value="">' . $category . '</option>'; -			} +		// Deactivate styles +		$sql = 'UPDATE ' . STYLES_TABLE . ' +			SET style_active = 0 +			WHERE style_id IN (' . implode(', ', $ids) . ')'; +		$this->db->sql_query($sql); -			foreach ($tpl_ary as $filename => $file) -			{ -				$selected = ($theme_file == $filename) ? ' selected="selected"' : ''; -				$tpl_options .= '<option value="' . $filename . '"' . $selected . '>' . $file . '</option>'; -			} -		} +		// Purge cache +		$this->cache->destroy('sql', STYLES_TABLE); -		$template->assign_vars(array( -			'S_EDIT_THEME'		=> true, -			'S_HIDDEN_FIELDS'	=> build_hidden_fields(array('template_file' => $theme_file)), -			'S_THEME_IN_DB'		=> $theme_info['theme_storedb'], -			'S_TEMPLATES'		=> $tpl_options, - -			'U_ACTION'			=> $this->u_action . "&action=edit&id=$theme_id&text_rows=$text_rows", -			'U_BACK'			=> $this->u_action, - -			'L_EDIT'			=> $user->lang['EDIT_THEME'], -			'L_EDIT_EXPLAIN'	=> $user->lang['EDIT_THEME_EXPLAIN'], -			'L_EDITOR'			=> $user->lang['THEME_EDITOR'], -			'L_EDITOR_HEIGHT'	=> $user->lang['THEME_EDITOR_HEIGHT'], -			'L_FILE'			=> $user->lang['THEME_FILE'], -			'L_SELECT'			=> $user->lang['SELECT_THEME'], -			'L_SELECTED'		=> $user->lang['SELECTED_THEME'], -			'L_SELECTED_FILE'	=> $user->lang['SELECTED_THEME_FILE'], - -			'SELECTED_TEMPLATE'	=> $theme_info['theme_name'], -			'TEMPLATE_FILE'		=> $theme_file, -			'TEMPLATE_DATA'		=> utf8_htmlspecialchars($theme_data), -			'TEXT_ROWS'			=> $text_rows) -		); +		// Show styles list +		$this->frontend();  	}  	/** -	* Edit imagesets -	* -	* @param int $imageset_id specifies which imageset is being edited +	* Show style details  	*/ -	function edit_imageset($imageset_id) +	protected function action_details()  	{ -		global $db, $user, $phpbb_root_path, $cache, $template; - -		$this->page_title = 'EDIT_IMAGESET'; - -		if (!$imageset_id) +		$id = $this->request->variable('id', 0); +		if (!$id)  		{ -			trigger_error($user->lang['NO_IMAGESET'] . adm_back_link($this->u_action), E_USER_WARNING); +			trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);  		} -		$update		= (isset($_POST['update'])) ? true : false; - -		$imgname	= request_var('imgname', 'site_logo'); -		$imgname	= preg_replace('#[^a-z0-9\-+_]#i', '', $imgname); -		$sql_extra = $imgnamelang = ''; - -		$sql = 'SELECT imageset_path, imageset_name -			FROM ' . STYLES_IMAGESET_TABLE . " -			WHERE imageset_id = $imageset_id"; -		$result = $db->sql_query($sql); -		$imageset_row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); +		// Get all styles +		$styles = $this->get_styles(); +		usort($styles, array($this, 'sort_styles')); -		if (!$imageset_row) +		// Find current style +		$style = false; +		foreach ($styles as $row)  		{ -			trigger_error($user->lang['NO_IMAGESET'] . adm_back_link($this->u_action), E_USER_WARNING); -		} - -		$imageset_path		= $imageset_row['imageset_path']; -		$imageset_name		= $imageset_row['imageset_name']; - -		if (strpos($imgname, '-') !== false) -		{ -			list($imgname, $imgnamelang) = explode('-', $imgname); -			$sql_extra = " AND image_lang IN ('" . $db->sql_escape($imgnamelang) . "', '')"; -		} - -		$sql = 'SELECT image_filename, image_width, image_height, image_lang, image_id -			FROM ' . STYLES_IMAGESET_DATA_TABLE . " -			WHERE imageset_id = $imageset_id -				AND image_name = '" . $db->sql_escape($imgname) . "'$sql_extra"; -		$result = $db->sql_query($sql); -		$imageset_data_row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		$image_filename	= $imageset_data_row['image_filename']; -		$image_width	= $imageset_data_row['image_width']; -		$image_height	= $imageset_data_row['image_height']; -		$image_lang		= $imageset_data_row['image_lang']; -		$image_id		= $imageset_data_row['image_id']; -		$imgsize		= ($imageset_data_row['image_width'] && $imageset_data_row['image_height']) ? 1 : 0; - -		// Check to see whether the selected image exists in the table -		$valid_name = ($update) ? false : true; - -		foreach ($this->imageset_keys as $category => $img_ary) -		{ -			if (in_array($imgname, $img_ary)) +			if ($row['style_id'] == $id)  			{ -				$valid_name = true; +				$style = $row;  				break;  			}  		} -		if ($update && isset($_POST['imgpath']) && $valid_name) +		if ($style === false)  		{ -			// If imgwidth and imgheight are non-zero grab the actual size -			// from the image itself ... we ignore width settings for the poll center image -			$imgwidth	= request_var('imgwidth', 0); -			$imgheight	= request_var('imgheight', 0); -			$imgsize	= request_var('imgsize', 0); -			$imgpath	= request_var('imgpath', ''); -			$imgpath	= str_replace('..', '.', $imgpath); - -			// If no dimensions selected, we reset width and height to 0 ;) -			if (!$imgsize) -			{ -				$imgwidth = $imgheight = 0; -			} - -			$imglang = ''; +			trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); +		} -			if ($imgpath && !file_exists("{$phpbb_root_path}styles/$imageset_path/imageset/$imgpath")) -			{ -				trigger_error($user->lang['NO_IMAGE_ERROR'] . adm_back_link($this->u_action), E_USER_WARNING); -			} +		// Find all available parent styles +		$list = $this->find_possible_parents($styles, $id); -			// Determine width/height. If dimensions included and no width/height given, we detect them automatically... -			if ($imgsize && $imgpath) -			{ -				if (!$imgwidth || !$imgheight) -				{ -					list($imgwidth_file, $imgheight_file) = getimagesize("{$phpbb_root_path}styles/$imageset_path/imageset/$imgpath"); -					$imgwidth = ($imgwidth) ? $imgwidth : $imgwidth_file; -					$imgheight = ($imgheight) ? $imgheight : $imgheight_file; -				} -				$imgwidth	= ($imgname != 'poll_center') ? (int) $imgwidth : 0; -				$imgheight	= (int) $imgheight; -			} +		// Add form key +		$form_key = 'acp_styles'; +		add_form_key($form_key); -			if (strpos($imgpath, '/') !== false) -			{ -				list($imglang, $imgfilename) = explode('/', $imgpath); -			} -			else +		// Change data +		if ($this->request->variable('update', false)) +		{ +			if (!check_form_key($form_key))  			{ -				$imgfilename = $imgpath; +				trigger_error($this->user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);  			} -			$sql_ary = array( -				'image_filename'	=> (string) $imgfilename, -				'image_width'		=> (int) $imgwidth, -				'image_height'		=> (int) $imgheight, -				'image_lang'		=> (string) $imglang, +			$update = array( +				'style_name'		=> trim($this->request->variable('style_name', $style['style_name'])), +				'style_parent_id'	=> $this->request->variable('style_parent', (int) $style['style_parent_id']), +				'style_active'		=> $this->request->variable('style_active', (int) $style['style_active']),  			); +			$update_action = $this->u_action . '&action=details&id=' . $id; -			// already exists -			if ($imageset_data_row) +			// Check style name +			if ($update['style_name'] != $style['style_name'])  			{ -				$sql = 'UPDATE ' . STYLES_IMAGESET_DATA_TABLE . ' -					SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " -					WHERE image_id = $image_id"; -				$db->sql_query($sql); -			} -			// does not exist -			else if (!$imageset_data_row) -			{ -				$sql_ary['image_name']	= $imgname; -				$sql_ary['imageset_id']	= (int) $imageset_id; -				$db->sql_query('INSERT INTO ' . STYLES_IMAGESET_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); -			} - -			$cache->destroy('sql', STYLES_IMAGESET_DATA_TABLE); - -			add_log('admin', 'LOG_IMAGESET_EDIT', $imageset_name); - -			$template->assign_var('SUCCESS', true); - -			$image_filename = $imgfilename; -			$image_width	= $imgwidth; -			$image_height	= $imgheight; -			$image_lang		= $imglang; -		} - -		$imglang = ''; -		$imagesetlist = array('nolang' => array(), 'lang' => array()); -		$langs = array(); - -		$dir = "{$phpbb_root_path}styles/$imageset_path/imageset"; -		$dp = @opendir($dir); - -		if ($dp) -		{ -			while (($file = readdir($dp)) !== false) -			{ -				if ($file[0] != '.' && strtoupper($file) != 'CVS' && !is_file($dir . '/' . $file) && !is_link($dir . '/' . $file)) +				if (!strlen($update['style_name']))  				{ -					$langs[] = $file; +					trigger_error($this->user->lang['STYLE_ERR_STYLE_NAME'] . adm_back_link($update_action), E_USER_WARNING);  				} -				else if (preg_match('#\.(?:gif|jpg|png)$#', $file)) +				foreach ($styles as $row)  				{ -					$imagesetlist['nolang'][] = $file; +					if ($row['style_name'] == $update['style_name']) +					{ +						trigger_error($this->user->lang['STYLE_ERR_NAME_EXIST'] . adm_back_link($update_action), E_USER_WARNING); +					}  				}  			} - -			if ($sql_extra) +			else  			{ -				$dp2 = @opendir("$dir/$imgnamelang"); +				unset($update['style_name']); +			} -				if ($dp2) +			// Check parent style id +			if ($update['style_parent_id'] != $style['style_parent_id']) +			{ +				if ($update['style_parent_id'] != 0)  				{ -					while (($file2 = readdir($dp2)) !== false) +					$found = false; +					foreach ($list as $row)  					{ -						if (preg_match('#\.(?:gif|jpg|png)$#', $file2)) +						if ($row['style_id'] == $update['style_parent_id'])  						{ -							$imagesetlist['lang'][] = "$imgnamelang/$file2"; +							$found = true; +							$update['style_parent_tree'] = ($row['style_parent_tree'] != '' ? $row['style_parent_tree'] . '/' : '') . $row['style_path']; +							break;  						}  					} -					closedir($dp2); -				} -			} -			closedir($dp); -		} - -		// Generate list of image options -		$img_options = ''; -		foreach ($this->imageset_keys as $category => $img_ary) -		{ -			$template->assign_block_vars('category', array( -				'NAME'			=> $user->lang['IMG_CAT_' . strtoupper($category)] -			)); - -			foreach ($img_ary as $img) -			{ -				if ($category == 'buttons') -				{ -					foreach ($langs as $language) +					if (!$found)  					{ -						$template->assign_block_vars('category.images', array( -							'SELECTED'			=> ($img == $imgname && $language == $imgnamelang), -							'VALUE'				=> $img . '-' . $language, -							'TEXT'				=> $user->lang['IMG_' . strtoupper($img)] . ' [ ' . $language . ' ]' -						)); +						trigger_error($this->user->lang['STYLE_ERR_INVALID_PARENT'] . adm_back_link($update_action), E_USER_WARNING);  					}  				}  				else  				{ -					$template->assign_block_vars('category.images', array( -						'SELECTED'			=> ($img == $imgname), -						'VALUE'				=> $img, -						'TEXT'				=> (($category == 'custom') ? $img : $user->lang['IMG_' . strtoupper($img)]) -					)); +					$update['style_parent_tree'] = '';  				}  			} -		} - -		// Make sure the list of possible images is sorted alphabetically -		sort($imagesetlist['lang']); -		sort($imagesetlist['nolang']); - -		$image_found = false; -		$img_val = ''; -		foreach ($imagesetlist as $type => $img_ary) -		{ -			if ($type !== 'lang' || $sql_extra) +			else  			{ -				$template->assign_block_vars('imagesetlist', array( -					'TYPE'	=> ($type == 'lang') -				)); +				unset($update['style_parent_id']);  			} -			foreach ($img_ary as $img) +			// Check style_active +			if ($update['style_active'] != $style['style_active'])  			{ -				$imgtext = preg_replace('/^([^\/]+\/)/', '', $img); -				$selected = (!empty($imgname) && strpos($image_filename, $imgtext) !== false); -				if ($selected) +				if (!$update['style_active'] && $this->default_style == $style['style_id'])  				{ -					$image_found = true; -					$img_val = htmlspecialchars($img); +					trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($update_action), E_USER_WARNING);  				} -				$template->assign_block_vars('imagesetlist.images', array( -					'SELECTED'			=> $selected, -					'TEXT'				=> $imgtext, -					'VALUE'				=> htmlspecialchars($img) -				));  			} -		} - -		$imgsize_bool = (!empty($imgname) && $image_width && $image_height) ? true : false; -		$image_request = '../styles/' . $imageset_path . '/imageset/' . ($image_lang ? $imgnamelang . '/' : '') . $image_filename; - -		$template->assign_vars(array( -			'S_EDIT_IMAGESET'	=> true, -			'L_TITLE'			=> $user->lang[$this->page_title], -			'L_EXPLAIN'			=> $user->lang[$this->page_title . '_EXPLAIN'], -			'IMAGE_OPTIONS'		=> $img_options, -			'IMAGE_SIZE'		=> $image_width, -			'IMAGE_HEIGHT'		=> $image_height, -			'IMAGE_REQUEST'		=> (empty($image_filename)) ? 'images/no_image.png' : $image_request, -			'U_ACTION'			=> $this->u_action . "&action=edit&id=$imageset_id", -			'U_BACK'			=> $this->u_action, -			'NAME'				=> $imageset_name, -			'A_NAME'			=> addslashes($imageset_name), -			'PATH'				=> $imageset_path, -			'A_PATH'			=> addslashes($imageset_path), -			'ERROR'				=> !$valid_name, -			'IMG_SRC'			=> ($image_found) ? '../styles/' . $imageset_path . '/imageset/' . $img_val : 'images/no_image.png', -			'IMAGE_SELECT'		=> $image_found -		)); -	} - -	/** -	* Remove style/template/theme/imageset -	*/ -	function remove($mode, $style_id) -	{ -		global $db, $template, $user, $phpbb_root_path, $cache, $config; - -		$new_id = request_var('new_id', 0); -		$update = (isset($_POST['update'])) ? true : false; -		$sql_where = ''; - -		switch ($mode) -		{ -			case 'style': -				$sql_from = STYLES_TABLE; -				$sql_select = 'style_id, style_name, template_id, theme_id, imageset_id'; -				$sql_where = 'AND style_active = 1'; -			break; - -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -				$sql_select = 'template_id, template_name, template_path, template_storedb'; -			break; - -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -				$sql_select = 'theme_id, theme_name, theme_path, theme_storedb'; -			break; - -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -				$sql_select = 'imageset_id, imageset_name, imageset_path'; -			break; -		} - -		if ($mode === 'template' && ($conflicts = $this->check_inheritance($mode, $style_id))) -		{ -			$l_type = strtoupper($mode); -			$msg = $user->lang[$l_type . '_DELETE_DEPENDENT']; -			foreach ($conflicts as $id => $values) +			else  			{ -				$msg .= '<br />' . $values['template_name']; +				unset($update['style_active']);  			} -			trigger_error($msg . adm_back_link($this->u_action), E_USER_WARNING); -		} - -		$l_prefix = strtoupper($mode); - -		$sql = "SELECT $sql_select -			FROM $sql_from -			WHERE {$mode}_id = $style_id"; -		$result = $db->sql_query($sql); -		$style_row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		if (!$style_row) -		{ -			trigger_error($user->lang['NO_' . $l_prefix] . adm_back_link($this->u_action), E_USER_WARNING); -		} - -		if ($update) -		{ -			if ($mode == 'style') +			// Update data +			if (count($update))  			{ -				$sql = "DELETE FROM $sql_from -					WHERE {$mode}_id = $style_id"; -				$db->sql_query($sql); - -				$sql = 'UPDATE ' . USERS_TABLE . " -					SET user_style = $new_id -					WHERE user_style = $style_id"; -				$db->sql_query($sql); +				$sql = 'UPDATE ' . STYLES_TABLE . ' +					SET ' . $this->db->sql_build_array('UPDATE', $update) . " +					WHERE style_id = $id"; +				$this->db->sql_query($sql); -				$sql = 'UPDATE ' . FORUMS_TABLE . " -					SET forum_style = $new_id -					WHERE forum_style = $style_id"; -				$db->sql_query($sql); +				$style = array_merge($style, $update); -				if ($style_id == $config['default_style']) +				if (isset($update['style_parent_id']))  				{ -					set_config('default_style', $new_id); +					// Update styles tree +					$styles = $this->get_styles(); +					if ($this->update_styles_tree($styles, $style)) +					{ +						// Something was changed in styles tree, purge all cache +						$this->cache->purge(); +					}  				} +				add_log('admin', 'LOG_STYLE_EDIT_DETAILS', $style['style_name']); +			} -				// Remove the components -				$components = array('template', 'theme', 'imageset'); -				foreach ($components as $component) +			// Update default style +			$default = $this->request->variable('style_default', 0); +			if ($default) +			{ +				if (!$style['style_active'])  				{ -					$new_id = request_var('new_' . $component . '_id', 0); -					$component_id = $style_row[$component . '_id']; -					$this->remove_component($component, $component_id, $new_id, $style_id); +					trigger_error($this->user->lang['STYLE_DEFAULT_CHANGE_INACTIVE'] . adm_back_link($update_action), E_USER_WARNING);  				} -			} -			else -			{ -				$this->remove_component($mode, $style_id, $new_id); +				set_config('default_style', $id); +				$this->cache->purge();  			} -			$cache->destroy('sql', STYLES_TABLE); - -			add_log('admin', 'LOG_' . $l_prefix . '_DELETE', $style_row[$mode . '_name']); -			$message = ($mode != 'style') ? $l_prefix . '_DELETED_FS' : $l_prefix . '_DELETED'; -			trigger_error($user->lang[$message] . adm_back_link($this->u_action)); +			// Show styles list +			$this->frontend(); +			return;  		} -		$this->display_component_options($mode, $style_row[$mode . '_id'], $style_row); - -		$this->page_title = 'DELETE_' . $l_prefix; - -		$template->assign_vars(array( -			'S_DELETE'			=> true, - -			'L_TITLE'			=> $user->lang[$this->page_title], -			'L_EXPLAIN'			=> $user->lang[$this->page_title . '_EXPLAIN'], -			'L_NAME'			=> $user->lang[$l_prefix . '_NAME'], -			'L_REPLACE'			=> $user->lang['REPLACE_' . $l_prefix], -			'L_REPLACE_EXPLAIN'	=> $user->lang['REPLACE_' . $l_prefix . '_EXPLAIN'], - -			'U_ACTION'		=> $this->u_action . "&action=delete&id=$style_id", -			'U_BACK'		=> $this->u_action, +		// Show parent styles +		foreach ($list as $row) +		{ +			$this->template->assign_block_vars('parent_styles', array( +				'STYLE_ID'		=> $row['style_id'], +				'STYLE_NAME'	=> htmlspecialchars($row['style_name']), +				'LEVEL'			=> $row['level'], +				'SPACER'		=> str_repeat('  ', $row['level']), +				) +			); +		} -			'NAME'			=> $style_row[$mode . '_name'], +		// Show style details +		$this->template->assign_vars(array( +			'S_STYLE_DETAILS'	=> true, +			'STYLE_ID'			=> $style['style_id'], +			'STYLE_NAME'		=> htmlspecialchars($style['style_name']), +			'STYLE_PATH'		=> htmlspecialchars($style['style_path']), +			'STYLE_COPYRIGHT'	=> strip_tags($style['style_copyright']), +			'STYLE_PARENT'		=> $style['style_parent_id'], +			'S_STYLE_ACTIVE'	=> $style['style_active'], +			'S_STYLE_DEFAULT'	=> ($style['style_id'] == $this->default_style)  			)  		); - -		if ($mode == 'style') -		{ -			$template->assign_vars(array( -				'S_DELETE_STYLE'		=> true, -			)); -		}  	}  	/** -	* Remove template/theme/imageset entry from the database +	* List installed styles  	*/ -	function remove_component($component, $component_id, $new_id, $style_id = false) +	protected function show_installed()  	{ -		global $db; +		// Get all installed styles +		$styles = $this->get_styles(); -		if (($new_id == 0) || ($component === 'template' && ($conflicts = $this->check_inheritance($component, $component_id)))) +		if (!count($styles))  		{ -			// We can not delete the template, as the user wants to keep the component or an other template is inheriting from this one. -			return; +			trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);  		} -		$component_in_use = array(); -		if ($component != 'style') -		{ -			$component_in_use = $this->component_in_use($component, $component_id, $style_id); -		} +		usort($styles, array($this, 'sort_styles')); -		if (($new_id == -1) && !empty($component_in_use)) -		{ -			// We can not delete the component, as it is still in use -			return; -		} +		// Get users +		$users = $this->get_users(); -		if ($component == 'imageset') +		// Add users counter to rows +		foreach ($styles as &$style)  		{ -			$sql = 'DELETE FROM ' . STYLES_IMAGESET_DATA_TABLE . " -				WHERE imageset_id = $component_id"; -			$db->sql_query($sql); +			$style['_users'] = isset($users[$style['style_id']]) ? $users[$style['style_id']] : 0;  		} -		switch ($component) -		{ -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -			break; +		// Set up styles list variables +		// Addons should increase this number and update template variable +		$this->styles_list_cols = 4; +		$this->template->assign_var('STYLES_LIST_COLS', $this->styles_list_cols); -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; - -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE;; -			break; -		} - -		$sql = "DELETE FROM $sql_from -			WHERE {$component}_id = $component_id"; -		$db->sql_query($sql); - -		$sql = 'UPDATE ' . STYLES_TABLE . " -			SET {$component}_id = $new_id -			WHERE {$component}_id = $component_id"; -		$db->sql_query($sql); -	} - -	/** -	* Display the options which can be used to replace a style/template/theme/imageset -	*/ -	function display_component_options($component, $component_id, $style_row = false, $style_id = false) -	{ -		global $db, $template, $user; - -		$component_in_use = array(); -		if ($component != 'style') -		{ -			$component_in_use = $this->component_in_use($component, $component_id, $style_id); -		} - -		$sql_where = ''; -		switch ($component) -		{ -			case 'style': -				$sql_from = STYLES_TABLE; -				$sql_where = 'WHERE style_active = 1'; -			break; - -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -				$sql_where = 'WHERE template_inherits_id <> ' . $component_id; -			break; - -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; - -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -			break; -		} - -		$s_options = ''; -		if (($component != 'style') && empty($component_in_use)) -		{ -			$sql = "SELECT {$component}_id, {$component}_name -				FROM $sql_from -				WHERE {$component}_id = {$component_id}"; -			$result = $db->sql_query($sql); -			$row = $db->sql_fetchrow($result); -			$db->sql_freeresult($result); - -			$s_options .= '<option value="-1" selected="selected">' . $user->lang['DELETE_' . strtoupper($component)] . '</option>'; -			$s_options .= '<option value="0">' . sprintf($user->lang['KEEP_' . strtoupper($component)], $row[$component . '_name']) . '</option>'; -		} -		else -		{ -			$sql = "SELECT {$component}_id, {$component}_name -				FROM $sql_from -				$sql_where -				ORDER BY {$component}_name ASC"; -			$result = $db->sql_query($sql); - -			$s_keep_option = $s_options = ''; -			while ($row = $db->sql_fetchrow($result)) -			{ -				if ($row[$component . '_id'] != $component_id) -				{ -					$s_options .= '<option value="' . $row[$component . '_id'] . '">' . sprintf($user->lang['REPLACE_WITH_OPTION'], $row[$component . '_name']) . '</option>'; -				} -				else if ($component != 'style') -				{ -					$s_keep_option = '<option value="0" selected="selected">' . sprintf($user->lang['KEEP_' . strtoupper($component)], $row[$component . '_name']) . '</option>'; -				} -			} -			$db->sql_freeresult($result); -			$s_options = $s_keep_option . $s_options; -		} +		// Show styles list +		$this->show_styles_list($styles, 0, 0); -		if (!$style_row) -		{ -			$template->assign_var('S_REPLACE_' . strtoupper($component) . '_OPTIONS', $s_options); -		} -		else +		// Show styles with invalid inherits_id +		foreach ($styles as $style)  		{ -			$template->assign_var('S_REPLACE_OPTIONS', $s_options); -			if ($component == 'style') +			if (empty($style['_shown']))  			{ -				$components = array('template', 'theme', 'imageset'); -				foreach ($components as $component) -				{ -					$this->display_component_options($component, $style_row[$component . '_id'], false, $component_id, true); -				} +				$style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($style['style_parent_tree'])); +				$this->list_style($style, 0);  			}  		} -	} -	/** -	* Check whether the component is still used by another style or component -	*/ -	function component_in_use($component, $component_id, $style_id = false) -	{ -		global $db; - -		$component_in_use = array(); +		// Add buttons +		$this->template->assign_block_vars('extra_actions', array( +				'ACTION_NAME'	=> 'activate', +				'L_ACTION'		=> $this->user->lang['STYLE_ACTIVATE'], +			) +		); -		if ($style_id) -		{ -			$sql = 'SELECT style_id, style_name -				FROM ' . STYLES_TABLE . " -				WHERE {$component}_id = {$component_id} -					AND style_id <> {$style_id} -				ORDER BY style_name ASC"; -		} -		else -		{ -			$sql = 'SELECT style_id, style_name -				FROM ' . STYLES_TABLE . " -				WHERE {$component}_id = {$component_id} -				ORDER BY style_name ASC"; -		} -		$result = $db->sql_query($sql); -		while ($row = $db->sql_fetchrow($result)) -		{ -			$component_in_use[] = $row['style_name']; -		} -		$db->sql_freeresult($result); +		$this->template->assign_block_vars('extra_actions', array( +				'ACTION_NAME'	=> 'deactivate', +				'L_ACTION'		=> $this->user->lang['STYLE_DEACTIVATE'], +			) +		); -		if ($component === 'template' && ($conflicts = $this->check_inheritance($component, $component_id))) +		if (isset($this->style_counters) && $this->style_counters['total'] > 1)  		{ -			foreach ($conflicts as $temp_id => $conflict_data) -			{ -				$component_in_use[] = $conflict_data['template_name']; -			} +			$this->template->assign_block_vars('extra_actions', array( +					'ACTION_NAME'	=> 'uninstall', +					'L_ACTION'		=> $this->user->lang['STYLE_UNINSTALL'], +				) +			);  		} - -		return $component_in_use;  	}  	/** -	* Export style or style elements +	* Show list of styles that can be installed  	*/ -	function export($mode, $style_id) +	protected function show_available()  	{ -		global $db, $template, $user, $phpbb_root_path, $cache, $phpEx, $config; - -		$update = (isset($_POST['update'])) ? true : false; - -		$inc_template = request_var('inc_template', 0); -		$inc_theme = request_var('inc_theme', 0); -		$inc_imageset = request_var('inc_imageset', 0); -		$store = request_var('store', 0); -		$format = request_var('format', ''); - -		$error = array(); -		$methods = array('tar'); - -		$available_methods = array('tar.gz' => 'zlib', 'tar.bz2' => 'bz2', 'zip' => 'zlib'); -		foreach ($available_methods as $type => $module) -		{ -			if (!@extension_loaded($module)) -			{ -				continue; -			} - -			$methods[] = $type; -		} +		// Get list of styles +		$styles = $this->find_available(true); -		if (!in_array($format, $methods)) +		// Show styles +		if (empty($styles))  		{ -			$format = 'tar'; +			trigger_error($this->user->lang['NO_UNINSTALLED_STYLE'] . adm_back_link($this->u_base_action), E_USER_NOTICE);  		} -		switch ($mode) -		{ -			case 'style': -				if ($update && ($inc_template + $inc_theme + $inc_imageset) < 1) -				{ -					$error[] = $user->lang['STYLE_ERR_MORE_ELEMENTS']; -				} - -				$name = 'style_name'; - -				$sql_select = 's.style_id, s.style_name, s.style_copyright'; -				$sql_select .= ($inc_template) ? ', t.*' : ', t.template_name'; -				$sql_select .= ($inc_theme) ? ', c.*' : ', c.theme_name'; -				$sql_select .= ($inc_imageset) ? ', i.*' : ', i.imageset_name'; -				$sql_from = STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . ' i'; -				$sql_where = "s.style_id = $style_id AND t.template_id = s.template_id AND c.theme_id = s.theme_id AND i.imageset_id = s.imageset_id"; - -				$l_prefix = 'STYLE'; -			break; - -			case 'template': -				$name = 'template_name'; - -				$sql_select = '*'; -				$sql_from = STYLES_TEMPLATE_TABLE; -				$sql_where = "template_id = $style_id"; - -				$l_prefix = 'TEMPLATE'; -			break; - -			case 'theme': -				$name = 'theme_name'; - -				$sql_select = '*'; -				$sql_from = STYLES_THEME_TABLE; -				$sql_where = "theme_id = $style_id"; - -				$l_prefix = 'THEME'; -			break; - -			case 'imageset': -				$name = 'imageset_name'; +		usort($styles, array($this, 'sort_styles')); -				$sql_select = '*'; -				$sql_from = STYLES_IMAGESET_TABLE; -				$sql_where = "imageset_id = $style_id"; - -				$l_prefix = 'IMAGESET'; -			break; -		} +		$this->styles_list_cols = 3; +		$this->template->assign_vars(array( +			'STYLES_LIST_COLS'	=> $this->styles_list_cols, +			'STYLES_LIST_HIDE_COUNT'	=> true +			) +		); -		if ($update && !sizeof($error)) +		// Show styles +		foreach ($styles as &$style)  		{ -			$sql = "SELECT $sql_select -				FROM $sql_from -				WHERE $sql_where"; -			$result = $db->sql_query($sql); -			$style_row = $db->sql_fetchrow($result); -			$db->sql_freeresult($result); - -			if (!$style_row) -			{ -				trigger_error($user->lang['NO_' . $l_prefix] . adm_back_link($this->u_action), E_USER_WARNING); -			} - -			$var_ary = array('style_id', 'style_name', 'style_copyright', 'template_id', 'template_name', 'template_path', 'template_copyright', 'template_storedb', 'template_inherits_id', 'bbcode_bitfield', 'theme_id', 'theme_name', 'theme_path', 'theme_copyright', 'theme_storedb', 'theme_mtime', 'theme_data', 'imageset_id', 'imageset_name', 'imageset_path', 'imageset_copyright'); - -			foreach ($var_ary as $var) -			{ -				if (!isset($style_row[$var])) -				{ -					$style_row[$var] = ''; -				} -			} - -			$files = $data = array(); - -			if ($mode == 'style') -			{ -				$style_cfg = str_replace(array('{MODE}', '{NAME}', '{COPYRIGHT}', '{VERSION}'), array($mode, $style_row['style_name'], $style_row['style_copyright'], $config['version']), $this->style_cfg); - -				$style_cfg .= (!$inc_template) ? "\nrequired_template = {$style_row['template_name']}" : ''; -				$style_cfg .= (!$inc_theme) ? "\nrequired_theme = {$style_row['theme_name']}" : ''; -				$style_cfg .= (!$inc_imageset) ? "\nrequired_imageset = {$style_row['imageset_name']}" : ''; - -				$data[] = array( -					'src'		=> $style_cfg, -					'prefix'	=> 'style.cfg' -				); - -				unset($style_cfg); -			} - -			// Export template core code -			if ($mode == 'template' || $inc_template) +			// Check if style has a parent style in styles list +			$has_parent = false; +			if ($style['_inherit_name'] != '')  			{ -				$template_cfg = str_replace(array('{MODE}', '{NAME}', '{COPYRIGHT}', '{VERSION}'), array($mode, $style_row['template_name'], $style_row['template_copyright'], $config['version']), $this->template_cfg); - -				$use_template_name = ''; - -				// Add the inherit from variable, depending on it's use... -				if ($style_row['template_inherits_id']) -				{ -					// Get the template name -					$sql = 'SELECT template_name -						FROM ' . STYLES_TEMPLATE_TABLE . ' -						WHERE template_id = ' . (int) $style_row['template_inherits_id']; -					$result = $db->sql_query($sql); -					$use_template_name = (string) $db->sql_fetchfield('template_name'); -					$db->sql_freeresult($result); -				} - -				$template_cfg .= ($use_template_name) ? "\ninherit_from = $use_template_name" : "\n#inherit_from = "; -				$template_cfg .= "\n\nbbcode_bitfield = {$style_row['bbcode_bitfield']}"; - -				$data[] = array( -					'src'		=> $template_cfg, -					'prefix'	=> 'template/template.cfg' -				); - -				// This is potentially nasty memory-wise ... -				if (!$style_row['template_storedb']) +				foreach ($styles as $parent_style)  				{ -					$files[] = array( -						'src'		=> "styles/{$style_row['template_path']}/template/", -						'prefix-'	=> "styles/{$style_row['template_path']}/", -						'prefix+'	=> false, -						'exclude'	=> 'template.cfg' -					); -				} -				else -				{ -					$sql = 'SELECT template_filename, template_data -						FROM ' . STYLES_TEMPLATE_DATA_TABLE . " -						WHERE template_id = {$style_row['template_id']}"; -					$result = $db->sql_query($sql); - -					while ($row = $db->sql_fetchrow($result)) +					if ($parent_style['style_name'] == $style['_inherit_name'] && empty($parent_style['_shown']))  					{ -						$data[] = array( -							'src' => $row['template_data'], -							'prefix' => 'template/' . $row['template_filename'] -						); +						// Show parent style first +						$has_parent = true;  					} -					$db->sql_freeresult($result);  				} -				unset($template_cfg);  			} - -			// Export theme core code -			if ($mode == 'theme' || $inc_theme) +			if (!$has_parent)  			{ -				$theme_cfg = str_replace(array('{MODE}', '{NAME}', '{COPYRIGHT}', '{VERSION}'), array($mode, $style_row['theme_name'], $style_row['theme_copyright'], $config['version']), $this->theme_cfg); - -				// Read old cfg file -				$items = $cache->obtain_cfg_items($style_row); -				$items = $items['theme']; - -				if (!isset($items['parse_css_file'])) -				{ -					$items['parse_css_file'] = 'off'; -				} - -				$theme_cfg = str_replace(array('{PARSE_CSS_FILE}'), array($items['parse_css_file']), $theme_cfg); - -				$files[] = array( -					'src'		=> "styles/{$style_row['theme_path']}/theme/", -					'prefix-'	=> "styles/{$style_row['theme_path']}/", -					'prefix+'	=> false, -					'exclude'	=> ($style_row['theme_storedb']) ? 'stylesheet.css,theme.cfg' : 'theme.cfg' -				); - -				$data[] = array( -					'src'		=> $theme_cfg, -					'prefix'	=> 'theme/theme.cfg' -				); - -				if ($style_row['theme_storedb']) -				{ -					$data[] = array( -						'src'		=> $style_row['theme_data'], -						'prefix'	=> 'theme/stylesheet.css' -					); -				} - -				unset($items, $theme_cfg); -			} - -			// Export imageset core code -			if ($mode == 'imageset' || $inc_imageset) -			{ -				$imageset_cfg = str_replace(array('{MODE}', '{NAME}', '{COPYRIGHT}', '{VERSION}'), array($mode, $style_row['imageset_name'], $style_row['imageset_copyright'], $config['version']), $this->imageset_cfg); - -				$imageset_main = array(); - -				$sql = 'SELECT image_filename, image_name, image_height, image_width -					FROM ' . STYLES_IMAGESET_DATA_TABLE . " -					WHERE imageset_id = $style_id -						AND image_lang = ''"; -				$result = $db->sql_query($sql); -				while ($row = $db->sql_fetchrow($result)) -				{ -					$imageset_main[$row['image_name']] = $row['image_filename'] . ($row['image_height'] ? '*' . $row['image_height']: '') . ($row['image_width'] ? '*' . $row['image_width']: ''); -				} -				$db->sql_freeresult($result); - -				foreach ($this->imageset_keys as $topic => $key_array) -				{ -					foreach ($key_array as $key) -					{ -						if (isset($imageset_main[$key])) -						{ -							$imageset_cfg .= "\nimg_" . $key . ' = ' . str_replace("styles/{$style_row['imageset_path']}/imageset/", '{PATH}', $imageset_main[$key]); -						} -					} -				} - -				$files[] = array( -					'src'		=> "styles/{$style_row['imageset_path']}/imageset/", -					'prefix-'	=> "styles/{$style_row['imageset_path']}/", -					'prefix+'	=> false, -					'exclude'	=> 'imageset.cfg' -				); - -				$data[] = array( -					'src'		=> trim($imageset_cfg), -					'prefix'	=> 'imageset/imageset.cfg' -				); - -				end($data); - -				$imageset_root = "{$phpbb_root_path}styles/{$style_row['imageset_path']}/imageset/"; - -				if ($dh = @opendir($imageset_root)) -				{ -					while (($fname = readdir($dh)) !== false) -					{ -						if ($fname[0] != '.' && $fname != 'CVS' && is_dir("$imageset_root$fname")) -						{ -							$files[key($files)]['exclude'] .= ',' . $fname . '/imageset.cfg'; -						} -					} -					closedir($dh); -				} - -				$imageset_lang = array(); - -				$sql = 'SELECT image_filename, image_name, image_height, image_width, image_lang -					FROM ' . STYLES_IMAGESET_DATA_TABLE . " -					WHERE imageset_id = $style_id -						AND image_lang <> ''"; -				$result = $db->sql_query($sql); -				while ($row = $db->sql_fetchrow($result)) -				{ -					$imageset_lang[$row['image_lang']][$row['image_name']] = $row['image_filename'] . ($row['image_height'] ? '*' . $row['image_height']: '') . ($row['image_width'] ? '*' . $row['image_width']: ''); -				} -				$db->sql_freeresult($result); - -				foreach ($imageset_lang as $lang => $imageset_localized) -				{ -					$imageset_cfg = str_replace(array('{MODE}', '{NAME}', '{COPYRIGHT}', '{VERSION}'), array($mode, $style_row['imageset_name'], $style_row['imageset_copyright'], $config['version']), $this->imageset_cfg); - -					foreach ($this->imageset_keys as $topic => $key_array) -					{ -						foreach ($key_array as $key) -						{ -							if (isset($imageset_localized[$key])) -							{ -								$imageset_cfg .= "\nimg_" . $key . ' = ' . str_replace("styles/{$style_row['imageset_path']}/imageset/", '{PATH}', $imageset_localized[$key]); -							} -						} -					} - -					$data[] = array( -						'src'		=> trim($imageset_cfg), -						'prefix'	=> 'imageset/' . $lang . '/imageset.cfg' -					); -				} - -				unset($imageset_cfg); -			} - -			switch ($format) -			{ -				case 'tar': -					$ext = '.tar'; -				break; - -				case 'zip': -					$ext = '.zip'; -				break; - -				case 'tar.gz': -					$ext = '.tar.gz'; -				break; - -				case 'tar.bz2': -					$ext = '.tar.bz2'; -				break; - -				default: -					$error[] = $user->lang[$l_prefix . '_ERR_ARCHIVE']; -			} - -			if (!sizeof($error)) -			{ -				include($phpbb_root_path . 'includes/functions_compress.' . $phpEx); - -				if ($mode == 'style') -				{ -					$path = preg_replace('#[^\w-]+#', '_', $style_row['style_name']); -				} -				else -				{ -					$path = $style_row[$mode . '_path']; -				} - -				if ($format == 'zip') -				{ -					$compress = new compress_zip('w', $phpbb_root_path . "store/$path$ext"); -				} -				else -				{ -					$compress = new compress_tar('w', $phpbb_root_path . "store/$path$ext", $ext); -				} - -				if (sizeof($files)) -				{ -					foreach ($files as $file_ary) -					{ -						$compress->add_file($file_ary['src'], $file_ary['prefix-'], $file_ary['prefix+'], $file_ary['exclude']); -					} -				} - -				if (sizeof($data)) -				{ -					foreach ($data as $data_ary) -					{ -						$compress->add_data($data_ary['src'], $data_ary['prefix']); -					} -				} - -				$compress->close(); - -				add_log('admin', 'LOG_' . $l_prefix . '_EXPORT', $style_row[$mode . '_name']); - -				if (!$store) -				{ -					$compress->download($path); -					@unlink("{$phpbb_root_path}store/$path$ext"); -					exit; -				} - -				trigger_error(sprintf($user->lang[$l_prefix . '_EXPORTED'], "store/$path$ext") . adm_back_link($this->u_action)); +				$this->list_style($style, 0); +				$this->show_available_child_styles($styles, $style['style_name'], 1);  			}  		} -		$sql = "SELECT {$mode}_id, {$mode}_name -			FROM " . (($mode == 'style') ? STYLES_TABLE : $sql_from) . " -			WHERE {$mode}_id = $style_id"; -		$result = $db->sql_query($sql); -		$style_row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		if (!$style_row) +		// Show styles that do not have parent style in styles list +		foreach ($styles as $style)  		{ -			trigger_error($user->lang['NO_' . $l_prefix] . adm_back_link($this->u_action), E_USER_WARNING); +			if (empty($style['_shown'])) +			{ +				$this->list_style($style, 0); +			}  		} -		$this->page_title = $l_prefix . '_EXPORT'; - -		$format_buttons = ''; -		foreach ($methods as $method) +		// Add button +		if (isset($this->style_counters) && $this->style_counters['caninstall'] > 0)  		{ -			$format_buttons .= '<label><input type="radio"' . ((!$format_buttons) ? ' id="format"' : '') . ' class="radio" value="' . $method . '" name="format"' . (($method == $format) ? ' checked="checked"' : '') . ' /> ' . $method . '</label>'; +			$this->template->assign_block_vars('extra_actions', array( +					'ACTION_NAME'	=> 'install', +					'L_ACTION'		=> $this->user->lang['INSTALL_STYLES'], +				) +			);  		} - -		$template->assign_vars(array( -			'S_EXPORT'		=> true, -			'S_ERROR_MSG'	=> (sizeof($error)) ? true : false, -			'S_STYLE'		=> ($mode == 'style') ? true : false, - -			'L_TITLE'		=> $user->lang[$this->page_title], -			'L_EXPLAIN'		=> $user->lang[$this->page_title . '_EXPLAIN'], -			'L_NAME'		=> $user->lang[$l_prefix . '_NAME'], - -			'U_ACTION'		=> $this->u_action . '&action=export&id=' . $style_id, -			'U_BACK'		=> $this->u_action, - -			'ERROR_MSG'			=> (sizeof($error)) ? implode('<br />', $error) : '', -			'NAME'				=> $style_row[$mode . '_name'], -			'FORMAT_BUTTONS'	=> $format_buttons) -		);  	}  	/** -	* Display details +	* Find styles available for installation +	* +	* @param bool $all if true, function will return all installable styles. if false, function will return only styles that can be installed +	* @return array List of styles  	*/ -	function details($mode, $style_id) +	protected function find_available($all)  	{ -		global $template, $db, $config, $user, $safe_mode, $cache, $phpbb_root_path; - -		$update = (isset($_POST['update'])) ? true : false; -		$l_type = strtoupper($mode); - -		$error = array(); -		$element_ary = array('template' => STYLES_TEMPLATE_TABLE, 'theme' => STYLES_THEME_TABLE, 'imageset' => STYLES_IMAGESET_TABLE); - -		switch ($mode) -		{ -			case 'style': -				$sql_from = STYLES_TABLE; -			break; - -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -			break; - -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; - -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -			break; -		} - -		$sql = "SELECT * -			FROM $sql_from -			WHERE {$mode}_id = $style_id"; -		$result = $db->sql_query($sql); -		$style_row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		if (!$style_row) -		{ -			trigger_error($user->lang['NO_' . $l_type] . adm_back_link($this->u_action), E_USER_WARNING); +		// Get list of installed styles +		$installed = $this->get_styles(); + +		$installed_dirs = array(); +		$installed_names = array(); +		foreach ($installed as $style) +		{ +			$installed_dirs[] = $style['style_path']; +			$installed_names[$style['style_name']] = array( +				'path'		=> $style['style_path'], +				'id'		=> $style['style_id'], +				'parent'	=> $style['style_parent_id'], +				'tree'		=> (strlen($style['style_parent_tree']) ? $style['style_parent_tree'] . '/' : '') . $style['style_path'], +			);  		} -		$style_row['style_default'] = ($mode == 'style' && $config['default_style'] == $style_id) ? 1 : 0; +		// Get list of directories +		$dirs = $this->find_style_dirs(); -		if ($update) +		// Find styles that can be installed +		$styles = array(); +		foreach ($dirs as $dir)  		{ -			$name = utf8_normalize_nfc(request_var('name', '', true)); -			$copyright = utf8_normalize_nfc(request_var('copyright', '', true)); - -			$template_id = request_var('template_id', 0); -			$theme_id = request_var('theme_id', 0); -			$imageset_id = request_var('imageset_id', 0); - -			$style_active = request_var('style_active', 0); -			$style_default = request_var('style_default', 0); -			$store_db = request_var('store_db', 0); - -			// If the admin selected the style to be the default style, but forgot to activate it... we will do it for him -			if ($style_default) -			{ -				$style_active = 1; -			} - -			$sql = "SELECT {$mode}_id, {$mode}_name -				FROM $sql_from -				WHERE {$mode}_id <> $style_id -				AND LOWER({$mode}_name) = '" . $db->sql_escape(strtolower($name)) . "'"; -			$result = $db->sql_query($sql); -			$conflict = $db->sql_fetchrow($result); -			$db->sql_freeresult($result); - -			if ($mode == 'style' && (!$template_id || !$theme_id || !$imageset_id)) -			{ -				$error[] = $user->lang['STYLE_ERR_NO_IDS']; -			} - -			if ($mode == 'style' && $style_row['style_active'] && !$style_active && $config['default_style'] == $style_id) +			if (in_array($dir, $installed_dirs))  			{ -				$error[] = $user->lang['DEACTIVATE_DEFAULT']; -			} - -			if (!$name || $conflict) -			{ -				$error[] = $user->lang[$l_type . '_ERR_STYLE_NAME']; -			} - -			if ($mode === 'theme' || $mode === 'template') -			{ -				// a rather elaborate check we have to do here once to avoid trouble later -				$check = "{$phpbb_root_path}styles/" . $style_row["{$mode}_path"] . (($mode === 'theme') ? '/theme/stylesheet.css' : '/template'); -				if (($style_row["{$mode}_storedb"] != $store_db) && !$store_db && ($safe_mode || !phpbb_is_writable($check))) -				{ -					$error[] = $user->lang['EDIT_' . strtoupper($mode) . '_STORED_DB']; -					$store_db = 1; -				} - -				// themes which have to be parsed have to go into db -				if ($mode == 'theme') -				{ -					$cfg = parse_cfg_file("{$phpbb_root_path}styles/" . $style_row["{$mode}_path"] . "/theme/theme.cfg"); - -					if (isset($cfg['parse_css_file']) && $cfg['parse_css_file'] && !$store_db) -					{ -						$error[] = $user->lang['EDIT_THEME_STORE_PARSED']; -						$store_db = 1; -					} -				} +				// Style is already installed +				continue;  			} - -			if (!sizeof($error)) +			$cfg = $this->read_style_cfg($dir); +			if ($cfg === false)  			{ -				// Check length settings -				if (utf8_strlen($name) > 30) -				{ -					$error[] = $user->lang[$l_type . '_ERR_NAME_LONG']; -				} - -				if (utf8_strlen($copyright) > 60) -				{ -					$error[] = $user->lang[$l_type . '_ERR_COPY_LONG']; -				} +				// Invalid style.cfg +				continue;  			} -		} - -		if ($update && sizeof($error)) -		{ -			$style_row = array_merge($style_row, array( -				'template_id'			=> $template_id, -				'theme_id'				=> $theme_id, -				'imageset_id'			=> $imageset_id, -				'style_active'			=> $style_active, -				$mode . '_storedb'		=> $store_db, -				$mode . '_name'			=> $name, -				$mode . '_copyright'	=> $copyright) -			); -		} -		// User has submitted form and no errors have occurred -		if ($update && !sizeof($error)) -		{ -			$sql_ary = array( -				$mode . '_name'			=> $name, -				$mode . '_copyright'	=> $copyright +			// Style should be available for installation +			$parent = $cfg['parent']; +			$style = array( +				'style_id'			=> 0, +				'style_name'		=> $cfg['name'], +				'style_copyright'	=> $cfg['copyright'], +				'style_active'		=> 0, +				'style_path'		=> $dir, +				'bbcode_bitfield'	=> $cfg['template_bitfield'], +				'style_parent_id'	=> 0, +				'style_parent_tree'	=> '', +				// Extra values for styles list +				// All extra variable start with _ so they won't be confused with data that can be added to styles table +				'_inherit_name'			=> $parent, +				'_available'			=> true, +				'_note'					=> '',  			); -			switch ($mode) -			{ -				case 'style': - -					$sql_ary += array( -						'template_id'		=> (int) $template_id, -						'theme_id'			=> (int) $theme_id, -						'imageset_id'		=> (int) $imageset_id, -						'style_active'		=> (int) $style_active, -					); -				break; - -				case 'imageset': -				break; - -				case 'theme': - -					if ($style_row['theme_storedb'] != $store_db) -					{ -						$theme_data = ''; - -						if (!$style_row['theme_storedb']) -						{ -							$theme_data = $this->db_theme_data($style_row); -						} -						else if (!$store_db && !$safe_mode && phpbb_is_writable("{$phpbb_root_path}styles/{$style_row['theme_path']}/theme/stylesheet.css")) -						{ -							$store_db = 1; -							$theme_data = $style_row['theme_data']; - -							if ($fp = @fopen("{$phpbb_root_path}styles/{$style_row['theme_path']}/theme/stylesheet.css", 'wb')) -							{ -								$store_db = (@fwrite($fp, str_replace("styles/{$style_row['theme_path']}/theme/", './', $theme_data))) ? 0 : 1; -							} -							fclose($fp); -						} - -						$sql_ary += array( -							'theme_mtime'	=> ($store_db) ? filemtime("{$phpbb_root_path}styles/{$style_row['theme_path']}/theme/stylesheet.css") : 0, -							'theme_storedb'	=> $store_db, -							'theme_data'	=> ($store_db) ? $theme_data : '', -						); -					} -				break; - -				case 'template': - -					if ($style_row['template_storedb'] != $store_db) -					{ -						if ($super = $this->get_super($mode, $style_row['template_id'])) -						{ -							$error[] = (sprintf($user->lang["{$l_type}_INHERITS"], $super['template_name'])); -							$sql_ary = array(); -						} -						else -						{ -							if (!$store_db && !$safe_mode && phpbb_is_writable("{$phpbb_root_path}styles/{$style_row['template_path']}/template")) -							{ -								$err = $this->store_in_fs('template', $style_row['template_id']); -								if ($err) -								{ -									$error += $err; -								} -							} -							else if ($store_db) -							{ -								$this->store_in_db('template', $style_row['template_id']); -							} -							else -							{ -								// We no longer store within the db, but are also not able to update the file structure -								// Since the admin want to switch this, we adhere to his decision. But we also need to remove the cache -								$sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . " -									WHERE template_id = $style_id"; -								$db->sql_query($sql); -							} - -							$sql_ary += array( -								'template_storedb'	=> $store_db, -							); -						} -					} -				break; -			} - -			if (sizeof($sql_ary)) +			// Check style inheritance +			if ($parent != '')  			{ -				$sql = "UPDATE $sql_from -					SET " . $db->sql_build_array('UPDATE', $sql_ary) . " -					WHERE {$mode}_id = $style_id"; -				$db->sql_query($sql); - -				// Making this the default style? -				if ($mode == 'style' && $style_default) +				if (isset($installed_names[$parent]))  				{ -					set_config('default_style', $style_id); +					// Parent style is installed +					$row = $installed_names[$parent]; +					$style['style_parent_id'] = $row['id']; +					$style['style_parent_tree'] = $row['tree'];  				} -			} - -			$cache->destroy('sql', STYLES_TABLE); - -			add_log('admin', 'LOG_' . $l_type . '_EDIT_DETAILS', $name); -			if (sizeof($error)) -			{ -				trigger_error(implode('<br />', $error) . adm_back_link($this->u_action), E_USER_WARNING); -			} -			else -			{ -				trigger_error($user->lang[$l_type . '_DETAILS_UPDATED'] . adm_back_link($this->u_action)); -			} -		} - -		if ($mode == 'style') -		{ -			foreach ($element_ary as $element => $table) -			{ -				$sql = "SELECT {$element}_id, {$element}_name -					FROM $table -					ORDER BY {$element}_id ASC"; -				$result = $db->sql_query($sql); - -				${$element . '_options'} = ''; -				while ($row = $db->sql_fetchrow($result)) +				else  				{ -					$selected = ($row[$element . '_id'] == $style_row[$element . '_id']) ? ' selected="selected"' : ''; -					${$element . '_options'} .= '<option value="' . $row[$element . '_id'] . '"' . $selected . '>' . $row[$element . '_name'] . '</option>'; +					// Parent style is not installed yet +					$style['_available'] = false; +					$style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($parent));  				} -				$db->sql_freeresult($result);  			} -		} -		if ($mode == 'template') -		{ -			$super = array(); -			if (isset($style_row[$mode . '_inherits_id']) && $style_row['template_inherits_id']) +			if ($all || $style['_available'])  			{ -				$super = $this->get_super($mode, $style_row['template_id']); +				$styles[] = $style;  			}  		} -		$this->page_title = 'EDIT_DETAILS_' . $l_type; - -		$template->assign_vars(array( -			'S_DETAILS'				=> true, -			'S_ERROR_MSG'			=> (sizeof($error)) ? true : false, -			'S_STYLE'				=> ($mode == 'style') ? true : false, -			'S_TEMPLATE'			=> ($mode == 'template') ? true : false, -			'S_THEME'				=> ($mode == 'theme') ? true : false, -			'S_IMAGESET'			=> ($mode == 'imageset') ? true : false, -			'S_STORE_DB'			=> (isset($style_row[$mode . '_storedb'])) ? $style_row[$mode . '_storedb'] : 0, -			'S_STORE_DB_DISABLED'	=> (isset($style_row[$mode . '_inherits_id'])) ? $style_row[$mode . '_inherits_id'] : 0, -			'S_STYLE_ACTIVE'		=> (isset($style_row['style_active'])) ? $style_row['style_active'] : 0, -			'S_STYLE_DEFAULT'		=> (isset($style_row['style_default'])) ? $style_row['style_default'] : 0, -			'S_SUPERTEMPLATE'		=> (isset($style_row[$mode . '_inherits_id']) && $style_row[$mode . '_inherits_id']) ? $super['template_name'] : 0, - -			'S_TEMPLATE_OPTIONS'	=> ($mode == 'style') ? $template_options : '', -			'S_THEME_OPTIONS'		=> ($mode == 'style') ? $theme_options : '', -			'S_IMAGESET_OPTIONS'	=> ($mode == 'style') ? $imageset_options : '', - -			'U_ACTION'		=> $this->u_action . '&action=details&id=' . $style_id, -			'U_BACK'		=> $this->u_action, - -			'L_TITLE'				=> $user->lang[$this->page_title], -			'L_EXPLAIN'				=> $user->lang[$this->page_title . '_EXPLAIN'], -			'L_NAME'				=> $user->lang[$l_type . '_NAME'], -			'L_LOCATION'			=> ($mode == 'template' || $mode == 'theme') ? $user->lang[$l_type . '_LOCATION'] : '', -			'L_LOCATION_EXPLAIN'	=> ($mode == 'template' || $mode == 'theme') ? $user->lang[$l_type . '_LOCATION_EXPLAIN'] : '', - -			'ERROR_MSG'		=> (sizeof($error)) ? implode('<br />', $error) : '', -			'NAME'			=> $style_row[$mode . '_name'], -			'COPYRIGHT'		=> $style_row[$mode . '_copyright'], -			) -		); +		return $styles;  	}  	/** -	* Load css file contents +	* Show styles list +	* +	* @param array $styles styles list +	* @param int $parent parent style id +	* @param int $level style inheritance level  	*/ -	function load_css_file($path, $filename) +	protected function show_styles_list(&$styles, $parent, $level)  	{ -		global $phpbb_root_path; - -		$file = "{$phpbb_root_path}styles/$path/theme/$filename"; - -		if (file_exists($file) && ($content = file_get_contents($file))) +		foreach ($styles as &$style)  		{ -			$content = trim($content); -		} -		else -		{ -			$content = ''; -		} -		if (defined('DEBUG')) -		{ -			$content = "/* BEGIN @include $filename */ \n $content \n /* END @include $filename */ \n"; +			if (empty($style['_shown']) && $style['style_parent_id'] == $parent) +			{ +				$this->list_style($style, $level); +				$this->show_styles_list($styles, $style['style_id'], $level + 1); +			}  		} - -		return $content;  	}  	/** -	* Returns a string containing the value that should be used for the theme_data column in the theme database table. -	* Includes contents of files loaded via @import -	* -	* @param array $theme_row is an associative array containing the theme's current database entry -	* @param mixed $stylesheet can either be the new content for the stylesheet or false to load from the standard file -	* @param string $root_path should only be used in case you want to use a different root path than "{$phpbb_root_path}styles/{$theme_row['theme_path']}" +	* Show available styles tree  	* -	* @return string Stylesheet data for theme_data column in the theme table +	* @param array $styles Styles list, passed as reference +	* @param string $name Name of parent style +	* @param string $level Styles tree level  	*/ -	function db_theme_data($theme_row, $stylesheet = false, $root_path = '') +	protected function show_available_child_styles(&$styles, $name, $level)  	{ -		global $phpbb_root_path; - -		if (!$root_path) -		{ -			$root_path = $phpbb_root_path . 'styles/' . $theme_row['theme_path']; -		} - -		if (!$stylesheet) -		{ -			$stylesheet = ''; -			if (file_exists($root_path . '/theme/stylesheet.css')) -			{ -				$stylesheet = file_get_contents($root_path . '/theme/stylesheet.css'); -			} -		} - -		// Match CSS imports -		$matches = array(); -		preg_match_all('/@import url\((["\'])(.*)\1\);/i', $stylesheet, $matches); - -		// remove commented stylesheets (very simple parser, allows only whitespace -		// around an @import statement) -		preg_match_all('#/\*\s*@import url\((["\'])(.*)\1\);\s\*/#i', $stylesheet, $commented); -		$matches[2] = array_diff($matches[2], $commented[2]); - -		if (sizeof($matches)) +		foreach ($styles as &$style)  		{ -			foreach ($matches[0] as $idx => $match) +			if (empty($style['_shown']) && $style['_inherit_name'] == $name)  			{ -				if (isset($matches[2][$idx])) -				{ -					$stylesheet = str_replace($match, acp_styles::load_css_file($theme_row['theme_path'], $matches[2][$idx]), $stylesheet); -				} +				$this->list_style($style, $level); +				$this->show_available_child_styles($styles, $style['style_name'], $level + 1);  			}  		} - -		// adjust paths -		return str_replace('./', 'styles/' . $theme_row['theme_path'] . '/theme/', $stylesheet);  	}  	/** -	* Store template files into db +	* Update styles tree +	* +	* @param array $styles Styles list, passed as reference +	* @param array $style Current style, false if root +	* @return bool True if something was updated, false if not  	*/ -	function store_templates($mode, $style_id, $template_path, $filelist) +	protected function update_styles_tree(&$styles, $style = false)  	{ -		global $phpbb_root_path, $phpEx, $db; - -		$template_path = $template_path . '/template/'; -		$includes = array(); -		foreach ($filelist as $pathfile => $file_ary) +		$parent_id = ($style === false) ? 0 : $style['style_id']; +		$parent_tree = ($style === false) ? '' : ($style['style_parent_tree'] == '' ? '' : $style['style_parent_tree']) . $style['style_path']; +		$update = false; +		$updated = false; +		foreach ($styles as &$row)  		{ -			foreach ($file_ary as $file) +			if ($row['style_parent_id'] == $parent_id)  			{ -				if (!($fp = @fopen("{$phpbb_root_path}styles/$template_path$pathfile$file", 'r'))) -				{ -					trigger_error("Could not open {$phpbb_root_path}styles/$template_path$pathfile$file", E_USER_ERROR); -				} - -				$filesize = filesize("{$phpbb_root_path}styles/$template_path$pathfile$file"); - -				if ($filesize) +				if ($row['style_parent_tree'] != $parent_tree)  				{ -					$template_data = fread($fp, $filesize); -				} - -				fclose($fp); - -				if (!$filesize) -				{ -					// File is empty -					continue; -				} - -				if (preg_match_all('#<!-- INCLUDE (.*?\.html) -->#is', $template_data, $matches)) -				{ -					foreach ($matches[1] as $match) -					{ -						$includes[trim($match)][] = $file; -					} +					$row['style_parent_tree'] = $parent_tree; +					$update = true;  				} +				$updated |= $this->update_styles_tree($styles, $row);  			}  		} - -		foreach ($filelist as $pathfile => $file_ary) +		if ($update)  		{ -			foreach ($file_ary as $file) -			{ -				// Skip index. -				if (strpos($file, 'index.') === 0) -				{ -					continue; -				} - -				// We could do this using extended inserts ... but that could be one -				// heck of a lot of data ... -				$sql_ary = array( -					'template_id'			=> (int) $style_id, -					'template_filename'		=> "$pathfile$file", -					'template_included'		=> (isset($includes[$file])) ? implode(':', $includes[$file]) . ':' : '', -					'template_mtime'		=> (int) filemtime("{$phpbb_root_path}styles/$template_path$pathfile$file"), -					'template_data'			=> (string) file_get_contents("{$phpbb_root_path}styles/$template_path$pathfile$file"), -				); - -				if ($mode == 'insert') -				{ -					$sql = 'INSERT INTO ' . STYLES_TEMPLATE_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); -				} -				else -				{ -					$sql = 'UPDATE ' . STYLES_TEMPLATE_DATA_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " -						WHERE template_id = $style_id -							AND template_filename = '" . $db->sql_escape("$pathfile$file") . "'"; -				} -				$db->sql_query($sql); -			} +			$sql = 'UPDATE ' . STYLES_TABLE . " +				SET style_parent_tree = '" . $this->db->sql_escape($parent_tree) . "' +				WHERE style_parent_id = {$parent_id}"; +			$this->db->sql_query($sql); +			$updated = true;  		} +		return $updated;  	}  	/** -	* Returns an array containing all template filenames for one template that are currently cached. -	* -	* @param string $template_path contains the name of the template's folder in /styles/ +	* Find all possible parent styles for style  	* -	* @return array of filenames that exist in /styles/$template_path/template/ (without extension!) +	* @param array $styles list of styles +	* @param int $id id of style +	* @param int $parent current parent style id +	* @param int $level current tree level +	* @return array Style ids, names and levels  	*/ -	function template_cache_filelist($template_path) +	protected function find_possible_parents($styles, $id = -1, $parent = 0, $level = 0)  	{ -		global $phpbb_root_path, $phpEx, $user; - -		$cache_prefix = 'tpl_' . str_replace('_', '-', $template_path); - -		if (!($dp = @opendir("{$phpbb_root_path}cache"))) -		{ -			trigger_error($user->lang['TEMPLATE_ERR_CACHE_READ'] . adm_back_link($this->u_action), E_USER_WARNING); -		} - -		$file_ary = array(); -		while ($file = readdir($dp)) -		{ -			if ($file[0] == '.') -			{ -				continue; -			} - -			if (is_file($phpbb_root_path . 'cache/' . $file) && (strpos($file, $cache_prefix) === 0)) -			{ -				$file_ary[] = str_replace('.', '/', preg_replace('#^' . preg_quote($cache_prefix, '#') . '_(.*?)\.html\.' . $phpEx . '$#i', '\1', $file)); +		$results = array(); +		foreach ($styles as $style) +		{ +			if ($style['style_id'] != $id && $style['style_parent_id'] == $parent) +			{ +				$results[] = array( +					'style_id'		=> $style['style_id'], +					'style_name'	=> $style['style_name'], +					'style_path'	=> $style['style_path'], +					'style_parent_id'	=> $style['style_parent_id'], +					'style_parent_tree'	=> $style['style_parent_tree'], +					'level'			=> $level +				); +				$results = array_merge($results, $this->find_possible_parents($styles, $id, $style['style_id'], $level + 1));  			}  		} -		closedir($dp); - -		return $file_ary; +		return $results;  	}  	/** -	* Destroys cached versions of template files +	* Show item in styles list  	* -	* @param array $template_row contains the template's row in the STYLES_TEMPLATE_TABLE database table -	* @param mixed $file_ary is optional and may contain an array of template file names which should be refreshed in the cache. -	*	The file names should be the original template file names and not the cache file names. +	* @param array $style style row +	* @param array $level style inheritance level  	*/ -	function clear_template_cache($template_row, $file_ary = false) +	protected function list_style(&$style, $level)  	{ -		global $phpbb_root_path, $phpEx, $user; - -		$cache_prefix = 'tpl_' . str_replace('_', '-', $template_row['template_path']); - -		if (!$file_ary || !is_array($file_ary)) -		{ -			$file_ary = $this->template_cache_filelist($template_row['template_path']); -			$log_file_list = $user->lang['ALL_FILES']; -		} -		else -		{ -			$log_file_list = implode(', ', $file_ary); -		} +		// Mark row as shown +		if (!empty($style['_shown'])) return; +		$style['_shown'] = true; + +		// Generate template variables +		$actions = array(); +		$row = array( +			// Style data +			'STYLE_ID'		=> $style['style_id'], +			'STYLE_NAME'	=> htmlspecialchars($style['style_name']), +			'STYLE_PATH'	=> htmlspecialchars($style['style_path']), +			'STYLE_COPYRIGHT'	=> strip_tags($style['style_copyright']), +			'STYLE_ACTIVE'	=> $style['style_active'], + +			// Additional data +			'DEFAULT'		=> ($style['style_id'] && $style['style_id'] == $this->default_style), +			'USERS'			=> (isset($style['_users'])) ? $style['_users'] : '', +			'LEVEL'			=> $level, +			'PADDING'		=> (4 + 16 * $level), +			'SHOW_COPYRIGHT'	=> ($style['style_id']) ? false : true, +			'STYLE_PATH_FULL'	=> htmlspecialchars($this->styles_path_absolute . '/' . $style['style_path']) . '/', + +			// Comment to show below style +			'COMMENT'		=> (isset($style['_note'])) ? $style['_note'] : '', + +			// The following variables should be used by hooks to add custom HTML code +			'EXTRA'			=> '', +			'EXTRA_OPTIONS'	=> '' +		); -		foreach ($file_ary as $file) +		// Status specific data +		if ($style['style_id'])  		{ -			$file = str_replace('/', '.', $file); - -			$file = "{$phpbb_root_path}cache/{$cache_prefix}_$file.html.$phpEx"; -			if (file_exists($file) && is_file($file)) -			{ -				@unlink($file); -			} -		} -		unset($file_ary); +			// Style is installed -		add_log('admin', 'LOG_TEMPLATE_CACHE_CLEARED', $template_row['template_name'], $log_file_list); -	} - -	/** -	* Install Style/Template/Theme/Imageset -	*/ -	function install($mode) -	{ -		global $phpbb_root_path, $phpEx, $config, $db, $cache, $user, $template; +			// Details +			$actions[] = array( +				'U_ACTION'	=> $this->u_action . '&action=details&id=' . $style['style_id'], +				'L_ACTION'	=> $this->user->lang['DETAILS'] +			); -		$l_type = strtoupper($mode); +			// Activate +			$actions[] = array( +				'U_ACTION'	=> $this->u_action . '&action=' . ($style['style_active'] ? 'de' : '') . 'activate&id=' . $style['style_id'], +				'L_ACTION'	=> $this->user->lang['STYLE_' . ($style['style_active'] ? 'DE' : '') . 'ACTIVATE'] +			); -		$error = $installcfg = $style_row = array(); -		$root_path = $cfg_file = ''; -		$element_ary = array('template' => STYLES_TEMPLATE_TABLE, 'theme' => STYLES_THEME_TABLE, 'imageset' => STYLES_IMAGESET_TABLE); +/*			// Export +			$actions[] = array( +				'U_ACTION'	=> $this->u_action . '&action=export&id=' . $style['style_id'], +				'L_ACTION'	=> $this->user->lang['EXPORT'] +			); */ -		$install_path = request_var('path', ''); -		$update = (isset($_POST['update'])) ? true : false; +			// Uninstall +			$actions[] = array( +				'U_ACTION'	=> $this->u_action . '&action=uninstall&id=' . $style['style_id'], +				'L_ACTION'	=> $this->user->lang['STYLE_UNINSTALL'] +			); -		// Installing, obtain cfg file contents -		if ($install_path) +			// Preview +			$actions[] = array( +				'U_ACTION'	=> append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'style=' . $style['style_id']), +				'L_ACTION'	=> $this->user->lang['PREVIEW'] +			); +		} +		else  		{ -			$root_path = $phpbb_root_path . 'styles/' . $install_path . '/'; -			$cfg_file = ($mode == 'style') ? "$root_path$mode.cfg" : "$root_path$mode/$mode.cfg"; - -			if (!file_exists($cfg_file)) +			// Style is not installed +			if (empty($style['_available']))  			{ -				$error[] = $user->lang[$l_type . '_ERR_NOT_' . $l_type]; +				$actions[] = array( +					'HTML'		=> $this->user->lang['CANNOT_BE_INSTALLED'] +				);  			}  			else  			{ -				$installcfg = parse_cfg_file($cfg_file); +				$actions[] = array( +					'U_ACTION'	=> $this->u_action . '&action=install&dir=' . urlencode($style['style_path']), +					'L_ACTION'	=> $this->user->lang['INSTALL_STYLE'] +				);  			}  		} -		// Installing -		if (sizeof($installcfg)) -		{ -			$name		= $installcfg['name']; -			$copyright	= $installcfg['copyright']; -			$version	= $installcfg['version']; - -			$style_row = array( -				$mode . '_id'			=> 0, -				$mode . '_name'			=> '', -				$mode . '_copyright'	=> '' -			); - -			switch ($mode) -			{ -				case 'style': - -					$style_row = array( -						'style_id'			=> 0, -						'style_name'		=> $installcfg['name'], -						'style_copyright'	=> $installcfg['copyright'] -					); +		// todo: add hook -					$reqd_template = (isset($installcfg['required_template'])) ? $installcfg['required_template'] : false; -					$reqd_theme = (isset($installcfg['required_theme'])) ? $installcfg['required_theme'] : false; -					$reqd_imageset = (isset($installcfg['required_imageset'])) ? $installcfg['required_imageset'] : false; - -					// Check to see if each element is already installed, if it is grab the id -					foreach ($element_ary as $element => $table) -					{ -						$style_row = array_merge($style_row, array( -							$element . '_id'			=> 0, -							$element . '_name'			=> '', -							$element . '_copyright'		=> '') -						); - -			 			$this->test_installed($element, $error, (${'reqd_' . $element}) ? $phpbb_root_path . 'styles/' . $reqd_template . '/' : $root_path, ${'reqd_' . $element}, $style_row[$element . '_id'], $style_row[$element . '_name'], $style_row[$element . '_copyright']); - -						if (!$style_row[$element . '_name']) -						{ -							$style_row[$element . '_name'] = $reqd_template; -						} - -						// Merge other information to installcfg... if present -						$cfg_file = $phpbb_root_path . 'styles/' . $install_path . '/' . $element . '/' . $element . '.cfg'; - -						if (file_exists($cfg_file)) -						{ -							$cfg_contents = parse_cfg_file($cfg_file); - -							// Merge only specific things. We may need them later. -							foreach (array('inherit_from', 'parse_css_file') as $key) -							{ -								if (!empty($cfg_contents[$key]) && !isset($installcfg[$key])) -								{ -									$installcfg[$key] = $cfg_contents[$key]; -								} -							} -						} -					} - -				break; - -				case 'template': -					$this->test_installed('template', $error, $root_path, false, $style_row['template_id'], $style_row['template_name'], $style_row['template_copyright']); -				break; - -				case 'theme': -					$this->test_installed('theme', $error, $root_path, false, $style_row['theme_id'], $style_row['theme_name'], $style_row['theme_copyright']); -				break; - -				case 'imageset': -					$this->test_installed('imageset', $error, $root_path, false, $style_row['imageset_id'], $style_row['imageset_name'], $style_row['imageset_copyright']); -				break; -			} -		} -		else +		// Assign template variables +		$this->template->assign_block_vars('styles_list', $row); +		foreach($actions as $action)  		{ -			trigger_error($user->lang['NO_' . $l_type] . adm_back_link($this->u_action), E_USER_WARNING); +			$this->template->assign_block_vars('styles_list.actions', $action);  		} -		$style_row['store_db'] = request_var('store_db', 0); -		$style_row['style_active'] = request_var('style_active', 1); -		$style_row['style_default'] = request_var('style_default', 0); - -		// User has submitted form and no errors have occurred -		if ($update && !sizeof($error)) +		// Increase counters +		$counter = ($style['style_id']) ? ($style['style_active'] ? 'active' : 'inactive') : (empty($style['_available']) ? 'cannotinstall' : 'caninstall'); +		if (!isset($this->style_counters))  		{ -			if ($mode == 'style') -			{ -				foreach ($element_ary as $element => $table) -				{ -					${$element . '_root_path'} = (${'reqd_' . $element}) ? $phpbb_root_path . 'styles/' . ${'reqd_' . $element} . '/' : false; -					${$element . '_path'} = (${'reqd_' . $element}) ? ${'reqd_' . $element} : false; -				} -				$this->install_style($error, 'install', $root_path, $style_row['style_id'], $style_row['style_name'], $install_path, $style_row['style_copyright'], $style_row['style_active'], $style_row['style_default'], $style_row, $template_root_path, $template_path, $theme_root_path, $theme_path, $imageset_root_path, $imageset_path); -			} -			else -			{ -				$style_row['store_db'] = $this->install_element($mode, $error, 'install', $root_path, $style_row[$mode . '_id'], $style_row[$mode . '_name'], $install_path, $style_row[$mode . '_copyright'], $style_row['store_db']); -			} - -			if (!sizeof($error)) -			{ -				$cache->destroy('sql', STYLES_TABLE); - -				$message = ($style_row['store_db']) ? '_ADDED_DB' : '_ADDED'; -				trigger_error($user->lang[$l_type . $message] . adm_back_link($this->u_action)); -			} +			$this->style_counters = array( +				'total'		=> 0, +				'active'	=> 0, +				'inactive'	=> 0, +				'caninstall'	=> 0, +				'cannotinstall'	=> 0 +				);  		} - -		$this->page_title = 'INSTALL_' . $l_type; - -		$template->assign_vars(array( -			'S_DETAILS'			=> true, -			'S_INSTALL'			=> true, -			'S_ERROR_MSG'		=> (sizeof($error)) ? true : false, -			'S_LOCATION'		=> (isset($installcfg['inherit_from']) && $installcfg['inherit_from']) ? false : true, -			'S_STYLE'			=> ($mode == 'style') ? true : false, -			'S_TEMPLATE'		=> ($mode == 'template') ? true : false, -			'S_SUPERTEMPLATE'	=> (isset($installcfg['inherit_from'])) ? $installcfg['inherit_from'] : '', -			'S_THEME'			=> ($mode == 'theme') ? true : false, - -			'S_STORE_DB'			=> (isset($style_row[$mode . '_storedb'])) ? $style_row[$mode . '_storedb'] : 0, -			'S_STYLE_ACTIVE'		=> (isset($style_row['style_active'])) ? $style_row['style_active'] : 0, -			'S_STYLE_DEFAULT'		=> (isset($style_row['style_default'])) ? $style_row['style_default'] : 0, - -			'U_ACTION'			=> $this->u_action . "&action=install&path=" . urlencode($install_path), -			'U_BACK'			=> $this->u_action, - -			'L_TITLE'				=> $user->lang[$this->page_title], -			'L_EXPLAIN'				=> $user->lang[$this->page_title . '_EXPLAIN'], -			'L_NAME'				=> $user->lang[$l_type . '_NAME'], -			'L_LOCATION'			=> ($mode == 'template' || $mode == 'theme') ? $user->lang[$l_type . '_LOCATION'] : '', -			'L_LOCATION_EXPLAIN'	=> ($mode == 'template' || $mode == 'theme') ? $user->lang[$l_type . '_LOCATION_EXPLAIN'] : '', - -			'ERROR_MSG'			=> (sizeof($error)) ? implode('<br />', $error) : '', -			'NAME'				=> $style_row[$mode . '_name'], -			'COPYRIGHT'			=> $style_row[$mode . '_copyright'], -			'TEMPLATE_NAME'		=> ($mode == 'style') ? $style_row['template_name'] : '', -			'THEME_NAME'		=> ($mode == 'style') ? $style_row['theme_name'] : '', -			'IMAGESET_NAME'		=> ($mode == 'style') ? $style_row['imageset_name'] : '') -		); +		$this->style_counters[$counter]++; +		$this->style_counters['total']++;  	}  	/** -	* Add new style +	* Show welcome message +	* +	* @param string $title main title +	* @param string $description page description  	*/ -	function add($mode) +	protected function welcome_message($title, $description)  	{ -		global $phpbb_root_path, $phpEx, $config, $db, $cache, $user, $template; - -		$l_type = strtoupper($mode); -		$element_ary = array('template' => STYLES_TEMPLATE_TABLE, 'theme' => STYLES_THEME_TABLE, 'imageset' => STYLES_IMAGESET_TABLE); -		$error = array(); - -		$style_row = array( -			$mode . '_name'			=> utf8_normalize_nfc(request_var('name', '', true)), -			$mode . '_copyright'	=> utf8_normalize_nfc(request_var('copyright', '', true)), -			'template_id'			=> 0, -			'theme_id'				=> 0, -			'imageset_id'			=> 0, -			'store_db'				=> request_var('store_db', 0), -			'style_active'			=> request_var('style_active', 1), -			'style_default'			=> request_var('style_default', 0), +		$this->template->assign_vars(array( +			'L_TITLE'	=> $this->user->lang[$title], +			'L_EXPLAIN'	=> (isset($this->user->lang[$description])) ? $this->user->lang[$description] : '' +			)  		); +	} -		$basis = request_var('basis', 0); -		$update = (isset($_POST['update'])) ? true : false; - -		if ($basis) -		{ -			switch ($mode) -			{ -				case 'style': -					$sql_select = 'template_id, theme_id, imageset_id'; -					$sql_from = STYLES_TABLE; -				break; - -				case 'template': -					$sql_select = 'template_id'; -					$sql_from = STYLES_TEMPLATE_TABLE; -				break; - -				case 'theme': -					$sql_select = 'theme_id'; -					$sql_from = STYLES_THEME_TABLE; -				break; - -				case 'imageset': -					$sql_select = 'imageset_id'; -					$sql_from = STYLES_IMAGESET_TABLE; -				break; -			} - -			$sql = "SELECT $sql_select -				FROM $sql_from -				WHERE {$mode}_id = $basis"; -			$result = $db->sql_query($sql); -			$row = $db->sql_fetchrow($result); -			$db->sql_freeresult($result); - -			if (!$row) -			{ -				$error[] = $user->lang['NO_' . $l_type]; -			} - -			if (!sizeof($error)) -			{ -				$style_row['template_id']	= (isset($row['template_id'])) ? $row['template_id'] : $style_row['template_id']; -				$style_row['theme_id']		= (isset($row['theme_id'])) ? $row['theme_id'] : $style_row['theme_id']; -				$style_row['imageset_id']	= (isset($row['imageset_id'])) ? $row['imageset_id'] : $style_row['imageset_id']; -			} -		} - -		if ($update) -		{ -			$style_row['template_id'] = request_var('template_id', $style_row['template_id']); -			$style_row['theme_id'] = request_var('theme_id', $style_row['theme_id']); -			$style_row['imageset_id'] = request_var('imageset_id', $style_row['imageset_id']); - -			if ($mode == 'style' && (!$style_row['template_id'] || !$style_row['theme_id'] || !$style_row['imageset_id'])) -			{ -				$error[] = $user->lang['STYLE_ERR_NO_IDS']; -			} -		} - -		// User has submitted form and no errors have occurred -		if ($update && !sizeof($error)) -		{ -			if ($mode == 'style') -			{ -				$style_row['style_id'] = 0; - -				$this->install_style($error, 'add', '', $style_row['style_id'], $style_row['style_name'], '', $style_row['style_copyright'], $style_row['style_active'], $style_row['style_default'], $style_row); -			} - -			if (!sizeof($error)) -			{ -				$cache->destroy('sql', STYLES_TABLE); - -				$message = ($style_row['store_db']) ? '_ADDED_DB' : '_ADDED'; -				trigger_error($user->lang[$l_type . $message] . adm_back_link($this->u_action)); -			} -		} +	/** +	* Find all directories that have styles +	* +	* @return array Directory names +	*/ +	protected function find_style_dirs() +	{ +		$styles = array(); -		if ($mode == 'style') +		$dp = @opendir($this->styles_path); +		if ($dp)  		{ -			foreach ($element_ary as $element => $table) +			while (($file = readdir($dp)) !== false)  			{ -				$sql = "SELECT {$element}_id, {$element}_name -					FROM $table -					ORDER BY {$element}_id ASC"; -				$result = $db->sql_query($sql); +				$dir = $this->styles_path . $file; +				if ($file[0] == '.' || !is_dir($dir)) +				{ +					continue; +				} -				${$element . '_options'} = ''; -				while ($row = $db->sql_fetchrow($result)) +				if (file_exists("{$dir}/style.cfg"))  				{ -					$selected = ($row[$element . '_id'] == $style_row[$element . '_id']) ? ' selected="selected"' : ''; -					${$element . '_options'} .= '<option value="' . $row[$element . '_id'] . '"' . $selected . '>' . $row[$element . '_name'] . '</option>'; +					$styles[] = $file;  				} -				$db->sql_freeresult($result);  			} +			closedir($dp);  		} -		$this->page_title = 'ADD_' . $l_type; - -		$template->assign_vars(array( -			'S_DETAILS'			=> true, -			'S_ADD'				=> true, -			'S_ERROR_MSG'		=> (sizeof($error)) ? true : false, -			'S_STYLE'			=> ($mode == 'style') ? true : false, -			'S_TEMPLATE'		=> ($mode == 'template') ? true : false, -			'S_THEME'			=> ($mode == 'theme') ? true : false, -			'S_BASIS'			=> ($basis) ? true : false, - -			'S_STORE_DB'			=> (isset($style_row['storedb'])) ? $style_row['storedb'] : 0, -			'S_STYLE_ACTIVE'		=> (isset($style_row['style_active'])) ? $style_row['style_active'] : 0, -			'S_STYLE_DEFAULT'		=> (isset($style_row['style_default'])) ? $style_row['style_default'] : 0, -			'S_TEMPLATE_OPTIONS'	=> ($mode == 'style') ? $template_options : '', -			'S_THEME_OPTIONS'		=> ($mode == 'style') ? $theme_options : '', -			'S_IMAGESET_OPTIONS'	=> ($mode == 'style') ? $imageset_options : '', - -			'U_ACTION'			=> $this->u_action . '&action=add&basis=' . $basis, -			'U_BACK'			=> $this->u_action, - -			'L_TITLE'				=> $user->lang[$this->page_title], -			'L_EXPLAIN'				=> $user->lang[$this->page_title . '_EXPLAIN'], -			'L_NAME'				=> $user->lang[$l_type . '_NAME'], -			'L_LOCATION'			=> ($mode == 'template' || $mode == 'theme') ? $user->lang[$l_type . '_LOCATION'] : '', -			'L_LOCATION_EXPLAIN'	=> ($mode == 'template' || $mode == 'theme') ? $user->lang[$l_type . '_LOCATION_EXPLAIN'] : '', - -			'ERROR_MSG'			=> (sizeof($error)) ? implode('<br />', $error) : '', -			'NAME'				=> $style_row[$mode . '_name'], -			'COPYRIGHT'			=> $style_row[$mode . '_copyright']) -		); - +		return $styles;  	}  	/** - -					$reqd_template = (isset($installcfg['required_template'])) ? $installcfg['required_template'] : false; -					$reqd_theme = (isset($installcfg['required_theme'])) ? $installcfg['required_theme'] : false; -					$reqd_imageset = (isset($installcfg['required_imageset'])) ? $installcfg['required_imageset'] : false; - -					// Check to see if each element is already installed, if it is grab the id -					foreach ($element_ary as $element => $table) -					{ -						$style_row = array_merge($style_row, array( -							$element . '_id'			=> 0, -							$element . '_name'			=> '', -							$element . '_copyright'		=> '') -						); - -			 			$this->test_installed($element, $error, $root_path, ${'reqd_' . $element}, $style_row[$element . '_id'], $style_row[$element . '_name'], $style_row[$element . '_copyright']); -	* Is this element installed? If not, grab its cfg details +	* Sort styles  	*/ -	function test_installed($element, &$error, $root_path, $reqd_name, &$id, &$name, &$copyright) +	public function sort_styles($style1, $style2)  	{ -		global $db, $user; - -		switch ($element) +		if ($style1['style_active'] != $style2['style_active'])  		{ -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -			break; - -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; - -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -			break; +			return ($style1['style_active']) ? -1 : 1;  		} - -		$l_element = strtoupper($element); - -		$chk_name = ($reqd_name !== false) ? $reqd_name : $name; - -		$sql = "SELECT {$element}_id, {$element}_name -			FROM $sql_from -			WHERE {$element}_name = '" . $db->sql_escape($chk_name) . "'"; -		$result = $db->sql_query($sql); - -		if ($row = $db->sql_fetchrow($result)) +		if (isset($style1['_available']) && $style1['_available'] != $style2['_available'])  		{ -			$name = $row[$element . '_name']; -			$id = $row[$element . '_id']; -		} -		else -		{ -			if (!($cfg = @file("$root_path$element/$element.cfg"))) -			{ -				$error[] = sprintf($user->lang['REQUIRES_' . $l_element], $reqd_name); -				return false; -			} - -			$cfg = parse_cfg_file("$root_path$element/$element.cfg", $cfg); - -			$name = $cfg['name']; -			$copyright = $cfg['copyright']; -			$id = 0; - -			unset($cfg); +			return ($style1['_available']) ? -1 : 1;  		} -		$db->sql_freeresult($result); +		return strcasecmp(isset($style1['style_name']) ? $style1['style_name'] : $style1['name'], isset($style2['style_name']) ? $style2['style_name'] : $style2['name']);  	}  	/** -	* Install/Add style +	* Read style configuration file +	* +	* @param string $dir style directory +	* @return array|bool Style data, false on error  	*/ -	function install_style(&$error, $action, $root_path, &$id, $name, $path, $copyright, $active, $default, &$style_row, $template_root_path = false, $template_path = false, $theme_root_path = false, $theme_path = false, $imageset_root_path = false, $imageset_path = false) +	protected function read_style_cfg($dir)  	{ -		global $config, $db, $user; - -		$element_ary = array('template', 'theme', 'imageset'); - -		if (!$name) -		{ -			$error[] = $user->lang['STYLE_ERR_STYLE_NAME']; -		} - -		// Check length settings -		if (utf8_strlen($name) > 30) -		{ -			$error[] = $user->lang['STYLE_ERR_NAME_LONG']; -		} - -		if (utf8_strlen($copyright) > 60) -		{ -			$error[] = $user->lang['STYLE_ERR_COPY_LONG']; -		} - -		// Check if the name already exist -		$sql = 'SELECT style_id -			FROM ' . STYLES_TABLE . " -			WHERE style_name = '" . $db->sql_escape($name) . "'"; -		$result = $db->sql_query($sql); -		$row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		if ($row) -		{ -			$error[] = $user->lang['STYLE_ERR_NAME_EXIST']; -		} - -		if (sizeof($error)) -		{ -			return false; -		} +		static $required = array('name', 'phpbb_version', 'copyright'); +		$cfg = parse_cfg_file($this->styles_path . $dir . '/style.cfg'); -		foreach ($element_ary as $element) +		// Check if it is a valid file +		foreach ($required as $key)  		{ -			// Zero id value ... need to install element ... run usual checks -			// and do the install if necessary -			if (!$style_row[$element . '_id']) +			if (!isset($cfg[$key]))  			{ -				$this->install_element($element, $error, $action, (${$element . '_root_path'}) ? ${$element . '_root_path'} : $root_path, $style_row[$element . '_id'], $style_row[$element . '_name'], (${$element . '_path'}) ? ${$element . '_path'} : $path, $style_row[$element . '_copyright']); +				return false;  			}  		} -		if (!$style_row['template_id'] || !$style_row['theme_id'] || !$style_row['imageset_id']) +		// Check data +		if (!isset($cfg['parent']) || !is_string($cfg['parent']) || $cfg['parent'] == $cfg['name'])  		{ -			$error[] = $user->lang['STYLE_ERR_NO_IDS']; +			$cfg['parent'] = '';  		} - -		if (sizeof($error)) +		if (!isset($cfg['template_bitfield']))  		{ -			return false; +			$cfg['template_bitfield'] = $this->default_bitfield();  		} -		$db->sql_transaction('begin'); - -		$sql_ary = array( -			'style_name'		=> $name, -			'style_copyright'	=> $copyright, -			'style_active'		=> (int) $active, -			'template_id'		=> (int) $style_row['template_id'], -			'theme_id'			=> (int) $style_row['theme_id'], -			'imageset_id'		=> (int) $style_row['imageset_id'], -		); - -		$sql = 'INSERT INTO ' . STYLES_TABLE . ' -			' . $db->sql_build_array('INSERT', $sql_ary); -		$db->sql_query($sql); - -		$id = $db->sql_nextid(); - -		if ($default) -		{ -			$sql = 'UPDATE ' . USERS_TABLE . " -				SET user_style = $id -				WHERE user_style = " . $config['default_style']; -			$db->sql_query($sql); - -			set_config('default_style', $id); -		} - -		$db->sql_transaction('commit'); - -		add_log('admin', 'LOG_STYLE_ADD', $name); +		return $cfg;  	}  	/** -	* Install/add an element, doing various checks as we go +	* Install style +	* +	* @param $style style data +	* @return int Style id  	*/ -	function install_element($mode, &$error, $action, $root_path, &$id, $name, $path, $copyright, $store_db = 0) +	protected function install_style($style)  	{ -		global $phpbb_root_path, $db, $user; - -		// we parse the cfg here (again) -		$cfg_data = parse_cfg_file("$root_path$mode/$mode.cfg"); - -		switch ($mode) -		{ -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -			break; - -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; - -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -			break; -		} - -		$l_type = strtoupper($mode); - -		if (!$name) -		{ -			$error[] = $user->lang[$l_type . '_ERR_STYLE_NAME']; -		} - -		// Check length settings -		if (utf8_strlen($name) > 30) -		{ -			$error[] = $user->lang[$l_type . '_ERR_NAME_LONG']; -		} - -		if (utf8_strlen($copyright) > 60) +		// Generate row +		$sql_ary = array(); +		foreach ($style as $key => $value)  		{ -			$error[] = $user->lang[$l_type . '_ERR_COPY_LONG']; -		} - -		// Check if the name already exist -		$sql = "SELECT {$mode}_id -			FROM $sql_from -			WHERE {$mode}_name = '" . $db->sql_escape($name) . "'"; -		$result = $db->sql_query($sql); -		$row = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		if ($row) -		{ -			// If it exist, we just use the style on installation -			if ($action == 'install') +			if ($key != 'style_id' && substr($key, 0, 1) != '_')  			{ -				$id = $row[$mode . '_id']; -				return false; +				$sql_ary[$key] = $value;  			} - -			$error[] = $user->lang[$l_type . '_ERR_NAME_EXIST']; -		} - -		if (isset($cfg_data['inherit_from']) && $cfg_data['inherit_from']) -		{ -			if ($mode === 'template') -			{ -				$select_bf = ', bbcode_bitfield'; -			} -			else -			{ -				$select_bf = ''; -			} - -			$sql = "SELECT {$mode}_id, {$mode}_name, {$mode}_path, {$mode}_storedb $select_bf -				FROM $sql_from -				WHERE {$mode}_name = '" . $db->sql_escape($cfg_data['inherit_from']) . "' -					AND {$mode}_inherits_id = 0"; -			$result = $db->sql_query($sql); -			$row = $db->sql_fetchrow($result); -			$db->sql_freeresult($result); -			if (!$row) -			{ -				$error[] = sprintf($user->lang[$l_type . '_ERR_REQUIRED_OR_INCOMPLETE'], $cfg_data['inherit_from']); -			} -			else -			{ -				$inherit_id = $row["{$mode}_id"]; -				$inherit_path = $row["{$mode}_path"]; -				$inherit_bf = ($mode === 'template') ? $row["bbcode_bitfield"] : false; -				$cfg_data['store_db'] = $row["{$mode}_storedb"]; -				$store_db = $row["{$mode}_storedb"]; -			} -		} -		else -		{ -			$inherit_id = 0; -			$inherit_path = ''; -			$inherit_bf = false; -		} - -		if (sizeof($error)) -		{ -			return false; -		} - -		$sql_ary = array( -			$mode . '_name'			=> $name, -			$mode . '_copyright'	=> $copyright, -			$mode . '_path'			=> $path, -		); - -		switch ($mode) -		{ -			case 'template': -				// We check if the template author defined a different bitfield -				if (!empty($cfg_data['template_bitfield'])) -				{ -					$sql_ary['bbcode_bitfield'] = $cfg_data['template_bitfield']; -				} -				else if ($inherit_bf) -				{ -					$sql_ary['bbcode_bitfield'] = $inherit_bf; -				} -				else -				{ -					$sql_ary['bbcode_bitfield'] = $this->template_bitfield; -				} - -				// We set a pre-defined bitfield here which we may use further in 3.2 -				$sql_ary += array( -					'template_storedb'		=> $store_db, -				); -				if (isset($cfg_data['inherit_from']) && $cfg_data['inherit_from']) -				{ -					$sql_ary += array( -						'template_inherits_id'	=> $inherit_id, -						'template_inherit_path' => $inherit_path, -					); -				} -			break; - -			case 'theme': -				// We are only interested in the theme configuration for now - -				if (isset($cfg_data['parse_css_file']) && $cfg_data['parse_css_file']) -				{ -					$store_db = 1; -				} - -				$sql_ary += array( -					'theme_storedb'	=> $store_db, -					'theme_data'	=> ($store_db) ? $this->db_theme_data($sql_ary, false, $root_path) : '', -					'theme_mtime'	=> (int) filemtime("{$phpbb_root_path}styles/$path/theme/stylesheet.css") -				); -			break; - -			// all the heavy lifting is done later -			case 'imageset': -			break; -		} - -		$db->sql_transaction('begin'); - -		$sql = "INSERT INTO $sql_from -			" . $db->sql_build_array('INSERT', $sql_ary); -		$db->sql_query($sql); - -		$id = $db->sql_nextid(); - -		if ($mode == 'template' && $store_db) -		{ -			$filelist = filelist("{$root_path}template", '', 'html'); -			$this->store_templates('insert', $id, $path, $filelist);  		} -		else if ($mode == 'imageset') -		{ -			$cfg_data = parse_cfg_file("$root_path$mode/imageset.cfg"); - -			$imageset_definitions = array(); -			foreach ($this->imageset_keys as $topic => $key_array) -			{ -				$imageset_definitions = array_merge($imageset_definitions, $key_array); -			} - -			foreach ($cfg_data as $key => $value) -			{ -				if (strpos($value, '*') !== false) -				{ -					if (substr($value, -1, 1) === '*') -					{ -						list($image_filename, $image_height) = explode('*', $value); -						$image_width = 0; -					} -					else -					{ -						list($image_filename, $image_height, $image_width) = explode('*', $value); -					} -				} -				else -				{ -					$image_filename = $value; -					$image_height = $image_width = 0; -				} -				if (strpos($key, 'img_') === 0 && $image_filename) -				{ -					$key = substr($key, 4); -					if (in_array($key, $imageset_definitions)) -					{ -						$sql_ary = array( -							'image_name'		=> $key, -							'image_filename'	=> str_replace('{PATH}', "styles/$path/imageset/", trim($image_filename)), -							'image_height'		=> (int) $image_height, -							'image_width'		=> (int) $image_width, -							'imageset_id'		=> (int) $id, -							'image_lang'		=> '', -						); -						$db->sql_query('INSERT INTO ' . STYLES_IMAGESET_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); -					} -				} -			} -			unset($cfg_data); +		// Add to database +		$this->db->sql_transaction('begin'); -			$sql = 'SELECT lang_dir -				FROM ' . LANG_TABLE; -			$result = $db->sql_query($sql); +		$sql = 'INSERT INTO ' . STYLES_TABLE . ' +			' . $this->db->sql_build_array('INSERT', $sql_ary); +		$this->db->sql_query($sql); -			while ($row = $db->sql_fetchrow($result)) -			{ -				if (@file_exists("$root_path$mode/{$row['lang_dir']}/imageset.cfg")) -				{ -					$cfg_data_imageset_data = parse_cfg_file("$root_path$mode/{$row['lang_dir']}/imageset.cfg"); -					foreach ($cfg_data_imageset_data as $image_name => $value) -					{ -						if (strpos($value, '*') !== false) -						{ -							if (substr($value, -1, 1) === '*') -							{ -								list($image_filename, $image_height) = explode('*', $value); -								$image_width = 0; -							} -							else -							{ -								list($image_filename, $image_height, $image_width) = explode('*', $value); -							} -						} -						else -						{ -							$image_filename = $value; -							$image_height = $image_width = 0; -						} +		$id = $this->db->sql_nextid(); -						if (strpos($image_name, 'img_') === 0 && $image_filename) -						{ -							$image_name = substr($image_name, 4); -							if (in_array($image_name, $imageset_definitions)) -							{ -								$sql_ary = array( -									'image_name'		=> $image_name, -									'image_filename'	=> $image_filename, -									'image_height'		=> (int) $image_height, -									'image_width'		=> (int) $image_width, -									'imageset_id'		=> (int) $id, -									'image_lang'		=> $row['lang_dir'], -								); -								$db->sql_query('INSERT INTO ' . STYLES_IMAGESET_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); -							} -						} -					} -					unset($cfg_data_imageset_data); -				} -			} -			$db->sql_freeresult($result); -		} - -		$db->sql_transaction('commit'); +		$this->db->sql_transaction('commit'); -		$log = ($store_db) ? 'LOG_' . $l_type . '_ADD_DB' : 'LOG_' . $l_type . '_ADD_FS'; -		add_log('admin', $log, $name); +		add_log('admin', 'LOG_STYLE_ADD', $sql_ary['style_name']); -		// Return store_db in case it had to be altered -		return $store_db; +		return $id;  	}  	/** -	* Checks downwards dependencies +	* Lists all styles  	* -	* @access public -	* @param string $mode The element type to check - only template is supported -	* @param int $id The template id -	* @returns false if no component inherits, array with name, path and id for each subtemplate otherwise +	* @return array Rows with styles data  	*/ -	function check_inheritance($mode, $id) +	protected function get_styles()  	{ -		global $db; - -		$l_type = strtoupper($mode); - -		switch ($mode) -		{ -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -			break; +		$sql = 'SELECT * +			FROM ' . STYLES_TABLE; +		$result = $this->db->sql_query($sql); -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; +		$rows = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -			break; -		} +		return $rows; +	} -		$sql = "SELECT {$mode}_id, {$mode}_name, {$mode}_path -			FROM $sql_from -			WHERE {$mode}_inherits_id = " . (int) $id; -		$result = $db->sql_query($sql); +	/** +	* Count users for each style +	* +	* @return array Styles in following format: [style_id] = number of users +	*/ +	protected function get_users() +	{ +		$sql = 'SELECT user_style, COUNT(user_style) AS style_count +			FROM ' . USERS_TABLE . ' +			GROUP BY user_style'; +		$result = $this->db->sql_query($sql); -		$names = array(); -		while ($row = $db->sql_fetchrow($result)) +		$style_count = array(); +		while ($row = $this->db->sql_fetchrow($result))  		{ - -			$names[$row["{$mode}_id"]] = array( -				"{$mode}_id" => $row["{$mode}_id"], -				"{$mode}_name" => $row["{$mode}_name"], -				"{$mode}_path" => $row["{$mode}_path"], -			); +			$style_count[$row['user_style']] = $row['style_count'];  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result); -		if (sizeof($names)) -		{ -			return $names; -		} -		else -		{ -			return false; -		} +		return $style_count;  	}  	/** -	* Checks upwards dependencies +	* Uninstall style  	* -	* @access public -	* @param string $mode The element type to check - only template is supported -	* @param int $id The template id -	* @returns false if the component does not inherit, array with name, path and id otherwise +	* @param array $style Style data +	* @return bool|string True on success, error message on error  	*/ -	function get_super($mode, $id) +	protected function uninstall_style($style)  	{ -		global $db; - -		$l_type = strtoupper($mode); - -		switch ($mode) -		{ -			case 'template': -				$sql_from = STYLES_TEMPLATE_TABLE; -			break; - -			case 'theme': -				$sql_from = STYLES_THEME_TABLE; -			break; +		$id = $style['style_id']; +		$path = $style['style_path']; -			case 'imageset': -				$sql_from = STYLES_IMAGESET_TABLE; -			break; -		} +		// Check if style has child styles +		$sql = 'SELECT style_id +			FROM ' . STYLES_TABLE . ' +			WHERE style_parent_id = ' . (int) $id . " OR style_parent_tree = '" . $this->db->sql_escape($path) . "'"; +		$result = $this->db->sql_query($sql); -		$sql = "SELECT {$mode}_inherits_id -			FROM $sql_from -			WHERE {$mode}_id = " . (int) $id; -		$result = $db->sql_query_limit($sql, 1); +		$conflict = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); -		if ($row = $db->sql_fetchrow($result)) -		{ -			$db->sql_freeresult($result); -		} -		else +		if ($conflict !== false)  		{ -			return false; +			return sprintf($this->user->lang['STYLE_UNINSTALL_DEPENDENT'], $style['style_name']);  		} -		$super_id = $row["{$mode}_inherits_id"]; - -		$sql = "SELECT {$mode}_id, {$mode}_name, {$mode}_path -			FROM $sql_from -			WHERE {$mode}_id = " . (int) $super_id; - -		$result = $db->sql_query_limit($sql, 1); -		if ($row = $db->sql_fetchrow($result)) -		{ -			$db->sql_freeresult($result); -			return $row; -		} +		// Change default style for users +		$sql = 'UPDATE ' . USERS_TABLE . ' +			SET user_style = 0 +			WHERE user_style = ' . $id; +		$this->db->sql_query($sql); -		return false; +		// Uninstall style +		$sql = 'DELETE FROM ' . STYLES_TABLE . ' +			WHERE style_id = ' . $id; +		$this->db->sql_query($sql); +		return true;  	}  	/** -	* Moves a template set and its subtemplates to the database +	* Delete all files in style directory  	* -	* @access public -	* @param string $mode The component to move - only template is supported -	* @param int $id The template id +	* @param string $path Style directory +	* @param string $dir Directory to remove inside style's directory +	* @return bool True on success, false on error  	*/ -	function store_in_db($mode, $id) +	protected function delete_style_files($path, $dir = '')  	{ -		global $db, $user; - -		$error = array(); -		$l_type = strtoupper($mode); -		if ($super = $this->get_super($mode, $id)) -		{ -			$error[] = (sprintf($user->lang["{$l_type}_INHERITS"], $super['template_name'])); -			return $error; -		} +		$dirname = $this->styles_path . $path . $dir; +		$result = true; -		$sql = "SELECT {$mode}_id, {$mode}_name, {$mode}_path -			FROM " . STYLES_TEMPLATE_TABLE . ' -			WHERE template_id = ' . (int) $id; +		$dp = @opendir($dirname); -		$result = $db->sql_query_limit($sql, 1); -		if ($row = $db->sql_fetchrow($result)) +		if ($dp)  		{ -			$db->sql_freeresult($result); -			$subs = $this->check_inheritance($mode, $id); - -			$this->_store_in_db($mode, $id, $row["{$mode}_path"]); -			if ($subs && sizeof($subs)) +			while (($file = readdir($dp)) !== false)  			{ -				foreach ($subs as $sub_id => $sub) +				if ($file == '.' || $file == '..') +				{ +					continue; +				} +				$filename = $dirname . '/' . $file; +				if (is_dir($filename))  				{ -					if ($err = $this->_store_in_db($mode, $sub["{$mode}_id"], $sub["{$mode}_path"])) +					if (!$this->delete_style_files($path, $dir . '/' . $file))  					{ -						$error[] = $err; +						$result = false; +					} +				} +				else +				{ +					if (!@unlink($filename)) +					{ +						$result = false;  					}  				}  			} +			closedir($dp);  		} -		if (sizeof($error)) +		if (!@rmdir($dirname))  		{ -			return $error; +			return false;  		} -		return false; -	} - -	/** -	* Moves a template set to the database -	* -	* @access private -	* @param string $mode The component to move - only template is supported -	* @param int $id The template id -	* @param string $path TThe path to the template files -	*/ -	function _store_in_db($mode, $id, $path) -	{ -		global $phpbb_root_path, $db; - -		$filelist = filelist("{$phpbb_root_path}styles/{$path}/template", '', 'html'); -		$this->store_templates('insert', $id, $path, $filelist); - -		// Okay, we do the query here -shouldn't be triggered often. -		$sql = 'UPDATE ' . STYLES_TEMPLATE_TABLE . ' -						SET template_storedb = 1 -						WHERE template_id = ' . $id; -		$db->sql_query($sql); +		return $result;  	}  	/** -	* Moves a template set and its subtemplates to the filesystem +	* Get list of items from posted data  	* -	* @access public -	* @param string $mode The component to move - only template is supported -	* @param int $id The template id +	* @param string $name Variable name +	* @param string|int $default Default value for array +	* @param bool $error If true, error will be triggered if list is empty +	* @return array Items  	*/ -	function store_in_fs($mode, $id) +	protected function request_vars($name, $default, $error = false)  	{ -		global $db, $user; +		$item = $this->request->variable($name, $default); +		$items = $this->request->variable($name . 's', array($default)); -		$error = array(); -		$l_type = strtoupper($mode); -		if ($super = $this->get_super($mode, $id)) +		if (count($items) == 1 && $items[0] == $default)  		{ -			$error[] = (sprintf($user->lang["{$l_type}_INHERITS"], $super['template_name'])); -			return($error); +			$items = array();  		} -		$sql = "SELECT {$mode}_id, {$mode}_name, {$mode}_path -			FROM " . STYLES_TEMPLATE_TABLE . ' -			WHERE template_id = ' . (int) $id; - -		$result = $db->sql_query_limit($sql, 1); -		if ($row = $db->sql_fetchrow($result)) +		if ($item != $default && !count($items))  		{ -			$db->sql_freeresult($result); -			if (!sizeof($error)) -			{ -				$subs = $this->check_inheritance($mode, $id); - -				$this->_store_in_fs($mode, $id, $row["{$mode}_path"]); +			$items[] = $item; +		} -				if ($subs && sizeof($subs)) -				{ -					foreach ($subs as $sub_id => $sub) -					{ -						$this->_store_in_fs($mode, $sub["{$mode}_id"], $sub["{$mode}_path"]); -					} -				} -			} -			if (sizeof($error)) -			{ -				$this->store_in_db($id, $mode); -				return $error; -			} +		if ($error && !count($items)) +		{ +			trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING);  		} -		return false; + +		return $items;  	}  	/** -	* Moves a template set to the filesystem +	* Generates default bitfield  	* -	* @access private -	* @param string $mode The component to move - only template is supported -	* @param int $id The template id -	* @param string $path The path to the template +	* This bitfield decides which bbcodes are defined in a template. +	* +	* @return string Bitfield  	*/ -	function _store_in_fs($mode, $id, $path) +	protected function default_bitfield()  	{ -		global $phpbb_root_path, $db, $user, $safe_mode; - -		$store_db = 0; -		$error = array(); -		if (!$safe_mode && phpbb_is_writable("{$phpbb_root_path}styles/{$path}/template")) +		static $value; +		if (isset($value))  		{ -			$sql = 'SELECT * -					FROM ' . STYLES_TEMPLATE_DATA_TABLE . " -					WHERE template_id = $id"; -			$result = $db->sql_query($sql); - -			while ($row = $db->sql_fetchrow($result)) -			{ -				if (!($fp = @fopen("{$phpbb_root_path}styles/{$path}/template/" . $row['template_filename'], 'wb'))) -				{ -					$store_db = 1; -					$error[] = $user->lang['EDIT_TEMPLATE_STORED_DB']; -					break; -				} - -				fwrite($fp, $row['template_data']); -				fclose($fp); -			} -			$db->sql_freeresult($result); - -			if (!$store_db) -			{ -				$sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . " -						WHERE template_id = $id"; -				$db->sql_query($sql); -			} -		} -		if (sizeof($error)) -		{ -			return $error; +			return $value;  		} -		$sql = 'UPDATE ' . STYLES_TEMPLATE_TABLE . ' -				SET template_storedb = 0 -				WHERE template_id = ' . $id; -		$db->sql_query($sql); -		return false; +		// Hardcoded template bitfield to add for new templates +		$bitfield = new bitfield(); +		$bitfield->set(0); +		$bitfield->set(1); +		$bitfield->set(2); +		$bitfield->set(3); +		$bitfield->set(4); +		$bitfield->set(8); +		$bitfield->set(9); +		$bitfield->set(11); +		$bitfield->set(12); +		$value = $bitfield->get_base64(); +		return $value;  	}  } diff --git a/phpBB/includes/acp/acp_update.php b/phpBB/includes/acp/acp_update.php index 41fb0884b7..f7f003781d 100644 --- a/phpBB/includes/acp/acp_update.php +++ b/phpBB/includes/acp/acp_update.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -37,7 +36,7 @@ class acp_update  		$errstr = '';  		$errno = 0; -		$info = obtain_latest_version_info(request_var('versioncheck_force', false), true); +		$info = obtain_latest_version_info(request_var('versioncheck_force', false));  		if ($info === false)  		{ @@ -69,12 +68,9 @@ class acp_update  		$current_version = (!empty($version_update_from)) ? $version_update_from : $config['version']; -		$up_to_date_automatic = (version_compare(str_replace('rc', 'RC', strtolower($current_version)), str_replace('rc', 'RC', strtolower($latest_version)), '<')) ? false : true; -		$up_to_date = (version_compare(str_replace('rc', 'RC', strtolower($config['version'])), str_replace('rc', 'RC', strtolower($latest_version)), '<')) ? false : true; -  		$template->assign_vars(array( -			'S_UP_TO_DATE'		=> $up_to_date, -			'S_UP_TO_DATE_AUTO'	=> $up_to_date_automatic, +			'S_UP_TO_DATE'		=> phpbb_version_compare($latest_version, $config['version'], '<='), +			'S_UP_TO_DATE_AUTO'	=> phpbb_version_compare($latest_version, $current_version, '<='),  			'S_VERSION_CHECK'	=> true,  			'U_ACTION'			=> $this->u_action,  			'U_VERSIONCHECK_FORCE' => append_sid($this->u_action . '&versioncheck_force=1'), diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 006c3617f7..82d8ef5cbb 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -33,6 +32,7 @@ class acp_users  	{  		global $config, $db, $user, $auth, $template, $cache;  		global $phpbb_root_path, $phpbb_admin_path, $phpEx, $table_prefix, $file_uploads; +		global $phpbb_dispatcher, $request;  		$user->add_lang(array('posting', 'ucp', 'acp/users'));  		$this->tpl_name = 'acp_users'; @@ -120,7 +120,7 @@ class acp_users  		// Build modes dropdown list  		$sql = 'SELECT module_mode, module_auth  			FROM ' . MODULES_TABLE . " -			WHERE module_basename = 'users' +			WHERE module_basename = 'acp_users'  				AND module_enabled = 1  				AND module_class = 'acp'  			ORDER BY left_id, module_mode"; @@ -129,7 +129,7 @@ class acp_users  		$dropdown_modes = array();  		while ($row = $db->sql_fetchrow($result))  		{ -			if (!$this->p_master->module_auth($row['module_auth'])) +			if (!$this->p_master->module_auth_self($row['module_auth']))  			{  				continue;  			} @@ -348,10 +348,7 @@ class acp_users  								$messenger->to($user_row['user_email'], $user_row['username']); -								$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -								$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -								$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -								$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +								$messenger->anti_abuse_headers($config, $user);  								$messenger->assign_vars(array(  									'WELCOME_MSG'	=> htmlspecialchars_decode(sprintf($user->lang['WELCOME_SUBJECT'], $config['sitename'])), @@ -406,10 +403,7 @@ class acp_users  									$messenger->to($user_row['user_email'], $user_row['username']); -									$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -									$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -									$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -									$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +									$messenger->anti_abuse_headers($config, $user);  									$messenger->assign_vars(array(  										'USERNAME'	=> htmlspecialchars_decode($user_row['username'])) @@ -756,6 +750,19 @@ class acp_users  							}  						break; + +						default: +							/** +							* Run custom quicktool code +							* +							* @event core.acp_users_overview_run_quicktool +							* @var	array	user_row	Current user data +							* @var	string	action		Quick tool that should be run +							* @since 3.1-A1 +							*/ +							$vars = array('action', 'user_row'); +							extract($phpbb_dispatcher->trigger_event('core.acp_users_overview_run_quicktool', compact($vars))); +						break;  					}  					// Handle registration info updates @@ -763,9 +770,8 @@ class acp_users  						'username'			=> utf8_normalize_nfc(request_var('user', $user_row['username'], true)),  						'user_founder'		=> request_var('user_founder', ($user_row['user_type'] == USER_FOUNDER) ? 1 : 0),  						'email'				=> strtolower(request_var('user_email', $user_row['user_email'])), -						'email_confirm'		=> strtolower(request_var('email_confirm', '')), -						'new_password'		=> request_var('new_password', '', true), -						'password_confirm'	=> request_var('password_confirm', '', true), +						'new_password'		=> $request->variable('new_password', '', true), +						'password_confirm'	=> $request->variable('password_confirm', '', true),  					);  					// Validation data - we do not check the password complexity setting here @@ -795,7 +801,6 @@ class acp_users  								array('string', false, 6, 60),  								array('email', $user_row['user_email'])  							), -							'email_confirm'		=> array('string', true, 6, 60)  						);  					} @@ -806,11 +811,6 @@ class acp_users  						$error[] = 'NEW_PASSWORD_ERROR';  					} -					if ($data['email'] != $user_row['user_email'] && $data['email_confirm'] != $data['email']) -					{ -						$error[] = 'NEW_EMAIL_ERROR'; -					} -  					if (!check_form_key($form_name))  					{  						$error[] = 'FORM_INVALID'; @@ -818,7 +818,7 @@ class acp_users  					// Which updates do we need to do?  					$update_username = ($user_row['username'] != $data['username']) ? $data['username'] : false; -					$update_password = ($data['new_password'] && !phpbb_check_hash($user_row['user_password'], $data['new_password'])) ? true : false; +					$update_password = ($data['new_password'] && !phpbb_check_hash($data['new_password'], $user_row['user_password'])) ? true : false;  					$update_email = ($data['email'] != $user_row['user_email']) ? $data['email'] : false;  					if (!sizeof($error)) @@ -869,6 +869,18 @@ class acp_users  							}  						} +						/** +						* Modify user data before we update it +						* +						* @event core.acp_users_overview_modify_data +						* @var	array	user_row	Current user data +						* @var	array	data		Submitted user data +						* @var	array	sql_ary		User data we udpate +						* @since 3.1-A1 +						*/ +						$vars = array('user_row', 'data', 'sql_ary'); +						extract($phpbb_dispatcher->trigger_event('core.acp_users_overview_modify_data', compact($vars))); +  						if ($update_username !== false)  						{  							$sql_ary['username'] = $update_username; @@ -959,12 +971,6 @@ class acp_users  					}  				} -				$s_action_options = '<option class="sep" value="">' . $user->lang['SELECT_OPTION'] . '</option>'; -				foreach ($quick_tool_ary as $value => $lang) -				{ -					$s_action_options .= '<option value="' . $value . '">' . $user->lang['USER_ADMIN_' . $lang] . '</option>'; -				} -  				if ($config['load_onlinetrack'])  				{  					$sql = 'SELECT MAX(session_time) AS session_time, MIN(session_viewonline) AS session_viewonline @@ -979,6 +985,23 @@ class acp_users  					unset($row);  				} +				/** +				* Add additional quick tool options and overwrite user data +				* +				* @event core.acp_users_display_overview +				* @var	array	user_row			Array with user data +				* @var	array	quick_tool_ary		Ouick tool options +				* @since 3.1-A1 +				*/ +				$vars = array('user_row', 'quick_tool_ary'); +				extract($phpbb_dispatcher->trigger_event('core.acp_users_display_overview', compact($vars))); + +				$s_action_options = '<option class="sep" value="">' . $user->lang['SELECT_OPTION'] . '</option>'; +				foreach ($quick_tool_ary as $value => $lang) +				{ +					$s_action_options .= '<option value="' . $value . '">' . $user->lang['USER_ADMIN_' . $lang] . '</option>'; +				} +  				$last_visit = (!empty($user_row['session_time'])) ? $user_row['session_time'] : $user_row['user_lastvisit'];  				$inactive_reason = ''; @@ -1015,9 +1038,16 @@ class acp_users  				$user_row['posts_in_queue'] = (int) $db->sql_fetchfield('posts_in_queue');  				$db->sql_freeresult($result); +				$sql = 'SELECT post_id +					FROM ' . POSTS_TABLE . ' +					WHERE poster_id = '. $user_id; +				$result = $db->sql_query_limit($sql, 1); +				$user_row['user_has_posts'] = (bool) $db->sql_fetchfield('post_id'); +				$db->sql_freeresult($result); +  				$template->assign_vars(array( -					'L_NAME_CHARS_EXPLAIN'		=> sprintf($user->lang[$config['allow_name_chars'] . '_EXPLAIN'], $config['min_name_chars'], $config['max_name_chars']), -					'L_CHANGE_PASSWORD_EXPLAIN'	=> sprintf($user->lang[$config['pass_complex'] . '_EXPLAIN'], $config['min_pass_chars'], $config['max_pass_chars']), +					'L_NAME_CHARS_EXPLAIN'		=> $user->lang($config['allow_name_chars'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_name_chars']), $user->lang('CHARACTERS', (int) $config['max_name_chars'])), +					'L_CHANGE_PASSWORD_EXPLAIN'	=> $user->lang($config['pass_complex'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_pass_chars']), $user->lang('CHARACTERS', (int) $config['max_pass_chars'])),  					'L_POSTS_IN_QUEUE'			=> $user->lang('NUM_POSTS_IN_QUEUE', $user_row['posts_in_queue']),  					'S_FOUNDER'					=> ($user->data['user_type'] == USER_FOUNDER) ? true : false, @@ -1042,6 +1072,7 @@ class acp_users  					'USER_EMAIL'		=> $user_row['user_email'],  					'USER_WARNINGS'		=> $user_row['user_warnings'],  					'USER_POSTS'		=> $user_row['user_posts'], +					'USER_HAS_POSTS'	=> $user_row['user_has_posts'],  					'USER_INACTIVE_REASON'	=> $inactive_reason,  				)); @@ -1124,12 +1155,14 @@ class acp_users  				// Grab log data  				$log_data = array();  				$log_count = 0; -				view_log('user', $log_data, $log_count, $config['topics_per_page'], $start, 0, 0, $user_id, $sql_where, $sql_sort); +				$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, @@ -1464,9 +1497,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']), @@ -1501,7 +1533,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), @@ -1537,7 +1569,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'], @@ -1568,7 +1599,7 @@ class acp_users  								|| $user_row['user_allow_viewonline'] && !$sql_ary['user_allow_viewonline'])  							{  								// We also need to check if the user has the permission to cloak. -								$user_auth = new auth(); +								$user_auth = new phpbb_auth();  								$user_auth->acl($user_row);  								$session_sql_ary = array( @@ -1647,6 +1678,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, @@ -1660,7 +1692,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'], @@ -1687,7 +1718,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'],  					)  				); @@ -1754,8 +1786,8 @@ class acp_users  					'USER_AVATAR_WIDTH'		=> $user_row['user_avatar_width'],  					'USER_AVATAR_HEIGHT'	=> $user_row['user_avatar_height'], -					'L_AVATAR_EXPLAIN'	=> sprintf($user->lang['AVATAR_EXPLAIN'], $config['avatar_max_width'], $config['avatar_max_height'], round($config['avatar_filesize'] / 1024))) -				); +					'L_AVATAR_EXPLAIN'	=> phpbb_avatar_explanation_string(), +				));  			break; @@ -1887,7 +1919,7 @@ class acp_users  					'FLASH_STATUS'			=> ($config['allow_sig_flash']) ? $user->lang['FLASH_IS_ON'] : $user->lang['FLASH_IS_OFF'],  					'URL_STATUS'			=> ($config['allow_sig_links']) ? $user->lang['URL_IS_ON'] : $user->lang['URL_IS_OFF'], -					'L_SIGNATURE_EXPLAIN'	=> sprintf($user->lang['SIGNATURE_EXPLAIN'], $config['max_sig_chars']), +					'L_SIGNATURE_EXPLAIN'	=> $user->lang('SIGNATURE_EXPLAIN', (int) $config['max_sig_chars']),  					'S_BBCODE_ALLOWED'		=> $config['allow_sig_bbcode'],  					'S_SMILIES_ALLOWED'		=> $config['allow_sig_smilies'], @@ -1948,7 +1980,7 @@ class acp_users  						$message = (sizeof($log_attachments) == 1) ? $user->lang['ATTACHMENT_DELETED'] : $user->lang['ATTACHMENTS_DELETED']; -						add_log('admin', 'LOG_ATTACHMENTS_DELETED', implode(', ', $log_attachments)); +						add_log('admin', 'LOG_ATTACHMENTS_DELETED', implode($user->lang['COMMA_SEPARATOR'], $log_attachments));  						trigger_error($message . adm_back_link($this->u_action . '&u=' . $user_id));  					}  					else @@ -2041,14 +2073,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; @@ -2345,46 +2378,61 @@ class acp_users  	}  	/** -	* Optionset replacement for this module based on $user->optionset +	* Set option bit field for user options in a user row array. +	* +	* Optionset replacement for this module based on $user->optionset. +	* +	* @param array $user_row Row from the users table. +	* @param int $key Option key, as defined in $user->keyoptions property. +	* @param bool $value True to set the option, false to clear the option. +	* @param int $data Current bit field value, or false to use $user_row['user_options'] +	* @return int|bool If $data is false, the bit field is modified and +	*                  written back to $user_row['user_options'], and +	*                  return value is true if the bit field changed and +	*                  false otherwise. If $data is not false, the new +	*                  bitfield value is returned.  	*/  	function optionset(&$user_row, $key, $value, $data = false)  	{  		global $user; -		$var = ($data) ? $data : $user_row['user_options']; +		$var = ($data !== false) ? $data : $user_row['user_options']; -		if ($value && !($var & 1 << $user->keyoptions[$key])) -		{ -			$var += 1 << $user->keyoptions[$key]; -		} -		else if (!$value && ($var & 1 << $user->keyoptions[$key])) -		{ -			$var -= 1 << $user->keyoptions[$key]; -		} -		else -		{ -			return ($data) ? $var : false; -		} +		$new_var = phpbb_optionset($user->keyoptions[$key], $value, $var); -		if (!$data) +		if ($data === false)  		{ -			$user_row['user_options'] = $var; -			return true; +			if ($new_var != $var) +			{ +				$user_row['user_options'] = $new_var; +				return true; +			} +			else +			{ +				return false; +			}  		}  		else  		{ -			return $var; +			return $new_var;  		}  	}  	/** -	* Optionget replacement for this module based on $user->optionget +	* Get option bit field from user options in a user row array. +	* +	* Optionget replacement for this module based on $user->optionget. +	* +	* @param array $user_row Row from the users table. +	* @param int $key option key, as defined in $user->keyoptions property. +	* @param int $data bit field value to use, or false to use $user_row['user_options'] +	* @return bool true if the option is set in the bit field, false otherwise  	*/  	function optionget(&$user_row, $key, $data = false)  	{  		global $user; -		$var = ($data) ? $data : $user_row['user_options']; -		return ($var & 1 << $user->keyoptions[$key]) ? true : false; +		$var = ($data !== false) ? $data : $user_row['user_options']; +		return phpbb_optionget($user->keyoptions[$key], $var);  	}  } diff --git a/phpBB/includes/acp/acp_words.php b/phpBB/includes/acp/acp_words.php index 450a2fad4c..d8d14ba4ad 100644 --- a/phpBB/includes/acp/acp_words.php +++ b/phpBB/includes/acp/acp_words.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/auth.php b/phpBB/includes/acp/auth.php index e0e07e51eb..6b1da46a12 100644 --- a/phpBB/includes/acp/auth.php +++ b/phpBB/includes/acp/auth.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -20,7 +19,7 @@ if (!defined('IN_PHPBB'))  * ACP Permission/Auth class  * @package phpBB3  */ -class auth_admin extends auth +class auth_admin extends phpbb_auth  {  	/**  	* Init auth settings @@ -131,7 +130,7 @@ class auth_admin extends auth  			{  				if ($user->data['user_id'] != $userdata['user_id'])  				{ -					$auth2 = new auth(); +					$auth2 = new phpbb_auth();  					$auth2->acl($userdata);  				}  				else @@ -530,8 +529,8 @@ class auth_admin extends auth  					'NAME'			=> $ug_name,  					'CATEGORIES'	=> implode('</th><th>', $categories), -					'USER_GROUPS_DEFAULT'	=> ($user_mode == 'user' && isset($user_groups_default[$ug_id]) && sizeof($user_groups_default[$ug_id])) ? implode(', ', $user_groups_default[$ug_id]) : '', -					'USER_GROUPS_CUSTOM'	=> ($user_mode == 'user' && isset($user_groups_custom[$ug_id]) && sizeof($user_groups_custom[$ug_id])) ? implode(', ', $user_groups_custom[$ug_id]) : '', +					'USER_GROUPS_DEFAULT'	=> ($user_mode == 'user' && isset($user_groups_default[$ug_id]) && sizeof($user_groups_default[$ug_id])) ? implode($user->lang['COMMA_SEPARATOR'], $user_groups_default[$ug_id]) : '', +					'USER_GROUPS_CUSTOM'	=> ($user_mode == 'user' && isset($user_groups_custom[$ug_id]) && sizeof($user_groups_custom[$ug_id])) ? implode($user->lang['COMMA_SEPARATOR'], $user_groups_custom[$ug_id]) : '',  					'L_ACL_TYPE'			=> $l_acl_type,  					'S_LOCAL'		=> ($local) ? true : false, diff --git a/phpBB/includes/acp/info/acp_attachments.php b/phpBB/includes/acp/info/acp_attachments.php index 4bcd7e2ea5..8fad241451 100644 --- a/phpBB/includes/acp/info/acp_attachments.php +++ b/phpBB/includes/acp/info/acp_attachments.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_ban.php b/phpBB/includes/acp/info/acp_ban.php index 7db7cf1371..37f0f021a7 100644 --- a/phpBB/includes/acp/info/acp_ban.php +++ b/phpBB/includes/acp/info/acp_ban.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_bbcodes.php b/phpBB/includes/acp/info/acp_bbcodes.php index f5a3cfe10f..5c88ca8a0f 100644 --- a/phpBB/includes/acp/info/acp_bbcodes.php +++ b/phpBB/includes/acp/info/acp_bbcodes.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_board.php b/phpBB/includes/acp/info/acp_board.php index 5f73241012..50d5a4f4e1 100644 --- a/phpBB/includes/acp/info/acp_board.php +++ b/phpBB/includes/acp/info/acp_board.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_bots.php b/phpBB/includes/acp/info/acp_bots.php index 6dce4b352b..c30ab588ab 100644 --- a/phpBB/includes/acp/info/acp_bots.php +++ b/phpBB/includes/acp/info/acp_bots.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_captcha.php b/phpBB/includes/acp/info/acp_captcha.php index cfeef20e34..3f31b4c102 100644 --- a/phpBB/includes/acp/info/acp_captcha.php +++ b/phpBB/includes/acp/info/acp_captcha.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_database.php b/phpBB/includes/acp/info/acp_database.php index 7d57dd4974..c8ad65e255 100644 --- a/phpBB/includes/acp/info/acp_database.php +++ b/phpBB/includes/acp/info/acp_database.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_disallow.php b/phpBB/includes/acp/info/acp_disallow.php index 93ec5633a6..f9dd4c32c0 100644 --- a/phpBB/includes/acp/info/acp_disallow.php +++ b/phpBB/includes/acp/info/acp_disallow.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_email.php b/phpBB/includes/acp/info/acp_email.php index eb16ba05f1..620904c956 100644 --- a/phpBB/includes/acp/info/acp_email.php +++ b/phpBB/includes/acp/info/acp_email.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_extensions.php b/phpBB/includes/acp/info/acp_extensions.php new file mode 100644 index 0000000000..f5953fb1dd --- /dev/null +++ b/phpBB/includes/acp/info/acp_extensions.php @@ -0,0 +1,34 @@ +<?php +/** +* +* @package acp +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @package module_install +*/ +class acp_extensions_info +{ +	function module() +	{ +		return array( +			'filename'	=> 'acp_extensions', +			'title'		=> 'ACP_EXTENSIONS', +			'version'	=> '1.0.0', +			'modes'		=> array( +				'main'		=> array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_GENERAL_TASKS')), +			), +		); +	} + +	function install() +	{ +	} + +	function uninstall() +	{ +	} +} diff --git a/phpBB/includes/acp/info/acp_forums.php b/phpBB/includes/acp/info/acp_forums.php index 7d4b04d384..e5281a4e58 100644 --- a/phpBB/includes/acp/info/acp_forums.php +++ b/phpBB/includes/acp/info/acp_forums.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_groups.php b/phpBB/includes/acp/info/acp_groups.php index 36e8793007..af3f4893fd 100644 --- a/phpBB/includes/acp/info/acp_groups.php +++ b/phpBB/includes/acp/info/acp_groups.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_icons.php b/phpBB/includes/acp/info/acp_icons.php index e4939aad2a..e0cf05660c 100644 --- a/phpBB/includes/acp/info/acp_icons.php +++ b/phpBB/includes/acp/info/acp_icons.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_inactive.php b/phpBB/includes/acp/info/acp_inactive.php index 1453c51c50..02b1fcdaa2 100644 --- a/phpBB/includes/acp/info/acp_inactive.php +++ b/phpBB/includes/acp/info/acp_inactive.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_jabber.php b/phpBB/includes/acp/info/acp_jabber.php index 2259b78e19..3ad05e1a6a 100644 --- a/phpBB/includes/acp/info/acp_jabber.php +++ b/phpBB/includes/acp/info/acp_jabber.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_language.php b/phpBB/includes/acp/info/acp_language.php index 40eadd0abf..85dfb119ea 100644 --- a/phpBB/includes/acp/info/acp_language.php +++ b/phpBB/includes/acp/info/acp_language.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_logs.php b/phpBB/includes/acp/info/acp_logs.php index a28ff57bf3..033f9baf50 100644 --- a/phpBB/includes/acp/info/acp_logs.php +++ b/phpBB/includes/acp/info/acp_logs.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_main.php b/phpBB/includes/acp/info/acp_main.php index 2e9969d2be..4c1cb6dc0f 100644 --- a/phpBB/includes/acp/info/acp_main.php +++ b/phpBB/includes/acp/info/acp_main.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_modules.php b/phpBB/includes/acp/info/acp_modules.php index 6cda51ed9c..c9d2cffa72 100644 --- a/phpBB/includes/acp/info/acp_modules.php +++ b/phpBB/includes/acp/info/acp_modules.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_permission_roles.php b/phpBB/includes/acp/info/acp_permission_roles.php index 47496e3859..ee2a3ee560 100644 --- a/phpBB/includes/acp/info/acp_permission_roles.php +++ b/phpBB/includes/acp/info/acp_permission_roles.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_permissions.php b/phpBB/includes/acp/info/acp_permissions.php index 59d1b449f0..7b51b67a96 100644 --- a/phpBB/includes/acp/info/acp_permissions.php +++ b/phpBB/includes/acp/info/acp_permissions.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_php_info.php b/phpBB/includes/acp/info/acp_php_info.php index a131581750..a456e4b8b7 100644 --- a/phpBB/includes/acp/info/acp_php_info.php +++ b/phpBB/includes/acp/info/acp_php_info.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_profile.php b/phpBB/includes/acp/info/acp_profile.php index 9575477333..6fa673b094 100644 --- a/phpBB/includes/acp/info/acp_profile.php +++ b/phpBB/includes/acp/info/acp_profile.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_prune.php b/phpBB/includes/acp/info/acp_prune.php index c688776380..7498e46cad 100644 --- a/phpBB/includes/acp/info/acp_prune.php +++ b/phpBB/includes/acp/info/acp_prune.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_ranks.php b/phpBB/includes/acp/info/acp_ranks.php index 12afeb041b..651a86471d 100644 --- a/phpBB/includes/acp/info/acp_ranks.php +++ b/phpBB/includes/acp/info/acp_ranks.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_reasons.php b/phpBB/includes/acp/info/acp_reasons.php index 5bc1310088..9f8f2ced77 100644 --- a/phpBB/includes/acp/info/acp_reasons.php +++ b/phpBB/includes/acp/info/acp_reasons.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_search.php b/phpBB/includes/acp/info/acp_search.php index 979d84e63a..494d8afd67 100644 --- a/phpBB/includes/acp/info/acp_search.php +++ b/phpBB/includes/acp/info/acp_search.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_send_statistics.php b/phpBB/includes/acp/info/acp_send_statistics.php index 93efa99b6d..07e7f3ba5c 100644 --- a/phpBB/includes/acp/info/acp_send_statistics.php +++ b/phpBB/includes/acp/info/acp_send_statistics.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_styles.php b/phpBB/includes/acp/info/acp_styles.php index d6afd53cef..3137c4781b 100644 --- a/phpBB/includes/acp/info/acp_styles.php +++ b/phpBB/includes/acp/info/acp_styles.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -18,12 +17,11 @@ class acp_styles_info  		return array(  			'filename'	=> 'acp_styles',  			'title'		=> 'ACP_CAT_STYLES', -			'version'	=> '1.0.0', +			'version'	=> '2.0.0',  			'modes'		=> array(  				'style'		=> array('title' => 'ACP_STYLES', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_MANAGEMENT')), -				'template'	=> array('title' => 'ACP_TEMPLATES', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_COMPONENTS')), -				'theme'		=> array('title' => 'ACP_THEMES', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_COMPONENTS')), -				'imageset'	=> array('title' => 'ACP_IMAGESETS', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_COMPONENTS')), +				'install'	=> array('title' => 'ACP_STYLES_INSTALL', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_MANAGEMENT')), +				'cache'		=> array('title' => 'ACP_STYLES_CACHE', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_MANAGEMENT')),  			),  		);  	} diff --git a/phpBB/includes/acp/info/acp_update.php b/phpBB/includes/acp/info/acp_update.php index 8ecb062d4e..3d491216a8 100644 --- a/phpBB/includes/acp/info/acp_update.php +++ b/phpBB/includes/acp/info/acp_update.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_users.php b/phpBB/includes/acp/info/acp_users.php index a63480d0f0..1848622a1c 100644 --- a/phpBB/includes/acp/info/acp_users.php +++ b/phpBB/includes/acp/info/acp_users.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/acp/info/acp_words.php b/phpBB/includes/acp/info/acp_words.php index 4a2ddfea7f..48cb3fbdd1 100644 --- a/phpBB/includes/acp/info/acp_words.php +++ b/phpBB/includes/acp/info/acp_words.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/auth.php b/phpBB/includes/auth/auth.php index 22aca5faf9..e3bccaf47b 100644 --- a/phpBB/includes/auth.php +++ b/phpBB/includes/auth/auth.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -20,7 +19,7 @@ if (!defined('IN_PHPBB'))  * Permission/Auth class  * @package phpBB3  */ -class auth +class phpbb_auth  {  	var $acl = array();  	var $cache = array(); @@ -349,6 +348,14 @@ class auth  	/**  	* Get permission listing based on user_id/options/forum_ids +	* +	* Be careful when using this function with permissions a_, m_, u_ and f_ ! +	* It may not work correctly. When a user group grants an a_* permission, +	* e.g. a_foo, but the user's a_foo permission is set to "Never", then +	* the user does not in fact have the a_ permission. +	* But the user will still be listed as having the a_ permission. +	* +	* For more information see: http://tracker.phpbb.com/browse/PHPBB3-10252  	*/  	function acl_get_list($user_id = false, $opts = false, $forum_id = false)  	{ @@ -908,7 +915,7 @@ class auth  		$method = 'login_' . $method;  		if (function_exists($method))  		{ -			$login = $method($username, $password); +			$login = $method($username, $password, $user->ip, $user->browser, $user->forwarded_for);  			// If the auth module wants us to create an empty profile do so and then treat the status as LOGIN_SUCCESS  			if ($login['status'] == LOGIN_SUCCESS_CREATE_PROFILE) diff --git a/phpBB/includes/auth/auth_apache.php b/phpBB/includes/auth/auth_apache.php index a148403c6f..10b288aa09 100644 --- a/phpBB/includes/auth/auth_apache.php +++ b/phpBB/includes/auth/auth_apache.php @@ -5,9 +5,8 @@  * Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him.  *  * @package login -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -28,9 +27,9 @@ if (!defined('IN_PHPBB'))  */  function init_apache()  { -	global $user; +	global $user, $request; -	if (!isset($_SERVER['PHP_AUTH_USER']) || $user->data['username'] !== $_SERVER['PHP_AUTH_USER']) +	if (!$request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER) || $user->data['username'] !== htmlspecialchars_decode($request->server('PHP_AUTH_USER')))  	{  		return $user->lang['APACHE_SETUP_BEFORE_USE'];  	} @@ -42,7 +41,7 @@ function init_apache()  */  function login_apache(&$username, &$password)  { -	global $db; +	global $db, $request;  	// do not allow empty password  	if (!$password) @@ -63,7 +62,7 @@ function login_apache(&$username, &$password)  		);  	} -	if (!isset($_SERVER['PHP_AUTH_USER'])) +	if (!$request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER))  	{  		return array(  			'status'		=> LOGIN_ERROR_EXTERNAL_AUTH, @@ -72,8 +71,8 @@ function login_apache(&$username, &$password)  		);  	} -	$php_auth_user = $_SERVER['PHP_AUTH_USER']; -	$php_auth_pw = $_SERVER['PHP_AUTH_PW']; +	$php_auth_user = htmlspecialchars_decode($request->server('PHP_AUTH_USER')); +	$php_auth_pw = htmlspecialchars_decode($request->server('PHP_AUTH_PW'));  	if (!empty($php_auth_user) && !empty($php_auth_pw))  	{ @@ -136,15 +135,15 @@ function login_apache(&$username, &$password)  */  function autologin_apache()  { -	global $db; +	global $db, $request; -	if (!isset($_SERVER['PHP_AUTH_USER'])) +	if (!$request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER))  	{  		return array();  	} -	$php_auth_user = $_SERVER['PHP_AUTH_USER']; -	$php_auth_pw = $_SERVER['PHP_AUTH_PW']; +	$php_auth_user = htmlspecialchars_decode($request->server('PHP_AUTH_USER')); +	$php_auth_pw = htmlspecialchars_decode($request->server('PHP_AUTH_PW'));  	if (!empty($php_auth_user) && !empty($php_auth_pw))  	{ @@ -228,11 +227,12 @@ function user_row_apache($username, $password)  */  function validate_session_apache(&$user)  { +	global $request; +  	// Check if PHP_AUTH_USER is set and handle this case -	if (isset($_SERVER['PHP_AUTH_USER'])) +	if ($request->is_set('PHP_AUTH_USER', phpbb_request_interface::SERVER))  	{ -		$php_auth_user = ''; -		set_var($php_auth_user, $_SERVER['PHP_AUTH_USER'], 'string', true); +		$php_auth_user = $request->server('PHP_AUTH_USER');  		return ($php_auth_user === $user['username']) ? true : false;  	} diff --git a/phpBB/includes/auth/auth_db.php b/phpBB/includes/auth/auth_db.php index 6304d6e49a..ac944532a5 100644 --- a/phpBB/includes/auth/auth_db.php +++ b/phpBB/includes/auth/auth_db.php @@ -7,9 +7,8 @@  * This is for authentication via the integrated user table  *  * @package login -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -23,12 +22,29 @@ if (!defined('IN_PHPBB'))  /**  * Login function +* +* @param string $username +* @param string $password +* @param string $ip			IP address the login is taking place from. Used to +*							limit the number of login attempts per IP address. +* @param string $browser	The user agent used to login +* @param string $forwarded_for X_FORWARDED_FOR header sent with login request +* @return array				A associative array of the format +*							array( +*								'status' => status constant +*								'error_msg' => string +*								'user_row' => array +*							)  */ -function login_db(&$username, &$password) +function login_db($username, $password, $ip = '', $browser = '', $forwarded_for = '')  {  	global $db, $config;  	global $request; +	// Auth plugins get the password untrimmed. +	// For compatibility we trim() here. +	$password = trim($password); +  	// do not allow empty password  	if (!$password)  	{ @@ -48,22 +64,71 @@ function login_db(&$username, &$password)  		);  	} +	$username_clean = utf8_clean_string($username); +  	$sql = 'SELECT user_id, username, user_password, user_passchg, user_pass_convert, user_email, user_type, user_login_attempts  		FROM ' . USERS_TABLE . " -		WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; +		WHERE username_clean = '" . $db->sql_escape($username_clean) . "'";  	$result = $db->sql_query($sql);  	$row = $db->sql_fetchrow($result);  	$db->sql_freeresult($result); +	if (($ip && !$config['ip_login_limit_use_forwarded']) || +		($forwarded_for && $config['ip_login_limit_use_forwarded'])) +	{ +		$sql = 'SELECT COUNT(*) AS attempts +			FROM ' . LOGIN_ATTEMPT_TABLE . ' +			WHERE attempt_time > ' . (time() - (int) $config['ip_login_limit_time']); +		if ($config['ip_login_limit_use_forwarded']) +		{ +			$sql .= " AND attempt_forwarded_for = '" . $db->sql_escape($forwarded_for) . "'"; +		} +		else +		{ +			$sql .= " AND attempt_ip = '" . $db->sql_escape($ip) . "' "; +		} + +		$result = $db->sql_query($sql); +		$attempts = (int) $db->sql_fetchfield('attempts'); +		$db->sql_freeresult($result); + +		$attempt_data = array( +			'attempt_ip'			=> $ip, +			'attempt_browser'		=> trim(substr($browser, 0, 149)), +			'attempt_forwarded_for'	=> $forwarded_for, +			'attempt_time'			=> time(), +			'user_id'				=> ($row) ? (int) $row['user_id'] : 0, +			'username'				=> $username, +			'username_clean'		=> $username_clean, +		); +		$sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $db->sql_build_array('INSERT', $attempt_data); +		$result = $db->sql_query($sql); +	} +	else +	{ +		$attempts = 0; +	} +  	if (!$row)  	{ +		if ($config['ip_login_limit_max'] && $attempts >= $config['ip_login_limit_max']) +		{ +			return array( +				'status'		=> LOGIN_ERROR_ATTEMPTS, +				'error_msg'		=> 'LOGIN_ERROR_ATTEMPTS', +				'user_row'		=> array('user_id' => ANONYMOUS), +			); +		} +  		return array(  			'status'	=> LOGIN_ERROR_USERNAME,  			'error_msg'	=> 'LOGIN_ERROR_USERNAME',  			'user_row'	=> array('user_id' => ANONYMOUS),  		);  	} -	$show_captcha = $config['max_login_attempts'] && $row['user_login_attempts'] >= $config['max_login_attempts']; + +	$show_captcha = ($config['max_login_attempts'] && $row['user_login_attempts'] >= $config['max_login_attempts']) || +		($config['ip_login_limit_max'] && $attempts >= $config['ip_login_limit_max']);  	// If there are too much login attempts, we need to check for an confirm image  	// Every auth module is able to define what to do by itself... @@ -76,7 +141,7 @@ function login_db(&$username, &$password)  			include ($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx);  		} -		$captcha =& phpbb_captcha_factory::get_instance($config['captcha_plugin']); +		$captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']);  		$captcha->init(CONFIRM_LOGIN);  		$vc_response = $captcha->validate($row);  		if ($vc_response) @@ -177,6 +242,10 @@ function login_db(&$username, &$password)  			$row['user_password'] = $hash;  		} +		$sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' +			WHERE user_id = ' . $row['user_id']; +		$db->sql_query($sql); +  		if ($row['user_login_attempts'] != 0)  		{  			// Successful, reset login attempts (the user passed all stages) diff --git a/phpBB/includes/auth/auth_ldap.php b/phpBB/includes/auth/auth_ldap.php index 4f311797b2..26029efe1e 100644 --- a/phpBB/includes/auth/auth_ldap.php +++ b/phpBB/includes/auth/auth_ldap.php @@ -6,9 +6,8 @@  * Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him.  *  * @package login -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -156,7 +155,11 @@ function login_ldap(&$username, &$password)  	{  		if (!@ldap_bind($ldap, htmlspecialchars_decode($config['ldap_user']), htmlspecialchars_decode($config['ldap_password'])))  		{ -			return $user->lang['LDAP_NO_SERVER_CONNECTION']; +			return array( +				'status'		=> LOGIN_ERROR_EXTERNAL_AUTH, +				'error_msg'		=> 'LDAP_NO_SERVER_CONNECTION', +				'user_row'		=> array('user_id' => ANONYMOUS), +			);  		}  	} @@ -335,7 +338,7 @@ function acp_ldap(&$new)  	</dl>  	<dl>  		<dt><label for="ldap_password">' . $user->lang['LDAP_PASSWORD'] . ':</label><br /><span>' . $user->lang['LDAP_PASSWORD_EXPLAIN'] . '</span></dt> -		<dd><input type="password" id="ldap_password" size="40" name="config[ldap_password]" value="' . $new['ldap_password'] . '" /></dd> +		<dd><input type="password" id="ldap_password" size="40" name="config[ldap_password]" value="' . $new['ldap_password'] . '" autocomplete="off" /></dd>  	</dl>  	'; diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index a360bcd5d1..b9ffa8091c 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -30,7 +29,6 @@ class bbcode  	var $bbcodes = array();  	var $template_bitfield; -	var $template_filename = '';  	/**  	* Constructor @@ -128,28 +126,19 @@ class bbcode  	*/  	function bbcode_cache_init()  	{ -		global $phpbb_root_path, $template, $user; +		global $phpbb_root_path, $phpEx, $config, $user, $phpbb_extension_manager;  		if (empty($this->template_filename))  		{ -			$this->template_bitfield = new bitfield($user->theme['bbcode_bitfield']); -			$this->template_filename = $phpbb_root_path . 'styles/' . $user->theme['template_path'] . '/template/bbcode.html'; - -			if (!@file_exists($this->template_filename)) -			{ -				if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id']) -				{ -					$this->template_filename = $phpbb_root_path . 'styles/' . $user->theme['template_inherit_path'] . '/template/bbcode.html'; -					if (!@file_exists($this->template_filename)) -					{ -						trigger_error('The file ' . $this->template_filename . ' is missing.', E_USER_ERROR); -					} -				} -				else -				{ -					trigger_error('The file ' . $this->template_filename . ' is missing.', E_USER_ERROR); -				} -			} +			$this->template_bitfield = new bitfield($user->style['bbcode_bitfield']); + +			$style_resource_locator = new phpbb_style_resource_locator(); +			$style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider()); +			$template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context()); +			$style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $template); +			$style->set_style(); +			$template->set_filenames(array('bbcode.html' => 'bbcode.html')); +			$this->template_filename = $style_resource_locator->get_source_file_for_handle('bbcode.html');  		}  		$bbcode_ids = $rowset = $sql = array(); @@ -584,6 +573,13 @@ class bbcode  				$code = str_replace("\t", '   ', $code);  				$code = str_replace('  ', '  ', $code);  				$code = str_replace('  ', '  ', $code); +				$code = str_replace("\n ", "\n ", $code); + +				// keep space at the beginning +				if (!empty($code) && $code[0] == ' ') +				{ +					$code = ' ' . substr($code, 1); +				}  				// remove newline at the beginning  				if (!empty($code) && $code[0] == "\n") diff --git a/phpBB/includes/cache/driver/apc.php b/phpBB/includes/cache/driver/apc.php index a97cbe4dd1..dc0144fac3 100644 --- a/phpBB/includes/cache/driver/apc.php +++ b/phpBB/includes/cache/driver/apc.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2005, 2009 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/cache/driver/base.php b/phpBB/includes/cache/driver/base.php index a71eca45d7..32e04f813a 100644 --- a/phpBB/includes/cache/driver/base.php +++ b/phpBB/includes/cache/driver/base.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/cache/driver/eaccelerator.php b/phpBB/includes/cache/driver/eaccelerator.php index b2dd37dbb5..7939f043c9 100644 --- a/phpBB/includes/cache/driver/eaccelerator.php +++ b/phpBB/includes/cache/driver/eaccelerator.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2005, 2009 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index 73eaff25bd..f64a9e3ea8 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2005, 2009 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -365,7 +364,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base  	/**  	* Save sql query  	*/ -	function sql_save($query, &$query_result, $ttl) +	function sql_save($query, $query_result, $ttl)  	{  		global $db; @@ -386,6 +385,8 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base  		{  			$query_result = $query_id;  		} + +		return $query_id;  	}  	/** diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php index 91d364abf6..847ba97262 100644 --- a/phpBB/includes/cache/driver/interface.php +++ b/phpBB/includes/cache/driver/interface.php @@ -3,7 +3,7 @@  *  * @package acm  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -75,7 +75,7 @@ interface phpbb_cache_driver_interface  	/**  	* Save sql query  	*/ -	public function sql_save($query, &$query_result, $ttl); +	public function sql_save($query, $query_result, $ttl);  	/**  	* Ceck if a given sql query exist in cache diff --git a/phpBB/includes/cache/driver/memcache.php b/phpBB/includes/cache/driver/memcache.php index 46ba51049f..9fe70f8b44 100644 --- a/phpBB/includes/cache/driver/memcache.php +++ b/phpBB/includes/cache/driver/memcache.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2005, 2009 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index 633a0fe699..e0771ab1d3 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -20,7 +19,7 @@ if (!defined('IN_PHPBB'))  * ACM Abstract Memory Class  * @package acm  */ -class phpbb_cache_driver_memory extends phpbb_cache_driver_base +abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base  {  	var $key_prefix; @@ -281,7 +280,7 @@ class phpbb_cache_driver_memory extends phpbb_cache_driver_base  	/**  	* Save sql query  	*/ -	function sql_save($query, &$query_result, $ttl) +	function sql_save($query, $query_result, $ttl)  	{  		global $db; @@ -336,6 +335,8 @@ class phpbb_cache_driver_memory extends phpbb_cache_driver_base  		$this->_write('sql_' . $hash, $this->sql_rowset[$query_id], $ttl);  		$query_result = $query_id; + +		return $query_id;  	}  	/** diff --git a/phpBB/includes/cache/driver/null.php b/phpBB/includes/cache/driver/null.php index 0a520b572e..df2c6c026f 100644 --- a/phpBB/includes/cache/driver/null.php +++ b/phpBB/includes/cache/driver/null.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2005, 2009 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -108,7 +107,7 @@ class phpbb_cache_driver_null extends phpbb_cache_driver_base  	/**  	* Save sql query  	*/ -	function sql_save($query, &$query_result, $ttl) +	function sql_save($query, $query_result, $ttl)  	{  	} diff --git a/phpBB/includes/cache/driver/redis.php b/phpBB/includes/cache/driver/redis.php index f0997c3cad..a768885962 100755..100644 --- a/phpBB/includes/cache/driver/redis.php +++ b/phpBB/includes/cache/driver/redis.php @@ -3,7 +3,7 @@  *  * @package acm  * @copyright (c) 2011 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -25,12 +25,6 @@ if (!defined('PHPBB_ACM_REDIS_HOST'))  	define('PHPBB_ACM_REDIS_HOST', 'localhost');  } -if (!defined('PHPBB_ACM_REDIS')) -{ -	//can define multiple servers with host1/port1,host2/port2 format -	define('PHPBB_ACM_REDIS', PHPBB_ACM_REDIS_HOST . '/' . PHPBB_ACM_REDIS_PORT); -} -  /**  * ACM for Redis  * @@ -51,12 +45,8 @@ class phpbb_cache_driver_redis extends phpbb_cache_driver_memory  		parent::__construct();  		$this->redis = new Redis(); -		foreach (explode(',', PHPBB_ACM_REDIS) as $server) -		{ -			$parts = explode('/', $server); -			$this->redis->connect(trim($parts[0]), trim($parts[1])); -		} -		 +		$this->redis->connect(PHPBB_ACM_REDIS_HOST, PHPBB_ACM_REDIS_PORT); +  		if (defined('PHPBB_ACM_REDIS_PASSWORD'))  		{  			if (!$this->redis->auth(PHPBB_ACM_REDIS_PASSWORD)) diff --git a/phpBB/includes/cache/driver/wincache.php b/phpBB/includes/cache/driver/wincache.php index 2e1a138ee9..fa9eb95f88 100644 --- a/phpBB/includes/cache/driver/wincache.php +++ b/phpBB/includes/cache/driver/wincache.php @@ -3,7 +3,7 @@  *  * @package acm  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/cache/driver/xcache.php b/phpBB/includes/cache/driver/xcache.php index 9363ff501d..0b768bdb3e 100644 --- a/phpBB/includes/cache/driver/xcache.php +++ b/phpBB/includes/cache/driver/xcache.php @@ -2,9 +2,8 @@  /**  *  * @package acm -* @version $Id$  * @copyright (c) 2005, 2009 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/cache/factory.php b/phpBB/includes/cache/factory.php deleted file mode 100644 index f38e19cbe6..0000000000 --- a/phpBB/includes/cache/factory.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php -/** -* -* @package acm -* @version $Id$ -* @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ -	exit; -} - -/** -* @package acm -*/ -class phpbb_cache_factory -{ -	private $acm_type; - -	public function __construct($acm_type) -	{ -		$this->acm_type = $acm_type; -	} - -	public function get_driver() -	{ -		$class_name = 'phpbb_cache_driver_' . $this->acm_type; -		return new $class_name(); -	} - -	public function get_service() -	{ -		$driver = $this->get_driver(); -		$service = new phpbb_cache_service($driver); -		return $service; -	} -} diff --git a/phpBB/includes/cache/service.php b/phpBB/includes/cache/service.php index 0c01953d55..e63ec6e33a 100644 --- a/phpBB/includes/cache/service.php +++ b/phpBB/includes/cache/service.php @@ -3,7 +3,7 @@  *  * @package acm  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -321,50 +321,34 @@ class phpbb_cache_service  	/**  	* Obtain cfg file data  	*/ -	function obtain_cfg_items($theme) +	function obtain_cfg_items($style)  	{  		global $config, $phpbb_root_path; -		$parsed_items = array( -			'theme'		=> array(), -			'template'	=> array(), -			'imageset'	=> array() -		); +		$parsed_array = $this->driver->get('_cfg_' . $style['style_path']); -		foreach ($parsed_items as $key => $parsed_array) +		if ($parsed_array === false)  		{ -			$parsed_array = $this->driver->get('_cfg_' . $key . '_' . $theme[$key . '_path']); - -			if ($parsed_array === false) -			{ -				$parsed_array = array(); -			} - -			$reparse = false; -			$filename = $phpbb_root_path . 'styles/' . $theme[$key . '_path'] . '/' . $key . '/' . $key . '.cfg'; +			$parsed_array = array(); +		} -			if (!file_exists($filename)) -			{ -				continue; -			} +		$filename = $phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; -			if (!isset($parsed_array['filetime']) || (($config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) -			{ -				$reparse = true; -			} +		if (!file_exists($filename)) +		{ +			return $parsed_array; +		} +		if (!isset($parsed_array['filetime']) || (($config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) +		{  			// Re-parse cfg file -			if ($reparse) -			{ -				$parsed_array = parse_cfg_file($filename); -				$parsed_array['filetime'] = @filemtime($filename); +			$parsed_array = parse_cfg_file($filename); +			$parsed_array['filetime'] = @filemtime($filename); -				$this->driver->put('_cfg_' . $key . '_' . $theme[$key . '_path'], $parsed_array); -			} -			$parsed_items[$key] = $parsed_array; +			$this->driver->put('_cfg_' . $style['style_path'], $parsed_array);  		} -		return $parsed_items; +		return $parsed_array;  	}  	/** diff --git a/phpBB/includes/captcha/captcha_factory.php b/phpBB/includes/captcha/captcha_factory.php index a3766f4054..d57b333c69 100644 --- a/phpBB/includes/captcha/captcha_factory.php +++ b/phpBB/includes/captcha/captcha_factory.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -26,7 +25,7 @@ class phpbb_captcha_factory  	/**  	* return an instance of class $name in file $name_plugin.php  	*/ -	function &get_instance($name) +	public static function get_instance($name)  	{  		global $phpbb_root_path, $phpEx; @@ -59,38 +58,38 @@ class phpbb_captcha_factory  	*/  	function get_captcha_types()  	{ -		global $phpbb_root_path, $phpEx; +		global $phpbb_root_path, $phpEx, $phpbb_extension_manager;  		$captchas = array(  			'available'		=> array(),  			'unavailable'	=> array(),  		); -		$dp = @opendir($phpbb_root_path . 'includes/captcha/plugins'); +		$finder = $phpbb_extension_manager->get_finder(); +		$captcha_plugin_classes = $finder +			->extension_directory('/captcha') +			->suffix('_plugin') +			->core_path('includes/captcha/plugins/') +			->get_classes(); -		if ($dp) +		foreach ($captcha_plugin_classes as $class)  		{ -			while (($file = readdir($dp)) !== false) +			// check if this class needs to be loaded in legacy mode +			$old_class = preg_replace('/^phpbb_captcha_plugins_/', '', $class); +			if (file_exists($phpbb_root_path . "includes/captcha/plugins/$old_class.$phpEx") && !class_exists($old_class))  			{ -				if ((preg_match('#_plugin\.' . $phpEx . '$#', $file))) -				{ -					$name = preg_replace('#^(.*?)_plugin\.' . $phpEx . '$#', '\1', $file); -					if (!class_exists($name)) -					{ -						include($phpbb_root_path . "includes/captcha/plugins/$file"); -					} +				include($phpbb_root_path . "includes/captcha/plugins/$old_class.$phpEx"); +				$class = preg_replace('/_plugin$/', '', $old_class); +			} -					if (call_user_func(array($name, 'is_available'))) -					{ -						$captchas['available'][$name] = call_user_func(array($name, 'get_name')); -					} -					else -					{ -						$captchas['unavailable'][$name] = call_user_func(array($name, 'get_name')); -					} -				} +			if (call_user_func(array($class, 'is_available'))) +			{ +				$captchas['available'][$class] = call_user_func(array($class, 'get_name')); +			} +			else +			{ +				$captchas['unavailable'][$class] = call_user_func(array($class, 'get_name'));  			} -			closedir($dp);  		}  		return $captchas; diff --git a/phpBB/includes/captcha/captcha_gd.php b/phpBB/includes/captcha/captcha_gd.php index 15f34aa58f..f58b590c4b 100644 --- a/phpBB/includes/captcha/captcha_gd.php +++ b/phpBB/includes/captcha/captcha_gd.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -77,7 +76,7 @@ class captcha  		{  			$denom = ($code_len - $i);  			$denom = max(1.3, $denom); -			$offset[$i] = mt_rand(0, (1.5 * $width_avail) / $denom); +			$offset[$i] = phpbb_mt_rand(0, (int) round((1.5 * $width_avail) / $denom));  			$width_avail -= $offset[$i];  		} diff --git a/phpBB/includes/captcha/captcha_gd_wave.php b/phpBB/includes/captcha/captcha_gd_wave.php index 503283d848..e19f54f777 100644 --- a/phpBB/includes/captcha/captcha_gd_wave.php +++ b/phpBB/includes/captcha/captcha_gd_wave.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/captcha/captcha_non_gd.php b/phpBB/includes/captcha/captcha_non_gd.php index 38ff276189..c2b97423e6 100644 --- a/phpBB/includes/captcha/captcha_non_gd.php +++ b/phpBB/includes/captcha/captcha_non_gd.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/captcha/plugins/captcha_abstract.php b/phpBB/includes/captcha/plugins/captcha_abstract.php index aea39b3123..7fd88feeb5 100644 --- a/phpBB/includes/captcha/plugins/captcha_abstract.php +++ b/phpBB/includes/captcha/plugins/captcha_abstract.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006, 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -22,7 +21,7 @@ if (!defined('IN_PHPBB'))  *  * @package VC  */ -class phpbb_default_captcha +class phpbb_captcha_plugins_captcha_abstract  {  	var $confirm_id;  	var $confirm_code; @@ -364,3 +363,10 @@ class phpbb_default_captcha  	}  } + +/** +* Old class name for legacy use. The new class name is auto loadable. +*/ +class phpbb_default_captcha extends phpbb_captcha_plugins_captcha_abstract +{ +} diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php index add8c3959f..4ad34f2a26 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006, 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -50,13 +49,13 @@ class phpbb_captcha_gd extends phpbb_default_captcha  		}  	} -	function &get_instance() +	static public function get_instance()  	{ -		$instance =& new phpbb_captcha_gd(); +		$instance = new phpbb_captcha_gd();  		return $instance;  	} -	function is_available() +	static public function is_available()  	{  		global $phpbb_root_path, $phpEx; @@ -81,7 +80,7 @@ class phpbb_captcha_gd extends phpbb_default_captcha  		return true;  	} -	function get_name() +	static public function get_name()  	{  		return 'CAPTCHA_GD';  	} diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_wave_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_wave_plugin.php index 2aed332295..26383c76a8 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_wave_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_wave_plugin.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006, 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -40,12 +39,12 @@ class phpbb_captcha_gd_wave extends phpbb_default_captcha  		}  	} -	function get_instance() +	static public function get_instance()  	{  		return new phpbb_captcha_gd_wave();  	} -	function is_available() +	static public function is_available()  	{  		global $phpbb_root_path, $phpEx; @@ -62,7 +61,7 @@ class phpbb_captcha_gd_wave extends phpbb_default_captcha  		return can_load_dll('gd');  	} -	function get_name() +	static public function get_name()  	{  		return 'CAPTCHA_GD_3D';  	} diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php index 490a0d62ab..c5ef8c78b0 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006, 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -40,18 +39,18 @@ class phpbb_captcha_nogd extends phpbb_default_captcha  		}  	} -	function &get_instance() +	static public function get_instance()  	{ -		$instance =& new phpbb_captcha_nogd(); +		$instance = new phpbb_captcha_nogd();  		return $instance;  	} -	function is_available() +	static public function is_available()  	{  		return true;  	} -	function get_name() +	static public function get_name()  	{  		return 'CAPTCHA_NO_GD';  	} diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php index 75fef25a9f..ec7636f511 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006, 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -99,9 +98,9 @@ class phpbb_captcha_qa  	/**  	*  API function  	*/ -	function &get_instance() +	static public function get_instance()  	{ -		$instance =& new phpbb_captcha_qa(); +		$instance = new phpbb_captcha_qa();  		return $instance;  	} @@ -109,7 +108,7 @@ class phpbb_captcha_qa  	/**  	* See if the captcha has created its tables.  	*/ -	function is_installed() +	static public function is_installed()  	{  		global $db, $phpbb_root_path, $phpEx; @@ -125,14 +124,14 @@ class phpbb_captcha_qa  	/**  	*  API function - for the captcha to be available, it must have installed itself and there has to be at least one question in the board's default lang  	*/ -	function is_available() +	static public function is_available()  	{  		global $config, $db, $phpbb_root_path, $phpEx, $user;  		// load language file for pretty display in the ACP dropdown  		$user->add_lang('captcha_qa'); -		if (!phpbb_captcha_qa::is_installed()) +		if (!self::is_installed())  		{  			return false;  		} @@ -158,7 +157,7 @@ class phpbb_captcha_qa  	/**  	*  API function  	*/ -	function get_name() +	static public function get_name()  	{  		return 'CAPTCHA_QA';  	} @@ -319,7 +318,7 @@ class phpbb_captcha_qa  					),  					'PRIMARY_KEY'		=> 'question_id',  					'KEYS'				=> array( -						'lang_iso'			=> array('INDEX', 'lang_iso'), +						'lang'			=> array('INDEX', 'lang_iso'),  					),  				),  				CAPTCHA_ANSWERS_TABLE		=> array ( @@ -328,7 +327,7 @@ class phpbb_captcha_qa  						'answer_text'	=> array('STEXT_UNI', ''),  					),  					'KEYS'				=> array( -						'question_id'			=> array('INDEX', 'question_id'), +						'qid'			=> array('INDEX', 'question_id'),  					),  				),  				CAPTCHA_QA_CONFIRM_TABLE		=> array ( @@ -613,7 +612,7 @@ class phpbb_captcha_qa  		$user->add_lang('acp/board');  		$user->add_lang('captcha_qa'); -		if (!$this->is_installed()) +		if (!self::is_installed())  		{  			$this->install();  		} diff --git a/phpBB/includes/captcha/plugins/phpbb_recaptcha_plugin.php b/phpBB/includes/captcha/plugins/phpbb_recaptcha_plugin.php index 12e3536893..83d40bbba7 100644 --- a/phpBB/includes/captcha/plugins/phpbb_recaptcha_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_recaptcha_plugin.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2006, 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -27,16 +26,22 @@ if (!class_exists('phpbb_default_captcha', false))  */  class phpbb_recaptcha extends phpbb_default_captcha  { -	var $recaptcha_server = 'http://api.recaptcha.net'; -	var $recaptcha_server_secure = 'https://api-secure.recaptcha.net'; // class constants :( -	var $recaptcha_verify_server = 'api-verify.recaptcha.net'; +	var $recaptcha_server = 'http://www.google.com/recaptcha/api'; +	var $recaptcha_server_secure = 'https://www.google.com/recaptcha/api'; // class constants :( + +	// We are opening a socket to port 80 of this host and send +	// the POST request asking for verification to the path specified here. +	var $recaptcha_verify_server = 'www.google.com'; +	var $recaptcha_verify_path = '/recaptcha/api/verify'; +  	var $challenge;  	var $response;  	// PHP4 Constructor  	function phpbb_recaptcha()  	{ -		$this->recaptcha_server = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? $this->recaptcha_server_secure : $this->recaptcha_server; +		global $request; +		$this->recaptcha_server = $request->is_secure() ? $this->recaptcha_server_secure : $this->recaptcha_server;  	}  	function init($type) @@ -49,13 +54,13 @@ class phpbb_recaptcha extends phpbb_default_captcha  		$this->response = request_var('recaptcha_response_field', '');  	} -	function &get_instance() +	static public function get_instance()  	{ -		$instance =& new phpbb_recaptcha(); +		$instance = new phpbb_recaptcha();  		return $instance;  	} -	function is_available() +	static public function is_available()  	{  		global $config, $user;  		$user->add_lang('captcha_recaptcha'); @@ -70,7 +75,7 @@ class phpbb_recaptcha extends phpbb_default_captcha  		return true;  	} -	function get_name() +	static public function get_name()  	{  		return 'CAPTCHA_RECAPTCHA';  	} @@ -158,7 +163,7 @@ class phpbb_recaptcha extends phpbb_default_captcha  				'RECAPTCHA_SERVER'			=> $this->recaptcha_server,  				'RECAPTCHA_PUBKEY'			=> isset($config['recaptcha_pubkey']) ? $config['recaptcha_pubkey'] : '',  				'RECAPTCHA_ERRORGET'		=> '', -				'S_RECAPTCHA_AVAILABLE'		=> $this->is_available(), +				'S_RECAPTCHA_AVAILABLE'		=> self::is_available(),  				'S_CONFIRM_CODE'			=> true,  				'S_TYPE'					=> $this->type,  				'L_CONFIRM_EXPLAIN'			=> $explain, @@ -296,7 +301,7 @@ class phpbb_recaptcha extends phpbb_default_captcha  			return $user->lang['RECAPTCHA_INCORRECT'];  		} -		$response = $this->_recaptcha_http_post($this->recaptcha_verify_server, '/verify', +		$response = $this->_recaptcha_http_post($this->recaptcha_verify_server, $this->recaptcha_verify_path,  			array(  				'privatekey'	=> $config['recaptcha_privkey'],  				'remoteip'		=> $user->ip, diff --git a/phpBB/includes/class_loader.php b/phpBB/includes/class_loader.php index a28d745983..6082800908 100644 --- a/phpBB/includes/class_loader.php +++ b/phpBB/includes/class_loader.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -31,22 +30,32 @@ if (!defined('IN_PHPBB'))  */  class phpbb_class_loader  { -	private $phpbb_root_path; +	private $prefix; +	private $path;  	private $php_ext;  	private $cache; + +	/** +	* A map of looked up class names to paths relative to $this->path. +	* This map is stored in cache and looked up if the cache is available. +	* +	* @var array +	*/  	private $cached_paths = array();  	/**  	* Creates a new phpbb_class_loader, which loads files with the given -	* file extension from the given phpbb root path. +	* file extension from the given path.  	* -	* @param string $phpbb_root_path phpBB's root directory containing includes/ -	* @param string $php_ext         The file extension for PHP files +	* @param string $prefix  Required class name prefix for files to be loaded +	* @param string $path    Directory to load files from +	* @param string $php_ext The file extension for PHP files  	* @param phpbb_cache_driver_interface $cache An implementation of the phpBB cache interface.  	*/ -	public function __construct($phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null) +	public function __construct($prefix, $path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null)  	{ -		$this->phpbb_root_path = $phpbb_root_path; +		$this->prefix = $prefix; +		$this->path = $path;  		$this->php_ext = $php_ext;  		$this->set_cache($cache); @@ -63,7 +72,7 @@ class phpbb_class_loader  	{  		if ($cache)  		{ -			$this->cached_paths = $cache->get('class_loader'); +			$this->cached_paths = $cache->get('class_loader_' . $this->prefix);  			if ($this->cached_paths === false)  			{ @@ -100,23 +109,21 @@ class phpbb_class_loader  	*/  	public function resolve_path($class)  	{ -		$path_prefix = $this->phpbb_root_path . 'includes/'; -  		if (isset($this->cached_paths[$class]))  		{ -			return $path_prefix . $this->cached_paths[$class] . $this->php_ext; +			return $this->path . $this->cached_paths[$class] . $this->php_ext;  		} -		if (!preg_match('/phpbb_[a-zA-Z0-9_]+/', $class)) +		if (!preg_match('/^' . $this->prefix . '[a-zA-Z0-9_]+$/', $class))  		{  			return false;  		} -		$parts = explode('_', substr($class, 6)); +		$parts = explode('_', substr($class, strlen($this->prefix)));  		$dirs = ''; -		for ($i = 0, $n = sizeof($parts); $i < $n && is_dir($path_prefix . $dirs . $parts[$i]); $i++) +		for ($i = 0, $n = sizeof($parts); $i < $n && is_dir($this->path . $dirs . $parts[$i]); $i++)  		{  			$dirs .= $parts[$i] . '/';  		} @@ -129,7 +136,7 @@ class phpbb_class_loader  		$relative_path = $dirs . implode(array_slice($parts, $i, sizeof($parts) - $i), '_'); -		if (!file_exists($path_prefix . $relative_path . $this->php_ext)) +		if (!file_exists($this->path . $relative_path . $this->php_ext))  		{  			return false;  		} @@ -137,10 +144,10 @@ class phpbb_class_loader  		if ($this->cache)  		{  			$this->cached_paths[$class] = $relative_path; -			$this->cache->put('class_loader', $this->cached_paths); +			$this->cache->put('class_loader_' . $this->prefix, $this->cached_paths);  		} -		return $path_prefix . $relative_path . $this->php_ext; +		return $this->path . $relative_path . $this->php_ext;  	}  	/** @@ -150,7 +157,7 @@ class phpbb_class_loader  	*/  	public function load_class($class)  	{ -		if (substr($class, 0, 6) === 'phpbb_') +		if (substr($class, 0, strlen($this->prefix)) === $this->prefix)  		{  			$path = $this->resolve_path($class); diff --git a/phpBB/includes/config/config.php b/phpBB/includes/config/config.php index 354dc85c03..12a4a418b2 100644 --- a/phpBB/includes/config/config.php +++ b/phpBB/includes/config/config.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/config/db.php b/phpBB/includes/config/db.php index a2db4056df..993a764a7f 100644 --- a/phpBB/includes/config/db.php +++ b/phpBB/includes/config/db.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 216aac7489..68af41ab20 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -226,6 +225,7 @@ define('CONFIG_TABLE',				$table_prefix . 'config');  define('CONFIRM_TABLE',				$table_prefix . 'confirm');  define('DISALLOW_TABLE',			$table_prefix . 'disallow');  define('DRAFTS_TABLE',				$table_prefix . 'drafts'); +define('EXT_TABLE',					$table_prefix . 'ext');  define('EXTENSIONS_TABLE',			$table_prefix . 'extensions');  define('EXTENSION_GROUPS_TABLE',	$table_prefix . 'extension_groups');  define('FORUMS_TABLE',				$table_prefix . 'forums'); @@ -236,6 +236,7 @@ define('GROUPS_TABLE',				$table_prefix . 'groups');  define('ICONS_TABLE',				$table_prefix . 'icons');  define('LANG_TABLE',				$table_prefix . 'lang');  define('LOG_TABLE',					$table_prefix . 'log'); +define('LOGIN_ATTEMPT_TABLE',		$table_prefix . 'login_attempts');  define('MODERATOR_CACHE_TABLE',		$table_prefix . 'moderator_cache');  define('MODULES_TABLE',				$table_prefix . 'modules');  define('POLL_OPTIONS_TABLE',		$table_prefix . 'poll_options'); @@ -259,6 +260,7 @@ define('SESSIONS_TABLE',			$table_prefix . 'sessions');  define('SESSIONS_KEYS_TABLE',		$table_prefix . 'sessions_keys');  define('SITELIST_TABLE',			$table_prefix . 'sitelist');  define('SMILIES_TABLE',				$table_prefix . 'smilies'); +define('SPHINX_TABLE',				$table_prefix . 'sphinx');  define('STYLES_TABLE',				$table_prefix . 'styles');  define('STYLES_TEMPLATE_TABLE',		$table_prefix . 'styles_template');  define('STYLES_TEMPLATE_DATA_TABLE',$table_prefix . 'styles_template_data'); diff --git a/phpBB/includes/cron/manager.php b/phpBB/includes/cron/manager.php index 21dcb91695..ccaa4f3764 100644 --- a/phpBB/includes/cron/manager.php +++ b/phpBB/includes/cron/manager.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -32,136 +32,35 @@ class phpbb_cron_manager  	*/  	protected $tasks = array(); -	/** -	* Path to the root of directory tree with tasks. -	* For bundled phpBB tasks, this is the path to includes/cron/tasks -	* under phpBB root. -	* @var string -	*/ -	protected $task_path; - -	/** -	* PHP file extension -	* @var string -	*/ -	protected $phpEx; - -	/** -	* Cache driver -	* @var phpbb_cache_driver_interface -	*/ -	protected $cache; +	protected $phpbb_root_path; +	protected $php_ext;  	/**  	* Constructor. Loads all available tasks.  	* -	* Tasks will be looked up in directory tree rooted at $task_path. -	* Task classes will be autoloaded and must be named according to -	* autoloading naming conventions. To load cron tasks shipped with -	* phpbb, pass $phpbb_root_path . 'includes/cron/task' as $task_path. -	* -	* If $cache is given, names of found cron tasks will be cached in it -	* for one hour. Note that the cron task names are stored without -	* namespacing; if two different phbb_cron_manager instances are -	* constructed with different $task_path arguments but the same $cache, -	* the second instance will use task names found by the first instance. -	* -	* @param string $task_path                   Directory containing cron tasks -	* @param string $phpEx                       PHP file extension -	* @param phpbb_cache_driver_interface $cache Cache for task names (optional) -	* @return void +	* @param array|Traversable $tasks Provides an iterable set of task names  	*/ -	public function __construct($task_path, $phpEx, phpbb_cache_driver_interface $cache = null) +	public function __construct($tasks, $phpbb_root_path, $php_ext)  	{ -		$this->task_path = $task_path; -		$this->phpEx = $phpEx; -		$this->cache = $cache; - -		$task_names = $this->find_cron_task_names(); -		$this->load_tasks($task_names); -	} - -	/** -	* Finds cron task names. -	* -	* A cron task file must follow the naming convention: -	* includes/cron/task/$mod/$name.php. -	* $mod is core for tasks that are part of phpbb. -	* Modifications should use their name as $mod. -	* $name is the name of the cron task. -	* Cron task is expected to be a class named phpbb_cron_task_${mod}_${name}. -	* -	* @return array		List of task names -	*/ -	public function find_cron_task_names() -	{ -		if ($this->cache) -		{ -			$task_names = $this->cache->get('_cron_tasks'); - -			if ($task_names !== false) -			{ -				return $task_names; -			} -		} - -		$task_names = array(); -		$ext = '.' . $this->phpEx; -		$ext_length = strlen($ext); - -		$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->task_path)); - -		foreach ($iterator as $fileinfo) -		{ -			$file = preg_replace('#^' . preg_quote($this->task_path, '#') . '#', '', $fileinfo->getPathname()); - -			// skip directories and files direclty in the task root path -			if ($fileinfo->isFile() && strpos($file, '/') !== false) -			{ -				$task_name = str_replace('/', '_', substr($file, 0, -$ext_length)); -				if (substr($file, -$ext_length) == $ext && $this->is_valid_name($task_name)) -				{ -					$task_names[] = 'phpbb_cron_task_' . $task_name; -				} -			} -		} - -		if ($this->cache) -		{ -			$this->cache->put('_cron_tasks', $task_names, 3600); -		} +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; -		return $task_names; -	} - -	/** -	* Checks whether $name is a valid identifier, and -	* therefore part of valid cron task class name. -	* -	* @param string $name		Name to check -	* -	* @return bool -	*/ -	public function is_valid_name($name) -	{ -		return (bool) preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $name); +		$this->load_tasks($tasks);  	}  	/**  	* Loads tasks given by name, wraps them  	* and puts them into $this->tasks.  	* -	* @param array $task_names		Array of strings +	* @param array|Traversable $tasks		Array of instances of phpbb_cron_task  	*  	* @return void  	*/ -	public function load_tasks(array $task_names) +	public function load_tasks($tasks)  	{ -		foreach ($task_names as $task_name) +		foreach ($tasks as $task)  		{ -			$task = new $task_name(); -			$wrapper = new phpbb_cron_task_wrapper($task); -			$this->tasks[] = $wrapper; +			$this->tasks[] = $this->wrap_task($task);  		}  	} @@ -227,25 +126,13 @@ class phpbb_cron_manager  	}  	/** -	* Creates an instance of parametrized cron task $name with args $args. -	* The constructed task is wrapped with cron task wrapper before being returned. -	* -	* @param string $name		The task name, which is the same as cron task class name. -	* @param array $args		Will be passed to the task class's constructor. +	* Wraps a task inside an instance of phpbb_cron_task_wrapper.  	* -	* @return phpbb_cron_task_wrapper|null +	* @param  phpbb_cron_task 			$task The task. +	* @return phpbb_cron_task_wrapper	The wrapped task.  	*/ -	public function instantiate_task($name, array $args) +	public function wrap_task(phpbb_cron_task $task)  	{ -		$task = $this->find_task($name); -		if ($task) -		{ -			// task here is actually an instance of cron task wrapper -			$class = $task->get_name(); -			$task = new $class($args); -			// need to wrap the new task too -			$task = new phpbb_cron_task_wrapper($task); -		} -		return $task; +		return new phpbb_cron_task_wrapper($task, $this->phpbb_root_path, $this->php_ext);  	}  } diff --git a/phpBB/includes/cron/task/base.php b/phpBB/includes/cron/task/base.php index 9db8e3bd44..94a2f267b4 100644 --- a/phpBB/includes/cron/task/base.php +++ b/phpBB/includes/cron/task/base.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -28,6 +28,28 @@ if (!defined('IN_PHPBB'))  */  abstract class phpbb_cron_task_base implements phpbb_cron_task  { +	private $name; + +	/** +	* Returns the name of the task. +	* +	* @return string		Name of wrapped task. +	*/ +	public function get_name() +	{ +		return $this->name; +	} + +	/** +	* Sets the name of the task. +	* +	* @param string	$name The task name +	*/ +	public function set_name($name) +	{ +		$this->name = $name; +	} +  	/**  	* Returns whether this cron task can run, given current board configuration.  	* diff --git a/phpBB/includes/cron/task/core/prune_all_forums.php b/phpBB/includes/cron/task/core/prune_all_forums.php index 39b5765229..252e16e57d 100644 --- a/phpBB/includes/cron/task/core/prune_all_forums.php +++ b/phpBB/includes/cron/task/core/prune_all_forums.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -26,6 +26,27 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base  { +	protected $phpbb_root_path; +	protected $php_ext; +	protected $config; +	protected $db; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path The root path +	* @param string $php_ext The PHP extension +	* @param phpbb_config $config The config +	* @param dbal $db The db connection +	*/ +	public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, dbal $db) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->config = $config; +		$this->db = $db; +	} +  	/**  	* Runs this cron task.  	* @@ -33,19 +54,17 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base  	*/  	public function run()  	{ -		global $phpbb_root_path, $phpEx, $db; -  		if (!function_exists('auto_prune'))  		{ -			include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +			include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext);  		}  		$sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq  			FROM ' . FORUMS_TABLE . " -			WHERE enable_prune = 1  +			WHERE enable_prune = 1  				AND prune_next < " . time(); -		$result = $db->sql_query($sql); -		while ($row = $db->sql_fetchrow($result)) +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result))  		{  			if ($row['prune_days'])  			{ @@ -57,7 +76,7 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base  				auto_prune($row['forum_id'], 'viewed', $row['forum_flags'], $row['prune_viewed'], $row['prune_freq']);  			}  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result);  	}  	/** @@ -69,7 +88,6 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base  	*/  	public function is_runnable()  	{ -		global $config; -		return (bool) $config['use_system_cron']; +		return (bool) $this->config['use_system_cron'];  	}  } diff --git a/phpBB/includes/cron/task/core/prune_forum.php b/phpBB/includes/cron/task/core/prune_forum.php index 55b1c58cd4..41d60af921 100644 --- a/phpBB/includes/cron/task/core/prune_forum.php +++ b/phpBB/includes/cron/task/core/prune_forum.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -26,31 +26,45 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements phpbb_cron_task_parametrized  { -	private $forum_data; +	protected $phpbb_root_path; +	protected $php_ext; +	protected $config; +	protected $db;  	/** -	* Constructor. -	*  	* If $forum_data is given, it is assumed to contain necessary information  	* about a single forum that is to be pruned.  	*  	* If $forum_data is not given, forum id will be retrieved via request_var  	* and a database query will be performed to load the necessary information  	* about the forum. +	*/ +	protected $forum_data; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path The root path +	* @param string $php_ext The PHP extension +	* @param phpbb_config $config The config +	* @param dbal $db The db connection +	*/ +	public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, dbal $db) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->config = $config; +		$this->db = $db; +	} + +	/** +	* Manually set forum data.  	*  	* @param array $forum_data Information about a forum to be pruned.  	*/ -	public function __construct($forum_data = null) +	public function set_forum_data($forum_data)  	{ -		global $db; -		if ($forum_data) -		{ -			$this->forum_data = $forum_data; -		} -		else -		{ -			$this->forum_data = null; -		} +		$this->forum_data = $forum_data;  	}  	/** @@ -60,10 +74,9 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p  	*/  	public function run()  	{ -		global $phpbb_root_path, $phpEx;  		if (!function_exists('auto_prune'))  		{ -			include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +			include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext);  		}  		if ($this->forum_data['prune_days']) @@ -90,8 +103,7 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p  	*/  	public function is_runnable()  	{ -		global $config; -		return !$config['use_system_cron'] && $this->forum_data; +		return !$this->config['use_system_cron'] && $this->forum_data;  	}  	/** @@ -130,8 +142,6 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p  	*/  	public function parse_parameters(phpbb_request_interface $request)  	{ -		global $db; -  		$this->forum_data = null;  		if ($request->is_set('f'))  		{ @@ -140,9 +150,9 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p  			$sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq  				FROM ' . FORUMS_TABLE . "  				WHERE forum_id = $forum_id"; -			$result = $db->sql_query($sql); -			$row = $db->sql_fetchrow($result); -			$db->sql_freeresult($result); +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result);  			if ($row)  			{ diff --git a/phpBB/includes/cron/task/core/queue.php b/phpBB/includes/cron/task/core/queue.php index 96cade0ce5..c765660906 100644 --- a/phpBB/includes/cron/task/core/queue.php +++ b/phpBB/includes/cron/task/core/queue.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -22,6 +22,24 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_queue extends phpbb_cron_task_base  { +	protected $phpbb_root_path; +	protected $php_ext; +	protected $config; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path The root path +	* @param string $php_ext The PHP extension +	* @param phpbb_config $config The config +	*/ +	public function __construct($phpbb_root_path, $php_ext, phpbb_config $config) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->config = $config; +	} +  	/**  	* Runs this cron task.  	* @@ -29,10 +47,9 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base  	*/  	public function run()  	{ -		global $phpbb_root_path, $phpEx;  		if (!class_exists('queue'))  		{ -			include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); +			include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext);  		}  		$queue = new queue();  		$queue->process(); @@ -47,8 +64,7 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base  	*/  	public function is_runnable()  	{ -		global $phpbb_root_path, $phpEx; -		return file_exists($phpbb_root_path . 'cache/queue.' . $phpEx); +		return file_exists($this->phpbb_root_path . 'cache/queue.' . $this->php_ext);  	}  	/** @@ -61,7 +77,6 @@ class phpbb_cron_task_core_queue extends phpbb_cron_task_base  	*/  	public function should_run()  	{ -		global $config; -		return $config['last_queue_run'] < time() - $config['queue_interval_config']; +		return $this->config['last_queue_run'] < time() - $this->config['queue_interval_config'];  	}  } diff --git a/phpBB/includes/cron/task/core/tidy_cache.php b/phpBB/includes/cron/task/core/tidy_cache.php index 793ce746b4..6017eea561 100644 --- a/phpBB/includes/cron/task/core/tidy_cache.php +++ b/phpBB/includes/cron/task/core/tidy_cache.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -22,6 +22,21 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base  { +	protected $config; +	protected $cache; + +	/** +	* Constructor. +	* +	* @param phpbb_config $config The config +	* @param phpbb_cache_driver_interface $cache The cache driver +	*/ +	public function __construct(phpbb_config $config, phpbb_cache_driver_interface $cache) +	{ +		$this->config = $config; +		$this->cache = $cache; +	} +  	/**  	* Runs this cron task.  	* @@ -29,8 +44,7 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base  	*/  	public function run()  	{ -		global $cache; -		$cache->tidy(); +		$this->cache->tidy();  	}  	/** @@ -43,8 +57,7 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base  	*/  	public function is_runnable()  	{ -		global $cache; -		return method_exists($cache, 'tidy'); +		return true;  	}  	/** @@ -58,7 +71,6 @@ class phpbb_cron_task_core_tidy_cache extends phpbb_cron_task_base  	*/  	public function should_run()  	{ -		global $config; -		return $config['cache_last_gc'] < time() - $config['cache_gc']; +		return $this->config['cache_last_gc'] < time() - $this->config['cache_gc'];  	}  } diff --git a/phpBB/includes/cron/task/core/tidy_database.php b/phpBB/includes/cron/task/core/tidy_database.php index fb0e81eaba..1d256f964f 100644 --- a/phpBB/includes/cron/task/core/tidy_database.php +++ b/phpBB/includes/cron/task/core/tidy_database.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -22,6 +22,24 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base  { +	protected $phpbb_root_path; +	protected $php_ext; +	protected $config; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path The root path +	* @param string $php_ext The PHP extension +	* @param phpbb_config $config The config +	*/ +	public function __construct($phpbb_root_path, $php_ext, phpbb_config $config) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->config = $config; +	} +  	/**  	* Runs this cron task.  	* @@ -29,10 +47,9 @@ class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base  	*/  	public function run()  	{ -		global $phpbb_root_path, $phpEx;  		if (!function_exists('tidy_database'))  		{ -			include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +			include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext);  		}  		tidy_database();  	} @@ -48,7 +65,6 @@ class phpbb_cron_task_core_tidy_database extends phpbb_cron_task_base  	*/  	public function should_run()  	{ -		global $config; -		return $config['database_last_gc'] < time() - $config['database_gc']; +		return $this->config['database_last_gc'] < time() - $this->config['database_gc'];  	}  } diff --git a/phpBB/includes/cron/task/core/tidy_search.php b/phpBB/includes/cron/task/core/tidy_search.php index dcc78abbb8..2e5f3d79d5 100644 --- a/phpBB/includes/cron/task/core/tidy_search.php +++ b/phpBB/includes/cron/task/core/tidy_search.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -24,6 +24,33 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base  { +	protected $phpbb_root_path; +	protected $php_ext; +	protected $auth; +	protected $config; +	protected $db; +	protected $user; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path The root path +	* @param string $php_ext The PHP extension +	* @param phpbb_auth $auth The auth +	* @param phpbb_config $config The config +	* @param dbal $db The db connection +	* @param phpbb_user $user The user +	*/ +	public function __construct($phpbb_root_path, $php_ext, phpbb_auth $auth, phpbb_config $config, dbal $db, phpbb_user $user) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->auth = $auth; +		$this->config = $config; +		$this->db = $db; +		$this->user = $user; +	} +  	/**  	* Runs this cron task.  	* @@ -31,19 +58,17 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base  	*/  	public function run()  	{ -		global $phpbb_root_path, $phpEx, $config, $error; -  		// Select the search method -		$search_type = basename($config['search_type']); +		$search_type = basename($this->config['search_type']);  		if (!class_exists($search_type))  		{ -			include("{$phpbb_root_path}includes/search/$search_type.$phpEx"); +			include($this->phpbb_root_path . "includes/search/$search_type." . $this->php_ext);  		}  		// We do some additional checks in the module to ensure it can actually be utilised  		$error = false; -		$search = new $search_type($error); +		$search = new $search_type($error, $this->phpbb_root_path, $this->php_ext, $this->auth, $this->config, $this->db, $this->user);  		if (!$error)  		{ @@ -62,12 +87,10 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base  	*/  	public function is_runnable()  	{ -		global $phpbb_root_path, $phpEx, $config; -  		// Select the search method -		$search_type = basename($config['search_type']); +		$search_type = basename($this->config['search_type']); -		return file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx); +		return file_exists($this->phpbb_root_path . 'includes/search/' . $search_type . '.' . $this->php_ext);  	}  	/** @@ -81,7 +104,6 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base  	*/  	public function should_run()  	{ -		global $config; -		return $config['search_last_gc'] < time() - $config['search_gc']; +		return $this->config['search_last_gc'] < time() - $this->config['search_gc'];  	}  } diff --git a/phpBB/includes/cron/task/core/tidy_sessions.php b/phpBB/includes/cron/task/core/tidy_sessions.php index 81e7e6a147..13531aa30b 100644 --- a/phpBB/includes/cron/task/core/tidy_sessions.php +++ b/phpBB/includes/cron/task/core/tidy_sessions.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -22,6 +22,21 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base  { +	protected $config; +	protected $user; + +	/** +	* Constructor. +	* +	* @param phpbb_config $config The config +	* @param phpbb_user $user The user +	*/ +	public function __construct(phpbb_config $config, phpbb_user $user) +	{ +		$this->config = $config; +		$this->user = $user; +	} +  	/**  	* Runs this cron task.  	* @@ -29,8 +44,7 @@ class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base  	*/  	public function run()  	{ -		global $user; -		$user->session_gc(); +		$this->user->session_gc();  	}  	/** @@ -44,7 +58,6 @@ class phpbb_cron_task_core_tidy_sessions extends phpbb_cron_task_base  	*/  	public function should_run()  	{ -		global $config; -		return $config['session_last_gc'] < time() - $config['session_gc']; +		return $this->config['session_last_gc'] < time() - $this->config['session_gc'];  	}  } diff --git a/phpBB/includes/cron/task/core/tidy_warnings.php b/phpBB/includes/cron/task/core/tidy_warnings.php index e7d4cc9eea..8dd0674fe5 100644 --- a/phpBB/includes/cron/task/core/tidy_warnings.php +++ b/phpBB/includes/cron/task/core/tidy_warnings.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -24,6 +24,24 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base  { +	protected $phpbb_root_path; +	protected $php_ext; +	protected $config; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path The root path +	* @param string $php_ext The PHP extension +	* @param phpbb_config $config The config +	*/ +	public function __construct($phpbb_root_path, $php_ext, phpbb_config $config) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->config = $config; +	} +  	/**  	* Runs this cron task.  	* @@ -31,10 +49,9 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base  	*/  	public function run()  	{ -		global $phpbb_root_path, $phpEx;  		if (!function_exists('tidy_warnings'))  		{ -			include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +			include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext);  		}  		tidy_warnings();  	} @@ -48,8 +65,7 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base  	*/  	public function is_runnable()  	{ -		global $config; -		return (bool) $config['warnings_expire_days']; +		return (bool) $this->config['warnings_expire_days'];  	}  	/** @@ -63,7 +79,6 @@ class phpbb_cron_task_core_tidy_warnings extends phpbb_cron_task_base  	*/  	public function should_run()  	{ -		global $config; -		return $config['warnings_last_gc'] < time() - $config['warnings_gc']; +		return $this->config['warnings_last_gc'] < time() - $this->config['warnings_gc'];  	}  } diff --git a/phpBB/includes/cron/task/parametrized.php b/phpBB/includes/cron/task/parametrized.php index c6c45be0c0..0714b2e701 100644 --- a/phpBB/includes/cron/task/parametrized.php +++ b/phpBB/includes/cron/task/parametrized.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/cron/task/provider.php b/phpBB/includes/cron/task/provider.php new file mode 100644 index 0000000000..134723ebd1 --- /dev/null +++ b/phpBB/includes/cron/task/provider.php @@ -0,0 +1,59 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +use Symfony\Component\DependencyInjection\TaggedContainerInterface; + +/** +* Provides cron manager with tasks +* +* Finds installed cron tasks and makes them available to the cron manager. +* +* @package phpBB3 +*/ +class phpbb_cron_task_provider implements IteratorAggregate +{ +	private $container; + +	public function __construct(TaggedContainerInterface $container) +	{ +		$this->container = $container; +	} + +	/** +	* Retrieve an iterator over all items +	* +	* @return ArrayIterator An iterator for the array of cron tasks +	*/ +	public function getIterator() +	{ +		$definitions = $this->container->findTaggedServiceIds('cron.task'); + +		$tasks = array(); +		foreach ($definitions as $name => $definition) +		{ +			$task = $this->container->get($name); +			if ($task instanceof phpbb_cron_task_base) +			{ +				$task->set_name($name); +			} + +			$tasks[] = $task; +		} + +		return new ArrayIterator($tasks); +	} +} diff --git a/phpBB/includes/cron/task/task.php b/phpBB/includes/cron/task/task.php index cceccce44f..7b08fed413 100644 --- a/phpBB/includes/cron/task/task.php +++ b/phpBB/includes/cron/task/task.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -22,6 +22,13 @@ if (!defined('IN_PHPBB'))  interface phpbb_cron_task  {  	/** +	* Returns the name of the task. +	* +	* @return string		Name of wrapped task. +	*/ +	public function get_name(); + +	/**  	* Runs this cron task.  	*  	* @return void diff --git a/phpBB/includes/cron/task/wrapper.php b/phpBB/includes/cron/task/wrapper.php index 238d97853c..386fb5b383 100644 --- a/phpBB/includes/cron/task/wrapper.php +++ b/phpBB/includes/cron/task/wrapper.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -23,6 +23,10 @@ if (!defined('IN_PHPBB'))  */  class phpbb_cron_task_wrapper  { +	protected $task; +	protected $phpbb_root_path; +	protected $php_ext; +  	/**  	* Constructor.  	* @@ -30,9 +34,11 @@ class phpbb_cron_task_wrapper  	*  	* @param phpbb_cron_task $task The cron task to wrap.  	*/ -	public function __construct(phpbb_cron_task $task) +	public function __construct(phpbb_cron_task $task, $phpbb_root_path, $php_ext)  	{  		$this->task = $task; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext;  	}  	/** @@ -62,16 +68,6 @@ class phpbb_cron_task_wrapper  	}  	/** -	* Returns the name of wrapped task. It is the same as the wrapped class's class name. -	* -	* @return string		Class name of wrapped task. -	*/ -	public function get_name() -	{ -		return get_class($this->task); -	} - -	/**  	* Returns a url through which this task may be invoked via web.  	*  	* When system cron is not in use, running a cron task is accomplished @@ -82,8 +78,6 @@ class phpbb_cron_task_wrapper  	*/  	public function get_url()  	{ -		global $phpbb_root_path, $phpEx; -  		$name = $this->get_name();  		if ($this->is_parametrized())  		{ @@ -98,7 +92,7 @@ class phpbb_cron_task_wrapper  		{  			$extra = '';  		} -		$url = append_sid($phpbb_root_path . 'cron.' . $phpEx, 'cron_type=' . $name . $extra); +		$url = append_sid($this->phpbb_root_path . 'cron.' . $this->php_ext, 'cron_type=' . $name . $extra);  		return $url;  	} 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/db_tools.php b/phpBB/includes/db/db_tools.php index 88bbd614e6..6df3aac9ce 100644 --- a/phpBB/includes/db/db_tools.php +++ b/phpBB/includes/db/db_tools.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2007 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -21,7 +20,6 @@ if (!defined('IN_PHPBB'))  * Currently not supported is returning SQL for creating tables.  *  * @package dbal -* @note currently not used within phpBB3, but may be utilized later.  */  class phpbb_db_tools  { @@ -348,6 +346,66 @@ class phpbb_db_tools  	}  	/** +	* Gets a list of tables in the database. +	* +	* @return array		Array of table names  (all lower case) +	*/ +	function sql_list_tables() +	{ +		switch ($this->db->sql_layer) +		{ +			case 'mysql': +			case 'mysql4': +			case 'mysqli': +				$sql = 'SHOW TABLES'; +			break; + +			case 'sqlite': +				$sql = 'SELECT name +					FROM sqlite_master +					WHERE type = "table"'; +			break; + +			case 'mssql': +			case 'mssql_odbc': +			case 'mssqlnative': +				$sql = "SELECT name +					FROM sysobjects +					WHERE type='U'"; +			break; + +			case 'postgres': +				$sql = 'SELECT relname +					FROM pg_stat_user_tables'; +			break; + +			case 'firebird': +				$sql = 'SELECT rdb$relation_name +					FROM rdb$relations +					WHERE rdb$view_source is null +						AND rdb$system_flag = 0'; +			break; + +			case 'oracle': +				$sql = 'SELECT table_name +					FROM USER_TABLES'; +			break; +		} + +		$result = $this->db->sql_query($sql); + +		$tables = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$name = current($row); +			$tables[$name] = $name; +		} +		$this->db->sql_freeresult($result); + +		return $tables; +	} + +	/**  	* Check if table exists  	*  	* @@ -417,6 +475,11 @@ class phpbb_db_tools  			// here lies an array, filled with information compiled on the column's data  			$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +			if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "${column_name}_gen" +			{ +				trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); +			} +  			// here we add the definition of the new column to the list of columns  			switch ($this->sql_layer)  			{ @@ -538,7 +601,7 @@ class phpbb_db_tools  			break;  			case 'oracle': -				$table_sql .= "\n);"; +				$table_sql .= "\n)";  				$statements[] = $table_sql;  				// do we need to add a sequence and a tigger for auto incrementing columns? @@ -556,7 +619,7 @@ class phpbb_db_tools  					$trigger .= "BEGIN\n";  					$trigger .= "\tSELECT {$table_name}_seq.nextval\n";  					$trigger .= "\tINTO :new.{$create_sequence}\n"; -					$trigger .= "\tFROM dual\n"; +					$trigger .= "\tFROM dual;\n";  					$trigger .= "END;";  					$statements[] = $trigger; @@ -566,7 +629,13 @@ class phpbb_db_tools  			case 'firebird':  				if ($create_sequence)  				{ -					$statements[] = "CREATE SEQUENCE {$table_name}_seq;"; +					$statements[] = "CREATE GENERATOR {$table_name}_gen;"; +					$statements[] = "SET GENERATOR {$table_name}_gen TO 0;"; + +					$trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n"; +					$trigger .= "BEFORE INSERT\nAS\nBEGIN\n"; +					$trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;"; +					$statements[] = $trigger;  				}  			break;  		} @@ -638,6 +707,36 @@ class phpbb_db_tools  			$sqlite = true;  		} +		// Drop tables? +		if (!empty($schema_changes['drop_tables'])) +		{ +			foreach ($schema_changes['drop_tables'] as $table) +			{ +				// only drop table if it exists +				if ($this->sql_table_exists($table)) +				{ +					$result = $this->sql_table_drop($table); +					if ($this->return_statements) +					{ +						$statements = array_merge($statements, $result); +					} +				} +			} +		} + +		// Add tables? +		if (!empty($schema_changes['add_tables'])) +		{ +			foreach ($schema_changes['add_tables'] as $table => $table_data) +			{ +				$result = $this->sql_create_table($table, $table_data); +				if ($this->return_statements) +				{ +					$statements = array_merge($statements, $result); +				} +			} +		} +  		// Change columns?  		if (!empty($schema_changes['change_columns']))  		{ @@ -681,10 +780,12 @@ class phpbb_db_tools  			{  				foreach ($columns as $column_name => $column_data)  				{ -					// Only add the column if it does not exist yet, else change it (to be consistent) +					// Only add the column if it does not exist yet  					if ($column_exists = $this->sql_column_exists($table, $column_name))  					{ -						$result = $this->sql_column_change($table, $column_name, $column_data, true); +						continue; +						// This is commented out here because it can take tremendous time on updates +//						$result = $this->sql_column_change($table, $column_name, $column_data, true);  					}  					else  					{ @@ -695,7 +796,8 @@ class phpbb_db_tools  					{  						if ($column_exists)  						{ -							$sqlite_data[$table]['change_columns'][] = $result; +							continue; +//							$sqlite_data[$table]['change_columns'][] = $result;  						}  						else  						{ @@ -717,6 +819,11 @@ class phpbb_db_tools  			{  				foreach ($indexes as $index_name)  				{ +					if (!$this->sql_index_exists($table, $index_name)) +					{ +						continue; +					} +  					$result = $this->sql_index_drop($table, $index_name);  					if ($this->return_statements) @@ -777,6 +884,11 @@ class phpbb_db_tools  			{  				foreach ($index_array as $index_name => $column)  				{ +					if ($this->sql_unique_index_exists($table, $index_name)) +					{ +						continue; +					} +  					$result = $this->sql_create_unique_index($table, $index_name, $column);  					if ($this->return_statements) @@ -794,6 +906,11 @@ class phpbb_db_tools  			{  				foreach ($index_array as $index_name => $column)  				{ +					if ($this->sql_index_exists($table, $index_name)) +					{ +						continue; +					} +  					$result = $this->sql_create_index($table, $index_name, $column);  					if ($this->return_statements) @@ -952,34 +1069,21 @@ class phpbb_db_tools  	}  	/** -	* Check if a specified column exist +	* Gets a list of columns of a table.  	* -	* @param string	$table			Table to check the column at -	* @param string	$column_name	The column to check +	* @param string $table		Table name  	* -	* @return bool True if column exists, else false +	* @return array				Array of column names (all lower case)  	*/ -	function sql_column_exists($table, $column_name) +	function sql_list_columns($table)  	{ +		$columns = array(); +  		switch ($this->sql_layer)  		{  			case 'mysql_40':  			case 'mysql_41': -  				$sql = "SHOW COLUMNS FROM $table"; -				$result = $this->db->sql_query($sql); - -				while ($row = $this->db->sql_fetchrow($result)) -				{ -					// lower case just in case -					if (strtolower($row['Field']) == $column_name) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -				$this->db->sql_freeresult($result); -				return false;  			break;  			// PostgreSQL has a way of doing this in a much simpler way but would @@ -990,19 +1094,6 @@ class phpbb_db_tools  					WHERE c.relname = '{$table}'  						AND a.attnum > 0  						AND a.attrelid = c.oid"; -				$result = $this->db->sql_query($sql); -				while ($row = $this->db->sql_fetchrow($result)) -				{ -					// lower case just in case -					if (strtolower($row['attname']) == $column_name) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -				$this->db->sql_freeresult($result); - -				return false;  			break;  			// same deal with PostgreSQL, we must perform more complex operations than @@ -1013,62 +1104,26 @@ class phpbb_db_tools  					FROM syscolumns c  					LEFT JOIN sysobjects o ON c.id = o.id  					WHERE o.name = '{$table}'"; -				$result = $this->db->sql_query($sql); -				while ($row = $this->db->sql_fetchrow($result)) -				{ -					// lower case just in case -					if (strtolower($row['name']) == $column_name) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -				$this->db->sql_freeresult($result); -				return false;  			break;  			case 'oracle':  				$sql = "SELECT column_name  					FROM user_tab_columns  					WHERE LOWER(table_name) = '" . strtolower($table) . "'"; -				$result = $this->db->sql_query($sql); -				while ($row = $this->db->sql_fetchrow($result)) -				{ -					// lower case just in case -					if (strtolower($row['column_name']) == $column_name) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -				$this->db->sql_freeresult($result); -				return false;  			break;  			case 'firebird':  				$sql = "SELECT RDB\$FIELD_NAME as FNAME  					FROM RDB\$RELATION_FIELDS  					WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'"; -				$result = $this->db->sql_query($sql); -				while ($row = $this->db->sql_fetchrow($result)) -				{ -					// lower case just in case -					if (strtolower($row['fname']) == $column_name) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -				$this->db->sql_freeresult($result); -				return false;  			break; -			// ugh, SQLite  			case 'sqlite':  				$sql = "SELECT sql  					FROM sqlite_master  					WHERE type = 'table'  						AND name = '{$table}'"; +  				$result = $this->db->sql_query($sql);  				if (!$result) @@ -1092,14 +1147,269 @@ class phpbb_db_tools  						continue;  					} -					if (strtolower($entities[0]) == $column_name) +					$column = strtolower($entities[0]); +					$columns[$column] = $column; +				} + +				return $columns; +			break; +		} + +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$column = strtolower(current($row)); +			$columns[$column] = $column; +		} +		$this->db->sql_freeresult($result); + +		return $columns; +	} + +	/** +	* Check whether a specified column exist in a table +	* +	* @param string	$table			Table to check +	* @param string	$column_name	Column to check +	* +	* @return bool		True if column exists, false otherwise +	*/ +	function sql_column_exists($table, $column_name) +	{ +		$columns = $this->sql_list_columns($table); + +		return isset($columns[$column_name]); +	} + +	/** +	* Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. +	* +	* @param string	$table_name		Table to check the index at +	* @param string	$index_name		The index name to check +	* +	* @return bool True if index exists, else false +	*/ +	function sql_index_exists($table_name, $index_name) +	{ +		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') +		{ +			$sql = "EXEC sp_statistics '$table_name'"; +			$result = $this->db->sql_query($sql); + +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				if ($row['TYPE'] == 3) +				{ +					if (strtolower($row['INDEX_NAME']) == strtolower($index_name))  					{ +						$this->db->sql_freeresult($result);  						return true;  					}  				} -				return false; +			} +			$this->db->sql_freeresult($result); + +			return false; +		} + +		switch ($this->sql_layer) +		{ +			case 'firebird': +				$sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name +					FROM RDB\$INDICES +					WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' +						AND RDB\$UNIQUE_FLAG IS NULL +						AND RDB\$FOREIGN_KEY IS NULL"; +				$col = 'index_name'; +			break; + +			case 'postgres': +				$sql = "SELECT ic.relname as index_name +					FROM pg_class bc, pg_class ic, pg_index i +					WHERE (bc.oid = i.indrelid) +						AND (ic.oid = i.indexrelid) +						AND (bc.relname = '" . $table_name . "') +						AND (i.indisunique != 't') +						AND (i.indisprimary != 't')"; +				$col = 'index_name'; +			break; + +			case 'mysql_40': +			case 'mysql_41': +				$sql = 'SHOW KEYS +					FROM ' . $table_name; +				$col = 'Key_name';  			break; + +			case 'oracle': +				$sql = "SELECT index_name +					FROM user_indexes +					WHERE table_name = '" . strtoupper($table_name) . "' +						AND generated = 'N' +						AND uniqueness = 'NONUNIQUE'"; +				$col = 'index_name'; +			break; + +			case 'sqlite': +				$sql = "PRAGMA index_list('" . $table_name . "');"; +				$col = 'name'; +			break; +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) +			{ +				continue; +			} + +			// These DBMS prefix index name with the table name +			switch ($this->sql_layer) +			{ +				case 'firebird': +				case 'oracle': +				case 'postgres': +				case 'sqlite': +					$row[$col] = substr($row[$col], strlen($table_name) + 1); +				break; +			} + +			if (strtolower($row[$col]) == strtolower($index_name)) +			{ +				$this->db->sql_freeresult($result); +				return true; +			}  		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	* Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. +	* +	* @param string	$table_name		Table to check the index at +	* @param string	$index_name		The index name to check +	* +	* @return bool True if index exists, else false +	*/ +	function sql_unique_index_exists($table_name, $index_name) +	{ +		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') +		{ +			$sql = "EXEC sp_statistics '$table_name'"; +			$result = $this->db->sql_query($sql); + +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				// Usually NON_UNIQUE is the column we want to check, but we allow for both +				if ($row['TYPE'] == 3) +				{ +					if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) +					{ +						$this->db->sql_freeresult($result); +						return true; +					} +				} +			} +			$this->db->sql_freeresult($result); +			return false; +		} + +		switch ($this->sql_layer) +		{ +			case 'firebird': +				$sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name +					FROM RDB\$INDICES +					WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "' +						AND RDB\$UNIQUE_FLAG IS NOT NULL +						AND RDB\$FOREIGN_KEY IS NULL"; +				$col = 'index_name'; +			break; + +			case 'postgres': +				$sql = "SELECT ic.relname as index_name, i.indisunique +					FROM pg_class bc, pg_class ic, pg_index i +					WHERE (bc.oid = i.indrelid) +						AND (ic.oid = i.indexrelid) +						AND (bc.relname = '" . $table_name . "') +						AND (i.indisprimary != 't')"; +				$col = 'index_name'; +			break; + +			case 'mysql_40': +			case 'mysql_41': +				$sql = 'SHOW KEYS +					FROM ' . $table_name; +				$col = 'Key_name'; +			break; + +			case 'oracle': +				$sql = "SELECT index_name, table_owner +					FROM user_indexes +					WHERE table_name = '" . strtoupper($table_name) . "' +						AND generated = 'N' +						AND uniqueness = 'UNIQUE'"; +				$col = 'index_name'; +			break; + +			case 'sqlite': +				$sql = "PRAGMA index_list('" . $table_name . "');"; +				$col = 'name'; +			break; +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) +			{ +				continue; +			} + +			if ($this->sql_layer == 'sqlite' && !$row['unique']) +			{ +				continue; +			} + +			if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') +			{ +				continue; +			} + +			// These DBMS prefix index name with the table name +			switch ($this->sql_layer) +			{ +				case 'oracle': +					// Two cases here... prefixed with U_[table_owner] and not prefixed with table_name +					if (strpos($row[$col], 'U_') === 0) +					{ +						$row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1); +					} +					else if (strpos($row[$col], strtoupper($table_name)) === 0) +					{ +						$row[$col] = substr($row[$col], strlen($table_name) + 1); +					} +				break; + +				case 'firebird': +				case 'postgres': +				case 'sqlite': +					$row[$col] = substr($row[$col], strlen($table_name) + 1); +				break; +			} + +			if (strtolower($row[$col]) == strtolower($index_name)) +			{ +				$this->db->sql_freeresult($result); +				return true; +			} +		} +		$this->db->sql_freeresult($result); + +		return false;  	}  	/** @@ -1139,6 +1449,11 @@ class phpbb_db_tools  	*/  	function sql_prepare_column_data($table_name, $column_name, $column_data)  	{ +		if (strlen($column_name) > 30) +		{ +			trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); +		} +  		// Get type  		if (strpos($column_data[0], ':') !== false)  		{ @@ -1187,7 +1502,7 @@ class phpbb_db_tools  			$column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]];  		} -		// Adjust default value if db-dependant specified +		// Adjust default value if db-dependent specified  		if (is_array($column_data[1]))  		{  			$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; @@ -1371,24 +1686,29 @@ class phpbb_db_tools  		switch ($this->sql_layer)  		{  			case 'firebird': +				// Does not support AFTER statement, only POSITION (and there you need the column position)  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql'];  			break;  			case 'mssql':  			case 'mssqlnative': +				// Does not support AFTER, only through temporary table  				$statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default'];  			break;  			case 'mysql_40':  			case 'mysql_41': -				$statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql']; +				$after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; +				$statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after;  			break;  			case 'oracle': +				// Does not support AFTER, only through temporary table  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];  			break;  			case 'postgres': +				// Does not support AFTER, only through temporary table  				if (version_compare($this->db->sql_server_info(true), '8.0', '>='))  				{  					$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; @@ -1506,7 +1826,7 @@ class phpbb_db_tools  			break;  			case 'oracle': -				$statements[] = 'ALTER TABLE ' . $table_name . ' DROP ' . $column_name; +				$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;  			break;  			case 'postgres': @@ -1657,6 +1977,7 @@ class phpbb_db_tools  					$statements[] = "DROP SEQUENCE {$row['referenced_name']}";  				}  				$this->db->sql_freeresult($result); +			break;  			case 'postgres':  				// PGSQL does not "tightly" bind sequences and tables, we must guess... @@ -1774,6 +2095,13 @@ class phpbb_db_tools  	{  		$statements = array(); +		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) +		if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) +		{ +			$max_length = strlen($table_prefix) + 24; +			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); +		} +  		switch ($this->sql_layer)  		{  			case 'firebird': @@ -1785,7 +2113,7 @@ class phpbb_db_tools  			case 'mysql_40':  			case 'mysql_41': -				$statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; +				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')';  			break;  			case 'mssql': @@ -1804,6 +2132,13 @@ class phpbb_db_tools  	{  		$statements = array(); +		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) +		if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) +		{ +			$max_length = strlen($table_prefix) + 24; +			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); +		} +  		// remove index length unless MySQL4  		if ('mysql_40' != $this->sql_layer)  		{ @@ -1831,7 +2166,7 @@ class phpbb_db_tools  				}  			// no break  			case 'mysql_41': -				$statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; +				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')';  			break;  			case 'mssql': @@ -1957,6 +2292,7 @@ class phpbb_db_tools  				}  				else  				{ +					// TODO: try to change pkey without removing trigger, generator or constraints. ATM this query may fail.  					$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type'];  				}  			break; diff --git a/phpBB/includes/db/dbal.php b/phpBB/includes/db/dbal.php index 148f56b5a8..1de236d3de 100644 --- a/phpBB/includes/db/dbal.php +++ b/phpBB/includes/db/dbal.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -195,6 +194,49 @@ class dbal  	}  	/** +	* Seek to given row number +	* rownum is zero-based +	*/ +	function sql_rowseek($rownum, &$query_id) +	{ +		global $cache; + +		if ($query_id === false) +		{ +			$query_id = $this->query_result; +		} + +		if ($cache->sql_exists($query_id)) +		{ +			return $cache->sql_rowseek($rownum, $query_id); +		} + +		if ($query_id === false) +		{ +			return false; +		} + +		$this->sql_freeresult($query_id); +		$query_id = $this->sql_query($this->last_query_text); + +		if ($query_id === false) +		{ +			return false; +		} + +		// We do not fetch the row for rownum == 0 because then the next resultset would be the second row +		for ($i = 0; $i < $rownum; $i++) +		{ +			if (!$this->sql_fetchrow($query_id)) +			{ +				return false; +			} +		} + +		return true; +	} + +	/**  	* Fetch field  	* if rownum is false, the current row is used, else it is pointing to the row (zero-based)  	*/ @@ -214,7 +256,7 @@ class dbal  				$this->sql_rowseek($rownum, $query_id);  			} -			if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) +			if (!is_object($query_id) && $cache->sql_exists($query_id))  			{  				return $cache->sql_fetchfield($query_id, $field);  			} @@ -242,11 +284,42 @@ 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.  	*/ -	function sql_buffer_nested_transaction() +	function sql_buffer_nested_transactions()  	{  		return false;  	} @@ -480,6 +553,18 @@ class dbal  	}  	/** +	* Run LOWER() on DB column of type text (i.e. neither varchar nor char). +	* +	* @param string $column_name	The column name to use +	* +	* @return string				A SQL statement like "LOWER($column_name)" +	*/ +	function sql_lower_text($column_name) +	{ +		return "LOWER($column_name)"; +	} + +	/**  	* Run more than one insert statement.  	*  	* @param string $table table name to run the statements on @@ -631,7 +716,7 @@ class dbal  					}  				} -				$sql .= $this->_sql_custom_build('FROM', implode(', ', $table_array)); +				$sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array));  				if (!empty($array['LEFT_JOIN']))  				{ @@ -684,12 +769,7 @@ class dbal  			// The DEBUG_EXTRA constant is for development only!  			if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG_EXTRA'))  			{ -				// Print out a nice backtrace... -				$backtrace = get_backtrace(); -  				$message .= ($sql) ? '<br /><br />SQL<br /><br />' . htmlspecialchars($sql) : ''; -				$message .= ($backtrace) ? '<br /><br />BACKTRACE<br />' . $backtrace : ''; -				$message .= '<br />';  			}  			else  			{ @@ -767,12 +847,10 @@ class dbal  				$mtime = explode(' ', microtime());  				$totaltime = $mtime[0] + $mtime[1] - $starttime; -				echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -					<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr"> +				echo '<!DOCTYPE html> +					<html dir="ltr">  					<head> -						<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> -						<meta http-equiv="Content-Style-Type" content="text/css" /> -						<meta http-equiv="imagetoolbar" content="no" /> +						<meta charset="utf-8">  						<title>SQL Report</title>  						<link href="' . $phpbb_root_path . 'adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" />  					</head> @@ -800,7 +878,7 @@ class dbal  							</div>  						</div>  						<div id="page-footer"> -							Powered by <a href="http://www.phpbb.com/">phpBB</a> © phpBB Group +							Powered by <a href="http://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group  						</div>  					</div>  					</body> @@ -928,6 +1006,41 @@ class dbal  		return true;  	} + +	/** +	* Gets the estimated number of rows in a specified table. +	* +	* @param string $table_name		Table name +	* +	* @return string				Number of rows in $table_name. +	*								Prefixed with ~ if estimated (otherwise exact). +	* +	* @access public +	*/ +	function get_estimated_row_count($table_name) +	{ +		return $this->get_row_count($table_name); +	} + +	/** +	* Gets the exact number of rows in a specified table. +	* +	* @param string $table_name		Table name +	* +	* @return string				Exact number of rows in $table_name. +	* +	* @access public +	*/ +	function get_row_count($table_name) +	{ +		$sql = 'SELECT COUNT(*) AS rows_total +			FROM ' . $this->sql_escape($table_name); +		$result = $this->sql_query($sql); +		$rows_total = $this->sql_fetchfield('rows_total'); +		$this->sql_freeresult($result); + +		return $rows_total; +	}  }  /** diff --git a/phpBB/includes/db/firebird.php b/phpBB/includes/db/firebird.php index 8868d4e317..9f9b8a1abd 100644 --- a/phpBB/includes/db/firebird.php +++ b/phpBB/includes/db/firebird.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -157,7 +156,7 @@ class dbal_firebird extends dbal  			}  			$this->last_query_text = $query; -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -270,10 +269,10 @@ class dbal_firebird extends dbal  					}  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -333,7 +332,7 @@ class dbal_firebird extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -360,49 +359,6 @@ class dbal_firebird extends dbal  	}  	/** -	* Seek to given row number -	* rownum is zero-based -	*/ -	function sql_rowseek($rownum, &$query_id) -	{ -		global $cache; - -		if ($query_id === false) -		{ -			$query_id = $this->query_result; -		} - -		if (isset($cache->sql_rowset[$query_id])) -		{ -			return $cache->sql_rowseek($rownum, $query_id); -		} - -		if ($query_id === false) -		{ -			return; -		} - -		$this->sql_freeresult($query_id); -		$query_id = $this->sql_query($this->last_query_text); - -		if ($query_id === false) -		{ -			return false; -		} - -		// We do not fetch the row for rownum == 0 because then the next resultset would be the second row -		for ($i = 0; $i < $rownum; $i++) -		{ -			if (!$this->sql_fetchrow($query_id)) -			{ -				return false; -			} -		} - -		return true; -	} - -	/**  	* Get last inserted id after insert statement  	*/  	function sql_nextid() @@ -442,7 +398,7 @@ class dbal_firebird extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} @@ -497,7 +453,8 @@ class dbal_firebird extends dbal  	*/  	function cast_expr_to_bigint($expression)  	{ -		return 'CAST(' . $expression . ' as DECIMAL(255, 0))'; +		// Precision must be from 1 to 18 +		return 'CAST(' . $expression . ' as DECIMAL(18, 0))';  	}  	/** diff --git a/phpBB/includes/db/mssql.php b/phpBB/includes/db/mssql.php index 386b0a9a23..bde283c3ea 100644 --- a/phpBB/includes/db/mssql.php +++ b/phpBB/includes/db/mssql.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -93,6 +92,14 @@ class dbal_mssql extends dbal  	}  	/** +	* {@inheritDoc} +	*/ +	public function sql_concatenate($expr1, $expr2) +	{ +		return $expr1 . ' + ' . $expr2; +	} + +	/**  	* SQL Transaction  	* @access private  	*/ @@ -137,7 +144,7 @@ class dbal_mssql extends dbal  				$this->sql_report('start', $query);  			} -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -152,10 +159,10 @@ class dbal_mssql extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -227,7 +234,7 @@ class dbal_mssql extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -264,7 +271,7 @@ class dbal_mssql extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_rowseek($rownum, $query_id);  		} @@ -303,7 +310,7 @@ class dbal_mssql extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} @@ -326,6 +333,14 @@ class dbal_mssql extends dbal  	}  	/** +	* {@inheritDoc} +	*/ +	function sql_lower_text($column_name) +	{ +		return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; +	} + +	/**  	* Build LIKE expression  	* @access private  	*/ diff --git a/phpBB/includes/db/mssql_odbc.php b/phpBB/includes/db/mssql_odbc.php index d17280c5a6..687bc52abc 100644 --- a/phpBB/includes/db/mssql_odbc.php +++ b/phpBB/includes/db/mssql_odbc.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -111,6 +110,14 @@ class dbal_mssql_odbc extends dbal  	}  	/** +	* {@inheritDoc} +	*/ +	public function sql_concatenate($expr1, $expr2) +	{ +		return $expr1 . ' + ' . $expr2; +	} + +	/**  	* SQL Transaction  	* @access private  	*/ @@ -156,7 +163,7 @@ class dbal_mssql_odbc extends dbal  			}  			$this->last_query_text = $query; -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -171,10 +178,10 @@ class dbal_mssql_odbc extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -247,7 +254,7 @@ class dbal_mssql_odbc extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -256,49 +263,6 @@ class dbal_mssql_odbc extends dbal  	}  	/** -	* Seek to given row number -	* rownum is zero-based -	*/ -	function sql_rowseek($rownum, &$query_id) -	{ -		global $cache; - -		if ($query_id === false) -		{ -			$query_id = $this->query_result; -		} - -		if (isset($cache->sql_rowset[$query_id])) -		{ -			return $cache->sql_rowseek($rownum, $query_id); -		} - -		if ($query_id === false) -		{ -			return false; -		} - -		$this->sql_freeresult($query_id); -		$query_id = $this->sql_query($this->last_query_text); - -		if ($query_id === false) -		{ -			return false; -		} - -		// We do not fetch the row for rownum == 0 because then the next resultset would be the second row -		for ($i = 0; $i < $rownum; $i++) -		{ -			if (!$this->sql_fetchrow($query_id)) -			{ -				return false; -			} -		} - -		return true; -	} - -	/**  	* Get last inserted id after insert statement  	*/  	function sql_nextid() @@ -331,7 +295,7 @@ class dbal_mssql_odbc extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} @@ -354,6 +318,14 @@ class dbal_mssql_odbc extends dbal  	}  	/** +	* {@inheritDoc} +	*/ +	function sql_lower_text($column_name) +	{ +		return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; +	} + +	/**  	* Build LIKE expression  	* @access private  	*/ diff --git a/phpBB/includes/db/mssqlnative.php b/phpBB/includes/db/mssqlnative.php index 710a054e5f..c31f7f6892 100644 --- a/phpBB/includes/db/mssqlnative.php +++ b/phpBB/includes/db/mssqlnative.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  * This is the MS SQL Server Native database abstraction layer.  * PHP mssql native driver required. @@ -219,7 +218,6 @@ class dbal_mssqlnative extends dbal  		$this->server = $sqlserver . (($port) ? $port_delimiter . $port : '');  		//connect to database -		error_reporting(E_ALL);  		$this->db_connect_id = sqlsrv_connect($this->server, array(  			'Database' => $this->dbname,  			'UID' => $this->user, @@ -261,7 +259,15 @@ class dbal_mssqlnative extends dbal  	/**  	* {@inheritDoc}  	*/ -	function sql_buffer_nested_transaction() +	public function sql_concatenate($expr1, $expr2) +	{ +		return $expr1 . ' + ' . $expr2; +	} + +	/** +	* {@inheritDoc} +	*/ +	function sql_buffer_nested_transactions()  	{  		return true;  	} @@ -311,7 +317,7 @@ class dbal_mssqlnative extends dbal  			}  			$this->last_query_text = $query; -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -328,10 +334,10 @@ class dbal_mssqlnative extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -396,7 +402,7 @@ class dbal_mssqlnative extends dbal  	*/  	function sql_affectedrows()  	{ -		return ($this->db_connect_id) ? @sqlsrv_rows_affected($this->db_connect_id) : false; +		return (!empty($this->query_result)) ? @sqlsrv_rows_affected($this->query_result) : false;  	}  	/** @@ -411,7 +417,7 @@ class dbal_mssqlnative extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -436,25 +442,7 @@ class dbal_mssqlnative extends dbal  				unset($row['line2'], $row['line3']);  			}  		} -		return $row; -	} - -	/** -	* Seek to given row number -	* rownum is zero-based -	*/ -	function sql_rowseek($rownum, &$query_id) -	{ -		global $cache; - -		if (isset($cache->sql_rowset[$query_id])) -		{ -			return $cache->sql_rowseek($rownum, $query_id); -		} - -		$seek = new result_mssqlnative($query_id); -		$row = $seek->seek($rownum); -		return ($row = $seek->fetch()) ? $row : false; +		return (sizeof($row)) ? $row : false;  	}  	/** @@ -489,7 +477,7 @@ class dbal_mssqlnative extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} @@ -511,6 +499,14 @@ class dbal_mssqlnative extends dbal  	}  	/** +	* {@inheritDoc} +	*/ +	function sql_lower_text($column_name) +	{ +		return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; +	} + +	/**  	* Build LIKE expression  	* @access private  	*/ diff --git a/phpBB/includes/db/mysql.php b/phpBB/includes/db/mysql.php index d0537e2fe9..5b4ff86579 100644 --- a/phpBB/includes/db/mysql.php +++ b/phpBB/includes/db/mysql.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -121,6 +120,14 @@ class dbal_mysql extends dbal  	}  	/** +	* {@inheritDoc} +	*/ +	public function sql_concatenate($expr1, $expr2) +	{ +		return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; +	} + +	/**  	* SQL Transaction  	* @access private  	*/ @@ -165,7 +172,7 @@ class dbal_mysql extends dbal  				$this->sql_report('start', $query);  			} -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -180,10 +187,10 @@ class dbal_mysql extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -242,7 +249,7 @@ class dbal_mysql extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -263,7 +270,7 @@ class dbal_mysql extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_rowseek($rownum, $query_id);  		} @@ -291,7 +298,7 @@ class dbal_mysql extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} @@ -319,6 +326,76 @@ class dbal_mysql extends dbal  	}  	/** +	* Gets the estimated number of rows in a specified table. +	* +	* @param string $table_name		Table name +	* +	* @return string				Number of rows in $table_name. +	*								Prefixed with ~ if estimated (otherwise exact). +	* +	* @access public +	*/ +	function get_estimated_row_count($table_name) +	{ +		$table_status = $this->get_table_status($table_name); + +		if (isset($table_status['Engine'])) +		{ +			if ($table_status['Engine'] === 'MyISAM') +			{ +				return $table_status['Rows']; +			} +			else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) +			{ +				return '~' . $table_status['Rows']; +			} +		} + +		return parent::get_row_count($table_name); +	} + +	/** +	* Gets the exact number of rows in a specified table. +	* +	* @param string $table_name		Table name +	* +	* @return string				Exact number of rows in $table_name. +	* +	* @access public +	*/ +	function get_row_count($table_name) +	{ +		$table_status = $this->get_table_status($table_name); + +		if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') +		{ +			return $table_status['Rows']; +		} + +		return parent::get_row_count($table_name); +	} + +	/** +	* Gets some information about the specified table. +	* +	* @param string $table_name		Table name +	* +	* @return array +	* +	* @access protected +	*/ +	function get_table_status($table_name) +	{ +		$sql = "SHOW TABLE STATUS +			LIKE '" . $this->sql_escape($table_name) . "'"; +		$result = $this->sql_query($sql); +		$table_status = $this->sql_fetchrow($result); +		$this->sql_freeresult($result); + +		return $table_status; +	} + +	/**  	* Build LIKE expression  	* @access private  	*/ diff --git a/phpBB/includes/db/mysqli.php b/phpBB/includes/db/mysqli.php index 7c72fe9f01..1f13bd5459 100644 --- a/phpBB/includes/db/mysqli.php +++ b/phpBB/includes/db/mysqli.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -33,14 +32,33 @@ class dbal_mysqli extends dbal  	*/  	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false , $new_link = false)  	{ -		$this->persistency = $persistency; +		// Mysqli extension supports persistent connection since PHP 5.3.0 +		$this->persistency = (version_compare(PHP_VERSION, '5.3.0', '>=')) ? $persistency : false;  		$this->user = $sqluser; -		$this->server = $sqlserver; + +		// If persistent connection, set dbhost to localhost when empty and prepend it with 'p:' prefix +		$this->server = ($this->persistency) ? 'p:' . (($sqlserver) ? $sqlserver : 'localhost') : $sqlserver; +  		$this->dbname = $database;  		$port = (!$port) ? NULL : $port; -		// Persistant connections not supported by the mysqli extension? -		$this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, $this->dbname, $port); +		// If port is set and it is not numeric, most likely mysqli socket is set. +		// Try to map it to the $socket parameter. +		$socket = NULL; +		if ($port) +		{ +			if (is_numeric($port)) +			{ +				$port = (int) $port; +			} +			else +			{ +				$socket = $port; +				$port = NULL; +			} +		} + +		$this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, $this->dbname, $port, $socket);  		if ($this->db_connect_id && $this->dbname != '')  		{ @@ -105,6 +123,14 @@ class dbal_mysqli extends dbal  	}  	/** +	* {@inheritDoc} +	*/ +	public function sql_concatenate($expr1, $expr2) +	{ +		return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; +	} + +	/**  	* SQL Transaction  	* @access private  	*/ @@ -153,7 +179,7 @@ class dbal_mysqli extends dbal  				$this->sql_report('start', $query);  			} -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -168,9 +194,9 @@ class dbal_mysqli extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{ -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  			}  			else if (defined('DEBUG_EXTRA')) @@ -225,12 +251,18 @@ class dbal_mysqli extends dbal  			$query_id = $this->query_result;  		} -		if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) +		if (!is_object($query_id) && $cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @mysqli_fetch_assoc($query_id) : false; +		if ($query_id !== false) +		{ +			$result = @mysqli_fetch_assoc($query_id); +			return $result !== null ? $result : false; +		} + +		return false;  	}  	/** @@ -246,7 +278,7 @@ class dbal_mysqli extends dbal  			$query_id = $this->query_result;  		} -		if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) +		if (!is_object($query_id) && $cache->sql_exists($query_id))  		{  			return $cache->sql_rowseek($rownum, $query_id);  		} @@ -274,7 +306,7 @@ class dbal_mysqli extends dbal  			$query_id = $this->query_result;  		} -		if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) +		if (!is_object($query_id) && $cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} @@ -291,6 +323,76 @@ class dbal_mysqli extends dbal  	}  	/** +	* Gets the estimated number of rows in a specified table. +	* +	* @param string $table_name		Table name +	* +	* @return string				Number of rows in $table_name. +	*								Prefixed with ~ if estimated (otherwise exact). +	* +	* @access public +	*/ +	function get_estimated_row_count($table_name) +	{ +		$table_status = $this->get_table_status($table_name); + +		if (isset($table_status['Engine'])) +		{ +			if ($table_status['Engine'] === 'MyISAM') +			{ +				return $table_status['Rows']; +			} +			else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) +			{ +				return '~' . $table_status['Rows']; +			} +		} + +		return parent::get_row_count($table_name); +	} + +	/** +	* Gets the exact number of rows in a specified table. +	* +	* @param string $table_name		Table name +	* +	* @return string				Exact number of rows in $table_name. +	* +	* @access public +	*/ +	function get_row_count($table_name) +	{ +		$table_status = $this->get_table_status($table_name); + +		if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') +		{ +			return $table_status['Rows']; +		} + +		return parent::get_row_count($table_name); +	} + +	/** +	* Gets some information about the specified table. +	* +	* @param string $table_name		Table name +	* +	* @return array +	* +	* @access protected +	*/ +	function get_table_status($table_name) +	{ +		$sql = "SHOW TABLE STATUS +			LIKE '" . $this->sql_escape($table_name) . "'"; +		$result = $this->sql_query($sql); +		$table_status = $this->sql_fetchrow($result); +		$this->sql_freeresult($result); + +		return $table_status; +	} + +	/**  	* Build LIKE expression  	* @access private  	*/ diff --git a/phpBB/includes/db/oracle.php b/phpBB/includes/db/oracle.php index e0d9370bd8..de2729e973 100644 --- a/phpBB/includes/db/oracle.php +++ b/phpBB/includes/db/oracle.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -243,7 +242,7 @@ class dbal_oracle extends dbal  			}  			$this->last_query_text = $query; -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -419,10 +418,10 @@ class dbal_oracle extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -474,7 +473,7 @@ class dbal_oracle extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -526,7 +525,7 @@ class dbal_oracle extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_rowseek($rownum, $query_id);  		} @@ -595,7 +594,7 @@ class dbal_oracle extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} diff --git a/phpBB/includes/db/postgres.php b/phpBB/includes/db/postgres.php index 959d8df139..f0a4a7a7a2 100644 --- a/phpBB/includes/db/postgres.php +++ b/phpBB/includes/db/postgres.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -201,7 +200,7 @@ class dbal_postgres extends dbal  			}  			$this->last_query_text = $query; -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -216,10 +215,10 @@ class dbal_postgres extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -286,7 +285,7 @@ class dbal_postgres extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -307,7 +306,7 @@ class dbal_postgres extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_rowseek($rownum, $query_id);  		} @@ -356,7 +355,7 @@ class dbal_postgres extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} diff --git a/phpBB/includes/db/sqlite.php b/phpBB/includes/db/sqlite.php index 88d92d1ff2..2cf55b07e2 100644 --- a/phpBB/includes/db/sqlite.php +++ b/phpBB/includes/db/sqlite.php @@ -2,9 +2,8 @@  /**  *  * @package dbal -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -118,7 +117,7 @@ class dbal_sqlite extends dbal  				$this->sql_report('start', $query);  			} -			$this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; +			$this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false;  			$this->sql_add_num_queries($this->query_result);  			if ($this->query_result === false) @@ -133,10 +132,10 @@ class dbal_sqlite extends dbal  					$this->sql_report('stop', $query);  				} -				if ($cache_ttl && method_exists($cache, 'sql_save')) +				if ($cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$cache->sql_save($query, $this->query_result, $cache_ttl); +					$this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl);  				}  				else if (strpos($query, 'SELECT') === 0 && $this->query_result)  				{ @@ -194,7 +193,7 @@ class dbal_sqlite extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_fetchrow($query_id);  		} @@ -215,7 +214,7 @@ class dbal_sqlite extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_rowseek($rownum, $query_id);  		} @@ -243,7 +242,7 @@ class dbal_sqlite extends dbal  			$query_id = $this->query_result;  		} -		if (isset($cache->sql_rowset[$query_id])) +		if ($cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id);  		} diff --git a/phpBB/includes/di/processor/config.php b/phpBB/includes/di/processor/config.php new file mode 100644 index 0000000000..22b6252a6d --- /dev/null +++ b/phpBB/includes/di/processor/config.php @@ -0,0 +1,76 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** +* Configure the container for phpBB's services though +* user-defined parameters defined in the config.php file. +*/ +class phpbb_di_processor_config implements phpbb_di_processor_interface +{ +	private $config_file; +	private $phpbb_root_path; +	private $php_ext; + +	/** +	* Constructor. +	* +	* @param string $config_file The config file +	* @param string $phpbb_root_path The root path +	* @param string $php_ext The PHP extension +	*/ +	public function __construct($config_file, $phpbb_root_path, $php_ext) +	{ +		$this->config_file = $config_file; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +	} + +	/** +	* @inheritdoc +	*/ +	public function process(ContainerBuilder $container) +	{ +		require $this->config_file; + +		$container->setParameter('core.root_path', $this->phpbb_root_path); +		$container->setParameter('core.php_ext', $this->php_ext); + +		$container->setParameter('core.table_prefix', $table_prefix); +		$container->setParameter('cache.driver.class', $this->fix_acm_type($acm_type)); +		$container->setParameter('dbal.driver.class', 'dbal_'.$dbms); +		$container->setParameter('dbal.dbhost', $dbhost); +		$container->setParameter('dbal.dbuser', $dbuser); +		$container->setParameter('dbal.dbpasswd', $dbpasswd); +		$container->setParameter('dbal.dbname', $dbname); +		$container->setParameter('dbal.dbport', $dbport); +		$container->setParameter('dbal.new_link', defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK); + +		$container->set('container', $container); +	} + +	protected function fix_acm_type($acm_type) +	{ +		if (preg_match('#^[a-z]+$#', $acm_type)) +		{ +			return 'phpbb_cache_driver_'.$acm_type; +		} + +		return $acm_type; +	} +} diff --git a/phpBB/includes/di/processor/ext.php b/phpBB/includes/di/processor/ext.php new file mode 100644 index 0000000000..e69a3d73b3 --- /dev/null +++ b/phpBB/includes/di/processor/ext.php @@ -0,0 +1,54 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** +* Load the service configurations from all extensions into the container. +*/ +class phpbb_di_processor_ext implements phpbb_di_processor_interface +{ +	private $extension_manager; + +	/** +	* Constructor. +	* +	* @param string $extension_manager The extension manager +	*/ +	public function __construct($extension_manager) +	{ +		$this->extension_manager = $extension_manager; +	} + +	/** +	* @inheritdoc +	*/ +	public function process(ContainerBuilder $container) +	{ +		$enabled_exts = $this->extension_manager->all_enabled(); +		foreach ($enabled_exts as $name => $path) +		{ +			if (file_exists($path . '/config/services.yml')) +			{ +				$loader = new YamlFileLoader($container, new FileLocator($path . '/config')); +				$loader->load('services.yml'); +			} +		} +	} +} diff --git a/phpBB/includes/di/processor/interface.php b/phpBB/includes/di/processor/interface.php new file mode 100644 index 0000000000..b8563791cc --- /dev/null +++ b/phpBB/includes/di/processor/interface.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +interface phpbb_di_processor_interface +{ +	/** +	* Mutate the container. +	* +	* @param ContainerBuilder $container The container +	*/ +	public function process(ContainerBuilder $container); +} diff --git a/phpBB/includes/diff/diff.php b/phpBB/includes/diff/diff.php index 3ab546f03c..1ffa3429b5 100644 --- a/phpBB/includes/diff/diff.php +++ b/phpBB/includes/diff/diff.php @@ -2,9 +2,8 @@  /**  *  * @package diff -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/diff/engine.php b/phpBB/includes/diff/engine.php index 1aa052e86e..dc1ca5875f 100644 --- a/phpBB/includes/diff/engine.php +++ b/phpBB/includes/diff/engine.php @@ -2,9 +2,8 @@  /**  *  * @package diff -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/diff/renderer.php b/phpBB/includes/diff/renderer.php index 957491365b..eacd42d760 100644 --- a/phpBB/includes/diff/renderer.php +++ b/phpBB/includes/diff/renderer.php @@ -2,9 +2,8 @@  /**  *  * @package diff -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/error_collector.php b/phpBB/includes/error_collector.php index 55834f354c..358da747b8 100644 --- a/phpBB/includes/error_collector.php +++ b/phpBB/includes/error_collector.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB -* @version $Id$  * @copyright (c) 2011 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -49,13 +48,15 @@ class phpbb_error_collector  			{  				$text .= "<br />\n";  			} +  			list($errno, $msg_text, $errfile, $errline) = $error; -			$text .= "Errno $errno: $msg_text"; -			if (defined('DEBUG_EXTRA') || defined('IN_INSTALL')) -			{ -				$text .= " at $errfile line $errline"; -			} + +			// Prevent leakage of local path to phpBB install +			$errfile = phpbb_filter_root_path($errfile); + +			$text .= "Errno $errno: $msg_text at $errfile line $errline";  		} +  		return $text;  	}  } diff --git a/phpBB/includes/event/data.php b/phpBB/includes/event/data.php new file mode 100644 index 0000000000..70718ff0ae --- /dev/null +++ b/phpBB/includes/event/data.php @@ -0,0 +1,68 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +    exit; +} + +use Symfony\Component\EventDispatcher\Event; + +class phpbb_event_data extends Event implements ArrayAccess +{ +    private $data; + +    public function __construct(array $data = array()) +    { +        $this->set_data($data); +    } + +    public function set_data(array $data = array()) +    { +        $this->data = $data; +    } + +    public function get_data() +    { +        return $this->data; +    } + +    /** +     * Returns data filtered to only include specified keys. +     * +     * This effectively discards any keys added to data by hooks. +     */ +    public function get_data_filtered($keys) +    { +        return array_intersect_key($this->data, array_flip($keys)); +    } + +    public function offsetExists($offset) +    { +        return isset($this->data[$offset]); +    } + +    public function offsetGet($offset) +    { +        return isset($this->data[$offset]) ? $this->data[$offset] : null; +    } + +    public function offsetSet($offset, $value) +    { +        $this->data[$offset] = $value; +    } + +    public function offsetUnset($offset) +    { +        unset($this->data[$offset]); +    } +} diff --git a/phpBB/includes/event/dispatcher.php b/phpBB/includes/event/dispatcher.php new file mode 100644 index 0000000000..2bf46b9b06 --- /dev/null +++ b/phpBB/includes/event/dispatcher.php @@ -0,0 +1,42 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +use Symfony\Component\EventDispatcher\EventDispatcher; + +/** +* Extension of the Symfony2 EventDispatcher +* +* It provides an additional `trigger_event` method, which +* gives some syntactic sugar for dispatching events. Instead +* of creating the event object, the method will do that for +* you. +* +* Example: +* +*     $vars = array('page_title'); +*     extract($phpbb_dispatcher->trigger_event('core.index', compact($vars))); +* +*/ +class phpbb_event_dispatcher extends EventDispatcher +{ +	public function trigger_event($eventName, $data = array()) +	{ +		$event = new phpbb_event_data($data); +		$this->dispatch($eventName, $event); +		return $event->get_data_filtered(array_keys($data)); +	} +} diff --git a/phpBB/includes/event/extension_subscriber_loader.php b/phpBB/includes/event/extension_subscriber_loader.php new file mode 100644 index 0000000000..d933b943d7 --- /dev/null +++ b/phpBB/includes/event/extension_subscriber_loader.php @@ -0,0 +1,46 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class phpbb_event_extension_subscriber_loader +{ +	private $dispatcher; +	private $extension_manager; + +	public function __construct(EventDispatcherInterface $dispatcher, phpbb_extension_manager $extension_manager) +	{ +		$this->dispatcher = $dispatcher; +		$this->extension_manager = $extension_manager; +	} + +	public function load() +	{ +		$finder = $this->extension_manager->get_finder(); +		$subscriber_classes = $finder +			->extension_directory('/event') +			->suffix('listener') +			->core_path('event/') +			->get_classes(); + +		foreach ($subscriber_classes as $class) +		{ +			$subscriber = new $class(); +			$this->dispatcher->addSubscriber($subscriber); +		} +	} +} diff --git a/phpBB/includes/extension/base.php b/phpBB/includes/extension/base.php new file mode 100644 index 0000000000..9d076eb6c5 --- /dev/null +++ b/phpBB/includes/extension/base.php @@ -0,0 +1,57 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* A base class for extensions without custom enable/disable/purge code. +* +* @package extension +*/ +class phpbb_extension_base implements phpbb_extension_interface +{ +	/** +	* Single enable step that does nothing +	* +	* @param mixed $old_state State returned by previous call of this method +	* @return false Indicates no further steps are required +	*/ +	public function enable_step($old_state) +	{ +		return false; +	} + +	/** +	* Single disable step that does nothing +	* +	* @param mixed $old_state State returned by previous call of this method +	* @return false Indicates no further steps are required +	*/ +	public function disable_step($old_state) +	{ +		return false; +	} + +	/** +	* Single purge step that does nothing +	* +	* @param mixed $old_state State returned by previous call of this method +	* @return false Indicates no further steps are required +	*/ +	public function purge_step($old_state) +	{ +		return false; +	} +} diff --git a/phpBB/includes/extension/controller.php b/phpBB/includes/extension/controller.php new file mode 100644 index 0000000000..f97b69c7ed --- /dev/null +++ b/phpBB/includes/extension/controller.php @@ -0,0 +1,84 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Abstract class extended by extension front controller classes +* +* @package extension +*/ +abstract class phpbb_extension_controller implements phpbb_extension_controller_interface +{ +	/** +	* Request class object +	* @var phpbb_request +	*/ +	protected $request; + +	/** +	* DBAL class object +	* @var dbal +	*/ +	protected $db; + +	/** +	* User class object +	* @var phpbb_user +	*/ +	protected $user; + +	/** +	* Template class object +	* @var phpbb_template +	*/ +	protected $template; + +	/** +	* Config object +	* @var phpbb_config +	*/ +	protected $config; + +	/** +	* PHP Extension +	* @var string +	*/ +	protected $php_ext; + +	/** +	* Relative path to board root +	* @var string +	*/ +	protected $phpbb_root_path; + +	/** +	* Constructor method that provides the common phpBB objects as inherited class +	* properties for automatic availability in extension controllers +	*/ +	public function __construct() +	{ +		global $request, $db, $user, $template, $config; +		global $phpEx, $phpbb_root_path; + +		$this->request = $request; +		$this->db = $db; +		$this->user = $user; +		$this->template = $template; +		$this->config = $config; +		$this->php_ext = $phpEx; +		$this->phpbb_root_path = $phpbb_root_path; +	} +} diff --git a/phpBB/includes/extension/controller_interface.php b/phpBB/includes/extension/controller_interface.php new file mode 100644 index 0000000000..2b88925388 --- /dev/null +++ b/phpBB/includes/extension/controller_interface.php @@ -0,0 +1,31 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The interface that extension classes have to implement to run front pages +* +* @package extension +*/ +interface phpbb_extension_controller_interface +{ +	/** +	* Handle the request to display a page from an extension +	* +	* @return	null +	*/ +	public function handle(); +} diff --git a/phpBB/includes/extension/exception.php b/phpBB/includes/extension/exception.php new file mode 100644 index 0000000000..e08a8912ea --- /dev/null +++ b/phpBB/includes/extension/exception.php @@ -0,0 +1,27 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** + * Exception class for metadata + */ +class phpbb_extension_exception extends UnexpectedValueException +{ +	public function __toString() +	{ +		return $this->getMessage(); +	} +}
\ No newline at end of file diff --git a/phpBB/includes/extension/finder.php b/phpBB/includes/extension/finder.php new file mode 100644 index 0000000000..fb19b98429 --- /dev/null +++ b/phpBB/includes/extension/finder.php @@ -0,0 +1,436 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The extension finder provides a simple way to locate files in active extensions +* +* @package extension +*/ +class phpbb_extension_finder +{ +	protected $extension_manager; +	protected $phpbb_root_path; +	protected $cache; +	protected $php_ext; + +	/** +	* The cache variable name used to store $this->cached_queries in $this->cache. +	* +	* Allows the use of multiple differently configured finders with the same cache. +	* @var string +	*/ +	protected $cache_name; + +	/** +	* An associative array, containing all search parameters set in methods. +	* @var	array +	*/ +	protected $query; + +	/** +	* A map from md5 hashes of serialized queries to their previously retrieved +	* results. +	* @var	array +	*/ +	protected $cached_queries; + +	/** +	* Creates a new finder instance with its dependencies +	* +	* @param phpbb_extension_manager $extension_manager An extension manager +	*            instance that provides the finder with a list of active +	*            extensions and their locations +	* @param string $phpbb_root_path Path to the phpbb root directory +	* @param phpbb_cache_driver_interface $cache A cache instance or null +	* @param string $php_ext php file extension +	* @param string $cache_name The name of the cache variable, defaults to +	*                           _ext_finder +	*/ +	public function __construct(phpbb_extension_manager $extension_manager, $phpbb_root_path = '', phpbb_cache_driver_interface $cache = null, $php_ext = '.php', $cache_name = '_ext_finder') +	{ +		$this->extension_manager = $extension_manager; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->cache = $cache; +		$this->php_ext = $php_ext; +		$this->cache_name = $cache_name; + +		$this->query = array( +			'core_path' => false, +			'core_suffix' => false, +			'core_prefix' => false, +			'core_directory' => false, +			'extension_suffix' => false, +			'extension_prefix' => false, +			'extension_directory' => false, +		); + +		$this->cached_queries = ($this->cache) ? $this->cache->get($this->cache_name) : false; +	} + +	/** +	* Sets a core path to be searched in addition to extensions +	* +	* @param string $core_path The path relative to phpbb_root_path +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function core_path($core_path) +	{ +		$this->query['core_path'] = $core_path; +		return $this; +	} + +	/** +	* Sets the suffix all files found in extensions and core must match. +	* +	* There is no default file extension, so to find PHP files only, you will +	* have to specify .php as a suffix. However when using get_classes, the .php +	* file extension is automatically added to suffixes. +	* +	* @param string $suffix A filename suffix +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function suffix($suffix) +	{ +		$this->core_suffix($suffix); +		$this->extension_suffix($suffix); +		return $this; +	} + +	/** +	* Sets a suffix all files found in extensions must match +	* +	* There is no default file extension, so to find PHP files only, you will +	* have to specify .php as a suffix. However when using get_classes, the .php +	* file extension is automatically added to suffixes. +	* +	* @param string $extension_suffix A filename suffix +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function extension_suffix($extension_suffix) +	{ +		$this->query['extension_suffix'] = $extension_suffix; +		return $this; +	} + +	/** +	* Sets a suffix all files found in the core path must match +	* +	* There is no default file extension, so to find PHP files only, you will +	* have to specify .php as a suffix. However when using get_classes, the .php +	* file extension is automatically added to suffixes. +	* +	* @param string $core_suffix A filename suffix +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function core_suffix($core_suffix) +	{ +		$this->query['core_suffix'] = $core_suffix; +		return $this; +	} + +	/** +	* Sets the prefix all files found in extensions and core must match +	* +	* @param string $prefix A filename prefix +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function prefix($prefix) +	{ +		$this->core_prefix($prefix); +		$this->extension_prefix($prefix); +		return $this; +	} + +	/** +	* Sets a prefix all files found in extensions must match +	* +	* @param string $extension_prefix A filename prefix +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function extension_prefix($extension_prefix) +	{ +		$this->query['extension_prefix'] = $extension_prefix; +		return $this; +	} + +	/** +	* Sets a prefix all files found in the core path must match +	* +	* @param string $core_prefix A filename prefix +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function core_prefix($core_prefix) +	{ +		$this->query['core_prefix'] = $core_prefix; +		return $this; +	} + +	/** +	* Sets a directory all files found in extensions and core must be contained in +	* +	* Automatically sets the core_directory if its value does not differ from +	* the current directory. +	* +	* @param string $directory +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function directory($directory) +	{ +		$this->core_directory($directory); +		$this->extension_directory($directory); +		return $this; +	} + +	/** +	* Sets a directory all files found in extensions must be contained in +	* +	* @param string $extension_directory +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function extension_directory($extension_directory) +	{ +		$this->query['extension_directory'] = $this->sanitise_directory($extension_directory); +		return $this; +	} + +	/** +	* Sets a directory all files found in the core path must be contained in +	* +	* @param string $core_directory +	* @return phpbb_extension_finder This object for chaining calls +	*/ +	public function core_directory($core_directory) +	{ +		$this->query['core_directory'] = $this->sanitise_directory($core_directory); +		return $this; +	} + +	/** +	* Removes occurances of /./ and makes sure path ends without trailing slash +	* +	* @param string $directory A directory pattern +	* @return string A cleaned up directory pattern +	*/ +	protected function sanitise_directory($directory) +	{ +		$directory = preg_replace('#(?:^|/)\./#', '/', $directory); +		$dir_len = strlen($directory); + +		if ($dir_len > 1 && $directory[$dir_len - 1] === '/') +		{ +			$directory = substr($directory, 0, -1); +		} + +		return $directory; +	} + +	/** +	* Finds classes matching the configured options if they follow phpBB naming rules. +	* +	* The php file extension is automatically added to suffixes. +	* +	* Note: If a file is matched but contains a class name not following the +	* phpBB naming rules an incorrect class name will be returned. +	* +	* @param bool $cache Whether the result should be cached +	* @return array An array of found class names +	*/ +	public function get_classes($cache = true) +	{ +		$this->query['extension_suffix'] .= $this->php_ext; +		$this->query['core_suffix'] .= $this->php_ext; + +		$files = $this->find($cache, false); + +		$classes = array(); +		foreach ($files as $file => $ext_name) +		{ +			$file = preg_replace('#^includes/#', '', $file); + +			$classes[] = 'phpbb_' . str_replace('/', '_', substr($file, 0, -strlen($this->php_ext))); +		} +		return $classes; +	} + +	/** +	* Finds all directories matching the configured options +	* +	* @param bool $cache Whether the result should be cached +	* @param bool $extension_keys Whether the result should have extension name as array key +	* @return array An array of paths to found directories +	*/ +	public function get_directories($cache = true, $extension_keys = false) +	{ +		return $this->find_with_root_path($cache, true, $extension_keys); +	} + +	/** +	* Finds all files matching the configured options. +	* +	* @param bool $cache Whether the result should be cached +	* @return array An array of paths to found files +	*/ +	public function get_files($cache = true) +	{ +		return $this->find_with_root_path($cache, false); +	} + +	/** +	* A wrapper around the general find which prepends a root path to results +	* +	* @param bool $cache Whether the result should be cached +	* @param bool $is_dir Directories will be returned when true, only files +	*                     otherwise +	* @param bool $extension_keys If true, result will be associative array +	*					with extension name as key +	* @return array An array of paths to found items +	*/ +	protected function find_with_root_path($cache = true, $is_dir = false, $extension_keys = false) +	{ +		$items = $this->find($cache, $is_dir); + +		$result = array(); +		foreach ($items as $item => $ext_name) +		{ +			if ($extension_keys) +			{ +				$result[$ext_name] = $this->phpbb_root_path . $item; +			} +			else +			{ +				$result[] = $this->phpbb_root_path . $item; +			} +		} + +		return $result; +	} + +	/** +	* Finds all file system entries matching the configured options +	* +	* @param bool $cache Whether the result should be cached +	* @param bool $is_dir Directories will be returned when true, only files +	*                     otherwise +	* @return array An array of paths to found items +	*/ +	public function find($cache = true, $is_dir = false) +	{ +		$this->query['is_dir'] = $is_dir; +		$query = md5(serialize($this->query)); + +		if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query])) +		{ +			return $this->cached_queries[$query]; +		} + +		$files = array(); + +		$extensions = $this->extension_manager->all_enabled(); + +		if ($this->query['core_path']) +		{ +			$extensions['/'] = $this->phpbb_root_path . $this->query['core_path']; +		} + +		foreach ($extensions as $name => $path) +		{ +			$ext_name = $name; + +			if (!file_exists($path)) +			{ +				continue; +			} + +			if ($name === '/') +			{ +				$location = $this->query['core_path']; +				$name = ''; +				$suffix = $this->query['core_suffix']; +				$prefix = $this->query['core_prefix']; +				$directory = $this->query['core_directory']; +			} +			else +			{ +				$location = 'ext/'; +				$name .= '/'; +				$suffix = $this->query['extension_suffix']; +				$prefix = $this->query['extension_prefix']; +				$directory = $this->query['extension_directory']; +			} + +			// match only first directory if leading slash is given +			if ($directory === '/') +			{ +				$directory_pattern = '^' . preg_quote(DIRECTORY_SEPARATOR, '#'); +			} +			else if ($directory && $directory[0] === '/') +			{ +				$directory_pattern = '^' . preg_quote(str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); +			} +			else +			{ +				$directory_pattern = preg_quote(DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); +			} +			if ($is_dir) +			{ +				$directory_pattern .= '$'; +			} +			$directory_pattern = '#' . $directory_pattern . '#'; + +			$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST); +			foreach ($iterator as $file_info) +			{ +				$filename = $file_info->getFilename(); +				if ($filename == '.' || $filename == '..') +				{ +					continue; +				} + +				if ($file_info->isDir() == $is_dir) +				{ +					if ($is_dir) +					{ +						$relative_path = $iterator->getInnerIterator()->getSubPath() . DIRECTORY_SEPARATOR . basename($filename) . DIRECTORY_SEPARATOR; +						if ($relative_path[0] !== DIRECTORY_SEPARATOR) +						{ +							$relative_path = DIRECTORY_SEPARATOR . $relative_path; +						} +					} +					else +					{ +						$relative_path = DIRECTORY_SEPARATOR . $iterator->getInnerIterator()->getSubPathname(); +					} + +					if ((!$suffix || substr($relative_path, -strlen($suffix)) === $suffix) && +						(!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) && +						(!$directory || preg_match($directory_pattern, $relative_path))) +					{ +						$files[str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1))] = $ext_name; +					} +				} +			} +		} + +		if ($cache && $this->cache) +		{ +			$this->cached_queries[$query] = $files; +			$this->cache->put($this->cache_name, $this->cached_queries); +		} + +		return $files; +	} +} diff --git a/phpBB/includes/extension/interface.php b/phpBB/includes/extension/interface.php new file mode 100644 index 0000000000..74ecb9b762 --- /dev/null +++ b/phpBB/includes/extension/interface.php @@ -0,0 +1,65 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The interface extension meta classes have to implement to run custom code +* on enable/disable/purge. +* +* @package extension +*/ +interface phpbb_extension_interface +{ +	/** +	* enable_step is executed on enabling an extension until it returns false. +	* +	* Calls to this function can be made in subsequent requests, when the +	* function is invoked through a webserver with a too low max_execution_time. +	* +	* @param	mixed	$old_state	The return value of the previous call +	*								of this method, or false on the first call +	* @return	mixed				Returns false after last step, otherwise +	*								temporary state which is passed as an +	*								argument to the next step +	*/ +	public function enable_step($old_state); + +	/** +	* Disables the extension. +	* +	* Calls to this function can be made in subsequent requests, when the +	* function is invoked through a webserver with a too low max_execution_time. +	* +	* @param	mixed	$old_state	The return value of the previous call +	*								of this method, or false on the first call +	* @return null +	*/ +	public function disable_step($old_state); + +	/** +	* purge_step is executed on purging an extension until it returns false. +	* +	* Calls to this function can be made in subsequent requests, when the +	* function is invoked through a webserver with a too low max_execution_time. +	* +	* @param	mixed	$old_state	The return value of the previous call +	*								of this method, or false on the first call +	* @return	mixed				Returns false after last step, otherwise +	*								temporary state which is passed as an +	*								argument to the next step +	*/ +	public function purge_step($old_state); +} diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php new file mode 100644 index 0000000000..9a518c215f --- /dev/null +++ b/phpBB/includes/extension/manager.php @@ -0,0 +1,483 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The extension manager provides means to activate/deactivate extensions. +* +* @package extension +*/ +class phpbb_extension_manager +{ +	protected $db; +	protected $config; +	protected $cache; +	protected $php_ext; +	protected $extensions; +	protected $extension_table; +	protected $phpbb_root_path; +	protected $cache_name; + +	/** +	* Creates a manager and loads information from database +	* +	* @param dbal $db A database connection +	* @param phpbb_config $config phpbb_config +	* @param string $extension_table The name of the table holding extensions +	* @param string $phpbb_root_path Path to the phpbb includes directory. +	* @param string $php_ext php file extension +	* @param phpbb_cache_driver_interface $cache A cache instance or null +	* @param string $cache_name The name of the cache variable, defaults to _ext +	*/ +	public function __construct(dbal $db, phpbb_config $config, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->db = $db; +		$this->config = $config; +		$this->cache = $cache; +		$this->php_ext = $php_ext; +		$this->extension_table = $extension_table; +		$this->cache_name = $cache_name; + +		$this->extensions = ($this->cache) ? $this->cache->get($this->cache_name) : false; + +		if ($this->extensions === false) +		{ +			$this->load_extensions(); +		} +	} + +	/** +	* Loads all extension information from the database +	* +	* @return null +	*/ +	public function load_extensions() +	{ +		$sql = 'SELECT * +			FROM ' . $this->extension_table; + +		$result = $this->db->sql_query($sql); +		$extensions = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); + +		$this->extensions = array(); +		foreach ($extensions as $extension) +		{ +			$extension['ext_path'] = $this->get_extension_path($extension['ext_name']); +			$this->extensions[$extension['ext_name']] = $extension; +		} + +		ksort($this->extensions); + +		if ($this->cache) +		{ +			$this->cache->put($this->cache_name, $this->extensions); +		} +	} + +	/** +	* Generates the path to an extension +	* +	* @param string $name The name of the extension +	* @param bool $phpbb_relative Whether the path should be relative to phpbb root +	* @return string Path to an extension +	*/ +	public function get_extension_path($name, $phpbb_relative = false) +	{ +		$name = str_replace('.', '', $name); + +		return (($phpbb_relative) ? $this->phpbb_root_path : '') . 'ext/' . $name . '/'; +	} + +	/** +	* Instantiates the extension meta class for the extension with the given name +	* +	* @param string $name The extension name +	* @return phpbb_extension_interface Instance of the extension meta class or +	*                     phpbb_extension_base if the class does not exist +	*/ +	public function get_extension($name) +	{ +		$extension_class_name = 'phpbb_ext_' . str_replace('/', '_', $name) . '_ext'; + +		if (class_exists($extension_class_name)) +		{ +			return new $extension_class_name; +		} +		else +		{ +			return new phpbb_extension_base; +		} +	} + +	/** +	* Instantiates the metadata manager for the extension with the given name +	* +	* @param string $name The extension name +	* @param string $template The template manager +	* @return phpbb_extension_metadata_manager Instance of the metadata manager +	*/ +	public function create_extension_metadata_manager($name, phpbb_template $template) +	{ +		return new phpbb_extension_metadata_manager($name, $this->db, $this, $this->phpbb_root_path, $this->php_ext, $template, $this->config); +	} + +	/** +	* Runs a step of the extension enabling process. +	* +	* Allows the exentension to enable in a long running script that works +	* in multiple steps across requests. State is kept for the extension +	* in the extensions table. +	* +	* @param	string	$name	The extension's name +	* @return	bool			False if enabling is finished, true otherwise +	*/ +	public function enable_step($name) +	{ +		// ignore extensions that are already enabled +		if (isset($this->extensions[$name]) && $this->extensions[$name]['ext_active']) +		{ +			return false; +		} + +		$old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false; + +		$extension = $this->get_extension($name); +		$state = $extension->enable_step($old_state); + +		$active = ($state === false); + +		$extension_data = array( +			'ext_name'		=> $name, +			'ext_active'	=> $active, +			'ext_state'		=> serialize($state), +		); + +		$this->extensions[$name] = $extension_data; +		$this->extensions[$name]['ext_path'] = $this->get_extension_path($extension_data['ext_name']); +		ksort($this->extensions); + +		$sql = 'UPDATE ' . $this->extension_table . ' +			SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " +			WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; +		$this->db->sql_query($sql); + +		if (!$this->db->sql_affectedrows()) +		{ +			$sql = 'INSERT INTO ' . $this->extension_table . ' +				' . $this->db->sql_build_array('INSERT', $extension_data); +			$this->db->sql_query($sql); +		} + +		if ($this->cache) +		{ +			$this->cache->destroy($this->cache_name); +		} + +		return !$active; +	} + +	/** +	* Enables an extension +	* +	* This method completely enables an extension. But it could be long running +	* so never call this in a script that has a max_execution time. +	* +	* @param string $name The extension's name +	* @return null +	*/ +	public function enable($name) +	{ +		while ($this->enable_step($name)); +	} + +	/** +	* Disables an extension +	* +	* Calls the disable method on the extension's meta class to allow it to +	* process the event. +	* +	* @param string $name The extension's name +	* @return bool False if disabling is finished, true otherwise +	*/ +	public function disable_step($name) +	{ +		// ignore extensions that are already disabled +		if (!isset($this->extensions[$name]) || !$this->extensions[$name]['ext_active']) +		{ +			return false; +		} + +		$old_state = unserialize($this->extensions[$name]['ext_state']); + +		$extension = $this->get_extension($name); +		$state = $extension->disable_step($old_state); + +		// continue until the state is false +		if ($state !== false) +		{ +			$extension_data = array( +				'ext_state'		=> serialize($state), +			); +			$this->extensions[$name]['ext_state'] = serialize($state); + +			$sql = 'UPDATE ' . $this->extension_table . ' +				SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " +				WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; +			$this->db->sql_query($sql); + +			if ($this->cache) +			{ +				$this->cache->destroy($this->cache_name); +			} + +			return true; +		} + +		$extension_data = array( +			'ext_active'	=> false, +			'ext_state'		=> serialize(false), +		); +		$this->extensions[$name]['ext_active'] = false; +		$this->extensions[$name]['ext_state'] = serialize(false); + +		$sql = 'UPDATE ' . $this->extension_table . ' +			SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " +			WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; +		$this->db->sql_query($sql); + +		if ($this->cache) +		{ +			$this->cache->destroy($this->cache_name); +		} + +		return false; +	} + +	/** +	* Disables an extension +	* +	* Disables an extension completely at once. This process could run for a +	* while so never call this in a script that has a max_execution time. +	* +	* @param string $name The extension's name +	* @return null +	*/ +	public function disable($name) +	{ +		while ($this->disable_step($name)); +	} + +	/** +	* Purge an extension +	* +	* Disables the extension first if active, and then calls purge on the +	* extension's meta class to delete the extension's database content. +	* +	* @param string $name The extension's name +	* @return bool False if purging is finished, true otherwise +	*/ +	public function purge_step($name) +	{ +		// ignore extensions that do not exist +		if (!isset($this->extensions[$name])) +		{ +			return false; +		} + +		// disable first if necessary +		if ($this->extensions[$name]['ext_active']) +		{ +			$this->disable($name); +		} + +		$old_state = unserialize($this->extensions[$name]['ext_state']); + +		$extension = $this->get_extension($name); +		$state = $extension->purge_step($old_state); + +		// continue until the state is false +		if ($state !== false) +		{ +			$extension_data = array( +				'ext_state'		=> serialize($state), +			); +			$this->extensions[$name]['ext_state'] = serialize($state); + +			$sql = 'UPDATE ' . $this->extension_table . ' +				SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " +				WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; +			$this->db->sql_query($sql); + +			if ($this->cache) +			{ +				$this->cache->destroy($this->cache_name); +			} + +			return true; +		} + +		unset($this->extensions[$name]); + +		$sql = 'DELETE FROM ' . $this->extension_table . " +			WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; +		$this->db->sql_query($sql); + +		if ($this->cache) +		{ +			$this->cache->destroy($this->cache_name); +		} + +		return false; +	} + +	/** +	* Purge an extension +	* +	* Purges an extension completely at once. This process could run for a while +	* so never call this in a script that has a max_execution time. +	* +	* @param string $name The extension's name +	* @return null +	*/ +	public function purge($name) +	{ +		while ($this->purge_step($name)); +	} + +	/** +	* Retrieves a list of all available extensions on the filesystem +	* +	* @return array An array with extension names as keys and paths to the +	*               extension as values +	*/ +	public function all_available() +	{ +		$available = array(); +		if (!is_dir($this->phpbb_root_path . 'ext/')) +		{ +			return $available; +		} + +		$iterator = new RecursiveIteratorIterator( +			new RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/'), +			RecursiveIteratorIterator::SELF_FIRST); +		foreach ($iterator as $file_info) +		{ +			if ($file_info->isFile() && $file_info->getFilename() == 'ext' . $this->php_ext) +			{ +				$ext_name = $iterator->getInnerIterator()->getSubPath(); + +				$ext_name = str_replace(DIRECTORY_SEPARATOR, '/', $ext_name); + +				$available[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/'; +			} +		} +		ksort($available); +		return $available; +	} + +	/** +	* Retrieves all configured extensions. +	* +	* All enabled and disabled extensions are considered configured. A purged +	* extension that is no longer in the database is not configured. +	* +	* @return array An array with extension names as keys and and the +	*               database stored extension information as values +	*/ +	public function all_configured() +	{ +		$configured = array(); +		foreach ($this->extensions as $name => $data) +		{ +			$data['ext_path'] = $this->phpbb_root_path . $data['ext_path']; +			$configured[$name] = $data; +		} +		return $configured; +	} + +	/** +	* Retrieves all enabled extensions. +	* +	* @return array An array with extension names as keys and and the +	*               database stored extension information as values +	*/ +	public function all_enabled() +	{ +		$enabled = array(); +		foreach ($this->extensions as $name => $data) +		{ +			if ($data['ext_active']) +			{ +				$enabled[$name] = $this->phpbb_root_path . $data['ext_path']; +			} +		} +		return $enabled; +	} + +	/** +	* Retrieves all disabled extensions. +	* +	* @return array An array with extension names as keys and and the +	*               database stored extension information as values +	*/ +	public function all_disabled() +	{ +		$disabled = array(); +		foreach ($this->extensions as $name => $data) +		{ +			if (!$data['ext_active']) +			{ +				$disabled[$name] = $this->phpbb_root_path . $data['ext_path']; +			} +		} +		return $disabled; +	} + +	/** +	* Check to see if a given extension is available on the filesystem +	* +	* @param string $name Extension name to check NOTE: Can be user input +	* @return bool Depending on whether or not the extension is available +	*/ +	public function available($name) +	{ +		return file_exists($this->get_extension_path($name, true)); +	} + +	/** +	* Check to see if a given extension is enabled +	* +	* @param string $name Extension name to check +	* @return bool Depending on whether or not the extension is enabled +	*/ +	public function enabled($name) +	{ +		return isset($this->extensions[$name]) && $this->extensions[$name]['ext_active']; +	} + +	/** +	* Instantiates a phpbb_extension_finder. +	* +	* @return phpbb_extension_finder An extension finder instance +	*/ +	public function get_finder() +	{ +		return new phpbb_extension_finder($this, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder'); +	} +} diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php new file mode 100644 index 0000000000..ea85bd3c4e --- /dev/null +++ b/phpBB/includes/extension/metadata_manager.php @@ -0,0 +1,338 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The extension metadata manager validates and gets meta-data for extensions +* +* @package extension +*/ +class phpbb_extension_metadata_manager +{ +	protected $phpEx; +	protected $extension_manager; +	protected $db; +	protected $phpbb_root_path; +	protected $template; +	protected $ext_name; +	protected $metadata; +	protected $metadata_file; + +	/** +	* Creates the metadata manager +	* +	* @param dbal $db A database connection +	* @param string $extension_manager An instance of the phpbb extension manager +	* @param string $phpbb_root_path Path to the phpbb includes directory. +	* @param string $phpEx php file extension +	*/ +	public function __construct($ext_name, dbal $db, phpbb_extension_manager $extension_manager, $phpbb_root_path, $phpEx = '.php', phpbb_template $template, phpbb_config $config) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->db = $db; +		$this->config = $config; +		$this->phpEx = $phpEx; +		$this->template = $template; +		$this->extension_manager = $extension_manager; +		$this->ext_name = $ext_name; +		$this->metadata = array(); +		$this->metadata_file = ''; +	} + +	/** +	 * Processes and gets the metadata requested +	 * +	 * @param  string $element			All for all metadata that it has and is valid, otherwise specify which section you want by its shorthand term. +	 * @return array					Contains all of the requested metadata, throws an exception on failure +	 */ +	public function get_metadata($element = 'all') +	{ +		$this->set_metadata_file(); + +		// Fetch the metadata +		$this->fetch_metadata(); + +		// Clean the metadata +		$this->clean_metadata_array(); + +		switch ($element) +		{ +			case 'all': +			default: +				// Validate the metadata +				if (!$this->validate()) +				{ +					return false; +				} + +				return $this->metadata; +			break; + +			case 'name': +				return ($this->validate('name')) ? $this->metadata['name'] : false; +			break; + +			case 'display-name': +				if (isset($this->metadata['extra']['display-name'])) +				{ +					return $this->metadata['extra']['display-name']; +				} +				else +				{ +					return ($this->validate('name')) ? $this->metadata['name'] : false; +				} +			break; +		} +	} + +	/** +	 * Sets the filepath of the metadata file +	 * +	 * @return boolean  Set to true if it exists, throws an exception on failure +	 */ +	private function set_metadata_file() +	{ +		$ext_filepath = $this->extension_manager->get_extension_path($this->ext_name); +		$metadata_filepath = $this->phpbb_root_path . $ext_filepath . 'composer.json'; + +		$this->metadata_file = $metadata_filepath; + +		if (!file_exists($this->metadata_file)) +		{ +    		throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); +		} +	} + +	/** +	 * Gets the contents of the composer.json file +	 * +	 * @return bool True if success, throws an exception on failure +	 */ +	private function fetch_metadata() +	{ +		if (!file_exists($this->metadata_file)) +		{ +			throw new phpbb_extension_exception('The required file does not exist: ' . $this->metadata_file); +		} +		else +		{ +			if (!($file_contents = file_get_contents($this->metadata_file))) +			{ +    			throw new phpbb_extension_exception('file_get_contents failed on ' . $this->metadata_file); +			} + +			if (($metadata = json_decode($file_contents, true)) === NULL) +			{ +    			throw new phpbb_extension_exception('json_decode failed on ' . $this->metadata_file); +			} + +			$this->metadata = $metadata; + +			return true; +		} +	} + +	/** +	 * This array handles the cleaning of the array +	 * +	 * @return array Contains the cleaned metadata array +	 */ +	private function clean_metadata_array() +	{ +		return $this->metadata; +	} + +	/** +	* Validate fields +	* +	* @param string $name  ("all" for display and enable validation +	* 						"display" for name, type, and authors +	* 						"name", "type") +	* @return Bool True if valid, throws an exception if invalid +	*/ +	public function validate($name = 'display') +    { +    	// Basic fields +    	$fields = array( +    		'name'		=> '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#', +    		'type'		=> '#^phpbb3-extension$#', +    		'licence'	=> '#.+#', +    		'version'	=> '#.+#', +    	); + +    	switch ($name) +    	{ +    		case 'all': +    			$this->validate('display'); + +				$this->validate_enable(); +    		break; + +    		case 'display': +    			foreach ($fields as $field => $data) +				{ +					$this->validate($field); +				} + +				$this->validate_authors(); +    		break; + +    		default: +    			if (isset($fields[$name])) +    			{ +    				if (!isset($this->metadata[$name])) +    				{ +    					throw new phpbb_extension_exception("Required meta field '$name' has not been set."); +					} + +					if (!preg_match($fields[$name], $this->metadata[$name])) +					{ +    					throw new phpbb_extension_exception("Meta field '$name' is invalid."); +					} +				} +			break; +		} + +		return true; +    } + +	/** +	 * Validates the contents of the authors field +	 * +	 * @return boolean True when passes validation, throws exception if invalid +	 */ +	public function validate_authors() +	{ +		if (empty($this->metadata['authors'])) +		{ +    		throw new phpbb_extension_exception("Required meta field 'authors' has not been set."); +		} + +		foreach ($this->metadata['authors'] as $author) +		{ +			if (!isset($author['name'])) +			{ +    			throw new phpbb_extension_exception("Required meta field 'author name' has not been set."); +			} +		} + +		return true; +	} + +	/** +	 * This array handles the verification that this extension can be enabled on this board +	 * +	 * @return bool True if validation succeeded, False if failed +	 */ +	public function validate_enable() +	{ +		// Check for phpBB, PHP versions +		if (!$this->validate_require_phpbb() || !$this->validate_require_php()) +		{ +			return false; +		} + +		return true; +	} + + +	/** +	 * Validates the contents of the phpbb requirement field +	 * +	 * @return boolean True when passes validation +	 */ +	public function validate_require_phpbb() +	{ +		if (!isset($this->metadata['require']['phpbb'])) +		{ +			return true; +		} + +		return $this->_validate_version($this->metadata['require']['phpbb'], $this->config['version']); +	} + +	/** +	 * Validates the contents of the php requirement field +	 * +	 * @return boolean True when passes validation +	 */ +	public function validate_require_php() +	{ +		if (!isset($this->metadata['require']['php'])) +		{ +			return true; +		} + +		return $this->_validate_version($this->metadata['require']['php'], phpversion()); +	} + +	/** +	* Version validation helper +	* +	* @param string $string The string for comparing to a version +	* @param string $current_version The version to compare to +	* @return bool True/False if meets version requirements +	*/ +	private function _validate_version($string, $current_version) +	{ +		// Allow them to specify their own comparison operator (ex: <3.1.2, >=3.1.0) +		$comparison_matches = false; +		preg_match('#[=<>]+#', $string, $comparison_matches); + +		if (!empty($comparison_matches)) +		{ +			return version_compare($current_version, str_replace(array($comparison_matches[0], ' '), '', $string), $comparison_matches[0]); +		} + +		return version_compare($current_version, $string, '>='); +	} + +	/** +	 * Outputs the metadata into the template +	 * +	 * @return null +	 */ +	public function output_template_data() +	{ +		$this->template->assign_vars(array( +			'META_NAME'			=> htmlspecialchars($this->metadata['name']), +			'META_TYPE'			=> htmlspecialchars($this->metadata['type']), +			'META_DESCRIPTION'	=> (isset($this->metadata['description'])) ? htmlspecialchars($this->metadata['description']) : '', +			'META_HOMEPAGE'		=> (isset($this->metadata['homepage'])) ? $this->metadata['homepage'] : '', +			'META_VERSION'		=> (isset($this->metadata['version'])) ? htmlspecialchars($this->metadata['version']) : '', +			'META_TIME'			=> (isset($this->metadata['time'])) ? htmlspecialchars($this->metadata['time']) : '', +			'META_LICENCE'		=> htmlspecialchars($this->metadata['licence']), + +			'META_REQUIRE_PHP'		=> (isset($this->metadata['require']['php'])) ? htmlspecialchars($this->metadata['require']['php']) : '', +			'META_REQUIRE_PHP_FAIL'	=> !$this->validate_require_php(), + +			'META_REQUIRE_PHPBB'		=> (isset($this->metadata['require']['phpbb'])) ? htmlspecialchars($this->metadata['require']['phpbb']) : '', +			'META_REQUIRE_PHPBB_FAIL'	=> !$this->validate_require_phpbb(), + +			'META_DISPLAY_NAME'	=> (isset($this->metadata['extra']['display-name'])) ? htmlspecialchars($this->metadata['extra']['display-name']) : '', +		)); + +		foreach ($this->metadata['authors'] as $author) +		{ +			$this->template->assign_block_vars('meta_authors', array( +				'AUTHOR_NAME'		=> htmlspecialchars($author['name']), +				'AUTHOR_EMAIL'		=> (isset($author['email'])) ? $author['email'] : '', +				'AUTHOR_HOMEPAGE'	=> (isset($author['homepage'])) ? $author['homepage'] : '', +				'AUTHOR_ROLE'		=> (isset($author['role'])) ? htmlspecialchars($author['role']) : '', +			)); +		} +	} +} diff --git a/phpBB/includes/extension/provider.php b/phpBB/includes/extension/provider.php new file mode 100644 index 0000000000..45b55e5cab --- /dev/null +++ b/phpBB/includes/extension/provider.php @@ -0,0 +1,76 @@ +<?php +/** +* +* @package extension +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Provides a set of items found in extensions. +* +* This abstract class is essentially a wrapper around item-specific +* finding logic. It handles storing the extension manager via constructor +* for the finding logic to use to find the items, and provides an +* iterator interface over the items found by the finding logic. +* +* Items could be anything, for example template paths or cron task names. +* Derived classes completely define what the items are. +* +* @package extension +*/ +abstract class phpbb_extension_provider implements IteratorAggregate +{ +	/** +	* Array holding all found items +	* @var array|null +	*/ +	protected $items = null; + +	/** +	* An extension manager to search for items in extensions +	* @var phpbb_extension_manager +	*/ +	protected $extension_manager; + +	/** +	* Constructor. Loads all available items. +	* +	* @param phpbb_extension_manager $extension_manager phpBB extension manager +	*/ +	public function __construct(phpbb_extension_manager $extension_manager) +	{ +		$this->extension_manager = $extension_manager; +	} + +	/** +	* Finds items using the extension manager. +	* +	* @return array     List of task names +	*/ +	abstract protected function find(); + +	/** +	* Retrieve an iterator over all items +	* +	* @return ArrayIterator An iterator for the array of template paths +	*/ +	public function getIterator() +	{ +		if ($this->items === null) +		{ +			$this->items = $this->find(); +		} + +		return new ArrayIterator($this->items); +	} +} diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 63d0f3387a..43b81f3f26 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -191,6 +190,43 @@ function unique_id($extra = 'c')  }  /** +* Wrapper for mt_rand() which allows swapping $min and $max parameters. +* +* PHP does not allow us to swap the order of the arguments for mt_rand() anymore. +* (since PHP 5.3.4, see http://bugs.php.net/46587) +* +* @param int $min		Lowest value to be returned +* @param int $max		Highest value to be returned +* +* @return int			Random integer between $min and $max (or $max and $min) +*/ +function phpbb_mt_rand($min, $max) +{ +	return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max); +} + +/** +* Wrapper for getdate() which returns the equivalent array for UTC timestamps. +* +* @param int $time		Unix timestamp (optional) +* +* @return array			Returns an associative array of information related to the timestamp. +*						See http://www.php.net/manual/en/function.getdate.php +*/ +function phpbb_gmgetdate($time = false) +{ +	if ($time === false) +	{ +		$time = time(); +	} + +	// getdate() interprets timestamps in local time. +	// What follows uses the fact that getdate() and +	// date('Z') balance each other out. +	return getdate($time - date('Z')); +} + +/**  * Return formatted string for filesizes  *  * @param int	$value			filesize in bytes @@ -307,7 +343,7 @@ function still_on_time($extra_time = 15)  /**  * -* @version Version 0.1 / slightly modified for phpBB 3.0.x (using $H$ as hash type identifier) +* @version Version 0.1 / slightly modified for phpBB 3.1.x (using $H$ as hash type identifier)  *  * Portable PHP password hashing framework.  * @@ -512,6 +548,34 @@ function phpbb_email_hash($email)  }  /** +* Wrapper for version_compare() that allows using uppercase A and B +* for alpha and beta releases. +* +* See http://www.php.net/manual/en/function.version-compare.php +* +* @param string $version1		First version number +* @param string $version2		Second version number +* @param string $operator		Comparison operator (optional) +* +* @return mixed					Boolean (true, false) if comparison operator is specified. +*								Integer (-1, 0, 1) otherwise. +*/ +function phpbb_version_compare($version1, $version2, $operator = null) +{ +	$version1 = strtolower($version1); +	$version2 = strtolower($version2); + +	if (is_null($operator)) +	{ +		return version_compare($version1, $version2); +	} +	else +	{ +		return version_compare($version1, $version2, $operator); +	} +} + +/**  * Global function for chmodding directories and files for internal use  *  * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions. @@ -752,7 +816,7 @@ function phpbb_is_writable($file)  * @param string $path Path to check absoluteness of  * @return boolean  */ -function is_absolute($path) +function phpbb_is_absolute($path)  {  	return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:[/\\\]#i', $path))) ? true : false;  } @@ -765,6 +829,8 @@ function is_absolute($path)  */  function phpbb_own_realpath($path)  { +	global $request; +  	// Now to perform funky shizzle  	// Switch to use UNIX slashes @@ -772,7 +838,7 @@ function phpbb_own_realpath($path)  	$path_prefix = '';  	// Determine what sort of path we have -	if (is_absolute($path)) +	if (phpbb_is_absolute($path))  	{  		$absolute = true; @@ -808,11 +874,12 @@ function phpbb_own_realpath($path)  				$path_prefix = '';  			}  		} -		else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME'])) +		else if ($request->server('SCRIPT_FILENAME'))  		{  			// Warning: If chdir() has been used this will lie!  			// Warning: This has some problems sometime (CLI can create them easily) -			$path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path; +			$filename = htmlspecialchars_decode($request->server('SCRIPT_FILENAME')); +			$path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($filename)) . '/' . $path;  			$absolute = true;  			$path_prefix = '';  		} @@ -951,6 +1018,36 @@ else  	}  } +/** +* Eliminates useless . and .. components from specified path. +* +* @param string $path Path to clean +* @return string Cleaned path +*/ +function phpbb_clean_path($path) +{ +	$exploded = explode('/', $path); +	$filtered = array(); +	foreach ($exploded as $part) +	{ +		if ($part === '.' && !empty($filtered)) +		{ +			continue; +		} + +		if ($part === '..' && !empty($filtered) && $filtered[sizeof($filtered) - 1] !== '..') +		{ +			array_pop($filtered); +		} +		else +		{ +			$filtered[] = $part; +		} +	} +	$path = implode('/', $filtered); +	return $path; +} +  // functions used for building option fields  /** @@ -1002,32 +1099,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) +		{ +			$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'])  		{ -			$zone_trunc = truncate_string($zone, 50, 255, false, '...'); +			$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 @@ -1036,6 +1310,10 @@ function tz_select($default = '', $truncate = false)  * Marks a topic/forum as read  * Marks a topic as posted to  * +* @param string $mode (all, topics, topic, post) +* @param int|bool $forum_id Used in all, topics, and topic mode +* @param int|bool $topic_id Used in topic and post mode +* @param int $post_time 0 means current time(), otherwise to set a specific mark time  * @param int $user_id can only be used with $mode == 'post'  */  function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0) @@ -1043,6 +1321,8 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  	global $db, $user, $config;  	global $request; +	$post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time; +  	if ($mode == 'all')  	{  		if ($forum_id === false || !sizeof($forum_id)) @@ -1050,9 +1330,20 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  			if ($config['load_db_lastread'] && $user->data['is_registered'])  			{  				// Mark all forums read (index page) -				$db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}"); -				$db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}"); -				$db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}"); +				$tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE); +				foreach ($tables as $table) +				{ +					$sql = 'DELETE FROM ' . $table . " +						WHERE user_id = {$user->data['user_id']} +							AND mark_time < $post_time"; +					$db->sql_query($sql); +				} + +				$sql = 'UPDATE ' . USERS_TABLE . " +					SET user_lastmark = $post_time +					WHERE user_id = {$user->data['user_id']} +						AND user_lastmark < $post_time"; +				$db->sql_query($sql);  			}  			else if ($config['load_anon_lastread'] || $user->data['is_registered'])  			{ @@ -1062,16 +1353,20 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  				unset($tracking_topics['tf']);  				unset($tracking_topics['t']);  				unset($tracking_topics['f']); -				$tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36); +				$tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36); -				$user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000); +				$user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000);  				$request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), phpbb_request_interface::COOKIE);  				unset($tracking_topics);  				if ($user->data['is_registered'])  				{ -					$db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}"); +					$sql = 'UPDATE ' . USERS_TABLE . " +						SET user_lastmark = $post_time +						WHERE user_id = {$user->data['user_id']} +							AND user_lastmark < $post_time"; +					$db->sql_query($sql);  				}  			}  		} @@ -1093,12 +1388,14 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  		{  			$sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "  				WHERE user_id = {$user->data['user_id']} +					AND mark_time < $post_time  					AND " . $db->sql_in_set('forum_id', $forum_id);  			$db->sql_query($sql);  			$sql = 'SELECT forum_id  				FROM ' . FORUMS_TRACK_TABLE . "  				WHERE user_id = {$user->data['user_id']} +					AND mark_time < $post_time  					AND " . $db->sql_in_set('forum_id', $forum_id);  			$result = $db->sql_query($sql); @@ -1111,9 +1408,10 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  			if (sizeof($sql_update))  			{ -				$sql = 'UPDATE ' . FORUMS_TRACK_TABLE . ' -					SET mark_time = ' . time() . " +				$sql = 'UPDATE ' . FORUMS_TRACK_TABLE . " +					SET mark_time = $post_time  					WHERE user_id = {$user->data['user_id']} +						AND mark_time < $post_time  						AND " . $db->sql_in_set('forum_id', $sql_update);  				$db->sql_query($sql);  			} @@ -1126,7 +1424,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  					$sql_ary[] = array(  						'user_id'	=> (int) $user->data['user_id'],  						'forum_id'	=> (int) $f_id, -						'mark_time'	=> time() +						'mark_time'	=> $post_time,  					);  				} @@ -1157,7 +1455,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  					unset($tracking['f'][$f_id]);  				} -				$tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36); +				$tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36);  			}  			if (isset($tracking['tf']) && empty($tracking['tf'])) @@ -1165,7 +1463,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  				unset($tracking['tf']);  			} -			$user->set_cookie('track', tracking_serialize($tracking), time() + 31536000); +			$user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);  			$request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), phpbb_request_interface::COOKIE);  			unset($tracking); @@ -1182,9 +1480,10 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  		if ($config['load_db_lastread'] && $user->data['is_registered'])  		{ -			$sql = 'UPDATE ' . TOPICS_TRACK_TABLE . ' -				SET mark_time = ' . (($post_time) ? $post_time : time()) . " +			$sql = 'UPDATE ' . TOPICS_TRACK_TABLE . " +				SET mark_time = $post_time  				WHERE user_id = {$user->data['user_id']} +					AND mark_time < $post_time  					AND topic_id = $topic_id";  			$db->sql_query($sql); @@ -1197,7 +1496,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  					'user_id'		=> (int) $user->data['user_id'],  					'topic_id'		=> (int) $topic_id,  					'forum_id'		=> (int) $forum_id, -					'mark_time'		=> ($post_time) ? (int) $post_time : time(), +					'mark_time'		=> $post_time,  				);  				$db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); @@ -1217,7 +1516,6 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  				$tracking['tf'][$forum_id][$topic_id36] = true;  			} -			$post_time = ($post_time) ? $post_time : time();  			$tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);  			// If the cookie grows larger than 10000 characters we will remove the smallest value @@ -1253,7 +1551,12 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  				if ($user->data['is_registered'])  				{  					$user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10)); -					$db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}"); + +					$sql = 'UPDATE ' . USERS_TABLE . " +						SET user_lastmark = $post_time +						WHERE user_id = {$user->data['user_id']} +							AND mark_time < $post_time"; +					$db->sql_query($sql);  				}  				else  				{ @@ -1261,7 +1564,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  				}  			} -			$user->set_cookie('track', tracking_serialize($tracking), time() + 31536000); +			$user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);  			$request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), phpbb_request_interface::COOKIE);  		} @@ -1283,7 +1586,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $  			$sql_ary = array(  				'user_id'		=> (int) $use_user_id,  				'topic_id'		=> (int) $topic_id, -				'topic_posted'	=> 1 +				'topic_posted'	=> 1,  			);  			$db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); @@ -1586,7 +1889,7 @@ function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $s  */  function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)  { -	global $db, $tracking_topics, $user, $config, $request; +	global $db, $tracking_topics, $user, $config, $auth, $request;  	// Determine the users last forum mark time if not given.  	if ($mark_time_forum === false) @@ -1609,6 +1912,10 @@ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_ti  		}  	} +	// Handle update of unapproved topics info. +	// Only update for moderators having m_approve permission for the forum. +	$sql_update_unapproved = ($auth->acl_get('m_approve', $forum_id)) ? '': 'AND t.topic_approved = 1'; +  	// Check the forum for any left unread topics.  	// If there are none, we mark the forum as read.  	if ($config['load_db_lastread'] && $user->data['is_registered']) @@ -1620,13 +1927,17 @@ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_ti  		}  		else  		{ -			$sql = 'SELECT t.forum_id FROM ' . TOPICS_TABLE . ' t -				LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id'] . ') +			$sql = 'SELECT t.forum_id +				FROM ' . TOPICS_TABLE . ' t +				LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt +					ON (tt.topic_id = t.topic_id +						AND tt.user_id = ' . $user->data['user_id'] . ')  				WHERE t.forum_id = ' . $forum_id . '  					AND t.topic_last_post_time > ' . $mark_time_forum . ' -					AND t.topic_moved_id = 0 -					AND (tt.topic_id IS NULL OR tt.mark_time < t.topic_last_post_time) -				GROUP BY t.forum_id'; +					AND t.topic_moved_id = 0 ' . +					$sql_update_unapproved . ' +					AND (tt.topic_id IS NULL +						OR tt.mark_time < t.topic_last_post_time)';  			$result = $db->sql_query_limit($sql, 1);  			$row = $db->sql_fetchrow($result);  			$db->sql_freeresult($result); @@ -1644,11 +1955,12 @@ function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_ti  		}  		else  		{ -			$sql = 'SELECT topic_id -				FROM ' . TOPICS_TABLE . ' -				WHERE forum_id = ' . $forum_id . ' -					AND topic_last_post_time > ' . $mark_time_forum . ' -					AND topic_moved_id = 0'; +			$sql = 'SELECT t.topic_id +				FROM ' . TOPICS_TABLE . ' t +				WHERE t.forum_id = ' . $forum_id . ' +					AND t.topic_last_post_time > ' . $mark_time_forum . ' +					AND t.topic_moved_id = 0 ' . +					$sql_update_unapproved;  			$result = $db->sql_query($sql);  			$check_forum = $tracking_topics['tf'][$forum_id]; @@ -1805,104 +2117,190 @@ 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) ? ' ... ' : $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) ? ' ... ' : $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 != 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, +		)); +	} + +	// 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)); -		for ($i = 2; $i < $total_pages; $i++) +		// 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), +		)); + +		// 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( +	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, +		)); +	} + +	// If the block_var_name is a nested block, we will use the last (most +	// inner) block as a prefix for the template variables. If the last block +	// name is pagination, the prefix is empty. If the rest of the +	// block_var_name is not empty, we will modify the last row of that block +	// and add our pagination items. +	$tpl_block_name = $tpl_prefix = ''; +	if (strrpos($block_var_name, '.') !== false) +	{ +		$tpl_block_name = substr($block_var_name, 0, strrpos($block_var_name, '.')); +		$tpl_prefix = strtoupper(substr($block_var_name, strrpos($block_var_name, '.') + 1)); +	} +	else +	{ +		$tpl_prefix = strtoupper($block_var_name); +	} +	$tpl_prefix = ($tpl_prefix == 'PAGINATION') ? '' : $tpl_prefix . '_'; + +	$previous_page = ($on_page != 1) ? $base_url . $url_delim . $start_name . '=' . (($on_page - 2) * $per_page) : ''; + +	$template_array = array(  		$tpl_prefix . 'BASE_URL'		=> $base_url, -		'A_' . $tpl_prefix . 'BASE_URL'	=> addslashes($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 . 'PREVIOUS_PAGE'	=> $previous_page, +		$tpl_prefix . 'PREV_PAGE'		=> $previous_page, +		$tpl_prefix . 'NEXT_PAGE'		=> ($on_page != $total_pages) ? $base_url . $url_delim . $start_name . '=' . ($on_page * $per_page) : '',  		$tpl_prefix . 'TOTAL_PAGES'		=> $total_pages, -	)); +		$tpl_prefix . 'CURRENT_PAGE'	=> $on_page, +	); -	return $page_string; +	if ($tpl_block_name) +	{ +		$template->alter_block_array($tpl_block_name, $template_array, true, 'change'); +	} +	else +	{ +		$template->assign_vars($template_array); +	}  }  /** -* 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));  } @@ -1930,7 +2328,47 @@ function on_page($num_items, $per_page, $start)  function append_sid($url, $params = false, $is_amp = true, $session_id = false)  {  	global $_SID, $_EXTRA_URL, $phpbb_hook; +	global $phpbb_dispatcher; +	if ($params === '' || (is_array($params) && empty($params))) +	{ +		// Do not append the ? if the param-list is empty anyway. +		$params = false; +	} + +	$append_sid_overwrite = false; + +	/** +	* This event can either supplement or override the append_sid() function +	* +	* To override this function, the event must set $append_sid_overwrite to +	* the new URL value, which will be returned following the event +	* +	* @event core.append_sid +	* @var	string		url						The url the session id needs +	*											to be appended to (can have +	*											params) +	* @var	mixed		params					String or array of additional +	*											url parameters +	* @var	bool		is_amp					Is url using & (true) or +	*											& (false) +	* @var	bool|string	session_id				Possibility to use a custom +	*											session id (string) instead of +	*											the global one (false) +	* @var	bool|string	append_sid_overwrite	Overwrite function (string +	*											URL) or not (false) +	* @since 3.1-A1 +	*/ +	$vars = array('url', 'params', 'is_amp', 'session_id', 'append_sid_overwrite'); +	extract($phpbb_dispatcher->trigger_event('core.append_sid', compact($vars))); + +	if ($append_sid_overwrite) +	{ +		return $append_sid_overwrite; +	} + +	// The following hook remains for backwards compatibility, though use of +	// the event above is preferred.  	// Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately.  	// They could mimic most of what is within this function  	if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id)) @@ -2032,10 +2470,10 @@ function append_sid($url, $params = false, $is_amp = true, $session_id = false)  */  function generate_board_url($without_script_path = false)  { -	global $config, $user; +	global $config, $user, $request;  	$server_name = $user->host; -	$server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT'); +	$server_port = $request->server('SERVER_PORT', 0);  	// Forcing server vars is the only way to specify/override the protocol  	if ($config['force_server_vars'] || !$server_name) @@ -2051,7 +2489,7 @@ function generate_board_url($without_script_path = false)  	else  	{  		// Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection -		$cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0; +		$cookie_secure = $request->is_secure() ? 1 : 0;  		$url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;  		$script_path = $user->page['root_script_path']; @@ -2232,10 +2670,10 @@ function redirect($url, $return = false, $disable_cd_check = false)  	{  		header('Refresh: 0; URL=' . $url); -		echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; -		echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">'; +		echo '<!DOCTYPE html>'; +		echo '<html dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '">';  		echo '<head>'; -		echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />'; +		echo '<meta charset="utf-8">';  		echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&', $url) . '" />';  		echo '<title>' . $user->lang['REDIRECT'] . '</title>';  		echo '</head>'; @@ -2368,15 +2806,25 @@ function build_url($strip_vars = false)  */  function meta_refresh($time, $url, $disable_cd_check = false)  { -	global $template; +	global $template, $refresh_data, $request; -	$url = redirect($url, true, $disable_cd_check); -	$url = str_replace('&', '&', $url); +	if ($request->is_ajax()) +	{ +		$refresh_data = array( +			'time'	=> $time, +			'url'		=> str_replace('&', '&', $url) +		); +	} +	else +	{ +		$url = redirect($url, true, $disable_cd_check); +		$url = str_replace('&', '&', $url); -	// For XHTML compatibility we change back & to & -	$template->assign_vars(array( -		'META' => '<meta http-equiv="refresh" content="' . $time . ';url=' . $url . '" />') -	); +		// For XHTML compatibility we change back & to & +		$template->assign_vars(array( +			'META' => '<meta http-equiv="refresh" content="' . $time . ';url=' . $url . '" />') +		); +	}  	return $url;  } @@ -2403,6 +2851,8 @@ function meta_refresh($time, $url, $disable_cd_check = false)  */  function send_status_line($code, $message)  { +	global $request; +  	if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')  	{  		// in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though @@ -2410,15 +2860,9 @@ function send_status_line($code, $message)  	}  	else  	{ -		if (!empty($_SERVER['SERVER_PROTOCOL'])) -		{ -			$version = $_SERVER['SERVER_PROTOCOL']; -		} -		else if (!empty($_SERVER['HTTP_VERSION'])) +		if ($request->server('SERVER_PROTOCOL'))  		{ -			// I cannot remember where I got this from. -			// This code path may never be reachable in reality. -			$version = $_SERVER['HTTP_VERSION']; +			$version = $request->server('SERVER_PROTOCOL');  		}  		else  		{ @@ -2508,7 +2952,7 @@ function check_form_key($form_name, $timespan = false, $return_page = '', $trigg  		$diff = time() - $creation_time;  		// If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)... -		if ($diff && ($diff <= $timespan || $timespan === -1)) +		if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))  		{  			$token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';  			$key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid); @@ -2543,7 +2987,7 @@ function check_form_key($form_name, $timespan = false, $return_page = '', $trigg  */  function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')  { -	global $user, $template, $db; +	global $user, $template, $db, $request;  	global $phpEx, $phpbb_root_path, $request;  	if (isset($_POST['cancel'])) @@ -2623,6 +3067,21 @@ function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_bo  		WHERE user_id = " . $user->data['user_id'];  	$db->sql_query($sql); + +	if ($request->is_ajax()) +	{ +		$u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id; +		$json_response = new phpbb_json_response; +		$json_response->send(array( +			'MESSAGE_TITLE'		=> (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title], +			'MESSAGE_TEXT' 	=> (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'], + +			'YES_VALUE'			=> $user->lang['YES'], +			'S_CONFIRM_ACTION'	=> str_replace('&', '&', $u_action), //inefficient, rewrite whole function +			'S_HIDDEN_FIELDS'	=> $hidden . $s_hidden_fields +		)); +	} +  	if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])  	{  		adm_page_footer(); @@ -2682,11 +3141,11 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa  				trigger_error('NO_AUTH_ADMIN');  			} -			$password	= request_var('password_' . $credential, '', true); +			$password	= $request->untrimmed_variable('password_' . $credential, '', true);  		}  		else  		{ -			$password	= request_var('password', '', true); +			$password	= $request->untrimmed_variable('password', '', true);  		}  		$username	= request_var('username', '', true); @@ -3016,6 +3475,11 @@ function parse_cfg_file($filename, $lines = false)  		$parsed_items[$key] = $value;  	} +	if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name']) +	{ +		unset($parsed_items['parent']); +	} +  	return $parsed_items;  } @@ -3086,61 +3550,44 @@ function add_log()  }  /** -* Return a nicely formatted backtrace (parts from the php manual by diz at ysagoon dot com) +* Return a nicely formatted backtrace. +* +* Turns the array returned by debug_backtrace() into HTML markup. +* Also filters out absolute paths to phpBB root. +* +* @return string	HTML markup  */  function get_backtrace()  { -	global $phpbb_root_path; -  	$output = '<div style="font-family: monospace;">';  	$backtrace = debug_backtrace(); -	$path = phpbb_realpath($phpbb_root_path); -	foreach ($backtrace as $number => $trace) -	{ -		// We skip the first one, because it only shows this file/function -		if ($number == 0) -		{ -			continue; -		} +	// We skip the first one, because it only shows this file/function +	unset($backtrace[0]); +	foreach ($backtrace as $trace) +	{  		// Strip the current directory from path -		if (empty($trace['file'])) -		{ -			$trace['file'] = ''; -		} -		else -		{ -			$trace['file'] = str_replace(array($path, '\\'), array('', '/'), $trace['file']); -			$trace['file'] = substr($trace['file'], 1); -		} -		$args = array(); +		$trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file'])); +		$trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line']; -		// If include/require/include_once is not called, do not show arguments - they may contain sensible information -		if (!in_array($trace['function'], array('include', 'require', 'include_once'))) +		// Only show function arguments for include etc. +		// Other parameters may contain sensible information +		$argument = ''; +		if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))  		{ -			unset($trace['args']); -		} -		else -		{ -			// Path... -			if (!empty($trace['args'][0])) -			{ -				$argument = htmlspecialchars($trace['args'][0]); -				$argument = str_replace(array($path, '\\'), array('', '/'), $argument); -				$argument = substr($argument, 1); -				$args[] = "'{$argument}'"; -			} +			$argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]));  		}  		$trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];  		$trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];  		$output .= '<br />'; -		$output .= '<b>FILE:</b> ' . htmlspecialchars($trace['file']) . '<br />'; +		$output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';  		$output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />'; -		$output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']) . '(' . ((sizeof($args)) ? implode(', ', $args) : '') . ')<br />'; +		$output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']); +		$output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';  	}  	$output .= '</div>';  	return $output; @@ -3158,7 +3605,7 @@ function get_preg_expression($mode)  		case 'email':  			// Regex written by James Watts and Francisco Jose Martin Moreno  			// http://fightingforalostcause.net/misc/2006/compare-email-regex.php -			return '([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&)+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)'; +			return '([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&)+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)';  		break;  		case 'bbcode_htm': @@ -3202,6 +3649,10 @@ function get_preg_expression($mode)  			$inline = ($mode == 'relative_url') ? ')' : '';  			return "(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";  		break; + +		case 'table_prefix': +			return '#^[a-zA-Z][a-zA-Z0-9_]*$#'; +		break;  	}  	return ''; @@ -3221,7 +3672,7 @@ function get_censor_preg_expression($word, $use_unicode = true)  	// Unescape the asterisk to simplify further conversions  	$word = str_replace('\*', '*', preg_quote($word, '#')); -	if ($use_unicode && pcre_utf8_support()) +	if ($use_unicode && phpbb_pcre_utf8_support())  	{  		// Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes  		$word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word); @@ -3608,10 +4059,19 @@ function phpbb_checkdnsrr($host, $type = 'MX')  					{  						return true;  					} +				break;  				default: -				case 'A':  				case 'AAAA': +					// AAAA records returned by nslookup on Windows XP/2003 have this format. +					// Later Windows versions use the A record format below for AAAA records. +					if (stripos($line, "$host AAAA IPv6 address") === 0) +					{ +						return true; +					} +				// No break + +				case 'A':  					if (!empty($host_matches))  					{  						// Second line @@ -3646,7 +4106,7 @@ function phpbb_checkdnsrr($host, $type = 'MX')  */  function msg_handler($errno, $msg_text, $errfile, $errline)  { -	global $cache, $db, $auth, $template, $config, $user; +	global $cache, $db, $auth, $template, $config, $user, $request;  	global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;  	// Do not display notices if we suppress them via @ @@ -3680,25 +4140,10 @@ function msg_handler($errno, $msg_text, $errfile, $errline)  			if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)  			{ -				// flush the content, else we get a white page if output buffering is on -				if ((int) @ini_get('output_buffering') === 1 || strtolower(@ini_get('output_buffering')) === 'on') -				{ -					@ob_flush(); -				} - -				// Another quick fix for those having gzip compression enabled, but do not flush if the coder wants to catch "something". ;) -				if (!empty($config['gzip_compress'])) -				{ -					if (@extension_loaded('zlib') && !headers_sent() && !ob_get_level()) -					{ -						@ob_flush(); -					} -				} - -				// remove complete path to installation, with the risk of changing backslashes meant to be there -				$errfile = str_replace(array(phpbb_realpath($phpbb_root_path), '\\'), array('', '/'), $errfile); -				$msg_text = str_replace(array(phpbb_realpath($phpbb_root_path), '\\'), array('', '/'), $msg_text); -				echo '<b>[phpBB Debug] PHP Notice</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n"; +				$errfile = phpbb_filter_root_path($errfile); +				$msg_text = phpbb_filter_root_path($msg_text); +				$error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice'; +				echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";  				// we are writing an image - the user won't see the debug, so let's place it in the log  				if (defined('IMAGE_OUTPUT') || defined('IN_CRON')) @@ -3739,11 +4184,23 @@ function msg_handler($errno, $msg_text, $errfile, $errline)  				}  			} +			$log_text = $msg_text; +			$backtrace = get_backtrace(); +			if ($backtrace) +			{ +				$log_text .= '<br /><br />BACKTRACE<br />' . $backtrace; +			} + +			if (defined('IN_INSTALL') || defined('DEBUG_EXTRA') || isset($auth) && $auth->acl_get('a_')) +			{ +				$msg_text = $log_text; +			} +  			if ((defined('DEBUG') || defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))  			{  				// let's avoid loops  				$db->sql_return_on_error(true); -				add_log('critical', 'LOG_GENERAL_ERROR', $msg_title, $msg_text); +				add_log('critical', 'LOG_GENERAL_ERROR', $msg_title, $log_text);  				$db->sql_return_on_error(false);  			} @@ -3754,10 +4211,10 @@ function msg_handler($errno, $msg_text, $errfile, $errline)  			// Try to not call the adm page data... -			echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; -			echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">'; +			echo '<!DOCTYPE html>'; +			echo '<html dir="ltr">';  			echo '<head>'; -			echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />'; +			echo '<meta charset="utf-8">';  			echo '<title>' . $msg_title . '</title>';  			echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";  			echo '* { margin: 0; padding: 0; } html { font-size: 100%; height: 100%; margin-bottom: 1px; background-color: #E4EDF0; } body { font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; color: #536482; background: #E4EDF0; font-size: 62.5%; margin: 0; } '; @@ -3787,7 +4244,7 @@ function msg_handler($errno, $msg_text, $errfile, $errline)  			echo '	</div>';  			echo '	</div>';  			echo '	<div id="page-footer">'; -			echo '		Powered by <a href="http://www.phpbb.com/">phpBB</a> © phpBB Group'; +			echo '		Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group';  			echo '	</div>';  			echo '</div>';  			echo '</body>'; @@ -3848,6 +4305,20 @@ function msg_handler($errno, $msg_text, $errfile, $errline)  				'S_USER_NOTICE'		=> ($errno == E_USER_NOTICE) ? true : false)  			); +			if ($request->is_ajax()) +			{ +				global $refresh_data; + +				$json_response = new phpbb_json_response; +				$json_response->send(array( +					'MESSAGE_TITLE'		=> $msg_title, +					'MESSAGE_TEXT'		=> $msg_text, +					'S_USER_WARNING'	=> ($errno == E_USER_WARNING) ? true : false, +					'S_USER_NOTICE'		=> ($errno == E_USER_NOTICE) ? true : false, +					'REFRESH_DATA'		=> (!empty($refresh_data)) ? $refresh_data : null +				)); +			} +  			// We do not want the cron script to be called on error messages  			define('IN_CRON', true); @@ -3875,6 +4346,29 @@ function msg_handler($errno, $msg_text, $errfile, $errline)  }  /** +* Removes absolute path to phpBB root directory from error messages +* and converts backslashes to forward slashes. +* +* @param string $errfile	Absolute file path +*							(e.g. /var/www/phpbb3/phpBB/includes/functions.php) +*							Please note that if $errfile is outside of the phpBB root, +*							the root path will not be found and can not be filtered. +* @return string			Relative file path +*							(e.g. /includes/functions.php) +*/ +function phpbb_filter_root_path($errfile) +{ +	static $root_path; + +	if (empty($root_path)) +	{ +		$root_path = phpbb_realpath(dirname(__FILE__) . '/../'); +	} + +	return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile); +} + +/**  * Queries the session table to get information about online guests  * @param int $item_id Limits the search to the item with this id  * @param string $item The name of the item which is stored in the session table as session_{$item}_id @@ -4039,59 +4533,26 @@ function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum'  	}  	else if ($config['load_online_guests'])  	{ -		$l_online = ($online_users['guests_online'] === 1) ? $user->lang['BROWSING_' . $item_caps . '_GUEST'] : $user->lang['BROWSING_' . $item_caps . '_GUESTS']; -		$online_userlist = sprintf($l_online, $online_userlist, $online_users['guests_online']); +		$online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist);  	}  	else  	{  		$online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);  	}  	// Build online listing -	$vars_online = array( -		'ONLINE'	=> array('total_online', 'l_t_user_s', 0), -		'REG'		=> array('visible_online', 'l_r_user_s', !$config['load_online_guests']), -		'HIDDEN'	=> array('hidden_online', 'l_h_user_s', $config['load_online_guests']), -		'GUEST'		=> array('guests_online', 'l_g_user_s', 0) -	); +	$visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']); +	$hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']); -	foreach ($vars_online as $l_prefix => $var_ary) +	if ($config['load_online_guests'])  	{ -		if ($var_ary[2]) -		{ -			$l_suffix = '_AND'; -		} -		else -		{ -			$l_suffix = ''; -		} -		switch ($online_users[$var_ary[0]]) -		{ -			case 0: -				${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL' . $l_suffix]; -			break; - -			case 1: -				${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL' . $l_suffix]; -			break; - -			default: -				${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL' . $l_suffix]; -			break; -		} +		$guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']); +		$l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online);  	} -	unset($vars_online); - -	$l_online_users = sprintf($l_t_user_s, $online_users['total_online']); -	$l_online_users .= sprintf($l_r_user_s, $online_users['visible_online']); -	$l_online_users .= sprintf($l_h_user_s, $online_users['hidden_online']); - -	if ($config['load_online_guests']) +	else  	{ -		$l_online_users .= sprintf($l_g_user_s, $online_users['guests_online']); +		$l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online);  	} - -  	return array(  		'online_userlist'	=> $online_userlist,  		'l_online_users'	=> $l_online_users, @@ -4134,6 +4595,178 @@ function phpbb_optionset($bit, $set, $data)  }  /** +* Determine which plural form we should use. +* For some languages this is not as simple as for English. +* +* @param $rule		int			ID of the plural rule we want to use, see http://wiki.phpbb.com/Plural_Rules#Plural_Rules +* @param $number	int|float	The number we want to get the plural case for. Float numbers are floored. +* @return	int		The plural-case we need to use for the number plural-rule combination +*/ +function phpbb_get_plural_form($rule, $number) +{ +	$number = (int) $number; + +	if ($rule > 15 || $rule < 0) +	{ +		trigger_error('INVALID_PLURAL_RULE'); +	} + +	/** +	* The following plural rules are based on a list published by the Mozilla Developer Network +	* https://developer.mozilla.org/en/Localization_and_Plurals +	*/ +	switch ($rule) +	{ +		case 0: +			/** +			* Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao +			* 1 - everything: 0, 1, 2, ... +			*/ +			return 1; + +		case 1: +			/** +			* Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan) +			* 1 - 1 +			* 2 - everything else: 0, 2, 3, ... +			*/ +			return ($number == 1) ? 1 : 2; + +		case 2: +			/** +			* Families: Romanic (French, Brazilian Portuguese) +			* 1 - 0, 1 +			* 2 - everything else: 2, 3, ... +			*/ +			return (($number == 0) || ($number == 1)) ? 1 : 2; + +		case 3: +			/** +			* Families: Baltic (Latvian) +			* 1 - 0 +			* 2 - ends in 1, not 11: 1, 21, ... 101, 121, ... +			* 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ... +			*/ +			return ($number == 0) ? 1 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 2 : 3); + +		case 4: +			/** +			* Families: Celtic (Scottish Gaelic) +			* 1 - is 1 or 11: 1, 11 +			* 2 - is 2 or 12: 2, 12 +			* 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19 +			* 4 - everything else: 0, 20, 21, ... +			*/ +			return ($number == 1 || $number == 11) ? 1 : (($number == 2 || $number == 12) ? 2 : (($number >= 3 && $number <= 19) ? 3 : 4)); + +		case 5: +			/** +			* Families: Romanic (Romanian) +			* 1 - 1 +			* 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ... +			* 3 - everything else: 20, 21, ... +			*/ +			return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 2 : 3); + +		case 6: +			/** +			* Families: Baltic (Lithuanian) +			* 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... +			* 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ... +			* 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ... +			*/ +			return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 < 2) || (($number % 100 >= 10) && ($number % 100 < 20))) ? 2 : 3); + +		case 7: +			/** +			* Families: Slavic (Croatian, Serbian, Russian, Ukrainian) +			* 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... +			* 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... +			* 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ... +			*/ +			return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 2 : 3); + +		case 8: +			/** +			* Families: Slavic (Slovak, Czech) +			* 1 - 1 +			* 2 - 2, 3, 4 +			* 3 - everything else: 0, 5, 6, 7, ... +			*/ +			return ($number == 1) ? 1 : ((($number >= 2) && ($number <= 4)) ? 2 : 3); + +		case 9: +			/** +			* Families: Slavic (Polish) +			* 1 - 1 +			* 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ... +			* 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ... +			*/ +			return ($number == 1) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 2 : 3); + +		case 10: +			/** +			* Families: Slavic (Slovenian, Sorbian) +			* 1 - ends in 01: 1, 101, 201, ... +			* 2 - ends in 02: 2, 102, 202, ... +			* 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ... +			* 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ... +			*/ +			return ($number % 100 == 1) ? 1 : (($number % 100 == 2) ? 2 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 3 : 4)); + +		case 11: +			/** +			* Families: Celtic (Irish Gaeilge) +			* 1 - 1 +			* 2 - 2 +			* 3 - is 3-6: 3, 4, 5, 6 +			* 4 - is 7-10: 7, 8, 9, 10 +			* 5 - everything else: 0, 11, 12, ... +			*/ +			return ($number == 1) ? 1 : (($number == 2) ? 2 : (($number >= 3 && $number <= 6) ? 3 : (($number >= 7 && $number <= 10) ? 4 : 5))); + +		case 12: +			/** +			* Families: Semitic (Arabic) +			* 1 - 1 +			* 2 - 2 +			* 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ... +			* 4 - ends in 11-99: 11, ... 99, 111, 112, ... +			* 5 - everything else: 100, 101, 102, 200, 201, 202, ... +			* 6 - 0 +			*/ +			return ($number == 1) ? 1 : (($number == 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : (($number != 0) ? 5 : 6)))); + +		case 13: +			/** +			* Families: Semitic (Maltese) +			* 1 - 1 +			* 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ... +			* 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ... +			* 4 - everything else: 20, 21, ... +			*/ +			return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 2 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 3 : 4)); + +		case 14: +			/** +			* Families: Slavic (Macedonian) +			* 1 - ends in 1: 1, 11, 21, ... +			* 2 - ends in 2: 2, 12, 22, ... +			* 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ... +			*/ +			return ($number % 10 == 1) ? 1 : (($number % 10 == 2) ? 2 : 3); + +		case 15: +			/** +			* Families: Icelandic +			* 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ... +			* 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ... +			*/ +			return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2; +	} +} + +/**  * Login using http authenticate.  *  * @param array	$param		Parameter array, see $param_defaults array. @@ -4142,7 +4775,7 @@ function phpbb_optionset($bit, $set, $data)  */  function phpbb_http_login($param)  { -	global $auth, $user; +	global $auth, $user, $request;  	global $config;  	$param_defaults = array( @@ -4182,9 +4815,9 @@ function phpbb_http_login($param)  	$username = null;  	foreach ($username_keys as $k)  	{ -		if (isset($_SERVER[$k])) +		if ($request->is_set($k, phpbb_request_interface::SERVER))  		{ -			$username = $_SERVER[$k]; +			$username = htmlspecialchars_decode($request->server($k));  			break;  		}  	} @@ -4192,9 +4825,9 @@ function phpbb_http_login($param)  	$password = null;  	foreach ($password_keys as $k)  	{ -		if (isset($_SERVER[$k])) +		if ($request->is_set($k, phpbb_request_interface::SERVER))  		{ -			$password = $_SERVER[$k]; +			$password = htmlspecialchars_decode($request->server($k));  			break;  		}  	} @@ -4241,7 +4874,8 @@ function phpbb_http_login($param)  */  function page_header($page_title = '', $display_online_list = true, $item_id = 0, $item = 'forum')  { -	global $db, $config, $template, $SID, $_SID, $user, $auth, $phpEx, $phpbb_root_path; +	global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path; +	global $phpbb_dispatcher;  	if (defined('HEADER_INC'))  	{ @@ -4250,10 +4884,49 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  	define('HEADER_INC', true); +	// A listener can set this variable to `true` when it overrides this function +	$page_header_override = false; + +	/** +	* Execute code and/or overwrite page_header() +	* +	* @event core.page_header +	* @var	string	page_title			Page title +	* @var	bool	display_online_list		Do we display online users list +	* @var	string	item				Restrict online users to a certain +	*									session item, e.g. forum for +	*									session_forum_id +	* @var	int		item_id				Restrict online users to item id +	* @var	bool	page_header_override	Shall we return instead of running +	*										the rest of page_header() +	* @since 3.1-A1 +	*/ +	$vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override'); +	extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars))); + +	if ($page_header_override) +	{ +		return; +	} +  	// gzip_compression  	if ($config['gzip_compress'])  	{ -		if (@extension_loaded('zlib') && !headers_sent()) +		// to avoid partially compressed output resulting in blank pages in +		// the browser or error messages, compression is disabled in a few cases: +		// +		// 1) if headers have already been sent, this indicates plaintext output +		//    has been started so further content must not be compressed +		// 2) the length of the current output buffer is non-zero. This means +		//    there is already some uncompressed content in this output buffer +		//    so further output must not be compressed +		// 3) if more than one level of output buffering is used because we +		//    cannot test all output buffer level content lengths. One level +		//    could be caused by php.ini output_buffering. Anything +		//    beyond that is manual, so the code wrapping phpBB in output buffering +		//    can easily compress the output itself. +		// +		if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)  		{  			ob_start('ob_gzhandler');  		} @@ -4298,10 +4971,9 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  			set_config('record_online_date', time(), true);  		} -		$l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date'], false, true)); +		$l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true)); -		$l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES'; -		$l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']); +		$l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']);  	}  	$l_privmsgs_text = $l_privmsgs_text_unread = ''; @@ -4312,8 +4984,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  	{  		if ($user->data['user_new_privmsg'])  		{ -			$l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS']; -			$l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']); +			$l_privmsgs_text = $user->lang('NEW_PMS', (int) $user->data['user_new_privmsg']);  			if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])  			{ @@ -4331,7 +5002,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		}  		else  		{ -			$l_privmsgs_text = $user->lang['NO_NEW_PM']; +			$l_privmsgs_text = $user->lang('NEW_PMS', 0);  			$s_privmsg_new = false;  		} @@ -4339,8 +5010,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg'])  		{ -			$l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS']; -			$l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']); +			$l_privmsgs_text_unread = $user->lang('UNREAD_PMS', (int) $user->data['user_unread_privmsg']);  		}  	} @@ -4364,9 +5034,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) @@ -4374,6 +5041,29 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		$user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));  	} +	$s_search_hidden_fields = array(); +	if ($_SID) +	{ +		$s_search_hidden_fields['sid'] = $_SID; +	} + +	if (!empty($_EXTRA_URL)) +	{ +		foreach ($_EXTRA_URL as $url_param) +		{ +			$url_param = explode('=', $url_param, 2); +			$s_search_hidden_fields[$url_param[0]] = $url_param[1]; +		} +	} + +	$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'], @@ -4401,6 +5091,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		'L_LOGIN_LOGOUT'	=> $l_login_logout,  		'L_INDEX'			=> $user->lang['FORUM_INDEX'], +		'L_SITE_HOME'		=> ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'],  		'L_ONLINE_EXPLAIN'	=> $l_online_time,  		'U_PRIVATEMSGS'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), @@ -4412,6 +5103,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		'U_LOGIN_LOGOUT'		=> $u_login_logout,  		'U_INDEX'				=> append_sid("{$phpbb_root_path}index.$phpEx"),  		'U_SEARCH'				=> append_sid("{$phpbb_root_path}search.$phpEx"), +		'U_SITE_HOME'			=> $config['site_home_url'],  		'U_REGISTER'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),  		'U_PROFILE'				=> append_sid("{$phpbb_root_path}ucp.$phpEx"),  		'U_MODCP'				=> append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id), @@ -4441,7 +5133,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, @@ -4463,11 +5155,13 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		'S_LOAD_UNREADS'			=> ($config['load_unreads_search'] && ($config['load_anon_lastread'] || $user->data['is_registered'])) ? true : false, -		'T_THEME_PATH'			=> "{$web_path}styles/" . $user->theme['theme_path'] . '/theme', -		'T_TEMPLATE_PATH'		=> "{$web_path}styles/" . $user->theme['template_path'] . '/template', -		'T_SUPER_TEMPLATE_PATH'	=> (isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? "{$web_path}styles/" . $user->theme['template_inherit_path'] . '/template' : "{$web_path}styles/" . $user->theme['template_path'] . '/template', -		'T_IMAGESET_PATH'		=> "{$web_path}styles/" . $user->theme['imageset_path'] . '/imageset', -		'T_IMAGESET_LANG_PATH'	=> "{$web_path}styles/" . $user->theme['imageset_path'] . '/imageset/' . $user->data['user_lang'], +		'S_SEARCH_HIDDEN_FIELDS'	=> build_hidden_fields($s_search_hidden_fields), + +		'T_ASSETS_VERSION'		=> $config['assets_version'], +		'T_ASSETS_PATH'			=> "{$web_path}assets", +		'T_THEME_PATH'			=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme', +		'T_TEMPLATE_PATH'		=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template', +		'T_SUPER_TEMPLATE_PATH'	=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',  		'T_IMAGES_PATH'			=> "{$web_path}images/",  		'T_SMILIES_PATH'		=> "{$web_path}{$config['smilies_path']}/",  		'T_AVATAR_PATH'			=> "{$web_path}{$config['avatar_path']}/", @@ -4475,14 +5169,15 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		'T_ICONS_PATH'			=> "{$web_path}{$config['icons_path']}/",  		'T_RANKS_PATH'			=> "{$web_path}{$config['ranks_path']}/",  		'T_UPLOAD_PATH'			=> "{$web_path}{$config['upload_path']}/", -		'T_STYLESHEET_LINK'		=> (!$user->theme['theme_storedb']) ? "{$web_path}styles/" . $user->theme['theme_path'] . '/theme/stylesheet.css' : append_sid("{$phpbb_root_path}style.$phpEx", 'id=' . $user->theme['style_id'] . '&lang=' . $user->data['user_lang']), -		'T_STYLESHEET_NAME'		=> $user->theme['theme_name'], - -		'T_THEME_NAME'			=> $user->theme['theme_path'], -		'T_TEMPLATE_NAME'		=> $user->theme['template_path'], -		'T_SUPER_TEMPLATE_NAME'	=> (isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? $user->theme['template_inherit_path'] : $user->theme['template_path'], -		'T_IMAGESET_NAME'		=> $user->theme['imageset_path'], -		'T_IMAGESET_LANG_NAME'	=> $user->data['user_lang'], +		'T_STYLESHEET_LINK'		=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet.css?assets_version=' . $config['assets_version'], +		'T_STYLESHEET_LANG_LINK'    => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'], +		'T_JQUERY_LINK'			=> ($config['load_jquery_cdn'] && !empty($config['load_jquery_url'])) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery.js?assets_version=" . $config['assets_version'], +		'S_JQUERY_FALLBACK'		=> ($config['load_jquery_cdn']) ? true : false, + +		'T_THEME_NAME'			=> rawurlencode($user->style['style_path']), +		'T_THEME_LANG_NAME'		=> $user->data['user_lang'], +		'T_TEMPLATE_NAME'		=> $user->style['style_path'], +		'T_SUPER_TEMPLATE_NAME'	=> rawurlencode((isset($user->style['style_parent_tree']) && $user->style['style_parent_tree']) ? $user->style['style_parent_tree'] : $user->style['style_path']),  		'T_IMAGES'				=> 'images',  		'T_SMILIES'				=> $config['smilies_path'],  		'T_AVATAR'				=> $config['avatar_path'], @@ -4503,6 +5198,12 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  	header('Expires: 0');  	header('Pragma: no-cache'); +	if (!empty($user->data['is_bot'])) +	{ +		// Let reverse proxies know we detected a bot. +		header('X-PHPBB-IS-BOT: yes'); +	} +  	return;  } @@ -4512,7 +5213,27 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  function page_footer($run_cron = true)  {  	global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx; -	global $request; +	global $request, $phpbb_dispatcher; + +	// A listener can set this variable to `true` when it overrides this function +	$page_footer_override = false; + +	/** +	* Execute code and/or overwrite page_footer() +	* +	* @event core.page_footer +	* @var	bool	run_cron			Shall we run cron tasks +	* @var	bool	page_footer_override	Shall we return instead of running +	*										the rest of page_footer() +	* @since 3.1-A1 +	*/ +	$vars = array('run_cron', 'page_footer_override'); +	extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars))); + +	if ($page_footer_override) +	{ +		return; +	}  	// Output page creation time  	if (defined('DEBUG')) @@ -4529,15 +5250,13 @@ function page_footer($run_cron = true)  		if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))  		{ -			if (function_exists('memory_get_usage')) +			if (function_exists('memory_get_peak_usage'))  			{ -				if ($memory_usage = memory_get_usage()) +				if ($memory_usage = memory_get_peak_usage())  				{ -					global $base_memory_usage; -					$memory_usage -= $base_memory_usage;  					$memory_usage = get_formatted_filesize($memory_usage); -					$debug_output .= ' | Memory Usage: ' . $memory_usage; +					$debug_output .= ' | Peak Memory Usage: ' . $memory_usage;  				}  			} @@ -4548,6 +5267,7 @@ function page_footer($run_cron = true)  	$template->assign_vars(array(  		'DEBUG_OUTPUT'			=> (defined('DEBUG')) ? $debug_output : '',  		'TRANSLATION_INFO'		=> (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '', +		'CREDIT_LINE'			=> $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'),  		'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '')  	); @@ -4598,6 +5318,15 @@ function page_footer($run_cron = true)  function garbage_collection()  {  	global $cache, $db; +	global $phpbb_dispatcher; + +	/** +	* Unload some objects, to free some memory, before we finish our task +	* +	* @event core.garbage_collection +	* @since 3.1-A1 +	*/ +	$phpbb_dispatcher->dispatch('core.garbage_collection');  	// Unload cache, must be done before the DB connection if closed  	if (!empty($cache)) @@ -4631,13 +5360,13 @@ function exit_handler()  	}  	// As a pre-caution... some setups display a blank page if the flush() is not there. -	(empty($config['gzip_compress'])) ? @flush() : @ob_flush(); +	(ob_get_level() > 0) ? @ob_flush() : @flush();  	exit;  }  /** -* Handler for init calls in phpBB. This function is called in user::setup(); +* Handler for init calls in phpBB. This function is called in phpbb_user::setup();  * This function supports hooks.  */  function phpbb_user_session_handler() @@ -4661,7 +5390,7 @@ function phpbb_user_session_handler()  *  * @return bool	Returns true if PCRE (the regular expressions library) supports UTF-8 encoding  */ -function pcre_utf8_support() +function phpbb_pcre_utf8_support()  {  	static $utf8_pcre_properties = null;  	if (is_null($utf8_pcre_properties)) @@ -4670,3 +5399,16 @@ function pcre_utf8_support()  	}  	return $utf8_pcre_properties;  } + +/** +* Casts a numeric string $input to an appropriate numeric type (i.e. integer or float) +* +* @param string $input		A numeric string. +* +* @return int|float			Integer $input if $input fits integer, +*							float $input otherwise. +*/ +function phpbb_to_numeric($input) +{ +	return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; +} diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index f28bca91ee..11cc1f6dd8 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -2,19 +2,27 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */  /** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/**  * Header for acp pages  */  function adm_page_header($page_title)  {  	global $config, $db, $user, $template;  	global $phpbb_root_path, $phpbb_admin_path, $phpEx, $SID, $_SID; +	global $phpbb_dispatcher;  	if (defined('HEADER_INC'))  	{ @@ -23,6 +31,26 @@ function adm_page_header($page_title)  	define('HEADER_INC', true); +	// A listener can set this variable to `true` when it overrides this function +	$adm_page_header_override = false; + +	/** +	* Execute code and/or overwrite adm_page_header() +	* +	* @event core.adm_page_header +	* @var	string	page_title			Page title +	* @var	bool	adm_page_header_override	Shall we return instead of +	*									running the rest of adm_page_header() +	* @since 3.1-A1 +	*/ +	$vars = array('page_title', 'adm_page_header_override'); +	extract($phpbb_dispatcher->trigger_event('core.adm_page_header', compact($vars))); + +	if ($adm_page_header_override) +	{ +		return; +	} +  	// gzip_compression  	if ($config['gzip_compress'])  	{ @@ -89,7 +117,27 @@ function adm_page_footer($copyright_html = true)  {  	global $db, $config, $template, $user, $auth, $cache;  	global $starttime, $phpbb_root_path, $phpbb_admin_path, $phpEx; -	global $request; +	global $request, $phpbb_dispatcher; + +	// A listener can set this variable to `true` when it overrides this function +	$adm_page_footer_override = false; + +	/** +	* Execute code and/or overwrite adm_page_footer() +	* +	* @event core.adm_page_footer +	* @var	bool	copyright_html			Shall we display the copyright? +	* @var	bool	adm_page_footer_override	Shall we return instead of +	*									running the rest of adm_page_footer() +	* @since 3.1-A1 +	*/ +	$vars = array('copyright_html', 'adm_page_footer_override'); +	extract($phpbb_dispatcher->trigger_event('core.adm_page_footer', compact($vars))); + +	if ($adm_page_footer_override) +	{ +		return; +	}  	// Output page creation time  	if (defined('DEBUG')) @@ -106,15 +154,13 @@ function adm_page_footer($copyright_html = true)  		if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))  		{ -			if (function_exists('memory_get_usage')) +			if (function_exists('memory_get_peak_usage'))  			{ -				if ($memory_usage = memory_get_usage()) +				if ($memory_usage = memory_get_peak_usage())  				{ -					global $base_memory_usage; -					$memory_usage -= $base_memory_usage;  					$memory_usage = get_formatted_filesize($memory_usage); -					$debug_output .= ' | Memory Usage: ' . $memory_usage; +					$debug_output .= ' | Peak Memory Usage: ' . $memory_usage;  				}  			} @@ -126,6 +172,9 @@ function adm_page_footer($copyright_html = true)  		'DEBUG_OUTPUT'		=> (defined('DEBUG')) ? $debug_output : '',  		'TRANSLATION_INFO'	=> (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',  		'S_COPYRIGHT_HTML'	=> $copyright_html, +		'CREDIT_LINE'		=> $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'), +		'T_JQUERY_LINK'		=> ($config['load_jquery_cdn'] && !empty($config['load_jquery_url'])) ? $config['load_jquery_url'] : "{$phpbb_root_path}assets/javascript/jquery.js", +		'S_JQUERY_FALLBACK'	=> ($config['load_jquery_cdn']) ? true : false,  		'VERSION'			=> $config['version'])  	); @@ -164,7 +213,7 @@ function build_select($option_ary, $option_default = false)  /**  * Build radio fields in acp pages  */ -function h_radio($name, &$input_ary, $input_default = false, $id = false, $key = false) +function h_radio($name, $input_ary, $input_default = false, $id = false, $key = false, $separator = '')  {  	global $user; @@ -173,7 +222,7 @@ function h_radio($name, &$input_ary, $input_default = false, $id = false, $key =  	foreach ($input_ary as $value => $title)  	{  		$selected = ($input_default !== false && $value == $input_default) ? ' checked="checked"' : ''; -		$html .= '<label><input type="radio" name="' . $name . '"' . (($id && !$id_assigned) ? ' id="' . $id . '"' : '') . ' value="' . $value . '"' . $selected . (($key) ? ' accesskey="' . $key . '"' : '') . ' class="radio" /> ' . $user->lang[$title] . '</label>'; +		$html .= '<label><input type="radio" name="' . $name . '"' . (($id && !$id_assigned) ? ' id="' . $id . '"' : '') . ' value="' . $value . '"' . $selected . (($key) ? ' accesskey="' . $key . '"' : '') . ' class="radio" /> ' . $user->lang[$title] . '</label>' . $separator;  		$id_assigned = true;  	} @@ -185,7 +234,7 @@ function h_radio($name, &$input_ary, $input_default = false, $id = false, $key =  */  function build_cfg_template($tpl_type, $key, &$new, $config_key, $vars)  { -	global $user, $module; +	global $user, $module, $phpbb_dispatcher;  	$tpl = '';  	$name = 'config[' . $config_key . ']'; @@ -203,7 +252,7 @@ function build_cfg_template($tpl_type, $key, &$new, $config_key, $vars)  			$size = (int) $tpl_type[1];  			$maxlength = (int) $tpl_type[2]; -			$tpl = '<input id="' . $key . '" type="' . $tpl_type[0] . '"' . (($size) ? ' size="' . $size . '"' : '') . ' maxlength="' . (($maxlength) ? $maxlength : 255) . '" name="' . $name . '" value="' . $new[$config_key] . '" />'; +			$tpl = '<input id="' . $key . '" type="' . $tpl_type[0] . '"' . (($size) ? ' size="' . $size . '"' : '') . ' maxlength="' . (($maxlength) ? $maxlength : 255) . '" name="' . $name . '" value="' . $new[$config_key] . '"' . (($tpl_type[0] === 'password') ?  ' autocomplete="off"' : '') . ' />';  		break;  		case 'dimension': @@ -297,6 +346,24 @@ function build_cfg_template($tpl_type, $key, &$new, $config_key, $vars)  		$tpl .= $vars['append'];  	} +	/** +	* Overwrite the html code we display for the config value +	* +	* @event core.build_config_template +	* @var	array	tpl_type	Config type array: +	*						0 => data type +	*						1 [optional] => string: size, int: minimum +	*						2 [optional] => string: max. length, int: maximum +	* @var	string	key			Should be used for the id attribute in html +	* @var	array	new			Array with the config values we display +	* @var	string	name		Should be used for the name attribute +	* @var	array	vars		Array with the options for the config +	* @var	string	tpl			The resulting html code we display +	* @since 3.1-A1 +	*/ +	$vars = array('tpl_type', 'key', 'new', 'name', 'vars', 'tpl'); +	extract($phpbb_dispatcher->trigger_event('core.build_config_template', compact($vars))); +  	return $tpl;  } @@ -306,7 +373,8 @@ function build_cfg_template($tpl_type, $key, &$new, $config_key, $vars)  */  function validate_config_vars($config_vars, &$cfg_array, &$error)  { -	global $phpbb_root_path, $user; +	global $phpbb_root_path, $user, $phpbb_dispatcher; +  	$type	= 0;  	$min	= 1;  	$max	= 2; @@ -329,7 +397,7 @@ function validate_config_vars($config_vars, &$cfg_array, &$error)  		switch ($validator[$type])  		{  			case 'string': -				$length = strlen($cfg_array[$config_name]); +				$length = utf8_strlen($cfg_array[$config_name]);  				// the column is a VARCHAR  				$validator[$max] = (isset($validator[$max])) ? min(255, $validator[$max]) : 255; @@ -481,6 +549,24 @@ function validate_config_vars($config_vars, &$cfg_array, &$error)  				}  			break; + +			default: +				/** +				* Validate a config value +				* +				* @event core.validate_config_variable +				* @var	array	cfg_array	Array with config values +				* @var	string	config_name	Name of the config we validate +				* @var	array	config_definition	Array with the options for +				*									this config +				* @var	array	error		Array of errors, the errors should +				*							be strings only, language keys are +				*							not replaced afterwards +				* @since 3.1-A1 +				*/ +				$vars = array('cfg_array', 'config_name', 'config_definition', 'error'); +				extract($phpbb_dispatcher->trigger_event('core.validate_config_variable', compact($vars))); +			break;  		}  	} @@ -527,7 +613,7 @@ function validate_range($value_ary, &$error)  		{  			case 'string' :  				$max = (isset($column[1])) ? min($column[1],$type['max']) : $type['max']; -				if (strlen($value['value']) > $max) +				if (utf8_strlen($value['value']) > $max)  				{  					$error[] = sprintf($user->lang['SETTING_TOO_LONG'], $user->lang[$value['lang']], $max);  				} diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php index ee59d77cdb..5e2ee8c8f6 100644 --- a/phpBB/includes/functions_admin.php +++ b/phpBB/includes/functions_admin.php @@ -2,9 +2,8 @@  /**  *  * @package acp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -724,7 +723,7 @@ function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_s  */  function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = true, $post_count_sync = true, $call_delete_topics = true)  { -	global $db, $config, $phpbb_root_path, $phpEx; +	global $db, $config, $phpbb_root_path, $phpEx, $auth, $user;  	if ($where_type === 'range')  	{ @@ -848,17 +847,15 @@ function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync =  	}  	// Remove the message from the search index -	$search_type = basename($config['search_type']); +	$search_type = $config['search_type']; -	if (!file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx)) +	if (!class_exists($search_type))  	{  		trigger_error('NO_SUCH_SEARCH_MODULE');  	} -	include_once("{$phpbb_root_path}includes/search/$search_type.$phpEx"); -  	$error = false; -	$search = new $search_type($error); +	$search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user);  	if ($error)  	{ @@ -2295,41 +2292,6 @@ function auto_prune($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_fr  }  /** -* remove_comments will strip the sql comment lines out of an uploaded sql file -* specifically for mssql and postgres type files in the install.... -*/ -function remove_comments(&$output) -{ -	$lines = explode("\n", $output); -	$output = ''; - -	// try to keep mem. use down -	$linecount = sizeof($lines); - -	$in_comment = false; -	for ($i = 0; $i < $linecount; $i++) -	{ -		if (trim($lines[$i]) == '/*') -		{ -			$in_comment = true; -		} - -		if (!$in_comment) -		{ -			$output .= $lines[$i] . "\n"; -		} - -		if (trim($lines[$i]) == '*/') -		{ -			$in_comment = false; -		} -	} - -	unset($lines); -	return $output; -} - -/**  * Cache moderators, called whenever permissions are changed via admin_permissions. Changes of username  * and group names must be carried through for the moderators table  */ @@ -2366,7 +2328,7 @@ function cache_moderators()  		$ug_id_ary = array_keys($hold_ary);  		// Remove users who have group memberships with DENY moderator permissions -		$sql = $db->sql_build_query('SELECT', array( +		$sql_ary_deny = array(  			'SELECT'	=> 'a.forum_id, ug.user_id, g.group_id',  			'FROM'		=> array( @@ -2379,8 +2341,8 @@ function cache_moderators()  			'LEFT_JOIN'	=> array(  				array(  					'FROM'	=> array(ACL_ROLES_DATA_TABLE => 'r'), -					'ON'	=> 'a.auth_role_id = r.role_id' -				) +					'ON'	=> 'a.auth_role_id = r.role_id', +				),  			),  			'WHERE'		=> '(o.auth_option_id = a.auth_option_id OR o.auth_option_id = r.auth_option_id) @@ -2392,7 +2354,8 @@ function cache_moderators()  				AND ' . $db->sql_in_set('ug.user_id', $ug_id_ary) . "  				AND ug.user_pending = 0  				AND o.auth_option " . $db->sql_like_expression('m_' . $db->any_char), -		)); +		); +		$sql = $db->sql_build_query('SELECT', $sql_ary_deny);  		$result = $db->sql_query($sql);  		while ($row = $db->sql_fetchrow($result)) @@ -2593,7 +2556,37 @@ function view_log($mode, &$log, &$log_count, $limit = 0, $offset = 0, $forum_id  		{  			$sql_keywords .= $db->sql_in_set('l.log_operation', $operations) . ' OR ';  		} -		$sql_keywords .= 'LOWER(l.log_data) ' . implode(' OR LOWER(l.log_data) ', $keywords) . ')'; +		$sql_lower = $db->sql_lower_text('l.log_data'); +		$sql_keywords .= "$sql_lower " . implode(" OR $sql_lower ", $keywords) . ')'; +	} + +	if ($log_count !== false) +	{ +		$sql = 'SELECT COUNT(l.log_id) AS total_entries +			FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . " u +			WHERE l.log_type = $log_type +				AND l.user_id = u.user_id +				AND l.log_time >= $limit_days +				$sql_keywords +				$sql_forum"; +		$result = $db->sql_query($sql); +		$log_count = (int) $db->sql_fetchfield('total_entries'); +		$db->sql_freeresult($result); +	} + +	// $log_count may be false here if false was passed in for it, +	// because in this case we did not run the COUNT() query above. +	// If we ran the COUNT() query and it returned zero rows, return; +	// otherwise query for logs below. +	if ($log_count === 0) +	{ +		// Save the queries, because there are no logs to display +		return 0; +	} + +	if ($offset >= $log_count) +	{ +		$offset = ($offset - $limit < 0) ? 0 : $offset - $limit;  	}  	$sql = "SELECT l.*, u.username, u.username_clean, u.user_colour @@ -2743,21 +2736,7 @@ function view_log($mode, &$log, &$log_count, $limit = 0, $offset = 0, $forum_id  		}  	} -	if ($log_count !== false) -	{ -		$sql = 'SELECT COUNT(l.log_id) AS total_entries -			FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . " u -			WHERE l.log_type = $log_type -				AND l.user_id = u.user_id -				AND l.log_time >= $limit_days -				$sql_keywords -				$sql_forum"; -		$result = $db->sql_query($sql); -		$log_count = (int) $db->sql_fetchfield('total_entries'); -		$db->sql_freeresult($result); -	} - -	return; +	return $offset;  }  /** @@ -2781,18 +2760,18 @@ function update_foes($group_id = false, $user_id = false)  	if (is_array($group_id) && sizeof($group_id))  	{  		// Grab group settings... -		$sql = $db->sql_build_query('SELECT', array( +		$sql_ary = array(  			'SELECT'	=> 'a.group_id',  			'FROM'		=> array(  				ACL_OPTIONS_TABLE	=> 'ao', -				ACL_GROUPS_TABLE	=> 'a' +				ACL_GROUPS_TABLE	=> 'a',  			),  			'LEFT_JOIN'	=> array(  				array(  					'FROM'	=> array(ACL_ROLES_DATA_TABLE => 'r'), -					'ON'	=> 'a.auth_role_id = r.role_id' +					'ON'	=> 'a.auth_role_id = r.role_id',  				),  			), @@ -2800,8 +2779,9 @@ function update_foes($group_id = false, $user_id = false)  				AND ' . $db->sql_in_set('a.group_id', $group_id) . "  				AND ao.auth_option IN ('a_', 'm_')", -			'GROUP_BY'	=> 'a.group_id' -		)); +			'GROUP_BY'	=> 'a.group_id', +		); +		$sql = $db->sql_build_query('SELECT', $sql_ary);  		$result = $db->sql_query($sql);  		$groups = array(); @@ -2889,6 +2869,12 @@ function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $li  	$user_count = (int) $db->sql_fetchfield('user_count');  	$db->sql_freeresult($result); +	if ($user_count == 0) +	{ +		// Save the queries, because there are no users to display +		return 0; +	} +  	if ($offset >= $user_count)  	{  		$offset = ($offset - $limit < 0) ? 0 : $offset - $limit; @@ -3094,7 +3080,7 @@ function get_database_size()  /**  * Retrieve contents from remotely stored file  */ -function get_remote_file($host, $directory, $filename, &$errstr, &$errno, $port = 80, $timeout = 10) +function get_remote_file($host, $directory, $filename, &$errstr, &$errno, $port = 80, $timeout = 6)  {  	global $user; @@ -3104,6 +3090,9 @@ function get_remote_file($host, $directory, $filename, &$errstr, &$errno, $port  		@fputs($fsock, "HOST: $host\r\n");  		@fputs($fsock, "Connection: close\r\n\r\n"); +		$timer_stop = time() + $timeout; +		stream_set_timeout($fsock, $timeout); +  		$file_info = '';  		$get_info = false; @@ -3126,6 +3115,14 @@ function get_remote_file($host, $directory, $filename, &$errstr, &$errno, $port  					return false;  				}  			} + +			$stream_meta_data = stream_get_meta_data($fsock); + +			if (!empty($stream_meta_data['timed_out']) || time() >= $timer_stop) +			{ +				$errstr = $user->lang['FSOCK_TIMEOUT']; +				return false; +			}  		}  		@fclose($fsock);  	} diff --git a/phpBB/includes/functions_compress.php b/phpBB/includes/functions_compress.php index e3c1f31263..4675394633 100644 --- a/phpBB/includes/functions_compress.php +++ b/phpBB/includes/functions_compress.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -25,6 +24,11 @@ class compress  	var $fp = 0;  	/** +	* @var array +	*/ +	protected $filelist = array(); + +	/**  	* Add file to archive  	*/  	function add_file($src, $src_rm_prefix = '', $src_add_prefix = '', $skip_files = '') @@ -124,9 +128,41 @@ class compress  	}  	/** +	* Checks if a file by that name as already been added and, if it has, +	* returns a new, unique name. +	* +	* @param string $name The filename +	* @return string A unique filename +	*/ +	protected function unique_filename($name) +	{ +		if (isset($this->filelist[$name])) +		{ +			$start = $name; +			$ext = ''; +			$this->filelist[$name]++; + +			// Separate the extension off the end of the filename to preserve it +			$pos = strrpos($name, '.'); +			if ($pos !== false) +			{ +				$start = substr($name, 0, $pos); +				$ext = substr($name, $pos); +			} + +			return $start . '_' . $this->filelist[$name] . $ext; +		} + +		$this->filelist[$name] = 0; +		return $name; +	} + +	/**  	* Return available methods +	* +	* @return array Array of strings of available compression methods (.tar, .tar.gz, .zip, etc.)  	*/ -	function methods() +	public static function methods()  	{  		$methods = array('.tar');  		$available_methods = array('.tar.gz' => 'zlib', '.tar.bz2' => 'bz2', '.zip' => 'zlib'); @@ -362,6 +398,7 @@ class compress_zip extends compress  	function data($name, $data, $is_dir = false, $stat)  	{  		$name = str_replace('\\', '/', $name); +		$name = $this->unique_filename($name);  		$hexdtime = pack('V', $this->unix_to_dos_time($stat[9])); @@ -634,6 +671,7 @@ class compress_tar extends compress  	*/  	function data($name, $data, $is_dir = false, $stat)  	{ +		$name = $this->unique_filename($name);  		$this->wrote = true;  		$fzwrite = 	($this->isbz && function_exists('bzwrite')) ? 'bzwrite' : (($this->isgz && @extension_loaded('zlib')) ? 'gzwrite' : 'fwrite'); diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index 179fd3c2f8..e7772e14fe 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -412,13 +411,33 @@ function strip_bbcode(&$text, $uid = '')  function generate_text_for_display($text, $uid, $bitfield, $flags)  {  	static $bbcode; +	global $phpbb_dispatcher;  	if (!$text)  	{  		return '';  	} -	$text = censor_text($text); +	$censor_text = true; + +	/** +	* Use this event to modify the text before it is parsed +	* +	* @event core.modify_text_for_display_before +	* @var string	text			The text to parse +	* @var string	uid				The BBCode UID +	* @var string	bitfield		The BBCode Bitfield +	* @var int		flags			The BBCode Flags +	* @var bool		censor_text		Whether or not to apply word censors +	* @since 3.1-A1 +	*/ +	$vars = array('text', 'uid', 'bitfield', 'flags', 'censor_text'); +	extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_before', compact($vars))); + +	if ($censor_text) +	{		 +		$text = censor_text($text); +	}  	// Parse bbcode if bbcode uid stored and bbcode enabled  	if ($uid && ($flags & OPTION_FLAG_BBCODE)) @@ -444,6 +463,19 @@ function generate_text_for_display($text, $uid, $bitfield, $flags)  	$text = bbcode_nl2br($text);  	$text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES)); +	/** +	* Use this event to modify the text after it is parsed +	* +	* @event core.modify_text_for_display_after +	* @var string	text		The text to parse +	* @var string	uid			The BBCode UID +	* @var string	bitfield	The BBCode Bitfield +	* @var int		flags		The BBCode Flags +	* @since 3.1-A1 +	*/ +	$vars = array('text', 'uid', 'bitfield', 'flags'); +	extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_after', compact($vars))); +  	return $text;  } @@ -454,7 +486,23 @@ function generate_text_for_display($text, $uid, $bitfield, $flags)  */  function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bbcode = false, $allow_urls = false, $allow_smilies = false)  { -	global $phpbb_root_path, $phpEx; +	global $phpbb_root_path, $phpEx, $phpbb_dispatcher; + +	/** +	* Use this event to modify the text before it is prepared for storage +	* +	* @event core.modify_text_for_storage_before +	* @var string	text			The text to parse +	* @var string	uid				The BBCode UID +	* @var string	bitfield		The BBCode Bitfield +	* @var int		flags			The BBCode Flags +	* @var bool		allow_bbcode	Whether or not to parse BBCode +	* @var bool		allow_urls		Whether or not to parse URLs +	* @var bool		allow_smilies	Whether or not to parse Smilies +	* @since 3.1-A1 +	*/ +	$vars = array('text', 'uid', 'bitfield', 'flags', 'allow_bbcode', 'allow_urls', 'allow_smilies'); +	extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_before', compact($vars)));  	$uid = $bitfield = '';  	$flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0); @@ -483,6 +531,19 @@ function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bb  	$bitfield = $message_parser->bbcode_bitfield; +	/** +	* Use this event to modify the text after it is prepared for storage +	* +	* @event core.modify_text_for_storage_after +	* @var string	text			The text to parse +	* @var string	uid				The BBCode UID +	* @var string	bitfield		The BBCode Bitfield +	* @var int		flags			The BBCode Flags +	* @since 3.1-A1 +	*/ +	$vars = array('text', 'uid', 'bitfield', 'flags'); +	extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_after', compact($vars))); +  	return;  } @@ -492,10 +553,33 @@ function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bb  */  function generate_text_for_edit($text, $uid, $flags)  { -	global $phpbb_root_path, $phpEx; +	global $phpbb_root_path, $phpEx, $phpbb_dispatcher; + +	/** +	* Use this event to modify the text before it is decoded for editing +	* +	* @event core.modify_text_for_edit_before +	* @var string	text			The text to parse +	* @var string	uid				The BBCode UID +	* @var int		flags			The BBCode Flags +	* @since 3.1-A1 +	*/ +	$vars = array('text', 'uid', 'flags'); +	extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_before', compact($vars)));  	decode_message($text, $uid); +	/** +	* Use this event to modify the text after it is decoded for editing +	* +	* @event core.modify_text_for_edit_after +	* @var string	text			The text to parse +	* @var int		flags			The BBCode Flags +	* @since 3.1-A1 +	*/ +	$vars = array('text', 'flags'); +	extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_after', compact($vars))); +  	return array(  		'allow_bbcode'	=> ($flags & OPTION_FLAG_BBCODE) ? 1 : 0,  		'allow_smilies'	=> ($flags & OPTION_FLAG_SMILIES) ? 1 : 0, @@ -740,7 +824,7 @@ function smiley_text($text, $force_option = false)  	else  	{  		$root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_root_path; -		return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/(.*?) \/><!\-\- s\1 \-\->#', '<img src="' . $root_path . $config['smilies_path'] . '/\2 />', $text); +		return preg_replace('#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/(.*?) \/><!\-\- s\1 \-\->#', '<img class="smilies" src="' . $root_path . $config['smilies_path'] . '/\2 />', $text);  	}  } @@ -938,12 +1022,12 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count,  			}  			$download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']); +			$l_downloaded_viewed = 'VIEWED_COUNTS';  			switch ($display_cat)  			{  				// Images  				case ATTACHMENT_CATEGORY_IMAGE: -					$l_downloaded_viewed = 'VIEWED_COUNT';  					$inline_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']);  					$download_link .= '&mode=view'; @@ -957,7 +1041,6 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count,  				// Images, but display Thumbnail  				case ATTACHMENT_CATEGORY_THUMB: -					$l_downloaded_viewed = 'VIEWED_COUNT';  					$thumbnail_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id'] . '&t=1');  					$download_link .= '&mode=view'; @@ -971,7 +1054,6 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count,  				// Windows Media Streams  				case ATTACHMENT_CATEGORY_WM: -					$l_downloaded_viewed = 'VIEWED_COUNT';  					// Giving the filename directly because within the wm object all variables are in local context making it impossible  					// to validate against a valid session (all params can differ) @@ -990,7 +1072,6 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count,  				// Real Media Streams  				case ATTACHMENT_CATEGORY_RM:  				case ATTACHMENT_CATEGORY_QUICKTIME: -					$l_downloaded_viewed = 'VIEWED_COUNT';  					$block_array += array(  						'S_RM_FILE'			=> ($display_cat == ATTACHMENT_CATEGORY_RM) ? true : false, @@ -1007,8 +1088,6 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count,  				case ATTACHMENT_CATEGORY_FLASH:  					list($width, $height) = @getimagesize($filename); -					$l_downloaded_viewed = 'VIEWED_COUNT'; -  					$block_array += array(  						'S_FLASH_FILE'	=> true,  						'WIDTH'			=> $width, @@ -1021,7 +1100,7 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count,  				break;  				default: -					$l_downloaded_viewed = 'DOWNLOAD_COUNT'; +					$l_downloaded_viewed = 'DOWNLOAD_COUNTS';  					$block_array += array(  						'S_FILE'		=> true, @@ -1029,11 +1108,14 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count,  				break;  			} -			$l_download_count = (!isset($attachment['download_count']) || $attachment['download_count'] == 0) ? $user->lang[$l_downloaded_viewed . '_NONE'] : (($attachment['download_count'] == 1) ? sprintf($user->lang[$l_downloaded_viewed], $attachment['download_count']) : sprintf($user->lang[$l_downloaded_viewed . 'S'], $attachment['download_count'])); +			if (!isset($attachment['download_count'])) +			{ +				$attachment['download_count'] = 0; +			}  			$block_array += array(  				'U_DOWNLOAD_LINK'		=> $download_link, -				'L_DOWNLOAD_COUNT'		=> $l_download_count +				'L_DOWNLOAD_COUNT'		=> $user->lang($l_downloaded_viewed, (int) $attachment['download_count']),  			);  		} @@ -1106,8 +1188,8 @@ function extension_allowed($forum_id, $extension, &$extensions)  * @param string $string The text to truncate to the given length. String is specialchared.  * @param int $max_length Maximum length of string (multibyte character count as 1 char / Html entity count as 1 char)  * @param int $max_store_length Maximum character length of string (multibyte character count as 1 char / Html entity count as entity chars). -* @param bool $allow_reply Allow Re: in front of string  -* 	NOTE: This parameter can cause undesired behavior (returning strings longer than $max_store_legnth) and is deprecated.  +* @param bool $allow_reply Allow Re: in front of string +* 	NOTE: This parameter can cause undesired behavior (returning strings longer than $max_store_length) and is deprecated.  * @param string $append String to be appended  */  function truncate_string($string, $max_length = 60, $max_store_length = 255, $allow_reply = false, $append = '') @@ -1178,6 +1260,7 @@ function truncate_string($string, $max_length = 60, $max_store_length = 255, $al  function get_username_string($mode, $user_id, $username, $username_colour = '', $guest_username = false, $custom_profile_url = false)  {  	static $_profile_cache; +	global $phpbb_dispatcher;  	// We cache some common variables we need within this function  	if (empty($_profile_cache)) @@ -1255,10 +1338,50 @@ function get_username_string($mode, $user_id, $username, $username_colour = '',  	if (($mode == 'full' && !$profile_url) || $mode == 'no_profile')  	{ -		return str_replace(array('{USERNAME_COLOUR}', '{USERNAME}'), array($username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_noprofile'] : $_profile_cache['tpl_noprofile_colour']); +		$username_string = str_replace(array('{USERNAME_COLOUR}', '{USERNAME}'), array($username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_noprofile'] : $_profile_cache['tpl_noprofile_colour']); +	} +	else +	{ +		$username_string = str_replace(array('{PROFILE_URL}', '{USERNAME_COLOUR}', '{USERNAME}'), array($profile_url, $username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_profile'] : $_profile_cache['tpl_profile_colour']);  	} +	 +	/** +	* Use this event to change the output of get_username_string() +	* +	* @event core.modify_username_string +	* @var string mode				profile|username|colour|full|no_profile +	* @var int user_id				String or array of additional url +	*								parameters +	* @var string username			The user's username +	* @var string username_colour	The user's colour +	* @var string guest_username	Optional parameter to specify the +	*								guest username. +	* @var string custom_profile_url Optional parameter to specify a +	*								profile url. +	* @var string username_string	The string that has been generated +	* @var array _profile_cache		Array of original return templates +	* @since 3.1-A1 +	*/ +	$vars = array('mode', 'user_id', 'username', 'username_colour', 'guest_username', 'custom_profile_url', 'username_string', '_profile_cache'); +	extract($phpbb_dispatcher->trigger_event('core.modify_username_string', compact($vars))); -	return str_replace(array('{PROFILE_URL}', '{USERNAME_COLOUR}', '{USERNAME}'), array($profile_url, $username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_profile'] : $_profile_cache['tpl_profile_colour']); +	return $username_string; +} + +/** + * Add an option to the quick-mod tools. + * + * @param string $option The language key for the value of the option. + * @param string $lang_string The language string to use. + */ +function phpbb_add_quickmod_option($option, $lang_string) +{ +	global $template, $user; +	$lang_string = $user->lang($lang_string); +	$template->assign_block_vars('quickmod', array( +		'VALUE'  => $option, +		'TITLE'    => $lang_string, +	));  }  /** diff --git a/phpBB/includes/functions_convert.php b/phpBB/includes/functions_convert.php index ee77a444db..ac791e0d9b 100644 --- a/phpBB/includes/functions_convert.php +++ b/phpBB/includes/functions_convert.php @@ -2,9 +2,8 @@  /**  *  * @package install -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -424,7 +423,8 @@ function import_avatar_gallery($gallery_name = '', $subdirs_as_galleries = false  	$relative_path = empty($convert->convertor['source_path_absolute']); -	if (empty($convert->convertor['avatar_gallery_path'])) +	// check for trailing slash +	if (rtrim($convert->convertor['avatar_gallery_path'], '/') === '')  	{  		$convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_GALLERY_PATH'], 'import_avatar_gallery()'), __LINE__, __FILE__);  	} @@ -588,7 +588,8 @@ function import_attachment($source, $use_target = false)  	global $convert, $phpbb_root_path, $config, $user; -	if (empty($convert->convertor['upload_path'])) +	// check for trailing slash +	if (rtrim($convert->convertor['upload_path'], '/') === '')  	{  		$convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_UPLOAD_DIR'], 'import_attachment()'), __LINE__, __FILE__);  	} @@ -647,7 +648,8 @@ function import_smiley($source, $use_target = false)  	global $convert, $phpbb_root_path, $config, $user; -	if (!isset($convert->convertor['smilies_path'])) +	// check for trailing slash +	if (rtrim($convert->convertor['smilies_path'], '/') === '')  	{  		$convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_SMILIES_PATH'], 'import_smiley()'), __LINE__, __FILE__);  	} @@ -667,7 +669,8 @@ function import_avatar($source, $use_target = false, $user_id = false)  	global $convert, $phpbb_root_path, $config, $user; -	if (!isset($convert->convertor['avatar_path'])) +	// check for trailing slash +	if (rtrim($convert->convertor['avatar_path'], '/') === '')  	{  		$convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_AVATAR_PATH'], 'import_avatar()'), __LINE__, __FILE__);  	} @@ -1881,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 abc5b3e29f..73129803ee 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -23,7 +22,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  {  	global $db, $auth, $user, $template;  	global $phpbb_root_path, $phpEx, $config; -	global $request; +	global $request, $phpbb_dispatcher;  	$forum_rows = $subforums = $forum_ids = $forum_ids_moderator = $forum_moderators = $active_forum_ary = array();  	$parent_id = $visible_forums = 0; @@ -52,6 +51,27 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  		$sql_where = 'left_id > ' . $root_data['left_id'] . ' AND left_id < ' . $root_data['right_id'];  	} +	// Handle marking everything read +	if ($mark_read == 'all') +	{ +		$redirect = build_url(array('mark', 'hash', 'mark_time')); +		meta_refresh(3, $redirect); + +		if (check_link_hash(request_var('hash', ''), 'global')) +		{ +			markread('all', false, false, request_var('mark_time', 0)); + +			trigger_error( +				$user->lang['FORUMS_MARKED'] . '<br /><br />' . +				sprintf($user->lang['RETURN_INDEX'], '<a href="' . $redirect . '">', '</a>') +			); +		} +		else +		{ +			trigger_error(sprintf($user->lang['RETURN_PAGE'], '<a href="' . $redirect . '">', '</a>')); +		} +	} +  	// Display list of active topics for this category?  	$show_active = (isset($root_data['forum_flags']) && ($root_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS)) ? true : false; @@ -89,7 +109,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  		$sql_array['SELECT'] .= ', fa.user_id';  	} -	$sql = $db->sql_build_query('SELECT', array( +	$sql_ary = array(  		'SELECT'	=> $sql_array['SELECT'],  		'FROM'		=> $sql_array['FROM'],  		'LEFT_JOIN'	=> $sql_array['LEFT_JOIN'], @@ -97,8 +117,19 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  		'WHERE'		=> $sql_where,  		'ORDER_BY'	=> 'f.left_id', -	)); +	); +	/** +	* Event to modify the SQL query before the forum data is queried +	* +	* @event core.display_forums_modify_sql +	* @var	array	sql_ary		The SQL array to get the data of the forums +	* @since 3.1-A1 +	*/ +	$vars = array('sql_ary'); +	extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_sql', compact($vars))); + +	$sql = $db->sql_build_query('SELECT', $sql_ary);  	$result = $db->sql_query($sql);  	$forum_tracking_info = array(); @@ -106,16 +137,30 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  	while ($row = $db->sql_fetchrow($result))  	{ +		/** +		* Event to modify the data set of a forum +		* +		* This event is triggered once per forum +		* +		* @event core.display_forums_modify_row +		* @var	int		branch_root_id	Last top-level forum +		* @var	array	row				The data of the forum +		* @since 3.1-A1 +		*/ +		$vars = array('branch_root_id', 'row'); +		extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_row', compact($vars))); +  		$forum_id = $row['forum_id'];  		// Mark forums read? -		if ($mark_read == 'forums' || $mark_read == 'all') +		if ($mark_read == 'forums')  		{  			if ($auth->acl_get('f_list', $forum_id))  			{  				$forum_ids[] = $forum_id; -				continue;  			} + +			continue;  		}  		// Category with no members @@ -141,8 +186,6 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  			continue;  		} -		$forum_ids[] = $forum_id; -  		if ($config['load_db_lastread'] && $user->data['is_registered'])  		{  			$forum_tracking_info[$forum_id] = (!empty($row['mark_time'])) ? $row['mark_time'] : $user->data['user_lastmark']; @@ -240,27 +283,36 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  				$forum_rows[$parent_id]['forum_id_last_post'] = $forum_id;  			}  		} + +		/** +		* Event to modify the forum rows data set +		* +		* This event is triggered once per forum +		* +		* @event core.display_forums_modify_forum_rows +		* @var	array	forum_rows		Data array of all forums we display +		* @var	array	subforums		Data array of all subforums we display +		* @var	int		branch_root_id	Current top-level forum +		* @var	int		parent_id		Current parent forum +		* @var	array	row				The data of the forum +		* @since 3.1-A1 +		*/ +		$vars = array('forum_rows', 'subforums', 'branch_root_id', 'parent_id', 'row'); +		extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_forum_rows', compact($vars)));  	}  	$db->sql_freeresult($result);  	// Handle marking posts -	if ($mark_read == 'forums' || $mark_read == 'all') +	if ($mark_read == 'forums')  	{ -		$redirect = build_url(array('mark', 'hash')); +		$redirect = build_url(array('mark', 'hash', 'mark_time'));  		$token = request_var('hash', '');  		if (check_link_hash($token, 'global'))  		{ -			if ($mark_read == 'all') -			{ -				markread('all'); -				$message = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $redirect . '">', '</a>'); -			} -			else -			{ -				markread('topics', $forum_ids); -				$message = sprintf($user->lang['RETURN_FORUM'], '<a href="' . $redirect . '">', '</a>'); -			} +			markread('topics', $forum_ids, false, request_var('mark_time', 0)); +			$message = sprintf($user->lang['RETURN_FORUM'], '<a href="' . $redirect . '">', '</a>');  			meta_refresh(3, $redirect); +  			trigger_error($user->lang['FORUMS_MARKED'] . '<br /><br />' . $message);  		}  		else @@ -384,12 +436,13 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  		if ($row['forum_last_post_id'])  		{  			$last_post_subject = $row['forum_last_post_subject']; +			$last_post_subject_truncated = truncate_string(censor_text($last_post_subject), 30, 255, false, $user->lang['ELLIPSIS']);  			$last_post_time = $user->format_date($row['forum_last_post_time']);  			$last_post_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id_last_post'] . '&p=' . $row['forum_last_post_id']) . '#p' . $row['forum_last_post_id'];  		}  		else  		{ -			$last_post_subject = $last_post_time = $last_post_url = ''; +			$last_post_subject = $last_post_time = $last_post_url = $last_post_subject_truncated = '';  		}  		// Output moderator listing ... if applicable @@ -397,7 +450,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  		if ($display_moderators && !empty($forum_moderators[$forum_id]))  		{  			$l_moderator = (sizeof($forum_moderators[$forum_id]) == 1) ? $user->lang['MODERATOR'] : $user->lang['MODERATORS']; -			$moderators_list = implode(', ', $forum_moderators[$forum_id]); +			$moderators_list = implode($user->lang['COMMA_SEPARATOR'], $forum_moderators[$forum_id]);  		}  		$l_post_click_count = ($row['forum_type'] == FORUM_LINK) ? 'CLICKS' : 'POSTS'; @@ -408,7 +461,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  		{  			$s_subforums_list[] = '<a href="' . $subforum['link'] . '" class="subforum ' . (($subforum['unread']) ? 'unread' : 'read') . '" title="' . (($subforum['unread']) ? $user->lang['UNREAD_POSTS'] : $user->lang['NO_UNREAD_POSTS']) . '">' . $subforum['name'] . '</a>';  		} -		$s_subforums_list = (string) implode(', ', $s_subforums_list); +		$s_subforums_list = (string) implode($user->lang['COMMA_SEPARATOR'], $s_subforums_list);  		$catless = ($row['parent_id'] == $root_data['forum_id']) ? true : false;  		if ($row['forum_type'] != FORUM_LINK) @@ -429,14 +482,16 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  			}  		} -		$template->assign_block_vars('forumrow', array( +		$forum_row = array(  			'S_IS_CAT'			=> false,  			'S_NO_CAT'			=> $catless && !$last_catless,  			'S_IS_LINK'			=> ($row['forum_type'] == FORUM_LINK) ? true : false,  			'S_UNREAD_FORUM'	=> $forum_unread, +			'S_AUTH_READ'		=> $auth->acl_get('f_read', $row['forum_id']),  			'S_LOCKED_FORUM'	=> ($row['forum_status'] == ITEM_LOCKED) ? true : false,  			'S_LIST_SUBFORUMS'	=> ($row['display_subforum_list']) ? true : false,  			'S_SUBFORUMS'		=> (sizeof($subforums_list)) ? true : false, +			'S_DISPLAY_SUBJECT'	=>	($last_post_subject && $config['display_last_subject'] && !$row['forum_password'] && $auth->acl_get('f_read', $row['forum_id'])) ? true : false,  			'S_FEED_ENABLED'	=> ($config['feed_forum'] && !phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $row['forum_options']) && $row['forum_type'] == FORUM_POST) ? true : false,  			'FORUM_ID'				=> $row['forum_id'], @@ -446,11 +501,11 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  			$l_post_click_count		=> $post_click_count,  			'FORUM_IMG_STYLE'		=> $folder_image,  			'FORUM_FOLDER_IMG'		=> $user->img($folder_image, $folder_alt), -			'FORUM_FOLDER_IMG_SRC'	=> $user->img($folder_image, $folder_alt, false, '', 'src'),  			'FORUM_FOLDER_IMG_ALT'	=> isset($user->lang[$folder_alt]) ? $user->lang[$folder_alt] : '',  			'FORUM_IMAGE'			=> ($row['forum_image']) ? '<img src="' . $phpbb_root_path . $row['forum_image'] . '" alt="' . $user->lang[$folder_alt] . '" />' : '',  			'FORUM_IMAGE_SRC'		=> ($row['forum_image']) ? $phpbb_root_path . $row['forum_image'] : '', -			'LAST_POST_SUBJECT'		=> censor_text($last_post_subject), +			'LAST_POST_SUBJECT'		=> (!$row['forum_password'] && $auth->acl_get('f_read', $row['forum_id'])) ? censor_text($last_post_subject) : "", +			'LAST_POST_SUBJECT_TRUNCATED'	=> (!$row['forum_password'] && $auth->acl_get('f_read', $row['forum_id'])) ? $last_post_subject_truncated : "",  			'LAST_POST_TIME'		=> $last_post_time,  			'LAST_POSTER'			=> get_username_string('username', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']),  			'LAST_POSTER_COLOUR'	=> get_username_string('colour', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), @@ -459,15 +514,29 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  			'SUBFORUMS'				=> $s_subforums_list,  			'L_SUBFORUM_STR'		=> $l_subforums, -			'L_FORUM_FOLDER_ALT'	=> $folder_alt,  			'L_MODERATOR_STR'		=> $l_moderator,  			'U_UNAPPROVED_TOPICS'	=> ($row['forum_id_unapproved_topics']) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=unapproved_topics&f=' . $row['forum_id_unapproved_topics']) : '',  			'U_VIEWFORUM'		=> $u_viewforum,  			'U_LAST_POSTER'		=> get_username_string('profile', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), -			'U_LAST_POST'		=> $last_post_url) +			'U_LAST_POST'		=> $last_post_url,  		); +		/** +		* Modify the template data block of the forum +		* +		* This event is triggered once per forum +		* +		* @event core.display_forums_modify_template_vars +		* @var	array	forum_row		Template data of the forum +		* @var	array	row				The data of the forum +		* @since 3.1-A1 +		*/ +		$vars = array('forum_row', 'row'); +		extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_template_vars', compact($vars))); + +		$template->assign_block_vars('forumrow', $forum_row); +  		// Assign subforums loop for style authors  		foreach ($subforums_list as $subforum)  		{ @@ -482,7 +551,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod  	}  	$template->assign_vars(array( -		'U_MARK_FORUMS'		=> ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums') : '', +		'U_MARK_FORUMS'		=> ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums&mark_time=' . time()) : '',  		'S_HAS_SUBFORUM'	=> ($visible_forums) ? true : false,  		'L_SUBFORUM'		=> ($visible_forums == 1) ? $user->lang['SUBFORUM'] : $user->lang['SUBFORUMS'],  		'LAST_POST_IMG'		=> $user->img('icon_topic_latest', 'VIEW_LATEST_POST'), @@ -625,48 +694,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 .= ' ... '; - -				// 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) @@ -857,7 +884,7 @@ function topic_status(&$topic_row, $replies, $unread_topic, &$folder_img, &$fold  */  function display_custom_bbcodes()  { -	global $db, $template, $user; +	global $db, $template, $user, $phpbb_dispatcher;  	// Start counting from 22 for the bbcode ids (every bbcode takes two ids - opening/closing)  	$num_predefined_bbcodes = 22; @@ -877,17 +904,40 @@ function display_custom_bbcodes()  			$row['bbcode_helpline'] = $user->lang[strtoupper($row['bbcode_helpline'])];  		} -		$template->assign_block_vars('custom_tags', array( +		$custom_tags = array(  			'BBCODE_NAME'		=> "'[{$row['bbcode_tag']}]', '[/" . str_replace('=', '', $row['bbcode_tag']) . "]'",  			'BBCODE_ID'			=> $num_predefined_bbcodes + ($i * 2),  			'BBCODE_TAG'		=> $row['bbcode_tag'],  			'BBCODE_HELPLINE'	=> $row['bbcode_helpline'],  			'A_BBCODE_HELPLINE'	=> str_replace(array('&', '"', "'", '<', '>'), array('&', '"', "\'", '<', '>'), $row['bbcode_helpline']), -		)); +		); + +		/** +		* Modify the template data block of a bbcode +		* +		* This event is triggered once per bbcode +		* +		* @event core.display_custom_bbcodes_modify_row +		* @var	array	custom_tags		Template data of the bbcode +		* @var	array	row				The data of the bbcode +		* @since 3.1-A1 +		*/ +		$vars = array('custom_tags', 'row'); +		extract($phpbb_dispatcher->trigger_event('core.display_custom_bbcodes_modify_row', compact($vars))); + +		$template->assign_block_vars('custom_tags', $custom_tags);  		$i++;  	}  	$db->sql_freeresult($result); + +	/** +	* Display custom bbcodes +	* +	* @event core.display_custom_bbcodes +	* @since 3.1-A1 +	*/ +	$phpbb_dispatcher->dispatch('core.display_custom_bbcodes');  }  /** @@ -979,13 +1029,17 @@ function display_user_activity(&$userdata)  	}  	// Obtain active topic +	// We need to exclude passworded forums here so we do not leak the topic title +	$forum_ary_topic = array_unique(array_merge($forum_ary, $user->get_passworded_forums())); +	$forum_sql_topic = (!empty($forum_ary_topic)) ? 'AND ' . $db->sql_in_set('forum_id', $forum_ary_topic, true) : ''; +  	$sql = 'SELECT topic_id, COUNT(post_id) AS num_posts  		FROM ' . POSTS_TABLE . '  		WHERE poster_id = ' . $userdata['user_id'] . "  			AND post_postcount = 1  			AND (post_approved = 1  				$sql_m_approve) -			$forum_sql +			$forum_sql_topic  		GROUP BY topic_id  		ORDER BY num_posts DESC";  	$result = $db->sql_query_limit($sql, 1); @@ -1027,10 +1081,10 @@ function display_user_activity(&$userdata)  	$template->assign_vars(array(  		'ACTIVE_FORUM'			=> $active_f_name, -		'ACTIVE_FORUM_POSTS'	=> ($active_f_count == 1) ? sprintf($user->lang['USER_POST'], 1) : sprintf($user->lang['USER_POSTS'], $active_f_count), +		'ACTIVE_FORUM_POSTS'	=> $user->lang('USER_POSTS', (int) $active_f_count),  		'ACTIVE_FORUM_PCT'		=> sprintf($l_active_pct, $active_f_pct),  		'ACTIVE_TOPIC'			=> censor_text($active_t_name), -		'ACTIVE_TOPIC_POSTS'	=> ($active_t_count == 1) ? sprintf($user->lang['USER_POST'], 1) : sprintf($user->lang['USER_POSTS'], $active_t_count), +		'ACTIVE_TOPIC_POSTS'	=> $user->lang('USER_POSTS', (int) $active_t_count),  		'ACTIVE_TOPIC_PCT'		=> sprintf($l_active_pct, $active_t_pct),  		'U_ACTIVE_FORUM'		=> append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $active_f_id),  		'U_ACTIVE_TOPIC'		=> append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=' . $active_t_id), @@ -1041,7 +1095,7 @@ function display_user_activity(&$userdata)  /**  * Topic and forum watching common code  */ -function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id, $notify_status = 'unset', $start = 0) +function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id, $notify_status = 'unset', $start = 0, $item_title = '')  {  	global $template, $db, $user, $phpEx, $start, $phpbb_root_path;  	global $request; @@ -1051,6 +1105,7 @@ function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id,  	$match_id = ($mode == 'forum') ? $forum_id : $topic_id;  	$u_url = "uid={$user->data['user_id']}";  	$u_url .= ($mode == 'forum') ? '&f' : '&f=' . $forum_id . '&t'; +	$is_watching = 0;  	// Is user watching this thread?  	if ($user_id != ANONYMOUS) @@ -1071,32 +1126,54 @@ function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id,  		if (!is_null($notify_status) && $notify_status !== '')  		{ -  			if (isset($_GET['unwatch']))  			{  				$uid = request_var('uid', 0); -				if ($uid != $user_id) -				{ -					$redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); -					$message = $user->lang['ERR_UNWATCHING'] . '<br /><br />' . sprintf($user->lang['RETURN_' . strtoupper($mode)], '<a href="' . $redirect_url . '">', '</a>'); -					trigger_error($message); -				} -				if ($request->variable('unwatch', '', false, phpbb_request_interface::GET) == $mode) +				$token = request_var('hash', ''); + +				if ($token && check_link_hash($token, "{$mode}_$match_id") || confirm_box(true))  				{ -					$is_watching = 0; +					if ($uid != $user_id || $request->variable('unwatch', '', false, phpbb_request_interface::GET) != $mode) +					{ +						$redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); +						$message = $user->lang['ERR_UNWATCHING'] . '<br /><br />' . sprintf($user->lang['RETURN_' . strtoupper($mode)], '<a href="' . $redirect_url . '">', '</a>'); +						trigger_error($message); +					}  					$sql = 'DELETE FROM ' . $table_sql . "  						WHERE $where_sql = $match_id  							AND user_id = $user_id";  					$db->sql_query($sql); -				} - -				$redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); -				meta_refresh(3, $redirect_url); +					$redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); +					$message = $user->lang['NOT_WATCHING_' . strtoupper($mode)] . '<br /><br />'; +					$message .= sprintf($user->lang['RETURN_' . strtoupper($mode)], '<a href="' . $redirect_url . '">', '</a>'); +					meta_refresh(3, $redirect_url); +					trigger_error($message); +				} +				else +				{ +					$s_hidden_fields = array( +						'uid'		=> $user->data['user_id'], +						'unwatch'	=> $mode, +						'start'		=> $start, +						'f'			=> $forum_id, +					); +					if ($mode != 'forum') +					{ +						$s_hidden_fields['t'] = $topic_id; +					} -				$message = $user->lang['NOT_WATCHING_' . strtoupper($mode)] . '<br /><br />' . sprintf($user->lang['RETURN_' . strtoupper($mode)], '<a href="' . $redirect_url . '">', '</a>'); -				trigger_error($message); +					if ($item_title == '') +					{ +						$confirm_box_message = 'UNWATCH_' . strtoupper($mode); +					} +					else +					{ +						$confirm_box_message = $user->lang('UNWATCH_' . strtoupper($mode) . '_DETAILED', $item_title); +					} +					confirm_box(false, $confirm_box_message, build_hidden_fields($s_hidden_fields)); +				}  			}  			else  			{ @@ -1116,26 +1193,45 @@ function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id,  		{  			if (isset($_GET['watch']))  			{ +				$uid = request_var('uid', 0);  				$token = request_var('hash', ''); -				$redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); -				if ($request->variable('watch', '', false, phpbb_request_interface::GET) == $mode && check_link_hash($token, "{$mode}_$match_id")) +				if ($token && check_link_hash($token, "{$mode}_$match_id") || confirm_box(true))  				{ +					if ($uid != $user_id || $request->variable('watch', '', false, phpbb_request_interface::GET) != $mode) +					{ +						$redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); +						$message = $user->lang['ERR_WATCHING'] . '<br /><br />' . sprintf($user->lang['RETURN_' . strtoupper($mode)], '<a href="' . $redirect_url . '">', '</a>'); +						trigger_error($message); +					} +  					$is_watching = true;  					$sql = 'INSERT INTO ' . $table_sql . " (user_id, $where_sql, notify_status)  						VALUES ($user_id, $match_id, " . NOTIFY_YES . ')';  					$db->sql_query($sql); + +					$redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start");  					$message = $user->lang['ARE_WATCHING_' . strtoupper($mode)] . '<br /><br />' . sprintf($user->lang['RETURN_' . strtoupper($mode)], '<a href="' . $redirect_url . '">', '</a>'); +					meta_refresh(3, $redirect_url); +					trigger_error($message);  				}  				else  				{ -					$message = $user->lang['ERR_WATCHING'] . '<br /><br />' . sprintf($user->lang['RETURN_' . strtoupper($mode)], '<a href="' . $redirect_url . '">', '</a>'); -				} - -				meta_refresh(3, $redirect_url); +					$s_hidden_fields = array( +						'uid'		=> $user->data['user_id'], +						'watch'		=> $mode, +						'start'		=> $start, +						'f'			=> $forum_id, +					); +					if ($mode != 'forum') +					{ +						$s_hidden_fields['t'] = $topic_id; +					} -				trigger_error($message); +					$confirm_box_message = (($item_title == '') ? 'WATCH_' . strtoupper($mode) : $user->lang('WATCH_' . strtoupper($mode) . '_DETAILED', $item_title)); +					confirm_box(false, $confirm_box_message, build_hidden_fields($s_hidden_fields)); +				}  			}  			else  			{ @@ -1145,7 +1241,8 @@ function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id,  	}  	else  	{ -		if ($request->variable('unwatch', '', false, phpbb_request_interface::GET) == $mode) +		if ((isset($_GET['unwatch']) && $request->variable('unwatch', '', false, phpbb_request_interface::GET) == $mode) || +			(isset($_GET['watch']) && $request->variable('watch', '', false, phpbb_request_interface::GET) == $mode))  		{  			login_box();  		} @@ -1159,7 +1256,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;  	} @@ -1226,6 +1325,31 @@ function get_user_rank($user_rank, $user_posts, &$rank_title, &$rank_img, &$rank  function get_user_avatar($avatar, $avatar_type, $avatar_width, $avatar_height, $alt = 'USER_AVATAR', $ignore_config = false)  {  	global $user, $config, $phpbb_root_path, $phpEx; +	global $phpbb_dispatcher; + +	$overwrite_avatar = ''; + +	/** +	* Overwrite users avatar +	* +	* @event core.display_custom_bbcodes_modify_row +	* @var	string	avatar			Users assigned avatar name +	* @var	int		avatar_type		Type of avatar +	* @var	string	avatar_width	Width of users avatar +	* @var	string	avatar_height	Height of users avatar +	* @var	string	alt				Language string for alt tag within image +	*								Can be a language key or text +	* @var	bool	ignore_config	Ignores config and force displaying avatar +	* @var	string	overwrite_avatar	If set, this string will be the avatar +	* @since 3.1-A1 +	*/ +	$vars = array('avatar', 'avatar_type', 'avatar_width', 'avatar_height', 'alt', 'ignore_config', 'overwrite_avatar'); +	extract($phpbb_dispatcher->trigger_event('core.user_get_avatar', compact($vars))); + +	if ($overwrite_avatar) +	{ +		return $overwrite_avatar; +	}  	if (empty($avatar) || !$avatar_type || (!$config['allow_avatar'] && !$ignore_config))  	{ @@ -1263,3 +1387,39 @@ function get_user_avatar($avatar, $avatar_type, $avatar_width, $avatar_height, $  	$avatar_img .= $avatar;  	return '<img src="' . (str_replace(' ', '%20', $avatar_img)) . '" width="' . $avatar_width . '" height="' . $avatar_height . '" alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />';  } + +/** +* Generate a list of archive types available for compressing attachments +* +* @param string $param_key Either topic_id or post_id +* @param string $param_val The value of the topic or post id +* @param string $phpbb_root_path The root path of the phpBB installation +* @param string $phpEx The PHP extension +* +* @return array Array containing the link and the type of compression +*/ +function phpbb_gen_download_links($param_key, $param_val, $phpbb_root_path, $phpEx) +{ +	if (!class_exists('compress')) +	{ +		require $phpbb_root_path . 'includes/functions_compress.' . $phpEx; +	} + +	$methods = compress::methods(); +	$links = array(); + +	foreach ($methods as $method) +	{ +		$exploded = explode('.', $method); +		$type = array_pop($exploded); +		$params = array('archive' => $method); +		$params[$param_key] = $param_val; + +		$links[] = array( +			'LINK' => append_sid("{$phpbb_root_path}download/file.$phpEx", $params), +			'TYPE' => $type, +		); +	} + +	return $links; +} diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php index 94bcb36698..b6371dbecc 100644 --- a/phpBB/includes/functions_download.php +++ b/phpBB/includes/functions_download.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -100,10 +100,10 @@ function send_avatar_to_browser($file, $browser)  */  function wrap_img_in_html($src, $title)  { -	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-Strict.dtd">'; +	echo '<!DOCTYPE html>';  	echo '<html>';  	echo '<head>'; -	echo '<meta http-equiv="content-type" content="text/html; charset=UTF-8" />'; +	echo '<meta charset="utf-8">';  	echo '<title>' . $title . '</title>';  	echo '</head>';  	echo '<body>'; @@ -126,7 +126,7 @@ function send_file_to_browser($attachment, $upload_dir, $category)  	if (!@file_exists($filename))  	{  		send_status_line(404, 'Not Found'); -		trigger_error($user->lang['ERROR_NO_ATTACHMENT'] . '<br /><br />' . sprintf($user->lang['FILE_NOT_FOUND_404'], $filename)); +		trigger_error('ERROR_NO_ATTACHMENT');  	}  	// Correct the mime type - we force application/octetstream for all files, except images @@ -274,7 +274,9 @@ function send_file_to_browser($attachment, $upload_dir, $category)  */  function header_filename($file)  { -	$user_agent = (!empty($_SERVER['HTTP_USER_AGENT'])) ? htmlspecialchars((string) $_SERVER['HTTP_USER_AGENT']) : ''; +	global $request; + +	$user_agent = $request->header('User-Agent');  	// There be dragons here.  	// Not many follows the RFC... @@ -292,14 +294,14 @@ function header_filename($file)  */  function download_allowed()  { -	global $config, $user, $db; +	global $config, $user, $db, $request;  	if (!$config['secure_downloads'])  	{  		return true;  	} -	$url = (!empty($_SERVER['HTTP_REFERER'])) ? trim($_SERVER['HTTP_REFERER']) : trim(getenv('HTTP_REFERER')); +	$url = htmlspecialchars_decode($request->header('Referer'));  	if (!$url)  	{ @@ -404,8 +406,10 @@ function download_allowed()  */  function set_modified_headers($stamp, $browser)  { +	global $request; +  	// let's see if we have to send the file at all -	$last_load 	=  isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime(trim($_SERVER['HTTP_IF_MODIFIED_SINCE'])) : false; +	$last_load 	=  $request->header('Modified-Since') ? strtotime(trim($request->header('Modified-Since'))) : false;  	if ((strpos(strtolower($browser), 'msie 6.0') === false) && (strpos(strtolower($browser), 'msie 8.0') === false))  	{  		if ($last_load !== false && $last_load >= $stamp) @@ -473,12 +477,12 @@ function phpbb_http_byte_range($filesize)  	{  		$request_array = phpbb_find_range_request();  	} -	 +  	return (empty($request_array)) ? false : phpbb_parse_range_request($request_array, $filesize);  }  /** -* Searches for HTTP range request in super globals. +* Searches for HTTP range request in request headers.  *  * @return mixed		false if no request found  *					array of strings containing the requested ranges otherwise @@ -486,23 +490,16 @@ function phpbb_http_byte_range($filesize)  */  function phpbb_find_range_request()  { -	$globals = array( -		array('_SERVER',	'HTTP_RANGE'), -		array('_ENV',		'HTTP_RANGE'), -	); +	global $request; -	foreach ($globals as $array) -	{ -		$global	= $array[0]; -		$key	= $array[1]; +	$value = $request->header('Range'); -		// Make sure range request starts with "bytes=" -		if (isset($GLOBALS[$global][$key]) && strpos($GLOBALS[$global][$key], 'bytes=') === 0) -		{ -			// Strip leading 'bytes=' -			// Multiple ranges can be separated by a comma -			return explode(',', substr($GLOBALS[$global][$key], 6)); -		} +	// Make sure range request starts with "bytes=" +	if (strpos($value, 'bytes=') === 0) +	{ +		// Strip leading 'bytes=' +		// Multiple ranges can be separated by a comma +		return explode(',', substr($value, 6));  	}  	return false; @@ -595,3 +592,132 @@ function phpbb_parse_range_request($request_array, $filesize)  		);  	}  } + +/** +* Increments the download count of all provided attachments +* +* @param dbal $db The database object +* @param array|int $ids The attach_id of each attachment +* +* @return null +*/ +function phpbb_increment_downloads($db, $ids) +{ +	if (!is_array($ids)) +	{ +		$ids = array($ids); +	} + +	$sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' +		SET download_count = download_count + 1 +		WHERE ' . $db->sql_in_set('attach_id', $ids); +	$db->sql_query($sql); +} + +/** +* Handles authentication when downloading attachments from a post or topic +* +* @param dbal $db The database object +* @param phpbb_auth $auth The authentication object +* @param int $topic_id The id of the topic that we are downloading from +* +* @return null +*/ +function phpbb_download_handle_forum_auth($db, $auth, $topic_id) +{ +	$sql = 'SELECT t.forum_id, f.forum_password, f.parent_id +		FROM ' . TOPICS_TABLE . ' t, ' . FORUMS_TABLE . " f +		WHERE t.topic_id = " . (int) $topic_id . " +			AND t.forum_id = f.forum_id"; +	$result = $db->sql_query($sql); +	$row = $db->sql_fetchrow($result); +	$db->sql_freeresult($result); + +	if ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id'])) +	{ +		if ($row && $row['forum_password']) +		{ +			// Do something else ... ? +			login_forum_box($row); +		} +	} +	else +	{ +		send_status_line(403, 'Forbidden'); +		trigger_error('SORRY_AUTH_VIEW_ATTACH'); +	} +} + +/** +* Handles authentication when downloading attachments from PMs +* +* @param dbal $db The database object +* @param phpbb_auth $auth The authentication object +* @param int $user_id The user id +* @param int $msg_id The id of the PM that we are downloading from +* +* @return null +*/ +function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id) +{ +	if (!$auth->acl_get('u_pm_download')) +	{ +		send_status_line(403, 'Forbidden'); +		trigger_error('SORRY_AUTH_VIEW_ATTACH'); +	} + +	$allowed = phpbb_download_check_pm_auth($db, $user_id, $msg_id); + +	if (!$allowed) +	{ +		send_status_line(403, 'Forbidden'); +		trigger_error('ERROR_NO_ATTACHMENT'); +	} +} + +/** +* Checks whether a user can download from a particular PM +* +* @param dbal $db The database object +* @param int $user_id The user id +* @param int $msg_id The id of the PM that we are downloading from +* +* @return bool Whether the user is allowed to download from that PM or not +*/ +function phpbb_download_check_pm_auth($db, $user_id, $msg_id) +{ +	// Check if the attachment is within the users scope... +	$sql = 'SELECT msg_id +		FROM ' . PRIVMSGS_TO_TABLE . ' +		WHERE msg_id = ' . (int) $msg_id . ' +			AND ( +				user_id = ' . (int) $user_id . ' +				OR author_id = ' . (int) $user_id . ' +			)'; +	$result = $db->sql_query_limit($sql, 1); +	$allowed = (bool) $db->sql_fetchfield('msg_id'); +	$db->sql_freeresult($result); + +	return $allowed; +} + +/** +* Cleans a filename of any characters that could potentially cause a problem on +* a user's filesystem. +* +* @param string $filename The filename to clean +* +* @return string The cleaned filename +*/ +function phpbb_download_clean_filename($filename) +{ +	$bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|'); + +	// rawurlencode to convert any potentially 'bad' characters that we missed +	$filename = rawurlencode(str_replace($bad_chars, '_', $filename)); + +	// Turn the %xx entities created by rawurlencode to _ +	$filename = preg_replace("/%(\w{2})/", '_', $filename); + +	return $filename; +} diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 4746b2f6e1..10ec13669b 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -2,9 +2,8 @@  /**  *  * @package install -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -50,7 +49,6 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'firebird',  			'MODULE'		=> 'interbase',  			'DELIM'			=> ';;', -			'COMMENTS'		=> 'remove_remarks',  			'DRIVER'		=> 'firebird',  			'AVAILABLE'		=> true,  			'2.0.x'			=> false, @@ -60,7 +58,6 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'mysql_41',  			'MODULE'		=> 'mysqli',  			'DELIM'			=> ';', -			'COMMENTS'		=> 'remove_remarks',  			'DRIVER'		=> 'mysqli',  			'AVAILABLE'		=> true,  			'2.0.x'			=> true, @@ -70,7 +67,6 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'mysql',  			'MODULE'		=> 'mysql',  			'DELIM'			=> ';', -			'COMMENTS'		=> 'remove_remarks',  			'DRIVER'		=> 'mysql',  			'AVAILABLE'		=> true,  			'2.0.x'			=> true, @@ -80,7 +76,6 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'mssql',  			'MODULE'		=> 'mssql',  			'DELIM'			=> 'GO', -			'COMMENTS'		=> 'remove_comments',  			'DRIVER'		=> 'mssql',  			'AVAILABLE'		=> true,  			'2.0.x'			=> true, @@ -90,7 +85,6 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'mssql',  			'MODULE'		=> 'odbc',  			'DELIM'			=> 'GO', -			'COMMENTS'		=> 'remove_comments',  			'DRIVER'		=> 'mssql_odbc',  			'AVAILABLE'		=> true,  			'2.0.x'			=> true, @@ -100,17 +94,15 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'mssql',  			'MODULE'		=> 'sqlsrv',  			'DELIM'			=> 'GO', -			'COMMENTS'		=> 'remove_comments',  			'DRIVER'		=> 'mssqlnative',  			'AVAILABLE'		=> true,  			'2.0.x'			=> false, -		),			 +		),  		'oracle'	=>	array(  			'LABEL'			=> 'Oracle',  			'SCHEMA'		=> 'oracle',  			'MODULE'		=> 'oci8',  			'DELIM'			=> '/', -			'COMMENTS'		=> 'remove_comments',  			'DRIVER'		=> 'oracle',  			'AVAILABLE'		=> true,  			'2.0.x'			=> false, @@ -120,7 +112,6 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'postgres',  			'MODULE'		=> 'pgsql',  			'DELIM'			=> ';', -			'COMMENTS'		=> 'remove_comments',  			'DRIVER'		=> 'postgres',  			'AVAILABLE'		=> true,  			'2.0.x'			=> true, @@ -130,7 +121,6 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20  			'SCHEMA'		=> 'sqlite',  			'MODULE'		=> 'sqlite',  			'DELIM'			=> ';', -			'COMMENTS'		=> 'remove_remarks',  			'DRIVER'		=> 'sqlite',  			'AVAILABLE'		=> true,  			'2.0.x'			=> false, @@ -211,61 +201,20 @@ function dbms_select($default = '', $only_20x_options = false)  /**  * Get tables of a database +* +* @deprecated  */ -function get_tables($db) +function get_tables(&$db)  { -	switch ($db->sql_layer) -	{ -		case 'mysql': -		case 'mysql4': -		case 'mysqli': -			$sql = 'SHOW TABLES'; -		break; - -		case 'sqlite': -			$sql = 'SELECT name -				FROM sqlite_master -				WHERE type = "table"'; -		break; - -		case 'mssql': -		case 'mssql_odbc': -		case 'mssqlnative': -			$sql = "SELECT name -				FROM sysobjects -				WHERE type='U'"; -		break; - -		case 'postgres': -			$sql = 'SELECT relname -				FROM pg_stat_user_tables'; -		break; - -		case 'firebird': -			$sql = 'SELECT rdb$relation_name -				FROM rdb$relations -				WHERE rdb$view_source is null -					AND rdb$system_flag = 0'; -		break; - -		case 'oracle': -			$sql = 'SELECT table_name -				FROM USER_TABLES'; -		break; -	} - -	$result = $db->sql_query($sql); - -	$tables = array(); - -	while ($row = $db->sql_fetchrow($result)) +	if (!class_exists('phpbb_db_tools'))  	{ -		$tables[] = current($row); +		global $phpbb_root_path, $phpEx; +		require($phpbb_root_path . 'includes/db/db_tools.' . $phpEx);  	} -	$db->sql_freeresult($result); +	$db_tools = new phpbb_db_tools($db); -	return $tables; +	return $db_tools->sql_list_tables();  }  /** @@ -514,11 +463,21 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix,  }  /** -* remove_remarks will strip the sql comment lines out of an uploaded sql file +* Removes "/* style" as well as "# style" comments from $input. +* +* @param string $input		Input string +* +* @return string			Input string with comments removed  */ -function remove_remarks(&$sql) +function phpbb_remove_comments($input)  { -	$sql = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql)); +	// Remove /* */ comments (http://ostermiller.org/findcomment.html) +	$input = preg_replace('#/\*(.|[\r\n])*?\*/#', "\n", $input); + +	// Remove # style comments +	$input = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $input)); + +	return $input;  }  /** @@ -555,3 +514,60 @@ function adjust_language_keys_callback($matches)  		return (!empty($lang[$matches[1]])) ? $db->sql_escape($lang[$matches[1]]) : $db->sql_escape($matches[1]);  	}  } + +/** +* Creates the output to be stored in a phpBB config.php file +* +* @param	array	$data Array containing the database connection information +* @param	string	$dbms The name of the DBAL class to use +* @param	array	$load_extensions Array of additional extensions that should be loaded +* @param	bool	$debug If the debug constants should be enabled by default or not +* @param	bool	$debug_test If the DEBUG_TEST constant should be added +*					NOTE: Only for use within the testing framework +* +* @return	string	The output to write to the file +*/ +function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = false, $debug_test = false) +{ +	$load_extensions = implode(',', $load_extensions); + +	$config_data = "<?php\n"; +	$config_data .= "// phpBB 3.1.x auto-generated configuration file\n// Do not change anything in this file!\n"; + +	$config_data_array = array( +		'dbms'			=> $dbms, +		'dbhost'		=> $data['dbhost'], +		'dbport'		=> $data['dbport'], +		'dbname'		=> $data['dbname'], +		'dbuser'		=> $data['dbuser'], +		'dbpasswd'		=> htmlspecialchars_decode($data['dbpasswd']), +		'table_prefix'	=> $data['table_prefix'], +		'acm_type'		=> 'phpbb_cache_driver_file', +		'load_extensions'	=> $load_extensions, +	); + +	foreach ($config_data_array as $key => $value) +	{ +		$config_data .= "\${$key} = '" . str_replace("'", "\\'", str_replace('\\', '\\\\', $value)) . "';\n"; +	} + +	$config_data .= "\n@define('PHPBB_INSTALLED', true);\n"; + +	if ($debug) +	{ +		$config_data .= "@define('DEBUG', true);\n"; +		$config_data .= "@define('DEBUG_EXTRA', true);\n"; +	} +	else +	{ +		$config_data .= "// @define('DEBUG', true);\n"; +		$config_data .= "// @define('DEBUG_EXTRA', true);\n"; +	} + +	if ($debug_test) +	{ +		$config_data .= "@define('DEBUG_TEST', true);\n"; +	} + +	return $config_data; +} diff --git a/phpBB/includes/functions_jabber.php b/phpBB/includes/functions_jabber.php index 42862d12e9..3d8e403f4b 100644 --- a/phpBB/includes/functions_jabber.php +++ b/phpBB/includes/functions_jabber.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2007 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -69,7 +68,7 @@ class jabber  		}  		$this->password				= $password; -		$this->use_ssl				= ($use_ssl && $this->can_use_ssl()) ? true : false; +		$this->use_ssl				= ($use_ssl && self::can_use_ssl()) ? true : false;  		// Change port if we use SSL  		if ($this->port == 5222 && $this->use_ssl) @@ -84,7 +83,7 @@ class jabber  	/**  	* Able to use the SSL functionality?  	*/ -	function can_use_ssl() +	static public function can_use_ssl()  	{  		// Will not work with PHP >= 5.2.1 or < 5.2.3RC2 until timeout problem with ssl hasn't been fixed (http://bugs.php.net/41236)  		return ((version_compare(PHP_VERSION, '5.2.1', '<') || version_compare(PHP_VERSION, '5.2.3RC2', '>=')) && @extension_loaded('openssl')) ? true : false; @@ -93,7 +92,7 @@ class jabber  	/**  	* Able to use TLS?  	*/ -	function can_use_tls() +	static public function can_use_tls()  	{  		if (!@extension_loaded('openssl') || !function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('socket_set_blocking') || !function_exists('stream_get_wrappers'))  		{ @@ -443,7 +442,7 @@ class jabber  				}  				// Let's use TLS if SSL is not enabled and we can actually use it -				if (!$this->session['ssl'] && $this->can_use_tls() && $this->can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls'])) +				if (!$this->session['ssl'] && self::can_use_tls() && self::can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls']))  				{  					$this->add_to_log('Switching to TLS.');  					$this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n"); diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index f5d102b1da..cf03de08c4 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -163,6 +162,22 @@ class messenger  	}  	/** +	* Adds X-AntiAbuse headers +	* +	* @param array $config		Configuration array +	* @param user $user			A user object +	* +	* @return null +	*/ +	function anti_abuse_headers($config, $user) +	{ +		$this->headers('X-AntiAbuse: Board servername - ' . mail_encode($config['server_name'])); +		$this->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); +		$this->headers('X-AntiAbuse: Username - ' . mail_encode($user->data['username'])); +		$this->headers('X-AntiAbuse: User IP - ' . $user->ip); +	} + +	/**  	* Set the email priority  	*/  	function set_mail_priority($priority = MAIL_NORMAL_PRIORITY) @@ -175,7 +190,7 @@ class messenger  	*/  	function template($template_file, $template_lang = '', $template_path = '')  	{ -		global $config, $phpbb_root_path, $user; +		global $config, $phpbb_root_path, $phpEx, $user, $phpbb_extension_manager;  		if (!trim($template_file))  		{ @@ -186,15 +201,19 @@ class messenger  		{  			// fall back to board default language if the user's language is  			// missing $template_file.  If this does not exist either, -			// $tpl->set_custom_template will do a trigger_error +			// $tpl->set_filenames will do a trigger_error  			$template_lang = basename($config['default_lang']);  		}  		// tpl_msg now holds a template object we can use to parse the template file  		if (!isset($this->tpl_msg[$template_lang . $template_file]))  		{ -			$this->tpl_msg[$template_lang . $template_file] = new template(); -			$tpl = &$this->tpl_msg[$template_lang . $template_file]; +			$style_resource_locator = new phpbb_style_resource_locator(); +			$style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider()); +			$tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context()); +			$style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $tpl); + +			$this->tpl_msg[$template_lang . $template_file] = $tpl;  			$fallback_template_path = false; @@ -212,7 +231,7 @@ class messenger  				}  			} -			$tpl->set_custom_template($template_path, $template_lang . '_email', $fallback_template_path); +			$style->set_custom_style($template_lang . '_email', array($template_path, $fallback_template_path), '');  			$tpl->set_filenames(array(  				'body'		=> $template_file . '.txt', @@ -333,7 +352,7 @@ class messenger  	*/  	function error($type, $msg)  	{ -		global $user, $phpEx, $phpbb_root_path, $config; +		global $user, $phpEx, $phpbb_root_path, $config, $request;  		// Session doesn't exist, create it  		if (!isset($user->session_id) || $user->session_id === '') @@ -341,7 +360,7 @@ class messenger  			$user->session_begin();  		} -		$calling_page = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; +		$calling_page = htmlspecialchars_decode($request->server('PHP_SELF'));  		$message = '';  		switch ($type) @@ -552,7 +571,7 @@ class messenger  		if (!$use_queue)  		{  			include_once($phpbb_root_path . 'includes/functions_jabber.' . $phpEx); -			$this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], $config['jab_password'], $config['jab_use_ssl']); +			$this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], htmlspecialchars_decode($config['jab_password']), $config['jab_use_ssl']);  			if (!$this->jabber->connect())  			{ @@ -753,7 +772,7 @@ class queue  					}  					include_once($phpbb_root_path . 'includes/functions_jabber.' . $phpEx); -					$this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], $config['jab_password'], $config['jab_use_ssl']); +					$this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], htmlspecialchars_decode($config['jab_password']), $config['jab_use_ssl']);  					if (!$this->jabber->connect())  					{ @@ -975,9 +994,16 @@ function smtpmail($addresses, $subject, $message, &$err_msg, $headers = false)  	$smtp->add_backtrace('Connecting to ' . $config['smtp_host'] . ':' . $config['smtp_port']);  	// Ok we have error checked as much as we can to this point let's get on it already. -	ob_start(); +	if (!class_exists('phpbb_error_collector')) +	{ +		global $phpbb_root_path, $phpEx; +		include($phpbb_root_path . 'includes/error_collector.' . $phpEx); +	} +	$collector = new phpbb_error_collector; +	$collector->install();  	$smtp->socket = fsockopen($config['smtp_host'], $config['smtp_port'], $errno, $errstr, 20); -	$error_contents = ob_get_clean(); +	$collector->uninstall(); +	$error_contents = $collector->format_errors();  	if (!$smtp->socket)  	{ @@ -999,7 +1025,7 @@ function smtpmail($addresses, $subject, $message, &$err_msg, $headers = false)  	}  	// Let me in. This function handles the complete authentication process -	if ($err_msg = $smtp->log_into_server($config['smtp_host'], $config['smtp_username'], $config['smtp_password'], $config['smtp_auth_method'])) +	if ($err_msg = $smtp->log_into_server($config['smtp_host'], $config['smtp_username'], htmlspecialchars_decode($config['smtp_password']), $config['smtp_auth_method']))  	{  		$smtp->close_session($err_msg);  		return false; @@ -1112,6 +1138,7 @@ class smtp_class  {  	var $server_response = '';  	var $socket = 0; +	protected $socket_tls = false;  	var $responses = array();  	var $commands = array();  	var $numeric_response_code = 0; @@ -1262,30 +1289,29 @@ class smtp_class  			}  		} -		// Try EHLO first -		$this->server_send("EHLO {$local_host}"); -		if ($err_msg = $this->server_parse('250', __LINE__)) +		$hello_result = $this->hello($local_host); +		if (!is_null($hello_result))  		{ -			// a 503 response code means that we're already authenticated -			if ($this->numeric_response_code == 503) -			{ -				return false; -			} - -			// If EHLO fails, we try HELO -			$this->server_send("HELO {$local_host}"); -			if ($err_msg = $this->server_parse('250', __LINE__)) -			{ -				return ($this->numeric_response_code == 503) ? false : $err_msg; -			} +			return $hello_result;  		} -		foreach ($this->responses as $response) +		// SMTP STARTTLS (RFC 3207) +		if (!$this->socket_tls)  		{ -			$response = explode(' ', $response); -			$response_code = $response[0]; -			unset($response[0]); -			$this->commands[$response_code] = implode(' ', $response); +			$this->socket_tls = $this->starttls(); + +			if ($this->socket_tls) +			{ +				// Switched to TLS +				// RFC 3207: "The client MUST discard any knowledge obtained from the server, [...]" +				// So say hello again +				$hello_result = $this->hello($local_host); + +				if (!is_null($hello_result)) +				{ +					return $hello_result; +				} +			}  		}  		// If we are not authenticated yet, something might be wrong if no username and passwd passed @@ -1332,6 +1358,79 @@ class smtp_class  	}  	/** +	* SMTP EHLO/HELO +	* +	* @return mixed		Null if the authentication process is supposed to continue +	*					False if already authenticated +	*					Error message (string) otherwise +	*/ +	protected function hello($hostname) +	{ +		// Try EHLO first +		$this->server_send("EHLO $hostname"); +		if ($err_msg = $this->server_parse('250', __LINE__)) +		{ +			// a 503 response code means that we're already authenticated +			if ($this->numeric_response_code == 503) +			{ +				return false; +			} + +			// If EHLO fails, we try HELO +			$this->server_send("HELO $hostname"); +			if ($err_msg = $this->server_parse('250', __LINE__)) +			{ +				return ($this->numeric_response_code == 503) ? false : $err_msg; +			} +		} + +		foreach ($this->responses as $response) +		{ +			$response = explode(' ', $response); +			$response_code = $response[0]; +			unset($response[0]); +			$this->commands[$response_code] = implode(' ', $response); +		} +	} + +	/** +	* SMTP STARTTLS (RFC 3207) +	* +	* @return bool		Returns true if TLS was started +	*					Otherwise false +	*/ +	protected function starttls() +	{ +		if (!function_exists('stream_socket_enable_crypto')) +		{ +			return false; +		} + +		if (!isset($this->commands['STARTTLS'])) +		{ +			return false; +		} + +		$this->server_send('STARTTLS'); + +		if ($err_msg = $this->server_parse('220', __LINE__)) +		{ +			return false; +		} + +		$result = false; +		$stream_meta = stream_get_meta_data($this->socket); + +		if (socket_set_blocking($this->socket, 1)); +		{ +			$result = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); +			socket_set_blocking($this->socket, (int) $stream_meta['blocked']); +		} + +		return $result; +	} + +	/**  	* Pop before smtp authentication  	*/  	function pop_before_smtp($hostname, $username, $password) @@ -1608,18 +1707,27 @@ function mail_encode($str, $eol = "\r\n")  */  function phpbb_mail($to, $subject, $msg, $headers, $eol, &$err_msg)  { -	global $config; +	global $config, $phpbb_root_path, $phpEx;  	// We use the EOL character for the OS here because the PHP mail function does not correctly transform line endings. On Windows SMTP is used (SMTP is \r\n), on UNIX a command is used...  	// Reference: http://bugs.php.net/bug.php?id=15841  	$headers = implode($eol, $headers); -	ob_start(); +	if (!class_exists('phpbb_error_collector')) +	{ +		include($phpbb_root_path . 'includes/error_collector.' . $phpEx); +	} + +	$collector = new phpbb_error_collector; +	$collector->install(); +  	// On some PHP Versions mail() *may* fail if there are newlines within the subject.  	// Newlines are used as a delimiter for lines in mail_encode() according to RFC 2045 section 6.8.  	// Because PHP can't decide what is wanted we revert back to the non-RFC-compliant way of separating by one space (Use '' as parameter to mail_encode() results in SPACE used)  	$result = $config['email_function_name']($to, mail_encode($subject, ''), wordwrap(utf8_wordwrap($msg), 997, "\n", true), $headers); -	$err_msg = ob_get_clean(); + +	$collector->uninstall(); +	$err_msg = $collector->format_errors();  	return $result;  } diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index 09c54422b0..ad76be9f2f 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -129,7 +128,7 @@ class p_master  		foreach ($this->module_cache['modules'] as $key => $row)  		{  			// Not allowed to view module? -			if (!$this->module_auth($row['module_auth'])) +			if (!$this->module_auth_self($row['module_auth']))  			{  				unset($this->module_cache['modules'][$key]);  				continue; @@ -221,13 +220,15 @@ class p_master  			// We need to prefix the functions to not create a naming conflict  			// Function for building 'url_extra' -			$url_func = '_module_' . $row['module_basename'] . '_url'; +			$short_name = $this->get_short_name($row['module_basename']); + +			$url_func = '_module_' . $short_name . '_url';  			// Function for building the language name -			$lang_func = '_module_' . $row['module_basename'] . '_lang'; +			$lang_func = '_module_' . $short_name . '_lang';  			// Custom function for calling parameters on module init (for example assigning template variables) -			$custom_func = '_module_' . $row['module_basename']; +			$custom_func = '_module_' . $short_name;  			$names[$row['module_basename'] . '_' . $row['module_mode']][] = true; @@ -275,6 +276,11 @@ class p_master  	*/  	function loaded($module_basename, $module_mode = false)  	{ +		if (!$this->is_full_class($module_basename)) +		{ +			$module_basename = $this->p_class . '_' . $module_basename; +		} +  		if (empty($this->loaded_cache))  		{  			$this->loaded_cache = array(); @@ -309,9 +315,23 @@ class p_master  	}  	/** -	* Check module authorisation +	* Check module authorisation. +	* +	* This is a non-static version that uses $this->acl_forum_id +	* for the forum id.  	*/ -	function module_auth($module_auth, $forum_id = false) +	function module_auth_self($module_auth) +	{ +		return self::module_auth($module_auth, $this->acl_forum_id); +	} + +	/** +	* Check module authorisation. +	* +	* This is a static version, it must be given $forum_id. +	* See also module_auth_self. +	*/ +	static function module_auth($module_auth, $forum_id)  	{  		global $auth, $config;  		global $request; @@ -356,11 +376,9 @@ class p_master  		$module_auth = implode(' ', $tokens); -		// Make sure $id seperation is working fine +		// Make sure $id separation is working fine  		$module_auth = str_replace(' , ', ',', $module_auth); -		$forum_id = ($forum_id === false) ? $this->acl_forum_id : $forum_id; -  		$is_auth = false;  		eval('$is_auth = (int) (' . preg_replace(array('#acl_([a-z0-9_]+)(,\$id)?#', '#\$id#', '#aclf_([a-z0-9_]+)#', '#cfg_([a-z0-9_]+)#', '#request_([a-zA-Z0-9_]+)#'), array('(int) $auth->acl_get(\'\\1\'\\2)', '(int) $forum_id', '(int) $auth->acl_getf_global(\'\\1\')', '(int) $config[\'\\1\']', '$request->variable(\'\\1\', false)'), $module_auth) . ');'); @@ -381,6 +399,11 @@ class p_master  			$id = request_var('icat', '');  		} +		if ($id && !is_numeric($id) && !$this->is_full_class($id)) +		{ +			$id = $this->p_class . '_' . $id; +		} +  		$category = false;  		foreach ($this->module_ary as $row_id => $item_ary)  		{ @@ -389,9 +412,9 @@ class p_master  			// If this is a module and no mode selected, select first mode  			// If no category or module selected, go active for first module in first category  			if ( -				(($item_ary['name'] === $id || $item_ary['id'] === (int) $id) && (($item_ary['mode'] == $mode && !$item_ary['cat']) || ($icat && $item_ary['cat']))) || +				(($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && (($item_ary['mode'] == $mode && !$item_ary['cat']) || ($icat && $item_ary['cat']))) ||  				($item_ary['parent'] === $category && !$item_ary['cat'] && !$icat && $item_ary['display']) || -				(($item_ary['name'] === $id || $item_ary['id'] === (int) $id) && !$mode && !$item_ary['cat']) || +				(($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && !$mode && !$item_ary['cat']) ||  				(!$id && !$mode && !$item_ary['cat'] && $item_ary['display'])  				)  			{ @@ -427,6 +450,8 @@ class p_master  	* Loads currently active module  	*  	* This method loads a given module, passing it the relevant id and mode. +	* +	* @param string $mode mode, as passed through to the module  	*/  	function load_active($mode = false, $module_url = false, $execute_module = true)  	{ @@ -440,75 +465,74 @@ class p_master  			trigger_error('Module not accessible', E_USER_ERROR);  		} -		if (!class_exists("{$this->p_class}_$this->p_name")) +		// new modules use the full class names, old ones are always called <type>_<name>, e.g. acp_board +		if (!class_exists($this->p_name))  		{ -			if (!file_exists("$module_path/{$this->p_class}_$this->p_name.$phpEx")) +			if (!file_exists("$module_path/{$this->p_name}.$phpEx"))  			{ -				trigger_error("Cannot find module $module_path/{$this->p_class}_$this->p_name.$phpEx", E_USER_ERROR); +				trigger_error("Cannot find module $module_path/{$this->p_name}.$phpEx", E_USER_ERROR);  			} -			include("$module_path/{$this->p_class}_$this->p_name.$phpEx"); +			include("$module_path/{$this->p_name}.$phpEx"); -			if (!class_exists("{$this->p_class}_$this->p_name")) +			if (!class_exists($this->p_name))  			{ -				trigger_error("Module file $module_path/{$this->p_class}_$this->p_name.$phpEx does not contain correct class [{$this->p_class}_$this->p_name]", E_USER_ERROR); +				trigger_error("Module file $module_path/{$this->p_name}.$phpEx does not contain correct class [{$this->p_name}]", E_USER_ERROR);  			} +		} -			if (!empty($mode)) -			{ -				$this->p_mode = $mode; -			} +		if (!empty($mode)) +		{ +			$this->p_mode = $mode; +		} -			// Create a new instance of the desired module ... if it has a -			// constructor it will of course be executed -			$instance = "{$this->p_class}_$this->p_name"; +		// Create a new instance of the desired module ... +		$class_name = $this->p_name; -			$this->module = new $instance($this); +		$this->module = new $class_name($this); -			// We pre-define the action parameter we are using all over the place -			if (defined('IN_ADMIN')) +		// We pre-define the action parameter we are using all over the place +		if (defined('IN_ADMIN')) +		{ +			// Is first module automatically enabled a duplicate and the category not passed yet? +			if (!$icat && $this->module_ary[$this->active_module_row_id]['is_duplicate'])  			{ -				// Is first module automatically enabled a duplicate and the category not passed yet? -				if (!$icat && $this->module_ary[$this->active_module_row_id]['is_duplicate']) -				{ -					$icat = $this->module_ary[$this->active_module_row_id]['parent']; -				} +				$icat = $this->module_ary[$this->active_module_row_id]['parent']; +			} -				// Not being able to overwrite ;) -				$this->module->u_action = append_sid("{$phpbb_admin_path}index.$phpEx", "i={$this->p_name}") . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; +			// Not being able to overwrite ;) +			$this->module->u_action = append_sid("{$phpbb_admin_path}index.$phpEx", "i={$this->p_name}") . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; +		} +		else +		{ +			// If user specified the module url we will use it... +			if ($module_url !== false) +			{ +				$this->module->u_action = $module_url;  			}  			else  			{ -				// If user specified the module url we will use it... -				if ($module_url !== false) -				{ -					$this->module->u_action = $module_url; -				} -				else -				{ -					$this->module->u_action = $phpbb_root_path . (($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '') . $user->page['page_name']; -				} - -				$this->module->u_action = append_sid($this->module->u_action, "i={$this->p_name}") . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; +				$this->module->u_action = $phpbb_root_path . (($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '') . $user->page['page_name'];  			} -			// Add url_extra parameter to u_action url -			if (!empty($this->module_ary) && $this->active_module !== false && $this->module_ary[$this->active_module_row_id]['url_extra']) -			{ -				$this->module->u_action .= $this->module_ary[$this->active_module_row_id]['url_extra']; -			} +			$this->module->u_action = append_sid($this->module->u_action, "i={$this->p_name}") . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; +		} -			// Assign the module path for re-usage -			$this->module->module_path = $module_path . '/'; +		// Add url_extra parameter to u_action url +		if (!empty($this->module_ary) && $this->active_module !== false && $this->module_ary[$this->active_module_row_id]['url_extra']) +		{ +			$this->module->u_action .= $this->module_ary[$this->active_module_row_id]['url_extra']; +		} -			// Execute the main method for the new instance, we send the module id and mode as parameters -			// Users are able to call the main method after this function to be able to assign additional parameters manually -			if ($execute_module) -			{ -				$this->module->main($this->p_name, $this->p_mode); -			} +		// Assign the module path for re-usage +		$this->module->module_path = $module_path . '/'; -			return; +		// Execute the main method for the new instance, we send the module id and mode as parameters +		// Users are able to call the main method after this function to be able to assign additional parameters manually +		if ($execute_module) +		{ +			$short_name = preg_replace("#^{$this->p_class}_#", '', $this->p_name); +			$this->module->main($short_name, $this->p_mode);  		}  	} @@ -547,7 +571,7 @@ class p_master  		// If we find a name by this id and being enabled we have our active one...  		foreach ($this->module_ary as $row_id => $item_ary)  		{ -			if (($item_ary['name'] === $id || $item_ary['id'] === (int) $id) && $item_ary['display']) +			if (($item_ary['name'] === $id || $item_ary['id'] === (int) $id) && $item_ary['display'] || $item_ary['name'] === $this->p_class . '_' . $id)  			{  				if ($mode === false || $mode === $item_ary['mode'])  				{ @@ -791,9 +815,22 @@ class p_master  	/**  	* Load module as the current active one without the need for registering it +	* +	* @param string $class module class (acp/mcp/ucp) +	* @param string $name module name (class name of the module, or its basename +    *                     phpbb_ext_foo_acp_bar_module, ucp_zebra or zebra) +	* @param string $mode mode, as passed through to the module +	*  	*/  	function load($class, $name, $mode = false)  	{ +		// new modules use the full class names, old ones are always called <class>_<name>, e.g. acp_board +		// in the latter case this function may be called as load('acp', 'board') +		if (!class_exists($name) && substr($name, 0, strlen($class) + 1) !== $class . '_') +		{ +			$name = $class . '_' . $name; +		} +  		$this->p_class = $class;  		$this->p_name = $name; @@ -841,7 +878,7 @@ class p_master  	{  		foreach ($this->module_ary as $row_id => $item_ary)  		{ -			if (($item_ary['name'] === $id || $item_ary['id'] === (int) $id) && (!$mode || $item_ary['mode'] === $mode)) +			if (($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && (!$mode || $item_ary['mode'] === $mode))  			{  				$this->module_ary[$row_id]['display'] = (int) $display;  			} @@ -855,28 +892,49 @@ class p_master  	{  		global $user, $phpEx; -		if (file_exists($user->lang_path . $user->lang_name . '/mods')) -		{ -			$add_files = array(); +		global $phpbb_extension_manager; -			$dir = @opendir($user->lang_path . $user->lang_name . '/mods'); +		$finder = $phpbb_extension_manager->get_finder(); -			if ($dir) -			{ -				while (($entry = readdir($dir)) !== false) -				{ -					if (strpos($entry, 'info_' . strtolower($module_class) . '_') === 0 && substr(strrchr($entry, '.'), 1) == $phpEx) -					{ -						$add_files[] = 'mods/' . substr(basename($entry), 0, -(strlen($phpEx) + 1)); -					} -				} -				closedir($dir); -			} +		$lang_files = $finder +			->prefix('info_' . strtolower($module_class) . '_') +			->suffix(".$phpEx") +			->extension_directory('/language/' . $user->lang_name) +			->core_path('language/' . $user->lang_name . '/mods/') +			->find(); -			if (sizeof($add_files)) -			{ -				$user->add_lang($add_files); -			} +		foreach ($lang_files as $lang_file => $ext_name) +		{ +			$user->add_lang_ext($ext_name, $lang_file); +		} +	} + +	/** +	* Retrieve shortened module basename for legacy basenames (with xcp_ prefix) +	* +	* @param string $basename A module basename +	* @return string The basename if it starts with phpbb_ or the basename with +	*                the current p_class (e.g. acp_) stripped. +	*/ +	protected function get_short_name($basename) +	{ +		if (substr($basename, 0, 6) === 'phpbb_') +		{ +			return $basename;  		} + +		// strip xcp_ prefix from old classes +		return substr($basename, strlen($this->p_class) + 1); +	} + +	/** +	* Checks whether the given module basename is a correct class name +	* +	* @param string $basename A module basename +	* @return bool True if the basename starts with phpbb_ or (x)cp_, false otherwise +	*/ +	protected function is_full_class($basename) +	{ +		return (preg_match('/^(phpbb|ucp|mcp|acp)_/', $basename));  	}  } diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 0bb0ef8722..171cf988ce 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -21,7 +20,7 @@ if (!defined('IN_PHPBB'))  */  function generate_smilies($mode, $forum_id)  { -	global $auth, $db, $user, $config, $template; +	global $db, $user, $config, $template, $phpbb_dispatcher;  	global $phpEx, $phpbb_root_path;  	$start = request_var('start', 0); @@ -62,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; @@ -127,6 +123,18 @@ function generate_smilies($mode, $forum_id)  		}  	} +	/** +	* This event is called after the smilies are populated +	* +	* @event core.generate_smilies_after +	* @var	string	mode			Mode of the smilies: window|inline +	* @var	int		forum_id		The forum ID we are currently in +	* @var	bool	display_link	Shall we display the "more smilies" link? +	* @since 3.1-A1 +	*/ +	$vars = array('mode', 'forum_id', 'display_link'); +	extract($phpbb_dispatcher->trigger_event('core.generate_smilies_after', compact($vars))); +  	if ($mode == 'inline' && $display_link)  	{  		$template->assign_vars(array( @@ -288,13 +296,15 @@ function posting_gen_topic_icons($mode, $icon_id)  	if (sizeof($icons))  	{ +		$root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_root_path; +  		foreach ($icons as $id => $data)  		{  			if ($data['display'])  			{  				$template->assign_block_vars('topic_icon', array(  					'ICON_ID'		=> $id, -					'ICON_IMG'		=> $phpbb_root_path . $config['icons_path'] . '/' . $data['img'], +					'ICON_IMG'		=> $root_path . $config['icons_path'] . '/' . $data['img'],  					'ICON_WIDTH'	=> $data['width'],  					'ICON_HEIGHT'	=> $data['height'], @@ -497,7 +507,14 @@ function upload_attachment($form_name, $forum_id, $local = false, $local_storage  	{  		if ($free_space <= $file->get('filesize'))  		{ -			$filedata['error'][] = $user->lang['ATTACH_QUOTA_REACHED']; +			if ($auth->acl_get('a_')) +			{ +				$filedata['error'][] = $user->lang['ATTACH_DISK_FULL']; +			} +			else +			{ +				$filedata['error'][] = $user->lang['ATTACH_QUOTA_REACHED']; +			}  			$filedata['post_attach'] = false;  			$file->remove(); @@ -803,7 +820,7 @@ function posting_gen_inline_attachments(&$attachment_data)  */  function posting_gen_attachment_entry($attachment_data, &$filename_data, $show_attach_box = true)  { -	global $template, $config, $phpbb_root_path, $phpEx, $user, $auth; +	global $template, $config, $phpbb_root_path, $phpEx, $user;  	// Some default template variables  	$template->assign_vars(array( @@ -1005,7 +1022,7 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id  		$mode = 'post_review';  	} -	$sql = $db->sql_build_query('SELECT', array( +	$sql_ary = array(  		'SELECT'	=> 'u.username, u.user_id, u.user_colour, p.*, z.friend, z.foe',  		'FROM'		=> array( @@ -1016,14 +1033,15 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id  		'LEFT_JOIN'	=> array(  			array(  				'FROM'	=> array(ZEBRA_TABLE => 'z'), -				'ON'	=> 'z.user_id = ' . $user->data['user_id'] . ' AND z.zebra_id = p.poster_id' -			) +				'ON'	=> 'z.user_id = ' . $user->data['user_id'] . ' AND z.zebra_id = p.poster_id', +			),  		),  		'WHERE'		=> $db->sql_in_set('p.post_id', $post_list) . ' -			AND u.user_id = p.poster_id' -	)); +			AND u.user_id = p.poster_id', +	); +	$sql = $db->sql_build_query('SELECT', $sql_ary);  	$result = $db->sql_query($sql);  	$bbcode_bitfield = ''; @@ -1078,7 +1096,7 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id  			continue;  		} -		$row =& $rowset[$post_list[$i]]; +		$row = $rowset[$post_list[$i]];  		$poster_id		= $row['user_id'];  		$post_subject	= $row['post_subject']; @@ -1160,7 +1178,7 @@ function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id  /**  * User Notification  */ -function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id, $topic_id, $post_id) +function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id, $topic_id, $post_id, $author_name = '')  {  	global $db, $user, $config, $phpbb_root_path, $phpEx, $auth; @@ -1180,36 +1198,32 @@ function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id  	$topic_title = ($topic_notification) ? $topic_title : $subject;  	$topic_title = censor_text($topic_title); -	// Get banned User ID's -	$sql = 'SELECT ban_userid -		FROM ' . BANLIST_TABLE . ' -		WHERE ban_userid <> 0 -			AND ban_exclude <> 1'; -	$result = $db->sql_query($sql); - -	$sql_ignore_users = ANONYMOUS . ', ' . $user->data['user_id']; -	while ($row = $db->sql_fetchrow($result)) +	// Exclude guests, current user and banned users from notifications +	if (!function_exists('phpbb_get_banned_user_ids'))  	{ -		$sql_ignore_users .= ', ' . (int) $row['ban_userid']; +		include($phpbb_root_path . 'includes/functions_user.' . $phpEx);  	} -	$db->sql_freeresult($result); +	$sql_ignore_users = phpbb_get_banned_user_ids(); +	$sql_ignore_users[ANONYMOUS] = ANONYMOUS; +	$sql_ignore_users[$user->data['user_id']] = $user->data['user_id'];  	$notify_rows = array();  	// -- get forum_userids	|| topic_userids  	$sql = 'SELECT u.user_id, u.username, u.user_email, u.user_lang, u.user_notify_type, u.user_jabber  		FROM ' . (($topic_notification) ? TOPICS_WATCH_TABLE : FORUMS_WATCH_TABLE) . ' w, ' . USERS_TABLE . ' u -		WHERE w.' . (($topic_notification) ? 'topic_id' : 'forum_id') . ' = ' . (($topic_notification) ? $topic_id : $forum_id) . " -			AND w.user_id NOT IN ($sql_ignore_users) -			AND w.notify_status = " . NOTIFY_YES . ' +		WHERE w.' . (($topic_notification) ? 'topic_id' : 'forum_id') . ' = ' . (($topic_notification) ? $topic_id : $forum_id) . ' +			AND ' . $db->sql_in_set('w.user_id', $sql_ignore_users, true) . ' +			AND w.notify_status = ' . NOTIFY_YES . '  			AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')  			AND u.user_id = w.user_id';  	$result = $db->sql_query($sql);  	while ($row = $db->sql_fetchrow($result))  	{ -		$notify_rows[$row['user_id']] = array( -			'user_id'		=> $row['user_id'], +		$notify_user_id = (int) $row['user_id']; +		$notify_rows[$notify_user_id] = array( +			'user_id'		=> $notify_user_id,  			'username'		=> $row['username'],  			'user_email'	=> $row['user_email'],  			'user_jabber'	=> $row['user_jabber'], @@ -1219,30 +1233,29 @@ function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id  			'method'		=> $row['user_notify_type'],  			'allowed'		=> false  		); + +		// Add users who have been already notified to ignore list +		$sql_ignore_users[$notify_user_id] = $notify_user_id;  	}  	$db->sql_freeresult($result);  	// forum notification is sent to those not already receiving topic notifications  	if ($topic_notification)  	{ -		if (sizeof($notify_rows)) -		{ -			$sql_ignore_users .= ', ' . implode(', ', array_keys($notify_rows)); -		} -  		$sql = 'SELECT u.user_id, u.username, u.user_email, u.user_lang, u.user_notify_type, u.user_jabber  			FROM ' . FORUMS_WATCH_TABLE . ' fw, ' . USERS_TABLE . " u  			WHERE fw.forum_id = $forum_id -				AND fw.user_id NOT IN ($sql_ignore_users) -				AND fw.notify_status = " . NOTIFY_YES . ' +				AND " . $db->sql_in_set('fw.user_id', $sql_ignore_users, true) . ' +				AND fw.notify_status = ' . NOTIFY_YES . '  				AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')  				AND u.user_id = fw.user_id';  		$result = $db->sql_query($sql);  		while ($row = $db->sql_fetchrow($result))  		{ -			$notify_rows[$row['user_id']] = array( -				'user_id'		=> $row['user_id'], +			$notify_user_id = (int) $row['user_id']; +			$notify_rows[$notify_user_id] = array( +				'user_id'		=> $notify_user_id,  				'username'		=> $row['username'],  				'user_email'	=> $row['user_email'],  				'user_jabber'	=> $row['user_jabber'], @@ -1273,7 +1286,6 @@ function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id  		}  	} -  	// Now, we have to do a little step before really sending, we need to distinguish our users a little bit. ;)  	$msg_users = $delete_ids = $update_notification = array();  	foreach ($notify_rows as $user_id => $row) @@ -1286,6 +1298,20 @@ function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id  		{  			$msg_users[] = $row;  			$update_notification[$row['notify_type']][] = $row['user_id']; + +			/* +			* We also update the forums watch table for this user when we are +			* sending out a topic notification to prevent sending out another +			* notification in case this user is also subscribed to the forum +			* this topic was posted in. +			* Since an UPDATE query is used, this has no effect on users only +			* subscribed to the topic (i.e. no row is created) and should not +			* be a performance issue. +			*/ +			if ($row['notify_type'] === 'topic') +			{ +				$update_notification['forum'][] = $row['user_id']; +			}  		}  	}  	unset($notify_rows); @@ -1323,6 +1349,7 @@ function user_notification($mode, $subject, $topic_title, $forum_name, $forum_id  					'USERNAME'		=> htmlspecialchars_decode($addr['name']),  					'TOPIC_TITLE'	=> htmlspecialchars_decode($topic_title),  					'FORUM_NAME'	=> htmlspecialchars_decode($forum_name), +					'AUTHOR_NAME'	=> htmlspecialchars_decode($author_name),  					'U_FORUM'				=> generate_board_url() . "/viewforum.$phpEx?f=$forum_id",  					'U_TOPIC'				=> generate_board_url() . "/viewtopic.$phpEx?f=$forum_id&t=$topic_id", @@ -1642,8 +1669,8 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u  	// First of all make sure the subject and topic title are having the correct length.  	// To achieve this without cutting off between special chars we convert to an array and then count the elements. -	$subject = truncate_string($subject); -	$data['topic_title'] = truncate_string($data['topic_title']); +	$subject = truncate_string($subject, 120); +	$data['topic_title'] = truncate_string($data['topic_title'], 120);  	// Collect some basic information about which tables and which rows to update/insert  	$sql_data = $topic_row = array(); @@ -1855,9 +1882,9 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u  		case 'edit_topic':  		case 'edit_first_post': -			if (isset($poll['poll_options']) && !empty($poll['poll_options'])) +			if (isset($poll['poll_options']))  			{ -				$poll_start = ($poll['poll_start']) ? $poll['poll_start'] : $current_time; +				$poll_start = ($poll['poll_start'] || empty($poll['poll_options'])) ? $poll['poll_start'] : $current_time;  				$poll_length = $poll['poll_length'] * 86400;  				if ($poll_length < 0)  				{ @@ -2005,11 +2032,11 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u  	}  	// Update Poll Tables -	if (isset($poll['poll_options']) && !empty($poll['poll_options'])) +	if (isset($poll['poll_options']))  	{  		$cur_poll_options = array(); -		if ($poll['poll_start'] && $mode == 'edit') +		if ($mode == 'edit')  		{  			$sql = 'SELECT *  				FROM ' . POLL_OPTIONS_TABLE . ' @@ -2350,20 +2377,15 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u  	if ($update_search_index && $data['enable_indexing'])  	{  		// Select the search method and do some additional checks to ensure it can actually be utilised -		$search_type = basename($config['search_type']); - -		if (!file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx)) -		{ -			trigger_error('NO_SUCH_SEARCH_MODULE'); -		} +		$search_type = $config['search_type'];  		if (!class_exists($search_type))  		{ -			include("{$phpbb_root_path}includes/search/$search_type.$phpEx"); +			trigger_error('NO_SUCH_SEARCH_MODULE');  		}  		$error = false; -		$search = new $search_type($error); +		$search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user);  		if ($error)  		{ @@ -2433,7 +2455,8 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u  	// Send Notifications  	if (($mode == 'reply' || $mode == 'quote' || $mode == 'post') && $post_approval)  	{ -		user_notification($mode, $subject, $data['topic_title'], $data['forum_name'], $data['forum_id'], $data['topic_id'], $data['post_id']); +		$username = ($username) ? $username : $user->data['username']; +		user_notification($mode, $subject, $data['topic_title'], $data['forum_name'], $data['forum_id'], $data['topic_id'], $data['post_id'], $username);  	}  	$params = $add_anchor = ''; @@ -2458,3 +2481,105 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u  	return $url;  } + +/** +* Handle topic bumping +* @param int $forum_id The ID of the forum the topic is being bumped belongs to +* @param int $topic_id The ID of the topic is being bumping +* @param array $post_data Passes some topic parameters: +*				- 'topic_title' +*				- 'topic_last_post_id' +*				- 'topic_last_poster_id' +*				- 'topic_last_post_subject' +*				- 'topic_last_poster_name' +*				- 'topic_last_poster_colour' +* @param int $bump_time The time at which topic was bumped, usually it is a current time as obtained via time(). +* @return string An URL to the bumped topic, example: ./viewtopic.php?forum_id=1&topic_id=2&p=3#p3 +*/ +function phpbb_bump_topic($forum_id, $topic_id, $post_data, $bump_time = false) +{ +	global $config, $db, $user, $phpEx, $phpbb_root_path; + +	if ($bump_time === false) +	{ +		$bump_time = time(); +	} + +	// Begin bumping +	$db->sql_transaction('begin'); + +	// Update the topic's last post post_time +	$sql = 'UPDATE ' . POSTS_TABLE . " +		SET post_time = $bump_time +		WHERE post_id = {$post_data['topic_last_post_id']} +			AND topic_id = $topic_id"; +	$db->sql_query($sql); + +	// Sync the topic's last post time, the rest of the topic's last post data isn't changed +	$sql = 'UPDATE ' . TOPICS_TABLE . " +		SET topic_last_post_time = $bump_time, +			topic_bumped = 1, +			topic_bumper = " . $user->data['user_id'] . " +		WHERE topic_id = $topic_id"; +	$db->sql_query($sql); + +	// Update the forum's last post info +	$sql = 'UPDATE ' . FORUMS_TABLE . " +		SET forum_last_post_id = " . $post_data['topic_last_post_id'] . ", +			forum_last_poster_id = " . $post_data['topic_last_poster_id'] . ", +			forum_last_post_subject = '" . $db->sql_escape($post_data['topic_last_post_subject']) . "', +			forum_last_post_time = $bump_time, +			forum_last_poster_name = '" . $db->sql_escape($post_data['topic_last_poster_name']) . "', +			forum_last_poster_colour = '" . $db->sql_escape($post_data['topic_last_poster_colour']) . "' +		WHERE forum_id = $forum_id"; +	$db->sql_query($sql); + +	// Update bumper's time of the last posting to prevent flood +	$sql = 'UPDATE ' . USERS_TABLE . " +		SET user_lastpost_time = $bump_time +		WHERE user_id = " . $user->data['user_id']; +	$db->sql_query($sql); + +	$db->sql_transaction('commit'); + +	// Mark this topic as posted to +	markread('post', $forum_id, $topic_id, $bump_time); + +	// Mark this topic as read +	markread('topic', $forum_id, $topic_id, $bump_time); + +	// Update forum tracking info +	if ($config['load_db_lastread'] && $user->data['is_registered']) +	{ +		$sql = 'SELECT mark_time +			FROM ' . FORUMS_TRACK_TABLE . ' +			WHERE user_id = ' . $user->data['user_id'] . ' +				AND forum_id = ' . $forum_id; +		$result = $db->sql_query($sql); +		$f_mark_time = (int) $db->sql_fetchfield('mark_time'); +		$db->sql_freeresult($result); +	} +	else if ($config['load_anon_lastread'] || $user->data['is_registered']) +	{ +		$f_mark_time = false; +	} + +	if (($config['load_db_lastread'] && $user->data['is_registered']) || $config['load_anon_lastread'] || $user->data['is_registered']) +	{ +		// Update forum info +		$sql = 'SELECT forum_last_post_time +			FROM ' . FORUMS_TABLE . ' +			WHERE forum_id = ' . $forum_id; +		$result = $db->sql_query($sql); +		$forum_last_post_time = (int) $db->sql_fetchfield('forum_last_post_time'); +		$db->sql_freeresult($result); + +		update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time, false); +	} + +	add_log('mod', $forum_id, $topic_id, 'LOG_BUMP_TOPIC', $post_data['topic_title']); + +	$url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&p={$post_data['topic_last_post_id']}") . "#p{$post_data['topic_last_post_id']}"; + +	return $url; +} diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 9787bdfbc4..ba939d490e 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -19,7 +18,8 @@ if (!defined('IN_PHPBB'))  	Ability to simply add own rules by doing three things:  		1) Add an appropriate constant  		2) Add a new check array to the global_privmsgs_rules variable and the condition array (if one is required) -		3) Add a new language variable to ucp.php +		3) Implement the rule logic in the check_rule() function +		4) Add a new language variable to ucp.php  		The user is then able to select the new rule. It will be checked against and handled as specified.  		To add new actions (yes, checks can be added here too) to the rule management, the core code has to be modified. @@ -57,42 +57,42 @@ define('CHECK_TO', 5);  */  $global_privmsgs_rules = array(  	CHECK_SUBJECT	=> array( -		RULE_IS_LIKE		=> array('check0' => 'message_subject', 'function' => 'preg_match("/" . preg_quote({STRING}, "/") . "/i", {CHECK0})'), -		RULE_IS_NOT_LIKE	=> array('check0' => 'message_subject', 'function' => '!(preg_match("/" . preg_quote({STRING}, "/") . "/i", {CHECK0}))'), -		RULE_IS				=> array('check0' => 'message_subject', 'function' => '{CHECK0} == {STRING}'), -		RULE_IS_NOT			=> array('check0' => 'message_subject', 'function' => '{CHECK0} != {STRING}'), -		RULE_BEGINS_WITH	=> array('check0' => 'message_subject', 'function' => 'preg_match("/^" . preg_quote({STRING}, "/") . "/i", {CHECK0})'), -		RULE_ENDS_WITH		=> array('check0' => 'message_subject', 'function' => 'preg_match("/" . preg_quote({STRING}, "/") . "$/i", {CHECK0})'), +		RULE_IS_LIKE		=> array('check0' => 'message_subject'), +		RULE_IS_NOT_LIKE	=> array('check0' => 'message_subject'), +		RULE_IS				=> array('check0' => 'message_subject'), +		RULE_IS_NOT			=> array('check0' => 'message_subject'), +		RULE_BEGINS_WITH	=> array('check0' => 'message_subject'), +		RULE_ENDS_WITH		=> array('check0' => 'message_subject'),  	),  	CHECK_SENDER	=> array( -		RULE_IS_LIKE		=> array('check0' => 'username', 'function' => 'preg_match("/" . preg_quote({STRING}, "/") . "/i", {CHECK0})'), -		RULE_IS_NOT_LIKE	=> array('check0' => 'username', 'function' => '!(preg_match("/" . preg_quote({STRING}, "/") . "/i", {CHECK0}))'), -		RULE_IS				=> array('check0' => 'username', 'function' => '{CHECK0} == {STRING}'), -		RULE_IS_NOT			=> array('check0' => 'username', 'function' => '{CHECK0} != {STRING}'), -		RULE_BEGINS_WITH	=> array('check0' => 'username', 'function' => 'preg_match("/^" . preg_quote({STRING}, "/") . "/i", {CHECK0})'), -		RULE_ENDS_WITH		=> array('check0' => 'username', 'function' => 'preg_match("/" . preg_quote({STRING}, "/") . "$/i", {CHECK0})'), -		RULE_IS_FRIEND		=> array('check0' => 'friend', 'function' => '{CHECK0} == 1'), -		RULE_IS_FOE			=> array('check0' => 'foe', 'function' => '{CHECK0} == 1'), -		RULE_IS_USER		=> array('check0' => 'author_id', 'function' => '{CHECK0} == {USER_ID}'), -		RULE_IS_GROUP		=> array('check0' => 'author_in_group', 'function' => 'in_array({GROUP_ID}, {CHECK0})'), +		RULE_IS_LIKE		=> array('check0' => 'username'), +		RULE_IS_NOT_LIKE	=> array('check0' => 'username'), +		RULE_IS				=> array('check0' => 'username'), +		RULE_IS_NOT			=> array('check0' => 'username'), +		RULE_BEGINS_WITH	=> array('check0' => 'username'), +		RULE_ENDS_WITH		=> array('check0' => 'username'), +		RULE_IS_FRIEND		=> array('check0' => 'friend'), +		RULE_IS_FOE			=> array('check0' => 'foe'), +		RULE_IS_USER		=> array('check0' => 'author_id'), +		RULE_IS_GROUP		=> array('check0' => 'author_in_group'),  	),  	CHECK_MESSAGE	=> array( -		RULE_IS_LIKE		=> array('check0' => 'message_text', 'function' => 'preg_match("/" . preg_quote({STRING}, "/") . "/i", {CHECK0})'), -		RULE_IS_NOT_LIKE	=> array('check0' => 'message_text', 'function' => '!(preg_match("/" . preg_quote({STRING}, "/") . "/i", {CHECK0}))'), -		RULE_IS				=> array('check0' => 'message_text', 'function' => '{CHECK0} == {STRING}'), -		RULE_IS_NOT			=> array('check0' => 'message_text', 'function' => '{CHECK0} != {STRING}'), +		RULE_IS_LIKE		=> array('check0' => 'message_text'), +		RULE_IS_NOT_LIKE	=> array('check0' => 'message_text'), +		RULE_IS				=> array('check0' => 'message_text'), +		RULE_IS_NOT			=> array('check0' => 'message_text'),  	),  	CHECK_STATUS	=> array( -		RULE_ANSWERED		=> array('check0' => 'pm_replied', 'function' => '{CHECK0} == 1'), -		RULE_FORWARDED		=> array('check0' => 'pm_forwarded', 'function' => '{CHECK0} == 1'), +		RULE_ANSWERED		=> array('check0' => 'pm_replied'), +		RULE_FORWARDED		=> array('check0' => 'pm_forwarded'),  	),  	CHECK_TO		=> array( -		RULE_TO_GROUP		=> array('check0' => 'to', 'check1' => 'bcc', 'check2' => 'user_in_group', 'function' => 'in_array("g_" . {CHECK2}, {CHECK0}) || in_array("g_" . {CHECK2}, {CHECK1})'), -		RULE_TO_ME			=> array('check0' => 'to', 'check1' => 'bcc', 'function' => 'in_array("u_" . $user_id, {CHECK0}) || in_array("u_" . $user_id, {CHECK1})'), +		RULE_TO_GROUP		=> array('check0' => 'to', 'check1' => 'bcc', 'check2' => 'user_in_group'), +		RULE_TO_ME			=> array('check0' => 'to', 'check1' => 'bcc'),  	)  ); @@ -260,16 +260,60 @@ function check_rule(&$rules, &$rule_row, &$message_row, $user_id)  	$check_ary = $rules[$rule_row['rule_check']][$rule_row['rule_connection']]; -	// Replace Check Literals -	$evaluate = $check_ary['function']; -	$evaluate = preg_replace('/{(CHECK[0-9])}/', '$message_row[$check_ary[strtolower("\1")]]', $evaluate); +	$result = false; -	// Replace Rule Literals -	$evaluate = preg_replace('/{(STRING|USER_ID|GROUP_ID)}/', '$rule_row["rule_" . strtolower("\1")]', $evaluate); +	$check0 = $message_row[$check_ary['check0']]; + +	switch ($rule_row['rule_connection']) +	{ +		case RULE_IS_LIKE: +			$result = preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0); +		break; + +		case RULE_IS_NOT_LIKE: +			$result = !preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0); +		break; + +		case RULE_IS: +			$result = ($check0 == $rule_row['rule_string']); +		break; + +		case RULE_IS_NOT: +			$result = ($check0 != $rule_row['rule_string']); +		break; + +		case RULE_BEGINS_WITH: +			$result = preg_match("/^" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0); +		break; + +		case RULE_ENDS_WITH: +			$result = preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '$/i', $check0); +		break; + +		case RULE_IS_FRIEND: +		case RULE_IS_FOE: +		case RULE_ANSWERED: +		case RULE_FORWARDED: +			$result = ($check0 == 1); +		break; + +		case RULE_IS_USER: +			$result = ($check0 == $rule_row['rule_user_id']); +		break; + +		case RULE_IS_GROUP: +			$result = in_array($rule_row['rule_group_id'], $check0); +		break; + +		case RULE_TO_GROUP: +			$result = (in_array('g_' . $message_row[$check_ary['check2']], $check0) || in_array('g_' . $message_row[$check_ary['check2']], $message_row[$check_ary['check1']])); +		break; + +		case RULE_TO_ME: +			$result = (in_array('u_' . $user_id, $check0) || in_array('u_' . $user_id, $message_row[$check_ary['check1']])); +		break; +	} -	// Evil Statement -	$result = false; -	eval('$result = (' . $evaluate . ') ? true : false;');  	if (!$result)  	{ @@ -299,7 +343,7 @@ function check_rule(&$rules, &$rule_row, &$message_row, $user_id)  			$userdata = $db->sql_fetchrow($result);  			$db->sql_freeresult($result); -			$auth2 = new auth(); +			$auth2 = new phpbb_auth();  			$auth2->acl($userdata);  			if (!$auth2->acl_get('a_') && !$auth2->acl_get('m_') && !$auth2->acl_getf_global('m_')) @@ -1084,6 +1128,222 @@ function delete_pm($user_id, $msg_ids, $folder_id)  }  /** +* Delete all PM(s) for a given user and delete the ones without references +* +* @param	int		$user_id	ID of the user whose private messages we want to delete +* +* @return	boolean		False if there were no pms found, true otherwise. +*/ +function phpbb_delete_user_pms($user_id) +{ +	global $db, $user, $phpbb_root_path, $phpEx; + +	$user_id = (int) $user_id; + +	if (!$user_id) +	{ +		return false; +	} + +	return phpbb_delete_users_pms(array($user_id)); +} + +/** +* Delete all PM(s) for given users and delete the ones without references +* +* @param	array		$user_ids	IDs of the users whose private messages we want to delete +* +* @return	boolean		False if there were no pms found, true otherwise. +*/ +function phpbb_delete_users_pms($user_ids) +{ +	global $db, $user, $phpbb_root_path, $phpEx; + +	$user_id_sql = $db->sql_in_set('user_id', $user_ids); +	$author_id_sql = $db->sql_in_set('author_id', $user_ids); + +	// Get PM Information for later deleting +	// The two queries where split, so we can use our indexes +	$undelivered_msg = $delete_ids = array(); + +	// Part 1: get PMs the user received +	$sql = 'SELECT msg_id +		FROM ' . PRIVMSGS_TO_TABLE . ' +		WHERE ' . $user_id_sql; +	$result = $db->sql_query($sql); + +	while ($row = $db->sql_fetchrow($result)) +	{ +		$msg_id = (int) $row['msg_id']; +		$delete_ids[$msg_id] = $msg_id; +	} +	$db->sql_freeresult($result); + +	// Part 2: get PMs the users sent, but are yet to be received. +	// We cannot simply delete them. First we have to check +	// whether another user already received and read the message. +	$sql = 'SELECT msg_id +		FROM ' . PRIVMSGS_TO_TABLE . ' +		WHERE ' . $author_id_sql . ' +			AND folder_id = ' . PRIVMSGS_NO_BOX; +	$result = $db->sql_query($sql); + +	while ($row = $db->sql_fetchrow($result)) +	{ +		$msg_id = (int) $row['msg_id']; +		$undelivered_msg[$msg_id] = $msg_id; +	} +	$db->sql_freeresult($result); + +	if (empty($delete_ids) && empty($undelivered_msg)) +	{ +		return false; +	} + +	$db->sql_transaction('begin'); + +	if (!empty($undelivered_msg)) +	{ +		// A pm is delivered, if for any recipient the message was moved +		// from their NO_BOX to another folder. We do not delete such +		// messages, but only delete them for users, who have not yet +		// received them. +		$sql = 'SELECT msg_id +			FROM ' . PRIVMSGS_TO_TABLE . ' +			WHERE ' . $author_id_sql . ' +				AND folder_id <> ' . PRIVMSGS_NO_BOX . ' +				AND folder_id <> ' . PRIVMSGS_OUTBOX . ' +				AND folder_id <> ' . PRIVMSGS_SENTBOX; +		$result = $db->sql_query($sql); + +		$delivered_msg = array(); +		while ($row = $db->sql_fetchrow($result)) +		{ +			$msg_id = (int) $row['msg_id']; +			$delivered_msg[$msg_id] = $msg_id; +			unset($undelivered_msg[$msg_id]); +		} +		$db->sql_freeresult($result); + +		$undelivered_user = array(); + +		// Count the messages we delete, so we can correct the user pm data +		$sql = 'SELECT user_id, COUNT(msg_id) as num_undelivered_privmsgs +			FROM ' . PRIVMSGS_TO_TABLE . ' +			WHERE ' . $author_id_sql . ' +				AND folder_id = ' . PRIVMSGS_NO_BOX . ' +					AND ' . $db->sql_in_set('msg_id', array_merge($undelivered_msg, $delivered_msg)) . ' +			GROUP BY user_id'; +		$result = $db->sql_query($sql); + +		while ($row = $db->sql_fetchrow($result)) +		{ +			$num_pms = (int) $row['num_undelivered_privmsgs']; +			$undelivered_user[$num_pms][] = (int) $row['user_id']; + +			if (sizeof($undelivered_user[$num_pms]) > 50) +			{ +				// If there are too many users affected the query might get +				// too long, so we update the value for the first bunch here. +				$sql = 'UPDATE ' . USERS_TABLE . ' +					SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ', +						user_unread_privmsg = user_unread_privmsg - ' . $num_pms . ' +					WHERE ' . $db->sql_in_set('user_id', $undelivered_user[$num_pms]); +				$db->sql_query($sql); +				unset($undelivered_user[$num_pms]); +			} +		} +		$db->sql_freeresult($result); + +		foreach ($undelivered_user as $num_pms => $undelivered_user_set) +		{ +			$sql = 'UPDATE ' . USERS_TABLE . ' +				SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ', +					user_unread_privmsg = user_unread_privmsg - ' . $num_pms . ' +				WHERE ' . $db->sql_in_set('user_id', $undelivered_user_set); +			$db->sql_query($sql); +		} + +		if (!empty($delivered_msg)) +		{ +			$sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' +				WHERE folder_id = ' . PRIVMSGS_NO_BOX . ' +					AND ' . $db->sql_in_set('msg_id', $delivered_msg); +			$db->sql_query($sql); +		} + +		if (!empty($undelivered_msg)) +		{ +			$sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' +				WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg); +			$db->sql_query($sql); + +			$sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' +				WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg); +			$db->sql_query($sql); +		} +	} + +	// Reset the user's pm count to 0 +	$sql = 'UPDATE ' . USERS_TABLE . ' +		SET user_new_privmsg = 0, +			user_unread_privmsg = 0 +		WHERE ' . $user_id_sql; +	$db->sql_query($sql); + +	// Delete private message data of the user +	$sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' +		WHERE ' . $user_id_sql; +	$db->sql_query($sql); + +	if (!empty($delete_ids)) +	{ +		// Now we have to check which messages we can delete completely +		$sql = 'SELECT msg_id +			FROM ' . PRIVMSGS_TO_TABLE . ' +			WHERE ' . $db->sql_in_set('msg_id', $delete_ids); +		$result = $db->sql_query($sql); + +		while ($row = $db->sql_fetchrow($result)) +		{ +			unset($delete_ids[$row['msg_id']]); +		} +		$db->sql_freeresult($result); + +		if (!empty($delete_ids)) +		{ +			// Check if there are any attachments we need to remove +			if (!function_exists('delete_attachments')) +			{ +				include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +			} + +			delete_attachments('message', $delete_ids, false); + +			$sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' +				WHERE ' . $db->sql_in_set('msg_id', $delete_ids); +			$db->sql_query($sql); +		} +	} + +	// Set the remaining author id to anonymous +	// This way users are still able to read messages from users being removed +	$sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' +		SET author_id = ' . ANONYMOUS . ' +		WHERE ' . $author_id_sql; +	$db->sql_query($sql); + +	$sql = 'UPDATE ' . PRIVMSGS_TABLE . ' +		SET author_id = ' . ANONYMOUS . ' +		WHERE ' . $author_id_sql; +	$db->sql_query($sql); + +	$db->sql_transaction('commit'); + +	return true; +} + +/**  * Rebuild message header  */  function rebuild_header($check_ary) @@ -1285,7 +1545,7 @@ function get_folder_status($folder_id, $folder)  		'percent'		=> ($user->data['message_limit']) ? (($user->data['message_limit'] > 0) ? round(($folder['num_messages'] / $user->data['message_limit']) * 100) : 100) : 0,  	); -	$return['message']	= sprintf($user->lang['FOLDER_STATUS_MSG'], $return['percent'], $return['cur'], $return['max']); +	$return['message']	= $user->lang('FOLDER_STATUS_MSG', (int) $return['max'], $return['cur'], $return['percent']);  	return $return;  } @@ -1362,12 +1622,6 @@ function submit_pm($mode, $subject, &$data, $put_in_outbox = true)  			while ($row = $db->sql_fetchrow($result))  			{ -				// Additionally, do not include the sender if he is in the group he wants to send to. ;) -				if ($row['user_id'] === $user->data['user_id']) -				{ -					continue; -				} -  				$field = ($data['address_list']['g'][$row['group_id']] == 'to') ? 'to' : 'bcc';  				$recipients[$row['user_id']] = $field;  			} @@ -1607,7 +1861,7 @@ function submit_pm($mode, $subject, &$data, $put_in_outbox = true)  	// Send Notifications  	if ($mode != 'edit')  	{ -		pm_notification($mode, $data['from_username'], $recipients, $subject, $data['message']); +		pm_notification($mode, $data['from_username'], $recipients, $subject, $data['message'], $data['msg_id']);  	}  	return $data['msg_id']; @@ -1616,12 +1870,13 @@ function submit_pm($mode, $subject, &$data, $put_in_outbox = true)  /**  * PM Notification  */ -function pm_notification($mode, $author, $recipients, $subject, $message) +function pm_notification($mode, $author, $recipients, $subject, $message, $msg_id)  {  	global $db, $user, $config, $phpbb_root_path, $phpEx, $auth;  	$subject = censor_text($subject); +	// Exclude guests, current user and banned users from notifications  	unset($recipients[ANONYMOUS], $recipients[$user->data['user_id']]);  	if (!sizeof($recipients)) @@ -1629,18 +1884,12 @@ function pm_notification($mode, $author, $recipients, $subject, $message)  		return;  	} -	// Get banned User ID's -	$sql = 'SELECT ban_userid -		FROM ' . BANLIST_TABLE . ' -		WHERE ' . $db->sql_in_set('ban_userid', array_map('intval', array_keys($recipients))) . ' -			AND ban_exclude = 0'; -	$result = $db->sql_query($sql); - -	while ($row = $db->sql_fetchrow($result)) +	if (!function_exists('phpbb_get_banned_user_ids'))  	{ -		unset($recipients[$row['ban_userid']]); +		include($phpbb_root_path . 'includes/functions_user.' . $phpEx);  	} -	$db->sql_freeresult($result); +	$banned_users = phpbb_get_banned_user_ids(array_keys($recipients)); +	$recipients = array_diff(array_keys($recipients), $banned_users);  	if (!sizeof($recipients))  	{ @@ -1649,7 +1898,7 @@ function pm_notification($mode, $author, $recipients, $subject, $message)  	$sql = 'SELECT user_id, username, user_email, user_lang, user_notify_pm, user_notify_type, user_jabber  		FROM ' . USERS_TABLE . ' -		WHERE ' . $db->sql_in_set('user_id', array_map('intval', array_keys($recipients))); +		WHERE ' . $db->sql_in_set('user_id', $recipients);  	$result = $db->sql_query($sql);  	$msg_list_ary = array(); @@ -1688,8 +1937,9 @@ function pm_notification($mode, $author, $recipients, $subject, $message)  			'AUTHOR_NAME'	=> htmlspecialchars_decode($author),  			'USERNAME'		=> htmlspecialchars_decode($addr['name']), -			'U_INBOX'		=> generate_board_url() . "/ucp.$phpEx?i=pm&folder=inbox") -		); +			'U_INBOX'			=> generate_board_url() . "/ucp.$phpEx?i=pm&folder=inbox", +			'U_VIEW_MESSAGE'	=> generate_board_url() . "/ucp.$phpEx?i=pm&mode=view&p=$msg_id", +		));  		$messenger->send($addr['method']);  	} @@ -1851,7 +2101,7 @@ function message_history($msg_id, $user_id, $message_row, $folder, $in_post_mode  			'SUBJECT'			=> $subject,  			'SENT_DATE'			=> $user->format_date($row['message_time']),  			'MESSAGE'			=> $message, -			'FOLDER'			=> implode(', ', $row['folder']), +			'FOLDER'			=> implode($user->lang['COMMA_SEPARATOR'], $row['folder']),  			'DECODED_MESSAGE'	=> $decoded_message,  			'S_CURRENT_MSG'		=> ($row['msg_id'] == $msg_id), diff --git a/phpBB/includes/functions_profile_fields.php b/phpBB/includes/functions_profile_fields.php index 44deffa162..10af997bff 100644 --- a/phpBB/includes/functions_profile_fields.php +++ b/phpBB/includes/functions_profile_fields.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -122,7 +121,7 @@ class custom_profile  			case FIELD_BOOL:  				$field_value = (bool) $field_value; -			 +  				if (!$field_value && $field_data['field_required'])  				{  					return 'FIELD_REQUIRED'; @@ -134,7 +133,7 @@ class custom_profile  				{  					return false;  				} -				 +  				$field_value = (int) $field_value;  				if ($field_value < $field_data['field_minlen']) @@ -149,7 +148,18 @@ class custom_profile  			case FIELD_DROPDOWN:  				$field_value = (int) $field_value; -			 + +				// retrieve option lang data if necessary +				if (!isset($this->options_lang[$field_data['field_id']]) || !isset($this->options_lang[$field_data['field_id']][$field_data['lang_id']]) || !sizeof($this->options_lang[$file_data['field_id']][$field_data['lang_id']])) +				{ +					$this->get_option_lang($field_data['field_id'], $field_data['lang_id'], FIELD_DROPDOWN, false); +				} + +				if (!isset($this->options_lang[$field_data['field_id']][$field_data['lang_id']][$field_value])) +				{ +					return 'FIELD_INVALID_VALUE'; +				} +  				if ($field_value == $field_data['field_novalue'] && $field_data['field_required'])  				{  					return 'FIELD_REQUIRED'; @@ -302,33 +312,34 @@ class custom_profile  				switch ($cp_result)  				{  					case 'FIELD_INVALID_DATE': +					case 'FIELD_INVALID_VALUE':  					case 'FIELD_REQUIRED': -						$error = sprintf($user->lang[$cp_result], $row['lang_name']); +						$error = $user->lang($cp_result, $row['lang_name']);  					break;  					case 'FIELD_TOO_SHORT':  					case 'FIELD_TOO_SMALL': -						$error = sprintf($user->lang[$cp_result], $row['lang_name'], $row['field_minlen']); +						$error = $user->lang($cp_result, (int) $row['field_minlen'], $row['lang_name']);  					break;  					case 'FIELD_TOO_LONG':  					case 'FIELD_TOO_LARGE': -						$error = sprintf($user->lang[$cp_result], $row['lang_name'], $row['field_maxlen']); +						$error = $user->lang($cp_result, (int) $row['field_maxlen'], $row['lang_name']);  					break;  					case 'FIELD_INVALID_CHARS':  						switch ($row['field_validation'])  						{  							case '[0-9]+': -								$error = sprintf($user->lang[$cp_result . '_NUMBERS_ONLY'], $row['lang_name']); +								$error = $user->lang($cp_result . '_NUMBERS_ONLY', $row['lang_name']);  							break;  							case '[\w]+': -								$error = sprintf($user->lang[$cp_result . '_ALPHA_ONLY'], $row['lang_name']); +								$error = $user->lang($cp_result . '_ALPHA_ONLY', $row['lang_name']);  							break;  							case '[\w_\+\. \-\[\]]+': -								$error = sprintf($user->lang[$cp_result . '_SPACERS_ONLY'], $row['lang_name']); +								$error = $user->lang($cp_result . '_SPACERS_ONLY', $row['lang_name']);  							break;  						}  					break; @@ -444,6 +455,8 @@ class custom_profile  			$user_fields = array(); +			$user_ids = $user_id; +  			// Go through the fields in correct order  			foreach (array_keys($this->profile_cache) as $used_ident)  			{ @@ -452,6 +465,15 @@ class custom_profile  					$user_fields[$user_id][$used_ident]['value'] = $row['pf_' . $used_ident];  					$user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident];  				} + +				foreach ($user_ids as $user_id) +				{ +					if (!isset($user_fields[$user_id][$used_ident]) && $this->profile_cache[$used_ident]['field_show_novalue']) +					{ +						$user_fields[$user_id][$used_ident]['value'] = ''; +						$user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident]; +					} +				}  			}  			return $user_fields; @@ -509,7 +531,7 @@ class custom_profile  		switch ($this->profile_types[$field_type])  		{  			case 'int': -				if ($value === '') +				if ($value === '' && !$ident_ary['data']['field_show_novalue'])  				{  					return NULL;  				} @@ -518,7 +540,7 @@ class custom_profile  			case 'string':  			case 'text': -				if (!$value) +				if (!$value && !$ident_ary['data']['field_show_novalue'])  				{  					return NULL;  				} @@ -536,16 +558,19 @@ class custom_profile  				$month = (isset($date[1])) ? (int) $date[1] : 0;  				$year = (isset($date[2])) ? (int) $date[2] : 0; -				if (!$day && !$month && !$year) +				if (!$day && !$month && !$year && !$ident_ary['data']['field_show_novalue'])  				{  					return NULL;  				}  				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 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; @@ -559,7 +584,7 @@ class custom_profile  					$this->get_option_lang($field_id, $lang_id, FIELD_DROPDOWN, false);  				} -				if ($value == $ident_ary['data']['field_novalue']) +				if ($value == $ident_ary['data']['field_novalue'] && !$ident_ary['data']['field_show_novalue'])  				{  					return NULL;  				} @@ -569,7 +594,14 @@ class custom_profile  				// User not having a value assigned  				if (!isset($this->options_lang[$field_id][$lang_id][$value]))  				{ -					return NULL; +					if ($ident_ary['data']['field_show_novalue']) +					{ +						$value = $ident_ary['data']['field_novalue']; +					} +					else +					{ +						return NULL; +					}  				}  				return $this->options_lang[$field_id][$lang_id][$value]; @@ -583,6 +615,11 @@ class custom_profile  					$this->get_option_lang($field_id, $lang_id, FIELD_BOOL, false);  				} +				if (!$value && $ident_ary['data']['field_show_novalue']) +				{ +					$value = $ident_ary['data']['field_default_value']; +				} +  				if ($ident_ary['data']['field_length'] == 1)  				{  					return (isset($this->options_lang[$field_id][$lang_id][(int) $value])) ? $this->options_lang[$field_id][$lang_id][(int) $value] : NULL; @@ -614,10 +651,10 @@ class custom_profile  		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident'];  		$user_ident = $profile_row['field_ident']; -		// checkbox - only testing for isset +		// checkbox - set the value to "true" if it has been set to 1  		if ($profile_row['field_type'] == FIELD_BOOL && $profile_row['field_length'] == 2)  		{ -			$value = (isset($_REQUEST[$profile_row['field_ident']])) ? true : ((!isset($user->profile_fields[$user_ident]) || $preview) ? $default_value : $user->profile_fields[$user_ident]); +			$value = (isset($_REQUEST[$profile_row['field_ident']]) && request_var($profile_row['field_ident'], $default_value) == 1) ? true : ((!isset($user->profile_fields[$user_ident]) || $preview) ? $default_value : $user->profile_fields[$user_ident]);  		}  		else if ($profile_row['field_type'] == FIELD_INT)  		{ diff --git a/phpBB/includes/functions_template.php b/phpBB/includes/functions_template.php deleted file mode 100644 index 8c58c33b0d..0000000000 --- a/phpBB/includes/functions_template.php +++ /dev/null @@ -1,812 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @version $Id$ -* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc -* @license http://opensource.org/licenses/gpl-license.php GNU Public License -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ -	exit; -} - -/** -* Extension of template class - Functions needed for compiling templates only. -* -* psoTFX, phpBB Development Team - Completion of file caching, decompilation -* routines and implementation of conditionals/keywords and associated changes -* -* The interface was inspired by PHPLib templates,  and the template file (formats are -* quite similar) -* -* The keyword/conditional implementation is currently based on sections of code from -* the Smarty templating engine (c) 2001 ispi of Lincoln, Inc. which is released -* (on its own and in whole) under the LGPL. Section 3 of the LGPL states that any code -* derived from an LGPL application may be relicenced under the GPL, this applies -* to this source -* -* DEFINE directive inspired by a request by Cyberalien -* -* @package phpBB3 -*/ -class template_compile -{ -	var $template; - -	// Various storage arrays -	var $block_names = array(); -	var $block_else_level = array(); - -	/** -	* constuctor -	*/ -	function template_compile(&$template) -	{ -		$this->template = &$template; -	} - -	/** -	* Load template source from file -	* @access private -	*/ -	function _tpl_load_file($handle, $store_in_db = false) -	{ -		// Try and open template for read -		if (!file_exists($this->template->files[$handle])) -		{ -			trigger_error("template->_tpl_load_file(): File {$this->template->files[$handle]} does not exist or is empty", E_USER_ERROR); -		} - -		$this->template->compiled_code[$handle] = $this->compile(trim(@file_get_contents($this->template->files[$handle]))); - -		// Actually compile the code now. -		$this->compile_write($handle, $this->template->compiled_code[$handle]); - -		// Store in database if required... -		if ($store_in_db) -		{ -			global $db, $user; - -			$sql_ary = array( -				'template_id'			=> $this->template->files_template[$handle], -				'template_filename'		=> $this->template->filename[$handle], -				'template_included'		=> '', -				'template_mtime'		=> time(), -				'template_data'			=> trim(@file_get_contents($this->template->files[$handle])), -			); - -			$sql = 'INSERT INTO ' . STYLES_TEMPLATE_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); -			$db->sql_query($sql); -		} -	} - -	/** -	* Remove any PHP tags that do not belong, these regular expressions are derived from -	* the ones that exist in zend_language_scanner.l -	* @access private -	*/ -	function remove_php_tags(&$code) -	{ -		// This matches the information gathered from the internal PHP lexer -		$match = array( -			'#<([\?%])=?.*?\1>#s', -			'#<script\s+language\s*=\s*(["\']?)php\1\s*>.*?</script\s*>#s', -			'#<\?php(?:\r\n?|[ \n\t]).*?\?>#s' -		); - -		$code = preg_replace($match, '', $code); -	} - -	/** -	* The all seeing all doing compile method. Parts are inspired by or directly from Smarty -	* @access private -	*/ -	function compile($code, $no_echo = false, $echo_var = '') -	{ -		global $config; - -		if ($echo_var) -		{ -			global $$echo_var; -		} - -		// Remove any "loose" php ... we want to give admins the ability -		// to switch on/off PHP for a given template. Allowing unchecked -		// php is a no-no. There is a potential issue here in that non-php -		// content may be removed ... however designers should use entities -		// if they wish to display < and > -		$this->remove_php_tags($code); - -		// Pull out all block/statement level elements and separate plain text -		preg_match_all('#<!-- PHP -->(.*?)<!-- ENDPHP -->#s', $code, $matches); -		$php_blocks = $matches[1]; -		$code = preg_replace('#<!-- PHP -->.*?<!-- ENDPHP -->#s', '<!-- PHP -->', $code); - -		preg_match_all('#<!-- INCLUDE (\{\$?[A-Z0-9\-_]+\}|[a-zA-Z0-9\_\-\+\./]+) -->#', $code, $matches); -		$include_blocks = $matches[1]; -		$code = preg_replace('#<!-- INCLUDE (?:\{\$?[A-Z0-9\-_]+\}|[a-zA-Z0-9\_\-\+\./]+) -->#', '<!-- INCLUDE -->', $code); - -		preg_match_all('#<!-- INCLUDEPHP ([a-zA-Z0-9\_\-\+\./]+) -->#', $code, $matches); -		$includephp_blocks = $matches[1]; -		$code = preg_replace('#<!-- INCLUDEPHP [a-zA-Z0-9\_\-\+\./]+ -->#', '<!-- INCLUDEPHP -->', $code); - -		preg_match_all('#<!-- ([^<].*?) (.*?)? ?-->#', $code, $blocks, PREG_SET_ORDER); - -		$text_blocks = preg_split('#<!-- [^<].*? (?:.*?)? ?-->#', $code); - -		for ($i = 0, $j = sizeof($text_blocks); $i < $j; $i++) -		{ -			$this->compile_var_tags($text_blocks[$i]); -		} -		$compile_blocks = array(); - -		for ($curr_tb = 0, $tb_size = sizeof($blocks); $curr_tb < $tb_size; $curr_tb++) -		{ -			$block_val = &$blocks[$curr_tb]; - -			switch ($block_val[1]) -			{ -				case 'BEGIN': -					$this->block_else_level[] = false; -					$compile_blocks[] = '<?php ' . $this->compile_tag_block($block_val[2]) . ' ?>'; -				break; - -				case 'BEGINELSE': -					$this->block_else_level[sizeof($this->block_else_level) - 1] = true; -					$compile_blocks[] = '<?php }} else { ?>'; -				break; - -				case 'END': -					array_pop($this->block_names); -					$compile_blocks[] = '<?php ' . ((array_pop($this->block_else_level)) ? '}' : '}}') . ' ?>'; -				break; - -				case 'IF': -					$compile_blocks[] = '<?php ' . $this->compile_tag_if($block_val[2], false) . ' ?>'; -				break; - -				case 'ELSE': -					$compile_blocks[] = '<?php } else { ?>'; -				break; - -				case 'ELSEIF': -					$compile_blocks[] = '<?php ' . $this->compile_tag_if($block_val[2], true) . ' ?>'; -				break; - -				case 'ENDIF': -					$compile_blocks[] = '<?php } ?>'; -				break; - -				case 'DEFINE': -					$compile_blocks[] = '<?php ' . $this->compile_tag_define($block_val[2], true) . ' ?>'; -				break; - -				case 'UNDEFINE': -					$compile_blocks[] = '<?php ' . $this->compile_tag_define($block_val[2], false) . ' ?>'; -				break; - -				case 'INCLUDE': -					$temp = array_shift($include_blocks); - -					// Dynamic includes -					// Cheap match rather than a full blown regexp, we already know -					// the format of the input so just use string manipulation. -					if ($temp[0] == '{') -					{ -						$file = false; - -						if ($temp[1] == '$') -						{ -							$var = substr($temp, 2, -1); -							//$file = $this->template->_tpldata['DEFINE']['.'][$var]; -							$temp = "\$this->_tpldata['DEFINE']['.']['$var']"; -						} -						else -						{ -							$var = substr($temp, 1, -1); -							//$file = $this->template->_rootref[$var]; -							$temp = "\$this->_rootref['$var']"; -						} -					} -					else -					{ -						$file = $temp; -					} - -					$compile_blocks[] = '<?php ' . $this->compile_tag_include($temp) . ' ?>'; - -					// No point in checking variable includes -					if ($file) -					{ -						$this->template->_tpl_include($file, false); -					} -				break; - -				case 'INCLUDEPHP': -					$compile_blocks[] = ($config['tpl_allow_php']) ? '<?php ' . $this->compile_tag_include_php(array_shift($includephp_blocks)) . ' ?>' : ''; -				break; - -				case 'PHP': -					$compile_blocks[] = ($config['tpl_allow_php']) ? '<?php ' . array_shift($php_blocks) . ' ?>' : ''; -				break; - -				default: -					$this->compile_var_tags($block_val[0]); -					$trim_check = trim($block_val[0]); -					$compile_blocks[] = (!$no_echo) ? ((!empty($trim_check)) ? $block_val[0] : '') : ((!empty($trim_check)) ? $block_val[0] : ''); -				break; -			} -		} - -		$template_php = ''; -		for ($i = 0, $size = sizeof($text_blocks); $i < $size; $i++) -		{ -			$trim_check_text = trim($text_blocks[$i]); -			$template_php .= (!$no_echo) ? (($trim_check_text != '') ? $text_blocks[$i] : '') . ((isset($compile_blocks[$i])) ? $compile_blocks[$i] : '') : (($trim_check_text != '') ? $text_blocks[$i] : '') . ((isset($compile_blocks[$i])) ? $compile_blocks[$i] : ''); -		} - -		// Remove unused opening/closing tags -		$template_php = str_replace(' ?><?php ', ' ', $template_php); - -		// Now add a newline after each php closing tag which already has a newline -		// PHP itself strips a newline if a closing tag is used (this is documented behaviour) and it is mostly not intended by style authors to remove newlines -		$template_php = preg_replace('#\?\>([\r\n])#', '?>\1\1', $template_php); - -		// There will be a number of occasions where we switch into and out of -		// PHP mode instantaneously. Rather than "burden" the parser with this -		// we'll strip out such occurences, minimising such switching -		if ($no_echo) -		{ -			return "\$$echo_var .= '" . $template_php . "'"; -		} - -		return $template_php; -	} - -	/** -	* Compile variables -	* @access private -	*/ -	function compile_var_tags(&$text_blocks) -	{ -		// change template varrefs into PHP varrefs -		$varrefs = array(); - -		// This one will handle varrefs WITH namespaces -		preg_match_all('#\{((?:[a-z0-9\-_]+\.)+)(\$)?([A-Z0-9\-_]+)\}#', $text_blocks, $varrefs, PREG_SET_ORDER); - -		foreach ($varrefs as $var_val) -		{ -			$namespace = $var_val[1]; -			$varname = $var_val[3]; -			$new = $this->generate_block_varref($namespace, $varname, true, $var_val[2]); - -			$text_blocks = str_replace($var_val[0], $new, $text_blocks); -		} - -		// This will handle the remaining root-level varrefs -		// transform vars prefixed by L_ into their language variable pendant if nothing is set within the tpldata array -		if (strpos($text_blocks, '{L_') !== false) -		{ -			$text_blocks = preg_replace('#\{L_([A-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['L_\\1'])) ? \$this->_rootref['L_\\1'] : ((isset(\$user->lang['\\1'])) ? \$user->lang['\\1'] : '{ \\1 }')); ?>", $text_blocks); -		} - -		// Handle addslashed language variables prefixed with LA_ -		// If a template variable already exist, it will be used in favor of it... -		if (strpos($text_blocks, '{LA_') !== false) -		{ -			$text_blocks = preg_replace('#\{LA_([A-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['LA_\\1'])) ? \$this->_rootref['LA_\\1'] : ((isset(\$this->_rootref['L_\\1'])) ? addslashes(\$this->_rootref['L_\\1']) : ((isset(\$user->lang['\\1'])) ? addslashes(\$user->lang['\\1']) : '{ \\1 }'))); ?>", $text_blocks); -		} - -		// Handle remaining varrefs -		$text_blocks = preg_replace('#\{([A-Z0-9\-_]+)\}#', "<?php echo (isset(\$this->_rootref['\\1'])) ? \$this->_rootref['\\1'] : ''; ?>", $text_blocks); -		$text_blocks = preg_replace('#\{\$([A-Z0-9\-_]+)\}#', "<?php echo (isset(\$this->_tpldata['DEFINE']['.']['\\1'])) ? \$this->_tpldata['DEFINE']['.']['\\1'] : ''; ?>", $text_blocks); - -		return; -	} - -	/** -	* Compile blocks -	* @access private -	*/ -	function compile_tag_block($tag_args) -	{ -		$no_nesting = false; - -		// Is the designer wanting to call another loop in a loop? -		if (strpos($tag_args, '!') === 0) -		{ -			// Count the number of ! occurrences (not allowed in vars) -			$no_nesting = substr_count($tag_args, '!'); -			$tag_args = substr($tag_args, $no_nesting); -		} - -		// Allow for control of looping (indexes start from zero): -		// foo(2)    : Will start the loop on the 3rd entry -		// foo(-2)   : Will start the loop two entries from the end -		// foo(3,4)  : Will start the loop on the fourth entry and end it on the fifth -		// foo(3,-4) : Will start the loop on the fourth entry and end it four from last -		if (preg_match('#^([^()]*)\(([\-\d]+)(?:,([\-\d]+))?\)$#', $tag_args, $match)) -		{ -			$tag_args = $match[1]; - -			if ($match[2] < 0) -			{ -				$loop_start = '($_' . $tag_args . '_count ' . $match[2] . ' < 0 ? 0 : $_' . $tag_args . '_count ' . $match[2] . ')'; -			} -			else -			{ -				$loop_start = '($_' . $tag_args . '_count < ' . $match[2] . ' ? $_' . $tag_args . '_count : ' . $match[2] . ')'; -			} - -			if (strlen($match[3]) < 1 || $match[3] == -1) -			{ -				$loop_end = '$_' . $tag_args . '_count'; -			} -			else if ($match[3] >= 0) -			{ -				$loop_end = '(' . ($match[3] + 1) . ' > $_' . $tag_args . '_count ? $_' . $tag_args . '_count : ' . ($match[3] + 1) . ')'; -			} -			else //if ($match[3] < -1) -			{ -				$loop_end = '$_' . $tag_args . '_count' . ($match[3] + 1); -			} -		} -		else -		{ -			$loop_start = 0; -			$loop_end = '$_' . $tag_args . '_count'; -		} - -		$tag_template_php = ''; -		array_push($this->block_names, $tag_args); - -		if ($no_nesting !== false) -		{ -			// We need to implode $no_nesting times from the end... -			$block = array_slice($this->block_names, -$no_nesting); -		} -		else -		{ -			$block = $this->block_names; -		} - -		if (sizeof($block) < 2) -		{ -			// Block is not nested. -			$tag_template_php = '$_' . $tag_args . "_count = (isset(\$this->_tpldata['$tag_args'])) ? sizeof(\$this->_tpldata['$tag_args']) : 0;"; -			$varref = "\$this->_tpldata['$tag_args']"; -		} -		else -		{ -			// This block is nested. -			// Generate a namespace string for this block. -			$namespace = implode('.', $block); - -			// Get a reference to the data array for this block that depends on the -			// current indices of all parent blocks. -			$varref = $this->generate_block_data_ref($namespace, false); - -			// Create the for loop code to iterate over this block. -			$tag_template_php = '$_' . $tag_args . '_count = (isset(' . $varref . ')) ? sizeof(' . $varref . ') : 0;'; -		} - -		$tag_template_php .= 'if ($_' . $tag_args . '_count) {'; - -		/** -		* The following uses foreach for iteration instead of a for loop, foreach is faster but requires PHP to make a copy of the contents of the array which uses more memory -		* <code> -		*	if (!$offset) -		*	{ -		*		$tag_template_php .= 'foreach (' . $varref . ' as $_' . $tag_args . '_i => $_' . $tag_args . '_val){'; -		*	} -		* </code> -		*/ - -		$tag_template_php .= 'for ($_' . $tag_args . '_i = ' . $loop_start . '; $_' . $tag_args . '_i < ' . $loop_end . '; ++$_' . $tag_args . '_i){'; -		$tag_template_php .= '$_'. $tag_args . '_val = &' . $varref . '[$_'. $tag_args. '_i];'; - -		return $tag_template_php; -	} - -	/** -	* Compile IF tags - much of this is from Smarty with -	* some adaptions for our block level methods -	* @access private -	*/ -	function compile_tag_if($tag_args, $elseif) -	{ -		// Tokenize args for 'if' tag. -		preg_match_all('/(?: -			"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"         | -			\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'     | -			[(),]                                  | -			[^\s(),]+)/x', $tag_args, $match); - -		$tokens = $match[0]; -		$is_arg_stack = array(); - -		for ($i = 0, $size = sizeof($tokens); $i < $size; $i++) -		{ -			$token = &$tokens[$i]; - -			switch ($token) -			{ -				case '!==': -				case '===': -				case '<<': -				case '>>': -				case '|': -				case '^': -				case '&': -				case '~': -				case ')': -				case ',': -				case '+': -				case '-': -				case '*': -				case '/': -				case '@': -				break; - -				case '==': -				case 'eq': -					$token = '=='; -				break; - -				case '!=': -				case '<>': -				case 'ne': -				case 'neq': -					$token = '!='; -				break; - -				case '<': -				case 'lt': -					$token = '<'; -				break; - -				case '<=': -				case 'le': -				case 'lte': -					$token = '<='; -				break; - -				case '>': -				case 'gt': -					$token = '>'; -				break; - -				case '>=': -				case 'ge': -				case 'gte': -					$token = '>='; -				break; - -				case '&&': -				case 'and': -					$token = '&&'; -				break; - -				case '||': -				case 'or': -					$token = '||'; -				break; - -				case '!': -				case 'not': -					$token = '!'; -				break; - -				case '%': -				case 'mod': -					$token = '%'; -				break; - -				case '(': -					array_push($is_arg_stack, $i); -				break; - -				case 'is': -					$is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1; -					$is_arg	= implode('	', array_slice($tokens,	$is_arg_start, $i -	$is_arg_start)); - -					$new_tokens	= $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); - -					array_splice($tokens, $is_arg_start, sizeof($tokens), $new_tokens); - -					$i = $is_arg_start; - -				// no break - -				default: -					if (preg_match('#^((?:[a-z0-9\-_]+\.)+)?(\$)?(?=[A-Z])([A-Z0-9\-_]+)#s', $token, $varrefs)) -					{ -						$token = (!empty($varrefs[1])) ? $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[2]) . '[\'' . $varrefs[3] . '\']' : (($varrefs[2]) ? '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[3] . '\']' : '$this->_rootref[\'' . $varrefs[3] . '\']'); -					} -					else if (preg_match('#^\.((?:[a-z0-9\-_]+\.?)+)$#s', $token, $varrefs)) -					{ -						// Allow checking if loops are set with .loopname -						// It is also possible to check the loop count by doing <!-- IF .loopname > 1 --> for example -						$blocks = explode('.', $varrefs[1]); - -						// If the block is nested, we have a reference that we can grab. -						// If the block is not nested, we just go and grab the block from _tpldata -						if (sizeof($blocks) > 1) -						{ -							$block = array_pop($blocks); -							$namespace = implode('.', $blocks); -							$varref = $this->generate_block_data_ref($namespace, true); - -							// Add the block reference for the last child. -							$varref .= "['" . $block . "']"; -						} -						else -						{ -							$varref = '$this->_tpldata'; - -							// Add the block reference for the last child. -							$varref .= "['" . $blocks[0] . "']"; -						} -						$token = "sizeof($varref)"; -					} -					else if (!empty($token)) -					{ -						$token = '(' . $token . ')'; -					} - -				break; -			} -		} - -		// If there are no valid tokens left or only control/compare characters left, we do skip this statement -		if (!sizeof($tokens) || str_replace(array(' ', '=', '!', '<', '>', '&', '|', '%', '(', ')'), '', implode('', $tokens)) == '') -		{ -			$tokens = array('false'); -		} -		return (($elseif) ? '} else if (' : 'if (') . (implode(' ', $tokens) . ') { '); -	} - -	/** -	* Compile DEFINE tags -	* @access private -	*/ -	function compile_tag_define($tag_args, $op) -	{ -		preg_match('#^((?:[a-z0-9\-_]+\.)+)?\$(?=[A-Z])([A-Z0-9_\-]*)(?: = (\'?)([^\']*)(\'?))?$#', $tag_args, $match); - -		if (empty($match[2]) || (!isset($match[4]) && $op)) -		{ -			return ''; -		} - -		if (!$op) -		{ -			return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ');'; -		} - -		// Are we a string? -		if ($match[3] && $match[5]) -		{ -			$match[4] = str_replace(array('\\\'', '\\\\', '\''), array('\'', '\\', '\\\''), $match[4]); - -			// Compile reference, we allow template variables in defines... -			$match[4] = $this->compile($match[4]); - -			// Now replace the php code -			$match[4] = "'" . str_replace(array('<?php echo ', '; ?>'), array("' . ", " . '"), $match[4]) . "'"; -		} -		else -		{ -			preg_match('#true|false|\.#i', $match[4], $type); - -			switch (strtolower($type[0])) -			{ -				case 'true': -				case 'false': -					$match[4] = strtoupper($match[4]); -				break; - -				case '.': -					$match[4] = doubleval($match[4]); -				break; - -				default: -					$match[4] = intval($match[4]); -				break; -			} -		} - -		return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ' = ' . $match[4] . ';'; -	} - -	/** -	* Compile INCLUDE tag -	* @access private -	*/ -	function compile_tag_include($tag_args) -	{ -		// Process dynamic includes -		if ($tag_args[0] == '$') -		{ -			return "if (isset($tag_args)) { \$this->_tpl_include($tag_args); }"; -		} - -		return "\$this->_tpl_include('$tag_args');"; -	} - -	/** -	* Compile INCLUDE_PHP tag -	* @access private -	*/ -	function compile_tag_include_php($tag_args) -	{ -		return "\$this->_php_include('$tag_args');"; -	} - -	/** -	* parse expression -	* This is from Smarty -	* @access private -	*/ -	function _parse_is_expr($is_arg, $tokens) -	{ -		$expr_end = 0; -		$negate_expr = false; - -		if (($first_token = array_shift($tokens)) == 'not') -		{ -			$negate_expr = true; -			$expr_type = array_shift($tokens); -		} -		else -		{ -			$expr_type = $first_token; -		} - -		switch ($expr_type) -		{ -			case 'even': -				if (@$tokens[$expr_end] == 'by') -				{ -					$expr_end++; -					$expr_arg = $tokens[$expr_end++]; -					$expr = "!(($is_arg / $expr_arg) % $expr_arg)"; -				} -				else -				{ -					$expr = "!($is_arg & 1)"; -				} -			break; - -			case 'odd': -				if (@$tokens[$expr_end] == 'by') -				{ -					$expr_end++; -					$expr_arg = $tokens[$expr_end++]; -					$expr = "(($is_arg / $expr_arg) % $expr_arg)"; -				} -				else -				{ -					$expr = "($is_arg & 1)"; -				} -			break; - -			case 'div': -				if (@$tokens[$expr_end] == 'by') -				{ -					$expr_end++; -					$expr_arg = $tokens[$expr_end++]; -					$expr = "!($is_arg % $expr_arg)"; -				} -			break; -		} - -		if ($negate_expr) -		{ -			$expr = "!($expr)"; -		} - -		array_splice($tokens, 0, $expr_end, $expr); - -		return $tokens; -	} - -	/** -	* Generates a reference to the given variable inside the given (possibly nested) -	* block namespace. This is a string of the form: -	* ' . $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . ' -	* It's ready to be inserted into an "echo" line in one of the templates. -	* NOTE: expects a trailing "." on the namespace. -	* @access private -	*/ -	function generate_block_varref($namespace, $varname, $echo = true, $defop = false) -	{ -		// Strip the trailing period. -		$namespace = substr($namespace, 0, -1); - -		// Get a reference to the data block for this namespace. -		$varref = $this->generate_block_data_ref($namespace, true, $defop); -		// Prepend the necessary code to stick this in an echo line. - -		// Append the variable reference. -		$varref .= "['$varname']"; -		$varref = ($echo) ? "<?php echo $varref; ?>" : ((isset($varref)) ? $varref : ''); - -		return $varref; -	} - -	/** -	* Generates a reference to the array of data values for the given -	* (possibly nested) block namespace. This is a string of the form: -	* $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN'] -	* -	* If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above. -	* NOTE: does not expect a trailing "." on the blockname. -	* @access private -	*/ -	function generate_block_data_ref($blockname, $include_last_iterator, $defop = false) -	{ -		// Get an array of the blocks involved. -		$blocks = explode('.', $blockname); -		$blockcount = sizeof($blocks) - 1; - -		// DEFINE is not an element of any referenced variable, we must use _tpldata to access it -		if ($defop) -		{ -			$varref = '$this->_tpldata[\'DEFINE\']'; -			// Build up the string with everything but the last child. -			for ($i = 0; $i < $blockcount; $i++) -			{ -				$varref .= "['" . $blocks[$i] . "'][\$_" . $blocks[$i] . '_i]'; -			} -			// Add the block reference for the last child. -			$varref .= "['" . $blocks[$blockcount] . "']"; -			// Add the iterator for the last child if requried. -			if ($include_last_iterator) -			{ -				$varref .= '[$_' . $blocks[$blockcount] . '_i]'; -			} -			return $varref; -		} -		else if ($include_last_iterator) -		{ -			return '$_'. $blocks[$blockcount] . '_val'; -		} -		else -		{ -			return '$_'. $blocks[$blockcount - 1] . '_val[\''. $blocks[$blockcount]. '\']'; -		} -	} - -	/** -	* Write compiled file to cache directory -	* @access private -	*/ -	function compile_write($handle, $data) -	{ -		global $phpEx; - -		$filename = $this->template->cachepath . str_replace('/', '.', $this->template->filename[$handle]) . '.' . $phpEx; - -		$data = "<?php if (!defined('IN_PHPBB')) exit;" . ((strpos($data, '<?php') === 0) ? substr($data, 5) : ' ?>' . $data); - -		if ($fp = @fopen($filename, 'wb')) -		{ -			@flock($fp, LOCK_EX); -			@fwrite ($fp, $data); -			@flock($fp, LOCK_UN); -			@fclose($fp); - -			phpbb_chmod($filename, CHMOD_READ | CHMOD_WRITE); -		} - -		return; -	} -} diff --git a/phpBB/includes/functions_transfer.php b/phpBB/includes/functions_transfer.php index fca092a4de..07c9171c60 100644 --- a/phpBB/includes/functions_transfer.php +++ b/phpBB/includes/functions_transfer.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -808,23 +807,56 @@ class ftp_fsock extends transfer  	*/  	function _open_data_connection()  	{ -		$this->_send_command('PASV', '', false); - -		if (!$ip_port = $this->_check_command(true)) +		// Try to find out whether we have a IPv4 or IPv6 (control) connection +		if (function_exists('stream_socket_get_name'))  		{ -			return false; +			$socket_name = stream_socket_get_name($this->connection, true); +			$server_ip = substr($socket_name, 0, strrpos($socket_name, ':'));  		} -		// open the connection to start sending the file -		if (!preg_match('#[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+#', $ip_port, $temp)) +		if (!isset($server_ip) || preg_match(get_preg_expression('ipv4'), $server_ip))  		{ -			// bad ip and port -			return false; +			// Passive mode +			$this->_send_command('PASV', '', false); + +			if (!$ip_port = $this->_check_command(true)) +			{ +				return false; +			} + +			// open the connection to start sending the file +			if (!preg_match('#[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+#', $ip_port, $temp)) +			{ +				// bad ip and port +				return false; +			} + +			$temp = explode(',', $temp[0]); +			$server_ip = $temp[0] . '.' . $temp[1] . '.' . $temp[2] . '.' . $temp[3]; +			$server_port = $temp[4] * 256 + $temp[5]; +		} +		else +		{ +			// Extended Passive Mode - RFC2428 +			$this->_send_command('EPSV', '', false); + +			if (!$epsv_response = $this->_check_command(true)) +			{ +				return false; +			} + +			// Response looks like "229 Entering Extended Passive Mode (|||12345|)" +			// where 12345 is the tcp port for the data connection +			if (!preg_match('#\(\|\|\|([0-9]+)\|\)#', $epsv_response, $match)) +			{ +				return false; +			} +			$server_port = (int) $match[1]; + +			// fsockopen expects IPv6 address in square brackets +			$server_ip = "[$server_ip]";  		} -		$temp = explode(',', $temp[0]); -		$server_ip = $temp[0] . '.' . $temp[1] . '.' . $temp[2] . '.' . $temp[3]; -		$server_port = $temp[4] * 256 + $temp[5];  		$errno = 0;  		$errstr = ''; diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index f648c585f6..d4c6b42cf4 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -152,7 +151,7 @@ class filespec  	*/  	function is_image()  	{ -		return (strpos($this->mimetype, 'image/') !== false) ? true : false; +		return (strpos($this->mimetype, 'image/') === 0);  	}  	/** @@ -427,7 +426,13 @@ class filespec  		if (!$this->upload->valid_dimensions($this))  		{ -			$this->error[] = sprintf($user->lang[$this->upload->error_prefix . 'WRONG_SIZE'], $this->upload->min_width, $this->upload->min_height, $this->upload->max_width, $this->upload->max_height, $this->width, $this->height); +			$this->error[] = $user->lang($this->upload->error_prefix . 'WRONG_SIZE', +				$user->lang('PIXELS', (int) $this->upload->min_width), +				$user->lang('PIXELS', (int) $this->upload->min_height), +				$user->lang('PIXELS', (int) $this->upload->max_width), +				$user->lang('PIXELS', (int) $this->upload->max_height), +				$user->lang('PIXELS', (int) $this->width), +				$user->lang('PIXELS', (int) $this->height));  			return false;  		} @@ -751,6 +756,31 @@ class fileupload  		$filename = $url['path'];  		$filesize = 0; +		$remote_max_filesize = $this->max_filesize; +		if (!$remote_max_filesize) +		{ +			$max_filesize = @ini_get('upload_max_filesize'); + +			if (!empty($max_filesize)) +			{ +				$unit = strtolower(substr($max_filesize, -1, 1)); +				$remote_max_filesize = (int) $max_filesize; + +				switch ($unit) +				{ +					case 'g': +						$remote_max_filesize *= 1024; +					// no break +					case 'm': +						$remote_max_filesize *= 1024; +					// no break +					case 'k': +						$remote_max_filesize *= 1024; +					// no break +				} +			} +		} +  		$errno = 0;  		$errstr = ''; @@ -779,9 +809,9 @@ class fileupload  				$block = @fread($fsock, 1024);  				$filesize += strlen($block); -				if ($this->max_filesize && $filesize > $this->max_filesize) +				if ($remote_max_filesize && $filesize > $remote_max_filesize)  				{ -					$max_filesize = get_formatted_filesize($this->max_filesize, false); +					$max_filesize = get_formatted_filesize($remote_max_filesize, false);  					$file = new fileerror(sprintf($user->lang[$this->error_prefix . 'WRONG_FILESIZE'], $max_filesize['value'], $max_filesize['unit']));  					return $file; @@ -807,9 +837,9 @@ class fileupload  					{  						$length = (int) str_replace('content-length: ', '', strtolower($line)); -						if ($length && $length > $this->max_filesize) +						if ($remote_max_filesize && $length && $length > $remote_max_filesize)  						{ -							$max_filesize = get_formatted_filesize($this->max_filesize, false); +							$max_filesize = get_formatted_filesize($remote_max_filesize, false);  							$file = new fileerror(sprintf($user->lang[$this->error_prefix . 'WRONG_FILESIZE'], $max_filesize['value'], $max_filesize['unit']));  							return $file; diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 3db0d9ba96..42b613c4f0 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -113,7 +112,7 @@ function update_last_username()  */  function user_update_name($old_name, $new_name)  { -	global $config, $db, $cache; +	global $config, $db, $cache, $phpbb_dispatcher;  	$update_ary = array(  		FORUMS_TABLE			=> array('forum_last_poster_name'), @@ -138,6 +137,17 @@ function user_update_name($old_name, $new_name)  		set_config('newest_username', $new_name, true);  	} +	/** +	* Update a username when it is changed +	* +	* @event core.update_username +	* @var	string	old_name	The old username that is replaced +	* @var	string	new_name	The new username +	* @since 3.1-A1 +	*/ +	$vars = array('old_name', 'new_name'); +	extract($phpbb_dispatcher->trigger_event('core.update_username', compact($vars))); +  	// Because some tables/caches use username-specific data we need to purge this here.  	$cache->destroy('sql', MODERATOR_CACHE_TABLE);  } @@ -152,6 +162,7 @@ function user_update_name($old_name, $new_name)  function user_add($user_row, $cp_data = false)  {  	global $db, $user, $auth, $config, $phpbb_root_path, $phpEx; +	global $phpbb_dispatcher;  	if (empty($user_row['username']) || !isset($user_row['group_id']) || !isset($user_row['user_email']) || !isset($user_row['user_type']))  	{ @@ -198,7 +209,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'		=> '', @@ -246,6 +256,16 @@ function user_add($user_row, $cp_data = false)  		}  	} +	/** +	* Use this event to modify the values to be inserted when a user is added +	* +	* @event core.user_add_modify_data +	* @var array	sql_ary		Array of data to be inserted when a user is added +	* @since 3.1-A1 +	*/ +	$vars = array('sql_ary'); +	extract($phpbb_dispatcher->trigger_event('core.user_add_modify_data', compact($vars))); +  	$sql = 'INSERT INTO ' . USERS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary);  	$db->sql_query($sql); @@ -334,7 +354,7 @@ function user_add($user_row, $cp_data = false)  */  function user_delete($mode, $user_ids, $retain_username = true)  { -	global $cache, $config, $db, $user, $auth; +	global $cache, $config, $db, $user, $auth, $phpbb_dispatcher;  	global $phpbb_root_path, $phpEx;  	$db->sql_transaction('begin'); @@ -362,6 +382,18 @@ function user_delete($mode, $user_ids, $retain_username = true)  		return false;  	} +	/** +	* Event before a user is deleted +	* +	* @event core.delete_user_before +	* @var	string	mode			Mode of deletion (retain/delete posts) +	* @var	int		user_id			ID of the deleted user +	* @var	mixed	post_username	Guest username that is being used or false +	* @since 3.1-A1 +	*/ +	$vars = array('mode', 'user_id', 'post_username'); +	extract($phpbb_dispatcher->trigger_event('core.delete_user_before', compact($vars))); +  	// Before we begin, we will remove the reports the user issued.  	$sql = 'SELECT r.post_id, p.topic_id  		FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p @@ -571,69 +603,29 @@ function user_delete($mode, $user_ids, $retain_username = true)  		WHERE ' . $db->sql_in_set('session_user_id', $user_ids);  	$db->sql_query($sql); -	// Remove any undelivered mails... -	$sql = 'SELECT msg_id, user_id -		FROM ' . PRIVMSGS_TO_TABLE . ' -		WHERE ' . $author_id_sql . ' -			AND folder_id = ' . PRIVMSGS_NO_BOX; -	$result = $db->sql_query($sql); - -	$undelivered_msg = $undelivered_user = array(); -	while ($row = $db->sql_fetchrow($result)) -	{ -		$undelivered_msg[] = $row['msg_id']; -		$undelivered_user[$row['user_id']][] = true; -	} -	$db->sql_freeresult($result); - -	if (sizeof($undelivered_msg)) -	{ -		$sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' -			WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg); -		$db->sql_query($sql); -	} - -	$sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' -		WHERE ' . $author_id_sql . ' -			AND folder_id = ' . PRIVMSGS_NO_BOX; -	$db->sql_query($sql); - -	// Delete all to-information -	$sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' -		WHERE ' . $user_id_sql; -	$db->sql_query($sql); - -	// Set the remaining author id to anonymous - this way users are still able to read messages from users being removed -	$sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' -		SET author_id = ' . ANONYMOUS . ' -		WHERE ' . $author_id_sql; -	$db->sql_query($sql); - -	$sql = 'UPDATE ' . PRIVMSGS_TABLE . ' -		SET author_id = ' . ANONYMOUS . ' -		WHERE ' . $author_id_sql; -	$db->sql_query($sql); - -	$user_ids_map = array_flip($user_ids); - -	foreach ($undelivered_user as $_user_id => $ary) +	// Clean the private messages tables from the user +	if (!function_exists('phpbb_delete_user_pms'))  	{ -		if (isset($user_ids_map[$_user_id])) -		{ -			continue; -		} - -		$sql = 'UPDATE ' . USERS_TABLE . ' -			SET user_new_privmsg = user_new_privmsg - ' . sizeof($ary) . ', -				user_unread_privmsg = user_unread_privmsg - ' . sizeof($ary) . ' -			WHERE user_id = ' . $_user_id; -		$db->sql_query($sql); +		include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx);  	} +	phpbb_delete_users_pms($user_ids);  	$db->sql_transaction('commit'); +	/** +	* Event after a user is deleted +	* +	* @event core.delete_user_after +	* @var	string	mode			Mode of deletion (retain/delete posts) +	* @var	int		user_id			ID of the deleted user +	* @var	mixed	post_username	Guest username that is being used or false +	* @since 3.1-A1 +	*/ +	$vars = array('mode', 'user_id', 'post_username'); +	extract($phpbb_dispatcher->trigger_event('core.delete_user_after', compact($vars))); +  	// Reset newest user info if appropriate -	if (isset($user_ids_map[$config['newest_user_id']])) +	if (in_array($config['newest_user_id'], $user_ids))  	{  		update_last_username();  	} @@ -767,8 +759,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  			{ @@ -1337,10 +1331,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); +				}  			}  		}  	} @@ -1486,6 +1491,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 @@ -1516,7 +1537,7 @@ function validate_username($username, $allowed_username = false)  	$mbstring = $pcre = false;  	// generic UTF-8 character types supported? -	if (pcre_utf8_support()) +	if (phpbb_pcre_utf8_support())  	{  		$pcre = true;  	} @@ -1653,7 +1674,7 @@ function validate_password($password)  	$pcre = $mbstring = false;  	// generic UTF-8 character types supported? -	if (pcre_utf8_support()) +	if (phpbb_pcre_utf8_support())  	{  		$upp = '\p{Lu}';  		$low = '\p{Ll}'; @@ -1988,6 +2009,27 @@ function validate_jabber($jid)  }  /** +* Verifies whether a style ID corresponds to an active style. +* +* @param int $style_id The style_id of a style which should be checked if activated or not. +* @return boolean +*/ +function phpbb_style_is_active($style_id) +{ +	global $db; + +	$sql = 'SELECT style_active +		FROM ' . STYLES_TABLE . ' +		WHERE style_id = '. (int) $style_id; +	$result = $db->sql_query($sql); + +	$style_is_active = (bool) $db->sql_fetchfield('style_active'); +	$db->sql_freeresult($result); + +	return $style_is_active; +} + +/**  * Remove avatar  */  function avatar_delete($mode, $row, $clean_db = false) @@ -2078,7 +2120,7 @@ function avatar_remote($data, &$error)  	{  		if ($width > $config['avatar_max_width'] || $height > $config['avatar_max_height'])  		{ -			$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $width, $height); +			$error[] = phpbb_avatar_error_wrong_size($width, $height);  			return false;  		}  	} @@ -2087,7 +2129,7 @@ function avatar_remote($data, &$error)  	{  		if ($width < $config['avatar_min_width'] || $height < $config['avatar_min_height'])  		{ -			$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $width, $height); +			$error[] = phpbb_avatar_error_wrong_size($width, $height);  			return false;  		}  	} @@ -2427,7 +2469,7 @@ function avatar_process_user(&$error, $custom_userdata = false, $can_upload = nu  		{  			if ($data['width'] > $config['avatar_max_width'] || $data['height'] > $config['avatar_max_height'])  			{ -				$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $data['width'], $data['height']); +				$error[] = phpbb_avatar_error_wrong_size($data['width'], $data['height']);  			}  		} @@ -2437,7 +2479,7 @@ function avatar_process_user(&$error, $custom_userdata = false, $can_upload = nu  			{  				if ($data['width'] < $config['avatar_min_width'] || $data['height'] < $config['avatar_min_height'])  				{ -					$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $data['width'], $data['height']); +					$error[] = phpbb_avatar_error_wrong_size($data['width'], $data['height']);  				}  			}  		} @@ -2483,6 +2525,41 @@ function avatar_process_user(&$error, $custom_userdata = false, $can_upload = nu  	return (sizeof($error)) ? false : true;  } +/** +* Returns a language string with the avatar size of the new avatar and the allowed maximum and minimum +* +* @param $width		int		The width of the new uploaded/selected avatar +* @param $height	int		The height of the new uploaded/selected avatar +* @return string +*/ +function phpbb_avatar_error_wrong_size($width, $height) +{ +	global $config, $user; + +	return $user->lang('AVATAR_WRONG_SIZE', +		$user->lang('PIXELS', (int) $config['avatar_min_width']), +		$user->lang('PIXELS', (int) $config['avatar_min_height']), +		$user->lang('PIXELS', (int) $config['avatar_max_width']), +		$user->lang('PIXELS', (int) $config['avatar_max_height']), +		$user->lang('PIXELS', (int) $width), +		$user->lang('PIXELS', (int) $height)); +} + +/** +* Returns an explanation string with maximum avatar settings +* +* @return string +*/ +function phpbb_avatar_explanation_string() +{ +	global $config, $user; + +	return $user->lang('AVATAR_EXPLAIN', +		$user->lang('PIXELS', (int) $config['avatar_max_width']), +		$user->lang('PIXELS', (int) $config['avatar_max_height']), +		round($config['avatar_filesize'] / 1024)); +} +  //  // Usergroup functions  // @@ -2765,7 +2842,7 @@ function avatar_remove_db($avatar_name)  */  function group_delete($group_id, $group_name = false)  { -	global $db, $phpbb_root_path, $phpEx; +	global $db, $phpbb_root_path, $phpEx, $phpbb_dispatcher;  	if (!$group_name)  	{ @@ -2824,6 +2901,17 @@ function group_delete($group_id, $group_name = false)  		WHERE group_id = $group_id";  	$db->sql_query($sql); +	/** +	* Event after a group is deleted +	* +	* @event core.delete_group_after +	* @var	int		group_id	ID of the deleted group +	* @var	string	group_name	Name of the deleted group +	* @since 3.1-A1 +	*/ +	$vars = array('group_id', 'group_name'); +	extract($phpbb_dispatcher->trigger_event('core.delete_group_after', compact($vars))); +  	// Re-cache moderators  	if (!function_exists('cache_moderators'))  	{ @@ -2946,7 +3034,7 @@ function group_user_add($group_id, $user_id_ary = false, $username_ary = false,  */  function group_user_del($group_id, $user_id_ary = false, $username_ary = false, $group_name = false)  { -	global $db, $auth, $config; +	global $db, $auth, $config, $phpbb_dispatcher;  	if ($config['coppa_enable'])  	{ @@ -3045,6 +3133,19 @@ function group_user_del($group_id, $user_id_ary = false, $username_ary = false,  	}  	unset($special_group_data); +	/** +	* Event before users are removed from a group +	* +	* @event core.group_delete_user_before +	* @var	int		group_id	ID of the group from which users are deleted +	* @var	string	group_name	Name of the group +	* @var	array	user_id_ary		IDs of the users which are removed +	* @var	array	username_ary	names of the users which are removed +	* @since 3.1-A1 +	*/ +	$vars = array('group_id', 'group_name', 'user_id_ary', 'username_ary'); +	extract($phpbb_dispatcher->trigger_event('core.group_delete_user_before', compact($vars))); +  	$sql = 'DELETE FROM ' . USER_GROUP_TABLE . "  		WHERE group_id = $group_id  			AND " . $db->sql_in_set('user_id', $user_id_ary); @@ -3362,7 +3463,7 @@ function group_validate_groupname($group_id, $group_name)  */  function group_set_user_default($group_id, $user_id_ary, $group_attributes = false, $update_listing = false)  { -	global $cache, $db; +	global $cache, $db, $phpbb_dispatcher;  	if (empty($user_id_ary))  	{ @@ -3458,6 +3559,20 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal  		}  	} +	/** +	* Event when the default group is set for an array of users +	* +	* @event core.user_set_default_group +	* @var	int		group_id			ID of the group +	* @var	array	user_id_ary			IDs of the users +	* @var	array	group_attributes	Group attributes which were changed +	* @var	array	update_listing		Update the list of moderators and foes +	* @var	array	sql_ary				User attributes which were changed +	* @since 3.1-A1 +	*/ +	$vars = array('group_id', 'user_id_ary', 'group_attributes', 'update_listing', 'sql_ary'); +	extract($phpbb_dispatcher->trigger_event('core.user_set_default_group', compact($vars))); +  	if ($update_listing)  	{  		group_update_listings($group_id); @@ -3696,3 +3811,36 @@ function remove_newly_registered($user_id, $user_data = false)  	return $user_data['group_id'];  } + +/** +* Gets user ids of currently banned registered users. +* +* @param array $user_ids Array of users' ids to check for banning, +*						leave empty to get complete list of banned ids +* @return array	Array of banned users' ids if any, empty array otherwise +*/ +function phpbb_get_banned_user_ids($user_ids = array()) +{ +	global $db; + +	$sql_user_ids = (!empty($user_ids)) ? $db->sql_in_set('ban_userid', $user_ids) : 'ban_userid <> 0'; + +	// Get banned User ID's +	// Ignore stale bans which were not wiped yet +	$banned_ids_list = array(); +	$sql = 'SELECT ban_userid +		FROM ' . BANLIST_TABLE . " +		WHERE $sql_user_ids +			AND ban_exclude <> 1 +			AND (ban_end > " . time() . ' +				OR ban_end = 0)'; +	$result = $db->sql_query($sql); +	while ($row = $db->sql_fetchrow($result)) +	{ +		$user_id = (int) $row['ban_userid']; +		$banned_ids_list[$user_id] = $user_id; +	} +	$db->sql_freeresult($result); + +	return $banned_ids_list; +} diff --git a/phpBB/includes/group_positions.php b/phpBB/includes/group_positions.php index fc44e3249d..74de3516cb 100644 --- a/phpBB/includes/group_positions.php +++ b/phpBB/includes/group_positions.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2011 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/hooks/index.php b/phpBB/includes/hooks/index.php index 27651f3440..11478aee1e 100644 --- a/phpBB/includes/hooks/index.php +++ b/phpBB/includes/hooks/index.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2007 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/json_response.php b/phpBB/includes/json_response.php new file mode 100644 index 0000000000..5dd904da09 --- /dev/null +++ b/phpBB/includes/json_response.php @@ -0,0 +1,41 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* JSON class +* @package phpBB3 +*/ +class phpbb_json_response +{ +	/** +	 * Send the data to the client and exit the script. +	 * +	 * @param array $data Any additional data to send. +	 * @param bool $exit Will exit the script if true. +	 */ +	public function send($data, $exit = true) +	{ +		header('Content-Type: application/json'); +		echo json_encode($data); + +		if ($exit) +		{ +			garbage_collection(); +			exit_handler(); +		} +	} +} diff --git a/phpBB/includes/lock/db.php b/phpBB/includes/lock/db.php index 20dbb63e0c..fa559d6887 100644 --- a/phpBB/includes/lock/db.php +++ b/phpBB/includes/lock/db.php @@ -3,7 +3,7 @@  *  * @package phpBB3  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_ban.php b/phpBB/includes/mcp/info/mcp_ban.php index cc5b9f50ee..a3a1e0ef9a 100644 --- a/phpBB/includes/mcp/info/mcp_ban.php +++ b/phpBB/includes/mcp/info/mcp_ban.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_logs.php b/phpBB/includes/mcp/info/mcp_logs.php index 59a4fcb21e..fc30a600c0 100644 --- a/phpBB/includes/mcp/info/mcp_logs.php +++ b/phpBB/includes/mcp/info/mcp_logs.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_main.php b/phpBB/includes/mcp/info/mcp_main.php index d8ee155f60..705715cbeb 100644 --- a/phpBB/includes/mcp/info/mcp_main.php +++ b/phpBB/includes/mcp/info/mcp_main.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_notes.php b/phpBB/includes/mcp/info/mcp_notes.php index 0b83e77daf..a77b461bbd 100644 --- a/phpBB/includes/mcp/info/mcp_notes.php +++ b/phpBB/includes/mcp/info/mcp_notes.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_pm_reports.php b/phpBB/includes/mcp/info/mcp_pm_reports.php index c556fb62e1..07dc564b19 100644 --- a/phpBB/includes/mcp/info/mcp_pm_reports.php +++ b/phpBB/includes/mcp/info/mcp_pm_reports.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_queue.php b/phpBB/includes/mcp/info/mcp_queue.php index 171b1d3f35..7ad79f9781 100644 --- a/phpBB/includes/mcp/info/mcp_queue.php +++ b/phpBB/includes/mcp/info/mcp_queue.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_reports.php b/phpBB/includes/mcp/info/mcp_reports.php index a620e65ba1..cb6962a1d5 100644 --- a/phpBB/includes/mcp/info/mcp_reports.php +++ b/phpBB/includes/mcp/info/mcp_reports.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/info/mcp_warn.php b/phpBB/includes/mcp/info/mcp_warn.php index a5a8b7a489..d5ac1eedbf 100644 --- a/phpBB/includes/mcp/info/mcp_warn.php +++ b/phpBB/includes/mcp/info/mcp_warn.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/mcp_ban.php b/phpBB/includes/mcp/mcp_ban.php index 40f02919f9..d3bc336293 100644 --- a/phpBB/includes/mcp/mcp_ban.php +++ b/phpBB/includes/mcp/mcp_ban.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index f170dd68eb..151677bcfe 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -23,7 +22,7 @@ function mcp_forum_view($id, $mode, $action, $forum_info)  {  	global $template, $db, $user, $auth, $cache, $module;  	global $phpEx, $phpbb_root_path, $config; -	global $request; +	global $request, $phpbb_dispatcher;  	$user->add_lang(array('viewtopic', 'viewforum')); @@ -102,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'], @@ -130,9 +132,8 @@ 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), -		'TOTAL_TOPICS'			=> ($forum_topics == 1) ? $user->lang['VIEW_FORUM_TOPIC'] : sprintf($user->lang['VIEW_FORUM_TOPICS'], $forum_topics), +		'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),  	));  	// Grab icons @@ -227,7 +228,6 @@ function mcp_forum_view($id, $mode, $action, $forum_info)  			'ATTACH_ICON_IMG'		=> ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['topic_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '',  			'TOPIC_IMG_STYLE'		=> $folder_img,  			'TOPIC_FOLDER_IMG'		=> $user->img($folder_img, $folder_alt), -			'TOPIC_FOLDER_IMG_SRC'	=> $user->img($folder_img, $folder_alt, false, '', 'src'),  			'TOPIC_ICON_IMG'		=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['img'] : '',  			'TOPIC_ICON_IMG_WIDTH'	=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['width'] : '',  			'TOPIC_ICON_IMG_HEIGHT'	=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['height'] : '', @@ -288,6 +288,17 @@ function mcp_forum_view($id, $mode, $action, $forum_info)  			));  		} +		/** +		* Modify the topic data before it is assigned to the template in MCP +		* +		* @event core.mcp_view_forum_modify_topicrow +		* @var	array	row			Array with topic data +		* @var	array	topic_row	Template array with topic data +		* @since 3.1-A1 +		*/ +		$vars = array('row', 'topic_row'); +		extract($phpbb_dispatcher->trigger_event('core.mcp_view_forum_modify_topicrow', compact($vars))); +  		$template->assign_block_vars('topicrow', $topic_row);  	}  	unset($topic_rows); diff --git a/phpBB/includes/mcp/mcp_front.php b/phpBB/includes/mcp/mcp_front.php index eca67c5ac1..ba4b15895a 100644 --- a/phpBB/includes/mcp/mcp_front.php +++ b/phpBB/includes/mcp/mcp_front.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -79,7 +78,7 @@ function mcp_front_view($id, $mode, $action)  			if ($total)  			{ -				$sql = 'SELECT p.post_id, p.post_subject, p.post_time, p.poster_id, p.post_username, u.username, u.username_clean, u.user_colour, t.topic_id, t.topic_title, t.topic_first_post_id, p.forum_id +				$sql = 'SELECT p.post_id, p.post_subject, p.post_time, p.post_attachment, p.poster_id, p.post_username, u.username, u.username_clean, u.user_colour, t.topic_id, t.topic_title, t.topic_first_post_id, p.forum_id  					FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . USERS_TABLE . ' u  					WHERE ' . $db->sql_in_set('p.post_id', $post_list) . '  						AND t.topic_id = p.topic_id @@ -105,8 +104,9 @@ function mcp_front_view($id, $mode, $action)  						'POST_ID'		=> $row['post_id'],  						'TOPIC_TITLE'	=> $row['topic_title'],  						'SUBJECT'		=> ($row['post_subject']) ? $row['post_subject'] : $user->lang['NO_SUBJECT'], -						'POST_TIME'		=> $user->format_date($row['post_time'])) -					); +						'POST_TIME'		=> $user->format_date($row['post_time']), +						'ATTACH_ICON_IMG'	=> ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', +					));  				}  				$db->sql_freeresult($result);  			} @@ -118,22 +118,9 @@ function mcp_front_view($id, $mode, $action)  			$template->assign_vars(array(  				'S_HIDDEN_FIELDS'		=> $s_hidden_fields,  				'S_MCP_QUEUE_ACTION'	=> append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue"), +				'L_UNAPPROVED_TOTAL'	=> $user->lang('UNAPPROVED_POSTS_TOTAL', (int) $total), +				'S_HAS_UNAPPROVED_POSTS'=> ($total != 0),  			)); - -			if ($total == 0) -			{ -				$template->assign_vars(array( -					'L_UNAPPROVED_TOTAL'		=> $user->lang['UNAPPROVED_POSTS_ZERO_TOTAL'], -					'S_HAS_UNAPPROVED_POSTS'	=> false) -				); -			} -			else -			{ -				$template->assign_vars(array( -					'L_UNAPPROVED_TOTAL'		=> ($total == 1) ? $user->lang['UNAPPROVED_POST_TOTAL'] : sprintf($user->lang['UNAPPROVED_POSTS_TOTAL'], $total), -					'S_HAS_UNAPPROVED_POSTS'	=> true) -				); -			}  		}  	} @@ -158,22 +145,22 @@ function mcp_front_view($id, $mode, $action)  			if ($total)  			{ -				$sql = $db->sql_build_query('SELECT', array( -					'SELECT'	=> 'r.report_time, p.post_id, p.post_subject, p.post_time, u.username, u.username_clean, u.user_colour, u.user_id, u2.username as author_name, u2.username_clean as author_name_clean, u2.user_colour as author_colour, u2.user_id as author_id, t.topic_id, t.topic_title, f.forum_id, f.forum_name', +				$sql_ary = array( +					'SELECT'	=> 'r.report_time, p.post_id, p.post_subject, p.post_time, p.post_attachment, u.username, u.username_clean, u.user_colour, u.user_id, u2.username as author_name, u2.username_clean as author_name_clean, u2.user_colour as author_colour, u2.user_id as author_id, t.topic_id, t.topic_title, f.forum_id, f.forum_name',  					'FROM'		=> array(  						REPORTS_TABLE			=> 'r',  						REPORTS_REASONS_TABLE	=> 'rr',  						TOPICS_TABLE			=> 't',  						USERS_TABLE				=> array('u', 'u2'), -						POSTS_TABLE				=> 'p' +						POSTS_TABLE				=> 'p',  					),  					'LEFT_JOIN'	=> array(  						array(  							'FROM'	=> array(FORUMS_TABLE => 'f'), -							'ON'	=> 'f.forum_id = p.forum_id' -						) +							'ON'	=> 'f.forum_id = p.forum_id', +						),  					),  					'WHERE'		=> 'r.post_id = p.post_id @@ -185,8 +172,9 @@ function mcp_front_view($id, $mode, $action)  						AND p.poster_id = u2.user_id  						AND ' . $db->sql_in_set('p.forum_id', $forum_list), -					'ORDER_BY'	=> 'p.post_time DESC' -				)); +					'ORDER_BY'	=> 'p.post_time DESC', +				); +				$sql = $db->sql_build_query('SELECT', $sql_ary);  				$result = $db->sql_query_limit($sql, 5);  				while ($row = $db->sql_fetchrow($result)) @@ -213,24 +201,15 @@ function mcp_front_view($id, $mode, $action)  						'SUBJECT'		=> ($row['post_subject']) ? $row['post_subject'] : $user->lang['NO_SUBJECT'],  						'REPORT_TIME'	=> $user->format_date($row['report_time']),  						'POST_TIME'		=> $user->format_date($row['post_time']), +						'ATTACH_ICON_IMG'	=> ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '',  					));  				}  			} -			if ($total == 0) -			{ -				$template->assign_vars(array( -					'L_REPORTS_TOTAL'	=>	$user->lang['REPORTS_ZERO_TOTAL'], -					'S_HAS_REPORTS'		=>	false) -				); -			} -			else -			{ -				$template->assign_vars(array( -					'L_REPORTS_TOTAL'	=> ($total == 1) ? $user->lang['REPORT_TOTAL'] : sprintf($user->lang['REPORTS_TOTAL'], $total), -					'S_HAS_REPORTS'		=> true) -				); -			} +			$template->assign_vars(array( +				'L_REPORTS_TOTAL'	=> $user->lang('REPORTS_TOTAL', (int) $total), +				'S_HAS_REPORTS'		=> ($total != 0), +			));  		}  	} @@ -253,14 +232,14 @@ function mcp_front_view($id, $mode, $action)  		{  			include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); -			$sql = $db->sql_build_query('SELECT', array( -				'SELECT'	=> 'r.report_id, r.report_time, p.msg_id, p.message_subject, p.message_time, p.to_address, p.bcc_address, u.username, u.username_clean, u.user_colour, u.user_id, u2.username as author_name, u2.username_clean as author_name_clean, u2.user_colour as author_colour, u2.user_id as author_id', +			$sql_ary = array( +				'SELECT'	=> 'r.report_id, r.report_time, p.msg_id, p.message_subject, p.message_time, p.to_address, p.bcc_address, p.message_attachment, u.username, u.username_clean, u.user_colour, u.user_id, u2.username as author_name, u2.username_clean as author_name_clean, u2.user_colour as author_colour, u2.user_id as author_id',  				'FROM'		=> array(  					REPORTS_TABLE			=> 'r',  					REPORTS_REASONS_TABLE	=> 'rr',  					USERS_TABLE				=> array('u', 'u2'), -					PRIVMSGS_TABLE				=> 'p' +					PRIVMSGS_TABLE				=> 'p',  				),  				'WHERE'		=> 'r.pm_id = p.msg_id @@ -270,8 +249,9 @@ function mcp_front_view($id, $mode, $action)  					AND r.user_id = u.user_id  					AND p.author_id = u2.user_id', -				'ORDER_BY'	=> 'p.message_time DESC' -			)); +				'ORDER_BY'	=> 'p.message_time DESC', +			); +			$sql = $db->sql_build_query('SELECT', $sql_ary);  			$result = $db->sql_query_limit($sql, 5);  			$pm_by_id = $pm_list = array(); @@ -304,24 +284,15 @@ function mcp_front_view($id, $mode, $action)  					'REPORT_TIME'		=> $user->format_date($row['report_time']),  					'PM_TIME'			=> $user->format_date($row['message_time']),  					'RECIPIENTS'		=> implode(', ', $address_list[$row['msg_id']]), +					'ATTACH_ICON_IMG'	=> ($auth->acl_get('u_download') && $row['message_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '',  				));  			}  		} -		if ($total == 0) -		{ -			$template->assign_vars(array( -				'L_PM_REPORTS_TOTAL'	=>	$user->lang['PM_REPORTS_ZERO_TOTAL'], -				'S_HAS_PM_REPORTS'		=>	false) -			); -		} -		else -		{ -			$template->assign_vars(array( -				'L_PM_REPORTS_TOTAL'	=> ($total == 1) ? $user->lang['PM_REPORT_TOTAL'] : sprintf($user->lang['PM_REPORTS_TOTAL'], $total), -				'S_HAS_PM_REPORTS'		=> true) -			); -		} +		$template->assign_vars(array( +			'L_PM_REPORTS_TOTAL'	=> $user->lang('PM_REPORTS_TOTAL', (int) $total), +			'S_HAS_PM_REPORTS'		=> ($total != 0), +		));  	}  	// Latest 5 logs diff --git a/phpBB/includes/mcp/mcp_logs.php b/phpBB/includes/mcp/mcp_logs.php index 01f8584970..f706840492 100644 --- a/phpBB/includes/mcp/mcp_logs.php +++ b/phpBB/includes/mcp/mcp_logs.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -170,16 +169,18 @@ class mcp_logs  		// Grab log data  		$log_data = array();  		$log_count = 0; -		view_log('mod', $log_data, $log_count, $config['topics_per_page'], $start, $forum_list, $topic_id, 0, $sql_where, $sql_sort, $keywords); +		$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), -			'TOTAL'				=> ($log_count == 1) ? $user->lang['TOTAL_LOG'] : sprintf($user->lang['TOTAL_LOGS'], $log_count), -			'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'				=> $user->lang('TOTAL_LOGS', (int) $log_count),  			'L_TITLE'			=> $user->lang['MCP_LOGS'], -			'U_POST_ACTION'			=> $this->u_action, +			'U_POST_ACTION'			=> $this->u_action . "&$u_sort_param$keywords_param&start=$start",  			'S_CLEAR_ALLOWED'		=> ($auth->acl_get('a_clearlogs')) ? true : false,  			'S_SELECT_SORT_DIR'		=> $s_sort_dir,  			'S_SELECT_SORT_KEY'		=> $s_sort_key, diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php index 6c6c5a5532..95ca7c2e1b 100644 --- a/phpBB/includes/mcp/mcp_main.php +++ b/phpBB/includes/mcp/mcp_main.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -286,14 +285,6 @@ function change_topic_type($action, $topic_ids)  {  	global $auth, $user, $db, $phpEx, $phpbb_root_path; -	// For changing topic types, we only allow operations in one forum. -	$forum_id = check_ids($topic_ids, TOPICS_TABLE, 'topic_id', array('f_announce', 'f_sticky', 'm_'), true); - -	if ($forum_id === false) -	{ -		return; -	} -  	switch ($action)  	{  		case 'make_announce': @@ -316,11 +307,18 @@ function change_topic_type($action, $topic_ids)  		default:  			$new_topic_type = POST_NORMAL; -			$check_acl = ''; +			$check_acl = false;  			$l_new_type = (sizeof($topic_ids) == 1) ? 'MCP_MAKE_NORMAL' : 'MCP_MAKE_NORMALS';  		break;  	} +	$forum_id = check_ids($topic_ids, TOPICS_TABLE, 'topic_id', $check_acl, true); + +	if ($forum_id === false) +	{ +		return; +	} +  	$redirect = request_var('redirect', build_url(array('action', 'quickmod')));  	$s_hidden_fields = array( @@ -909,20 +907,15 @@ function mcp_fork_topic($topic_ids)  			if (!isset($search_type) && $topic_row['enable_indexing'])  			{  				// Select the search method and do some additional checks to ensure it can actually be utilised -				$search_type = basename($config['search_type']); - -				if (!file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx)) -				{ -					trigger_error('NO_SUCH_SEARCH_MODULE'); -				} +				$search_type = $config['search_type'];  				if (!class_exists($search_type))  				{ -					include("{$phpbb_root_path}includes/search/$search_type.$phpEx"); +					trigger_error('NO_SUCH_SEARCH_MODULE');  				}  				$error = false; -				$search = new $search_type($error); +				$search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user);  				$search_mode = 'post';  				if ($error) diff --git a/phpBB/includes/mcp/mcp_notes.php b/phpBB/includes/mcp/mcp_notes.php index 0b22ff74fd..59cdf3c27e 100644 --- a/phpBB/includes/mcp/mcp_notes.php +++ b/phpBB/includes/mcp/mcp_notes.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -198,7 +197,7 @@ class mcp_notes  		$log_data = array();  		$log_count = 0; -		view_log('user', $log_data, $log_count, $config['topics_per_page'], $start, 0, 0, $user_id, $sql_where, $sql_sort, $keywords); +		$start = view_log('user', $log_data, $log_count, $config['topics_per_page'], $start, 0, 0, $user_id, $sql_where, $sql_sort, $keywords);  		if ($log_count)  		{ @@ -216,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, @@ -226,9 +228,8 @@ 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), -			'TOTAL_REPORTS'		=> ($log_count == 1) ? $user->lang['LIST_REPORT'] : sprintf($user->lang['LIST_REPORTS'], $log_count), +			'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,  			'JOINED'			=> $user->format_date($userrow['user_regdate']), diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index e61e8390c9..be18dba944 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -292,12 +291,16 @@ class mcp_pm_reports  								'REPORT_ID'				=> $row['report_id'],  								'REPORT_TIME'			=> $user->format_date($row['report_time']), -								'RECIPIENTS'			=> implode(', ', $address_list[$row['msg_id']]), +								'RECIPIENTS'			=> implode($user->lang['COMMA_SEPARATOR'], $address_list[$row['msg_id']]), +								'ATTACH_ICON_IMG'		=> ($auth->acl_get('u_download') && $row['message_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '',  							));  						}  					}  				} +				$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,10 +310,9 @@ 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'			=> ($total == 1) ? $user->lang['LIST_REPORT'] : sprintf($user->lang['LIST_REPORTS'], $total),					 +					'TOTAL_REPORTS'			=> $user->lang('LIST_REPORTS', (int) $total),  					)  				); diff --git a/phpBB/includes/mcp/mcp_post.php b/phpBB/includes/mcp/mcp_post.php index 4e0aae1870..520c964228 100644 --- a/phpBB/includes/mcp/mcp_post.php +++ b/phpBB/includes/mcp/mcp_post.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -246,7 +245,7 @@ function mcp_post_details($id, $mode, $action)  	}  	// Get Reports -	if ($auth->acl_get('m_', $post_info['forum_id'])) +	if ($auth->acl_get('m_report', $post_info['forum_id']))  	{  		$sql = 'SELECT r.*, re.*, u.user_id, u.username  			FROM ' . REPORTS_TABLE . ' r, ' . USERS_TABLE . ' u, ' . REPORTS_REASONS_TABLE . " re @@ -394,7 +393,7 @@ function mcp_post_details($id, $mode, $action)  */  function change_poster(&$post_info, $userdata)  { -	global $auth, $db, $config, $phpbb_root_path, $phpEx; +	global $auth, $db, $config, $phpbb_root_path, $phpEx, $user;  	if (empty($userdata) || $userdata['user_id'] == $post_info['user_id'])  	{ @@ -465,15 +464,13 @@ function change_poster(&$post_info, $userdata)  	}  	// refresh search cache of this post -	$search_type = basename($config['search_type']); +	$search_type = $config['search_type']; -	if (file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx)) +	if (class_exists($search_type))  	{ -		require("{$phpbb_root_path}includes/search/$search_type.$phpEx"); -  		// We do some additional checks in the module to ensure it can actually be utilised  		$error = false; -		$search = new $search_type($error); +		$search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user);  		if (!$error && method_exists($search, 'destroy_cache'))  		{ diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 0630f55b12..0b195aa9d8 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -216,6 +215,7 @@ class mcp_queue  					'POST_IP'				=> $post_info['poster_ip'],  					'POST_IPADDR'			=> ($auth->acl_get('m_info', $post_info['forum_id']) && request_var('lookup', '')) ? @gethostbyaddr($post_info['poster_ip']) : '',  					'POST_ID'				=> $post_info['post_id'], +					'S_FIRST_POST'			=> ($post_info['topic_first_post_id'] == $post_id),  					'U_LOOKUP_IP'			=> ($auth->acl_get('m_info', $post_info['forum_id'])) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $post_info['forum_id'] . '&p=' . $post_id . '&lookup=' . $post_info['poster_ip']) . '#ip' : '',  				)); @@ -268,8 +268,6 @@ class mcp_queue  						trigger_error('NOT_MODERATOR');  					} -					$forum_list = implode(', ', $forum_list); -  					$sql = 'SELECT SUM(forum_topics) as sum_forum_topics  						FROM ' . FORUMS_TABLE . '  						WHERE ' . $db->sql_in_set('forum_id', $forum_list); @@ -331,7 +329,7 @@ class mcp_queue  					if (sizeof($post_ids))  					{ -						$sql = 'SELECT t.topic_id, t.topic_title, t.forum_id, p.post_id, p.post_subject, p.post_username, p.poster_id, p.post_time, u.username, u.username_clean, u.user_colour +						$sql = 'SELECT t.topic_id, t.topic_title, t.forum_id, p.post_id, p.post_subject, p.post_username, p.poster_id, p.post_time, p.post_attachment, u.username, u.username_clean, u.user_colour  							FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . USERS_TABLE . ' u  							WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . '  								AND t.topic_id = p.topic_id @@ -360,7 +358,7 @@ class mcp_queue  				}  				else  				{ -					$sql = 'SELECT t.forum_id, t.topic_id, t.topic_title, t.topic_title AS post_subject, t.topic_time AS post_time, t.topic_poster AS poster_id, t.topic_first_post_id AS post_id, t.topic_first_poster_name AS username, t.topic_first_poster_colour AS user_colour +					$sql = 'SELECT t.forum_id, t.topic_id, t.topic_title, t.topic_title AS post_subject, t.topic_time AS post_time, t.topic_poster AS poster_id, t.topic_first_post_id AS post_id, t.topic_attachment AS post_attachment, t.topic_first_poster_name AS username, t.topic_first_poster_colour AS user_colour  						FROM ' . TOPICS_TABLE . " t  						WHERE " . $db->sql_in_set('forum_id', $forum_list) . "  							AND topic_approved = 0 @@ -415,11 +413,15 @@ class mcp_queue  						'FORUM_NAME'	=> $forum_names[$row['forum_id']],  						'POST_SUBJECT'	=> ($row['post_subject'] != '') ? $row['post_subject'] : $user->lang['NO_SUBJECT'],  						'TOPIC_TITLE'	=> $row['topic_title'], -						'POST_TIME'		=> $user->format_date($row['post_time'])) -					); +						'POST_TIME'		=> $user->format_date($row['post_time']), +						'ATTACH_ICON_IMG'	=> ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', +					));  				}  				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'], @@ -431,10 +433,9 @@ 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'					=> ($total == 1) ? (($mode == 'unapproved_posts') ? $user->lang['VIEW_TOPIC_POST'] : $user->lang['VIEW_FORUM_TOPIC']) : sprintf((($mode == 'unapproved_posts') ? $user->lang['VIEW_TOPIC_POSTS'] : $user->lang['VIEW_FORUM_TOPICS']), $total), +					'TOTAL'					=> $user->lang((($mode == 'unapproved_posts') ? 'VIEW_TOPIC_POSTS' : 'VIEW_FORUM_TOPICS'), (int) $total),  				));  				$this->tpl_name = 'mcp_queue'; @@ -450,6 +451,7 @@ function approve_post($post_id_list, $id, $mode)  {  	global $db, $template, $user, $config;  	global $phpEx, $phpbb_root_path; +	global $request;  	if (!check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_approve')))  	{ @@ -708,7 +710,20 @@ function approve_post($post_id_list, $id, $mode)  			$add_message = '<br /><br />' . sprintf($user->lang['RETURN_POST'], '<a href="' . $post_url . '">', '</a>');  		} -		trigger_error($user->lang[$success_msg] . '<br /><br />' . sprintf($user->lang['RETURN_PAGE'], "<a href=\"$redirect\">", '</a>') . $add_message); +		$message = $user->lang[$success_msg] . '<br /><br />' . sprintf($user->lang['RETURN_PAGE'], "<a href=\"$redirect\">", '</a>') . $add_message; + +		if ($request->is_ajax()) +		{ +			$json_response = new phpbb_json_response; +			$json_response->send(array( +				'MESSAGE_TITLE'		=> $user->lang['INFORMATION'], +				'MESSAGE_TEXT'		=> $message, +				'REFRESH_DATA'		=> null, +				'approved'				=> true +			)); +		} + +		trigger_error($message);  	}  } @@ -754,7 +769,10 @@ function disapprove_post($post_id_list, $id, $mode)  		if (!$row || (!$reason && strtolower($row['reason_title']) == 'other'))  		{  			$additional_msg = $user->lang['NO_REASON_DISAPPROVAL']; +  			$request->overwrite('confirm', null, phpbb_request_interface::POST); +			$request->overwrite('confirm_key', null, phpbb_request_interface::POST); +			$request->overwrite('confirm_key', null, phpbb_request_interface::REQUEST);  		}  		else  		{ @@ -964,7 +982,20 @@ function disapprove_post($post_id_list, $id, $mode)  	}  	else  	{ +		$message = $user->lang[$success_msg] . '<br /><br />' . sprintf($user->lang['RETURN_PAGE'], "<a href=\"$redirect\">", '</a>'); + +		if ($request->is_ajax()) +		{ +			$json_response = new phpbb_json_response; +			$json_response->send(array( +				'MESSAGE_TITLE'		=> $user->lang['INFORMATION'], +				'MESSAGE_TEXT'		=> $message, +				'REFRESH_DATA'		=> null, +				'approved'				=> false +			)); +		} +  		meta_refresh(3, $redirect); -		trigger_error($user->lang[$success_msg] . '<br /><br />' . sprintf($user->lang['RETURN_PAGE'], "<a href=\"$redirect\">", '</a>')); +		trigger_error($message);  	}  } diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index a7275382e6..f2c5080df5 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -72,7 +71,7 @@ class mcp_reports  				// closed reports are accessed by report id  				$report_id = request_var('r', 0); -				$sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour +				$sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour  					FROM ' . REPORTS_TABLE . ' r, ' . REPORTS_REASONS_TABLE . ' rr, ' . USERS_TABLE . ' u  					WHERE ' . (($report_id) ? 'r.report_id = ' . $report_id : "r.post_id = $post_id") . '  						AND rr.reason_id = r.reason_id @@ -117,8 +116,9 @@ class mcp_reports  					$template->assign_vars(array(  						'S_TOPIC_REVIEW'	=> true,  						'S_BBCODE_ALLOWED'	=> $post_info['enable_bbcode'], -						'TOPIC_TITLE'		=> $post_info['topic_title']) -					); +						'TOPIC_TITLE'		=> $post_info['topic_title'], +						'REPORTED_POST_ID'	=> $post_id, +					));  				}  				$topic_tracking_info = $extensions = $attachments = array(); @@ -148,6 +148,7 @@ class mcp_reports  				$message = bbcode_nl2br($message);  				$message = smiley_text($message); +				$report['report_text'] = make_clickable(bbcode_nl2br($report['report_text']));  				if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id']))  				{ @@ -226,7 +227,7 @@ class mcp_reports  					'REPORTER_NAME'				=> get_username_string('username', $report['user_id'], $report['username'], $report['user_colour']),  					'U_VIEW_REPORTER_PROFILE'	=> get_username_string('profile', $report['user_id'], $report['username'], $report['user_colour']), -					'POST_PREVIEW'			=> $message, +					'POST_PREVIEW'			=> bbcode_nl2br($report['reported_post_text']),  					'POST_SUBJECT'			=> ($post_info['post_subject']) ? $post_info['post_subject'] : $user->lang['NO_SUBJECT'],  					'POST_DATE'				=> $user->format_date($post_info['post_time']),  					'POST_IP'				=> $post_info['poster_ip'], @@ -367,7 +368,7 @@ class mcp_reports  				if (sizeof($report_ids))  				{ -					$sql = 'SELECT t.forum_id, t.topic_id, t.topic_title, p.post_id, p.post_subject, p.post_username, p.poster_id, p.post_time, u.username, u.username_clean, u.user_colour, r.user_id as reporter_id, ru.username as reporter_name, ru.user_colour as reporter_colour, r.report_time, r.report_id +					$sql = 'SELECT t.forum_id, t.topic_id, t.topic_title, p.post_id, p.post_subject, p.post_username, p.poster_id, p.post_time, p.post_attachment, u.username, u.username_clean, u.user_colour, r.user_id as reporter_id, ru.username as reporter_name, ru.user_colour as reporter_colour, r.report_time, r.report_id  						FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . USERS_TABLE . ' u, ' . USERS_TABLE . ' ru  						WHERE ' . $db->sql_in_set('r.report_id', $report_ids) . '  							AND t.topic_id = p.topic_id @@ -402,13 +403,17 @@ class mcp_reports  							'POST_TIME'		=> $user->format_date($row['post_time']),  							'REPORT_ID'		=> $row['report_id'],  							'REPORT_TIME'	=> $user->format_date($row['report_time']), -							'TOPIC_TITLE'	=> $row['topic_title']) -						); +							'TOPIC_TITLE'	=> $row['topic_title'], +							'ATTACH_ICON_IMG'	=> ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', +						));  					}  					$db->sql_freeresult($result);  					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'], @@ -419,11 +424,10 @@ 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'			=> ($total == 1) ? $user->lang['LIST_REPORT'] : sprintf($user->lang['LIST_REPORTS'], $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 8a8ef11289..62483270c0 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -50,6 +49,16 @@ function mcp_topic_view($id, $mode, $action)  	$submitted_id_list	= request_var('post_ids', array(0));  	$checked_ids = $post_id_list = request_var('post_id_list', array(0)); +	// Resync Topic? +	if ($action == 'resync') +	{ +		if (!function_exists('mcp_resync_topics')) +		{ +			include($phpbb_root_path . 'includes/mcp/mcp_forum.' . $phpEx); +		} +		mcp_resync_topics(array($topic_id)); +	} +  	// Split Topic?  	if ($action == 'split_all' || $action == 'split_beyond')  	{ @@ -239,8 +248,8 @@ function mcp_topic_view($id, $mode, $action)  			'MINI_POST_IMG'			=> ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'), -			'S_POST_REPORTED'	=> ($row['post_reported']) ? true : false, -			'S_POST_UNAPPROVED'	=> ($row['post_approved']) ? false : true, +			'S_POST_REPORTED'	=> ($row['post_reported'] && $auth->acl_get('m_report', $topic_info['forum_id'])), +			'S_POST_UNAPPROVED'	=> (!$row['post_approved'] && $auth->acl_get('m_approve', $topic_info['forum_id'])),  			'S_CHECKED'			=> (($submitted_id_list && !in_array(intval($row['post_id']), $submitted_id_list)) || in_array(intval($row['post_id']), $checked_ids)) ? true : false,  			'S_HAS_ATTACHMENTS'	=> (!empty($attachments[$row['post_id']])) ? true : false, @@ -297,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']), @@ -320,6 +335,7 @@ function mcp_topic_view($id, $mode, $action)  		'S_CAN_APPROVE'		=> ($has_unapproved_posts && $auth->acl_get('m_approve', $topic_info['forum_id'])) ? true : false,  		'S_CAN_LOCK'		=> ($auth->acl_get('m_lock', $topic_info['forum_id'])) ? true : false,  		'S_CAN_REPORT'		=> ($auth->acl_get('m_report', $topic_info['forum_id'])) ? true : false, +		'S_CAN_SYNC'		=> $auth->acl_get('m_', $topic_info['forum_id']),  		'S_REPORT_VIEW'		=> ($action == 'reports') ? true : false,  		'S_MERGE_VIEW'		=> ($action == 'merge') ? true : false,  		'S_SPLIT_VIEW'		=> ($action == 'split') ? true : false, @@ -334,9 +350,8 @@ 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), -		'TOTAL_POSTS'		=> ($total == 1) ? $user->lang['VIEW_TOPIC_POST'] : sprintf($user->lang['VIEW_TOPIC_POSTS'], $total), +		'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 9339389ea4..6a8fb4c5d5 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -2,9 +2,8 @@  /**  *  * @package mcp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -176,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, @@ -183,9 +185,8 @@ 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), -			'TOTAL_USERS'		=> ($user_count == 1) ? $user->lang['LIST_USER'] : sprintf($user->lang['LIST_USERS'], $user_count), +			'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),  		));  	} @@ -252,7 +253,7 @@ class mcp_warn  		// Check if can send a notification  		if ($config['allow_privmsg'])  		{ -			$auth2 = new auth(); +			$auth2 = new phpbb_auth();  			$auth2->acl($user_row);  			$s_can_notify = ($auth2->acl_get('u_readpm')) ? true : false;  			unset($auth2); @@ -308,7 +309,7 @@ class mcp_warn  			include($phpbb_root_path . 'includes/functions_display.' . $phpEx);  		} -		$rank_title = $rank_img = ''; +		get_user_rank($user_row['user_rank'], $user_row['user_posts'], $rank_title, $rank_img, $rank_img_src);  		$avatar_img = get_user_avatar($user_row['user_avatar'], $user_row['user_avatar_type'], $user_row['user_avatar_width'], $user_row['user_avatar_height']);  		$template->assign_vars(array( @@ -375,7 +376,7 @@ class mcp_warn  		// Check if can send a notification  		if ($config['allow_privmsg'])  		{ -			$auth2 = new auth(); +			$auth2 = new phpbb_auth();  			$auth2->acl($user_row);  			$s_can_notify = ($auth2->acl_get('u_readpm')) ? true : false;  			unset($auth2); @@ -413,7 +414,7 @@ class mcp_warn  			include($phpbb_root_path . 'includes/functions_display.' . $phpEx);  		} -		$rank_title = $rank_img = ''; +		get_user_rank($user_row['user_rank'], $user_row['user_posts'], $rank_title, $rank_img, $rank_img_src);  		$avatar_img = get_user_avatar($user_row['user_avatar'], $user_row['user_avatar_type'], $user_row['user_avatar_width'], $user_row['user_avatar_height']);  		// OK, they didn't submit a warning so lets build the page for them to do so diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index c6132d86d4..6695047b56 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -102,20 +101,22 @@ class bbcode_firstpass extends bbcode  	/**  	* Init bbcode data for later parsing  	*/ -	function bbcode_init() +	function bbcode_init($allow_custom_bbcode = true)  	{  		static $rowset;  		// This array holds all bbcode data. BBCodes will be processed in this  		// order, so it is important to keep [code] in first position and  		// [quote] in second position. +		// To parse multiline URL we enable dotall option setting only for URL text +		// but not for link itself, thus [url][/url] is not affected.  		$this->bbcodes = array(  			'code'			=> array('bbcode_id' => 8,	'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#uise' => "\$this->bbcode_code('\$1', '\$2')")),  			'quote'			=> array('bbcode_id' => 0,	'regexp' => array('#\[quote(?:="(.*?)")?\](.+)\[/quote\]#uise' => "\$this->bbcode_quote('\$0')")),  			'attachment'	=> array('bbcode_id' => 12,	'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#uise' => "\$this->bbcode_attachment('\$1', '\$2')")),  			'b'				=> array('bbcode_id' => 1,	'regexp' => array('#\[b\](.*?)\[/b\]#uise' => "\$this->bbcode_strong('\$1')")),  			'i'				=> array('bbcode_id' => 2,	'regexp' => array('#\[i\](.*?)\[/i\]#uise' => "\$this->bbcode_italic('\$1')")), -			'url'			=> array('bbcode_id' => 3,	'regexp' => array('#\[url(=(.*))?\](.*)\[/url\]#uiUe' => "\$this->validate_url('\$2', '\$3')")), +			'url'			=> array('bbcode_id' => 3,	'regexp' => array('#\[url(=(.*))?\](?(1)((?s).*(?-s))|(.*))\[/url\]#uiUe' => "\$this->validate_url('\$2', ('\$3') ? '\$3' : '\$4')")),  			'img'			=> array('bbcode_id' => 4,	'regexp' => array('#\[img\](.*)\[/img\]#uiUe' => "\$this->bbcode_img('\$1')")),  			'size'			=> array('bbcode_id' => 5,	'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#uise' => "\$this->bbcode_size('\$1', '\$2')")),  			'color'			=> array('bbcode_id' => 6,	'regexp' => array('!\[color=(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!uise' => "\$this->bbcode_color('\$1', '\$2')")), @@ -133,6 +134,11 @@ class bbcode_firstpass extends bbcode  			$this->parsed_items[$tag] = 0;  		} +		if (!$allow_custom_bbcode) +		{ +			return; +		} +  		if (!is_array($rowset))  		{  			global $db; @@ -203,7 +209,7 @@ class bbcode_firstpass extends bbcode  		if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)  		{ -			$this->warn_msg[] = sprintf($user->lang['MAX_FONT_SIZE_EXCEEDED'], $config['max_' . $this->mode . '_font_size']); +			$this->warn_msg[] = $user->lang('MAX_FONT_SIZE_EXCEEDED', (int) $config['max_' . $this->mode . '_font_size']);  			return '[size=' . $stx . ']' . $in . '[/size]';  		} @@ -312,13 +318,13 @@ class bbcode_firstpass extends bbcode  				if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $stats[1])  				{  					$error = true; -					$this->warn_msg[] = sprintf($user->lang['MAX_IMG_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']); +					$this->warn_msg[] = $user->lang('MAX_IMG_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);  				}  				if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $stats[0])  				{  					$error = true; -					$this->warn_msg[] = sprintf($user->lang['MAX_IMG_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']); +					$this->warn_msg[] = $user->lang('MAX_IMG_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);  				}  			}  		} @@ -367,13 +373,13 @@ class bbcode_firstpass extends bbcode  			if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)  			{  				$error = true; -				$this->warn_msg[] = sprintf($user->lang['MAX_FLASH_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']); +				$this->warn_msg[] = $user->lang('MAX_FLASH_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']);  			}  			if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)  			{  				$error = true; -				$this->warn_msg[] = sprintf($user->lang['MAX_FLASH_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']); +				$this->warn_msg[] = $user->lang('MAX_FLASH_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']);  			}  		} @@ -765,7 +771,7 @@ class bbcode_firstpass extends bbcode  					if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])  					{  						// there are too many nested quotes -						$error_ary['quote_depth'] = sprintf($user->lang['QUOTE_DEPTH_EXCEEDED'], $config['max_quote_depth']); +						$error_ary['quote_depth'] = $user->lang('QUOTE_DEPTH_EXCEEDED', (int) $config['max_quote_depth']);  						$out .= $buffer . $tok;  						$tok = '[]'; @@ -1110,7 +1116,7 @@ class parse_message extends bbcode_firstpass  		// Maximum message length check. 0 disables this check completely.  		if ((int) $config['max_' . $mode . '_chars'] > 0 && $message_length > (int) $config['max_' . $mode . '_chars'])  		{ -			$this->warn_msg[] = sprintf($user->lang['TOO_MANY_CHARS_' . strtoupper($mode)], $message_length, (int) $config['max_' . $mode . '_chars']); +			$this->warn_msg[] = $user->lang('TOO_MANY_CHARS_' . strtoupper($mode), $message_length, (int) $config['max_' . $mode . '_chars']);  			return (!$update_this_message) ? $return_message : $this->warn_msg;  		} @@ -1119,7 +1125,7 @@ class parse_message extends bbcode_firstpass  		{  			if (!$message_length || $message_length < (int) $config['min_post_chars'])  			{ -				$this->warn_msg[] = (!$message_length) ? $user->lang['TOO_FEW_CHARS'] : sprintf($user->lang['TOO_FEW_CHARS_LIMIT'], $message_length, (int) $config['min_post_chars']); +				$this->warn_msg[] = (!$message_length) ? $user->lang['TOO_FEW_CHARS'] : $user->lang('TOO_FEW_CHARS_LIMIT', $message_length, (int) $config['min_post_chars']);  				return (!$update_this_message) ? $return_message : $this->warn_msg;  			}  		} @@ -1438,7 +1444,7 @@ class parse_message extends bbcode_firstpass  			}  			else  			{ -				$error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']); +				$error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);  			}  		} @@ -1529,7 +1535,7 @@ class parse_message extends bbcode_firstpass  				}  				else  				{ -					$error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']); +					$error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']);  				}  			}  		} diff --git a/phpBB/includes/php/ini.php b/phpBB/includes/php/ini.php new file mode 100644 index 0000000000..17e8c54a57 --- /dev/null +++ b/phpBB/includes/php/ini.php @@ -0,0 +1,175 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Wrapper class for ini_get function. +* +* Provides easier handling of the different interpretations of ini values. +* +* @package phpBB +*/ +class phpbb_php_ini +{ +	/** +	* Simple wrapper for ini_get() +	* See http://php.net/manual/en/function.ini-get.php +	* +	* @param string $varname	The configuration option name. +	* @return bool|string		False if configuration option does not exist, +	*							the configuration option value (string) otherwise. +	*/ +	public function get($varname) +	{ +		return ini_get($varname); +	} + +	/** +	* Gets the configuration option value as a trimmed string. +	* +	* @param string $varname	The configuration option name. +	* @return bool|string		False if configuration option does not exist, +	*							the configuration option value (string) otherwise. +	*/ +	public function get_string($varname) +	{ +		$value = $this->get($varname); + +		if ($value === false) +		{ +			return false; +		} + +		return trim($value); +	} + +	/** +	* Gets configuration option value as a boolean. +	* Interprets the string value 'off' as false. +	* +	* @param string $varname	The configuration option name. +	* @return bool				False if configuration option does not exist. +	*							False if configuration option is disabled. +	*							True otherwise. +	*/ +	public function get_bool($varname) +	{ +		$value = $this->get_string($varname); + +		if (empty($value) || strtolower($value) == 'off') +		{ +			return false; +		} + +		return true; +	} + +	/** +	* Gets configuration option value as an integer. +	* +	* @param string $varname	The configuration option name. +	* @return bool|int			False if configuration option does not exist, +	*							false if configuration option value is not numeric, +	*							the configuration option value (integer) otherwise. +	*/ +	public function get_int($varname) +	{ +		$value = $this->get_string($varname); + +		if (!is_numeric($value)) +		{ +			return false; +		} + +		return (int) $value; +	} + +	/** +	* Gets configuration option value as a float. +	* +	* @param string $varname	The configuration option name. +	* @return bool|float		False if configuration option does not exist, +	*							false if configuration option value is not numeric, +	*							the configuration option value (float) otherwise. +	*/ +	public function get_float($varname) +	{ +		$value = $this->get_string($varname); + +		if (!is_numeric($value)) +		{ +			return false; +		} + +		return (float) $value; +	} + +	/** +	* Gets configuration option value in bytes. +	* Converts strings like '128M' to bytes (integer or float). +	* +	* @param string $varname	The configuration option name. +	* @return bool|int|float	False if configuration option does not exist, +	*							false if configuration option value is not well-formed, +	*							the configuration option value otherwise. +	*/ +	public function get_bytes($varname) +	{ +		$value = $this->get_string($varname); + +		if ($value === false) +		{ +			return false; +		} + +		if (is_numeric($value)) +		{ +			// Already in bytes. +			return phpbb_to_numeric($value); +		} +		else if (strlen($value) < 2) +		{ +			// Single character. +			return false; +		} +		else if (strlen($value) < 3 && $value[0] === '-') +		{ +			// Two characters but the first one is a minus. +			return false; +		} + +		$value_lower = strtolower($value); +		$value_numeric = phpbb_to_numeric($value); + +		switch ($value_lower[strlen($value_lower) - 1]) +		{ +			case 'g': +				$value_numeric *= 1024; +			case 'm': +				$value_numeric *= 1024; +			case 'k': +				$value_numeric *= 1024; +			break; + +			default: +				// It's not already in bytes (and thus numeric) +				// and does not carry a unit. +				return false; +		} + +		return $value_numeric; +	} +} diff --git a/phpBB/includes/questionnaire/questionnaire.php b/phpBB/includes/questionnaire/questionnaire.php index b9231547cd..5cb441d536 100644 --- a/phpBB/includes/questionnaire/questionnaire.php +++ b/phpBB/includes/questionnaire/questionnaire.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -148,23 +147,15 @@ class phpbb_questionnaire_system_data_provider  	*/  	function get_data()  	{ -		// Start discovering the IPV4 server address, if available -		$server_address = '0.0.0.0'; - -		if (!empty($_SERVER['SERVER_ADDR'])) -		{ -			$server_address = $_SERVER['SERVER_ADDR']; -		} +		global $request; -		// Running on IIS? -		if (!empty($_SERVER['LOCAL_ADDR'])) -		{ -			$server_address = $_SERVER['LOCAL_ADDR']; -		} +		// Start discovering the IPV4 server address, if available +		// Try apache, IIS, fall back to 0.0.0.0 +		$server_address = htmlspecialchars_decode($request->server('SERVER_ADDR', $request->server('LOCAL_ADDR', '0.0.0.0')));  		return array(  			'os'	=> PHP_OS, -			'httpd'	=> $_SERVER['SERVER_SOFTWARE'], +			'httpd'	=> htmlspecialchars_decode($request->server('SERVER_SOFTWARE')),  			// we don't want the real IP address (for privacy policy reasons) but only  			// a network address to see whether your installation is running on a private or public network.  			'private_ip'	=> $this->is_private_ip($server_address), @@ -313,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, @@ -482,7 +472,7 @@ class phpbb_questionnaire_phpbb_data_provider  			}  		} -		global $db; +		global $db, $request;  		$result['dbms'] = $dbms;  		$result['acm_type'] = $acm_type; @@ -492,7 +482,7 @@ class phpbb_questionnaire_phpbb_data_provider  		// Try to get user agent vendor and version  		$match = array(); -		$user_agent = (!empty($_SERVER['HTTP_USER_AGENT'])) ? (string) $_SERVER['HTTP_USER_AGENT'] : ''; +		$user_agent = $request->header('User-Agent');  		$agents = array('firefox', 'msie', 'opera', 'chrome', 'safari', 'mozilla', 'seamonkey', 'konqueror', 'netscape', 'gecko', 'navigator', 'mosaic', 'lynx', 'amaya', 'omniweb', 'avant', 'camino', 'flock', 'aol');  		// We check here 1 by 1 because some strings occur after others (for example Mozilla [...] Firefox/) diff --git a/phpBB/includes/request/deactivated_super_global.php b/phpBB/includes/request/deactivated_super_global.php index 123d6ba5f7..cc05847ec7 100644 --- a/phpBB/includes/request/deactivated_super_global.php +++ b/phpBB/includes/request/deactivated_super_global.php @@ -3,7 +3,7 @@  *  * @package phpbb_request  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/request/interface.php b/phpBB/includes/request/interface.php index 7b5b600100..afd53002e3 100644 --- a/phpBB/includes/request/interface.php +++ b/phpBB/includes/request/interface.php @@ -3,7 +3,7 @@  *  * @package phpbb_request  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -29,6 +29,7 @@ interface phpbb_request_interface  	const GET = 1;  	const REQUEST = 2;  	const COOKIE = 3; +	const SERVER = 4;  	/**#@-*/  	/** @@ -67,6 +68,26 @@ interface phpbb_request_interface  	public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST);  	/** +	* Shortcut method to retrieve SERVER variables. +	* +	* @param	string|array	$var_name		See phpbb_request_interface::variable +	* @param	mixed			$default		See phpbb_request_interface::variable +	* +	* @return	mixed	The server variable value. +	*/ +	public function server($var_name, $default = ''); + +	/** +	* Shortcut method to retrieve the value of client HTTP headers. +	* +	* @param	string|array	$header_name	The name of the header to retrieve. +	* @param	mixed			$default		See phpbb_request_interface::variable +	* +	* @return	mixed	The header value. +	*/ +	public function header($var_name, $default = ''); + +	/**  	* Checks whether a certain variable was sent via POST.  	* To make sure that a request was sent using POST you should call this function  	* on at least one variable. @@ -91,6 +112,20 @@ interface phpbb_request_interface  	public function is_set($var, $super_global = phpbb_request_interface::REQUEST);  	/** +	* Checks whether the current request is an AJAX request (XMLHttpRequest) +	* +	* @return	bool			True if the current request is an ajax request +	*/ +	public function is_ajax(); + +	/** +	* Checks if the current request is happening over HTTPS. +	* +	* @return	bool			True if the request is secure. +	*/ +	public function is_secure(); + +	/**  	* Returns all variable names for a given super global  	*  	* @param	phpbb_request_interface::POST|GET|REQUEST|COOKIE	$super_global diff --git a/phpBB/includes/request/request.php b/phpBB/includes/request/request.php index 7d284a9bf7..a06fc0d85d 100644 --- a/phpBB/includes/request/request.php +++ b/phpBB/includes/request/request.php @@ -3,7 +3,7 @@  *  * @package phpbb_request  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -32,7 +32,8 @@ class phpbb_request implements phpbb_request_interface  		phpbb_request_interface::POST => '_POST',  		phpbb_request_interface::GET => '_GET',  		phpbb_request_interface::REQUEST => '_REQUEST', -		phpbb_request_interface::COOKIE => '_COOKIE' +		phpbb_request_interface::COOKIE => '_COOKIE', +		phpbb_request_interface::SERVER => '_SERVER',  	);  	/** @@ -199,46 +200,72 @@ class phpbb_request implements phpbb_request_interface  	*/  	public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST)  	{ -		$path = false; +		return $this->_variable($var_name, $default, $multibyte, $super_global, true); +	} -		// deep direct access to multi dimensional arrays -		if (is_array($var_name)) -		{ -			$path = $var_name; -			// make sure at least the variable name is specified -			if (empty($path)) -			{ -				return (is_array($default)) ? array() : $default; -			} -			// the variable name is the first element on the path -			$var_name = array_shift($path); -		} +	/** +	* Get a variable, but without trimming strings. +	* Same functionality as variable(), except does not run trim() on strings. +	* This method should be used when handling passwords. +	* +	* @param	string|array	$var_name	The form variable's name from which data shall be retrieved. +	* 										If the value is an array this may be an array of indizes which will give +	* 										direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") +	* 										then specifying array("var", 1) as the name will return "a". +	* @param	mixed			$default	A default value that is returned if the variable was not set. +	* 										This function will always return a value of the same type as the default. +	* @param	bool			$multibyte	If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters +	*										Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks +	* @param	phpbb_request_interface::POST|GET|REQUEST|COOKIE	$super_global +	* 										Specifies which super global should be used +	* +	* @return	mixed	The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the +	*					the same as that of $default. If the variable is not set $default is returned. +	*/ +	public function untrimmed_variable($var_name, $default, $multibyte, $super_global = phpbb_request_interface::REQUEST) +	{ +		return $this->_variable($var_name, $default, $multibyte, $super_global, false); +	} -		if (!isset($this->input[$super_global][$var_name])) +	/** +	* Shortcut method to retrieve SERVER variables. +	* +	* Also fall back to getenv(), some CGI setups may need it (probably not, but +	* whatever). +	* +	* @param	string|array	$var_name		See phpbb_request_interface::variable +	* @param	mixed			$Default		See phpbb_request_interface::variable +	* +	* @return	mixed	The server variable value. +	*/ +	public function server($var_name, $default = '') +	{ +		$multibyte = true; + +		if ($this->is_set($var_name, phpbb_request_interface::SERVER))  		{ -			return (is_array($default)) ? array() : $default; +			return $this->variable($var_name, $default, $multibyte, phpbb_request_interface::SERVER);  		} -		$var = $this->input[$super_global][$var_name]; - -		if ($path) +		else  		{ -			// walk through the array structure and find the element we are looking for -			foreach ($path as $key) -			{ -				if (is_array($var) && isset($var[$key])) -				{ -					$var = $var[$key]; -				} -				else -				{ -					return (is_array($default)) ? array() : $default; -				} -			} +			$var = getenv($var_name); +			$this->type_cast_helper->recursive_set_var($var, $default, $multibyte); +			return $var;  		} +	} -		$this->type_cast_helper->recursive_set_var($var, $default, $multibyte); - -		return $var; +	/** +	* Shortcut method to retrieve the value of client HTTP headers. +	* +	* @param	string|array	$header_name	The name of the header to retrieve. +	* @param	mixed			$default		See phpbb_request_interface::variable +	* +	* @return	mixed	The header value. +	*/ +	public function header($header_name, $default = '') +	{ +		$var_name = 'HTTP_' . str_replace('-', '_', strtoupper($header_name)); +		return $this->server($var_name, $default);  	}  	/** @@ -272,6 +299,26 @@ class phpbb_request implements phpbb_request_interface  	}  	/** +	* Checks whether the current request is an AJAX request (XMLHttpRequest) +	* +	* @return	bool			True if the current request is an ajax request +	*/ +	public function is_ajax() +	{ +		return $this->header('X-Requested-With') == 'XMLHttpRequest'; +	} + +	/** +	* Checks if the current request is happening over HTTPS. +	* +	* @return	bool			True if the request is secure. +	*/ +	public function is_secure() +	{ +		return $this->server('HTTPS') == 'on'; +	} + +	/**  	* Returns all variable names for a given super global  	*  	* @param	phpbb_request_interface::POST|GET|REQUEST|COOKIE	$super_global @@ -289,4 +336,66 @@ class phpbb_request implements phpbb_request_interface  		return array_keys($this->input[$super_global]);  	} + +	/** +	* Helper function used by variable() and untrimmed_variable(). +	* +	* @param	string|array	$var_name	The form variable's name from which data shall be retrieved. +	* 										If the value is an array this may be an array of indizes which will give +	* 										direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") +	* 										then specifying array("var", 1) as the name will return "a". +	* @param	mixed			$default	A default value that is returned if the variable was not set. +	* 										This function will always return a value of the same type as the default. +	* @param	bool			$multibyte	If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters +	*										Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks +	* @param	phpbb_request_interface::POST|GET|REQUEST|COOKIE	$super_global +	* 										Specifies which super global should be used +	* @param	bool			$trim		Indicates whether trim() should be applied to string values. +	* +	* @return	mixed	The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the +	*					the same as that of $default. If the variable is not set $default is returned. +	*/ +	protected function _variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST, $trim = true) +	{ +		$path = false; + +		// deep direct access to multi dimensional arrays +		if (is_array($var_name)) +		{ +			$path = $var_name; +			// make sure at least the variable name is specified +			if (empty($path)) +			{ +				return (is_array($default)) ? array() : $default; +			} +			// the variable name is the first element on the path +			$var_name = array_shift($path); +		} + +		if (!isset($this->input[$super_global][$var_name])) +		{ +			return (is_array($default)) ? array() : $default; +		} +		$var = $this->input[$super_global][$var_name]; + +		if ($path) +		{ +			// walk through the array structure and find the element we are looking for +			foreach ($path as $key) +			{ +				if (is_array($var) && isset($var[$key])) +				{ +					$var = $var[$key]; +				} +				else +				{ +					return (is_array($default)) ? array() : $default; +				} +			} +		} + +		$this->type_cast_helper->recursive_set_var($var, $default, $multibyte, $trim); + +		return $var; +	}  } diff --git a/phpBB/includes/request/type_cast_helper.php b/phpBB/includes/request/type_cast_helper.php index 29855a9804..1a5274ed14 100644 --- a/phpBB/includes/request/type_cast_helper.php +++ b/phpBB/includes/request/type_cast_helper.php @@ -3,7 +3,7 @@  *  * @package phpbb_request  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -34,7 +34,7 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i  	*/  	public function __construct()  	{ -		if (version_compare(PHP_VERSION, '6.0.0-dev', '>=')) +		if (version_compare(PHP_VERSION, '5.4.0-dev', '>='))  		{  			$this->strip = false;  		} @@ -88,20 +88,29 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i  	/**  	* Set variable $result to a particular type.  	* -	* @param mixed	&$result	The variable to fill -	* @param mixed	$var		The contents to fill with -	* @param mixed	$type		The variable type. Will be used with {@link settype()} -	* @param bool	$multibyte	Indicates whether string values may contain UTF-8 characters. -	* 							Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. +	* @param mixed	&$result		The variable to fill +	* @param mixed	$var			The contents to fill with +	* @param mixed	$type			The variable type. Will be used with {@link settype()} +	* @param bool	$multibyte		Indicates whether string values may contain UTF-8 characters. +	* 								Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. +	* @param bool	$trim			Indicates whether trim() should be applied to string values. +	* 								Default is true.  	*/ -	public function set_var(&$result, $var, $type, $multibyte = false) +	public function set_var(&$result, $var, $type, $multibyte = false, $trim = true)  	{  		settype($var, $type);  		$result = $var;  		if ($type == 'string')  		{ -			$result = trim(htmlspecialchars(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result), ENT_COMPAT, 'UTF-8')); +			$result = str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result); + +			if ($trim) +			{ +				$result = trim($result); +			} + +			$result = htmlspecialchars($result, ENT_COMPAT, 'UTF-8');  			if ($multibyte)  			{ @@ -140,8 +149,10 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i  	* @param	bool	$multibyte	Indicates whether string keys and values may contain UTF-8 characters.  	* 								Default is false, causing all bytes outside the ASCII range (0-127) to  	* 								be replaced with question marks. +	* @param	bool	$trim		Indicates whether trim() should be applied to string values. +	* 								Default is true.  	*/ -	public function recursive_set_var(&$var, $default, $multibyte) +	public function recursive_set_var(&$var, $default, $multibyte, $trim = true)  	{  		if (is_array($var) !== is_array($default))  		{ @@ -152,7 +163,7 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i  		if (!is_array($default))  		{  			$type = gettype($default); -			$this->set_var($var, $var, $type, $multibyte); +			$this->set_var($var, $var, $type, $multibyte, $trim);  		}  		else  		{ @@ -173,9 +184,9 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i  			foreach ($_var as $k => $v)  			{ -				$this->set_var($k, $k, $key_type, $multibyte, $multibyte); +				$this->set_var($k, $k, $key_type, $multibyte); -				$this->recursive_set_var($v, $default_value, $multibyte); +				$this->recursive_set_var($v, $default_value, $multibyte, $trim);  				$var[$k] = $v;  			}  		} diff --git a/phpBB/includes/request/type_cast_helper_interface.php b/phpBB/includes/request/type_cast_helper_interface.php index 366bd2e6ce..3920d16fc7 100644 --- a/phpBB/includes/request/type_cast_helper_interface.php +++ b/phpBB/includes/request/type_cast_helper_interface.php @@ -3,7 +3,7 @@  *  * @package phpbb_request  * @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/search/search.php b/phpBB/includes/search/base.php index 7c34ce9ff6..b364dead9a 100644 --- a/phpBB/includes/search/search.php +++ b/phpBB/includes/search/base.php @@ -2,9 +2,8 @@  /**  *  * @package search -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -24,12 +23,12 @@ define('SEARCH_RESULT_IN_CACHE', 1);  define('SEARCH_RESULT_INCOMPLETE', 2);  /** -* search_backend +* phpbb_search_base  * optional base class for search plugins providing simple caching based on ACM  * and functions to retrieve ignore_words and synonyms  * @package search  */ -class search_backend +class phpbb_search_base  {  	var $ignore_words = array();  	var $match_synonym = array(); @@ -295,7 +294,7 @@ class search_backend  			$sql_where = '';  			foreach ($authors as $author)  			{ -				$sql_where .= (($sql_where) ? ' OR ' : '') . 'search_authors LIKE \'% ' . (int) $author . ' %\''; +				$sql_where .= (($sql_where) ? ' OR ' : '') . 'search_authors ' . $db->sql_like_expression($db->any_char . ' ' . (int) $author . ' ' . $db->any_char);  			}  			$sql = 'SELECT search_key diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 9660fd6986..7cd06dee19 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -2,9 +2,8 @@  /**  *  * @package search -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -17,61 +16,93 @@ if (!defined('IN_PHPBB'))  }  /** -* @ignore -*/ -include_once($phpbb_root_path . 'includes/search/search.' . $phpEx); - -/**  * fulltext_mysql  * Fulltext search for MySQL  * @package search  */ -class fulltext_mysql extends search_backend +class phpbb_search_fulltext_mysql extends phpbb_search_base  { -	var $stats = array(); -	var $word_length = array(); -	var $split_words = array(); -	var $search_query; -	var $common_words = array(); -	var $pcre_properties = false; -	var $mbstring_regex = false; - -	function fulltext_mysql(&$error) +	protected $stats = array(); +	protected $split_words = array(); +	protected $config; +	protected $db; +	protected $user; +	protected $word_length = array(); +	protected $search_query; +	protected $common_words = array(); + +	/** +	 * Constructor +	 * Creates a new phpbb_search_fulltext_mysql, 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, $phpbb_root_path, $phpEx, $auth, $config, $db, $user)  	{ -		global $config; +		$this->config = $config; +		$this->db = $db; +		$this->user = $user; -		$this->word_length = array('min' => $config['fulltext_mysql_min_word_len'], 'max' => $config['fulltext_mysql_max_word_len']); +		$this->word_length = array('min' => $this->config['fulltext_mysql_min_word_len'], 'max' => $this->config['fulltext_mysql_max_word_len']); -		// PHP may not be linked with the bundled PCRE lib and instead with an older version -		if (pcre_utf8_support()) -		{ -			$this->pcre_properties = true; -		} +		$error = false; +	} -		if (function_exists('mb_ereg')) -		{ -			$this->mbstring_regex = true; -			mb_regex_encoding('UTF-8'); -		} +	/** +	* Returns the name of this search backend to be displayed to administrators +	* +	* @return string Name +	*/ +	public function get_name() +	{ +		return 'MySQL Fulltext'; +	} -		$error = false; +	/** +	 * Returns the search_query +	 * +	 * @return string search query +	 */ +	public function get_search_query() +	{ +		return $this->search_query; +	} + +	/** +	 * Returns the common_words array +	 * +	 * @return array common words that are ignored by search backend +	 */ +	public function get_common_words() +	{ +		return $this->common_words; +	} + +	/** +	 * Returns the word_length array +	 * +	 * @return array min and max word length for searching +	 */ +	public function get_word_length() +	{ +		return $this->word_length;  	}  	/**  	* Checks for correct MySQL version and stores min/max word length in the config +	* +	* @return string|bool Language key of the error/incompatiblity occured  	*/ -	function init() +	public function init()  	{ -		global $db, $user; - -		if ($db->sql_layer != 'mysql4' && $db->sql_layer != 'mysqli') +		if ($this->db->sql_layer != 'mysql4' && $this->db->sql_layer != 'mysqli')  		{ -			return $user->lang['FULLTEXT_MYSQL_INCOMPATIBLE_VERSION']; +			return $this->user->lang['FULLTEXT_MYSQL_INCOMPATIBLE_DATABASE'];  		} -		$result = $db->sql_query('SHOW TABLE STATUS LIKE \'' . POSTS_TABLE . '\''); -		$info = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); +		$result = $this->db->sql_query('SHOW TABLE STATUS LIKE \'' . POSTS_TABLE . '\''); +		$info = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result);  		$engine = '';  		if (isset($info['Engine'])) @@ -85,19 +116,19 @@ class fulltext_mysql extends search_backend  		if ($engine != 'MyISAM')  		{ -			return $user->lang['FULLTEXT_MYSQL_NOT_MYISAM']; +			return $this->user->lang['FULLTEXT_MYSQL_NOT_MYISAM'];  		}  		$sql = 'SHOW VARIABLES  			LIKE \'ft\_%\''; -		$result = $db->sql_query($sql); +		$result = $this->db->sql_query($sql);  		$mysql_info = array(); -		while ($row = $db->sql_fetchrow($result)) +		while ($row = $this->db->sql_fetchrow($result))  		{  			$mysql_info[$row['Variable_name']] = $row['Value'];  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result);  		set_config('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']);  		set_config('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']); @@ -113,10 +144,8 @@ class fulltext_mysql extends search_backend  	* @param string $terms is either 'all' or 'any'  	* @return bool false if no valid keywords were found and otherwise true  	*/ -	function split_keywords(&$keywords, $terms) +	public function split_keywords(&$keywords, $terms)  	{ -		global $config, $user; -  		if ($terms == 'all')  		{  			$match		= array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#'); @@ -129,45 +158,15 @@ class fulltext_mysql extends search_backend  		$split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords)));  		// Split words -		if ($this->pcre_properties) -		{ -			$split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); -		} -		else if ($this->mbstring_regex) -		{ -			$split_keywords = mb_ereg_replace('([^\w\'*"()])', '\\1\\1', str_replace('\'\'', '\' \'', trim($split_keywords))); -		} -		else -		{ -			$split_keywords = preg_replace('#([^\w\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); -		} - -		if ($this->pcre_properties) -		{ -			$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]; -		} -		else if ($this->mbstring_regex) -		{ -			mb_ereg_search_init($split_keywords, '(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)'); - -			while (($word = mb_ereg_search_regs())) -			{ -				$this->split_words[] = $word[1]; -			} -		} -		else -		{ -			$matches = array(); -			preg_match_all('#(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)#u', $split_keywords, $matches); -			$this->split_words = $matches[1]; -		} +		$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];  		// We limit the number of allowed keywords to minimize load on the database -		if ($config['max_num_search_keywords'] && sizeof($this->split_words) > $config['max_num_search_keywords']) +		if ($this->config['max_num_search_keywords'] && sizeof($this->split_words) > $this->config['max_num_search_keywords'])  		{ -			trigger_error($user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $config['max_num_search_keywords'], sizeof($this->split_words))); +			trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], sizeof($this->split_words)));  		}  		// to allow phrase search, we need to concatenate quoted words @@ -209,7 +208,7 @@ class fulltext_mysql extends search_backend  			// check word length  			$clean_len = utf8_strlen(str_replace('*', '', $clean_word)); -			if (($clean_len < $config['fulltext_mysql_min_word_len']) || ($clean_len > $config['fulltext_mysql_max_word_len'])) +			if (($clean_len < $this->config['fulltext_mysql_min_word_len']) || ($clean_len > $this->config['fulltext_mysql_max_word_len']))  			{  				$this->common_words[] = $word;  				unset($this->split_words[$i]); @@ -261,54 +260,22 @@ class fulltext_mysql extends search_backend  	/**  	* Turns text into an array of words +	* @param string $text contains post text/subject  	*/ -	function split_message($text) +	public function split_message($text)  	{ -		global $config; -  		// Split words -		if ($this->pcre_properties) -		{ -			$text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); -		} -		else if ($this->mbstring_regex) -		{ -			$text = mb_ereg_replace('([^\w\'*])', '\\1\\1', str_replace('\'\'', '\' \'', trim($text))); -		} -		else -		{ -			$text = preg_replace('#([^\w\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); -		} - -		if ($this->pcre_properties) -		{ -			$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]; -		} -		else if ($this->mbstring_regex) -		{ -			mb_ereg_search_init($text, '(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)'); - -			$text = array(); -			while (($word = mb_ereg_search_regs())) -			{ -				$text[] = $word[1]; -			} -		} -		else -		{ -			$matches = array(); -			preg_match_all('#(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)#u', $text, $matches); -			$text = $matches[1]; -		} +		$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_mysql_min_word_len'] || utf8_strlen($text[$i]) > $config['fulltext_mysql_max_word_len']) +			if (utf8_strlen($text[$i]) < $this->config['fulltext_mysql_min_word_len'] || utf8_strlen($text[$i]) > $this->config['fulltext_mysql_max_word_len'])  			{  				unset($text[$i]);  			} @@ -336,13 +303,9 @@ class fulltext_mysql extends search_backend  	* @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) +	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)  		{ @@ -431,7 +394,7 @@ class fulltext_mysql extends search_backend  		}  		else  		{ -			$m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; +			$m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $this->db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';  		}  		$sql_select			= (!$result_count) ? 'SQL_CALC_FOUND_ROWS ' : ''; @@ -441,11 +404,11 @@ class fulltext_mysql extends search_backend  		if (sizeof($author_ary) && $author_name)  		{  			// first one matches post of registered users, second one guests and deleted users -			$sql_author = ' AND (' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; +			$sql_author = ' AND (' . $this->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); +			$sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary);  		}  		else  		{ @@ -455,7 +418,7 @@ class fulltext_mysql extends search_backend  		$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 .= (sizeof($ex_fid_ary)) ? ' AND ' . $this->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)) : ''; @@ -463,16 +426,16 @@ class fulltext_mysql extends search_backend  		$sql = "SELECT $sql_select  			FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p -			WHERE MATCH ($sql_match) AGAINST ('" . $db->sql_escape(htmlspecialchars_decode($this->search_query)) . "' IN BOOLEAN MODE) +			WHERE MATCH ($sql_match) AGAINST ('" . $this->db->sql_escape(htmlspecialchars_decode($this->search_query)) . "' IN BOOLEAN MODE)  				$sql_where_options  			ORDER BY $sql_sort"; -		$result = $db->sql_query_limit($sql, $config['search_block_size'], $start); +		$result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); -		while ($row = $db->sql_fetchrow($result)) +		while ($row = $this->db->sql_fetchrow($result))  		{  			$id_ary[] = (int) $row[$field];  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result);  		$id_ary = array_unique($id_ary); @@ -485,9 +448,9 @@ class fulltext_mysql extends search_backend  		if (!$result_count)  		{  			$sql = 'SELECT FOUND_ROWS() as result_count'; -			$result = $db->sql_query($sql); -			$result_count = (int) $db->sql_fetchfield('result_count'); -			$db->sql_freeresult($result); +			$result = $this->db->sql_query($sql); +			$result_count = (int) $this->db->sql_fetchfield('result_count'); +			$this->db->sql_freeresult($result);  			if (!$result_count)  			{ @@ -520,13 +483,9 @@ class fulltext_mysql extends search_backend  	* @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) +	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))  		{ @@ -562,13 +521,13 @@ class fulltext_mysql extends search_backend  		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 . ')'; +			$sql_author = '(' . $this->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_author = $this->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_fora		= (sizeof($ex_fid_ary)) ? ' AND ' . $this->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' : ''; @@ -604,7 +563,7 @@ class fulltext_mysql extends search_backend  		}  		else  		{ -			$m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; +			$m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $this->db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';  		}  		// If the cache was completely empty count the results @@ -643,21 +602,21 @@ class fulltext_mysql extends search_backend  		}  		// Only read one block of posts from the db and then cache it -		$result = $db->sql_query_limit($sql, $config['search_block_size'], $start); +		$result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); -		while ($row = $db->sql_fetchrow($result)) +		while ($row = $this->db->sql_fetchrow($result))  		{  			$id_ary[] = (int) $row[$field];  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result);  		// retrieve the total result count if needed  		if (!$result_count)  		{  			$sql = 'SELECT FOUND_ROWS() as result_count'; -			$result = $db->sql_query($sql); -			$result_count = (int) $db->sql_fetchfield('result_count'); -			$db->sql_freeresult($result); +			$result = $this->db->sql_query($sql); +			$result_count = (int) $this->db->sql_fetchfield('result_count'); +			$this->db->sql_freeresult($result);  			if (!$result_count)  			{ @@ -678,12 +637,15 @@ class fulltext_mysql extends search_backend  	/**  	* 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	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  	*/ -	function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) +	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(); @@ -702,18 +664,16 @@ class fulltext_mysql extends search_backend  	/**  	* Destroy cached results, that might be outdated after deleting a post  	*/ -	function index_remove($post_ids, $author_ids, $forum_ids) +	public function index_remove($post_ids, $author_ids, $forum_ids)  	{ -		$this->destroy_cache(array(), $author_ids); +		$this->destroy_cache(array(), array_unique($author_ids));  	}  	/**  	* Destroy old cache entries  	*/ -	function tidy() +	public function tidy()  	{ -		global $db, $config; -  		// destroy too old cached search results  		$this->destroy_cache(array()); @@ -722,11 +682,11 @@ class fulltext_mysql extends search_backend  	/**  	* Create fulltext index +	* +	* @return string|bool error string is returned incase of errors otherwise false  	*/ -	function create_index($acp_module, $u_action) +	public function create_index($acp_module, $u_action)  	{ -		global $db; -  		// Make sure we can actually use MySQL with fulltext indexes  		if ($error = $this->init())  		{ @@ -742,9 +702,9 @@ class fulltext_mysql extends search_backend  		if (!isset($this->stats['post_subject']))  		{ -			if ($db->sql_layer == 'mysqli' || version_compare($db->sql_server_info(true), '4.1.3', '>=')) +			if ($this->db->sql_layer == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>='))  			{ -				//$alter[] = 'MODIFY post_subject varchar(100) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL'; +				$alter[] = 'MODIFY post_subject varchar(255) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL';  			}  			else  			{ @@ -755,7 +715,7 @@ class fulltext_mysql extends search_backend  		if (!isset($this->stats['post_text']))  		{ -			if ($db->sql_layer == 'mysqli' || version_compare($db->sql_server_info(true), '4.1.3', '>=')) +			if ($this->db->sql_layer == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>='))  			{  				$alter[] = 'MODIFY post_text mediumtext COLLATE utf8_unicode_ci NOT NULL';  			} @@ -773,21 +733,21 @@ class fulltext_mysql extends search_backend  		if (sizeof($alter))  		{ -			$db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter)); +			$this->db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter));  		} -		$db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); +		$this->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  	*/ -	function delete_index($acp_module, $u_action) +	public function delete_index($acp_module, $u_action)  	{ -		global $db; -  		// Make sure we can actually use MySQL with fulltext indexes  		if ($error = $this->init())  		{ @@ -818,10 +778,10 @@ class fulltext_mysql extends search_backend  		if (sizeof($alter))  		{ -			$db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter)); +			$this->db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter));  		} -		$db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); +		$this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);  		return false;  	} @@ -829,7 +789,7 @@ class fulltext_mysql extends search_backend  	/**  	* Returns true if both FULLTEXT indexes exist  	*/ -	function index_created() +	public function index_created()  	{  		if (empty($this->stats))  		{ @@ -842,25 +802,24 @@ class fulltext_mysql extends search_backend  	/**  	* Returns an associative array containing information about the indexes  	*/ -	function index_stats() +	public function index_stats()  	{ -		global $user; -  		if (empty($this->stats))  		{  			$this->get_stats();  		}  		return array( -			$user->lang['FULLTEXT_MYSQL_TOTAL_POSTS']			=> ($this->index_created()) ? $this->stats['total_posts'] : 0, +			$this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS']			=> ($this->index_created()) ? $this->stats['total_posts'] : 0,  		);  	} -	function get_stats() +	/** +	 * Computes the stats and store them in the $this->stats associative array +	 */ +	protected function get_stats()  	{ -		global $db; - -		if (strpos($db->sql_layer, 'mysql') === false) +		if (strpos($this->db->sql_layer, 'mysql') === false)  		{  			$this->stats = array();  			return; @@ -868,9 +827,9 @@ class fulltext_mysql extends search_backend  		$sql = 'SHOW INDEX  			FROM ' . POSTS_TABLE; -		$result = $db->sql_query($sql); +		$result = $this->db->sql_query($sql); -		while ($row = $db->sql_fetchrow($result)) +		while ($row = $this->db->sql_fetchrow($result))  		{  			// deal with older MySQL versions which didn't use Index_type  			$index_type = (isset($row['Index_type'])) ? $row['Index_type'] : $row['Comment']; @@ -891,38 +850,26 @@ class fulltext_mysql extends search_backend  				}  			}  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result); -		$sql = 'SELECT COUNT(post_id) as total_posts -			FROM ' . POSTS_TABLE; -		$result = $db->sql_query($sql); -		$this->stats['total_posts'] = (int) $db->sql_fetchfield('total_posts'); -		$db->sql_freeresult($result); +		$this->stats['total_posts'] = empty($this->stats) ? 0 : $this->db->get_estimated_row_count(POSTS_TABLE);  	}  	/**  	* Display a note, that UTF-8 support is not available with certain versions of PHP +	* +	* @return associative array containing template and config variables  	*/ -	function acp() +	public function acp()  	{ -		global $user, $config; -  		$tpl = '  		<dl> -			<dt><label>' . $user->lang['FULLTEXT_MYSQL_PCRE'] . '</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_PCRE_EXPLAIN'] . '</span></dt> -			<dd>' . (($this->pcre_properties) ? $user->lang['YES'] : $user->lang['NO']) . ' (PHP ' . PHP_VERSION . ')</dd> -		</dl> -		<dl> -			<dt><label>' . $user->lang['FULLTEXT_MYSQL_MBSTRING'] . '</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_MBSTRING_EXPLAIN'] . '</span></dt> -			<dd>' . (($this->mbstring_regex) ? $user->lang['YES'] : $user->lang['NO']). '</dd> -		</dl> -		<dl> -			<dt><label>' . $user->lang['MIN_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN'] . '</span></dt> -			<dd>' . $config['fulltext_mysql_min_word_len'] . '</dd> +			<dt><label>' . $this->user->lang['MIN_SEARCH_CHARS'] . ':</label><br /><span>' . $this->user->lang['FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN'] . '</span></dt> +			<dd>' . $this->config['fulltext_mysql_min_word_len'] . '</dd>  		</dl>  		<dl> -			<dt><label>' . $user->lang['MAX_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN'] . '</span></dt> -			<dd>' . $config['fulltext_mysql_max_word_len'] . '</dd> +			<dt><label>' . $this->user->lang['MAX_SEARCH_CHARS'] . ':</label><br /><span>' . $this->user->lang['FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN'] . '</span></dt> +			<dd>' . $this->config['fulltext_mysql_max_word_len'] . '</dd>  		</dl>  		'; diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php index e749e86f68..bbc2236b3c 100644 --- a/phpBB/includes/search/fulltext_native.php +++ b/phpBB/includes/search/fulltext_native.php @@ -2,9 +2,8 @@  /**  *  * @package search -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -17,52 +16,94 @@ if (!defined('IN_PHPBB'))  }  /** -* @ignore -*/ -include_once($phpbb_root_path . 'includes/search/search.' . $phpEx); - -/**  * fulltext_native  * phpBB's own db driven fulltext search, version 2  * @package search  */ -class fulltext_native extends search_backend +class phpbb_search_fulltext_native extends phpbb_search_base  { -	var $stats = array(); -	var $word_length = array(); -	var $search_query; -	var $common_words = array(); +	protected $stats = array(); +	protected $word_length = array(); +	protected $search_query; +	protected $common_words = array(); + +	protected $must_contain_ids = array(); +	protected $must_not_contain_ids = array(); +	protected $must_exclude_one_ids = array(); -	var $must_contain_ids = array(); -	var $must_not_contain_ids = array(); -	var $must_exclude_one_ids = array(); +	protected $phpbb_root_path; +	protected $php_ext; +	protected $config; +	protected $db; +	protected $user;  	/**  	* Initialises the fulltext_native search backend with min/max word length and makes sure the UTF-8 normalizer is loaded.  	*  	* @param	boolean|string	&$error	is passed by reference and should either be set to false on success or an error message on failure. -	* -	* @access	public  	*/ -	function fulltext_native(&$error) +	public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user)  	{ -		global $phpbb_root_path, $phpEx, $config; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $phpEx; +		$this->config = $config; +		$this->db = $db; +		$this->user = $user; -		$this->word_length = array('min' => $config['fulltext_native_min_chars'], 'max' => $config['fulltext_native_max_chars']); +		$this->word_length = array('min' => $this->config['fulltext_native_min_chars'], 'max' => $this->config['fulltext_native_max_chars']);  		/**  		* Load the UTF tools  		*/  		if (!class_exists('utf_normalizer'))  		{ -			include($phpbb_root_path . 'includes/utf/utf_normalizer.' . $phpEx); +			include($this->phpbb_root_path . 'includes/utf/utf_normalizer.' . $this->php_ext);  		} -  		$error = false;  	}  	/** +	* Returns the name of this search backend to be displayed to administrators +	* +	* @return string Name +	*/ +	public function get_name() +	{ +		return 'phpBB Native Fulltext'; +	} + +	/** +	 * Returns the search_query +	 * +	 * @return string search query +	 */ +	public function get_search_query() +	{ +		return $this->search_query; +	} + +	/** +	 * Returns the common_words array +	 * +	 * @return array common words that are ignored by search backend +	 */ +	public function get_common_words() +	{ +		return $this->common_words; +	} + +	/** +	 * Returns the word_length array +	 * +	 * @return array min and max word length for searching +	 */ +	public function get_word_length() +	{ +		return $this->word_length; +	} + +	/**  	* This function fills $this->search_query with the cleaned user search query.  	*  	* If $terms is 'any' then the words will be extracted from the search query @@ -76,13 +117,9 @@ class fulltext_native extends search_backend  	* @param	string	$terms		is either 'all' (use search query as entered, default words to 'must be contained in post')  	* 	or 'any' (find all posts containing at least one of the given words)  	* @return	boolean				false if no valid keywords were found and otherwise true -	* -	* @access	public  	*/ -	function split_keywords($keywords, $terms) +	public function split_keywords($keywords, $terms)  	{ -		global $db, $user, $config; -  		$tokens = '+-|()*';  		$keywords = trim($this->cleanup($keywords, $tokens)); @@ -181,9 +218,9 @@ class fulltext_native extends search_backend  		$num_keywords = sizeof(explode(' ', $keywords));  		// We limit the number of allowed keywords to minimize load on the database -		if ($config['max_num_search_keywords'] && $num_keywords > $config['max_num_search_keywords']) +		if ($this->config['max_num_search_keywords'] && $num_keywords > $this->config['max_num_search_keywords'])  		{ -			trigger_error($user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $config['max_num_search_keywords'], $num_keywords)); +			trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], $num_keywords));  		}  		// $keywords input format: each word separated by a space, words in a bracket are not separated @@ -213,12 +250,12 @@ class fulltext_native extends search_backend  		{  			$sql = 'SELECT word_id, word_text, word_common  				FROM ' . SEARCH_WORDLIST_TABLE . ' -				WHERE ' . $db->sql_in_set('word_text', $exact_words) . ' +				WHERE ' . $this->db->sql_in_set('word_text', $exact_words) . '  				ORDER BY word_count ASC'; -			$result = $db->sql_query($sql); +			$result = $this->db->sql_query($sql);  			// store an array of words and ids, remove common words -			while ($row = $db->sql_fetchrow($result)) +			while ($row = $this->db->sql_fetchrow($result))  			{  				if ($row['word_common'])  				{ @@ -229,7 +266,7 @@ class fulltext_native extends search_backend  				$words[$row['word_text']] = (int) $row['word_id'];  			} -			$db->sql_freeresult($result); +			$this->db->sql_freeresult($result);  		}  		unset($exact_words); @@ -300,7 +337,7 @@ class fulltext_native extends search_backend  				{  					if (strpos($word_part, '*') !== false)  					{ -						$id_words[] = '\'' . $db->sql_escape(str_replace('*', '%', $word_part)) . '\''; +						$id_words[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word_part)) . '\'';  						$non_common_words[] = $word_part;  					}  					else if (isset($words[$word_part])) @@ -333,7 +370,7 @@ class fulltext_native extends search_backend  				// throw an error if we shall not ignore unexistant words  				else if (!$ignore_no_id && sizeof($non_common_words))  				{ -					trigger_error(sprintf($user->lang['WORDS_IN_NO_POST'], implode(', ', $non_common_words))); +					trigger_error(sprintf($user->lang['WORDS_IN_NO_POST'], implode($user->lang['COMMA_SEPARATOR'], $non_common_words)));  				}  				unset($non_common_words);  			} @@ -345,7 +382,7 @@ class fulltext_native extends search_backend  					$len = utf8_strlen(str_replace('*', '', $word));  					if ($len >= $this->word_length['min'] && $len <= $this->word_length['max'])  					{ -						$this->{$mode . '_ids'}[] = '\'' . $db->sql_escape(str_replace('*', '%', $word)) . '\''; +						$this->{$mode . '_ids'}[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word)) . '\'';  					}  					else  					{ @@ -365,7 +402,7 @@ class fulltext_native extends search_backend  					$len = utf8_strlen($word);  					if ($len >= $this->word_length['min'] && $len <= $this->word_length['max'])  					{ -						trigger_error(sprintf($user->lang['WORD_IN_NO_POST'], $word)); +						trigger_error(sprintf($this->user->lang['WORD_IN_NO_POST'], $word));  					}  					else  					{ @@ -415,13 +452,9 @@ class fulltext_native extends search_backend  	* @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) +	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 (empty($this->search_query))  		{ @@ -536,7 +569,7 @@ class fulltext_native extends search_backend  					}  				} -				$sql_where[] = $db->sql_in_set("m$m_num.word_id", $word_ids); +				$sql_where[] = $this->db->sql_in_set("m$m_num.word_id", $word_ids);  				unset($word_id_sql);  				unset($word_ids); @@ -590,7 +623,7 @@ class fulltext_native extends search_backend  		{  			$sql_array['LEFT_JOIN'][] = array(  				'FROM'	=> array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), -				'ON'	=> $db->sql_in_set("m$m_num.word_id", $this->must_not_contain_ids) . (($title_match) ? " AND m$m_num.$title_match" : '') . " AND m$m_num.post_id = m0.post_id" +				'ON'	=> $this->db->sql_in_set("m$m_num.word_id", $this->must_not_contain_ids) . (($title_match) ? " AND m$m_num.$title_match" : '') . " AND m$m_num.post_id = m0.post_id"  			);  			$sql_where[] = "m$m_num.word_id IS NULL"; @@ -631,7 +664,7 @@ class fulltext_native extends search_backend  		}  		else if ($m_approve_fid_ary !== array(-1))  		{ -			$sql_where[] = '(p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; +			$sql_where[] = '(p.post_approved = 1 OR ' . $this->db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';  		}  		if ($topic_id) @@ -644,18 +677,18 @@ class fulltext_native extends search_backend  			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 . ')'; +				$sql_author = '(' . $this->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_author = $this->db->sql_in_set('p.poster_id', $author_ary);  			}  			$sql_where[] = $sql_author;  		}  		if (sizeof($ex_fid_ary))  		{ -			$sql_where[] = $db->sql_in_set('p.forum_id', $ex_fid_ary, true); +			$sql_where[] = $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true);  		}  		if ($sort_days) @@ -680,7 +713,7 @@ class fulltext_native extends search_backend  				);  			} -			switch ($db->sql_layer) +			switch ($this->db->sql_layer)  			{  				case 'mysql4':  				case 'mysqli': @@ -694,17 +727,17 @@ class fulltext_native extends search_backend  				case 'sqlite':  					$sql_array_count['SELECT'] = ($type == 'posts') ? 'DISTINCT p.post_id' : 'DISTINCT p.topic_id';  					$sql = 'SELECT COUNT(' . (($type == 'posts') ? 'post_id' : 'topic_id') . ') as total_results -							FROM (' . $db->sql_build_query('SELECT', $sql_array_count) . ')'; +							FROM (' . $this->db->sql_build_query('SELECT', $sql_array_count) . ')';  				// no break  				default:  					$sql_array_count['SELECT'] = ($type == 'posts') ? 'COUNT(DISTINCT p.post_id) AS total_results' : 'COUNT(DISTINCT p.topic_id) AS total_results'; -					$sql = (!$sql) ? $db->sql_build_query('SELECT', $sql_array_count) : $sql; +					$sql = (!$sql) ? $this->db->sql_build_query('SELECT', $sql_array_count) : $sql; -					$result = $db->sql_query($sql); -					$total_results = (int) $db->sql_fetchfield('total_results'); -					$db->sql_freeresult($result); +					$result = $this->db->sql_query($sql); +					$total_results = (int) $this->db->sql_fetchfield('total_results'); +					$this->db->sql_freeresult($result);  					if (!$total_results)  					{ @@ -750,14 +783,14 @@ class fulltext_native extends search_backend  		unset($sql_where, $sql_sort, $group_by); -		$sql = $db->sql_build_query('SELECT', $sql_array); -		$result = $db->sql_query_limit($sql, $config['search_block_size'], $start); +		$sql = $this->db->sql_build_query('SELECT', $sql_array); +		$result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); -		while ($row = $db->sql_fetchrow($result)) +		while ($row = $this->db->sql_fetchrow($result))  		{  			$id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')];  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result);  		if (!sizeof($id_ary))  		{ @@ -771,16 +804,16 @@ class fulltext_native extends search_backend  			$sql_array_copy = $sql_array;  			$sql_array_copy['SELECT'] = 'SQL_CALC_FOUND_ROWS p.post_id '; -			$sql = $db->sql_build_query('SELECT', $sql_array_copy); +			$sql = $this->db->sql_build_query('SELECT', $sql_array_copy);  			unset($sql_array_copy); -			$db->sql_query($sql); -			$db->sql_freeresult($result); +			$this->db->sql_query($sql); +			$this->db->sql_freeresult($result);  			$sql = 'SELECT FOUND_ROWS() as total_results'; -			$result = $db->sql_query($sql); -			$total_results = (int) $db->sql_fetchfield('total_results'); -			$db->sql_freeresult($result); +			$result = $this->db->sql_query($sql); +			$total_results = (int) $this->db->sql_fetchfield('total_results'); +			$this->db->sql_freeresult($result);  			if (!$total_results)  			{ @@ -813,13 +846,9 @@ class fulltext_native extends search_backend  	* @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) +	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))  		{ @@ -855,13 +884,13 @@ class fulltext_native extends search_backend  		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 . ')'; +			$sql_author = '(' . $this->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_author = $this->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_fora		= (sizeof($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : '';  		$sql_time		= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : '';  		$sql_topic_id	= ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : '';  		$sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; @@ -897,7 +926,7 @@ class fulltext_native extends search_backend  		}  		else  		{ -			$m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; +			$m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $this->db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';  		}  		$select = ($type == 'posts') ? 'p.post_id' : 't.topic_id'; @@ -906,7 +935,7 @@ class fulltext_native extends search_backend  		// If the cache was completely empty count the results  		if (!$total_results)  		{ -			switch ($db->sql_layer) +			switch ($this->db->sql_layer)  			{  				case 'mysql4':  				case 'mysqli': @@ -928,7 +957,7 @@ class fulltext_native extends search_backend  					}  					else  					{ -						if ($db->sql_layer == 'sqlite') +						if ($this->db->sql_layer == 'sqlite')  						{  							$sql = 'SELECT COUNT(topic_id) as total_results  								FROM (SELECT DISTINCT t.topic_id'; @@ -945,12 +974,12 @@ class fulltext_native extends search_backend  								$m_approve_fid_sql  								$sql_fora  								AND t.topic_id = p.topic_id -								$sql_time" . (($db->sql_layer == 'sqlite') ? ')' : ''); +								$sql_time" . (($this->db->sql_layer == 'sqlite') ? ')' : '');  					} -					$result = $db->sql_query($sql); +					$result = $this->db->sql_query($sql); -					$total_results = (int) $db->sql_fetchfield('total_results'); -					$db->sql_freeresult($result); +					$total_results = (int) $this->db->sql_fetchfield('total_results'); +					$this->db->sql_freeresult($result);  					if (!$total_results)  					{ @@ -993,26 +1022,26 @@ class fulltext_native extends search_backend  		}  		// Only read one block of posts from the db and then cache it -		$result = $db->sql_query_limit($sql, $config['search_block_size'], $start); +		$result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); -		while ($row = $db->sql_fetchrow($result)) +		while ($row = $this->db->sql_fetchrow($result))  		{  			$id_ary[] = (int) $row[$field];  		} -		$db->sql_freeresult($result); +		$this->db->sql_freeresult($result);  		if (!$total_results && $is_mysql)  		{  			// Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it.  			$sql = str_replace('SELECT ' . $select, 'SELECT DISTINCT SQL_CALC_FOUND_ROWS p.post_id', $sql); -			$db->sql_query($sql); -			$db->sql_freeresult($result); +			$this->db->sql_query($sql); +			$this->db->sql_freeresult($result);  			$sql = 'SELECT FOUND_ROWS() as total_results'; -			$result = $db->sql_query($sql); -			$total_results = (int) $db->sql_fetchfield('total_results'); -			$db->sql_freeresult($result); +			$result = $this->db->sql_query($sql); +			$total_results = (int) $this->db->sql_fetchfield('total_results'); +			$this->db->sql_freeresult($result);  			if (!$total_results)  			{ @@ -1040,13 +1069,9 @@ class fulltext_native extends search_backend  	*  	* @param	string	$text	Text to split, encoded in UTF-8  	* @return	array			Array of UTF-8 words -	* -	* @access	private  	*/ -	function split_message($text) +	public function split_message($text)  	{ -		global $phpbb_root_path, $phpEx, $user; -  		$match = $words = array();  		/** @@ -1119,14 +1144,10 @@ class fulltext_native extends search_backend  	* @param	string	&$subject	New or updated post subject  	* @param	int		$poster_id	Post author's user id  	* @param	int		$forum_id	The id of the forum in which the post is located -	* -	* @access	public  	*/ -	function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) +	public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id)  	{ -		global $config, $db, $user; - -		if (!$config['fulltext_native_load_upd']) +		if (!$this->config['fulltext_native_load_upd'])  		{  			/**  			* The search indexer is disabled, return @@ -1152,14 +1173,14 @@ class fulltext_native extends search_backend  				FROM ' . SEARCH_WORDLIST_TABLE . ' w, ' . SEARCH_WORDMATCH_TABLE . " m  				WHERE m.post_id = $post_id  					AND w.word_id = m.word_id"; -			$result = $db->sql_query($sql); +			$result = $this->db->sql_query($sql); -			while ($row = $db->sql_fetchrow($result)) +			while ($row = $this->db->sql_fetchrow($result))  			{  				$which = ($row['title_match']) ? 'title' : 'post';  				$cur_words[$which][$row['word_text']] = $row['word_id'];  			} -			$db->sql_freeresult($result); +			$this->db->sql_freeresult($result);  			$words['add']['post'] = array_diff($split_text, array_keys($cur_words['post']));  			$words['add']['title'] = array_diff($split_title, array_keys($cur_words['title'])); @@ -1187,18 +1208,18 @@ class fulltext_native extends search_backend  		{  			$sql = 'SELECT word_id, word_text  				FROM ' . SEARCH_WORDLIST_TABLE . ' -				WHERE ' . $db->sql_in_set('word_text', $unique_add_words); -			$result = $db->sql_query($sql); +				WHERE ' . $this->db->sql_in_set('word_text', $unique_add_words); +			$result = $this->db->sql_query($sql);  			$word_ids = array(); -			while ($row = $db->sql_fetchrow($result)) +			while ($row = $this->db->sql_fetchrow($result))  			{  				$word_ids[$row['word_text']] = $row['word_id'];  			} -			$db->sql_freeresult($result); +			$this->db->sql_freeresult($result);  			$new_words = array_diff($unique_add_words, array_keys($word_ids)); -			$db->sql_transaction('begin'); +			$this->db->sql_transaction('begin');  			if (sizeof($new_words))  			{  				$sql_ary = array(); @@ -1207,15 +1228,15 @@ class fulltext_native extends search_backend  				{  					$sql_ary[] = array('word_text' => (string) $word, 'word_count' => 0);  				} -				$db->sql_return_on_error(true); -				$db->sql_multi_insert(SEARCH_WORDLIST_TABLE, $sql_ary); -				$db->sql_return_on_error(false); +				$this->db->sql_return_on_error(true); +				$this->db->sql_multi_insert(SEARCH_WORDLIST_TABLE, $sql_ary); +				$this->db->sql_return_on_error(false);  			}  			unset($new_words, $sql_ary);  		}  		else  		{ -			$db->sql_transaction('begin'); +			$this->db->sql_transaction('begin');  		}  		// now update the search match table, remove links to removed words and add links to new words @@ -1232,22 +1253,22 @@ class fulltext_native extends search_backend  				}  				$sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' -					WHERE ' . $db->sql_in_set('word_id', $sql_in) . ' +					WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . '  						AND post_id = ' . intval($post_id) . "  						AND title_match = $title_match"; -				$db->sql_query($sql); +				$this->db->sql_query($sql);  				$sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '  					SET word_count = word_count - 1 -					WHERE ' . $db->sql_in_set('word_id', $sql_in) . ' +					WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . '  						AND word_count > 0'; -				$db->sql_query($sql); +				$this->db->sql_query($sql);  				unset($sql_in);  			}  		} -		$db->sql_return_on_error(true); +		$this->db->sql_return_on_error(true);  		foreach ($words['add'] as $word_in => $word_ary)  		{  			$title_match = ($word_in == 'title') ? 1 : 0; @@ -1257,18 +1278,18 @@ class fulltext_native extends search_backend  				$sql = 'INSERT INTO ' . SEARCH_WORDMATCH_TABLE . ' (post_id, word_id, title_match)  					SELECT ' . (int) $post_id . ', word_id, ' . (int) $title_match . '  					FROM ' . SEARCH_WORDLIST_TABLE . ' -					WHERE ' . $db->sql_in_set('word_text', $word_ary); -				$db->sql_query($sql); +					WHERE ' . $this->db->sql_in_set('word_text', $word_ary); +				$this->db->sql_query($sql);  				$sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '  					SET word_count = word_count + 1 -					WHERE ' . $db->sql_in_set('word_text', $word_ary); -				$db->sql_query($sql); +					WHERE ' . $this->db->sql_in_set('word_text', $word_ary); +				$this->db->sql_query($sql);  			}  		} -		$db->sql_return_on_error(false); +		$this->db->sql_return_on_error(false); -		$db->sql_transaction('commit'); +		$this->db->sql_transaction('commit');  		// destroy cached search results containing any of the words removed or added  		$this->destroy_cache(array_unique(array_merge($words['add']['post'], $words['add']['title'], $words['del']['post'], $words['del']['title'])), array($poster_id)); @@ -1281,20 +1302,18 @@ class fulltext_native extends search_backend  	/**  	* Removes entries from the wordmatch table for the specified post_ids  	*/ -	function index_remove($post_ids, $author_ids, $forum_ids) +	public function index_remove($post_ids, $author_ids, $forum_ids)  	{ -		global $db; -  		if (sizeof($post_ids))  		{  			$sql = 'SELECT w.word_id, w.word_text, m.title_match  				FROM ' . SEARCH_WORDMATCH_TABLE . ' m, ' . SEARCH_WORDLIST_TABLE . ' w -				WHERE ' . $db->sql_in_set('m.post_id', $post_ids) . ' +				WHERE ' . $this->db->sql_in_set('m.post_id', $post_ids) . '  					AND w.word_id = m.word_id'; -			$result = $db->sql_query($sql); +			$result = $this->db->sql_query($sql);  			$message_word_ids = $title_word_ids = $word_texts = array(); -			while ($row = $db->sql_fetchrow($result)) +			while ($row = $this->db->sql_fetchrow($result))  			{  				if ($row['title_match'])  				{ @@ -1306,48 +1325,46 @@ class fulltext_native extends search_backend  				}  				$word_texts[] = $row['word_text'];  			} -			$db->sql_freeresult($result); +			$this->db->sql_freeresult($result);  			if (sizeof($title_word_ids))  			{  				$sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '  					SET word_count = word_count - 1 -					WHERE ' . $db->sql_in_set('word_id', $title_word_ids) . ' +					WHERE ' . $this->db->sql_in_set('word_id', $title_word_ids) . '  						AND word_count > 0'; -				$db->sql_query($sql); +				$this->db->sql_query($sql);  			}  			if (sizeof($message_word_ids))  			{  				$sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '  					SET word_count = word_count - 1 -					WHERE ' . $db->sql_in_set('word_id', $message_word_ids) . ' +					WHERE ' . $this->db->sql_in_set('word_id', $message_word_ids) . '  						AND word_count > 0'; -				$db->sql_query($sql); +				$this->db->sql_query($sql);  			}  			unset($title_word_ids);  			unset($message_word_ids);  			$sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' -				WHERE ' . $db->sql_in_set('post_id', $post_ids); -			$db->sql_query($sql); +				WHERE ' . $this->db->sql_in_set('post_id', $post_ids); +			$this->db->sql_query($sql);  		} -		$this->destroy_cache(array_unique($word_texts), $author_ids); +		$this->destroy_cache(array_unique($word_texts), array_unique($author_ids));  	}  	/**  	* Tidy up indexes: Tag 'common words' and remove  	* words no longer referenced in the match table  	*/ -	function tidy() +	public function tidy()  	{ -		global $db, $config; -  		// Is the fulltext indexer disabled? If yes then we need not  		// carry on ... it's okay ... I know when I'm not wanted boo hoo -		if (!$config['fulltext_native_load_upd']) +		if (!$this->config['fulltext_native_load_upd'])  		{  			set_config('search_last_gc', time(), true);  			return; @@ -1356,31 +1373,31 @@ class fulltext_native extends search_backend  		$destroy_cache_words = array();  		// Remove common words -		if ($config['num_posts'] >= 100 && $config['fulltext_native_common_thres']) +		if ($this->config['num_posts'] >= 100 && $this->config['fulltext_native_common_thres'])  		{ -			$common_threshold = ((double) $config['fulltext_native_common_thres']) / 100.0; +			$common_threshold = ((double) $this->config['fulltext_native_common_thres']) / 100.0;  			// First, get the IDs of common words  			$sql = 'SELECT word_id, word_text  				FROM ' . SEARCH_WORDLIST_TABLE . ' -				WHERE word_count > ' . floor($config['num_posts'] * $common_threshold) . ' +				WHERE word_count > ' . floor($this->config['num_posts'] * $common_threshold) . '  					OR word_common = 1'; -			$result = $db->sql_query($sql); +			$result = $this->db->sql_query($sql);  			$sql_in = array(); -			while ($row = $db->sql_fetchrow($result)) +			while ($row = $this->db->sql_fetchrow($result))  			{  				$sql_in[] = $row['word_id'];  				$destroy_cache_words[] = $row['word_text'];  			} -			$db->sql_freeresult($result); +			$this->db->sql_freeresult($result);  			if (sizeof($sql_in))  			{  				// Flag the words  				$sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '  					SET word_common = 1 -					WHERE ' . $db->sql_in_set('word_id', $sql_in); -				$db->sql_query($sql); +					WHERE ' . $this->db->sql_in_set('word_id', $sql_in); +				$this->db->sql_query($sql);  				// by setting search_last_gc to the new time here we make sure that if a user reloads because the  				// following query takes too long, he won't run into it again @@ -1388,8 +1405,8 @@ class fulltext_native extends search_backend  				// Delete the matches  				$sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' -					WHERE ' . $db->sql_in_set('word_id', $sql_in); -				$db->sql_query($sql); +					WHERE ' . $this->db->sql_in_set('word_id', $sql_in); +				$this->db->sql_query($sql);  			}  			unset($sql_in);  		} @@ -1406,23 +1423,21 @@ class fulltext_native extends search_backend  	/**  	* Deletes all words from the index  	*/ -	function delete_index($acp_module, $u_action) +	public function delete_index($acp_module, $u_action)  	{ -		global $db; - -		switch ($db->sql_layer) +		switch ($this->db->sql_layer)  		{  			case 'sqlite':  			case 'firebird': -				$db->sql_query('DELETE FROM ' . SEARCH_WORDLIST_TABLE); -				$db->sql_query('DELETE FROM ' . SEARCH_WORDMATCH_TABLE); -				$db->sql_query('DELETE FROM ' . SEARCH_RESULTS_TABLE); +				$this->db->sql_query('DELETE FROM ' . SEARCH_WORDLIST_TABLE); +				$this->db->sql_query('DELETE FROM ' . SEARCH_WORDMATCH_TABLE); +				$this->db->sql_query('DELETE FROM ' . SEARCH_RESULTS_TABLE);  			break;  			default: -				$db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDLIST_TABLE); -				$db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDMATCH_TABLE); -				$db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); +				$this->db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDLIST_TABLE); +				$this->db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDMATCH_TABLE); +				$this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);  			break;  		}  	} @@ -1430,7 +1445,7 @@ class fulltext_native extends search_backend  	/**  	* Returns true if both FULLTEXT indexes exist  	*/ -	function index_created() +	public function index_created()  	{  		if (!sizeof($this->stats))  		{ @@ -1443,35 +1458,22 @@ class fulltext_native extends search_backend  	/**  	* Returns an associative array containing information about the indexes  	*/ -	function index_stats() +	public function index_stats()  	{ -		global $user; -  		if (!sizeof($this->stats))  		{  			$this->get_stats();  		}  		return array( -			$user->lang['TOTAL_WORDS']		=> $this->stats['total_words'], -			$user->lang['TOTAL_MATCHES']	=> $this->stats['total_matches']); +			$this->user->lang['TOTAL_WORDS']		=> $this->stats['total_words'], +			$this->user->lang['TOTAL_MATCHES']	=> $this->stats['total_matches']);  	} -	function get_stats() +	protected function get_stats()  	{ -		global $db; - -		$sql = 'SELECT COUNT(*) as total_words -			FROM ' . SEARCH_WORDLIST_TABLE; -		$result = $db->sql_query($sql); -		$this->stats['total_words'] = (int) $db->sql_fetchfield('total_words'); -		$db->sql_freeresult($result); - -		$sql = 'SELECT COUNT(*) as total_matches -			FROM ' . SEARCH_WORDMATCH_TABLE; -		$result = $db->sql_query($sql); -		$this->stats['total_matches'] = (int) $db->sql_fetchfield('total_matches'); -		$db->sql_freeresult($result); +		$this->stats['total_words']		= $this->db->get_estimated_row_count(SEARCH_WORDLIST_TABLE); +		$this->stats['total_matches']	= $this->db->get_estimated_row_count(SEARCH_WORDMATCH_TABLE);  	}  	/** @@ -1489,9 +1491,8 @@ class fulltext_native extends search_backend  	*  	* @todo normalizer::cleanup being able to be used?  	*/ -	function cleanup($text, $allowed_chars = null, $encoding = 'utf-8') +	protected function cleanup($text, $allowed_chars = null, $encoding = 'utf-8')  	{ -		global $phpbb_root_path, $phpEx;  		static $conv = array(), $conv_loaded = array();  		$words = $allow = array(); @@ -1688,7 +1689,7 @@ class fulltext_native extends search_backend  			if (!isset($conv_loaded[$idx]))  			{  				$conv_loaded[$idx] = 1; -				$file = $phpbb_root_path . 'includes/utf/data/search_indexer_' . $idx . '.' . $phpEx; +				$file = $this->phpbb_root_path . 'includes/utf/data/search_indexer_' . $idx . '.' . $this->php_ext;  				if (file_exists($file))  				{ @@ -1719,31 +1720,28 @@ class fulltext_native extends search_backend  	/**  	* Returns a list of options for the ACP to display  	*/ -	function acp() +	public function acp()  	{ -		global $user, $config; - -  		/**  		* if we need any options, copied from fulltext_native for now, will have to be adjusted or removed  		*/  		$tpl = '  		<dl> -			<dt><label for="fulltext_native_load_upd">' . $user->lang['YES_SEARCH_UPDATE'] . ':</label><br /><span>' . $user->lang['YES_SEARCH_UPDATE_EXPLAIN'] . '</span></dt> -			<dd><label><input type="radio" id="fulltext_native_load_upd" name="config[fulltext_native_load_upd]" value="1"' . (($config['fulltext_native_load_upd']) ? ' checked="checked"' : '') . ' class="radio" /> ' . $user->lang['YES'] . '</label><label><input type="radio" name="config[fulltext_native_load_upd]" value="0"' . ((!$config['fulltext_native_load_upd']) ? ' checked="checked"' : '') . ' class="radio" /> ' . $user->lang['NO'] . '</label></dd> +			<dt><label for="fulltext_native_load_upd">' . $this->user->lang['YES_SEARCH_UPDATE'] . ':</label><br /><span>' . $this->user->lang['YES_SEARCH_UPDATE_EXPLAIN'] . '</span></dt> +			<dd><label><input type="radio" id="fulltext_native_load_upd" name="config[fulltext_native_load_upd]" value="1"' . (($this->config['fulltext_native_load_upd']) ? ' checked="checked"' : '') . ' class="radio" /> ' . $this->user->lang['YES'] . '</label><label><input type="radio" name="config[fulltext_native_load_upd]" value="0"' . ((!$this->config['fulltext_native_load_upd']) ? ' checked="checked"' : '') . ' class="radio" /> ' . $this->user->lang['NO'] . '</label></dd>  		</dl>  		<dl> -			<dt><label for="fulltext_native_min_chars">' . $user->lang['MIN_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['MIN_SEARCH_CHARS_EXPLAIN'] . '</span></dt> -			<dd><input id="fulltext_native_min_chars" type="text" size="3" maxlength="3" name="config[fulltext_native_min_chars]" value="' . (int) $config['fulltext_native_min_chars'] . '" /></dd> +			<dt><label for="fulltext_native_min_chars">' . $this->user->lang['MIN_SEARCH_CHARS'] . ':</label><br /><span>' . $this->user->lang['MIN_SEARCH_CHARS_EXPLAIN'] . '</span></dt> +			<dd><input id="fulltext_native_min_chars" type="text" size="3" maxlength="3" name="config[fulltext_native_min_chars]" value="' . (int) $this->config['fulltext_native_min_chars'] . '" /></dd>  		</dl>  		<dl> -			<dt><label for="fulltext_native_max_chars">' . $user->lang['MAX_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['MAX_SEARCH_CHARS_EXPLAIN'] . '</span></dt> -			<dd><input id="fulltext_native_max_chars" type="text" size="3" maxlength="3" name="config[fulltext_native_max_chars]" value="' . (int) $config['fulltext_native_max_chars'] . '" /></dd> +			<dt><label for="fulltext_native_max_chars">' . $this->user->lang['MAX_SEARCH_CHARS'] . ':</label><br /><span>' . $this->user->lang['MAX_SEARCH_CHARS_EXPLAIN'] . '</span></dt> +			<dd><input id="fulltext_native_max_chars" type="text" size="3" maxlength="3" name="config[fulltext_native_max_chars]" value="' . (int) $this->config['fulltext_native_max_chars'] . '" /></dd>  		</dl>  		<dl> -			<dt><label for="fulltext_native_common_thres">' . $user->lang['COMMON_WORD_THRESHOLD'] . ':</label><br /><span>' . $user->lang['COMMON_WORD_THRESHOLD_EXPLAIN'] . '</span></dt> -			<dd><input id="fulltext_native_common_thres" type="text" size="3" maxlength="3" name="config[fulltext_native_common_thres]" value="' . (double) $config['fulltext_native_common_thres'] . '" /> %</dd> +			<dt><label for="fulltext_native_common_thres">' . $this->user->lang['COMMON_WORD_THRESHOLD'] . ':</label><br /><span>' . $this->user->lang['COMMON_WORD_THRESHOLD_EXPLAIN'] . '</span></dt> +			<dd><input id="fulltext_native_common_thres" type="text" size="3" maxlength="3" name="config[fulltext_native_common_thres]" value="' . (double) $this->config['fulltext_native_common_thres'] . '" /> %</dd>  		</dl>  		'; diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php new file mode 100644 index 0000000000..38989a9d9a --- /dev/null +++ b/phpBB/includes/search/fulltext_postgres.php @@ -0,0 +1,834 @@ +<?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 +{ +	protected $stats = array(); +	protected $split_words = array(); +	protected $tsearch_usable = false; +	protected $version; +	protected $tsearch_query; +	protected $phrase_search = false; +	protected $config; +	protected $db; +	protected $user; +	protected $search_query; +	protected $common_words = array(); +	protected $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, $phpbb_root_path, $phpEx, $auth, $config, $db, $user) +	{ +		$this->config = $config; +		$this->db = $db; +		$this->user = $user; + +		$this->word_length = array('min' => $this->config['fulltext_postgres_min_word_len'], 'max' => $this->config['fulltext_postgres_max_word_len']); + +		if ($this->db->sql_layer == 'postgres') +		{ +			$pgsql_version = explode(',', substr($this->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 +	*/ +	public function get_name() +	{ +		return 'PostgreSQL Fulltext'; +	} + +	/** +	 * Returns the search_query +	 * +	 * @return string search query +	 */ +	public function get_search_query() +	{ +		return $this->search_query; +	} + +	/** +	 * Returns the common_words array +	 * +	 * @return array common words that are ignored by search backend +	 */ +	public function get_common_words() +	{ +		return $this->common_words; +	} + +	/** +	 * Returns the word_length array +	 * +	 * @return array min and max word length for searching +	 */ +	public function get_word_length() +	{ +		return $this->word_length; +	} + +	/** +	 * Returns if phrase search is supported or not +	 * +	 * @return bool +	 */ +	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 +	*/ +	public function init() +	{ +		if ($this->db->sql_layer != 'postgres') +		{ +			return $this->user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE']; +		} + +		if (!$this->tsearch_usable) +		{ +			return $this->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 +	*/ +	public function split_keywords(&$keywords, $terms) +	{ +		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 < $this->config['fulltext_postgres_min_word_len']) || ($clean_len > $this->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 +	*/ +	public function split_message($text) +	{ +		// 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]) < $this->config['fulltext_postgres_min_word_len'] || utf8_strlen($text[$i]) > $this->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 +	*/ +	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) +	{ +		// 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 ' . $this->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 = '(' . $this->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 ' . $this->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 ' . $this->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 ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', '" . $this->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 = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$id_ary[] = $row[$field]; +		} +		$this->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 +	*/ +	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) +	{ +		// 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 = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; +		} +		else +		{ +			$sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); +		} +		$sql_fora		= (sizeof($ex_fid_ary)) ? ' AND ' . $this->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 ' . $this->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 = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$id_ary[] = $row[$field]; +		} +		$this->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 +	*/ +	public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) +	{ +		// 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 +	*/ +	public function index_remove($post_ids, $author_ids, $forum_ids) +	{ +		$this->destroy_cache(array(), $author_ids); +	} + +	/** +	* Destroy old cache entries +	*/ +	public function tidy() +	{ +		// 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 +	*/ +	public function create_index($acp_module, $u_action) +	{ +		// 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'])) +		{ +			$this->db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_subject))"); +		} + +		if (!isset($this->stats['post_text'])) +		{ +			$this->db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_text))"); +		} + +		$this->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 +	*/ +	public function delete_index($acp_module, $u_action) +	{ +		// 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'])) +		{ +			$this->db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); +		} + +		if (isset($this->stats['post_text'])) +		{ +			$this->db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); +		} + +		$this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + +		return false; +	} + +	/** +	* Returns true if both FULLTEXT indexes exist +	*/ +	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 +	*/ +	public function index_stats() +	{ +		if (empty($this->stats)) +		{ +			$this->get_stats(); +		} + +		return array( +			$this->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 +	 */ +	protected function get_stats() +	{ +		if ($this->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 = $this->db->sql_query($sql); + +		while ($row = $this->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 . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') +				{ +					$this->stats['post_text'] = $row; +				} +				else if ($row['relname'] == POSTS_TABLE . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') +				{ +					$this->stats['post_subject'] = $row; +				} +			} +		} +		$this->db->sql_freeresult($result); + +		$this->stats['total_posts'] = $this->config['num_posts']; +	} + +	/** +	* Display various options that can be configured for the backend from the acp +	* +	* @return associative array containing template and config variables +	*/ +	public function acp() +	{ +		$tpl = ' +		<dl> +			<dt><label>' . $this->user->lang['FULLTEXT_POSTGRES_VERSION_CHECK'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN'] . '</span></dt> +			<dd>' . (($this->tsearch_usable) ? $this->user->lang['YES'] : $this->user->lang['NO']) . ' (PostgreSQL ' . $this->version . ')</dd> +		</dl> +		<dl> +			<dt><label>' . $this->user->lang['FULLTEXT_POSTGRES_TS_NAME'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '</span></dt> +			<dd><select name="config[fulltext_postgres_ts_name]">'; + +		if ($this->db->sql_layer == 'postgres' && $this->tsearch_usable) +		{ +			$sql = 'SELECT cfgname AS ts_name +				  FROM pg_ts_config'; +			$result = $this->db->sql_query($sql); + +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$tpl .= '<option value="' . $row['ts_name'] . '"' . ($row['ts_name'] === $this->config['fulltext_postgres_ts_name'] ? ' selected="selected"' : '') . '>' . $row['ts_name'] . '</option>'; +			} +			$this->db->sql_freeresult($result); +		} +		else +		{ +			$tpl .= '<option value="' . $this->config['fulltext_postgres_ts_name'] . '" selected="selected">' . $this->config['fulltext_postgres_ts_name'] . '</option>'; +		} + +		$tpl .= '</select></dd> +		</dl> +                <dl> +                        <dt><label for="fulltext_postgres_min_word_len">' . $this->user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN'] . ':</label><br /><span>' . $this->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) $this->config['fulltext_postgres_min_word_len'] . '" /></dd> +                </dl> +                <dl> +                        <dt><label for="fulltext_postgres_max_word_len">' . $this->user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN'] . ':</label><br /><span>' . $this->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) $this->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/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php new file mode 100644 index 0000000000..0a230f0e98 --- /dev/null +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -0,0 +1,817 @@ +<?php +/** +* +* @package search +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* @ignore +*/ +define('SPHINX_MAX_MATCHES', 20000); +define('SPHINX_CONNECT_RETRIES', 3); +define('SPHINX_CONNECT_WAIT_TIME', 300); + +/** +* fulltext_sphinx +* Fulltext search based on the sphinx search deamon +* @package search +*/ +class phpbb_search_fulltext_sphinx +{ +	protected $stats = array(); +	protected $split_words = array(); +	protected $id; +	protected $indexes; +	protected $sphinx; +	protected $phpbb_root_path; +	protected $php_ext; +	protected $auth; +	protected $config; +	protected $db; +	protected $db_tools; +	protected $dbtype; +	protected $user; +	protected $config_file_data = ''; +	protected $search_query; +	protected $common_words = 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, $phpbb_root_path, $phpEx, $auth, $config, $db, $user) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $phpEx; +		$this->config = $config; +		$this->user = $user; +		$this->db = $db; +		$this->auth = $auth; + +		if (!class_exists('phpbb_db_tools')) +		{ +			require($this->phpbb_root_path . 'includes/db/db_tools.' . $this->php_ext); +		} +		 +		// Initialize phpbb_db_tools object +		$this->db_tools = new phpbb_db_tools($this->db); + +		if(!$this->config['fulltext_sphinx_id']) +		{ +			set_config('fulltext_sphinx_id', unique_id()); +		} +		$this->id = $this->config['fulltext_sphinx_id']; +		$this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; + +		if (!class_exists('SphinxClient')) +		{ +			require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext); +		} + +		// Initialize sphinx client +		$this->sphinx = new SphinxClient(); + +		$this->sphinx->SetServer(($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost'), ($this->config['fulltext_sphinx_port'] ? (int) $this->config['fulltext_sphinx_port'] : 9312)); + +		$error = false; +	} + +	/** +	* Returns the name of this search backend to be displayed to administrators +	* +	* @return string Name +	*/ +	public function get_name() +	{ +		return 'Sphinx Fulltext'; +	} + +	/** +	 * Returns the search_query +	 * +	 * @return string search query +	 */ +	public function get_search_query() +	{ +		return $this->search_query; +	} + +	/** +	 * Returns false as there is no word_len array +	 * +	 * @return false +	 */ +	public function get_word_length() +	{ +		return false; +	} + +	/** +	 * Returns the common_words array +	 * +	 * @return array common words that are ignored by search backend +	 */ +	public function get_common_words() +	{ +		return $this->common_words; +	} + +	/** +	* Checks permissions and paths, if everything is correct it generates the config file +	* +	* @return string|bool Language key of the error/incompatiblity encountered, or false if successful +	*/ +	public function init() +	{ +		if ($this->db->sql_layer != 'mysql' && $this->db->sql_layer != 'mysql4' && $this->db->sql_layer != 'mysqli' && $this->db->sql_layer != 'postgres') +		{ +			return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE']; +		} + +		// Move delta to main index each hour +		set_config('search_gc', 3600); + +		return false; +	} + +	/** +	 * Generates content of sphinx.conf +	 * +	 * @return bool True if sphinx.conf content is correctly generated, false otherwise +	 */ +	protected function config_generate() +	{ +		// Check if Database is supported by Sphinx +		if ($this->db->sql_layer =='mysql' || $this->db->sql_layer == 'mysql4' || $this->db->sql_layer == 'mysqli') +		{ +			$this->dbtype = 'mysql'; +		} +		else if ($this->db->sql_layer == 'postgres') +		{ +			$this->dbtype = 'pgsql'; +		} +		else +		{ +			$this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE'); +			return false; +		} + +		// Check if directory paths have been filled +		if (!$this->config['fulltext_sphinx_data_path']) +		{ +			$this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA'); +			return false; +		} + +		include($this->phpbb_root_path . 'config.' . $this->php_ext); + +		/* Now that we're sure everything was entered correctly, +		generate a config for the index. We use a config value +		fulltext_sphinx_id for this, as it should be unique. */ +		$config_object = new phpbb_search_sphinx_config($this->config_file_data); +		$config_data = array( +			'source source_phpbb_' . $this->id . '_main' => array( +				array('type',						$this->dbtype), +				// This config value sql_host needs to be changed incase sphinx and sql are on different servers +				array('sql_host',					$dbhost), +				array('sql_user',					$dbuser), +				array('sql_pass',					$dbpasswd), +				array('sql_db',						$dbname), +				array('sql_port',					$dbport), +				array('sql_query_pre',				'SET NAMES \'utf8\''), +				array('sql_query_pre',				'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'), +				array('sql_query_range',			'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''), +				array('sql_range_step',				'5000'), +				array('sql_query',					'SELECT +						p.post_id AS id, +						p.forum_id, +						p.topic_id, +						p.poster_id, +						CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, +						p.post_time, +						p.post_subject, +						p.post_subject as title, +						p.post_text as data, +						t.topic_last_post_time, +						0 as deleted +					FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t +					WHERE +						p.topic_id = t.topic_id +						AND p.post_id >= $start AND p.post_id <= $end'), +				array('sql_query_post',				''), +				array('sql_query_post_index',		'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'), +				array('sql_query_info',				'SELECT * FROM ' . POSTS_TABLE . ' WHERE post_id = $id'), +				array('sql_attr_uint',				'forum_id'), +				array('sql_attr_uint',				'topic_id'), +				array('sql_attr_uint',				'poster_id'), +				array('sql_attr_bool',				'topic_first_post'), +				array('sql_attr_bool',				'deleted'), +				array('sql_attr_timestamp'	,		'post_time'), +				array('sql_attr_timestamp'	,		'topic_last_post_time'), +				array('sql_attr_str2ordinal',		'post_subject'), +			), +			'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array( +				array('sql_query_pre',				''), +				array('sql_query_range',			''), +				array('sql_range_step',				''), +				array('sql_query',					'SELECT +						p.post_id AS id, +						p.forum_id, +						p.topic_id, +						p.poster_id, +						CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, +						p.post_time, +						p.post_subject, +						p.post_subject as title, +						p.post_text as data, +						t.topic_last_post_time, +						0 as deleted +					FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t +					WHERE +						p.topic_id = t.topic_id +						AND p.post_id >=  ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'), +			), +			'index index_phpbb_' . $this->id . '_main' => array( +				array('path',						$this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'), +				array('source',						'source_phpbb_' . $this->id . '_main'), +				array('docinfo',					'extern'), +				array('morphology',					'none'), +				array('stopwords',					''), +				array('min_word_len',				'2'), +				array('charset_type',				'utf-8'), +				array('charset_table',				'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), +				array('min_prefix_len',				'0'), +				array('min_infix_len',				'0'), +			), +			'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array( +				array('path',						$this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'), +				array('source',						'source_phpbb_' . $this->id . '_delta'), +			), +			'indexer' => array( +				array('mem_limit',					$this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'), +			), +			'searchd' => array( +				array('compat_sphinxql_magics'	,	'0'), +				array('listen'	,					($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')), +				array('log',						$this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'), +				array('query_log',					$this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'), +				array('read_timeout',				'5'), +				array('max_children',				'30'), +				array('pid_file',					$this->config['fulltext_sphinx_data_path'] . 'searchd.pid'), +				array('max_matches',				(string) SPHINX_MAX_MATCHES), +				array('binlog_path',				$this->config['fulltext_sphinx_data_path']), +			), +		); + +		$non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true); +		$delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true); +		foreach ($config_data as $section_name => $section_data) +		{ +			$section = $config_object->get_section_by_name($section_name); +			if (!$section) +			{ +				$section = $config_object->add_section($section_name); +			} + +			foreach ($delete as $key => $void) +			{ +				$section->delete_variables_by_name($key); +			} + +			foreach ($non_unique as $key => $void) +			{ +				$section->delete_variables_by_name($key); +			} + +			foreach ($section_data as $entry) +			{ +				$key = $entry[0]; +				$value = $entry[1]; + +				if (!isset($non_unique[$key])) +				{ +					$variable = $section->get_variable_by_name($key); +					if (!$variable) +					{ +						$variable = $section->create_variable($key, $value); +					} +					else +					{ +						$variable->set_value($value); +					} +				} +				else +				{ +					$variable = $section->create_variable($key, $value); +				} +			} +		} +		$this->config_file_data = $config_object->get_data(); + +		return true; +	} + +	/** +	* 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 false if no valid keywords were found and otherwise true +	*/ +	public function split_keywords(&$keywords, $terms) +	{ +		if ($terms == 'all') +		{ +			$match		= array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#'); +			$replace	= array(' & ', ' | ', '  - ', ' +', ' -', ' |', ''); + +			$replacements = 0; +			$keywords = preg_replace($match, $replace, $keywords); +			$this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED); +		} +		else +		{ +			$this->sphinx->SetMatchMode(SPH_MATCH_ANY); +		} + +		// Keep quotes and new lines +		$keywords = str_replace(array('"', "\n"), array('"', ' '), trim($keywords)); + +		if (strlen($keywords) > 0) +		{ +			$this->search_query = str_replace('"', '"', $keywords); +			return true; +		} + +		return false; +	} + +	/** +	* 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 +	*/ +	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) +	{ +		// No keywords? No posts. +		if (!strlen($this->search_query) && !sizeof($author_ary)) +		{ +			return false; +		} + +		$id_ary = array(); + +		$join_topic = ($type != 'posts'); + +		// Sorting + +		if ($type == 'topics') +		{ +			switch ($sort_key) +			{ +				case 'a': +					$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); +				break; + +				case 'f': +					$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); +				break; + +				case 'i': + +				case 's': +					$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); +				break; + +				case 't': + +				default: +					$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); +				break; +			} +		} +		else +		{ +			switch ($sort_key) +			{ +				case 'a': +					$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id'); +				break; + +				case 'f': +					$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id'); +				break; + +				case 'i': + +				case 's': +					$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject'); +				break; + +				case 't': + +				default: +					$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time'); +				break; +			} +		} + +		// Most narrow filters first +		if ($topic_id) +		{ +			$this->sphinx->SetFilter('topic_id', array($topic_id)); +		} + +		$search_query_prefix = ''; + +		switch ($fields) +		{ +			case 'titleonly': +				// Only search the title +				if ($terms == 'all') +				{ +					$search_query_prefix = '@title '; +				} +				// Weight for the title +				$this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); +				// 1 is first_post, 0 is not first post +				$this->sphinx->SetFilter('topic_first_post', array(1)); +			break; + +			case 'msgonly': +				// Only search the body +				if ($terms == 'all') +				{ +					$search_query_prefix = '@data '; +				} +				// Weight for the body +				$this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5)); +			break; + +			case 'firstpost': +				// More relative weight for the title, also search the body +				$this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); +				// 1 is first_post, 0 is not first post +				$this->sphinx->SetFilter('topic_first_post', array(1)); +			break; + +			default: +				// More relative weight for the title, also search the body +				$this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); +			break; +		} + +		if (sizeof($author_ary)) +		{ +			$this->sphinx->SetFilter('poster_id', $author_ary); +		} + +		if (sizeof($ex_fid_ary)) +		{ +			// All forums that a user is allowed to access +			$fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true)))); +			// All forums that the user wants to and can search in +			$search_forums = array_diff($fid_ary, $ex_fid_ary); + +			if (sizeof($search_forums)) +			{ +				$this->sphinx->SetFilter('forum_id', $search_forums); +			} +		} + +		$this->sphinx->SetFilter('deleted', array(0)); + +		$this->sphinx->SetLimits($start, (int) $per_page, SPHINX_MAX_MATCHES); +		$result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); + +		// Could be connection to localhost:9312 failed (errno=111, +		// msg=Connection refused) during rotate, retry if so +		$retries = SPHINX_CONNECT_RETRIES; +		while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) +		{ +			usleep(SPHINX_CONNECT_WAIT_TIME); +			$result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); +		} + +		if ($this->sphinx->GetLastError()) +		{ +			add_log('critical', 'LOG_SPHINX_ERROR', $this->sphinx->GetLastError()); +			if ($this->auth->acl_get('a_')) +			{ +				trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError())); +			} +			else +			{ +				trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG')); +			} +		} + +		$id_ary = array(); +		if (isset($result['matches'])) +		{ +			if ($type == 'posts') +			{ +				$id_ary = array_keys($result['matches']); +			} +			else +			{ +				foreach ($result['matches'] as $key => $value) +				{ +					$id_ary[] = $value['attrs']['topic_id']; +				} +			} +		} +		else +		{ +			return false; +		} + +		$result_count = $result['total_found']; + +		$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 +	*/ +	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) +	{ +		$this->search_query = ''; + +		$this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN); +		$fields = ($firstpost_only) ? 'firstpost' : 'all'; +		$terms = 'all'; +		return $this->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); +	} + +	/** +	 * Updates wordlist and wordmatch tables when a message is posted or changed +	 * +	 * @param	string	$mode	Contains the post mode: edit, post, reply, quote +	 * @param	int	$post_id	The id of the post which is modified/created +	 * @param	string	&$message	New or updated post content +	 * @param	string	&$subject	New or updated post subject +	 * @param	int	$poster_id	Post author's user id +	 * @param	int	$forum_id	The id of the forum in which the post is located +	 */ +	public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) +	{ +		if ($mode == 'edit') +		{ +			$this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int)$post_id => array((int)$forum_id, (int)$poster_id))); +		} +		else if ($mode != 'post' && $post_id) +		{ +			// Update topic_last_post_time for full topic +			$sql_array = array( +				'SELECT'	=> 'p1.post_id', +				'FROM'		=> array( +					POSTS_TABLE	=> 'p1', +				), +				'LEFT_JOIN'	=> array(array( +					'FROM'	=> array( +						POSTS_TABLE	=> 'p2' +					), +					'ON'	=> 'p1.topic_id = p2.topic_id', +				)), +			); + +			$sql = $this->db->sql_build_query('SELECT', $sql_array); +			$result = $this->db->sql_query($sql); + +			$post_updates = array(); +			$post_time = time(); +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$post_updates[(int)$row['post_id']] = array($post_time); +			} +			$this->db->sql_freeresult($result); + +			if (sizeof($post_updates)) +			{ +				$this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates); +			} +		} +	} + +	/** +	* Delete a post from the index after it was deleted +	*/ +	public function index_remove($post_ids, $author_ids, $forum_ids) +	{ +		$values = array(); +		foreach ($post_ids as $post_id) +		{ +			$values[$post_id] = array(1); +		} + +		$this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values); +	} + +	/** +	* Nothing needs to be destroyed +	*/ +	public function tidy($create = false) +	{ +		set_config('search_last_gc', time(), true); +	} + +	/** +	* Create sphinx table +	* +	* @return string|bool error string is returned incase of errors otherwise false +	*/ +	public function create_index($acp_module, $u_action) +	{ +		if (!$this->index_created()) +		{ +			$table_data = array( +				'COLUMNS'	=> array( +					'counter_id'	=> array('UINT', 0), +					'max_doc_id'	=> array('UINT', 0), +				), +				'PRIMARY_KEY'	=> 'counter_id', +			); +			$this->db_tools->sql_create_table(SPHINX_TABLE, $table_data); + +			$sql = 'TRUNCATE TABLE ' . SPHINX_TABLE; +			$this->db->sql_query($sql); + +			$data = array( +				'counter_id'	=> '1', +				'max_doc_id'	=> '0', +			); +			$sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data); +			$this->db->sql_query($sql); +		} + +		return false; +	} + +	/** +	* Drop sphinx table +	* +	* @return string|bool error string is returned incase of errors otherwise false +	*/ +	public function delete_index($acp_module, $u_action) +	{ +		if (!$this->index_created()) +		{ +			return false; +		} + +		$this->db_tools->sql_table_drop(SPHINX_TABLE); + +		return false; +	} + +	/** +	* Returns true if the sphinx table was created +	* +	* @return bool true if sphinx table was created +	*/ +	public function index_created($allow_new_files = true) +	{ +		$created = false; + +		if ($this->db_tools->sql_table_exists(SPHINX_TABLE)) +		{ +			$created = true; +		} + +		return $created; +	} + +	/** +	* Returns an associative array containing information about the indexes +	* +	* @return string|bool Language string of error false otherwise +	*/ +	public function index_stats() +	{ +		if (empty($this->stats)) +		{ +			$this->get_stats(); +		} + +		return array( +			$this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS']			=> ($this->index_created()) ? $this->stats['main_posts'] : 0, +			$this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS']			=> ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0, +			$this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS']			=> ($this->index_created()) ? $this->stats['total_posts'] : 0, +		); +	} + +	/** +	* Collects stats that can be displayed on the index maintenance page +	*/ +	protected function get_stats() +	{ +		if ($this->index_created()) +		{ +			$sql = 'SELECT COUNT(post_id) as total_posts +				FROM ' . POSTS_TABLE; +			$result = $this->db->sql_query($sql); +			$this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts'); +			$this->db->sql_freeresult($result); + +			$sql = 'SELECT COUNT(p.post_id) as main_posts +				FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m +				WHERE p.post_id <= m.max_doc_id +					AND m.counter_id = 1'; +			$result = $this->db->sql_query($sql); +			$this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts'); +			$this->db->sql_freeresult($result); +		} +	} + +	/** +	* Returns a list of options for the ACP to display +	* +	* @return associative array containing template and config variables +	*/ +	public function acp() +	{ +		$config_vars = array( +			'fulltext_sphinx_data_path' => 'string', +			'fulltext_sphinx_host' => 'string', +			'fulltext_sphinx_port' => 'string', +			'fulltext_sphinx_indexer_mem_limit' => 'int', +		); + +		$tpl = ' +		<span class="error">' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. '</span> +		<dl> +			<dt><label for="fulltext_sphinx_data_path">' . $this->user->lang['FULLTEXT_SPHINX_DATA_PATH'] . ':</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_DATA_PATH_EXPLAIN'] . '</span></dt> +			<dd><input id="fulltext_sphinx_data_path" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_data_path]" value="' . $this->config['fulltext_sphinx_data_path'] . '" /></dd> +		</dl> +		<dl> +			<dt><label for="fulltext_sphinx_host">' . $this->user->lang['FULLTEXT_SPHINX_HOST'] . ':</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_HOST_EXPLAIN'] . '</span></dt> +			<dd><input id="fulltext_sphinx_host" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_host]" value="' . $this->config['fulltext_sphinx_host'] . '" /></dd> +		</dl> +		<dl> +			<dt><label for="fulltext_sphinx_port">' . $this->user->lang['FULLTEXT_SPHINX_PORT'] . ':</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_PORT_EXPLAIN'] . '</span></dt> +			<dd><input id="fulltext_sphinx_port" type="text" size="4" maxlength="10" name="config[fulltext_sphinx_port]" value="' . $this->config['fulltext_sphinx_port'] . '" /></dd> +		</dl> +		<dl> +			<dt><label for="fulltext_sphinx_indexer_mem_limit">' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT'] . ':</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '</span></dt> +			<dd><input id="fulltext_sphinx_indexer_mem_limit" type="text" size="4" maxlength="10" name="config[fulltext_sphinx_indexer_mem_limit]" value="' . $this->config['fulltext_sphinx_indexer_mem_limit'] . '" />' . $this->user->lang['MIB'] . '</dd> +		</dl> +		<dl> +			<dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . ':</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</dt> +			<dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6">' . $this->config_file_data . '</textarea>' : $this->config_file_data) . '</dd> +		<dl> +		'; + +		// These are fields required in the config table +		return array( +			'tpl'		=> $tpl, +			'config'	=> $config_vars +		); +	} +} diff --git a/phpBB/includes/search/sphinx/config.php b/phpBB/includes/search/sphinx/config.php new file mode 100644 index 0000000000..f1864f0c8c --- /dev/null +++ b/phpBB/includes/search/sphinx/config.php @@ -0,0 +1,288 @@ +<?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; +} + +/** +* phpbb_search_sphinx_config +* An object representing the sphinx configuration +* Can read it from file and write it back out after modification +* @package search +*/ +class phpbb_search_sphinx_config +{ +	private $sections = array(); + +	/** +	* Constructor which optionally loads data from a variable +	* +	* @param	string	$config_data	Variable containing the sphinx configuration data +	* +	* @access	public +	*/ +	function __construct($config_data) +	{ +		if ($config_data != '') +		{ +			$this->read($config_data); +		} +	} + +	/** +	* Get a section object by its name +	* +	* @param	string 								$name	The name of the section that shall be returned +	* @return	phpbb_search_sphinx_config_section			The section object or null if none was found +	* +	* @access	public +	*/ +	function get_section_by_name($name) +	{ +		for ($i = 0, $size = sizeof($this->sections); $i < $size; $i++) +		{ +			// Make sure this is really a section object and not a comment +			if (($this->sections[$i] instanceof phpbb_search_sphinx_config_section) && $this->sections[$i]->get_name() == $name) +			{ +				return $this->sections[$i]; +			} +		} +	} + +	/** +	* Appends a new empty section to the end of the config +	* +	* @param	string								$name	The name for the new section +	* @return	phpbb_search_sphinx_config_section			The newly created section object +	* +	* @access	public +	*/ +	function add_section($name) +	{ +		$this->sections[] = new phpbb_search_sphinx_config_section($name, ''); +		return $this->sections[sizeof($this->sections) - 1]; +	} + +	/** +	* Reads the config file data +	* +	* @param	string	$config_data	The config file data +	* +	* @access	private +	*/ +	function read($config_data) +	{ +		$this->sections = array(); + +		$section = null; +		$found_opening_bracket = false; +		$in_value = false; + +		foreach ($config_data as $i => $line) +		{ +			// If the value of a variable continues to the next line because the line +			// break was escaped then we don't trim leading space but treat it as a part of the value +			if ($in_value) +			{ +				$line = rtrim($line); +			} +			else +			{ +				$line = trim($line); +			} + +			// If we're not inside a section look for one +			if (!$section) +			{ +				// Add empty lines and comments as comment objects to the section list +				// that way they're not deleted when reassembling the file from the sections +				if (!$line || $line[0] == '#') +				{ +					$this->sections[] = new phpbb_search_sphinx_config_comment($config_file[$i]); +					continue; +				} +				else +				{ +					// Otherwise we scan the line reading the section name until we find +					// an opening curly bracket or a comment +					$section_name = ''; +					$section_name_comment = ''; +					$found_opening_bracket = false; +					for ($j = 0, $length = strlen($line); $j < $length; $j++) +					{ +						if ($line[$j] == '#') +						{ +							$section_name_comment = substr($line, $j); +							break; +						} + +						if ($found_opening_bracket) +						{ +							continue; +						} + +						if ($line[$j] == '{') +						{ +							$found_opening_bracket = true; +							continue; +						} + +						$section_name .= $line[$j]; +					} + +					// And then we create the new section object +					$section_name = trim($section_name); +					$section = new phpbb_search_sphinx_config_section($section_name, $section_name_comment); +				} +			} +			else +			{ +				// If we're looking for variables inside a section +				$skip_first = false; + +				// If we're not in a value continuing over the line feed +				if (!$in_value) +				{ +					// Then add empty lines and comments as comment objects to the variable list +					// of this section so they're not deleted on reassembly +					if (!$line || $line[0] == '#') +					{ +						$section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); +						continue; +					} +	 +					// As long as we haven't yet actually found an opening bracket for this section +					// we treat everything as comments so it's not deleted either +					if (!$found_opening_bracket) +					{ +						if ($line[0] == '{') +						{ +							$skip_first = true; +							$line = substr($line, 1); +							$found_opening_bracket = true; +						} +						else +						{ +							$section->add_variable(new phpbb_search_sphinx_config_comment($config_file[$i])); +							continue; +						} +					} +				} + +				// If we did not find a comment in this line or still add to the previous  +				// line's value ... +				if ($line || $in_value) +				{ +					if (!$in_value) +					{ +						$name = ''; +						$value = ''; +						$comment = ''; +						$found_assignment = false; +					} +					$in_value = false; +					$end_section = false; + +					/* ... then we should prase this line char by char: +					 - first there's the variable name +					 - then an equal sign +					 - the variable value +					 - possibly a backslash before the linefeed in this case we need to continue +					   parsing the value in the next line +					 - a # indicating that the rest of the line is a comment +					 - a closing curly bracket indicating the end of this section*/ +					for ($j = 0, $length = strlen($line); $j < $length; $j++) +					{ +						if ($line[$j] == '#') +						{ +							$comment = substr($line, $j); +							break; +						} +						else if ($line[$j] == '}') +						{ +							$comment = substr($line, $j + 1); +							$end_section = true; +							break; +						} +						else if (!$found_assignment) +						{ +							if ($line[$j] == '=') +							{ +								$found_assignment = true; +							} +							else +							{ +								$name .= $line[$j]; +							} +						} +						else +						{ +							if ($line[$j] == '\\' && $j == $length - 1) +							{ +								$value .= "\n"; +								$in_value = true; +								// Go to the next line and keep processing the value in there +								continue 2; +							} +							$value .= $line[$j]; +						} +					} + +					// If a name and an equal sign were found then we have append a  +					// new variable object to the section +					if ($name && $found_assignment) +					{ +						$section->add_variable(new phpbb_search_sphinx_config_variable(trim($name), trim($value), ($end_section) ? '' : $comment)); +						continue; +					} + +					/* If we found a closing curly bracket this section has been completed +					and we can append it to the section list and continue with looking for  +					the next section */ +					if ($end_section) +					{ +						$section->set_end_comment($comment); +						$this->sections[] = $section; +						$section = null; +						continue; +					} +				} + +				// If we did not find anything meaningful up to here, then just treat it +				// as a comment +				$comment = ($skip_first) ? "\t" . substr(ltrim($config_file[$i]), 1) : $config_file[$i]; +				$section->add_variable(new phpbb_search_sphinx_config_comment($comment)); +			} +		} + +	} + +	/** +	* Returns the config data +	* +	* @return	string	$data	The config data that is generated +	* +	* @access	public +	*/ +	function get_data() +	{ +		$data = ""; +		foreach ($this->sections as $section) +		{ +			$data .= $section->to_string(); +		} + +		return $data; +	} +} diff --git a/phpBB/includes/search/sphinx/config_comment.php b/phpBB/includes/search/sphinx/config_comment.php new file mode 100644 index 0000000000..7f695dbf0c --- /dev/null +++ b/phpBB/includes/search/sphinx/config_comment.php @@ -0,0 +1,49 @@ +<?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; +} + +/** +* phpbb_search_sphinx_config_comment +* Represents a comment inside the sphinx configuration +*/ +class phpbb_search_sphinx_config_comment +{ +	private $exact_string; + +	/** +	* Create a new comment +	* +	* @param	string	$exact_string	The content of the comment including newlines, leading whitespace, etc. +	* +	* @access	public +	*/ +	function __construct($exact_string) +	{ +		$this->exact_string = $exact_string; +	} + +	/** +	* Simply returns the comment as it was created +	* +	* @return	string	The exact string that was specified in the constructor +	* +	* @access	public +	*/ +	function to_string() +	{ +		return $this->exact_string; +	} +} diff --git a/phpBB/includes/search/sphinx/config_section.php b/phpBB/includes/search/sphinx/config_section.php new file mode 100644 index 0000000000..79c9c8563d --- /dev/null +++ b/phpBB/includes/search/sphinx/config_section.php @@ -0,0 +1,162 @@ +<?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; +} + +/** +* phpbb_search_sphinx_config_section +* Represents a single section inside the sphinx configuration +*/ +class phpbb_search_sphinx_config_section +{ +	private $name; +	private $comment; +	private $end_comment; +	private $variables = array(); + +	/** +	* Construct a new section +	* +	* @param	string	$name		Name of the section +	* @param	string	$comment	Comment that should be appended after the name in the +	*								textual format. +	* +	* @access	public +	*/ +	function __construct($name, $comment) +	{ +		$this->name = $name; +		$this->comment = $comment; +		$this->end_comment = ''; +	} + +	/** +	* Add a variable object to the list of variables in this section +	* +	* @param	phpbb_search_sphinx_config_variable	$variable	The variable object +	* +	* @access	public +	*/ +	function add_variable($variable) +	{ +		$this->variables[] = $variable; +	} + +	/** +	* Adds a comment after the closing bracket in the textual representation +	* +	* @param	string	$end_comment +	* +	* @access	public +	*/ +	function set_end_comment($end_comment) +	{ +		$this->end_comment = $end_comment; +	} + +	/** +	* Getter for the name of this section +	* +	* @return	string	Section's name +	* +	* @access	public +	*/ +	function get_name() +	{ +		return $this->name; +	} + +	/** +	* Get a variable object by its name +	* +	* @param	string 								$name	The name of the variable that shall be returned +	* @return	phpbb_search_sphinx_config_section			The first variable object from this section with the +	*														given name or null if none was found +	* +	* @access	public +	*/ +	function get_variable_by_name($name) +	{ +		for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) +		{ +			// Make sure this is a variable object and not a comment +			if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) +			{ +				return $this->variables[$i]; +			} +		} +	} + +	/** +	* Deletes all variables with the given name +	* +	* @param	string	$name	The name of the variable objects that are supposed to be removed +	* +	* @access	public +	*/ +	function delete_variables_by_name($name) +	{ +		for ($i = 0, $size = sizeof($this->variables); $i < $size; $i++) +		{ +			// Make sure this is a variable object and not a comment +			if (($this->variables[$i] instanceof phpbb_search_sphinx_config_variable) && $this->variables[$i]->get_name() == $name) +			{ +				array_splice($this->variables, $i, 1); +				$i--; +			} +		} +	} + +	/** +	* Create a new variable object and append it to the variable list of this section +	* +	* @param	string								$name	The name for the new variable +	* @param	string								$value	The value for the new variable +	* @return	phpbb_search_sphinx_config_variable			Variable object that was created +	* +	* @access	public +	*/ +	function create_variable($name, $value) +	{ +		$this->variables[] = new phpbb_search_sphinx_config_variable($name, $value, ''); +		return $this->variables[sizeof($this->variables) - 1]; +	} + +	/** +	* Turns this object into a string which can be written to a config file +	* +	* @return	string	Config data in textual form, parsable for sphinx +	* +	* @access	public +	*/ +	function to_string() +	{ +		$content = $this->name . ' ' . $this->comment . "\n{\n"; + +		// Make sure we don't get too many newlines after the opening bracket +		while (trim($this->variables[0]->to_string()) == '') +		{ +			array_shift($this->variables); +		} + +		foreach ($this->variables as $variable) +		{ +			$content .= $variable->to_string(); +		} +		$content .= '}' . $this->end_comment . "\n"; + +		return $content; +	} +} diff --git a/phpBB/includes/search/sphinx/config_variable.php b/phpBB/includes/search/sphinx/config_variable.php new file mode 100644 index 0000000000..35abe281cb --- /dev/null +++ b/phpBB/includes/search/sphinx/config_variable.php @@ -0,0 +1,80 @@ +<?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; +} + +/** +* phpbb_search_sphinx_config_variable +* Represents a single variable inside the sphinx configuration +*/ +class phpbb_search_sphinx_config_variable +{ +	private $name; +	private $value; +	private $comment; + +	/** +	* Constructs a new variable object +	* +	* @param	string	$name		Name of the variable +	* @param	string	$value		Value of the variable +	* @param	string	$comment	Optional comment after the variable in the +	*								config file +	* +	* @access	public +	*/ +	function __construct($name, $value, $comment) +	{ +		$this->name = $name; +		$this->value = $value; +		$this->comment = $comment; +	} + +	/** +	* Getter for the variable's name +	* +	* @return	string	The variable object's name +	* +	* @access	public +	*/ +	function get_name() +	{ +		return $this->name; +	} + +	/** +	* Allows changing the variable's value +	* +	* @param	string	$value	New value for this variable +	* +	* @access	public +	*/ +	function set_value($value) +	{ +		$this->value = $value; +	} + +	/** +	* Turns this object into a string readable by sphinx +	* +	* @return	string	Config data in textual form +	* +	* @access	public +	*/ +	function to_string() +	{ +		return "\t" . $this->name . ' = ' . str_replace("\n", "\\\n", $this->value) . ' ' . $this->comment . "\n"; +	} +} diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index 893ec2c0cc..257ffb07f6 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -2,9 +2,8 @@  /**  *  * @package phpBB3 -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -20,7 +19,7 @@ if (!defined('IN_PHPBB'))  * Session class  * @package phpBB3  */ -class session +class phpbb_session  {  	var $cookie_data = array();  	var $page = array(); @@ -41,16 +40,18 @@ class session  	*/  	static function extract_current_page($root_path)  	{ +		global $request; +  		$page_array = array();  		// First of all, get the request uri... -		$script_name = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : getenv('PHP_SELF'); -		$args = (!empty($_SERVER['QUERY_STRING'])) ? explode('&', $_SERVER['QUERY_STRING']) : explode('&', getenv('QUERY_STRING')); +		$script_name = htmlspecialchars_decode($request->server('PHP_SELF')); +		$args = explode('&', htmlspecialchars_decode($request->server('QUERY_STRING')));  		// If we are unable to get the script name we use REQUEST_URI as a failover and note it within the page array for easier support...  		if (!$script_name)  		{ -			$script_name = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : getenv('REQUEST_URI'); +			$script_name = htmlspecialchars_decode($request->server('REQUEST_URI'));  			$script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name;  			$page_array['failover'] = 1;  		} @@ -141,10 +142,10 @@ class session  	*/  	function extract_current_hostname()  	{ -		global $config; +		global $config, $request;  		// Get hostname -		$host = (!empty($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : ((!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME')); +		$host = htmlspecialchars_decode($request->header('Host', $request->server('SERVER_NAME')));  		// Should be a string and lowered  		$host = (string) strtolower($host); @@ -212,9 +213,9 @@ class session  		$this->time_now				= time();  		$this->cookie_data			= array('u' => 0, 'k' => '');  		$this->update_session_page	= $update_session_page; -		$this->browser				= (!empty($_SERVER['HTTP_USER_AGENT'])) ? htmlspecialchars((string) $_SERVER['HTTP_USER_AGENT']) : ''; -		$this->referer				= (!empty($_SERVER['HTTP_REFERER'])) ? htmlspecialchars((string) $_SERVER['HTTP_REFERER']) : ''; -		$this->forwarded_for		= (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) ? htmlspecialchars((string) $_SERVER['HTTP_X_FORWARDED_FOR']) : ''; +		$this->browser				= $request->header('User-Agent'); +		$this->referer				= $request->header('Referer'); +		$this->forwarded_for		= $request->header('X-Forwarded-For');  		$this->host					= $this->extract_current_hostname();  		$this->page					= $this->extract_current_page($phpbb_root_path); @@ -222,7 +223,7 @@ class session  		// if the forwarded for header shall be checked we have to validate its contents  		if ($config['forwarded_for_check'])  		{ -			$this->forwarded_for = preg_replace('#[ ]{2,}#', ' ', str_replace(array(',', ' '), ' ', $this->forwarded_for)); +			$this->forwarded_for = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->forwarded_for));  			// split the list of IPs  			$ips = explode(' ', $this->forwarded_for); @@ -268,11 +269,11 @@ class session  		// Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests  		// it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip. -		$this->ip = (!empty($_SERVER['REMOTE_ADDR'])) ? htmlspecialchars((string) $_SERVER['REMOTE_ADDR']) : ''; -		$this->ip = preg_replace('#[ ]{2,}#', ' ', str_replace(array(',', ' '), ' ', $this->ip)); +		$this->ip = htmlspecialchars_decode($request->server('REMOTE_ADDR')); +		$this->ip = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->ip));  		// split the list of IPs -		$ips = explode(' ', $this->ip); +		$ips = explode(' ', trim($this->ip));  		// Default IP if REMOTE_ADDR is invalid  		$this->ip = '127.0.0.1'; @@ -297,26 +298,31 @@ class session  				continue;  			} -			// check IPv4 first, the IPv6 is hopefully only going to be used very seldomly -			if (!empty($ip) && !preg_match(get_preg_expression('ipv4'), $ip) && !preg_match(get_preg_expression('ipv6'), $ip)) +			if (preg_match(get_preg_expression('ipv4'), $ip))  			{ -				// Just break -				break; +				$this->ip = $ip;  			} - -			// Quick check for IPv4-mapped address in IPv6 -			if (stripos($ip, '::ffff:') === 0) +			else if (preg_match(get_preg_expression('ipv6'), $ip))  			{ -				$ipv4 = substr($ip, 7); - -				if (preg_match(get_preg_expression('ipv4'), $ipv4)) +				// Quick check for IPv4-mapped address in IPv6 +				if (stripos($ip, '::ffff:') === 0)  				{ -					$ip = $ipv4; +					$ipv4 = substr($ip, 7); + +					if (preg_match(get_preg_expression('ipv4'), $ipv4)) +					{ +						$ip = $ipv4; +					}  				} -			} -			// Use the last in chain -			$this->ip = $ip; +				$this->ip = $ip; +			} +			else +			{ +				// We want to use the last valid address in the chain +				// Leave foreach loop when address is invalid +				break; +			}  		}  		$this->load = false; @@ -336,8 +342,16 @@ class session  			}  		} -		// Is session_id is set or session_id is set and matches the url param if required -		if (!empty($this->session_id) && (!defined('NEED_SID') || (isset($_GET['sid']) && $this->session_id === request_var('sid', '')))) +		// if no session id is set, redirect to index.php +		$session_id = $request->variable('sid', ''); +		if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id)) +		{ +			send_status_line(401, 'Not authorized'); +			redirect(append_sid("{$phpbb_root_path}index.$phpEx")); +		} + +		// if session id is set +		if (!empty($this->session_id))  		{  			$sql = 'SELECT u.*, s.*  				FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u @@ -377,7 +391,7 @@ class session  				$referer_valid = true;  				// we assume HEAD and TRACE to be foul play and thus only whitelist GET -				if (@$config['referer_validation'] && isset($_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD']) !== 'get') +				if (@$config['referer_validation'] && strtolower($request->server('REQUEST_METHOD')) !== 'get')  				{  					$referer_valid = $this->validate_referer($check_referer_path);  				} @@ -602,6 +616,7 @@ class session  		// otherwise they'll index this page with the SID, duplicate content oh my!  		if ($bot && isset($_GET['sid']))  		{ +			send_status_line(301, 'Moved Permanently');  			redirect(build_url(array('sid')));  		} @@ -1013,6 +1028,10 @@ class session  				include($phpbb_root_path . "includes/captcha/captcha_factory." . $phpEx);  			}  			phpbb_captcha_factory::garbage_collect($config['captcha_plugin']); + +			$sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' +				WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']); +			$db->sql_query($sql);  		}  		return; @@ -1251,6 +1270,12 @@ class session  			$ip = $this->ip;  		} +		// Neither Spamhaus nor Spamcop supports IPv6 addresses. +		if (strpos($ip, ':') !== false) +		{ +			return false; +		} +  		$dnsbl_check = array(  			'sbl.spamhaus.org'	=> 'http://www.spamhaus.org/query/bl?ip=',  		); @@ -1433,7 +1458,7 @@ class session  	*/  	function validate_referer($check_script_path = false)  	{ -		global $config; +		global $config, $request;  		// no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason)  		if (empty($this->referer) || empty($this->host)) @@ -1451,7 +1476,7 @@ class session  		else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '')  		{  			$ref = substr($ref, strlen($host)); -			$server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT'); +			$server_port = $request->server('SERVER_PORT', 0);  			if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0)  			{ @@ -1494,900 +1519,3 @@ class session  		$db->sql_query($sql);  	}  } - - -/** -* Base user class -* -* This is the overarching class which contains (through session extend) -* all methods utilised for user functionality during a session. -* -* @package phpBB3 -*/ -class user extends session -{ -	var $lang = array(); -	var $help = array(); -	var $theme = array(); -	var $date_format; -	var $timezone; -	var $dst; - -	var $lang_name = false; -	var $lang_id = false; -	var $lang_path; -	var $img_lang; -	var $img_array = array(); - -	// Able to add new options (up to id 31) -	var $keyoptions = array('viewimg' => 0, 'viewflash' => 1, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'popuppm' => 10, 'sig_bbcode' => 15, 'sig_smilies' => 16, 'sig_links' => 17); -	var $keyvalues = array(); - -	/** -	* Constructor to set the lang path -	*/ -	function user() -	{ -		global $phpbb_root_path; - -		$this->lang_path = $phpbb_root_path . 'language/'; -	} - -	/** -	* Function to set custom language path (able to use directory outside of phpBB) -	* -	* @param string $lang_path New language path used. -	* @access public -	*/ -	function set_custom_lang_path($lang_path) -	{ -		$this->lang_path = $lang_path; - -		if (substr($this->lang_path, -1) != '/') -		{ -			$this->lang_path .= '/'; -		} -	} - -	/** -	* Setup basic user-specific items (style, language, ...) -	*/ -	function setup($lang_set = false, $style = false) -	{ -		global $db, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; - -		if ($this->data['user_id'] != ANONYMOUS) -		{ -			$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; -		} -		else -		{ -			$this->lang_name = basename($config['default_lang']); -			$this->date_format = $config['default_dateformat']; -			$this->timezone = $config['board_timezone'] * 3600; -			$this->dst = $config['board_dst'] * 3600; - -			/** -			* If a guest user is surfing, we try to guess his/her language first by obtaining the browser language -			* If re-enabled we need to make sure only those languages installed are checked -			* Commented out so we do not loose the code. - -			if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) -			{ -				$accept_lang_ary = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); - -				foreach ($accept_lang_ary as $accept_lang) -				{ -					// Set correct format ... guess full xx_YY form -					$accept_lang = substr($accept_lang, 0, 2) . '_' . strtoupper(substr($accept_lang, 3, 2)); -					$accept_lang = basename($accept_lang); - -					if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) -					{ -						$this->lang_name = $config['default_lang'] = $accept_lang; -						break; -					} -					else -					{ -						// No match on xx_YY so try xx -						$accept_lang = substr($accept_lang, 0, 2); -						$accept_lang = basename($accept_lang); - -						if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) -						{ -							$this->lang_name = $config['default_lang'] = $accept_lang; -							break; -						} -					} -				} -			} -			*/ -		} - -		// We include common language file here to not load it every time a custom language file is included -		$lang = &$this->lang; - -		// Do not suppress error if in DEBUG_EXTRA mode -		$include_result = (defined('DEBUG_EXTRA')) ? (include $this->lang_path . $this->lang_name . "/common.$phpEx") : (@include $this->lang_path . $this->lang_name . "/common.$phpEx"); - -		if ($include_result === false) -		{ -			die('Language file ' . $this->lang_path . $this->lang_name . "/common.$phpEx" . " couldn't be opened."); -		} - -		$this->add_lang($lang_set); -		unset($lang_set); - -		$style_request = request_var('style', 0); -		if ($style_request && $auth->acl_get('a_styles') && !defined('ADMIN_START')) -		{ -			global $SID, $_EXTRA_URL; - -			$style = $style_request; -			$SID .= '&style=' . $style; -			$_EXTRA_URL = array('style=' . $style); -		} -		else -		{ -			// Set up style -			$style = ($style) ? $style : ((!$config['override_user_style']) ? $this->data['user_style'] : $config['default_style']); -		} - -		$sql = 'SELECT s.style_id, t.template_storedb, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_name, c.theme_storedb, c.theme_id, i.imageset_path, i.imageset_id, i.imageset_name -			FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . " i -			WHERE s.style_id = $style -				AND t.template_id = s.template_id -				AND c.theme_id = s.theme_id -				AND i.imageset_id = s.imageset_id"; -		$result = $db->sql_query($sql, 3600); -		$this->theme = $db->sql_fetchrow($result); -		$db->sql_freeresult($result); - -		// User has wrong style -		if (!$this->theme && $style == $this->data['user_style']) -		{ -			$style = $this->data['user_style'] = $config['default_style']; - -			$sql = 'UPDATE ' . USERS_TABLE . " -				SET user_style = $style -				WHERE user_id = {$this->data['user_id']}"; -			$db->sql_query($sql); - -			$sql = 'SELECT s.style_id, t.template_storedb, t.template_path, t.template_id, t.bbcode_bitfield, c.theme_path, c.theme_name, c.theme_storedb, c.theme_id, i.imageset_path, i.imageset_id, i.imageset_name -				FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . ' t, ' . STYLES_THEME_TABLE . ' c, ' . STYLES_IMAGESET_TABLE . " i -				WHERE s.style_id = $style -					AND t.template_id = s.template_id -					AND c.theme_id = s.theme_id -					AND i.imageset_id = s.imageset_id"; -			$result = $db->sql_query($sql, 3600); -			$this->theme = $db->sql_fetchrow($result); -			$db->sql_freeresult($result); -		} - -		if (!$this->theme) -		{ -			trigger_error('Could not get style data', E_USER_ERROR); -		} - -		// Now parse the cfg file and cache it -		$parsed_items = $cache->obtain_cfg_items($this->theme); - -		// We are only interested in the theme configuration for now -		$parsed_items = $parsed_items['theme']; - -		$check_for = array( -			'parse_css_file'	=> (int) 0, -			'pagination_sep'	=> (string) ', ' -		); - -		foreach ($check_for as $key => $default_value) -		{ -			$this->theme[$key] = (isset($parsed_items[$key])) ? $parsed_items[$key] : $default_value; -			settype($this->theme[$key], gettype($default_value)); - -			if (is_string($default_value)) -			{ -				$this->theme[$key] = htmlspecialchars($this->theme[$key]); -			} -		} - -		// If the style author specified the theme needs to be cached -		// (because of the used paths and variables) than make sure it is the case. -		// For example, if the theme uses language-specific images it needs to be stored in db. -		if (!$this->theme['theme_storedb'] && $this->theme['parse_css_file']) -		{ -			$this->theme['theme_storedb'] = 1; - -			$stylesheet = file_get_contents("{$phpbb_root_path}styles/{$this->theme['theme_path']}/theme/stylesheet.css"); -			// Match CSS imports -			$matches = array(); -			preg_match_all('/@import url\(["\'](.*)["\']\);/i', $stylesheet, $matches); - -			if (sizeof($matches)) -			{ -				$content = ''; -				foreach ($matches[0] as $idx => $match) -				{ -					if ($content = @file_get_contents("{$phpbb_root_path}styles/{$this->theme['theme_path']}/theme/" . $matches[1][$idx])) -					{ -						$content = trim($content); -					} -					else -					{ -						$content = ''; -					} -					$stylesheet = str_replace($match, $content, $stylesheet); -				} -				unset($content); -			} - -			$stylesheet = str_replace('./', 'styles/' . $this->theme['theme_path'] . '/theme/', $stylesheet); - -			$sql_ary = array( -				'theme_data'	=> $stylesheet, -				'theme_mtime'	=> time(), -				'theme_storedb'	=> 1 -			); - -			$sql = 'UPDATE ' . STYLES_THEME_TABLE . ' -				SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' -				WHERE theme_id = ' . $this->theme['theme_id']; -			$db->sql_query($sql); - -			unset($sql_ary); -		} - -		$template->set_template(); - -		$this->img_lang = (file_exists($phpbb_root_path . 'styles/' . $this->theme['imageset_path'] . '/imageset/' . $this->lang_name)) ? $this->lang_name : $config['default_lang']; - -		// Same query in style.php -		$sql = 'SELECT * -			FROM ' . STYLES_IMAGESET_DATA_TABLE . ' -			WHERE imageset_id = ' . $this->theme['imageset_id'] . " -			AND image_filename <> '' -			AND image_lang IN ('" . $db->sql_escape($this->img_lang) . "', '')"; -		$result = $db->sql_query($sql, 3600); - -		$localised_images = false; -		while ($row = $db->sql_fetchrow($result)) -		{ -			if ($row['image_lang']) -			{ -				$localised_images = true; -			} - -			$row['image_filename'] = rawurlencode($row['image_filename']); -			$this->img_array[$row['image_name']] = $row; -		} -		$db->sql_freeresult($result); - -		// there were no localised images, try to refresh the localised imageset for the user's language -		if (!$localised_images) -		{ -			// Attention: this code ignores the image definition list from acp_styles and just takes everything -			// that the config file contains -			$sql_ary = array(); - -			$db->sql_transaction('begin'); - -			$sql = 'DELETE FROM ' . STYLES_IMAGESET_DATA_TABLE . ' -				WHERE imageset_id = ' . $this->theme['imageset_id'] . ' -					AND image_lang = \'' . $db->sql_escape($this->img_lang) . '\''; -			$result = $db->sql_query($sql); - -			if (@file_exists("{$phpbb_root_path}styles/{$this->theme['imageset_path']}/imageset/{$this->img_lang}/imageset.cfg")) -			{ -				$cfg_data_imageset_data = parse_cfg_file("{$phpbb_root_path}styles/{$this->theme['imageset_path']}/imageset/{$this->img_lang}/imageset.cfg"); -				foreach ($cfg_data_imageset_data as $image_name => $value) -				{ -					if (strpos($value, '*') !== false) -					{ -						if (substr($value, -1, 1) === '*') -						{ -							list($image_filename, $image_height) = explode('*', $value); -							$image_width = 0; -						} -						else -						{ -							list($image_filename, $image_height, $image_width) = explode('*', $value); -						} -					} -					else -					{ -						$image_filename = $value; -						$image_height = $image_width = 0; -					} - -					if (strpos($image_name, 'img_') === 0 && $image_filename) -					{ -						$image_name = substr($image_name, 4); -						$sql_ary[] = array( -							'image_name'		=> (string) $image_name, -							'image_filename'	=> (string) $image_filename, -							'image_height'		=> (int) $image_height, -							'image_width'		=> (int) $image_width, -							'imageset_id'		=> (int) $this->theme['imageset_id'], -							'image_lang'		=> (string) $this->img_lang, -						); -					} -				} -			} - -			if (sizeof($sql_ary)) -			{ -				$db->sql_multi_insert(STYLES_IMAGESET_DATA_TABLE, $sql_ary); -				$db->sql_transaction('commit'); -				$cache->destroy('sql', STYLES_IMAGESET_DATA_TABLE); - -				add_log('admin', 'LOG_IMAGESET_LANG_REFRESHED', $this->theme['imageset_name'], $this->img_lang); -			} -			else -			{ -				$db->sql_transaction('commit'); -				add_log('admin', 'LOG_IMAGESET_LANG_MISSING', $this->theme['imageset_name'], $this->img_lang); -			} -		} - -		// Call phpbb_user_session_handler() in case external application want to "bend" some variables or replace classes... -		// After calling it we continue script execution... -		phpbb_user_session_handler(); - -		// If this function got called from the error handler we are finished here. -		if (defined('IN_ERROR_HANDLER')) -		{ -			return; -		} - -		// Disable board if the install/ directory is still present -		// For the brave development army we do not care about this, else we need to comment out this everytime we develop locally -		if (!defined('DEBUG_EXTRA') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install') && !is_file($phpbb_root_path . 'install')) -		{ -			// Adjust the message slightly according to the permissions -			if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) -			{ -				$message = 'REMOVE_INSTALL'; -			} -			else -			{ -				$message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; -			} -			trigger_error($message); -		} - -		// Is board disabled and user not an admin or moderator? -		if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) -		{ -			if ($this->data['is_bot']) -			{ -				send_status_line(503, 'Service Unavailable'); -			} - -			$message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; -			trigger_error($message); -		} - -		// Is load exceeded? -		if ($config['limit_load'] && $this->load !== false) -		{ -			if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !defined('IN_ADMIN')) -			{ -				// Set board disabled to true to let the admins/mods get the proper notification -				$config['board_disable'] = '1'; - -				if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) -				{ -					if ($this->data['is_bot']) -					{ -						send_status_line(503, 'Service Unavailable'); -					} -					trigger_error('BOARD_UNAVAILABLE'); -				} -			} -		} - -		if (isset($this->data['session_viewonline'])) -		{ -			// Make sure the user is able to hide his session -			if (!$this->data['session_viewonline']) -			{ -				// Reset online status if not allowed to hide the session... -				if (!$auth->acl_get('u_hideonline')) -				{ -					$sql = 'UPDATE ' . SESSIONS_TABLE . ' -						SET session_viewonline = 1 -						WHERE session_user_id = ' . $this->data['user_id']; -					$db->sql_query($sql); -					$this->data['session_viewonline'] = 1; -				} -			} -			else if (!$this->data['user_allow_viewonline']) -			{ -				// the user wants to hide and is allowed to  -> cloaking device on. -				if ($auth->acl_get('u_hideonline')) -				{ -					$sql = 'UPDATE ' . SESSIONS_TABLE . ' -						SET session_viewonline = 0 -						WHERE session_user_id = ' . $this->data['user_id']; -					$db->sql_query($sql); -					$this->data['session_viewonline'] = 0; -				} -			} -		} - - -		// Does the user need to change their password? If so, redirect to the -		// ucp profile reg_details page ... of course do not redirect if we're already in the ucp -		if (!defined('IN_ADMIN') && !defined('ADMIN_START') && $config['chg_passforce'] && !empty($this->data['is_registered']) && $auth->acl_get('u_chgpasswd') && $this->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400)) -		{ -			if (strpos($this->page['query_string'], 'mode=reg_details') === false && $this->page['page_name'] != "ucp.$phpEx") -			{ -				redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=profile&mode=reg_details')); -			} -		} - -		return; -	} - -	/** -	* More advanced language substitution -	* Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms. -	* Params are the language key and the parameters to be substituted. -	* This function/functionality is inspired by SHS` and Ashe. -	* -	* Example call: <samp>$user->lang('NUM_POSTS_IN_QUEUE', 1);</samp> -	*/ -	function lang() -	{ -		$args = func_get_args(); -		$key = $args[0]; - -		if (is_array($key)) -		{ -			$lang = &$this->lang[array_shift($key)]; - -			foreach ($key as $_key) -			{ -				$lang = &$lang[$_key]; -			} -		} -		else -		{ -			$lang = &$this->lang[$key]; -		} - -		// Return if language string does not exist -		if (!isset($lang) || (!is_string($lang) && !is_array($lang))) -		{ -			return $key; -		} - -		// If the language entry is a string, we simply mimic sprintf() behaviour -		if (is_string($lang)) -		{ -			if (sizeof($args) == 1) -			{ -				return $lang; -			} - -			// Replace key with language entry and simply pass along... -			$args[0] = $lang; -			return call_user_func_array('sprintf', $args); -		} - -		// It is an array... now handle different nullar/singular/plural forms -		$key_found = false; - -		// We now get the first number passed and will select the key based upon this number -		for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++) -		{ -			if (is_int($args[$i])) -			{ -				$numbers = array_keys($lang); - -				foreach ($numbers as $num) -				{ -					if ($num > $args[$i]) -					{ -						break; -					} - -					$key_found = $num; -				} -				break; -			} -		} - -		// Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) -		if ($key_found === false) -		{ -			$numbers = array_keys($lang); -			$key_found = end($numbers); -		} - -		// Use the language string we determined and pass it to sprintf() -		$args[0] = $lang[$key_found]; -		return call_user_func_array('sprintf', $args); -	} - -	/** -	* Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion) -	* -	* @param mixed $lang_set specifies the language entries to include -	* @param bool $use_db internal variable for recursion, do not use -	* @param bool $use_help internal variable for recursion, do not use -	* -	* Examples: -	* <code> -	* $lang_set = array('posting', 'help' => 'faq'); -	* $lang_set = array('posting', 'viewtopic', 'help' => array('bbcode', 'faq')) -	* $lang_set = array(array('posting', 'viewtopic'), 'help' => array('bbcode', 'faq')) -	* $lang_set = 'posting' -	* $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting')) -	* </code> -	*/ -	function add_lang($lang_set, $use_db = false, $use_help = false) -	{ -		global $phpEx; - -		if (is_array($lang_set)) -		{ -			foreach ($lang_set as $key => $lang_file) -			{ -				// Please do not delete this line. -				// We have to force the type here, else [array] language inclusion will not work -				$key = (string) $key; - -				if ($key == 'db') -				{ -					$this->add_lang($lang_file, true, $use_help); -				} -				else if ($key == 'help') -				{ -					$this->add_lang($lang_file, $use_db, true); -				} -				else if (!is_array($lang_file)) -				{ -					$this->set_lang($this->lang, $this->help, $lang_file, $use_db, $use_help); -				} -				else -				{ -					$this->add_lang($lang_file, $use_db, $use_help); -				} -			} -			unset($lang_set); -		} -		else if ($lang_set) -		{ -			$this->set_lang($this->lang, $this->help, $lang_set, $use_db, $use_help); -		} -	} - -	/** -	* Set language entry (called by add_lang) -	* @access private -	*/ -	function set_lang(&$lang, &$help, $lang_file, $use_db = false, $use_help = false) -	{ -		global $phpEx; - -		// Make sure the language name is set (if the user setup did not happen it is not set) -		if (!$this->lang_name) -		{ -			global $config; -			$this->lang_name = basename($config['default_lang']); -		} - -		// $lang == $this->lang -		// $help == $this->help -		// - add appropriate variables here, name them as they are used within the language file... -		if (!$use_db) -		{ -			if ($use_help && strpos($lang_file, '/') !== false) -			{ -				$language_filename = $this->lang_path . $this->lang_name . '/' . substr($lang_file, 0, stripos($lang_file, '/') + 1) . 'help_' . substr($lang_file, stripos($lang_file, '/') + 1) . '.' . $phpEx; -			} -			else -			{ -				$language_filename = $this->lang_path . $this->lang_name . '/' . (($use_help) ? 'help_' : '') . $lang_file . '.' . $phpEx; -			} - -			if (!file_exists($language_filename)) -			{ -				global $config; - -				if ($this->lang_name == 'en') -				{ -					// The user's selected language is missing the file, the board default's language is missing the file, and the file doesn't exist in /en. -					$language_filename = str_replace($this->lang_path . 'en', $this->lang_path . $this->data['user_lang'], $language_filename); -					trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); -				} -				else if ($this->lang_name == basename($config['default_lang'])) -				{ -					// Fall back to the English Language -					$this->lang_name = 'en'; -					$this->set_lang($lang, $help, $lang_file, $use_db, $use_help); -				} -				else if ($this->lang_name == $this->data['user_lang']) -				{ -					// Fall back to the board default language -					$this->lang_name = basename($config['default_lang']); -					$this->set_lang($lang, $help, $lang_file, $use_db, $use_help); -				} - -				// Reset the lang name -				$this->lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); -				return; -			} - -			// Do not suppress error if in DEBUG_EXTRA mode -			$include_result = (defined('DEBUG_EXTRA')) ? (include $language_filename) : (@include $language_filename); - -			if ($include_result === false) -			{ -				trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); -			} -		} -		else if ($use_db) -		{ -			// Get Database Language Strings -			// Put them into $lang if nothing is prefixed, put them into $help if help: is prefixed -			// For example: help:faq, posting -		} -	} - -	/** -	* Format user date -	* -	* @param int $gmepoch unix timestamp -	* @param string $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i. -	* @param bool $forcedate force non-relative date format. -	* -	* @return mixed translated date -	*/ -	function format_date($gmepoch, $format = false, $forcedate = false) -	{ -		static $midnight; -		static $date_cache; - -		$format = (!$format) ? $this->date_format : $format; -		$now = time(); -		$delta = $now - $gmepoch; - -		if (!isset($date_cache[$format])) -		{ -			// Is the user requesting a friendly date format (i.e. 'Today 12:42')? -			$date_cache[$format] = array( -				'is_short'		=> strpos($format, '|'), -				'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']; -			} -		} - -		// 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'])) -		{ -			return $this->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); -		} - -		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; - -			if ($gmepoch > $midnight + 86400) -			{ -				$day = 'TOMORROW'; -			} -			else if ($gmepoch > $midnight) -			{ -				$day = 'TODAY'; -			} -			else if ($gmepoch > $midnight - 86400) -			{ -				$day = 'YESTERDAY'; -			} - -			if ($day !== false) -			{ -				return str_replace('||', $this->lang['datetime'][$day], strtr(@gmdate($date_cache[$format]['format_short'], $gmepoch + $zone_offset), $date_cache[$format]['lang'])); -			} -		} - -		return strtr(@gmdate($date_cache[$format]['format_long'], $gmepoch + $zone_offset), $date_cache[$format]['lang']); -	} - -	/** -	* Get language id currently used by the user -	*/ -	function get_iso_lang_id() -	{ -		global $config, $db; - -		if (!empty($this->lang_id)) -		{ -			return $this->lang_id; -		} - -		if (!$this->lang_name) -		{ -			$this->lang_name = $config['default_lang']; -		} - -		$sql = 'SELECT lang_id -			FROM ' . LANG_TABLE . " -			WHERE lang_iso = '" . $db->sql_escape($this->lang_name) . "'"; -		$result = $db->sql_query($sql); -		$this->lang_id = (int) $db->sql_fetchfield('lang_id'); -		$db->sql_freeresult($result); - -		return $this->lang_id; -	} - -	/** -	* Get users profile fields -	*/ -	function get_profile_fields($user_id) -	{ -		global $db; - -		if (isset($this->profile_fields)) -		{ -			return; -		} - -		$sql = 'SELECT * -			FROM ' . PROFILE_FIELDS_DATA_TABLE . " -			WHERE user_id = $user_id"; -		$result = $db->sql_query_limit($sql, 1); -		$this->profile_fields = (!($row = $db->sql_fetchrow($result))) ? array() : $row; -		$db->sql_freeresult($result); -	} - -	/** -	* Specify/Get image -	* $suffix is no longer used - we know it. ;) It is there for backward compatibility. -	*/ -	function img($img, $alt = '', $width = false, $suffix = '', $type = 'full_tag') -	{ -		static $imgs; -		global $phpbb_root_path; - -		$img_data = &$imgs[$img]; - -		if (empty($img_data)) -		{ -			if (!isset($this->img_array[$img])) -			{ -				// Do not fill the image to let designers decide what to do if the image is empty -				$img_data = ''; -				return $img_data; -			} - -			// Use URL if told so -			$root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_root_path; - -			$img_data['src'] = $root_path . 'styles/' . rawurlencode($this->theme['imageset_path']) . '/imageset/' . ($this->img_array[$img]['image_lang'] ? $this->img_array[$img]['image_lang'] .'/' : '') . $this->img_array[$img]['image_filename']; -			$img_data['width'] = $this->img_array[$img]['image_width']; -			$img_data['height'] = $this->img_array[$img]['image_height']; -		} - -		$alt = (!empty($this->lang[$alt])) ? $this->lang[$alt] : $alt; - -		switch ($type) -		{ -			case 'src': -				return $img_data['src']; -			break; - -			case 'width': -				return ($width === false) ? $img_data['width'] : $width; -			break; - -			case 'height': -				return $img_data['height']; -			break; - -			default: -				$use_width = ($width === false) ? $img_data['width'] : $width; - -				return '<img src="' . $img_data['src'] . '"' . (($use_width) ? ' width="' . $use_width . '"' : '') . (($img_data['height']) ? ' height="' . $img_data['height'] . '"' : '') . ' alt="' . $alt . '" title="' . $alt . '" />'; -			break; -		} -	} - -	/** -	* Get option bit field from user options -	*/ -	function optionget($key, $data = false) -	{ -		if (!isset($this->keyvalues[$key])) -		{ -			$var = ($data) ? $data : $this->data['user_options']; -			$this->keyvalues[$key] = ($var & 1 << $this->keyoptions[$key]) ? true : false; -		} - -		return $this->keyvalues[$key]; -	} - -	/** -	* Set option bit field for user options -	*/ -	function optionset($key, $value, $data = false) -	{ -		$var = ($data) ? $data : $this->data['user_options']; - -		if ($value && !($var & 1 << $this->keyoptions[$key])) -		{ -			$var += 1 << $this->keyoptions[$key]; -		} -		else if (!$value && ($var & 1 << $this->keyoptions[$key])) -		{ -			$var -= 1 << $this->keyoptions[$key]; -		} -		else -		{ -			return ($data) ? $var : false; -		} - -		if (!$data) -		{ -			$this->data['user_options'] = $var; -			return true; -		} -		else -		{ -			return $var; -		} -	} - -	/** -	* Funtion to make the user leave the NEWLY_REGISTERED system group. -	* @access public -	*/ -	function leave_newly_registered() -	{ -		global $db; - -		if (empty($this->data['user_new'])) -		{ -			return false; -		} - -		if (!function_exists('remove_newly_registered')) -		{ -			global $phpbb_root_path, $phpEx; - -			include($phpbb_root_path . 'includes/functions_user.' . $phpEx); -		} -		if ($group = remove_newly_registered($this->data['user_id'], $this->data)) -		{ -			$this->data['group_id'] = $group; - -		} -		$this->data['user_permissions'] = ''; -		$this->data['user_new'] = 0; - -		return true; -	} -} diff --git a/phpBB/includes/sphinxapi.php b/phpBB/includes/sphinxapi.php new file mode 100644 index 0000000000..bd83b1d2e0 --- /dev/null +++ b/phpBB/includes/sphinxapi.php @@ -0,0 +1,1712 @@ +<?php
 +
 +//
 +// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
 +//
 +
 +//
 +// Copyright (c) 2001-2012, Andrew Aksyonoff
 +// Copyright (c) 2008-2012, Sphinx Technologies Inc
 +// All rights reserved
 +//
 +// This program is free software; you can redistribute it and/or modify
 +// it under the terms of the GNU General Public License. You should have
 +// received a copy of the GPL license along with this program; if you
 +// did not, you can find it at http://www.gnu.org/
 +//
 +
 +/////////////////////////////////////////////////////////////////////////////
 +// PHP version of Sphinx searchd client (PHP API)
 +/////////////////////////////////////////////////////////////////////////////
 +
 +/// known searchd commands
 +define ( "SEARCHD_COMMAND_SEARCH",		0 );
 +define ( "SEARCHD_COMMAND_EXCERPT",		1 );
 +define ( "SEARCHD_COMMAND_UPDATE",		2 );
 +define ( "SEARCHD_COMMAND_KEYWORDS",	3 );
 +define ( "SEARCHD_COMMAND_PERSIST",		4 );
 +define ( "SEARCHD_COMMAND_STATUS",		5 );
 +define ( "SEARCHD_COMMAND_FLUSHATTRS",	7 );
 +
 +/// current client-side command implementation versions
 +define ( "VER_COMMAND_SEARCH",		0x119 );
 +define ( "VER_COMMAND_EXCERPT",		0x104 );
 +define ( "VER_COMMAND_UPDATE",		0x102 );
 +define ( "VER_COMMAND_KEYWORDS",	0x100 );
 +define ( "VER_COMMAND_STATUS",		0x100 );
 +define ( "VER_COMMAND_QUERY",		0x100 );
 +define ( "VER_COMMAND_FLUSHATTRS",	0x100 );
 +
 +/// known searchd status codes
 +define ( "SEARCHD_OK",				0 );
 +define ( "SEARCHD_ERROR",			1 );
 +define ( "SEARCHD_RETRY",			2 );
 +define ( "SEARCHD_WARNING",			3 );
 +
 +/// known match modes
 +define ( "SPH_MATCH_ALL",			0 );
 +define ( "SPH_MATCH_ANY",			1 );
 +define ( "SPH_MATCH_PHRASE",		2 );
 +define ( "SPH_MATCH_BOOLEAN",		3 );
 +define ( "SPH_MATCH_EXTENDED",		4 );
 +define ( "SPH_MATCH_FULLSCAN",		5 );
 +define ( "SPH_MATCH_EXTENDED2",		6 );	// extended engine V2 (TEMPORARY, WILL BE REMOVED)
 +
 +/// known ranking modes (ext2 only)
 +define ( "SPH_RANK_PROXIMITY_BM25",	0 );	///< default mode, phrase proximity major factor and BM25 minor one
 +define ( "SPH_RANK_BM25",			1 );	///< statistical mode, BM25 ranking only (faster but worse quality)
 +define ( "SPH_RANK_NONE",			2 );	///< no ranking, all matches get a weight of 1
 +define ( "SPH_RANK_WORDCOUNT",		3 );	///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
 +define ( "SPH_RANK_PROXIMITY",		4 );
 +define ( "SPH_RANK_MATCHANY",		5 );
 +define ( "SPH_RANK_FIELDMASK",		6 );
 +define ( "SPH_RANK_SPH04",			7 );
 +define ( "SPH_RANK_EXPR",			8 );
 +define ( "SPH_RANK_TOTAL",			9 );
 +
 +/// known sort modes
 +define ( "SPH_SORT_RELEVANCE",		0 );
 +define ( "SPH_SORT_ATTR_DESC",		1 );
 +define ( "SPH_SORT_ATTR_ASC",		2 );
 +define ( "SPH_SORT_TIME_SEGMENTS", 	3 );
 +define ( "SPH_SORT_EXTENDED", 		4 );
 +define ( "SPH_SORT_EXPR", 			5 );
 +
 +/// known filter types
 +define ( "SPH_FILTER_VALUES",		0 );
 +define ( "SPH_FILTER_RANGE",		1 );
 +define ( "SPH_FILTER_FLOATRANGE",	2 );
 +
 +/// known attribute types
 +define ( "SPH_ATTR_INTEGER",		1 );
 +define ( "SPH_ATTR_TIMESTAMP",		2 );
 +define ( "SPH_ATTR_ORDINAL",		3 );
 +define ( "SPH_ATTR_BOOL",			4 );
 +define ( "SPH_ATTR_FLOAT",			5 );
 +define ( "SPH_ATTR_BIGINT",			6 );
 +define ( "SPH_ATTR_STRING",			7 );
 +define ( "SPH_ATTR_MULTI",			0x40000001 );
 +define ( "SPH_ATTR_MULTI64",			0x40000002 );
 +
 +/// known grouping functions
 +define ( "SPH_GROUPBY_DAY",			0 );
 +define ( "SPH_GROUPBY_WEEK",		1 );
 +define ( "SPH_GROUPBY_MONTH",		2 );
 +define ( "SPH_GROUPBY_YEAR",		3 );
 +define ( "SPH_GROUPBY_ATTR",		4 );
 +define ( "SPH_GROUPBY_ATTRPAIR",	5 );
 +
 +// important properties of PHP's integers:
 +//  - always signed (one bit short of PHP_INT_SIZE)
 +//  - conversion from string to int is saturated
 +//  - float is double
 +//  - div converts arguments to floats
 +//  - mod converts arguments to ints
 +
 +// the packing code below works as follows:
 +//  - when we got an int, just pack it
 +//    if performance is a problem, this is the branch users should aim for
 +//
 +//  - otherwise, we got a number in string form
 +//    this might be due to different reasons, but we assume that this is
 +//    because it didn't fit into PHP int
 +//
 +//  - factor the string into high and low ints for packing
 +//    - if we have bcmath, then it is used
 +//    - if we don't, we have to do it manually (this is the fun part)
 +//
 +//    - x64 branch does factoring using ints
 +//    - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
 +//
 +// unpacking routines are pretty much the same.
 +//  - return ints if we can
 +//  - otherwise format number into a string
 +
 +/// pack 64-bit signed
 +function sphPackI64 ( $v )
 +{
 +	assert ( is_numeric($v) );
 +	
 +	// x64
 +	if ( PHP_INT_SIZE>=8 )
 +	{
 +		$v = (int)$v;
 +		return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
 +	}
 +
 +	// x32, int
 +	if ( is_int($v) )
 +		return pack ( "NN", $v < 0 ? -1 : 0, $v );
 +
 +	// x32, bcmath	
 +	if ( function_exists("bcmul") )
 +	{
 +		if ( bccomp ( $v, 0 ) == -1 )
 +			$v = bcadd ( "18446744073709551616", $v );
 +		$h = bcdiv ( $v, "4294967296", 0 );
 +		$l = bcmod ( $v, "4294967296" );
 +		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
 +	}
 +
 +	// x32, no-bcmath
 +	$p = max(0, strlen($v) - 13);
 +	$lo = abs((float)substr($v, $p));
 +	$hi = abs((float)substr($v, 0, $p));
 +
 +	$m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
 +	$q = floor($m/4294967296.0);
 +	$l = $m - ($q*4294967296.0);
 +	$h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
 +
 +	if ( $v<0 )
 +	{
 +		if ( $l==0 )
 +			$h = 4294967296.0 - $h;
 +		else
 +		{
 +			$h = 4294967295.0 - $h;
 +			$l = 4294967296.0 - $l;
 +		}
 +	}
 +	return pack ( "NN", $h, $l );
 +}
 +
 +/// pack 64-bit unsigned
 +function sphPackU64 ( $v )
 +{
 +	assert ( is_numeric($v) );
 +	
 +	// x64
 +	if ( PHP_INT_SIZE>=8 )
 +	{
 +		assert ( $v>=0 );
 +		
 +		// x64, int
 +		if ( is_int($v) )
 +			return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
 +						  
 +		// x64, bcmath
 +		if ( function_exists("bcmul") )
 +		{
 +			$h = bcdiv ( $v, 4294967296, 0 );
 +			$l = bcmod ( $v, 4294967296 );
 +			return pack ( "NN", $h, $l );
 +		}
 +		
 +		// x64, no-bcmath
 +		$p = max ( 0, strlen($v) - 13 );
 +		$lo = (int)substr ( $v, $p );
 +		$hi = (int)substr ( $v, 0, $p );
 +	
 +		$m = $lo + $hi*1316134912;
 +		$l = $m % 4294967296;
 +		$h = $hi*2328 + (int)($m/4294967296);
 +
 +		return pack ( "NN", $h, $l );
 +	}
 +
 +	// x32, int
 +	if ( is_int($v) )
 +		return pack ( "NN", 0, $v );
 +	
 +	// x32, bcmath
 +	if ( function_exists("bcmul") )
 +	{
 +		$h = bcdiv ( $v, "4294967296", 0 );
 +		$l = bcmod ( $v, "4294967296" );
 +		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
 +	}
 +
 +	// x32, no-bcmath
 +	$p = max(0, strlen($v) - 13);
 +	$lo = (float)substr($v, $p);
 +	$hi = (float)substr($v, 0, $p);
 +	
 +	$m = $lo + $hi*1316134912.0;
 +	$q = floor($m / 4294967296.0);
 +	$l = $m - ($q * 4294967296.0);
 +	$h = $hi*2328.0 + $q;
 +
 +	return pack ( "NN", $h, $l );
 +}
 +
 +// unpack 64-bit unsigned
 +function sphUnpackU64 ( $v )
 +{
 +	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
 +
 +	if ( PHP_INT_SIZE>=8 )
 +	{
 +		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
 +		if ( $lo<0 ) $lo += (1<<32);
 +
 +		// x64, int
 +		if ( $hi<=2147483647 )
 +			return ($hi<<32) + $lo;
 +
 +		// x64, bcmath
 +		if ( function_exists("bcmul") )
 +			return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
 +
 +		// x64, no-bcmath
 +		$C = 100000;
 +		$h = ((int)($hi / $C) << 32) + (int)($lo / $C);
 +		$l = (($hi % $C) << 32) + ($lo % $C);
 +		if ( $l>$C )
 +		{
 +			$h += (int)($l / $C);
 +			$l  = $l % $C;
 +		}
 +
 +		if ( $h==0 )
 +			return $l;
 +		return sprintf ( "%d%05d", $h, $l );
 +	}
 +
 +	// x32, int
 +	if ( $hi==0 )
 +	{
 +		if ( $lo>0 )
 +			return $lo;
 +		return sprintf ( "%u", $lo );
 +	}
 +
 +	$hi = sprintf ( "%u", $hi );
 +	$lo = sprintf ( "%u", $lo );
 +
 +	// x32, bcmath
 +	if ( function_exists("bcmul") )
 +		return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
 +	
 +	// x32, no-bcmath
 +	$hi = (float)$hi;
 +	$lo = (float)$lo;
 +	
 +	$q = floor($hi/10000000.0);
 +	$r = $hi - $q*10000000.0;
 +	$m = $lo + $r*4967296.0;
 +	$mq = floor($m/10000000.0);
 +	$l = $m - $mq*10000000.0;
 +	$h = $q*4294967296.0 + $r*429.0 + $mq;
 +
 +	$h = sprintf ( "%.0f", $h );
 +	$l = sprintf ( "%07.0f", $l );
 +	if ( $h=="0" )
 +		return sprintf( "%.0f", (float)$l );
 +	return $h . $l;
 +}
 +
 +// unpack 64-bit signed
 +function sphUnpackI64 ( $v )
 +{
 +	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
 +
 +	// x64
 +	if ( PHP_INT_SIZE>=8 )
 +	{
 +		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
 +		if ( $lo<0 ) $lo += (1<<32);
 +
 +		return ($hi<<32) + $lo;
 +	}
 +
 +	// x32, int
 +	if ( $hi==0 )
 +	{
 +		if ( $lo>0 )
 +			return $lo;
 +		return sprintf ( "%u", $lo );
 +	}
 +	// x32, int
 +	elseif ( $hi==-1 )
 +	{
 +		if ( $lo<0 )
 +			return $lo;
 +		return sprintf ( "%.0f", $lo - 4294967296.0 );
 +	}
 +	
 +	$neg = "";
 +	$c = 0;
 +	if ( $hi<0 )
 +	{
 +		$hi = ~$hi;
 +		$lo = ~$lo;
 +		$c = 1;
 +		$neg = "-";
 +	}	
 +
 +	$hi = sprintf ( "%u", $hi );
 +	$lo = sprintf ( "%u", $lo );
 +
 +	// x32, bcmath
 +	if ( function_exists("bcmul") )
 +		return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
 +
 +	// x32, no-bcmath
 +	$hi = (float)$hi;
 +	$lo = (float)$lo;
 +	
 +	$q = floor($hi/10000000.0);
 +	$r = $hi - $q*10000000.0;
 +	$m = $lo + $r*4967296.0;
 +	$mq = floor($m/10000000.0);
 +	$l = $m - $mq*10000000.0 + $c;
 +	$h = $q*4294967296.0 + $r*429.0 + $mq;
 +	if ( $l==10000000 )
 +	{
 +		$l = 0;
 +		$h += 1;
 +	}
 +
 +	$h = sprintf ( "%.0f", $h );
 +	$l = sprintf ( "%07.0f", $l );
 +	if ( $h=="0" )
 +		return $neg . sprintf( "%.0f", (float)$l );
 +	return $neg . $h . $l;
 +}
 +
 +
 +function sphFixUint ( $value )
 +{
 +	if ( PHP_INT_SIZE>=8 )
 +	{
 +		// x64 route, workaround broken unpack() in 5.2.2+
 +		if ( $value<0 ) $value += (1<<32);
 +		return $value;
 +	}
 +	else
 +	{
 +		// x32 route, workaround php signed/unsigned braindamage
 +		return sprintf ( "%u", $value );
 +	}
 +}
 +
 +
 +/// sphinx searchd client class
 +class SphinxClient
 +{
 +	var $_host;			///< searchd host (default is "localhost")
 +	var $_port;			///< searchd port (default is 9312)
 +	var $_offset;		///< how many records to seek from result-set start (default is 0)
 +	var $_limit;		///< how many records to return from result-set starting at offset (default is 20)
 +	var $_mode;			///< query matching mode (default is SPH_MATCH_ALL)
 +	var $_weights;		///< per-field weights (default is 1 for all fields)
 +	var $_sort;			///< match sorting mode (default is SPH_SORT_RELEVANCE)
 +	var $_sortby;		///< attribute to sort by (defualt is "")
 +	var $_min_id;		///< min ID to match (default is 0, which means no limit)
 +	var $_max_id;		///< max ID to match (default is 0, which means no limit)
 +	var $_filters;		///< search filters
 +	var $_groupby;		///< group-by attribute name
 +	var $_groupfunc;	///< group-by function (to pre-process group-by attribute value with)
 +	var $_groupsort;	///< group-by sorting clause (to sort groups in result set with)
 +	var $_groupdistinct;///< group-by count-distinct attribute
 +	var $_maxmatches;	///< max matches to retrieve
 +	var $_cutoff;		///< cutoff to stop searching at (default is 0)
 +	var $_retrycount;	///< distributed retries count
 +	var $_retrydelay;	///< distributed retries delay
 +	var $_anchor;		///< geographical anchor point
 +	var $_indexweights;	///< per-index weights
 +	var $_ranker;		///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
 +	var $_rankexpr;		///< ranking mode expression (for SPH_RANK_EXPR)
 +	var $_maxquerytime;	///< max query time, milliseconds (default is 0, do not limit)
 +	var $_fieldweights;	///< per-field-name weights
 +	var $_overrides;	///< per-query attribute values overrides
 +	var $_select;		///< select-list (attributes or expressions, with optional aliases)
 +
 +	var $_error;		///< last error message
 +	var $_warning;		///< last warning message
 +	var $_connerror;		///< connection error vs remote error flag
 +
 +	var $_reqs;			///< requests array for multi-query
 +	var $_mbenc;		///< stored mbstring encoding
 +	var $_arrayresult;	///< whether $result["matches"] should be a hash or an array
 +	var $_timeout;		///< connect timeout
 +
 +	/////////////////////////////////////////////////////////////////////////////
 +	// common stuff
 +	/////////////////////////////////////////////////////////////////////////////
 +
 +	/// create a new client object and fill defaults
 +	function SphinxClient ()
 +	{
 +		// per-client-object settings
 +		$this->_host		= "localhost";
 +		$this->_port		= 9312;
 +		$this->_path		= false;
 +		$this->_socket		= false;
 +
 +		// per-query settings
 +		$this->_offset		= 0;
 +		$this->_limit		= 20;
 +		$this->_mode		= SPH_MATCH_ALL;
 +		$this->_weights		= array ();
 +		$this->_sort		= SPH_SORT_RELEVANCE;
 +		$this->_sortby		= "";
 +		$this->_min_id		= 0;
 +		$this->_max_id		= 0;
 +		$this->_filters		= array ();
 +		$this->_groupby		= "";
 +		$this->_groupfunc	= SPH_GROUPBY_DAY;
 +		$this->_groupsort	= "@group desc";
 +		$this->_groupdistinct= "";
 +		$this->_maxmatches	= 1000;
 +		$this->_cutoff		= 0;
 +		$this->_retrycount	= 0;
 +		$this->_retrydelay	= 0;
 +		$this->_anchor		= array ();
 +		$this->_indexweights= array ();
 +		$this->_ranker		= SPH_RANK_PROXIMITY_BM25;
 +		$this->_rankexpr	= "";
 +		$this->_maxquerytime= 0;
 +		$this->_fieldweights= array();
 +		$this->_overrides 	= array();
 +		$this->_select		= "*";
 +
 +		$this->_error		= ""; // per-reply fields (for single-query case)
 +		$this->_warning		= "";
 +		$this->_connerror	= false;
 +
 +		$this->_reqs		= array ();	// requests storage (for multi-query case)
 +		$this->_mbenc		= "";
 +		$this->_arrayresult	= false;
 +		$this->_timeout		= 0;
 +	}
 +
 +	function __destruct()
 +	{
 +		if ( $this->_socket !== false )
 +			fclose ( $this->_socket );
 +	}
 +
 +	/// get last error message (string)
 +	function GetLastError ()
 +	{
 +		return $this->_error;
 +	}
 +
 +	/// get last warning message (string)
 +	function GetLastWarning ()
 +	{
 +		return $this->_warning;
 +	}
 +
 +	/// get last error flag (to tell network connection errors from searchd errors or broken responses)
 +	function IsConnectError()
 +	{
 +		return $this->_connerror;
 +	}
 +
 +	/// set searchd host name (string) and port (integer)
 +	function SetServer ( $host, $port = 0 )
 +	{
 +		assert ( is_string($host) );
 +		if ( $host[0] == '/')
 +		{
 +			$this->_path = 'unix://' . $host;
 +			return;
 +		}
 +		if ( substr ( $host, 0, 7 )=="unix://" )
 +		{
 +			$this->_path = $host;
 +			return;
 +		}
 +				
 +		assert ( is_int($port) );
 +		$this->_host = $host;
 +		$this->_port = $port;
 +		$this->_path = '';
 +
 +	}
 +
 +	/// set server connection timeout (0 to remove)
 +	function SetConnectTimeout ( $timeout )
 +	{
 +		assert ( is_numeric($timeout) );
 +		$this->_timeout = $timeout;
 +	}
 +
 +
 +	function _Send ( $handle, $data, $length )
 +	{
 +		if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
 +		{
 +			$this->_error = 'connection unexpectedly closed (timed out?)';
 +			$this->_connerror = true;
 +			return false;
 +		}
 +		return true;
 +	}
 +
 +	/////////////////////////////////////////////////////////////////////////////
 +
 +	/// enter mbstring workaround mode
 +	function _MBPush ()
 +	{
 +		$this->_mbenc = "";
 +		if ( ini_get ( "mbstring.func_overload" ) & 2 )
 +		{
 +			$this->_mbenc = mb_internal_encoding();
 +			mb_internal_encoding ( "latin1" );
 +		}
 +    }
 +
 +	/// leave mbstring workaround mode
 +	function _MBPop ()
 +	{
 +		if ( $this->_mbenc )
 +			mb_internal_encoding ( $this->_mbenc );
 +	}
 +
 +	/// connect to searchd server
 +	function _Connect ()
 +	{
 +		if ( $this->_socket!==false )
 +		{
 +			// we are in persistent connection mode, so we have a socket
 +			// however, need to check whether it's still alive
 +			if ( !@feof ( $this->_socket ) )
 +				return $this->_socket;
 +
 +			// force reopen
 +			$this->_socket = false;
 +		}
 +
 +		$errno = 0;
 +		$errstr = "";
 +		$this->_connerror = false;
 +
 +		if ( $this->_path )
 +		{
 +			$host = $this->_path;
 +			$port = 0;
 +		}
 +		else
 +		{
 +			$host = $this->_host;
 +			$port = $this->_port;
 +		}
 +
 +		if ( $this->_timeout<=0 )
 +			$fp = @fsockopen ( $host, $port, $errno, $errstr );
 +		else
 +			$fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
 +		
 +		if ( !$fp )
 +		{
 +			if ( $this->_path )
 +				$location = $this->_path;
 +			else
 +				$location = "{$this->_host}:{$this->_port}";
 +			
 +			$errstr = trim ( $errstr );
 +			$this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
 +			$this->_connerror = true;
 +			return false;
 +		}
 +
 +		// send my version
 +		// this is a subtle part. we must do it before (!) reading back from searchd.
 +		// because otherwise under some conditions (reported on FreeBSD for instance)
 +		// TCP stack could throttle write-write-read pattern because of Nagle.
 +		if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
 +		{
 +			fclose ( $fp );
 +			$this->_error = "failed to send client protocol version";
 +			return false;
 +		}
 +
 +		// check version
 +		list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
 +		$v = (int)$v;
 +		if ( $v<1 )
 +		{
 +			fclose ( $fp );
 +			$this->_error = "expected searchd protocol version 1+, got version '$v'";
 +			return false;
 +		}
 +
 +		return $fp;
 +	}
 +
 +	/// get and check response packet from searchd server
 +	function _GetResponse ( $fp, $client_ver )
 +	{
 +		$response = "";
 +		$len = 0;
 +
 +		$header = fread ( $fp, 8 );
 +		if ( strlen($header)==8 )
 +		{
 +			list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
 +			$left = $len;
 +			while ( $left>0 && !feof($fp) )
 +			{
 +				$chunk = fread ( $fp, min ( 8192, $left ) );
 +				if ( $chunk )
 +				{
 +					$response .= $chunk;
 +					$left -= strlen($chunk);
 +				}
 +			}
 +		}
 +		if ( $this->_socket === false )
 +			fclose ( $fp );
 +
 +		// check response
 +		$read = strlen ( $response );
 +		if ( !$response || $read!=$len )
 +		{
 +			$this->_error = $len
 +				? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
 +				: "received zero-sized searchd response";
 +			return false;
 +		}
 +
 +		// check status
 +		if ( $status==SEARCHD_WARNING )
 +		{
 +			list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
 +			$this->_warning = substr ( $response, 4, $wlen );
 +			return substr ( $response, 4+$wlen );
 +		}
 +		if ( $status==SEARCHD_ERROR )
 +		{
 +			$this->_error = "searchd error: " . substr ( $response, 4 );
 +			return false;
 +		}
 +		if ( $status==SEARCHD_RETRY )
 +		{
 +			$this->_error = "temporary searchd error: " . substr ( $response, 4 );
 +			return false;
 +		}
 +		if ( $status!=SEARCHD_OK )
 +		{
 +			$this->_error = "unknown status code '$status'";
 +			return false;
 +		}
 +
 +		// check version
 +		if ( $ver<$client_ver )
 +		{
 +			$this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
 +				$ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
 +		}
 +
 +		return $response;
 +	}
 +
 +	/////////////////////////////////////////////////////////////////////////////
 +	// searching
 +	/////////////////////////////////////////////////////////////////////////////
 +
 +	/// set offset and count into result set,
 +	/// and optionally set max-matches and cutoff limits
 +	function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
 +	{
 +		assert ( is_int($offset) );
 +		assert ( is_int($limit) );
 +		assert ( $offset>=0 );
 +		assert ( $limit>0 );
 +		assert ( $max>=0 );
 +		$this->_offset = $offset;
 +		$this->_limit = $limit;
 +		if ( $max>0 )
 +			$this->_maxmatches = $max;
 +		if ( $cutoff>0 )
 +			$this->_cutoff = $cutoff;
 +	}
 +
 +	/// set maximum query time, in milliseconds, per-index
 +	/// integer, 0 means "do not limit"
 +	function SetMaxQueryTime ( $max )
 +	{
 +		assert ( is_int($max) );
 +		assert ( $max>=0 );
 +		$this->_maxquerytime = $max;
 +	}
 +
 +	/// set matching mode
 +	function SetMatchMode ( $mode )
 +	{
 +		assert ( $mode==SPH_MATCH_ALL
 +			|| $mode==SPH_MATCH_ANY
 +			|| $mode==SPH_MATCH_PHRASE
 +			|| $mode==SPH_MATCH_BOOLEAN
 +			|| $mode==SPH_MATCH_EXTENDED
 +			|| $mode==SPH_MATCH_FULLSCAN
 +			|| $mode==SPH_MATCH_EXTENDED2 );
 +		$this->_mode = $mode;
 +	}
 +
 +	/// set ranking mode
 +	function SetRankingMode ( $ranker, $rankexpr="" )
 +	{
 +		assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL );
 +		assert ( is_string($rankexpr) );
 +		$this->_ranker = $ranker;
 +		$this->_rankexpr = $rankexpr;
 +	}
 +
 +	/// set matches sorting mode
 +	function SetSortMode ( $mode, $sortby="" )
 +	{
 +		assert (
 +			$mode==SPH_SORT_RELEVANCE ||
 +			$mode==SPH_SORT_ATTR_DESC ||
 +			$mode==SPH_SORT_ATTR_ASC ||
 +			$mode==SPH_SORT_TIME_SEGMENTS ||
 +			$mode==SPH_SORT_EXTENDED ||
 +			$mode==SPH_SORT_EXPR );
 +		assert ( is_string($sortby) );
 +		assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
 +
 +		$this->_sort = $mode;
 +		$this->_sortby = $sortby;
 +	}
 +
 +	/// bind per-field weights by order
 +	/// DEPRECATED; use SetFieldWeights() instead
 +	function SetWeights ( $weights )
 +	{
 +		assert ( is_array($weights) );
 +		foreach ( $weights as $weight )
 +			assert ( is_int($weight) );
 +
 +		$this->_weights = $weights;
 +	}
 +
 +	/// bind per-field weights by name
 +	function SetFieldWeights ( $weights )
 +	{
 +		assert ( is_array($weights) );
 +		foreach ( $weights as $name=>$weight )
 +		{
 +			assert ( is_string($name) );
 +			assert ( is_int($weight) );
 +		}
 +		$this->_fieldweights = $weights;
 +	}
 +
 +	/// bind per-index weights by name
 +	function SetIndexWeights ( $weights )
 +	{
 +		assert ( is_array($weights) );
 +		foreach ( $weights as $index=>$weight )
 +		{
 +			assert ( is_string($index) );
 +			assert ( is_int($weight) );
 +		}
 +		$this->_indexweights = $weights;
 +	}
 +
 +	/// set IDs range to match
 +	/// only match records if document ID is beetwen $min and $max (inclusive)
 +	function SetIDRange ( $min, $max )
 +	{
 +		assert ( is_numeric($min) );
 +		assert ( is_numeric($max) );
 +		assert ( $min<=$max );
 +		$this->_min_id = $min;
 +		$this->_max_id = $max;
 +	}
 +
 +	/// set values set filter
 +	/// only match records where $attribute value is in given set
 +	function SetFilter ( $attribute, $values, $exclude=false )
 +	{
 +		assert ( is_string($attribute) );
 +		assert ( is_array($values) );
 +		assert ( count($values) );
 +
 +		if ( is_array($values) && count($values) )
 +		{
 +			foreach ( $values as $value )
 +				assert ( is_numeric($value) );
 +
 +			$this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
 +		}
 +	}
 +
 +	/// set range filter
 +	/// only match records if $attribute value is beetwen $min and $max (inclusive)
 +	function SetFilterRange ( $attribute, $min, $max, $exclude=false )
 +	{
 +		assert ( is_string($attribute) );
 +		assert ( is_numeric($min) );
 +		assert ( is_numeric($max) );
 +		assert ( $min<=$max );
 +
 +		$this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
 +	}
 +
 +	/// set float range filter
 +	/// only match records if $attribute value is beetwen $min and $max (inclusive)
 +	function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
 +	{
 +		assert ( is_string($attribute) );
 +		assert ( is_float($min) );
 +		assert ( is_float($max) );
 +		assert ( $min<=$max );
 +
 +		$this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
 +	}
 +
 +	/// setup anchor point for geosphere distance calculations
 +	/// required to use @geodist in filters and sorting
 +	/// latitude and longitude must be in radians
 +	function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
 +	{
 +		assert ( is_string($attrlat) );
 +		assert ( is_string($attrlong) );
 +		assert ( is_float($lat) );
 +		assert ( is_float($long) );
 +
 +		$this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
 +	}
 +
 +	/// set grouping attribute and function
 +	function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
 +	{
 +		assert ( is_string($attribute) );
 +		assert ( is_string($groupsort) );
 +		assert ( $func==SPH_GROUPBY_DAY
 +			|| $func==SPH_GROUPBY_WEEK
 +			|| $func==SPH_GROUPBY_MONTH
 +			|| $func==SPH_GROUPBY_YEAR
 +			|| $func==SPH_GROUPBY_ATTR
 +			|| $func==SPH_GROUPBY_ATTRPAIR );
 +
 +		$this->_groupby = $attribute;
 +		$this->_groupfunc = $func;
 +		$this->_groupsort = $groupsort;
 +	}
 +
 +	/// set count-distinct attribute for group-by queries
 +	function SetGroupDistinct ( $attribute )
 +	{
 +		assert ( is_string($attribute) );
 +		$this->_groupdistinct = $attribute;
 +	}
 +
 +	/// set distributed retries count and delay
 +	function SetRetries ( $count, $delay=0 )
 +	{
 +		assert ( is_int($count) && $count>=0 );
 +		assert ( is_int($delay) && $delay>=0 );
 +		$this->_retrycount = $count;
 +		$this->_retrydelay = $delay;
 +	}
 +
 +	/// set result set format (hash or array; hash by default)
 +	/// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
 +	function SetArrayResult ( $arrayresult )
 +	{
 +		assert ( is_bool($arrayresult) );
 +		$this->_arrayresult = $arrayresult;
 +	}
 +
 +	/// set attribute values override
 +	/// there can be only one override per attribute
 +	/// $values must be a hash that maps document IDs to attribute values
 +	function SetOverride ( $attrname, $attrtype, $values )
 +	{
 +		assert ( is_string ( $attrname ) );
 +		assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
 +		assert ( is_array ( $values ) );
 +
 +		$this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
 +	}
 +
 +	/// set select-list (attributes or expressions), SQL-like syntax
 +	function SetSelect ( $select )
 +	{
 +		assert ( is_string ( $select ) );
 +		$this->_select = $select;
 +	}
 +
 +	//////////////////////////////////////////////////////////////////////////////
 +
 +	/// clear all filters (for multi-queries)
 +	function ResetFilters ()
 +	{
 +		$this->_filters = array();
 +		$this->_anchor = array();
 +	}
 +
 +	/// clear groupby settings (for multi-queries)
 +	function ResetGroupBy ()
 +	{
 +		$this->_groupby		= "";
 +		$this->_groupfunc	= SPH_GROUPBY_DAY;
 +		$this->_groupsort	= "@group desc";
 +		$this->_groupdistinct= "";
 +	}
 +
 +	/// clear all attribute value overrides (for multi-queries)
 +	function ResetOverrides ()
 +    {
 +    	$this->_overrides = array ();
 +    }
 +
 +	//////////////////////////////////////////////////////////////////////////////
 +
 +	/// connect to searchd server, run given search query through given indexes,
 +	/// and return the search results
 +	function Query ( $query, $index="*", $comment="" )
 +	{
 +		assert ( empty($this->_reqs) );
 +
 +		$this->AddQuery ( $query, $index, $comment );
 +		$results = $this->RunQueries ();
 +		$this->_reqs = array (); // just in case it failed too early
 +
 +		if ( !is_array($results) )
 +			return false; // probably network error; error message should be already filled
 +
 +		$this->_error = $results[0]["error"];
 +		$this->_warning = $results[0]["warning"];
 +		if ( $results[0]["status"]==SEARCHD_ERROR )
 +			return false;
 +		else
 +			return $results[0];
 +	}
 +
 +	/// helper to pack floats in network byte order
 +	function _PackFloat ( $f )
 +	{
 +		$t1 = pack ( "f", $f ); // machine order
 +		list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
 +		return pack ( "N", $t2 );
 +	}
 +
 +	/// add query to multi-query batch
 +	/// returns index into results array from RunQueries() call
 +	function AddQuery ( $query, $index="*", $comment="" )
 +	{
 +		// mbstring workaround
 +		$this->_MBPush ();
 +
 +		// build request
 +		$req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker );
 +		if ( $this->_ranker==SPH_RANK_EXPR )
 +			$req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr;
 +		$req .= pack ( "N", $this->_sort ); // (deprecated) sort mode
 +		$req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
 +		$req .= pack ( "N", strlen($query) ) . $query; // query itself
 +		$req .= pack ( "N", count($this->_weights) ); // weights
 +		foreach ( $this->_weights as $weight )
 +			$req .= pack ( "N", (int)$weight );
 +		$req .= pack ( "N", strlen($index) ) . $index; // indexes
 +		$req .= pack ( "N", 1 ); // id64 range marker
 +		$req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
 +
 +		// filters
 +		$req .= pack ( "N", count($this->_filters) );
 +		foreach ( $this->_filters as $filter )
 +		{
 +			$req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
 +			$req .= pack ( "N", $filter["type"] );
 +			switch ( $filter["type"] )
 +			{
 +				case SPH_FILTER_VALUES:
 +					$req .= pack ( "N", count($filter["values"]) );
 +					foreach ( $filter["values"] as $value )
 +						$req .= sphPackI64 ( $value );
 +					break;
 +
 +				case SPH_FILTER_RANGE:
 +					$req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
 +					break;
 +
 +				case SPH_FILTER_FLOATRANGE:
 +					$req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
 +					break;
 +
 +				default:
 +					assert ( 0 && "internal error: unhandled filter type" );
 +			}
 +			$req .= pack ( "N", $filter["exclude"] );
 +		}
 +
 +		// group-by clause, max-matches count, group-sort clause, cutoff count
 +		$req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
 +		$req .= pack ( "N", $this->_maxmatches );
 +		$req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
 +		$req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
 +		$req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
 +
 +		// anchor point
 +		if ( empty($this->_anchor) )
 +		{
 +			$req .= pack ( "N", 0 );
 +		} else
 +		{
 +			$a =& $this->_anchor;
 +			$req .= pack ( "N", 1 );
 +			$req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
 +			$req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
 +			$req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
 +		}
 +
 +		// per-index weights
 +		$req .= pack ( "N", count($this->_indexweights) );
 +		foreach ( $this->_indexweights as $idx=>$weight )
 +			$req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
 +
 +		// max query time
 +		$req .= pack ( "N", $this->_maxquerytime );
 +
 +		// per-field weights
 +		$req .= pack ( "N", count($this->_fieldweights) );
 +		foreach ( $this->_fieldweights as $field=>$weight )
 +			$req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
 +
 +		// comment
 +		$req .= pack ( "N", strlen($comment) ) . $comment;
 +
 +		// attribute overrides
 +		$req .= pack ( "N", count($this->_overrides) );
 +		foreach ( $this->_overrides as $key => $entry )
 +		{
 +			$req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
 +			$req .= pack ( "NN", $entry["type"], count($entry["values"]) );
 +			foreach ( $entry["values"] as $id=>$val )
 +			{
 +				assert ( is_numeric($id) );
 +				assert ( is_numeric($val) );
 +
 +				$req .= sphPackU64 ( $id );
 +				switch ( $entry["type"] )
 +				{
 +					case SPH_ATTR_FLOAT:	$req .= $this->_PackFloat ( $val ); break;
 +					case SPH_ATTR_BIGINT:	$req .= sphPackI64 ( $val ); break;
 +					default:				$req .= pack ( "N", $val ); break;
 +				}
 +			}
 +		}
 +
 +		// select-list
 +		$req .= pack ( "N", strlen($this->_select) ) . $this->_select;
 +
 +		// mbstring workaround
 +		$this->_MBPop ();
 +
 +		// store request to requests array
 +		$this->_reqs[] = $req;
 +		return count($this->_reqs)-1;
 +	}
 +
 +	/// connect to searchd, run queries batch, and return an array of result sets
 +	function RunQueries ()
 +	{
 +		if ( empty($this->_reqs) )
 +		{
 +			$this->_error = "no queries defined, issue AddQuery() first";
 +			return false;
 +		}
 +
 +		// mbstring workaround
 +		$this->_MBPush ();
 +
 +		if (!( $fp = $this->_Connect() ))
 +		{
 +			$this->_MBPop ();
 +			return false;
 +		}
 +
 +		// send query, get response
 +		$nreqs = count($this->_reqs);
 +		$req = join ( "", $this->_reqs );
 +		$len = 8+strlen($req);
 +		$req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header
 +
 +		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
 +			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
 +		{
 +			$this->_MBPop ();
 +			return false;
 +		}
 +
 +		// query sent ok; we can reset reqs now
 +		$this->_reqs = array ();
 +
 +		// parse and return response
 +		return $this->_ParseSearchResponse ( $response, $nreqs );
 +	}
 +
 +	/// parse and return search query (or queries) response
 +	function _ParseSearchResponse ( $response, $nreqs )
 +	{
 +		$p = 0; // current position
 +		$max = strlen($response); // max position for checks, to protect against broken responses
 +
 +		$results = array ();
 +		for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
 +		{
 +			$results[] = array();
 +			$result =& $results[$ires];
 +
 +			$result["error"] = "";
 +			$result["warning"] = "";
 +
 +			// extract status
 +			list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +			$result["status"] = $status;
 +			if ( $status!=SEARCHD_OK )
 +			{
 +				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +				$message = substr ( $response, $p, $len ); $p += $len;
 +
 +				if ( $status==SEARCHD_WARNING )
 +				{
 +					$result["warning"] = $message;
 +				} else
 +				{
 +					$result["error"] = $message;
 +					continue;
 +				}
 +			}
 +
 +			// read schema
 +			$fields = array ();
 +			$attrs = array ();
 +
 +			list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +			while ( $nfields-->0 && $p<$max )
 +			{
 +				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +				$fields[] = substr ( $response, $p, $len ); $p += $len;
 +			}
 +			$result["fields"] = $fields;
 +
 +			list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +			while ( $nattrs-->0 && $p<$max  )
 +			{
 +				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +				$attr = substr ( $response, $p, $len ); $p += $len;
 +				list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +				$attrs[$attr] = $type;
 +			}
 +			$result["attrs"] = $attrs;
 +
 +			// read match count
 +			list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +			list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +
 +			// read matches
 +			$idx = -1;
 +			while ( $count-->0 && $p<$max )
 +			{
 +				// index into result array
 +				$idx++;
 +
 +				// parse document id and weight
 +				if ( $id64 )
 +				{
 +					$doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
 +					list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +				}
 +				else
 +				{
 +					list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
 +						substr ( $response, $p, 8 ) ) );
 +					$p += 8;
 +					$doc = sphFixUint($doc);
 +				}
 +				$weight = sprintf ( "%u", $weight );
 +
 +				// create match entry
 +				if ( $this->_arrayresult )
 +					$result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
 +				else
 +					$result["matches"][$doc]["weight"] = $weight;
 +
 +				// parse and create attributes
 +				$attrvals = array ();
 +				foreach ( $attrs as $attr=>$type )
 +				{
 +					// handle 64bit ints
 +					if ( $type==SPH_ATTR_BIGINT )
 +					{
 +						$attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
 +						continue;
 +					}
 +
 +					// handle floats
 +					if ( $type==SPH_ATTR_FLOAT )
 +					{
 +						list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +						list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); 
 +						$attrvals[$attr] = $fval;
 +						continue;
 +					}
 +
 +					// handle everything else as unsigned ints
 +					list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +					if ( $type==SPH_ATTR_MULTI )
 +					{
 +						$attrvals[$attr] = array ();
 +						$nvalues = $val;
 +						while ( $nvalues-->0 && $p<$max )
 +						{
 +							list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +							$attrvals[$attr][] = sphFixUint($val);
 +						}
 +					} else if ( $type==SPH_ATTR_MULTI64 )
 +					{
 +						$attrvals[$attr] = array ();
 +						$nvalues = $val;
 +						while ( $nvalues>0 && $p<$max )
 +						{
 +							$attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
 +							$nvalues -= 2;
 +						}
 +					} else if ( $type==SPH_ATTR_STRING )
 +					{
 +						$attrvals[$attr] = substr ( $response, $p, $val );
 +						$p += $val;						
 +					} else
 +					{
 +						$attrvals[$attr] = sphFixUint($val);
 +					}
 +				}
 +
 +				if ( $this->_arrayresult )
 +					$result["matches"][$idx]["attrs"] = $attrvals;
 +				else
 +					$result["matches"][$doc]["attrs"] = $attrvals;
 +			}
 +
 +			list ( $total, $total_found, $msecs, $words ) =
 +				array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
 +			$result["total"] = sprintf ( "%u", $total );
 +			$result["total_found"] = sprintf ( "%u", $total_found );
 +			$result["time"] = sprintf ( "%.3f", $msecs/1000 );
 +			$p += 16;
 +
 +			while ( $words-->0 && $p<$max )
 +			{
 +				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +				$word = substr ( $response, $p, $len ); $p += $len;
 +				list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
 +				$result["words"][$word] = array (
 +					"docs"=>sprintf ( "%u", $docs ),
 +					"hits"=>sprintf ( "%u", $hits ) );
 +			}
 +		}
 +
 +		$this->_MBPop ();
 +		return $results;
 +	}
 +
 +	/////////////////////////////////////////////////////////////////////////////
 +	// excerpts generation
 +	/////////////////////////////////////////////////////////////////////////////
 +
 +	/// connect to searchd server, and generate exceprts (snippets)
 +	/// of given documents for given query. returns false on failure,
 +	/// an array of snippets on success
 +	function BuildExcerpts ( $docs, $index, $words, $opts=array() )
 +	{
 +		assert ( is_array($docs) );
 +		assert ( is_string($index) );
 +		assert ( is_string($words) );
 +		assert ( is_array($opts) );
 +
 +		$this->_MBPush ();
 +
 +		if (!( $fp = $this->_Connect() ))
 +		{
 +			$this->_MBPop();
 +			return false;
 +		}
 +
 +		/////////////////
 +		// fixup options
 +		/////////////////
 +
 +		if ( !isset($opts["before_match"]) )		$opts["before_match"] = "<b>";
 +		if ( !isset($opts["after_match"]) )			$opts["after_match"] = "</b>";
 +		if ( !isset($opts["chunk_separator"]) )		$opts["chunk_separator"] = " ... ";
 +		if ( !isset($opts["limit"]) )				$opts["limit"] = 256;
 +		if ( !isset($opts["limit_passages"]) )		$opts["limit_passages"] = 0;
 +		if ( !isset($opts["limit_words"]) )			$opts["limit_words"] = 0;
 +		if ( !isset($opts["around"]) )				$opts["around"] = 5;
 +		if ( !isset($opts["exact_phrase"]) )		$opts["exact_phrase"] = false;
 +		if ( !isset($opts["single_passage"]) )		$opts["single_passage"] = false;
 +		if ( !isset($opts["use_boundaries"]) )		$opts["use_boundaries"] = false;
 +		if ( !isset($opts["weight_order"]) )		$opts["weight_order"] = false;
 +		if ( !isset($opts["query_mode"]) )			$opts["query_mode"] = false;
 +		if ( !isset($opts["force_all_words"]) )		$opts["force_all_words"] = false;
 +		if ( !isset($opts["start_passage_id"]) )	$opts["start_passage_id"] = 1;
 +		if ( !isset($opts["load_files"]) )			$opts["load_files"] = false;
 +		if ( !isset($opts["html_strip_mode"]) )		$opts["html_strip_mode"] = "index";
 +		if ( !isset($opts["allow_empty"]) )			$opts["allow_empty"] = false;
 +		if ( !isset($opts["passage_boundary"]) )	$opts["passage_boundary"] = "none";
 +		if ( !isset($opts["emit_zones"]) )			$opts["emit_zones"] = false;
 +		if ( !isset($opts["load_files_scattered"]) )		$opts["load_files_scattered"] = false;
 +		
 +
 +		/////////////////
 +		// build request
 +		/////////////////
 +
 +		// v.1.2 req
 +		$flags = 1; // remove spaces
 +		if ( $opts["exact_phrase"] )	$flags |= 2;
 +		if ( $opts["single_passage"] )	$flags |= 4;
 +		if ( $opts["use_boundaries"] )	$flags |= 8;
 +		if ( $opts["weight_order"] )	$flags |= 16;
 +		if ( $opts["query_mode"] )		$flags |= 32;
 +		if ( $opts["force_all_words"] )	$flags |= 64;
 +		if ( $opts["load_files"] )		$flags |= 128;
 +		if ( $opts["allow_empty"] )		$flags |= 256;
 +		if ( $opts["emit_zones"] )		$flags |= 512;
 +		if ( $opts["load_files_scattered"] )	$flags |= 1024;
 +		$req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
 +		$req .= pack ( "N", strlen($index) ) . $index; // req index
 +		$req .= pack ( "N", strlen($words) ) . $words; // req words
 +
 +		// options
 +		$req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
 +		$req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
 +		$req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
 +		$req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] );
 +		$req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2
 +		$req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"];
 +		$req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"];
 +
 +		// documents
 +		$req .= pack ( "N", count($docs) );
 +		foreach ( $docs as $doc )
 +		{
 +			assert ( is_string($doc) );
 +			$req .= pack ( "N", strlen($doc) ) . $doc;
 +		}
 +
 +		////////////////////////////
 +		// send query, get response
 +		////////////////////////////
 +
 +		$len = strlen($req);
 +		$req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
 +		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
 +			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
 +		{
 +			$this->_MBPop ();
 +			return false;
 +		}
 +
 +		//////////////////
 +		// parse response
 +		//////////////////
 +
 +		$pos = 0;
 +		$res = array ();
 +		$rlen = strlen($response);
 +		for ( $i=0; $i<count($docs); $i++ )
 +		{
 +			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
 +			$pos += 4;
 +
 +			if ( $pos+$len > $rlen )
 +			{
 +				$this->_error = "incomplete reply";
 +				$this->_MBPop ();
 +				return false;
 +			}
 +			$res[] = $len ? substr ( $response, $pos, $len ) : "";
 +			$pos += $len;
 +		}
 +
 +		$this->_MBPop ();
 +		return $res;
 +	}
 +
 +
 +	/////////////////////////////////////////////////////////////////////////////
 +	// keyword generation
 +	/////////////////////////////////////////////////////////////////////////////
 +
 +	/// connect to searchd server, and generate keyword list for a given query
 +	/// returns false on failure,
 +	/// an array of words on success
 +	function BuildKeywords ( $query, $index, $hits )
 +	{
 +		assert ( is_string($query) );
 +		assert ( is_string($index) );
 +		assert ( is_bool($hits) );
 +
 +		$this->_MBPush ();
 +
 +		if (!( $fp = $this->_Connect() ))
 +		{
 +			$this->_MBPop();
 +			return false;
 +		}
 +
 +		/////////////////
 +		// build request
 +		/////////////////
 +
 +		// v.1.0 req
 +		$req  = pack ( "N", strlen($query) ) . $query; // req query
 +		$req .= pack ( "N", strlen($index) ) . $index; // req index
 +		$req .= pack ( "N", (int)$hits );
 +
 +		////////////////////////////
 +		// send query, get response
 +		////////////////////////////
 +
 +		$len = strlen($req);
 +		$req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
 +		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
 +			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
 +		{
 +			$this->_MBPop ();
 +			return false;
 +		}
 +
 +		//////////////////
 +		// parse response
 +		//////////////////
 +
 +		$pos = 0;
 +		$res = array ();
 +		$rlen = strlen($response);
 +		list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
 +		$pos += 4;
 +		for ( $i=0; $i<$nwords; $i++ )
 +		{
 +			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
 +			$tokenized = $len ? substr ( $response, $pos, $len ) : "";
 +			$pos += $len;
 +
 +			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
 +			$normalized = $len ? substr ( $response, $pos, $len ) : "";
 +			$pos += $len;
 +
 +			$res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
 +
 +			if ( $hits )
 +			{
 +				list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
 +				$pos += 8;
 +				$res [$i]["docs"] = $ndocs;
 +				$res [$i]["hits"] = $nhits;
 +			}
 +
 +			if ( $pos > $rlen )
 +			{
 +				$this->_error = "incomplete reply";
 +				$this->_MBPop ();
 +				return false;
 +			}
 +		}
 +
 +		$this->_MBPop ();
 +		return $res;
 +	}
 +
 +	function EscapeString ( $string )
 +	{
 +		$from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' );
 +		$to   = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' );
 +
 +		return str_replace ( $from, $to, $string );
 +	}
 +
 +	/////////////////////////////////////////////////////////////////////////////
 +	// attribute updates
 +	/////////////////////////////////////////////////////////////////////////////
 +
 +	/// batch update given attributes in given rows in given indexes
 +	/// returns amount of updated documents (0 or more) on success, or -1 on failure
 +	function UpdateAttributes ( $index, $attrs, $values, $mva=false )
 +	{
 +		// verify everything
 +		assert ( is_string($index) );
 +		assert ( is_bool($mva) );
 +
 +		assert ( is_array($attrs) );
 +		foreach ( $attrs as $attr )
 +			assert ( is_string($attr) );
 +
 +		assert ( is_array($values) );
 +		foreach ( $values as $id=>$entry )
 +		{
 +			assert ( is_numeric($id) );
 +			assert ( is_array($entry) );
 +			assert ( count($entry)==count($attrs) );
 +			foreach ( $entry as $v )
 +			{
 +				if ( $mva )
 +				{
 +					assert ( is_array($v) );
 +					foreach ( $v as $vv )
 +						assert ( is_int($vv) );
 +				} else
 +					assert ( is_int($v) );
 +			}
 +		}
 +
 +		// build request
 +		$this->_MBPush ();
 +		$req = pack ( "N", strlen($index) ) . $index;
 +
 +		$req .= pack ( "N", count($attrs) );
 +		foreach ( $attrs as $attr )
 +		{
 +			$req .= pack ( "N", strlen($attr) ) . $attr;
 +			$req .= pack ( "N", $mva ? 1 : 0 );
 +		}
 +
 +		$req .= pack ( "N", count($values) );
 +		foreach ( $values as $id=>$entry )
 +		{
 +			$req .= sphPackU64 ( $id );
 +			foreach ( $entry as $v )
 +			{
 +				$req .= pack ( "N", $mva ? count($v) : $v );
 +				if ( $mva )
 +					foreach ( $v as $vv )
 +						$req .= pack ( "N", $vv );
 +			}
 +		}
 +
 +		// connect, send query, get response
 +		if (!( $fp = $this->_Connect() ))
 +		{
 +			$this->_MBPop ();
 +			return -1;
 +		}
 +
 +		$len = strlen($req);
 +		$req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
 +		if ( !$this->_Send ( $fp, $req, $len+8 ) )
 +		{
 +			$this->_MBPop ();
 +			return -1;
 +		}
 +
 +		if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
 +		{
 +			$this->_MBPop ();
 +			return -1;
 +		}
 +
 +		// parse response
 +		list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
 +		$this->_MBPop ();
 +		return $updated;
 +	}
 +
 +	/////////////////////////////////////////////////////////////////////////////
 +	// persistent connections
 +	/////////////////////////////////////////////////////////////////////////////
 +
 +	function Open()
 +	{
 +		if ( $this->_socket !== false )
 +		{
 +			$this->_error = 'already connected';
 +			return false;
 +		}
 +		if ( !$fp = $this->_Connect() )
 +			return false;
 +
 +		// command, command version = 0, body length = 4, body = 1
 +		$req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
 +		if ( !$this->_Send ( $fp, $req, 12 ) )
 +			return false;
 +
 +		$this->_socket = $fp;
 +		return true;
 +	}
 +
 +	function Close()
 +	{
 +		if ( $this->_socket === false )
 +		{
 +			$this->_error = 'not connected';
 +			return false;
 +		}
 +
 +		fclose ( $this->_socket );
 +		$this->_socket = false;
 +		
 +		return true;
 +	}
 +
 +	//////////////////////////////////////////////////////////////////////////
 +	// status
 +	//////////////////////////////////////////////////////////////////////////
 +
 +	function Status ()
 +	{
 +		$this->_MBPush ();
 +		if (!( $fp = $this->_Connect() ))
 +		{
 +			$this->_MBPop();
 +			return false;
 +		}
 +
 +		$req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
 +		if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
 +			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
 +		{
 +			$this->_MBPop ();
 +			return false;
 +		}
 +
 +		$res = substr ( $response, 4 ); // just ignore length, error handling, etc
 +		$p = 0;
 +		list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
 +
 +		$res = array();
 +		for ( $i=0; $i<$rows; $i++ )
 +			for ( $j=0; $j<$cols; $j++ )
 +		{
 +			list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 +			$res[$i][] = substr ( $response, $p, $len ); $p += $len;
 +		}
 +
 +		$this->_MBPop ();
 +		return $res;
 +	}
 +
 +	//////////////////////////////////////////////////////////////////////////
 +	// flush
 +	//////////////////////////////////////////////////////////////////////////
 +
 +	function FlushAttributes ()
 +	{
 +		$this->_MBPush ();
 +		if (!( $fp = $this->_Connect() ))
 +		{
 +			$this->_MBPop();
 +			return -1;
 +		}
 +
 +		$req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
 +		if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
 +			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
 +		{
 +			$this->_MBPop ();
 +			return -1;
 +		}
 +
 +		$tag = -1;
 +		if ( strlen($response)==4 )
 +			list(,$tag) = unpack ( "N*", $response );
 +		else
 +			$this->_error = "unexpected response length";
 +
 +		$this->_MBPop ();
 +		return $tag;
 +	}
 +}
 +
 +//
 +// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
 +//
 diff --git a/phpBB/includes/startup.php b/phpBB/includes/startup.php new file mode 100644 index 0000000000..441eaec6b1 --- /dev/null +++ b/phpBB/includes/startup.php @@ -0,0 +1,182 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +// Report all errors, except notices and deprecation messages +if (!defined('E_DEPRECATED')) +{ +	define('E_DEPRECATED', 8192); +} +$level = E_ALL & ~E_NOTICE & ~E_DEPRECATED; +error_reporting($level); + +/* +* Remove variables created by register_globals from the global scope +* Thanks to Matt Kavanagh +*/ +function deregister_globals() +{ +	$not_unset = array( +		'GLOBALS'	=> true, +		'_GET'		=> true, +		'_POST'		=> true, +		'_COOKIE'	=> true, +		'_REQUEST'	=> true, +		'_SERVER'	=> true, +		'_SESSION'	=> true, +		'_ENV'		=> true, +		'_FILES'	=> true, +		'phpEx'		=> true, +		'phpbb_root_path'	=> true +	); + +	// Not only will array_merge and array_keys give a warning if +	// a parameter is not an array, array_merge will actually fail. +	// So we check if _SESSION has been initialised. +	if (!isset($_SESSION) || !is_array($_SESSION)) +	{ +		$_SESSION = array(); +	} + +	// Merge all into one extremely huge array; unset this later +	$input = array_merge( +		array_keys($_GET), +		array_keys($_POST), +		array_keys($_COOKIE), +		array_keys($_SERVER), +		array_keys($_SESSION), +		array_keys($_ENV), +		array_keys($_FILES) +	); + +	foreach ($input as $varname) +	{ +		if (isset($not_unset[$varname])) +		{ +			// Hacking attempt. No point in continuing unless it's a COOKIE (so a cookie called GLOBALS doesn't lock users out completely) +			if ($varname !== 'GLOBALS' || isset($_GET['GLOBALS']) || isset($_POST['GLOBALS']) || isset($_SERVER['GLOBALS']) || isset($_SESSION['GLOBALS']) || isset($_ENV['GLOBALS']) || isset($_FILES['GLOBALS'])) +			{ +				exit; +			} +			else +			{ +				$cookie = &$_COOKIE; +				while (isset($cookie['GLOBALS'])) +				{ +					if (!is_array($cookie['GLOBALS'])) +					{ +						break; +					} + +					foreach ($cookie['GLOBALS'] as $registered_var => $value) +					{ +						if (!isset($not_unset[$registered_var])) +						{ +							unset($GLOBALS[$registered_var]); +						} +					} +					$cookie = &$cookie['GLOBALS']; +				} +			} +		} + +		unset($GLOBALS[$varname]); +	} + +	unset($input); +} + +// Register globals and magic quotes have been dropped in PHP 5.4 +if (version_compare(PHP_VERSION, '5.4.0-dev', '>=')) +{ +	/** +	* @ignore +	*/ +	define('STRIP', false); +} +else +{ +	@set_magic_quotes_runtime(0); + +	// Be paranoid with passed vars +	if (@ini_get('register_globals') == '1' || strtolower(@ini_get('register_globals')) == 'on' || !function_exists('ini_get')) +	{ +		deregister_globals(); +	} + +	define('STRIP', (get_magic_quotes_gpc()) ? true : false); +} + +// Prevent date/time functions from throwing E_WARNING on PHP 5.3 by setting a default timezone +if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) +{ +	// For PHP 5.1.0 the date/time functions have been rewritten +	// and setting a timezone is required prior to calling any date/time function. + +	// Since PHP 5.2.0 calls to date/time functions without having a timezone set +	// result in E_STRICT errors being thrown. +	// Note: We already exclude E_STRICT errors +	// (to be exact: they are not included in E_ALL in PHP 5.2) + +	// In PHP 5.3.0 the error level has been raised to E_WARNING which causes problems +	// because we show E_WARNING errors and do not set a default timezone. +	// This is because we have our own timezone handling and work in UTC only anyway. + +	// So what we basically want to do is set our timezone to UTC, +	// but we don't know what other scripts (such as bridges) are involved, +	// so we check whether a timezone is already set by calling date_default_timezone_get(). + +	// Unfortunately, date_default_timezone_get() itself might throw E_WARNING +	// if no timezone has been set, so we have to keep it quiet with @. + +	// date_default_timezone_get() tries to guess the correct timezone first +	// and then falls back to UTC when everything fails. +	// We just set the timezone to whatever date_default_timezone_get() returns. +	date_default_timezone_set(@date_default_timezone_get()); +} + +// Autoloading of dependencies. +// Three options are supported: +// 1. If dependencies are installed with Composer, Composer will create a +//    vendor/autoload.php. If this file exists it will be +//    automatically used by phpBB. This is the default mode that phpBB +//    will use when shipped. +// 2. To disable composer autoloading, PHPBB_NO_COMPOSER_AUTOLOAD can be specified. +// 	  Additionally specify PHPBB_AUTOLOAD=/path/to/autoload.php in the +//    environment. This is useful for running CLI scripts and tests. +//    /path/to/autoload.php should define and register class loaders +//    for all of phpBB's dependencies. +// 3. You can also set PHPBB_NO_COMPOSER_AUTOLOAD without setting PHPBB_AUTOLOAD. +//    In this case autoloading needs to be defined before running any phpBB +//    script. This might be useful in cases when phpBB is integrated into a +//    larger program. +if (getenv('PHPBB_NO_COMPOSER_AUTOLOAD')) +{ +	if (getenv('PHPBB_AUTOLOAD')) +	{ +		require(getenv('PHPBB_AUTOLOAD')); +	} +} +else +{ +	if (!file_exists($phpbb_root_path . 'vendor/autoload.php')) +	{ +		trigger_error('You have not set up composer dependencies. See http://getcomposer.org/.', E_USER_ERROR); +	} +	require($phpbb_root_path . 'vendor/autoload.php'); +} + +$starttime = explode(' ', microtime()); +$starttime = $starttime[1] + $starttime[0]; diff --git a/phpBB/includes/style/extension_path_provider.php b/phpBB/includes/style/extension_path_provider.php new file mode 100644 index 0000000000..4eac300424 --- /dev/null +++ b/phpBB/includes/style/extension_path_provider.php @@ -0,0 +1,119 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Provides a style resource locator with core style paths and extension style paths +* +* Finds installed style paths and makes them available to the resource locator. +* +* @package phpBB3 +*/ +class phpbb_style_extension_path_provider extends phpbb_extension_provider implements phpbb_style_path_provider_interface +{ +	/** +	* Optional prefix for style paths searched within extensions. +	* +	* Empty by default. Relative to the extension directory. As an example, it +	* could be adm/ for admin style. +	* +	* @var string +	*/ +	protected $ext_dir_prefix = ''; + +	/** +	* A provider of paths to be searched for styles +	* @var phpbb_style_path_provider +	*/ +	protected $base_path_provider; + +	/** +	* Constructor stores extension manager +	* +	* @param phpbb_extension_manager $extension_manager phpBB extension manager +	* @param phpbb_style_path_provider $base_path_provider A simple path provider +	*            to provide paths to be located in extensions +	*/ +	public function __construct(phpbb_extension_manager $extension_manager, phpbb_style_path_provider $base_path_provider) +	{ +		parent::__construct($extension_manager); +		$this->base_path_provider = $base_path_provider; +	} + +	/** +	* Sets a prefix for style paths searched within extensions. +	* +	* The prefix is inserted between the extension's path e.g. ext/foo/ and +	* the looked up style path, e.g. styles/bar/. So it should not have a +	* leading slash, but should have a trailing slash. +	* +	* @param string $ext_dir_prefix The prefix including trailing slash +	* @return null +	*/ +	public function set_ext_dir_prefix($ext_dir_prefix) +	{ +		$this->ext_dir_prefix = $ext_dir_prefix; +	} + +	/** +	* Finds style paths using the extension manager +	* +	* Locates a path (e.g. styles/prosilver/) in all active extensions. +	* Then appends the core style paths based in the current working +	* directory. +	* +	* @return array     List of style paths +	*/ +	public function find() +	{ +		$directories = array(); + +		$finder = $this->extension_manager->get_finder(); +		foreach ($this->base_path_provider as $key => $paths) +		{ +			if ($key == 'style') +			{ +				foreach ($paths as $path) +				{ +					$directories['style'][] = $path; +					if ($path && !phpbb_is_absolute($path)) +					{ +						$result = $finder->directory('/' . $this->ext_dir_prefix . $path) +							->get_directories(true, true); +						foreach ($result as $ext => $ext_path) +						{ +							$directories[$ext][] = $ext_path; +						} +					} +				} +			} +		} + +		return $directories; +	} + +	/** +	* Overwrites the current style paths +	* +	* @param array $styles An array of style paths. The first element is the main style. +	* @return null +	*/ +	public function set_styles(array $styles) +	{ +		$this->base_path_provider->set_styles($styles); +		$this->items = null; +	} +} diff --git a/phpBB/includes/style/path_provider.php b/phpBB/includes/style/path_provider.php new file mode 100644 index 0000000000..731d682e88 --- /dev/null +++ b/phpBB/includes/style/path_provider.php @@ -0,0 +1,62 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Provides a style resource locator with paths +* +* Finds installed style paths and makes them available to the resource locator. +* +* @package phpBB3 +*/ +class phpbb_style_path_provider implements IteratorAggregate, phpbb_style_path_provider_interface +{ +	protected $paths = array(); + +	/** +	* Ignores the extension dir prefix +	* +	* @param string $ext_dir_prefix The prefix including trailing slash +	* @return null +	*/ +	public function set_ext_dir_prefix($ext_dir_prefix) +	{ +	} + +	/** +	* Overwrites the current style paths +	* +	* The first element of the passed styles map, is considered the main +	* style. +	* +	* @param array $styles An array of style paths. The first element is the main style. +	* @return null +	*/ +	public function set_styles(array $styles) +	{ +		$this->paths = array('style' => $styles); +	} + +	/** +	* Retrieve an iterator over all style paths +	* +	* @return ArrayIterator An iterator for the array of style paths +	*/ +	public function getIterator() +	{ +		return new ArrayIterator($this->paths); +	} +} diff --git a/phpBB/includes/style/path_provider_interface.php b/phpBB/includes/style/path_provider_interface.php new file mode 100644 index 0000000000..1a6153a4d3 --- /dev/null +++ b/phpBB/includes/style/path_provider_interface.php @@ -0,0 +1,42 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Provides a style resource locator with paths +* +* Finds installed style paths and makes them available to the resource locator. +* +* @package phpBB3 +*/ +interface phpbb_style_path_provider_interface extends Traversable +{ +	/** +	* Defines a prefix to use for style paths in extensions +	* +	* @param string $ext_dir_prefix The prefix including trailing slash +	* @return null +	*/ +	public function set_ext_dir_prefix($ext_dir_prefix); + +	/** +	* Overwrites the current style paths +	* +	* @param array $styles An array of style paths. The first element is the main style. +	* @return null +	*/ +	public function set_styles(array $styles); +} diff --git a/phpBB/includes/style/resource_locator.php b/phpBB/includes/style/resource_locator.php new file mode 100644 index 0000000000..8658fe4a36 --- /dev/null +++ b/phpBB/includes/style/resource_locator.php @@ -0,0 +1,232 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + + +/** +* Style resource locator.  +* Maintains mapping from template handles to source template file paths. +* Locates style files: resources (such as .js and .css files) and templates. +* +* Style resource locator is aware of styles tree, and can return actual +* filesystem paths (i.e., the "child" style or the "parent" styles) +* depending on what files exist. +* +* Root paths stored in locator are paths to style directories. Templates are +* stored in subdirectory that $template_path points to. +* +* @package phpBB3 +*/ +class phpbb_style_resource_locator implements phpbb_template_locator +{ +	/** +	* Paths to style directories. +	* @var array +	*/ +	private $roots = array(); + +	/** +	* Location of templates directory within style directories. +	* Must have trailing slash. Empty if templates are stored in root +	* style directory, such as admin control panel templates. +	* @var string +	*/ +	public $template_path = 'template/'; + +	/** +	* Map from root index to handles to source template file paths. +	* Normally it only contains paths for handles that are used +	* (or are likely to be used) by the page being rendered and not +	* all templates that exist on the filesystem. +	* @var array +	*/ +	private $files = array(); + +	/** +	* Map from handles to source template file names. +	* Covers the same data as $files property but maps to basenames +	* instead of paths. +	* @var array +	*/ +	private $filenames = array(); + +	/** +	* Sets the list of style paths +	* +	* These paths will be searched for style files in the provided order. +	* Paths may be outside of phpBB, but templates loaded from these paths +	* will still be cached. +	* +	* @param array $style_paths An array of paths to style directories +	* @return null +	*/ +	public function set_paths($style_paths) +	{ +		$this->roots = array(); +		$this->files = array(); +		$this->filenames = array(); + +		foreach ($style_paths as $key => $paths) +		{ +			foreach ($paths as $path) +			{ +				// Make sure $path has no ending slash +				if (substr($path, -1) === '/') +				{ +					$path = substr($path, 0, -1); +				} +				$this->roots[$key][] = $path; +			} +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function set_filenames(array $filename_array) +	{ +		foreach ($filename_array as $handle => $filename) +		{ +			if (empty($filename)) +			{ +				trigger_error("style resource locator: set_filenames: Empty filename specified for $handle", E_USER_ERROR); +			} + +			$this->filename[$handle] = $filename; + +			foreach ($this->roots as $root_key => $root_paths) +			{ +				foreach ($root_paths as $root_index => $root) +				{ +					$this->files[$root_key][$root_index][$handle] = $root . '/' . $this->template_path . $filename; +				} +			} +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_filename_for_handle($handle) +	{ +		if (!isset($this->filename[$handle])) +		{ +			trigger_error("style resource locator: get_filename_for_handle: No file specified for handle $handle", E_USER_ERROR); +		} +		return $this->filename[$handle]; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_virtual_source_file_for_handle($handle) +	{ +		// If we don't have a file assigned to this handle, die. +		if (!isset($this->files['style'][0][$handle])) +		{ +			trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); +		} + +		$source_file = $this->files['style'][0][$handle]; +		return $source_file; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_source_file_for_handle($handle, $find_all = false) +	{ +		// If we don't have a file assigned to this handle, die. +		if (!isset($this->files['style'][0][$handle])) +		{ +			trigger_error("style resource locator: No file specified for handle $handle", E_USER_ERROR); +		} + +		// locate a source file that exists +		$source_file = $this->files['style'][0][$handle]; +		$tried = $source_file; +		$found = false; +		$found_all = array(); +		foreach ($this->roots as $root_key => $root_paths) +		{ +			foreach ($root_paths as $root_index => $root) +			{ +				$source_file = $this->files[$root_key][$root_index][$handle]; +				$tried .= ', ' . $source_file; +				if (file_exists($source_file)) +				{ +					$found = true; +					break; +				} +			} +			if ($found) +			{ +				if ($find_all) +				{ +					$found_all[] = $source_file; +					$found = false; +				} +				else +				{ +					break; +				} +			} +		} + +		// search failed +		if (!$found && !$find_all) +		{ +			trigger_error("style resource locator: File for handle $handle does not exist. Could not find: $tried", E_USER_ERROR); +		} + +		return ($find_all) ? $found_all : $source_file; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_first_file_location($files, $return_default = false, $return_full_path = true) +	{ +		// set default value +		$default_result = false; + +		// check all available paths +		foreach ($this->roots as $root_paths) +		{ +			foreach ($root_paths as $path) +			{ +				// check all files +				foreach ($files as $filename) +				{ +					$source_file = $path . '/' . $filename; +					if (file_exists($source_file)) +					{ +						return ($return_full_path) ? $source_file : $filename; +					} + +					// assign first file as result if $return_default is true +					if ($return_default && $default_result === false) +					{ +						$default_result = $source_file; +					} +				} +			} +		} + +		// search failed +		return $default_result; +	} +} diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php new file mode 100644 index 0000000000..36298b49ec --- /dev/null +++ b/phpBB/includes/style/style.php @@ -0,0 +1,184 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Base Style class. +* @package phpBB3 +*/ +class phpbb_style +{ +	/** +	* Template class. +	* Handles everything related to templates. +	* @var phpbb_template +	*/ +	private $template; + +	/** +	* phpBB root path +	* @var string +	*/ +	private $phpbb_root_path; + +	/** +	* PHP file extension +	* @var string +	*/ +	private $php_ext; + +	/** +	* phpBB config instance +	* @var phpbb_config +	*/ +	private $config; + +	/** +	* Current user +	* @var phpbb_user +	*/ +	private $user; + +	/** +	* Style resource locator +	* @var phpbb_style_resource_locator +	*/ +	private $locator; + +	/** +	* Style path provider +	* @var phpbb_style_path_provider +	*/ +	private $provider; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path phpBB root path +	* @param user $user current user +	* @param phpbb_style_resource_locator $locator style resource locator +	* @param phpbb_style_path_provider $provider style path provider +	* @param phpbb_template $template template +	*/ +	public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_style_resource_locator $locator, phpbb_style_path_provider_interface $provider, phpbb_template $template) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->config = $config; +		$this->user = $user; +		$this->locator = $locator; +		$this->provider = $provider; +		$this->template = $template; +	} + +	/** +	* Set style location based on (current) user's chosen style. +	*/ +	public function set_style() +	{ +		$style_path = $this->user->style['style_path']; +		$style_dirs = ($this->user->style['style_parent_id']) ? array_reverse(explode('/', $this->user->style['style_parent_tree'])) : array(); +		$paths = array($this->get_style_path($style_path)); +		foreach ($style_dirs as $dir) +		{ +			$paths[] = $this->get_style_path($dir); +		} + +		// Add 'all' path, used as last fallback path by hooks and extensions +		$paths[] = $this->get_style_path('all'); + +		return $this->set_custom_style($style_path, $paths); +	} + +	/** +	* Set custom style location (able to use directory outside of phpBB). +	* +	* Note: Templates are still compiled to phpBB's cache directory. +	* +	* @param string $name Name of style, used for cache prefix. Examples: "admin", "prosilver" +	* @param array or string $paths Array of style paths, relative to current root directory +	* @param string $template_path Path to templates, relative to style directory. False if path should not be changed. +	*/ +	public function set_custom_style($name, $paths, $template_path = false) +	{ +		if (is_string($paths)) +		{ +			$paths = array($paths); +		} + +		$this->provider->set_styles($paths); +		$this->locator->set_paths($this->provider); + +		$this->template->cachepath = $this->phpbb_root_path . 'cache/tpl_' . str_replace('_', '-', $name) . '_'; + +		if ($template_path !== false) +		{ +			$this->template->template_path = $this->locator->template_path = $template_path; +		} + +		return true; +	} + +	/** +	* Get location of style directory for specific style_path +	* +	* @param string $path Style path, such as "prosilver" +	* @return string Path to style directory, relative to current path +	*/ +	public function get_style_path($path) +	{ +		return $this->phpbb_root_path . 'styles/' . $path; +	} + +	/** +	* Defines a prefix to use for style paths in extensions +	* +	* @param string $ext_dir_prefix The prefix including trailing slash +	* @return null +	*/ +	public function set_ext_dir_prefix($ext_dir_prefix) +	{ +		$this->provider->set_ext_dir_prefix($ext_dir_prefix); +	} + +	/** +	* Locates source file path, accounting for styles tree and verifying that +	* the path exists. +	* +	* @param string or array $files List of files to locate. If there is only +	*				one file, $files can be a string to make code easier to read. +	* @param bool $return_default Determines what to return if file does not +	*				exist. If true, function will return location where file is +	*				supposed to be. If false, function will return false. +	* @param bool $return_full_path If true, function will return full path +	*				to file. If false, function will return file name. This +	*				parameter can be used to check which one of set of files +	*				is available. +	* @return string or boolean Source file path if file exists or $return_default is +	*				true. False if file does not exist and $return_default is false +	*/ +	public function locate($files, $return_default = false, $return_full_path = true) +	{ +		// convert string to array +		if (is_string($files)) +		{ +			$files = array($files); +		} + +		// use resource locator to find files +		return $this->locator->get_first_file_location($files, $return_default, $return_full_path); +	} +} diff --git a/phpBB/includes/template.php b/phpBB/includes/template.php deleted file mode 100644 index 5192c334d8..0000000000 --- a/phpBB/includes/template.php +++ /dev/null @@ -1,690 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @version $Id$ -* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc -* @license http://opensource.org/licenses/gpl-license.php GNU Public License -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ -	exit; -} - -/** -* Base Template class. -* @package phpBB3 -*/ -class template -{ -	/** variable that holds all the data we'll be substituting into -	* the compiled templates. Takes form: -	* --> $this->_tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value -	* if it's a root-level variable, it'll be like this: -	* --> $this->_tpldata[.][0][varname] == value -	*/ -	var $_tpldata = array('.' => array(0 => array())); -	var $_rootref; - -	// Root dir and hash of filenames for each template handle. -	var $root = ''; -	var $cachepath = ''; -	var $files = array(); -	var $filename = array(); -	var $files_inherit = array(); -	var $files_template = array(); -	var $inherit_root = ''; -	var $orig_tpl_storedb; -	var $orig_tpl_inherits_id; - -	// this will hash handle names to the compiled/uncompiled code for that handle. -	var $compiled_code = array(); - -	/** -	* Set template location -	* @access public -	*/ -	function set_template() -	{ -		global $phpbb_root_path, $user; - -		if (file_exists($phpbb_root_path . 'styles/' . $user->theme['template_path'] . '/template')) -		{ -			$this->root = $phpbb_root_path . 'styles/' . $user->theme['template_path'] . '/template'; -			$this->cachepath = $phpbb_root_path . 'cache/tpl_' . str_replace('_', '-', $user->theme['template_path']) . '_'; - -			if ($this->orig_tpl_storedb === null) -			{ -				$this->orig_tpl_storedb = $user->theme['template_storedb']; -			} - -			if ($this->orig_tpl_inherits_id === null) -			{ -				$this->orig_tpl_inherits_id = $user->theme['template_inherits_id']; -			} - -			$user->theme['template_storedb'] = $this->orig_tpl_storedb; -			$user->theme['template_inherits_id'] = $this->orig_tpl_inherits_id; - -			if ($user->theme['template_inherits_id']) -			{ -				$this->inherit_root = $phpbb_root_path . 'styles/' . $user->theme['template_inherit_path'] . '/template'; -			} -		} -		else -		{ -			trigger_error('Template path could not be found: styles/' . $user->theme['template_path'] . '/template', E_USER_ERROR); -		} - -		$this->_rootref = &$this->_tpldata['.'][0]; - -		return true; -	} - -	/** -	* Set custom template location (able to use directory outside of phpBB) -	* @access public -	*/ -	function set_custom_template($template_path, $template_name, $fallback_template_path = false) -	{ -		global $phpbb_root_path, $user; - -		// Make sure $template_path has no ending slash -		if (substr($template_path, -1) == '/') -		{ -			$template_path = substr($template_path, 0, -1); -		} - -		$this->root = $template_path; -		$this->cachepath = $phpbb_root_path . 'cache/ctpl_' . str_replace('_', '-', $template_name) . '_'; - -		if ($fallback_template_path !== false) -		{ -			if (substr($fallback_template_path, -1) == '/') -			{ -				$fallback_template_path = substr($fallback_template_path, 0, -1); -			} - -			$this->inherit_root = $fallback_template_path; -			$this->orig_tpl_inherits_id = true; -		} -		else -		{ -			$this->orig_tpl_inherits_id = false; -		} - -		// the database does not store the path or name of a custom template -		// so there is no way we can properly store custom templates there -		$this->orig_tpl_storedb = false; - -		$this->_rootref = &$this->_tpldata['.'][0]; - -		return true; -	} - -	/** -	* Sets the template filenames for handles. $filename_array -	* should be a hash of handle => filename pairs. -	* @access public -	*/ -	function set_filenames($filename_array) -	{ -		if (!is_array($filename_array)) -		{ -			return false; -		} -		foreach ($filename_array as $handle => $filename) -		{ -			if (empty($filename)) -			{ -				trigger_error("template->set_filenames: Empty filename specified for $handle", E_USER_ERROR); -			} - -			$this->filename[$handle] = $filename; -			$this->files[$handle] = $this->root . '/' . $filename; - -			if ($this->inherit_root) -			{ -				$this->files_inherit[$handle] = $this->inherit_root . '/' . $filename; -			} -		} - -		return true; -	} - -	/** -	* Destroy template data set -	* @access public -	*/ -	function destroy() -	{ -		$this->_tpldata = array('.' => array(0 => array())); -		$this->_rootref = &$this->_tpldata['.'][0]; -	} - -	/** -	* Reset/empty complete block -	* @access public -	*/ -	function destroy_block_vars($blockname) -	{ -		if (strpos($blockname, '.') !== false) -		{ -			// Nested block. -			$blocks = explode('.', $blockname); -			$blockcount = sizeof($blocks) - 1; - -			$str = &$this->_tpldata; -			for ($i = 0; $i < $blockcount; $i++) -			{ -				$str = &$str[$blocks[$i]]; -				$str = &$str[sizeof($str) - 1]; -			} - -			unset($str[$blocks[$blockcount]]); -		} -		else -		{ -			// Top-level block. -			unset($this->_tpldata[$blockname]); -		} - -		return true; -	} - -	/** -	* Display handle -	* @access public -	*/ -	function display($handle, $include_once = true) -	{ -		global $user, $phpbb_hook; - -		if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, __FUNCTION__), $handle, $include_once, $this)) -		{ -			if ($phpbb_hook->hook_return(array(__CLASS__, __FUNCTION__))) -			{ -				return $phpbb_hook->hook_return_result(array(__CLASS__, __FUNCTION__)); -			} -		} - -		if (defined('IN_ERROR_HANDLER')) -		{ -			if ((E_NOTICE & error_reporting()) == E_NOTICE) -			{ -				error_reporting(error_reporting() ^ E_NOTICE); -			} -		} - -		if ($filename = $this->_tpl_load($handle)) -		{ -			($include_once) ? include_once($filename) : include($filename); -		} -		else -		{ -			eval(' ?>' . $this->compiled_code[$handle] . '<?php '); -		} - -		return true; -	} - -	/** -	* Display the handle and assign the output to a template variable or return the compiled result. -	* @access public -	*/ -	function assign_display($handle, $template_var = '', $return_content = true, $include_once = false) -	{ -		ob_start(); -		$this->display($handle, $include_once); -		$contents = ob_get_clean(); - -		if ($return_content) -		{ -			return $contents; -		} - -		$this->assign_var($template_var, $contents); - -		return true; -	} - -	/** -	* Load a compiled template if possible, if not, recompile it -	* @access private -	*/ -	function _tpl_load(&$handle) -	{ -		global $user, $phpEx, $config; - -		if (!isset($this->filename[$handle])) -		{ -			trigger_error("template->_tpl_load(): No file specified for handle $handle", E_USER_ERROR); -		} - -		// reload these settings to have the values they had when this object was initialised -		// using set_template or set_custom_template, they might otherwise have been overwritten -		// by other template class instances in between. -		$user->theme['template_storedb'] = $this->orig_tpl_storedb; -		$user->theme['template_inherits_id'] = $this->orig_tpl_inherits_id; - -		$filename = $this->cachepath . str_replace('/', '.', $this->filename[$handle]) . '.' . $phpEx; -		$this->files_template[$handle] = (isset($user->theme['template_id'])) ? $user->theme['template_id'] : 0; - -		$recompile = false; -		if (!file_exists($filename) || @filesize($filename) === 0 || defined('DEBUG_EXTRA')) -		{ -			$recompile = true; -		} -		else if ($config['load_tplcompile']) -		{ -			// No way around it: we need to check inheritance here -			if ($user->theme['template_inherits_id'] && !file_exists($this->files[$handle])) -			{ -				$this->files[$handle] = $this->files_inherit[$handle]; -				$this->files_template[$handle] = $user->theme['template_inherits_id']; -			} -			$recompile = (@filemtime($filename) < filemtime($this->files[$handle])) ? true : false; -		} - -		// Recompile page if the original template is newer, otherwise load the compiled version -		if (!$recompile) -		{ -			return $filename; -		} - -		global $db, $phpbb_root_path; - -		if (!class_exists('template_compile')) -		{ -			include($phpbb_root_path . 'includes/functions_template.' . $phpEx); -		} - -		// Inheritance - we point to another template file for this one. Equality is also used for store_db -		if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id'] && !file_exists($this->files[$handle])) -		{ -			$this->files[$handle] = $this->files_inherit[$handle]; -			$this->files_template[$handle] = $user->theme['template_inherits_id']; -		} - -		$compile = new template_compile($this); - -		// If we don't have a file assigned to this handle, die. -		if (!isset($this->files[$handle])) -		{ -			trigger_error("template->_tpl_load(): No file specified for handle $handle", E_USER_ERROR); -		} - -		// Just compile if no user object is present (happens within the installer) -		if (!$user) -		{ -			$compile->_tpl_load_file($handle); -			return false; -		} - -		if (isset($user->theme['template_storedb']) && $user->theme['template_storedb']) -		{ -			$rows = array(); -			$ids = array(); -			// Inheritance -			if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id']) -			{ -				$ids[] = $user->theme['template_inherits_id']; -			} -			$ids[] = $user->theme['template_id']; - -			foreach ($ids as $id) -			{ -				$sql = 'SELECT * -				FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' -				WHERE template_id = ' . $id . " -					AND (template_filename = '" . $db->sql_escape($this->filename[$handle]) . "' -						OR template_included " . $db->sql_like_expression($db->any_char . $this->filename[$handle] . ':' . $db->any_char) . ')'; - -				$result = $db->sql_query($sql); -				while ($row = $db->sql_fetchrow($result)) -				{ -					$rows[$row['template_filename']] = $row; -				} -				$db->sql_freeresult($result); -			} - -			if (sizeof($rows)) -			{ -				foreach ($rows as $row) -				{ -					$file = $this->root . '/' . $row['template_filename']; -					$force_reload = false; -					if ($row['template_id'] != $user->theme['template_id']) -					{ -						// make sure that we are not overlooking a file not in the db yet -						if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id'] && !file_exists($file)) -						{ -							$file = $this->inherit_root . '/' . $row['template_filename']; -							$this->files[$row['template_filename']] = $file; -							$this->files_inherit[$row['template_filename']] = $file; -							$this->files_template[$row['template_filename']] = $user->theme['template_inherits_id']; -						} -						else if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id']) -						{ -							// Ok, we have a situation. There is a file in the subtemplate, but nothing in the DB. We have to fix that. -							$force_reload = true; -							$this->files_template[$row['template_filename']] = $user->theme['template_inherits_id']; -						} -					} -					else -					{ -						$this->files_template[$row['template_filename']] = $user->theme['template_id']; -					} - -					if ($force_reload || $row['template_mtime'] < filemtime($file)) -					{ -						if ($row['template_filename'] == $this->filename[$handle]) -						{ -							$compile->_tpl_load_file($handle, true); -						} -						else -						{ -							$this->files[$row['template_filename']] = $file; -							$this->filename[$row['template_filename']] = $row['template_filename']; -							$compile->_tpl_load_file($row['template_filename'], true); -							unset($this->compiled_code[$row['template_filename']]); -							unset($this->files[$row['template_filename']]); -							unset($this->filename[$row['template_filename']]); -						} -					} - -					if ($row['template_filename'] == $this->filename[$handle]) -					{ -						$this->compiled_code[$handle] = $compile->compile(trim($row['template_data'])); -						$compile->compile_write($handle, $this->compiled_code[$handle]); -					} -					else -					{ -						// Only bother compiling if it doesn't already exist -						if (!file_exists($this->cachepath . str_replace('/', '.', $row['template_filename']) . '.' . $phpEx)) -						{ -							$this->filename[$row['template_filename']] = $row['template_filename']; -							$compile->compile_write($row['template_filename'], $compile->compile(trim($row['template_data']))); -							unset($this->filename[$row['template_filename']]); -						} -					} -				} -			} -			else -			{ -				$file = $this->root . '/' . $row['template_filename']; - -				if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id'] && !file_exists($file)) -				{ -					$file = $this->inherit_root . '/' . $row['template_filename']; -					$this->files[$row['template_filename']] = $file; -					$this->files_inherit[$row['template_filename']] = $file; -					$this->files_template[$row['template_filename']] = $user->theme['template_inherits_id']; -				} -				// Try to load from filesystem and instruct to insert into the styles table... -				$compile->_tpl_load_file($handle, true); -				return false; -			} - -			return false; -		} - -		$compile->_tpl_load_file($handle); -		return false; -	} - -	/** -	* Assign key variable pairs from an array -	* @access public -	*/ -	function assign_vars($vararray) -	{ -		foreach ($vararray as $key => $val) -		{ -			$this->_rootref[$key] = $val; -		} - -		return true; -	} - -	/** -	* Assign a single variable to a single key -	* @access public -	*/ -	function assign_var($varname, $varval) -	{ -		$this->_rootref[$varname] = $varval; - -		return true; -	} - -	/** -	* Assign key variable pairs from an array to a specified block -	* @access public -	*/ -	function assign_block_vars($blockname, $vararray) -	{ -		if (strpos($blockname, '.') !== false) -		{ -			// Nested block. -			$blocks = explode('.', $blockname); -			$blockcount = sizeof($blocks) - 1; - -			$str = &$this->_tpldata; -			for ($i = 0; $i < $blockcount; $i++) -			{ -				$str = &$str[$blocks[$i]]; -				$str = &$str[sizeof($str) - 1]; -			} - -			$s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; -			$vararray['S_ROW_COUNT'] = $s_row_count; - -			// Assign S_FIRST_ROW -			if (!$s_row_count) -			{ -				$vararray['S_FIRST_ROW'] = true; -			} - -			// Now the tricky part, we always assign S_LAST_ROW and remove the entry before -			// This is much more clever than going through the complete template data on display (phew) -			$vararray['S_LAST_ROW'] = true; -			if ($s_row_count > 0) -			{ -				unset($str[$blocks[$blockcount]][($s_row_count - 1)]['S_LAST_ROW']); -			} - -			// Now we add the block that we're actually assigning to. -			// We're adding a new iteration to this block with the given -			// variable assignments. -			$str[$blocks[$blockcount]][] = $vararray; -		} -		else -		{ -			// Top-level block. -			$s_row_count = (isset($this->_tpldata[$blockname])) ? sizeof($this->_tpldata[$blockname]) : 0; -			$vararray['S_ROW_COUNT'] = $s_row_count; - -			// Assign S_FIRST_ROW -			if (!$s_row_count) -			{ -				$vararray['S_FIRST_ROW'] = true; -			} - -			// We always assign S_LAST_ROW and remove the entry before -			$vararray['S_LAST_ROW'] = true; -			if ($s_row_count > 0) -			{ -				unset($this->_tpldata[$blockname][($s_row_count - 1)]['S_LAST_ROW']); -			} - -			// Add a new iteration to this block with the variable assignments we were given. -			$this->_tpldata[$blockname][] = $vararray; -		} - -		return true; -	} - -	/** -	* Change already assigned key variable pair (one-dimensional - single loop entry) -	* -	* An example of how to use this function: -	* {@example alter_block_array.php} -	* -	* @param	string	$blockname	the blockname, for example 'loop' -	* @param	array	$vararray	the var array to insert/add or merge -	* @param	mixed	$key		Key to search for -	* -	* array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] -	* -	* int: Position [the position to change or insert at directly given] -	* -	* If key is false the position is set to 0 -	* If key is true the position is set to the last entry -	* -	* @param	string	$mode		Mode to execute (valid modes are 'insert' and 'change') -	* -	*	If insert, the vararray is inserted at the given position (position counting from zero). -	*	If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). -	* -	* Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) -	* and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) -	* -	* @return bool false on error, true on success -	* @access public -	*/ -	function alter_block_array($blockname, $vararray, $key = false, $mode = 'insert') -	{ -		if (strpos($blockname, '.') !== false) -		{ -			// Nested blocks are not supported -			return false; -		} - -		// Change key to zero (change first position) if false and to last position if true -		if ($key === false || $key === true) -		{ -			$key = ($key === false) ? 0 : sizeof($this->_tpldata[$blockname]); -		} - -		// Get correct position if array given -		if (is_array($key)) -		{ -			// Search array to get correct position -			list($search_key, $search_value) = @each($key); - -			$key = NULL; -			foreach ($this->_tpldata[$blockname] as $i => $val_ary) -			{ -				if ($val_ary[$search_key] === $search_value) -				{ -					$key = $i; -					break; -				} -			} - -			// key/value pair not found -			if ($key === NULL) -			{ -				return false; -			} -		} - -		// Insert Block -		if ($mode == 'insert') -		{ -			// Make sure we are not exceeding the last iteration -			if ($key >= sizeof($this->_tpldata[$blockname])) -			{ -				$key = sizeof($this->_tpldata[$blockname]); -				unset($this->_tpldata[$blockname][($key - 1)]['S_LAST_ROW']); -				$vararray['S_LAST_ROW'] = true; -			} -			else if ($key === 0) -			{ -				unset($this->_tpldata[$blockname][0]['S_FIRST_ROW']); -				$vararray['S_FIRST_ROW'] = true; -			} - -			// Re-position template blocks -			for ($i = sizeof($this->_tpldata[$blockname]); $i > $key; $i--) -			{ -				$this->_tpldata[$blockname][$i] = $this->_tpldata[$blockname][$i-1]; -				$this->_tpldata[$blockname][$i]['S_ROW_COUNT'] = $i; -			} - -			// Insert vararray at given position -			$vararray['S_ROW_COUNT'] = $key; -			$this->_tpldata[$blockname][$key] = $vararray; - -			return true; -		} - -		// Which block to change? -		if ($mode == 'change') -		{ -			if ($key == sizeof($this->_tpldata[$blockname])) -			{ -				$key--; -			} - -			$this->_tpldata[$blockname][$key] = array_merge($this->_tpldata[$blockname][$key], $vararray); -			return true; -		} - -		return false; -	} - -	/** -	* Include a separate template -	* @access private -	*/ -	function _tpl_include($filename, $include = true) -	{ -		$handle = $filename; -		$this->filename[$handle] = $filename; -		$this->files[$handle] = $this->root . '/' . $filename; -		if ($this->inherit_root) -		{ -			$this->files_inherit[$handle] = $this->inherit_root . '/' . $filename; -		} - -		$filename = $this->_tpl_load($handle); - -		if ($include) -		{ -			global $user; - -			if ($filename) -			{ -				include($filename); -				return; -			} -			eval(' ?>' . $this->compiled_code[$handle] . '<?php '); -		} -	} - -	/** -	* Include a php-file -	* @access private -	*/ -	function _php_include($filename) -	{ -		global $phpbb_root_path; - -		$file = $phpbb_root_path . $filename; - -		if (!file_exists($file)) -		{ -			// trigger_error cannot be used here, as the output already started -			echo 'template->_php_include(): File ' . htmlspecialchars($file) . ' does not exist or is empty'; -			return; -		} -		include($file); -	} -} diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php new file mode 100644 index 0000000000..82b301c1a2 --- /dev/null +++ b/phpBB/includes/template/compile.php @@ -0,0 +1,128 @@ +<?php +/** +* +* @package phpBB3 +* @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; +} + +stream_filter_register('phpbb_template', 'phpbb_template_filter'); + +/** +* Extension of template class - Functions needed for compiling templates only. +* +* @package phpBB3 +* @uses template_filter As a PHP stream filter to perform compilation of templates +*/ +class phpbb_template_compile +{ +	/** +	* Array of parameters to forward to template filter +	* +	* @var array +	*/ +	private $filter_params; + +	/** +	* Constructor. +	* +	* @param bool @allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) +	* @param phpbb_style_resource_locator $locator Resource locator +	* @param string $phpbb_root_path Path to phpBB root directory +	*/ +	public function __construct($allow_php, $locator, $phpbb_root_path) +	{ +		$this->filter_params = array( +			'allow_php'	=> $allow_php, +			'locator'	=> $locator, +			'phpbb_root_path'	=> $phpbb_root_path +		); +	} + +	/** +	* Compiles template in $source_file and writes compiled template to +	* cache directory +	* +	* @param string $handle Template handle to compile +	* @param string $source_file Source template file +	* @return bool Return true on success otherwise false +	*/ +	public function compile_file_to_file($source_file, $compiled_file) +	{ +		$source_handle = @fopen($source_file, 'rb'); +		$destination_handle = @fopen($compiled_file, 'wb'); + +		if (!$source_handle || !$destination_handle) +		{ +			return false; +		} + +		@flock($destination_handle, LOCK_EX); + +		$this->compile_stream_to_stream($source_handle, $destination_handle); + +		@fclose($source_handle); +		@flock($destination_handle, LOCK_UN); +		@fclose($destination_handle); + +		phpbb_chmod($compiled_file, CHMOD_READ | CHMOD_WRITE); + +		clearstatcache(); + +		return true; +	} + +	/** +	* Compiles a template located at $source_file. +	* +	* Returns PHP source suitable for eval(). +	* +	* @param string $source_file Source template file +	* @return string|bool Return compiled code on successful compilation otherwise false +	*/ +	public function compile_file($source_file) +	{ +		$source_handle = @fopen($source_file, 'rb'); +		$destination_handle = @fopen('php://temp' ,'r+b'); + +		if (!$source_handle || !$destination_handle) +		{ +			return false; +		} + +		$this->compile_stream_to_stream($source_handle, $destination_handle); + +		@fclose($source_handle); + +		rewind($destination_handle); +		$contents = stream_get_contents($destination_handle); +		@fclose($dest_handle); + +		return $contents; +	} + +	/** +	* Compiles contents of $source_stream into $dest_stream. +	* +	* A stream filter is appended to $source_stream as part of the +	* process. +	* +	* @param resource $source_stream Source stream +	* @param resource $dest_stream Destination stream +	* @return void +	*/ +	private function compile_stream_to_stream($source_stream, $dest_stream) +	{ +		stream_filter_append($source_stream, 'phpbb_template', null, $this->filter_params); +		stream_copy_to_stream($source_stream, $dest_stream); +	} +} diff --git a/phpBB/includes/template/context.php b/phpBB/includes/template/context.php new file mode 100644 index 0000000000..ec09da1cf3 --- /dev/null +++ b/phpBB/includes/template/context.php @@ -0,0 +1,359 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Stores variables assigned to template. +* +* @package phpBB3 +*/ +class phpbb_template_context +{ +	/** +	* variable that holds all the data we'll be substituting into +	* the compiled templates. Takes form: +	* --> $this->tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value +	* if it's a root-level variable, it'll be like this: +	* --> $this->tpldata[.][0][varname] == value +	* +	* @var array +	*/ +	private $tpldata = array('.' => array(0 => array())); + +	/** +	* @var array Reference to template->tpldata['.'][0] +	*/ +	private $rootref; + +	public function __construct() +	{ +		$this->clear(); +	} + +	/** +	* Clears template data set. +	*/ +	public function clear() +	{ +		$this->tpldata = array('.' => array(0 => array())); +		$this->rootref = &$this->tpldata['.'][0]; +	} + +	/** +	* Assign a single scalar value to a single key. +	* +	* Value can be a string, an integer or a boolean. +	* +	* @param string $varname Variable name +	* @param string $varval Value to assign to variable +	*/ +	public function assign_var($varname, $varval) +	{ +		$this->rootref[$varname] = $varval; + +		return true; +	} + +	/** +	* Append text to the string value stored in a key. +	* +	* Text is appended using the string concatenation operator (.). +	* +	* @param string $varname Variable name +	* @param string $varval Value to append to variable +	*/ +	public function append_var($varname, $varval) +	{ +		$this->rootref[$varname] = (isset($this->rootref[$varname]) ? $this->rootref[$varname] : '') . $varval; + +		return true; +	} + +	/** +	* Returns a reference to template data array. +	* +	* This function is public so that template renderer may invoke it. +	* Users should alter template variables via functions in phpbb_template. +	* +	* Note: modifying returned array will affect data stored in the context. +	* +	* @return array template data +	*/ +	public function &get_data_ref() +	{ +		// returning a reference directly is not +		// something php is capable of doing +		$ref = &$this->tpldata; +		return $ref; +	} + +	/** +	* Returns a reference to template root scope. +	* +	* This function is public so that template renderer may invoke it. +	* Users should not need to invoke this function. +	* +	* Note: modifying returned array will affect data stored in the context. +	* +	* @return array template data +	*/ +	public function &get_root_ref() +	{ +		// rootref is already a reference +		return $this->rootref; +	} + +	/** +	* Assign key variable pairs from an array to a specified block +	* +	* @param string $blockname Name of block to assign $vararray to +	* @param array $vararray A hash of variable name => value pairs +	*/ +	public function assign_block_vars($blockname, array $vararray) +	{ +		if (strpos($blockname, '.') !== false) +		{ +			// Nested block. +			$blocks = explode('.', $blockname); +			$blockcount = sizeof($blocks) - 1; + +			$str = &$this->tpldata; +			for ($i = 0; $i < $blockcount; $i++) +			{ +				$str = &$str[$blocks[$i]]; +				$str = &$str[sizeof($str) - 1]; +			} + +			$s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; +			$vararray['S_ROW_COUNT'] = $s_row_count; + +			// Assign S_FIRST_ROW +			if (!$s_row_count) +			{ +				$vararray['S_FIRST_ROW'] = true; +			} + +			// Now the tricky part, we always assign S_LAST_ROW and remove the entry before +			// This is much more clever than going through the complete template data on display (phew) +			$vararray['S_LAST_ROW'] = true; +			if ($s_row_count > 0) +			{ +				unset($str[$blocks[$blockcount]][($s_row_count - 1)]['S_LAST_ROW']); +			} + +			// Now we add the block that we're actually assigning to. +			// We're adding a new iteration to this block with the given +			// variable assignments. +			$str[$blocks[$blockcount]][] = $vararray; +		} +		else +		{ +			// Top-level block. +			$s_row_count = (isset($this->tpldata[$blockname])) ? sizeof($this->tpldata[$blockname]) : 0; +			$vararray['S_ROW_COUNT'] = $s_row_count; + +			// Assign S_FIRST_ROW +			if (!$s_row_count) +			{ +				$vararray['S_FIRST_ROW'] = true; +			} + +			// We always assign S_LAST_ROW and remove the entry before +			$vararray['S_LAST_ROW'] = true; +			if ($s_row_count > 0) +			{ +				unset($this->tpldata[$blockname][($s_row_count - 1)]['S_LAST_ROW']); +			} + +			// Add a new iteration to this block with the variable assignments we were given. +			$this->tpldata[$blockname][] = $vararray; +		} + +		return true; +	} + +	/** +	* Change already assigned key variable pair (one-dimensional - single loop entry) +	* +	* An example of how to use this function: +	* {@example alter_block_array.php} +	* +	* @param	string	$blockname	the blockname, for example 'loop' +	* @param	array	$vararray	the var array to insert/add or merge +	* @param	mixed	$key		Key to search for +	* +	* array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] +	* +	* int: Position [the position to change or insert at directly given] +	* +	* If key is false the position is set to 0 +	* If key is true the position is set to the last entry +	* +	* @param	string	$mode		Mode to execute (valid modes are 'insert' and 'change') +	* +	*	If insert, the vararray is inserted at the given position (position counting from zero). +	*	If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). +	* +	* Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) +	* and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) +	* +	* @return bool false on error, true on success +	*/ +	public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') +	{ +		if (strpos($blockname, '.') !== false) +		{ +			// Nested block. +			$blocks = explode('.', $blockname); +			$blockcount = sizeof($blocks) - 1; + +			$block = &$this->tpldata; +			for ($i = 0; $i < $blockcount; $i++) +			{ +				if (($pos = strpos($blocks[$i], '[')) !== false) +				{ +					$name = substr($blocks[$i], 0, $pos); + +					if (strpos($blocks[$i], '[]') === $pos) +					{ +						$index = sizeof($block[$name]) - 1; +					} +					else +					{ +						$index = min((int) substr($blocks[$i], $pos + 1, -1), sizeof($block[$name]) - 1); +					} +				} +				else +				{ +					$name = $blocks[$i]; +					$index = sizeof($block[$name]) - 1; +				} +				$block = &$block[$name]; +				$block = &$block[$index]; +			} + +			$block = &$block[$blocks[$i]]; // Traverse the last block +		} +		else +		{ +			// Top-level block. +			$block = &$this->tpldata[$blockname]; +		} + +		// Change key to zero (change first position) if false and to last position if true +		if ($key === false || $key === true) +		{ +			$key = ($key === false) ? 0 : sizeof($block); +		} + +		// Get correct position if array given +		if (is_array($key)) +		{ +			// Search array to get correct position +			list($search_key, $search_value) = @each($key); + +			$key = NULL; +			foreach ($block as $i => $val_ary) +			{ +				if ($val_ary[$search_key] === $search_value) +				{ +					$key = $i; +					break; +				} +			} + +			// key/value pair not found +			if ($key === NULL) +			{ +				return false; +			} +		} + +		// Insert Block +		if ($mode == 'insert') +		{ +			// Make sure we are not exceeding the last iteration +			if ($key >= sizeof($this->tpldata[$blockname])) +			{ +				$key = sizeof($this->tpldata[$blockname]); +				unset($this->tpldata[$blockname][($key - 1)]['S_LAST_ROW']); +				$vararray['S_LAST_ROW'] = true; +			} +			else if ($key === 0) +			{ +				unset($this->tpldata[$blockname][0]['S_FIRST_ROW']); +				$vararray['S_FIRST_ROW'] = true; +			} + +			// Re-position template blocks +			for ($i = sizeof($block); $i > $key; $i--) +			{ +				$block[$i] = $block[$i-1]; +			} + +			// Insert vararray at given position +			$block[$key] = $vararray; + +			return true; +		} + +		// Which block to change? +		if ($mode == 'change') +		{ +			if ($key == sizeof($block)) +			{ +				$key--; +			} + +			$block[$key] = array_merge($block[$key], $vararray); + +			return true; +		} + +		return false; +	} + +	/** +	* Reset/empty complete block +	* +	* @param string $blockname Name of block to destroy +	*/ +	public function destroy_block_vars($blockname) +	{ +		if (strpos($blockname, '.') !== false) +		{ +			// Nested block. +			$blocks = explode('.', $blockname); +			$blockcount = sizeof($blocks) - 1; + +			$str = &$this->tpldata; +			for ($i = 0; $i < $blockcount; $i++) +			{ +				$str = &$str[$blocks[$i]]; +				$str = &$str[sizeof($str) - 1]; +			} + +			unset($str[$blocks[$blockcount]]); +		} +		else +		{ +			// Top-level block. +			unset($this->tpldata[$blockname]); +		} + +		return true; +	} +} diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php new file mode 100644 index 0000000000..abee32c8f7 --- /dev/null +++ b/phpBB/includes/template/filter.php @@ -0,0 +1,1028 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group, sections (c) 2001 ispi of Lincoln Inc +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The template filter that does the actual compilation +* +* psoTFX, phpBB Development Team - Completion of file caching, decompilation +* routines and implementation of conditionals/keywords and associated changes +* +* The interface was inspired by PHPLib templates, and the template file (formats are +* quite similar) +* +* The keyword/conditional implementation is currently based on sections of code from +* the Smarty templating engine (c) 2001 ispi of Lincoln, Inc. which is released +* (on its own and in whole) under the LGPL. Section 3 of the LGPL states that any code +* derived from an LGPL application may be relicenced under the GPL, this applies +* to this source +* +* DEFINE directive inspired by a request by Cyberalien +* +* @see template_compile +* @package phpBB3 +*/ +class phpbb_template_filter extends php_user_filter +{ +	const REGEX_NS = '[a-z_][a-z_0-9]+'; + +	const REGEX_VAR = '[A-Z_][A-Z_0-9]+'; +	const REGEX_VAR_SUFFIX = '[A-Z_0-9]+'; + +	const REGEX_TAG = '<!-- ([A-Z][A-Z_0-9]+)(?: (.*?) ?)?-->'; + +	const REGEX_TOKENS = '~<!-- ([A-Z][A-Z_0-9]+)(?: (.*?) ?)?-->|{((?:[a-z_][a-z_0-9]+\.)*\\$?[A-Z][A-Z_0-9]+)}~'; + +	/** +	* @var array +	*/ +	private $block_names = array(); + +	/** +	* @var array +	*/ +	private $block_else_level = array(); + +	/** +	* @var string +	*/ +	private $chunk; + +	/** +	* @var bool +	*/ +	private $in_php; + +	/** +	* Whether inline PHP code, <!-- PHP --> and <!-- INCLUDEPHP --> tags +	* are allowed. If this is false all PHP code will be silently +	* removed from the template during compilation. +	* +	* @var bool +	*/ +	private $allow_php; + +	/** +	* Resource locator. +	* +	* @var phpbb_template_locator +	*/ +	private $locator; + +	/** +	* @var string phpBB root path +	*/ +	private $phpbb_root_path; + +	/** +	* Stream filter +	* +	* Is invoked for evey chunk of the stream, allowing us +	* to work on a chunk at a time, which saves memory. +	*/ +	public function filter($in, $out, &$consumed, $closing) +	{ +		$written = false; +		$first = false; + +		while ($bucket = stream_bucket_make_writeable($in)) +		{ +			$consumed += $bucket->datalen; + +			$data = $this->chunk . $bucket->data; +			$last_nl = strrpos($data, "\n"); +			$this->chunk = substr($data, $last_nl); +			$data = substr($data, 0, $last_nl); + +			if (!strlen($data)) +			{ +				continue; +			} + +			$written = true; + +			$data = $this->compile($data); +			if (!$first) +			{ +				$data = $this->prepend_preamble($data); +				$first = false; +			} +			$bucket->data = $data; +			$bucket->datalen = strlen($bucket->data); +			stream_bucket_append($out, $bucket); +		} + +		if ($closing && strlen($this->chunk)) +		{ +			$written = true; +			$bucket = stream_bucket_new($this->stream, $this->compile($this->chunk)); +			stream_bucket_append($out, $bucket); +		} + +		return $written ? PSFS_PASS_ON : PSFS_FEED_ME; +	} + +	/** +	* Initializer, called on creation. +	* +	* Get the allow_php option, root directory and locator from params, +	* which are passed to stream_filter_append. +	*/ +	public function onCreate() +	{ +		$this->chunk = ''; +		$this->in_php = false; +		$this->allow_php = $this->params['allow_php']; +		$this->locator = $this->params['locator']; +		$this->phpbb_root_path = $this->params['phpbb_root_path']; +		return true; +	} + +	/** +	* Compiles a chunk of template. +	* +	* The chunk must comprise of one or more complete lines from the source +	* template. +	* +	* @param string $data Chunk of source template to compile +	* @return string Compiled PHP/HTML code +	*/ +	private function compile($data) +	{ +		$block_start_in_php = $this->in_php; + +		$data = preg_replace('#<(?:[\\?%]|script)#s', '<?php echo\'\\0\';?>', $data); +		$data = preg_replace_callback(self::REGEX_TOKENS, array($this, 'replace'), $data); + +		// Remove php +		if (!$this->allow_php) +		{ +			if ($block_start_in_php +				&& $this->in_php +				&& strpos($data, '<!-- PHP -->') === false +				&& strpos($data, '<!-- ENDPHP -->') === false) +			{ +				// This is just php code +				return ''; +			} +			$data = preg_replace('~^.*?<!-- ENDPHP -->~', '', $data); +			$data = preg_replace('~<!-- PHP -->.*?<!-- ENDPHP -->~', '', $data); +			$data = preg_replace('~<!-- ENDPHP -->.*?$~', '', $data); +		} + +		/* +			Preserve whitespace. +			PHP removes a newline after the closing tag (if it's there). This is by design. + + +			Consider the following template: + +				<!-- IF condition --> +				some content +				<!-- ENDIF --> + +			If we were to simply preserve all whitespace, we could simply replace all "?>" tags +			with "?>\n". +			Doing that, would add additional newlines to the compiled tempalte in place of the +			IF and ENDIF statements. These newlines are unwanted (and one is conditional). +			The IF and ENDIF are usually on their own line for ease of reading. + +			This replacement preserves newlines only for statements that aren't the only statement on a line. +			It will NOT preserve newlines at the end of statements in the above examle. +			It will preserve newlines in situations like: + +				<!-- IF condition -->inline content<!-- ENDIF --> + + +		*/ + +		$data = preg_replace('~(?<!^)(<\?php.+(?<!/\*\*/)\?>)$~m', "$1\n", $data); +		$data = str_replace('/**/?>', "?>\n", $data); +		$data = str_replace('?><?php', '', $data); +		return $data; +	} + +	/** +	* Prepends a preamble to compiled template. +	* Currently preamble checks if IN_PHPBB is defined and calls exit() if it is not. +	* +	* @param string $data Compiled template chunk +	* @return string Compiled template chunk with preamble prepended +	*/ +	private function prepend_preamble($data) +	{ +		$data = "<?php if (!defined('IN_PHPBB')) exit;" . ((strncmp($data, '<?php', 5) === 0) ? substr($data, 5) : ' ?>' . $data); +		return $data; +	} + +	/** +	* Callback for replacing matched tokens with PHP code +	* +	* @param array $matches Regular expression matches +	* @return string compiled template code +	*/ +	private function replace($matches) +	{ +		if ($this->in_php && $matches[1] != 'ENDPHP') +		{ +			return ''; +		} + +		if (isset($matches[3])) +		{ +			return $this->compile_var_tags($matches[0]); +		} + +		switch ($matches[1]) +		{ +			case 'BEGIN': +				$this->block_else_level[] = false; +				return '<?php ' . $this->compile_tag_block($matches[2]) . ' ?>'; +			break; + +			case 'BEGINELSE': +				$this->block_else_level[sizeof($this->block_else_level) - 1] = true; +				return '<?php }} else { ?>'; +			break; + +			case 'END': +				array_pop($this->block_names); +				return '<?php ' . ((array_pop($this->block_else_level)) ? '}' : '}}') . ' ?>'; +			break; + +			case 'IF': +				return '<?php ' . $this->compile_tag_if($matches[2], false) . ' ?>'; +			break; + +			case 'ELSE': +				return '<?php } else { ?>'; +			break; + +			case 'ELSEIF': +				return '<?php ' . $this->compile_tag_if($matches[2], true) . ' ?>'; +			break; + +			case 'ENDIF': +				return '<?php } ?>'; +			break; + +			case 'DEFINE': +				return '<?php ' . $this->compile_tag_define($matches[2], true) . ' ?>'; +			break; + +			case 'UNDEFINE': +				return '<?php ' . $this->compile_tag_define($matches[2], false) . ' ?>'; +			break; + +			case 'INCLUDE': +				return '<?php ' . $this->compile_tag_include($matches[2]) . ' ?>'; +			break; + +			case 'INCLUDEPHP': +				return ($this->allow_php) ? '<?php ' . $this->compile_tag_include_php($matches[2]) . ' ?>' : ''; +			break; + +			case 'INCLUDEJS': +				return '<?php ' . $this->compile_tag_include_js($matches[2]) . ' ?>'; +			break; + +			case 'PHP': +				if ($this->allow_php) +				{ +					$this->in_php = true; +					return '<?php '; +				} +				return '<!-- PHP -->'; +			break; + +			case 'ENDPHP': +				if ($this->allow_php) +				{ +					$this->in_php = false; +					return ' ?>'; +				} +				return '<!-- ENDPHP -->'; +			break; + +			default: +				return $matches[0]; +			break; + +		} +		return ''; +	} + +	/** +	* Convert template variables into PHP varrefs +	* +	* @param string $text_blocks Variable reference in source template +	* @param bool $is_expr Returns whether the source was an expression type variable (i.e. S_FIRST_ROW) +	* @return string PHP variable name +	*/ +	private function get_varref($text_blocks, &$is_expr) +	{ +		// change template varrefs into PHP varrefs +		$varrefs = array(); + +		// This one will handle varrefs WITH namespaces +		preg_match_all('#\{((?:' . self::REGEX_NS . '\.)+)(\$)?(' . self::REGEX_VAR . ')\}#', $text_blocks, $varrefs, PREG_SET_ORDER); + +		foreach ($varrefs as $var_val) +		{ +			$namespace = $var_val[1]; +			$varname = $var_val[3]; +			$new = $this->generate_block_varref($namespace, $varname, $is_expr, $var_val[2]); + +			$text_blocks = str_replace($var_val[0], $new, $text_blocks); +		} + +		// Language variables cannot be reduced to a single varref, so they must be skipped +		// These two replacements would break language variables, so we can only run them on non-language types +		if (strpos($text_blocks, '{L_') === false && strpos($text_blocks, '{LA_') === false) +		{ +			// This will handle the remaining root-level varrefs +			$text_blocks = preg_replace('#\{(' . self::REGEX_VAR . ')\}#', "\$_rootref['\\1']", $text_blocks); +			$text_blocks = preg_replace('#\{\$(' . self::REGEX_VAR . ')\}#', "\$_tpldata['DEFINE']['.']['\\1']", $text_blocks); +		} + +		return $text_blocks; +	} + +	/** +	* Compile variables +	* +	* @param string $text_blocks Variable reference in source template +	* @return string compiled template code +	*/ +	private function compile_var_tags(&$text_blocks) +	{ +		$text_blocks = $this->get_varref($text_blocks, $is_expr); +		$lang_replaced = $this->compile_language_tags($text_blocks); + +		if(!$lang_replaced) +		{ +			$text_blocks = '<?php echo ' . ($is_expr ? "$text_blocks" : "(isset($text_blocks)) ? $text_blocks : ''") . '; /**/?>'; +		} + +		return $text_blocks; +	} + +	/** +	* Handles special language tags L_ and LA_ +	* +	* @param string $text_blocks Variable reference in source template +	* @return bool Whether a replacement occurred or not +	*/ +	private function compile_language_tags(&$text_blocks) +	{ +		$replacements = 0; + +		// transform vars prefixed by L_ into their language variable pendant if nothing is set within the tpldata array +		if (strpos($text_blocks, '{L_') !== false) +		{ +			$text_blocks = preg_replace('#\{L_(' . self::REGEX_VAR_SUFFIX . ')\}#', "<?php echo ((isset(\$_rootref['L_\\1'])) ? \$_rootref['L_\\1'] : ((isset(\$_lang['\\1'])) ? \$_lang['\\1'] : '{ \\1 }')); /**/?>", $text_blocks, -1, $replacements); +			return (bool) $replacements; +		} + +		// Handle addslashed language variables prefixed with LA_ +		// If a template variable already exist, it will be used in favor of it... +		if (strpos($text_blocks, '{LA_') !== false) +		{ +			$text_blocks = preg_replace('#\{LA_(' . self::REGEX_VAR_SUFFIX . '+)\}#', "<?php echo ((isset(\$_rootref['LA_\\1'])) ? \$_rootref['LA_\\1'] : ((isset(\$_rootref['L_\\1'])) ? addslashes(\$_rootref['L_\\1']) : ((isset(\$_lang['\\1'])) ? addslashes(\$_lang['\\1']) : '{ \\1 }'))); /**/?>", $text_blocks, -1, $replacements); +			return (bool) $replacements; +		} + +		return false; +	} + +	/** +	* Compile blocks +	* +	* @param string $tag_args Block contents in source template +	* @return string compiled template code +	*/ +	private function compile_tag_block($tag_args) +	{ +		$no_nesting = false; + +		// Is the designer wanting to call another loop in a loop? +		// <!-- BEGIN loop --> +		// <!-- BEGIN !loop2 --> +		// <!-- END !loop2 --> +		// <!-- END loop --> +		// 'loop2' is actually on the same nesting level as 'loop' you assign +		// variables to it with template->assign_block_vars('loop2', array(...)) +		if (strpos($tag_args, '!') === 0) +		{ +			// Count the number if ! occurrences (not allowed in vars) +			$no_nesting = substr_count($tag_args, '!'); +			$tag_args = substr($tag_args, $no_nesting); +		} + +		// Allow for control of looping (indexes start from zero): +		// foo(2)    : Will start the loop on the 3rd entry +		// foo(-2)   : Will start the loop two entries from the end +		// foo(3,4)  : Will start the loop on the fourth entry and end it on the fifth +		// foo(3,-4) : Will start the loop on the fourth entry and end it four from last +		$match = array(); + +		if (preg_match('#^([^()]*)\(([\-\d]+)(?:,([\-\d]+))?\)$#', $tag_args, $match)) +		{ +			$tag_args = $match[1]; + +			if ($match[2] < 0) +			{ +				$loop_start = '($_' . $tag_args . '_count ' . $match[2] . ' < 0 ? 0 : $_' . $tag_args . '_count ' . $match[2] . ')'; +			} +			else +			{ +				$loop_start = '($_' . $tag_args . '_count < ' . $match[2] . ' ? $_' . $tag_args . '_count : ' . $match[2] . ')'; +			} + +			if (!isset($match[3]) || strlen($match[3]) < 1 || $match[3] == -1) +			{ +				$loop_end = '$_' . $tag_args . '_count'; +			} +			else if ($match[3] >= 0) +			{ +				$loop_end = '(' . ($match[3] + 1) . ' > $_' . $tag_args . '_count ? $_' . $tag_args . '_count : ' . ($match[3] + 1) . ')'; +			} +			else //if ($match[3] < -1) +			{ +				$loop_end = '$_' . $tag_args . '_count' . ($match[3] + 1); +			} +		} +		else +		{ +			$loop_start = 0; +			$loop_end = '$_' . $tag_args . '_count'; +		} + +		$tag_template_php = ''; +		array_push($this->block_names, $tag_args); + +		if ($no_nesting !== false) +		{ +			// We need to implode $no_nesting times from the end... +			$block = array_slice($this->block_names, -$no_nesting); +		} +		else +		{ +			$block = $this->block_names; +		} + +		if (sizeof($block) < 2) +		{ +			// Block is not nested. +			$tag_template_php = '$_' . $tag_args . "_count = (isset(\$_tpldata['$tag_args'])) ? sizeof(\$_tpldata['$tag_args']) : 0;"; +			$varref = "\$_tpldata['$tag_args']"; +		} +		else +		{ +			// This block is nested. +			// Generate a namespace string for this block. +			$namespace = implode('.', $block); + +			// Get a reference to the data array for this block that depends on the +			// current indices of all parent blocks. +			$varref = $this->generate_block_data_ref($namespace, false); + +			// Create the for loop code to iterate over this block. +			$tag_template_php = '$_' . $tag_args . '_count = (isset(' . $varref . ')) ? sizeof(' . $varref . ') : 0;'; +		} + +		$tag_template_php .= 'if ($_' . $tag_args . '_count) {'; + +		/** +		* The following uses foreach for iteration instead of a for loop, foreach is faster but requires PHP to make a copy of the contents of the array which uses more memory +		* <code> +		*	if (!$offset) +		*	{ +		*		$tag_template_php .= 'foreach (' . $varref . ' as $_' . $tag_args . '_i => $_' . $tag_args . '_val){'; +		*	} +		* </code> +		*/ + +		$tag_template_php .= 'for ($_' . $tag_args . '_i = ' . $loop_start . '; $_' . $tag_args . '_i < ' . $loop_end . '; ++$_' . $tag_args . '_i){'; +		$tag_template_php .= '$_' . $tag_args . '_val = &' . $varref . '[$_' . $tag_args . '_i];'; + +		return $tag_template_php; +	} + +	/** +	* Compile a general expression - much of this is from Smarty with +	* some adaptions for our block level methods +	* +	* @param string $tag_args Expression (tag arguments) in source template +	* @return string compiled template code +	*/ +	private function compile_expression($tag_args) +	{ +		$match = array(); +		preg_match_all('/(?: +			"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"         | +			\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'     | +			[(),]                                  | +			[^\s(),]+)/x', $tag_args, $match); + +		$tokens = $match[0]; +		$is_arg_stack = array(); + +		for ($i = 0, $size = sizeof($tokens); $i < $size; $i++) +		{ +			$token = &$tokens[$i]; + +			switch ($token) +			{ +				case '!==': +				case '===': +				case '<<': +				case '>>': +				case '|': +				case '^': +				case '&': +				case '~': +				case ')': +				case ',': +				case '+': +				case '-': +				case '*': +				case '/': +				case '@': +				break; + +				case '==': +				case 'eq': +					$token = '=='; +				break; + +				case '!=': +				case '<>': +				case 'ne': +				case 'neq': +					$token = '!='; +				break; + +				case '<': +				case 'lt': +					$token = '<'; +				break; + +				case '<=': +				case 'le': +				case 'lte': +					$token = '<='; +				break; + +				case '>': +				case 'gt': +					$token = '>'; +				break; + +				case '>=': +				case 'ge': +				case 'gte': +					$token = '>='; +				break; + +				case '&&': +				case 'and': +					$token = '&&'; +				break; + +				case '||': +				case 'or': +					$token = '||'; +				break; + +				case '!': +				case 'not': +					$token = '!'; +				break; + +				case '%': +				case 'mod': +					$token = '%'; +				break; + +				case '(': +					array_push($is_arg_stack, $i); +				break; + +				case 'is': +					$is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1; +					$is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start)); + +					$new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); + +					array_splice($tokens, $is_arg_start, sizeof($tokens), $new_tokens); + +					$i = $is_arg_start; + +				// no break + +				default: +					$varrefs = array(); +					if (preg_match('#^((?:' . self::REGEX_NS . '\.)+)?(\$)?(?=[A-Z])([A-Z0-9\-_]+)#s', $token, $varrefs)) +					{ +						if (!empty($varrefs[1])) +						{ +							$namespace = substr($varrefs[1], 0, -1); +							$dot_pos = strrchr($namespace, '.'); +							if ($dot_pos !== false) +							{ +								$namespace = substr($dot_pos, 1); +							} + +							// S_ROW_COUNT is deceptive, it returns the current row number not the number of rows +							// hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM +							switch ($varrefs[3]) +							{ +								case 'S_ROW_NUM': +								case 'S_ROW_COUNT': +									$token = "\$_${namespace}_i"; +								break; + +								case 'S_NUM_ROWS': +									$token = "\$_${namespace}_count"; +								break; + +								case 'S_FIRST_ROW': +									$token = "(\$_${namespace}_i == 0)"; +								break; + +								case 'S_LAST_ROW': +									$token = "(\$_${namespace}_i == \$_${namespace}_count - 1)"; +								break; + +								case 'S_BLOCK_NAME': +									$token = "'$namespace'"; +								break; + +								default: +									$token = $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[2]) . '[\'' . $varrefs[3] . '\']'; +									$token = '(isset(' . $token . ') ? ' . $token . ' : null)'; +								break; +							} +						} +						else +						{ +							$token = ($varrefs[2]) ? '$_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[3] . '\']' : '$_rootref[\'' . $varrefs[3] . '\']'; +							$token = '(isset(' . $token . ') ? ' . $token . ' : null)'; +						} + +					} +					else if (preg_match('#^\.((?:' . self::REGEX_NS . '\.?)+)$#s', $token, $varrefs)) +					{ +						// Allow checking if loops are set with .loopname +						// It is also possible to check the loop count by doing <!-- IF .loopname > 1 --> for example +						$blocks = explode('.', $varrefs[1]); + +						// If the block is nested, we have a reference that we can grab. +						// If the block is not nested, we just go and grab the block from _tpldata +						if (sizeof($blocks) > 1) +						{ +							$block = array_pop($blocks); +							$namespace = implode('.', $blocks); +							$varref = $this->generate_block_data_ref($namespace, true); + +							// Add the block reference for the last child. +							$varref .= "['" . $block . "']"; +						} +						else +						{ +							$varref = '$_tpldata'; + +							// Add the block reference for the last child. +							$varref .= "['" . $blocks[0] . "']"; +						} +						$token = "(isset($varref) ? sizeof($varref) : 0)"; +					} + +				break; +			} +		} + +		return $tokens; +	} + +	/** +	* Compile IF tags +	* +	* @param string $tag_args Expression given with IF in source template +	* @param bool $elseif True if compiling an IF tag, false if compiling an ELSEIF tag +	* @return string compiled template code +	*/ +	private function compile_tag_if($tag_args, $elseif) +	{ +		$tokens = $this->compile_expression($tag_args); + +		$tpl = ($elseif) ? '} else if (' : 'if ('; + +		$tpl .= implode(' ', $tokens); +		$tpl .= ') { '; + +		return $tpl; +	} + +	/** +	* Compile DEFINE tags +	* +	* @param string $tag_args Expression given with DEFINE in source template +	* @param bool $op True if compiling a DEFINE tag, false if compiling an UNDEFINE tag +	* @return string compiled template code +	*/ +	private function compile_tag_define($tag_args, $op) +	{ +		$match = array(); +		preg_match('#^((?:' . self::REGEX_NS . '\.)+)?\$(?=[A-Z])([A-Z0-9_\-]*)(?: = (.*?))?$#', $tag_args, $match); + +		if (empty($match[2]) || (!isset($match[3]) && $op)) +		{ +			return ''; +		} + +		if (!$op) +		{ +			return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ');'; +		} + +		$parsed_statement = implode(' ', $this->compile_expression($match[3])); + +		return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ' = ' . $parsed_statement . ';'; +	} + +	/** +	* Compile INCLUDE tag +	* +	* @param string $tag_args Expression given with INCLUDE in source template +	* @return string compiled template code +	*/ +	private function compile_tag_include($tag_args) +	{ +		// Process dynamic includes +		if ($tag_args[0] == '{') +		{ +			$var = $this->get_varref($tag_args, $is_expr); + +			// Make sure someone didn't try to include S_FIRST_ROW or similar +			if (!$is_expr) +			{ +				return "if (isset($var)) { \$_template->_tpl_include($var); }"; +			} +		} + +		return "\$_template->_tpl_include('$tag_args');"; +	} + +	/** +	* Compile INCLUDE_PHP tag +	* +	* @param string $tag_args Expression given with INCLUDEPHP in source template +	* @return string compiled template code +	*/ +	private function compile_tag_include_php($tag_args) +	{ +		return "\$_template->_php_include('$tag_args');"; +	} + +	/** +	* parse expression +	* This is from Smarty +	*/ +	private function _parse_is_expr($is_arg, $tokens) +	{ +		$expr_end = 0; +		$negate_expr = false; + +		if (($first_token = array_shift($tokens)) == 'not') +		{ +			$negate_expr = true; +			$expr_type = array_shift($tokens); +		} +		else +		{ +			$expr_type = $first_token; +		} + +		switch ($expr_type) +		{ +			case 'even': +				if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') +				{ +					$expr_end++; +					$expr_arg = $tokens[$expr_end++]; +					$expr = "!(($is_arg / $expr_arg) & 1)"; +				} +				else +				{ +					$expr = "!($is_arg & 1)"; +				} +			break; + +			case 'odd': +				if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') +				{ +					$expr_end++; +					$expr_arg = $tokens[$expr_end++]; +					$expr = "(($is_arg / $expr_arg) & 1)"; +				} +				else +				{ +					$expr = "($is_arg & 1)"; +				} +			break; + +			case 'div': +				if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') +				{ +					$expr_end++; +					$expr_arg = $tokens[$expr_end++]; +					$expr = "!($is_arg % $expr_arg)"; +				} +			break; +		} + +		if ($negate_expr) +		{ +			if ($expr[0] == '!') +			{ +				// Negated expression, de-negate it. +				$expr = substr($expr, 1); +			} +			else +			{ +				$expr = "!($expr)"; +			} +		} + +		array_splice($tokens, 0, $expr_end, $expr); + +		return $tokens; +	} + +	/** +	* Compile INCLUDEJS tag +	* +	* @param string $tag_args Expression given with INCLUDEJS in source template +	* @return string compiled template code +	*/ +	private function compile_tag_include_js($tag_args) +	{ +		// Process dynamic includes +		if ($tag_args[0] == '{') +		{ +			$var = $this->get_varref($tag_args, $is_expr); +			if (!$is_expr) +			{ +				return " \$_template->_js_include($var, true);"; +			} +			return ''; +		} + +		// Locate file +		$filename = $this->locator->get_first_file_location(array($tag_args), false, true); + +		if ($filename === false) +		{ +			// File does not exist, find it during run time +			return ' $_template->_js_include(\'' . addslashes($tag_args) . '\', true); '; +		} + +		if (substr($filename, 0, strlen($this->phpbb_root_path)) != $this->phpbb_root_path) +		{ +			// Absolute path, include as is +			return ' $_template->_js_include(\'' . addslashes($filename) . '\', false, false); '; +		} + +		// Relative path, remove root path from it +		$filename = substr($filename, strlen($this->phpbb_root_path)); +		return ' $_template->_js_include(\'' . addslashes($filename) . '\', false, true); '; +	} + +	/** +	* Generates a reference to the given variable inside the given (possibly nested) +	* block namespace. This is a string of the form: +	* ' . $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . ' +	* It's ready to be inserted into an "echo" line in one of the templates. +	* +	* @param string $namespace Namespace to access (expects a trailing "." on the namespace) +	* @param string $varname Variable name to use +	* @param bool $expr Returns whether the source was an expression type +	* @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable +	* @return string Code to access variable or echo it if $echo is true +	*/ +	private function generate_block_varref($namespace, $varname, &$expr, $defop = false) +	{ +		// Strip the trailing period. +		$namespace = substr($namespace, 0, -1); + +		if (($pos = strrpos($namespace, '.')) !== false) +		{ +			$local_namespace = substr($namespace, $pos + 1); +		} +		else +		{ +			$local_namespace = $namespace; +		} + +		$expr = true; + +		// S_ROW_COUNT is deceptive, it returns the current row number now the number of rows +		// hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM +		switch ($varname) +		{ +			case 'S_ROW_NUM': +			case 'S_ROW_COUNT': +				$varref = "\$_${local_namespace}_i"; +			break; + +			case 'S_NUM_ROWS': +				$varref = "\$_${local_namespace}_count"; +			break; + +			case 'S_FIRST_ROW': +				$varref = "(\$_${local_namespace}_i == 0)"; +			break; + +			case 'S_LAST_ROW': +				$varref = "(\$_${local_namespace}_i == \$_${local_namespace}_count - 1)"; +			break; + +			case 'S_BLOCK_NAME': +				$varref = "'$local_namespace'"; +			break; + +			default: +				// Get a reference to the data block for this namespace. +				$varref = $this->generate_block_data_ref($namespace, true, $defop); +				// Prepend the necessary code to stick this in an echo line. + +				// Append the variable reference. +				$varref .= "['$varname']"; + +				$expr = false; +			break; +		} +		// @todo Test the !$expr more + +		return $varref; +	} + +	/** +	* Generates a reference to the array of data values for the given +	* (possibly nested) block namespace. This is a string of the form: +	* $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN'] +	* +	* @param string $blockname Block to access (does not expect a trailing "." on the blockname) +	* @param bool $include_last_iterator If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above. +	* @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable +	* @return string Code to access variable +	*/ +	private function generate_block_data_ref($blockname, $include_last_iterator, $defop = false) +	{ +		// Get an array of the blocks involved. +		$blocks = explode('.', $blockname); +		$blockcount = sizeof($blocks) - 1; + +		// DEFINE is not an element of any referenced variable, we must use _tpldata to access it +		if ($defop) +		{ +			$varref = '$_tpldata[\'DEFINE\']'; +			// Build up the string with everything but the last child. +			for ($i = 0; $i < $blockcount; $i++) +			{ +				$varref .= "['" . $blocks[$i] . "'][\$_" . $blocks[$i] . '_i]'; +			} +			// Add the block reference for the last child. +			$varref .= "['" . $blocks[$blockcount] . "']"; +			// Add the iterator for the last child if requried. +			if ($include_last_iterator) +			{ +				$varref .= '[$_' . $blocks[$blockcount] . '_i]'; +			} +			return $varref; +		} +		else if ($include_last_iterator) +		{ +			return '$_'. $blocks[$blockcount] . '_val'; +		} +		else +		{ +			return '$_'. $blocks[$blockcount - 1] . '_val[\''. $blocks[$blockcount]. '\']'; +		} +	} +} diff --git a/phpBB/includes/template/locator.php b/phpBB/includes/template/locator.php new file mode 100644 index 0000000000..01c79eec4e --- /dev/null +++ b/phpBB/includes/template/locator.php @@ -0,0 +1,121 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + + +/** +* Resource locator interface. +* +* Objects implementing this interface maintain mapping from template handles +* to source template file paths and locate templates. +* +* Locates style files. +* +* Resource locator is aware of styles tree, and can return actual +* filesystem paths (i.e., the "child" style or the "parent" styles) +* depending on what files exist. +* +* Root paths stored in locator are paths to style directories. Templates are +* stored in subdirectory that $template_path points to. +* +* @package phpBB3 +*/ +interface phpbb_template_locator +{ +	/** +	* Sets the template filenames for handles. $filename_array +	* should be a hash of handle => filename pairs. +	* +	* @param array $filname_array Should be a hash of handle => filename pairs. +	*/ +	public function set_filenames(array $filename_array); + +	/** +	* Determines the filename for a template handle. +	* +	* The filename comes from array used in a set_filenames call, +	* which should have been performed prior to invoking this function. +	* Return value is a file basename (without path). +	* +	* @param $handle string Template handle +	* @return string Filename corresponding to the template handle +	*/ +	public function get_filename_for_handle($handle); + +	/** +	* Determines the source file path for a template handle without +	* regard for styles tree. +	* +	* This function returns the path in "primary" style directory +	* corresponding to the given template handle. That path may or +	* may not actually exist on the filesystem. Because this function +	* does not perform stat calls to determine whether the path it +	* returns actually exists, it is faster than get_source_file_for_handle. +	* +	* Use get_source_file_for_handle to obtain the actual path that is +	* guaranteed to exist (which might come from the parent style  +	* directory if primary style has parent styles). +	* +	* This function will trigger an error if the handle was never +	* associated with a template file via set_filenames. +	* +	* @param $handle string Template handle +	* @return string Path to source file path in primary style directory +	*/ +	public function get_virtual_source_file_for_handle($handle); + +	/** +	* Determines the source file path for a template handle, accounting +	* for styles tree and verifying that the path exists. +	* +	* This function returns the actual path that may be compiled for +	* the specified template handle. It will trigger an error if +	* the template handle was never associated with a template path +	* via set_filenames or if the template file does not exist on the +	* filesystem. +	* +	* Use get_virtual_source_file_for_handle to just resolve a template +	* handle to a path without any filesystem or styles tree checks. +	* +	* @param string $handle Template handle (i.e. "friendly" template name) +	* @param bool $find_all If true, each root path will be checked and function +	*				will return array of files instead of string and will not +	*				trigger a error if template does not exist +	* @return string Source file path +	*/ +	public function get_source_file_for_handle($handle, $find_all = false); + +	/** +	* Locates source file path, accounting for styles tree and verifying that +	* the path exists. +	* +	* Unlike previous functions, this function works without template handle +	* and it can search for more than one file. If more than one file name is +	* specified, it will return location of file that it finds first. +	* +	* @param array $files List of files to locate. +	* @param bool $return_default Determines what to return if file does not +	*				exist. If true, function will return location where file is +	*				supposed to be. If false, function will return false. +	* @param bool $return_full_path If true, function will return full path +	*				to file. If false, function will return file name. This +	*				parameter can be used to check which one of set of files +	*				is available. +	* @return string or boolean Source file path if file exists or $return_default is +	*				true. False if file does not exist and $return_default is false +	*/ +	public function get_first_file_location($files, $return_default = false, $return_full_path = true); +} diff --git a/phpBB/includes/template/renderer.php b/phpBB/includes/template/renderer.php new file mode 100644 index 0000000000..30e234a733 --- /dev/null +++ b/phpBB/includes/template/renderer.php @@ -0,0 +1,35 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Template renderer interface. +* +* Objects implementing this interface encapsulate a means of displaying +* a template. +* +* @package phpBB3 +*/ +interface phpbb_template_renderer +{ +	/** +	* Displays the template managed by this renderer. +	* +	* @param phpbb_template_context $context Template context to use +	* @param array $lang Language entries to use +	*/ +	public function render($context, $lang); +} diff --git a/phpBB/includes/template/renderer_eval.php b/phpBB/includes/template/renderer_eval.php new file mode 100644 index 0000000000..f8e4cb7b10 --- /dev/null +++ b/phpBB/includes/template/renderer_eval.php @@ -0,0 +1,60 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Template renderer that stores compiled template's php code and +* displays it via eval. +* +* @package phpBB3 +*/ +class phpbb_template_renderer_eval implements phpbb_template_renderer +{ +	/** +	* Template code to be eval'ed. +	*/ +	private $code; + +	/** +	* Constructor. Stores provided code for future evaluation. +	* Template includes are delegated to template object $template. +	* +	* @param string $code php code of the template +	* @param phpbb_template $template template object +	*/ +	public function __construct($code, $template) +	{ +		$this->code = $code; +		$this->template = $template; +	} + +	/** +	* Displays the template managed by this renderer by eval'ing php code +	* of the template. +	* +	* @param phpbb_template_context $context Template context to use +	* @param array $lang Language entries to use +	*/ +	public function render($context, $lang) +	{ +		$_template = $this->template; +		$_tpldata = &$context->get_data_ref(); +		$_rootref = &$context->get_root_ref(); +		$_lang = $lang; + +		eval(' ?>' . $this->code . '<?php '); +	} +} diff --git a/phpBB/includes/template/renderer_include.php b/phpBB/includes/template/renderer_include.php new file mode 100644 index 0000000000..f5c9026abf --- /dev/null +++ b/phpBB/includes/template/renderer_include.php @@ -0,0 +1,60 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + + +/** +* Template renderer that stores path to php file with template code +* and displays it by including the file. +* +* @package phpBB3 +*/ +class phpbb_template_renderer_include implements phpbb_template_renderer +{ +	/** +	* Template path to be included. +	*/ +	private $path; + +	/** +	* Constructor. Stores path to the template for future inclusion. +	* Template includes are delegated to template object $template. +	* +	* @param string $path path to the template +	*/ +	public function __construct($path, $template) +	{ +		$this->path = $path; +		$this->template = $template; +	} + +	/** +	* Displays the template managed by this renderer by including +	* the php file containing the template. +	* +	* @param phpbb_template_context $context Template context to use +	* @param array $lang Language entries to use +	*/ +	public function render($context, $lang) +	{ +		$_template = $this->template; +		$_tpldata = &$context->get_data_ref(); +		$_rootref = &$context->get_root_ref(); +		$_lang = $lang; + +		include($this->path); +	} +} diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php new file mode 100644 index 0000000000..5d3ce4c82b --- /dev/null +++ b/phpBB/includes/template/template.php @@ -0,0 +1,555 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* @todo +* IMG_ for image substitution? +* {IMG_[key]:[alt]:[type]} +* {IMG_ICON_CONTACT:CONTACT:full} -> $user->img('icon_contact', 'CONTACT', 'full'); +* +* More in-depth... +* yadayada +*/ + +/** +* Base Template class. +* @package phpBB3 +*/ +class phpbb_template +{ +	/** +	* Template context. +	* Stores template data used during template rendering. +	* @var phpbb_template_context +	*/ +	private $context; + +	/** +	* Path of the cache directory for the template +	* @var string +	*/ +	public $cachepath = ''; + +	/** +	* phpBB root path +	* @var string +	*/ +	private $phpbb_root_path; + +	/** +	* PHP file extension +	* @var string +	*/ +	private $php_ext; + +	/** +	* phpBB config instance +	* @var phpbb_config +	*/ +	private $config; + +	/** +	* Current user +	* @var phpbb_user +	*/ +	private $user; + +	/** +	* Template locator +	* @var phpbb_template_locator +	*/ +	private $locator; + +	/** +	* Location of templates directory within style directories +	* @var string +	*/ +	public $template_path = 'template/'; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path phpBB root path +	* @param user $user current user +	* @param phpbb_template_locator $locator template locator +	* @param phpbb_template_context $context template context +	*/ +	public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->config = $config; +		$this->user = $user; +		$this->locator = $locator; +		$this->template_path = $this->locator->template_path; +		$this->context = $context; +	} + +	/** +	* Sets the template filenames for handles. +	* +	* @param array $filname_array Should be a hash of handle => filename pairs. +	*/ +	public function set_filenames(array $filename_array) +	{ +		$this->locator->set_filenames($filename_array); + +		return true; +	} + +	/** +	* Clears all variables and blocks assigned to this template. +	*/ +	public function destroy() +	{ +		$this->context->clear(); +	} + +	/** +	* Reset/empty complete block +	* +	* @param string $blockname Name of block to destroy +	*/ +	public function destroy_block_vars($blockname) +	{ +		$this->context->destroy_block_vars($blockname); +	} + +	/** +	* Display a template for provided handle. +	* +	* The template will be loaded and compiled, if necessary, first. +	* +	* This function calls hooks. +	* +	* @param string $handle Handle to display +	* @return bool True on success, false on failure +	*/ +	public function display($handle) +	{ +		$result = $this->call_hook($handle, __FUNCTION__); +		if ($result !== false) +		{ +			return $result[0]; +		} + +		return $this->load_and_render($handle); +	} + +	/** +	* Loads a template for $handle, compiling it if necessary, and +	* renders the template. +	* +	* @param string $handle Template handle to render +	* @return bool True on success, false on failure +	*/ +	private function load_and_render($handle) +	{ +		$renderer = $this->_tpl_load($handle); + +		if ($renderer) +		{ +			$renderer->render($this->context, $this->get_lang()); +			return true; +		} +		else +		{ +			return false; +		} +	} + +	/** +	* Calls hook if any is defined. +	* +	* @param string $handle Template handle being displayed. +	* @param string $method Method name of the caller. +	*/ +	private function call_hook($handle, $method) +	{ +		global $phpbb_hook; + +		if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) +		{ +			if ($phpbb_hook->hook_return(array(__CLASS__, $method))) +			{ +				$result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); +				return array($result); +			} +		} + +		return false; +	} + +	/** +	* Obtains language array. +	* This is either lang property of $user property, or if +	* it is not set an empty array. +	* @return array language entries +	*/ +	public function get_lang() +	{ +		if (isset($this->user->lang)) +		{ +			$lang = $this->user->lang; +		} +		else +		{ +			$lang = array(); +		} +		return $lang; +	} + +	/** +	* Display the handle and assign the output to a template variable +	* or return the compiled result. +	* +	* @param string $handle Handle to operate on +	* @param string $template_var Template variable to assign compiled handle to +	* @param bool $return_content If true return compiled handle, otherwise assign to $template_var +	* @return bool|string false on failure, otherwise if $return_content is true return string of the compiled handle, otherwise return true +	*/ +	public function assign_display($handle, $template_var = '', $return_content = true) +	{ +		ob_start(); +		$result = $this->display($handle); +		$contents = ob_get_clean(); +		if ($result === false) +		{ +			return false; +		} + +		if ($return_content) +		{ +			return $contents; +		} + +		$this->assign_var($template_var, $contents); + +		return true; +	} + +	/** +	* Obtains a template renderer for a template identified by specified +	* handle. The template renderer can display the template later. +	* +	* Template source will first be compiled into php code. +	* If template cache is writable the compiled php code will be stored +	* on filesystem and template will not be subsequently recompiled. +	* If template cache is not writable template source will be recompiled +	* every time it is needed. DEBUG_EXTRA define and load_tplcompile +	* configuration setting may be used to force templates to be always +	* recompiled. +	* +	* Returns an object implementing phpbb_template_renderer, or null +	* if template loading or compilation failed. Call render() on the +	* renderer to display the template. This will result in template +	* contents sent to the output stream (unless, of course, output +	* buffering is in effect). +	* +	* @param string $handle Handle of the template to load +	* @return phpbb_template_renderer Template renderer object, or null on failure +	* @uses phpbb_template_compile is used to compile template source +	*/ +	private function _tpl_load($handle) +	{ +		$output_file = $this->_compiled_file_for_handle($handle); + +		$recompile = defined('DEBUG_EXTRA') || +			!file_exists($output_file) || +			@filesize($output_file) === 0; + +		if ($recompile || $this->config['load_tplcompile']) +		{ +			// Set only if a recompile or an mtime check are required. +			$source_file = $this->locator->get_source_file_for_handle($handle); + +			if (!$recompile && @filemtime($output_file) < @filemtime($source_file)) +			{ +				$recompile = true; +			} +		} + +		// Recompile page if the original template is newer, otherwise load the compiled version +		if (!$recompile) +		{ +			return new phpbb_template_renderer_include($output_file, $this); +		} + +		$compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path); + +		if ($compile->compile_file_to_file($source_file, $output_file) !== false) +		{ +			$renderer = new phpbb_template_renderer_include($output_file, $this); +		} +		else if (($code = $compile->compile_file($source_file)) !== false) +		{ +			$renderer = new phpbb_template_renderer_eval($code, $this); +		} +		else +		{ +			$renderer = null; +		} + +		return $renderer; +	} + +	/** +	* Determines compiled file path for handle $handle. +	* +	* @param string $handle Template handle (i.e. "friendly" template name) +	* @return string Compiled file path +	*/ +	private function _compiled_file_for_handle($handle) +	{ +		$source_file = $this->locator->get_filename_for_handle($handle); +		$compiled_file = $this->cachepath . str_replace('/', '.', $source_file) . '.' . $this->php_ext; +		return $compiled_file; +	} + +	/** +	* Assign key variable pairs from an array +	* +	* @param array $vararray A hash of variable name => value pairs +	*/ +	public function assign_vars(array $vararray) +	{ +		foreach ($vararray as $key => $val) +		{ +			$this->assign_var($key, $val); +		} +	} + +	/** +	* Assign a single scalar value to a single key. +	* +	* Value can be a string, an integer or a boolean. +	* +	* @param string $varname Variable name +	* @param string $varval Value to assign to variable +	*/ +	public function assign_var($varname, $varval) +	{ +		$this->context->assign_var($varname, $varval); +	} + +	/** +	* Append text to the string value stored in a key. +	* +	* Text is appended using the string concatenation operator (.). +	* +	* @param string $varname Variable name +	* @param string $varval Value to append to variable +	*/ +	public function append_var($varname, $varval) +	{ +		$this->context->append_var($varname, $varval); +	} + +	// Docstring is copied from phpbb_template_context method with the same name. +	/** +	* Assign key variable pairs from an array to a specified block +	* @param string $blockname Name of block to assign $vararray to +	* @param array $vararray A hash of variable name => value pairs +	*/ +	public function assign_block_vars($blockname, array $vararray) +	{ +		return $this->context->assign_block_vars($blockname, $vararray); +	} + +	// Docstring is copied from phpbb_template_context method with the same name. +	/** +	* Change already assigned key variable pair (one-dimensional - single loop entry) +	* +	* An example of how to use this function: +	* {@example alter_block_array.php} +	* +	* @param	string	$blockname	the blockname, for example 'loop' +	* @param	array	$vararray	the var array to insert/add or merge +	* @param	mixed	$key		Key to search for +	* +	* array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] +	* +	* int: Position [the position to change or insert at directly given] +	* +	* If key is false the position is set to 0 +	* If key is true the position is set to the last entry +	* +	* @param	string	$mode		Mode to execute (valid modes are 'insert' and 'change') +	* +	*	If insert, the vararray is inserted at the given position (position counting from zero). +	*	If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). +	* +	* Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) +	* and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) +	* +	* @return bool false on error, true on success +	*/ +	public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') +	{ +		return $this->context->alter_block_array($blockname, $vararray, $key, $mode); +	} + +	/** +	* Include a separate template. +	* +	* This function is marked public due to the way the template +	* implementation uses it. It is actually an implementation function +	* and should not be considered part of template class's public API. +	* +	* @param string $filename Template filename to include +	* @param bool $include True to include the file, false to just load it +	* @uses template_compile is used to compile uncached templates +	*/ +	public function _tpl_include($filename, $include = true) +	{ +		$this->locator->set_filenames(array($filename => $filename)); + +		if (!$this->load_and_render($filename)) +		{ +			// trigger_error cannot be used here, as the output already started +			echo 'template->_tpl_include(): Failed including ' . htmlspecialchars($handle) . "\n"; +		} +	} + +	/** +	* Include a PHP file. +	* +	* If a relative path is passed in $filename, it is considered to be +	* relative to board root ($phpbb_root_path). Absolute paths are +	* also allowed. +	* +	* This function is marked public due to the way the template +	* implementation uses it. It is actually an implementation function +	* and should not be considered part of template class's public API. +	* +	* @param string $filename Path to PHP file to include +	*/ +	public function _php_include($filename) +	{ +		if (phpbb_is_absolute($filename)) +		{ +			$file = $filename; +		} +		else +		{ +			$file = $this->phpbb_root_path . $filename; +		} + +		if (!file_exists($file)) +		{ +			// trigger_error cannot be used here, as the output already started +			echo 'template->_php_include(): File ' . htmlspecialchars($file) . " does not exist\n"; +			return; +		} +		include($file); +	} + +	/** +	* Obtains filesystem path for a template file. +	* +	* The simplest use is specifying a single template file as a string +	* in the first argument. This template file should be a basename +	* of a template file in the selected style, or its parent styles +	* if template inheritance is being utilized. +	* +	* Note: "selected style" is whatever style the style resource locator +	* is configured for. +	* +	* The return value then will be a path, relative to the current +	* directory or absolute, to the template file in the selected style +	* or its closest parent. +	* +	* If the selected style does not have the template file being searched, +	* (and if inheritance is involved, none of the parents have it either), +	* false will be returned. +	* +	* Specifying true for $return_default will cause the function to +	* return the first path which was checked for existence in the event +	* that the template file was not found, instead of false. +	* This is the path in the selected style itself, not any of its +	* parents. +	* +	* $files can be given an array of templates instead of a single +	* template. When given an array, the function will try to resolve +	* each template in the array to a path, and will return the first +	* path that exists, or false if none exist. +	* +	* If $return_full_path is false, then instead of returning a usable +	* path (when the template is found) only the template's basename +	* will be returned. This can be used to check which of the templates +	* specified in $files exists, provided different file names are +	* used for different templates. +	* +	* @param string or array $files List of templates to locate. If there is only +	*				one template, $files can be a string to make code easier to read. +	* @param bool $return_default Determines what to return if template does not +	*				exist. If true, function will return location where template is +	*				supposed to be. If false, function will return false. +	* @param bool $return_full_path If true, function will return full path +	*				to template. If false, function will return template file name. +	*				This parameter can be used to check which one of set of template +	*				files is available. +	* @return string or boolean Source template path if template exists or $return_default is +	*				true. False if template does not exist and $return_default is false +	*/ +	public function locate($files, $return_default = false, $return_full_path = true) +	{ +		// add template path prefix +		$templates = array(); +		if (is_string($files)) +		{ +			$templates[] = $this->template_path . $files; +		} +		else +		{ +			foreach ($files as $file) +			{ +				$templates[] = $this->template_path . $file; +			} +		} + +		// use resource locator to find files +		return $this->locator->get_first_file_location($templates, $return_default, $return_full_path); +	} + +	/** +	* Include JS file +	* +	* @param string $file file name +	* @param bool $locate True if file needs to be located +	* @param bool $relative True if path is relative to phpBB root directory. Ignored if $locate == true +	*/ +	public function _js_include($file, $locate = false, $relative = false) +	{ +		// Locate file +		if ($locate) +		{ +			$file = $this->locator->get_first_file_location(array($file), true, true); +		} +		else if ($relative) +		{ +			$file = $this->phpbb_root_path . $file; +		} + +		$file .= (strpos($file, '?') === false) ? '?' : '&'; +		$file .= 'assets_version=' . $this->config['assets_version']; + +		// Add HTML code +		$code = '<script src="' . htmlspecialchars($file) . '"></script>'; +		$this->context->append_var('SCRIPTS', $code); +	} +} diff --git a/phpBB/includes/ucp/info/ucp_attachments.php b/phpBB/includes/ucp/info/ucp_attachments.php index f8c21b7068..adc7b92920 100644 --- a/phpBB/includes/ucp/info/ucp_attachments.php +++ b/phpBB/includes/ucp/info/ucp_attachments.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/ucp/info/ucp_groups.php b/phpBB/includes/ucp/info/ucp_groups.php index e4bb882753..b7ffcd0971 100644 --- a/phpBB/includes/ucp/info/ucp_groups.php +++ b/phpBB/includes/ucp/info/ucp_groups.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/ucp/info/ucp_main.php b/phpBB/includes/ucp/info/ucp_main.php index a070f3620c..e40a0cc1c5 100644 --- a/phpBB/includes/ucp/info/ucp_main.php +++ b/phpBB/includes/ucp/info/ucp_main.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/ucp/info/ucp_pm.php b/phpBB/includes/ucp/info/ucp_pm.php index 1b6b438835..02931e9d31 100644 --- a/phpBB/includes/ucp/info/ucp_pm.php +++ b/phpBB/includes/ucp/info/ucp_pm.php @@ -1,9 +1,8 @@  <?php  /**  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/ucp/info/ucp_prefs.php b/phpBB/includes/ucp/info/ucp_prefs.php index 3a73cdbd3d..91fbd7a14c 100644 --- a/phpBB/includes/ucp/info/ucp_prefs.php +++ b/phpBB/includes/ucp/info/ucp_prefs.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/ucp/info/ucp_profile.php b/phpBB/includes/ucp/info/ucp_profile.php index 797458c51b..201216e9fd 100644 --- a/phpBB/includes/ucp/info/ucp_profile.php +++ b/phpBB/includes/ucp/info/ucp_profile.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -21,9 +20,10 @@ class ucp_profile_info  			'version'	=> '1.0.0',  			'modes'		=> array(  				'profile_info'	=> array('title' => 'UCP_PROFILE_PROFILE_INFO', 'auth' => '', 'cat' => array('UCP_PROFILE')), -				'signature'		=> array('title' => 'UCP_PROFILE_SIGNATURE', 'auth' => '', 'cat' => array('UCP_PROFILE')), +				'signature'		=> array('title' => 'UCP_PROFILE_SIGNATURE', 'auth' => 'acl_u_sig', 'cat' => array('UCP_PROFILE')),  				'avatar'		=> array('title' => 'UCP_PROFILE_AVATAR', 'auth' => 'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)', 'cat' => array('UCP_PROFILE')),  				'reg_details'	=> array('title' => 'UCP_PROFILE_REG_DETAILS', 'auth' => '', 'cat' => array('UCP_PROFILE')), +				'autologin_keys'=> array('title' => 'UCP_PROFILE_AUTOLOGIN_KEYS', 'auth' => '', 'cat' => array('UCP_PROFILE')),  			),  		);  	} diff --git a/phpBB/includes/ucp/info/ucp_zebra.php b/phpBB/includes/ucp/info/ucp_zebra.php index 9f4519cf53..db57102aae 100644 --- a/phpBB/includes/ucp/info/ucp_zebra.php +++ b/phpBB/includes/ucp/info/ucp_zebra.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php index 34b0b6d879..a0d0baf10f 100644 --- a/phpBB/includes/ucp/ucp_activate.php +++ b/phpBB/includes/ucp/ucp_activate.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -117,10 +116,7 @@ class ucp_activate  			$messenger->to($user_row['user_email'], $user_row['username']); -			$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -			$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -			$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -			$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +			$messenger->anti_abuse_headers($config, $user);  			$messenger->assign_vars(array(  				'USERNAME'	=> htmlspecialchars_decode($user_row['username'])) diff --git a/phpBB/includes/ucp/ucp_attachments.php b/phpBB/includes/ucp/ucp_attachments.php index 3f99168c25..dc095e7b73 100644 --- a/phpBB/includes/ucp/ucp_attachments.php +++ b/phpBB/includes/ucp/ucp_attachments.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -171,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_confirm.php b/phpBB/includes/ucp/ucp_confirm.php index 45bc8186e0..aafb92d8e4 100644 --- a/phpBB/includes/ucp/ucp_confirm.php +++ b/phpBB/includes/ucp/ucp_confirm.php @@ -2,9 +2,8 @@  /**  *  * @package VC -* @version $Id$  * @copyright (c) 2005 2008 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php index 676c0a8ef0..65ab92e78e 100644 --- a/phpBB/includes/ucp/ucp_groups.php +++ b/phpBB/includes/ucp/ucp_groups.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -194,47 +193,43 @@ class ucp_groups  								if ($group_row[$group_id]['group_type'] == GROUP_FREE)  								{  									group_user_add($group_id, $user->data['user_id']); - -									$email_template = 'group_added';  								}  								else  								{  									group_user_add($group_id, $user->data['user_id'], false, false, false, 0, 1); -									$email_template = 'group_request'; -								} +									include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); +									$messenger = new messenger(); -								include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); -								$messenger = new messenger(); +									$sql = 'SELECT u.username, u.username_clean, u.user_email, u.user_notify_type, u.user_jabber, u.user_lang +										FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . " u +										WHERE ug.user_id = u.user_id +											AND ug.group_leader = 1 +											AND ug.group_id = $group_id"; +									$result = $db->sql_query($sql); -								$sql = 'SELECT u.username, u.username_clean, u.user_email, u.user_notify_type, u.user_jabber, u.user_lang -									FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . ' u -									WHERE ug.user_id = u.user_id -										AND ' . (($group_row[$group_id]['group_type'] == GROUP_FREE) ? "ug.user_id = {$user->data['user_id']}" : 'ug.group_leader = 1') . " -										AND ug.group_id = $group_id"; -								$result = $db->sql_query($sql); +									while ($row = $db->sql_fetchrow($result)) +									{ +										$messenger->template('group_request', $row['user_lang']); -								while ($row = $db->sql_fetchrow($result)) -								{ -									$messenger->template($email_template, $row['user_lang']); +										$messenger->to($row['user_email'], $row['username']); +										$messenger->im($row['user_jabber'], $row['username']); -									$messenger->to($row['user_email'], $row['username']); -									$messenger->im($row['user_jabber'], $row['username']); +										$messenger->assign_vars(array( +											'USERNAME'			=> htmlspecialchars_decode($row['username']), +											'GROUP_NAME'		=> htmlspecialchars_decode($group_row[$group_id]['group_name']), +											'REQUEST_USERNAME'	=> $user->data['username'], -									$messenger->assign_vars(array( -										'USERNAME'			=> htmlspecialchars_decode($row['username']), -										'GROUP_NAME'		=> htmlspecialchars_decode($group_row[$group_id]['group_name']), -										'REQUEST_USERNAME'	=> $user->data['username'], +											'U_PENDING'		=> generate_board_url() . "/ucp.$phpEx?i=groups&mode=manage&action=list&g=$group_id", +											'U_GROUP'		=> generate_board_url() . "/memberlist.$phpEx?mode=group&g=$group_id") +										); -										'U_PENDING'		=> generate_board_url() . "/ucp.$phpEx?i=groups&mode=manage&action=list&g=$group_id", -										'U_GROUP'		=> generate_board_url() . "/memberlist.$phpEx?mode=group&g=$group_id") -									); +										$messenger->send($row['user_notify_type']); +									} +									$db->sql_freeresult($result); -									$messenger->send($row['user_notify_type']); +									$messenger->save_queue();  								} -								$db->sql_freeresult($result); - -								$messenger->save_queue();  								add_log('user', $user->data['user_id'], 'LOG_USER_GROUP_JOIN' . (($group_row[$group_id]['group_type'] == GROUP_FREE) ? '' : '_PENDING'), $group_row[$group_id]['group_name']); @@ -565,7 +560,7 @@ class ucp_groups  								{  									if ($data['width'] > $config['avatar_max_width'] || $data['height'] > $config['avatar_max_height'])  									{ -										$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $data['width'], $data['height']); +										$error[] = phpbb_avatar_error_wrong_size($data['width'], $data['height']);  									}  								} @@ -575,7 +570,7 @@ class ucp_groups  									{  										if ($data['width'] < $config['avatar_min_width'] || $data['height'] < $config['avatar_min_height'])  										{ -											$error[] = sprintf($user->lang['AVATAR_WRONG_SIZE'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], $data['width'], $data['height']); +											$error[] = phpbb_avatar_error_wrong_size($data['width'], $data['height']);  										}  									}  								} @@ -736,7 +731,7 @@ class ucp_groups  							'U_SWATCH'			=> append_sid("{$phpbb_root_path}adm/swatch.$phpEx", 'form=ucp&name=group_colour'),  							'S_UCP_ACTION'		=> $this->u_action . "&action=$action&g=$group_id", -							'L_AVATAR_EXPLAIN'	=> sprintf($user->lang['AVATAR_EXPLAIN'], $config['avatar_max_width'], $config['avatar_max_height'], $config['avatar_filesize'] / 1024), +							'L_AVATAR_EXPLAIN'	=> phpbb_avatar_explanation_string(),  						));  					break; @@ -849,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", @@ -1072,7 +1069,8 @@ class ucp_groups  								'mode'		=> $mode,  								'action'	=> $action  							); -							confirm_box(false, sprintf($user->lang['GROUP_CONFIRM_ADD_USER' . ((sizeof($name_ary) == 1) ? '' : 'S')], implode(', ', $name_ary)), build_hidden_fields($s_hidden_fields)); + +							confirm_box(false, $user->lang('GROUP_CONFIRM_ADD_USERS', sizeof($name_ary), implode($user->lang['COMMA_SEPARATOR'], $name_ary)), build_hidden_fields($s_hidden_fields));  						}  						trigger_error($user->lang['NO_USERS_ADDED'] . '<br /><br />' . sprintf($user->lang['RETURN_PAGE'], '<a href="' . $this->u_action . '&action=list&g=' . $group_id . '">', '</a>')); diff --git a/phpBB/includes/ucp/ucp_main.php b/phpBB/includes/ucp/ucp_main.php index f4fdb50ecd..94fd59433b 100644 --- a/phpBB/includes/ucp/ucp_main.php +++ b/phpBB/includes/ucp/ucp_main.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -70,17 +69,16 @@ class ucp_main  				// Get cleaned up list... return only those forums having the f_read permission  				$forum_ary = $auth->acl_getf('f_read', true);  				$forum_ary = array_unique(array_keys($forum_ary)); - -				$sql = "SELECT t.* $sql_select -					FROM $sql_from -					WHERE t.topic_type = " . POST_GLOBAL . ' -						AND ' . $db->sql_in_set('t.forum_id', $forum_ary) . ' -					ORDER BY t.topic_last_post_time DESC'; -  				$topic_list = $rowset = array(); +  				// If the user can't see any forums, he can't read any posts because fid of 0 is invalid  				if (!empty($forum_ary))  				{ +					$sql = "SELECT t.* $sql_select +						FROM $sql_from +						WHERE t.topic_type = " . POST_GLOBAL . ' +							AND ' . $db->sql_in_set('t.forum_id', $forum_ary) . ' +						ORDER BY t.topic_last_post_time DESC';  					$result = $db->sql_query($sql);  					while ($row = $db->sql_fetchrow($result)) @@ -161,7 +159,6 @@ class ucp_main  						'TOPIC_IMG_STYLE'		=> $folder_img,  						'TOPIC_FOLDER_IMG'		=> $user->img($folder_img, $folder_alt), -						'TOPIC_FOLDER_IMG_SRC'	=> $user->img($folder_img, $folder_alt, false, '', 'src'),  						'ATTACH_ICON_IMG'		=> ($auth->acl_get('u_download') && $auth->acl_get('f_download', $forum_id) && $row['topic_attachment']) ? $user->img('icon_topic_attach', '') : '',  						'S_USER_POSTED'		=> (!empty($row['topic_posted']) && $row['topic_posted']) ? true : false, @@ -195,8 +192,8 @@ class ucp_main  					'VISITED'			=> (empty($last_visit)) ? ' - ' : $user->format_date($last_visit),  					'WARNINGS'			=> ($user->data['user_warnings']) ? $user->data['user_warnings'] : 0,  					'POSTS'				=> ($user->data['user_posts']) ? $user->data['user_posts'] : 0, -					'POSTS_DAY'			=> sprintf($user->lang['POST_DAY'], $posts_per_day), -					'POSTS_PCT'			=> sprintf($user->lang['POST_PCT'], $percentage), +					'POSTS_DAY'			=> $user->lang('POST_DAY', $posts_per_day), +					'POSTS_PCT'			=> $user->lang('POST_PCT', $percentage),  					'OCCUPATION'	=> (!empty($row['user_occ'])) ? $row['user_occ'] : '',  					'INTERESTS'		=> (!empty($row['user_interests'])) ? $row['user_interests'] : '', @@ -348,7 +345,6 @@ class ucp_main  							'FORUM_ID'				=> $forum_id,  							'FORUM_IMG_STYLE'		=> $folder_image,  							'FORUM_FOLDER_IMG'		=> $user->img($folder_image, $folder_alt), -							'FORUM_FOLDER_IMG_SRC'	=> $user->img($folder_image, $folder_alt, false, '', 'src'),  							'FORUM_IMAGE'			=> ($row['forum_image']) ? '<img src="' . $phpbb_root_path . $row['forum_image'] . '" alt="' . $user->lang[$folder_alt] . '" />' : '',  							'FORUM_IMAGE_SRC'		=> ($row['forum_image']) ? $phpbb_root_path . $row['forum_image'] : '',  							'FORUM_NAME'			=> $row['forum_name'], @@ -673,11 +669,12 @@ 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), -				'TOTAL_TOPICS'	=> ($topics_count == 1) ? $user->lang['VIEW_FORUM_TOPIC'] : sprintf($user->lang['VIEW_FORUM_TOPICS'], $topics_count)) -			); +				'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), +			));  		}  		if ($mode == 'subscribed') @@ -816,7 +813,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']), @@ -825,7 +821,6 @@ class ucp_main  				'TOPIC_IMG_STYLE'		=> $folder_img,  				'TOPIC_FOLDER_IMG'		=> $user->img($folder_img, $folder_alt), -				'TOPIC_FOLDER_IMG_SRC'	=> $user->img($folder_img, $folder_alt, false, '', 'src'),  				'TOPIC_FOLDER_IMG_ALT'	=> $user->lang[$folder_alt],  				'TOPIC_ICON_IMG'		=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['img'] : '',  				'TOPIC_ICON_IMG_WIDTH'	=> (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['width'] : '', @@ -841,6 +836,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.php b/phpBB/includes/ucp/ucp_pm.php index 84fa9b18dc..d4ce8e41ee 100644 --- a/phpBB/includes/ucp/ucp_pm.php +++ b/phpBB/includes/ucp/ucp_pm.php @@ -1,9 +1,8 @@  <?php  /**  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -115,7 +114,7 @@ class ucp_pm  			case 'compose':  				$action = request_var('action', 'post'); -				get_folder($user->data['user_id']); +				$user_folders = get_folder($user->data['user_id']);  				if (!$auth->acl_get('u_sendpm'))  				{ @@ -130,7 +129,7 @@ class ucp_pm  				}  				include($phpbb_root_path . 'includes/ucp/ucp_pm_compose.' . $phpEx); -				compose_pm($id, $mode, $action); +				compose_pm($id, $mode, $action, $user_folders);  				$tpl_file = 'posting_body';  			break; @@ -243,7 +242,7 @@ class ucp_pm  				$num_not_moved = $num_removed = 0;  				$release = request_var('release', 0); -				if ($user->data['user_new_privmsg'] && $action == 'view_folder') +				if ($user->data['user_new_privmsg'] && ($action == 'view_folder' || $action == 'view_message'))  				{  					$return = place_pm_into_folder($global_privmsgs_rules, $release);  					$num_not_moved = $return['not_moved']; @@ -345,8 +344,8 @@ class ucp_pm  					'NUM_NOT_MOVED'			=> $num_not_moved,  					'NUM_REMOVED'			=> $num_removed,  					'RELEASE_MESSAGE_INFO'	=> sprintf($user->lang['RELEASE_MESSAGES'], '<a href="' . $this->u_action . '&folder=' . $folder_id . '&release=1">', '</a>'), -					'NOT_MOVED_MESSAGES'	=> ($num_not_moved == 1) ? $user->lang['NOT_MOVED_MESSAGE'] : sprintf($user->lang['NOT_MOVED_MESSAGES'], $num_not_moved), -					'RULE_REMOVED_MESSAGES'	=> ($num_removed == 1) ? $user->lang['RULE_REMOVED_MESSAGE'] : sprintf($user->lang['RULE_REMOVED_MESSAGES'], $num_removed), +					'NOT_MOVED_MESSAGES'	=> $user->lang('NOT_MOVED_MESSAGES', (int) $num_not_moved), +					'RULE_REMOVED_MESSAGES'	=> $user->lang('RULE_REMOVED_MESSAGES', (int) $num_removed),  					'S_FOLDER_OPTIONS'		=> $s_folder_options,  					'S_TO_FOLDER_OPTIONS'	=> $s_to_folder_options, diff --git a/phpBB/includes/ucp/ucp_pm_compose.php b/phpBB/includes/ucp/ucp_pm_compose.php index 07aa25d67b..934ff566cc 100644 --- a/phpBB/includes/ucp/ucp_pm_compose.php +++ b/phpBB/includes/ucp/ucp_pm_compose.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -20,7 +19,7 @@ if (!defined('IN_PHPBB'))  * Compose private message  * Called from ucp_pm with mode == 'compose'  */ -function compose_pm($id, $mode, $action) +function compose_pm($id, $mode, $action, $user_folders = array())  {  	global $template, $db, $auth, $user;  	global $phpbb_root_path, $phpEx, $config; @@ -130,6 +129,7 @@ function compose_pm($id, $mode, $action)  	}  	$sql = ''; +	$folder_id = 0;  	// What is all this following SQL for? Well, we need to know  	// some basic information in all cases before we do anything. @@ -393,7 +393,7 @@ function compose_pm($id, $mode, $action)  	unset($message_text);  	$s_action = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=$id&mode=$mode&action=$action", true, $user->session_id); -	$s_action .= ($msg_id) ? "&p=$msg_id" : ''; +	$s_action .= (($folder_id) ? "&f=$folder_id" : '') . (($msg_id) ? "&p=$msg_id" : '');  	// Delete triggered ?  	if ($action == 'delete') @@ -736,10 +736,31 @@ function compose_pm($id, $mode, $action)  			$msg_id = submit_pm($action, $subject, $pm_data);  			$return_message_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=view&p=' . $msg_id); -			$return_folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=outbox'); -			meta_refresh(3, $return_message_url); +			$inbox_folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'); +			$outbox_folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=outbox'); + +			$folder_url = ''; +			if (($folder_id > 0) && isset($user_folders[$folder_id])) +			{ +				$folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=' . $folder_id); +			} + +			$return_box_url = ($action === 'post' || $action === 'edit') ? $outbox_folder_url : $inbox_folder_url; +			$return_box_lang = ($action === 'post' || $action === 'edit') ? 'PM_OUTBOX' : 'PM_INBOX'; + -			$message = $user->lang['MESSAGE_STORED'] . '<br /><br />' . sprintf($user->lang['VIEW_PRIVATE_MESSAGE'], '<a href="' . $return_message_url . '">', '</a>') . '<br /><br />' . sprintf($user->lang['CLICK_RETURN_FOLDER'], '<a href="' . $return_folder_url . '">', '</a>', $user->lang['PM_OUTBOX']); +			$save_message = ($action === 'edit') ? $user->lang['MESSAGE_EDITED'] : $user->lang['MESSAGE_STORED']; +			$message = $save_message . '<br /><br />' . $user->lang('VIEW_PRIVATE_MESSAGE', '<a href="' . $return_message_url . '">', '</a>'); + +			$last_click_type = 'CLICK_RETURN_FOLDER'; +			if ($folder_url) +			{ +				$message .= '<br /><br />' . sprintf($user->lang['CLICK_RETURN_FOLDER'], '<a href="' . $folder_url . '">', '</a>', $user_folders[$folder_id]['folder_name']); +				$last_click_type = 'CLICK_GOTO_FOLDER'; +			} +			$message .= '<br /><br />' . sprintf($user->lang[$last_click_type], '<a href="' . $return_box_url . '">', '</a>', $user->lang[$return_box_lang]); + +			meta_refresh(3, $return_message_url);  			trigger_error($message);  		} @@ -854,7 +875,7 @@ function compose_pm($id, $mode, $action)  		$forward_text[] = sprintf($user->lang['FWD_SUBJECT'], censor_text($message_subject));  		$forward_text[] = sprintf($user->lang['FWD_DATE'], $user->format_date($message_time, false, true));  		$forward_text[] = sprintf($user->lang['FWD_FROM'], $quote_username_text); -		$forward_text[] = sprintf($user->lang['FWD_TO'], implode(', ', $fwd_to_field['to'])); +		$forward_text[] = sprintf($user->lang['FWD_TO'], implode($user->lang['COMMA_SEPARATOR'], $fwd_to_field['to']));  		$message_parser->message = implode("\n", $forward_text) . "\n\n[quote="{$quote_username}"]\n" . censor_text(trim($message_parser->message)) . "\n[/quote]";  		$message_subject = ((!preg_match('/^Fwd:/', $message_subject)) ? 'Fwd: ' : '') . censor_text($message_subject); @@ -1032,7 +1053,7 @@ function compose_pm($id, $mode, $action)  	$template->assign_vars(array(  		'L_POST_A'					=> $page_title,  		'L_ICON'					=> $user->lang['PM_ICON'], -		'L_MESSAGE_BODY_EXPLAIN'	=> (intval($config['max_post_chars'])) ? sprintf($user->lang['MESSAGE_BODY_EXPLAIN'], intval($config['max_post_chars'])) : '', +		'L_MESSAGE_BODY_EXPLAIN'	=> $user->lang('MESSAGE_BODY_EXPLAIN', (int) $config['max_post_chars']),  		'SUBJECT'				=> (isset($message_subject)) ? $message_subject : '',  		'MESSAGE'				=> $message_text, diff --git a/phpBB/includes/ucp/ucp_pm_options.php b/phpBB/includes/ucp/ucp_pm_options.php index 49c727847a..bf7334b307 100644 --- a/phpBB/includes/ucp/ucp_pm_options.php +++ b/phpBB/includes/ucp/ucp_pm_options.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -231,7 +230,7 @@ function message_options($id, $mode, $global_privmsgs_rules, $global_rule_condit  					// Something went wrong, only partially moved?  					if ($num_moved != $folder_row['pm_count'])  					{ -						trigger_error(sprintf($user->lang['MOVE_PM_ERROR'], $num_moved, $folder_row['pm_count'])); +						trigger_error($user->lang('MOVE_PM_ERROR', (int) $folder_row['pm_count'], $num_moved));  					}  				break; @@ -328,10 +327,23 @@ function message_options($id, $mode, $global_privmsgs_rules, $global_rule_condit  				trigger_error('RULE_ALREADY_DEFINED');  			} +			// Prevent users from flooding the rules table +			$sql = 'SELECT COUNT(rule_id) AS num_rules +				FROM ' . PRIVMSGS_RULES_TABLE . ' +				WHERE user_id = ' . (int) $user->data['user_id']; +			$result = $db->sql_query($sql); +			$num_rules = (int) $db->sql_fetchfield('num_rules'); +			$db->sql_freeresult($result); + +			if ($num_rules >= 5000) +			{ +				trigger_error('RULE_LIMIT_REACHED'); +			} +  			$sql = 'INSERT INTO ' . PRIVMSGS_RULES_TABLE . ' ' . $db->sql_build_array('INSERT', $rule_ary);  			$db->sql_query($sql); -			// Update users message rules +			// Set the user_message_rules bit  			$sql = 'UPDATE ' . USERS_TABLE . '  				SET user_message_rules = 1  				WHERE user_id = ' . $user->data['user_id']; @@ -378,7 +390,7 @@ function message_options($id, $mode, $global_privmsgs_rules, $global_rule_condit  			$row = $db->sql_fetchrow($result);  			$db->sql_freeresult($result); -			// Update users message rules +			// Unset the user_message_rules bit  			if (!$row)  			{  				$sql = 'UPDATE ' . USERS_TABLE . ' @@ -409,7 +421,7 @@ function message_options($id, $mode, $global_privmsgs_rules, $global_rule_condit  	$folder[PRIVMSGS_INBOX] = array(  		'folder_name'		=> $user->lang['PM_INBOX'], -		'message_status'	=> sprintf($user->lang['FOLDER_MESSAGE_STATUS'], $num_messages, $user->data['message_limit']) +		'message_status'	=> $user->lang('FOLDER_MESSAGE_STATUS', (int) $user->data['message_limit'], $num_messages),  	);  	$sql = 'SELECT folder_id, folder_name, pm_count @@ -423,7 +435,7 @@ function message_options($id, $mode, $global_privmsgs_rules, $global_rule_condit  		$num_user_folder++;  		$folder[$row['folder_id']] = array(  			'folder_name'		=> $row['folder_name'], -			'message_status'	=> sprintf($user->lang['FOLDER_MESSAGE_STATUS'], $row['pm_count'], $user->data['message_limit']) +			'message_status'	=> $user->lang('FOLDER_MESSAGE_STATUS', (int) $user->data['message_limit'], $row['pm_count']),  		);  	}  	$db->sql_freeresult($result); diff --git a/phpBB/includes/ucp/ucp_pm_viewfolder.php b/phpBB/includes/ucp/ucp_pm_viewfolder.php index 1758bb5eb1..625da23736 100644 --- a/phpBB/includes/ucp/ucp_pm_viewfolder.php +++ b/phpBB/includes/ucp/ucp_pm_viewfolder.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -166,7 +165,6 @@ function view_folder($id, $mode, $folder_id, $folder)  					'PM_ICON_URL'		=> (!empty($icons[$row['icon_id']])) ? $config['icons_path'] . '/' . $icons[$row['icon_id']]['img'] : '',  					'FOLDER_IMG'		=> $user->img($folder_img, $folder_alt),  					'FOLDER_IMG_STYLE'	=> $folder_img, -					'FOLDER_IMG_SRC'	=> $user->img($folder_img, $folder_alt, false, '', 'src'),  					'PM_IMG'			=> ($row_indicator) ? $user->img('pm_' . $row_indicator, '') : '',  					'ATTACH_ICON_IMG'	=> ($auth->acl_get('u_pm_download') && $row['message_attachment'] && $config['allow_pm_attach']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', @@ -178,7 +176,7 @@ function view_folder($id, $mode, $folder_id, $folder)  					'U_VIEW_PM'			=> ($row['pm_deleted']) ? '' : $view_message_url,  					'U_REMOVE_PM'		=> ($row['pm_deleted']) ? $remove_message_url : '',  					'U_MCP_REPORT'		=> (isset($row['report_id'])) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=pm_reports&mode=pm_report_details&r=' . $row['report_id']) : '', -					'RECIPIENTS'		=> ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) ? implode(', ', $address_list[$message_id]) : '') +					'RECIPIENTS'		=> ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) ? implode($user->lang['COMMA_SEPARATOR'], $address_list[$message_id]) : '')  				);  			}  			unset($folder_info['rowset']); @@ -268,9 +266,9 @@ function view_folder($id, $mode, $folder_id, $folder)  					}  				} -				// There is the chance that all recipients of the message got deleted. To avoid creating  +				// There is the chance that all recipients of the message got deleted. To avoid creating  				// exports without recipients, we add a bogus "undisclosed recipient". -				if (!(isset($address[$message_id]['g']) && sizeof($address[$message_id]['g'])) &&  +				if (!(isset($address[$message_id]['g']) && sizeof($address[$message_id]['g'])) &&  				    !(isset($address[$message_id]['u']) && sizeof($address[$message_id]['u'])))  				{  					$address[$message_id]['u'] = array(); @@ -279,7 +277,7 @@ function view_folder($id, $mode, $folder_id, $folder)  				}  				decode_message($message_row['message_text'], $message_row['bbcode_uid']); -				 +  				$data[] = array(  					'subject'	=> censor_text($row['message_subject']),  					'sender'	=> $row['username'], @@ -453,10 +451,12 @@ 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), -		'TOTAL_MESSAGES'	=> (($pm_count == 1) ? $user->lang['VIEW_PM_MESSAGE'] : sprintf($user->lang['VIEW_PM_MESSAGES'], $pm_count)), +		'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_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index 74a32a68c9..c85b05f144 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -22,7 +21,7 @@ if (!defined('IN_PHPBB'))  function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)  {  	global $user, $template, $auth, $db, $cache; -	global $phpbb_root_path, $phpEx, $config; +	global $phpbb_root_path, $request, $phpEx, $config, $phpbb_dispatcher;  	$user->add_lang(array('viewtopic', 'memberlist')); @@ -59,6 +58,18 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)  		$bbcode = new bbcode($message_row['bbcode_bitfield']);  	} +	// Load the custom profile fields +	if ($config['load_cpf_pm']) +	{ +		if (!class_exists('custom_profile')) +		{ +			include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); +		} +		$cp = new custom_profile(); + +		$profile_fields = $cp->generate_profile_fields_template('grab', $author_id); +	} +  	// Assign TO/BCC Addresses to template  	write_pm_addresses(array('to' => $message_row['to_address'], 'bcc' => $message_row['bcc_address']), $author_id); @@ -174,7 +185,26 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)  	$bbcode_status	= ($config['allow_bbcode'] && $config['auth_bbcode_pm'] && $auth->acl_get('u_pm_bbcode')) ? true : false; -	$template->assign_vars(array( +	// Get the profile fields template data +	$cp_row = array(); +	if ($config['load_cpf_pm'] && isset($profile_fields[$author_id])) +	{ +		// Filter the fields we don't want to show +		foreach ($profile_fields[$author_id] as $used_ident => $profile_field) +		{ +			if (!$profile_field['data']['field_show_on_pm']) +			{ +				unset($profile_fields[$author_id][$used_ident]); +			} +		} + +		if (isset($profile_fields[$author_id])) +		{ +			$cp_row = $cp->generate_profile_fields_template('show', false, $profile_fields[$author_id]); +		} +	} + +	$msg_data = array(  		'MESSAGE_AUTHOR_FULL'		=> get_username_string('full', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']),  		'MESSAGE_AUTHOR_COLOUR'		=> get_username_string('colour', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']),  		'MESSAGE_AUTHOR'			=> get_username_string('username', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), @@ -208,7 +238,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)  		'U_PM'			=> ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && ($user_info['user_allow_pm'] || $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $author_id) : '',  		'U_WWW'			=> (!empty($user_info['user_website'])) ? $user_info['user_website'] : '', -		'U_ICQ'			=> ($user_info['user_icq']) ? 'http://www.icq.com/people' . urlencode($user_info['user_icq']) . '/' : '', +		'U_ICQ'			=> ($user_info['user_icq']) ? 'http://www.icq.com/people/' . urlencode($user_info['user_icq']) . '/' : '',  		'U_AIM'			=> ($user_info['user_aim'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=aim&u=' . $author_id) : '',  		'U_YIM'			=> ($user_info['user_yim']) ? 'http://edit.yahoo.com/config/send_webmesg?.target=' . urlencode($user_info['user_yim']) . '&.src=pg' : '',  		'U_MSN'			=> ($user_info['user_msnm'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=msnm&u=' . $author_id) : '', @@ -227,19 +257,57 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)  		'U_PM_ACTION'		=> $url . '&mode=compose&f=' . $folder_id . '&p=' . $message_row['msg_id'],  		'S_HAS_ATTACHMENTS'	=> (sizeof($attachments)) ? true : false, +		'S_HAS_MULTIPLE_ATTACHMENTS' => (sizeof($attachments) > 1),  		'S_DISPLAY_NOTICE'	=> $display_notice && $message_row['message_attachment'],  		'S_AUTHOR_DELETED'	=> ($author_id == ANONYMOUS) ? true : false,  		'S_SPECIAL_FOLDER'	=> in_array($folder_id, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX)),  		'S_PM_RECIPIENTS'	=> $num_recipients,  		'S_BBCODE_ALLOWED'	=> ($bbcode_status) ? 1 : 0, +		'S_CUSTOM_FIELDS'	=> (!empty($cp_row['row'])) ? true : false,  		'U_PRINT_PM'		=> ($config['print_pm'] && $auth->acl_get('u_pm_printpm')) ? "$url&f=$folder_id&p=" . $message_row['msg_id'] . "&view=print" : '', -		'U_FORWARD_PM'		=> ($config['forward_pm'] && $auth->acl_get('u_sendpm') && $auth->acl_get('u_pm_forward')) ? "$url&mode=compose&action=forward&f=$folder_id&p=" . $message_row['msg_id'] : '') +		'U_FORWARD_PM'		=> ($config['forward_pm'] && $auth->acl_get('u_sendpm') && $auth->acl_get('u_pm_forward')) ? "$url&mode=compose&action=forward&f=$folder_id&p=" . $message_row['msg_id'] : '',  	); +	/** +	* Modify pm and sender data before it is assigned to the template +	* +	* @event core.ucp_pm_view_messsage +	* @var	mixed	id			Active module category (can be int or string) +	* @var	string	mode		Active module +	* @var	int		folder_id	ID of the folder the message is in +	* @var	int		msg_id		ID of the private message +	* var	array	folder		Array with data of user's message folders +	* @var	array	message_row		Array with message data +	* @var	array	cp_row		Array with senders custom profile field data +	* @var	array	msg_data	Template array with message data +	* @since 3.1-A1 +	*/ +	$vars = array('id', 'mode', 'folder_id', 'msg_id', 'folder', 'message_row', 'cp_row', 'msg_data'); +	extract($phpbb_dispatcher->trigger_event('core.ucp_pm_view_messsage', compact($vars))); + +	$template->assign_vars($msg_data); + +	// Display the custom profile fields +	if (!empty($cp_row['row'])) +	{ +		$template->assign_vars($cp_row['row']); + +		foreach ($cp_row['blockrow'] as $cp_block_row) +		{ +			$template->assign_block_vars('custom_fields', $cp_block_row); +		} +	} +  	// Display not already displayed Attachments for this post, we already parsed them. ;)  	if (isset($attachments) && sizeof($attachments))  	{ +		$methods = phpbb_gen_download_links('post_msg_id', $msg_id, $phpbb_root_path, $phpEx); +		foreach ($methods as $method) +		{ +			$template->assign_block_vars('dl_method', $method); +		} +	  		foreach ($attachments as $attachment)  		{  			$template->assign_block_vars('attachment', array( diff --git a/phpBB/includes/ucp/ucp_prefs.php b/phpBB/includes/ucp/ucp_prefs.php index 51262c2289..2228bc7931 100644 --- a/phpBB/includes/ucp/ucp_prefs.php +++ b/phpBB/includes/ucp/ucp_prefs.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -42,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']), @@ -61,12 +59,19 @@ class ucp_prefs  				if ($submit)  				{ -					$data['style'] = ($config['override_user_style']) ? $config['default_style'] : $data['style']; +					if ($config['override_user_style']) +					{ +						$data['style'] = (int) $config['default_style']; +					} +					else if (!phpbb_style_is_active($data['style'])) +					{ +						$data['style'] = (int) $user->data['user_style']; +					}  					$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')) @@ -87,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'], @@ -127,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) : '', @@ -139,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']), @@ -150,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_profile.php b/phpBB/includes/ucp/ucp_profile.php index 4572dfd25d..89bf20a30f 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -47,10 +46,9 @@ class ucp_profile  				$data = array(  					'username'			=> utf8_normalize_nfc(request_var('username', $user->data['username'], true)),  					'email'				=> strtolower(request_var('email', $user->data['user_email'])), -					'email_confirm'		=> strtolower(request_var('email_confirm', '')), -					'new_password'		=> request_var('new_password', '', true), -					'cur_password'		=> request_var('cur_password', '', true), -					'password_confirm'	=> request_var('password_confirm', '', true), +					'new_password'		=> $request->variable('new_password', '', true), +					'cur_password'		=> $request->variable('cur_password', '', true), +					'password_confirm'	=> $request->variable('password_confirm', '', true),  				);  				add_form_key('ucp_reg_details'); @@ -66,7 +64,6 @@ class ucp_profile  						'email'				=> array(  							array('string', false, 6, 60),  							array('email')), -						'email_confirm'		=> array('string', true, 6, 60),  					);  					if ($auth->acl_get('u_chgname') && $config['allow_namechange']) @@ -81,12 +78,7 @@ class ucp_profile  					if ($auth->acl_get('u_chgpasswd') && $data['new_password'] && $data['password_confirm'] != $data['new_password'])  					{ -						$error[] = 'NEW_PASSWORD_ERROR'; -					} - -					if (($data['new_password'] || ($auth->acl_get('u_chgemail') && $data['email'] != $user->data['user_email']) || ($data['username'] != $user->data['username'] && $auth->acl_get('u_chgname') && $config['allow_namechange'])) && !phpbb_check_hash($data['cur_password'], $user->data['user_password'])) -					{ -						$error[] = 'CUR_PASSWORD_ERROR'; +						$error[] = ($data['password_confirm']) ? 'NEW_PASSWORD_ERROR' : 'NEW_PASSWORD_CONFIRM_EMPTY';  					}  					// Only check the new password against the previous password if there have been no errors @@ -95,9 +87,9 @@ class ucp_profile  						$error[] = 'SAME_PASSWORD_ERROR';  					} -					if ($auth->acl_get('u_chgemail') && $data['email'] != $user->data['user_email'] && $data['email_confirm'] != $data['email']) +					if (!phpbb_check_hash($data['cur_password'], $user->data['user_password']))  					{ -						$error[] = 'NEW_EMAIL_ERROR'; +						$error[] = ($data['cur_password']) ? 'CUR_PASSWORD_ERROR' : 'CUR_PASSWORD_EMPTY';  					}  					if (!check_form_key('ucp_reg_details')) @@ -151,10 +143,7 @@ class ucp_profile  							$messenger->to($data['email'], $data['username']); -							$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -							$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -							$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -							$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +							$messenger->anti_abuse_headers($config, $user);  							$messenger->assign_vars(array(  								'USERNAME'		=> htmlspecialchars_decode($data['username']), @@ -251,8 +240,8 @@ class ucp_profile  					'NEW_PASSWORD'		=> $data['new_password'],  					'CUR_PASSWORD'		=> '', -					'L_USERNAME_EXPLAIN'		=> sprintf($user->lang[$config['allow_name_chars'] . '_EXPLAIN'], $config['min_name_chars'], $config['max_name_chars']), -					'L_CHANGE_PASSWORD_EXPLAIN'	=> sprintf($user->lang[$config['pass_complex'] . '_EXPLAIN'], $config['min_pass_chars'], $config['max_pass_chars']), +					'L_USERNAME_EXPLAIN'		=> $user->lang($config['allow_name_chars'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_name_chars']), $user->lang('CHARACTERS', (int) $config['max_name_chars'])), +					'L_CHANGE_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_FORCE_PASSWORD'	=> ($auth->acl_get('u_chgpasswd') && $config['chg_passforce'] && $user->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400)) ? true : false,  					'S_CHANGE_USERNAME' => ($config['allow_namechange'] && $auth->acl_get('u_chgname')) ? true : false, @@ -540,7 +529,7 @@ class ucp_profile  					'URL_STATUS'			=> ($config['allow_sig_links']) ? $user->lang['URL_IS_ON'] : $user->lang['URL_IS_OFF'],  					'MAX_FONT_SIZE'			=> (int) $config['max_sig_font_size'], -					'L_SIGNATURE_EXPLAIN'	=> sprintf($user->lang['SIGNATURE_EXPLAIN'], $config['max_sig_chars']), +					'L_SIGNATURE_EXPLAIN'	=> $user->lang('SIGNATURE_EXPLAIN', (int) $config['max_sig_chars']),  					'S_BBCODE_ALLOWED'		=> $config['allow_sig_bbcode'],  					'S_SMILIES_ALLOWED'		=> $config['allow_sig_smilies'], @@ -605,7 +594,7 @@ class ucp_profile  					'S_FORM_ENCTYPE'	=> ($can_upload && ($config['allow_avatar_upload'] || $config['allow_avatar_remote_upload'])) ? ' enctype="multipart/form-data"' : '', -					'L_AVATAR_EXPLAIN'	=> sprintf($user->lang['AVATAR_EXPLAIN'], $config['avatar_max_width'], $config['avatar_max_height'], $config['avatar_filesize'] / 1024), +					'L_AVATAR_EXPLAIN'	=> phpbb_avatar_explanation_string(),  				));  				if ($config['allow_avatar'] && $display_gallery && $auth->acl_get('u_chgavatar') && $config['allow_avatar_local']) @@ -629,6 +618,60 @@ class ucp_profile  				}  			break; + +			case 'autologin_keys': + +				add_form_key('ucp_autologin_keys'); + +				if ($submit) +				{ +					$keys = request_var('keys', array('')); + +					if (!check_form_key('ucp_autologin_keys')) +					{ +						$error[] = 'FORM_INVALID'; +					} + +					if (!sizeof($error)) +					{ +						if (!empty($keys)) +						{ +							$sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' +								WHERE user_id = ' . (int) $user->data['user_id'] . ' +								AND ' . $db->sql_in_set('key_id', $keys) ; + +							$db->sql_query($sql); + +							meta_refresh(3, $this->u_action); +							$message = $user->lang['AUTOLOGIN_SESSION_KEYS_DELETED'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>'); +							trigger_error($message); +						} +					} + +					// Replace "error" strings with their real, localised form +					$error = array_map(array($user, 'lang'), $error); +				} + +				$sql = 'SELECT key_id, last_ip, last_login +					FROM ' . SESSIONS_KEYS_TABLE . ' +					WHERE user_id = ' . (int) $user->data['user_id']; + +				$result = $db->sql_query($sql); + +				while ($row = $db->sql_fetchrow($result)) +				{ +					$template->assign_block_vars('sessions', array( +						'errors' => $error, + +						'KEY' => $row['key_id'], +						'IP' => $row['last_ip'], +						'LOGIN_TIME' => $user->format_date($row['last_login']), +					)); +				} + +				$db->sql_freeresult($result); + +			break;  		}  		$template->assign_vars(array( diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 71374a9381..c57aec00a0 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -100,9 +99,8 @@ class ucp_register  				$s_hidden_fields = array_merge($s_hidden_fields, array(  					'username'			=> utf8_normalize_nfc(request_var('username', '', true)),  					'email'				=> strtolower(request_var('email', '')), -					'email_confirm'		=> strtolower(request_var('email_confirm', '')),  					'lang'				=> $user->lang_name, -					'tz'				=> request_var('tz', (float) $config['board_timezone']), +					'tz'				=> request_var('tz', $config['board_timezone']),  				));  			} @@ -122,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( @@ -156,43 +157,24 @@ class ucp_register  			$this->tpl_name = 'ucp_agreement';  			return;  		} -		 -		 -		// The CAPTCHA kicks in here. We can't help that the information gets lost on language change.  + +		// The CAPTCHA kicks in here. We can't help that the information gets lost on language change.  		if ($config['enable_confirm'])  		{  			include($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx); -			$captcha =& phpbb_captcha_factory::get_instance($config['captcha_plugin']); +			$captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']);  			$captcha->init(CONFIRM_REG);  		} -		// Try to manually determine the timezone and adjust the dst if the server date/time complies with the default setting +/- 1 -		$timezone = date('Z') / 3600; -		$is_dst = date('I'); - -		if ($config['board_timezone'] == $timezone || $config['board_timezone'] == ($timezone - 1)) -		{ -			$timezone = ($is_dst) ? $timezone - 1 : $timezone; - -			if (!isset($user->lang['tz_zones'][(string) $timezone])) -			{ -				$timezone = $config['board_timezone']; -			} -		} -		else -		{ -			$is_dst = $config['board_dst']; -			$timezone = $config['board_timezone']; -		} +		$timezone = $config['board_timezone'];  		$data = array(  			'username'			=> utf8_normalize_nfc(request_var('username', '', true)), -			'new_password'		=> request_var('new_password', '', true), -			'password_confirm'	=> request_var('password_confirm', '', true), +			'new_password'		=> $request->variable('new_password', '', true), +			'password_confirm'	=> $request->variable('password_confirm', '', true),  			'email'				=> strtolower(request_var('email', '')), -			'email_confirm'		=> strtolower(request_var('email_confirm', '')),  			'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 @@ -209,8 +191,7 @@ class ucp_register  				'email'				=> array(  					array('string', false, 6, 60),  					array('email')), -				'email_confirm'		=> array('string', false, 6, 60), -				'tz'				=> array('num', false, -14, 14), +				'tz'				=> array('timezone'),  				'lang'				=> array('language_iso_name'),  			)); @@ -254,11 +235,6 @@ class ucp_register  				{  					$error[] = $user->lang['NEW_PASSWORD_ERROR'];  				} - -				if ($data['email'] != $data['email_confirm']) -				{ -					$error[] = $user->lang['NEW_EMAIL_ERROR']; -				}  			}  			if (!sizeof($error)) @@ -305,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, @@ -367,10 +342,7 @@ class ucp_register  					$messenger->to($data['email'], $data['username']); -					$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -					$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -					$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -					$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +					$messenger->anti_abuse_headers($config, $user);  					$messenger->assign_vars(array(  						'WELCOME_MSG'	=> htmlspecialchars_decode(sprintf($user->lang['WELCOME_SUBJECT'], $config['sitename'])), @@ -470,20 +442,21 @@ 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'],  			'PASSWORD'			=> $data['new_password'],  			'PASSWORD_CONFIRM'	=> $data['password_confirm'],  			'EMAIL'				=> $data['email'], -			'EMAIL_CONFIRM'		=> $data['email_confirm'],  			'L_REG_COND'				=> $l_reg_cond, -			'L_USERNAME_EXPLAIN'		=> sprintf($user->lang[$config['allow_name_chars'] . '_EXPLAIN'], $config['min_name_chars'], $config['max_name_chars']), -			'L_PASSWORD_EXPLAIN'		=> sprintf($user->lang[$config['pass_complex'] . '_EXPLAIN'], $config['min_pass_chars'], $config['max_pass_chars']), +			'L_USERNAME_EXPLAIN'		=> $user->lang($config['allow_name_chars'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_name_chars']), $user->lang('CHARACTERS', (int) $config['max_name_chars'])), +			'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/ucp/ucp_remind.php b/phpBB/includes/ucp/ucp_remind.php index 4d7e0f90b0..4f65ed1866 100644 --- a/phpBB/includes/ucp/ucp_remind.php +++ b/phpBB/includes/ucp/ucp_remind.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -67,7 +66,7 @@ class ucp_remind  			}  			// Check users permissions -			$auth2 = new auth(); +			$auth2 = new phpbb_auth();  			$auth2->acl($user_row);  			if (!$auth2->acl_get('u_chgpasswd')) diff --git a/phpBB/includes/ucp/ucp_resend.php b/phpBB/includes/ucp/ucp_resend.php index 66677b9a4f..5f1e3a92c3 100644 --- a/phpBB/includes/ucp/ucp_resend.php +++ b/phpBB/includes/ucp/ucp_resend.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -94,10 +93,7 @@ class ucp_resend  				$messenger->template(($coppa) ? 'coppa_resend_inactive' : 'user_resend_inactive', $user_row['user_lang']);  				$messenger->to($user_row['user_email'], $user_row['username']); -				$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -				$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -				$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -				$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +				$messenger->anti_abuse_headers($config, $user);  				$messenger->assign_vars(array(  					'WELCOME_MSG'	=> htmlspecialchars_decode(sprintf($user->lang['WELCOME_SUBJECT'], $config['sitename'])), @@ -133,10 +129,7 @@ class ucp_resend  					$messenger->to($row['user_email'], $row['username']);  					$messenger->im($row['user_jabber'], $row['username']); -					$messenger->headers('X-AntiAbuse: Board servername - ' . $config['server_name']); -					$messenger->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); -					$messenger->headers('X-AntiAbuse: Username - ' . $user->data['username']); -					$messenger->headers('X-AntiAbuse: User IP - ' . $user->ip); +					$messenger->anti_abuse_headers($config, $user);  					$messenger->assign_vars(array(  						'USERNAME'			=> htmlspecialchars_decode($user_row['username']), diff --git a/phpBB/includes/ucp/ucp_zebra.php b/phpBB/includes/ucp/ucp_zebra.php index 6e160b9bb9..a669c450a4 100644 --- a/phpBB/includes/ucp/ucp_zebra.php +++ b/phpBB/includes/ucp/ucp_zebra.php @@ -2,9 +2,8 @@  /**  *  * @package ucp -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -26,7 +25,7 @@ class ucp_zebra  	function main($id, $mode)  	{ -		global $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx; +		global $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx, $request, $phpbb_dispatcher;  		$submit	= (isset($_POST['submit']) || isset($_GET['add']) || isset($_GET['remove'])) ? true : false;  		$s_hidden_fields = ''; @@ -55,9 +54,22 @@ class ucp_zebra  					// Remove users  					if (!empty($data['usernames']))  					{ +						$user_ids = $data['usernames']; + +						/** +						* Remove users from friends/foes +						* +						* @event core.ucp_remove_zebra +						* @var	string	mode		Zebra type: friends|foes +						* @var	array	user_ids	User ids we remove +						* @since 3.1-A1 +						*/ +						$vars = array('user_ids'); +						extract($phpbb_dispatcher->trigger_event('core.ucp_remove_zebra', compact($vars))); +  						$sql = 'DELETE FROM ' . ZEBRA_TABLE . '  							WHERE user_id = ' . $user->data['user_id'] . ' -								AND ' . $db->sql_in_set('zebra_id', $data['usernames']); +								AND ' . $db->sql_in_set('zebra_id', $user_ids);  						$db->sql_query($sql);  						$updated = true; @@ -187,6 +199,19 @@ class ucp_zebra  										);  									} +									/** +									* Add users to friends/foes +									* +									* @event core.ucp_add_zebra +									* @var	string	mode		Zebra type: +									*							friends|foes +									* @var	array	sql_ary		Array of +									*							entries we add +									* @since 3.1-A1 +									*/ +									$vars = array('mode', 'sql_ary'); +									extract($phpbb_dispatcher->trigger_event('core.ucp_add_zebra', compact($vars))); +  									$db->sql_multi_insert(ZEBRA_TABLE, $sql_ary);  									$updated = true; @@ -199,8 +224,24 @@ class ucp_zebra  							}  						}  					} - -					if ($updated) +					 +					if ($request->is_ajax()) +					{ +						$message = ($updated) ? $user->lang[$l_mode . '_UPDATED'] : implode('<br />', $error); +						 +						$json_response = new phpbb_json_response; +						$json_response->send(array( +							'success' => $updated, +							 +							'MESSAGE_TITLE'	=> $user->lang['INFORMATION'], +							'MESSAGE_TEXT'	=> $message, +							'REFRESH_DATA'	=> array( +								'time'	=> 3, +								'url'		=> $this->u_action +							) +						)); +					} +					else if ($updated)  					{  						meta_refresh(3, $this->u_action);  						$message = $user->lang[$l_mode . '_UPDATED'] . '<br />' . implode('<br />', $error) . ((sizeof($error)) ? '<br />' : '') . '<br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>'); diff --git a/phpBB/includes/update_helpers.php b/phpBB/includes/update_helpers.php new file mode 100644 index 0000000000..69d678b2f8 --- /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+' . abs($offset);	//'[UTC - 12] Baker Island Time' +			case '-11': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 11] Niue Time, Samoa Standard Time' +			case '-10': +				return 'Etc/GMT+' . abs($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+' . abs($offset);	//'[UTC - 9] Alaska Standard Time, Gambier Island Time' +			case '-8': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 8] Pacific Standard Time' +			case '-7': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 7] Mountain Standard Time' +			case '-6': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 6] Central Standard Time' +			case '-5': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 5] Eastern Standard Time' +			case '-4.5': +				return 'America/Caracas';			//'[UTC - 4:30] Venezuelan Standard Time' +			case '-4': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 4] Atlantic Standard Time' +			case '-3.5': +				return 'America/St_Johns';			//'[UTC - 3:30] Newfoundland Standard Time' +			case '-3': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 3] Amazon Standard Time, Central Greenland Time' +			case '-2': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time' +			case '-1': +				return 'Etc/GMT+' . abs($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 new file mode 100644 index 0000000000..93557f3558 --- /dev/null +++ b/phpBB/includes/user.php @@ -0,0 +1,857 @@ +<?php +/** +* +* @package phpBB3 +* @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; +} + +/** +* Base user class +* +* This is the overarching class which contains (through session extend) +* all methods utilised for user functionality during a session. +* +* @package phpBB3 +*/ +class phpbb_user extends phpbb_session +{ +	var $lang = array(); +	var $help = array(); +	var $style = array(); +	var $date_format; + +	/** +	* DateTimeZone object holding the timezone of the user +	*/ +	public $timezone; + +	var $lang_name = false; +	var $lang_id = false; +	var $lang_path; +	var $img_lang; +	var $img_array = array(); + +	// Able to add new options (up to id 31) +	var $keyoptions = array('viewimg' => 0, 'viewflash' => 1, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'popuppm' => 10, 'sig_bbcode' => 15, 'sig_smilies' => 16, 'sig_links' => 17); + +	/** +	* Constructor to set the lang path +	*/ +	function __construct() +	{ +		global $phpbb_root_path; + +		$this->lang_path = $phpbb_root_path . 'language/'; +	} + +	/** +	* Function to set custom language path (able to use directory outside of phpBB) +	* +	* @param string $lang_path New language path used. +	* @access public +	*/ +	function set_custom_lang_path($lang_path) +	{ +		$this->lang_path = $lang_path; + +		if (substr($this->lang_path, -1) != '/') +		{ +			$this->lang_path .= '/'; +		} +	} + +	/** +	* Setup basic user-specific items (style, language, ...) +	*/ +	function setup($lang_set = false, $style_id = false) +	{ +		global $db, $phpbb_style, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache; +		global $phpbb_dispatcher; + +		if ($this->data['user_id'] != ANONYMOUS) +		{ +			$user_lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); +			$user_date_format = $this->data['user_dateformat']; +			$user_timezone = $this->data['user_timezone']; +		} +		else +		{ +			$user_lang_name = basename($config['default_lang']); +			$user_date_format = $config['default_dateformat']; +			$user_timezone = $config['board_timezone']; + +			/** +			* If a guest user is surfing, we try to guess his/her language first by obtaining the browser language +			* If re-enabled we need to make sure only those languages installed are checked +			* Commented out so we do not loose the code. + +			if ($request->header('Accept-Language')) +			{ +				$accept_lang_ary = explode(',', $request->header('Accept-Language')); + +				foreach ($accept_lang_ary as $accept_lang) +				{ +					// Set correct format ... guess full xx_YY form +					$accept_lang = substr($accept_lang, 0, 2) . '_' . strtoupper(substr($accept_lang, 3, 2)); +					$accept_lang = basename($accept_lang); + +					if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) +					{ +						$user_lang_name = $config['default_lang'] = $accept_lang; +						break; +					} +					else +					{ +						// No match on xx_YY so try xx +						$accept_lang = substr($accept_lang, 0, 2); +						$accept_lang = basename($accept_lang); + +						if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) +						{ +							$user_lang_name = $config['default_lang'] = $accept_lang; +							break; +						} +					} +				} +			} +			*/ +		} + +		$user_data = $this->data; + +		/** +		* Event to load language files and modify user data on every page +		* +		* @event core.user_setup +		* @var	array	user_data			Array with user's data row +		* @var	string	user_lang_name		Basename of the user's langauge +		* @var	string	user_date_format	User's date/time format +		* @var	string	user_timezone		User's timezone, should be one of +		*							http://www.php.net/manual/en/timezones.php +		* @var	mixed	lang_set			String or array of language files +		* @var	mixed	style_id			Style we are going to display +		* @since 3.1-A1 +		*/ +		$vars = array('user_data', 'user_lang_name', 'user_date_format', 'user_timezone', 'lang_set', 'style_id'); +		extract($phpbb_dispatcher->trigger_event('core.user_setup', compact($vars))); + +		$this->data = $user_data; +		$this->lang_name = $user_lang_name; +		$this->date_format = $user_date_format; + +		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; + +		// Do not suppress error if in DEBUG_EXTRA mode +		$include_result = (defined('DEBUG_EXTRA')) ? (include $this->lang_path . $this->lang_name . "/common.$phpEx") : (@include $this->lang_path . $this->lang_name . "/common.$phpEx"); + +		if ($include_result === false) +		{ +			die('Language file ' . $this->lang_path . $this->lang_name . "/common.$phpEx" . " couldn't be opened."); +		} + +		$this->add_lang($lang_set); +		unset($lang_set); + +		$style_request = request_var('style', 0); +		if ($style_request && $auth->acl_get('a_styles') && !defined('ADMIN_START')) +		{ +			global $SID, $_EXTRA_URL; + +			$style_id = $style_request; +			$SID .= '&style=' . $style_id; +			$_EXTRA_URL = array('style=' . $style_id); +		} +		else +		{ +			// Set up style +			$style_id = ($style_id) ? $style_id : ((!$config['override_user_style']) ? $this->data['user_style'] : $config['default_style']); +		} + +		$sql = 'SELECT * +			FROM ' . STYLES_TABLE . " s +			WHERE s.style_id = $style_id"; +		$result = $db->sql_query($sql, 3600); +		$this->style = $db->sql_fetchrow($result); +		$db->sql_freeresult($result); + +		// User has wrong style +		if (!$this->style && $style_id == $this->data['user_style']) +		{ +			$style_id = $this->data['user_style'] = $config['default_style']; + +			$sql = 'UPDATE ' . USERS_TABLE . " +				SET user_style = $style_id +				WHERE user_id = {$this->data['user_id']}"; +			$db->sql_query($sql); + +			$sql = 'SELECT * +				FROM ' . STYLES_TABLE . " s +				WHERE s.style_id = $style_id"; +			$result = $db->sql_query($sql, 3600); +			$this->style = $db->sql_fetchrow($result); +			$db->sql_freeresult($result); +		} + +		if (!$this->style) +		{ +			trigger_error('Could not get style data', E_USER_ERROR); +		} + +		// Now parse the cfg file and cache it +		$parsed_items = $cache->obtain_cfg_items($this->style); + +		$check_for = array( +			'pagination_sep'    => (string) ', ' +		); + +		foreach ($check_for as $key => $default_value) +		{ +			$this->style[$key] = (isset($parsed_items[$key])) ? $parsed_items[$key] : $default_value; +			settype($this->style[$key], gettype($default_value)); + +			if (is_string($default_value)) +			{ +				$this->style[$key] = htmlspecialchars($this->style[$key]); +			} +		} + +		$phpbb_style->set_style(); + +		$this->img_lang = $this->lang_name; + +		// Call phpbb_user_session_handler() in case external application want to "bend" some variables or replace classes... +		// After calling it we continue script execution... +		phpbb_user_session_handler(); + +		// If this function got called from the error handler we are finished here. +		if (defined('IN_ERROR_HANDLER')) +		{ +			return; +		} + +		// Disable board if the install/ directory is still present +		// For the brave development army we do not care about this, else we need to comment out this everytime we develop locally +		if (!defined('DEBUG_EXTRA') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install') && !is_file($phpbb_root_path . 'install')) +		{ +			// Adjust the message slightly according to the permissions +			if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) +			{ +				$message = 'REMOVE_INSTALL'; +			} +			else +			{ +				$message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; +			} +			trigger_error($message); +		} + +		// Is board disabled and user not an admin or moderator? +		if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) +		{ +			if ($this->data['is_bot']) +			{ +				send_status_line(503, 'Service Unavailable'); +			} + +			$message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; +			trigger_error($message); +		} + +		// Is load exceeded? +		if ($config['limit_load'] && $this->load !== false) +		{ +			if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !defined('IN_ADMIN')) +			{ +				// Set board disabled to true to let the admins/mods get the proper notification +				$config['board_disable'] = '1'; + +				if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) +				{ +					if ($this->data['is_bot']) +					{ +						send_status_line(503, 'Service Unavailable'); +					} +					trigger_error('BOARD_UNAVAILABLE'); +				} +			} +		} + +		if (isset($this->data['session_viewonline'])) +		{ +			// Make sure the user is able to hide his session +			if (!$this->data['session_viewonline']) +			{ +				// Reset online status if not allowed to hide the session... +				if (!$auth->acl_get('u_hideonline')) +				{ +					$sql = 'UPDATE ' . SESSIONS_TABLE . ' +						SET session_viewonline = 1 +						WHERE session_user_id = ' . $this->data['user_id']; +					$db->sql_query($sql); +					$this->data['session_viewonline'] = 1; +				} +			} +			else if (!$this->data['user_allow_viewonline']) +			{ +				// the user wants to hide and is allowed to  -> cloaking device on. +				if ($auth->acl_get('u_hideonline')) +				{ +					$sql = 'UPDATE ' . SESSIONS_TABLE . ' +						SET session_viewonline = 0 +						WHERE session_user_id = ' . $this->data['user_id']; +					$db->sql_query($sql); +					$this->data['session_viewonline'] = 0; +				} +			} +		} + + +		// Does the user need to change their password? If so, redirect to the +		// ucp profile reg_details page ... of course do not redirect if we're already in the ucp +		if (!defined('IN_ADMIN') && !defined('ADMIN_START') && $config['chg_passforce'] && !empty($this->data['is_registered']) && $auth->acl_get('u_chgpasswd') && $this->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400)) +		{ +			if (strpos($this->page['query_string'], 'mode=reg_details') === false && $this->page['page_name'] != "ucp.$phpEx") +			{ +				redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=profile&mode=reg_details')); +			} +		} + +		return; +	} + +	/** +	* More advanced language substitution +	* Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms. +	* Params are the language key and the parameters to be substituted. +	* This function/functionality is inspired by SHS` and Ashe. +	* +	* Example call: <samp>$user->lang('NUM_POSTS_IN_QUEUE', 1);</samp> +	* +	* If the first parameter is an array, the elements are used as keys and subkeys to get the language entry: +	* Example: <samp>$user->lang(array('datetime', 'AGO'), 1)</samp> uses $user->lang['datetime']['AGO'] as language entry. +	*/ +	function lang() +	{ +		$args = func_get_args(); +		$key = $args[0]; + +		if (is_array($key)) +		{ +			$lang = &$this->lang[array_shift($key)]; + +			foreach ($key as $_key) +			{ +				$lang = &$lang[$_key]; +			} +		} +		else +		{ +			$lang = &$this->lang[$key]; +		} + +		// Return if language string does not exist +		if (!isset($lang) || (!is_string($lang) && !is_array($lang))) +		{ +			return $key; +		} + +		// If the language entry is a string, we simply mimic sprintf() behaviour +		if (is_string($lang)) +		{ +			if (sizeof($args) == 1) +			{ +				return $lang; +			} + +			// Replace key with language entry and simply pass along... +			$args[0] = $lang; +			return call_user_func_array('sprintf', $args); +		} +		else if (sizeof($lang) == 0) +		{ +			// If the language entry is an empty array, we just return the language key +			return $args[0]; +		} + +		// It is an array... now handle different nullar/singular/plural forms +		$key_found = false; + +		// We now get the first number passed and will select the key based upon this number +		for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++) +		{ +			if (is_int($args[$i]) || is_float($args[$i])) +			{ +				if ($args[$i] == 0 && isset($lang[0])) +				{ +					// We allow each translation using plural forms to specify a version for the case of 0 things, +					// so that "0 users" may be displayed as "No users". +					$key_found = 0; +					break; +				} +				else +				{ +					$use_plural_form = $this->get_plural_form($args[$i]); +					if (isset($lang[$use_plural_form])) +					{ +						// The key we should use exists, so we use it. +						$key_found = $use_plural_form; +					} +					else +					{ +						// If the key we need to use does not exist, we fall back to the previous one. +						$numbers = array_keys($lang); + +						foreach ($numbers as $num) +						{ +							if ($num > $use_plural_form) +							{ +								break; +							} + +							$key_found = $num; +						} +					} +					break; +				} +			} +		} + +		// Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) +		if ($key_found === false) +		{ +			$numbers = array_keys($lang); +			$key_found = end($numbers); +		} + +		// Use the language string we determined and pass it to sprintf() +		$args[0] = $lang[$key_found]; +		return call_user_func_array('sprintf', $args); +	} + +	/** +	* Determine which plural form we should use. +	* For some languages this is not as simple as for English. +	* +	* @param $number        int|float   The number we want to get the plural case for. Float numbers are floored. +	* @param $force_rule    mixed   False to use the plural rule of the language package +	*                               or an integer to force a certain plural rule +	* @return   int     The plural-case we need to use for the number plural-rule combination +	*/ +	function get_plural_form($number, $force_rule = false) +	{ +		$number = (int) $number; + +		// Default to English system +		$plural_rule = ($force_rule !== false) ? $force_rule : ((isset($this->lang['PLURAL_RULE'])) ? $this->lang['PLURAL_RULE'] : 1); + +		return phpbb_get_plural_form($plural_rule, $number); +	} + +	/** +	* Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion) +	* +	* @param mixed $lang_set specifies the language entries to include +	* @param bool $use_db internal variable for recursion, do not use +	* @param bool $use_help internal variable for recursion, do not use +	* @param string $ext_name The extension to load language from, or empty for core files +	* +	* Examples: +	* <code> +	* $lang_set = array('posting', 'help' => 'faq'); +	* $lang_set = array('posting', 'viewtopic', 'help' => array('bbcode', 'faq')) +	* $lang_set = array(array('posting', 'viewtopic'), 'help' => array('bbcode', 'faq')) +	* $lang_set = 'posting' +	* $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting')) +	* </code> +	*/ +	function add_lang($lang_set, $use_db = false, $use_help = false, $ext_name = '') +	{ +		global $phpEx; + +		if (is_array($lang_set)) +		{ +			foreach ($lang_set as $key => $lang_file) +			{ +				// Please do not delete this line. +				// We have to force the type here, else [array] language inclusion will not work +				$key = (string) $key; + +				if ($key == 'db') +				{ +					$this->add_lang($lang_file, true, $use_help, $ext_name); +				} +				else if ($key == 'help') +				{ +					$this->add_lang($lang_file, $use_db, true, $ext_name); +				} +				else if (!is_array($lang_file)) +				{ +					$this->set_lang($this->lang, $this->help, $lang_file, $use_db, $use_help, $ext_name); +				} +				else +				{ +					$this->add_lang($lang_file, $use_db, $use_help, $ext_name); +				} +			} +			unset($lang_set); +		} +		else if ($lang_set) +		{ +			$this->set_lang($this->lang, $this->help, $lang_set, $use_db, $use_help, $ext_name); +		} +	} + +	/** +	* Add Language Items from an extension - use_db and use_help are assigned where needed (only use them to force inclusion) +	* +	* @param string $ext_name The extension to load language from, or empty for core files +	* @param mixed $lang_set specifies the language entries to include +	* @param bool $use_db internal variable for recursion, do not use +	* @param bool $use_help internal variable for recursion, do not use +	*/ +	function add_lang_ext($ext_name, $lang_set, $use_db = false, $use_help = false) +	{ +		if ($ext_name === '/') +		{ +			$ext_name = ''; +		} + +		$this->add_lang($lang_set, $use_db, $use_help, $ext_name); +	} + +	/** +	* Set language entry (called by add_lang) +	* @access private +	*/ +	function set_lang(&$lang, &$help, $lang_file, $use_db = false, $use_help = false, $ext_name = '') +	{ +		global $phpbb_root_path, $phpEx; + +		// Make sure the language name is set (if the user setup did not happen it is not set) +		if (!$this->lang_name) +		{ +			global $config; +			$this->lang_name = basename($config['default_lang']); +		} + +		// $lang == $this->lang +		// $help == $this->help +		// - add appropriate variables here, name them as they are used within the language file... +		if (!$use_db) +		{ +			if ($use_help && strpos($lang_file, '/') !== false) +			{ +				$filename = dirname($lang_file) . '/help_' . basename($lang_file); +			} +			else +			{ +				$filename = (($use_help) ? 'help_' : '') . $lang_file; +			} + +			if ($ext_name) +			{ +				global $phpbb_extension_manager; +				$ext_path = $phpbb_extension_manager->get_extension_path($ext_name, true); + +				$lang_path = $ext_path . 'language/'; +			} +			else +			{ +				$lang_path = $this->lang_path; +			} + +			if (strpos($phpbb_root_path . $filename, $lang_path . $this->lang_name . '/') === 0) +			{ +				$language_filename = $phpbb_root_path . $filename; +			} +			else +			{ +				$language_filename = $lang_path . $this->lang_name . '/' . $filename . '.' . $phpEx; +			} + +			if (!file_exists($language_filename)) +			{ +				global $config; + +				if ($this->lang_name == 'en') +				{ +					// The user's selected language is missing the file, the board default's language is missing the file, and the file doesn't exist in /en. +					$language_filename = str_replace($lang_path . 'en', $lang_path . $this->data['user_lang'], $language_filename); +					trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); +				} +				else if ($this->lang_name == basename($config['default_lang'])) +				{ +					// Fall back to the English Language +					$this->lang_name = 'en'; +					$this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); +				} +				else if ($this->lang_name == $this->data['user_lang']) +				{ +					// Fall back to the board default language +					$this->lang_name = basename($config['default_lang']); +					$this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); +				} + +				// Reset the lang name +				$this->lang_name = (file_exists($lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); +				return; +			} + +			// Do not suppress error if in DEBUG_EXTRA mode +			$include_result = (defined('DEBUG_EXTRA')) ? (include $language_filename) : (@include $language_filename); + +			if ($include_result === false) +			{ +				trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); +			} +		} +		else if ($use_db) +		{ +			// Get Database Language Strings +			// Put them into $lang if nothing is prefixed, put them into $help if help: is prefixed +			// For example: help:faq, posting +		} +	} + +	/** +	* Format user date +	* +	* @param int $gmepoch unix timestamp +	* @param string $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i. +	* @param bool $forcedate force non-relative date format. +	* +	* @return mixed translated date +	*/ +	function format_date($gmepoch, $format = false, $forcedate = false) +	{ +		static $utc; + +		if (!isset($utc)) +		{ +			$utc = new DateTimeZone('UTC'); +		} + +		$time = new phpbb_datetime($this, "@$gmepoch", $utc); +		$time->setTimezone($this->timezone); + +		return $time->format($format, $forcedate); +	} + +	/** +	* 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); +	} + +	/** +	* 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; +	} + +	/** +	* Get language id currently used by the user +	*/ +	function get_iso_lang_id() +	{ +		global $config, $db; + +		if (!empty($this->lang_id)) +		{ +			return $this->lang_id; +		} + +		if (!$this->lang_name) +		{ +			$this->lang_name = $config['default_lang']; +		} + +		$sql = 'SELECT lang_id +			FROM ' . LANG_TABLE . " +			WHERE lang_iso = '" . $db->sql_escape($this->lang_name) . "'"; +		$result = $db->sql_query($sql); +		$this->lang_id = (int) $db->sql_fetchfield('lang_id'); +		$db->sql_freeresult($result); + +		return $this->lang_id; +	} + +	/** +	* Get users profile fields +	*/ +	function get_profile_fields($user_id) +	{ +		global $db; + +		if (isset($this->profile_fields)) +		{ +			return; +		} + +		$sql = 'SELECT * +			FROM ' . PROFILE_FIELDS_DATA_TABLE . " +			WHERE user_id = $user_id"; +		$result = $db->sql_query_limit($sql, 1); +		$this->profile_fields = (!($row = $db->sql_fetchrow($result))) ? array() : $row; +		$db->sql_freeresult($result); +	} + +	/** +	* Specify/Get image +	*/ +	function img($img, $alt = '') +	{ +		$alt = (!empty($this->lang[$alt])) ? $this->lang[$alt] : $alt; +		return '<span class="imageset ' . $img . '">' . $alt . '</span>'; +	} + +	/** +	* Get option bit field from user options. +	* +	* @param int $key option key, as defined in $keyoptions property. +	* @param int $data bit field value to use, or false to use $this->data['user_options'] +	* @return bool true if the option is set in the bit field, false otherwise +	*/ +	function optionget($key, $data = false) +	{ +		$var = ($data !== false) ? $data : $this->data['user_options']; +		return phpbb_optionget($this->keyoptions[$key], $var); +	} + +	/** +	* Set option bit field for user options. +	* +	* @param int $key Option key, as defined in $keyoptions property. +	* @param bool $value True to set the option, false to clear the option. +	* @param int $data Current bit field value, or false to use $this->data['user_options'] +	* @return int|bool If $data is false, the bit field is modified and +	*                  written back to $this->data['user_options'], and +	*                  return value is true if the bit field changed and +	*                  false otherwise. If $data is not false, the new +	*                  bitfield value is returned. +	*/ +	function optionset($key, $value, $data = false) +	{ +		$var = ($data !== false) ? $data : $this->data['user_options']; + +		$new_var = phpbb_optionset($this->keyoptions[$key], $value, $var); + +		if ($data === false) +		{ +			if ($new_var != $var) +			{ +				$this->data['user_options'] = $new_var; +				return true; +			} +			else +			{ +				return false; +			} +		} +		else +		{ +			return $new_var; +		} +	} + +	/** +	* Funtion to make the user leave the NEWLY_REGISTERED system group. +	* @access public +	*/ +	function leave_newly_registered() +	{ +		global $db; + +		if (empty($this->data['user_new'])) +		{ +			return false; +		} + +		if (!function_exists('remove_newly_registered')) +		{ +			global $phpbb_root_path, $phpEx; + +			include($phpbb_root_path . 'includes/functions_user.' . $phpEx); +		} +		if ($group = remove_newly_registered($this->data['user_id'], $this->data)) +		{ +			$this->data['group_id'] = $group; + +		} +		$this->data['user_permissions'] = ''; +		$this->data['user_new'] = 0; + +		return true; +	} + +	/** +	* Returns all password protected forum ids the user is currently NOT authenticated for. +	* +	* @return array     Array of forum ids +	* @access public +	*/ +	function get_passworded_forums() +	{ +		global $db; + +		$sql = 'SELECT f.forum_id, fa.user_id +			FROM ' . FORUMS_TABLE . ' f +			LEFT JOIN ' . FORUMS_ACCESS_TABLE . " fa +				ON (fa.forum_id = f.forum_id +					AND fa.session_id = '" . $db->sql_escape($this->session_id) . "') +			WHERE f.forum_password <> ''"; +		$result = $db->sql_query($sql); + +		$forum_ids = array(); +		while ($row = $db->sql_fetchrow($result)) +		{ +			$forum_id = (int) $row['forum_id']; + +			if ($row['user_id'] != $this->data['user_id']) +			{ +				$forum_ids[$forum_id] = $forum_id; +			} +		} +		$db->sql_freeresult($result); + +		return $forum_ids; +	} +} diff --git a/phpBB/includes/utf/utf_normalizer.php b/phpBB/includes/utf/utf_normalizer.php index 52cdf85827..a208552d53 100644 --- a/phpBB/includes/utf/utf_normalizer.php +++ b/phpBB/includes/utf/utf_normalizer.php @@ -2,9 +2,8 @@  /**  *  * @package utf -* @version $Id$  * @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ diff --git a/phpBB/includes/utf/utf_tools.php b/phpBB/includes/utf/utf_tools.php index 65d40c0fd3..c402e15032 100644 --- a/phpBB/includes/utf/utf_tools.php +++ b/phpBB/includes/utf/utf_tools.php @@ -2,9 +2,8 @@  /**  *  * @package utf -* @version $Id$  * @copyright (c) 2006 phpBB Group -* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2  *  */ @@ -1934,7 +1933,7 @@ function utf8_wordwrap($string, $width = 75, $break = "\n", $cut = false)  * UTF8-safe basename() function  *  * basename() has some limitations and is dependent on the locale setting -* according to the PHP manual. Therefore we provide our own locale independant +* according to the PHP manual. Therefore we provide our own locale independent  * basename function.  *  * @param string $filename The filename basename() should be applied to  | 
