$topic, ) ); list ($starter, $subject, $approved) = $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'); // 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['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 $modSettings, $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? if ($_POST['delete_type'] == 'moved') { $condition .= ' AND m.icon = {string:icon} AND t.locked = {int:locked}'; $condition_params['icon'] = 'moved'; $condition_params['locked'] = 1; } // Otherwise, maybe locked topics only? elseif ($_POST['delete_type'] == 'locked') { $condition .= ' AND t.locked = {int:locked}'; $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 if true users' post count will be reduced * @param bool $ignoreRecycling if true topics are not moved to the recycle board (if it exists). */ function removeTopics($topics, $decreasePostCount = true, $ignoreRecycling = false) { global $sourcedir, $modSettings, $smcFunc; // Nothing to do? if (empty($topics)) return; // Only a single topic. if (is_numeric($topics)) $topics = array($topics); // 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}) AND m.icon != {string:recycled} 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' => 'recycled', '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($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 && !$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 ' . count($topics), array( 'recycle_board' => $modSettings['recycle_board'], 'topics' => $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); // Mark recycled topics as recycled. $smcFunc['db_query']('', ' UPDATE {db_prefix}messages SET icon = {string:recycled} WHERE id_topic IN ({array_int:recycle_topics})', array( 'recycle_topics' => $recycleTopics, 'recycled' => 'recycled', ) ); // 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())); recountOpenReports(); // 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; $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); // 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 ' . count($topics), array( 'no_poll' => 0, 'topics' => $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 = unserialize($modSettings['search_custom_index_config']); $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(), )); 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). * - 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 * @return array an array to set the cookie on with domain and path in it, in that order */ function removeMessage($message, $decreasePostCount = true) { global $board, $sourcedir, $modSettings, $user_info, $smcFunc, $context; if (empty($message) || !is_numeric($message)) return false; $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); 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'); } // 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 . '/ModerationCenter.php'); updateSettings(array('last_mod_report_action' => time())); recountOpenReports(); } // 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 ($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 = $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 (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 ($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)) $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') ); // Capture the ID of the new topic... $topicID = empty($id_recycle_topic) ? $smcFunc['db_insert_id']('{db_prefix}topics', '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}, 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... $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'), array($topicID, $user_info['id'], $modSettings['maxMsgID']), 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) { // 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 = unserialize($modSettings['search_custom_index_config']); $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)); } // 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']); return false; } /** * Move back a topic from the recycle board to its original board. */ function RestoreTopic() { global $context, $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, IFNULL(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']; } } // Put the icons back. if (!empty($messages)) $smcFunc['db_query']('', ' UPDATE {db_prefix}messages SET icon = {string:icon} WHERE id_msg IN ({array_int:messages})', array( 'icon' => 'xx', 'messages' => $messages, ) ); } // Now any topics? if (!empty($_REQUEST['topics'])) { $topics = explode(',', $_REQUEST['topics']); foreach ($topics as $key => $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 remove the recycled icon. $smcFunc['db_query']('', ' UPDATE {db_prefix}messages SET icon = {string:icon} WHERE id_topic = {int:id_topic}', array( 'icon' => 'xx', 'id_topic' => $row['id_topic'], ) ); // 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(id_msg) 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('
', $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 * @param integer $from_topic * @param integer $target_topic */ function mergePosts($msgs = array(), $from_topic, $target_topic) { global $context, $smcFunc, $modSettings, $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}, icon = {string:icon} WHERE id_msg IN({array_int:msgs})', array( 'target_topic' => $target_topic, 'target_board' => $target_board, 'icon' => $target_board == $modSettings['recycle_board'] ? 'recycled' : 'xx', '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 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. */ function removeDeleteConcurrence() { global $modSettings, $board, $topic, $smcFunc, $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)); } ?>