<?php

/**
 * The contents of this file handle the deletion of topics, posts, and related
 * paraphernalia.
 *
 * Simple Machines Forum (SMF)
 *
 * @package SMF
 * @author Simple Machines https://www.simplemachines.org
 * @copyright 2025 Simple Machines and individual contributors
 * @license https://www.simplemachines.org/about/smf/license.php BSD
 *
 * @version 2.1.7
 */

if (!defined('SMF'))
	die('No direct access...');

/*	The contents of this file handle the deletion of topics, posts, and related
	paraphernalia.  It has the following functions:

*/

/**
 * Completely remove an entire topic.
 * Redirects to the board when completed.
 */
function RemoveTopic2()
{
	global $user_info, $topic, $board, $sourcedir, $smcFunc, $modSettings;

	// Make sure they aren't being lead around by someone. (:@)
	checkSession('get');

	// This file needs to be included for sendNotifications().
	require_once($sourcedir . '/Subs-Post.php');

	// Trying to fool us around, are we?
	if (empty($topic))
		redirectexit();

	removeDeleteConcurrence();

	$request = $smcFunc['db_query']('', '
		SELECT t.id_member_started, ms.subject, t.approved, t.locked
		FROM {db_prefix}topics AS t
			INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
		WHERE t.id_topic = {int:current_topic}
		LIMIT 1',
		array(
			'current_topic' => $topic,
		)
	);
	list ($starter, $subject, $approved, $locked) = $smcFunc['db_fetch_row']($request);
	$smcFunc['db_free_result']($request);

	if ($starter == $user_info['id'] && !allowedTo('remove_any'))
		isAllowedTo('remove_own');
	else
		isAllowedTo('remove_any');

	// Can they see the topic?
	if ($modSettings['postmod_active'] && !$approved && $starter != $user_info['id'])
		isAllowedTo('approve_posts');

	// Ok, we got that far, but is it locked?
	if ($locked)
	{
		if (!($locked == 1 && $starter == $user_info['id'] || allowedTo('lock_any')))
			fatal_lang_error('cannot_remove_locked', 'user');
	}

	// Notify people that this topic has been removed.
	sendNotifications($topic, 'remove');

	removeTopics($topic);

	// Note, only log topic ID in native form if it's not gone forever.
	if (allowedTo('remove_any') || (allowedTo('remove_own') && $starter == $user_info['id']))
		logAction('remove', array((empty($modSettings['recycle_enable']) || $modSettings['recycle_board'] != $board ? 'topic' : 'old_topic_id') => $topic, 'subject' => $subject, 'member' => $starter, 'board' => $board));

	redirectexit('board=' . $board . '.0');
}

/**
 * Remove just a single post.
 * On completion redirect to the topic or to the board.
 */
function DeleteMessage()
{
	global $user_info, $topic, $board, $modSettings, $smcFunc;

	checkSession('get');

	$_REQUEST['msg'] = (int) $_REQUEST['msg'];

	// Is $topic set?
	if (empty($topic) && isset($_REQUEST['topic']))
		$topic = (int) $_REQUEST['topic'];

	removeDeleteConcurrence();

	$request = $smcFunc['db_query']('', '
		SELECT t.id_member_started, m.id_member, m.subject, m.poster_time, m.approved
		FROM {db_prefix}topics AS t
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = {int:id_msg} AND m.id_topic = {int:current_topic})
		WHERE t.id_topic = {int:current_topic}
		LIMIT 1',
		array(
			'current_topic' => $topic,
			'id_msg' => $_REQUEST['msg'],
		)
	);
	list ($starter, $poster, $subject, $post_time, $approved) = $smcFunc['db_fetch_row']($request);
	$smcFunc['db_free_result']($request);

	// Verify they can see this!
	if ($modSettings['postmod_active'] && !$approved && !empty($poster) && $poster != $user_info['id'])
		isAllowedTo('approve_posts');

	if ($poster == $user_info['id'])
	{
		if (!allowedTo('delete_own'))
		{
			if ($starter == $user_info['id'] && !allowedTo('delete_any'))
				isAllowedTo('delete_replies');
			elseif (!allowedTo('delete_any'))
				isAllowedTo('delete_own');
		}
		elseif (!allowedTo('delete_any') && ($starter != $user_info['id'] || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $post_time + $modSettings['edit_disable_time'] * 60 < time())
			fatal_lang_error('modify_post_time_passed', false);
	}
	elseif ($starter == $user_info['id'] && !allowedTo('delete_any'))
		isAllowedTo('delete_replies');
	else
		isAllowedTo('delete_any');

	// If the full topic was removed go back to the board.
	$full_topic = removeMessage($_REQUEST['msg']);

	if (allowedTo('delete_any') && (!allowedTo('delete_own') || $poster != $user_info['id']))
		logAction('delete', array('topic' => $topic, 'subject' => $subject, 'member' => $poster, 'board' => $board));

	// We want to redirect back to recent action.
	if (isset($_REQUEST['modcenter']))
		redirectexit('action=moderate;area=reportedposts;done');
	elseif (isset($_REQUEST['recent']))
		redirectexit('action=recent');
	elseif (isset($_REQUEST['profile'], $_REQUEST['start'], $_REQUEST['u']))
		redirectexit('action=profile;u=' . $_REQUEST['u'] . ';area=showposts;start=' . $_REQUEST['start']);
	elseif ($full_topic)
		redirectexit('board=' . $board . '.0');
	else
		redirectexit('topic=' . $topic . '.' . $_REQUEST['start']);
}

/**
 * So long as you are sure... all old posts will be gone.
 * Used in ManageMaintenance.php to prune old topics.
 */
function RemoveOldTopics2()
{
	global $smcFunc;

	isAllowedTo('admin_forum');
	checkSession('post', 'admin');

	// No boards at all?  Forget it then :/.
	if (empty($_POST['boards']))
		redirectexit('action=admin;area=maintain;sa=topics');

	// This should exist, but we can make sure.
	$_POST['delete_type'] = isset($_POST['delete_type']) ? $_POST['delete_type'] : 'nothing';

	// Custom conditions.
	$condition = '';
	$condition_params = array(
		'boards' => array_keys($_POST['boards']),
		'poster_time' => time() - 3600 * 24 * $_POST['maxdays'],
	);

	// Just moved notice topics?
	// Note that this ignores redirection topics unless it's a non-expiring one
	if ($_POST['delete_type'] == 'moved')
	{
		$condition .= '
			AND m.icon = {string:icon}
			AND t.locked = {int:locked}
			AND t.redirect_expires = {int:not_expiring}';
		$condition_params['icon'] = 'moved';
		$condition_params['locked'] = 1;
		$condition_params['not_expiring'] = 0;
	}
	// Otherwise, maybe locked topics only?
	elseif ($_POST['delete_type'] == 'locked')
	{
		// Exclude moved/merged notices since we have another option for those...
		$condition .= '
			AND t.icon != {string:icon}
			AND t.locked = {int:locked}';
		$condition_params['icon'] = 'moved';
		$condition_params['locked'] = 1;
	}

	// Exclude stickies?
	if (isset($_POST['delete_old_not_sticky']))
	{
		$condition .= '
			AND t.is_sticky = {int:is_sticky}';
		$condition_params['is_sticky'] = 0;
	}

	// All we're gonna do here is grab the id_topic's and send them to removeTopics().
	$request = $smcFunc['db_query']('', '
		SELECT t.id_topic
		FROM {db_prefix}topics AS t
			INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_last_msg)
		WHERE
			m.poster_time < {int:poster_time}' . $condition . '
			AND t.id_board IN ({array_int:boards})',
		$condition_params
	);
	$topics = array();
	while ($row = $smcFunc['db_fetch_assoc']($request))
		$topics[] = $row['id_topic'];
	$smcFunc['db_free_result']($request);

	removeTopics($topics, false, true);

	// Log an action into the moderation log.
	logAction('pruned', array('days' => $_POST['maxdays']));

	redirectexit('action=admin;area=maintain;sa=topics;done=purgeold');
}

/**
 * Removes the passed id_topic's. (permissions are NOT checked here!).
 *
 * @param array|int $topics The topics to remove (can be an id or an array of ids).
 * @param bool $decreasePostCount Whether to decrease the users' post counts
 * @param bool $ignoreRecycling Whether to ignore recycling board settings
 * @param bool $updateBoardCount Whether to adjust topic counts for the boards
 */
function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = false, $updateBoardCount = true)
{
	global $sourcedir, $modSettings, $smcFunc;

	// Nothing to do?
	if (empty($topics))
		return;
	// Only a single topic.
	if (is_numeric($topics))
		$topics = array($topics);

	$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;

	// Do something before?
	call_integration_hook('integrate_remove_topics_before', array($topics, $recycle_board));

	// Decrease the post counts.
	if ($decreasePostCount)
	{
		$requestMembers = $smcFunc['db_query']('', '
			SELECT m.id_member, COUNT(*) AS posts
			FROM {db_prefix}messages AS m
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
			WHERE m.id_topic IN ({array_int:topics})' . (!empty($recycle_board) ? '
				AND m.id_board != {int:recycled_board}' : '') . '
				AND b.count_posts = {int:do_count_posts}
				AND m.approved = {int:is_approved}
			GROUP BY m.id_member',
			array(
				'do_count_posts' => 0,
				'recycled_board' => $recycle_board,
				'topics' => $topics,
				'is_approved' => 1,
			)
		);
		if ($smcFunc['db_num_rows']($requestMembers) > 0)
		{
			while ($rowMembers = $smcFunc['db_fetch_assoc']($requestMembers))
				updateMemberData($rowMembers['id_member'], array('posts' => 'posts - ' . $rowMembers['posts']));
		}
		$smcFunc['db_free_result']($requestMembers);
	}

	// Recycle topics that aren't in the recycle board...
	if (!empty($recycle_board) && !$ignoreRecycling)
	{
		$request = $smcFunc['db_query']('', '
			SELECT id_topic, id_board, unapproved_posts, approved
			FROM {db_prefix}topics
			WHERE id_topic IN ({array_int:topics})
				AND id_board != {int:recycle_board}
			LIMIT {int:limit}',
			array(
				'recycle_board' => $recycle_board,
				'topics' => $topics,
				'limit' => count($topics),
			)
		);
		if ($smcFunc['db_num_rows']($request) > 0)
		{
			// Get topics that will be recycled.
			$recycleTopics = array();
			while ($row = $smcFunc['db_fetch_assoc']($request))
			{
				if (function_exists('apache_reset_timeout'))
					@apache_reset_timeout();

				$recycleTopics[] = $row['id_topic'];

				// Set the id_previous_board for this topic - and make it not sticky.
				$smcFunc['db_query']('', '
					UPDATE {db_prefix}topics
					SET id_previous_board = {int:id_previous_board}, is_sticky = {int:not_sticky}
					WHERE id_topic = {int:id_topic}',
					array(
						'id_previous_board' => $row['id_board'],
						'id_topic' => $row['id_topic'],
						'not_sticky' => 0,
					)
				);
			}
			$smcFunc['db_free_result']($request);

			// Move the topics to the recycle board.
			require_once($sourcedir . '/MoveTopic.php');
			moveTopics($recycleTopics, $modSettings['recycle_board']);

			// Close reports that are being recycled.
			require_once($sourcedir . '/ModerationCenter.php');

			$smcFunc['db_query']('', '
				UPDATE {db_prefix}log_reported
				SET closed = {int:is_closed}
				WHERE id_topic IN ({array_int:recycle_topics})',
				array(
					'recycle_topics' => $recycleTopics,
					'is_closed' => 1,
				)
			);

			updateSettings(array('last_mod_report_action' => time()));

			require_once($sourcedir . '/Subs-ReportedContent.php');
			recountOpenReports('posts');

			// Topics that were recycled don't need to be deleted, so subtract them.
			$topics = array_diff($topics, $recycleTopics);
		}
		else
			$smcFunc['db_free_result']($request);
	}

	// Still topics left to delete?
	if (empty($topics))
		return;

	// Callback for search APIs to do their thing
	require_once($sourcedir . '/Search.php');
	$searchAPI = findSearchAPI();
	if ($searchAPI->supportsMethod('topicsRemoved'))
		$searchAPI->topicsRemoved($topics);

	$adjustBoards = array();

	// Find out how many posts we are deleting.
	$request = $smcFunc['db_query']('', '
		SELECT id_board, approved, COUNT(*) AS num_topics, SUM(unapproved_posts) AS unapproved_posts,
			SUM(num_replies) AS num_replies
		FROM {db_prefix}topics
		WHERE id_topic IN ({array_int:topics})
		GROUP BY id_board, approved',
		array(
			'topics' => $topics,
		)
	);
	while ($row = $smcFunc['db_fetch_assoc']($request))
	{
		if (!isset($adjustBoards[$row['id_board']]['num_posts']))
		{
			$adjustBoards[$row['id_board']] = array(
				'num_posts' => 0,
				'num_topics' => 0,
				'unapproved_posts' => 0,
				'unapproved_topics' => 0,
				'id_board' => $row['id_board']
			);
		}
		// Posts = (num_replies + 1) for each approved topic.
		$adjustBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0);
		$adjustBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts'];

		// Add the topics to the right type.
		if ($row['approved'])
			$adjustBoards[$row['id_board']]['num_topics'] += $row['num_topics'];
		else
			$adjustBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics'];
	}
	$smcFunc['db_free_result']($request);

	if ($updateBoardCount)
	{
		// Decrease the posts/topics...
		foreach ($adjustBoards as $stats)
		{
			if (function_exists('apache_reset_timeout'))
				@apache_reset_timeout();

			$smcFunc['db_query']('', '
				UPDATE {db_prefix}boards
				SET
					num_posts = CASE WHEN {int:num_posts} > num_posts THEN 0 ELSE num_posts - {int:num_posts} END,
					num_topics = CASE WHEN {int:num_topics} > num_topics THEN 0 ELSE num_topics - {int:num_topics} END,
					unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END,
					unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END
				WHERE id_board = {int:id_board}',
				array(
					'id_board' => $stats['id_board'],
					'num_posts' => $stats['num_posts'],
					'num_topics' => $stats['num_topics'],
					'unapproved_posts' => $stats['unapproved_posts'],
					'unapproved_topics' => $stats['unapproved_topics'],
				)
			);
		}
	}
	// Remove Polls.
	$request = $smcFunc['db_query']('', '
		SELECT id_poll
		FROM {db_prefix}topics
		WHERE id_topic IN ({array_int:topics})
			AND id_poll > {int:no_poll}
		LIMIT {int:limit}',
		array(
			'no_poll' => 0,
			'topics' => $topics,
			'limit' => count($topics),
		)
	);
	$polls = array();
	while ($row = $smcFunc['db_fetch_assoc']($request))
		$polls[] = $row['id_poll'];
	$smcFunc['db_free_result']($request);

	if (!empty($polls))
	{
		$smcFunc['db_query']('', '
			DELETE FROM {db_prefix}polls
			WHERE id_poll IN ({array_int:polls})',
			array(
				'polls' => $polls,
			)
		);
		$smcFunc['db_query']('', '
			DELETE FROM {db_prefix}poll_choices
			WHERE id_poll IN ({array_int:polls})',
			array(
				'polls' => $polls,
			)
		);
		$smcFunc['db_query']('', '
			DELETE FROM {db_prefix}log_polls
			WHERE id_poll IN ({array_int:polls})',
			array(
				'polls' => $polls,
			)
		);
	}

	// Get rid of the attachment, if it exists.
	require_once($sourcedir . '/ManageAttachments.php');
	$attachmentQuery = array(
		'attachment_type' => 0,
		'id_topic' => $topics,
	);
	removeAttachments($attachmentQuery, 'messages');

	// Delete possible search index entries.
	if (!empty($modSettings['search_custom_index_config']))
	{
		$customIndexSettings = $smcFunc['json_decode']($modSettings['search_custom_index_config'], true);

		$words = array();
		$messages = array();
		$request = $smcFunc['db_query']('', '
			SELECT id_msg, body
			FROM {db_prefix}messages
			WHERE id_topic IN ({array_int:topics})',
			array(
				'topics' => $topics,
			)
		);
		while ($row = $smcFunc['db_fetch_assoc']($request))
		{
			if (function_exists('apache_reset_timeout'))
				@apache_reset_timeout();

			$words = array_merge($words, text2words($row['body'], $customIndexSettings['bytes_per_word'], true));
			$messages[] = $row['id_msg'];
		}
		$smcFunc['db_free_result']($request);
		$words = array_unique($words);

		if (!empty($words) && !empty($messages))
			$smcFunc['db_query']('', '
				DELETE FROM {db_prefix}log_search_words
				WHERE id_word IN ({array_int:word_list})
					AND id_msg IN ({array_int:message_list})',
				array(
					'word_list' => $words,
					'message_list' => $messages,
				)
			);
	}

	// Delete anything related to the topic.
	$smcFunc['db_query']('', '
		DELETE FROM {db_prefix}messages
		WHERE id_topic IN ({array_int:topics})',
		array(
			'topics' => $topics,
		)
	);
	$smcFunc['db_query']('', '
		DELETE FROM {db_prefix}calendar
		WHERE id_topic IN ({array_int:topics})',
		array(
			'topics' => $topics,
		)
	);
	$smcFunc['db_query']('', '
		DELETE FROM {db_prefix}log_topics
		WHERE id_topic IN ({array_int:topics})',
		array(
			'topics' => $topics,
		)
	);
	$smcFunc['db_query']('', '
		DELETE FROM {db_prefix}log_notify
		WHERE id_topic IN ({array_int:topics})',
		array(
			'topics' => $topics,
		)
	);
	$smcFunc['db_query']('', '
		DELETE FROM {db_prefix}topics
		WHERE id_topic IN ({array_int:topics})',
		array(
			'topics' => $topics,
		)
	);
	$smcFunc['db_query']('', '
		DELETE FROM {db_prefix}log_search_subjects
		WHERE id_topic IN ({array_int:topics})',
		array(
			'topics' => $topics,
		)
	);

	// Maybe there's a mod that wants to delete topic related data of its own
	call_integration_hook('integrate_remove_topics', array($topics));

	// Update the totals...
	updateStats('message');
	updateStats('topic');
	updateSettings(array(
		'calendar_updated' => time(),
	));
	if (!empty($cache_enable) && $cache_enable >= 3) {
		cache_put_data('board-' . $recycle_board, null);
		foreach ($adjustBoards as $stats) {
			cache_put_data('board-' . $stats['id_board'], null);
		}
	}

	require_once($sourcedir . '/Subs-Post.php');
	$updates = array();
	foreach ($adjustBoards as $stats)
		$updates[] = $stats['id_board'];
	updateLastMessages($updates);
}

/**
 * Remove a specific message (including permission checks).
 *
 * @param int $message The message id
 * @param bool $decreasePostCount Whether to decrease users' post counts
 * @return bool Whether the operation succeeded
 */
function removeMessage($message, $decreasePostCount = true)
{
	global $board, $sourcedir, $modSettings, $user_info, $smcFunc;

	if (empty($message) || !is_numeric($message))
		return false;

	$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;

	$request = $smcFunc['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 ($smcFunc['db_num_rows']($request) == 0)
		return false;

	$row = $smcFunc['db_fetch_assoc']($request);
	$smcFunc['db_free_result']($request);

	// Give mods a heads-up before we do anything.
	call_integration_hook('integrate_pre_remove_message', array($message, $decreasePostCount, $row));

	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 = 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);

		removeTopics($row['id_topic']);
		return true;
	}

	// Deleting a recycled message can not lower anyone's post count.
	if (!empty($recycle_board) && $row['id_board'] == $recycle_board)
		$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 = $smcFunc['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 = $smcFunc['db_fetch_assoc']($request);
		$smcFunc['db_free_result']($request);

		$smcFunc['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'],
			)
		);
	}
	// Only decrease post counts.
	else
		$smcFunc['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 = $smcFunc['db_query']('', '
			SELECT (COALESCE(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 ($smcFunc['db_num_rows']($request) == 0)
			fatal_lang_error('recycle_no_valid_board');
		list ($isRead, $last_board_msg) = $smcFunc['db_fetch_row']($request);
		$smcFunc['db_free_result']($request);

		// Is there an existing topic in the recycle board to group this post with?
		$request = $smcFunc['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) = $smcFunc['db_fetch_row']($request);
		$smcFunc['db_free_result']($request);

		// Insert a new topic in the recycle board if $id_recycle_topic is empty.
		if (empty($id_recycle_topic))
			$id_topic = $smcFunc['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'),
				1
			);

		// Capture the ID of the new topic...
		$topicID = empty($id_recycle_topic) ? $id_topic : $id_recycle_topic;

		// If the topic creation went successful, move the message.
		if ($topicID > 0)
		{
			$smcFunc['db_query']('', '
				UPDATE {db_prefix}messages
				SET
					id_topic = {int:id_topic},
					id_board = {int:recycle_board},
					approved = {int:is_approved}
				WHERE id_msg = {int:id_msg}',
				array(
					'id_topic' => $topicID,
					'recycle_board' => $modSettings['recycle_board'],
					'id_msg' => $message,
					'is_approved' => 1,
				)
			);

			// Take any reported posts with us...
			$smcFunc['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'])
				$smcFunc['db_insert']('replace',
					'{db_prefix}log_topics',
					array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int', 'unwatched' => 'int'),
					array($topicID, $user_info['id'], $modSettings['maxMsgID'], 0),
					array('id_topic', 'id_member')
				);

			// Mark recycle board as seen, if it was marked as seen before.
			if (!empty($isRead) && !$user_info['is_guest'])
				$smcFunc['db_insert']('replace',
					'{db_prefix}log_boards',
					array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
					array($modSettings['recycle_board'], $user_info['id'], $modSettings['maxMsgID']),
					array('id_board', 'id_member')
				);

			// Add one topic and post to the recycle bin board.
			$smcFunc['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))
				$smcFunc['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.
			updateStats('subject', $topicID, $row['subject']);
		}

		// If it wasn't approved don't keep it in the queue.
		if (!$row['approved'])
			$smcFunc['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,
				)
			);
	}

	$smcFunc['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)
	{
		// Callback for search APIs to do their thing
		require_once($sourcedir . '/Search.php');
		$searchAPI = findSearchAPI();
		if ($searchAPI->supportsMethod('postRemoved'))
			$searchAPI->postRemoved($message);

		// Remove the message!
		$smcFunc['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 = $smcFunc['json_decode']($modSettings['search_custom_index_config'], true);
			$words = text2words($row['body'], $customIndexSettings['bytes_per_word'], true);
			if (!empty($words))
				$smcFunc['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($sourcedir . '/ManageAttachments.php');
		$attachmentQuery = array(
			'attachment_type' => 0,
			'id_msg' => $message,
		);
		removeAttachments($attachmentQuery);
	}

	// Allow mods to remove message related data of their own (likes, maybe?)
	call_integration_hook('integrate_remove_message', array($message, $row, $recycle));

	// Update the pesky statistics.
	updateStats('message');
	updateStats('topic');
	updateSettings(array(
		'calendar_updated' => time(),
	));

	// And now to update the last message of each board we messed with.
	require_once($sourcedir . '/Subs-Post.php');
	if ($recycle)
		updateLastMessages(array($row['id_board'], $modSettings['recycle_board']));
	else
		updateLastMessages($row['id_board']);

	// Close any moderation reports for this message.
	$smcFunc['db_query']('', '
		UPDATE {db_prefix}log_reported
		SET closed = {int:is_closed}
		WHERE id_msg = {int:id_msg}',
		array(
			'is_closed' => 1,
			'id_msg' => $message,
		)
	);
	if ($smcFunc['db_affected_rows']() != 0)
	{
		require_once($sourcedir . '/Subs-ReportedContent.php');
		updateSettings(array('last_mod_report_action' => time()));
		recountOpenReports('posts');
	}

	return false;
}

/**
 * Move back a topic from the recycle board to its original board.
 */
function RestoreTopic()
{
	global $smcFunc, $modSettings, $sourcedir;

	// Check session.
	checkSession('get');

	// Is recycled board enabled?
	if (empty($modSettings['recycle_enable']))
		fatal_lang_error('restored_disabled', 'critical');

	// Can we be in here?
	isAllowedTo('move_any', $modSettings['recycle_board']);

	// We need this file.
	require_once($sourcedir . '/MoveTopic.php');

	$unfound_messages = array();
	$topics_to_restore = array();

	// Restoring messages?
	if (!empty($_REQUEST['msgs']))
	{
		$msgs = explode(',', $_REQUEST['msgs']);
		foreach ($msgs as $k => $msg)
			$msgs[$k] = (int) $msg;

		// Get the id_previous_board and id_previous_topic.
		$request = $smcFunc['db_query']('', '
			SELECT m.id_topic, m.id_msg, m.id_board, m.subject, m.id_member, t.id_previous_board, t.id_previous_topic,
				t.id_first_msg, b.count_posts, COALESCE(pt.id_board, 0) AS possible_prev_board
			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 = m.id_board)
				LEFT JOIN {db_prefix}topics AS pt ON (pt.id_topic = t.id_previous_topic)
			WHERE m.id_msg IN ({array_int:messages})',
			array(
				'messages' => $msgs,
			)
		);

		$actioned_messages = array();
		$previous_topics = array();
		while ($row = $smcFunc['db_fetch_assoc']($request))
		{
			// Restoring the first post means topic.
			if ($row['id_msg'] == $row['id_first_msg'] && $row['id_previous_topic'] == $row['id_topic'])
			{
				$topics_to_restore[] = $row['id_topic'];
				continue;
			}
			// Don't know where it's going?
			if (empty($row['id_previous_topic']))
			{
				$unfound_messages[$row['id_msg']] = $row['subject'];
				continue;
			}

			$previous_topics[] = $row['id_previous_topic'];
			if (empty($actioned_messages[$row['id_previous_topic']]))
				$actioned_messages[$row['id_previous_topic']] = array(
					'msgs' => array(),
					'count_posts' => $row['count_posts'],
					'subject' => $row['subject'],
					'previous_board' => $row['id_previous_board'],
					'possible_prev_board' => $row['possible_prev_board'],
					'current_topic' => $row['id_topic'],
					'current_board' => $row['id_board'],
					'members' => array(),
				);

			$actioned_messages[$row['id_previous_topic']]['msgs'][$row['id_msg']] = $row['subject'];
			if ($row['id_member'])
				$actioned_messages[$row['id_previous_topic']]['members'][] = $row['id_member'];
		}
		$smcFunc['db_free_result']($request);

		// Check for topics we are going to fully restore.
		foreach ($actioned_messages as $topic => $data)
			if (in_array($topic, $topics_to_restore))
				unset($actioned_messages[$topic]);

		// Load any previous topics to check they exist.
		if (!empty($previous_topics))
		{
			$request = $smcFunc['db_query']('', '
				SELECT t.id_topic, t.id_board, m.subject
				FROM {db_prefix}topics AS t
					INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
				WHERE t.id_topic IN ({array_int:previous_topics})',
				array(
					'previous_topics' => $previous_topics,
				)
			);
			$previous_topics = array();
			while ($row = $smcFunc['db_fetch_assoc']($request))
				$previous_topics[$row['id_topic']] = array(
					'board' => $row['id_board'],
					'subject' => $row['subject'],
				);
			$smcFunc['db_free_result']($request);
		}

		// Restore each topic.
		$messages = array();
		foreach ($actioned_messages as $topic => $data)
		{
			// If we have topics we are going to restore the whole lot ignore them.
			if (in_array($topic, $topics_to_restore))
			{
				unset($actioned_messages[$topic]);
				continue;
			}

			// Move the posts back then!
			if (isset($previous_topics[$topic]))
			{
				mergePosts(array_keys($data['msgs']), $data['current_topic'], $topic);
				// Log em.
				logAction('restore_posts', array('topic' => $topic, 'subject' => $previous_topics[$topic]['subject'], 'board' => empty($data['previous_board']) ? $data['possible_prev_board'] : $data['previous_board']));
				$messages = array_merge(array_keys($data['msgs']), $messages);
			}
			else
			{
				foreach ($data['msgs'] as $msg)
					$unfound_messages[$msg['id']] = $msg['subject'];
			}
		}
	}

	// Now any topics?
	if (!empty($_REQUEST['topics']))
	{
		$topics = explode(',', $_REQUEST['topics']);
		foreach ($topics as $id)
			$topics_to_restore[] = (int) $id;
	}

	if (!empty($topics_to_restore))
	{
		// Lets get the data for these topics.
		$request = $smcFunc['db_query']('', '
			SELECT t.id_topic, t.id_previous_board, t.id_board, t.id_first_msg, m.subject
			FROM {db_prefix}topics AS t
				INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
			WHERE t.id_topic IN ({array_int:topics})',
			array(
				'topics' => $topics_to_restore,
			)
		);
		while ($row = $smcFunc['db_fetch_assoc']($request))
		{
			// We can only restore if the previous board is set.
			if (empty($row['id_previous_board']))
			{
				$unfound_messages[$row['id_first_msg']] = $row['subject'];
				continue;
			}

			// Ok we got here so me move them from here to there.
			moveTopics($row['id_topic'], $row['id_previous_board']);

			// Lets see if the board that we are returning to has post count enabled.
			$request2 = $smcFunc['db_query']('', '
				SELECT count_posts
				FROM {db_prefix}boards
				WHERE id_board = {int:board}',
				array(
					'board' => $row['id_previous_board'],
				)
			);
			list ($count_posts) = $smcFunc['db_fetch_row']($request2);
			$smcFunc['db_free_result']($request2);

			if (empty($count_posts))
			{
				// Lets get the members that need their post count restored.
				$request2 = $smcFunc['db_query']('', '
					SELECT id_member, COUNT(*) AS post_count
					FROM {db_prefix}messages
					WHERE id_topic = {int:topic}
						AND approved = {int:is_approved}
					GROUP BY id_member',
					array(
						'topic' => $row['id_topic'],
						'is_approved' => 1,
					)
				);

				while ($member = $smcFunc['db_fetch_assoc']($request2))
					updateMemberData($member['id_member'], array('posts' => 'posts + ' . $member['post_count']));
				$smcFunc['db_free_result']($request2);
			}

			// Log it.
			logAction('restore_topic', array('topic' => $row['id_topic'], 'board' => $row['id_board'], 'board_to' => $row['id_previous_board']));
		}
		$smcFunc['db_free_result']($request);
	}

	// Didn't find some things?
	if (!empty($unfound_messages))
		fatal_lang_error('restore_not_found', false, array(implode('<br>', $unfound_messages)));

	// Just send them to the index if they get here.
	redirectexit();
}

/**
 * Take a load of messages from one place and stick them in a topic
 *
 * @param array $msgs The IDs of the posts to merge
 * @param integer $from_topic The ID of the topic the messages were originally in
 * @param integer $target_topic The ID of the topic the messages are being merged into
 */
function mergePosts($msgs, $from_topic, $target_topic)
{
	global $smcFunc, $sourcedir;

	//!!! This really needs to be rewritten to take a load of messages from ANY topic, it's also inefficient.

	// Is it an array?
	if (!is_array($msgs))
		$msgs = array($msgs);

	// Lets make sure they are int.
	foreach ($msgs as $key => $msg)
		$msgs[$key] = (int) $msg;

	// Get the source information.
	$request = $smcFunc['db_query']('', '
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts
		FROM {db_prefix}topics AS t
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
		WHERE t.id_topic = {int:from_topic}',
		array(
			'from_topic' => $from_topic,
		)
	);
	list ($from_board, $from_first_msg, $from_replies, $from_unapproved_posts) = $smcFunc['db_fetch_row']($request);
	$smcFunc['db_free_result']($request);

	// Get some target topic and board stats.
	$request = $smcFunc['db_query']('', '
		SELECT t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts, b.count_posts
		FROM {db_prefix}topics AS t
			INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
		WHERE t.id_topic = {int:target_topic}',
		array(
			'target_topic' => $target_topic,
		)
	);
	list ($target_board, $target_first_msg, $target_replies, $target_unapproved_posts, $count_posts) = $smcFunc['db_fetch_row']($request);
	$smcFunc['db_free_result']($request);

	// Lets see if the board that we are returning to has post count enabled.
	if (empty($count_posts))
	{
		// Lets get the members that need their post count restored.
		$request = $smcFunc['db_query']('', '
			SELECT id_member
			FROM {db_prefix}messages
			WHERE id_msg IN ({array_int:messages})
				AND approved = {int:is_approved}',
			array(
				'messages' => $msgs,
				'is_approved' => 1,
			)
		);

		while ($row = $smcFunc['db_fetch_assoc']($request))
			updateMemberData($row['id_member'], array('posts' => '+'));
	}

	// Time to move the messages.
	$smcFunc['db_query']('', '
		UPDATE {db_prefix}messages
		SET
			id_topic = {int:target_topic},
			id_board = {int:target_board}
		WHERE id_msg IN({array_int:msgs})',
		array(
			'target_topic' => $target_topic,
			'target_board' => $target_board,
			'msgs' => $msgs,
		)
	);

	// Fix the id_first_msg and id_last_msg for the target topic.
	$target_topic_data = array(
		'num_replies' => 0,
		'unapproved_posts' => 0,
		'id_first_msg' => 9999999999,
	);
	$request = $smcFunc['db_query']('', '
		SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved
		FROM {db_prefix}messages
		WHERE id_topic = {int:target_topic}
		GROUP BY id_topic, approved
		ORDER BY approved ASC
		LIMIT 2',
		array(
			'target_topic' => $target_topic,
		)
	);
	while ($row = $smcFunc['db_fetch_assoc']($request))
	{
		if ($row['id_first_msg'] < $target_topic_data['id_first_msg'])
			$target_topic_data['id_first_msg'] = $row['id_first_msg'];
		$target_topic_data['id_last_msg'] = $row['id_last_msg'];
		if (!$row['approved'])
			$target_topic_data['unapproved_posts'] = $row['message_count'];
		else
			$target_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
	}
	$smcFunc['db_free_result']($request);

	// We have a new post count for the board.
	$smcFunc['db_query']('', '
		UPDATE {db_prefix}boards
		SET
			num_posts = num_posts + {int:diff_replies},
			unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
		WHERE id_board = {int:target_board}',
		array(
			'diff_replies' => $target_topic_data['num_replies'] - $target_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board.
			'diff_unapproved_posts' => $target_topic_data['unapproved_posts'] - $target_unapproved_posts,
			'target_board' => $target_board,
		)
	);

	// In some cases we merged the only post in a topic so the topic data is left behind in the topic table.
	$request = $smcFunc['db_query']('', '
		SELECT id_topic
		FROM {db_prefix}messages
		WHERE id_topic = {int:from_topic}',
		array(
			'from_topic' => $from_topic,
		)
	);

	// Remove the topic if it doesn't have any messages.
	$topic_exists = true;
	if ($smcFunc['db_num_rows']($request) == 0)
	{
		removeTopics($from_topic, false, true);
		$topic_exists = false;
	}
	$smcFunc['db_free_result']($request);

	// Recycled topic.
	if ($topic_exists == true)
	{
		// Fix the id_first_msg and id_last_msg for the source topic.
		$source_topic_data = array(
			'num_replies' => 0,
			'unapproved_posts' => 0,
			'id_first_msg' => 9999999999,
		);
		$request = $smcFunc['db_query']('', '
			SELECT MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved, subject
			FROM {db_prefix}messages
			WHERE id_topic = {int:from_topic}
			GROUP BY id_topic, approved, subject
			ORDER BY approved ASC
			LIMIT 2',
			array(
				'from_topic' => $from_topic,
			)
		);
		while ($row = $smcFunc['db_fetch_assoc']($request))
		{
			if ($row['id_first_msg'] < $source_topic_data['id_first_msg'])
				$source_topic_data['id_first_msg'] = $row['id_first_msg'];
			$source_topic_data['id_last_msg'] = $row['id_last_msg'];
			if (!$row['approved'])
				$source_topic_data['unapproved_posts'] = $row['message_count'];
			else
				$source_topic_data['num_replies'] = max(0, $row['message_count'] - 1);
		}
		$smcFunc['db_free_result']($request);

		// Update the topic details for the source topic.
		$smcFunc['db_query']('', '
			UPDATE {db_prefix}topics
			SET
				id_first_msg = {int:id_first_msg},
				id_last_msg = {int:id_last_msg},
				num_replies = {int:num_replies},
				unapproved_posts = {int:unapproved_posts}
			WHERE id_topic = {int:from_topic}',
			array(
				'id_first_msg' => $source_topic_data['id_first_msg'],
				'id_last_msg' => $source_topic_data['id_last_msg'],
				'num_replies' => $source_topic_data['num_replies'],
				'unapproved_posts' => $source_topic_data['unapproved_posts'],
				'from_topic' => $from_topic,
			)
		);

		// We have a new post count for the source board.
		$smcFunc['db_query']('', '
			UPDATE {db_prefix}boards
			SET
				num_posts = num_posts + {int:diff_replies},
				unapproved_posts = unapproved_posts + {int:diff_unapproved_posts}
			WHERE id_board = {int:from_board}',
			array(
				'diff_replies' => $source_topic_data['num_replies'] - $from_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board.
				'diff_unapproved_posts' => $source_topic_data['unapproved_posts'] - $from_unapproved_posts,
				'from_board' => $from_board,
			)
		);
	}

	// Finally get around to updating the destination topic, now all indexes etc on the source are fixed.
	$smcFunc['db_query']('', '
		UPDATE {db_prefix}topics
		SET
			id_first_msg = {int:id_first_msg},
			id_last_msg = {int:id_last_msg},
			num_replies = {int:num_replies},
			unapproved_posts = {int:unapproved_posts}
		WHERE id_topic = {int:target_topic}',
		array(
			'id_first_msg' => $target_topic_data['id_first_msg'],
			'id_last_msg' => $target_topic_data['id_last_msg'],
			'num_replies' => $target_topic_data['num_replies'],
			'unapproved_posts' => $target_topic_data['unapproved_posts'],
			'target_topic' => $target_topic,
		)
	);

	// Need it to update some stats.
	require_once($sourcedir . '/Subs-Post.php');

	// Update stats.
	updateStats('topic');
	updateStats('message');

	// Subject cache?
	$cache_updates = array();
	if ($target_first_msg != $target_topic_data['id_first_msg'])
		$cache_updates[] = $target_topic_data['id_first_msg'];
	if (!empty($source_topic_data['id_first_msg']) && $from_first_msg != $source_topic_data['id_first_msg'])
		$cache_updates[] = $source_topic_data['id_first_msg'];

	if (!empty($cache_updates))
	{
		$request = $smcFunc['db_query']('', '
			SELECT id_topic, subject
			FROM {db_prefix}messages
			WHERE id_msg IN ({array_int:first_messages})',
			array(
				'first_messages' => $cache_updates,
			)
		);
		while ($row = $smcFunc['db_fetch_assoc']($request))
			updateStats('subject', $row['id_topic'], $row['subject']);
		$smcFunc['db_free_result']($request);
	}

	updateLastMessages(array($from_board, $target_board));
}

/**
 * Try to determine if the topic has already been deleted by another user.
 *
 * @return bool False if it can't be deleted (recycling not enabled or no recycling board set), true if we've confirmed it can be deleted. Dies with an error if it's already been deleted.
 */
function removeDeleteConcurrence()
{
	global $modSettings, $board, $scripturl, $context;

	// No recycle no need to go further
	if (empty($modSettings['recycle_enable']) || empty($modSettings['recycle_board']))
		return false;

	// If it's confirmed go on and delete (from recycle)
	if (isset($_GET['confirm_delete']))
		return true;

	if (empty($board))
		return false;

	if ($modSettings['recycle_board'] != $board)
		return true;
	elseif (isset($_REQUEST['msg']))
		$confirm_url = $scripturl . '?action=deletemsg;confirm_delete;topic=' . $context['current_topic'] . '.0;msg=' . $_REQUEST['msg'] . ';' . $context['session_var'] . '=' . $context['session_id'];
	else
		$confirm_url = $scripturl . '?action=removetopic2;confirm_delete;topic=' . $context['current_topic'] . '.0;' . $context['session_var'] . '=' . $context['session_id'];

	fatal_lang_error('post_already_deleted', false, array($confirm_url));
}

?>