From 0d419f39647e38ed6f3aa452bc09ef4838ddef34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrej=20Rama=C5=A1euski?= <andrej@x2.cz> Date: Thu, 6 Oct 2022 23:34:38 +0200 Subject: [PATCH] Podpora show results --- .gitlab-ci.yml | 2 +- phpbb/posting.php | 2109 +++++++++++++++++++++++++++++++++++++ phpbb/viewtopic.php | 2449 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 4559 insertions(+), 1 deletion(-) create mode 100644 phpbb/posting.php create mode 100644 phpbb/viewtopic.php diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e7cff8..dd6dfba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ image: docker:20.10.9 variables: DOCKER_TLS_CERTDIR: "/certs" - BUILD_VERSION: p5 + BUILD_VERSION: p6 services: - docker:20.10.9-dind diff --git a/phpbb/posting.php b/phpbb/posting.php new file mode 100644 index 0000000..f722d0e --- /dev/null +++ b/phpbb/posting.php @@ -0,0 +1,2109 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_posting.' . $phpEx); +include($phpbb_root_path . 'includes/functions_display.' . $phpEx); +include($phpbb_root_path . 'includes/message_parser.' . $phpEx); + + +// Start session management +$user->session_begin(); +$auth->acl($user->data); + + +// Grab only parameters needed here +$draft_id = $request->variable('d', 0); + +$preview = (isset($_POST['preview'])) ? true : false; +$save = (isset($_POST['save'])) ? true : false; +$load = (isset($_POST['load'])) ? true : false; +$confirm = $request->is_set_post('confirm'); +$cancel = (isset($_POST['cancel']) && !isset($_POST['save'])) ? true : false; + +$refresh = (isset($_POST['add_file']) || isset($_POST['delete_file']) || $save || $load || $preview); +$submit = $request->is_set_post('post') && !$refresh && !$preview; +$mode = $request->variable('mode', ''); + +// Only assign required URL parameters +$forum_id = 0; +$topic_id = 0; +$post_id = 0; + +switch ($mode) +{ + case 'popup': + case 'smilies': + $forum_id = $request->variable('f', 0); + break; + + case 'post': + $forum_id = $request->variable('f', 0); + if (!$forum_id) + { + trigger_error('NO_FORUM'); + } + break; + + case 'bump': + case 'reply': + $topic_id = $request->variable('t', 0); + if ($topic_id) + { + $sql = 'SELECT forum_id + FROM ' . TOPICS_TABLE . " + WHERE topic_id = $topic_id"; + $result = $db->sql_query($sql); + $forum_id = (int) $db->sql_fetchfield('forum_id'); + $db->sql_freeresult($result); + } + + if (!$topic_id || !$forum_id) + { + trigger_error('NO_TOPIC'); + } + break; + + case 'edit': + case 'delete': + case 'quote': + case 'soft_delete': + $post_id = $request->variable('p', 0); + if ($post_id) + { + $topic_forum = []; + + $sql = 'SELECT t.topic_id, t.forum_id + FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . ' p + WHERE p.post_id = ' . $post_id . ' + AND t.topic_id = p.topic_id'; + $result = $db->sql_query($sql); + $topic_forum = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + if (!$post_id || !$topic_forum) + { + $user->setup('posting'); + trigger_error('NO_POST'); + } + + $topic_id = (int) $topic_forum['topic_id']; + $forum_id = (int) $topic_forum['forum_id']; + break; +} + +// If the user is not allowed to delete the post, we try to soft delete it, so we overwrite the mode here. +if ($mode == 'delete' && (($confirm && !$request->is_set_post('delete_permanent')) || !$auth->acl_gets('f_delete', 'm_delete', $forum_id))) +{ + $mode = 'soft_delete'; +} + +$error = $post_data = array(); +$current_time = time(); + +/** +* This event allows you to alter the above parameters, such as submit and mode +* +* Note: $refresh must be true to retain previously submitted form data. +* +* Note: The template class will not work properly until $user->setup() is +* called, and it has not been called yet. Extensions requiring template +* assignments should use an event that comes later in this file. +* +* @event core.modify_posting_parameters +* @var int post_id ID of the post +* @var int topic_id ID of the topic +* @var int forum_id ID of the forum +* @var int draft_id ID of the draft +* @var bool submit Whether or not the form has been submitted +* @var bool preview Whether or not the post is being previewed +* @var bool save Whether or not a draft is being saved +* @var bool load Whether or not a draft is being loaded +* @var bool cancel Whether or not to cancel the form (returns to +* viewtopic or viewforum depending on if the user +* is posting a new topic or editing a post) +* @var bool refresh Whether or not to retain previously submitted data +* @var string mode What action to take if the form has been submitted +* post|reply|quote|edit|delete|bump|smilies|popup +* @var array error Any error strings; a non-empty array aborts +* form submission. +* NOTE: Should be actual language strings, NOT +* language keys. +* @since 3.1.0-a1 +* @changed 3.1.2-RC1 Removed 'delete' var as it does not exist +* @changed 3.2.4-RC1 Remove unused 'lastclick' var +*/ +$vars = array( + 'post_id', + 'topic_id', + 'forum_id', + 'draft_id', + 'submit', + 'preview', + 'save', + 'load', + 'cancel', + 'refresh', + 'mode', + 'error', +); +extract($phpbb_dispatcher->trigger_event('core.modify_posting_parameters', compact($vars))); + +// Was cancel pressed? If so then redirect to the appropriate page +if ($cancel) +{ + $redirect = ($post_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $post_id) . '#p' . $post_id : (($topic_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=' . $topic_id) : (($forum_id) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id) : append_sid("{$phpbb_root_path}index.$phpEx"))); + redirect($redirect); +} + +/* @var $phpbb_content_visibility \phpbb\content_visibility */ +$phpbb_content_visibility = $phpbb_container->get('content.visibility'); + +// We need to know some basic information in all cases before we do anything. +switch ($mode) +{ + case 'post': + $sql = 'SELECT * + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + break; + + case 'bump': + case 'reply': + $sql = 'SELECT f.*, t.* + FROM ' . TOPICS_TABLE . ' t, ' . FORUMS_TABLE . " f + WHERE t.topic_id = $topic_id + AND f.forum_id = t.forum_id + AND " . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.'); + break; + + case 'quote': + case 'edit': + case 'delete': + case 'soft_delete': + $sql = 'SELECT f.*, t.*, p.*, u.username, u.username_clean, u.user_sig, u.user_sig_bbcode_uid, u.user_sig_bbcode_bitfield + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . FORUMS_TABLE . ' f, ' . USERS_TABLE . " u + WHERE p.post_id = $post_id + AND t.topic_id = p.topic_id + AND u.user_id = p.poster_id + AND f.forum_id = t.forum_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.'); + break; + + case 'smilies': + $sql = ''; + generate_smilies('window', $forum_id); + break; + + case 'popup': + if ($forum_id) + { + $sql = 'SELECT forum_style + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $forum_id; + } + else + { + phpbb_upload_popup(); + return; + } + break; + + default: + $sql = ''; + break; +} + +if (!$sql) +{ + $user->setup('posting'); + trigger_error('NO_POST_MODE'); +} + +$result = $db->sql_query($sql); +$post_data = $db->sql_fetchrow($result); +$db->sql_freeresult($result); + +if (!$post_data) +{ + if (!($mode == 'post' || $mode == 'bump' || $mode == 'reply')) + { + $user->setup('posting'); + } + trigger_error(($mode == 'post' || $mode == 'bump' || $mode == 'reply') ? 'NO_TOPIC' : 'NO_POST'); +} + +/** +* This event allows you to bypass reply/quote test of an unapproved post. +* +* @event core.posting_modify_row_data +* @var array post_data All post data from database +* @var string mode What action to take if the form has been submitted +* post|reply|quote|edit|delete|bump|smilies|popup +* @var int topic_id ID of the topic +* @var int forum_id ID of the forum +* @since 3.2.8-RC1 +*/ +$vars = array( + 'post_data', + 'mode', + 'topic_id', + 'forum_id', +); +extract($phpbb_dispatcher->trigger_event('core.posting_modify_row_data', compact($vars))); + +// Not able to reply to unapproved posts/topics +// TODO: add more descriptive language key +if ($auth->acl_get('m_approve', $forum_id) && ((($mode == 'reply' || $mode == 'bump') && $post_data['topic_visibility'] != ITEM_APPROVED) || ($mode == 'quote' && $post_data['post_visibility'] != ITEM_APPROVED))) +{ + trigger_error(($mode == 'reply' || $mode == 'bump') ? 'TOPIC_UNAPPROVED' : 'POST_UNAPPROVED'); +} + +if ($mode == 'popup') +{ + phpbb_upload_popup($post_data['forum_style']); + return; +} + +$user->setup(array('posting', 'mcp', 'viewtopic'), $post_data['forum_style']); + +// Need to login to passworded forum first? +if ($post_data['forum_password']) +{ + login_forum_box(array( + 'forum_id' => $forum_id, + 'forum_name' => $post_data['forum_name'], + 'forum_password' => $post_data['forum_password']) + ); +} + +// Check permissions +if ($user->data['is_bot']) +{ + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); +} + +// Is the user able to read within this forum? +if (!$auth->acl_get('f_read', $forum_id)) +{ + if ($user->data['user_id'] != ANONYMOUS) + { + trigger_error('USER_CANNOT_READ'); + } + $message = $user->lang['LOGIN_EXPLAIN_POST']; + + if ($request->is_ajax()) + { + $json = new phpbb\json_response(); + $json->send(array( + 'title' => $user->lang['INFORMATION'], + 'message' => $message, + )); + } + + login_box('', $message); +} + +// Permission to do the action asked? +$is_authed = false; + +switch ($mode) +{ + case 'post': + if ($auth->acl_get('f_post', $forum_id)) + { + $is_authed = true; + } + break; + + case 'bump': + if ($auth->acl_get('f_bump', $forum_id)) + { + $is_authed = true; + } + break; + + case 'quote': + + $post_data['post_edit_locked'] = 0; + + // no break; + + case 'reply': + if ($auth->acl_get('f_reply', $forum_id)) + { + $is_authed = true; + } + break; + + case 'edit': + if ($user->data['is_registered'] && $auth->acl_gets('f_edit', 'm_edit', $forum_id)) + { + $is_authed = true; + } + break; + + case 'delete': + if ($user->data['is_registered'] && ($auth->acl_get('m_delete', $forum_id) || ($post_data['poster_id'] == $user->data['user_id'] && $auth->acl_get('f_delete', $forum_id)))) + { + $is_authed = true; + } + + // no break; + + case 'soft_delete': + if (!$is_authed && $user->data['is_registered'] && $phpbb_content_visibility->can_soft_delete($forum_id, $post_data['poster_id'], $post_data['post_edit_locked'])) + { + // Fall back to soft_delete if we have no permissions to delete posts but to soft delete them + $is_authed = true; + $mode = 'soft_delete'; + } + break; +} +/** +* This event allows you to do extra auth checks and verify if the user +* has the required permissions +* +* Extensions should only change the error and is_authed variables. +* +* @event core.modify_posting_auth +* @var int post_id ID of the post +* @var int topic_id ID of the topic +* @var int forum_id ID of the forum +* @var int draft_id ID of the draft +* @var bool submit Whether or not the form has been submitted +* @var bool preview Whether or not the post is being previewed +* @var bool save Whether or not a draft is being saved +* @var bool load Whether or not a draft is being loaded +* @var bool refresh Whether or not to retain previously submitted data +* @var string mode What action to take if the form has been submitted +* post|reply|quote|edit|delete|bump|smilies|popup +* @var array error Any error strings; a non-empty array aborts +* form submission. +* NOTE: Should be actual language strings, NOT +* language keys. +* @var bool is_authed Does the user have the required permissions? +* @var array post_data All post data from database +* @since 3.1.3-RC1 +* @changed 3.1.10-RC1 Added post_data +* @changed 3.2.4-RC1 Remove unused 'lastclick' var +*/ +$vars = array( + 'post_id', + 'topic_id', + 'forum_id', + 'draft_id', + 'submit', + 'preview', + 'save', + 'load', + 'refresh', + 'mode', + 'error', + 'is_authed', + 'post_data', +); +extract($phpbb_dispatcher->trigger_event('core.modify_posting_auth', compact($vars))); + +if (!$is_authed || !empty($error)) +{ + $check_auth = ($mode == 'quote') ? 'reply' : (($mode == 'soft_delete') ? 'delete' : $mode); + + if ($user->data['is_registered']) + { + trigger_error(empty($error) ? 'USER_CANNOT_' . strtoupper($check_auth) : implode('<br/>', $error)); + } + $message = $user->lang['LOGIN_EXPLAIN_' . strtoupper($mode)]; + + if ($request->is_ajax()) + { + $json = new phpbb\json_response(); + $json->send(array( + 'title' => $user->lang['INFORMATION'], + 'message' => $message, + )); + } + + login_box('', $message); +} + +if ($config['enable_post_confirm'] && !$user->data['is_registered']) +{ + $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_POST); +} + +// Is the user able to post within this forum? +if ($post_data['forum_type'] != FORUM_POST && in_array($mode, array('post', 'bump', 'quote', 'reply'))) +{ + trigger_error('USER_CANNOT_FORUM_POST'); +} + +// Forum/Topic locked? +if (($post_data['forum_status'] == ITEM_LOCKED || (isset($post_data['topic_status']) && $post_data['topic_status'] == ITEM_LOCKED)) && !$auth->acl_get($mode == 'reply' ? 'm_lock' : 'm_edit', $forum_id)) +{ + trigger_error(($post_data['forum_status'] == ITEM_LOCKED) ? 'FORUM_LOCKED' : 'TOPIC_LOCKED'); +} + +// Can we edit this post ... if we're a moderator with rights then always yes +// else it depends on editing times, lock status and if we're the correct user +if ($mode == 'edit' && !$auth->acl_get('m_edit', $forum_id)) +{ + $force_edit_allowed = false; + + $s_cannot_edit = $user->data['user_id'] != $post_data['poster_id']; + $s_cannot_edit_time = $config['edit_time'] && $post_data['post_time'] <= time() - ($config['edit_time'] * 60); + $s_cannot_edit_locked = $post_data['post_edit_locked']; + + /** + * This event allows you to modify the conditions for the "cannot edit post" checks + * + * @event core.posting_modify_cannot_edit_conditions + * @var array post_data Array with post data + * @var bool force_edit_allowed Allow the user to edit the post (all permissions and conditions are ignored) + * @var bool s_cannot_edit User can not edit the post because it's not his + * @var bool s_cannot_edit_locked User can not edit the post because it's locked + * @var bool s_cannot_edit_time User can not edit the post because edit_time has passed + * @since 3.1.0-b4 + */ + $vars = array( + 'post_data', + 'force_edit_allowed', + 's_cannot_edit', + 's_cannot_edit_locked', + 's_cannot_edit_time', + ); + extract($phpbb_dispatcher->trigger_event('core.posting_modify_cannot_edit_conditions', compact($vars))); + + if (!$force_edit_allowed) + { + if ($s_cannot_edit) + { + trigger_error('USER_CANNOT_EDIT'); + } + else if ($s_cannot_edit_time) + { + trigger_error('CANNOT_EDIT_TIME'); + } + else if ($s_cannot_edit_locked) + { + trigger_error('CANNOT_EDIT_POST_LOCKED'); + } + } +} + +// Handle delete mode... +if ($mode == 'delete' || $mode == 'soft_delete') +{ + if ($mode == 'soft_delete' && $post_data['post_visibility'] == ITEM_DELETED) + { + $user->setup('posting'); + trigger_error('NO_POST'); + } + + $delete_reason = $request->variable('delete_reason', '', true); + phpbb_handle_post_delete($forum_id, $topic_id, $post_id, $post_data, ($mode == 'soft_delete' && !$request->is_set_post('delete_permanent')), $delete_reason); + return; +} + +// Handle bump mode... +if ($mode == 'bump') +{ + if ($bump_time = bump_topic_allowed($forum_id, $post_data['topic_bumped'], $post_data['topic_last_post_time'], $post_data['topic_poster'], $post_data['topic_last_poster_id']) + && check_link_hash($request->variable('hash', ''), "topic_{$post_data['topic_id']}")) + { + $meta_url = phpbb_bump_topic($forum_id, $topic_id, $post_data, $current_time); + meta_refresh(3, $meta_url); + $message = $user->lang['TOPIC_BUMPED']; + + if (!$request->is_ajax()) + { + $message .= '<br /><br />' . $user->lang('VIEW_MESSAGE', '<a href="' . $meta_url . '">', '</a>'); + $message .= '<br /><br />' . $user->lang('RETURN_FORUM', '<a href="' . append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id) . '">', '</a>'); + } + + trigger_error($message); + } + + trigger_error('BUMP_ERROR'); +} + +// Subject length limiting to 60 characters if first post... +if ($mode == 'post' || ($mode == 'edit' && $post_data['topic_first_post_id'] == $post_data['post_id'])) +{ + $template->assign_var('S_NEW_MESSAGE', true); +} + +// Determine some vars +if (isset($post_data['poster_id']) && $post_data['poster_id'] == ANONYMOUS) +{ + $post_data['quote_username'] = (!empty($post_data['post_username'])) ? $post_data['post_username'] : $user->lang['GUEST']; +} +else +{ + $post_data['quote_username'] = isset($post_data['username']) ? $post_data['username'] : ''; +} + +$post_data['post_edit_locked'] = (isset($post_data['post_edit_locked'])) ? (int) $post_data['post_edit_locked'] : 0; +$post_data['post_subject_md5'] = (isset($post_data['post_subject']) && $mode == 'edit') ? md5($post_data['post_subject']) : ''; +$post_data['post_subject'] = (in_array($mode, array('quote', 'edit'))) ? $post_data['post_subject'] : ((isset($post_data['topic_title'])) ? $post_data['topic_title'] : ''); +$post_data['topic_time_limit'] = (isset($post_data['topic_time_limit'])) ? (($post_data['topic_time_limit']) ? (int) $post_data['topic_time_limit'] / 86400 : (int) $post_data['topic_time_limit']) : 0; +$post_data['poll_length'] = (!empty($post_data['poll_length'])) ? (int) $post_data['poll_length'] / 86400 : 0; +$post_data['poll_start'] = (!empty($post_data['poll_start'])) ? (int) $post_data['poll_start'] : 0; +$post_data['icon_id'] = (!isset($post_data['icon_id']) || in_array($mode, array('quote', 'reply'))) ? 0 : (int) $post_data['icon_id']; +$post_data['poll_options'] = array(); + +// Get Poll Data +if ($post_data['poll_start']) +{ + $sql = 'SELECT poll_option_text + FROM ' . POLL_OPTIONS_TABLE . " + WHERE topic_id = $topic_id + ORDER BY poll_option_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $post_data['poll_options'][] = trim($row['poll_option_text']); + } + $db->sql_freeresult($result); +} + +/** +* This event allows you to modify the post data before parsing +* +* @event core.posting_modify_post_data +* @var int forum_id ID of the forum +* @var string mode What action to take if the form has been submitted +* post|reply|quote|edit|delete|bump|smilies|popup +* @var array post_data Array with post data +* @var int post_id ID of the post +* @var int topic_id ID of the topic +* @since 3.2.2-RC1 +*/ +$vars = array( + 'forum_id', + 'mode', + 'post_data', + 'post_id', + 'topic_id', +); +extract($phpbb_dispatcher->trigger_event('core.posting_modify_post_data', compact($vars))); + +if ($mode == 'edit') +{ + $original_poll_data = array( + 'poll_title' => $post_data['poll_title'], + 'poll_length' => $post_data['poll_length'], + 'poll_max_options' => $post_data['poll_max_options'], + 'poll_option_text' => implode("\n", $post_data['poll_options']), + 'poll_start' => $post_data['poll_start'], + 'poll_last_vote' => $post_data['poll_last_vote'], + 'poll_vote_change' => $post_data['poll_vote_change'], + 'poll_show_results' => $post_data['poll_show_results'], + ); +} + +$orig_poll_options_size = count($post_data['poll_options']); + +$message_parser = new parse_message(); +/* @var $plupload \phpbb\plupload\plupload */ +$plupload = $phpbb_container->get('plupload'); + +/* @var $mimetype_guesser \phpbb\mimetype\guesser */ +$mimetype_guesser = $phpbb_container->get('mimetype.guesser'); +$message_parser->set_plupload($plupload); + +if (isset($post_data['post_text'])) +{ + $message_parser->message = &$post_data['post_text']; + unset($post_data['post_text']); +} + +// Set some default variables +$uninit = array('post_attachment' => 0, 'poster_id' => $user->data['user_id'], 'enable_magic_url' => 0, 'topic_status' => 0, 'topic_type' => POST_NORMAL, 'post_subject' => '', 'topic_title' => '', 'post_time' => 0, 'post_edit_reason' => '', 'notify_set' => 0); + +/** +* This event allows you to modify the default variables for post_data, and unset them in post_data if needed +* +* @event core.posting_modify_default_variables +* @var array post_data Array with post data +* @var array uninit Array with default vars to put into post_data, if they aren't there +* @since 3.2.5-RC1 +*/ +$vars = array( + 'post_data', + 'uninit', +); +extract($phpbb_dispatcher->trigger_event('core.posting_modify_default_variables', compact($vars))); + +foreach ($uninit as $var_name => $default_value) +{ + if (!isset($post_data[$var_name])) + { + $post_data[$var_name] = $default_value; + } +} +unset($uninit); + +// Always check if the submitted attachment data is valid and belongs to the user. +// Further down (especially in submit_post()) we do not check this again. +$message_parser->get_submitted_attachment_data($post_data['poster_id']); + +if ($post_data['post_attachment'] && !$submit && !$refresh && !$preview && $mode == 'edit') +{ + // Do not change to SELECT * + $sql = 'SELECT attach_id, is_orphan, attach_comment, real_filename, filesize + FROM ' . ATTACHMENTS_TABLE . " + WHERE post_msg_id = $post_id + AND in_message = 0 + AND is_orphan = 0 + ORDER BY attach_id DESC"; + $result = $db->sql_query($sql); + $message_parser->attachment_data = array_merge($message_parser->attachment_data, $db->sql_fetchrowset($result)); + $db->sql_freeresult($result); +} + +if ($post_data['poster_id'] == ANONYMOUS) +{ + $post_data['username'] = ($mode == 'quote' || $mode == 'edit') ? trim($post_data['post_username']) : ''; +} +else +{ + $post_data['username'] = ($mode == 'quote' || $mode == 'edit') ? trim($post_data['username']) : ''; +} + +$post_data['enable_urls'] = $post_data['enable_magic_url']; + +if ($mode != 'edit') +{ + $post_data['enable_sig'] = ($config['allow_sig'] && $user->optionget('attachsig')) ? true: false; + $post_data['enable_smilies'] = ($config['allow_smilies'] && $user->optionget('smilies')) ? true : false; + $post_data['enable_bbcode'] = ($config['allow_bbcode'] && $user->optionget('bbcode')) ? true : false; + $post_data['enable_urls'] = true; +} + +if ($mode == 'post') +{ + $post_data['topic_status'] = ($request->is_set_post('lock_topic') && $auth->acl_gets('m_lock', 'f_user_lock', $forum_id)) ? ITEM_LOCKED : ITEM_UNLOCKED; +} + +$post_data['enable_magic_url'] = $post_data['drafts'] = false; + +// User own some drafts? +if ($user->data['is_registered'] && $auth->acl_get('u_savedrafts') && ($mode == 'reply' || $mode == 'post' || $mode == 'quote')) +{ + $sql = 'SELECT draft_id + FROM ' . DRAFTS_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . + (($forum_id) ? ' AND forum_id = ' . (int) $forum_id : '') . + (($topic_id) ? ' AND topic_id = ' . (int) $topic_id : '') . + (($draft_id) ? " AND draft_id <> $draft_id" : ''); + $result = $db->sql_query_limit($sql, 1); + + if ($db->sql_fetchrow($result)) + { + $post_data['drafts'] = true; + } + $db->sql_freeresult($result); +} + +$check_value = (($post_data['enable_bbcode']+1) << 8) + (($post_data['enable_smilies']+1) << 4) + (($post_data['enable_urls']+1) << 2) + (($post_data['enable_sig']+1) << 1); + +// Check if user is watching this topic +if ($mode != 'post' && $config['allow_topic_notify'] && $user->data['is_registered']) +{ + $sql = 'SELECT topic_id + FROM ' . TOPICS_WATCH_TABLE . ' + WHERE topic_id = ' . $topic_id . ' + AND user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + $post_data['notify_set'] = (int) $db->sql_fetchfield('topic_id'); + $db->sql_freeresult($result); +} + +// Do we want to edit our post ? +if ($mode == 'edit' && $post_data['bbcode_uid']) +{ + $message_parser->bbcode_uid = $post_data['bbcode_uid']; +} + +// HTML, BBCode, Smilies, Images and Flash status +$bbcode_status = ($config['allow_bbcode'] && $auth->acl_get('f_bbcode', $forum_id)) ? true : false; +$smilies_status = ($config['allow_smilies'] && $auth->acl_get('f_smilies', $forum_id)) ? true : false; +$img_status = ($bbcode_status && $auth->acl_get('f_img', $forum_id)) ? true : false; +$url_status = ($config['allow_post_links']) ? true : false; +$flash_status = ($bbcode_status && $auth->acl_get('f_flash', $forum_id) && $config['allow_post_flash']) ? true : false; +$quote_status = true; + +/** + * Event to override message BBCode status indications + * + * @event core.posting_modify_bbcode_status + * + * @var bool bbcode_status BBCode status + * @var bool smilies_status Smilies status + * @var bool img_status Image BBCode status + * @var bool url_status URL BBCode status + * @var bool flash_status Flash BBCode status + * @var bool quote_status Quote BBCode status + * @since 3.3.3-RC1 + */ +$vars = [ + 'bbcode_status', + 'smilies_status', + 'img_status', + 'url_status', + 'flash_status', + 'quote_status', +]; +extract($phpbb_dispatcher->trigger_event('core.posting_modify_bbcode_status', compact($vars))); + +// Save Draft +if ($save && $user->data['is_registered'] && $auth->acl_get('u_savedrafts') && ($mode == 'reply' || $mode == 'post' || $mode == 'quote')) +{ + $subject = $request->variable('subject', '', true); + $subject = (!$subject && $mode != 'post') ? $post_data['topic_title'] : $subject; + $message = $request->variable('message', '', true); + + /** + * Replace Emojis and other 4bit UTF-8 chars not allowed by MySQL to UCR/NCR. + * Using their Numeric Character Reference's Hexadecimal notation. + */ + $subject = utf8_encode_ucr($subject); + + if ($subject && $message) + { + if (confirm_box(true)) + { + $message_parser->message = $message; + $message_parser->parse($post_data['enable_bbcode'], ($config['allow_post_links']) ? $post_data['enable_urls'] : false, $post_data['enable_smilies'], $img_status, $flash_status, $quote_status, $config['allow_post_links']); + + $sql = 'INSERT INTO ' . DRAFTS_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'user_id' => (int) $user->data['user_id'], + 'topic_id' => (int) $topic_id, + 'forum_id' => (int) $forum_id, + 'save_time' => (int) $current_time, + 'draft_subject' => (string) $subject, + 'draft_message' => (string) $message_parser->message) + ); + $db->sql_query($sql); + + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('attach', array_column($message_parser->attachment_data, 'attach_id')); + + $meta_info = ($mode == 'post') ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id) : append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id"); + + meta_refresh(3, $meta_info); + + $message = $user->lang['DRAFT_SAVED'] . '<br /><br />'; + $message .= ($mode != 'post') ? sprintf($user->lang['RETURN_TOPIC'], '<a href="' . $meta_info . '">', '</a>') . '<br /><br />' : ''; + $message .= sprintf($user->lang['RETURN_FORUM'], '<a href="' . append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id) . '">', '</a>'); + + trigger_error($message); + } + else + { + $s_hidden_fields = build_hidden_fields(array( + 'mode' => $mode, + 'save' => true, + 'f' => $forum_id, + 't' => $topic_id, + 'subject' => $subject, + 'message' => $message, + 'attachment_data' => $message_parser->attachment_data, + ) + ); + + $hidden_fields = array( + 'icon_id' => 0, + + 'disable_bbcode' => false, + 'disable_smilies' => false, + 'disable_magic_url' => false, + 'attach_sig' => true, + 'lock_topic' => false, + + 'topic_type' => POST_NORMAL, + 'topic_time_limit' => 0, + + 'poll_title' => '', + 'poll_option_text' => '', + 'poll_max_options' => 1, + 'poll_length' => 0, + 'poll_vote_change' => false, + 'poll_show_results' => true, + ); + + foreach ($hidden_fields as $name => $default) + { + if (!isset($_POST[$name])) + { + // Don't include it, if its not available + unset($hidden_fields[$name]); + continue; + } + + if (is_bool($default)) + { + // Use the string representation + $hidden_fields[$name] = $request->variable($name, ''); + } + else + { + $hidden_fields[$name] = $request->variable($name, $default); + } + } + + $s_hidden_fields .= build_hidden_fields($hidden_fields); + + confirm_box(false, 'SAVE_DRAFT', $s_hidden_fields); + } + } + else + { + if (utf8_clean_string($subject) === '') + { + $error[] = $user->lang['EMPTY_SUBJECT']; + } + + if (utf8_clean_string($message) === '') + { + $error[] = $user->lang['TOO_FEW_CHARS']; + } + } + unset($subject, $message); +} + +// Load requested Draft +if ($draft_id && ($mode == 'reply' || $mode == 'quote' || $mode == 'post') && $user->data['is_registered'] && $auth->acl_get('u_savedrafts')) +{ + $sql = 'SELECT draft_subject, draft_message + FROM ' . DRAFTS_TABLE . " + WHERE draft_id = $draft_id + AND user_id = " . $user->data['user_id']; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $post_data['post_subject'] = $row['draft_subject']; + $message_parser->message = $row['draft_message']; + + $template->assign_var('S_DRAFT_LOADED', true); + } + else + { + $draft_id = 0; + } +} + +// Load draft overview +if ($load && ($mode == 'reply' || $mode == 'quote' || $mode == 'post') && $post_data['drafts']) +{ + load_drafts($topic_id, $forum_id); +} + +/** @var \phpbb\textformatter\utils_interface $bbcode_utils */ +$bbcode_utils = $phpbb_container->get('text_formatter.utils'); + +if ($submit || $preview || $refresh) +{ + $post_data['topic_cur_post_id'] = $request->variable('topic_cur_post_id', 0); + $post_data['post_subject'] = $request->variable('subject', '', true); + $message_parser->message = $request->variable('message', '', true); + + $post_data['username'] = $request->variable('username', $post_data['username'], true); + $post_data['post_edit_reason'] = ($request->variable('edit_reason', false, false, \phpbb\request\request_interface::POST) && $mode == 'edit' && $auth->acl_get('m_edit', $forum_id)) ? $request->variable('edit_reason', '', true) : ''; + + $post_data['orig_topic_type'] = $post_data['topic_type']; + $post_data['topic_type'] = $request->variable('topic_type', (($mode != 'post') ? (int) $post_data['topic_type'] : POST_NORMAL)); + $post_data['topic_time_limit'] = $request->variable('topic_time_limit', (($mode != 'post') ? (int) $post_data['topic_time_limit'] : 0)); + + if ($post_data['enable_icons'] && $auth->acl_get('f_icons', $forum_id)) + { + $post_data['icon_id'] = $request->variable('icon', (int) $post_data['icon_id']); + } + + $post_data['enable_bbcode'] = (!$bbcode_status || isset($_POST['disable_bbcode'])) ? false : true; + $post_data['enable_smilies'] = (!$smilies_status || isset($_POST['disable_smilies'])) ? false : true; + $post_data['enable_urls'] = (isset($_POST['disable_magic_url'])) ? 0 : 1; + $post_data['enable_sig'] = (!$config['allow_sig'] || !$auth->acl_get('f_sigs', $forum_id) || !$auth->acl_get('u_sig')) ? false : ((isset($_POST['attach_sig']) && $user->data['is_registered']) ? true : false); + + if ($config['allow_topic_notify'] && $user->data['is_registered']) + { + $notify = (isset($_POST['notify'])) ? true : false; + } + else + { + $notify = false; + } + + $topic_lock = (isset($_POST['lock_topic'])) ? true : false; + $post_lock = (isset($_POST['lock_post'])) ? true : false; + $poll_delete = (isset($_POST['poll_delete'])) ? true : false; + + if ($submit) + { + $status_switch = (($post_data['enable_bbcode']+1) << 8) + (($post_data['enable_smilies']+1) << 4) + (($post_data['enable_urls']+1) << 2) + (($post_data['enable_sig']+1) << 1); + $status_switch = ($status_switch != $check_value); + } + else + { + $status_switch = 1; + } + + // Delete Poll + if ($poll_delete && $mode == 'edit' && count($post_data['poll_options']) && + ((!$post_data['poll_last_vote'] && $post_data['poster_id'] == $user->data['user_id'] && $auth->acl_get('f_delete', $forum_id)) || $auth->acl_get('m_delete', $forum_id))) + { + if ($submit && check_form_key('posting')) + { + $sql = 'DELETE FROM ' . POLL_OPTIONS_TABLE . " + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . POLL_VOTES_TABLE . " + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + + $topic_sql = array( + 'poll_title' => '', + 'poll_start' => 0, + 'poll_length' => 0, + 'poll_last_vote' => 0, + 'poll_max_options' => 0, + 'poll_vote_change' => 0, + 'poll_show_results' => 0 + ); + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $topic_sql) . " + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + } + + $post_data['poll_title'] = $post_data['poll_option_text'] = ''; + $post_data['poll_vote_change'] = $post_data['poll_show_results'] = $post_data['poll_max_options'] = $post_data['poll_length'] = 0; + } + else + { + $post_data['poll_title'] = $request->variable('poll_title', '', true); + $post_data['poll_length'] = $request->variable('poll_length', 0); + $post_data['poll_option_text'] = $request->variable('poll_option_text', '', true); + $post_data['poll_max_options'] = $request->variable('poll_max_options', 1); + $post_data['poll_vote_change'] = ($auth->acl_get('f_votechg', $forum_id) && $auth->acl_get('f_vote', $forum_id) && isset($_POST['poll_vote_change'])) ? 1 : 0; + $post_data['poll_show_results'] = isset($_POST['poll_show_results']) ? 1 : 0; + } + + // If replying/quoting and last post id has changed + // give user option to continue submit or return to post + // notify and show user the post made between his request and the final submit + if (($mode == 'reply' || $mode == 'quote') && $post_data['topic_cur_post_id'] && $post_data['topic_cur_post_id'] != $post_data['topic_last_post_id']) + { + // Only do so if it is allowed forum-wide + if ($post_data['forum_flags'] & FORUM_FLAG_POST_REVIEW) + { + if (topic_review($topic_id, $forum_id, 'post_review', $post_data['topic_cur_post_id'])) + { + $template->assign_var('S_POST_REVIEW', true); + } + + $submit = false; + $refresh = true; + } + } + + // Parse Attachments - before checksum is calculated + if ($message_parser->check_attachment_form_token($language, $request, 'posting')) + { + $message_parser->parse_attachments('fileupload', $mode, $forum_id, $submit, $preview, $refresh); + } + + /** + * This event allows you to modify message text before parsing + * + * @event core.posting_modify_message_text + * @var array post_data Array with post data + * @var string mode What action to take if the form is submitted + * post|reply|quote|edit|delete|bump|smilies|popup + * @var int post_id ID of the post + * @var int topic_id ID of the topic + * @var int forum_id ID of the forum + * @var bool submit Whether or not the form has been submitted + * @var bool preview Whether or not the post is being previewed + * @var bool save Whether or not a draft is being saved + * @var bool load Whether or not a draft is being loaded + * @var bool cancel Whether or not to cancel the form (returns to + * viewtopic or viewforum depending on if the user + * is posting a new topic or editing a post) + * @var bool refresh Whether or not to retain previously submitted data + * @var object message_parser The message parser object + * @var array error Array of errors + * @since 3.1.2-RC1 + * @changed 3.1.11-RC1 Added error + */ + $vars = array( + 'post_data', + 'mode', + 'post_id', + 'topic_id', + 'forum_id', + 'submit', + 'preview', + 'save', + 'load', + 'cancel', + 'refresh', + 'message_parser', + 'error', + ); + extract($phpbb_dispatcher->trigger_event('core.posting_modify_message_text', compact($vars))); + + // Grab md5 'checksum' of new message + $message_md5 = md5($message_parser->message); + + // If editing and checksum has changed we know the post was edited while we're editing + // Notify and show user the changed post + if ($mode == 'edit' && $post_data['forum_flags'] & FORUM_FLAG_POST_REVIEW) + { + $edit_post_message_checksum = $request->variable('edit_post_message_checksum', ''); + $edit_post_subject_checksum = $request->variable('edit_post_subject_checksum', ''); + + // $post_data['post_checksum'] is the checksum of the post submitted in the meantime + // $message_md5 is the checksum of the post we're about to submit + // $edit_post_message_checksum is the checksum of the post we're editing + // ... + + // We make sure nobody else made exactly the same change + // we're about to submit by also checking $message_md5 != $post_data['post_checksum'] + if ($edit_post_message_checksum !== '' && + $edit_post_message_checksum != $post_data['post_checksum'] && + $message_md5 != $post_data['post_checksum'] + || + $edit_post_subject_checksum !== '' && + $edit_post_subject_checksum != $post_data['post_subject_md5'] && + md5($post_data['post_subject']) != $post_data['post_subject_md5']) + { + if (topic_review($topic_id, $forum_id, 'post_review_edit', $post_id)) + { + $template->assign_vars(array( + 'S_POST_REVIEW' => true, + + 'L_POST_REVIEW' => $user->lang['POST_REVIEW_EDIT'], + 'L_POST_REVIEW_EXPLAIN' => $user->lang['POST_REVIEW_EDIT_EXPLAIN'], + )); + } + + $submit = false; + $refresh = true; + } + } + + // Check checksum ... don't re-parse message if the same + $update_message = ($mode != 'edit' || $message_md5 != $post_data['post_checksum'] || $status_switch || strlen($post_data['bbcode_uid']) < BBCODE_UID_LEN) ? true : false; + + // Also check if subject got updated... + $update_subject = $mode != 'edit' || ($post_data['post_subject_md5'] && $post_data['post_subject_md5'] != md5($post_data['post_subject'])); + + // Parse message + if ($update_message) + { + if (count($message_parser->warn_msg)) + { + $error[] = implode('<br />', $message_parser->warn_msg); + $message_parser->warn_msg = array(); + } + + if (!$preview || !empty($message_parser->message)) + { + $message_parser->parse($post_data['enable_bbcode'], ($config['allow_post_links']) ? $post_data['enable_urls'] : false, $post_data['enable_smilies'], $img_status, $flash_status, $quote_status, $config['allow_post_links']); + } + + // On a refresh we do not care about message parsing errors + if (count($message_parser->warn_msg) && $refresh && !$preview) + { + $message_parser->warn_msg = array(); + } + } + else + { + $message_parser->bbcode_bitfield = $post_data['bbcode_bitfield']; + } + + $ignore_flood = $auth->acl_get('u_ignoreflood') ? true : $auth->acl_get('f_ignoreflood', $forum_id); + if ($mode != 'edit' && !$preview && !$refresh && $config['flood_interval'] && !$ignore_flood) + { + // Flood check + $last_post_time = 0; + + if ($user->data['is_registered']) + { + $last_post_time = $user->data['user_lastpost_time']; + } + else + { + $sql = 'SELECT post_time AS last_post_time + FROM ' . POSTS_TABLE . " + WHERE poster_ip = '" . $user->ip . "' + AND post_time > " . ($current_time - $config['flood_interval']); + $result = $db->sql_query_limit($sql, 1); + if ($row = $db->sql_fetchrow($result)) + { + $last_post_time = $row['last_post_time']; + } + $db->sql_freeresult($result); + } + + if ($last_post_time && ($current_time - $last_post_time) < intval($config['flood_interval'])) + { + $error[] = $user->lang['FLOOD_ERROR']; + } + } + + // Validate username + if (($post_data['username'] && !$user->data['is_registered']) || ($mode == 'edit' && $post_data['poster_id'] == ANONYMOUS && $post_data['username'] && $post_data['post_username'] && $post_data['post_username'] != $post_data['username'])) + { + if (!function_exists('validate_username')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $user->add_lang('ucp'); + + if (($result = validate_username($post_data['username'], (!empty($post_data['post_username'])) ? $post_data['post_username'] : '')) !== false) + { + $error[] = $user->lang[$result . '_USERNAME']; + } + + if (($result = validate_string($post_data['username'], false, $config['min_name_chars'], $config['max_name_chars'])) !== false) + { + $min_max_amount = ($result == 'TOO_SHORT') ? $config['min_name_chars'] : $config['max_name_chars']; + $error[] = $user->lang('FIELD_' . $result, $min_max_amount, $user->lang['USERNAME']); + } + } + + if ($config['enable_post_confirm'] && !$user->data['is_registered'] && in_array($mode, array('quote', 'post', 'reply'))) + { + $captcha_data = array( + 'message' => $request->variable('message', '', true), + 'subject' => $request->variable('subject', '', true), + 'username' => $request->variable('username', '', true), + ); + $vc_response = $captcha->validate($captcha_data); + if ($vc_response) + { + $error[] = $vc_response; + } + } + + // check form + if (($submit || $preview) && !check_form_key('posting')) + { + $error[] = $user->lang['FORM_INVALID']; + } + + if ($submit && $mode == 'edit' && $post_data['post_visibility'] == ITEM_DELETED && !$request->is_set_post('delete') && $auth->acl_get('m_approve', $forum_id)) + { + $is_first_post = ($post_id <= $post_data['topic_first_post_id'] || !$post_data['topic_posts_approved']); + $is_last_post = ($post_id >= $post_data['topic_last_post_id'] || !$post_data['topic_posts_approved']); + $updated_post_data = $phpbb_content_visibility->set_post_visibility(ITEM_APPROVED, $post_id, $post_data['topic_id'], $post_data['forum_id'], $user->data['user_id'], time(), '', $is_first_post, $is_last_post); + + if (!empty($updated_post_data)) + { + // Update the post_data, so we don't need to refetch it. + $post_data = array_merge($post_data, $updated_post_data); + } + } + + // Parse subject + if (!$preview && !$refresh && utf8_clean_string($post_data['post_subject']) === '' && ($mode == 'post' || ($mode == 'edit' && $post_data['topic_first_post_id'] == $post_id))) + { + $error[] = $user->lang['EMPTY_SUBJECT']; + } + + /** + * Replace Emojis and other 4bit UTF-8 chars not allowed by MySQL to UCR/NCR. + * Using their Numeric Character Reference's Hexadecimal notation. + * Check the permissions for posting Emojis first. + */ + if ($auth->acl_get('u_emoji')) + { + $post_data['post_subject'] = utf8_encode_ucr($post_data['post_subject']); + } + else + { + /** + * Check for out-of-bounds characters that are currently + * not supported by utf8_bin in MySQL + */ + if (preg_match_all('/[\x{10000}-\x{10FFFF}]/u', $post_data['post_subject'], $matches)) + { + $character_list = implode('<br>', $matches[0]); + + $error[] = $user->lang('UNSUPPORTED_CHARACTERS_SUBJECT', $character_list); + } + } + + $post_data['poll_last_vote'] = (isset($post_data['poll_last_vote'])) ? $post_data['poll_last_vote'] : 0; + + if ($post_data['poll_option_text'] && + ($mode == 'post' || ($mode == 'edit' && $post_id == $post_data['topic_first_post_id']/* && (!$post_data['poll_last_vote'] || $auth->acl_get('m_edit', $forum_id))*/)) + && $auth->acl_get('f_poll', $forum_id)) + { + $poll = array( + 'poll_title' => $post_data['poll_title'], + 'poll_length' => $post_data['poll_length'], + 'poll_max_options' => $post_data['poll_max_options'], + 'poll_option_text' => $post_data['poll_option_text'], + 'poll_start' => $post_data['poll_start'], + 'poll_last_vote' => $post_data['poll_last_vote'], + 'poll_vote_change' => $post_data['poll_vote_change'], + 'poll_show_results' => $post_data['poll_show_results'], + 'enable_bbcode' => $post_data['enable_bbcode'], + 'enable_urls' => $post_data['enable_urls'], + 'enable_smilies' => $post_data['enable_smilies'], + 'img_status' => $img_status + ); + + $message_parser->parse_poll($poll); + + $post_data['poll_options'] = (isset($poll['poll_options'])) ? $poll['poll_options'] : array(); + $post_data['poll_title'] = (isset($poll['poll_title'])) ? $poll['poll_title'] : ''; + + /* We reset votes, therefore also allow removing options + if ($post_data['poll_last_vote'] && ($poll['poll_options_size'] < $orig_poll_options_size)) + { + $message_parser->warn_msg[] = $user->lang['NO_DELETE_POLL_OPTIONS']; + }*/ + } + else if ($mode == 'edit' && $post_id == $post_data['topic_first_post_id'] && $auth->acl_get('f_poll', $forum_id)) + { + // The user removed all poll options, this is equal to deleting the poll. + $poll = array( + 'poll_title' => '', + 'poll_length' => 0, + 'poll_max_options' => 0, + 'poll_option_text' => '', + 'poll_start' => 0, + 'poll_last_vote' => 0, + 'poll_vote_change' => 0, + 'poll_show_results' => 0, + 'poll_options' => array(), + ); + + $post_data['poll_options'] = array(); + $post_data['poll_title'] = ''; + $post_data['poll_start'] = $post_data['poll_length'] = $post_data['poll_max_options'] = $post_data['poll_last_vote'] = $post_data['poll_vote_change'] = $post_data['poll_show_results'] = 0; + } + else if (!$auth->acl_get('f_poll', $forum_id) && ($mode == 'edit') && ($post_id == $post_data['topic_first_post_id']) && !$bbcode_utils->is_empty($original_poll_data['poll_title'])) + { + // We have a poll but the editing user is not permitted to create/edit it. + // So we just keep the original poll-data. + // Decode the poll title and options text fisrt. + $original_poll_data['poll_title'] = $bbcode_utils->unparse($original_poll_data['poll_title']); + $original_poll_data['poll_option_text'] = $bbcode_utils->unparse($original_poll_data['poll_option_text']); + $original_poll_data['poll_options'] = explode("\n", $original_poll_data['poll_option_text']); + + $poll = array_merge($original_poll_data, array( + 'enable_bbcode' => $post_data['enable_bbcode'], + 'enable_urls' => $post_data['enable_urls'], + 'enable_smilies' => $post_data['enable_smilies'], + 'img_status' => $img_status, + )); + + $message_parser->parse_poll($poll); + + $post_data['poll_options'] = (isset($poll['poll_options'])) ? $poll['poll_options'] : array(); + $post_data['poll_title'] = (isset($poll['poll_title'])) ? $poll['poll_title'] : ''; + } + else + { + $poll = array(); + } + + // Check topic type + if ($post_data['topic_type'] != POST_NORMAL && ($mode == 'post' || ($mode == 'edit' && $post_data['topic_first_post_id'] == $post_id))) + { + switch ($post_data['topic_type']) + { + case POST_GLOBAL: + $auth_option = 'f_announce_global'; + break; + + case POST_ANNOUNCE: + $auth_option = 'f_announce'; + break; + + case POST_STICKY: + $auth_option = 'f_sticky'; + break; + + default: + $auth_option = ''; + break; + } + + if ($auth_option != '' && !$auth->acl_get($auth_option, $forum_id)) + { + // There is a special case where a user edits his post whereby the topic type got changed by an admin/mod. + // Another case would be a mod not having sticky permissions for example but edit permissions. + if ($mode == 'edit') + { + // To prevent non-authed users messing around with the topic type we reset it to the original one. + $post_data['topic_type'] = $post_data['orig_topic_type']; + } + else + { + $error[] = $user->lang['CANNOT_POST_' . str_replace('F_', '', strtoupper($auth_option))]; + } + } + } + + if (count($message_parser->warn_msg)) + { + $error[] = implode('<br />', $message_parser->warn_msg); + } + + // DNSBL check + if ($config['check_dnsbl'] && !$refresh) + { + if (($dnsbl = $user->check_dnsbl('post')) !== false) + { + $error[] = sprintf($user->lang['IP_BLACKLISTED'], $user->ip, $dnsbl[1]); + } + } + + /** + * This event allows you to define errors before the post action is performed + * + * @event core.posting_modify_submission_errors + * @var array post_data Array with post data + * @var array poll Array with poll data from post (must be used instead of the post_data equivalent) + * @var string mode What action to take if the form is submitted + * post|reply|quote|edit|delete|bump|smilies|popup + * @var int post_id ID of the post + * @var int topic_id ID of the topic + * @var int forum_id ID of the forum + * @var bool submit Whether or not the form has been submitted + * @var array error Any error strings; a non-empty array aborts form submission. + * NOTE: Should be actual language strings, NOT language keys. + * @since 3.1.0-RC5 + * @changed 3.1.5-RC1 Added poll array to the event + * @changed 3.2.0-a1 Removed undefined page_title + */ + $vars = array( + 'post_data', + 'poll', + 'mode', + 'post_id', + 'topic_id', + 'forum_id', + 'submit', + 'error', + ); + extract($phpbb_dispatcher->trigger_event('core.posting_modify_submission_errors', compact($vars))); + + // Store message, sync counters + if (!count($error) && $submit) + { + if ($submit) + { + // Lock/Unlock Topic + $change_topic_status = $post_data['topic_status']; + $perm_lock_unlock = ($auth->acl_get('m_lock', $forum_id) || ($auth->acl_get('f_user_lock', $forum_id) && $user->data['is_registered'] && !empty($post_data['topic_poster']) && $user->data['user_id'] == $post_data['topic_poster'] && $post_data['topic_status'] == ITEM_UNLOCKED)) ? true : false; + + if ($post_data['topic_status'] == ITEM_LOCKED && !$topic_lock && $perm_lock_unlock) + { + $change_topic_status = ITEM_UNLOCKED; + } + else if ($post_data['topic_status'] == ITEM_UNLOCKED && $topic_lock && $perm_lock_unlock) + { + $change_topic_status = ITEM_LOCKED; + } + + if ($change_topic_status != $post_data['topic_status']) + { + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_status = $change_topic_status + WHERE topic_id = $topic_id + AND topic_moved_id = 0"; + $db->sql_query($sql); + + $user_lock = ($auth->acl_get('f_user_lock', $forum_id) && $user->data['is_registered'] && $user->data['user_id'] == $post_data['topic_poster']) ? 'USER_' : ''; + + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_' . $user_lock . (($change_topic_status == ITEM_LOCKED) ? 'LOCK' : 'UNLOCK'), false, array( + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + $post_data['topic_title'] + )); + } + + // Lock/Unlock Post Edit + if ($mode == 'edit' && $post_data['post_edit_locked'] == ITEM_LOCKED && !$post_lock && $auth->acl_get('m_edit', $forum_id)) + { + $post_data['post_edit_locked'] = ITEM_UNLOCKED; + } + else if ($mode == 'edit' && $post_data['post_edit_locked'] == ITEM_UNLOCKED && $post_lock && $auth->acl_get('m_edit', $forum_id)) + { + $post_data['post_edit_locked'] = ITEM_LOCKED; + } + + $data = array( + 'topic_title' => (empty($post_data['topic_title'])) ? $post_data['post_subject'] : $post_data['topic_title'], + 'topic_first_post_id' => (isset($post_data['topic_first_post_id'])) ? (int) $post_data['topic_first_post_id'] : 0, + 'topic_last_post_id' => (isset($post_data['topic_last_post_id'])) ? (int) $post_data['topic_last_post_id'] : 0, + 'topic_time_limit' => (int) $post_data['topic_time_limit'], + 'topic_attachment' => (isset($post_data['topic_attachment'])) ? (int) $post_data['topic_attachment'] : 0, + 'post_id' => (int) $post_id, + 'topic_id' => (int) $topic_id, + 'forum_id' => (int) $forum_id, + 'icon_id' => (int) $post_data['icon_id'], + 'poster_id' => (int) $post_data['poster_id'], + 'enable_sig' => (bool) $post_data['enable_sig'], + 'enable_bbcode' => (bool) $post_data['enable_bbcode'], + 'enable_smilies' => (bool) $post_data['enable_smilies'], + 'enable_urls' => (bool) $post_data['enable_urls'], + 'enable_indexing' => (bool) $post_data['enable_indexing'], + 'message_md5' => (string) $message_md5, + 'post_checksum' => (isset($post_data['post_checksum'])) ? (string) $post_data['post_checksum'] : '', + 'post_edit_reason' => $post_data['post_edit_reason'], + 'post_edit_user' => ($mode == 'edit') ? $user->data['user_id'] : ((isset($post_data['post_edit_user'])) ? (int) $post_data['post_edit_user'] : 0), + 'forum_parents' => $post_data['forum_parents'], + 'forum_name' => $post_data['forum_name'], + 'notify' => $notify, + 'notify_set' => $post_data['notify_set'], + 'poster_ip' => (isset($post_data['poster_ip'])) ? $post_data['poster_ip'] : $user->ip, + 'post_edit_locked' => (int) $post_data['post_edit_locked'], + 'bbcode_bitfield' => $message_parser->bbcode_bitfield, + 'bbcode_uid' => $message_parser->bbcode_uid, + 'message' => $message_parser->message, + 'attachment_data' => $message_parser->attachment_data, + 'filename_data' => $message_parser->filename_data, + 'topic_status' => $post_data['topic_status'], + + 'topic_visibility' => (isset($post_data['topic_visibility'])) ? $post_data['topic_visibility'] : false, + 'post_visibility' => (isset($post_data['post_visibility'])) ? $post_data['post_visibility'] : false, + ); + + if ($mode == 'edit') + { + $data['topic_posts_approved'] = $post_data['topic_posts_approved']; + $data['topic_posts_unapproved'] = $post_data['topic_posts_unapproved']; + $data['topic_posts_softdeleted'] = $post_data['topic_posts_softdeleted']; + } + + // Only return the username when it is either a guest posting or we are editing a post and + // the username was supplied; otherwise post_data might hold the data of the post that is + // being quoted (which could result in the username being returned being that of the quoted + // post's poster, not the poster of the current post). See: PHPBB3-11769 for more information. + $post_author_name = ((!$user->data['is_registered'] || $mode == 'edit') && $post_data['username'] !== '') ? $post_data['username'] : ''; + + /** + * This event allows you to define errors before the post action is performed + * + * @event core.posting_modify_submit_post_before + * @var array post_data Array with post data + * @var array poll Array with poll data + * @var array data Array with post data going to be stored in the database + * @var string mode What action to take if the form is submitted + * post|reply|quote|edit|delete + * @var int post_id ID of the post + * @var int topic_id ID of the topic + * @var int forum_id ID of the forum + * @var string post_author_name Author name for guest posts + * @var bool update_message Boolean if the post message was changed + * @var bool update_subject Boolean if the post subject was changed + * NOTE: Should be actual language strings, NOT language keys. + * @since 3.1.0-RC5 + * @changed 3.1.6-RC1 remove submit and error from event Submit and Error are checked previously prior to running event + * @change 3.2.0-a1 Removed undefined page_title + */ + $vars = array( + 'post_data', + 'poll', + 'data', + 'mode', + 'post_id', + 'topic_id', + 'forum_id', + 'post_author_name', + 'update_message', + 'update_subject', + ); + extract($phpbb_dispatcher->trigger_event('core.posting_modify_submit_post_before', compact($vars))); + + // The last parameter tells submit_post if search indexer has to be run + $redirect_url = submit_post($mode, $post_data['post_subject'], $post_author_name, $post_data['topic_type'], $poll, $data, $update_message, ($update_message || $update_subject) ? true : false); + + /** + * This event allows you to define errors after the post action is performed + * + * @event core.posting_modify_submit_post_after + * @var array post_data Array with post data + * @var array poll Array with poll data + * @var array data Array with post data going to be stored in the database + * @var string mode What action to take if the form is submitted + * post|reply|quote|edit|delete + * @var int post_id ID of the post + * @var int topic_id ID of the topic + * @var int forum_id ID of the forum + * @var string post_author_name Author name for guest posts + * @var bool update_message Boolean if the post message was changed + * @var bool update_subject Boolean if the post subject was changed + * @var string redirect_url URL the user is going to be redirected to + * NOTE: Should be actual language strings, NOT language keys. + * @since 3.1.0-RC5 + * @changed 3.1.6-RC1 remove submit and error from event Submit and Error are checked previously prior to running event + * @change 3.2.0-a1 Removed undefined page_title + */ + $vars = array( + 'post_data', + 'poll', + 'data', + 'mode', + 'post_id', + 'topic_id', + 'forum_id', + 'post_author_name', + 'update_message', + 'update_subject', + 'redirect_url', + ); + extract($phpbb_dispatcher->trigger_event('core.posting_modify_submit_post_after', compact($vars))); + + if ($config['enable_post_confirm'] && !$user->data['is_registered'] && (isset($captcha) && $captcha->is_solved() === true) && ($mode == 'post' || $mode == 'reply' || $mode == 'quote')) + { + $captcha->reset(); + } + + // Handle delete mode... + if ($request->is_set_post('delete_permanent') || ($request->is_set_post('delete') && $post_data['post_visibility'] != ITEM_DELETED)) + { + $delete_reason = $request->variable('delete_reason', '', true); + phpbb_handle_post_delete($forum_id, $topic_id, $post_id, $post_data, !$request->is_set_post('delete_permanent'), $delete_reason); + return; + } + + // Check the permissions for post approval. + // Moderators must go through post approval like ordinary users. + if ((!$auth->acl_get('f_noapprove', $data['forum_id']) && empty($data['force_approved_state'])) || (isset($data['force_approved_state']) && !$data['force_approved_state'])) + { + meta_refresh(10, $redirect_url); + $message = ($mode == 'edit') ? $user->lang['POST_EDITED_MOD'] : $user->lang['POST_STORED_MOD']; + $message .= (($user->data['user_id'] == ANONYMOUS) ? '' : ' '. $user->lang['POST_APPROVAL_NOTIFY']); + $message .= '<br /><br />' . sprintf($user->lang['RETURN_FORUM'], '<a href="' . append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $data['forum_id']) . '">', '</a>'); + trigger_error($message); + } + + redirect($redirect_url); + } + } +} + +// Preview +if (!count($error) && $preview) +{ + $post_data['post_time'] = ($mode == 'edit') ? $post_data['post_time'] : $current_time; + + $preview_message = $message_parser->format_display($post_data['enable_bbcode'], $post_data['enable_urls'], $post_data['enable_smilies'], false); + + $preview_signature = ($mode == 'edit') ? $post_data['user_sig'] : $user->data['user_sig']; + $preview_signature_uid = ($mode == 'edit') ? $post_data['user_sig_bbcode_uid'] : $user->data['user_sig_bbcode_uid']; + $preview_signature_bitfield = ($mode == 'edit') ? $post_data['user_sig_bbcode_bitfield'] : $user->data['user_sig_bbcode_bitfield']; + + // Signature + if ($post_data['enable_sig'] && $config['allow_sig'] && $preview_signature && $auth->acl_get('f_sigs', $forum_id)) + { + $flags = ($config['allow_sig_bbcode']) ? OPTION_FLAG_BBCODE : 0; + $flags |= ($config['allow_sig_links']) ? OPTION_FLAG_LINKS : 0; + $flags |= ($config['allow_sig_smilies']) ? OPTION_FLAG_SMILIES : 0; + + $preview_signature = generate_text_for_display($preview_signature, $preview_signature_uid, $preview_signature_bitfield, $flags, false); + } + else + { + $preview_signature = ''; + } + + $preview_subject = censor_text($post_data['post_subject']); + + // Poll Preview + if (!$poll_delete && ($mode == 'post' || ($mode == 'edit' && $post_id == $post_data['topic_first_post_id']/* && (!$post_data['poll_last_vote'] || $auth->acl_get('m_edit', $forum_id))*/)) + && $auth->acl_get('f_poll', $forum_id)) + { + $parse_poll = new parse_message($post_data['poll_title']); + $parse_poll->bbcode_uid = $message_parser->bbcode_uid; + $parse_poll->bbcode_bitfield = $message_parser->bbcode_bitfield; + + $parse_poll->format_display($post_data['enable_bbcode'], $post_data['enable_urls'], $post_data['enable_smilies']); + + if ($post_data['poll_length']) + { + $poll_end = ($post_data['poll_length'] * 86400) + (($post_data['poll_start']) ? $post_data['poll_start'] : time()); + } + + $template->assign_vars(array( + 'S_HAS_POLL_OPTIONS' => (count($post_data['poll_options'])), + 'S_IS_MULTI_CHOICE' => ($post_data['poll_max_options'] > 1) ? true : false, + + 'POLL_QUESTION' => $parse_poll->message, + + 'L_POLL_LENGTH' => ($post_data['poll_length']) ? sprintf($user->lang['POLL_RUN_TILL'], $user->format_date($poll_end)) : '', + 'L_MAX_VOTES' => $user->lang('MAX_OPTIONS_SELECT', (int) $post_data['poll_max_options']), + )); + + $preview_poll_options = array(); + foreach ($post_data['poll_options'] as $poll_option) + { + $parse_poll->message = $poll_option; + $parse_poll->format_display($post_data['enable_bbcode'], $post_data['enable_urls'], $post_data['enable_smilies']); + $preview_poll_options[] = $parse_poll->message; + } + unset($parse_poll); + + foreach ($preview_poll_options as $key => $option) + { + $template->assign_block_vars('poll_option', array( + 'POLL_OPTION_CAPTION' => $option, + 'POLL_OPTION_ID' => $key + 1) + ); + } + unset($preview_poll_options); + } + + // Attachment Preview + if (count($message_parser->attachment_data)) + { + $template->assign_var('S_HAS_ATTACHMENTS', true); + + $update_count = array(); + $attachment_data = $message_parser->attachment_data; + + parse_attachments($forum_id, $preview_message, $attachment_data, $update_count, true); + + foreach ($attachment_data as $i => $attachment) + { + $template->assign_block_vars('attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + unset($attachment_data); + } + + if (!count($error)) + { + $template->assign_vars(array( + 'PREVIEW_SUBJECT' => $preview_subject, + 'PREVIEW_MESSAGE' => $preview_message, + 'PREVIEW_SIGNATURE' => $preview_signature, + + 'S_DISPLAY_PREVIEW' => !empty($preview_message), + )); + } +} + +// Remove quotes that would become nested too deep before decoding the text +$generate_quote = ($mode == 'quote' && !$submit && !$preview && !$refresh); +if ($generate_quote && $config['max_quote_depth'] > 0) +{ + $tmp_bbcode_uid = $message_parser->bbcode_uid; + $message_parser->bbcode_uid = $post_data['bbcode_uid']; + $message_parser->remove_nested_quotes($config['max_quote_depth'] - 1); + $message_parser->bbcode_uid = $tmp_bbcode_uid; +} + +// Decode text for message display +$post_data['bbcode_uid'] = ($mode == 'quote' && !$preview && !$refresh && !count($error)) ? $post_data['bbcode_uid'] : $message_parser->bbcode_uid; +$message_parser->decode_message($post_data['bbcode_uid']); + +if ($generate_quote) +{ + // Remove attachment bbcode tags from the quoted message to avoid mixing with the new post attachments if any + $message_parser->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#uis', '\\2', $message_parser->message); + + $quote_attributes = array( + 'author' => $post_data['quote_username'], + 'post_id' => $post_data['post_id'], + 'time' => $post_data['post_time'], + 'user_id' => $post_data['poster_id'], + ); + + /** + * This event allows you to modify the quote attributes of the post being quoted + * + * @event core.posting_modify_quote_attributes + * @var array quote_attributes Array with quote attributes + * @var array post_data Array with post data + * @since 3.2.6-RC1 + */ + $vars = array( + 'quote_attributes', + 'post_data', + ); + extract($phpbb_dispatcher->trigger_event('core.posting_modify_quote_attributes', compact($vars))); + + /** @var \phpbb\language\language $language */ + $language = $phpbb_container->get('language'); + phpbb_format_quote($language, $message_parser, $bbcode_utils, $bbcode_status, $quote_attributes); +} + +if (($mode == 'reply' || $mode == 'quote') && !$submit && !$preview && !$refresh) +{ + $post_data['post_subject'] = ((strpos($post_data['post_subject'], 'Re: ') !== 0) ? 'Re: ' : '') . censor_text($post_data['post_subject']); + + $post_subject = $post_data['post_subject']; + + /** + * This event allows you to modify the post subject of the post being quoted + * + * @event core.posting_modify_post_subject + * @var string post_subject String with the post subject already censored. + * @since 3.2.8-RC1 + */ + $vars = array('post_subject'); + extract($phpbb_dispatcher->trigger_event('core.posting_modify_post_subject', compact($vars))); + + $post_data['post_subject'] = $post_subject; +} + +$attachment_data = $message_parser->attachment_data; +$filename_data = $message_parser->filename_data; +$post_data['post_text'] = $message_parser->message; + +if (count($post_data['poll_options']) || (isset($post_data['poll_title']) && !$bbcode_utils->is_empty($post_data['poll_title']))) +{ + $message_parser->message = $post_data['poll_title']; + $message_parser->bbcode_uid = $post_data['bbcode_uid']; + + $message_parser->decode_message(); + $post_data['poll_title'] = $message_parser->message; + + $message_parser->message = implode("\n", $post_data['poll_options']); + $message_parser->decode_message(); + $post_data['poll_options'] = explode("\n", $message_parser->message); +} + +// MAIN POSTING PAGE BEGINS HERE + +// Forum moderators? +$moderators = array(); +if ($config['load_moderators']) +{ + get_moderators($moderators, $forum_id); +} + +// Generate smiley listing +generate_smilies('inline', $forum_id); + +// Generate inline attachment select box +posting_gen_inline_attachments($attachment_data); + +// Do show topic type selection only in first post. +$topic_type_toggle = false; + +if ($mode == 'post' || ($mode == 'edit' && $post_id == $post_data['topic_first_post_id'])) +{ + $topic_type_toggle = posting_gen_topic_types($forum_id, $post_data['topic_type']); +} + +$s_topic_icons = false; +if ($post_data['enable_icons'] && $auth->acl_get('f_icons', $forum_id)) +{ + $s_topic_icons = posting_gen_topic_icons($mode, $post_data['icon_id']); +} + +$bbcode_checked = (isset($post_data['enable_bbcode'])) ? !$post_data['enable_bbcode'] : (($config['allow_bbcode']) ? !$user->optionget('bbcode') : 1); +$smilies_checked = (isset($post_data['enable_smilies'])) ? !$post_data['enable_smilies'] : (($config['allow_smilies']) ? !$user->optionget('smilies') : 1); +$urls_checked = (isset($post_data['enable_urls'])) ? !$post_data['enable_urls'] : 0; +$sig_checked = $post_data['enable_sig']; +$lock_topic_checked = (isset($topic_lock) && $topic_lock) ? $topic_lock : (($post_data['topic_status'] == ITEM_LOCKED) ? 1 : 0); +$lock_post_checked = (isset($post_lock)) ? $post_lock : $post_data['post_edit_locked']; + +// If the user is replying or posting and not already watching this topic but set to always being notified we need to overwrite this setting +$notify_set = ($mode != 'edit' && $config['allow_topic_notify'] && $user->data['is_registered'] && !$post_data['notify_set']) ? $user->data['user_notify'] : $post_data['notify_set']; +$notify_checked = (isset($notify)) ? $notify : (($mode == 'post') ? $user->data['user_notify'] : $notify_set); + +// Page title & action URL +$s_action = append_sid("{$phpbb_root_path}posting.$phpEx", "mode=$mode"); + +switch ($mode) +{ + case 'post': + $s_action .= $forum_id ? "&f=$forum_id" : ''; + $page_title = $user->lang['POST_TOPIC']; + break; + + case 'reply': + $s_action .= $topic_id ? "&t=$topic_id" : ''; + $page_title = $user->lang['POST_REPLY']; + break; + + case 'quote': + $s_action .= $post_id ? "&p=$post_id" : ''; + $page_title = $user->lang['POST_REPLY']; + break; + + case 'delete': + case 'edit': + $s_action .= $post_id ? "&p=$post_id" : ''; + $page_title = $user->lang['EDIT_POST']; + break; +} + +// Build Navigation Links +generate_forum_nav($post_data); + +// Build Forum Rules +generate_forum_rules($post_data); + +// Posting uses is_solved for legacy reasons. Plugins have to use is_solved to force themselves to be displayed. +if ($config['enable_post_confirm'] && !$user->data['is_registered'] && (isset($captcha) && $captcha->is_solved() === false) && ($mode == 'post' || $mode == 'reply' || $mode == 'quote')) +{ + + $template->assign_vars(array( + 'S_CONFIRM_CODE' => true, + 'CAPTCHA_TEMPLATE' => $captcha->get_template(), + )); +} + +$s_hidden_fields = ($mode == 'reply' || $mode == 'quote') ? '<input type="hidden" name="topic_cur_post_id" value="' . $post_data['topic_last_post_id'] . '" />' : ''; +$s_hidden_fields .= ($draft_id || isset($_REQUEST['draft_loaded'])) ? '<input type="hidden" name="draft_loaded" value="' . $request->variable('draft_loaded', $draft_id) . '" />' : ''; + +if ($mode == 'edit') +{ + $s_hidden_fields .= build_hidden_fields(array( + 'edit_post_message_checksum' => $post_data['post_checksum'], + 'edit_post_subject_checksum' => $post_data['post_subject_md5'], + )); +} + +// Add the confirm id/code pair to the hidden fields, else an error is displayed on next submit/preview +if (isset($captcha) && $captcha->is_solved() !== false) +{ + $s_hidden_fields .= build_hidden_fields($captcha->get_hidden_fields()); +} + +$form_enctype = (@ini_get('file_uploads') == '0' || strtolower(@ini_get('file_uploads')) == 'off' || !$config['allow_attachments'] || !$auth->acl_get('u_attach') || !$auth->acl_get('f_attach', $forum_id)) ? '' : ' enctype="multipart/form-data"'; +add_form_key('posting'); + +/** @var \phpbb\controller\helper $controller_helper */ +$controller_helper = $phpbb_container->get('controller.helper'); + +// Build array of variables for main posting page +$page_data = array( + 'L_POST_A' => $page_title, + 'L_ICON' => ($mode == 'reply' || $mode == 'quote' || ($mode == 'edit' && $post_id != $post_data['topic_first_post_id'])) ? $user->lang['POST_ICON'] : $user->lang['TOPIC_ICON'], + 'L_MESSAGE_BODY_EXPLAIN' => $user->lang('MESSAGE_BODY_EXPLAIN', (int) $config['max_post_chars']), + 'L_DELETE_POST_PERMANENTLY' => $user->lang('DELETE_POST_PERMANENTLY', 1), + + 'FORUM_NAME' => $post_data['forum_name'], + 'FORUM_DESC' => ($post_data['forum_desc']) ? generate_text_for_display($post_data['forum_desc'], $post_data['forum_desc_uid'], $post_data['forum_desc_bitfield'], $post_data['forum_desc_options']) : '', + 'TOPIC_TITLE' => censor_text($post_data['topic_title']), + 'MODERATORS' => (count($moderators)) ? implode($user->lang['COMMA_SEPARATOR'], $moderators[$forum_id]) : '', + 'USERNAME' => ((!$preview && $mode != 'quote') || $preview) ? $post_data['username'] : '', + 'SUBJECT' => $post_data['post_subject'], + 'MESSAGE' => $post_data['post_text'], + 'BBCODE_STATUS' => $user->lang(($bbcode_status ? 'BBCODE_IS_ON' : 'BBCODE_IS_OFF'), '<a href="' . $controller_helper->route('phpbb_help_bbcode_controller') . '">', '</a>'), + 'IMG_STATUS' => ($img_status) ? $user->lang['IMAGES_ARE_ON'] : $user->lang['IMAGES_ARE_OFF'], + 'FLASH_STATUS' => ($flash_status) ? $user->lang['FLASH_IS_ON'] : $user->lang['FLASH_IS_OFF'], + 'SMILIES_STATUS' => ($smilies_status) ? $user->lang['SMILIES_ARE_ON'] : $user->lang['SMILIES_ARE_OFF'], + 'URL_STATUS' => ($bbcode_status && $url_status) ? $user->lang['URL_IS_ON'] : $user->lang['URL_IS_OFF'], + 'MAX_FONT_SIZE' => (int) $config['max_post_font_size'], + 'MINI_POST_IMG' => $user->img('icon_post_target', $user->lang['POST']), + 'POST_DATE' => ($post_data['post_time']) ? $user->format_date($post_data['post_time']) : '', + 'ERROR' => (count($error)) ? implode('<br />', $error) : '', + 'TOPIC_TIME_LIMIT' => (int) $post_data['topic_time_limit'], + 'EDIT_REASON' => $request->variable('edit_reason', '', true), + 'SHOW_PANEL' => $request->variable('show_panel', ''), + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id"), + 'U_VIEW_TOPIC' => ($mode != 'post') ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id") : '', + 'U_PROGRESS_BAR' => append_sid("{$phpbb_root_path}posting.$phpEx", "f=$forum_id&mode=popup"), + 'UA_PROGRESS_BAR' => addslashes(append_sid("{$phpbb_root_path}posting.$phpEx", "f=$forum_id&mode=popup")), + + 'S_PRIVMSGS' => false, + 'S_CLOSE_PROGRESS_WINDOW' => (isset($_POST['add_file'])) ? true : false, + 'S_EDIT_POST' => ($mode == 'edit') ? true : false, + 'S_EDIT_REASON' => ($mode == 'edit' && $auth->acl_get('m_edit', $forum_id)) ? true : false, + 'S_DISPLAY_USERNAME' => (!$user->data['is_registered'] || ($mode == 'edit' && $post_data['poster_id'] == ANONYMOUS)) ? true : false, + 'S_SHOW_TOPIC_ICONS' => $s_topic_icons, + 'S_DELETE_ALLOWED' => ($mode == 'edit' && (($post_id == $post_data['topic_last_post_id'] && $post_data['poster_id'] == $user->data['user_id'] && $auth->acl_get('f_delete', $forum_id) && !$post_data['post_edit_locked'] && ($post_data['post_time'] > time() - ($config['delete_time'] * 60) || !$config['delete_time'])) || $auth->acl_get('m_delete', $forum_id))) ? true : false, + 'S_BBCODE_ALLOWED' => ($bbcode_status) ? 1 : 0, + 'S_BBCODE_CHECKED' => ($bbcode_checked) ? ' checked="checked"' : '', + 'S_SMILIES_ALLOWED' => $smilies_status, + 'S_SMILIES_CHECKED' => ($smilies_checked) ? ' checked="checked"' : '', + 'S_SIG_ALLOWED' => ($auth->acl_get('f_sigs', $forum_id) && $config['allow_sig'] && $user->data['is_registered']) ? true : false, + 'S_SIGNATURE_CHECKED' => ($sig_checked) ? ' checked="checked"' : '', + 'S_NOTIFY_ALLOWED' => (!$user->data['is_registered'] || ($mode == 'edit' && $user->data['user_id'] != $post_data['poster_id']) || !$config['allow_topic_notify'] || !$config['email_enable']) ? false : true, + 'S_NOTIFY_CHECKED' => ($notify_checked) ? ' checked="checked"' : '', + 'S_LOCK_TOPIC_ALLOWED' => (($mode == 'edit' || $mode == 'reply' || $mode == 'quote' || $mode == 'post') && ($auth->acl_get('m_lock', $forum_id) || ($auth->acl_get('f_user_lock', $forum_id) && $user->data['is_registered'] && !empty($post_data['topic_poster']) && $user->data['user_id'] == $post_data['topic_poster'] && $post_data['topic_status'] == ITEM_UNLOCKED))) ? true : false, + 'S_LOCK_TOPIC_CHECKED' => ($lock_topic_checked) ? ' checked="checked"' : '', + 'S_LOCK_POST_ALLOWED' => ($mode == 'edit' && $auth->acl_get('m_edit', $forum_id)) ? true : false, + 'S_LOCK_POST_CHECKED' => ($lock_post_checked) ? ' checked="checked"' : '', + 'S_SOFTDELETE_CHECKED' => ($mode == 'edit' && $post_data['post_visibility'] == ITEM_DELETED) ? ' checked="checked"' : '', + 'S_SOFTDELETE_ALLOWED' => ($mode == 'edit' && $phpbb_content_visibility->can_soft_delete($forum_id, $post_data['poster_id'], $lock_post_checked) && $post_id == $post_data['topic_last_post_id'] && ($post_data['post_time'] > time() - ($config['delete_time'] * 60) || !$config['delete_time'])) ? true : false, + 'S_RESTORE_ALLOWED' => $auth->acl_get('m_approve', $forum_id), + 'S_IS_DELETED' => ($mode == 'edit' && $post_data['post_visibility'] == ITEM_DELETED) ? true : false, + 'S_LINKS_ALLOWED' => $url_status, + 'S_MAGIC_URL_CHECKED' => ($urls_checked) ? ' checked="checked"' : '', + 'S_TYPE_TOGGLE' => $topic_type_toggle, + 'S_SAVE_ALLOWED' => ($auth->acl_get('u_savedrafts') && $user->data['is_registered'] && $mode != 'edit') ? true : false, + 'S_HAS_DRAFTS' => ($auth->acl_get('u_savedrafts') && $user->data['is_registered'] && $post_data['drafts']) ? true : false, + 'S_FORM_ENCTYPE' => $form_enctype, + + 'S_BBCODE_IMG' => $img_status, + 'S_BBCODE_URL' => $url_status, + 'S_BBCODE_FLASH' => $flash_status, + 'S_BBCODE_QUOTE' => $quote_status, + + 'S_POST_ACTION' => $s_action, + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + 'S_ATTACH_DATA' => json_encode($message_parser->attachment_data), + 'S_IN_POSTING' => true, +); + +// Build custom bbcodes array +display_custom_bbcodes(); + +// Poll entry +if (($mode == 'post' || ($mode == 'edit' && $post_id == $post_data['topic_first_post_id']/* && (!$post_data['poll_last_vote'] || $auth->acl_get('m_edit', $forum_id))*/)) + && $auth->acl_get('f_poll', $forum_id)) +{ + $page_data = array_merge($page_data, array( + 'S_SHOW_POLL_BOX' => true, + 'S_POLL_VOTE_CHANGE' => ($auth->acl_get('f_votechg', $forum_id) && $auth->acl_get('f_vote', $forum_id)), + 'S_POLL_DELETE' => ($mode == 'edit' && count($post_data['poll_options']) && ((!$post_data['poll_last_vote'] && $post_data['poster_id'] == $user->data['user_id'] && $auth->acl_get('f_delete', $forum_id)) || $auth->acl_get('m_delete', $forum_id))), + 'S_POLL_DELETE_CHECKED' => (!empty($poll_delete)) ? true : false, + + 'L_POLL_OPTIONS_EXPLAIN' => $user->lang('POLL_OPTIONS_' . (($mode == 'edit') ? 'EDIT_' : '') . 'EXPLAIN', (int) $config['max_poll_options']), + + 'VOTE_CHANGE_CHECKED' => (!empty($post_data['poll_vote_change'])) ? ' checked="checked"' : '', + 'SHOW_RESULTS_CHECKED' => (!empty($post_data['poll_show_results'])) ? ' checked="checked"' : '', + 'POLL_TITLE' => (isset($post_data['poll_title'])) ? $post_data['poll_title'] : '', + 'POLL_OPTIONS' => (!empty($post_data['poll_options'])) ? implode("\n", $post_data['poll_options']) : '', + 'POLL_MAX_OPTIONS' => (isset($post_data['poll_max_options'])) ? (int) $post_data['poll_max_options'] : 1, + 'POLL_LENGTH' => $post_data['poll_length'], + ) + ); +} + +/** +* This event allows you to modify template variables for the posting screen +* +* @event core.posting_modify_template_vars +* @var array post_data Array with post data +* @var array moderators Array with forum moderators +* @var string mode What action to take if the form is submitted +* post|reply|quote|edit|delete|bump|smilies|popup +* @var string page_title Title of the mode page +* @var bool s_topic_icons Whether or not to show the topic icons +* @var string form_enctype If attachments are allowed for this form +* "multipart/form-data" or empty string +* @var string s_action The URL to submit the POST data to +* @var string s_hidden_fields Concatenated hidden input tags of posting form +* @var int post_id ID of the post +* @var int topic_id ID of the topic +* @var int forum_id ID of the forum +* @var int draft_id ID of the draft +* @var bool submit Whether or not the form has been submitted +* @var bool preview Whether or not the post is being previewed +* @var bool save Whether or not a draft is being saved +* @var bool load Whether or not a draft is being loaded +* @var bool cancel Whether or not to cancel the form (returns to +* viewtopic or viewforum depending on if the user +* is posting a new topic or editing a post) +* @var array error Any error strings; a non-empty array aborts +* form submission. +* NOTE: Should be actual language strings, NOT +* language keys. +* @var bool refresh Whether or not to retain previously submitted data +* @var array page_data Posting page data that should be passed to the +* posting page via $template->assign_vars() +* @var object message_parser The message parser object +* @since 3.1.0-a1 +* @changed 3.1.0-b3 Added vars post_data, moderators, mode, page_title, +* s_topic_icons, form_enctype, s_action, s_hidden_fields, +* post_id, topic_id, forum_id, submit, preview, save, load, +* delete, cancel, refresh, error, page_data, message_parser +* @changed 3.1.2-RC1 Removed 'delete' var as it does not exist +* @changed 3.1.5-RC1 Added poll variables to the page_data array +* @changed 3.1.6-RC1 Added 'draft_id' var +*/ +$vars = array( + 'post_data', + 'moderators', + 'mode', + 'page_title', + 's_topic_icons', + 'form_enctype', + 's_action', + 's_hidden_fields', + 'post_id', + 'topic_id', + 'forum_id', + 'draft_id', + 'submit', + 'preview', + 'save', + 'load', + 'cancel', + 'refresh', + 'error', + 'page_data', + 'message_parser', +); +extract($phpbb_dispatcher->trigger_event('core.posting_modify_template_vars', compact($vars))); + +// Start assigning vars for main posting page ... +$template->assign_vars($page_data); + +// Show attachment box for adding attachments if true +$allowed = ($auth->acl_get('f_attach', $forum_id) && $auth->acl_get('u_attach') && $config['allow_attachments'] && $form_enctype); + +if ($allowed) +{ + $max_files = ($auth->acl_get('a_') || $auth->acl_get('m_', $forum_id)) ? 0 : (int) $config['max_attachments']; + $plupload->configure($cache, $template, $s_action, $forum_id, $max_files); +} + +// Attachment entry +posting_gen_attachment_entry($attachment_data, $filename_data, $allowed, $forum_id); + +// Output page ... +page_header($page_title); + +$template->set_filenames(array( + 'body' => 'posting_body.html') +); + +make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); + +// Topic review +if ($mode == 'reply' || $mode == 'quote') +{ + if (topic_review($topic_id, $forum_id)) + { + $template->assign_var('S_DISPLAY_REVIEW', true); + } +} + +page_footer(); diff --git a/phpbb/viewtopic.php b/phpbb/viewtopic.php new file mode 100644 index 0000000..161c038 --- /dev/null +++ b/phpbb/viewtopic.php @@ -0,0 +1,2449 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_display.' . $phpEx); +include($phpbb_root_path . 'includes/bbcode.' . $phpEx); +include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); + +// Initial var setup +$forum_id = 0; +$topic_id = $request->variable('t', 0); +$post_id = $request->variable('p', 0); +$voted_id = $request->variable('vote_id', array('' => 0)); + +$voted_id = (count($voted_id) > 1) ? array_unique($voted_id) : $voted_id; + + +$start = $request->variable('start', 0); +$view = $request->variable('view', ''); + +$default_sort_days = (!empty($user->data['user_post_show_days'])) ? $user->data['user_post_show_days'] : 0; +$default_sort_key = (!empty($user->data['user_post_sortby_type'])) ? $user->data['user_post_sortby_type'] : 't'; +$default_sort_dir = (!empty($user->data['user_post_sortby_dir'])) ? $user->data['user_post_sortby_dir'] : 'a'; + +$sort_days = $request->variable('st', $default_sort_days); +$sort_key = $request->variable('sk', $default_sort_key); +$sort_dir = $request->variable('sd', $default_sort_dir); + +$update = $request->variable('update', false); + +/* @var $pagination \phpbb\pagination */ +$pagination = $phpbb_container->get('pagination'); + +$s_can_vote = false; +/** +* @todo normalize? +*/ +$hilit_words = $request->variable('hilit', '', true); + +// Do we have a topic or post id? +if (!$topic_id && !$post_id) +{ + trigger_error('NO_TOPIC'); +} + +/* @var $phpbb_content_visibility \phpbb\content_visibility */ +$phpbb_content_visibility = $phpbb_container->get('content.visibility'); + +// Find topic id if user requested a newer or older topic +if ($view && !$post_id) +{ + + if ($view == 'unread') + { + $sql = 'SELECT forum_id + FROM ' . TOPICS_TABLE . " + WHERE topic_id = $topic_id"; + $result = $db->sql_query($sql); + $forum_id = (int) $db->sql_fetchfield('forum_id'); + $db->sql_freeresult($result); + + if (!$forum_id) + { + trigger_error('NO_TOPIC'); + } + + // Get topic tracking info + $topic_tracking_info = get_complete_topic_tracking($forum_id, $topic_id); + $topic_last_read = (isset($topic_tracking_info[$topic_id])) ? $topic_tracking_info[$topic_id] : 0; + + $sql = 'SELECT post_id, topic_id, forum_id + FROM ' . POSTS_TABLE . " + WHERE topic_id = $topic_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id) . " + AND post_time > $topic_last_read + AND forum_id = $forum_id + ORDER BY post_time ASC, post_id ASC"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $sql = 'SELECT topic_last_post_id as post_id, topic_id, forum_id + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + if (!$row) + { + // Setup user environment so we can process lang string + $user->setup('viewtopic'); + + trigger_error('NO_TOPIC'); + } + + $post_id = $row['post_id']; + $topic_id = $row['topic_id']; + } + else if ($view == 'next' || $view == 'previous') + { + $sql_condition = ($view == 'next') ? '>' : '<'; + $sql_ordering = ($view == 'next') ? 'ASC' : 'DESC'; + + $sql = 'SELECT forum_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $user->setup('viewtopic'); + // OK, the topic doesn't exist. This error message is not helpful, but technically correct. + trigger_error(($view == 'next') ? 'NO_NEWER_TOPICS' : 'NO_OLDER_TOPICS'); + } + else + { + $forum_id = $row['forum_id']; + $sql = 'SELECT topic_id, forum_id + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = ' . $forum_id . " + AND topic_moved_id = 0 + AND topic_last_post_time $sql_condition {$row['topic_last_post_time']} + AND " . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id) . " + ORDER BY topic_last_post_time $sql_ordering, topic_last_post_id $sql_ordering"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $sql = 'SELECT forum_style + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql); + $forum_style = (int) $db->sql_fetchfield('forum_style'); + $db->sql_freeresult($result); + + $user->setup('viewtopic', $forum_style); + trigger_error(($view == 'next') ? 'NO_NEWER_TOPICS' : 'NO_OLDER_TOPICS'); + } + else + { + $topic_id = $row['topic_id']; + $forum_id = $row['forum_id']; + } + } + } + + if (isset($row) && $row['forum_id']) + { + $forum_id = $row['forum_id']; + } +} + +// This rather complex gaggle of code handles querying for topics but +// also allows for direct linking to a post (and the calculation of which +// page the post is on and the correct display of viewtopic) +$sql_array = array( + 'SELECT' => 't.*, f.*', + + 'FROM' => array(FORUMS_TABLE => 'f'), +); + +// The FROM-Order is quite important here, else t.* columns can not be correctly bound. +if ($post_id) +{ + $sql_array['SELECT'] .= ', p.post_visibility, p.post_time, p.post_id'; + $sql_array['FROM'][POSTS_TABLE] = 'p'; +} + +// Topics table need to be the last in the chain +$sql_array['FROM'][TOPICS_TABLE] = 't'; + +if ($user->data['is_registered']) +{ + $sql_array['SELECT'] .= ', tw.notify_status'; + $sql_array['LEFT_JOIN'] = array(); + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_WATCH_TABLE => 'tw'), + 'ON' => 'tw.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tw.topic_id' + ); + + if ($config['allow_bookmarks']) + { + $sql_array['SELECT'] .= ', bm.topic_id as bookmarked'; + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(BOOKMARKS_TABLE => 'bm'), + 'ON' => 'bm.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = bm.topic_id' + ); + } + + if ($config['load_db_lastread']) + { + $sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as forum_mark_time'; + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'), + 'ON' => 'tt.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tt.topic_id' + ); + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND t.forum_id = ft.forum_id' + ); + } +} + +if (!$post_id) +{ + $sql_array['WHERE'] = "t.topic_id = $topic_id"; +} +else +{ + $sql_array['WHERE'] = "p.post_id = $post_id AND t.topic_id = p.topic_id"; +} + +$sql_array['WHERE'] .= ' AND f.forum_id = t.forum_id'; + +$sql = $db->sql_build_query('SELECT', $sql_array); +$result = $db->sql_query($sql); +$topic_data = $db->sql_fetchrow($result); +$db->sql_freeresult($result); + +// link to unapproved post or incorrect link +if (!$topic_data) +{ + // If post_id was submitted, we try at least to display the topic as a last resort... + if ($post_id && $topic_id) + { + redirect(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id")); + } + + trigger_error('NO_TOPIC'); +} + +$forum_id = (int) $topic_data['forum_id']; + +/** + * Modify the forum ID to handle the correct display of viewtopic if needed + * + * @event core.viewtopic_modify_forum_id + * @var string forum_id forum ID + * @var array topic_data array of topic's data + * @since 3.2.5-RC1 + */ +$vars = array( + 'forum_id', + 'topic_data', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_forum_id', compact($vars))); + +// If the request is missing the f parameter, the forum id in the user session data is 0 at the moment. +// Let's fix that now so that the user can't hide from the forum's Who Is Online list. +$user->page['forum'] = $forum_id; + +// Now we know the forum_id and can check the permissions +if (!$phpbb_content_visibility->is_visible('topic', $forum_id, $topic_data)) +{ + trigger_error('NO_TOPIC'); +} + +// This is for determining where we are (page) +if ($post_id) +{ + // are we where we are supposed to be? + if (($topic_data['post_visibility'] == ITEM_UNAPPROVED || $topic_data['post_visibility'] == ITEM_REAPPROVE) && !$auth->acl_get('m_approve', $topic_data['forum_id'])) + { + // If post_id was submitted, we try at least to display the topic as a last resort... + if ($topic_id) + { + redirect(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id")); + } + + trigger_error('NO_TOPIC'); + } + if ($post_id == $topic_data['topic_first_post_id'] || $post_id == $topic_data['topic_last_post_id']) + { + $check_sort = ($post_id == $topic_data['topic_first_post_id']) ? 'd' : 'a'; + + if ($sort_dir == $check_sort) + { + $topic_data['prev_posts'] = $phpbb_content_visibility->get_count('topic_posts', $topic_data, $forum_id) - 1; + } + else + { + $topic_data['prev_posts'] = 0; + } + } + else + { + $sql = 'SELECT COUNT(p.post_id) AS prev_posts + FROM ' . POSTS_TABLE . " p + WHERE p.topic_id = {$topic_data['topic_id']} + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.'); + + if ($sort_dir == 'd') + { + $sql .= " AND (p.post_time > {$topic_data['post_time']} OR (p.post_time = {$topic_data['post_time']} AND p.post_id >= {$topic_data['post_id']}))"; + } + else + { + $sql .= " AND (p.post_time < {$topic_data['post_time']} OR (p.post_time = {$topic_data['post_time']} AND p.post_id <= {$topic_data['post_id']}))"; + } + + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $topic_data['prev_posts'] = $row['prev_posts'] - 1; + } +} + +$topic_id = (int) $topic_data['topic_id']; +$topic_replies = $phpbb_content_visibility->get_count('topic_posts', $topic_data, $forum_id) - 1; + +// Check sticky/announcement/global time limit +if (($topic_data['topic_type'] != POST_NORMAL) && $topic_data['topic_time_limit'] && ($topic_data['topic_time'] + $topic_data['topic_time_limit']) < time()) +{ + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_type = ' . POST_NORMAL . ', topic_time_limit = 0 + WHERE topic_id = ' . $topic_id; + $db->sql_query($sql); + + $topic_data['topic_type'] = POST_NORMAL; + $topic_data['topic_time_limit'] = 0; +} + +// Setup look and feel +$user->setup('viewtopic', $topic_data['forum_style']); + +if ($view == 'print' && !$auth->acl_get('f_print', $forum_id)) +{ + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_PRINT_TOPIC'); +} + +$overrides_f_read_check = false; +$overrides_forum_password_check = false; +$topic_tracking_info = isset($topic_tracking_info) ? $topic_tracking_info : null; + +/** +* Event to apply extra permissions and to override original phpBB's f_read permission and forum password check +* on viewtopic access +* +* @event core.viewtopic_before_f_read_check +* @var int forum_id The forum id from where the topic belongs +* @var int topic_id The id of the topic the user tries to access +* @var int post_id The id of the post the user tries to start viewing at. +* It may be 0 for none given. +* @var array topic_data All the information from the topic and forum tables for this topic +* It includes posts information if post_id is not 0 +* @var bool overrides_f_read_check Set true to remove f_read check afterwards +* @var bool overrides_forum_password_check Set true to remove forum_password check afterwards +* @var array topic_tracking_info Information upon calling get_topic_tracking() +* Set it to NULL to allow auto-filling later. +* Set it to an array to override original data. +* @since 3.1.3-RC1 +*/ +$vars = array( + 'forum_id', + 'topic_id', + 'post_id', + 'topic_data', + 'overrides_f_read_check', + 'overrides_forum_password_check', + 'topic_tracking_info', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_before_f_read_check', compact($vars))); + +// Start auth check +if (!$overrides_f_read_check && !$auth->acl_get('f_read', $forum_id)) +{ + if ($user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + trigger_error('SORRY_AUTH_READ'); + } + + login_box('', $user->lang['LOGIN_VIEWFORUM']); +} + +// Forum is passworded ... check whether access has been granted to this +// user this session, if not show login box +if (!$overrides_forum_password_check && $topic_data['forum_password']) +{ + login_forum_box($topic_data); +} + +// Redirect to login upon emailed notification links if user is not logged in. +if (isset($_GET['e']) && $user->data['user_id'] == ANONYMOUS) +{ + login_box(build_url('e') . '#unread', $user->lang['LOGIN_NOTIFY_TOPIC']); +} + +// What is start equal to? +if ($post_id) +{ + $start = floor(($topic_data['prev_posts']) / $config['posts_per_page']) * $config['posts_per_page']; +} + +// Get topic tracking info +if (!isset($topic_tracking_info)) +{ + $topic_tracking_info = array(); + + // Get topic tracking info + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $tmp_topic_data = array($topic_id => $topic_data); + $topic_tracking_info = get_topic_tracking($forum_id, $topic_id, $tmp_topic_data, array($forum_id => $topic_data['forum_mark_time'])); + unset($tmp_topic_data); + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $topic_tracking_info = get_complete_topic_tracking($forum_id, $topic_id); + } +} + +// Post ordering options +$limit_days = array(0 => $user->lang['ALL_POSTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + +$sort_by_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']); +$sort_by_sql = array('a' => array('u.username_clean', 'p.post_id'), 't' => array('p.post_time', 'p.post_id'), 's' => array('p.post_subject', 'p.post_id')); +$join_user_sql = array('a' => true, 't' => false, 's' => false); + +$s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + +/** +* Event to add new sorting options +* +* @event core.viewtopic_gen_sort_selects_before +* @var array limit_days Limit results by time +* @var array sort_by_text Language strings for sorting options +* @var array sort_by_sql SQL conditions for sorting options +* @var array join_user_sql SQL joins required for sorting options +* @var int sort_days User selected sort days +* @var string sort_key User selected sort key +* @var string sort_dir User selected sort direction +* @var string s_limit_days Initial value of limit days selectbox +* @var string s_sort_key Initial value of sort key selectbox +* @var string s_sort_dir Initial value of sort direction selectbox +* @var string u_sort_param Initial value of sorting form action +* @since 3.2.8-RC1 +*/ +$vars = array( + 'limit_days', + 'sort_by_text', + 'sort_by_sql', + 'join_user_sql', + 'sort_days', + 'sort_key', + 'sort_dir', + 's_limit_days', + 's_sort_key', + 's_sort_dir', + 'u_sort_param', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_gen_sort_selects_before', compact($vars))); + +gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param, $default_sort_days, $default_sort_key, $default_sort_dir); + +// Obtain correct post count and ordering SQL if user has +// requested anything different +if ($sort_days) +{ + $min_post_time = time() - ($sort_days * 86400); + + $sql = 'SELECT COUNT(post_id) AS num_posts + FROM ' . POSTS_TABLE . " + WHERE topic_id = $topic_id + AND post_time >= $min_post_time + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id); + $result = $db->sql_query($sql); + $total_posts = (int) $db->sql_fetchfield('num_posts'); + $db->sql_freeresult($result); + + $limit_posts_time = "AND p.post_time >= $min_post_time "; + + if (isset($_POST['sort'])) + { + $start = 0; + } +} +else +{ + $total_posts = $topic_replies + 1; + $limit_posts_time = ''; +} + +// Was a highlight request part of the URI? +$highlight_match = $highlight = ''; +if ($hilit_words) +{ + $highlight_match = phpbb_clean_search_string($hilit_words); + $highlight = urlencode($highlight_match); + $highlight_match = str_replace('\*', '\w+?', preg_quote($highlight_match, '#')); + $highlight_match = preg_replace('#(?<=^|\s)\\\\w\*\?(?=\s|$)#', '\w+?', $highlight_match); + $highlight_match = str_replace(' ', '|', $highlight_match); +} + +// Make sure $start is set to the last page if it exceeds the amount +$start = $pagination->validate_start($start, $config['posts_per_page'], $total_posts); + +// General Viewtopic URL for return links +$viewtopic_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&start=$start") . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($highlight_match) ? "&hilit=$highlight" : '')); + +// Are we watching this topic? +$s_watching_topic = array( + 'link' => '', + 'link_toggle' => '', + 'title' => '', + 'title_toggle' => '', + 'is_watching' => false, +); + +if ($config['allow_topic_notify']) +{ + $notify_status = (isset($topic_data['notify_status'])) ? $topic_data['notify_status'] : null; + watch_topic_forum('topic', $s_watching_topic, $user->data['user_id'], $forum_id, $topic_id, $notify_status, $start, $topic_data['topic_title']); + + // Reset forum notification if forum notify is set + if ($config['allow_forum_notify'] && $auth->acl_get('f_subscribe', $forum_id)) + { + $s_watching_forum = $s_watching_topic; + watch_topic_forum('forum', $s_watching_forum, $user->data['user_id'], $forum_id, 0); + } +} + +/** +* Event to modify highlight. +* +* @event core.viewtopic_highlight_modify +* @var string highlight String to be highlighted +* @var string highlight_match Highlight string to be used in preg_replace +* @var array topic_data Topic data +* @var int start Pagination start +* @var int total_posts Number of posts +* @var string viewtopic_url Current viewtopic URL +* @since 3.1.11-RC1 +*/ +$vars = array( + 'highlight', + 'highlight_match', + 'topic_data', + 'start', + 'total_posts', + 'viewtopic_url', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_highlight_modify', compact($vars))); + +// Bookmarks +if ($config['allow_bookmarks'] && $user->data['is_registered'] && $request->variable('bookmark', 0)) +{ + if (check_link_hash($request->variable('hash', ''), "topic_$topic_id")) + { + if (!$topic_data['bookmarked']) + { + $sql = 'INSERT INTO ' . BOOKMARKS_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'user_id' => $user->data['user_id'], + 'topic_id' => $topic_id, + )); + $db->sql_query($sql); + } + else + { + $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND topic_id = $topic_id"; + $db->sql_query($sql); + } + $message = (($topic_data['bookmarked']) ? $user->lang['BOOKMARK_REMOVED'] : $user->lang['BOOKMARK_ADDED']); + + if (!$request->is_ajax()) + { + $message .= '<br /><br />' . $user->lang('RETURN_TOPIC', '<a href="' . $viewtopic_url . '">', '</a>'); + } + } + else + { + $message = $user->lang['BOOKMARK_ERR']; + + if (!$request->is_ajax()) + { + $message .= '<br /><br />' . $user->lang('RETURN_TOPIC', '<a href="' . $viewtopic_url . '">', '</a>'); + } + } + meta_refresh(3, $viewtopic_url); + + trigger_error($message); +} + +// Grab ranks +$ranks = $cache->obtain_ranks(); + +// Grab icons +$icons = $cache->obtain_icons(); + +// Grab extensions +$extensions = array(); +if ($topic_data['topic_attachment']) +{ + $extensions = $cache->obtain_attach_extensions($forum_id); +} + +// Forum rules listing +$s_forum_rules = ''; +gen_forum_auth_level('topic', $forum_id, $topic_data['forum_status']); + +// Quick mod tools +$allow_change_type = ($auth->acl_get('m_', $forum_id) || ($user->data['is_registered'] && $user->data['user_id'] == $topic_data['topic_poster'])) ? true : false; + +$s_quickmod_action = append_sid( + "{$phpbb_root_path}mcp.$phpEx", + array( + 'f' => $forum_id, + 't' => $topic_id, + 'start' => $start, + 'quickmod' => 1, + 'redirect' => urlencode(str_replace('&', '&', $viewtopic_url)), + ), + true, + $user->session_id +); + +$quickmod_array = array( +// 'key' => array('LANG_KEY', $userHasPermissions), + + 'lock' => array('LOCK_TOPIC', ($topic_data['topic_status'] == ITEM_UNLOCKED) && ($auth->acl_get('m_lock', $forum_id) || ($auth->acl_get('f_user_lock', $forum_id) && $user->data['is_registered'] && $user->data['user_id'] == $topic_data['topic_poster']))), + 'unlock' => array('UNLOCK_TOPIC', ($topic_data['topic_status'] != ITEM_UNLOCKED) && ($auth->acl_get('m_lock', $forum_id))), + 'delete_topic' => array('DELETE_TOPIC', ($auth->acl_get('m_delete', $forum_id) || (($topic_data['topic_visibility'] != ITEM_DELETED) && $auth->acl_get('m_softdelete', $forum_id)))), + 'restore_topic' => array('RESTORE_TOPIC', (($topic_data['topic_visibility'] == ITEM_DELETED) && $auth->acl_get('m_approve', $forum_id))), + 'move' => array('MOVE_TOPIC', $auth->acl_get('m_move', $forum_id) && $topic_data['topic_status'] != ITEM_MOVED), + 'split' => array('SPLIT_TOPIC', $auth->acl_get('m_split', $forum_id)), + 'merge' => array('MERGE_POSTS', $auth->acl_get('m_merge', $forum_id)), + 'merge_topic' => array('MERGE_TOPIC', $auth->acl_get('m_merge', $forum_id)), + 'fork' => array('FORK_TOPIC', $auth->acl_get('m_move', $forum_id)), + 'make_normal' => array('MAKE_NORMAL', ($allow_change_type && $auth->acl_gets('f_sticky', 'f_announce', 'f_announce_global', $forum_id) && $topic_data['topic_type'] != POST_NORMAL)), + 'make_sticky' => array('MAKE_STICKY', ($allow_change_type && $auth->acl_get('f_sticky', $forum_id) && $topic_data['topic_type'] != POST_STICKY)), + 'make_announce' => array('MAKE_ANNOUNCE', ($allow_change_type && $auth->acl_get('f_announce', $forum_id) && $topic_data['topic_type'] != POST_ANNOUNCE)), + 'make_global' => array('MAKE_GLOBAL', ($allow_change_type && $auth->acl_get('f_announce_global', $forum_id) && $topic_data['topic_type'] != POST_GLOBAL)), + 'topic_logs' => array('VIEW_TOPIC_LOGS', $auth->acl_get('m_', $forum_id)), +); + +/** +* Event to modify data in the quickmod_array before it gets sent to the +* phpbb_add_quickmod_option function. +* +* @event core.viewtopic_add_quickmod_option_before +* @var int forum_id Forum ID +* @var int post_id Post ID +* @var array quickmod_array Array with quick moderation options data +* @var array topic_data Array with topic data +* @var int topic_id Topic ID +* @var array topic_tracking_info Array with topic tracking data +* @var string viewtopic_url URL to the topic page +* @var bool allow_change_type Topic change permissions check +* @since 3.1.9-RC1 +*/ +$vars = array( + 'forum_id', + 'post_id', + 'quickmod_array', + 'topic_data', + 'topic_id', + 'topic_tracking_info', + 'viewtopic_url', + 'allow_change_type', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_add_quickmod_option_before', compact($vars))); + +foreach ($quickmod_array as $option => $qm_ary) +{ + if (!empty($qm_ary[1])) + { + phpbb_add_quickmod_option($s_quickmod_action, $option, $qm_ary[0]); + } +} + +// Navigation links +generate_forum_nav($topic_data); + +// Forum Rules +generate_forum_rules($topic_data); + +// Moderators +$forum_moderators = array(); +if ($config['load_moderators']) +{ + get_moderators($forum_moderators, $forum_id); +} + +// This is only used for print view so ... +$server_path = (!$view) ? $phpbb_root_path : generate_board_url() . '/'; + +// Replace naughty words in title +$topic_data['topic_title'] = censor_text($topic_data['topic_title']); + +$s_search_hidden_fields = array( + 't' => $topic_id, + 'sf' => 'msgonly', +); +if ($_SID) +{ + $s_search_hidden_fields['sid'] = $_SID; +} + +if (!empty($_EXTRA_URL)) +{ + foreach ($_EXTRA_URL as $url_param) + { + $url_param = explode('=', $url_param, 2); + $s_search_hidden_fields[$url_param[0]] = $url_param[1]; + } +} + +// If we've got a hightlight set pass it on to pagination. +$base_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($highlight_match) ? "&hilit=$highlight" : '')); + +/** +* Event to modify data before template variables are being assigned +* +* @event core.viewtopic_assign_template_vars_before +* @var string base_url URL to be passed to generate pagination +* @var int forum_id Forum ID +* @var int post_id Post ID +* @var array quickmod_array Array with quick moderation options data +* @var int start Pagination information +* @var array topic_data Array with topic data +* @var int topic_id Topic ID +* @var array topic_tracking_info Array with topic tracking data +* @var int total_posts Topic total posts count +* @var string viewtopic_url URL to the topic page +* @since 3.1.0-RC4 +* @changed 3.1.2-RC1 Added viewtopic_url +*/ +$vars = array( + 'base_url', + 'forum_id', + 'post_id', + 'quickmod_array', + 'start', + 'topic_data', + 'topic_id', + 'topic_tracking_info', + 'total_posts', + 'viewtopic_url', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_assign_template_vars_before', compact($vars))); + +$pagination->generate_template_pagination($base_url, 'pagination', 'start', $total_posts, $config['posts_per_page'], $start); + +// Send vars to template +$template->assign_vars(array( + 'FORUM_ID' => $forum_id, + 'FORUM_NAME' => $topic_data['forum_name'], + 'FORUM_DESC' => generate_text_for_display($topic_data['forum_desc'], $topic_data['forum_desc_uid'], $topic_data['forum_desc_bitfield'], $topic_data['forum_desc_options']), + 'TOPIC_ID' => $topic_id, + 'TOPIC_TITLE' => $topic_data['topic_title'], + 'TOPIC_POSTER' => $topic_data['topic_poster'], + + 'TOPIC_AUTHOR_FULL' => get_username_string('full', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), + 'TOPIC_AUTHOR_COLOUR' => get_username_string('colour', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), + 'TOPIC_AUTHOR' => get_username_string('username', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), + + 'TOTAL_POSTS' => $user->lang('VIEW_TOPIC_POSTS', (int) $total_posts), + 'U_MCP' => ($auth->acl_get('m_', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=topic_view&t=$topic_id" . (($start == 0) ? '' : "&start=$start") . ((strlen($u_sort_param)) ? "&$u_sort_param" : ''), true, $user->session_id) : '', + 'MODERATORS' => (isset($forum_moderators[$forum_id]) && count($forum_moderators[$forum_id])) ? implode($user->lang['COMMA_SEPARATOR'], $forum_moderators[$forum_id]) : '', + + 'POST_IMG' => ($topic_data['forum_status'] == ITEM_LOCKED) ? $user->img('button_topic_locked', 'FORUM_LOCKED') : $user->img('button_topic_new', 'POST_NEW_TOPIC'), + 'QUOTE_IMG' => $user->img('icon_post_quote', 'REPLY_WITH_QUOTE'), + 'REPLY_IMG' => ($topic_data['forum_status'] == ITEM_LOCKED || $topic_data['topic_status'] == ITEM_LOCKED) ? $user->img('button_topic_locked', 'TOPIC_LOCKED') : $user->img('button_topic_reply', 'REPLY_TO_TOPIC'), + 'EDIT_IMG' => $user->img('icon_post_edit', 'EDIT_POST'), + 'DELETE_IMG' => $user->img('icon_post_delete', 'DELETE_POST'), + 'DELETED_IMG' => $user->img('icon_topic_deleted', 'POST_DELETED_RESTORE'), + 'INFO_IMG' => $user->img('icon_post_info', 'VIEW_INFO'), + 'PROFILE_IMG' => $user->img('icon_user_profile', 'READ_PROFILE'), + 'SEARCH_IMG' => $user->img('icon_user_search', 'SEARCH_USER_POSTS'), + 'PM_IMG' => $user->img('icon_contact_pm', 'SEND_PRIVATE_MESSAGE'), + 'EMAIL_IMG' => $user->img('icon_contact_email', 'SEND_EMAIL'), + 'JABBER_IMG' => $user->img('icon_contact_jabber', 'JABBER') , + 'REPORT_IMG' => $user->img('icon_post_report', 'REPORT_POST'), + 'REPORTED_IMG' => $user->img('icon_topic_reported', 'POST_REPORTED'), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', 'POST_UNAPPROVED'), + 'WARN_IMG' => $user->img('icon_user_warn', 'WARN_USER'), + + 'S_IS_LOCKED' => ($topic_data['topic_status'] == ITEM_UNLOCKED && $topic_data['forum_status'] == ITEM_UNLOCKED) ? false : true, + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days, + 'S_SINGLE_MODERATOR' => (!empty($forum_moderators[$forum_id]) && count($forum_moderators[$forum_id]) > 1) ? false : true, + 'S_TOPIC_ACTION' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&start=$start")), + 'S_MOD_ACTION' => $s_quickmod_action, + + 'L_RETURN_TO_FORUM' => $user->lang('RETURN_TO', $topic_data['forum_name']), + 'S_VIEWTOPIC' => true, + 'S_UNREAD_VIEW' => $view == 'unread', + 'S_DISPLAY_SEARCHBOX' => ($auth->acl_get('u_search') && $auth->acl_get('f_search', $forum_id) && $config['load_search']) ? true : false, + 'S_SEARCHBOX_ACTION' => append_sid("{$phpbb_root_path}search.$phpEx"), + 'S_SEARCH_LOCAL_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields), + + 'S_DISPLAY_POST_INFO' => ($topic_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS)) ? true : false, + 'S_DISPLAY_REPLY_INFO' => ($topic_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_reply', $forum_id) || $user->data['user_id'] == ANONYMOUS)) ? true : false, + 'S_ENABLE_FEEDS_TOPIC' => ($config['feed_topic'] && !phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $topic_data['forum_options'])) ? true : false, + + 'U_TOPIC' => "{$server_path}viewtopic.$phpEx?t=$topic_id", + 'U_FORUM' => $server_path, + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&start=$start") . (strlen($u_sort_param) ? "&$u_sort_param" : '')), + 'U_CANONICAL' => generate_board_url() . '/' . append_sid("viewtopic.$phpEx", "t=$topic_id" . (($start) ? "&start=$start" : ''), true, ''), + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), + 'U_VIEW_OLDER_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&view=previous"), + 'U_VIEW_NEWER_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&view=next"), + 'U_PRINT_TOPIC' => ($auth->acl_get('f_print', $forum_id)) ? $viewtopic_url . '&view=print' : '', + 'U_EMAIL_TOPIC' => ($auth->acl_get('f_email', $forum_id) && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=email&t=$topic_id") : '', + + 'U_WATCH_TOPIC' => $s_watching_topic['link'], + 'U_WATCH_TOPIC_TOGGLE' => $s_watching_topic['link_toggle'], + 'S_WATCH_TOPIC_TITLE' => $s_watching_topic['title'], + 'S_WATCH_TOPIC_TOGGLE' => $s_watching_topic['title_toggle'], + 'S_WATCHING_TOPIC' => $s_watching_topic['is_watching'], + + 'U_BOOKMARK_TOPIC' => ($user->data['is_registered'] && $config['allow_bookmarks']) ? $viewtopic_url . '&bookmark=1&hash=' . generate_link_hash("topic_$topic_id") : '', + 'S_BOOKMARK_TOPIC' => ($user->data['is_registered'] && $config['allow_bookmarks'] && $topic_data['bookmarked']) ? $user->lang['BOOKMARK_TOPIC_REMOVE'] : $user->lang['BOOKMARK_TOPIC'], + 'S_BOOKMARK_TOGGLE' => (!$user->data['is_registered'] || !$config['allow_bookmarks'] || !$topic_data['bookmarked']) ? $user->lang['BOOKMARK_TOPIC_REMOVE'] : $user->lang['BOOKMARK_TOPIC'], + 'S_BOOKMARKED_TOPIC' => ($user->data['is_registered'] && $config['allow_bookmarks'] && $topic_data['bookmarked']) ? true : false, + + 'U_POST_NEW_TOPIC' => ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=post&f=$forum_id") : '', + 'U_POST_REPLY_TOPIC' => ($auth->acl_get('f_reply', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=reply&t=$topic_id") : '', + 'U_BUMP_TOPIC' => (bump_topic_allowed($forum_id, $topic_data['topic_bumped'], $topic_data['topic_last_post_time'], $topic_data['topic_poster'], $topic_data['topic_last_poster_id'])) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=bump&t=$topic_id&hash=" . generate_link_hash("topic_$topic_id")) : '') +); + +// Does this topic contain a poll? +if (!empty($topic_data['poll_start'])) +{ + $sql = 'SELECT o.*, p.bbcode_bitfield, p.bbcode_uid + FROM ' . POLL_OPTIONS_TABLE . ' o, ' . POSTS_TABLE . " p + WHERE o.topic_id = $topic_id + AND p.post_id = {$topic_data['topic_first_post_id']} + AND p.topic_id = o.topic_id + ORDER BY o.poll_option_id"; + $result = $db->sql_query($sql); + + $poll_info = $vote_counts = array(); + while ($row = $db->sql_fetchrow($result)) + { + $poll_info[] = $row; + $option_id = (int) $row['poll_option_id']; + $vote_counts[$option_id] = (int) $row['poll_option_total']; + } + $db->sql_freeresult($result); + + $cur_voted_id = array(); + if ($user->data['is_registered']) + { + $sql = 'SELECT poll_option_id + FROM ' . POLL_VOTES_TABLE . ' + WHERE topic_id = ' . $topic_id . ' + AND vote_user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $cur_voted_id[] = $row['poll_option_id']; + } + $db->sql_freeresult($result); + } + else + { + // Cookie based guest tracking ... I don't like this but hum ho + // it's oft requested. This relies on "nice" users who don't feel + // the need to delete cookies to mess with results. + if ($request->is_set($config['cookie_name'] . '_poll_' . $topic_id, \phpbb\request\request_interface::COOKIE)) + { + $cur_voted_id = explode(',', $request->variable($config['cookie_name'] . '_poll_' . $topic_id, '', true, \phpbb\request\request_interface::COOKIE)); + $cur_voted_id = array_map('intval', $cur_voted_id); + } + } + + // Can not vote at all if no vote permission + $s_can_vote = ($auth->acl_get('f_vote', $forum_id) && + (($topic_data['poll_length'] != 0 && $topic_data['poll_start'] + $topic_data['poll_length'] > time()) || $topic_data['poll_length'] == 0) && + $topic_data['topic_status'] != ITEM_LOCKED && + $topic_data['forum_status'] != ITEM_LOCKED && + (!count($cur_voted_id) || + ($auth->acl_get('f_votechg', $forum_id) && $topic_data['poll_vote_change']))) ? true : false; + $s_display_results = (!$s_can_vote || ($s_can_vote && count($cur_voted_id)) || $view == 'viewpoll') ? true : false; + + /* BEGIN SHOW_RESULTS */ + + if (($topic_data['poll_start'] + $topic_data['poll_length'] > time()) + && ($topic_data['poll_show_results'] == 0)) { + $s_display_results = false; + $s_can_display_results = false; + } else { + $s_can_display_results = !$s_display_results; + } + + /* END SHOW_RESULTS */ + /** + * Event to manipulate the poll data + * + * @event core.viewtopic_modify_poll_data + * @var array cur_voted_id Array with options' IDs current user has voted for + * @var int forum_id The topic's forum id + * @var array poll_info Array with the poll information + * @var bool s_can_vote Flag indicating if a user can vote + * @var bool s_display_results Flag indicating if results or poll options should be displayed + * @var int topic_id The id of the topic the user tries to access + * @var array topic_data All the information from the topic and forum tables for this topic + * @var string viewtopic_url URL to the topic page + * @var array vote_counts Array with the vote counts for every poll option + * @var array voted_id Array with updated options' IDs current user is voting for + * @since 3.1.5-RC1 + */ + $vars = array( + 'cur_voted_id', + 'forum_id', + 'poll_info', + 's_can_vote', + 's_display_results', + 'topic_id', + 'topic_data', + 'viewtopic_url', + 'vote_counts', + 'voted_id', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_data', compact($vars))); + + if ($update && $s_can_vote) + { + + if (!count($voted_id) || count($voted_id) > $topic_data['poll_max_options'] || in_array(VOTE_CONVERTED, $cur_voted_id) || !check_form_key('posting')) + { + $redirect_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&start=$start")); + + meta_refresh(5, $redirect_url); + if (!count($voted_id)) + { + $message = 'NO_VOTE_OPTION'; + } + else if (count($voted_id) > $topic_data['poll_max_options']) + { + $message = 'TOO_MANY_VOTE_OPTIONS'; + } + else if (in_array(VOTE_CONVERTED, $cur_voted_id)) + { + $message = 'VOTE_CONVERTED'; + } + else + { + $message = 'FORM_INVALID'; + } + + $message = $user->lang[$message] . '<br /><br />' . sprintf($user->lang['RETURN_TOPIC'], '<a href="' . $redirect_url . '">', '</a>'); + trigger_error($message); + } + + foreach ($voted_id as $option) + { + if (in_array($option, $cur_voted_id)) + { + continue; + } + + $sql = 'UPDATE ' . POLL_OPTIONS_TABLE . ' + SET poll_option_total = poll_option_total + 1 + WHERE poll_option_id = ' . (int) $option . ' + AND topic_id = ' . (int) $topic_id; + $db->sql_query($sql); + + $vote_counts[$option]++; + + if ($user->data['is_registered']) + { + $sql_ary = array( + 'topic_id' => (int) $topic_id, + 'poll_option_id' => (int) $option, + 'vote_user_id' => (int) $user->data['user_id'], + 'vote_user_ip' => (string) $user->ip, + ); + + $sql = 'INSERT INTO ' . POLL_VOTES_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + } + } + + foreach ($cur_voted_id as $option) + { + if (!in_array($option, $voted_id)) + { + $sql = 'UPDATE ' . POLL_OPTIONS_TABLE . ' + SET poll_option_total = poll_option_total - 1 + WHERE poll_option_id = ' . (int) $option . ' + AND topic_id = ' . (int) $topic_id; + $db->sql_query($sql); + + $vote_counts[$option]--; + + if ($user->data['is_registered']) + { + $sql = 'DELETE FROM ' . POLL_VOTES_TABLE . ' + WHERE topic_id = ' . (int) $topic_id . ' + AND poll_option_id = ' . (int) $option . ' + AND vote_user_id = ' . (int) $user->data['user_id']; + $db->sql_query($sql); + } + } + } + + if ($user->data['user_id'] == ANONYMOUS && !$user->data['is_bot']) + { + $user->set_cookie('poll_' . $topic_id, implode(',', $voted_id), time() + 31536000); + } + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET poll_last_vote = ' . time() . " + WHERE topic_id = $topic_id"; + //, topic_last_post_time = ' . time() . " -- for bumping topics with new votes, ignore for now + $db->sql_query($sql); + + $redirect_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($start == 0) ? '' : "&start=$start")); + $message = $user->lang['VOTE_SUBMITTED'] . '<br /><br />' . sprintf($user->lang['RETURN_TOPIC'], '<a href="' . $redirect_url . '">', '</a>'); + + if ($request->is_ajax()) + { + // Filter out invalid options + $valid_user_votes = array_intersect(array_keys($vote_counts), $voted_id); + + $data = array( + 'NO_VOTES' => $user->lang['NO_VOTES'], + 'success' => true, + 'user_votes' => array_flip($valid_user_votes), + 'vote_counts' => $vote_counts, + 'total_votes' => array_sum($vote_counts), + 'can_vote' => !count($valid_user_votes) || ($auth->acl_get('f_votechg', $forum_id) && $topic_data['poll_vote_change']), + ); + + /** + * Event to manipulate the poll data sent by AJAX response + * + * @event core.viewtopic_modify_poll_ajax_data + * @var array data JSON response data + * @var array valid_user_votes Valid user votes + * @var array vote_counts Vote counts + * @var int forum_id Forum ID + * @var array topic_data Topic data + * @var array poll_info Array with the poll information + * @since 3.2.4-RC1 + */ + $vars = array( + 'data', + 'valid_user_votes', + 'vote_counts', + 'forum_id', + 'topic_data', + 'poll_info', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_ajax_data', compact($vars))); + + $json_response = new \phpbb\json_response(); + $json_response->send($data); + } + + meta_refresh(5, $redirect_url); + trigger_error($message); + } + + $poll_total = 0; + $poll_most = 0; + foreach ($poll_info as $poll_option) + { + $poll_total += $poll_option['poll_option_total']; + $poll_most = ($poll_option['poll_option_total'] >= $poll_most) ? $poll_option['poll_option_total'] : $poll_most; + } + + $parse_flags = ($poll_info[0]['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + + for ($i = 0, $size = count($poll_info); $i < $size; $i++) + { + $poll_info[$i]['poll_option_text'] = generate_text_for_display($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield'], $parse_flags, true); + } + + $topic_data['poll_title'] = generate_text_for_display($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield'], $parse_flags, true); + + $poll_template_data = $poll_options_template_data = array(); + foreach ($poll_info as $poll_option) + { + $option_pct = ($poll_total > 0) ? $poll_option['poll_option_total'] / $poll_total : 0; + $option_pct_txt = sprintf("%.1d%%", round($option_pct * 100)); + $option_pct_rel = ($poll_most > 0) ? $poll_option['poll_option_total'] / $poll_most : 0; + $option_pct_rel_txt = sprintf("%.1d%%", round($option_pct_rel * 100)); + $option_most_votes = ($poll_option['poll_option_total'] > 0 && $poll_option['poll_option_total'] == $poll_most) ? true : false; + + $poll_options_template_data[] = array( + 'POLL_OPTION_ID' => $poll_option['poll_option_id'], + 'POLL_OPTION_CAPTION' => $poll_option['poll_option_text'], + 'POLL_OPTION_RESULT' => $poll_option['poll_option_total'], + 'POLL_OPTION_PERCENT' => $option_pct_txt, + 'POLL_OPTION_PERCENT_REL' => $option_pct_rel_txt, + 'POLL_OPTION_PCT' => round($option_pct * 100), + 'POLL_OPTION_WIDTH' => round($option_pct * 250), + 'POLL_OPTION_VOTED' => (in_array($poll_option['poll_option_id'], $cur_voted_id)) ? true : false, + 'POLL_OPTION_MOST_VOTES' => $option_most_votes, + ); + } + + $poll_end = $topic_data['poll_length'] + $topic_data['poll_start']; + + $poll_template_data = array( + 'POLL_QUESTION' => $topic_data['poll_title'], + 'TOTAL_VOTES' => $poll_total, + 'POLL_LEFT_CAP_IMG' => $user->img('poll_left'), + 'POLL_RIGHT_CAP_IMG'=> $user->img('poll_right'), + + 'L_MAX_VOTES' => $user->lang('MAX_OPTIONS_SELECT', (int) $topic_data['poll_max_options']), + 'L_POLL_LENGTH' => ($topic_data['poll_length']) ? sprintf($user->lang[($poll_end > time()) ? 'POLL_RUN_TILL' : 'POLL_ENDED_AT'], $user->format_date($poll_end)) : '', + + 'S_HAS_POLL' => true, + 'S_CAN_VOTE' => $s_can_vote, + 'S_DISPLAY_RESULTS' => $s_display_results, + /* BEGIN SHOW_RESULTS */ + 'S_CAN_DISPLAY_RESULTS' => $s_can_display_results, + /* END SHOW_RESULTS */ + 'S_IS_MULTI_CHOICE' => ($topic_data['poll_max_options'] > 1) ? true : false, + 'S_POLL_ACTION' => $viewtopic_url, + + 'U_VIEW_RESULTS' => $viewtopic_url . '&view=viewpoll', + ); + + /* BEGIN TOTAL_VOTERS */ + + $sql = 'SELECT COUNT(*) AS poll_total_voters FROM (SELECT DISTINCT vote_user_id FROM ' . POLL_VOTES_TABLE . ' WHERE topic_id = ' . (int) $topic_id . ') AS T1'; + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) $poll_total_voters = $row['poll_total_voters']; + $db->sql_freeresult($result); + $template->assign_vars(array('TOTAL_VOTERS' => $poll_total_voters)); + + /* END TOTAL_VOTERS */ + /** + * Event to add/modify poll template data + * + * @event core.viewtopic_modify_poll_template_data + * @var array cur_voted_id Array with options' IDs current user has voted for + * @var int poll_end The poll end time + * @var array poll_info Array with the poll information + * @var array poll_options_template_data Array with the poll options template data + * @var array poll_template_data Array with the common poll template data + * @var int poll_total Total poll votes count + * @var int poll_most Mostly voted option votes count + * @var array topic_data All the information from the topic and forum tables for this topic + * @var string viewtopic_url URL to the topic page + * @var array vote_counts Array with the vote counts for every poll option + * @var array voted_id Array with updated options' IDs current user is voting for + * @since 3.1.5-RC1 + */ + $vars = array( + 'cur_voted_id', + 'poll_end', + 'poll_info', + 'poll_options_template_data', + 'poll_template_data', + 'poll_total', + 'poll_most', + 'topic_data', + 'viewtopic_url', + 'vote_counts', + 'voted_id', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_template_data', compact($vars))); + + $template->assign_block_vars_array('poll_option', $poll_options_template_data); + + $template->assign_vars($poll_template_data); + + unset($poll_end, $poll_info, $poll_options_template_data, $poll_template_data, $voted_id); +} + +// If the user is trying to reach the second half of the topic, fetch it starting from the end +$store_reverse = false; +$sql_limit = $config['posts_per_page']; +$sql_sort_order = $direction = ''; + +if ($start > $total_posts / 2) +{ + $store_reverse = true; + + // Select the sort order + $direction = (($sort_dir == 'd') ? 'ASC' : 'DESC'); + + $sql_limit = $pagination->reverse_limit($start, $sql_limit, $total_posts); + $sql_start = $pagination->reverse_start($start, $sql_limit, $total_posts); +} +else +{ + // Select the sort order + $direction = (($sort_dir == 'd') ? 'DESC' : 'ASC'); + $sql_start = $start; +} + +if (is_array($sort_by_sql[$sort_key])) +{ + $sql_sort_order = implode(' ' . $direction . ', ', $sort_by_sql[$sort_key]) . ' ' . $direction; +} +else +{ + $sql_sort_order = $sort_by_sql[$sort_key] . ' ' . $direction; +} + +// Container for user details, only process once +$post_list = $user_cache = $id_cache = $attachments = $attach_list = $rowset = $update_count = $post_edit_list = $post_delete_list = array(); +$has_unapproved_attachments = $has_approved_attachments = $display_notice = false; +$i = $i_total = 0; + +// Go ahead and pull all data for this topic +$sql = 'SELECT p.post_id + FROM ' . POSTS_TABLE . ' p' . (($join_user_sql[$sort_key]) ? ', ' . USERS_TABLE . ' u': '') . " + WHERE p.topic_id = $topic_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.') . " + " . (($join_user_sql[$sort_key]) ? 'AND u.user_id = p.poster_id': '') . " + $limit_posts_time + ORDER BY $sql_sort_order"; + +/** +* Event to modify the SQL query that gets post_list +* +* @event core.viewtopic_modify_post_list_sql +* @var string sql The SQL query to generate the post_list +* @var int sql_limit The number of posts the query fetches +* @var int sql_start The index the query starts to fetch from +* @var string sort_key Key the posts are sorted by +* @var string sort_days Display posts of previous x days +* @var int forum_id Forum ID +* @since 3.2.4-RC1 +*/ +$vars = array( + 'sql', + 'sql_limit', + 'sql_start', + 'sort_key', + 'sort_days', + 'forum_id', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_list_sql', compact($vars))); + +$result = $db->sql_query_limit($sql, $sql_limit, $sql_start); + +$i = ($store_reverse) ? $sql_limit - 1 : 0; +while ($row = $db->sql_fetchrow($result)) +{ + $post_list[$i] = (int) $row['post_id']; + ($store_reverse) ? $i-- : $i++; +} +$db->sql_freeresult($result); + +if (!count($post_list)) +{ + if ($sort_days) + { + trigger_error('NO_POSTS_TIME_FRAME'); + } + else + { + trigger_error('NO_TOPIC'); + } +} + +// Holding maximum post time for marking topic read +// We need to grab it because we do reverse ordering sometimes +$max_post_time = 0; + +$sql_ary = array( + 'SELECT' => 'u.*, z.friend, z.foe, p.*', + + 'FROM' => array( + USERS_TABLE => 'u', + POSTS_TABLE => 'p', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(ZEBRA_TABLE => 'z'), + 'ON' => 'z.user_id = ' . $user->data['user_id'] . ' AND z.zebra_id = p.poster_id', + ), + ), + + 'WHERE' => $db->sql_in_set('p.post_id', $post_list) . ' + AND u.user_id = p.poster_id', +); + +/** +* Event to modify the SQL query before the post and poster data is retrieved +* +* @event core.viewtopic_get_post_data +* @var int forum_id Forum ID +* @var int topic_id Topic ID +* @var array topic_data Array with topic data +* @var array post_list Array with post_ids we are going to retrieve +* @var int sort_days Display posts of previous x days +* @var string sort_key Key the posts are sorted by +* @var string sort_dir Direction the posts are sorted by +* @var int start Pagination information +* @var array sql_ary The SQL array to get the data of posts and posters +* @since 3.1.0-a1 +* @changed 3.1.0-a2 Added vars forum_id, topic_id, topic_data, post_list, sort_days, sort_key, sort_dir, start +*/ +$vars = array( + 'forum_id', + 'topic_id', + 'topic_data', + 'post_list', + 'sort_days', + 'sort_key', + 'sort_dir', + 'start', + 'sql_ary', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_get_post_data', compact($vars))); + +$sql = $db->sql_build_query('SELECT', $sql_ary); +$result = $db->sql_query($sql); + +$now = $user->create_datetime(); +$now = phpbb_gmgetdate($now->getTimestamp() + $now->getOffset()); + +// Posts are stored in the $rowset array while $attach_list, $user_cache +// and the global bbcode_bitfield are built +while ($row = $db->sql_fetchrow($result)) +{ + // Set max_post_time + if ($row['post_time'] > $max_post_time) + { + $max_post_time = $row['post_time']; + } + + $poster_id = (int) $row['poster_id']; + + // Does post have an attachment? If so, add it to the list + if ($row['post_attachment'] && $config['allow_attachments']) + { + $attach_list[] = (int) $row['post_id']; + + if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) + { + $has_unapproved_attachments = true; + } + else if ($row['post_visibility'] == ITEM_APPROVED) + { + $has_approved_attachments = true; + } + } + + $rowset_data = array( + 'hide_post' => (($row['foe'] || $row['post_visibility'] == ITEM_DELETED) && ($view != 'show' || $post_id != $row['post_id'])) ? true : false, + + 'post_id' => $row['post_id'], + 'post_time' => $row['post_time'], + 'user_id' => $row['user_id'], + 'username' => $row['username'], + 'user_colour' => $row['user_colour'], + 'topic_id' => $row['topic_id'], + 'forum_id' => $row['forum_id'], + 'post_subject' => $row['post_subject'], + 'post_edit_count' => $row['post_edit_count'], + 'post_edit_time' => $row['post_edit_time'], + 'post_edit_reason' => $row['post_edit_reason'], + 'post_edit_user' => $row['post_edit_user'], + 'post_edit_locked' => $row['post_edit_locked'], + 'post_delete_time' => $row['post_delete_time'], + 'post_delete_reason'=> $row['post_delete_reason'], + 'post_delete_user' => $row['post_delete_user'], + + // Make sure the icon actually exists + 'icon_id' => (isset($icons[$row['icon_id']]['img'], $icons[$row['icon_id']]['height'], $icons[$row['icon_id']]['width'])) ? $row['icon_id'] : 0, + 'post_attachment' => $row['post_attachment'], + 'post_visibility' => $row['post_visibility'], + 'post_reported' => $row['post_reported'], + 'post_username' => $row['post_username'], + 'post_text' => $row['post_text'], + 'bbcode_uid' => $row['bbcode_uid'], + 'bbcode_bitfield' => $row['bbcode_bitfield'], + 'enable_smilies' => $row['enable_smilies'], + 'enable_sig' => $row['enable_sig'], + 'friend' => $row['friend'], + 'foe' => $row['foe'], + ); + + /** + * Modify the post rowset containing data to be displayed with posts + * + * @event core.viewtopic_post_rowset_data + * @var array rowset_data Array with the rowset data for this post + * @var array row Array with original user and post data + * @since 3.1.0-a1 + */ + $vars = array('rowset_data', 'row'); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_post_rowset_data', compact($vars))); + + $rowset[$row['post_id']] = $rowset_data; + + // Cache various user specific data ... so we don't have to recompute + // this each time the same user appears on this page + if (!isset($user_cache[$poster_id])) + { + if ($poster_id == ANONYMOUS) + { + $user_cache_data = array( + 'user_type' => USER_IGNORE, + 'joined' => '', + 'posts' => '', + + 'sig' => '', + 'sig_bbcode_uid' => '', + 'sig_bbcode_bitfield' => '', + + 'online' => false, + 'avatar' => ($user->optionget('viewavatars')) ? phpbb_get_user_avatar($row) : '', + 'rank_title' => '', + 'rank_image' => '', + 'rank_image_src' => '', + 'pm' => '', + 'email' => '', + 'jabber' => '', + 'search' => '', + 'age' => '', + + 'username' => $row['username'], + 'user_colour' => $row['user_colour'], + 'contact_user' => '', + + 'warnings' => 0, + 'allow_pm' => 0, + ); + + /** + * Modify the guest user's data displayed with the posts + * + * @event core.viewtopic_cache_guest_data + * @var array user_cache_data Array with the user's data + * @var int poster_id Poster's user id + * @var array row Array with original user and post data + * @since 3.1.0-a1 + */ + $vars = array('user_cache_data', 'poster_id', 'row'); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_cache_guest_data', compact($vars))); + + $user_cache[$poster_id] = $user_cache_data; + + $user_rank_data = phpbb_get_user_rank($row, false); + $user_cache[$poster_id]['rank_title'] = $user_rank_data['title']; + $user_cache[$poster_id]['rank_image'] = $user_rank_data['img']; + $user_cache[$poster_id]['rank_image_src'] = $user_rank_data['img_src']; + } + else + { + $user_sig = ''; + + // We add the signature to every posters entry because enable_sig is post dependent + if ($row['user_sig'] && $config['allow_sig'] && $user->optionget('viewsigs')) + { + $user_sig = $row['user_sig']; + } + + $id_cache[] = $poster_id; + + $user_cache_data = array( + 'user_type' => $row['user_type'], + 'user_inactive_reason' => $row['user_inactive_reason'], + + 'joined' => $user->format_date($row['user_regdate']), + 'posts' => $row['user_posts'], + 'warnings' => (isset($row['user_warnings'])) ? $row['user_warnings'] : 0, + + 'sig' => $user_sig, + 'sig_bbcode_uid' => (!empty($row['user_sig_bbcode_uid'])) ? $row['user_sig_bbcode_uid'] : '', + 'sig_bbcode_bitfield' => (!empty($row['user_sig_bbcode_bitfield'])) ? $row['user_sig_bbcode_bitfield'] : '', + + 'viewonline' => $row['user_allow_viewonline'], + 'allow_pm' => $row['user_allow_pm'], + + 'avatar' => ($user->optionget('viewavatars')) ? phpbb_get_user_avatar($row) : '', + 'age' => '', + + 'rank_title' => '', + 'rank_image' => '', + 'rank_image_src' => '', + + 'username' => $row['username'], + 'user_colour' => $row['user_colour'], + 'contact_user' => $user->lang('CONTACT_USER', get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['username'])), + + 'online' => false, + 'jabber' => ($config['jab_enable'] && $row['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&action=jabber&u=$poster_id") : '', + 'search' => ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$poster_id&sr=posts") : '', + + 'author_full' => get_username_string('full', $poster_id, $row['username'], $row['user_colour']), + 'author_colour' => get_username_string('colour', $poster_id, $row['username'], $row['user_colour']), + 'author_username' => get_username_string('username', $poster_id, $row['username'], $row['user_colour']), + 'author_profile' => get_username_string('profile', $poster_id, $row['username'], $row['user_colour']), + ); + + /** + * Modify the users' data displayed with their posts + * + * @event core.viewtopic_cache_user_data + * @var array user_cache_data Array with the user's data + * @var int poster_id Poster's user id + * @var array row Array with original user and post data + * @since 3.1.0-a1 + */ + $vars = array('user_cache_data', 'poster_id', 'row'); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_cache_user_data', compact($vars))); + + $user_cache[$poster_id] = $user_cache_data; + + $user_rank_data = phpbb_get_user_rank($row, $row['user_posts']); + $user_cache[$poster_id]['rank_title'] = $user_rank_data['title']; + $user_cache[$poster_id]['rank_image'] = $user_rank_data['img']; + $user_cache[$poster_id]['rank_image_src'] = $user_rank_data['img_src']; + + if ((!empty($row['user_allow_viewemail']) && $auth->acl_get('u_sendemail')) || $auth->acl_get('a_email')) + { + $user_cache[$poster_id]['email'] = ($config['board_email_form'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=email&u=$poster_id") : (($config['board_hide_emails'] && !$auth->acl_get('a_email')) ? '' : 'mailto:' . $row['user_email']); + } + else + { + $user_cache[$poster_id]['email'] = ''; + } + + if ($config['allow_birthdays'] && !empty($row['user_birthday'])) + { + list($bday_day, $bday_month, $bday_year) = array_map('intval', explode('-', $row['user_birthday'])); + + if ($bday_year) + { + $diff = $now['mon'] - $bday_month; + if ($diff == 0) + { + $diff = ($now['mday'] - $bday_day < 0) ? 1 : 0; + } + else + { + $diff = ($diff < 0) ? 1 : 0; + } + + $user_cache[$poster_id]['age'] = (int) ($now['year'] - $bday_year - $diff); + } + } + } + } +} +$db->sql_freeresult($result); + +// Load custom profile fields +if ($config['load_cpf_viewtopic']) +{ + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + + // Grab all profile fields from users in id cache for later use - similar to the poster cache + $profile_fields_tmp = $cp->grab_profile_fields_data($id_cache); + + // filter out fields not to be displayed on viewtopic. Yes, it's a hack, but this shouldn't break any MODs. + $profile_fields_cache = array(); + foreach ($profile_fields_tmp as $profile_user_id => $profile_fields) + { + $profile_fields_cache[$profile_user_id] = array(); + foreach ($profile_fields as $used_ident => $profile_field) + { + if ($profile_field['data']['field_show_on_vt']) + { + $profile_fields_cache[$profile_user_id][$used_ident] = $profile_field; + } + } + } + unset($profile_fields_tmp); +} + +// Generate online information for user +if ($config['load_onlinetrack'] && count($id_cache)) +{ + $sql = 'SELECT session_user_id, MAX(session_time) as online_time, MIN(session_viewonline) AS viewonline + FROM ' . SESSIONS_TABLE . ' + WHERE ' . $db->sql_in_set('session_user_id', $id_cache) . ' + GROUP BY session_user_id'; + $result = $db->sql_query($sql); + + $update_time = $config['load_online_time'] * 60; + while ($row = $db->sql_fetchrow($result)) + { + $user_cache[$row['session_user_id']]['online'] = (time() - $update_time < $row['online_time'] && (($row['viewonline']) || $auth->acl_get('u_viewonline'))) ? true : false; + } + $db->sql_freeresult($result); +} +unset($id_cache); + +// Pull attachment data +if (count($attach_list)) +{ + if ($auth->acl_get('u_download') && $auth->acl_get('f_download', $forum_id)) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_msg_id', $attach_list) . ' + AND in_message = 0 + ORDER BY attach_id DESC, post_msg_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[$row['post_msg_id']][] = $row; + } + $db->sql_freeresult($result); + + // No attachments exist, but post table thinks they do so go ahead and reset post_attach flags + if (!count($attachments)) + { + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_attachment = 0 + WHERE ' . $db->sql_in_set('post_id', $attach_list); + $db->sql_query($sql); + + // We need to update the topic indicator too if the complete topic is now without an attachment + if (count($rowset) != $total_posts) + { + // Not all posts are displayed so we query the db to find if there's any attachment for this topic + $sql = 'SELECT a.post_msg_id as post_id + FROM ' . ATTACHMENTS_TABLE . ' a, ' . POSTS_TABLE . " p + WHERE p.topic_id = $topic_id + AND p.post_visibility = " . ITEM_APPROVED . ' + AND p.topic_id = a.topic_id'; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_attachment = 0 + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + } + } + else + { + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_attachment = 0 + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + } + } + else if ($has_approved_attachments && !$topic_data['topic_attachment']) + { + // Topic has approved attachments but its flag is wrong + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_attachment = 1 + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + + $topic_data['topic_attachment'] = 1; + } + else if ($has_unapproved_attachments && !$topic_data['topic_attachment']) + { + // Topic has only unapproved attachments but we have the right to see and download them + $topic_data['topic_attachment'] = 1; + } + } + else + { + $display_notice = true; + } +} + +if ($config['enable_accurate_pm_button']) +{ + // Get the list of users who can receive private messages + $can_receive_pm_list = $auth->acl_get_list(array_keys($user_cache), 'u_readpm'); + $can_receive_pm_list = (empty($can_receive_pm_list) || !isset($can_receive_pm_list[0]['u_readpm'])) ? array() : $can_receive_pm_list[0]['u_readpm']; + + // Get the list of permanently banned users + $permanently_banned_users = phpbb_get_banned_user_ids(array_keys($user_cache), false); +} +else +{ + $can_receive_pm_list = array_keys($user_cache); + $permanently_banned_users = []; +} + +$i_total = count($rowset) - 1; +$prev_post_id = ''; + +$template->assign_vars(array( + 'S_HAS_ATTACHMENTS' => $topic_data['topic_attachment'], + 'S_NUM_POSTS' => count($post_list)) +); + +/** +* Event to modify the post, poster and attachment data before assigning the posts +* +* @event core.viewtopic_modify_post_data +* @var int forum_id Forum ID +* @var int topic_id Topic ID +* @var array topic_data Array with topic data +* @var array post_list Array with post_ids we are going to display +* @var array rowset Array with post_id => post data +* @var array user_cache Array with prepared user data +* @var int start Pagination information +* @var int sort_days Display posts of previous x days +* @var string sort_key Key the posts are sorted by +* @var string sort_dir Direction the posts are sorted by +* @var bool display_notice Shall we display a notice instead of attachments +* @var bool has_approved_attachments Does the topic have approved attachments +* @var array attachments List of attachments post_id => array of attachments +* @var array permanently_banned_users List of permanently banned users +* @var array can_receive_pm_list Array with posters that can receive pms +* @since 3.1.0-RC3 +*/ +$vars = array( + 'forum_id', + 'topic_id', + 'topic_data', + 'post_list', + 'rowset', + 'user_cache', + 'sort_days', + 'sort_key', + 'sort_dir', + 'start', + 'permanently_banned_users', + 'can_receive_pm_list', + 'display_notice', + 'has_approved_attachments', + 'attachments', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_data', compact($vars))); + +// Output the posts +$first_unread = $post_unread = false; +for ($i = 0, $end = count($post_list); $i < $end; ++$i) +{ + // A non-existing rowset only happens if there was no user present for the entered poster_id + // This could be a broken posts table. + if (!isset($rowset[$post_list[$i]])) + { + continue; + } + + $row = $rowset[$post_list[$i]]; + $poster_id = $row['user_id']; + + // End signature parsing, only if needed + if ($user_cache[$poster_id]['sig'] && $row['enable_sig'] && empty($user_cache[$poster_id]['sig_parsed'])) + { + $parse_flags = ($user_cache[$poster_id]['sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $user_cache[$poster_id]['sig_bbcode_bitfield'], $parse_flags, true); + $user_cache[$poster_id]['sig_parsed'] = true; + } + + // Parse the message and subject + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, true); + + if (!empty($attachments[$row['post_id']])) + { + parse_attachments($forum_id, $message, $attachments[$row['post_id']], $update_count); + } + + // Replace naughty words such as farty pants + $row['post_subject'] = censor_text($row['post_subject']); + + // Highlight active words (primarily for search) + if ($highlight_match) + { + $message = preg_replace('#(?!<.*)(?<!\w)(' . $highlight_match . ')(?!\w|[^<>]*(?:</s(?:cript|tyle))?>)#is', '<span class="posthilit">\1</span>', $message); + $row['post_subject'] = preg_replace('#(?!<.*)(?<!\w)(' . $highlight_match . ')(?!\w|[^<>]*(?:</s(?:cript|tyle))?>)#is', '<span class="posthilit">\1</span>', $row['post_subject']); + } + + // Editing information + if (($row['post_edit_count'] && $config['display_last_edited']) || $row['post_edit_reason']) + { + // Get usernames for all following posts if not already stored + if (!count($post_edit_list) && ($row['post_edit_reason'] || ($row['post_edit_user'] && !isset($user_cache[$row['post_edit_user']])))) + { + // Remove all post_ids already parsed (we do not have to check them) + $post_storage_list = (!$store_reverse) ? array_slice($post_list, $i) : array_slice(array_reverse($post_list), $i); + + $sql = 'SELECT DISTINCT u.user_id, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_storage_list) . ' + AND p.post_edit_count <> 0 + AND p.post_edit_user <> 0 + AND p.post_edit_user = u.user_id'; + $result2 = $db->sql_query($sql); + while ($user_edit_row = $db->sql_fetchrow($result2)) + { + $post_edit_list[$user_edit_row['user_id']] = $user_edit_row; + } + $db->sql_freeresult($result2); + + unset($post_storage_list); + } + + if ($row['post_edit_reason']) + { + // User having edited the post also being the post author? + if (!$row['post_edit_user'] || $row['post_edit_user'] == $poster_id) + { + $display_username = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']); + } + else + { + $display_username = get_username_string('full', $row['post_edit_user'], $post_edit_list[$row['post_edit_user']]['username'], $post_edit_list[$row['post_edit_user']]['user_colour']); + } + + $l_edited_by = $user->lang('EDITED_TIMES_TOTAL', (int) $row['post_edit_count'], $display_username, $user->format_date($row['post_edit_time'], false, true)); + } + else + { + if ($row['post_edit_user'] && !isset($user_cache[$row['post_edit_user']])) + { + $user_cache[$row['post_edit_user']] = $post_edit_list[$row['post_edit_user']]; + } + + // User having edited the post also being the post author? + if (!$row['post_edit_user'] || $row['post_edit_user'] == $poster_id) + { + $display_username = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']); + } + else + { + $display_username = get_username_string('full', $row['post_edit_user'], $user_cache[$row['post_edit_user']]['username'], $user_cache[$row['post_edit_user']]['user_colour']); + } + + $l_edited_by = $user->lang('EDITED_TIMES_TOTAL', (int) $row['post_edit_count'], $display_username, $user->format_date($row['post_edit_time'], false, true)); + } + } + else + { + $l_edited_by = ''; + } + + // Deleting information + if ($row['post_visibility'] == ITEM_DELETED && $row['post_delete_user']) + { + // Get usernames for all following posts if not already stored + if (!count($post_delete_list) && ($row['post_delete_reason'] || ($row['post_delete_user'] && !isset($user_cache[$row['post_delete_user']])))) + { + // Remove all post_ids already parsed (we do not have to check them) + $post_storage_list = (!$store_reverse) ? array_slice($post_list, $i) : array_slice(array_reverse($post_list), $i); + + $sql = 'SELECT DISTINCT u.user_id, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_storage_list) . ' + AND p.post_delete_user <> 0 + AND p.post_delete_user = u.user_id'; + $result2 = $db->sql_query($sql); + while ($user_delete_row = $db->sql_fetchrow($result2)) + { + $post_delete_list[$user_delete_row['user_id']] = $user_delete_row; + } + $db->sql_freeresult($result2); + + unset($post_storage_list); + } + + if ($row['post_delete_user'] && !isset($user_cache[$row['post_delete_user']])) + { + $user_cache[$row['post_delete_user']] = $post_delete_list[$row['post_delete_user']]; + } + + $display_postername = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']); + + // User having deleted the post also being the post author? + if (!$row['post_delete_user'] || $row['post_delete_user'] == $poster_id) + { + $display_username = $display_postername; + } + else + { + $display_username = get_username_string('full', $row['post_delete_user'], $user_cache[$row['post_delete_user']]['username'], $user_cache[$row['post_delete_user']]['user_colour']); + } + + if ($row['post_delete_reason']) + { + $l_deleted_message = $user->lang('POST_DELETED_BY_REASON', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true), $row['post_delete_reason']); + } + else + { + $l_deleted_message = $user->lang('POST_DELETED_BY', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true)); + } + $l_deleted_by = $user->lang('DELETED_INFORMATION', $display_username, $user->format_date($row['post_delete_time'], false, true)); + } + else + { + $l_deleted_by = $l_deleted_message = ''; + } + + // Bump information + if ($topic_data['topic_bumped'] && $row['post_id'] == $topic_data['topic_last_post_id'] && isset($user_cache[$topic_data['topic_bumper']]) ) + { + // It is safe to grab the username from the user cache array, we are at the last + // post and only the topic poster and last poster are allowed to bump. + // Admins and mods are bound to the above rules too... + $l_bumped_by = sprintf($user->lang['BUMPED_BY'], $user_cache[$topic_data['topic_bumper']]['username'], $user->format_date($topic_data['topic_last_post_time'], false, true)); + } + else + { + $l_bumped_by = ''; + } + + $cp_row = array(); + + // + if ($config['load_cpf_viewtopic']) + { + $cp_row = (isset($profile_fields_cache[$poster_id])) ? $cp->generate_profile_fields_template_data($profile_fields_cache[$poster_id]) : array(); + } + + $post_unread = (isset($topic_tracking_info[$topic_id]) && $row['post_time'] > $topic_tracking_info[$topic_id]) ? true : false; + + $s_first_unread = false; + if (!$first_unread && $post_unread) + { + $s_first_unread = $first_unread = true; + } + + $force_edit_allowed = $force_delete_allowed = $force_softdelete_allowed = false; + + $s_cannot_edit = !$auth->acl_get('f_edit', $forum_id) || $user->data['user_id'] != $poster_id; + $s_cannot_edit_time = $config['edit_time'] && $row['post_time'] <= time() - ($config['edit_time'] * 60); + $s_cannot_edit_locked = ($topic_data['topic_status'] == ITEM_LOCKED && !$auth->acl_get('m_lock', $forum_id)) || $row['post_edit_locked']; + + $s_cannot_delete = $user->data['user_id'] != $poster_id || ( + !$auth->acl_get('f_delete', $forum_id) && + (!$auth->acl_get('f_softdelete', $forum_id) || $row['post_visibility'] == ITEM_DELETED) + ); + $s_cannot_delete_lastpost = $topic_data['topic_last_post_id'] != $row['post_id']; + $s_cannot_delete_time = $config['delete_time'] && $row['post_time'] <= time() - ($config['delete_time'] * 60); + // we do not want to allow removal of the last post if a moderator locked it! + $s_cannot_delete_locked = $topic_data['topic_status'] == ITEM_LOCKED || $row['post_edit_locked']; + + /** + * This event allows you to modify the conditions for the "can edit post" and "can delete post" checks + * + * @event core.viewtopic_modify_post_action_conditions + * @var array row Array with post data + * @var array topic_data Array with topic data + * @var bool force_edit_allowed Allow the user to edit the post (all permissions and conditions are ignored) + * @var bool s_cannot_edit User can not edit the post because it's not his + * @var bool s_cannot_edit_locked User can not edit the post because it's locked + * @var bool s_cannot_edit_time User can not edit the post because edit_time has passed + * @var bool force_delete_allowed Allow the user to delete the post (all permissions and conditions are ignored) + * @var bool s_cannot_delete User can not delete the post because it's not his + * @var bool s_cannot_delete_lastpost User can not delete the post because it's not the last post of the topic + * @var bool s_cannot_delete_locked User can not delete the post because it's locked + * @var bool s_cannot_delete_time User can not delete the post because edit_time has passed + * @var bool force_softdelete_allowed Allow the user to Ñ‹oftdelete the post (all permissions and conditions are ignored) + * @since 3.1.0-b4 + * @changed 3.1.11-RC1 Added force_softdelete_allowed var + */ + $vars = array( + 'row', + 'topic_data', + 'force_edit_allowed', + 's_cannot_edit', + 's_cannot_edit_locked', + 's_cannot_edit_time', + 'force_delete_allowed', + 's_cannot_delete', + 's_cannot_delete_lastpost', + 's_cannot_delete_locked', + 's_cannot_delete_time', + 'force_softdelete_allowed', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_action_conditions', compact($vars))); + + $edit_allowed = $force_edit_allowed || ($user->data['is_registered'] && ($auth->acl_get('m_edit', $forum_id) || ( + !$s_cannot_edit && + !$s_cannot_edit_time && + !$s_cannot_edit_locked + ))); + + $quote_allowed = $auth->acl_get('m_edit', $forum_id) || ($topic_data['topic_status'] != ITEM_LOCKED && + ($user->data['user_id'] == ANONYMOUS || $auth->acl_get('f_reply', $forum_id)) + ); + + // Only display the quote button if the post is quotable. Posts not approved are not quotable. + $quote_allowed = ($quote_allowed && $row['post_visibility'] == ITEM_APPROVED) ? true : false; + + $delete_allowed = $force_delete_allowed || ($user->data['is_registered'] && ( + ($auth->acl_get('m_delete', $forum_id) || ($auth->acl_get('m_softdelete', $forum_id) && $row['post_visibility'] != ITEM_DELETED)) || + (!$s_cannot_delete && !$s_cannot_delete_lastpost && !$s_cannot_delete_time && !$s_cannot_delete_locked) + )); + + $softdelete_allowed = $force_softdelete_allowed || (($auth->acl_get('m_softdelete', $forum_id) || + ($auth->acl_get('f_softdelete', $forum_id) && $user->data['user_id'] == $poster_id)) && ($row['post_visibility'] != ITEM_DELETED)); + + $permanent_delete_allowed = $force_delete_allowed || ($auth->acl_get('m_delete', $forum_id) || + ($auth->acl_get('f_delete', $forum_id) && $user->data['user_id'] == $poster_id)); + + // Can this user receive a Private Message? + $can_receive_pm = ( + // They must be a "normal" user + $user_cache[$poster_id]['user_type'] != USER_IGNORE && + + // They must not be deactivated by the administrator + ($user_cache[$poster_id]['user_type'] != USER_INACTIVE || $user_cache[$poster_id]['user_inactive_reason'] != INACTIVE_MANUAL) && + + // They must be able to read PMs + in_array($poster_id, $can_receive_pm_list) && + + // They must not be permanently banned + !in_array($poster_id, $permanently_banned_users) && + + // They must allow users to contact via PM + (($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) || $user_cache[$poster_id]['allow_pm']) + ); + + $u_pm = ''; + + if ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && $can_receive_pm) + { + $u_pm = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&action=quotepost&p=' . $row['post_id']); + } + + // + $post_row = array( + 'POST_AUTHOR_FULL' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_full'] : get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR_COLOUR' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_colour'] : get_username_string('colour', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_username'] : get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'U_POST_AUTHOR' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_profile'] : get_username_string('profile', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + + 'RANK_TITLE' => $user_cache[$poster_id]['rank_title'], + 'RANK_IMG' => $user_cache[$poster_id]['rank_image'], + 'RANK_IMG_SRC' => $user_cache[$poster_id]['rank_image_src'], + 'POSTER_JOINED' => $user_cache[$poster_id]['joined'], + 'POSTER_POSTS' => $user_cache[$poster_id]['posts'], + 'POSTER_AVATAR' => $user_cache[$poster_id]['avatar'], + 'POSTER_WARNINGS' => $auth->acl_get('m_warn') ? $user_cache[$poster_id]['warnings'] : '', + 'POSTER_AGE' => $user_cache[$poster_id]['age'], + 'CONTACT_USER' => $user_cache[$poster_id]['contact_user'], + + 'POST_DATE' => $user->format_date($row['post_time'], false, ($view == 'print') ? true : false), + 'POST_DATE_RFC3339' => gmdate(DATE_RFC3339, $row['post_time']), + 'POST_SUBJECT' => $row['post_subject'], + 'MESSAGE' => $message, + 'SIGNATURE' => ($row['enable_sig']) ? $user_cache[$poster_id]['sig'] : '', + 'EDITED_MESSAGE' => $l_edited_by, + 'EDIT_REASON' => $row['post_edit_reason'], + 'DELETED_MESSAGE' => $l_deleted_by, + 'DELETE_REASON' => $row['post_delete_reason'], + 'BUMPED_MESSAGE' => $l_bumped_by, + + 'MINI_POST_IMG' => ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'), + 'POST_ICON_IMG' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['img'] : '', + 'POST_ICON_IMG_WIDTH' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['width'] : '', + 'POST_ICON_IMG_HEIGHT' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['height'] : '', + 'POST_ICON_IMG_ALT' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['alt'] : '', + 'ONLINE_IMG' => ($poster_id == ANONYMOUS || !$config['load_onlinetrack']) ? '' : (($user_cache[$poster_id]['online']) ? $user->img('icon_user_online', 'ONLINE') : $user->img('icon_user_offline', 'OFFLINE')), + 'S_ONLINE' => ($poster_id == ANONYMOUS || !$config['load_onlinetrack']) ? false : (($user_cache[$poster_id]['online']) ? true : false), + + 'U_EDIT' => ($edit_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=edit&p={$row['post_id']}") : '', + 'U_QUOTE' => ($quote_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=quote&p={$row['post_id']}") : '', + 'U_INFO' => ($auth->acl_get('m_info', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=post_details&p=" . $row['post_id'], true, $user->session_id) : '', + 'U_DELETE' => ($delete_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=' . (($softdelete_allowed) ? 'soft_delete' : 'delete') . "&p={$row['post_id']}") : '', + + 'U_SEARCH' => $user_cache[$poster_id]['search'], + 'U_PM' => $u_pm, + 'U_EMAIL' => $user_cache[$poster_id]['email'], + 'U_JABBER' => $user_cache[$poster_id]['jabber'], + + 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p={$row['post_id']}&redirect=" . urlencode(str_replace('&', '&', $viewtopic_url . '&p=' . $row['post_id'] . '#p' . $row['post_id']))), + 'U_REPORT' => ($auth->acl_get('f_report', $forum_id)) ? $phpbb_container->get('controller.helper')->route('phpbb_report_post_controller', array('id' => $row['post_id'])) : '', + 'U_MCP_REPORT' => ($auth->acl_get('m_report', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&p=' . $row['post_id'], true, $user->session_id) : '', + 'U_MCP_APPROVE' => ($auth->acl_get('m_approve', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&p=' . $row['post_id'], true, $user->session_id) : '', + 'U_MCP_RESTORE' => ($auth->acl_get('m_approve', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=' . (($topic_data['topic_visibility'] != ITEM_DELETED) ? 'deleted_posts' : 'deleted_topics') . '&p=' . $row['post_id'], true, $user->session_id) : '', + 'U_MINI_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['post_id']) . '#p' . $row['post_id'], + 'U_MINI_POST_VIEW' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['post_id']) . '&view=show#p' . $row['post_id'], + 'U_NEXT_POST_ID' => ($i < $i_total && isset($rowset[$post_list[$i + 1]])) ? $rowset[$post_list[$i + 1]]['post_id'] : '', + 'U_PREV_POST_ID' => $prev_post_id, + 'U_NOTES' => ($auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $poster_id, true, $user->session_id) : '', + 'U_WARN' => ($auth->acl_get('m_warn') && $poster_id != $user->data['user_id'] && $poster_id != ANONYMOUS) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_post&p=' . $row['post_id'], true, $user->session_id) : '', + + 'POST_ID' => $row['post_id'], + 'POST_NUMBER' => $i + $start + 1, + 'POSTER_ID' => $poster_id, + 'MINI_POST' => ($post_unread) ? $user->lang['UNREAD_POST'] : $user->lang['POST'], + + + 'S_HAS_ATTACHMENTS' => (!empty($attachments[$row['post_id']])) ? true : false, + 'S_MULTIPLE_ATTACHMENTS' => !empty($attachments[$row['post_id']]) && count($attachments[$row['post_id']]) > 1, + 'S_POST_UNAPPROVED' => ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) ? true : false, + 'S_CAN_APPROVE' => $auth->acl_get('m_approve', $forum_id), + 'S_POST_DELETED' => ($row['post_visibility'] == ITEM_DELETED) ? true : false, + 'L_POST_DELETED_MESSAGE' => $l_deleted_message, + 'S_POST_REPORTED' => ($row['post_reported'] && $auth->acl_get('m_report', $forum_id)) ? true : false, + 'S_DISPLAY_NOTICE' => $display_notice && $row['post_attachment'], + 'S_FRIEND' => ($row['friend']) ? true : false, + 'S_UNREAD_POST' => $post_unread, + 'S_FIRST_UNREAD' => $s_first_unread, + 'S_CUSTOM_FIELDS' => (isset($cp_row['row']) && count($cp_row['row'])) ? true : false, + 'S_TOPIC_POSTER' => ($topic_data['topic_poster'] == $poster_id) ? true : false, + 'S_FIRST_POST' => ($topic_data['topic_first_post_id'] == $row['post_id']) ? true : false, + + 'S_IGNORE_POST' => ($row['foe']) ? true : false, + 'L_IGNORE_POST' => ($row['foe']) ? sprintf($user->lang['POST_BY_FOE'], get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username'])) : '', + 'S_POST_HIDDEN' => $row['hide_post'], + 'S_DELETE_PERMANENT' => $permanent_delete_allowed, + ); + + $user_poster_data = $user_cache[$poster_id]; + + $current_row_number = $i; + + /** + * Modify the posts template block + * + * @event core.viewtopic_modify_post_row + * @var int start Start item of this page + * @var int current_row_number Number of the post on this page + * @var int end Number of posts on this page + * @var int total_posts Total posts count + * @var int poster_id Post author id + * @var array row Array with original post and user data + * @var array cp_row Custom profile field data of the poster + * @var array attachments List of attachments + * @var array user_poster_data Poster's data from user cache + * @var array post_row Template block array of the post + * @var array topic_data Array with topic data + * @var array user_cache Array with cached user data + * @var array post_edit_list Array with post edited list + * @since 3.1.0-a1 + * @changed 3.1.0-a3 Added vars start, current_row_number, end, attachments + * @changed 3.1.0-b3 Added topic_data array, total_posts + * @changed 3.1.0-RC3 Added poster_id + * @changed 3.2.2-RC1 Added user_cache and post_edit_list + */ + $vars = array( + 'start', + 'current_row_number', + 'end', + 'total_posts', + 'poster_id', + 'row', + 'cp_row', + 'attachments', + 'user_poster_data', + 'post_row', + 'topic_data', + 'user_cache', + 'post_edit_list', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_row', compact($vars))); + + $i = $current_row_number; + + if (isset($cp_row['row']) && count($cp_row['row'])) + { + $post_row = array_merge($post_row, $cp_row['row']); + } + + // Dump vars into template + $template->assign_block_vars('postrow', $post_row); + + $contact_fields = array( + array( + 'ID' => 'pm', + 'NAME' => $user->lang['SEND_PRIVATE_MESSAGE'], + 'U_CONTACT' => $post_row['U_PM'], + ), + array( + 'ID' => 'email', + 'NAME' => $user->lang['SEND_EMAIL'], + 'U_CONTACT' => $user_cache[$poster_id]['email'], + ), + array( + 'ID' => 'jabber', + 'NAME' => $user->lang['JABBER'], + 'U_CONTACT' => $user_cache[$poster_id]['jabber'], + ), + ); + + foreach ($contact_fields as $field) + { + if ($field['U_CONTACT']) + { + $template->assign_block_vars('postrow.contact', $field); + } + } + + if (!empty($cp_row['blockrow'])) + { + foreach ($cp_row['blockrow'] as $field_data) + { + $template->assign_block_vars('postrow.custom_fields', $field_data); + + if ($field_data['S_PROFILE_CONTACT']) + { + $template->assign_block_vars('postrow.contact', array( + 'ID' => $field_data['PROFILE_FIELD_IDENT'], + 'NAME' => $field_data['PROFILE_FIELD_NAME'], + 'U_CONTACT' => $field_data['PROFILE_FIELD_CONTACT'], + )); + } + } + } + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments[$row['post_id']])) + { + foreach ($attachments[$row['post_id']] as $attachment) + { + $template->assign_block_vars('postrow.attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + + $current_row_number = $i; + + /** + * Event after the post data has been assigned to the template + * + * @event core.viewtopic_post_row_after + * @var int start Start item of this page + * @var int current_row_number Number of the post on this page + * @var int end Number of posts on this page + * @var int total_posts Total posts count + * @var array row Array with original post and user data + * @var array cp_row Custom profile field data of the poster + * @var array attachments List of attachments + * @var array user_poster_data Poster's data from user cache + * @var array post_row Template block array of the post + * @var array topic_data Array with topic data + * @since 3.1.0-a3 + * @changed 3.1.0-b3 Added topic_data array, total_posts + */ + $vars = array( + 'start', + 'current_row_number', + 'end', + 'total_posts', + 'row', + 'cp_row', + 'attachments', + 'user_poster_data', + 'post_row', + 'topic_data', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_post_row_after', compact($vars))); + + $i = $current_row_number; + + $prev_post_id = $row['post_id']; + + unset($rowset[$post_list[$i]]); + unset($attachments[$row['post_id']]); +} +unset($rowset, $user_cache); + +// Update topic view and if necessary attachment view counters ... but only for humans and if this is the first 'page view' +if (isset($user->data['session_page']) && !$user->data['is_bot'] && (strpos($user->data['session_page'], '&t=' . $topic_id) === false || isset($user->data['session_created']))) +{ + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_views = topic_views + 1, topic_last_view_time = ' . time() . " + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + + // Update the attachment download counts + if (count($update_count)) + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET download_count = download_count + 1 + WHERE ' . $db->sql_in_set('attach_id', array_unique($update_count)); + $db->sql_query($sql); + } +} + +// Only mark topic if it's currently unread. Also make sure we do not set topic tracking back if earlier pages are viewed. +if (isset($topic_tracking_info[$topic_id]) && $topic_data['topic_last_post_time'] > $topic_tracking_info[$topic_id] && $max_post_time > $topic_tracking_info[$topic_id]) +{ + markread('topic', $forum_id, $topic_id, $max_post_time); + + // Update forum info + $all_marked_read = update_forum_tracking_info($forum_id, $topic_data['forum_last_post_time'], (isset($topic_data['forum_mark_time'])) ? $topic_data['forum_mark_time'] : false, false); +} +else +{ + $all_marked_read = true; +} + +// If there are absolutely no more unread posts in this forum +// and unread posts shown, we can safely show the #unread link +if ($all_marked_read) +{ + if ($post_unread) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => '#unread', + )); + } + else if (isset($topic_tracking_info[$topic_id]) && $topic_data['topic_last_post_time'] > $topic_tracking_info[$topic_id]) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&view=unread") . '#unread', + )); + } +} +else if (!$all_marked_read) +{ + $last_page = ((floor($start / $config['posts_per_page']) + 1) == max(ceil($total_posts / $config['posts_per_page']), 1)) ? true : false; + + // What can happen is that we are at the last displayed page. If so, we also display the #unread link based in $post_unread + if ($last_page && $post_unread) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => '#unread', + )); + } + else if (!$last_page) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id&view=unread") . '#unread', + )); + } +} + +// let's set up quick_reply +$s_quick_reply = false; +if ($user->data['is_registered'] && $config['allow_quick_reply'] && ($topic_data['forum_flags'] & FORUM_FLAG_QUICK_REPLY) && $auth->acl_get('f_reply', $forum_id)) +{ + // Quick reply enabled forum + $s_quick_reply = (($topic_data['forum_status'] == ITEM_UNLOCKED && $topic_data['topic_status'] == ITEM_UNLOCKED) || $auth->acl_get('m_edit', $forum_id)) ? true : false; +} + +if ($s_can_vote || $s_quick_reply) +{ + add_form_key('posting'); + + if ($s_quick_reply) + { + $s_attach_sig = $config['allow_sig'] && $user->optionget('attachsig') && $auth->acl_get('f_sigs', $forum_id) && $auth->acl_get('u_sig'); + $s_smilies = $config['allow_smilies'] && $user->optionget('smilies') && $auth->acl_get('f_smilies', $forum_id); + $s_bbcode = $config['allow_bbcode'] && $user->optionget('bbcode') && $auth->acl_get('f_bbcode', $forum_id); + $s_notify = $config['allow_topic_notify'] && ($user->data['user_notify'] || $s_watching_topic['is_watching']); + + $qr_hidden_fields = array( + 'topic_cur_post_id' => (int) $topic_data['topic_last_post_id'], + 'topic_id' => (int) $topic_data['topic_id'], + 'forum_id' => (int) $forum_id, + ); + + // Originally we use checkboxes and check with isset(), so we only provide them if they would be checked + (!$s_bbcode) ? $qr_hidden_fields['disable_bbcode'] = 1 : true; + (!$s_smilies) ? $qr_hidden_fields['disable_smilies'] = 1 : true; + (!$config['allow_post_links']) ? $qr_hidden_fields['disable_magic_url'] = 1 : true; + ($s_attach_sig) ? $qr_hidden_fields['attach_sig'] = 1 : true; + ($s_notify) ? $qr_hidden_fields['notify'] = 1 : true; + ($topic_data['topic_status'] == ITEM_LOCKED) ? $qr_hidden_fields['lock_topic'] = 1 : true; + + $tpl_ary = [ + 'S_QUICK_REPLY' => true, + 'U_QR_ACTION' => append_sid("{$phpbb_root_path}posting.$phpEx", "mode=reply&t=$topic_id"), + 'QR_HIDDEN_FIELDS' => build_hidden_fields($qr_hidden_fields), + 'SUBJECT' => 'Re: ' . censor_text($topic_data['topic_title']), + ]; + + /** + * Event after the quick-reply has been setup + * + * @event core.viewtopic_modify_quick_reply_template_vars + * @var array tpl_ary Array with template data + * @var array topic_data Array with topic data + * @since 3.2.9-RC1 + */ + $vars = ['tpl_ary', 'topic_data']; + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_quick_reply_template_vars', compact($vars))); + + $template->assign_vars($tpl_ary); + } +} +// now I have the urge to wash my hands :( + + +// We overwrite $_REQUEST['f'] if there is no forum specified +// to be able to display the correct online list. +// One downside is that the user currently viewing this topic/post is not taken into account. +if (!$request->variable('f', 0)) +{ + $request->overwrite('f', $forum_id); +} + +// We need to do the same with the topic_id. See #53025. +if (!$request->variable('t', 0) && !empty($topic_id)) +{ + $request->overwrite('t', $topic_id); +} + +$page_title = $topic_data['topic_title'] . ($start ? ' - ' . sprintf($user->lang['PAGE_TITLE_NUMBER'], $pagination->get_on_page($config['posts_per_page'], $start)) : ''); + +/** +* You can use this event to modify the page title of the viewtopic page +* +* @event core.viewtopic_modify_page_title +* @var string page_title Title of the viewtopic page +* @var array topic_data Array with topic data +* @var int forum_id Forum ID of the topic +* @var int start Start offset used to calculate the page +* @var array post_list Array with post_ids we are going to display +* @since 3.1.0-a1 +* @changed 3.1.0-RC4 Added post_list var +*/ +$vars = array('page_title', 'topic_data', 'forum_id', 'start', 'post_list'); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_page_title', compact($vars))); + +// Output the page +page_header($page_title, true, $forum_id); + +$template->set_filenames(array( + 'body' => ($view == 'print') ? 'viewtopic_print.html' : 'viewtopic_body.html') +); +make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_id); + +page_footer(); -- GitLab