Example #1
0
/**
 * Modifying a post...
 *
 * @package Posts
 * @param mixed[] $msgOptions
 * @param mixed[] $topicOptions
 * @param mixed[] $posterOptions
 */
function modifyPost(&$msgOptions, &$topicOptions, &$posterOptions)
{
    global $user_info, $modSettings;
    $db = database();
    $topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null;
    $topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null;
    $topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null;
    // This is longer than it has to be, but makes it so we only set/change what we have to.
    $messages_columns = array();
    if (isset($posterOptions['name'])) {
        $messages_columns['poster_name'] = $posterOptions['name'];
    }
    if (isset($posterOptions['email'])) {
        $messages_columns['poster_email'] = $posterOptions['email'];
    }
    if (isset($msgOptions['icon'])) {
        $messages_columns['icon'] = $msgOptions['icon'];
    }
    if (isset($msgOptions['subject'])) {
        $messages_columns['subject'] = $msgOptions['subject'];
    }
    if (isset($msgOptions['body'])) {
        $messages_columns['body'] = $msgOptions['body'];
        // using a custom search index, then lets get the old message so we can update our index as needed
        if (!empty($modSettings['search_custom_index_config'])) {
            require_once SUBSDIR . '/Messages.subs.php';
            $message = basicMessageInfo($msgOptions['id'], true);
            $msgOptions['old_body'] = $message['body'];
        }
    }
    if (!empty($msgOptions['modify_time'])) {
        $messages_columns['modified_time'] = $msgOptions['modify_time'];
        $messages_columns['modified_name'] = $msgOptions['modify_name'];
        $messages_columns['id_msg_modified'] = $modSettings['maxMsgID'];
    }
    if (isset($msgOptions['smileys_enabled'])) {
        $messages_columns['smileys_enabled'] = empty($msgOptions['smileys_enabled']) ? 0 : 1;
    }
    // Which columns need to be ints?
    $messageInts = array('modified_time', 'id_msg_modified', 'smileys_enabled');
    $update_parameters = array('id_msg' => $msgOptions['id']);
    call_integration_hook('integrate_before_modify_post', array(&$messages_columns, &$update_parameters, &$msgOptions, &$topicOptions, &$posterOptions, &$messageInts));
    foreach ($messages_columns as $var => $val) {
        $messages_columns[$var] = $var . ' = {' . (in_array($var, $messageInts) ? 'int' : 'string') . ':var_' . $var . '}';
        $update_parameters['var_' . $var] = $val;
    }
    // Nothing to do?
    if (empty($messages_columns)) {
        return true;
    }
    // Change the post.
    $db->query('', '
		UPDATE {db_prefix}messages
		SET ' . implode(', ', $messages_columns) . '
		WHERE id_msg = {int:id_msg}', $update_parameters);
    // Lock and or sticky the post.
    if ($topicOptions['sticky_mode'] !== null || $topicOptions['lock_mode'] !== null || $topicOptions['poll'] !== null) {
        $db->query('', '
			UPDATE {db_prefix}topics
			SET
				is_sticky = {raw:is_sticky},
				locked = {raw:locked},
				id_poll = {raw:id_poll}
			WHERE id_topic = {int:id_topic}', array('is_sticky' => $topicOptions['sticky_mode'] === null ? 'is_sticky' : (int) $topicOptions['sticky_mode'], 'locked' => $topicOptions['lock_mode'] === null ? 'locked' : (int) $topicOptions['lock_mode'], 'id_poll' => $topicOptions['poll'] === null ? 'id_poll' : (int) $topicOptions['poll'], 'id_topic' => $topicOptions['id']));
    }
    // Mark the edited post as read.
    if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest']) {
        // Since it's likely they *read* it before editing, let's try an UPDATE first.
        $db->query('', '
			UPDATE {db_prefix}log_topics
			SET id_msg = {int:id_msg}
			WHERE id_member = {int:current_member}
				AND id_topic = {int:id_topic}', array('current_member' => $user_info['id'], 'id_msg' => $modSettings['maxMsgID'], 'id_topic' => $topicOptions['id']));
        $flag = $db->affected_rows() != 0;
        if (empty($flag)) {
            require_once SUBSDIR . '/Topic.subs.php';
            markTopicsRead(array($user_info['id'], $topicOptions['id'], $modSettings['maxMsgID'], 0), false);
        }
    }
    // If there's a custom search index, it needs to be modified...
    require_once SUBSDIR . '/Search.subs.php';
    $searchAPI = findSearchAPI();
    if (is_callable(array($searchAPI, 'postModified'))) {
        $searchAPI->postModified($msgOptions, $topicOptions, $posterOptions);
    }
    if (isset($msgOptions['subject'])) {
        // Only update the subject if this was the first message in the topic.
        $request = $db->query('', '
			SELECT id_topic
			FROM {db_prefix}topics
			WHERE id_first_msg = {int:id_first_msg}
			LIMIT 1', array('id_first_msg' => $msgOptions['id']));
        if ($db->num_rows($request) == 1) {
            updateStats('subject', $topicOptions['id'], $msgOptions['subject']);
        }
        $db->free_result($request);
    }
    // Finally, if we are setting the approved state we need to do much more work :(
    if ($modSettings['postmod_active'] && isset($msgOptions['approved'])) {
        approvePosts($msgOptions['id'], $msgOptions['approved']);
    }
    return true;
}
Example #2
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');
    }
Example #3
0
/**
 * Updates all the tables involved when two or more topics are merged
 *
 * @param int $first_msg the first message of the new topic
 * @param int[] $topics ids of all the topics merged
 * @param int $id_topic id of the merged topic
 * @param int $target_board id of the target board where the topic will resides
 * @param string $target_subject subject of the new topic
 * @param string $enforce_subject if not empty all the messages will be set to the same subject
 * @param int[] $notifications array of topics with active notifications
 */
function fixMergedTopics($first_msg, $topics, $id_topic, $target_board, $target_subject, $enforce_subject, $notifications)
{
    global $context;
    $db = database();
    // Delete the remaining topics.
    $deleted_topics = array_diff($topics, array($id_topic));
    $db->query('', '
		DELETE FROM {db_prefix}topics
		WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics));
    $db->query('', '
		DELETE FROM {db_prefix}log_search_subjects
		WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics));
    // Change the topic IDs of all messages that will be merged.  Also adjust subjects if 'enforce subject' was checked.
    $db->query('', '
		UPDATE {db_prefix}messages
		SET
			id_topic = {int:id_topic},
			id_board = {int:target_board}' . (empty($enforce_subject) ? '' : ',
			subject = {string:subject}') . '
		WHERE id_topic IN ({array_int:topic_list})', array('topic_list' => $topics, 'id_topic' => $id_topic, 'target_board' => $target_board, 'subject' => $context['response_prefix'] . $target_subject));
    // Any reported posts should reflect the new board.
    $db->query('', '
		UPDATE {db_prefix}log_reported
		SET
			id_topic = {int:id_topic},
			id_board = {int:target_board}
		WHERE id_topic IN ({array_int:topics_list})', array('topics_list' => $topics, 'id_topic' => $id_topic, 'target_board' => $target_board));
    // Change the subject of the first message...
    $db->query('', '
		UPDATE {db_prefix}messages
		SET subject = {string:target_subject}
		WHERE id_msg = {int:first_msg}', array('first_msg' => $first_msg, 'target_subject' => $target_subject));
    // Adjust all calendar events to point to the new topic.
    $db->query('', '
		UPDATE {db_prefix}calendar
		SET
			id_topic = {int:id_topic},
			id_board = {int:target_board}
		WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics, 'id_topic' => $id_topic, 'target_board' => $target_board));
    // Merge log topic entries.
    // The unwatched setting comes from the oldest topic
    $request = $db->query('', '
		SELECT id_member, MIN(id_msg) AS new_id_msg, unwatched
		FROM {db_prefix}log_topics
		WHERE id_topic IN ({array_int:topics})
		GROUP BY id_member', array('topics' => $topics));
    if ($db->num_rows($request) > 0) {
        $replaceEntries = array();
        while ($row = $db->fetch_assoc($request)) {
            $replaceEntries[] = array($row['id_member'], $id_topic, $row['new_id_msg'], $row['unwatched']);
        }
        markTopicsRead($replaceEntries, true);
        unset($replaceEntries);
        // Get rid of the old log entries.
        $db->query('', '
			DELETE FROM {db_prefix}log_topics
			WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics));
    }
    $db->free_result($request);
    if (!empty($notifications)) {
        $request = $db->query('', '
			SELECT id_member, MAX(sent) AS sent
			FROM {db_prefix}log_notify
			WHERE id_topic IN ({array_int:topics_list})
			GROUP BY id_member', array('topics_list' => $notifications));
        if ($db->num_rows($request) > 0) {
            $replaceEntries = array();
            while ($row = $db->fetch_assoc($request)) {
                $replaceEntries[] = array($row['id_member'], $id_topic, 0, $row['sent']);
            }
            $db->insert('replace', '{db_prefix}log_notify', array('id_member' => 'int', 'id_topic' => 'int', 'id_board' => 'int', 'sent' => 'int'), $replaceEntries, array('id_member', 'id_topic', 'id_board'));
            unset($replaceEntries);
            $db->query('', '
				DELETE FROM {db_prefix}log_topics
				WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics));
        }
        $db->free_result($request);
    }
}
Example #4
0
 /**
  * Mark a single topic as unread.
  * Accessed by action=markasread;sa=topic
  */
 public function action_marktopic()
 {
     global $board, $topic, $user_info;
     require_once SUBSDIR . '/Topic.subs.php';
     require_once SUBSDIR . '/Messages.subs.php';
     // Mark a topic unread.
     // First, let's figure out what the latest message is.
     $topicinfo = getTopicInfo($topic, 'all');
     $topic_msg_id = (int) $_GET['t'];
     if (!empty($topic_msg_id)) {
         // If they read the whole topic, go back to the beginning.
         if ($topic_msg_id >= $topicinfo['id_last_msg']) {
             $earlyMsg = 0;
         } elseif ($topic_msg_id <= $topicinfo['id_first_msg']) {
             $earlyMsg = 0;
         } else {
             $earlyMsg = previousMessage($topic_msg_id, $topic);
         }
     } elseif ($_REQUEST['start'] == 0) {
         $earlyMsg = 0;
     } else {
         list($earlyMsg) = messageAt((int) $_REQUEST['start'], $topic);
         $earlyMsg--;
     }
     // Blam, unread!
     markTopicsRead(array($user_info['id'], $topic, $earlyMsg, $topicinfo['unwatched']), true);
     return 'board=' . $board . '.0';
 }
Example #5
0
/**
 * Remove a specific message.
 * !! This includes permission checks.
 *
 * - normally, local and global should be the localCookies and globalCookies settings, respectively.
 * - uses boardurl to determine these two things.
 *
 * @param int $message The message id
 * @param bool $decreasePostCount if true users' post count will be reduced
 */
function removeMessage($message, $decreasePostCount = true)
{
    global $board, $modSettings, $user_info;
    $db = database();
    if (empty($message) || !is_numeric($message)) {
        return false;
    }
    $request = $db->query('', '
		SELECT
			m.id_member, m.icon, m.poster_time, m.subject,' . (empty($modSettings['search_custom_index_config']) ? '' : ' m.body,') . '
			m.approved, t.id_topic, t.id_first_msg, t.id_last_msg, t.num_replies, t.id_board,
			t.id_member_started AS id_member_poster,
			b.count_posts
		FROM {db_prefix}messages AS m
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
		WHERE m.id_msg = {int:id_msg}
		LIMIT 1', array('id_msg' => $message));
    if ($db->num_rows($request) == 0) {
        return false;
    }
    $row = $db->fetch_assoc($request);
    $db->free_result($request);
    if (empty($board) || $row['id_board'] != $board) {
        $delete_any = boardsAllowedTo('delete_any');
        if (!in_array(0, $delete_any) && !in_array($row['id_board'], $delete_any)) {
            $delete_own = boardsAllowedTo('delete_own');
            $delete_own = in_array(0, $delete_own) || in_array($row['id_board'], $delete_own);
            $delete_replies = boardsAllowedTo('delete_replies');
            $delete_replies = in_array(0, $delete_replies) || in_array($row['id_board'], $delete_replies);
            if ($row['id_member'] == $user_info['id']) {
                if (!$delete_own) {
                    if ($row['id_member_poster'] == $user_info['id']) {
                        if (!$delete_replies) {
                            fatal_lang_error('cannot_delete_replies', 'permission');
                        }
                    } else {
                        fatal_lang_error('cannot_delete_own', 'permission');
                    }
                } elseif (($row['id_member_poster'] != $user_info['id'] || !$delete_replies) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time()) {
                    fatal_lang_error('modify_post_time_passed', false);
                }
            } elseif ($row['id_member_poster'] == $user_info['id']) {
                if (!$delete_replies) {
                    fatal_lang_error('cannot_delete_replies', 'permission');
                }
            } else {
                fatal_lang_error('cannot_delete_any', 'permission');
            }
        }
        // Can't delete an unapproved message, if you can't see it!
        if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !(in_array(0, $delete_any) || in_array($row['id_board'], $delete_any))) {
            $approve_posts = !empty($user_info['mod_cache']['ap']) ? $user_info['mod_cache']['ap'] : boardsAllowedTo('approve_posts');
            if (!in_array(0, $approve_posts) && !in_array($row['id_board'], $approve_posts)) {
                return false;
            }
        }
    } else {
        // Check permissions to delete this message.
        if ($row['id_member'] == $user_info['id']) {
            if (!allowedTo('delete_own')) {
                if ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any')) {
                    isAllowedTo('delete_replies');
                } elseif (!allowedTo('delete_any')) {
                    isAllowedTo('delete_own');
                }
            } elseif (!allowedTo('delete_any') && ($row['id_member_poster'] != $user_info['id'] || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time()) {
                fatal_lang_error('modify_post_time_passed', false);
            }
        } elseif ($row['id_member_poster'] == $user_info['id'] && !allowedTo('delete_any')) {
            isAllowedTo('delete_replies');
        } else {
            isAllowedTo('delete_any');
        }
        if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $user_info['id'] && !allowedTo('delete_own')) {
            isAllowedTo('approve_posts');
        }
    }
    // Delete the *whole* topic, but only if the topic consists of one message.
    if ($row['id_first_msg'] == $message) {
        if (empty($board) || $row['id_board'] != $board) {
            $remove_any = boardsAllowedTo('remove_any');
            $remove_any = in_array(0, $remove_any) || in_array($row['id_board'], $remove_any);
            if (!$remove_any) {
                $remove_own = boardsAllowedTo('remove_own');
                $remove_own = in_array(0, $remove_own) || in_array($row['id_board'], $remove_own);
            }
            if ($row['id_member'] != $user_info['id'] && !$remove_any) {
                fatal_lang_error('cannot_remove_any', 'permission');
            } elseif (!$remove_any && !$remove_own) {
                fatal_lang_error('cannot_remove_own', 'permission');
            }
        } else {
            // Check permissions to delete a whole topic.
            if ($row['id_member'] != $user_info['id']) {
                isAllowedTo('remove_any');
            } elseif (!allowedTo('remove_any')) {
                isAllowedTo('remove_own');
            }
        }
        // ...if there is only one post.
        if (!empty($row['num_replies'])) {
            fatal_lang_error('delFirstPost', false);
        }
        // This needs to be included for topic functions
        require_once SUBSDIR . '/Topic.subs.php';
        removeTopics($row['id_topic']);
        return true;
    }
    // Deleting a recycled message can not lower anyone's post count.
    if ($row['icon'] == 'recycled') {
        $decreasePostCount = false;
    }
    // This is the last post, update the last post on the board.
    if ($row['id_last_msg'] == $message) {
        // Find the last message, set it, and decrease the post count.
        $request = $db->query('', '
			SELECT id_msg, id_member
			FROM {db_prefix}messages
			WHERE id_topic = {int:id_topic}
				AND id_msg != {int:id_msg}
			ORDER BY ' . ($modSettings['postmod_active'] ? 'approved DESC, ' : '') . 'id_msg DESC
			LIMIT 1', array('id_topic' => $row['id_topic'], 'id_msg' => $message));
        $row2 = $db->fetch_assoc($request);
        $db->free_result($request);
        $db->query('', '
			UPDATE {db_prefix}topics
			SET
				id_last_msg = {int:id_last_msg},
				id_member_updated = {int:id_member_updated}' . (!$modSettings['postmod_active'] || $row['approved'] ? ',
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ',
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
			WHERE id_topic = {int:id_topic}', array('id_last_msg' => $row2['id_msg'], 'id_member_updated' => $row2['id_member'], 'no_replies' => 0, 'no_unapproved' => 0, 'id_topic' => $row['id_topic']));
    } else {
        $db->query('', '
			UPDATE {db_prefix}topics
			SET ' . ($row['approved'] ? '
				num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : '
				unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
			WHERE id_topic = {int:id_topic}', array('no_replies' => 0, 'no_unapproved' => 0, 'id_topic' => $row['id_topic']));
    }
    // Default recycle to false.
    $recycle = false;
    // If recycle topics has been set, make a copy of this message in the recycle board.
    // Make sure we're not recycling messages that are already on the recycle board.
    if (!empty($modSettings['recycle_enable']) && $row['id_board'] != $modSettings['recycle_board'] && $row['icon'] != 'recycled') {
        // Check if the recycle board exists and if so get the read status.
        $request = $db->query('', '
			SELECT (IFNULL(lb.id_msg, 0) >= b.id_msg_updated) AS is_seen, id_last_msg
			FROM {db_prefix}boards AS b
				LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
			WHERE b.id_board = {int:recycle_board}', array('current_member' => $user_info['id'], 'recycle_board' => $modSettings['recycle_board']));
        if ($db->num_rows($request) == 0) {
            fatal_lang_error('recycle_no_valid_board');
        }
        list($isRead, $last_board_msg) = $db->fetch_row($request);
        $db->free_result($request);
        // Is there an existing topic in the recycle board to group this post with?
        $request = $db->query('', '
			SELECT id_topic, id_first_msg, id_last_msg
			FROM {db_prefix}topics
			WHERE id_previous_topic = {int:id_previous_topic}
				AND id_board = {int:recycle_board}', array('id_previous_topic' => $row['id_topic'], 'recycle_board' => $modSettings['recycle_board']));
        list($id_recycle_topic, $first_topic_msg, $last_topic_msg) = $db->fetch_row($request);
        $db->free_result($request);
        // Insert a new topic in the recycle board if $id_recycle_topic is empty.
        if (empty($id_recycle_topic)) {
            $db->insert('', '{db_prefix}topics', array('id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int', 'id_last_msg' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', 'id_previous_topic' => 'int'), array($modSettings['recycle_board'], $row['id_member'], $row['id_member'], $message, $message, 0, 1, $row['id_topic']), array('id_topic'));
        }
        // Capture the ID of the new topic...
        $topicID = empty($id_recycle_topic) ? $db->insert_id('{db_prefix}topics', 'id_topic') : $id_recycle_topic;
        // If the topic creation went successful, move the message.
        if ($topicID > 0) {
            $db->query('', '
				UPDATE {db_prefix}messages
				SET
					id_topic = {int:id_topic},
					id_board = {int:recycle_board},
					icon = {string:recycled},
					approved = {int:is_approved}
				WHERE id_msg = {int:id_msg}', array('id_topic' => $topicID, 'recycle_board' => $modSettings['recycle_board'], 'id_msg' => $message, 'recycled' => 'recycled', 'is_approved' => 1));
            // Take any reported posts with us...
            $db->query('', '
				UPDATE {db_prefix}log_reported
				SET
					id_topic = {int:id_topic},
					id_board = {int:recycle_board}
				WHERE id_msg = {int:id_msg}', array('id_topic' => $topicID, 'recycle_board' => $modSettings['recycle_board'], 'id_msg' => $message));
            // Mark recycled topic as read.
            if (!$user_info['is_guest']) {
                require_once SUBSDIR . '/Topic.subs.php';
                markTopicsRead(array($user_info['id'], $topicID, $modSettings['maxMsgID'], 0), true);
            }
            // Mark recycle board as seen, if it was marked as seen before.
            if (!empty($isRead) && !$user_info['is_guest']) {
                require_once SUBSDIR . '/Boards.subs.php';
                markBoardsRead($modSettings['recycle_board']);
            }
            // Add one topic and post to the recycle bin board.
            $db->query('', '
				UPDATE {db_prefix}boards
				SET
					num_topics = num_topics + {int:num_topics_inc},
					num_posts = num_posts + 1' . ($message > $last_board_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . '
				WHERE id_board = {int:recycle_board}', array('num_topics_inc' => empty($id_recycle_topic) ? 1 : 0, 'recycle_board' => $modSettings['recycle_board'], 'id_merged_msg' => $message));
            // Lets increase the num_replies, and the first/last message ID as appropriate.
            if (!empty($id_recycle_topic)) {
                $db->query('', '
					UPDATE {db_prefix}topics
					SET num_replies = num_replies + 1' . ($message > $last_topic_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . ($message < $first_topic_msg ? ', id_first_msg = {int:id_merged_msg}' : '') . '
					WHERE id_topic = {int:id_recycle_topic}', array('id_recycle_topic' => $id_recycle_topic, 'id_merged_msg' => $message));
            }
            // Make sure this message isn't getting deleted later on.
            $recycle = true;
            // Make sure we update the search subject index.
            updateSubjectStats($topicID, $row['subject']);
        }
        // If it wasn't approved don't keep it in the queue.
        if (!$row['approved']) {
            $db->query('', '
				DELETE FROM {db_prefix}approval_queue
				WHERE id_msg = {int:id_msg}
					AND id_attach = {int:id_attach}', array('id_msg' => $message, 'id_attach' => 0));
        }
    }
    $db->query('', '
		UPDATE {db_prefix}boards
		SET ' . ($row['approved'] ? '
			num_posts = CASE WHEN num_posts = {int:no_posts} THEN 0 ELSE num_posts - 1 END' : '
			unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . '
		WHERE id_board = {int:id_board}', array('no_posts' => 0, 'no_unapproved' => 0, 'id_board' => $row['id_board']));
    // If the poster was registered and the board this message was on incremented
    // the member's posts when it was posted, decrease his or her post count.
    if (!empty($row['id_member']) && $decreasePostCount && empty($row['count_posts']) && $row['approved']) {
        updateMemberData($row['id_member'], array('posts' => '-'));
    }
    // Only remove posts if they're not recycled.
    if (!$recycle) {
        // Remove the likes!
        $db->query('', '
			DELETE FROM {db_prefix}message_likes
			WHERE id_msg = {int:id_msg}', array('id_msg' => $message));
        // Remove the mentions!
        $db->query('', '
			DELETE FROM {db_prefix}log_mentions
			WHERE id_msg = {int:id_msg}', array('id_msg' => $message));
        // Remove the message!
        $db->query('', '
			DELETE FROM {db_prefix}messages
			WHERE id_msg = {int:id_msg}', array('id_msg' => $message));
        if (!empty($modSettings['search_custom_index_config'])) {
            $customIndexSettings = unserialize($modSettings['search_custom_index_config']);
            $words = text2words($row['body'], $customIndexSettings['bytes_per_word'], true);
            if (!empty($words)) {
                $db->query('', '
					DELETE FROM {db_prefix}log_search_words
					WHERE id_word IN ({array_int:word_list})
						AND id_msg = {int:id_msg}', array('word_list' => $words, 'id_msg' => $message));
            }
        }
        // Delete attachment(s) if they exist.
        require_once SUBSDIR . '/ManageAttachments.subs.php';
        $attachmentQuery = array('attachment_type' => 0, 'id_msg' => $message);
        removeAttachments($attachmentQuery);
        // Delete follow-ups too
        require_once SUBSDIR . '/FollowUps.subs.php';
        // If it is an entire topic
        if ($row['id_first_msg'] == $message) {
            $db->query('', '
				DELETE FROM {db_prefix}follow_ups
				WHERE follow_ups IN ({array_int:topics})', array('topics' => $row['id_topic']));
        }
        // Allow mods to remove message related data of their own (likes, maybe?)
        call_integration_hook('integrate_remove_message', array($message));
    }
    // Update the pesky statistics.
    updateMessageStats();
    updateStats('topic');
    updateSettings(array('calendar_updated' => time()));
    // And now to update the last message of each board we messed with.
    require_once SUBSDIR . '/Post.subs.php';
    if ($recycle) {
        updateLastMessages(array($row['id_board'], $modSettings['recycle_board']));
    } else {
        updateLastMessages($row['id_board']);
    }
    // Close any moderation reports for this message.
    require_once SUBSDIR . '/Moderation.subs.php';
    $updated_reports = updateReportsStatus($message, 'close', 1);
    if ($updated_reports != 0) {
        updateSettings(array('last_mod_report_action' => time()));
        recountOpenReports();
    }
    return false;
}
    /**
     * Allows for moderation from the message index.
     * @todo refactor this...
     */
    public function action_quickmod()
    {
        global $board, $user_info, $modSettings, $context;
        $db = database();
        // Check the session = get or post.
        checkSession('request');
        // Lets go straight to the restore area.
        if (isset($_REQUEST['qaction']) && $_REQUEST['qaction'] == 'restore' && !empty($_REQUEST['topics'])) {
            redirectexit('action=restoretopic;topics=' . implode(',', $_REQUEST['topics']) . ';' . $context['session_var'] . '=' . $context['session_id']);
        }
        if (isset($_SESSION['topicseen_cache'])) {
            $_SESSION['topicseen_cache'] = array();
        }
        // This is going to be needed to send off the notifications and for updateLastMessages().
        require_once SUBSDIR . '/Post.subs.php';
        require_once SUBSDIR . '/Notification.subs.php';
        // Process process process data.
        require_once SUBSDIR . '/Topic.subs.php';
        // Remember the last board they moved things to.
        if (isset($_REQUEST['move_to'])) {
            $_SESSION['move_to_topic'] = array('move_to' => $_REQUEST['move_to'], 'redirect_topic' => !empty($_REQUEST['redirect_topic']) ? (int) $_REQUEST['redirect_topic'] : 0, 'redirect_expires' => !empty($_REQUEST['redirect_expires']) ? (int) $_REQUEST['redirect_expires'] : 0);
        }
        // Only a few possible actions.
        $possibleActions = array();
        if (!empty($board)) {
            $boards_can = array('make_sticky' => allowedTo('make_sticky') ? array($board) : array(), 'move_any' => allowedTo('move_any') ? array($board) : array(), 'move_own' => allowedTo('move_own') ? array($board) : array(), 'remove_any' => allowedTo('remove_any') ? array($board) : array(), 'remove_own' => allowedTo('remove_own') ? array($board) : array(), 'lock_any' => allowedTo('lock_any') ? array($board) : array(), 'lock_own' => allowedTo('lock_own') ? array($board) : array(), 'merge_any' => allowedTo('merge_any') ? array($board) : array(), 'approve_posts' => allowedTo('approve_posts') ? array($board) : array());
            $redirect_url = 'board=' . $board . '.' . $_REQUEST['start'];
        } else {
            $boards_can = boardsAllowedTo(array('make_sticky', 'move_any', 'move_own', 'remove_any', 'remove_own', 'lock_any', 'lock_own', 'merge_any', 'approve_posts'), true, false);
            $redirect_url = isset($_POST['redirect_url']) ? $_POST['redirect_url'] : (isset($_SESSION['old_url']) ? $_SESSION['old_url'] : '');
        }
        if (!$user_info['is_guest']) {
            $possibleActions[] = 'markread';
        }
        if (!empty($boards_can['make_sticky']) && !empty($modSettings['enableStickyTopics'])) {
            $possibleActions[] = 'sticky';
        }
        if (!empty($boards_can['move_any']) || !empty($boards_can['move_own'])) {
            $possibleActions[] = 'move';
        }
        if (!empty($boards_can['remove_any']) || !empty($boards_can['remove_own'])) {
            $possibleActions[] = 'remove';
        }
        if (!empty($boards_can['lock_any']) || !empty($boards_can['lock_own'])) {
            $possibleActions[] = 'lock';
        }
        if (!empty($boards_can['merge_any'])) {
            $possibleActions[] = 'merge';
        }
        if (!empty($boards_can['approve_posts'])) {
            $possibleActions[] = 'approve';
        }
        // Two methods: $_REQUEST['actions'] (id_topic => action), and $_REQUEST['topics'] and $_REQUEST['qaction'].
        // (if action is 'move', $_REQUEST['move_to'] or $_REQUEST['move_tos'][$topic] is used.)
        if (!empty($_REQUEST['topics'])) {
            // If the action isn't valid, just quit now.
            if (empty($_REQUEST['qaction']) || !in_array($_REQUEST['qaction'], $possibleActions)) {
                redirectexit($redirect_url);
            }
            // Merge requires all topics as one parameter and can be done at once.
            if ($_REQUEST['qaction'] == 'merge') {
                // Merge requires at least two topics.
                if (empty($_REQUEST['topics']) || count($_REQUEST['topics']) < 2) {
                    redirectexit($redirect_url);
                }
                require_once CONTROLLERDIR . '/MergeTopics.controller.php';
                $controller = new MergeTopics_Controller();
                return $controller->action_mergeExecute($_REQUEST['topics']);
            }
            // Just convert to the other method, to make it easier.
            foreach ($_REQUEST['topics'] as $topic) {
                $_REQUEST['actions'][(int) $topic] = $_REQUEST['qaction'];
            }
        }
        // Weird... how'd you get here?
        if (empty($_REQUEST['actions'])) {
            redirectexit($redirect_url);
        }
        // Validate each action.
        $temp = array();
        foreach ($_REQUEST['actions'] as $topic => $action) {
            if (in_array($action, $possibleActions)) {
                $temp[(int) $topic] = $action;
            }
        }
        $_REQUEST['actions'] = $temp;
        if (!empty($_REQUEST['actions'])) {
            // Find all topics...
            $topics_info = topicsDetails(array_keys($_REQUEST['actions']));
            foreach ($topics_info as $row) {
                if (!empty($board)) {
                    if ($row['id_board'] != $board || $modSettings['postmod_active'] && !$row['approved'] && !allowedTo('approve_posts')) {
                        unset($_REQUEST['actions'][$row['id_topic']]);
                    }
                } else {
                    // Don't allow them to act on unapproved posts they can't see...
                    if ($modSettings['postmod_active'] && !$row['approved'] && !in_array(0, $boards_can['approve_posts']) && !in_array($row['id_board'], $boards_can['approve_posts'])) {
                        unset($_REQUEST['actions'][$row['id_topic']]);
                    } elseif ($_REQUEST['actions'][$row['id_topic']] == 'sticky' && !in_array(0, $boards_can['make_sticky']) && !in_array($row['id_board'], $boards_can['make_sticky'])) {
                        unset($_REQUEST['actions'][$row['id_topic']]);
                    } elseif ($_REQUEST['actions'][$row['id_topic']] == 'move' && !in_array(0, $boards_can['move_any']) && !in_array($row['id_board'], $boards_can['move_any']) && ($row['id_member_started'] != $user_info['id'] || !in_array(0, $boards_can['move_own']) && !in_array($row['id_board'], $boards_can['move_own']))) {
                        unset($_REQUEST['actions'][$row['id_topic']]);
                    } elseif ($_REQUEST['actions'][$row['id_topic']] == 'remove' && !in_array(0, $boards_can['remove_any']) && !in_array($row['id_board'], $boards_can['remove_any']) && ($row['id_member_started'] != $user_info['id'] || !in_array(0, $boards_can['remove_own']) && !in_array($row['id_board'], $boards_can['remove_own']))) {
                        unset($_REQUEST['actions'][$row['id_topic']]);
                    } elseif ($_REQUEST['actions'][$row['id_topic']] == 'lock' && !in_array(0, $boards_can['lock_any']) && !in_array($row['id_board'], $boards_can['lock_any']) && ($row['id_member_started'] != $user_info['id'] || $row['locked'] == 1 || !in_array(0, $boards_can['lock_own']) && !in_array($row['id_board'], $boards_can['lock_own']))) {
                        unset($_REQUEST['actions'][$row['id_topic']]);
                    } elseif ($_REQUEST['actions'][$row['id_topic']] == 'approve' && (!$row['unapproved_posts'] || !in_array(0, $boards_can['approve_posts']) && !in_array($row['id_board'], $boards_can['approve_posts']))) {
                        unset($_REQUEST['actions'][$row['id_topic']]);
                    }
                }
            }
        }
        $stickyCache = array();
        $moveCache = array(0 => array(), 1 => array());
        $removeCache = array();
        $lockCache = array();
        $markCache = array();
        $approveCache = array();
        // Separate the actions.
        foreach ($_REQUEST['actions'] as $topic => $action) {
            $topic = (int) $topic;
            if ($action == 'markread') {
                $markCache[] = $topic;
            } elseif ($action == 'sticky') {
                $stickyCache[] = $topic;
            } elseif ($action == 'move') {
                moveTopicConcurrence();
                // $moveCache[0] is the topic, $moveCache[1] is the board to move to.
                $moveCache[1][$topic] = (int) (isset($_REQUEST['move_tos'][$topic]) ? $_REQUEST['move_tos'][$topic] : $_REQUEST['move_to']);
                if (empty($moveCache[1][$topic])) {
                    continue;
                }
                $moveCache[0][] = $topic;
            } elseif ($action == 'remove') {
                $removeCache[] = $topic;
            } elseif ($action == 'lock') {
                $lockCache[] = $topic;
            } elseif ($action == 'approve') {
                $approveCache[] = $topic;
            }
        }
        if (empty($board)) {
            $affectedBoards = array();
        } else {
            $affectedBoards = array($board => array(0, 0));
        }
        // Do all the stickies...
        if (!empty($stickyCache)) {
            toggleTopicSticky($stickyCache);
            // Get the board IDs and Sticky status
            $request = $db->query('', '
				SELECT id_topic, id_board, is_sticky
				FROM {db_prefix}topics
				WHERE id_topic IN ({array_int:sticky_topic_ids})
				LIMIT ' . count($stickyCache), array('sticky_topic_ids' => $stickyCache));
            $stickyCacheBoards = array();
            $stickyCacheStatus = array();
            while ($row = $db->fetch_assoc($request)) {
                $stickyCacheBoards[$row['id_topic']] = $row['id_board'];
                $stickyCacheStatus[$row['id_topic']] = empty($row['is_sticky']);
            }
            $db->free_result($request);
        }
        // Move sucka! (this is, by the by, probably the most complicated part....)
        if (!empty($moveCache[0])) {
            // I know - I just KNOW you're trying to beat the system.  Too bad for you... we CHECK :P.
            $request = $db->query('', '
				SELECT t.id_topic, t.id_board, b.count_posts
				FROM {db_prefix}topics AS t
					LEFT JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board)
				WHERE t.id_topic IN ({array_int:move_topic_ids})' . (!empty($board) && !allowedTo('move_any') ? '
					AND t.id_member_started = {int:current_member}' : '') . '
				LIMIT ' . count($moveCache[0]), array('current_member' => $user_info['id'], 'move_topic_ids' => $moveCache[0]));
            $moveTos = array();
            $moveCache2 = array();
            $countPosts = array();
            while ($row = $db->fetch_assoc($request)) {
                $to = $moveCache[1][$row['id_topic']];
                if (empty($to)) {
                    continue;
                }
                // Does this topic's board count the posts or not?
                $countPosts[$row['id_topic']] = empty($row['count_posts']);
                if (!isset($moveTos[$to])) {
                    $moveTos[$to] = array();
                }
                $moveTos[$to][] = $row['id_topic'];
                // For reporting...
                $moveCache2[] = array($row['id_topic'], $row['id_board'], $to);
            }
            $db->free_result($request);
            $moveCache = $moveCache2;
            // Do the actual moves...
            foreach ($moveTos as $to => $topics) {
                moveTopics($topics, $to);
            }
            // Does the post counts need to be updated?
            if (!empty($moveTos)) {
                require_once SUBSDIR . '/Boards.subs.php';
                $topicRecounts = array();
                $boards_info = fetchBoardsInfo(array('boards' => array_keys($moveTos)), array('selects' => 'posts'));
                foreach ($boards_info as $row) {
                    $cp = empty($row['count_posts']);
                    // Go through all the topics that are being moved to this board.
                    foreach ($moveTos[$row['id_board']] as $topic) {
                        // If both boards have the same value for post counting then no adjustment needs to be made.
                        if ($countPosts[$topic] != $cp) {
                            // If the board being moved to does count the posts then the other one doesn't so add to their post count.
                            $topicRecounts[$topic] = $cp ? 1 : -1;
                        }
                    }
                }
                if (!empty($topicRecounts)) {
                    // Get all the members who have posted in the moved topics.
                    $posters = topicsPosters(array_keys($topicRecounts));
                    foreach ($posters as $id_member => $topics) {
                        $post_adj = 0;
                        foreach ($topics as $id_topic) {
                            $post_adj += $topicRecounts[$id_topic];
                        }
                        // And now update that member's post counts
                        if (!empty($post_adj)) {
                            updateMemberData($id_member, array('posts' => 'posts + ' . $post_adj));
                        }
                    }
                }
            }
        }
        // Now delete the topics...
        if (!empty($removeCache)) {
            // They can only delete their own topics. (we wouldn't be here if they couldn't do that..)
            $result = $db->query('', '
				SELECT id_topic, id_board
				FROM {db_prefix}topics
				WHERE id_topic IN ({array_int:removed_topic_ids})' . (!empty($board) && !allowedTo('remove_any') ? '
					AND id_member_started = {int:current_member}' : '') . '
				LIMIT ' . count($removeCache), array('current_member' => $user_info['id'], 'removed_topic_ids' => $removeCache));
            $removeCache = array();
            $removeCacheBoards = array();
            while ($row = $db->fetch_assoc($result)) {
                $removeCache[] = $row['id_topic'];
                $removeCacheBoards[$row['id_topic']] = $row['id_board'];
            }
            $db->free_result($result);
            // Maybe *none* were their own topics.
            if (!empty($removeCache)) {
                // Gotta send the notifications *first*!
                foreach ($removeCache as $topic) {
                    // Only log the topic ID if it's not in the recycle board.
                    logAction('remove', array(empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $removeCacheBoards[$topic] ? 'topic' : 'old_topic_id' => $topic, 'board' => $removeCacheBoards[$topic]));
                    sendNotifications($topic, 'remove');
                }
                removeTopics($removeCache);
            }
        }
        // Approve the topics...
        if (!empty($approveCache)) {
            // We need unapproved topic ids and their authors!
            $request = $db->query('', '
				SELECT id_topic, id_member_started
				FROM {db_prefix}topics
				WHERE id_topic IN ({array_int:approve_topic_ids})
					AND approved = {int:not_approved}
				LIMIT ' . count($approveCache), array('approve_topic_ids' => $approveCache, 'not_approved' => 0));
            $approveCache = array();
            $approveCacheMembers = array();
            while ($row = $db->fetch_assoc($request)) {
                $approveCache[] = $row['id_topic'];
                $approveCacheMembers[$row['id_topic']] = $row['id_member_started'];
            }
            $db->free_result($request);
            // Any topics to approve?
            if (!empty($approveCache)) {
                // Handle the approval part...
                approveTopics($approveCache);
                // Time for some logging!
                foreach ($approveCache as $topic) {
                    logAction('approve_topic', array('topic' => $topic, 'member' => $approveCacheMembers[$topic]));
                }
            }
        }
        // And (almost) lastly, lock the topics...
        if (!empty($lockCache)) {
            $lockStatus = array();
            // Gotta make sure they CAN lock/unlock these topics...
            if (!empty($board) && !allowedTo('lock_any')) {
                // Make sure they started the topic AND it isn't already locked by someone with higher priv's.
                $result = $db->query('', '
					SELECT id_topic, locked, id_board
					FROM {db_prefix}topics
					WHERE id_topic IN ({array_int:locked_topic_ids})
						AND id_member_started = {int:current_member}
						AND locked IN (2, 0)
					LIMIT ' . count($lockCache), array('current_member' => $user_info['id'], 'locked_topic_ids' => $lockCache));
                $lockCache = array();
                $lockCacheBoards = array();
                while ($row = $db->fetch_assoc($result)) {
                    $lockCache[] = $row['id_topic'];
                    $lockCacheBoards[$row['id_topic']] = $row['id_board'];
                    $lockStatus[$row['id_topic']] = empty($row['locked']);
                }
                $db->free_result($result);
            } else {
                $result = $db->query('', '
					SELECT id_topic, locked, id_board
					FROM {db_prefix}topics
					WHERE id_topic IN ({array_int:locked_topic_ids})
					LIMIT ' . count($lockCache), array('locked_topic_ids' => $lockCache));
                $lockCacheBoards = array();
                while ($row = $db->fetch_assoc($result)) {
                    $lockStatus[$row['id_topic']] = empty($row['locked']);
                    $lockCacheBoards[$row['id_topic']] = $row['id_board'];
                }
                $db->free_result($result);
            }
            // It could just be that *none* were their own topics...
            if (!empty($lockCache)) {
                // Alternate the locked value.
                $db->query('', '
					UPDATE {db_prefix}topics
					SET locked = CASE WHEN locked = {int:is_locked} THEN ' . (allowedTo('lock_any') ? '1' : '2') . ' ELSE 0 END
					WHERE id_topic IN ({array_int:locked_topic_ids})', array('locked_topic_ids' => $lockCache, 'is_locked' => 0));
            }
        }
        if (!empty($markCache)) {
            $logged_topics = getLoggedTopics($user_info['id'], $markCache);
            $markArray = array();
            foreach ($markCache as $topic) {
                $markArray[] = array($user_info['id'], $topic, $modSettings['maxMsgID'], (int) (!empty($logged_topics[$topic])));
            }
            markTopicsRead($markArray, true);
        }
        foreach ($moveCache as $topic) {
            // Didn't actually move anything!
            if (!isset($topic[0])) {
                break;
            }
            logAction('move', array('topic' => $topic[0], 'board_from' => $topic[1], 'board_to' => $topic[2]));
            sendNotifications($topic[0], 'move');
        }
        foreach ($lockCache as $topic) {
            logAction($lockStatus[$topic] ? 'lock' : 'unlock', array('topic' => $topic, 'board' => $lockCacheBoards[$topic]));
            sendNotifications($topic, $lockStatus[$topic] ? 'lock' : 'unlock');
        }
        foreach ($stickyCache as $topic) {
            logAction($stickyCacheStatus[$topic] ? 'unsticky' : 'sticky', array('topic' => $topic, 'board' => $stickyCacheBoards[$topic]));
            sendNotifications($topic, 'sticky');
        }
        updateStats('topic');
        updateStats('message');
        updateSettings(array('calendar_updated' => time()));
        if (!empty($affectedBoards)) {
            updateLastMessages(array_keys($affectedBoards));
        }
        redirectexit($redirect_url);
    }