示例#1
0
    /**
     * The central part of the board - topic display.
     *
     * What it does:
     * - This function loads the posts in a topic up so they can be displayed.
     * - It uses the main sub template of the Display template.
     * - It requires a topic, and can go to the previous or next topic from it.
     * - It jumps to the correct post depending on a number/time/IS_MSG passed.
     * - It depends on the messages_per_page, defaultMaxMessages and enableAllMessages settings.
     * - It is accessed by ?topic=id_topic.START.
     */
    public function action_display()
    {
        global $scripturl, $txt, $modSettings, $context, $settings;
        global $options, $user_info, $board_info, $topic, $board;
        global $attachments, $messages_request;
        // What are you gonna display if these are empty?!
        if (empty($topic)) {
            fatal_lang_error('no_board', false);
        }
        // Load the template
        loadTemplate('Display');
        $context['sub_template'] = 'messages';
        // And the topic functions
        require_once SUBSDIR . '/Topic.subs.php';
        require_once SUBSDIR . '/Messages.subs.php';
        // Not only does a prefetch make things slower for the server, but it makes it impossible to know if they read it.
        if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') {
            @ob_end_clean();
            header('HTTP/1.1 403 Prefetch Forbidden');
            die;
        }
        // How much are we sticking on each page?
        $context['messages_per_page'] = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
        $template_layers = Template_Layers::getInstance();
        $template_layers->addEnd('messages_informations');
        $includeUnapproved = !$modSettings['postmod_active'] || allowedTo('approve_posts');
        // Let's do some work on what to search index.
        if (count($_GET) > 2) {
            foreach ($_GET as $k => $v) {
                if (!in_array($k, array('topic', 'board', 'start', session_name()))) {
                    $context['robot_no_index'] = true;
                }
            }
        }
        if (!empty($_REQUEST['start']) && (!is_numeric($_REQUEST['start']) || $_REQUEST['start'] % $context['messages_per_page'] != 0)) {
            $context['robot_no_index'] = true;
        }
        // Find the previous or next topic.  Make a fuss if there are no more.
        if (isset($_REQUEST['prev_next']) && ($_REQUEST['prev_next'] == 'prev' || $_REQUEST['prev_next'] == 'next')) {
            // No use in calculating the next topic if there's only one.
            if ($board_info['num_topics'] > 1) {
                $includeStickies = !empty($modSettings['enableStickyTopics']);
                $topic = $_REQUEST['prev_next'] === 'prev' ? previousTopic($topic, $board, $user_info['id'], $includeUnapproved, $includeStickies) : nextTopic($topic, $board, $user_info['id'], $includeUnapproved, $includeStickies);
                $context['current_topic'] = $topic;
            }
            // Go to the newest message on this topic.
            $_REQUEST['start'] = 'new';
        }
        // Add 1 to the number of views of this topic (except for robots).
        if (!$user_info['possibly_robot'] && (empty($_SESSION['last_read_topic']) || $_SESSION['last_read_topic'] != $topic)) {
            increaseViewCounter($topic);
            $_SESSION['last_read_topic'] = $topic;
        }
        $topic_selects = array();
        $topic_tables = array();
        $topic_parameters = array('topic' => $topic, 'member' => $user_info['id'], 'board' => (int) $board);
        // Allow addons to add additional details to the topic query
        call_integration_hook('integrate_topic_query', array(&$topic_selects, &$topic_tables, &$topic_parameters));
        // Load the topic details
        $topicinfo = getTopicInfo($topic_parameters, 'all', $topic_selects, $topic_tables);
        if (empty($topicinfo)) {
            fatal_lang_error('not_a_topic', false);
        }
        // Is this a moved topic that we are redirecting to?
        if (!empty($topicinfo['id_redirect_topic']) && !isset($_GET['noredir'])) {
            markTopicsRead(array($user_info['id'], $topic, $topicinfo['id_last_msg'], 0), $topicinfo['new_from'] !== 0);
            redirectexit('topic=' . $topicinfo['id_redirect_topic'] . '.0;redirfrom=' . $topicinfo['id_topic']);
        }
        $context['real_num_replies'] = $context['num_replies'] = $topicinfo['num_replies'];
        $context['topic_first_message'] = $topicinfo['id_first_msg'];
        $context['topic_last_message'] = $topicinfo['id_last_msg'];
        $context['topic_unwatched'] = isset($topicinfo['unwatched']) ? $topicinfo['unwatched'] : 0;
        if (isset($_GET['redirfrom'])) {
            $redir_topics = topicsList(array((int) $_GET['redirfrom']));
            if (!empty($redir_topics[(int) $_GET['redirfrom']])) {
                $context['topic_redirected_from'] = $redir_topics[(int) $_GET['redirfrom']];
                $context['topic_redirected_from']['redir_href'] = $scripturl . '?topic=' . $context['topic_redirected_from']['id_topic'] . '.0;noredir';
            }
        }
        // Add up unapproved replies to get real number of replies...
        if ($modSettings['postmod_active'] && allowedTo('approve_posts')) {
            $context['real_num_replies'] += $topicinfo['unapproved_posts'] - ($topicinfo['approved'] ? 0 : 1);
        }
        // If this topic was derived from another, set the followup details
        if (!empty($topicinfo['derived_from'])) {
            require_once SUBSDIR . '/FollowUps.subs.php';
            $context['topic_derived_from'] = topicStartedHere($topic, $includeUnapproved);
        }
        // If this topic has unapproved posts, we need to work out how many posts the user can see, for page indexing.
        if (!$includeUnapproved && $topicinfo['unapproved_posts'] && !$user_info['is_guest']) {
            $myUnapprovedPosts = unapprovedPosts($topic, $user_info['id']);
            $context['total_visible_posts'] = $context['num_replies'] + $myUnapprovedPosts + ($topicinfo['approved'] ? 1 : 0);
        } elseif ($user_info['is_guest']) {
            $context['total_visible_posts'] = $context['num_replies'] + ($topicinfo['approved'] ? 1 : 0);
        } else {
            $context['total_visible_posts'] = $context['num_replies'] + $topicinfo['unapproved_posts'] + ($topicinfo['approved'] ? 1 : 0);
        }
        // When was the last time this topic was replied to?  Should we warn them about it?
        if (!empty($modSettings['oldTopicDays'])) {
            $mgsOptions = basicMessageInfo($topicinfo['id_last_msg'], true);
            $context['oldTopicError'] = $mgsOptions['poster_time'] + $modSettings['oldTopicDays'] * 86400 < time() && empty($topicinfo['is_sticky']);
        } else {
            $context['oldTopicError'] = false;
        }
        // The start isn't a number; it's information about what to do, where to go.
        if (!is_numeric($_REQUEST['start'])) {
            // Redirect to the page and post with new messages, originally by Omar Bazavilvazo.
            if ($_REQUEST['start'] == 'new') {
                // Guests automatically go to the last post.
                if ($user_info['is_guest']) {
                    $context['start_from'] = $context['total_visible_posts'] - 1;
                    $_REQUEST['start'] = $context['start_from'];
                } else {
                    // Fall through to the next if statement.
                    $_REQUEST['start'] = 'msg' . $topicinfo['new_from'];
                }
            }
            // Start from a certain time index, not a message.
            if (substr($_REQUEST['start'], 0, 4) == 'from') {
                $timestamp = (int) substr($_REQUEST['start'], 4);
                if ($timestamp === 0) {
                    $_REQUEST['start'] = 0;
                } else {
                    // Find the number of messages posted before said time...
                    $context['start_from'] = countNewPosts($topic, $topicinfo, $timestamp);
                    $_REQUEST['start'] = $context['start_from'];
                }
            } elseif (substr($_REQUEST['start'], 0, 3) == 'msg') {
                $virtual_msg = (int) substr($_REQUEST['start'], 3);
                if (!$topicinfo['unapproved_posts'] && $virtual_msg >= $topicinfo['id_last_msg']) {
                    $context['start_from'] = $context['total_visible_posts'] - 1;
                } elseif (!$topicinfo['unapproved_posts'] && $virtual_msg <= $topicinfo['id_first_msg']) {
                    $context['start_from'] = 0;
                } else {
                    $only_approved = $modSettings['postmod_active'] && $topicinfo['unapproved_posts'] && !allowedTo('approve_posts');
                    $context['start_from'] = countMessagesBefore($topic, $virtual_msg, false, $only_approved, !$user_info['is_guest']);
                }
                // We need to reverse the start as well in this case.
                $_REQUEST['start'] = $context['start_from'];
            }
        }
        // Mark the mention as read if requested
        if (isset($_REQUEST['mentionread']) && !empty($virtual_msg)) {
            require_once CONTROLLERDIR . '/Mentions.controller.php';
            $mentions = new Mentions_Controller();
            $mentions->setData(array('id_mention' => $_REQUEST['item'], 'mark' => $_REQUEST['mark']));
            $mentions->action_markread();
        }
        // Create a previous next string if the selected theme has it as a selected option.
        if ($modSettings['enablePreviousNext']) {
            $context['links'] += array('go_prev' => $scripturl . '?topic=' . $topic . '.0;prev_next=prev#new', 'go_next' => $scripturl . '?topic=' . $topic . '.0;prev_next=next#new');
        }
        // Derived from, set the link back
        if (!empty($context['topic_derived_from'])) {
            $context['links']['derived_from'] = $scripturl . '?msg=' . $context['topic_derived_from']['derived_from'];
        }
        // Check if spellchecking is both enabled and actually working. (for quick reply.)
        $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
        if ($context['show_spellchecking']) {
            loadJavascriptFile('spellcheck.js', array('defer' => true));
        }
        // Do we need to show the visual verification image?
        $context['require_verification'] = !$user_info['is_mod'] && !$user_info['is_admin'] && !empty($modSettings['posts_require_captcha']) && ($user_info['posts'] < $modSettings['posts_require_captcha'] || $user_info['is_guest'] && $modSettings['posts_require_captcha'] == -1);
        if ($context['require_verification']) {
            require_once SUBSDIR . '/VerificationControls.class.php';
            $verificationOptions = array('id' => 'post');
            $context['require_verification'] = create_control_verification($verificationOptions);
            $context['visual_verification_id'] = $verificationOptions['id'];
        }
        // Are we showing signatures - or disabled fields?
        $context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
        $context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
        // Censor the title...
        censorText($topicinfo['subject']);
        $context['page_title'] = $topicinfo['subject'];
        // Is this topic sticky, or can it even be?
        $topicinfo['is_sticky'] = empty($modSettings['enableStickyTopics']) ? '0' : $topicinfo['is_sticky'];
        // Allow addons access to the topicinfo array
        call_integration_hook('integrate_display_topic', array($topicinfo));
        // Default this topic to not marked for notifications... of course...
        $context['is_marked_notify'] = false;
        // Did we report a post to a moderator just now?
        $context['report_sent'] = isset($_GET['reportsent']);
        if ($context['report_sent']) {
            $template_layers->add('report_sent');
        }
        // Let's get nosey, who is viewing this topic?
        if (!empty($settings['display_who_viewing'])) {
            require_once SUBSDIR . '/Who.subs.php';
            formatViewers($topic, 'topic');
        }
        // If all is set, but not allowed... just unset it.
        $can_show_all = !empty($modSettings['enableAllMessages']) && $context['total_visible_posts'] > $context['messages_per_page'] && $context['total_visible_posts'] < $modSettings['enableAllMessages'];
        if (isset($_REQUEST['all']) && !$can_show_all) {
            unset($_REQUEST['all']);
        } elseif (isset($_REQUEST['all'])) {
            $_REQUEST['start'] = -1;
        }
        // Construct the page index, allowing for the .START method...
        $context['page_index'] = constructPageIndex($scripturl . '?topic=' . $topic . '.%1$d', $_REQUEST['start'], $context['total_visible_posts'], $context['messages_per_page'], true, array('all' => $can_show_all, 'all_selected' => isset($_REQUEST['all'])));
        $context['start'] = $_REQUEST['start'];
        // This is information about which page is current, and which page we're on - in case you don't like the constructed page index. (again, wireles..)
        $context['page_info'] = array('current_page' => $_REQUEST['start'] / $context['messages_per_page'] + 1, 'num_pages' => floor(($context['total_visible_posts'] - 1) / $context['messages_per_page']) + 1);
        // Figure out all the link to the next/prev
        $context['links'] += array('prev' => $_REQUEST['start'] >= $context['messages_per_page'] ? $scripturl . '?topic=' . $topic . '.' . ($_REQUEST['start'] - $context['messages_per_page']) : '', 'next' => $_REQUEST['start'] + $context['messages_per_page'] < $context['total_visible_posts'] ? $scripturl . '?topic=' . $topic . '.' . ($_REQUEST['start'] + $context['messages_per_page']) : '');
        // If they are viewing all the posts, show all the posts, otherwise limit the number.
        if ($can_show_all && isset($_REQUEST['all'])) {
            // No limit! (actually, there is a limit, but...)
            $context['messages_per_page'] = -1;
            // Set start back to 0...
            $_REQUEST['start'] = 0;
        }
        // Build the link tree.
        $context['linktree'][] = array('url' => $scripturl . '?topic=' . $topic . '.0', 'name' => $topicinfo['subject']);
        // Build a list of this board's moderators.
        $context['moderators'] =& $board_info['moderators'];
        $context['link_moderators'] = array();
        // Information about the current topic...
        $context['is_locked'] = $topicinfo['locked'];
        $context['is_sticky'] = $topicinfo['is_sticky'];
        $context['is_very_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicVeryPosts'];
        $context['is_hot'] = $topicinfo['num_replies'] >= $modSettings['hotTopicPosts'];
        $context['is_approved'] = $topicinfo['approved'];
        $context['is_poll'] = $topicinfo['id_poll'] > 0 && !empty($modSettings['pollMode']) && allowedTo('poll_view');
        determineTopicClass($context);
        // Did this user start the topic or not?
        $context['user']['started'] = $user_info['id'] == $topicinfo['id_member_started'] && !$user_info['is_guest'];
        $context['topic_starter_id'] = $topicinfo['id_member_started'];
        // Set the topic's information for the template.
        $context['subject'] = $topicinfo['subject'];
        $context['num_views'] = $topicinfo['num_views'];
        $context['num_views_text'] = $context['num_views'] == 1 ? $txt['read_one_time'] : sprintf($txt['read_many_times'], $context['num_views']);
        $context['mark_unread_time'] = !empty($virtual_msg) ? $virtual_msg : $topicinfo['new_from'];
        // Set a canonical URL for this page.
        $context['canonical_url'] = $scripturl . '?topic=' . $topic . '.' . $context['start'];
        // For quick reply we need a response prefix in the default forum language.
        $context['response_prefix'] = response_prefix();
        // If we want to show event information in the topic, prepare the data.
        if (allowedTo('calendar_view') && !empty($modSettings['cal_showInTopic']) && !empty($modSettings['cal_enabled'])) {
            // We need events details and all that jazz
            require_once SUBSDIR . '/Calendar.subs.php';
            // First, try create a better time format, ignoring the "time" elements.
            if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0])) {
                $date_string = $user_info['time_format'];
            } else {
                $date_string = $matches[0];
            }
            // Get event information for this topic.
            $events = eventInfoForTopic($topic);
            $context['linked_calendar_events'] = array();
            foreach ($events as $event) {
                // Prepare the dates for being formatted.
                $start_date = sscanf($event['start_date'], '%04d-%02d-%02d');
                $start_date = mktime(12, 0, 0, $start_date[1], $start_date[2], $start_date[0]);
                $end_date = sscanf($event['end_date'], '%04d-%02d-%02d');
                $end_date = mktime(12, 0, 0, $end_date[1], $end_date[2], $end_date[0]);
                $context['linked_calendar_events'][] = array('id' => $event['id_event'], 'title' => $event['title'], 'can_edit' => allowedTo('calendar_edit_any') || $event['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own'), 'modify_href' => $scripturl . '?action=post;msg=' . $topicinfo['id_first_msg'] . ';topic=' . $topic . '.0;calendar;eventid=' . $event['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'can_export' => allowedTo('calendar_edit_any') || $event['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own'), 'export_href' => $scripturl . '?action=calendar;sa=ical;eventid=' . $event['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'], 'start_date' => standardTime($start_date, $date_string, 'none'), 'start_timestamp' => $start_date, 'end_date' => standardTime($end_date, $date_string, 'none'), 'end_timestamp' => $end_date, 'is_last' => false);
            }
            if (!empty($context['linked_calendar_events'])) {
                $context['linked_calendar_events'][count($context['linked_calendar_events']) - 1]['is_last'] = true;
                $template_layers->add('display_calendar');
            }
        }
        // Create the poll info if it exists.
        if ($context['is_poll']) {
            $template_layers->add('display_poll');
            require_once SUBSDIR . '/Poll.subs.php';
            loadPollContext($topicinfo['id_poll']);
            // Build the poll moderation button array.
            $context['poll_buttons'] = array('vote' => array('test' => 'allow_return_vote', 'text' => 'poll_return_vote', 'image' => 'poll_options.png', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start']), 'results' => array('test' => 'allow_poll_view', 'text' => 'poll_results', 'image' => 'poll_results.png', 'lang' => true, 'url' => $scripturl . '?topic=' . $context['current_topic'] . '.' . $context['start'] . ';viewresults'), 'change_vote' => array('test' => 'allow_change_vote', 'text' => 'poll_change_vote', 'image' => 'poll_change_vote.png', 'lang' => true, 'url' => $scripturl . '?action=poll;sa=vote;topic=' . $context['current_topic'] . '.' . $context['start'] . ';poll=' . $context['poll']['id'] . ';' . $context['session_var'] . '=' . $context['session_id']), 'lock' => array('test' => 'allow_lock_poll', 'text' => !$context['poll']['is_locked'] ? 'poll_lock' : 'poll_unlock', 'image' => 'poll_lock.png', 'lang' => true, 'url' => $scripturl . '?action=lockvoting;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), 'edit' => array('test' => 'allow_edit_poll', 'text' => 'poll_edit', 'image' => 'poll_edit.png', 'lang' => true, 'url' => $scripturl . '?action=editpoll;topic=' . $context['current_topic'] . '.' . $context['start']), 'remove_poll' => array('test' => 'can_remove_poll', 'text' => 'poll_remove', 'image' => 'admin_remove_poll.png', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['poll_remove_warn'] . '\');"', 'url' => $scripturl . '?action=poll;sa=remove;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']));
            // Allow mods to add additional buttons here
            call_integration_hook('integrate_poll_buttons');
        }
        // Calculate the fastest way to get the messages!
        $ascending = true;
        $start = $_REQUEST['start'];
        $limit = $context['messages_per_page'];
        $firstIndex = 0;
        if ($start >= $context['total_visible_posts'] / 2 && $context['messages_per_page'] != -1) {
            $ascending = !$ascending;
            $limit = $context['total_visible_posts'] <= $start + $limit ? $context['total_visible_posts'] - $start : $limit;
            $start = $context['total_visible_posts'] <= $start + $limit ? 0 : $context['total_visible_posts'] - $start - $limit;
            $firstIndex = $limit - 1;
        }
        // Taking care of member specific settings
        $limit_settings = array('messages_per_page' => $context['messages_per_page'], 'start' => $start, 'offset' => $limit);
        // Get each post and poster in this topic.
        $topic_details = getTopicsPostsAndPoster($topic, $limit_settings, $ascending);
        $messages = $topic_details['messages'];
        $posters = array_unique($topic_details['all_posters']);
        $all_posters = $topic_details['all_posters'];
        unset($topic_details);
        call_integration_hook('integrate_display_message_list', array(&$messages, &$posters));
        // Guests can't mark topics read or for notifications, just can't sorry.
        if (!$user_info['is_guest'] && !empty($messages)) {
            $mark_at_msg = max($messages);
            if ($mark_at_msg >= $topicinfo['id_last_msg']) {
                $mark_at_msg = $modSettings['maxMsgID'];
            }
            if ($mark_at_msg >= $topicinfo['new_from']) {
                markTopicsRead(array($user_info['id'], $topic, $mark_at_msg, $topicinfo['unwatched']), $topicinfo['new_from'] !== 0);
            }
            updateReadNotificationsFor($topic, $board);
            // Have we recently cached the number of new topics in this board, and it's still a lot?
            if (isset($_REQUEST['topicseen']) && isset($_SESSION['topicseen_cache'][$board]) && $_SESSION['topicseen_cache'][$board] > 5) {
                $_SESSION['topicseen_cache'][$board]--;
            } elseif (isset($_REQUEST['topicseen'])) {
                // Use the mark read tables... and the last visit to figure out if this should be read or not.
                $numNewTopics = getUnreadCountSince($board, empty($_SESSION['id_msg_last_visit']) ? 0 : $_SESSION['id_msg_last_visit']);
                // If there're no real new topics in this board, mark the board as seen.
                if (empty($numNewTopics)) {
                    $_REQUEST['boardseen'] = true;
                } else {
                    $_SESSION['topicseen_cache'][$board] = $numNewTopics;
                }
            } elseif (isset($_SESSION['topicseen_cache'][$board])) {
                $_SESSION['topicseen_cache'][$board]--;
            }
            // Mark board as seen if we came using last post link from BoardIndex. (or other places...)
            if (isset($_REQUEST['boardseen'])) {
                require_once SUBSDIR . '/Boards.subs.php';
                markBoardsRead($board, false, false);
            }
        }
        $attachments = array();
        // If there _are_ messages here... (probably an error otherwise :!)
        if (!empty($messages)) {
            require_once SUBSDIR . '/Attachments.subs.php';
            // Fetch attachments.
            $includeUnapproved = !$modSettings['postmod_active'] || allowedTo('approve_posts');
            if (!empty($modSettings['attachmentEnable']) && allowedTo('view_attachments')) {
                $attachments = getAttachments($messages, $includeUnapproved, 'filter_accessible_attachment', $all_posters);
            }
            $msg_parameters = array('message_list' => $messages, 'new_from' => $topicinfo['new_from']);
            $msg_selects = array();
            $msg_tables = array();
            call_integration_hook('integrate_message_query', array(&$msg_selects, &$msg_tables, &$msg_parameters));
            // What?  It's not like it *couldn't* be only guests in this topic...
            if (!empty($posters)) {
                loadMemberData($posters);
            }
            // Load in the likes for this group of messages
            if (!empty($modSettings['likes_enabled'])) {
                require_once SUBSDIR . '/Likes.subs.php';
                $context['likes'] = loadLikes($messages, true);
                // ajax controller for likes
                loadJavascriptFile('like_posts.js', array('defer' => true));
                loadLanguage('Errors');
                // Initiate likes and the tooltips for likes
                addInlineJavascript('
				$(document).ready(function () {
					var likePostInstance = likePosts.prototype.init({
						oTxt: ({
							btnText : ' . JavaScriptEscape($txt['ok_uppercase']) . ',
							likeHeadingError : ' . JavaScriptEscape($txt['like_heading_error']) . ',
							error_occurred : ' . JavaScriptEscape($txt['error_occurred']) . '
						}),
					});

					$(".like_button, .unlike_button").SiteTooltip({
						hoverIntent: {
							sensitivity: 10,
							interval: 150,
							timeout: 50
						}
					});
				});', true);
            }
            $messages_request = loadMessageRequest($msg_selects, $msg_tables, $msg_parameters);
            if (!empty($modSettings['enableFollowup'])) {
                require_once SUBSDIR . '/FollowUps.subs.php';
                $context['follow_ups'] = followupTopics($messages, $includeUnapproved);
            }
            // Go to the last message if the given time is beyond the time of the last message.
            if (isset($context['start_from']) && $context['start_from'] >= $topicinfo['num_replies']) {
                $context['start_from'] = $topicinfo['num_replies'];
            }
            // Since the anchor information is needed on the top of the page we load these variables beforehand.
            $context['first_message'] = isset($messages[$firstIndex]) ? $messages[$firstIndex] : $messages[0];
            $context['first_new_message'] = isset($context['start_from']) && $_REQUEST['start'] == $context['start_from'];
        } else {
            $messages_request = false;
            $context['first_message'] = 0;
            $context['first_new_message'] = false;
        }
        $context['jump_to'] = array('label' => addslashes(un_htmlspecialchars($txt['jump_to'])), 'board_name' => htmlspecialchars(strtr(strip_tags($board_info['name']), array('&amp;' => '&')), ENT_COMPAT, 'UTF-8'), 'child_level' => $board_info['child_level']);
        // Set the callback.  (do you REALIZE how much memory all the messages would take?!?)
        // This will be called from the template.
        $context['get_message'] = array($this, 'prepareDisplayContext_callback');
        // Now set all the wonderful, wonderful permissions... like moderation ones...
        $common_permissions = array('can_approve' => 'approve_posts', 'can_ban' => 'manage_bans', 'can_sticky' => 'make_sticky', 'can_merge' => 'merge_any', 'can_split' => 'split_any', 'calendar_post' => 'calendar_post', 'can_mark_notify' => 'mark_any_notify', 'can_send_topic' => 'send_topic', 'can_send_pm' => 'pm_send', 'can_send_email' => 'send_email_to_members', 'can_report_moderator' => 'report_any', 'can_moderate_forum' => 'moderate_forum', 'can_issue_warning' => 'issue_warning', 'can_restore_topic' => 'move_any', 'can_restore_msg' => 'move_any');
        foreach ($common_permissions as $contextual => $perm) {
            $context[$contextual] = allowedTo($perm);
        }
        // Permissions with _any/_own versions.  $context[YYY] => ZZZ_any/_own.
        $anyown_permissions = array('can_move' => 'move', 'can_lock' => 'lock', 'can_delete' => 'remove', 'can_add_poll' => 'poll_add', 'can_remove_poll' => 'poll_remove', 'can_reply' => 'post_reply', 'can_reply_unapproved' => 'post_unapproved_replies');
        foreach ($anyown_permissions as $contextual => $perm) {
            $context[$contextual] = allowedTo($perm . '_any') || $context['user']['started'] && allowedTo($perm . '_own');
        }
        // Cleanup all the permissions with extra stuff...
        $context['can_mark_notify'] &= !$context['user']['is_guest'];
        $context['can_sticky'] &= !empty($modSettings['enableStickyTopics']);
        $context['calendar_post'] &= !empty($modSettings['cal_enabled']) && (allowedTo('modify_any') || $context['user']['started'] && allowedTo('modify_own'));
        $context['can_add_poll'] &= !empty($modSettings['pollMode']) && $topicinfo['id_poll'] <= 0;
        $context['can_remove_poll'] &= !empty($modSettings['pollMode']) && $topicinfo['id_poll'] > 0;
        $context['can_reply'] &= empty($topicinfo['locked']) || allowedTo('moderate_board');
        $context['can_reply_unapproved'] &= $modSettings['postmod_active'] && (empty($topicinfo['locked']) || allowedTo('moderate_board'));
        $context['can_issue_warning'] &= in_array('w', $context['admin_features']) && !empty($modSettings['warning_enable']);
        // Handle approval flags...
        $context['can_reply_approved'] = $context['can_reply'];
        $context['can_reply'] |= $context['can_reply_unapproved'];
        $context['can_quote'] = $context['can_reply'] && (empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC'])));
        $context['can_mark_unread'] = !$user_info['is_guest'] && $settings['show_mark_read'];
        $context['can_unwatch'] = !$user_info['is_guest'] && $modSettings['enable_unwatch'];
        $context['can_send_topic'] = (!$modSettings['postmod_active'] || $topicinfo['approved']) && allowedTo('send_topic');
        $context['can_print'] = empty($modSettings['disable_print_topic']);
        // Start this off for quick moderation - it will be or'd for each post.
        $context['can_remove_post'] = allowedTo('delete_any') || allowedTo('delete_replies') && $context['user']['started'];
        // Can restore topic?  That's if the topic is in the recycle board and has a previous restore state.
        $context['can_restore_topic'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_board']);
        $context['can_restore_msg'] &= !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $board && !empty($topicinfo['id_previous_topic']);
        $context['can_follow_up'] = !empty($modSettings['enableFollowup']) && boardsallowedto('post_new') !== array();
        // Check if the draft functions are enabled and that they have permission to use them (for quick reply.)
        $context['drafts_save'] = !empty($modSettings['drafts_enabled']) && !empty($modSettings['drafts_post_enabled']) && allowedTo('post_draft') && $context['can_reply'];
        $context['drafts_autosave'] = !empty($context['drafts_save']) && !empty($modSettings['drafts_autosave_enabled']) && allowedTo('post_autosave_draft');
        if (!empty($context['drafts_save'])) {
            loadLanguage('Drafts');
        }
        if (!empty($context['drafts_autosave']) && empty($options['use_editor_quick_reply'])) {
            loadJavascriptFile('drafts.js');
        }
        if (!empty($modSettings['mentions_enabled'])) {
            $context['mentions_enabled'] = true;
            // Just using the plain text quick reply and not the editor
            if (empty($options['use_editor_quick_reply'])) {
                loadJavascriptFile(array('jquery.atwho.js', 'jquery.caret.min.js', 'mentioning.js'));
            }
            loadCSSFile('jquery.atwho.css');
            addInlineJavascript('
			$(document).ready(function () {
				for (var i = 0, count = all_elk_mentions.length; i < count; i++)
					all_elk_mentions[i].oMention = new elk_mentions(all_elk_mentions[i].oOptions);
			});');
        }
        // Load up the Quick ModifyTopic and Quick Reply scripts
        loadJavascriptFile('topic.js');
        // Auto video embeding enabled?
        if (!empty($modSettings['enableVideoEmbeding'])) {
            addInlineJavascript('
		$(document).ready(function() {
			$().linkifyvideo(oEmbedtext);
		});');
        }
        // Load up the "double post" sequencing magic.
        if (!empty($options['display_quick_reply'])) {
            checkSubmitOnce('register');
            $context['name'] = isset($_SESSION['guest_name']) ? $_SESSION['guest_name'] : '';
            $context['email'] = isset($_SESSION['guest_email']) ? $_SESSION['guest_email'] : '';
            if (!empty($options['use_editor_quick_reply']) && $context['can_reply']) {
                // Needed for the editor and message icons.
                require_once SUBSDIR . '/Editor.subs.php';
                // Now create the editor.
                $editorOptions = array('id' => 'message', 'value' => '', 'labels' => array('post_button' => $txt['post']), 'height' => '250px', 'width' => '100%', 'preview_type' => 0);
                create_control_richedit($editorOptions);
                $context['attached'] = '';
                $context['make_poll'] = isset($_REQUEST['poll']);
                // Message icons - customized icons are off?
                $context['icons'] = getMessageIcons($board);
                if (!empty($context['icons'])) {
                    $context['icons'][count($context['icons']) - 1]['is_last'] = true;
                }
            }
        }
        addJavascriptVar(array('notification_topic_notice' => $context['is_marked_notify'] ? $txt['notification_disable_topic'] : $txt['notification_enable_topic']), true);
        if ($context['can_send_topic']) {
            addJavascriptVar(array('sendtopic_cancel' => $txt['modify_cancel'], 'sendtopic_back' => $txt['back'], 'sendtopic_close' => $txt['find_close'], 'sendtopic_error' => $txt['send_error_occurred'], 'required_field' => $txt['require_field']), true);
        }
        // Build the normal button array.
        $context['normal_buttons'] = array('reply' => array('test' => 'can_reply', 'text' => 'reply', 'image' => 'reply.png', 'lang' => true, 'url' => $scripturl . '?action=post;topic=' . $context['current_topic'] . '.' . $context['start'] . ';last_msg=' . $context['topic_last_message'], 'active' => true), 'notify' => array('test' => 'can_mark_notify', 'text' => $context['is_marked_notify'] ? 'unnotify' : 'notify', 'image' => ($context['is_marked_notify'] ? 'un' : '') . 'notify.png', 'lang' => true, 'custom' => 'onclick="return notifyButton(this);"', 'url' => $scripturl . '?action=notify;sa=' . ($context['is_marked_notify'] ? 'off' : 'on') . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), 'mark_unread' => array('test' => 'can_mark_unread', 'text' => 'mark_unread', 'image' => 'markunread.png', 'lang' => true, 'url' => $scripturl . '?action=markasread;sa=topic;t=' . $context['mark_unread_time'] . ';topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), 'unwatch' => array('test' => 'can_unwatch', 'text' => ($context['topic_unwatched'] ? '' : 'un') . 'watch', 'image' => ($context['topic_unwatched'] ? '' : 'un') . 'watched.png', 'lang' => true, 'custom' => 'onclick="return unwatchButton(this);"', 'url' => $scripturl . '?action=unwatchtopic;topic=' . $context['current_topic'] . '.' . $context['start'] . ';sa=' . ($context['topic_unwatched'] ? 'off' : 'on') . ';' . $context['session_var'] . '=' . $context['session_id']), 'send' => array('test' => 'can_send_topic', 'text' => 'send_topic', 'image' => 'sendtopic.png', 'lang' => true, 'url' => $scripturl . '?action=emailuser;sa=sendtopic;topic=' . $context['current_topic'] . '.0', 'custom' => 'onclick="return sendtopicOverlayDiv(this.href, \'' . $txt['send_topic'] . '\');"'), 'print' => array('test' => 'can_print', 'text' => 'print', 'image' => 'print.png', 'lang' => true, 'custom' => 'rel="nofollow"', 'class' => 'new_win', 'url' => $scripturl . '?action=topic;sa=printpage;topic=' . $context['current_topic'] . '.0'));
        // Build the mod button array
        $context['mod_buttons'] = array('move' => array('test' => 'can_move', 'text' => 'move_topic', 'image' => 'admin_move.png', 'lang' => true, 'url' => $scripturl . '?action=movetopic;current_board=' . $context['current_board'] . ';topic=' . $context['current_topic'] . '.0'), 'delete' => array('test' => 'can_delete', 'text' => 'remove_topic', 'image' => 'admin_rem.png', 'lang' => true, 'custom' => 'onclick="return confirm(\'' . $txt['are_sure_remove_topic'] . '\');"', 'url' => $scripturl . '?action=removetopic2;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id']), 'lock' => array('test' => 'can_lock', 'text' => empty($context['is_locked']) ? 'set_lock' : 'set_unlock', 'image' => 'admin_lock.png', 'lang' => true, 'url' => $scripturl . '?action=topic;sa=lock;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), 'sticky' => array('test' => 'can_sticky', 'text' => empty($context['is_sticky']) ? 'set_sticky' : 'set_nonsticky', 'image' => 'admin_sticky.png', 'lang' => true, 'url' => $scripturl . '?action=topic;sa=sticky;topic=' . $context['current_topic'] . '.' . $context['start'] . ';' . $context['session_var'] . '=' . $context['session_id']), 'merge' => array('test' => 'can_merge', 'text' => 'merge', 'image' => 'merge.png', 'lang' => true, 'url' => $scripturl . '?action=mergetopics;board=' . $context['current_board'] . '.0;from=' . $context['current_topic']), 'calendar' => array('test' => 'calendar_post', 'text' => 'calendar_link', 'image' => 'linktocal.png', 'lang' => true, 'url' => $scripturl . '?action=post;calendar;msg=' . $context['topic_first_message'] . ';topic=' . $context['current_topic'] . '.0'));
        // Restore topic. eh?  No monkey business.
        if ($context['can_restore_topic']) {
            $context['mod_buttons'][] = array('text' => 'restore_topic', 'image' => '', 'lang' => true, 'url' => $scripturl . '?action=restoretopic;topics=' . $context['current_topic'] . ';' . $context['session_var'] . '=' . $context['session_id']);
        }
        if ($context['can_reply'] && !empty($options['display_quick_reply'])) {
            $template_layers->add('quickreply');
        }
        $template_layers->add('pages_and_buttons');
        // Allow adding new buttons easily.
        call_integration_hook('integrate_display_buttons');
        call_integration_hook('integrate_mod_buttons');
    }
示例#2
0
 /**
  * Tests the response of the response_prefix function
  */
 function testResponsePrefix()
 {
     global $txt;
     $this->assertEqual(response_prefix(), $txt['response_prefix']);
 }
示例#3
0
 /**
  * Execute the move of a topic.
  * It is called on the submit of action_movetopic.
  * This function logs that topics have been moved in the moderation log.
  * If the member is the topic starter requires the move_own permission,
  * otherwise requires the move_any permission.
  * Upon successful completion redirects to message index.
  * Accessed via ?action=movetopic2.
  *
  * @uses subs/Post.subs.php.
  */
 public function action_movetopic2()
 {
     global $txt, $board, $topic, $scripturl, $context, $language, $user_info;
     if (empty($topic)) {
         fatal_lang_error('no_access', false);
     }
     // You can't choose to have a redirection topic and use an empty reason.
     if (isset($_POST['postRedirect']) && (!isset($_POST['reason']) || trim($_POST['reason']) == '')) {
         fatal_lang_error('movetopic_no_reason', false);
     }
     // You have to tell us were you are moving to
     if (!isset($_POST['toboard'])) {
         fatal_lang_error('movetopic_no_board', false);
     }
     // We will need this
     require_once SUBSDIR . '/Topic.subs.php';
     moveTopicConcurrence();
     // Make sure this form hasn't been submitted before.
     checkSubmitOnce('check');
     // Get the basic details on this topic
     $topic_info = getTopicInfo($topic);
     $context['is_approved'] = $topic_info['approved'];
     // Can they see it?
     if (!$context['is_approved']) {
         isAllowedTo('approve_posts');
     }
     // Can they move topics on this board?
     if (!allowedTo('move_any')) {
         if ($topic_info['id_member_started'] == $user_info['id']) {
             isAllowedTo('move_own');
         } else {
             isAllowedTo('move_any');
         }
     }
     checkSession();
     require_once SUBSDIR . '/Post.subs.php';
     require_once SUBSDIR . '/Boards.subs.php';
     // The destination board must be numeric.
     $toboard = (int) $_POST['toboard'];
     // Make sure they can see the board they are trying to move to (and get whether posts count in the target board).
     $board_info = boardInfo($toboard, $topic);
     if (empty($board_info)) {
         fatal_lang_error('no_board');
     }
     // Remember this for later.
     $_SESSION['move_to_topic'] = array('move_to' => $toboard);
     // Rename the topic...
     if (isset($_POST['reset_subject'], $_POST['custom_subject']) && $_POST['custom_subject'] != '') {
         $custom_subject = strtr(Util::htmltrim(Util::htmlspecialchars($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => ''));
         // Keep checking the length.
         if (Util::strlen($custom_subject) > 100) {
             $custom_subject = Util::substr($custom_subject, 0, 100);
         }
         // If it's still valid move onwards and upwards.
         if ($custom_subject != '') {
             $all_messages = isset($_POST['enforce_subject']);
             if ($all_messages) {
                 // Get a response prefix, but in the forum's default language.
                 $context['response_prefix'] = response_prefix();
                 topicSubject($topic_info, $custom_subject, $context['response_prefix'], $all_messages);
             } else {
                 topicSubject($topic_info, $custom_subject);
             }
             // Fix the subject cache.
             updateStats('subject', $topic, $custom_subject);
         }
     }
     // Create a link to this in the old board.
     // @todo Does this make sense if the topic was unapproved before? I'd just about say so.
     if (isset($_POST['postRedirect'])) {
         // Should be in the boardwide language.
         if ($user_info['language'] != $language) {
             loadLanguage('index', $language);
         }
         $reason = Util::htmlspecialchars($_POST['reason'], ENT_QUOTES);
         preparsecode($reason);
         // Add a URL onto the message.
         $reason = strtr($reason, array($txt['movetopic_auto_board'] => '[url=' . $scripturl . '?board=' . $toboard . '.0]' . $board_info['name'] . '[/url]', $txt['movetopic_auto_topic'] => '[iurl]' . $scripturl . '?topic=' . $topic . '.0[/iurl]'));
         // Auto remove this MOVED redirection topic in the future?
         $redirect_expires = !empty($_POST['redirect_expires']) ? (int) $_POST['redirect_expires'] : 0;
         // Redirect to the MOVED topic from topic list?
         $redirect_topic = isset($_POST['redirect_topic']) ? $topic : 0;
         // And remember the last expiry period too.
         $_SESSION['move_to_topic']['redirect_topic'] = $redirect_topic;
         $_SESSION['move_to_topic']['redirect_expires'] = $redirect_expires;
         $msgOptions = array('subject' => $txt['moved'] . ': ' . $board_info['subject'], 'body' => $reason, 'icon' => 'moved', 'smileys_enabled' => 1);
         $topicOptions = array('board' => $board, 'lock_mode' => 1, 'mark_as_read' => true, 'redirect_expires' => empty($redirect_expires) ? 0 : $redirect_expires * 60 + time(), 'redirect_topic' => $redirect_topic);
         $posterOptions = array('id' => $user_info['id'], 'update_post_count' => empty($board_info['count_posts']));
         createPost($msgOptions, $topicOptions, $posterOptions);
     }
     $board_from = boardInfo($board);
     if ($board_from['count_posts'] != $board_info['count_posts']) {
         $posters = postersCount($topic);
         foreach ($posters as $id_member => $posts) {
             // The board we're moving from counted posts, but not to.
             if (empty($board_from['count_posts'])) {
                 updateMemberData($id_member, array('posts' => 'posts - ' . $posts));
             } else {
                 updateMemberData($id_member, array('posts' => 'posts + ' . $posts));
             }
         }
     }
     // Do the move (includes statistics update needed for the redirect topic).
     moveTopics($topic, $toboard);
     // Log that they moved this topic.
     if (!allowedTo('move_own') || $topic_info['id_member_started'] != $user_info['id']) {
         logAction('move', array('topic' => $topic, 'board_from' => $board, 'board_to' => $toboard));
     }
     // Notify people that this topic has been moved?
     require_once SUBSDIR . '/Notification.subs.php';
     sendNotifications($topic, 'move');
     // Why not go back to the original board in case they want to keep moving?
     if (!isset($_REQUEST['goback'])) {
         redirectexit('board=' . $board . '.0');
     } else {
         redirectexit('topic=' . $topic . '.0');
     }
 }
示例#4
0
    /**
     * Used to edit the body or subject of a message inline
     * called from action=jsmodify from script and topic js
     */
    public function action_jsmodify()
    {
        global $modSettings, $board, $topic;
        global $user_info, $context;
        $db = database();
        // We have to have a topic!
        if (empty($topic)) {
            obExit(false);
        }
        checkSession('get');
        require_once SUBSDIR . '/Post.subs.php';
        // Assume the first message if no message ID was given.
        $request = $db->query('', '
			SELECT
				t.locked, t.num_replies, t.id_member_started, t.id_first_msg,
				m.id_msg, m.id_member, m.poster_time, m.subject, m.smileys_enabled, m.body, m.icon,
				m.modified_time, m.modified_name, m.approved
			FROM {db_prefix}messages AS m
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})
			WHERE m.id_msg = {raw:id_msg}
				AND m.id_topic = {int:current_topic}' . (allowedTo('modify_any') || allowedTo('approve_posts') ? '' : (!$modSettings['postmod_active'] ? '
				AND (m.id_member != {int:guest_id} AND m.id_member = {int:current_member})' : '
				AND (m.approved = {int:is_approved} OR (m.id_member != {int:guest_id} AND m.id_member = {int:current_member}))')), array('current_member' => $user_info['id'], 'current_topic' => $topic, 'id_msg' => empty($_REQUEST['msg']) ? 't.id_first_msg' : (int) $_REQUEST['msg'], 'is_approved' => 1, 'guest_id' => 0));
        if ($db->num_rows($request) == 0) {
            fatal_lang_error('no_board', false);
        }
        $row = $db->fetch_assoc($request);
        $db->free_result($request);
        // Change either body or subject requires permissions to modify messages.
        if (isset($_POST['message']) || isset($_POST['subject']) || isset($_REQUEST['icon'])) {
            if (!empty($row['locked'])) {
                isAllowedTo('moderate_board');
            }
            if ($row['id_member'] == $user_info['id'] && !allowedTo('modify_any')) {
                if ((!$modSettings['postmod_active'] || $row['approved']) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + ($modSettings['edit_disable_time'] + 5) * 60 < time()) {
                    fatal_lang_error('modify_post_time_passed', false);
                } elseif ($row['id_member_started'] == $user_info['id'] && !allowedTo('modify_own')) {
                    isAllowedTo('modify_replies');
                } else {
                    isAllowedTo('modify_own');
                }
            } elseif ($row['id_member_started'] == $user_info['id'] && !allowedTo('modify_any')) {
                isAllowedTo('modify_replies');
            } else {
                isAllowedTo('modify_any');
            }
            // Only log this action if it wasn't your message.
            $moderationAction = $row['id_member'] != $user_info['id'];
        }
        $post_errors = Error_Context::context('post', 1);
        if (isset($_POST['subject']) && Util::htmltrim(Util::htmlspecialchars($_POST['subject'])) !== '') {
            $_POST['subject'] = strtr(Util::htmlspecialchars($_POST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
            // Maximum number of characters.
            if (Util::strlen($_POST['subject']) > 100) {
                $_POST['subject'] = Util::substr($_POST['subject'], 0, 100);
            }
        } elseif (isset($_POST['subject'])) {
            $post_errors->addError('no_subject');
            unset($_POST['subject']);
        }
        if (isset($_POST['message'])) {
            if (Util::htmltrim(Util::htmlspecialchars($_POST['message'])) === '') {
                $post_errors->addError('no_message');
                unset($_POST['message']);
            } elseif (!empty($modSettings['max_messageLength']) && Util::strlen($_POST['message']) > $modSettings['max_messageLength']) {
                $post_errors->addError(array('long_message', array($modSettings['max_messageLength'])));
                unset($_POST['message']);
            } else {
                $_POST['message'] = Util::htmlspecialchars($_POST['message'], ENT_QUOTES);
                preparsecode($_POST['message']);
                if (Util::htmltrim(strip_tags(parse_bbc($_POST['message'], false), '<img>')) === '') {
                    $post_errors->addError('no_message');
                    unset($_POST['message']);
                }
            }
        }
        if (isset($_POST['lock'])) {
            if (!allowedTo(array('lock_any', 'lock_own')) || !allowedTo('lock_any') && $user_info['id'] != $row['id_member']) {
                unset($_POST['lock']);
            } elseif (!allowedTo('lock_any')) {
                if ($row['locked'] == 1) {
                    unset($_POST['lock']);
                } else {
                    $_POST['lock'] = empty($_POST['lock']) ? 0 : 2;
                }
            } elseif (!empty($row['locked']) && !empty($_POST['lock']) || $_POST['lock'] == $row['locked']) {
                unset($_POST['lock']);
            } else {
                $_POST['lock'] = empty($_POST['lock']) ? 0 : 1;
            }
        }
        if (isset($_POST['sticky']) && !allowedTo('make_sticky')) {
            unset($_POST['sticky']);
        }
        if (!$post_errors->hasErrors()) {
            $msgOptions = array('id' => $row['id_msg'], 'subject' => isset($_POST['subject']) ? $_POST['subject'] : null, 'body' => isset($_POST['message']) ? $_POST['message'] : null, 'icon' => isset($_REQUEST['icon']) ? preg_replace('~[\\./\\\\*\':"<>]~', '', $_REQUEST['icon']) : null);
            $topicOptions = array('id' => $topic, 'board' => $board, 'lock_mode' => isset($_POST['lock']) ? (int) $_POST['lock'] : null, 'sticky_mode' => isset($_POST['sticky']) && !empty($modSettings['enableStickyTopics']) ? (int) $_POST['sticky'] : null, 'mark_as_read' => false);
            $posterOptions = array();
            // Only consider marking as editing if they have edited the subject, message or icon.
            if (isset($_POST['subject']) && $_POST['subject'] != $row['subject'] || isset($_POST['message']) && $_POST['message'] != $row['body'] || isset($_REQUEST['icon']) && $_REQUEST['icon'] != $row['icon']) {
                // And even then only if the time has passed...
                if (time() - $row['poster_time'] > $modSettings['edit_wait_time'] || $user_info['id'] != $row['id_member']) {
                    $msgOptions['modify_time'] = time();
                    $msgOptions['modify_name'] = $user_info['name'];
                }
            } else {
                $moderationAction = false;
            }
            modifyPost($msgOptions, $topicOptions, $posterOptions);
            // If we didn't change anything this time but had before put back the old info.
            if (!isset($msgOptions['modify_time']) && !empty($row['modified_time'])) {
                $msgOptions['modify_time'] = $row['modified_time'];
                $msgOptions['modify_name'] = $row['modified_name'];
            }
            // Changing the first subject updates other subjects to 'Re: new_subject'.
            if (isset($_POST['subject']) && isset($_REQUEST['change_all_subjects']) && $row['id_first_msg'] == $row['id_msg'] && !empty($row['num_replies']) && (allowedTo('modify_any') || $row['id_member_started'] == $user_info['id'] && allowedTo('modify_replies'))) {
                // Get the proper (default language) response prefix first.
                $context['response_prefix'] = response_prefix();
                $db->query('', '
					UPDATE {db_prefix}messages
					SET subject = {string:subject}
					WHERE id_topic = {int:current_topic}
						AND id_msg != {int:id_first_msg}', array('current_topic' => $topic, 'id_first_msg' => $row['id_first_msg'], 'subject' => $context['response_prefix'] . $_POST['subject']));
            }
            if (!empty($moderationAction)) {
                logAction('modify', array('topic' => $topic, 'message' => $row['id_msg'], 'member' => $row['id_member'], 'board' => $board));
            }
        }
        if (isset($_REQUEST['xml'])) {
            $context['sub_template'] = 'modifydone';
            if (!$post_errors->hasErrors() && isset($msgOptions['subject']) && isset($msgOptions['body'])) {
                $context['message'] = array('id' => $row['id_msg'], 'modified' => array('time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '', 'html_time' => isset($msgOptions['modify_time']) ? htmlTime($msgOptions['modify_time']) : '', 'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0, 'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : ''), 'subject' => $msgOptions['subject'], 'first_in_topic' => $row['id_msg'] == $row['id_first_msg'], 'body' => strtr($msgOptions['body'], array(']]>' => ']]]]><![CDATA[>')));
                censorText($context['message']['subject']);
                censorText($context['message']['body']);
                $context['message']['body'] = parse_bbc($context['message']['body'], $row['smileys_enabled'], $row['id_msg']);
            } elseif (!$post_errors->hasErrors()) {
                $context['sub_template'] = 'modifytopicdone';
                $context['message'] = array('id' => $row['id_msg'], 'modified' => array('time' => isset($msgOptions['modify_time']) ? standardTime($msgOptions['modify_time']) : '', 'html_time' => isset($msgOptions['modify_time']) ? htmlTime($msgOptions['modify_time']) : '', 'timestamp' => isset($msgOptions['modify_time']) ? forum_time(true, $msgOptions['modify_time']) : 0, 'name' => isset($msgOptions['modify_time']) ? $msgOptions['modify_name'] : ''), 'subject' => isset($msgOptions['subject']) ? $msgOptions['subject'] : '');
                censorText($context['message']['subject']);
            } else {
                $context['message'] = array('id' => $row['id_msg'], 'errors' => array(), 'error_in_subject' => $post_errors->hasError('no_subject'), 'error_in_body' => $post_errors->hasError('no_message') || $post_errors->hasError('long_message'));
                $context['message']['errors'] = $post_errors->prepareErrors();
            }
        } else {
            obExit(false);
        }
    }
 /**
  * Send a new personal message?
  */
 public function action_send()
 {
     global $txt, $scripturl, $modSettings, $context, $user_info;
     // Load in some text and template dependencies
     loadLanguage('PersonalMessage');
     loadTemplate('PersonalMessage');
     // Set the template we will use
     $context['sub_template'] = 'send';
     // Extract out the spam settings - cause it's neat.
     list($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
     // Set up some items for the template
     $context['page_title'] = $txt['send_message'];
     $context['reply'] = isset($_REQUEST['pmsg']) || isset($_REQUEST['quote']);
     // Check whether we've gone over the limit of messages we can send per hour.
     if (!empty($modSettings['pm_posts_per_hour']) && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) && $user_info['mod_cache']['bq'] == '0=1' && $user_info['mod_cache']['gq'] == '0=1') {
         // How many messages have they sent this last hour?
         $pmCount = pmCount($user_info['id'], 3600);
         if (!empty($pmCount) && $pmCount >= $modSettings['pm_posts_per_hour']) {
             fatal_lang_error('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour']));
         }
     }
     // Quoting / Replying to a message?
     if (!empty($_REQUEST['pmsg'])) {
         $pmsg = (int) $_REQUEST['pmsg'];
         // Make sure this is accessible (not deleted)
         if (!isAccessiblePM($pmsg)) {
             fatal_lang_error('no_access', false);
         }
         // Validate that this is one has been received?
         $isReceived = checkPMReceived($pmsg);
         // Get the quoted message (and make sure you're allowed to see this quote!).
         $row_quoted = loadPMQuote($pmsg, $isReceived);
         if ($row_quoted === false) {
             fatal_lang_error('pm_not_yours', false);
         }
         // Censor the message.
         censorText($row_quoted['subject']);
         censorText($row_quoted['body']);
         // Lets make sure we mark this one as read
         markMessages($pmsg);
         // Figure out which flavor or 'Re: ' to use
         $context['response_prefix'] = response_prefix();
         $form_subject = $row_quoted['subject'];
         // Add 'Re: ' to it....
         if ($context['reply'] && trim($context['response_prefix']) != '' && Util::strpos($form_subject, trim($context['response_prefix'])) !== 0) {
             $form_subject = $context['response_prefix'] . $form_subject;
         }
         // If quoting, lets clean up some things and set the quote header for the pm body
         if (isset($_REQUEST['quote'])) {
             // Remove any nested quotes and <br />...
             $form_message = preg_replace('~<br ?/?' . '>~i', "\n", $row_quoted['body']);
             if (!empty($modSettings['removeNestedQuotes'])) {
                 $form_message = preg_replace(array('~\\n?\\[quote.*?\\].+?\\[/quote\\]\\n?~is', '~^\\n~', '~\\[/quote\\]~'), '', $form_message);
             }
             if (empty($row_quoted['id_member'])) {
                 $form_message = '[quote author=&quot;' . $row_quoted['real_name'] . '&quot;]' . "\n" . $form_message . "\n" . '[/quote]';
             } else {
                 $form_message = '[quote author=' . $row_quoted['real_name'] . ' link=action=profile;u=' . $row_quoted['id_member'] . ' date=' . $row_quoted['msgtime'] . ']' . "\n" . $form_message . "\n" . '[/quote]';
             }
         } else {
             $form_message = '';
         }
         // Do the BBC thang on the message.
         $row_quoted['body'] = parse_bbc($row_quoted['body'], true, 'pm' . $row_quoted['id_pm']);
         // Set up the quoted message array.
         $context['quoted_message'] = array('id' => $row_quoted['id_pm'], 'pm_head' => $row_quoted['pm_head'], 'member' => array('name' => $row_quoted['real_name'], 'username' => $row_quoted['member_name'], 'id' => $row_quoted['id_member'], 'href' => !empty($row_quoted['id_member']) ? $scripturl . '?action=profile;u=' . $row_quoted['id_member'] : '', 'link' => !empty($row_quoted['id_member']) ? '<a href="' . $scripturl . '?action=profile;u=' . $row_quoted['id_member'] . '">' . $row_quoted['real_name'] . '</a>' : $row_quoted['real_name']), 'subject' => $row_quoted['subject'], 'time' => standardTime($row_quoted['msgtime']), 'html_time' => htmlTime($row_quoted['msgtime']), 'timestamp' => forum_time(true, $row_quoted['msgtime']), 'body' => $row_quoted['body']);
     } else {
         $context['quoted_message'] = false;
         $form_subject = '';
         $form_message = '';
     }
     // Start of like we don't know where this is going
     $context['recipients'] = array('to' => array(), 'bcc' => array());
     // Sending by ID?  Replying to all?  Fetch the real_name(s).
     if (isset($_REQUEST['u'])) {
         // If the user is replying to all, get all the other members this was sent to..
         if ($_REQUEST['u'] == 'all' && isset($row_quoted)) {
             // Firstly, to reply to all we clearly already have $row_quoted - so have the original member from.
             if ($row_quoted['id_member'] != $user_info['id']) {
                 $context['recipients']['to'][] = array('id' => $row_quoted['id_member'], 'name' => htmlspecialchars($row_quoted['real_name'], ENT_COMPAT, 'UTF-8'));
             }
             // Now to get all the others.
             $context['recipients']['to'] = array_merge($context['recipients']['to'], isset($pmsg) ? loadPMRecipientsAll($pmsg) : array());
         } else {
             $users = array_map('intval', explode(',', $_REQUEST['u']));
             $users = array_unique($users);
             // For all the member's this is going to, get their display name.
             require_once SUBSDIR . '/Members.subs.php';
             $result = getBasicMemberData($users);
             foreach ($result as $row) {
                 $context['recipients']['to'][] = array('id' => $row['id_member'], 'name' => $row['real_name']);
             }
         }
         // Get a literal name list in case the user has JavaScript disabled.
         $names = array();
         foreach ($context['recipients']['to'] as $to) {
             $names[] = $to['name'];
         }
         $context['to_value'] = empty($names) ? '' : '&quot;' . implode('&quot;, &quot;', $names) . '&quot;';
     } else {
         $context['to_value'] = '';
     }
     // Set the defaults...
     $context['subject'] = $form_subject;
     $context['message'] = str_replace(array('"', '<', '>', '&nbsp;'), array('&quot;', '&lt;', '&gt;', ' '), $form_message);
     // And build the link tree.
     $context['linktree'][] = array('url' => $scripturl . '?action=pm;sa=send', 'name' => $txt['new_message']);
     // If drafts are enabled, lets generate a list of drafts that they can load in to the editor
     if (!empty($context['drafts_pm_save'])) {
         $pm_seed = isset($_REQUEST['pmsg']) ? $_REQUEST['pmsg'] : (isset($_REQUEST['quote']) ? $_REQUEST['quote'] : 0);
         prepareDraftsContext($user_info['id'], $pm_seed);
     }
     // Needed for the editor.
     require_once SUBSDIR . '/Editor.subs.php';
     // Now create the editor.
     $editorOptions = array('id' => 'message', 'value' => $context['message'], 'height' => '250px', 'width' => '100%', 'labels' => array('post_button' => $txt['send_message']), 'preview_type' => 2);
     create_control_richedit($editorOptions);
     // No one is bcc'ed just yet
     $context['bcc_value'] = '';
     // Verification control needed for this PM?
     $context['require_verification'] = !$user_info['is_admin'] && !empty($modSettings['pm_posts_verification']) && $user_info['posts'] < $modSettings['pm_posts_verification'];
     if ($context['require_verification']) {
         require_once SUBSDIR . '/VerificationControls.class.php';
         $verificationOptions = array('id' => 'pm');
         $context['require_verification'] = create_control_verification($verificationOptions);
         $context['visual_verification_id'] = $verificationOptions['id'];
     }
     // Register this form and get a sequence number in $context.
     checkSubmitOnce('register');
 }
    /**
     * Set merge options and do the actual merge of two or more topics.
     *
     * the merge options screen:
     * * shows topics to be merged and allows to set some merge options.
     * * is accessed by ?action=mergetopics;sa=options.and can also internally be called by action_quickmod().
     * * uses 'merge_extra_options' sub template of the MergeTopics template.
     *
     * the actual merge:
     * * is accessed with ?action=mergetopics;sa=execute.
     * * updates the statistics to reflect the merge.
     * * logs the action in the moderation log.
     * * sends a notification is sent to all users monitoring this topic.
     * * redirects to ?action=mergetopics;sa=done.
     *
     * @param int[] $topics = array() of topic ids
     */
    public function action_mergeExecute($topics = array())
    {
        global $user_info, $txt, $context, $scripturl, $modSettings;
        $db = database();
        // Check the session.
        checkSession('request');
        require_once SUBSDIR . '/Topic.subs.php';
        require_once SUBSDIR . '/Post.subs.php';
        // Handle URLs from action_mergeIndex.
        if (!empty($_GET['from']) && !empty($_GET['to'])) {
            $topics = array((int) $_GET['from'], (int) $_GET['to']);
        }
        // If we came from a form, the topic IDs came by post.
        if (!empty($_POST['topics']) && is_array($_POST['topics'])) {
            $topics = $_POST['topics'];
        }
        // There's nothing to merge with just one topic...
        if (empty($topics) || !is_array($topics) || count($topics) == 1) {
            fatal_lang_error('merge_need_more_topics');
        }
        // Make sure every topic is numeric, or some nasty things could be done with the DB.
        foreach ($topics as $id => $topic) {
            $topics[$id] = (int) $topic;
        }
        // Joy of all joys, make sure they're not pi**ing about with unapproved topics they can't see :P
        if ($modSettings['postmod_active']) {
            $can_approve_boards = !empty($user_info['mod_cache']['ap']) ? $user_info['mod_cache']['ap'] : boardsAllowedTo('approve_posts');
        }
        // Get info about the topics and polls that will be merged.
        $request = $db->query('', '
			SELECT
				t.id_topic, t.id_board, t.id_poll, t.num_views, t.is_sticky, t.approved, t.num_replies, t.unapproved_posts,
				m1.subject, m1.poster_time AS time_started, IFNULL(mem1.id_member, 0) AS id_member_started, IFNULL(mem1.real_name, m1.poster_name) AS name_started,
				m2.poster_time AS time_updated, IFNULL(mem2.id_member, 0) AS id_member_updated, IFNULL(mem2.real_name, m2.poster_name) AS name_updated
			FROM {db_prefix}topics AS t
				INNER JOIN {db_prefix}messages AS m1 ON (m1.id_msg = t.id_first_msg)
				INNER JOIN {db_prefix}messages AS m2 ON (m2.id_msg = t.id_last_msg)
				LEFT JOIN {db_prefix}members AS mem1 ON (mem1.id_member = m1.id_member)
				LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = m2.id_member)
			WHERE t.id_topic IN ({array_int:topic_list})
			ORDER BY t.id_first_msg
			LIMIT ' . count($topics), array('topic_list' => $topics));
        if ($db->num_rows($request) < 2) {
            fatal_lang_error('no_topic_id');
        }
        $num_views = 0;
        $is_sticky = 0;
        $boardTotals = array();
        $topic_data = array();
        $boards = array();
        $polls = array();
        $firstTopic = 0;
        while ($row = $db->fetch_assoc($request)) {
            // Make a note for the board counts...
            if (!isset($boardTotals[$row['id_board']])) {
                $boardTotals[$row['id_board']] = array('num_posts' => 0, 'num_topics' => 0, 'unapproved_posts' => 0, 'unapproved_topics' => 0);
            }
            // We can't see unapproved topics here?
            if ($modSettings['postmod_active'] && !$row['approved'] && $can_approve_boards != array(0) && in_array($row['id_board'], $can_approve_boards)) {
                continue;
            } elseif (!$row['approved']) {
                $boardTotals[$row['id_board']]['unapproved_topics']++;
            } else {
                $boardTotals[$row['id_board']]['num_topics']++;
            }
            $boardTotals[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];
            $boardTotals[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? 1 : 0);
            $topic_data[$row['id_topic']] = array('id' => $row['id_topic'], 'board' => $row['id_board'], 'poll' => $row['id_poll'], 'num_views' => $row['num_views'], 'subject' => $row['subject'], 'started' => array('time' => standardTime($row['time_started']), 'html_time' => htmlTime($row['time_started']), 'timestamp' => forum_time(true, $row['time_started']), 'href' => empty($row['id_member_started']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_started'], 'link' => empty($row['id_member_started']) ? $row['name_started'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_started'] . '">' . $row['name_started'] . '</a>'), 'updated' => array('time' => standardTime($row['time_updated']), 'html_time' => htmlTime($row['time_updated']), 'timestamp' => forum_time(true, $row['time_updated']), 'href' => empty($row['id_member_updated']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_updated'], 'link' => empty($row['id_member_updated']) ? $row['name_updated'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_updated'] . '">' . $row['name_updated'] . '</a>'));
            $num_views += $row['num_views'];
            $boards[] = $row['id_board'];
            // If there's no poll, id_poll == 0...
            if ($row['id_poll'] > 0) {
                $polls[] = $row['id_poll'];
            }
            // Store the id_topic with the lowest id_first_msg.
            if (empty($firstTopic)) {
                $firstTopic = $row['id_topic'];
            }
            $is_sticky = max($is_sticky, $row['is_sticky']);
        }
        $db->free_result($request);
        // If we didn't get any topics then they've been messing with unapproved stuff.
        if (empty($topic_data)) {
            fatal_lang_error('no_topic_id');
        }
        $boards = array_values(array_unique($boards));
        // The parameters of action_mergeExecute were set, so this must've been an internal call.
        if (!empty($topics)) {
            isAllowedTo('merge_any', $boards);
            loadTemplate('MergeTopics');
        }
        // Get the boards a user is allowed to merge in.
        $merge_boards = boardsAllowedTo('merge_any');
        if (empty($merge_boards)) {
            fatal_lang_error('cannot_merge_any', 'user');
        }
        require_once SUBSDIR . '/Boards.subs.php';
        // Make sure they can see all boards....
        $query_boards = array('boards' => $boards);
        if (!in_array(0, $merge_boards)) {
            $query_boards['boards'] = array_merge($query_boards['boards'], $merge_boards);
        }
        // Saved in a variable to (potentially) save a query later
        $boards_info = fetchBoardsInfo($query_boards);
        // This happens when a member is moderator of a board he cannot see
        foreach ($boards as $board) {
            if (!isset($boards_info[$board])) {
                fatal_lang_error('no_board');
            }
        }
        if (empty($_REQUEST['sa']) || $_REQUEST['sa'] == 'options') {
            if (count($polls) > 1) {
                $request = $db->query('', '
					SELECT t.id_topic, t.id_poll, m.subject, p.question
					FROM {db_prefix}polls AS p
						INNER JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll)
						INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
					WHERE p.id_poll IN ({array_int:polls})
					LIMIT ' . count($polls), array('polls' => $polls));
                while ($row = $db->fetch_assoc($request)) {
                    $context['polls'][] = array('id' => $row['id_poll'], 'topic' => array('id' => $row['id_topic'], 'subject' => $row['subject']), 'question' => $row['question'], 'selected' => $row['id_topic'] == $firstTopic);
                }
                $db->free_result($request);
            }
            if (count($boards) > 1) {
                foreach ($boards_info as $row) {
                    $context['boards'][] = array('id' => $row['id_board'], 'name' => $row['name'], 'selected' => $row['id_board'] == $topic_data[$firstTopic]['board']);
                }
            }
            $context['topics'] = $topic_data;
            foreach ($topic_data as $id => $topic) {
                $context['topics'][$id]['selected'] = $topic['id'] == $firstTopic;
            }
            $context['page_title'] = $txt['merge'];
            $context['sub_template'] = 'merge_extra_options';
            return;
        }
        // Determine target board.
        $target_board = count($boards) > 1 ? (int) $_REQUEST['board'] : $boards[0];
        if (!in_array($target_board, $boards)) {
            fatal_lang_error('no_board');
        }
        // Determine which poll will survive and which polls won't.
        $target_poll = count($polls) > 1 ? (int) $_POST['poll'] : (count($polls) == 1 ? $polls[0] : 0);
        if ($target_poll > 0 && !in_array($target_poll, $polls)) {
            fatal_lang_error('no_access', false);
        }
        $deleted_polls = empty($target_poll) ? $polls : array_diff($polls, array($target_poll));
        // Determine the subject of the newly merged topic - was a custom subject specified?
        if (empty($_POST['subject']) && isset($_POST['custom_subject']) && $_POST['custom_subject'] != '') {
            $target_subject = strtr(Util::htmltrim(Util::htmlspecialchars($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => ''));
            // Keep checking the length.
            if (Util::strlen($target_subject) > 100) {
                $target_subject = Util::substr($target_subject, 0, 100);
            }
            // Nothing left - odd but pick the first topics subject.
            if ($target_subject == '') {
                $target_subject = $topic_data[$firstTopic]['subject'];
            }
        } elseif (!empty($topic_data[(int) $_POST['subject']]['subject'])) {
            $target_subject = $topic_data[(int) $_POST['subject']]['subject'];
        } else {
            $target_subject = $topic_data[$firstTopic]['subject'];
        }
        // Get the first and last message and the number of messages....
        $request = $db->query('', '
			SELECT approved, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg, COUNT(*) AS message_count
			FROM {db_prefix}messages
			WHERE id_topic IN ({array_int:topics})
			GROUP BY approved
			ORDER BY approved DESC', array('topics' => $topics));
        $topic_approved = 1;
        $first_msg = 0;
        while ($row = $db->fetch_assoc($request)) {
            // If this is approved, or is fully unapproved.
            if ($row['approved'] || !isset($first_msg)) {
                $first_msg = $row['first_msg'];
                $last_msg = $row['last_msg'];
                if ($row['approved']) {
                    $num_replies = $row['message_count'] - 1;
                    $num_unapproved = 0;
                } else {
                    $topic_approved = 0;
                    $num_replies = 0;
                    $num_unapproved = $row['message_count'];
                }
            } else {
                // If this has a lower first_msg then the first post is not approved and hence the number of replies was wrong!
                if ($first_msg > $row['first_msg']) {
                    $first_msg = $row['first_msg'];
                    $num_replies++;
                    $topic_approved = 0;
                }
                $num_unapproved = $row['message_count'];
            }
        }
        $db->free_result($request);
        // Ensure we have a board stat for the target board.
        if (!isset($boardTotals[$target_board])) {
            $boardTotals[$target_board] = array('num_posts' => 0, 'num_topics' => 0, 'unapproved_posts' => 0, 'unapproved_topics' => 0);
        }
        // Fix the topic count stuff depending on what the new one counts as.
        if ($topic_approved) {
            $boardTotals[$target_board]['num_topics']--;
        } else {
            $boardTotals[$target_board]['unapproved_topics']--;
        }
        $boardTotals[$target_board]['unapproved_posts'] -= $num_unapproved;
        $boardTotals[$target_board]['num_posts'] -= $topic_approved ? $num_replies + 1 : $num_replies;
        // Get the member ID of the first and last message.
        $request = $db->query('', '
			SELECT id_member
			FROM {db_prefix}messages
			WHERE id_msg IN ({int:first_msg}, {int:last_msg})
			ORDER BY id_msg
			LIMIT 2', array('first_msg' => $first_msg, 'last_msg' => $last_msg));
        list($member_started) = $db->fetch_row($request);
        list($member_updated) = $db->fetch_row($request);
        // First and last message are the same, so only row was returned.
        if ($member_updated === null) {
            $member_updated = $member_started;
        }
        $db->free_result($request);
        // Obtain all the message ids we are going to affect.
        $affected_msgs = messagesInTopics($topics);
        // Assign the first topic ID to be the merged topic.
        $id_topic = min($topics);
        // Grab the response prefix (like 'Re: ') in the default forum language.
        $context['response_prefix'] = response_prefix();
        $enforce_subject = isset($_POST['enforce_subject']) ? Util::htmlspecialchars(trim($_POST['enforce_subject'])) : '';
        // Merge topic notifications.
        $notifications = isset($_POST['notifications']) && is_array($_POST['notifications']) ? array_intersect($topics, $_POST['notifications']) : array();
        fixMergedTopics($first_msg, $topics, $id_topic, $target_board, $target_subject, $enforce_subject, $notifications);
        // Asssign the properties of the newly merged topic.
        $db->query('', '
			UPDATE {db_prefix}topics
			SET
				id_board = {int:id_board},
				id_member_started = {int:id_member_started},
				id_member_updated = {int:id_member_updated},
				id_first_msg = {int:id_first_msg},
				id_last_msg = {int:id_last_msg},
				id_poll = {int:id_poll},
				num_replies = {int:num_replies},
				unapproved_posts = {int:unapproved_posts},
				num_views = {int:num_views},
				is_sticky = {int:is_sticky},
				approved = {int:approved}
			WHERE id_topic = {int:id_topic}', array('id_board' => $target_board, 'is_sticky' => $is_sticky, 'approved' => $topic_approved, 'id_topic' => $id_topic, 'id_member_started' => $member_started, 'id_member_updated' => $member_updated, 'id_first_msg' => $first_msg, 'id_last_msg' => $last_msg, 'id_poll' => $target_poll, 'num_replies' => $num_replies, 'unapproved_posts' => $num_unapproved, 'num_views' => $num_views));
        // Get rid of the redundant polls.
        if (!empty($deleted_polls)) {
            require_once SUBSDIR . '/Poll.subs.php';
            removePoll($deleted_polls);
        }
        // Cycle through each board...
        foreach ($boardTotals as $id_board => $stats) {
            decrementBoard($id_board, $stats);
        }
        // Determine the board the final topic resides in
        $topic_info = getTopicInfo($id_topic);
        $id_board = $topic_info['id_board'];
        // Update all the statistics.
        updateStats('topic');
        updateStats('subject', $id_topic, $target_subject);
        updateLastMessages($boards);
        logAction('merge', array('topic' => $id_topic, 'board' => $id_board));
        // Notify people that these topics have been merged?
        require_once SUBSDIR . '/Notification.subs.php';
        sendNotifications($id_topic, 'merge');
        // If there's a search index that needs updating, update it...
        require_once SUBSDIR . '/Search.subs.php';
        $searchAPI = findSearchAPI();
        if (is_callable(array($searchAPI, 'topicMerge'))) {
            $searchAPI->topicMerge($id_topic, $topics, $affected_msgs, empty($enforce_subject) ? null : array($context['response_prefix'], $target_subject));
        }
        // Send them to the all done page.
        redirectexit('action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board);
    }