diff options
Diffstat (limited to 'phpBB/includes')
115 files changed, 7991 insertions, 1268 deletions
diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index 3433d7e2cd..9d6c2d5de1 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -98,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', @@ -917,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(); @@ -1074,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 { @@ -1163,7 +1163,7 @@ class acp_attachments $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 @@ -1224,7 +1224,7 @@ class acp_attachments $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), diff --git a/phpBB/includes/acp/acp_ban.php b/phpBB/includes/acp/acp_ban.php index d275c7f10c..3ed9c225f5 100644 --- a/phpBB/includes/acp/acp_ban.php +++ b/phpBB/includes/acp/acp_ban.php @@ -97,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, @@ -118,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; diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 575d05933f..322e1c55d8 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -53,6 +53,8 @@ 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), diff --git a/phpBB/includes/acp/acp_captcha.php b/phpBB/includes/acp/acp_captcha.php index 51b5dd3301..c7c64ae56b 100644 --- a/phpBB/includes/acp/acp_captcha.php +++ b/phpBB/includes/acp/acp_captcha.php @@ -29,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']; 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 3a3b2021eb..c6dbf5eb9c 100644 --- a/phpBB/includes/acp/acp_forums.php +++ b/phpBB/includes/acp/acp_forums.php @@ -25,7 +25,7 @@ class acp_forums function main($id, $mode) { - global $db, $user, $auth, $template, $cache, $request; + global $db, $user, $auth, $template, $cache, $request, $phpbb_dispatcher; global $config, $phpbb_admin_path, $phpbb_root_path, $phpEx; $user->add_lang('acp/forums'); @@ -150,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') { @@ -386,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') { @@ -453,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, @@ -582,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, @@ -647,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; @@ -872,10 +928,22 @@ 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(); + /** + * 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']; @@ -968,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']); @@ -1239,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; } @@ -1247,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(); @@ -1259,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; @@ -1342,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 f88fa76df1..b604e20094 100644 --- a/phpBB/includes/acp/acp_groups.php +++ b/phpBB/includes/acp/acp_groups.php @@ -26,6 +26,7 @@ class acp_groups { global $config, $db, $user, $auth, $template, $cache; global $phpbb_root_path, $phpbb_admin_path, $phpEx, $table_prefix, $file_uploads; + global $request; $user->add_lang('acp/groups'); $this->tpl_name = 'acp_groups'; @@ -323,7 +324,8 @@ class acp_groups $submit_ary['founder_manage'] = isset($_REQUEST['group_founder_manage']) ? 1 : 0; } - if (!empty($_FILES['uploadfile']['tmp_name']) || $data['uploadurl'] || $data['remotelink']) + $uploadfile = $request->file('uploadfile'); + if (!empty($uploadfile['tmp_name']) || $data['uploadurl'] || $data['remotelink']) { // Avatar stuff $var_ary = array( @@ -337,7 +339,7 @@ class acp_groups { $data['user_id'] = "g$group_id"; - if ((!empty($_FILES['uploadfile']['tmp_name']) || $data['uploadurl']) && $can_upload) + if ((!empty($uploadfile['tmp_name']) || $data['uploadurl']) && $can_upload) { list($submit_ary['avatar_type'], $submit_ary['avatar'], $submit_ary['avatar_width'], $submit_ary['avatar_height']) = avatar_upload($data, $error); } @@ -683,8 +685,8 @@ class acp_groups } $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); - + 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, diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index b7be92d477..db4b4263b0 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -927,8 +927,8 @@ class acp_icons } } $db->sql_freeresult($result); - - phpbb_generate_template_pagination($template, $this->u_action, 'pagination', 'start', $item_count, $config['smilies_per_page'], $pagination_start); + + 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 1e23c2e6cf..e61115f681 100644 --- a/phpBB/includes/acp/acp_inactive.php +++ b/phpBB/includes/acp/acp_inactive.php @@ -155,10 +155,7 @@ class acp_inactive trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); } - foreach ($mark as $user_id) - { - user_delete('retain', $user_id, $user_affected[$user_id]); - } + user_delete('retain', $mark, true); add_log('admin', 'LOG_INACTIVE_' . strtoupper($action), implode(', ', $user_affected)); @@ -289,8 +286,8 @@ class acp_inactive } $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); - + 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), @@ -299,7 +296,6 @@ class acp_inactive 'S_SORT_KEY' => $s_sort_key, 'S_SORT_DIR' => $s_sort_dir, 'S_ON_PAGE' => phpbb_on_page($template, $user, $base_url, $inactive_count, $per_page, $start), - 'USERS_PER_PAGE' => $per_page, 'U_ACTION' => $this->u_action . "&$u_sort_param&users_per_page=$per_page&start=$start", diff --git a/phpBB/includes/acp/acp_language.php b/phpBB/includes/acp/acp_language.php index 2b19f93c75..2be1ccfc41 100644 --- a/phpBB/includes/acp/acp_language.php +++ b/phpBB/includes/acp/acp_language.php @@ -100,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: @@ -404,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) { diff --git a/phpBB/includes/acp/acp_logs.php b/phpBB/includes/acp/acp_logs.php index 4538633d6c..d86521532c 100644 --- a/phpBB/includes/acp/acp_logs.php +++ b/phpBB/includes/acp/acp_logs.php @@ -131,7 +131,7 @@ class acp_logs $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, diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index eb613535bf..d419bc3b99 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -426,7 +426,7 @@ class acp_main // Version check $user->add_lang('install'); - if ($auth->acl_get('a_server') && version_compare(PHP_VERSION, '5.3.2', '<')) + if ($auth->acl_get('a_server') && version_compare(PHP_VERSION, '5.3.3', '<')) { $template->assign_vars(array( 'S_PHP_VERSION_OLD' => true, diff --git a/phpBB/includes/acp/acp_permissions.php b/phpBB/includes/acp/acp_permissions.php index d728744c04..dd071074de 100644 --- a/phpBB/includes/acp/acp_permissions.php +++ b/phpBB/includes/acp/acp_permissions.php @@ -497,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)) ); } diff --git a/phpBB/includes/acp/acp_profile.php b/phpBB/includes/acp/acp_profile.php index 3ffffd3047..849160f1fa 100644 --- a/phpBB/includes/acp/acp_profile.php +++ b/phpBB/includes/acp/acp_profile.php @@ -383,6 +383,7 @@ 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, @@ -399,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_pm', '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') ); @@ -424,6 +425,7 @@ 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', @@ -779,6 +781,7 @@ 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, @@ -1096,6 +1099,7 @@ 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'], diff --git a/phpBB/includes/acp/acp_prune.php b/phpBB/includes/acp/acp_prune.php index 240e3596ba..a5dc02849a 100644 --- a/phpBB/includes/acp/acp_prune.php +++ b/phpBB/includes/acp/acp_prune.php @@ -242,8 +242,8 @@ class acp_prune if (confirm_box(true)) { $user_ids = $usernames = array(); - $this->get_prune_users($user_ids, $usernames); + $this->get_prune_users($user_ids, $usernames); if (sizeof($user_ids)) { if ($action == 'deactivate') @@ -255,19 +255,13 @@ class acp_prune { if ($deleteposts) { - foreach ($user_ids as $user_id) - { - user_delete('remove', $user_id); - } + user_delete('remove', $user_ids); $l_log = 'LOG_PRUNE_USER_DEL_DEL'; } else { - foreach ($user_ids as $user_id) - { - user_delete('retain', $user_id, $usernames[$user_id]); - } + user_delete('retain', $user_ids, true); $l_log = 'LOG_PRUNE_USER_DEL_ANON'; } @@ -299,6 +293,7 @@ class acp_prune { $template->assign_block_vars('users', array( 'USERNAME' => $usernames[$user_id], + 'USER_ID' => $user_id, 'U_PROFILE' => append_sid($phpbb_root_path . 'memberlist.' . $phpEx, 'mode=viewprofile&u=' . $user_id), 'U_USER_ADMIN' => ($auth->acl_get('a_user')) ? append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=overview&u=' . $user_id, true, $user->session_id) : '', )); @@ -314,17 +309,7 @@ class acp_prune 'mode' => $mode, 'prune' => 1, - 'users' => utf8_normalize_nfc(request_var('users', '', true)), - 'username' => utf8_normalize_nfc(request_var('username', '', true)), - 'email' => request_var('email', ''), - 'joined_select' => request_var('joined_select', ''), - 'joined' => request_var('joined', ''), - 'active_select' => request_var('active_select', ''), - 'active' => request_var('active', ''), - 'count_select' => request_var('count_select', ''), - 'count' => request_var('count', ''), 'deleteposts' => request_var('deleteposts', 0), - 'action' => request_var('action', ''), )), 'confirm_body_prune.html'); } @@ -340,22 +325,29 @@ class acp_prune } $find_time = array('lt' => $user->lang['BEFORE'], 'gt' => $user->lang['AFTER']); - $s_find_join_time = ''; - foreach ($find_time as $key => $value) - { - $s_find_join_time .= '<option value="' . $key . '">' . $value . '</option>'; - } - $s_find_active_time = ''; foreach ($find_time as $key => $value) { $s_find_active_time .= '<option value="' . $key . '">' . $value . '</option>'; } + $s_group_list = ''; + $sql = 'SELECT group_id, group_name + FROM ' . GROUPS_TABLE . ' + WHERE group_type <> ' . GROUP_SPECIAL . ' + ORDER BY group_name ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $s_group_list .= '<option value="' . $row['group_id'] . '">' . $row['group_name'] . '</select>'; + } + $db->sql_freeresult($result); + $template->assign_vars(array( 'U_ACTION' => $this->u_action, - 'S_JOINED_OPTIONS' => $s_find_join_time, 'S_ACTIVE_OPTIONS' => $s_find_active_time, + 'S_GROUP_LIST' => $s_group_list, 'S_COUNT_OPTIONS' => $s_find_count, 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=acp_prune&field=users'), )); @@ -368,33 +360,70 @@ class acp_prune { global $user, $db; - $users = utf8_normalize_nfc(request_var('users', '', true)); - - if ($users) + $users_by_name = request_var('users', '', true); + $users_by_id = request_var('user_ids', array(0)); + $group_id = request_var('group_id', 0); + $posts_on_queue = request_var('posts_on_queue', 0); + + if ($users_by_name) { - $users = explode("\n", $users); + $users = explode("\n", $users_by_name); $where_sql = ' AND ' . $db->sql_in_set('username_clean', array_map('utf8_clean_string', $users)); } + else if (!empty($users_by_id)) + { + $user_ids = $users_by_id; + user_get_id_name($user_ids, $usernames); + + $where_sql = ' AND ' . $db->sql_in_set('user_id', $user_ids); + } else { - $username = utf8_normalize_nfc(request_var('username', '', true)); + $username = request_var('username', '', true); $email = request_var('email', ''); + $website = request_var('website', ''); - $joined_select = request_var('joined_select', 'lt'); $active_select = request_var('active_select', 'lt'); $count_select = request_var('count_select', 'eq'); - $joined = request_var('joined', ''); + $queue_select = request_var('queue_select', 'gt'); + $joined_before = request_var('joined_before', ''); + $joined_after = request_var('joined_after', ''); $active = request_var('active', ''); + $count = request_var('count', 0); + $active = ($active) ? explode('-', $active) : array(); - $joined = ($joined) ? explode('-', $joined) : array(); + $joined_before = ($joined_before) ? explode('-', $joined_before) : array(); + $joined_after = ($joined_after) ? explode('-', $joined_after) : array(); - if ((sizeof($active) && sizeof($active) != 3) || (sizeof($joined) && sizeof($joined) != 3)) + // calculate the conditions required by the join time criteria + $joined_sql = ''; + if (!empty($joined_before) && !empty($joined_after)) { - trigger_error($user->lang['WRONG_ACTIVE_JOINED_DATE'] . adm_back_link($this->u_action), E_USER_WARNING); + // if the two entered dates are equal, we need to adjust + // so that our time range is a full day instead of 1 second + if ($joined_after == $joined_before) + { + $joined_after[2] += 1; + } + + $joined_sql = ' AND user_regdate BETWEEN ' . gmmktime(0, 0, 0, (int) $joined_after[1], (int) $joined_after[2], (int) $joined_after[0]) . + ' AND ' . gmmktime(0, 0, 0, (int) $joined_before[1], (int) $joined_before[2], (int) $joined_before[0]); } + else if (empty($joined_before) && !empty($joined_after)) + { + $joined_sql = ' AND user_regdate > ' . gmmktime(0, 0, 0, (int) $joined_after[1], (int) $joined_after[2], (int) $joined_after[0]); + } + else if (empty($joined_after) && !empty($joined_before)) + { + $joined_sql = ' AND user_regdate < ' . gmmktime(0, 0, 0, (int) $joined_before[1], (int) $joined_before[2], (int) $joined_before[0]); + } + // implicit else when both arrays are empty do nothing - $count = request_var('count', ''); + if ((sizeof($active) && sizeof($active) != 3) || (sizeof($joined_before) && sizeof($joined_before) != 3) || (sizeof($joined_after) && sizeof($joined_after) != 3)) + { + trigger_error($user->lang['WRONG_ACTIVE_JOINED_DATE'] . adm_back_link($this->u_action), E_USER_WARNING); + } $key_match = array('lt' => '<', 'gt' => '>', 'eq' => '='); $sort_by_types = array('username', 'user_email', 'user_posts', 'user_regdate', 'user_lastvisit'); @@ -402,8 +431,9 @@ class acp_prune $where_sql = ''; $where_sql .= ($username) ? ' AND username_clean ' . $db->sql_like_expression(str_replace('*', $db->any_char, utf8_clean_string($username))) : ''; $where_sql .= ($email) ? ' AND user_email ' . $db->sql_like_expression(str_replace('*', $db->any_char, $email)) . ' ' : ''; - $where_sql .= (sizeof($joined)) ? " AND user_regdate " . $key_match[$joined_select] . ' ' . gmmktime(0, 0, 0, (int) $joined[1], (int) $joined[2], (int) $joined[0]) : ''; - $where_sql .= ($count !== '') ? " AND user_posts " . $key_match[$count_select] . ' ' . (int) $count . ' ' : ''; + $where_sql .= ($website) ? ' AND user_website ' . $db->sql_like_expression(str_replace('*', $db->any_char, $website)) . ' ' : ''; + $where_sql .= $joined_sql; + $where_sql .= ($count) ? " AND user_posts " . $key_match[$count_select] . ' ' . (int) $count . ' ' : ''; // First handle pruning of users who never logged in, last active date is 0000-00-00 if (sizeof($active) && (int) $active[0] == 0 && (int) $active[1] == 0 && (int) $active[2] == 0) @@ -446,7 +476,6 @@ class acp_prune $where_sql"; $result = $db->sql_query($sql); - $where_sql = ''; $user_ids = $usernames = array(); while ($row = $db->sql_fetchrow($result)) @@ -459,5 +488,53 @@ class acp_prune } } $db->sql_freeresult($result); + + if ($group_id) + { + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . ' + WHERE group_id = ' . (int) $group_id . ' + AND user_pending = 0 + AND ' . $db->sql_in_set('user_id', $user_ids, false, true); + $result = $db->sql_query($sql); + + // we're performing an intersection operation, so all the relevant users + // come from this most recent query (which was limited to the results of the + // previous query) + $user_ids = $usernames = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_ids[] = $row['poster_id']; + } + $db->sql_freeresult($result); + + // only get usernames if they are needed (not part of some later query) + if (!$posts_on_queue) + { + // this is an additional query aginst the users table + user_get_id_name($user_ids, $usernames); + } + } + + if ($posts_on_queue) + { + $sql = 'SELECT poster_id, COUNT(post_id) AS queue_posts + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('poster_id', $user_ids, false, true) . ' + GROUP BY poster_id + HAVING queue_posts ' . $key_match[$queue_select] . ' ' . $posts_on_queue; + $result = $db->sql_query($result); + + // same intersection logic as the above group ID portion + $user_ids = $usernames = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_ids[] = $row['poster_id']; + } + $db->sql_freeresult($result); + + // do an additional query to get the correct set of usernames + user_get_id_name($user_ids, $usernames); + } } } diff --git a/phpBB/includes/acp/acp_reasons.php b/phpBB/includes/acp/acp_reasons.php index 479fcfba81..71e9108c2c 100644 --- a/phpBB/includes/acp/acp_reasons.php +++ b/phpBB/includes/acp/acp_reasons.php @@ -113,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'], @@ -171,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, @@ -303,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 54a3e7aaa1..6618e2c3f9 100644 --- a/phpBB/includes/acp/acp_search.php +++ b/phpBB/includes/acp/acp_search.php @@ -596,16 +596,16 @@ class acp_search */ function init_search($type, &$search, &$error) { - global $phpbb_root_path, $phpEx, $user; + global $phpbb_root_path, $phpEx, $user, $auth, $config, $db; - if (!class_exists($type) || !method_exists($type, 'get_name')) + 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_styles.php b/phpBB/includes/acp/acp_styles.php index d41ef571dd..db77825ae7 100644 --- a/phpBB/includes/acp/acp_styles.php +++ b/phpBB/includes/acp/acp_styles.php @@ -40,7 +40,7 @@ class acp_styles public function main($id, $mode) { - global $db, $user, $phpbb_admin_path, $phpbb_root_path, $php_ext, $template, $request, $cache, $auth, $config; + global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config; $this->db = $db; $this->user = $user; @@ -50,12 +50,12 @@ class acp_styles $this->auth = $auth; $this->config = $config; $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; + $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.$php_ext", "i={$id}"); + $this->u_base_action = append_sid("{$phpbb_admin_path}index.{$this->php_ext}", "i={$id}"); $this->s_hidden_fields = array( 'mode' => $mode, ); diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 79c91dd7ee..82d8ef5cbb 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -32,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'; @@ -749,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 @@ -756,8 +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'])), - '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 @@ -855,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; @@ -945,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 @@ -965,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 = ''; @@ -1122,7 +1159,7 @@ class acp_users $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' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), @@ -1943,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 @@ -2038,7 +2075,7 @@ class acp_users $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' => phpbb_on_page($template, $user, $base_url, $num_attachments, $config['topics_per_page'], $start), diff --git a/phpBB/includes/acp/auth.php b/phpBB/includes/acp/auth.php index 7d9fd267ff..6b1da46a12 100644 --- a/phpBB/includes/acp/auth.php +++ b/phpBB/includes/acp/auth.php @@ -529,8 +529,8 @@ class auth_admin extends phpbb_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_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/auth/auth_db.php b/phpBB/includes/auth/auth_db.php index 76790e4dad..ac944532a5 100644 --- a/phpBB/includes/auth/auth_db.php +++ b/phpBB/includes/auth/auth_db.php @@ -41,6 +41,10 @@ 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) { diff --git a/phpBB/includes/auth/auth_ldap.php b/phpBB/includes/auth/auth_ldap.php index 26029efe1e..24823f9ce7 100644 --- a/phpBB/includes/auth/auth_ldap.php +++ b/phpBB/includes/auth/auth_ldap.php @@ -309,35 +309,35 @@ function acp_ldap(&$new) $tpl = ' <dl> - <dt><label for="ldap_server">' . $user->lang['LDAP_SERVER'] . ':</label><br /><span>' . $user->lang['LDAP_SERVER_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_server">' . $user->lang['LDAP_SERVER'] . $user->lang['COLON'] . '</label><br /><span>' . $user->lang['LDAP_SERVER_EXPLAIN'] . '</span></dt> <dd><input type="text" id="ldap_server" size="40" name="config[ldap_server]" value="' . $new['ldap_server'] . '" /></dd> </dl> <dl> - <dt><label for="ldap_port">' . $user->lang['LDAP_PORT'] . ':</label><br /><span>' . $user->lang['LDAP_PORT_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_port">' . $user->lang['LDAP_PORT'] . $user->lang['COLON'] . '</label><br /><span>' . $user->lang['LDAP_PORT_EXPLAIN'] . '</span></dt> <dd><input type="text" id="ldap_port" size="40" name="config[ldap_port]" value="' . $new['ldap_port'] . '" /></dd> </dl> <dl> - <dt><label for="ldap_dn">' . $user->lang['LDAP_DN'] . ':</label><br /><span>' . $user->lang['LDAP_DN_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_dn">' . $user->lang['LDAP_DN'] . $user->lang['COLON'] . '</label><br /><span>' . $user->lang['LDAP_DN_EXPLAIN'] . '</span></dt> <dd><input type="text" id="ldap_dn" size="40" name="config[ldap_base_dn]" value="' . $new['ldap_base_dn'] . '" /></dd> </dl> <dl> - <dt><label for="ldap_uid">' . $user->lang['LDAP_UID'] . ':</label><br /><span>' . $user->lang['LDAP_UID_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_uid">' . $user->lang['LDAP_UID'] . $user->lang['COLON'] . '</label><br /><span>' . $user->lang['LDAP_UID_EXPLAIN'] . '</span></dt> <dd><input type="text" id="ldap_uid" size="40" name="config[ldap_uid]" value="' . $new['ldap_uid'] . '" /></dd> </dl> <dl> - <dt><label for="ldap_user_filter">' . $user->lang['LDAP_USER_FILTER'] . ':</label><br /><span>' . $user->lang['LDAP_USER_FILTER_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_user_filter">' . $user->lang['LDAP_USER_FILTER'] . $user->lang['COLON'] . '</label><br /><span>' . $user->lang['LDAP_USER_FILTER_EXPLAIN'] . '</span></dt> <dd><input type="text" id="ldap_user_filter" size="40" name="config[ldap_user_filter]" value="' . $new['ldap_user_filter'] . '" /></dd> </dl> <dl> - <dt><label for="ldap_email">' . $user->lang['LDAP_EMAIL'] . ':</label><br /><span>' . $user->lang['LDAP_EMAIL_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_email">' . $user->lang['LDAP_EMAIL'] . $user->lang['COLON'] . '</label><br /><span>' . $user->lang['LDAP_EMAIL_EXPLAIN'] . '</span></dt> <dd><input type="text" id="ldap_email" size="40" name="config[ldap_email]" value="' . $new['ldap_email'] . '" /></dd> </dl> <dl> - <dt><label for="ldap_user">' . $user->lang['LDAP_USER'] . ':</label><br /><span>' . $user->lang['LDAP_USER_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_user">' . $user->lang['LDAP_USER'] . $user->lang['COLON'] . '</label><br /><span>' . $user->lang['LDAP_USER_EXPLAIN'] . '</span></dt> <dd><input type="text" id="ldap_user" size="40" name="config[ldap_user]" value="' . $new['ldap_user'] . '" /></dd> </dl> <dl> - <dt><label for="ldap_password">' . $user->lang['LDAP_PASSWORD'] . ':</label><br /><span>' . $user->lang['LDAP_PASSWORD_EXPLAIN'] . '</span></dt> + <dt><label for="ldap_password">' . $user->lang['LDAP_PASSWORD'] . $user->lang['COLON'] . '</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'] . '" autocomplete="off" /></dd> </dl> '; diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index 444446e9c3..b9ffa8091c 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -134,7 +134,7 @@ class bbcode $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); + $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')); diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index 0d3b06f621..ced45b29ea 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -385,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/memory.php b/phpBB/includes/cache/driver/memory.php index e25c9229a1..6142b6f65d 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -19,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; @@ -335,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/redis.php b/phpBB/includes/cache/driver/redis.php index a768885962..a768885962 100755..100644 --- a/phpBB/includes/cache/driver/redis.php +++ b/phpBB/includes/cache/driver/redis.php diff --git a/phpBB/includes/cache/factory.php b/phpBB/includes/cache/factory.php deleted file mode 100644 index 01c4d0b901..0000000000 --- a/phpBB/includes/cache/factory.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** -* -* @package acm -* @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* @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 37f32aa753..7858e27a5c 100644 --- a/phpBB/includes/cache/service.php +++ b/phpBB/includes/cache/service.php @@ -58,6 +58,11 @@ class phpbb_cache_service return call_user_func_array(array($this->driver, $method), $arguments); } + public function __get($var) + { + return $this->driver->$var; + } + /** * Obtain list of naughty words and build preg style replacement arrays for use by the * calling script @@ -332,27 +337,22 @@ class phpbb_cache_service $parsed_array = array(); } - $reparse = false; $filename = $phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; if (!file_exists($filename)) { - continue; + return $parsed_array; } if (!isset($parsed_array['filetime']) || (($config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) { - $reparse = true; - } - - // Re-parse cfg file - if ($reparse) - { + // Re-parse cfg file $parsed_array = parse_cfg_file($filename); $parsed_array['filetime'] = @filemtime($filename); $this->driver->put('_cfg_' . $style['style_path'], $parsed_array); } + return $parsed_array; } @@ -413,4 +413,39 @@ class phpbb_cache_service return $hook_files; } + + public function sql_load() + { + return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); + } + + public function sql_save($query, &$query_result, $ttl) + { + return call_user_func_array(array($this->driver, __FUNCTION__), array($query, &$query_result, $ttl)); + } + + public function sql_exists() + { + return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); + } + + public function sql_fetchrow() + { + return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); + } + + public function sql_fetchfield() + { + return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); + } + + public function sql_rowseek() + { + return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); + } + + public function sql_freeresult() + { + return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); + } } diff --git a/phpBB/includes/captcha/captcha_factory.php b/phpBB/includes/captcha/captcha_factory.php index d57b333c69..1ed8e119b5 100644 --- a/phpBB/includes/captcha/captcha_factory.php +++ b/phpBB/includes/captcha/captcha_factory.php @@ -25,7 +25,7 @@ class phpbb_captcha_factory /** * return an instance of class $name in file $name_plugin.php */ - public static function get_instance($name) + static public function get_instance($name) { global $phpbb_root_path, $phpEx; diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php index 6c1f3bf00b..c0c355f33b 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php @@ -49,27 +49,15 @@ class phpbb_captcha_gd extends phpbb_default_captcha } } - public static function get_instance() + static public function get_instance() { $instance = new phpbb_captcha_gd(); return $instance; } - function is_available() + static public function is_available() { - global $phpbb_root_path, $phpEx; - - if (@extension_loaded('gd')) - { - return true; - } - - if (!function_exists('can_load_dll')) - { - include($phpbb_root_path . 'includes/functions_install.' . $phpEx); - } - - return can_load_dll('gd'); + return @extension_loaded('gd'); } /** @@ -80,7 +68,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 a5588178bb..0d4b8bd451 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_wave_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_wave_plugin.php @@ -39,29 +39,17 @@ class phpbb_captcha_gd_wave extends phpbb_default_captcha } } - public static 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; - - if (@extension_loaded('gd')) - { - return true; - } - - if (!function_exists('can_load_dll')) - { - include($phpbb_root_path . 'includes/functions_install.' . $phpEx); - } - - return can_load_dll('gd'); + return @extension_loaded('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 96f13fbe1b..c5ef8c78b0 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php @@ -39,18 +39,18 @@ class phpbb_captcha_nogd extends phpbb_default_captcha } } - public static function get_instance() + static public function get_instance() { $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 99813189d7..ec7636f511 100644 --- a/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_qa_plugin.php @@ -98,7 +98,7 @@ class phpbb_captcha_qa /** * API function */ - public static function get_instance() + static public function get_instance() { $instance = new phpbb_captcha_qa(); @@ -108,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; @@ -124,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; } @@ -157,7 +157,7 @@ class phpbb_captcha_qa /** * API function */ - function get_name() + static public function get_name() { return 'CAPTCHA_QA'; } @@ -612,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 70c24a8c30..83d40bbba7 100644 --- a/phpBB/includes/captcha/plugins/phpbb_recaptcha_plugin.php +++ b/phpBB/includes/captcha/plugins/phpbb_recaptcha_plugin.php @@ -54,13 +54,13 @@ class phpbb_recaptcha extends phpbb_default_captcha $this->response = request_var('recaptcha_response_field', ''); } - public static function get_instance() + static public function get_instance() { $instance = new phpbb_recaptcha(); return $instance; } - function is_available() + static public function is_available() { global $config, $user; $user->add_lang('captcha_recaptcha'); @@ -75,7 +75,7 @@ class phpbb_recaptcha extends phpbb_default_captcha return true; } - function get_name() + static public function get_name() { return 'CAPTCHA_RECAPTCHA'; } @@ -163,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, diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index 66d2a003c6..68af41ab20 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -260,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 7a78a1b054..ccaa4f3764 100644 --- a/phpBB/includes/cron/manager.php +++ b/phpBB/includes/cron/manager.php @@ -32,31 +32,35 @@ class phpbb_cron_manager */ protected $tasks = array(); + protected $phpbb_root_path; + protected $php_ext; + /** * Constructor. Loads all available tasks. * - * @param array|Traversable $task_names Provides an iterable set of task names + * @param array|Traversable $tasks Provides an iterable set of task names */ - public function __construct($task_names) + public function __construct($tasks, $phpbb_root_path, $php_ext) { - $this->load_tasks($task_names); + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->load_tasks($tasks); } /** * Loads tasks given by name, wraps them * and puts them into $this->tasks. * - * @param array|Traversable $task_names Array of strings + * @param array|Traversable $tasks Array of instances of phpbb_cron_task * * @return void */ - public function load_tasks($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); } } @@ -122,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 c05fb9a87c..94a2f267b4 100644 --- a/phpBB/includes/cron/task/base.php +++ b/phpBB/includes/cron/task/base.php @@ -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 15b93a9ca6..006de4d040 100644 --- a/phpBB/includes/cron/task/core/prune_all_forums.php +++ b/phpBB/includes/cron/task/core/prune_all_forums.php @@ -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, phpbb_db_driver $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 7686fd4281..1dba77af8d 100644 --- a/phpBB/includes/cron/task/core/prune_forum.php +++ b/phpBB/includes/cron/task/core/prune_forum.php @@ -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, phpbb_db_driver $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 1c72eec7c7..c765660906 100644 --- a/phpBB/includes/cron/task/core/queue.php +++ b/phpBB/includes/cron/task/core/queue.php @@ -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 c9dc0bd9ae..6017eea561 100644 --- a/phpBB/includes/cron/task/core/tidy_cache.php +++ b/phpBB/includes/cron/task/core/tidy_cache.php @@ -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 80a1901b1e..1d256f964f 100644 --- a/phpBB/includes/cron/task/core/tidy_database.php +++ b/phpBB/includes/cron/task/core/tidy_database.php @@ -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 8a0b1b690a..ecc329eec6 100644 --- a/phpBB/includes/cron/task/core/tidy_search.php +++ b/phpBB/includes/cron/task/core/tidy_search.php @@ -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, phpbb_db_driver $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 ae7bb242b8..13531aa30b 100644 --- a/phpBB/includes/cron/task/core/tidy_sessions.php +++ b/phpBB/includes/cron/task/core/tidy_sessions.php @@ -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 e1434e7087..8dd0674fe5 100644 --- a/phpBB/includes/cron/task/core/tidy_warnings.php +++ b/phpBB/includes/cron/task/core/tidy_warnings.php @@ -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/provider.php b/phpBB/includes/cron/task/provider.php index 1482051699..134723ebd1 100644 --- a/phpBB/includes/cron/task/provider.php +++ b/phpBB/includes/cron/task/provider.php @@ -15,6 +15,8 @@ if (!defined('IN_PHPBB')) exit; } +use Symfony\Component\DependencyInjection\TaggedContainerInterface; + /** * Provides cron manager with tasks * @@ -22,27 +24,36 @@ if (!defined('IN_PHPBB')) * * @package phpBB3 */ -class phpbb_cron_task_provider extends phpbb_extension_provider +class phpbb_cron_task_provider implements IteratorAggregate { + private $container; + + public function __construct(TaggedContainerInterface $container) + { + $this->container = $container; + } + /** - * Finds cron task names using the extension manager. - * - * All PHP files in includes/cron/task/core/ are considered tasks. Tasks - * in extensions have to be located in a directory called cron or a subdir - * of a directory called cron. The class and filename must end in a _task - * suffix. Additionally all PHP files in includes/cron/task/core/ are - * tasks. + * Retrieve an iterator over all items * - * @return array List of task names + * @return ArrayIterator An iterator for the array of cron tasks */ - protected function find() + public function getIterator() { - $finder = $this->extension_manager->get_finder(); + $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 $finder - ->extension_suffix('_task') - ->extension_directory('/cron') - ->core_path('includes/cron/task/core/') - ->get_classes(); + return new ArrayIterator($tasks); } } diff --git a/phpBB/includes/cron/task/task.php b/phpBB/includes/cron/task/task.php index 2f2a9e51f9..7b08fed413 100644 --- a/phpBB/includes/cron/task/task.php +++ b/phpBB/includes/cron/task/task.php @@ -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 66c45189e5..386fb5b383 100644 --- a/phpBB/includes/cron/task/wrapper.php +++ b/phpBB/includes/cron/task/wrapper.php @@ -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/db/db_tools.php b/phpBB/includes/db/db_tools.php index 73eae4e967..6df3aac9ce 100644 --- a/phpBB/includes/db/db_tools.php +++ b/phpBB/includes/db/db_tools.php @@ -20,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 { diff --git a/phpBB/includes/db/dbal.php b/phpBB/includes/db/dbal.php new file mode 100644 index 0000000000..ef1dd7d14d --- /dev/null +++ b/phpBB/includes/db/dbal.php @@ -0,0 +1,1049 @@ +<?php +/** +* +* @package dbal +* @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; +} + +/** +* Database Abstraction Layer +* @package dbal +*/ +class dbal +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = array(); + var $open_queries = array(); + + var $curtime = 0; + var $query_hold = ''; + var $html_hold = ''; + var $sql_report = ''; + + var $persistency = false; + var $user = ''; + var $server = ''; + var $dbname = ''; + + // Set to true if error triggered + var $sql_error_triggered = false; + + // Holding the last sql query on sql error + var $sql_error_sql = ''; + // Holding the error information - only populated if sql_error_triggered is set + var $sql_error_returned = array(); + + // Holding transaction count + var $transactions = 0; + + // Supports multi inserts? + var $multi_insert = false; + + /** + * Current sql layer + */ + var $sql_layer = ''; + + /** + * Wildcards for matching any (%) or exactly one (_) character within LIKE expressions + */ + var $any_char; + var $one_char; + + /** + * Exact version of the DBAL, directly queried + */ + var $sql_server_version = false; + + /** + * Constructor + */ + function dbal() + { + $this->num_queries = array( + 'cached' => 0, + 'normal' => 0, + 'total' => 0, + ); + + // Fill default sql layer based on the class being called. + // This can be changed by the specified layer itself later if needed. + $this->sql_layer = substr(get_class($this), 5); + + // Do not change this please! This variable is used to easy the use of it - and is hardcoded. + $this->any_char = chr(0) . '%'; + $this->one_char = chr(0) . '_'; + } + + /** + * return on error or display error message + */ + function sql_return_on_error($fail = false) + { + $this->sql_error_triggered = false; + $this->sql_error_sql = ''; + + $this->return_on_error = $fail; + } + + /** + * Return number of sql queries and cached sql queries used + */ + function sql_num_queries($cached = false) + { + return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; + } + + /** + * Add to query count + */ + function sql_add_num_queries($cached = false) + { + $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; + $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; + $this->num_queries['total'] += 1; + } + + /** + * DBAL garbage collection, close sql connection + */ + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + do + { + $this->sql_transaction('commit'); + } + while ($this->transaction); + } + + foreach ($this->open_queries as $query_id) + { + $this->sql_freeresult($query_id); + } + + // Connection closed correctly. Set db_connect_id to false to prevent errors + if ($result = $this->_sql_close()) + { + $this->db_connect_id = false; + } + + return $result; + } + + /** + * Build LIMIT query + * Doing some validation here. + */ + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if (empty($query)) + { + return false; + } + + // Never use a negative total or offset + $total = ($total < 0) ? 0 : $total; + $offset = ($offset < 0) ? 0 : $offset; + + return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); + } + + /** + * Fetch all rows + */ + function sql_fetchrowset($query_id = false) + { + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + $result = array(); + while ($row = $this->sql_fetchrow($query_id)) + { + $result[] = $row; + } + + return $result; + } + + return false; + } + + /** + * 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) + */ + function sql_fetchfield($field, $rownum = false, $query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + if ($rownum !== false) + { + $this->sql_rowseek($rownum, $query_id); + } + + if (!is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchfield($query_id, $field); + } + + $row = $this->sql_fetchrow($query_id); + return (isset($row[$field])) ? $row[$field] : false; + } + + return false; + } + + /** + * Correctly adjust LIKE expression for special characters + * Some DBMS are handling them in a different way + * + * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char + * @return string LIKE expression including the keyword! + */ + function sql_like_expression($expression) + { + $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + + return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); + } + + /** + * 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_transactions() + { + return false; + } + + /** + * SQL Transaction + * @access private + */ + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) + if ($this->transaction) + { + $this->transactions++; + return true; + } + + $result = $this->_sql_transaction('begin'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = true; + break; + + case 'commit': + // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions + if ($this->transaction && $this->transactions) + { + $this->transactions--; + return true; + } + + // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) + // This implies we have transaction always set for autocommit db's + if (!$this->transaction) + { + return false; + } + + $result = $this->_sql_transaction('commit'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = false; + $this->transactions = 0; + break; + + case 'rollback': + $result = $this->_sql_transaction('rollback'); + $this->transaction = false; + $this->transactions = 0; + break; + + default: + $result = $this->_sql_transaction($status); + break; + } + + return $result; + } + + /** + * Build sql statement from array for insert/update/select statements + * + * Idea for this from Ikonboard + * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT + * + */ + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = $values = array(); + + if ($query == 'INSERT' || $query == 'INSERT_SELECT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_array($var) && is_string($var[0])) + { + // This is used for INSERT_SELECT(s) + $values[] = $var[0]; + } + else + { + $values[] = $this->_sql_validate_value($var); + } + } + + $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; + } + else if ($query == 'MULTI_INSERT') + { + trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + $values[] = "$key = " . $this->_sql_validate_value($var); + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + /** + * Build IN or NOT IN sql comparison string, uses <> or = on single element + * arrays to improve comparison speed + * + * @access public + * @param string $field name of the sql column that shall be compared + * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) + * @param bool $negate true for NOT IN (), false for IN () (default) + * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. + */ + function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) + { + if (!sizeof($array)) + { + if (!$allow_empty_set) + { + // Print the backtrace to help identifying the location of the problematic code + $this->sql_error('No values specified for SQL IN comparison'); + } + else + { + // NOT IN () actually means everything so use a tautology + if ($negate) + { + return '1=1'; + } + // IN () actually means nothing so use a contradiction + else + { + return '1=0'; + } + } + } + + if (!is_array($array)) + { + $array = array($array); + } + + if (sizeof($array) == 1) + { + @reset($array); + $var = current($array); + + return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); + } + else + { + return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; + } + } + + /** + * Run binary AND operator on DB column. + * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_and($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_and')) + { + return $this->_sql_bit_and($column_name, $bit, $compare); + } + + return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Run binary OR operator on DB column. + * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_or($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_or')) + { + return $this->_sql_bit_or($column_name, $bit, $compare); + } + + return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Returns SQL string to cast a string expression to an int. + * + * @param string $expression An expression evaluating to string + * @return string Expression returning an int + */ + function cast_expr_to_bigint($expression) + { + return $expression; + } + + /** + * Returns SQL string to cast an integer expression to a string. + * + * @param string $expression An expression evaluating to int + * @return string Expression returning a string + */ + function cast_expr_to_string($expression) + { + return $expression; + } + + /** + * 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 + * @param array &$sql_ary multi-dimensional array holding the statement data. + * + * @return bool false if no statements were executed. + * @access public + */ + function sql_multi_insert($table, &$sql_ary) + { + if (!sizeof($sql_ary)) + { + return false; + } + + if ($this->multi_insert) + { + $ary = array(); + foreach ($sql_ary as $id => $_sql_ary) + { + // If by accident the sql array is only one-dimensional we build a normal insert statement + if (!is_array($_sql_ary)) + { + return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); + } + + $values = array(); + foreach ($_sql_ary as $key => $var) + { + $values[] = $this->_sql_validate_value($var); + } + $ary[] = '(' . implode(', ', $values) . ')'; + } + + return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); + } + else + { + foreach ($sql_ary as $ary) + { + if (!is_array($ary)) + { + return false; + } + + $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); + + if (!$result) + { + return false; + } + } + } + + return true; + } + + /** + * Function for validating values + * @access private + */ + function _sql_validate_value($var) + { + if (is_null($var)) + { + return 'NULL'; + } + else if (is_string($var)) + { + return "'" . $this->sql_escape($var) . "'"; + } + else + { + return (is_bool($var)) ? intval($var) : $var; + } + } + + /** + * Build sql statement from array for select and select distinct statements + * + * Possible query values: SELECT, SELECT_DISTINCT + */ + function sql_build_query($query, $array) + { + $sql = ''; + switch ($query) + { + case 'SELECT': + case 'SELECT_DISTINCT'; + + $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; + + // Build table array. We also build an alias array for later checks. + $table_array = $aliases = array(); + $used_multi_alias = false; + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + $used_multi_alias = true; + + foreach ($alias as $multi_alias) + { + $table_array[] = $table_name . ' ' . $multi_alias; + $aliases[] = $multi_alias; + } + } + else + { + $table_array[] = $table_name . ' ' . $alias; + $aliases[] = $alias; + } + } + + // We run the following code to determine if we need to re-order the table array. ;) + // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. + // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. + if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) + { + // Take first LEFT JOIN + $join = current($array['LEFT_JOIN']); + + // Determine the table used there (even if there are more than one used, we only want to have one + preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); + + // If there is a first join match, we need to make sure the table order is correct + if (!empty($matches[1])) + { + $first_join_match = trim($matches[1]); + $table_array = $last = array(); + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + foreach ($alias as $multi_alias) + { + ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; + } + } + else + { + ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; + } + } + + $table_array = array_merge($table_array, $last); + } + } + + $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); + + if (!empty($array['LEFT_JOIN'])) + { + foreach ($array['LEFT_JOIN'] as $join) + { + $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; + } + } + + if (!empty($array['WHERE'])) + { + $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); + } + + if (!empty($array['GROUP_BY'])) + { + $sql .= ' GROUP BY ' . $array['GROUP_BY']; + } + + if (!empty($array['ORDER_BY'])) + { + $sql .= ' ORDER BY ' . $array['ORDER_BY']; + } + + break; + } + + return $sql; + } + + /** + * display sql error page + */ + function sql_error($sql = '') + { + global $auth, $user, $config; + + // Set var to retrieve errored status + $this->sql_error_triggered = true; + $this->sql_error_sql = $sql; + + $this->sql_error_returned = $this->_sql_error(); + + if (!$this->return_on_error) + { + $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]<br /><br />' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; + + // Show complete SQL error and path to administrators only + // Additionally show complete error on installation or if extended debug mode is enabled + // The DEBUG constant is for development only! + if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) + { + $message .= ($sql) ? '<br /><br />SQL<br /><br />' . htmlspecialchars($sql) : ''; + } + else + { + // If error occurs in initiating the session we need to use a pre-defined language string + // This could happen if the connection could not be established for example (then we are not able to grab the default language) + if (!isset($user->lang['SQL_ERROR_OCCURRED'])) + { + $message .= '<br /><br />An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; + } + else + { + if (!empty($config['board_contact'])) + { + $message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>'); + } + else + { + $message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + } + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + if (strlen($message) > 1024) + { + // We need to define $msg_long_text here to circumvent text stripping. + global $msg_long_text; + $msg_long_text = $message; + + trigger_error(false, E_USER_ERROR); + } + + trigger_error($message, E_USER_ERROR); + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + return $this->sql_error_returned; + } + + /** + * Explain queries + */ + function sql_report($mode, $query = '') + { + global $cache, $starttime, $phpbb_root_path, $user; + global $request; + + if (is_object($request) && !$request->variable('explain', false)) + { + return false; + } + + if (!$query && $this->query_hold != '') + { + $query = $this->query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE html> + <html dir="ltr"> + <head> + <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> + <body id="errorpage"> + <div id="wrap"> + <div id="page-header"> + <a href="' . build_url('explain') . '">Return to previous page</a> + </div> + <div id="page-body"> + <div id="acp"> + <div class="panel"> + <span class="corners-top"><span></span></span> + <div id="content"> + <h1>SQL Report</h1> + <br /> + <p><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></p> + + <p>Time spent on ' . $this->sql_layer . ' queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></p> + + <br /><br /> + ' . $this->sql_report . ' + </div> + <span class="corners-bottom"><span></span></span> + </div> + </div> + </div> + <div id="page-footer"> + Powered by <a href="http://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group + </div> + </div> + </body> + </html>'; + + exit_handler(); + + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $this->sql_report .= ' + + <table cellspacing="1"> + <thead> + <tr> + <th>Query #' . $this->num_queries['total'] . '</th> + </tr> + </thead> + <tbody> + <tr> + <td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td> + </tr> + </tbody> + </table> + + ' . $this->html_hold . ' + + <p style="text-align: center;"> + '; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $this->sql_report .= 'Affected rows: <b>' . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $this->curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $this->sql_report .= '<b style="color: red">FAILED</b> - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $this->sql_report .= '</p><br /><br />'; + + $this->sql_time += $endtime - $this->curtime; + break; + + case 'start': + $this->query_hold = $query; + $this->html_hold = ''; + + $this->_sql_report($mode, $query); + + $this->curtime = explode(' ', microtime()); + $this->curtime = $this->curtime[0] + $this->curtime[1]; + + break; + + case 'add_select_row': + + $html_table = func_get_arg(2); + $row = func_get_arg(3); + + if (!$html_table && sizeof($row)) + { + $html_table = true; + $this->html_hold .= '<table cellspacing="1"><tr>'; + + foreach (array_keys($row) as $val) + { + $this->html_hold .= '<th>' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '</th>'; + } + $this->html_hold .= '</tr>'; + } + $this->html_hold .= '<tr>'; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $this->html_hold .= '<td class="' . $class . '">' . (($val) ? $val : ' ') . '</td>'; + } + $this->html_hold .= '</tr>'; + + return $html_table; + + break; + + case 'fromcache': + + $this->_sql_report($mode, $query); + + break; + + case 'record_fromcache': + + $endtime = func_get_arg(2); + $splittime = func_get_arg(3); + + $time_cache = $endtime - $this->curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $this->sql_report .= '<table cellspacing="1"><thead><tr><th>Query results obtained from the cache</th></tr></thead><tbody><tr>'; + $this->sql_report .= '<td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></tbody></table>'; + $this->sql_report .= '<p style="text-align: center;">'; + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p><br /><br />'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + break; + + default: + + $this->_sql_report($mode, $query); + + break; + } + + 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; + } +} + +/** +* This variable holds the class name to use later +*/ +$sql_db = (!empty($dbms)) ? 'dbal_' . basename($dbms) : 'dbal'; diff --git a/phpBB/includes/di/processor/config.php b/phpBB/includes/di/processor/config.php new file mode 100644 index 0000000000..2abb1c1c33 --- /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', 'phpbb_db_driver_'.$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/extension/controller.php b/phpBB/includes/extension/controller.php index ec051c756f..f97b69c7ed 100644 --- a/phpBB/includes/extension/controller.php +++ b/phpBB/includes/extension/controller.php @@ -23,37 +23,44 @@ if (!defined('IN_PHPBB')) abstract class phpbb_extension_controller implements phpbb_extension_controller_interface { /** - * @var phpbb_request Request class object + * Request class object + * @var phpbb_request */ protected $request; /** - * @var dbal DBAL class object + * DBAL class object + * @var dbal */ protected $db; /** - * @var user User class object + * User class object + * @var phpbb_user */ protected $user; /** - * @var phpbb_template Template class object + * Template class object + * @var phpbb_template */ protected $template; /** - * @var array Config array + * Config object + * @var phpbb_config */ protected $config; /** - * @var string PHP Extension + * PHP Extension + * @var string */ protected $php_ext; /** - * @var string Relative path to board root + * Relative path to board root + * @var string */ protected $phpbb_root_path; @@ -64,14 +71,14 @@ abstract class phpbb_extension_controller implements phpbb_extension_controller_ public function __construct() { global $request, $db, $user, $template, $config; - global $php_ext, $phpbb_root_path; + global $phpEx, $phpbb_root_path; $this->request = $request; $this->db = $db; $this->user = $user; $this->template = $template; $this->config = $config; - $this->php_ext = $php_ext; + $this->php_ext = $phpEx; $this->phpbb_root_path = $phpbb_root_path; } } 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/manager.php b/phpBB/includes/extension/manager.php index f0341d305a..55502a610c 100644 --- a/phpBB/includes/extension/manager.php +++ b/phpBB/includes/extension/manager.php @@ -22,6 +22,8 @@ if (!defined('IN_PHPBB')) */ class phpbb_extension_manager { + protected $db; + protected $config; protected $cache; protected $php_ext; protected $extensions; @@ -33,16 +35,18 @@ class phpbb_extension_manager * 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(phpbb_db_driver $db, $extension_table, $phpbb_root_path, $php_ext = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') + public function __construct(phpbb_db_driver $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; @@ -63,6 +67,17 @@ class phpbb_extension_manager */ public function load_extensions() { + $this->extensions = array(); + + // Do not try to load any extensions when installing or updating + // Note: database updater invokes this code, and in 3.0 + // there is no extension table therefore the rest of this function + // fails + if (defined('IN_INSTALL')) + { + return; + } + $sql = 'SELECT * FROM ' . $this->extension_table; @@ -70,7 +85,6 @@ class phpbb_extension_manager $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']); @@ -121,6 +135,18 @@ class phpbb_extension_manager } /** + * 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 diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php new file mode 100644 index 0000000000..813459eb67 --- /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, phpbb_db_driver $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 index d0541fa007..45b55e5cab 100644 --- a/phpBB/includes/extension/provider.php +++ b/phpBB/includes/extension/provider.php @@ -16,7 +16,15 @@ if (!defined('IN_PHPBB')) } /** -* Provides a set of items found in extensions +* 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 */ @@ -45,7 +53,7 @@ abstract class phpbb_extension_provider implements IteratorAggregate } /** - * Finds template paths using the extension manager. + * Finds items using the extension manager. * * @return array List of task names */ diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index e5b721b1f5..804d89d1a2 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1018,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 /** @@ -1280,6 +1310,10 @@ function phpbb_timezone_select($user, $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) @@ -1287,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)) @@ -1294,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']) { @@ -1306,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); } } } @@ -1337,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); @@ -1355,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); } @@ -1370,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, ); } @@ -1401,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'])) @@ -1409,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); @@ -1426,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); @@ -1441,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)); @@ -1461,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 @@ -1497,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 { @@ -1505,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); } @@ -1527,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)); @@ -2085,7 +2144,7 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam $on_page = floor($start_item / $per_page) + 1; $url_delim = (strpos($base_url, '?') === false) ? '?' : ((strpos($base_url, '?') === strlen($base_url) - 1) ? '' : '&'); - + if ($reverse_count) { $start_page = ($total_pages > 5) ? $total_pages - 4 : 1; @@ -2094,9 +2153,9 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam else { // 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. + // 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 @@ -2112,21 +2171,21 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam $end_page = ($total_pages > 5) ? max(min($total_pages, $on_page + 3), 5) : $total_pages; } - if ($on_page != $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 * $per_page), - 'S_IS_CURRENT' => false, - 'S_IS_PREV' => false, - 'S_IS_NEXT' => true, - 'S_IS_ELLIPSIS' => false, + '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). + // 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 { @@ -2137,17 +2196,17 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam // 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_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), + '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 + // 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 + // 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 @@ -2166,21 +2225,60 @@ function phpbb_generate_template_pagination($template, $base_url, $block_var_nam } while ($at_page <= $total_pages); - if ($on_page != 1) + 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 - 2) * $per_page), - 'S_IS_CURRENT' => false, - 'S_IS_PREV' => true, - 'S_IS_NEXT' => false, - 'S_IS_ELLIPSIS' => false, + '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), + $tpl_prefix . 'PER_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, + ); + + if ($tpl_block_name) + { + $template->alter_block_array($tpl_block_name, $template_array, true, 'change'); + } + else + { + $template->assign_vars($template_array); + } } /** -* Return current page +* Return current page * This function also sets certain specific template variables * * @param object $template the template object @@ -2200,9 +2298,8 @@ function phpbb_on_page($template, $user, $base_url, $num_items, $per_page, $star $template->assign_vars(array( 'PER_PAGE' => $per_page, - 'ON_PAGE' => $on_page, - - 'A_BASE_URL' => addslashes($base_url), + '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)); @@ -2231,6 +2328,7 @@ function phpbb_on_page($template, $user, $base_url, $num_items, $per_page, $star 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))) { @@ -2238,6 +2336,39 @@ function append_sid($url, $params = false, $is_amp = true, $session_id = false) $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)) @@ -2821,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); @@ -3010,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); @@ -3343,7 +3474,7 @@ 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']); @@ -4060,12 +4191,12 @@ function msg_handler($errno, $msg_text, $errfile, $errline) $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace; } - if (defined('IN_INSTALL') || defined('DEBUG_EXTRA') || isset($auth) && $auth->acl_get('a_')) + if (defined('IN_INSTALL') || defined('DEBUG') || isset($auth) && $auth->acl_get('a_')) { $msg_text = $log_text; } - if ((defined('DEBUG') || defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db)) + if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db)) { // let's avoid loops $db->sql_return_on_error(true); @@ -4113,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>® Forum Software © phpBB Group'; + echo ' Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'; echo ' </div>'; echo '</div>'; echo '</body>'; @@ -4753,6 +4884,31 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 define('HEADER_INC', true); + // A listener can set this variable to `true` when it overrides this function + $page_header_override = false; + + /** + * Execute code and/or overwrite page_header() + * + * @event core.page_header + * @var string page_title Page title + * @var bool display_online_list Do we display online users list + * @var string item Restrict online users to a certain + * session item, e.g. forum for + * session_forum_id + * @var int item_id Restrict online users to item id + * @var bool page_header_override Shall we return instead of running + * the rest of page_header() + * @since 3.1-A1 + */ + $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override'); + extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars))); + + if ($page_header_override) + { + return; + } + // gzip_compression if ($config['gzip_compress']) { @@ -4935,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'), @@ -4946,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), @@ -5033,9 +5191,6 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 'A_COOKIE_SETTINGS' => addslashes('; path=' . $config['cookie_path'] . ((!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']) . ((!$config['cookie_secure']) ? '' : '; secure')), )); - $vars = array('page_title', 'display_online_list', 'item_id', 'item'); - extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars))); - // application/xhtml+xml not used because of IE header('Content-type: text/html; charset=UTF-8'); @@ -5058,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')) @@ -5066,14 +5241,14 @@ function page_footer($run_cron = true) $mtime = explode(' ', microtime()); $totaltime = $mtime[0] + $mtime[1] - $starttime; - if ($request->variable('explain', false) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report')) + if ($request->variable('explain', false) && $auth->acl_get('a_') && defined('DEBUG') && method_exists($db, 'sql_report')) { $db->sql_report('display'); } $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime); - if ($auth->acl_get('a_') && defined('DEBUG_EXTRA')) + if ($auth->acl_get('a_') && defined('DEBUG')) { if (function_exists('memory_get_peak_usage')) { @@ -5092,7 +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="http://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'), + '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) : '') ); @@ -5143,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)) diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php index dc61859363..2f3fd7bac0 100644 --- a/phpBB/includes/functions_acp.php +++ b/phpBB/includes/functions_acp.php @@ -22,6 +22,7 @@ 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')) { @@ -30,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']) { @@ -96,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')) @@ -104,14 +145,14 @@ function adm_page_footer($copyright_html = true) $mtime = explode(' ', microtime()); $totaltime = $mtime[0] + $mtime[1] - $starttime; - if ($request->variable('explain', false) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report')) + if ($request->variable('explain', false) && $auth->acl_get('a_') && defined('DEBUG') && method_exists($db, 'sql_report')) { $db->sql_report('display'); } $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress']) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime); - if ($auth->acl_get('a_') && defined('DEBUG_EXTRA')) + if ($auth->acl_get('a_') && defined('DEBUG')) { if (function_exists('memory_get_peak_usage')) { @@ -131,7 +172,7 @@ 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="http://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group'), + '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']) @@ -193,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 . ']'; @@ -305,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; } @@ -314,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; @@ -489,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; } } diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php index 5d19cd7adb..5e2ee8c8f6 100644 --- a/phpBB/includes/functions_admin.php +++ b/phpBB/includes/functions_admin.php @@ -723,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') { @@ -855,7 +855,7 @@ function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = } $error = false; - $search = new $search_type($error); + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user); if ($error) { diff --git a/phpBB/includes/functions_compress.php b/phpBB/includes/functions_compress.php index 72d8eabe76..c79a31930e 100644 --- a/phpBB/includes/functions_compress.php +++ b/phpBB/includes/functions_compress.php @@ -24,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 = '') @@ -123,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() + static public function methods() { $methods = array('.tar'); $available_methods = array('.tar.gz' => 'zlib', '.tar.bz2' => 'bz2', '.zip' => 'zlib'); @@ -361,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])); @@ -633,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 6b2ee98d7a..c54cc25f34 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -408,16 +408,34 @@ function strip_bbcode(&$text, $uid = '') * For display of custom parsed text on user-facing pages * Expects $text to be the value directly from the database (stored value) */ -function generate_text_for_display($text, $uid, $bitfield, $flags) +function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text = true) { static $bbcode; + global $phpbb_dispatcher; if (!$text) { return ''; } - $text = censor_text($text); + /** + * 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)) @@ -443,6 +461,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; } @@ -453,7 +484,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); @@ -482,6 +529,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; } @@ -491,10 +551,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, @@ -1175,6 +1258,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)) @@ -1252,10 +1336,34 @@ 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; } /** diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 00efd281c0..73129803ee 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -22,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; @@ -54,12 +54,12 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod // Handle marking everything read if ($mark_read == 'all') { - $redirect = build_url(array('mark', 'hash')); + $redirect = build_url(array('mark', 'hash', 'mark_time')); meta_refresh(3, $redirect); if (check_link_hash(request_var('hash', ''), 'global')) { - markread('all'); + markread('all', false, false, request_var('mark_time', 0)); trigger_error( $user->lang['FORUMS_MARKED'] . '<br /><br />' . @@ -119,6 +119,16 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod '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); @@ -127,6 +137,19 @@ 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? @@ -260,17 +283,33 @@ 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') { - $redirect = build_url(array('mark', 'hash')); + $redirect = build_url(array('mark', 'hash', 'mark_time')); $token = request_var('hash', ''); if (check_link_hash($token, 'global')) { - markread('topics', $forum_ids); + markread('topics', $forum_ids, false, request_var('mark_time', 0)); $message = sprintf($user->lang['RETURN_FORUM'], '<a href="' . $redirect . '">', '</a>'); meta_refresh(3, $redirect); @@ -411,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'; @@ -422,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) @@ -443,7 +482,7 @@ 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, @@ -480,9 +519,24 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod '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) { @@ -497,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'), @@ -830,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; @@ -850,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'); } /** @@ -1248,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)) { @@ -1285,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 1486113013..b6371dbecc 100644 --- a/phpBB/includes/functions_download.php +++ b/phpBB/includes/functions_download.php @@ -592,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 413d8481b0..612f9c901c 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -16,27 +16,6 @@ if (!defined('IN_PHPBB')) } /** -* Determine if we are able to load a specified PHP module and do so if possible -*/ -function can_load_dll($dll) -{ - // SQLite2 is a tricky thing, from 5.0.0 it requires PDO; if PDO is not loaded we must state that SQLite is unavailable - // as the installer doesn't understand that the extension has a prerequisite. - // - // On top of this sometimes the SQLite extension is compiled for a different version of PDO - // by some Linux distributions which causes phpBB to bomb out with a blank page. - // - // Net result we'll disable automatic inclusion of SQLite support - // - // See: r9618 and #56105 - if ($dll == 'sqlite') - { - return false; - } - return ((@ini_get('enable_dl') || strtolower(@ini_get('enable_dl')) == 'on') && (!@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == 'off') && function_exists('dl') && @dl($dll . '.' . PHP_SHLIB_SUFFIX)) ? true : false; -} - -/** * Returns an array of available DBMS with some data, if a DBMS is specified it will only * return data for that DBMS and will load its extension if necessary. */ @@ -159,18 +138,15 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 if (!@extension_loaded($dll)) { - if (!can_load_dll($dll)) + if ($return_unavailable) { - if ($return_unavailable) - { - $available_dbms[$db_name]['AVAILABLE'] = false; - } - else - { - unset($available_dbms[$db_name]); - } - continue; + $available_dbms[$db_name]['AVAILABLE'] = false; + } + else + { + unset($available_dbms[$db_name]); } + continue; } $any_db_support = true; } @@ -513,15 +489,14 @@ function adjust_language_keys_callback($matches) * * @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) +function phpbb_create_config_file_data($data, $dbms, $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"; @@ -533,8 +508,7 @@ function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = 'dbuser' => $data['dbuser'], 'dbpasswd' => htmlspecialchars_decode($data['dbpasswd']), 'table_prefix' => $data['table_prefix'], - 'acm_type' => 'file', - 'load_extensions' => $load_extensions, + 'acm_type' => 'phpbb_cache_driver_file', ); foreach ($config_data_array as $key => $value) @@ -547,12 +521,15 @@ function phpbb_create_config_file_data($data, $dbms, $load_extensions, $debug = 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 d76309d5bb..3d8e403f4b 100644 --- a/phpBB/includes/functions_jabber.php +++ b/phpBB/includes/functions_jabber.php @@ -68,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) @@ -83,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; @@ -92,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')) { @@ -442,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 e9073553d0..cf03de08c4 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -210,7 +210,7 @@ class messenger { $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); + $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; diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 6c21b0f412..171cf988ce 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -20,7 +20,7 @@ if (!defined('IN_PHPBB')) */ function generate_smilies($mode, $forum_id) { - global $db, $user, $config, $template; + global $db, $user, $config, $template, $phpbb_dispatcher; global $phpEx, $phpbb_root_path; $start = request_var('start', 0); @@ -123,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( @@ -284,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'], @@ -1164,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; @@ -1335,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", @@ -2370,7 +2385,7 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u } $error = false; - $search = new $search_type($error); + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user); if ($error) { @@ -2440,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 = ''; diff --git a/phpBB/includes/functions_privmsgs.php b/phpBB/includes/functions_privmsgs.php index 089cb06641..ba939d490e 100644 --- a/phpBB/includes/functions_privmsgs.php +++ b/phpBB/includes/functions_privmsgs.php @@ -269,46 +269,46 @@ function check_rule(&$rules, &$rule_row, &$message_row, $user_id) 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; @@ -1145,6 +1145,23 @@ function phpbb_delete_user_pms($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(); @@ -1152,7 +1169,7 @@ function phpbb_delete_user_pms($user_id) // Part 1: get PMs the user received $sql = 'SELECT msg_id FROM ' . PRIVMSGS_TO_TABLE . ' - WHERE user_id = ' . $user_id; + WHERE ' . $user_id_sql; $result = $db->sql_query($sql); while ($row = $db->sql_fetchrow($result)) @@ -1162,12 +1179,12 @@ function phpbb_delete_user_pms($user_id) } $db->sql_freeresult($result); - // Part 2: get PMs the user sent, but have yet to be received - // We cannot simply delete them. First we have to check, + // 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 = ' . $user_id . ' + WHERE ' . $author_id_sql . ' AND folder_id = ' . PRIVMSGS_NO_BOX; $result = $db->sql_query($sql); @@ -1193,7 +1210,7 @@ function phpbb_delete_user_pms($user_id) // received them. $sql = 'SELECT msg_id FROM ' . PRIVMSGS_TO_TABLE . ' - WHERE author_id = ' . $user_id . ' + WHERE ' . $author_id_sql . ' AND folder_id <> ' . PRIVMSGS_NO_BOX . ' AND folder_id <> ' . PRIVMSGS_OUTBOX . ' AND folder_id <> ' . PRIVMSGS_SENTBOX; @@ -1213,7 +1230,7 @@ function phpbb_delete_user_pms($user_id) // 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 = ' . $user_id . ' + 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'; @@ -1271,12 +1288,12 @@ function phpbb_delete_user_pms($user_id) $sql = 'UPDATE ' . USERS_TABLE . ' SET user_new_privmsg = 0, user_unread_privmsg = 0 - WHERE user_id = ' . $user_id; + WHERE ' . $user_id_sql; $db->sql_query($sql); // Delete private message data of the user $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' - WHERE user_id = ' . (int) $user_id; + WHERE ' . $user_id_sql; $db->sql_query($sql); if (!empty($delete_ids)) @@ -1313,12 +1330,12 @@ function phpbb_delete_user_pms($user_id) // 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 = ' . $user_id; + WHERE ' . $author_id_sql; $db->sql_query($sql); $sql = 'UPDATE ' . PRIVMSGS_TABLE . ' SET author_id = ' . ANONYMOUS . ' - WHERE author_id = ' . $user_id; + WHERE ' . $author_id_sql; $db->sql_query($sql); $db->sql_transaction('commit'); @@ -2084,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 1c15ef897f..10af997bff 100644 --- a/phpBB/includes/functions_profile_fields.php +++ b/phpBB/includes/functions_profile_fields.php @@ -121,7 +121,7 @@ class custom_profile case FIELD_BOOL: $field_value = (bool) $field_value; - + if (!$field_value && $field_data['field_required']) { return 'FIELD_REQUIRED'; @@ -133,7 +133,7 @@ class custom_profile { return false; } - + $field_value = (int) $field_value; if ($field_value < $field_data['field_minlen']) @@ -455,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) { @@ -463,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; @@ -520,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; } @@ -529,7 +540,7 @@ class custom_profile case 'string': case 'text': - if (!$value) + if (!$value && !$ident_ary['data']['field_show_novalue']) { return NULL; } @@ -547,7 +558,7 @@ 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; } @@ -573,12 +584,7 @@ class custom_profile $this->get_option_lang($field_id, $lang_id, FIELD_DROPDOWN, false); } - // If a dropdown field is required, users - // cannot choose the "no value" option. - // They must choose one of the other options. - // Therefore, here we treat a value equal to - // the "no value" as a lack of value, i.e. NULL. - if ($value == $ident_ary['data']['field_novalue'] && $ident_ary['data']['field_required']) + if ($value == $ident_ary['data']['field_novalue'] && !$ident_ary['data']['field_show_novalue']) { return NULL; } @@ -588,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]; @@ -602,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; diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index d4c6b42cf4..b467aa93d1 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -566,10 +566,11 @@ class fileupload */ function form_upload($form_name) { - global $user; + global $user, $request; - unset($_FILES[$form_name]['local_mode']); - $file = new filespec($_FILES[$form_name], $this); + $upload = $request->file($form_name); + unset($upload['local_mode']); + $file = new filespec($upload, $this); if ($file->init_error) { @@ -578,9 +579,9 @@ class fileupload } // Error array filled? - if (isset($_FILES[$form_name]['error'])) + if (isset($upload['error'])) { - $error = $this->assign_internal_error($_FILES[$form_name]['error']); + $error = $this->assign_internal_error($upload['error']); if ($error !== false) { @@ -590,7 +591,7 @@ class fileupload } // Check if empty file got uploaded (not catched by is_uploaded_file) - if (isset($_FILES[$form_name]['size']) && $_FILES[$form_name]['size'] == 0) + if (isset($upload['size']) && $upload['size'] == 0) { $file->error[] = $user->lang[$this->error_prefix . 'EMPTY_FILEUPLOAD']; return $file; @@ -631,17 +632,17 @@ class fileupload */ function local_upload($source_file, $filedata = false) { - global $user; + global $user, $request; - $form_name = 'local'; + $upload = array(); - $_FILES[$form_name]['local_mode'] = true; - $_FILES[$form_name]['tmp_name'] = $source_file; + $upload['local_mode'] = true; + $upload['tmp_name'] = $source_file; if ($filedata === false) { - $_FILES[$form_name]['name'] = utf8_basename($source_file); - $_FILES[$form_name]['size'] = 0; + $upload['name'] = utf8_basename($source_file); + $upload['size'] = 0; $mimetype = ''; if (function_exists('mime_content_type')) @@ -655,16 +656,16 @@ class fileupload $mimetype = 'application/octetstream'; } - $_FILES[$form_name]['type'] = $mimetype; + $upload['type'] = $mimetype; } else { - $_FILES[$form_name]['name'] = $filedata['realname']; - $_FILES[$form_name]['size'] = $filedata['size']; - $_FILES[$form_name]['type'] = $filedata['type']; + $upload['name'] = $filedata['realname']; + $upload['size'] = $filedata['size']; + $upload['type'] = $filedata['type']; } - $file = new filespec($_FILES[$form_name], $this); + $file = new filespec($upload, $this); if ($file->init_error) { @@ -672,9 +673,9 @@ class fileupload return $file; } - if (isset($_FILES[$form_name]['error'])) + if (isset($upload['error'])) { - $error = $this->assign_internal_error($_FILES[$form_name]['error']); + $error = $this->assign_internal_error($upload['error']); if ($error !== false) { @@ -709,6 +710,7 @@ class fileupload } $this->common_checks($file); + $request->overwrite('local', $upload, phpbb_request_interface::FILES); return $file; } @@ -1001,7 +1003,10 @@ class fileupload */ function is_valid($form_name) { - return (isset($_FILES[$form_name]) && $_FILES[$form_name]['name'] != 'none') ? true : false; + global $request; + $upload = $request->file($form_name); + + return (!empty($upload) && $upload['name'] !== 'none'); } diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 6e658b4ef4..8f9c9198f4 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -112,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'), @@ -137,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); } @@ -151,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'])) { @@ -244,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); @@ -328,28 +350,54 @@ function user_add($user_row, $cp_data = false) /** * Remove User +* @param $mode Either 'retain' or 'remove' */ -function user_delete($mode, $user_id, $post_username = 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'); + + $user_rows = array(); + if (!is_array($user_ids)) + { + $user_ids = array($user_ids); + } + + $user_id_sql = $db->sql_in_set('user_id', $user_ids); + $sql = 'SELECT * FROM ' . USERS_TABLE . ' - WHERE user_id = ' . $user_id; + WHERE ' . $user_id_sql; $result = $db->sql_query($sql); - $user_row = $db->sql_fetchrow($result); + while ($row = $db->sql_fetchrow($result)) + { + $user_rows[(int) $row['user_id']] = $row; + } $db->sql_freeresult($result); - if (!$user_row) + if (empty($user_rows)) { 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 - WHERE r.user_id = ' . $user_id . ' + WHERE ' . $db->sql_in_set('r.user_id', $user_ids) . ' AND p.post_id = r.post_id'; $result = $db->sql_query($sql); @@ -403,97 +451,124 @@ function user_delete($mode, $user_id, $post_username = false) } // Remove reports - $db->sql_query('DELETE FROM ' . REPORTS_TABLE . ' WHERE user_id = ' . $user_id); + $db->sql_query('DELETE FROM ' . REPORTS_TABLE . ' WHERE ' . $user_id_sql); - if ($user_row['user_avatar'] && $user_row['user_avatar_type'] == AVATAR_UPLOAD) - { - avatar_delete('user', $user_row); - } + $num_users_delta = 0; - switch ($mode) + // Some things need to be done in the loop (if the query changes based + // on which user is currently being deleted) + $added_guest_posts = 0; + foreach ($user_rows as $user_id => $user_row) { - case 'retain': - - $db->sql_transaction('begin'); - - if ($post_username === false) - { - $post_username = $user->lang['GUEST']; - } + if ($user_row['user_avatar'] && $user_row['user_avatar_type'] == AVATAR_UPLOAD) + { + avatar_delete('user', $user_row); + } - // If the user is inactive and newly registered we assume no posts from this user being there... - if ($user_row['user_type'] == USER_INACTIVE && $user_row['user_inactive_reason'] == INACTIVE_REGISTER && !$user_row['user_posts']) - { - } - else - { - $sql = 'UPDATE ' . FORUMS_TABLE . ' - SET forum_last_poster_id = ' . ANONYMOUS . ", forum_last_poster_name = '" . $db->sql_escape($post_username) . "', forum_last_poster_colour = '' - WHERE forum_last_poster_id = $user_id"; - $db->sql_query($sql); + // Decrement number of users if this user is active + if ($user_row['user_type'] != USER_INACTIVE && $user_row['user_type'] != USER_IGNORE) + { + --$num_users_delta; + } - $sql = 'UPDATE ' . POSTS_TABLE . ' - SET poster_id = ' . ANONYMOUS . ", post_username = '" . $db->sql_escape($post_username) . "' - WHERE poster_id = $user_id"; - $db->sql_query($sql); + switch ($mode) + { + case 'retain': + if ($retain_username === false) + { + $post_username = $user->lang['GUEST']; + } + else + { + $post_username = $user_row['username']; + } - $sql = 'UPDATE ' . POSTS_TABLE . ' - SET post_edit_user = ' . ANONYMOUS . " - WHERE post_edit_user = $user_id"; - $db->sql_query($sql); + // If the user is inactive and newly registered + // we assume no posts from the user, and save + // the queries + if ($user_row['user_type'] != USER_INACTIVE || $user_row['user_inactive_reason'] != INACTIVE_REGISTER || $user_row['user_posts']) + { + // When we delete these users and retain the posts, we must assign all the data to the guest user + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_last_poster_id = ' . ANONYMOUS . ", forum_last_poster_name = '" . $db->sql_escape($post_username) . "', forum_last_poster_colour = '' + WHERE forum_last_poster_id = $user_id"; + $db->sql_query($sql); - $sql = 'UPDATE ' . TOPICS_TABLE . ' - SET topic_poster = ' . ANONYMOUS . ", topic_first_poster_name = '" . $db->sql_escape($post_username) . "', topic_first_poster_colour = '' - WHERE topic_poster = $user_id"; - $db->sql_query($sql); + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET poster_id = ' . ANONYMOUS . ", post_username = '" . $db->sql_escape($post_username) . "' + WHERE poster_id = $user_id"; + $db->sql_query($sql); - $sql = 'UPDATE ' . TOPICS_TABLE . ' - SET topic_last_poster_id = ' . ANONYMOUS . ", topic_last_poster_name = '" . $db->sql_escape($post_username) . "', topic_last_poster_colour = '' - WHERE topic_last_poster_id = $user_id"; - $db->sql_query($sql); + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_poster = ' . ANONYMOUS . ", topic_first_poster_name = '" . $db->sql_escape($post_username) . "', topic_first_poster_colour = '' + WHERE topic_poster = $user_id"; + $db->sql_query($sql); - $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' - SET poster_id = ' . ANONYMOUS . " - WHERE poster_id = $user_id"; - $db->sql_query($sql); + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_last_poster_id = ' . ANONYMOUS . ", topic_last_poster_name = '" . $db->sql_escape($post_username) . "', topic_last_poster_colour = '' + WHERE topic_last_poster_id = $user_id"; + $db->sql_query($sql); - // Since we change every post by this author, we need to count this amount towards the anonymous user + // Since we change every post by this author, we need to count this amount towards the anonymous user - // Update the post count for the anonymous user - if ($user_row['user_posts']) - { - $sql = 'UPDATE ' . USERS_TABLE . ' - SET user_posts = user_posts + ' . $user_row['user_posts'] . ' - WHERE user_id = ' . ANONYMOUS; - $db->sql_query($sql); + if ($user_row['user_posts']) + { + $added_guest_posts += $user_row['user_posts']; + } } - } - - $db->sql_transaction('commit'); + break; - break; + case 'remove': + // there is nothing variant specific to deleting posts + break; + } + } - case 'remove': + if ($num_users_delta != 0) + { + set_config_count('num_users', $num_users_delta, true); + } - if (!function_exists('delete_posts')) - { - include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); - } + // Now do the invariant tasks + // all queries performed in one call of this function are in a single transaction + // so this is kosher + if ($mode == 'retain') + { + // Assign more data to the Anonymous user + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET poster_id = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('poster_id', $user_ids); + $db->sql_query($sql); - // Delete posts, attachments, etc. - delete_posts('poster_id', $user_id); + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_edit_user = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('post_edit_user', $user_ids); + $db->sql_query($sql); - break; + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts + ' . $added_guest_posts . ' + WHERE user_id = ' . ANONYMOUS; + $db->sql_query($sql); } + else if ($mode == 'remove') + { + if (!function_exists('delete_posts')) + { + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } - $db->sql_transaction('begin'); + // Delete posts, attachments, etc. + // delete_posts can handle any number of IDs in its second argument + delete_posts('poster_id', $user_ids); + } $table_ary = array(USERS_TABLE, USER_GROUP_TABLE, TOPICS_WATCH_TABLE, FORUMS_WATCH_TABLE, ACL_USERS_TABLE, TOPICS_TRACK_TABLE, TOPICS_POSTED_TABLE, FORUMS_TRACK_TABLE, PROFILE_FIELDS_DATA_TABLE, MODERATOR_CACHE_TABLE, DRAFTS_TABLE, BOOKMARKS_TABLE, SESSIONS_KEYS_TABLE, PRIVMSGS_FOLDER_TABLE, PRIVMSGS_RULES_TABLE); + // Delete the miscellaneous (non-post) data for the user foreach ($table_ary as $table) { $sql = "DELETE FROM $table - WHERE user_id = $user_id"; + WHERE " . $user_id_sql; $db->sql_query($sql); } @@ -501,29 +576,29 @@ function user_delete($mode, $user_id, $post_username = false) // Delete user log entries about this user $sql = 'DELETE FROM ' . LOG_TABLE . ' - WHERE reportee_id = ' . $user_id; + WHERE ' . $db->sql_in_set('reportee_id', $user_ids); $db->sql_query($sql); // Change user_id to anonymous for this users triggered events $sql = 'UPDATE ' . LOG_TABLE . ' SET user_id = ' . ANONYMOUS . ' - WHERE user_id = ' . $user_id; + WHERE ' . $user_id_sql; $db->sql_query($sql); // Delete the user_id from the zebra table $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' - WHERE user_id = ' . $user_id . ' - OR zebra_id = ' . $user_id; + WHERE ' . $user_id_sql . ' + OR ' . $db->sql_in_set('zebra_id', $user_ids); $db->sql_query($sql); // Delete the user_id from the banlist $sql = 'DELETE FROM ' . BANLIST_TABLE . ' - WHERE ban_userid = ' . $user_id; + WHERE ' . $db->sql_in_set('ban_userid', $user_ids); $db->sql_query($sql); // Delete the user_id from the session table $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' - WHERE session_user_id = ' . $user_id; + WHERE ' . $db->sql_in_set('session_user_id', $user_ids); $db->sql_query($sql); // Clean the private messages tables from the user @@ -531,22 +606,28 @@ function user_delete($mode, $user_id, $post_username = false) { include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); } - phpbb_delete_user_pms($user_id); + 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 ($config['newest_user_id'] == $user_id) + if (in_array($config['newest_user_id'], $user_ids)) { update_last_username(); } - // Decrement number of users if this user is active - if ($user_row['user_type'] != USER_INACTIVE && $user_row['user_type'] != USER_IGNORE) - { - set_config_count('num_users', -1, true); - } - return false; } @@ -2059,13 +2140,14 @@ function avatar_remote($data, &$error) */ function avatar_upload($data, &$error) { - global $phpbb_root_path, $config, $db, $user, $phpEx; + global $phpbb_root_path, $config, $db, $user, $phpEx, $request; // Init upload class include_once($phpbb_root_path . 'includes/functions_upload.' . $phpEx); $upload = new fileupload('AVATAR_', array('jpg', 'jpeg', 'gif', 'png'), $config['avatar_filesize'], $config['avatar_min_width'], $config['avatar_min_height'], $config['avatar_max_width'], $config['avatar_max_height'], (isset($config['mime_triggers']) ? explode('|', $config['mime_triggers']) : false)); - if (!empty($_FILES['uploadfile']['name'])) + $uploadfile = $request->file('uploadfile'); + if (!empty($uploadfile['name'])) { $file = $upload->form_upload('uploadfile'); } @@ -2288,7 +2370,7 @@ function avatar_get_dimensions($avatar, $avatar_type, &$error, $current_x = 0, $ */ function avatar_process_user(&$error, $custom_userdata = false, $can_upload = null) { - global $config, $phpbb_root_path, $auth, $user, $db; + global $config, $phpbb_root_path, $auth, $user, $db, $request; $data = array( 'uploadurl' => request_var('uploadurl', ''), @@ -2330,7 +2412,8 @@ function avatar_process_user(&$error, $custom_userdata = false, $can_upload = nu $can_upload = ($config['allow_avatar_upload'] && file_exists($phpbb_root_path . $config['avatar_path']) && phpbb_is_writable($phpbb_root_path . $config['avatar_path']) && $change_avatar && (@ini_get('file_uploads') || strtolower(@ini_get('file_uploads')) == 'on')) ? true : false; } - if ((!empty($_FILES['uploadfile']['name']) || $data['uploadurl']) && $can_upload) + $uploadfile = $request->file('uploadfile'); + if ((!empty($uploadfile['name']) || $data['uploadurl']) && $can_upload) { list($sql_ary['user_avatar_type'], $sql_ary['user_avatar'], $sql_ary['user_avatar_width'], $sql_ary['user_avatar_height']) = avatar_upload($data, $error); } @@ -2759,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) { @@ -2818,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')) { @@ -2940,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']) { @@ -3039,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); @@ -3356,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)) { @@ -3452,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); diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index 4518e7b7cf..4dd5e5856a 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -22,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')); @@ -103,7 +103,7 @@ function mcp_forum_view($id, $mode, $action, $forum_info) $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'], @@ -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); @@ -435,7 +446,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) confirm_box(false, 'MERGE_TOPICS', $s_hidden_fields); } - $redirect = request_var('redirect', "index.$phpEx"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) @@ -444,7 +455,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) } else { - meta_refresh(3, append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id")); + meta_refresh(3, $redirect); trigger_error($user->lang[$success_msg] . '<br /><br />' . $return_link); } } diff --git a/phpBB/includes/mcp/mcp_front.php b/phpBB/includes/mcp/mcp_front.php index 13398e62bc..ba4b15895a 100644 --- a/phpBB/includes/mcp/mcp_front.php +++ b/phpBB/includes/mcp/mcp_front.php @@ -251,7 +251,7 @@ function mcp_front_view($id, $mode, $action) 'ORDER_BY' => 'p.message_time DESC', ); - $sql_ary = $db->sql_build_query('SELECT', $sql_ary); + $sql = $db->sql_build_query('SELECT', $sql_ary); $result = $db->sql_query_limit($sql, 5); $pm_by_id = $pm_list = array(); diff --git a/phpBB/includes/mcp/mcp_logs.php b/phpBB/includes/mcp/mcp_logs.php index c1724b20d9..f706840492 100644 --- a/phpBB/includes/mcp/mcp_logs.php +++ b/phpBB/includes/mcp/mcp_logs.php @@ -172,8 +172,8 @@ class mcp_logs $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); - + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + $template->assign_vars(array( 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $log_count, $config['topics_per_page'], $start), 'TOTAL' => $user->lang('TOTAL_LOGS', (int) $log_count), diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php index a21c67924d..95ca7c2e1b 100644 --- a/phpBB/includes/mcp/mcp_main.php +++ b/phpBB/includes/mcp/mcp_main.php @@ -915,7 +915,7 @@ function mcp_fork_topic($topic_ids) } $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 bbf618ebef..59cdf3c27e 100644 --- a/phpBB/includes/mcp/mcp_notes.php +++ b/phpBB/includes/mcp/mcp_notes.php @@ -217,7 +217,7 @@ 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, diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index 41e912fff9..be18dba944 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -291,16 +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'], diff --git a/phpBB/includes/mcp/mcp_post.php b/phpBB/includes/mcp/mcp_post.php index 2a52a858b3..520c964228 100644 --- a/phpBB/includes/mcp/mcp_post.php +++ b/phpBB/includes/mcp/mcp_post.php @@ -393,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']) { @@ -470,7 +470,7 @@ function change_poster(&$post_info, $userdata) { // 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 b44685b8a3..0b195aa9d8 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -421,7 +421,7 @@ class mcp_queue $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'], diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 2890cd56e2..3426d62cdb 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -71,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, r.reported_post_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, r.reported_post_uid, r.reported_post_bitfield, 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 @@ -227,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' => bbcode_nl2br($report['reported_post_text']), + 'POST_PREVIEW' => generate_text_for_display($report['reported_post_text'], $report['reported_post_uid'], $report['reported_post_bitfield'], OPTION_FLAG_BBCODE | OPTION_FLAG_SMILIES, false), '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'], @@ -413,7 +413,7 @@ class mcp_reports $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'], diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index e39e553ab6..63ff7bed72 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -311,7 +311,7 @@ function mcp_topic_view($id, $mode, $action) { 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']), @@ -534,7 +534,7 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) confirm_box(false, ($action == 'split_all') ? 'SPLIT_TOPIC_ALL' : 'SPLIT_TOPIC_BEYOND', $s_hidden_fields); } - $redirect = request_var('redirect', "index.$phpEx"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) @@ -543,7 +543,7 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) } else { - meta_refresh(3, append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id")); + meta_refresh(3, $redirect); trigger_error($user->lang[$success_msg] . '<br /><br />' . $return_link); } } @@ -640,7 +640,7 @@ function merge_posts($topic_id, $to_topic_id) confirm_box(false, 'MERGE_POSTS', $s_hidden_fields); } - $redirect = request_var('redirect', "index.$phpEx"); + $redirect = request_var('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); $redirect = reapply_sid($redirect); if (!$success_msg) @@ -649,7 +649,7 @@ function merge_posts($topic_id, $to_topic_id) } else { - meta_refresh(3, append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$to_forum_id&t=$to_topic_id")); + meta_refresh(3, $redirect); trigger_error($user->lang[$success_msg] . '<br /><br />' . $return_link); } } diff --git a/phpBB/includes/mcp/mcp_warn.php b/phpBB/includes/mcp/mcp_warn.php index aefddb7c01..6a8fb4c5d5 100644 --- a/phpBB/includes/mcp/mcp_warn.php +++ b/phpBB/includes/mcp/mcp_warn.php @@ -177,7 +177,7 @@ 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, diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index 6695047b56..1cd2a46fa1 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -1363,13 +1363,14 @@ class parse_message extends bbcode_firstpass */ function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false) { - global $config, $auth, $user, $phpbb_root_path, $phpEx, $db; + global $config, $auth, $user, $phpbb_root_path, $phpEx, $db, $request; $error = array(); $num_attachments = sizeof($this->attachment_data); $this->filename_data['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true)); - $upload_file = (isset($_FILES[$form_name]) && $_FILES[$form_name]['name'] != 'none' && trim($_FILES[$form_name]['name'])) ? true : false; + $upload = $request->file($form_name); + $upload_file = (!empty($upload) && $upload['name'] !== 'none' && trim($upload['name'])); $add_file = (isset($_POST['add_file'])) ? true : false; $delete_file = (isset($_POST['delete_file'])) ? true : false; diff --git a/phpBB/includes/questionnaire/questionnaire.php b/phpBB/includes/questionnaire/questionnaire.php index 5cb441d536..f0fb8c3c06 100644 --- a/phpBB/includes/questionnaire/questionnaire.php +++ b/phpBB/includes/questionnaire/questionnaire.php @@ -476,7 +476,6 @@ class phpbb_questionnaire_phpbb_data_provider $result['dbms'] = $dbms; $result['acm_type'] = $acm_type; - $result['load_extensions'] = $load_extensions; $result['user_agent'] = 'Unknown'; $result['dbms_version'] = $db->sql_server_info(true); diff --git a/phpBB/includes/request/interface.php b/phpBB/includes/request/interface.php index afd53002e3..741db35917 100644 --- a/phpBB/includes/request/interface.php +++ b/phpBB/includes/request/interface.php @@ -30,6 +30,7 @@ interface phpbb_request_interface const REQUEST = 2; const COOKIE = 3; const SERVER = 4; + const FILES = 5; /**#@-*/ /** diff --git a/phpBB/includes/request/request.php b/phpBB/includes/request/request.php index 4e425dbd27..ae3c526d89 100644 --- a/phpBB/includes/request/request.php +++ b/phpBB/includes/request/request.php @@ -34,6 +34,7 @@ class phpbb_request implements phpbb_request_interface phpbb_request_interface::REQUEST => '_REQUEST', phpbb_request_interface::COOKIE => '_COOKIE', phpbb_request_interface::SERVER => '_SERVER', + phpbb_request_interface::FILES => '_FILES', ); /** @@ -200,46 +201,31 @@ class phpbb_request implements phpbb_request_interface */ public function variable($var_name, $default, $multibyte = false, $super_global = phpbb_request_interface::REQUEST) { - $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); + return $this->_variable($var_name, $default, $multibyte, $super_global, true); + } - return $var; + /** + * 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); } /** @@ -284,6 +270,19 @@ class phpbb_request implements phpbb_request_interface } /** + * Shortcut method to retrieve $_FILES variables + * + * @param string $form_name The name of the file input form element + * + * @return array The uploaded file's information or an empty array if the + * variable does not exist in _FILES. + */ + public function file($form_name) + { + return $this->variable($form_name, array('name' => 'none'), false, phpbb_request_interface::FILES); + } + + /** * 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. @@ -351,4 +350,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 561e8fc251..1a5274ed14 100644 --- a/phpBB/includes/request/type_cast_helper.php +++ b/phpBB/includes/request/type_cast_helper.php @@ -93,15 +93,23 @@ class phpbb_request_type_cast_helper implements phpbb_request_type_cast_helper_i * @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(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result)); + $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) @@ -141,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)) { @@ -153,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 { @@ -174,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/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 20dcb74c0d..58a4dd7d6a 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -22,17 +22,69 @@ if (!defined('IN_PHPBB')) */ 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(); + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Holds the words entered by user, obtained by splitting the entered query on whitespace + * @var array + */ + protected $split_words = array(); + + /** + * Config object + * @var phpbb_config + */ + protected $config; + + /** + * DBAL object + * @var dbal + */ + protected $db; + + /** + * User object + * @var phpbb_user + */ + protected $user; + + /** + * Associative array stores the min and max word length to be searched + * @var array + */ + protected $word_length = array(); - public function __construct(&$error) + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * Contains common words. + * Common words are words with length less/more than min/max length + * @var array + */ + 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']); $error = false; } @@ -48,20 +100,50 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } /** + * 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_DATABASE']; + 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'])) @@ -75,19 +157,19 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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']); @@ -103,10 +185,8 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @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)\|#'); @@ -125,9 +205,9 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $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 @@ -169,7 +249,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base // 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]); @@ -221,11 +301,10 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base /** * 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 $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); $matches = array(); @@ -237,7 +316,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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]); } @@ -247,7 +326,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } /** - * Performs a search on keywords depending on display specific params. You have to run split_keywords() first. + * 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) @@ -265,14 +344,10 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @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. + // No keywords? No posts if (!$this->search_query) { return false; @@ -360,7 +435,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } 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 ' : ''; @@ -370,11 +445,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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 { @@ -384,7 +459,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $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)) : ''; @@ -392,16 +467,16 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $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); @@ -414,9 +489,9 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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) { @@ -449,14 +524,10 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base * @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. + // No author? No posts if (!sizeof($author_ary)) { return 0; @@ -491,13 +562,13 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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' : ''; @@ -533,7 +604,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } 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 @@ -572,21 +643,21 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } // 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) { @@ -605,14 +676,17 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } /** - * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated. + * 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(); @@ -631,7 +705,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base /** * 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(), array_unique($author_ids)); } @@ -639,10 +713,8 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base /** * Destroy old cache entries */ - function tidy() + public function tidy() { - global $db, $config; - // destroy too old cached search results $this->destroy_cache(array()); @@ -651,11 +723,11 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base /** * 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()) { @@ -671,7 +743,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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(255) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL'; } @@ -684,7 +756,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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'; } @@ -702,21 +774,21 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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()) { @@ -747,10 +819,10 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base 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; } @@ -758,7 +830,7 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base /** * Returns true if both FULLTEXT indexes exist */ - function index_created() + public function index_created() { if (empty($this->stats)) { @@ -771,25 +843,24 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base /** * 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; @@ -797,9 +868,9 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base $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']; @@ -820,26 +891,26 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base } } } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); - $this->stats['total_posts'] = empty($this->stats) ? 0 : $db->get_estimated_row_count(POSTS_TABLE); + $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['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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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 3e029c86d0..4623326fc7 100644 --- a/phpBB/includes/search/fulltext_native.php +++ b/phpBB/includes/search/fulltext_native.php @@ -22,32 +22,105 @@ if (!defined('IN_PHPBB')) */ class phpbb_search_fulltext_native extends phpbb_search_base { - var $stats = array(); - var $word_length = array(); - var $search_query; - var $common_words = array(); + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Associative array stores the min and max word length to be searched + * @var array + */ + protected $word_length = array(); + + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * Contains common words. + * Common words are words with length less/more than min/max length + * @var array + */ + protected $common_words = array(); - var $must_contain_ids = array(); - var $must_not_contain_ids = array(); - var $must_exclude_one_ids = array(); + /** + * Post ids of posts containing words that are to be included + * @var array + */ + protected $must_contain_ids = array(); + + /** + * Post ids of posts containing words that should not be included + * @var array + */ + protected $must_not_contain_ids = array(); + + /** + * Post ids of posts containing atleast one word that needs to be excluded + * @var array + */ + protected $must_exclude_one_ids = array(); + + /** + * Relative path to board root + * @var string + */ + protected $phpbb_root_path; /** - * Initialises the fulltext_native search backend with min/max word length and makes sure the UTF-8 normalizer is loaded. + * PHP Extension + * @var string + */ + protected $php_ext; + + /** + * Config object + * @var phpbb_config + */ + protected $config; + + /** + * DBAL object + * @var dbal + */ + protected $db; + + /** + * User object + * @var phpbb_user + */ + 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. + * @param boolean|string &$error is passed by reference and should either be set to false on success or an error message on failure */ - public function __construct(&$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); + } + if (!function_exists('utf8_decode_ncr')) + { + include($this->phpbb_root_path . 'includes/utf/utf_tools.' . $this->php_ext); } $error = false; @@ -64,26 +137,52 @@ class phpbb_search_fulltext_native extends phpbb_search_base } /** - * This function fills $this->search_query with the cleaned user search query. + * 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 * and combined with | inside brackets. They will afterwards be treated like * an standard search query. * * Then it analyses the query and fills the internal arrays $must_not_contain_ids, - * $must_contain_ids and $must_exclude_one_ids which are later used by keyword_search(). + * $must_contain_ids and $must_exclude_one_ids which are later used by keyword_search() * * @param string $keywords contains the search query string as entered by the user * @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)); @@ -182,9 +281,9 @@ class phpbb_search_fulltext_native extends phpbb_search_base $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 @@ -214,12 +313,12 @@ class phpbb_search_fulltext_native extends phpbb_search_base { $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']) { @@ -230,7 +329,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base $words[$row['word_text']] = (int) $row['word_id']; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); } unset($exact_words); @@ -301,7 +400,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base { 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])) @@ -334,7 +433,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base // 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); } @@ -346,7 +445,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base $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 { @@ -366,7 +465,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base $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 { @@ -398,7 +497,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base } /** - * Performs a search on keywords depending on display specific params. You have to run split_keywords() first. + * 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) @@ -416,13 +515,9 @@ class phpbb_search_fulltext_native extends phpbb_search_base * @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)) { @@ -537,7 +632,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base } } - $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); @@ -591,7 +686,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base { $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"; @@ -632,7 +727,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base } 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) @@ -645,18 +740,18 @@ class phpbb_search_fulltext_native extends phpbb_search_base 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) @@ -681,7 +776,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base ); } - switch ($db->sql_layer) + switch ($this->db->sql_layer) { case 'mysql4': case 'mysqli': @@ -695,17 +790,17 @@ class phpbb_search_fulltext_native extends phpbb_search_base 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) { @@ -751,14 +846,14 @@ class phpbb_search_fulltext_native extends phpbb_search_base 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)) { @@ -768,20 +863,20 @@ class phpbb_search_fulltext_native extends phpbb_search_base // if we use mysql and the total result count is not cached yet, retrieve it from the db if (!$total_results && $is_mysql) { - // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it. + // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it $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) { @@ -814,14 +909,10 @@ class phpbb_search_fulltext_native extends phpbb_search_base * @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. + // No author? No posts if (!sizeof($author_ary)) { return 0; @@ -856,13 +947,13 @@ class phpbb_search_fulltext_native extends phpbb_search_base 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' : ''; @@ -898,7 +989,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base } 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'; @@ -907,7 +998,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base // 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': @@ -929,7 +1020,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base } 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'; @@ -946,12 +1037,12 @@ class phpbb_search_fulltext_native extends phpbb_search_base $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) { @@ -994,26 +1085,26 @@ class phpbb_search_fulltext_native extends phpbb_search_base } // 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) { @@ -1041,13 +1132,9 @@ class phpbb_search_fulltext_native extends phpbb_search_base * * @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(); /** @@ -1120,14 +1207,10 @@ class phpbb_search_fulltext_native extends phpbb_search_base * @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 @@ -1153,14 +1236,14 @@ class phpbb_search_fulltext_native extends phpbb_search_base 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'])); @@ -1188,18 +1271,18 @@ class phpbb_search_fulltext_native extends phpbb_search_base { $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(); @@ -1208,15 +1291,15 @@ class phpbb_search_fulltext_native extends phpbb_search_base { $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 @@ -1233,22 +1316,22 @@ class phpbb_search_fulltext_native extends phpbb_search_base } $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; @@ -1258,18 +1341,18 @@ class phpbb_search_fulltext_native extends phpbb_search_base $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)); @@ -1282,20 +1365,18 @@ class phpbb_search_fulltext_native extends phpbb_search_base /** * 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']) { @@ -1307,32 +1388,32 @@ class phpbb_search_fulltext_native extends phpbb_search_base } $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), array_unique($author_ids)); @@ -1342,13 +1423,11 @@ class phpbb_search_fulltext_native extends phpbb_search_base * 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; @@ -1357,31 +1436,31 @@ class phpbb_search_fulltext_native extends phpbb_search_base $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 @@ -1389,8 +1468,8 @@ class phpbb_search_fulltext_native extends phpbb_search_base // 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); } @@ -1407,23 +1486,21 @@ class phpbb_search_fulltext_native extends phpbb_search_base /** * 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; } } @@ -1431,7 +1508,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base /** * Returns true if both FULLTEXT indexes exist */ - function index_created() + public function index_created() { if (!sizeof($this->stats)) { @@ -1444,26 +1521,22 @@ class phpbb_search_fulltext_native extends phpbb_search_base /** * 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; - - $this->stats['total_words'] = $db->get_estimated_row_count(SEARCH_WORDLIST_TABLE); - $this->stats['total_matches'] = $db->get_estimated_row_count(SEARCH_WORDMATCH_TABLE); + $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); } /** @@ -1481,9 +1554,8 @@ class phpbb_search_fulltext_native extends phpbb_search_base * * @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(); @@ -1680,7 +1752,7 @@ class phpbb_search_fulltext_native extends phpbb_search_base 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)) { @@ -1711,31 +1783,28 @@ class phpbb_search_fulltext_native extends phpbb_search_base /** * 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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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 index 84ce674564..08f64735b6 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -22,32 +22,98 @@ if (!defined('IN_PHPBB')) */ class phpbb_search_fulltext_postgres extends phpbb_search_base { - private $stats = array(); - private $split_words = array(); - private $tsearch_usable = false; - private $version; - private $tsearch_query; - private $phrase_search = false; - public $search_query; - public $common_words = array(); - public $word_length = array(); + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Holds the words entered by user, obtained by splitting the entered query on whitespace + * @var array + */ + protected $split_words = array(); + + /** + * True if PostgreSQL version supports tsearch + * @var boolean + */ + protected $tsearch_usable = false; + + /** + * Stores the PostgreSQL version + * @var string + */ + protected $version; + + /** + * Stores the tsearch query + * @var string + */ + protected $tsearch_query; + + /** + * True if phrase search is supported. + * PostgreSQL fulltext currently doesn't support it + * @var boolean + */ + protected $phrase_search = false; + + /** + * Config object + * @var phpbb_config + */ + protected $config; + + /** + * DBAL object + * @var dbal + */ + protected $db; + + /** + * User object + * @var phpbb_user + */ + protected $user; + + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * Contains common words. + * Common words are words with length less/more than min/max length + * @var array + */ + protected $common_words = array(); + + /** + * Associative array stores the min and max word length to be searched + * @var array + */ + protected $word_length = array(); /** * Constructor - * Creates a new phpbb_search_fulltext_postgres, which is used as a search backend. + * 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) + public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user) { - global $db, $config; - - $this->word_length = array('min' => $config['fulltext_postgres_min_word_len'], 'max' => $config['fulltext_postgres_max_word_len']); + $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 ($db->sql_layer == 'postgres') + if ($this->db->sql_layer == 'postgres') { - $pgsql_version = explode(',', substr($db->sql_server_info(), 10)); + $pgsql_version = explode(',', substr($this->db->sql_server_info(), 10)); $this->version = trim($pgsql_version[0]); if (version_compare($this->version, '8.3', '>=')) { @@ -62,8 +128,6 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * Returns the name of this search backend to be displayed to administrators * * @return string Name - * - * @access public */ public function get_name() { @@ -71,11 +135,39 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } /** + * 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 - * - * @access public */ public function supports_phrase_search() { @@ -86,21 +178,17 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * Checks for correct PostgreSQL version and stores min/max word length in the config * * @return string|bool Language key of the error/incompatiblity occured - * - * @access public */ - function init() + public function init() { - global $db, $user; - - if ($db->sql_layer != 'postgres') + if ($this->db->sql_layer != 'postgres') { - return $user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE']; + return $this->user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE']; } if (!$this->tsearch_usable) { - return $user->lang['FULLTEXT_POSTGRES_TS_NOT_USABLE']; + return $this->user->lang['FULLTEXT_POSTGRES_TS_NOT_USABLE']; } return false; @@ -113,13 +201,9 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @param string &$keywords Contains the keyword as entered by the user * @param string $terms is either 'all' or 'any' * @return bool false if no valid keywords were found and otherwise true - * - * @access public */ - function split_keywords(&$keywords, $terms) + public function split_keywords(&$keywords, $terms) { - global $config; - if ($terms == 'all') { $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#\+#', '#-#', '#\|#'); @@ -143,7 +227,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base // check word length $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); - if (($clean_len < $config['fulltext_postgres_min_word_len']) || ($clean_len > $config['fulltext_postgres_max_word_len'])) + 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]); @@ -208,13 +292,9 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base /** * Turns text into an array of words * @param string $text contains post text/subject - * - * @access public */ - function split_message($text) + public function split_message($text) { - global $config; - // Split words $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); $matches = array(); @@ -226,7 +306,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base for ($i = 0, $n = sizeof($text); $i < $n; $i++) { $text[$i] = trim($text[$i]); - if (utf8_strlen($text[$i]) < $config['fulltext_postgres_min_word_len'] || utf8_strlen($text[$i]) > $config['fulltext_postgres_max_word_len']) + 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]); } @@ -236,7 +316,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } /** - * Performs a search on keywords depending on display specific params. You have to run split_keywords() first. + * 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) @@ -254,19 +334,21 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @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. + // No keywords? No posts if (!$this->search_query) { return false; } + // When search query contains queries like -foo + if (strpos($this->search_query, '+') === false) + { + return false; + } + // generate a search_key from all the options to identify the results $search_key = md5(implode('#', array( implode(', ', $this->split_words), @@ -349,7 +431,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } 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 = ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id'; @@ -360,11 +442,11 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base if (sizeof($author_ary) && $author_name) { // first one matches post of registered users, second one guests and deleted users - $sql_author = '(' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + $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 ' . $db->sql_in_set('p.poster_id', $author_ary); + $sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary); } else { @@ -374,7 +456,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $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)) : ''; @@ -383,7 +465,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base $tmp_sql_match = array(); foreach (explode(',', $sql_match) as $sql_match_column) { - $tmp_sql_match[] = "to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', " . $sql_match_column . ") @@ to_tsquery ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', '" . $db->sql_escape($this->tsearch_query) . "')"; + $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 @@ -391,13 +473,13 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base WHERE (" . implode(' OR ', $tmp_sql_match) . ") $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[] = $row[$field]; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); $id_ary = array_unique($id_ary); @@ -442,14 +524,10 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @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. + // No author? No posts if (!sizeof($author_ary)) { return 0; @@ -484,13 +562,13 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base 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' : ''; @@ -526,7 +604,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } 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) . ')'; } // Build the query for really selecting the post_ids @@ -562,13 +640,13 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } // 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[] = $row[$field]; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); // retrieve the total result count if needed if (!$result_count) @@ -592,7 +670,7 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base } /** - * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated. + * 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 @@ -600,13 +678,9 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * @param string $subject contains the subject of the post to index * @param int $poster_id contains the user id of the poster * @param int $forum_id contains the forum id of parent forum of the post - * - * @access public */ - function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + 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(); @@ -624,23 +698,17 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base /** * Destroy cached results, that might be outdated after deleting a post - * - * @access public */ - 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); } /** * Destroy old cache entries - * - * @access public */ - function tidy() + public function tidy() { - global $db, $config; - // destroy too old cached search results $this->destroy_cache(array()); @@ -651,13 +719,9 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * Create fulltext index * * @return string|bool error string is returned incase of errors otherwise false - * - * @access public */ - function create_index($acp_module, $u_action) + public function create_index($acp_module, $u_action) { - global $db, $config; - // Make sure we can actually use PostgreSQL with fulltext indexes if ($error = $this->init()) { @@ -671,15 +735,15 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base if (!isset($this->stats['post_subject'])) { - $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_subject))"); + $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'])) { - $db->sql_query("CREATE INDEX " . POSTS_TABLE . "_" . $config['fulltext_postgres_ts_name'] . "_post_text ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $db->sql_escape($config['fulltext_postgres_ts_name']) . "', post_text))"); + $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))"); } - $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); return false; } @@ -688,13 +752,9 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base * Drop fulltext index * * @return string|bool error string is returned incase of errors otherwise false - * - * @access public */ - function delete_index($acp_module, $u_action) + public function delete_index($acp_module, $u_action) { - global $db; - // Make sure we can actually use PostgreSQL with fulltext indexes if ($error = $this->init()) { @@ -708,25 +768,23 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base if (isset($this->stats['post_subject'])) { - $db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); + $this->db->sql_query('DROP INDEX ' . $this->stats['post_subject']['relname']); } if (isset($this->stats['post_text'])) { - $db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); + $this->db->sql_query('DROP INDEX ' . $this->stats['post_text']['relname']); } - $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); return false; } /** * Returns true if both FULLTEXT indexes exist - * - * @access public */ - function index_created() + public function index_created() { if (empty($this->stats)) { @@ -738,33 +796,25 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base /** * Returns an associative array containing information about the indexes - * - * @access public */ - function index_stats() + public function index_stats() { - global $user; - if (empty($this->stats)) { $this->get_stats(); } return array( - $user->lang['FULLTEXT_POSTGRES_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + $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 - * - * @access private */ - function get_stats() + protected function get_stats() { - global $db, $config; - - if ($db->sql_layer != 'postgres') + if ($this->db->sql_layer != 'postgres') { $this->stats = array(); return; @@ -776,74 +826,70 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base AND pg_catalog.pg_table_is_visible(c1.oid) AND c1.oid = i.indrelid AND i.indexrelid = c2.oid"; - $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 PostgreSQL versions which didn't use Index_type if (strpos($row['indexdef'], 'to_tsvector') !== false) { - if ($row['relname'] == POSTS_TABLE . '_' . $config['fulltext_postgres_ts_name'] . '_post_text' || $row['relname'] == POSTS_TABLE . '_post_text') + 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 . '_' . $config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') + 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; } } } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); - $this->stats['total_posts'] = $config['num_posts']; + $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 - * - * @access public */ - function acp() + public function acp() { - global $user, $config, $db; - $tpl = ' <dl> - <dt><label>' . $user->lang['FULLTEXT_POSTGRES_VERSION_CHECK'] . '</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN'] . '</span></dt> - <dd>' . (($this->tsearch_usable) ? $user->lang['YES'] : $user->lang['NO']) . ' (PostgreSQL ' . $this->version . ')</dd> + <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>' . $user->lang['FULLTEXT_POSTGRES_TS_NAME'] . '</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '</span></dt> + <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 ($db->sql_layer == 'postgres' && $this->tsearch_usable) + if ($this->db->sql_layer == 'postgres' && $this->tsearch_usable) { $sql = 'SELECT cfgname AS ts_name FROM pg_ts_config'; - $result = $db->sql_query($sql); + $result = $this->db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) + while ($row = $this->db->sql_fetchrow($result)) { - $tpl .= '<option value="' . $row['ts_name'] . '"' . ($row['ts_name'] === $config['fulltext_postgres_ts_name'] ? ' selected="selected"' : '') . '>' . $row['ts_name'] . '</option>'; + $tpl .= '<option value="' . $row['ts_name'] . '"' . ($row['ts_name'] === $this->config['fulltext_postgres_ts_name'] ? ' selected="selected"' : '') . '>' . $row['ts_name'] . '</option>'; } - $db->sql_freeresult($result); + $this->db->sql_freeresult($result); } else { - $tpl .= '<option value="' . $config['fulltext_postgres_ts_name'] . '" selected="selected">' . $config['fulltext_postgres_ts_name'] . '</option>'; + $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">' . $user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN'] . ':</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN'] . '</span></dt> - <dd><input id="fulltext_postgres_min_word_len" type="text" size="3" maxlength="3" name="config[fulltext_postgres_min_word_len]" value="' . (int) $config['fulltext_postgres_min_word_len'] . '" /></dd> + <dt><label for="fulltext_postgres_min_word_len">' . $this->user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN'] . $this->user->lang['COLON'] . '</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">' . $user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN'] . ':</label><br /><span>' . $user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN'] . '</span></dt> - <dd><input id="fulltext_postgres_max_word_len" type="text" size="3" maxlength="3" name="config[fulltext_postgres_max_word_len]" value="' . (int) $config['fulltext_postgres_max_word_len'] . '" /></dd> + <dt><label for="fulltext_postgres_max_word_len">' . $this->user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN'] . $this->user->lang['COLON'] . '</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> '; diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php new file mode 100644 index 0000000000..dd5634b623 --- /dev/null +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -0,0 +1,892 @@ +<?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 +{ + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Holds the words entered by user, obtained by splitting the entered query on whitespace + * @var array + */ + protected $split_words = array(); + + /** + * Holds unique sphinx id + * @var string + */ + protected $id; + + /** + * Stores the names of both main and delta sphinx indexes + * separated by a semicolon + * @var string + */ + protected $indexes; + + /** + * Sphinx searchd client object + * @var SphinxClient + */ + protected $sphinx; + + /** + * Relative path to board root + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP Extension + * @var string + */ + protected $php_ext; + + /** + * Auth object + * @var phpbb_auth + */ + protected $auth; + + /** + * Config object + * @var phpbb_config + */ + protected $config; + + /** + * DBAL object + * @var dbal + */ + protected $db; + + /** + * Database Tools object + * @var phpbb_db_tools + */ + protected $db_tools; + + /** + * Stores the database type if supported by sphinx + * @var string + */ + protected $dbtype; + + /** + * User object + * @var phpbb_user + */ + protected $user; + + /** + * Stores the generated content of the sphinx config file + * @var string + */ + protected $config_file_data = ''; + + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * 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 an empty array as there are no common_words + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return array(); + } + + /** + * 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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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'] . $this->user->lang['COLON'] . '</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 257ffb07f6..ee8a4094c7 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -474,7 +474,7 @@ class phpbb_session else { // Added logging temporarly to help debug bugs... - if (defined('DEBUG_EXTRA') && $this->data['user_id'] != ANONYMOUS) + if (defined('DEBUG') && $this->data['user_id'] != ANONYMOUS) { if ($referer_valid) { 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/style/resource_locator.php b/phpBB/includes/style/resource_locator.php index fafa11c352..8658fe4a36 100644 --- a/phpBB/includes/style/resource_locator.php +++ b/phpBB/includes/style/resource_locator.php @@ -94,10 +94,7 @@ class phpbb_style_resource_locator implements 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. + * {@inheritDoc} */ public function set_filenames(array $filename_array) { @@ -121,14 +118,7 @@ class phpbb_style_resource_locator implements phpbb_template_locator } /** - * 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 + * {@inheritDoc} */ public function get_filename_for_handle($handle) { @@ -140,24 +130,7 @@ class phpbb_style_resource_locator implements phpbb_template_locator } /** - * 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 + * {@inheritDoc} */ public function get_virtual_source_file_for_handle($handle) { @@ -172,23 +145,7 @@ class phpbb_style_resource_locator implements phpbb_template_locator } /** - * 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 + * {@inheritDoc} */ public function get_source_file_for_handle($handle, $find_all = false) { @@ -239,23 +196,7 @@ class phpbb_style_resource_locator implements phpbb_template_locator } /** - * 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 + * {@inheritDoc} */ public function get_first_file_location($files, $return_default = false, $return_full_path = true) { diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index 6b7cd31cb3..36298b49ec 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -124,8 +124,6 @@ class phpbb_style $this->template->cachepath = $this->phpbb_root_path . 'cache/tpl_' . str_replace('_', '-', $name) . '_'; - $this->template->context = new phpbb_template_context(); - if ($template_path !== false) { $this->template->template_path = $this->locator->template_path = $template_path; diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index ad2e35de6a..66d28242a3 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -209,7 +209,7 @@ class phpbb_template_filter extends php_user_filter */ - $data = preg_replace('~(?<!^)(<\?php(?:(?<!\?>).)+(?<!/\*\*/)\?>)$~m', "$1\n", $data); + $data = preg_replace('~(?<!^)(<\?php.+(?<!/\*\*/)\?>)$~m', "$1\n", $data); $data = str_replace('/**/?>', "?>\n", $data); $data = str_replace('?><?php', '', $data); return $data; @@ -362,6 +362,43 @@ class phpbb_template_filter extends php_user_filter } /** + * Parse paths of the form {FOO}/a/{BAR}/b + * + * Note: this method assumes at least one variable in the path, this should + * be checked before this method is called. + * + * @param string $path The path to parse + * @param string $include_type The type of template function to call + * @return string An appropriately formatted string to include in the + * template or an empty string if an expression like S_FIRST_ROW was + * incorrectly used + */ + private function parse_dynamic_path($path, $include_type) + { + $matches = array(); + $replace = array(); + $is_expr = true; + + preg_match_all('#\{((?:' . self::REGEX_NS . '\.)*)(\$)?(' . self::REGEX_VAR . ')\}#', $path, $matches); + foreach ($matches[0] as $var_str) + { + $tmp_is_expr = false; + $var = $this->get_varref($var_str, $tmp_is_expr); + $is_expr = $is_expr && $tmp_is_expr; + $replace[] = "' . $var . '"; + } + + if (!$is_expr) + { + return " \$_template->$include_type('" . str_replace($matches[0], $replace, $path) . "', true);"; + } + else + { + return ''; + } + } + + /** * Compile variables * * @param string $text_blocks Variable reference in source template @@ -774,15 +811,9 @@ class phpbb_template_filter extends php_user_filter private function compile_tag_include($tag_args) { // Process dynamic includes - if ($tag_args[0] == '{') + if (strpos($tag_args, '{') !== false) { - $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 $this->parse_dynamic_path($tag_args, '_tpl_include'); } return "\$_template->_tpl_include('$tag_args');"; @@ -796,6 +827,11 @@ class phpbb_template_filter extends php_user_filter */ private function compile_tag_include_php($tag_args) { + if (strpos($tag_args, '{') !== false) + { + return $this->parse_dynamic_path($tag_args, '_php_include'); + } + return "\$_template->_php_include('$tag_args');"; } @@ -883,14 +919,9 @@ class phpbb_template_filter extends php_user_filter private function compile_tag_include_js($tag_args) { // Process dynamic includes - if ($tag_args[0] == '{') + if (strpos($tag_args, '{') !== false) { - $var = $this->get_varref($tag_args, $is_expr); - if (!$is_expr) - { - return " \$_template->_js_include($var, true);"; - } - return ''; + return $this->parse_dynamic_path($tag_args, '_js_include'); } // Locate file diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 13fa845659..8a7dc6b2f3 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -36,7 +36,7 @@ class phpbb_template * Stores template data used during template rendering. * @var phpbb_template_context */ - public $context; + private $context; /** * Path of the cache directory for the template @@ -86,8 +86,9 @@ class phpbb_template * @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) + 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; @@ -95,6 +96,7 @@ class phpbb_template $this->user = $user; $this->locator = $locator; $this->template_path = $this->locator->template_path; + $this->context = $context; } /** @@ -139,7 +141,7 @@ class phpbb_template */ public function display($handle) { - $result = $this->call_hook($handle); + $result = $this->call_hook($handle, __FUNCTION__); if ($result !== false) { return $result[0]; @@ -174,16 +176,17 @@ class phpbb_template * 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) + private function call_hook($handle, $method) { global $phpbb_hook; - if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, __FUNCTION__), $handle, $this)) + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) { - if ($phpbb_hook->hook_return(array(__CLASS__, __FUNCTION__))) + if ($phpbb_hook->hook_return(array(__CLASS__, $method))) { - $result = $phpbb_hook->hook_return_result(array(__CLASS__, __FUNCTION__)); + $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); return array($result); } } @@ -247,7 +250,7 @@ class phpbb_template * 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 + * every time it is needed. DEBUG define and load_tplcompile * configuration setting may be used to force templates to be always * recompiled. * @@ -265,7 +268,7 @@ class phpbb_template { $output_file = $this->_compiled_file_for_handle($handle); - $recompile = defined('DEBUG_EXTRA') || + $recompile = defined('DEBUG') || !file_exists($output_file) || @filesize($output_file) === 0; @@ -456,8 +459,40 @@ class phpbb_template } /** - * Locates source template path, accounting for styles tree and verifying that - * the path exists. + * 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. @@ -473,7 +508,7 @@ class phpbb_template */ public function locate($files, $return_default = false, $return_full_path = true) { - // add tempalte path prefix + // add template path prefix $templates = array(); if (is_string($files)) { @@ -503,7 +538,11 @@ class phpbb_template // Locate file if ($locate) { - $file = $this->locator->get_first_file_location(array($file), true, true); + $located = $this->locator->get_first_file_location(array($file), false, true); + if ($located) + { + $file = $located; + } } else if ($relative) { diff --git a/phpBB/includes/ucp/info/ucp_profile.php b/phpBB/includes/ucp/info/ucp_profile.php index 968538a178..201216e9fd 100644 --- a/phpBB/includes/ucp/info/ucp_profile.php +++ b/phpBB/includes/ucp/info/ucp_profile.php @@ -20,7 +20,7 @@ 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/ucp_attachments.php b/phpBB/includes/ucp/ucp_attachments.php index e4c351709b..dc095e7b73 100644 --- a/phpBB/includes/ucp/ucp_attachments.php +++ b/phpBB/includes/ucp/ucp_attachments.php @@ -171,8 +171,8 @@ 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); - + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); + $template->assign_vars(array( 'PAGE_NUMBER' => phpbb_on_page($template, $user, $base_url, $num_attachments, $config['topics_per_page'], $start), 'TOTAL_ATTACHMENTS' => $num_attachments, diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php index aa9510b63b..9652986cf2 100644 --- a/phpBB/includes/ucp/ucp_groups.php +++ b/phpBB/includes/ucp/ucp_groups.php @@ -513,7 +513,8 @@ class ucp_groups $data['height'] = request_var('height', ''); $delete = request_var('delete', ''); - if (!empty($_FILES['uploadfile']['tmp_name']) || $data['uploadurl'] || $data['remotelink']) + $uploadfile = $request->file('uploadfile'); + if (!empty($uploadfile['tmp_name']) || $data['uploadurl'] || $data['remotelink']) { // Avatar stuff $var_ary = array( @@ -527,7 +528,7 @@ class ucp_groups { $data['user_id'] = "g$group_id"; - if ((!empty($_FILES['uploadfile']['tmp_name']) || $data['uploadurl']) && $can_upload) + if ((!empty($uploadfile['tmp_name']) || $data['uploadurl']) && $can_upload) { list($submit_ary['avatar_type'], $submit_ary['avatar'], $submit_ary['avatar_width'], $submit_ary['avatar_height']) = avatar_upload($data, $error); } @@ -846,7 +847,7 @@ class ucp_groups $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, @@ -1069,7 +1070,8 @@ class ucp_groups 'mode' => $mode, 'action' => $action ); - confirm_box(false, $user->lang('GROUP_CONFIRM_ADD_USERS', sizeof($name_ary), 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 f21ea2471b..94fd59433b 100644 --- a/phpBB/includes/ucp/ucp_main.php +++ b/phpBB/includes/ucp/ucp_main.php @@ -69,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)) @@ -671,7 +670,7 @@ 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( '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), @@ -837,7 +836,7 @@ 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_compose.php b/phpBB/includes/ucp/ucp_pm_compose.php index 1b474457b3..5577e8dab3 100644 --- a/phpBB/includes/ucp/ucp_pm_compose.php +++ b/phpBB/includes/ucp/ucp_pm_compose.php @@ -749,7 +749,8 @@ function compose_pm($id, $mode, $action, $user_folders = array()) $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>'); + $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) @@ -837,11 +838,11 @@ function compose_pm($id, $mode, $action, $user_folders = array()) $post_id = request_var('p', 0); if ($config['allow_post_links']) { - $message_link = "[url=" . generate_board_url() . "/viewtopic.$phpEx?p={$post_id}#p{$post_id}]{$user->lang['SUBJECT']}: {$message_subject}[/url]\n\n"; + $message_link = "[url=" . generate_board_url() . "/viewtopic.$phpEx?p={$post_id}#p{$post_id}]{$user->lang['SUBJECT']}{$user->lang['COLON']} {$message_subject}[/url]\n\n"; } else { - $message_link = $user->lang['SUBJECT'] . ': ' . $message_subject . " (" . generate_board_url() . "/viewtopic.$phpEx?p={$post_id}#p{$post_id})\n\n"; + $message_link = $user->lang['SUBJECT'] . $user->lang['COLON'] . ' ' . $message_subject . " (" . generate_board_url() . "/viewtopic.$phpEx?p={$post_id}#p{$post_id})\n\n"; } } else @@ -874,7 +875,7 @@ function compose_pm($id, $mode, $action, $user_folders = array()) $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); diff --git a/phpBB/includes/ucp/ucp_pm_viewfolder.php b/phpBB/includes/ucp/ucp_pm_viewfolder.php index dc77587452..625da23736 100644 --- a/phpBB/includes/ucp/ucp_pm_viewfolder.php +++ b/phpBB/includes/ucp/ucp_pm_viewfolder.php @@ -176,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']); @@ -266,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(); @@ -277,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,7 +453,7 @@ function get_pm_from($folder_id, $folder, $user_id) $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( '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), diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index c55e8850a6..c85b05f144 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -21,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, $request, $phpEx, $config; + global $phpbb_root_path, $request, $phpEx, $config, $phpbb_dispatcher; $user->add_lang(array('viewtopic', 'memberlist')); @@ -204,7 +204,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) } } - $template->assign_vars(array( + $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']), @@ -257,6 +257,7 @@ 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)), @@ -265,9 +266,28 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) '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'])) { @@ -282,6 +302,12 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_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_profile.php b/phpBB/includes/ucp/ucp_profile.php index 2ac82fb52f..89bf20a30f 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -46,9 +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'])), - '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'); diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 6ce53a79ab..c57aec00a0 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -170,8 +170,8 @@ class ucp_register $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', '')), 'lang' => basename(request_var('lang', $user->lang_name)), 'tz' => request_var('tz', $timezone), diff --git a/phpBB/includes/ucp/ucp_zebra.php b/phpBB/includes/ucp/ucp_zebra.php index efe928b387..a669c450a4 100644 --- a/phpBB/includes/ucp/ucp_zebra.php +++ b/phpBB/includes/ucp/ucp_zebra.php @@ -25,7 +25,7 @@ class ucp_zebra function main($id, $mode) { - global $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx, $request; + 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 = ''; @@ -54,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; @@ -186,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; diff --git a/phpBB/includes/user.php b/phpBB/includes/user.php index fcbfaaddfa..9ddd806b27 100644 --- a/phpBB/includes/user.php +++ b/phpBB/includes/user.php @@ -76,18 +76,18 @@ class phpbb_user extends phpbb_session 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) { - $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']; + $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 { - $this->lang_name = basename($config['default_lang']); - $this->date_format = $config['default_dateformat']; + $user_lang_name = basename($config['default_lang']); + $user_date_format = $config['default_dateformat']; $user_timezone = $config['board_timezone']; /** @@ -107,7 +107,7 @@ class phpbb_user extends phpbb_session if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) { - $this->lang_name = $config['default_lang'] = $accept_lang; + $user_lang_name = $config['default_lang'] = $accept_lang; break; } else @@ -118,7 +118,7 @@ class phpbb_user extends phpbb_session if (file_exists($this->lang_path . $accept_lang . "/common.$phpEx")) { - $this->lang_name = $config['default_lang'] = $accept_lang; + $user_lang_name = $config['default_lang'] = $accept_lang; break; } } @@ -127,6 +127,28 @@ class phpbb_user extends phpbb_session */ } + $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); @@ -140,8 +162,8 @@ class phpbb_user extends phpbb_session // 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"); + // Do not suppress error if in DEBUG mode + $include_result = (defined('DEBUG')) ? (include $this->lang_path . $this->lang_name . "/common.$phpEx") : (@include $this->lang_path . $this->lang_name . "/common.$phpEx"); if ($include_result === false) { @@ -230,7 +252,7 @@ class phpbb_user extends phpbb_session // 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')) + if (!defined('DEBUG') && !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_')) @@ -596,8 +618,8 @@ class phpbb_user extends phpbb_session return; } - // Do not suppress error if in DEBUG_EXTRA mode - $include_result = (defined('DEBUG_EXTRA')) ? (include $language_filename) : (@include $language_filename); + // Do not suppress error if in DEBUG mode + $include_result = (defined('DEBUG')) ? (include $language_filename) : (@include $language_filename); if ($include_result === false) { |