/** * Approve (or not) some posts... without permission checks... * * @package Posts * @param int[] $msgs - array of message ids * @param bool $approve = true */ function approvePosts($msgs, $approve = true) { global $modSettings; $db = database(); if (!is_array($msgs)) { $msgs = array($msgs); } if (empty($msgs)) { return false; } // May as well start at the beginning, working out *what* we need to change. $request = $db->query('', ' SELECT m.id_msg, m.approved, m.id_topic, m.id_board, t.id_first_msg, t.id_last_msg, m.body, m.subject, IFNULL(mem.real_name, m.poster_name) AS poster_name, m.id_member, t.approved AS topic_approved, 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 = m.id_board) LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member) WHERE m.id_msg IN ({array_int:message_list}) AND m.approved = {int:approved_state}', array('message_list' => $msgs, 'approved_state' => $approve ? 0 : 1)); $msgs = array(); $topics = array(); $topic_changes = array(); $board_changes = array(); $notification_topics = array(); $notification_posts = array(); $member_post_changes = array(); while ($row = $db->fetch_assoc($request)) { // Easy... $msgs[] = $row['id_msg']; $topics[] = $row['id_topic']; // Ensure our change array exists already. if (!isset($topic_changes[$row['id_topic']])) { $topic_changes[$row['id_topic']] = array('id_last_msg' => $row['id_last_msg'], 'approved' => $row['topic_approved'], 'replies' => 0, 'unapproved_posts' => 0); } if (!isset($board_changes[$row['id_board']])) { $board_changes[$row['id_board']] = array('posts' => 0, 'topics' => 0, 'unapproved_posts' => 0, 'unapproved_topics' => 0); } // If it's the first message then the topic state changes! if ($row['id_msg'] == $row['id_first_msg']) { $topic_changes[$row['id_topic']]['approved'] = $approve ? 1 : 0; $board_changes[$row['id_board']]['unapproved_topics'] += $approve ? -1 : 1; $board_changes[$row['id_board']]['topics'] += $approve ? 1 : -1; // Note we need to ensure we announce this topic! $notification_topics[] = array('body' => $row['body'], 'subject' => $row['subject'], 'name' => $row['poster_name'], 'board' => $row['id_board'], 'topic' => $row['id_topic'], 'msg' => $row['id_first_msg'], 'poster' => $row['id_member']); } else { $topic_changes[$row['id_topic']]['replies'] += $approve ? 1 : -1; // This will be a post... but don't notify unless it's not followed by approved ones. if ($row['id_msg'] > $row['id_last_msg']) { $notification_posts[$row['id_topic']][] = array('id' => $row['id_msg'], 'body' => $row['body'], 'subject' => $row['subject'], 'name' => $row['poster_name'], 'topic' => $row['id_topic']); } } // If this is being approved and id_msg is higher than the current id_last_msg then it changes. if ($approve && $row['id_msg'] > $topic_changes[$row['id_topic']]['id_last_msg']) { $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_msg']; } elseif (!$approve) { // Default to the first message and then we'll override in a bit ;) $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_first_msg']; } $topic_changes[$row['id_topic']]['unapproved_posts'] += $approve ? -1 : 1; $board_changes[$row['id_board']]['unapproved_posts'] += $approve ? -1 : 1; $board_changes[$row['id_board']]['posts'] += $approve ? 1 : -1; // Post count for the user? if ($row['id_member'] && empty($row['count_posts'])) { $member_post_changes[$row['id_member']] = isset($member_post_changes[$row['id_member']]) ? $member_post_changes[$row['id_member']] + 1 : 1; } } $db->free_result($request); if (empty($msgs)) { return; } // Now we have the differences make the changes, first the easy one. $db->query('', ' UPDATE {db_prefix}messages SET approved = {int:approved_state} WHERE id_msg IN ({array_int:message_list})', array('message_list' => $msgs, 'approved_state' => $approve ? 1 : 0)); // If we were unapproving find the last msg in the topics... if (!$approve) { $request = $db->query('', ' SELECT id_topic, MAX(id_msg) AS id_last_msg FROM {db_prefix}messages WHERE id_topic IN ({array_int:topic_list}) AND approved = {int:approved} GROUP BY id_topic', array('topic_list' => $topics, 'approved' => 1)); while ($row = $db->fetch_assoc($request)) { $topic_changes[$row['id_topic']]['id_last_msg'] = $row['id_last_msg']; } $db->free_result($request); } // ... next the topics... foreach ($topic_changes as $id => $changes) { $db->query('', ' UPDATE {db_prefix}topics SET approved = {int:approved}, unapproved_posts = CASE WHEN unapproved_posts + {int:unapproved_posts} < 0 THEN 0 ELSE unapproved_posts + {int:unapproved_posts} END, num_replies = CASE WHEN num_replies + {int:num_replies} < 0 THEN 0 ELSE num_replies + {int:num_replies} END, id_last_msg = {int:id_last_msg} WHERE id_topic = {int:id_topic}', array('approved' => $changes['approved'], 'unapproved_posts' => $changes['unapproved_posts'], 'num_replies' => $changes['replies'], 'id_last_msg' => $changes['id_last_msg'], 'id_topic' => $id)); } // ... finally the boards... foreach ($board_changes as $id => $changes) { $db->query('', ' UPDATE {db_prefix}boards SET num_posts = num_posts + {int:num_posts}, unapproved_posts = CASE WHEN unapproved_posts + {int:unapproved_posts} < 0 THEN 0 ELSE unapproved_posts + {int:unapproved_posts} END, num_topics = CASE WHEN num_topics + {int:num_topics} < 0 THEN 0 ELSE num_topics + {int:num_topics} END, unapproved_topics = CASE WHEN unapproved_topics + {int:unapproved_topics} < 0 THEN 0 ELSE unapproved_topics + {int:unapproved_topics} END WHERE id_board = {int:id_board}', array('num_posts' => $changes['posts'], 'unapproved_posts' => $changes['unapproved_posts'], 'num_topics' => $changes['topics'], 'unapproved_topics' => $changes['unapproved_topics'], 'id_board' => $id)); } // Finally, least importantly, notifications! if ($approve) { require_once SUBSDIR . '/Notification.subs.php'; if (!empty($notification_topics)) { sendBoardNotifications($notification_topics); } if (!empty($notification_posts)) { sendApprovalNotifications($notification_posts); } $db->query('', ' DELETE FROM {db_prefix}approval_queue WHERE id_msg IN ({array_int:message_list}) AND id_attach = {int:id_attach}', array('message_list' => $msgs, 'id_attach' => 0)); } else { $msgInserts = array(); foreach ($msgs as $msg) { $msgInserts[] = array($msg); } $db->insert('ignore', '{db_prefix}approval_queue', array('id_msg' => 'int'), $msgInserts, array('id_msg')); } if (!empty($modSettings['mentions_enabled'])) { require_once SUBSDIR . '/Mentions.subs.php'; toggleMentionsApproval($msgs, $approve); } // Update the last messages on the boards... updateLastMessages(array_keys($board_changes)); // Post count for the members? if (!empty($member_post_changes)) { foreach ($member_post_changes as $id_member => $count_change) { updateMemberData($id_member, array('posts' => 'posts ' . ($approve ? '+' : '-') . ' ' . $count_change)); } } return true; }
/** * Allows for moderation from the message index. * @todo refactor this... */ function QuickModeration() { global $sourcedir, $board, $user_info, $modSettings, $smcFunc, $context; // 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 $sourcedir . '/Subs-Post.php'; // Remember the last board they moved things to. if (isset($_REQUEST['move_to'])) { $_SESSION['move_to_topic'] = $_REQUEST['move_to']; } // 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 { /** * @todo Ugly. There's no getting around this, is there? * @todo Maybe just do this on the actions people want to use? */ $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 $sourcedir . '/SplitTopics.php'; return 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... $request = $smcFunc['db_query']('', ' SELECT id_topic, id_member_started, id_board, locked, approved, unapproved_posts FROM {db_prefix}topics WHERE id_topic IN ({array_int:action_topic_ids}) LIMIT ' . count($_REQUEST['actions']), array('action_topic_ids' => array_keys($_REQUEST['actions']))); while ($row = $smcFunc['db_fetch_assoc']($request)) { 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']]); } } } $smcFunc['db_free_result']($request); } $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') { require_once $sourcedir . '/MoveTopic.php'; 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)) { $smcFunc['db_query']('', ' UPDATE {db_prefix}topics SET is_sticky = CASE WHEN is_sticky = {int:is_sticky} THEN 0 ELSE 1 END WHERE id_topic IN ({array_int:sticky_topic_ids})', array('sticky_topic_ids' => $stickyCache, 'is_sticky' => 1)); // Get the board IDs and Sticky status $request = $smcFunc['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 = $smcFunc['db_fetch_assoc']($request)) { $stickyCacheBoards[$row['id_topic']] = $row['id_board']; $stickyCacheStatus[$row['id_topic']] = empty($row['is_sticky']); } $smcFunc['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 = $smcFunc['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 = $smcFunc['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); } $smcFunc['db_free_result']($request); $moveCache = $moveCache2; require_once $sourcedir . '/MoveTopic.php'; // Do the actual moves... foreach ($moveTos as $to => $topics) { moveTopics($topics, $to); } // Does the post counts need to be updated? if (!empty($moveTos)) { $topicRecounts = array(); $request = $smcFunc['db_query']('', ' SELECT id_board, count_posts FROM {db_prefix}boards WHERE id_board IN ({array_int:move_boards})', array('move_boards' => array_keys($moveTos))); while ($row = $smcFunc['db_fetch_assoc']($request)) { $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 ? '+' : '-'; } } } $smcFunc['db_free_result']($request); if (!empty($topicRecounts)) { $members = array(); // Get all the members who have posted in the moved topics. $request = $smcFunc['db_query']('', ' SELECT id_member, id_topic FROM {db_prefix}messages WHERE id_topic IN ({array_int:moved_topic_ids})', array('moved_topic_ids' => array_keys($topicRecounts))); while ($row = $smcFunc['db_fetch_assoc']($request)) { if (!isset($members[$row['id_member']])) { $members[$row['id_member']] = 0; } if ($topicRecounts[$row['id_topic']] === '+') { $members[$row['id_member']] += 1; } else { $members[$row['id_member']] -= 1; } } $smcFunc['db_free_result']($request); // And now update them member's post counts foreach ($members as $id_member => $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 = $smcFunc['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 = $smcFunc['db_fetch_assoc']($result)) { $removeCache[] = $row['id_topic']; $removeCacheBoards[$row['id_topic']] = $row['id_board']; } $smcFunc['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'); } require_once $sourcedir . '/RemoveTopic.php'; removeTopics($removeCache); } } // Approve the topics... if (!empty($approveCache)) { // We need unapproved topic ids and their authors! $request = $smcFunc['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 = $smcFunc['db_fetch_assoc']($request)) { $approveCache[] = $row['id_topic']; $approveCacheMembers[$row['id_topic']] = $row['id_member_started']; } $smcFunc['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 = $smcFunc['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 = $smcFunc['db_fetch_assoc']($result)) { $lockCache[] = $row['id_topic']; $lockCacheBoards[$row['id_topic']] = $row['id_board']; $lockStatus[$row['id_topic']] = empty($row['locked']); } $smcFunc['db_free_result']($result); } else { $result = $smcFunc['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 = $smcFunc['db_fetch_assoc']($result)) { $lockStatus[$row['id_topic']] = empty($row['locked']); $lockCacheBoards[$row['id_topic']] = $row['id_board']; } $smcFunc['db_free_result']($result); } // It could just be that *none* were their own topics... if (!empty($lockCache)) { // Alternate the locked value. $smcFunc['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)) { $markArray = array(); foreach ($markCache as $topic) { $markArray[] = array($modSettings['maxMsgID'], $user_info['id'], $topic); } $smcFunc['db_insert']('replace', '{db_prefix}log_topics', array('id_msg' => 'int', 'id_member' => 'int', 'id_topic' => 'int'), $markArray, array('id_member', 'id_topic')); } 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); }
function move_topic($topic, $board, $newboard, $topicinfo) { global $mobdb; // Move the topic and fix the stats $mobdb->query(' UPDATE {db_prefix}topics SET ID_BOARD = {int:board} WHERE ID_TOPIC = {int:topic}', array('board' => $newboard['id_board'], 'topic' => $topic)); $mobdb->query(' UPDATE {db_prefix}calendar SET ID_BOARD = {int:board} WHERE ID_TOPIC = {int:topic}', array('topic' => $topic, 'board' => $newboard['id_board'])); $mobdb->query(' UPDATE {db_prefix}messages SET ID_BOARD = {int:board} WHERE ID_TOPIC = {int:topic}', array('board' => $newboard['id_board'], 'topic' => $topic)); $mobdb->query(' UPDATE {db_prefix}boards SET numPosts = numPosts + {int:new_posts}, numTopics = numTopics + 1 WHERE ID_BOARD = {int:board}', array('new_posts' => $topicinfo['replies'] + 1, 'board' => $newboard['id_board'])); $mobdb->query(' UPDATE {db_prefix}boards SET numPosts = numPosts - {int:new_posts}, numTopics = numTopics - 1 WHERE ID_BOARD = {int:board}', array('new_posts' => $topicinfo['replies'] + 1, 'board' => $board)); // Update the last messages updateLastMessages(array($board, $newboard['id_board'])); }
function MergeExecute($topics = array()) { global $db_prefix, $user_info, $txt, $context, $scripturl, $sourcedir; global $func, $language, $modSettings; // The parameters of MergeExecute were set, so this must've been an internal call. if (!empty($topics)) { isAllowedTo('merge_any'); loadTemplate('SplitTopics'); } checkSession('request'); // Handle URLs from MergeIndex. if (!empty($_GET['from']) && !empty($_GET['to'])) { $topics = array((int) $_GET['from'], (int) $_GET['to']); } // If we came from a form, the topic IDs came by post. if (!empty($_POST['topics']) && is_array($_POST['topics'])) { $topics = $_POST['topics']; } // There's nothing to merge with just one topic... if (empty($topics) || !is_array($topics) || count($topics) == 1) { fatal_lang_error('merge_need_more_topics'); } // Make sure every topic is numeric, or some nasty things could be done with the DB. foreach ($topics as $id => $topic) { $topics[$id] = (int) $topic; } // Get info about the topics and polls that will be merged. $request = db_query("\n\t\tSELECT\n\t\t\tt.ID_TOPIC, t.ID_BOARD, t.ID_POLL, t.numViews, t.isSticky,\n\t\t\tm1.subject, m1.posterTime AS time_started, IFNULL(mem1.ID_MEMBER, 0) AS ID_MEMBER_STARTED, IFNULL(mem1.realName, m1.posterName) AS name_started,\n\t\t\tm2.posterTime AS time_updated, IFNULL(mem2.ID_MEMBER, 0) AS ID_MEMBER_UPDATED, IFNULL(mem2.realName, m2.posterName) AS name_updated\n\t\tFROM ({$db_prefix}topics AS t, {$db_prefix}messages AS m1, {$db_prefix}messages AS m2)\n\t\t\tLEFT JOIN {$db_prefix}members AS mem1 ON (mem1.ID_MEMBER = m1.ID_MEMBER)\n\t\t\tLEFT JOIN {$db_prefix}members AS mem2 ON (mem2.ID_MEMBER = m2.ID_MEMBER)\n\t\tWHERE t.ID_TOPIC IN (" . implode(', ', $topics) . ")\n\t\t\tAND m1.ID_MSG = t.ID_FIRST_MSG\n\t\t\tAND m2.ID_MSG = t.ID_LAST_MSG\n\t\tORDER BY t.ID_FIRST_MSG\n\t\tLIMIT " . count($topics), __FILE__, __LINE__); if (mysql_num_rows($request) < 2) { fatal_lang_error('smf263'); } $num_views = 0; $isSticky = 0; $boards = array(); $polls = array(); while ($row = mysql_fetch_assoc($request)) { $topic_data[$row['ID_TOPIC']] = array('id' => $row['ID_TOPIC'], 'board' => $row['ID_BOARD'], 'poll' => $row['ID_POLL'], 'numViews' => $row['numViews'], 'subject' => $row['subject'], 'started' => array('time' => timeformat($row['time_started']), 'timestamp' => forum_time(true, $row['time_started']), 'href' => empty($row['ID_MEMBER_STARTED']) ? '' : $scripturl . '?action=profile;u=' . $row['ID_MEMBER_STARTED'], 'link' => empty($row['ID_MEMBER_STARTED']) ? $row['name_started'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['ID_MEMBER_STARTED'] . '">' . $row['name_started'] . '</a>'), 'updated' => array('time' => timeformat($row['time_updated']), 'timestamp' => forum_time(true, $row['time_updated']), 'href' => empty($row['ID_MEMBER_UPDATED']) ? '' : $scripturl . '?action=profile;u=' . $row['ID_MEMBER_UPDATED'], 'link' => empty($row['ID_MEMBER_UPDATED']) ? $row['name_updated'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['ID_MEMBER_UPDATED'] . '">' . $row['name_updated'] . '</a>')); $num_views += $row['numViews']; $boards[] = $row['ID_BOARD']; // If there's no poll, ID_POLL == 0... if ($row['ID_POLL'] > 0) { $polls[] = $row['ID_POLL']; } // Store the ID_TOPIC with the lowest ID_FIRST_MSG. if (empty($firstTopic)) { $firstTopic = $row['ID_TOPIC']; } $isSticky = max($isSticky, $row['isSticky']); } mysql_free_result($request); $boards = array_values(array_unique($boards)); // Get the boards a user is allowed to merge in. $merge_boards = boardsAllowedTo('merge_any'); if (empty($merge_boards)) { fatal_lang_error('cannot_merge_any'); } // Make sure they can see all boards.... $request = db_query("\n\t\tSELECT b.ID_BOARD\n\t\tFROM {$db_prefix}boards AS b\n\t\tWHERE b.ID_BOARD IN (" . implode(', ', $boards) . ")\n\t\t\tAND {$user_info['query_see_board']}" . (!in_array(0, $merge_boards) ? "\n\t\t\tAND b.ID_BOARD IN (" . implode(', ', $merge_boards) . ")" : '') . "\n\t\tLIMIT " . count($boards), __FILE__, __LINE__); // If the number of boards that's in the output isn't exactly the same as we've put in there, you're in trouble. if (mysql_num_rows($request) != count($boards)) { fatal_lang_error('smf232'); } mysql_free_result($request); if (empty($_REQUEST['sa']) || $_REQUEST['sa'] == 'options') { if (count($polls) > 1) { $request = db_query("\n\t\t\t\tSELECT t.ID_TOPIC, t.ID_POLL, m.subject, p.question\n\t\t\t\tFROM ({$db_prefix}polls AS p, {$db_prefix}topics AS t, {$db_prefix}messages AS m)\n\t\t\t\tWHERE p.ID_POLL IN (" . implode(', ', $polls) . ")\n\t\t\t\t\tAND t.ID_POLL = p.ID_POLL\n\t\t\t\t\tAND m.ID_MSG = t.ID_FIRST_MSG\n\t\t\t\tLIMIT " . count($polls), __FILE__, __LINE__); while ($row = mysql_fetch_assoc($request)) { $context['polls'][] = array('id' => $row['ID_POLL'], 'topic' => array('id' => $row['ID_TOPIC'], 'subject' => $row['subject']), 'question' => $row['question'], 'selected' => $row['ID_TOPIC'] == $firstTopic); } mysql_free_result($request); } if (count($boards) > 1) { $request = db_query("\n\t\t\t\tSELECT ID_BOARD, name\n\t\t\t\tFROM {$db_prefix}boards\n\t\t\t\tWHERE ID_BOARD IN (" . implode(', ', $boards) . ")\n\t\t\t\tORDER BY name\n\t\t\t\tLIMIT " . count($boards), __FILE__, __LINE__); while ($row = mysql_fetch_assoc($request)) { $context['boards'][] = array('id' => $row['ID_BOARD'], 'name' => $row['name'], 'selected' => $row['ID_BOARD'] == $topic_data[$firstTopic]['board']); } mysql_free_result($request); } $context['topics'] = $topic_data; foreach ($topic_data as $id => $topic) { $context['topics'][$id]['selected'] = $topic['id'] == $firstTopic; } $context['page_title'] = $txt['smf252']; $context['sub_template'] = 'merge_extra_options'; return; } // Determine target board. $target_board = count($boards) > 1 ? (int) $_REQUEST['board'] : $boards[0]; if (!in_array($target_board, $boards)) { fatal_lang_error('smf232'); } // Determine which poll will survive and which polls won't. $target_poll = count($polls) > 1 ? (int) $_POST['poll'] : (count($polls) == 1 ? $polls[0] : 0); if ($target_poll > 0 && !in_array($target_poll, $polls)) { fatal_lang_error(1, false); } $deleted_polls = empty($target_poll) ? $polls : array_diff($polls, array($target_poll)); // Determine the subject of the newly merged topic - was a custom subject specified? if (empty($_POST['subject']) && isset($_POST['custom_subject']) && $_POST['custom_subject'] != '') { $target_subject = $func['htmlspecialchars']($_POST['custom_subject']); } elseif (!empty($topic_data[(int) $_POST['subject']]['subject'])) { $target_subject = addslashes($topic_data[(int) $_POST['subject']]['subject']); } else { $target_subject = addslashes($topic_data[$firstTopic]['subject']); } // Get the first and last message and the number of messages.... $request = db_query("\n\t\tSELECT MIN(ID_MSG), MAX(ID_MSG), COUNT(ID_MSG) - 1\n\t\tFROM {$db_prefix}messages\n\t\tWHERE ID_TOPIC IN (" . implode(', ', $topics) . ")", __FILE__, __LINE__); list($first_msg, $last_msg, $num_replies) = mysql_fetch_row($request); mysql_free_result($request); // Get the member ID of the first and last message. $request = db_query("\n\t\tSELECT ID_MEMBER\n\t\tFROM {$db_prefix}messages\n\t\tWHERE ID_MSG IN ({$first_msg}, {$last_msg})\n\t\tORDER BY ID_MSG\n\t\tLIMIT 2", __FILE__, __LINE__); list($member_started) = mysql_fetch_row($request); list($member_updated) = mysql_fetch_row($request); mysql_free_result($request); // Assign the first topic ID to be the merged topic. $ID_TOPIC = min($topics); // Delete the remaining topics. $deleted_topics = array_diff($topics, array($ID_TOPIC)); db_query("\n\t\tDELETE FROM {$db_prefix}topics\n\t\tWHERE ID_TOPIC IN (" . implode(', ', $deleted_topics) . ")\n\t\tLIMIT " . count($deleted_topics), __FILE__, __LINE__); db_query("\n\t\tDELETE FROM {$db_prefix}log_search_subjects\n\t\tWHERE ID_TOPIC IN (" . implode(', ', $deleted_topics) . ")", __FILE__, __LINE__); // Asssign the properties of the newly merged topic. db_query("\n\t\tUPDATE {$db_prefix}topics\n\t\tSET\n\t\t\tID_BOARD = {$target_board},\n\t\t\tID_MEMBER_STARTED = {$member_started},\n\t\t\tID_MEMBER_UPDATED = {$member_updated},\n\t\t\tID_FIRST_MSG = {$first_msg},\n\t\t\tID_LAST_MSG = {$last_msg},\n\t\t\tID_POLL = {$target_poll},\n\t\t\tnumReplies = {$num_replies},\n\t\t\tnumViews = {$num_views},\n\t\t\tisSticky = {$isSticky}\n\t\tWHERE ID_TOPIC = {$ID_TOPIC}\n\t\tLIMIT 1", __FILE__, __LINE__); // Grab the response prefix (like 'Re: ') in the default forum language. if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix'))) { if ($language === $user_info['language']) { $context['response_prefix'] = $txt['response_prefix']; } else { loadLanguage('index', $language, false); $context['response_prefix'] = $txt['response_prefix']; loadLanguage('index'); } cache_put_data('response_prefix', $context['response_prefix'], 600); } // Change the topic IDs of all messages that will be merged. Also adjust subjects if 'enforce subject' was checked. db_query("\n\t\tUPDATE {$db_prefix}messages\n\t\tSET\n\t\t\tID_TOPIC = {$ID_TOPIC},\n\t\t\tID_BOARD = {$target_board}" . (!empty($_POST['enforce_subject']) ? ",\n\t\t\tsubject = '{$context['response_prefix']}{$target_subject}'" : '') . "\n\t\tWHERE ID_TOPIC IN (" . implode(', ', $topics) . ")", __FILE__, __LINE__); // Change the subject of the first message... db_query("\n\t\tUPDATE {$db_prefix}messages\n\t\tSET subject = '{$target_subject}'\n\t\tWHERE ID_MSG = {$first_msg}\n\t\tLIMIT 1", __FILE__, __LINE__); // Adjust all calendar events to point to the new topic. db_query("\n\t\tUPDATE {$db_prefix}calendar\n\t\tSET\n\t\t\tID_TOPIC = {$ID_TOPIC},\n\t\t\tID_BOARD = {$target_board}\n\t\tWHERE ID_TOPIC IN (" . implode(', ', $deleted_topics) . ")", __FILE__, __LINE__); // Merge log topic entries. $request = db_query("\n\t\tSELECT ID_MEMBER, MIN(ID_MSG) AS new_ID_MSG\n\t\tFROM {$db_prefix}log_topics\n\t\tWHERE ID_TOPIC IN (" . implode(', ', $topics) . ")\n\t\tGROUP BY ID_MEMBER", __FILE__, __LINE__); if (mysql_num_rows($request) > 0) { $replaceEntries = array(); while ($row = mysql_fetch_assoc($request)) { $replaceEntries[] = "({$row['ID_MEMBER']}, {$ID_TOPIC}, {$row['new_ID_MSG']})"; } db_query("\n\t\t\tREPLACE INTO {$db_prefix}log_topics\n\t\t\t\t(ID_MEMBER, ID_TOPIC, ID_MSG)\n\t\t\tVALUES " . implode(', ', $replaceEntries), __FILE__, __LINE__); unset($replaceEntries); // Get rid of the old log entries. db_query("\n\t\t\tDELETE FROM {$db_prefix}log_topics\n\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $deleted_topics) . ")", __FILE__, __LINE__); } mysql_free_result($request); // Merge topic notifications. if (!empty($_POST['notifications']) && is_array($_POST['notifications'])) { // Check if the notification array contains valid topics. if (count(array_diff($_POST['notifications'], $topics)) > 0) { fatal_lang_error('smf232'); } $request = db_query("\n\t\t\tSELECT ID_MEMBER, MAX(sent) AS sent\n\t\t\tFROM {$db_prefix}log_notify\n\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $_POST['notifications']) . ")\n\t\t\tGROUP BY ID_MEMBER", __FILE__, __LINE__); if (mysql_num_rows($request) > 0) { $replaceEntries = array(); while ($row = mysql_fetch_assoc($request)) { $replaceEntries[] = "({$row['ID_MEMBER']}, {$ID_TOPIC}, 0, {$row['sent']})"; } db_query("\n\t\t\t\tREPLACE INTO {$db_prefix}log_notify\n\t\t\t\t\t(ID_MEMBER, ID_TOPIC, ID_BOARD, sent)\n\t\t\t\tVALUES " . implode(', ', $replaceEntries), __FILE__, __LINE__); unset($replaceEntries); db_query("\n\t\t\t\tDELETE FROM {$db_prefix}log_topics\n\t\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $deleted_topics) . ")", __FILE__, __LINE__); } mysql_free_result($request); } // Get rid of the redundant polls. if (!empty($deleted_polls)) { db_query("\n\t\t\tDELETE FROM {$db_prefix}polls\n\t\t\tWHERE ID_POLL IN (" . implode(', ', $deleted_polls) . ")\n\t\t\tLIMIT 1", __FILE__, __LINE__); db_query("\n\t\t\tDELETE FROM {$db_prefix}poll_choices\n\t\t\tWHERE ID_POLL IN (" . implode(', ', $deleted_polls) . ")", __FILE__, __LINE__); db_query("\n\t\t\tDELETE FROM {$db_prefix}log_polls\n\t\t\tWHERE ID_POLL IN (" . implode(', ', $deleted_polls) . ")", __FILE__, __LINE__); } // Fix the board totals. if (count($boards) > 1) { $request = db_query("\n\t\t\tSELECT ID_BOARD, COUNT(*) AS numTopics, SUM(numReplies) + COUNT(*) AS numPosts\n\t\t\tFROM {$db_prefix}topics\n\t\t\tWHERE ID_BOARD IN (" . implode(', ', $boards) . ")\n\t\t\tGROUP BY ID_BOARD\n\t\t\tLIMIT " . count($boards), __FILE__, __LINE__); while ($row = mysql_fetch_assoc($request)) { db_query("\n\t\t\t\tUPDATE {$db_prefix}boards\n\t\t\t\tSET\n\t\t\t\t\tnumPosts = {$row['numPosts']},\n\t\t\t\t\tnumTopics = {$row['numTopics']}\n\t\t\t\tWHERE ID_BOARD = {$row['ID_BOARD']}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); } mysql_free_result($request); } else { db_query("\n\t\t\tUPDATE {$db_prefix}boards\n\t\t\tSET numTopics = IF(" . (count($topics) - 1) . " > numTopics, 0, numTopics - " . (count($topics) - 1) . ")\n\t\t\tWHERE ID_BOARD = {$target_board}\n\t\t\tLIMIT 1", __FILE__, __LINE__); } require_once $sourcedir . '/Subs-Post.php'; // Update all the statistics. updateStats('topic'); updateStats('subject', $ID_TOPIC, $target_subject); updateLastMessages($boards); logAction('merge', array('topic' => $ID_TOPIC)); // Notify people that these topics have been merged? sendNotifications($ID_TOPIC, 'merge'); // Send them to the all done page. redirectexit('action=mergetopics;sa=done;to=' . $ID_TOPIC . ';targetboard=' . $target_board); }
/** * set merge options and do the actual merge of two or more topics. * * the merge options screen: * * shows topics to be merged and allows to set some merge options. * * is accessed by ?action=mergetopics;sa=options.and can also internally be called by QuickModeration() (Subs-Boards.php). * * uses 'merge_extra_options' sub template of the SplitTopics template. * * the actual merge: * * is accessed with ?action=mergetopics;sa=execute. * * updates the statistics to reflect the merge. * * logs the action in the moderation log. * * sends a notification is sent to all users monitoring this topic. * * redirects to ?action=mergetopics;sa=done. * @param array $topics = array() */ function MergeExecute($topics = array()) { global $user_info, $txt, $context, $scripturl, $sourcedir; global $smcFunc, $language, $modSettings; // Check the session. checkSession('request'); // Handle URLs from MergeIndex. if (!empty($_GET['from']) && !empty($_GET['to'])) { $topics = array((int) $_GET['from'], (int) $_GET['to']); } // If we came from a form, the topic IDs came by post. if (!empty($_POST['topics']) && is_array($_POST['topics'])) { $topics = $_POST['topics']; } // There's nothing to merge with just one topic... if (empty($topics) || !is_array($topics) || count($topics) == 1) { fatal_lang_error('merge_need_more_topics'); } // Make sure every topic is numeric, or some nasty things could be done with the DB. foreach ($topics as $id => $topic) { $topics[$id] = (int) $topic; } // Joy of all joys, make sure they're not pi**ing about with unapproved topics they can't see :P if ($modSettings['postmod_active']) { $can_approve_boards = boardsAllowedTo('approve_posts'); } // Get info about the topics and polls that will be merged. $request = $smcFunc['db_query']('', ' SELECT t.id_topic, t.id_board, t.id_poll, t.num_views, t.is_sticky, t.approved, t.num_replies, t.unapproved_posts, m1.subject, m1.poster_time AS time_started, IFNULL(mem1.id_member, 0) AS id_member_started, IFNULL(mem1.real_name, m1.poster_name) AS name_started, m2.poster_time AS time_updated, IFNULL(mem2.id_member, 0) AS id_member_updated, IFNULL(mem2.real_name, m2.poster_name) AS name_updated FROM {db_prefix}topics AS t INNER JOIN {db_prefix}messages AS m1 ON (m1.id_msg = t.id_first_msg) INNER JOIN {db_prefix}messages AS m2 ON (m2.id_msg = t.id_last_msg) LEFT JOIN {db_prefix}members AS mem1 ON (mem1.id_member = m1.id_member) LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = m2.id_member) WHERE t.id_topic IN ({array_int:topic_list}) ORDER BY t.id_first_msg LIMIT ' . count($topics), array('topic_list' => $topics)); if ($smcFunc['db_num_rows']($request) < 2) { fatal_lang_error('no_topic_id'); } $num_views = 0; $is_sticky = 0; $boardTotals = array(); $boards = array(); $polls = array(); $firstTopic = 0; while ($row = $smcFunc['db_fetch_assoc']($request)) { // Make a note for the board counts... if (!isset($boardTotals[$row['id_board']])) { $boardTotals[$row['id_board']] = array('posts' => 0, 'topics' => 0, 'unapproved_posts' => 0, 'unapproved_topics' => 0); } // We can't see unapproved topics here? if ($modSettings['postmod_active'] && !$row['approved'] && $can_approve_boards != array(0) && in_array($row['id_board'], $can_approve_boards)) { continue; } elseif (!$row['approved']) { $boardTotals[$row['id_board']]['unapproved_topics']++; } else { $boardTotals[$row['id_board']]['topics']++; } $boardTotals[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts']; $boardTotals[$row['id_board']]['posts'] += $row['num_replies'] + ($row['approved'] ? 1 : 0); $topic_data[$row['id_topic']] = array('id' => $row['id_topic'], 'board' => $row['id_board'], 'poll' => $row['id_poll'], 'num_views' => $row['num_views'], 'subject' => $row['subject'], 'started' => array('time' => timeformat($row['time_started']), 'timestamp' => forum_time(true, $row['time_started']), 'href' => empty($row['id_member_started']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_started'], 'link' => empty($row['id_member_started']) ? $row['name_started'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_started'] . '">' . $row['name_started'] . '</a>'), 'updated' => array('time' => timeformat($row['time_updated']), 'timestamp' => forum_time(true, $row['time_updated']), 'href' => empty($row['id_member_updated']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_updated'], 'link' => empty($row['id_member_updated']) ? $row['name_updated'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_updated'] . '">' . $row['name_updated'] . '</a>')); $num_views += $row['num_views']; $boards[] = $row['id_board']; // If there's no poll, id_poll == 0... if ($row['id_poll'] > 0) { $polls[] = $row['id_poll']; } // Store the id_topic with the lowest id_first_msg. if (empty($firstTopic)) { $firstTopic = $row['id_topic']; } $is_sticky = max($is_sticky, $row['is_sticky']); } $smcFunc['db_free_result']($request); // If we didn't get any topics then they've been messing with unapproved stuff. if (empty($topic_data)) { fatal_lang_error('no_topic_id'); } $boards = array_values(array_unique($boards)); // The parameters of MergeExecute were set, so this must've been an internal call. if (!empty($topics)) { isAllowedTo('merge_any', $boards); loadTemplate('SplitTopics'); } // Get the boards a user is allowed to merge in. $merge_boards = boardsAllowedTo('merge_any'); if (empty($merge_boards)) { fatal_lang_error('cannot_merge_any', 'user'); } // Make sure they can see all boards.... $request = $smcFunc['db_query']('', ' SELECT b.id_board FROM {db_prefix}boards AS b WHERE b.id_board IN ({array_int:boards}) AND {query_see_board}' . (!in_array(0, $merge_boards) ? ' AND b.id_board IN ({array_int:merge_boards})' : '') . ' LIMIT ' . count($boards), array('boards' => $boards, 'merge_boards' => $merge_boards)); // If the number of boards that's in the output isn't exactly the same as we've put in there, you're in trouble. if ($smcFunc['db_num_rows']($request) != count($boards)) { fatal_lang_error('no_board'); } $smcFunc['db_free_result']($request); if (empty($_REQUEST['sa']) || $_REQUEST['sa'] == 'options') { if (count($polls) > 1) { $request = $smcFunc['db_query']('', ' SELECT t.id_topic, t.id_poll, m.subject, p.question FROM {db_prefix}polls AS p INNER JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll) INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) WHERE p.id_poll IN ({array_int:polls}) LIMIT ' . count($polls), array('polls' => $polls)); while ($row = $smcFunc['db_fetch_assoc']($request)) { $context['polls'][] = array('id' => $row['id_poll'], 'topic' => array('id' => $row['id_topic'], 'subject' => $row['subject']), 'question' => $row['question'], 'selected' => $row['id_topic'] == $firstTopic); } $smcFunc['db_free_result']($request); } if (count($boards) > 1) { $request = $smcFunc['db_query']('', ' SELECT id_board, name FROM {db_prefix}boards WHERE id_board IN ({array_int:boards}) ORDER BY name LIMIT ' . count($boards), array('boards' => $boards)); while ($row = $smcFunc['db_fetch_assoc']($request)) { $context['boards'][] = array('id' => $row['id_board'], 'name' => $row['name'], 'selected' => $row['id_board'] == $topic_data[$firstTopic]['board']); } $smcFunc['db_free_result']($request); } $context['topics'] = $topic_data; foreach ($topic_data as $id => $topic) { $context['topics'][$id]['selected'] = $topic['id'] == $firstTopic; } $context['page_title'] = $txt['merge']; $context['sub_template'] = 'merge_extra_options'; return; } // Determine target board. $target_board = count($boards) > 1 ? (int) $_REQUEST['board'] : $boards[0]; if (!in_array($target_board, $boards)) { fatal_lang_error('no_board'); } // Determine which poll will survive and which polls won't. $target_poll = count($polls) > 1 ? (int) $_POST['poll'] : (count($polls) == 1 ? $polls[0] : 0); if ($target_poll > 0 && !in_array($target_poll, $polls)) { fatal_lang_error('no_access', false); } $deleted_polls = empty($target_poll) ? $polls : array_diff($polls, array($target_poll)); // Determine the subject of the newly merged topic - was a custom subject specified? if (empty($_POST['subject']) && isset($_POST['custom_subject']) && $_POST['custom_subject'] != '') { $target_subject = strtr($smcFunc['htmltrim']($smcFunc['htmlspecialchars']($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => '')); // Keep checking the length. if ($smcFunc['strlen']($target_subject) > 100) { $target_subject = $smcFunc['substr']($target_subject, 0, 100); } // Nothing left - odd but pick the first topics subject. if ($target_subject == '') { $target_subject = $topic_data[$firstTopic]['subject']; } } elseif (!empty($topic_data[(int) $_POST['subject']]['subject'])) { $target_subject = $topic_data[(int) $_POST['subject']]['subject']; } else { $target_subject = $topic_data[$firstTopic]['subject']; } // Get the first and last message and the number of messages.... $request = $smcFunc['db_query']('', ' SELECT approved, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg, COUNT(*) AS message_count FROM {db_prefix}messages WHERE id_topic IN ({array_int:topics}) GROUP BY approved ORDER BY approved DESC', array('topics' => $topics)); $topic_approved = 1; $first_msg = 0; while ($row = $smcFunc['db_fetch_assoc']($request)) { // If this is approved, or is fully unapproved. if ($row['approved'] || !isset($first_msg)) { $first_msg = $row['first_msg']; $last_msg = $row['last_msg']; if ($row['approved']) { $num_replies = $row['message_count'] - 1; $num_unapproved = 0; } else { $topic_approved = 0; $num_replies = 0; $num_unapproved = $row['message_count']; } } else { // If this has a lower first_msg then the first post is not approved and hence the number of replies was wrong! if ($first_msg > $row['first_msg']) { $first_msg = $row['first_msg']; $num_replies++; $topic_approved = 0; } $num_unapproved = $row['message_count']; } } $smcFunc['db_free_result']($request); // Ensure we have a board stat for the target board. if (!isset($boardTotals[$target_board])) { $boardTotals[$target_board] = array('posts' => 0, 'topics' => 0, 'unapproved_posts' => 0, 'unapproved_topics' => 0); } // Fix the topic count stuff depending on what the new one counts as. if ($topic_approved) { $boardTotals[$target_board]['topics']--; } else { $boardTotals[$target_board]['unapproved_topics']--; } $boardTotals[$target_board]['unapproved_posts'] -= $num_unapproved; $boardTotals[$target_board]['posts'] -= $topic_approved ? $num_replies + 1 : $num_replies; // Get the member ID of the first and last message. $request = $smcFunc['db_query']('', ' SELECT id_member FROM {db_prefix}messages WHERE id_msg IN ({int:first_msg}, {int:last_msg}) ORDER BY id_msg LIMIT 2', array('first_msg' => $first_msg, 'last_msg' => $last_msg)); list($member_started) = $smcFunc['db_fetch_row']($request); list($member_updated) = $smcFunc['db_fetch_row']($request); // First and last message are the same, so only row was returned. if ($member_updated === NULL) { $member_updated = $member_started; } $smcFunc['db_free_result']($request); // Obtain all the message ids we are going to affect. $affected_msgs = array(); $request = $smcFunc['db_query']('', ' SELECT id_msg FROM {db_prefix}messages WHERE id_topic IN ({array_int:topic_list})', array('topic_list' => $topics)); while ($row = $smcFunc['db_fetch_row']($request)) { $affected_msgs[] = $row[0]; } $smcFunc['db_free_result']($request); // Assign the first topic ID to be the merged topic. $id_topic = min($topics); // Delete the remaining topics. $deleted_topics = array_diff($topics, array($id_topic)); $smcFunc['db_query']('', ' DELETE FROM {db_prefix}topics WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics)); $smcFunc['db_query']('', ' DELETE FROM {db_prefix}log_search_subjects WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics)); // Asssign the properties of the newly merged topic. $smcFunc['db_query']('', ' UPDATE {db_prefix}topics SET id_board = {int:id_board}, id_member_started = {int:id_member_started}, id_member_updated = {int:id_member_updated}, id_first_msg = {int:id_first_msg}, id_last_msg = {int:id_last_msg}, id_poll = {int:id_poll}, num_replies = {int:num_replies}, unapproved_posts = {int:unapproved_posts}, num_views = {int:num_views}, is_sticky = {int:is_sticky}, approved = {int:approved} WHERE id_topic = {int:id_topic}', array('id_board' => $target_board, 'is_sticky' => $is_sticky, 'approved' => $topic_approved, 'id_topic' => $id_topic, 'id_member_started' => $member_started, 'id_member_updated' => $member_updated, 'id_first_msg' => $first_msg, 'id_last_msg' => $last_msg, 'id_poll' => $target_poll, 'num_replies' => $num_replies, 'unapproved_posts' => $num_unapproved, 'num_views' => $num_views)); // Grab the response prefix (like 'Re: ') in the default forum language. if (!isset($context['response_prefix']) && !($context['response_prefix'] = cache_get_data('response_prefix'))) { if ($language === $user_info['language']) { $context['response_prefix'] = $txt['response_prefix']; } else { loadLanguage('index', $language, false); $context['response_prefix'] = $txt['response_prefix']; loadLanguage('index'); } cache_put_data('response_prefix', $context['response_prefix'], 600); } // Change the topic IDs of all messages that will be merged. Also adjust subjects if 'enforce subject' was checked. $smcFunc['db_query']('', ' UPDATE {db_prefix}messages SET id_topic = {int:id_topic}, id_board = {int:target_board}' . (empty($_POST['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. $smcFunc['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... $smcFunc['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. $smcFunc['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. $request = $smcFunc['db_query']('', ' SELECT id_member, MIN(id_msg) AS new_id_msg FROM {db_prefix}log_topics WHERE id_topic IN ({array_int:topics}) GROUP BY id_member', array('topics' => $topics)); if ($smcFunc['db_num_rows']($request) > 0) { $replaceEntries = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $replaceEntries[] = array($row['id_member'], $id_topic, $row['new_id_msg']); } $smcFunc['db_insert']('replace', '{db_prefix}log_topics', array('id_member' => 'int', 'id_topic' => 'int', 'id_msg' => 'int'), $replaceEntries, array('id_member', 'id_topic')); unset($replaceEntries); // Get rid of the old log entries. $smcFunc['db_query']('', ' DELETE FROM {db_prefix}log_topics WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics)); } $smcFunc['db_free_result']($request); // Merge topic notifications. $notifications = isset($_POST['notifications']) && is_array($_POST['notifications']) ? array_intersect($topics, $_POST['notifications']) : array(); if (!empty($notifications)) { $request = $smcFunc['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 ($smcFunc['db_num_rows']($request) > 0) { $replaceEntries = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $replaceEntries[] = array($row['id_member'], $id_topic, 0, $row['sent']); } $smcFunc['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); $smcFunc['db_query']('', ' DELETE FROM {db_prefix}log_topics WHERE id_topic IN ({array_int:deleted_topics})', array('deleted_topics' => $deleted_topics)); } $smcFunc['db_free_result']($request); } // Get rid of the redundant polls. if (!empty($deleted_polls)) { $smcFunc['db_query']('', ' DELETE FROM {db_prefix}polls WHERE id_poll IN ({array_int:deleted_polls})', array('deleted_polls' => $deleted_polls)); $smcFunc['db_query']('', ' DELETE FROM {db_prefix}poll_choices WHERE id_poll IN ({array_int:deleted_polls})', array('deleted_polls' => $deleted_polls)); $smcFunc['db_query']('', ' DELETE FROM {db_prefix}log_polls WHERE id_poll IN ({array_int:deleted_polls})', array('deleted_polls' => $deleted_polls)); } // Cycle through each board... foreach ($boardTotals as $id_board => $stats) { $smcFunc['db_query']('', ' UPDATE {db_prefix}boards SET num_topics = CASE WHEN {int:topics} > num_topics THEN 0 ELSE num_topics - {int:topics} END, unapproved_topics = CASE WHEN {int:unapproved_topics} > unapproved_topics THEN 0 ELSE unapproved_topics - {int:unapproved_topics} END, num_posts = CASE WHEN {int:posts} > num_posts THEN 0 ELSE num_posts - {int:posts} END, unapproved_posts = CASE WHEN {int:unapproved_posts} > unapproved_posts THEN 0 ELSE unapproved_posts - {int:unapproved_posts} END WHERE id_board = {int:id_board}', array('id_board' => $id_board, 'topics' => $stats['topics'], 'unapproved_topics' => $stats['unapproved_topics'], 'posts' => $stats['posts'], 'unapproved_posts' => $stats['unapproved_posts'])); } // Determine the board the final topic resides in $request = $smcFunc['db_query']('', ' SELECT id_board FROM {db_prefix}topics WHERE id_topic = {int:id_topic} LIMIT 1', array('id_topic' => $id_topic)); list($id_board) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); require_once $sourcedir . '/Subs-Post.php'; // Update all the statistics. updateStats('topic'); updateStats('subject', $id_topic, $target_subject); updateLastMessages($boards); logAction('merge', array('topic' => $id_topic, 'board' => $id_board)); // Notify people that these topics have been merged? sendNotifications($id_topic, 'merge'); // If there's a search index that needs updating, update it... require_once $sourcedir . '/Search.php'; $searchAPI = findSearchAPI(); if (is_callable(array($searchAPI, 'topicMerge'))) { $searchAPI->topicMerge($id_topic, $topics, $affected_msgs, empty($_POST['enforce_subject']) ? null : array($context['response_prefix'], $target_subject)); } // Send them to the all done page. redirectexit('action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board); }
/** * Take a load of messages from one place and stick them in a 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, '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, '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)); }
function CopyTopics($original_topic_id, $board_id, $count_posts) { global $txt, $scripturl, $sourcedir, $modSettings, $context, $user_info, $smcFunc; // Try to buy some time... @set_time_limit(0); // Check Topic Exists and get some info for now and later $request = $smcFunc['db_query']('', ' SELECT id_board, id_poll, num_replies FROM {db_prefix}topics WHERE id_topic = {int:original_topic_id} LIMIT 1', array('original_topic_id' => $original_topic_id)); if ($smcFunc['db_num_rows']($request) == 0) { fatal_lang_error('no_topic_id'); } list($original_board_id, $original_poll_id, $num_posts) = $smcFunc['db_fetch_row']($request); // Its topic so it has 1 more reply that it states $num_posts++; $smcFunc['db_free_result']($request); // --- Copy Topic Entry --- // Query to Copy the Topic // The Columns for the table with our new topic and board ids. // id_topic is not listed because it is Auto-Incremented // id_board is set to our destination board // id_first_msg and id_last_msg are set to 0 for now(to prevent key errors if copying into same forum) $smcFunc['db_query']('', ' INSERT INTO {db_prefix}topics (id_board, id_first_msg, id_last_msg, id_member_started, id_member_updated, is_sticky, id_poll, num_replies, num_views, locked) SELECT {int:board_id}, 0, 0, id_member_started, id_member_updated, is_sticky, id_poll, num_replies, num_views, locked FROM {db_prefix}topics WHERE id_topic = {int:original_topic_id} LIMIT 1', array('original_topic_id' => $original_topic_id, 'board_id' => $board_id)); $topic_id = $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic'); // --- Copy Messages Entries --- // Query to Copy EVERY post in the topic (Potentially could be a beast of a query) // The Columns for the table with our new topic and board ids. // id_msg is not listed because it is Auto-Incremented // id_msg_modified is made to the old msg id temporarily. $smcFunc['db_query']('', ' INSERT INTO {db_prefix}messages (id_topic, id_board, id_msg_modified, id_member, poster_time, subject, poster_name, poster_email, poster_ip, modified_name, body, icon, approved) SELECT {int:topic_id}, {int:board_id}, id_msg, id_member, poster_time, subject, poster_name, poster_email, poster_ip, modified_name, body, icon, approved FROM {db_prefix}messages WHERE id_topic = {int:original_topic_id} ORDER BY id_msg ASC', array('original_topic_id' => $original_topic_id, 'topic_id' => $topic_id, 'board_id' => $board_id)); // --- Log Search Subject Cache (for searching) --- // Standard // Query to Copy EVERY matching row // The Columns for the table with our new topic id instead $smcFunc['db_query']('', ' INSERT INTO {db_prefix}log_search_subjects (word, id_topic) SELECT word, {int:topic_id} FROM {db_prefix}log_search_subjects WHERE id_topic = {int:original_topic_id}', array('original_topic_id' => $original_topic_id, 'topic_id' => $topic_id)); // Custom Search Index? if (!empty($modSettings['search_custom_index_config'])) { // Query to Copy EVERY matching row // The Columns for the table with our new msg id instead $smcFunc['db_query']('', ' INSERT INTO {db_prefix}log_search_words (id_word, id_msg) SELECT w.id_word, m.id_msg FROM {db_prefix}log_search_words as w LEFT JOIN {db_prefix}messages as m ON (w.id_msg = m.id_msg_modified) WHERE m.id_topic = {int:topic_id}', array('topic_id' => $topic_id)); } /* Disabled v1.2 - Causing duplicate key issues // --- Log_Search_Results --- // Query to Copy EVERY matching row // Include this new topic in existing search results // The Columns for the table with our new msg id and topic id instead $smcFunc['db_query']('', ' INSERT INTO {db_prefix}log_search_results (id_search, id_topic, id_msg, relevance, num_matches) SELECT r.id_search, {int:topic_id}, m.id_msg, r.relevance, r.num_matches FROM {db_prefix}log_search_results as r LEFT JOIN {db_prefix}messages as m ON (r.id_msg = m.id_msg_modified) WHERE m.id_topic = {int:topic_id}', array( 'topic_id' => $topic_id, ) ); */ // --- Copy Attachments --- // * Only those less than 1mb in size, larger files may crash your server // We need to know where this thing is going. if (!empty($modSettings['currentAttachmentUploadDir'])) { if (!is_array($modSettings['attachmentUploadDir'])) { $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']); } // Just use the current path for temp files. $attach_dir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']]; $id_folder = $modSettings['currentAttachmentUploadDir']; } else { $attach_dir = $modSettings['attachmentUploadDir']; $id_folder = 1; } $doattachments = 1; // First make sure the attachment dir is writable. if (!is_writable($attach_dir)) { // Try to fix it. @chmod($attach_dir, 0777); // Guess that didn't work :/? if (!is_writable($attach_dir)) { $doattachments = 0; } } // Check how we are on directory filesize? if ($doattachments && !empty($modSettings['attachmentDirSizeLimit'])) { $doattachments = attachmentDirectorySizeCheck($attach_dir); } // Right try to go ahead with the attachments. if (!empty($doattachments)) { // id_attach is not listed because it is Auto-Incremented // Matching the id_msg_modified and switching it for the new id_msg // id_folder is new in 2.0 (for multiple attachment directory support), will be changed later once its been copied $smcFunc['db_query']('', ' INSERT INTO {db_prefix}attachments (fileext, mime_type, approved, id_folder, id_thumb, id_msg, id_member, attachment_type, filename, size, downloads, width, height) SELECT fileext, a.mime_type, a.approved, a.id_folder, a.id_attach, m.id_msg, a.id_member, a.attachment_type, a.filename, a.size, a.downloads, a.width, a.height FROM {db_prefix}attachments as a LEFT JOIN {db_prefix}messages as m ON (a.id_msg = m.id_msg_modified) WHERE m.id_topic = {int:topic_id} AND size < 1048600 ORDER BY id_attach ASC', array('topic_id' => $topic_id)); // Grab and store the new vs old attachment ids in a array (we temporarily stored the old ID in the id_thumb column) $request = $smcFunc['db_query']('', ' SELECT a.id_attach, a.id_thumb FROM {db_prefix}attachments as a LEFT JOIN {db_prefix}messages as m ON (a.id_msg = m.id_msg) WHERE m.id_topic = {int:topic_id} ORDER BY id_attach ASC', array('topic_id' => $topic_id)); // Did it copy any attachment entries in the db? if ($smcFunc['db_num_rows']($request) != 0) { $ids = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $ids[$row['id_attach']] = $row['id_thumb']; } // Tidy up $smcFunc['db_free_result']($request); unset($row); // Re-Associate Thumbs with our new attachment and thumb ids // Grab all the information about the attachments so we can re-associate them to the correct $request = $smcFunc['db_query']('', ' SELECT a.id_attach as attach_id, IFNULL(a3.id_attach, 0) as thumb_id FROM {db_prefix}attachments as a LEFT JOIN {db_prefix}messages as m ON (a.id_msg = m.id_msg) LEFT JOIN {db_prefix}attachments as a2 ON (a.id_thumb = a2.id_attach) LEFT JOIN {db_prefix}attachments as a3 ON (a2.id_thumb = a3.id_thumb AND m.id_msg = a3.id_msg) WHERE m.id_topic = {int:topic_id} AND a2.id_attach != 0 ', array('topic_id' => $topic_id)); $change = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $change[$row['attach_id']] = $row['thumb_id']; } foreach ($change as $a => $b) { $smcFunc['db_query']('', ' UPDATE {db_prefix}attachments SET id_thumb = {int:thumb_id} WHERE id_attach = {int:attach_id} AND id_attach != 0', array('attach_id' => $a, 'thumb_id' => (int) $b)); } unset($a, $b, $change, $row); // Grab all the information about the attachments (including thumbs) attached to this topic $request = $smcFunc['db_query']('', ' SELECT a.id_attach as file_id, a.filename as filename, a.size as filesize, a.id_thumb as thumb_id, a2.filename as thumbname, a2.size as thumbsize, a.id_folder as folder, a2.id_folder as thumbfolder FROM {db_prefix}attachments as a LEFT JOIN {db_prefix}messages as m ON (a.id_msg = m.id_msg) LEFT JOIN {db_prefix}attachments as a2 ON (a.id_thumb = a2.id_attach) WHERE m.id_topic = {int:topic_id} AND a.attachment_type != 3 ORDER BY a.id_attach ASC', array('topic_id' => $topic_id)); // Attachments found if ($smcFunc['db_num_rows']($request) != 0) { $attachments = array(); // For each attachment, we will try to copy the file while ($row = $smcFunc['db_fetch_assoc']($request)) { $row['original_id'] = $ids[$row['file_id']]; $row['original_thumb_id'] = empty($row['thumb_id']) ? 0 : $ids[$row['thumb_id']]; $attachments[] = $row; } // Tidy up $smcFunc['db_free_result']($request); unset($ids, $row); foreach ($attachments as $row) { // Is there enough space for the copied attachment + thumb if (empty($modSettings['attachmentDirSizeLimit']) || $doattachments + (int) $row['filesize'] + (int) $row['thumbsize'] < $modSettings['attachmentDirSizeLimit'] * 1024) { // Copy the attachment if ($filename = copyAttachment($row, $attach_dir)) { // Successly copied the attachment // Update the attachment db entry with the new filename (only if new filename was generated); $smcFunc['db_query']('', ' UPDATE {db_prefix}attachments SET filename = {string:filename}, id_folder = {int:id_folder} WHERE id_attach = {int:file_id} ', array('file_id' => $row['file_id'], 'filename' => $filename, 'id_folder' => $id_folder)); // Increase the size $doattachments = $doattachments + (int) $row['filesize'] + (int) $row['thumbsize']; // If theres a thumb, rename that aswell if (!empty($row['thumb_id'])) { $smcFunc['db_query']('', ' UPDATE {db_prefix}attachments SET filename = {string:thumbname}, id_folder = {int:id_folder} WHERE id_attach = {int:thumb_id} ', array('thumb_id' => $row['thumb_id'], 'thumbname' => $filename . '_thumb', 'id_folder' => $id_folder)); } } else { // Copying the attachment failed, so delete the db entries $smcFunc['db_query']('', ' DELETE FROM {db_prefix}attachments WHERE id_attach = {int:file_id} OR id_attach = {int:thumb_id} ', array('file_id' => $row['file_id'], 'thumb_id' => $row['thumb_id'])); } } else { // Ran out of space or error, so delete the db entries $smcFunc['db_query']('', ' DELETE FROM {db_prefix}attachments WHERE id_attach = {int:file_id} OR id_attach = {int:thumb_id} ', array('file_id' => $row['file_id'], 'thumb_id' => $row['thumb_id'])); } // Tidy up unset($row); } // Tidy up unset($attachments, $ids, $row); } } } // Fix Attachment Icon, if icon is set to clip and no attachments were copied. $request = $smcFunc['db_query']('', ' SELECT count(a.id_attach) as attachments, m.icon, m.id_msg FROM {db_prefix}messages as m LEFT JOIN {db_prefix}attachments as a ON (m.id_msg = a.id_msg) WHERE m.id_topic = {int:topic_id} GROUP BY a.id_attach ', array('topic_id' => $topic_id)); $fix = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { if ($row['attachments'] == 0 && $row['icon'] == 'clip') { $fix[] = $row['id_msg']; } } if (!empty($fix)) { // So change it back to default $smcFunc['db_query']('', ' UPDATE {db_prefix}messages SET icon = {string:default_icon} WHERE id_topic = {int:topic_id} AND id_msg IN ({array_int:id_msgs}) AND icon = {string:clip} ', array('topic_id' => $topic_id, 'id_msgs' => $fix, 'default_icon' => 'xx', 'clip' => 'clip')); } // Tidy up unset($fix, $row); // --- Fix some stats and logs --- // Fix id_msg_modified to the New id_msg $smcFunc['db_query']('', ' UPDATE {db_prefix}messages SET id_msg_modified = id_msg WHERE id_topic = {int:topic_id}', array('topic_id' => $topic_id)); // Grab First & Last Message Id $request = $smcFunc['db_query']('', ' SELECT max(id_msg) as last, min(id_msg) as first FROM {db_prefix}messages WHERE id_topic = {int:topic_id}', array('topic_id' => $topic_id)); $row = $smcFunc['db_fetch_assoc']($request); // Update the topic info with that info $smcFunc['db_query']('', ' UPDATE {db_prefix}topics SET id_first_msg = {int:first}, id_last_msg = {int:last} WHERE id_topic = {int:topic_id}', array('topic_id' => $topic_id, 'first' => $row['first'], 'last' => $row['last'])); // Update log topics (for this user only) $smcFunc['db_query']('', ' REPLACE INTO {db_prefix}log_topics (id_topic, id_member, id_msg) VALUES ({int:topic_id}, {int:user_id}, {int:last}) ', array('topic_id' => $topic_id, 'last' => $row['last'], 'user_id' => $context['user']['id'])); // Update log boards (for this user only) $smcFunc['db_query']('', ' REPLACE INTO {db_prefix}log_boards (id_board, id_member, id_msg) VALUES ({int:board_id}, {int:user_id}, {int:last}) ', array('board_id' => $board_id, 'last' => $row['last'], 'user_id' => $context['user']['id'])); require_once $sourcedir . '/Subs-Post.php'; updateLastMessages($board_id, $row['last']); // --- Fix Post Counts --- // Posts in the board we're copying the topic to count, so we need to get the figures for each if ($count_posts) { // Increase the stats for the board $smcFunc['db_query']('', ' UPDATE {db_prefix}boards SET num_posts = num_posts + {int:num_posts}, num_topics = num_topics + 1 WHERE id_board = {int:board_id} ', array('board_id' => $board_id, 'num_posts' => $num_posts)); // How many posts have been made by each user in the copied topic? $request = $smcFunc['db_query']('', ' SELECT count(*) as increase, id_member FROM {db_prefix}messages WHERE id_topic = {int:topic_id} AND id_member > 0 GROUP BY id_member ', array('topic_id' => $topic_id)); // Any members to update (non-guests); if ($smcFunc['db_num_rows']($request) != 0) { $members = $increase = array(); // Prepare the information in arrays for easy update while ($row = $smcFunc['db_fetch_assoc']($request)) { $increase[$row['id_member']] = $row['increase']; // Store the member ids, as we will need to Update PostGroups if (!in_array($row['id_member'], $members)) { $members[] = $row['id_member']; } } // Update each users postcount accordingly. Could add significant number of queries for large topics. foreach ($increase as $a => $b) { $smcFunc['db_query']('', ' UPDATE {db_prefix}members SET posts = posts + {int:posts} WHERE id_member = {int:id_member} ', array('id_member' => $a, 'posts' => $b)); } unset($increase, $a, $b); // Update PostGroups for member who's postcounts have been altered. updateStats('postgroups', $members); } } $_SESSION['last_read_topic'] = $original_topic_id; // --- Copy Poll --- // Is it a poll? if ($original_poll_id > 0) { // Copy the poll // id_poll is not listed because it is Auto-Incremented // The rest are the same $smcFunc['db_query']('', ' INSERT INTO {db_prefix}polls (question, voting_locked, max_votes, expire_time, hide_results, change_vote, id_member, poster_name) SELECT question, voting_locked, max_votes, expire_time, hide_results, change_vote, id_member, poster_name FROM {db_prefix}polls WHERE id_poll = {int:original_poll_id} ', array('original_poll_id' => $original_poll_id)); // Save the new poll id $poll_id = $smcFunc['db_insert_id']('{db_prefix}polls', 'id_poll'); // Update the topic info with the poll id $smcFunc['db_query']('', ' UPDATE {db_prefix}topics SET id_poll = {int:poll_id} WHERE id_topic = {int:topic_id} ', array('poll_id' => $poll_id, 'topic_id' => $topic_id)); // --- Copy Poll Choices --- // Query to Select & Copy ALL the poll choices in one query. // id_poll is set to our new poll id $smcFunc['db_query']('', ' INSERT INTO {db_prefix}poll_choices (id_poll, id_choice, label, votes) SELECT {int:poll_id}, id_choice, label, votes FROM {db_prefix}poll_choices WHERE id_poll = {int:original_poll_id} ORDER BY id_choice ASC ', array('poll_id' => $poll_id, 'original_poll_id' => $original_poll_id)); // --- Copy Log Polls --- // Query to Select & Copy the log polls in one query. (depending on the no. of voters, it could be heavy) // id_poll is set to our new poll id $smcFunc['db_query']('', ' INSERT INTO {db_prefix}log_polls(id_poll, id_member, id_choice) SELECT {int:poll_id}, id_member, id_choice FROM {db_prefix}log_polls WHERE id_poll = {int:original_poll_id} ', array('poll_id' => $poll_id, 'original_poll_id' => $original_poll_id)); } // --- Calender Events --- // Query to Copy each calendar entry related to this topic // id_event is not listed as its auto-incremented // id_topic and id_board are set to our new poll id $smcFunc['db_query']('', ' INSERT INTO {db_prefix}calendar (start_date, end_date, id_board, id_topic, title, id_member) SELECT start_date, end_date, {int:board_id}, {int:topic_id}, title, id_member FROM {db_prefix}calendar WHERE id_topic = {int:original_topic_id} ORDER BY id_event ASC ', array('topic_id' => $topic_id, 'board_id' => $board_id, 'original_topic_id' => $original_topic_id)); updateStats('topic'); updateStats('message'); updateSettings(array('calendar_updated' => time())); }
function CopyTopics($original_topic_id, $board_id, $countPosts) { global $db_prefix, $txt, $scripturl, $sourcedir, $modSettings, $context, $user_info; // Try to buy some time... @set_time_limit(0); // Check Topic Exists and get some info for now and later $request = db_query("\n\t\tSELECT ID_BOARD, ID_POLL, numReplies\n\t\tFROM {$db_prefix}topics\n\t\tWHERE ID_TOPIC = {$original_topic_id}\n\t\tLIMIT 1\n\t\t", __FILE__, __LINE__); if (mysql_num_rows($request) == 0) { fatal_lang_error('smf263'); } list($original_board_id, $original_poll_id, $numPosts) = mysql_fetch_row($request); // Its topic so it has 1 more reply that it states $numPosts++; mysql_free_result($request); // --- Copy Topic Entry --- // The Columns for the table with our new topic and board ids. // ID_TOPIC is not listed because it is Auto-Incremented // ID_BOARD is set to our destination board // ID_FIRST_MSG and ID_LAST_MSG are set to 0 for now(to prevent key errors if copying into same forum) $insert = "ID_BOARD, ID_FIRST_MSG, ID_LAST_MSG, ID_MEMBER_STARTED, ID_MEMBER_UPDATED, isSticky, ID_POLL, numReplies, numViews, locked"; $select = "'{$board_id}', 0, 0, ID_MEMBER_STARTED, ID_MEMBER_UPDATED, isSticky, ID_POLL, numReplies, numViews, locked"; // Query to Copy the Topic db_query("\n\t\tINSERT INTO {$db_prefix}topics (" . $insert . ")\n\t\tSELECT " . $select . "\n\t\tFROM {$db_prefix}topics\n\t\tWHERE ID_TOPIC = '" . $original_topic_id . "'\n\t\tLIMIT 1\n\t", __FILE__, __LINE__); $topic_id = db_insert_id(); // Tidy up unset($insert, $select); // --- Copy Messages Entries --- // The Columns for the table with our new topic and board ids. // ID_MSG is not listed because it is Auto-Incremented // ID_MSG_MODIFIED is made to the old msg id temporarily. $insert = "ID_TOPIC, ID_BOARD, ID_MSG_MODIFIED, ID_MEMBER, posterTime, subject, posterName, posterEmail, posterIP, modifiedName, body, icon"; $select = "'{$topic_id}', '{$board_id}', ID_MSG, ID_MEMBER, posterTime, subject, posterName, posterEmail, posterIP, modifiedName, body, icon"; // Query to Copy EVERY post in the topic (Potentially could be a beast of a query) db_query("\n\t\tINSERT INTO {$db_prefix}messages (" . $insert . ")\n\t\tSELECT " . $select . "\n\t\tFROM {$db_prefix}messages\n\t\tWHERE ID_TOPIC = " . $original_topic_id . "\n\t\tORDER BY ID_MSG ASC\n\t", __FILE__, __LINE__); // Tidy up unset($insert, $select); // --- Log Search Subject Cache (for searching) --- // Standard // The Columns for the table with our new topic id instead $insert = "word, ID_TOPIC"; $select = "word, '{$topic_id}'"; // Query to Copy EVERY matching row db_query("\n\t\tINSERT INTO {$db_prefix}log_search_subjects (" . $insert . ")\n\t\tSELECT " . $select . "\n\t\tFROM {$db_prefix}log_search_subjects\n\t\tWHERE ID_TOPIC = " . $original_topic_id . "\n\t", __FILE__, __LINE__); // Tidy up unset($insert, $select); // Custom Search Index? if (!empty($modSettings['search_custom_index_config'])) { // The Columns for the table with our new msg id instead $insert = "ID_WORD, ID_MSG"; $select = "w.ID_WORD, m.ID_MSG"; // Query to Copy EVERY matching row db_query("\n\t\t\tINSERT INTO {$db_prefix}log_search_words (" . $insert . ")\n\t\t\tSELECT " . $select . "\n\t\t\tFROM {$db_prefix}log_search_words as w\n\t\t\t\tLEFT JOIN {$db_prefix}messages as m ON (w.ID_MSG = m.ID_MSG_MODIFIED)\n\t\t\tWHERE m.ID_TOPIC = " . $topic_id . "\n\t\t", __FILE__, __LINE__); // Tidy up unset($insert, $select); } /* Disabled v1.2 - Causing duplicate key issues // --- Log_Search_Results --- // Include this new topic in existing search results // The Columns for the table with our new msg id and topic id instead $insert = "ID_SEARCH, ID_TOPIC, ID_MSG, relevance, num_matches"; $select = "r.ID_SEARCH, '$topic_id', m.ID_MSG, r.relevance, r.num_matches"; // Query to Copy EVERY matching row db_query(" INSERT INTO {$db_prefix}log_search_results (". $insert .") SELECT ". $select ." FROM {$db_prefix}log_search_results as r LEFT JOIN {$db_prefix}messages as m ON (r.ID_MSG = m.ID_MSG_MODIFIED) WHERE m.ID_TOPIC = ".$topic_id." ", __FILE__, __LINE__); // Tidy up unset($insert,$select); */ // --- Copy Attachments --- // * Only those less than 1mb in size, larger files may crash your server $doattachments = 1; // First make sure the attachment dir is writable. if (!is_writable($modSettings['attachmentUploadDir'])) { // Try to fix it. @chmod($modSettings['attachmentUploadDir'], 0777); // Guess that didn't work :/? if (!is_writable($modSettings['attachmentUploadDir'])) { $doattachments = 0; } } // Check how we are on directory filesize? if ($doattachments && !empty($modSettings['attachmentDirSizeLimit'])) { $doattachments = attachmentDirectorySizeCheck(); } // Right try to go ahead with the attachments. if (!empty($doattachments)) { // The Columns for the table // ID_ATTACH is not listed because it is Auto-Incremented // Matching the ID_MSG_MODIFIED and switching it for the new ID_MSG $insert = "ID_THUMB, ID_MSG, ID_MEMBER, attachmentType, filename, size, downloads, width, height"; $select = "a.ID_ATTACH, m.ID_MSG, a.ID_MEMBER, a.attachmentType, a.filename, a.size, a.downloads, a.width, a.height"; db_query("\n\t\t\tINSERT INTO {$db_prefix}attachments (" . $insert . ")\n\t\t\tSELECT " . $select . "\n\t\t\tFROM {$db_prefix}attachments as a\n\t\t\t\tLEFT JOIN {$db_prefix}messages as m ON (a.ID_MSG = m.ID_MSG_MODIFIED)\n\t\t\tWHERE m.ID_TOPIC = " . $topic_id . "\n\t\t\t\tAND size < 1048600\n\t\t\tORDER BY ID_ATTACH ASC\n\t\t", __FILE__, __LINE__); // Tidy up unset($insert, $select); // Grab and store the new vs old attachment ids in a array (we temporarily stored the old ID in the ID_THUMB column) $request = db_query("\n\t\t\tSELECT a.ID_ATTACH, a.ID_THUMB\n\t\t\tFROM {$db_prefix}attachments as a\n\t\t\t\tLEFT JOIN {$db_prefix}messages as m ON (a.ID_MSG = m.ID_MSG)\n\t\t\tWHERE m.ID_TOPIC = " . $topic_id . "\n\t\t\tORDER BY ID_ATTACH ASC\n\t\t", __FILE__, __LINE__); // Did it copy any attachment entries in the db? if (mysql_num_rows($request) != 0) { $ids = array(); while ($row = mysql_fetch_assoc($request)) { $ids[$row['ID_ATTACH']] = $row['ID_THUMB']; } // Tidy up mysql_free_result($request); unset($row); // Re-Associate Thumbs with our new attachment and thumb ids db_query("\n\t\t\t\tUPDATE {$db_prefix}attachments as a\n\t\t\t\t\tLEFT JOIN {$db_prefix}messages as m ON (a.ID_MSG = m.ID_MSG)\n\t\t\t\t\tLEFT JOIN {$db_prefix}attachments as a2 ON (a.ID_THUMB = a2.ID_ATTACH)\n\t\t\t\t\tLEFT JOIN {$db_prefix}attachments as a3 ON (a2.ID_THUMB = a3.ID_THUMB AND m.ID_MSG = a3.ID_MSG)\n\t\t\t\tSET a.ID_THUMB = a3.ID_ATTACH\n\t\t\t\tWHERE m.ID_TOPIC = " . $topic_id . "\n\t\t\t\t\tAND a2.ID_ATTACH != 0\n\t\t\t", __FILE__, __LINE__); // Grab all the information about the attachments (including thumbs) attached to this topic $request = db_query("\n\t\t\t\tSELECT \ta.ID_ATTACH as file_id, a.filename as filename, a.size as filesize,\n\t\t\t\t\t\ta.ID_THUMB as thumb_id, a2.filename as thumbname, a2.size as thumbsize\n\t\t\t\tFROM {$db_prefix}attachments as a\n\t\t\t\t\tLEFT JOIN {$db_prefix}messages as m ON (a.ID_MSG = m.ID_MSG)\n\t\t\t\t\tLEFT JOIN {$db_prefix}attachments as a2 ON (a.ID_THUMB = a2.ID_ATTACH)\n\t\t\t\tWHERE m.ID_TOPIC = " . $topic_id . "\n\t\t\t\t\tAND\ta.attachmentType != 3\n\t\t\t\tORDER BY a.ID_ATTACH ASC\n\t\t\t", __FILE__, __LINE__); // Attachments found if (mysql_num_rows($request) != 0) { $attachments = array(); // For each attachment, we will try to copy the file while ($row = mysql_fetch_assoc($request)) { $row['original_id'] = $ids[$row['file_id']]; $row['original_thumb_id'] = empty($row['thumb_id']) ? 0 : $ids[$row['thumb_id']]; $attachments[] = $row; } // Tidy up mysql_free_result($request); unset($ids, $row); foreach ($attachments as $row) { // Is there enough space for the copied attachment + thumb if (empty($modSettings['attachmentDirSizeLimit']) || $doattachments + (int) $row['filesize'] + (int) $row['thumbsize'] < $modSettings['attachmentDirSizeLimit'] * 1024) { // Copy the attachment if ($filename = copyAttachment($row)) { // Successly copied the attachment // Update the attachment db entry with the new filename (only if new filename was generated); db_query("\n\t\t\t\t\t\t\t\tUPDATE {$db_prefix}attachments\n\t\t\t\t\t\t\t\tSET filename = '{$filename}'\n\t\t\t\t\t\t\t\tWHERE ID_ATTACH = " . (int) $row['file_id'] . "\n\t\t\t\t\t\t\t", __FILE__, __LINE__); // Increase the size $doattachments = $doattachments + (int) $row['filesize'] + (int) $row['thumbsize']; // If theres a thumb, rename that aswell if (!empty($row['thumb_id'])) { db_query("\n\t\t\t\t\t\t\t\t\tUPDATE {$db_prefix}attachments\n\t\t\t\t\t\t\t\t\tSET filename = '" . $filename . "_thumb'\n\t\t\t\t\t\t\t\t\tWHERE ID_ATTACH = " . (int) $row['thumb_id'] . "\n\t\t\t\t\t\t\t\t", __FILE__, __LINE__); } } else { // Copying the attachment failed, so delete the db entries db_query("\n\t\t\t\t\t\t\t\tDELETE FROM {$db_prefix}attachments\n\t\t\t\t\t\t\t\tWHERE ID_ATTACH = " . (int) $row['file_id'] . "\n\t\t\t\t\t\t\t\t\tOR ID_ATTACH = " . (int) $row['thumb_id'] . "\n\t\t\t\t\t\t\t", __FILE__, __LINE__); } } else { // Ran out of space or error, so delete the db entries db_query("\n\t\t\t\t\t\t\tDELETE FROM {$db_prefix}attachments\n\t\t\t\t\t\t\tWHERE ID_ATTACH = " . (int) $row['file_id'] . "\n\t\t\t\t\t\t\t\tOR ID_ATTACH = " . (int) $row['thumb_id'] . "\n\t\t\t\t\t\t", __FILE__, __LINE__); } // Tidy up unset($row); } // Tidy up unset($attachments, $ids, $row); } } } // --- Fix some stats and logs --- // Fix ID_MSG_MODIFIED to the New ID_MSG db_query("\n\t\tUPDATE {$db_prefix}messages\n\t\tSET ID_MSG_MODIFIED = ID_MSG\n\t\tWHERE ID_TOPIC = " . $topic_id . "\n\t", __FILE__, __LINE__); // Grab First & Last Message Id $request = db_query("\n\t\tSELECT max(ID_MSG) as last, min(ID_MSG) as first\n\t\tFROM {$db_prefix}messages\n\t\tWHERE ID_TOPIC = " . $topic_id . "\n\t", __FILE__, __LINE__); $row = mysql_fetch_assoc($request); // Update the topic info with that info db_query("\n\t\tUPDATE {$db_prefix}topics\n\t\tSET ID_FIRST_MSG = " . $row['first'] . ", ID_LAST_MSG = " . $row['last'] . "\n\t\tWHERE ID_TOPIC = " . $topic_id . "\n\t", __FILE__, __LINE__); // Update log topics (for this user only) db_query("\n\t\tREPLACE\n\t\tINTO {$db_prefix}log_topics\n\t\t\t(ID_TOPIC, ID_MEMBER, ID_MSG)\n\t\tVALUES (" . $topic_id . ", " . $context['user']['id'] . ", " . $row['last'] . ")\n\t\t", __FILE__, __LINE__); // Update log boards (for this user only) db_query("\n\t\tREPLACE\n\t\tINTO {$db_prefix}log_boards\n\t\t\t(ID_BOARD, ID_MEMBER, ID_MSG)\n\t\tVALUES (" . $board_id . ", " . $context['user']['id'] . ", " . $row['last'] . ")\n\t\t", __FILE__, __LINE__); require_once $sourcedir . '/Subs-Post.php'; updateLastMessages($board_id, $row['last']); // --- Fix Post Counts --- // Posts in the board we're copying the topic to count, so we need to get the figures for each if ($countPosts) { // Increase the stats for the board db_query("\n\t\t\tUPDATE {$db_prefix}boards\n\t\t\tSET numPosts = numPosts + " . $numPosts . ", numTopics = numTopics + 1\n\t\t\tWHERE ID_BOARD = " . $board_id . "\n\t\t", __FILE__, __LINE__); // How many posts have been made by each user in the copied topic? $request = db_query("\n\t\t\tSELECT count(*) as increase, ID_MEMBER\n\t\t\tFROM {$db_prefix}messages\n\t\t\tWHERE ID_TOPIC = " . $topic_id . "\n\t\t\t\tAND ID_MEMBER > 0\n\t\t\tGROUP BY ID_MEMBER\n\t\t", __FILE__, __LINE__); // Any members to update (non-guests); if (mysql_num_rows($request) != 0) { $members = $increase = array(); // Prepare the information in arrays for easy update while ($row = mysql_fetch_assoc($request)) { $increase[$row['ID_MEMBER']] = $row['increase']; // Store the member ids, as we will need to Update PostGroups if (!in_array($row['ID_MEMBER'], $members)) { $members[] = $row['ID_MEMBER']; } } // Update each users postcount accordingly. Could add significant number of queries for large topics. foreach ($increase as $a => $b) { db_query("\n\t\t\t\t\tUPDATE {$db_prefix}members\n\t\t\t\t\tSET posts = posts + " . $b . "\n\t\t\t\t\tWHERE ID_MEMBER = " . $a . "\n\t\t\t\t", __FILE__, __LINE__); } unset($increase, $a, $b); // Update PostGroups for member who's postcounts have been altered. updateStats('postgroups', 'ID_MEMBER IN (' . implode(', ', $members) . ')'); } } $_SESSION['last_read_topic'] = $original_topic_id; // --- Copy Poll --- // Is it a poll? if ($original_poll_id > 0) { // The Columns for the table // ID_POLL is not listed because it is Auto-Incremented // The rest are the same $select = $insert = "question, votingLocked, maxVotes, expireTime, hideResults, changeVote, ID_MEMBER, posterName"; // Copy the poll db_query("\n\t\t\tINSERT INTO {$db_prefix}polls (" . $insert . ")\n\t\t\tSELECT " . $select . "\n\t\t\tFROM {$db_prefix}polls\n\t\t\tWHERE ID_POLL = '" . $original_poll_id . "'\n\t\t", __FILE__, __LINE__); // Save the new poll id $poll_id = db_insert_id(); // Update the topic info with the poll id db_query("\n\t\t\tUPDATE {$db_prefix}topics\n\t\t\tSET ID_POLL = " . $poll_id . "\n\t\t\tWHERE ID_TOPIC = " . $topic_id . "\n\t\t", __FILE__, __LINE__); // Tidy up unset($insert, $select); // --- Copy Poll Choices --- // The Columns for the table // ID_POLL is set to our new poll id $insert = "ID_POLL, ID_CHOICE, label, votes"; $select = "'{$poll_id}', ID_CHOICE, label, votes"; // Query to Select & Copy ALL the poll choices in one query. db_query("\n\t\t\tINSERT INTO {$db_prefix}poll_choices (" . $insert . ")\n\t\t\tSELECT " . $select . "\n\t\t\tFROM {$db_prefix}poll_choices\n\t\t\tWHERE ID_POLL = " . $original_poll_id . "\n\t\t\tORDER BY ID_CHOICE ASC\n\t\t", __FILE__, __LINE__); // Tidy up unset($insert, $select); // --- Copy Log Polls --- // The Columns for the table // ID_POLL is set to our new poll id $insert = "ID_POLL, ID_MEMBER, ID_CHOICE"; $select = "'{$poll_id}', ID_MEMBER, ID_CHOICE"; // Query to Select & Copy the log polls in one query. (depending on the no. of voters, it could be heavy) db_query("\n\t\t\tINSERT INTO {$db_prefix}log_polls(" . $insert . ")\n\t\t\tSELECT " . $select . "\n\t\t\tFROM {$db_prefix}log_polls\n\t\t\tWHERE ID_POLL = " . $original_poll_id . "\n\t\t", __FILE__, __LINE__); // Tidy up unset($insert, $select); } // --- Calender Events --- // The Columns for the table // ID_EVENT is not listed as its auto-incremented // ID_TOPIC and ID_BOARD are set to our new poll id $insert = "startDate, endDate, ID_BOARD, ID_TOPIC, title, ID_MEMBER"; $select = "startDate, endDate, '{$board_id}', '{$topic_id}', title, ID_MEMBER"; // Query to Copy each calendar entry related to this topic db_query("\n\t\tINSERT INTO {$db_prefix}calendar (" . $insert . ")\n\t\tSELECT " . $select . "\n\t\tFROM {$db_prefix}calendar\n\t\tWHERE ID_TOPIC = " . $original_topic_id . "\n\t\tORDER BY ID_EVENT ASC\n\t", __FILE__, __LINE__); updateStats('topic'); updateStats('message'); updateStats('calendar'); }
/** * Handles moving a topic into the helpdesk. * * After checking permissions, and so on, begin to actually move posts. * * Broadly this is done using {@link shd_create_ticket_post()}, which has hooks specifically to deal with post modification times (written in specifically to ease this function's workload) * * Operations: * - get the topic information (and checking topic access permission in the process) * - identify the status of the topic (new/with staff/with user) * - create the new ticket from these details * - assuming there are replies, query for them * - step through and repost * - send the notification PM if we're doing that * - update the attachments table * - update the action log * - remove the topic from the forum * * @see shd_topictoticket() * @since 1.0 */ function shd_topictoticket2() { global $smcFunc, $context, $txt, $modSettings, $scripturl, $sourcedir; checkSession(); checkSubmitOnce('check'); $_REQUEST['dept'] = isset($_REQUEST['dept']) ? (int) $_REQUEST['dept'] : 0; if (empty($_REQUEST['dept'])) { $_REQUEST['dept'] = -1; } // which is never a valid department! if (!shd_allowed_to('shd_topic_to_ticket', $_REQUEST['dept']) || !empty($modSettings['shd_helpdesk_only']) || !empty($modSettings['shd_disable_tickettotopic'])) { fatal_lang_error('shd_cannot_move_topic', false); } if (empty($_REQUEST['topic'])) { fatal_lang_error('shd_no_topic'); } $context['topic_id'] = (int) $_REQUEST['topic']; // Just in case, are they cancelling? if (isset($_REQUEST['cancel'])) { redirectexit('topic=' . $context['topic_id']); } if (isset($_POST['send_pm']) && (!isset($_POST['pm_content']) || trim($_POST['pm_content']) == '')) { fatal_lang_error('shd_move_no_pm_topic', false); } require_once $sourcedir . '/sd_source/Subs-SimpleDeskPost.php'; // Fetch the topic information. $request = shd_db_query('', ' SELECT m.subject, t.id_board, t.id_member_started, m.body, t.id_first_msg, m.smileys_enabled, t.id_member_updated, t.num_replies, m.poster_email, m.poster_name, m.poster_ip, m.poster_time, m.modified_time, m.modified_name, m.id_msg FROM {db_prefix}topics AS t INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) INNER JOIN {db_prefix}boards AS b ON (t.id_board = b.id_board) WHERE {query_see_board} AND t.id_topic = {int:topic} LIMIT 1', array('topic' => $context['topic_id'])); if ($smcFunc['db_num_rows']($request) == 0) { fatal_lang_error('shd_move_ticket_not_created'); } else { list($subject, $board, $owner, $body, $firstmsg, $smileys_enabled, $memberupdated, $numreplies, $postername, $posteremail, $posterip, $postertime, $modified_time, $modified_name, $smf_id_msg) = $smcFunc['db_fetch_row']($request); } $smcFunc['db_free_result']($request); // Figure out what the status of the ticket should be. $status = shd_determine_status('topictoticket', $owner, $memberupdated, $numreplies, $_REQUEST['dept']); // Are we changing the subject? $old_subject = $subject; $subject = !empty($_POST['change_subject']) && !empty($_POST['subject']) ? $_POST['subject'] : $subject; // Just before we do this, make sure we call any hooks. $context and $_POST have lots of interesting things for us. call_integration_hook('shd_hook_topictoticket'); // All okay, it seems. Let's go create the ticket. $msg_assoc = array(); $msgOptions = array('body' => $body, 'smileys_enabled' => !empty($smileys_enabled) ? 1 : 0, 'modified' => array('time' => $modified_time, 'name' => $modified_name), 'time' => $postertime); $ticketOptions = array('dept' => $_REQUEST['dept'], 'subject' => $subject, 'mark_as_read' => false, 'private' => false, 'status' => $status, 'urgency' => 0, 'assigned' => 0); $posterOptions = array('id' => $owner, 'name' => $postername, 'email' => $posteremail, 'ip' => $posterip); shd_create_ticket_post($msgOptions, $ticketOptions, $posterOptions); $msg_assoc[$smf_id_msg] = $msgOptions['id']; // Ticket created, let's dig out the replies and post them in the ticket, if there are any. if (isset($ticketOptions['id'])) { $request = shd_db_query('', ' SELECT body, id_member, poster_time, poster_name, poster_email, poster_ip, smileys_enabled, id_msg FROM {db_prefix}messages WHERE id_topic = {int:topic} AND id_msg != {int:topic_msg}', array('topic' => $context['topic_id'], 'topic_msg' => $firstmsg)); $num_replies = $smcFunc['db_num_rows']($request) + 1; // Plus one since we want to count the main ticket post as well. // The ID of the ticket we created $ticket = $ticketOptions['id']; if ($smcFunc['db_num_rows']($request) != 0) { // Now loop through each reply and post it. Hopefully there aren't too many. *looks at clock* while ($row = $smcFunc['db_fetch_assoc']($request)) { $msgOptions = array('body' => $row['body'], 'smileys_enabled' => !empty($row['smileys_enabled']) ? 1 : 0); $ticketOptions = array('id' => $ticket, 'mark_as_read' => false); $posterOptions = array('id' => $row['id_member'], 'name' => !empty($row['poster_name']) ? $row['poster_name'] : '', 'email' => !empty($row['poster_email']) ? $row['poster_email'] : '', 'ip' => !empty($row['poster_ip']) ? $row['poster_ip'] : ''); shd_create_ticket_post($msgOptions, $ticketOptions, $posterOptions); $msg_assoc[$row['id_msg']] = $msgOptions['id']; } } // Ticket: check; Replies: check; Notfiy the topic starter, if desired. if (isset($_POST['send_pm'])) { require_once $sourcedir . '/Subs-Post.php'; $request = shd_db_query('pm_find_username', ' SELECT id_member, real_name FROM {db_prefix}members WHERE id_member = {int:user} LIMIT 1', array('user' => $owner)); list($userid, $username) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); // Fix the content $replacements = array('{user}' => $username, '{subject}' => $old_subject, '{link}' => $scripturl . '?action=helpdesk;sa=ticket;ticket=' . $ticket); $message = str_replace(array_keys($replacements), array_values($replacements), $_POST['pm_content']); $recipients = array('to' => array($owner), 'bcc' => array()); sendpm($recipients, $txt['shd_ticket_moved_subject_topic'], un_htmlspecialchars($message)); } // And now for something completely different: attachments if (!empty($msg_assoc)) { // 1. Get all the attachments for these messages from the attachments table $attachIDs = array(); $query = shd_db_query('', ' SELECT id_attach, id_msg FROM {db_prefix}attachments WHERE id_msg IN ({array_int:smf_msgs})', array('smf_msgs' => array_keys($msg_assoc))); while ($row = $smcFunc['db_fetch_assoc']($query)) { $attachIDs[] = $row; } $smcFunc['db_free_result']($query); if (!empty($attachIDs)) { // 2. Do the switch // 2.1. Add them to SD's tables $array = array(); foreach ($attachIDs as $attach) { $array[] = array($attach['id_attach'], $ticket, $msg_assoc[$attach['id_msg']]); } $smcFunc['db_insert']('replace', '{db_prefix}helpdesk_attachments', array('id_attach' => 'int', 'id_ticket' => 'int', 'id_msg' => 'int'), $array, array('id_attach')); // 2.2. "Remove" them from SMF's table shd_db_query('', ' UPDATE {db_prefix}attachments SET id_msg = 0 WHERE id_msg IN ({array_int:smf_msgs})', array('smf_msgs' => array_keys($msg_assoc))); } } // Now we'll add this to the log. $log_params = array('subject' => $subject, 'ticket' => $ticket); shd_log_action('topictoticket', $log_params); // Update post counts. $request = shd_db_query('', ' SELECT id_member FROM {db_prefix}messages WHERE id_topic = {int:topic}', array('topic' => $context['topic_id'])); $posters = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { if (!isset($posters[$row['id_member']])) { $posters[$row['id_member']] = 0; } $posters[$row['id_member']]++; } $smcFunc['db_free_result']($request); foreach ($posters as $id_member => $posts) { updateMemberData($id_member, array('posts' => 'posts - ' . $posts)); } // Lastly, delete the topic from the database. shd_db_query('', ' DELETE FROM {db_prefix}topics WHERE id_topic = {int:topic} LIMIT 1', array('topic' => $context['topic_id'])); // And the replies, too. shd_db_query('', ' DELETE FROM {db_prefix}messages WHERE id_topic = {int:topic}', array('topic' => $context['topic_id'])); // Update the stats. require_once $sourcedir . '/Subs-Post.php'; updateStats('message'); updateStats('topic'); updateLastMessages($board); // Update board post counts. shd_db_query('', ' UPDATE {db_prefix}boards SET num_topics = num_topics - 1, num_posts = num_posts - {int:num_posts} WHERE id_board = {int:board}', array('board' => $board, 'num_posts' => $num_replies)); } else { fatal_lang_error('shd_move_ticket_not_created', false); } // Send them to the ticket. redirectexit('action=helpdesk;sa=ticket;ticket=' . $ticket); }
/** * 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; }
function removeMessage($message, $decreasePostCount = true) { global $db_prefix, $board, $sourcedir, $modSettings, $ID_MEMBER, $user_info; if (empty($message) || !is_numeric($message)) { return false; } $request = db_query("\n\t\tSELECT\n\t\t\tm.ID_MEMBER, m.icon, m.posterTime, m.subject," . (empty($modSettings['search_custom_index_config']) ? '' : ' m.body,') . "\n\t\t\tt.ID_TOPIC, t.ID_FIRST_MSG, t.ID_LAST_MSG, t.numReplies, t.ID_BOARD,\n\t\t\tt.ID_MEMBER_STARTED AS ID_MEMBER_POSTER,\n\t\t\tb.countPosts\n\t\tFROM ({$db_prefix}messages AS m, {$db_prefix}topics AS t, {$db_prefix}boards AS b)\n\t\tWHERE m.ID_MSG = {$message}\n\t\t\tAND t.ID_TOPIC = m.ID_TOPIC\n\t\t\tAND b.ID_BOARD = t.ID_BOARD\n\t\tLIMIT 1", __FILE__, __LINE__); if (mysql_num_rows($request) == 0) { return false; } $row = mysql_fetch_assoc($request); mysql_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'] == $ID_MEMBER) { if (!$delete_own) { if ($row['ID_MEMBER_POSTER'] == $ID_MEMBER) { if (!$delete_replies) { fatal_lang_error('cannot_delete_replies'); } } else { fatal_lang_error('cannot_delete_own'); } } elseif (($row['ID_MEMBER_POSTER'] != $ID_MEMBER || !$delete_replies) && !empty($modSettings['edit_disable_time']) && $row['posterTime'] + $modSettings['edit_disable_time'] * 60 < time()) { fatal_lang_error('modify_post_time_passed', false); } } elseif ($row['ID_MEMBER_POSTER'] == $ID_MEMBER) { if (!$delete_replies) { fatal_lang_error('cannot_delete_replies'); } } else { fatal_lang_error('cannot_delete_any'); } } } else { // Check permissions to delete this message. if ($row['ID_MEMBER'] == $ID_MEMBER) { if (!allowedTo('delete_own')) { if ($row['ID_MEMBER_POSTER'] == $ID_MEMBER && !allowedTo('delete_any')) { isAllowedTo('delete_replies'); } elseif (!allowedTo('delete_any')) { isAllowedTo('delete_own'); } } elseif (!allowedTo('delete_any') && ($row['ID_MEMBER_POSTER'] != $ID_MEMBER || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $row['posterTime'] + $modSettings['edit_disable_time'] * 60 < time()) { fatal_lang_error('modify_post_time_passed', false); } } elseif ($row['ID_MEMBER_POSTER'] == $ID_MEMBER && !allowedTo('delete_any')) { isAllowedTo('delete_replies'); } else { isAllowedTo('delete_any'); } } // 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'] != $ID_MEMBER && !$remove_any) { fatal_lang_error('cannot_remove_any'); } elseif (!$remove_any && !$remove_own) { fatal_lang_error('cannot_remove_own'); } } else { // Check permissions to delete a whole topic. if ($row['ID_MEMBER'] != $ID_MEMBER) { isAllowedTo('remove_any'); } elseif (!allowedTo('remove_any')) { isAllowedTo('remove_own'); } } // ...if there is only one post. if (!empty($row['numReplies'])) { fatal_lang_error('delFirstPost', false); } removeTopics($row['ID_TOPIC']); return true; } // 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("\n\t\t\tSELECT (IFNULL(lb.ID_MSG, 0) >= b.ID_MSG_UPDATED) AS isSeen\n\t\t\tFROM {$db_prefix}boards AS b\n\t\t\t\tLEFT JOIN {$db_prefix}log_boards AS lb ON (lb.ID_BOARD = b.ID_BOARD AND lb.ID_MEMBER = {$ID_MEMBER})\n\t\t\tWHERE b.ID_BOARD = {$modSettings['recycle_board']}", __FILE__, __LINE__); if (mysql_num_rows($request) == 0) { fatal_lang_error('recycle_no_valid_board'); } list($isRead) = mysql_fetch_row($request); mysql_free_result($request); // Insert a new topic in the recycle board. db_query("\n\t\t\tINSERT INTO {$db_prefix}topics\n\t\t\t\t(ID_BOARD, ID_MEMBER_STARTED, ID_MEMBER_UPDATED, ID_FIRST_MSG, ID_LAST_MSG)\n\t\t\tVALUES ({$modSettings['recycle_board']}, {$row['ID_MEMBER']}, {$row['ID_MEMBER']}, {$message}, {$message})", __FILE__, __LINE__); // Capture the ID of the new topic... $topicID = db_insert_id(); // If the topic creation went successful, move the message. if ($topicID > 0) { db_query("\n\t\t\t\tUPDATE {$db_prefix}messages\n\t\t\t\tSET \n\t\t\t\t\tID_TOPIC = {$topicID},\n\t\t\t\t\tID_BOARD = {$modSettings['recycle_board']},\n\t\t\t\t\ticon = 'recycled'\n\t\t\t\tWHERE ID_MSG = {$message}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); // Mark recycled topic as read. if (!$user_info['is_guest']) { db_query("\n\t\t\t\t\tREPLACE INTO {$db_prefix}log_topics\n\t\t\t\t\t\t(ID_TOPIC, ID_MEMBER, ID_MSG)\n\t\t\t\t\tVALUES ({$topicID}, {$ID_MEMBER}, {$modSettings['maxMsgID']})", __FILE__, __LINE__); } // Mark recycle board as seen, if it was marked as seen before. if (!empty($isRead) && !$user_info['is_guest']) { db_query("\n\t\t\t\t\tREPLACE INTO {$db_prefix}log_boards\n\t\t\t\t\t\t(ID_BOARD, ID_MEMBER, ID_MSG)\n\t\t\t\t\tVALUES ({$modSettings['recycle_board']}, {$ID_MEMBER}, {$modSettings['maxMsgID']})", __FILE__, __LINE__); } // Add one topic and post to the recycle bin board. db_query("\n\t\t\t\tUPDATE {$db_prefix}boards\n\t\t\t\tSET\n\t\t\t\t\tnumTopics = numTopics + 1,\n\t\t\t\t\tnumPosts = numPosts + 1\n\t\t\t\tWHERE ID_BOARD = {$modSettings['recycle_board']}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); // 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']); } } // 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("\n\t\t\tSELECT ID_MSG, ID_MEMBER\n\t\t\tFROM {$db_prefix}messages\n\t\t\tWHERE ID_TOPIC = {$row['ID_TOPIC']}\n\t\t\t\tAND ID_MSG != {$message}\n\t\t\tORDER BY ID_MSG DESC\n\t\t\tLIMIT 1", __FILE__, __LINE__); $row2 = mysql_fetch_assoc($request); mysql_free_result($request); db_query("\n\t\t\tUPDATE {$db_prefix}topics\n\t\t\tSET\n\t\t\t\tID_LAST_MSG = {$row2['ID_MSG']},\n\t\t\t\tnumReplies = IF(numReplies = 0, 0, numReplies - 1),\n\t\t\t\tID_MEMBER_UPDATED = {$row2['ID_MEMBER']}\n\t\t\tWHERE ID_TOPIC = {$row['ID_TOPIC']}\n\t\t\tLIMIT 1", __FILE__, __LINE__); } else { db_query("\n\t\t\tUPDATE {$db_prefix}topics\n\t\t\tSET numReplies = IF(numReplies = 0, 0, numReplies - 1)\n\t\t\tWHERE ID_TOPIC = {$row['ID_TOPIC']}\n\t\t\tLIMIT 1", __FILE__, __LINE__); } db_query("\n\t\tUPDATE {$db_prefix}boards\n\t\tSET numPosts = IF(numPosts = 0, 0, numPosts - 1)\n\t\tWHERE ID_BOARD = {$row['ID_BOARD']}\n\t\tLIMIT 1", __FILE__, __LINE__); // 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['countPosts'])) { updateMemberData($row['ID_MEMBER'], array('posts' => '-')); } // Only remove posts if they're not recycled. if (!$recycle) { // Remove the message! db_query("\n\t\t\tDELETE FROM {$db_prefix}messages\n\t\t\tWHERE ID_MSG = {$message}\n\t\t\tLIMIT 1", __FILE__, __LINE__); 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("\n\t\t\t\t\tDELETE FROM {$db_prefix}log_search_words\n\t\t\t\t\tWHERE ID_WORD IN (" . implode(', ', $words) . ")\n\t\t\t\t\t\tAND ID_MSG = {$message}", __FILE__, __LINE__); } } // Delete attachment(s) if they exist. require_once $sourcedir . '/ManageAttachments.php'; removeAttachments('a.attachmentType = 0 AND a.ID_MSG = ' . $message); } // Update the pesky statistics. updateStats('message'); updateStats('topic'); updateStats('calendar'); // 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; }
function moveTopics($topics, $toBoard) { global $db_prefix, $sourcedir, $ID_MEMBER, $user_info, $modSettings; // Empty array? if (empty($topics)) { return; } elseif (is_numeric($topics)) { $condition = '= ' . $topics; } elseif (count($topics) == 1) { $condition = '= ' . $topics[0]; } else { $condition = 'IN (' . implode(', ', $topics) . ')'; } $numTopics = count($topics); $fromBoards = array(); // Destination board empty or equal to 0? if (empty($toBoard)) { return; } // Determine the source boards... $request = db_query("\n\t\tSELECT ID_BOARD, COUNT(*) AS numTopics, SUM(numReplies) AS numReplies\n\t\tFROM {$db_prefix}topics\n\t\tWHERE ID_TOPIC {$condition}\n\t\tGROUP BY ID_BOARD", __FILE__, __LINE__); // Num of rows = 0 -> no topics found. Num of rows > 1 -> topics are on multiple boards. if (mysql_num_rows($request) == 0) { return; } while ($row = mysql_fetch_assoc($request)) { // Posts = (numReplies + 1) for each topic. $fromBoards[$row['ID_BOARD']] = array('numPosts' => $row['numReplies'] + $row['numTopics'], 'numTopics' => $row['numTopics'], 'ID_BOARD' => $row['ID_BOARD']); } mysql_free_result($request); // Move over the mark_read data. (because it may be read and now not by some!) $SaveAServer = max(0, $modSettings['maxMsgID'] - 50000); $request = db_query("\n\t\tSELECT lmr.ID_MEMBER, lmr.ID_MSG, t.ID_TOPIC\n\t\tFROM ({$db_prefix}topics AS t, {$db_prefix}log_mark_read AS lmr)\n\t\t\tLEFT JOIN {$db_prefix}log_topics AS lt ON (lt.ID_TOPIC = t.ID_TOPIC AND lt.ID_MEMBER = lmr.ID_MEMBER)\n\t\tWHERE t.ID_TOPIC {$condition}\n\t\t\tAND lmr.ID_BOARD = t.ID_BOARD\n\t\t\tAND lmr.ID_MSG > t.ID_FIRST_MSG\n\t\t\tAND lmr.ID_MSG > {$SaveAServer}\n\t\t\tAND lmr.ID_MSG > IFNULL(lt.ID_MSG, 0)", __FILE__, __LINE__); $log_topics = array(); while ($row = mysql_fetch_assoc($request)) { $log_topics[] = '(' . $row['ID_TOPIC'] . ', ' . $row['ID_MEMBER'] . ', ' . $row['ID_MSG'] . ')'; // Prevent queries from getting too big. Taking some steam off. if (count($log_topics) > 500) { db_query("\n\t\t\t\tREPLACE INTO {$db_prefix}log_topics\n\t\t\t\t\t(ID_TOPIC, ID_MEMBER, ID_MSG)\n\t\t\t\tVALUES " . implode(', ', $log_topics), __FILE__, __LINE__); $log_topics = array(); } } mysql_free_result($request); // Now that we have all the topics that *should* be marked read, and by which members... if (!empty($log_topics)) { // Insert that information into the database! db_query("\n\t\t\tREPLACE INTO {$db_prefix}log_topics\n\t\t\t\t(ID_TOPIC, ID_MEMBER, ID_MSG)\n\t\t\tVALUES " . implode(', ', $log_topics), __FILE__, __LINE__); } // Update the number of posts on each board. $totalTopics = 0; $totalPosts = 0; foreach ($fromBoards as $stats) { db_query("\n\t\t\tUPDATE {$db_prefix}boards\n\t\t\tSET \n\t\t\t\tnumPosts = IF({$stats['numPosts']} > numPosts, 0, numPosts - {$stats['numPosts']}),\n\t\t\t\tnumTopics = IF({$stats['numTopics']} > numTopics, 0, numTopics - {$stats['numTopics']})\n\t\t\tWHERE ID_BOARD = {$stats['ID_BOARD']}\n\t\t\tLIMIT 1", __FILE__, __LINE__); $totalTopics += $stats['numTopics']; $totalPosts += $stats['numPosts']; } db_query("\n\t\tUPDATE {$db_prefix}boards\n\t\tSET \n\t\t\tnumTopics = numTopics + {$totalTopics}, \n\t\t\tnumPosts = numPosts + {$totalPosts}\n\t\tWHERE ID_BOARD = {$toBoard}\n\t\tLIMIT 1", __FILE__, __LINE__); // Move the topic. Done. :P db_query("\n\t\tUPDATE {$db_prefix}topics\n\t\tSET ID_BOARD = {$toBoard}\n\t\tWHERE ID_TOPIC {$condition}\n\t\tLIMIT {$numTopics}", __FILE__, __LINE__); db_query("\n\t\tUPDATE {$db_prefix}messages\n\t\tSET ID_BOARD = {$toBoard}\n\t\tWHERE ID_TOPIC {$condition}", __FILE__, __LINE__); db_query("\n\t\tUPDATE {$db_prefix}calendar\n\t\tSET ID_BOARD = {$toBoard}\n\t\tWHERE ID_TOPIC {$condition}\n\t\tLIMIT {$numTopics}", __FILE__, __LINE__); // Mark target board as seen, if it was already marked as seen before. $request = db_query("\n\t\tSELECT (IFNULL(lb.ID_MSG, 0) >= b.ID_MSG_UPDATED) AS isSeen\n\t\tFROM {$db_prefix}boards AS b\n\t\t\tLEFT JOIN {$db_prefix}log_boards AS lb ON (lb.ID_BOARD = b.ID_BOARD AND lb.ID_MEMBER = {$ID_MEMBER})\n\t\tWHERE b.ID_BOARD = {$toBoard}", __FILE__, __LINE__); list($isSeen) = mysql_fetch_row($request); mysql_free_result($request); if (!empty($isSeen) && !$user_info['is_guest']) { db_query("\n\t\t\tREPLACE INTO {$db_prefix}log_boards\n\t\t\t\t(ID_BOARD, ID_MEMBER, ID_MSG)\n\t\t\tVALUES ({$toBoard}, {$ID_MEMBER}, {$modSettings['maxMsgID']})", __FILE__, __LINE__); } // Update 'em pesky stats. updateStats('topic'); updateStats('message'); updateStats('calendar'); require_once $sourcedir . '/Subs-Post.php'; $updates = array_keys($fromBoards); $updates[] = $toBoard; updateLastMessages(array_unique($updates)); }
function QuickModeration() { global $db_prefix, $sourcedir, $board, $ID_MEMBER, $modSettings, $sourcedir; // Check the session = get or post. checkSession('request'); 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 $sourcedir . '/Subs-Post.php'; // Remember the last board they moved things to. if (isset($_REQUEST['move_to'])) { $_SESSION['move_to_topic'] = $_REQUEST['move_to']; } // Only a few possible actions. $possibleActions = array('markread'); 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()); $redirect_url = 'board=' . $board . '.' . $_REQUEST['start']; } else { // !!! Ugly. There's no getting around this, is there? $boards_can = array('make_sticky' => boardsAllowedTo('make_sticky'), 'move_any' => boardsAllowedTo('move_any'), 'move_own' => boardsAllowedTo('move_own'), 'remove_any' => boardsAllowedTo('remove_any'), 'remove_own' => boardsAllowedTo('remove_own'), 'lock_any' => boardsAllowedTo('lock_any'), 'lock_own' => boardsAllowedTo('lock_own'), 'merge_any' => boardsAllowedTo('merge_any')); $redirect_url = isset($_POST['redirect_url']) ? $_POST['redirect_url'] : (isset($_SESSION['old_url']) ? $_SESSION['old_url'] : ''); } 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'; } // 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 $sourcedir . '/SplitTopics.php'; return 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 that *aren't* on this board. $request = db_query("\n\t\t\tSELECT ID_TOPIC, ID_MEMBER_STARTED, ID_BOARD, locked\n\t\t\tFROM {$db_prefix}topics\n\t\t\tWHERE ID_TOPIC IN (" . implode(', ', array_keys($_REQUEST['actions'])) . ")" . (!empty($board) ? "\n\t\t\t\tAND ID_BOARD != {$board}" : '') . "\n\t\t\tLIMIT " . count($_REQUEST['actions']), __FILE__, __LINE__); while ($row = mysql_fetch_assoc($request)) { if (!empty($board)) { unset($_REQUEST['actions'][$row['ID_TOPIC']]); } else { // Goodness, this is fun. We need to validate the action. if ($_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'] != $ID_MEMBER || !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'] != $ID_MEMBER || !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'] != $ID_MEMBER || $locked == 1 || !in_array(0, $boards_can['lock_own']) && !in_array($row['ID_BOARD'], $boards_can['lock_own']))) { unset($_REQUEST['actions'][$row['ID_TOPIC']]); } } } mysql_free_result($request); } $stickyCache = array(); $moveCache = array(0 => array(), 1 => array()); $removeCache = array(); $lockCache = array(); $markCache = 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') { // $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; } } if (empty($board)) { $affectedBoards = array(); } else { $affectedBoards = array($board => array(0, 0)); } // Do all the stickies... if (!empty($stickyCache)) { db_query("\n\t\t\tUPDATE {$db_prefix}topics\n\t\t\tSET isSticky = IF(isSticky = 1, 0, 1)\n\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $stickyCache) . ")\n\t\t\tLIMIT " . count($stickyCache), __FILE__, __LINE__); } // 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("\n\t\t\tSELECT numReplies, ID_TOPIC, ID_BOARD\n\t\t\tFROM {$db_prefix}topics\n\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $moveCache[0]) . ")" . (!empty($board) && !allowedTo('move_any') ? "\n\t\t\t\tAND ID_MEMBER_STARTED = {$ID_MEMBER}" : '') . "\n\t\t\tLIMIT " . count($moveCache[0]), __FILE__, __LINE__); $moveCache2 = array(); while ($row = mysql_fetch_assoc($request)) { $to = $moveCache[1][$row['ID_TOPIC']]; $row['numReplies']++; if (empty($to)) { continue; } if (!isset($affectedBoards[$to])) { $affectedBoards[$to] = array(0, 0); } if (!isset($affectedBoards[$row['ID_BOARD']])) { $affectedBoards[$row['ID_BOARD']] = array(0, 0); } $affectedBoards[$row['ID_BOARD']][0]--; $affectedBoards[$row['ID_BOARD']][1] -= $row['numReplies']; $affectedBoards[$to][0]++; $affectedBoards[$to][1] += $row['numReplies']; // Move the actual topic. db_query("\n\t\t\t\tUPDATE {$db_prefix}topics\n\t\t\t\tSET ID_BOARD = {$to}\n\t\t\t\tWHERE ID_TOPIC = {$row['ID_TOPIC']}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); db_query("\n\t\t\t\tUPDATE {$db_prefix}messages\n\t\t\t\tSET ID_BOARD = {$to}\n\t\t\t\tWHERE ID_TOPIC = {$row['ID_TOPIC']}", __FILE__, __LINE__); db_query("\n\t\t\t\tUPDATE {$db_prefix}calendar\n\t\t\t\tSET ID_BOARD = {$to}\n\t\t\t\tWHERE ID_TOPIC = {$row['ID_TOPIC']}", __FILE__, __LINE__); $moveCache2[] = array($row['ID_TOPIC'], $row['ID_BOARD'], $to); } mysql_free_result($request); $moveCache = $moveCache2; foreach ($affectedBoards as $ID_BOARD => $topicsPosts) { db_query("\n\t\t\t\tUPDATE {$db_prefix}boards\n\t\t\t\tSET numPosts = numPosts + {$topicsPosts['1']}, numTopics = numTopics + {$topicsPosts['0']}\n\t\t\t\tWHERE ID_BOARD = {$ID_BOARD}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); } } // 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..) if (!empty($board) && !allowedTo('remove_any')) { $result = db_query("\n\t\t\t\tSELECT ID_TOPIC\n\t\t\t\tFROM {$db_prefix}topics\n\t\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $removeCache) . ")\n\t\t\t\t\tAND ID_MEMBER_STARTED = {$ID_MEMBER}\n\t\t\t\tLIMIT " . count($removeCache), __FILE__, __LINE__); $removeCache = array(); while ($row = mysql_fetch_assoc($result)) { $removeCache[] = $row['ID_TOPIC']; } mysql_free_result($result); } // Maybe *none* were their own topics. if (!empty($removeCache)) { // Gotta send the notifications *first*! foreach ($removeCache as $topic) { logAction('remove', array('topic' => $topic)); sendNotifications($topic, 'remove'); } require_once $sourcedir . '/RemoveTopic.php'; removeTopics($removeCache); } } // And 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("\n\t\t\t\tSELECT ID_TOPIC, locked\n\t\t\t\tFROM {$db_prefix}topics\n\t\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $lockCache) . ")\n\t\t\t\t\tAND ID_MEMBER_STARTED = {$ID_MEMBER}\n\t\t\t\t\tAND locked IN (2, 0)\n\t\t\t\tLIMIT " . count($lockCache), __FILE__, __LINE__); $lockCache = array(); while ($row = mysql_fetch_assoc($result)) { $lockCache[] = $row['ID_TOPIC']; $lockStatus[$row['ID_TOPIC']] = empty($row['locked']); } mysql_free_result($result); } else { $result = db_query("\n\t\t\t\tSELECT ID_TOPIC, locked\n\t\t\t\tFROM {$db_prefix}topics\n\t\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $lockCache) . ")\n\t\t\t\tLIMIT " . count($lockCache), __FILE__, __LINE__); while ($row = mysql_fetch_assoc($result)) { $lockStatus[$row['ID_TOPIC']] = empty($row['locked']); } mysql_free_result($result); } // It could just be that *none* were their own topics... if (!empty($lockCache)) { // Alternate the locked value. db_query("\n\t\t\t\tUPDATE {$db_prefix}topics\n\t\t\t\tSET locked = IF(locked = 0, " . (allowedTo('lock_any') ? '1' : '2') . ", 0)\n\t\t\t\tWHERE ID_TOPIC IN (" . implode(', ', $lockCache) . ")\n\t\t\t\tLIMIT " . count($lockCache), __FILE__, __LINE__); } } if (!empty($markCache)) { $setString = ''; foreach ($markCache as $topic) { $setString .= "\n\t\t\t\t({$modSettings['maxMsgID']}, {$ID_MEMBER}, {$topic}),"; } db_query("\n\t\t\tREPLACE INTO {$db_prefix}log_topics\n\t\t\t\t(ID_MSG, ID_MEMBER, ID_TOPIC)\n\t\t\tVALUES" . substr($setString, 0, -1), __FILE__, __LINE__); } 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('lock', array('topic' => $topic)); sendNotifications($topic, $lockStatus ? 'lock' : 'unlock'); } foreach ($stickyCache as $topic) { logAction('sticky', array('topic' => $topic)); sendNotifications($topic, 'sticky'); } updateStats('topic'); updateStats('message'); updateStats('calendar'); if (!empty($affectedBoards)) { updateLastMessages(array_keys($affectedBoards)); } redirectexit($redirect_url); }
function createPost(&$msgOptions, &$topicOptions, &$posterOptions) { global $db_prefix, $user_info, $ID_MEMBER, $txt, $modSettings; // Set optional parameters to the default value. $msgOptions['icon'] = empty($msgOptions['icon']) ? 'xx' : $msgOptions['icon']; $msgOptions['smileys_enabled'] = !empty($msgOptions['smileys_enabled']); $msgOptions['attachments'] = empty($msgOptions['attachments']) ? array() : $msgOptions['attachments']; $topicOptions['id'] = empty($topicOptions['id']) ? 0 : (int) $topicOptions['id']; $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; $posterOptions['id'] = empty($posterOptions['id']) ? 0 : (int) $posterOptions['id']; $posterOptions['ip'] = empty($posterOptions['ip']) ? $user_info['ip2'] : $posterOptions['ip']; // If nothing was filled in as name/e-mail address, try the member table. if (!isset($posterOptions['name']) || $posterOptions['name'] == '' || empty($posterOptions['email']) && !empty($posterOptions['id'])) { if (empty($posterOptions['id'])) { $posterOptions['id'] = 0; $posterOptions['name'] = $txt[28]; $posterOptions['email'] = ''; } elseif ($posterOptions['id'] != $ID_MEMBER) { $request = db_query("\n\t\t\t\tSELECT memberName, emailAddress\n\t\t\t\tFROM {$db_prefix}members\n\t\t\t\tWHERE ID_MEMBER = {$posterOptions['id']}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); // Couldn't find the current poster? if (mysql_num_rows($request) == 0) { trigger_error('createPost(): Invalid member id ' . $posterOptions['id'], E_USER_NOTICE); $posterOptions['id'] = 0; $posterOptions['name'] = $txt[28]; $posterOptions['email'] = ''; } else { list($posterOptions['name'], $posterOptions['email']) = mysql_fetch_row($request); } mysql_free_result($request); } else { $posterOptions['name'] = $user_info['name']; $posterOptions['email'] = $user_info['email']; } $posterOptions['email'] = addslashes($posterOptions['email']); } // It's do or die time: forget any user aborts! $previous_ignore_user_abort = ignore_user_abort(true); $new_topic = empty($topicOptions['id']); // Insert the post. db_query("\n\t\tINSERT INTO {$db_prefix}messages\n\t\t\t(ID_BOARD, ID_TOPIC, ID_MEMBER, subject, body, posterName, posterEmail, posterTime,\n\t\t\tposterIP, smileysEnabled, modifiedName, icon)\n\t\tVALUES ({$topicOptions['board']}, {$topicOptions['id']}, {$posterOptions['id']}, SUBSTRING('{$msgOptions['subject']}', 1, 255), SUBSTRING('{$msgOptions['body']}', 1, 65534), SUBSTRING('{$posterOptions['name']}', 1, 255), SUBSTRING('{$posterOptions['email']}', 1, 255), " . time() . ",\n\t\t\tSUBSTRING('{$posterOptions['ip']}', 1, 255), " . ($msgOptions['smileys_enabled'] ? '1' : '0') . ", '', SUBSTRING('{$msgOptions['icon']}', 1, 16))", __FILE__, __LINE__); $msgOptions['id'] = db_insert_id(); // Something went wrong creating the message... if (empty($msgOptions['id'])) { return false; } // Fix the attachments. if (!empty($msgOptions['attachments'])) { db_query("\n\t\t\tUPDATE {$db_prefix}attachments\n\t\t\tSET ID_MSG = {$msgOptions['id']}\n\t\t\tWHERE ID_ATTACH IN (" . implode(', ', $msgOptions['attachments']) . ')', __FILE__, __LINE__); } // Insert a new topic (if the topicID was left empty. if ($new_topic) { db_query("\n\t\t\tINSERT INTO {$db_prefix}topics\n\t\t\t\t(ID_BOARD, ID_MEMBER_STARTED, ID_MEMBER_UPDATED, ID_FIRST_MSG, ID_LAST_MSG, locked, isSticky, numViews, ID_POLL)\n\t\t\tVALUES ({$topicOptions['board']}, {$posterOptions['id']}, {$posterOptions['id']}, {$msgOptions['id']}, {$msgOptions['id']},\n\t\t\t\t" . ($topicOptions['lock_mode'] === null ? '0' : $topicOptions['lock_mode']) . ', ' . ($topicOptions['sticky_mode'] === null ? '0' : $topicOptions['sticky_mode']) . ", 0, " . ($topicOptions['poll'] === null ? '0' : $topicOptions['poll']) . ')', __FILE__, __LINE__); $topicOptions['id'] = db_insert_id(); // The topic couldn't be created for some reason. if (empty($topicOptions['id'])) { // We should delete the post that did work, though... db_query("\n\t\t\t\tDELETE FROM {$db_prefix}messages\n\t\t\t\tWHERE ID_MSG = {$msgOptions['id']}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); return false; } // Fix the message with the topic. db_query("\n\t\t\tUPDATE {$db_prefix}messages\n\t\t\tSET ID_TOPIC = {$topicOptions['id']}\n\t\t\tWHERE ID_MSG = {$msgOptions['id']}\n\t\t\tLIMIT 1", __FILE__, __LINE__); // There's been a new topic AND a new post today. trackStats(array('topics' => '+', 'posts' => '+')); updateStats('topic', true); updateStats('subject', $topicOptions['id'], $msgOptions['subject']); } else { // Update the number of replies and the lock/sticky status. db_query("\n\t\t\tUPDATE {$db_prefix}topics\n\t\t\tSET\n\t\t\t\tID_MEMBER_UPDATED = {$posterOptions['id']}, ID_LAST_MSG = {$msgOptions['id']},\n\t\t\t\tnumReplies = numReplies + 1" . ($topicOptions['lock_mode'] === null ? '' : ",\n\t\t\t\tlocked = {$topicOptions['lock_mode']}") . ($topicOptions['sticky_mode'] === null ? '' : ",\n\t\t\t\tisSticky = {$topicOptions['sticky_mode']}") . "\n\t\t\tWHERE ID_TOPIC = {$topicOptions['id']}\n\t\t\tLIMIT 1", __FILE__, __LINE__); // One new post has been added today. trackStats(array('posts' => '+')); } // Creating is modifying...in a way. db_query("\n\t\tUPDATE {$db_prefix}messages\n\t\tSET ID_MSG_MODIFIED = {$msgOptions['id']}\n\t\tWHERE ID_MSG = {$msgOptions['id']}", __FILE__, __LINE__); // Increase the number of posts and topics on the board. db_query("\n\t\tUPDATE {$db_prefix}boards\n\t\tSET numPosts = numPosts + 1" . ($new_topic ? ', numTopics = numTopics + 1' : '') . "\n\t\tWHERE ID_BOARD = {$topicOptions['board']}\n\t\tLIMIT 1", __FILE__, __LINE__); // Mark inserted topic as read (only for the user calling this function). if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest']) { // Since it's likely they *read* it before replying, let's try an UPDATE first. if (!$new_topic) { db_query("\n\t\t\t\tUPDATE {$db_prefix}log_topics\n\t\t\t\tSET ID_MSG = {$msgOptions['id']} + 1\n\t\t\t\tWHERE ID_MEMBER = {$ID_MEMBER}\n\t\t\t\t\tAND ID_TOPIC = {$topicOptions['id']}\n\t\t\t\tLIMIT 1", __FILE__, __LINE__); $flag = db_affected_rows() != 0; } if (empty($flag)) { db_query("\n\t\t\t\tREPLACE INTO {$db_prefix}log_topics\n\t\t\t\t\t(ID_TOPIC, ID_MEMBER, ID_MSG)\n\t\t\t\tVALUES ({$topicOptions['id']}, {$ID_MEMBER}, {$msgOptions['id']} + 1)", __FILE__, __LINE__); } } // If there's a custom search index, it needs updating... if (!empty($modSettings['search_custom_index_config'])) { //$index_settings = unserialize($modSettings['search_custom_index_config']); $inserts = ''; foreach (text2words(stripslashes($msgOptions['body']), 4, true) as $word) { $inserts .= "({$word}, {$msgOptions['id']}),\n"; } if (!empty($inserts)) { db_query("\n\t\t\t\tINSERT IGNORE INTO {$db_prefix}log_search_words\n\t\t\t\t\t(ID_WORD, ID_MSG)\n\t\t\t\tVALUES\n\t\t\t\t\t" . substr($inserts, 0, -2), __FILE__, __LINE__); } } // Increase the post counter for the user that created the post. if (!empty($posterOptions['update_post_count']) && !empty($posterOptions['id'])) { // Are you the one that happened to create this post? if ($ID_MEMBER == $posterOptions['id']) { $user_info['posts']++; } updateMemberData($posterOptions['id'], array('posts' => '+')); } // They've posted, so they can make the view count go up one if they really want. (this is to keep views >= replies...) $_SESSION['last_read_topic'] = 0; // Better safe than sorry. if (isset($_SESSION['topicseen_cache'][$topicOptions['board']])) { $_SESSION['topicseen_cache'][$topicOptions['board']]--; } // Update all the stats so everyone knows about this new topic and message. updateStats('message', true, $msgOptions['id']); updateLastMessages($topicOptions['board'], $msgOptions['id']); // Alright, done now... we can abort now, I guess... at least this much is done. ignore_user_abort($previous_ignore_user_abort); // Success. return true; }
/** * General function to split off a topic. * creates a new topic and moves the messages with the IDs in * array messagesToBeSplit to the new topic. * the subject of the newly created topic is set to 'newSubject'. * marks the newly created message as read for the user splitting it. * updates the statistics to reflect a newly created topic. * logs the action in the moderation log. * a notification is sent to all users monitoring this topic. * * @param int $split1_ID_TOPIC * @param int[] $splitMessages * @param string $new_subject * @return int the topic ID of the new split topic. */ function splitTopic($split1_ID_TOPIC, $splitMessages, $new_subject) { global $txt; $db = database(); // Nothing to split? if (empty($splitMessages)) { fatal_lang_error('no_posts_selected', false); } // Get some board info. $request = $db->query('', ' SELECT id_board, approved FROM {db_prefix}topics WHERE id_topic = {int:id_topic} LIMIT 1', array('id_topic' => $split1_ID_TOPIC)); list($id_board, $split1_approved) = $db->fetch_row($request); $db->free_result($request); // Find the new first and last not in the list. (old topic) $request = $db->query('', ' SELECT MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg, COUNT(*) AS message_count, m.approved FROM {db_prefix}messages AS m INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:id_topic}) WHERE m.id_msg NOT IN ({array_int:no_msg_list}) AND m.id_topic = {int:id_topic} GROUP BY m.approved ORDER BY m.approved DESC LIMIT 2', array('id_topic' => $split1_ID_TOPIC, 'no_msg_list' => $splitMessages)); // You can't select ALL the messages! if ($db->num_rows($request) == 0) { fatal_lang_error('selected_all_posts', false); } $split1_first_msg = null; $split1_last_msg = null; while ($row = $db->fetch_assoc($request)) { // Get the right first and last message dependant on approved state... if (empty($split1_first_msg) || $row['myid_first_msg'] < $split1_first_msg) { $split1_first_msg = $row['myid_first_msg']; } if (empty($split1_last_msg) || $row['approved']) { $split1_last_msg = $row['myid_last_msg']; } // Get the counts correct... if ($row['approved']) { $split1_replies = $row['message_count'] - 1; $split1_unapprovedposts = 0; } else { if (!isset($split1_replies)) { $split1_replies = 0; } elseif (!$split1_approved) { $split1_replies++; } $split1_unapprovedposts = $row['message_count']; } } $db->free_result($request); $split1_firstMem = getMsgMemberID($split1_first_msg); $split1_lastMem = getMsgMemberID($split1_last_msg); // Find the first and last in the list. (new topic) $request = $db->query('', ' SELECT MIN(id_msg) AS myid_first_msg, MAX(id_msg) AS myid_last_msg, COUNT(*) AS message_count, approved FROM {db_prefix}messages WHERE id_msg IN ({array_int:msg_list}) AND id_topic = {int:id_topic} GROUP BY id_topic, approved ORDER BY approved DESC LIMIT 2', array('msg_list' => $splitMessages, 'id_topic' => $split1_ID_TOPIC)); while ($row = $db->fetch_assoc($request)) { // As before get the right first and last message dependant on approved state... if (empty($split2_first_msg) || $row['myid_first_msg'] < $split2_first_msg) { $split2_first_msg = $row['myid_first_msg']; } if (empty($split2_last_msg) || $row['approved']) { $split2_last_msg = $row['myid_last_msg']; } // Then do the counts again... if ($row['approved']) { $split2_approved = true; $split2_replies = $row['message_count'] - 1; $split2_unapprovedposts = 0; } else { // Should this one be approved?? if ($split2_first_msg == $row['myid_first_msg']) { $split2_approved = false; } if (!isset($split2_replies)) { $split2_replies = 0; } elseif (!$split2_approved) { $split2_replies++; } $split2_unapprovedposts = $row['message_count']; } } $db->free_result($request); $split2_firstMem = getMsgMemberID($split2_first_msg); $split2_lastMem = getMsgMemberID($split2_last_msg); // No database changes yet, so let's double check to see if everything makes at least a little sense. if ($split1_first_msg <= 0 || $split1_last_msg <= 0 || $split2_first_msg <= 0 || $split2_last_msg <= 0 || $split1_replies < 0 || $split2_replies < 0 || $split1_unapprovedposts < 0 || $split2_unapprovedposts < 0 || !isset($split1_approved) || !isset($split2_approved)) { fatal_lang_error('cant_find_messages'); } // You cannot split off the first message of a topic. if ($split1_first_msg > $split2_first_msg) { fatal_lang_error('split_first_post', false); } // We're off to insert the new topic! Use 0 for now to avoid UNIQUE errors. $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', 'num_replies' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', 'is_sticky' => 'int'), array((int) $id_board, $split2_firstMem, $split2_lastMem, 0, 0, $split2_replies, $split2_unapprovedposts, (int) $split2_approved, 0), array('id_topic')); $split2_ID_TOPIC = $db->insert_id('{db_prefix}topics', 'id_topic'); if ($split2_ID_TOPIC <= 0) { fatal_lang_error('cant_insert_topic'); } // Move the messages over to the other topic. $new_subject = strtr(Util::htmltrim(Util::htmlspecialchars($new_subject)), array("\r" => '', "\n" => '', "\t" => '')); // Check the subject length. if (Util::strlen($new_subject) > 100) { $new_subject = Util::substr($new_subject, 0, 100); } // Valid subject? if ($new_subject != '') { $db->query('', ' UPDATE {db_prefix}messages SET id_topic = {int:id_topic}, subject = CASE WHEN id_msg = {int:split_first_msg} THEN {string:new_subject} ELSE {string:new_subject_replies} END WHERE id_msg IN ({array_int:split_msgs})', array('split_msgs' => $splitMessages, 'id_topic' => $split2_ID_TOPIC, 'new_subject' => $new_subject, 'split_first_msg' => $split2_first_msg, 'new_subject_replies' => $txt['response_prefix'] . $new_subject)); // Cache the new topics subject... we can do it now as all the subjects are the same! updateStats('subject', $split2_ID_TOPIC, $new_subject); } // Any associated reported posts better follow... require_once SUBSDIR . '/Topic.subs.php'; updateSplitTopics(array('splitMessages' => $splitMessages, 'split2_ID_TOPIC' => $split2_ID_TOPIC, 'split1_replies' => $split1_replies, 'split1_first_msg' => $split1_first_msg, 'split1_last_msg' => $split1_last_msg, 'split1_firstMem' => $split1_firstMem, 'split1_lastMem' => $split1_lastMem, 'split1_unapprovedposts' => $split1_unapprovedposts, 'split1_ID_TOPIC' => $split1_ID_TOPIC, 'split2_first_msg' => $split2_first_msg, 'split2_last_msg' => $split2_last_msg, 'split2_ID_TOPIC' => $split2_ID_TOPIC, 'split2_approved' => $split2_approved), $id_board); require_once SUBSDIR . '/FollowUps.subs.php'; // Let's see if we can create a stronger bridge between the two topics // @todo not sure what message from the oldest topic I should link to the new one, so I'll go with the first linkMessages($split1_first_msg, $split2_ID_TOPIC); // Copy log topic entries. // @todo This should really be chunked. $request = $db->query('', ' SELECT id_member, id_msg, unwatched FROM {db_prefix}log_topics WHERE id_topic = {int:id_topic}', array('id_topic' => (int) $split1_ID_TOPIC)); if ($db->num_rows($request) > 0) { $replaceEntries = array(); while ($row = $db->fetch_assoc($request)) { $replaceEntries[] = array($row['id_member'], $split2_ID_TOPIC, $row['id_msg'], $row['unwatched']); } require_once SUBSDIR . '/Topic.subs.php'; markTopicsRead($replaceEntries, false); unset($replaceEntries); } $db->free_result($request); // Housekeeping. updateTopicStats(); updateLastMessages($id_board); logAction('split', array('topic' => $split1_ID_TOPIC, 'new_topic' => $split2_ID_TOPIC, 'board' => $id_board)); // Notify people that this topic has been split? require_once SUBSDIR . '/Notification.subs.php'; sendNotifications($split1_ID_TOPIC, 'split'); // If there's a search index that needs updating, update it... require_once SUBSDIR . '/Search.subs.php'; $searchAPI = findSearchAPI(); if (is_callable(array($searchAPI, 'topicSplit'))) { $searchAPI->topicSplit($split2_ID_TOPIC, $splitMessages); } // Return the ID of the newly created topic. return $split2_ID_TOPIC; }
function moveTopics($topics, $toBoard) { global $sourcedir, $user_info, $modSettings, $smcFunc; // Empty array? if (empty($topics)) { return; } elseif (is_numeric($topics)) { $topics = array($topics); } $num_topics = count($topics); $fromBoards = array(); // Destination board empty or equal to 0? if (empty($toBoard)) { return; } // Are we moving to the recycle board? $isRecycleDest = !empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] == $toBoard; // Determine the source boards... $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)); // Num of rows = 0 -> no topics found. Num of rows > 1 -> topics are on multiple boards. if ($smcFunc['db_num_rows']($request) == 0) { return; } while ($row = $smcFunc['db_fetch_assoc']($request)) { if (!isset($fromBoards[$row['id_board']]['num_posts'])) { $fromBoards[$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. $fromBoards[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? $row['num_topics'] : 0); $fromBoards[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts']; // Add the topics to the right type. if ($row['approved']) { $fromBoards[$row['id_board']]['num_topics'] += $row['num_topics']; } else { $fromBoards[$row['id_board']]['unapproved_topics'] += $row['num_topics']; } } $smcFunc['db_free_result']($request); // Move over the mark_read data. (because it may be read and now not by some!) $SaveAServer = max(0, $modSettings['maxMsgID'] - 50000); $request = $smcFunc['db_query']('', ' SELECT lmr.id_member, lmr.id_msg, t.id_topic FROM {db_prefix}topics AS t INNER JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = t.id_board AND lmr.id_msg > t.id_first_msg AND lmr.id_msg > {int:protect_lmr_msg}) LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = lmr.id_member) WHERE t.id_topic IN ({array_int:topics}) AND lmr.id_msg > IFNULL(lt.id_msg, 0)', array('protect_lmr_msg' => $SaveAServer, 'topics' => $topics)); $log_topics = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $log_topics[] = array($row['id_topic'], $row['id_member'], $row['id_msg']); // Prevent queries from getting too big. Taking some steam off. if (count($log_topics) > 500) { $smcFunc['db_insert']('replace', '{db_prefix}log_topics', array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), $log_topics, array('id_topic', 'id_member')); $log_topics = array(); } } $smcFunc['db_free_result']($request); // Now that we have all the topics that *should* be marked read, and by which members... if (!empty($log_topics)) { // Insert that information into the database! $smcFunc['db_insert']('replace', '{db_prefix}log_topics', array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), $log_topics, array('id_topic', 'id_member')); } // Update the number of posts on each board. $totalTopics = 0; $totalPosts = 0; $totalUnapprovedTopics = 0; $totalUnapprovedPosts = 0; foreach ($fromBoards as $stats) { $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'])); $totalTopics += $stats['num_topics']; $totalPosts += $stats['num_posts']; $totalUnapprovedTopics += $stats['unapproved_topics']; $totalUnapprovedPosts += $stats['unapproved_posts']; } $smcFunc['db_query']('', ' UPDATE {db_prefix}boards SET num_topics = num_topics + {int:total_topics}, num_posts = num_posts + {int:total_posts},' . ($isRecycleDest ? ' unapproved_posts = {int:no_unapproved}, unapproved_topics = {int:no_unapproved}' : ' unapproved_posts = unapproved_posts + {int:total_unapproved_posts}, unapproved_topics = unapproved_topics + {int:total_unapproved_topics}') . ' WHERE id_board = {int:id_board}', array('id_board' => $toBoard, 'total_topics' => $totalTopics, 'total_posts' => $totalPosts, 'total_unapproved_topics' => $totalUnapprovedTopics, 'total_unapproved_posts' => $totalUnapprovedPosts, 'no_unapproved' => 0)); // Move the topic. Done. :P $smcFunc['db_query']('', ' UPDATE {db_prefix}topics SET id_board = {int:id_board}' . ($isRecycleDest ? ', unapproved_posts = {int:no_unapproved}, approved = {int:is_approved}' : '') . ' WHERE id_topic IN ({array_int:topics})', array('id_board' => $toBoard, 'topics' => $topics, 'is_approved' => 1, 'no_unapproved' => 0)); // If this was going to the recycle bin, check what messages are being recycled, and remove them from the queue. if ($isRecycleDest && ($totalUnapprovedTopics || $totalUnapprovedPosts)) { $request = $smcFunc['db_query']('', ' SELECT id_msg FROM {db_prefix}messages WHERE id_topic IN ({array_int:topics}) and approved = {int:not_approved}', array('topics' => $topics, 'not_approved' => 0)); $approval_msgs = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $approval_msgs[] = $row['id_msg']; } $smcFunc['db_free_result']($request); // Empty the approval queue for these, as we're going to approve them next. if (!empty($approval_msgs)) { $smcFunc['db_query']('', ' DELETE FROM {db_prefix}approval_queue WHERE id_msg IN ({array_int:message_list}) AND id_attach = {int:id_attach}', array('message_list' => $approval_msgs, 'id_attach' => 0)); } // Get all the current max and mins. $request = $smcFunc['db_query']('', ' SELECT id_topic, id_first_msg, id_last_msg FROM {db_prefix}topics WHERE id_topic IN ({array_int:topics})', array('topics' => $topics)); $topicMaxMin = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $topicMaxMin[$row['id_topic']] = array('min' => $row['id_first_msg'], 'max' => $row['id_last_msg']); } $smcFunc['db_free_result']($request); // Check the MAX and MIN are correct. $request = $smcFunc['db_query']('', ' SELECT id_topic, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg FROM {db_prefix}messages WHERE id_topic IN ({array_int:topics}) GROUP BY id_topic', array('topics' => $topics)); while ($row = $smcFunc['db_fetch_assoc']($request)) { // If not, update. if ($row['first_msg'] != $topicMaxMin[$row['id_topic']]['min'] || $row['last_msg'] != $topicMaxMin[$row['id_topic']]['max']) { $smcFunc['db_query']('', ' UPDATE {db_prefix}topics SET id_first_msg = {int:first_msg}, id_last_msg = {int:last_msg} WHERE id_topic = {int:selected_topic}', array('first_msg' => $row['first_msg'], 'last_msg' => $row['last_msg'], 'selected_topic' => $row['id_topic'])); } } $smcFunc['db_free_result']($request); } $smcFunc['db_query']('', ' UPDATE {db_prefix}messages SET id_board = {int:id_board}' . ($isRecycleDest ? ',approved = {int:is_approved}' : '') . ' WHERE id_topic IN ({array_int:topics})', array('id_board' => $toBoard, 'topics' => $topics, 'is_approved' => 1)); $smcFunc['db_query']('', ' UPDATE {db_prefix}log_reported SET id_board = {int:id_board} WHERE id_topic IN ({array_int:topics})', array('id_board' => $toBoard, 'topics' => $topics)); $smcFunc['db_query']('', ' UPDATE {db_prefix}calendar SET id_board = {int:id_board} WHERE id_topic IN ({array_int:topics})', array('id_board' => $toBoard, 'topics' => $topics)); // Mark target board as seen, if it was already marked as seen before. $request = $smcFunc['db_query']('', ' SELECT (IFNULL(lb.id_msg, 0) >= b.id_msg_updated) AS isSeen 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:id_board}', array('current_member' => $user_info['id'], 'id_board' => $toBoard)); list($isSeen) = $smcFunc['db_fetch_row']($request); $smcFunc['db_free_result']($request); if (!empty($isSeen) && !$user_info['is_guest']) { $smcFunc['db_insert']('replace', '{db_prefix}log_boards', array('id_board' => 'int', 'id_member' => 'int', 'id_msg' => 'int'), array($toBoard, $user_info['id'], $modSettings['maxMsgID']), array('id_board', 'id_member')); } // Update 'em pesky stats. updateStats('topic'); updateStats('message'); updateSettings(array('calendar_updated' => time())); // Update the cache? if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3) { foreach ($topics as $topic_id) { cache_put_data('topic_board-' . $topic_id, null, 120); } } require_once $sourcedir . '/Subs-Post.php'; $updates = array_keys($fromBoards); $updates[] = $toBoard; updateLastMessages(array_unique($updates)); }
/** * Set merge options and do the actual merge of two or more topics. * * the merge options screen: * * shows topics to be merged and allows to set some merge options. * * is accessed by ?action=mergetopics;sa=options.and can also internally be called by action_quickmod(). * * uses 'merge_extra_options' sub template of the MergeTopics template. * * the actual merge: * * is accessed with ?action=mergetopics;sa=execute. * * updates the statistics to reflect the merge. * * logs the action in the moderation log. * * sends a notification is sent to all users monitoring this topic. * * redirects to ?action=mergetopics;sa=done. * * @param int[] $topics = array() of topic ids */ public function action_mergeExecute($topics = array()) { global $user_info, $txt, $context, $scripturl, $modSettings; $db = database(); // Check the session. checkSession('request'); require_once SUBSDIR . '/Topic.subs.php'; require_once SUBSDIR . '/Post.subs.php'; // Handle URLs from action_mergeIndex. if (!empty($_GET['from']) && !empty($_GET['to'])) { $topics = array((int) $_GET['from'], (int) $_GET['to']); } // If we came from a form, the topic IDs came by post. if (!empty($_POST['topics']) && is_array($_POST['topics'])) { $topics = $_POST['topics']; } // There's nothing to merge with just one topic... if (empty($topics) || !is_array($topics) || count($topics) == 1) { fatal_lang_error('merge_need_more_topics'); } // Make sure every topic is numeric, or some nasty things could be done with the DB. foreach ($topics as $id => $topic) { $topics[$id] = (int) $topic; } // Joy of all joys, make sure they're not pi**ing about with unapproved topics they can't see :P if ($modSettings['postmod_active']) { $can_approve_boards = !empty($user_info['mod_cache']['ap']) ? $user_info['mod_cache']['ap'] : boardsAllowedTo('approve_posts'); } // Get info about the topics and polls that will be merged. $request = $db->query('', ' SELECT t.id_topic, t.id_board, t.id_poll, t.num_views, t.is_sticky, t.approved, t.num_replies, t.unapproved_posts, m1.subject, m1.poster_time AS time_started, IFNULL(mem1.id_member, 0) AS id_member_started, IFNULL(mem1.real_name, m1.poster_name) AS name_started, m2.poster_time AS time_updated, IFNULL(mem2.id_member, 0) AS id_member_updated, IFNULL(mem2.real_name, m2.poster_name) AS name_updated FROM {db_prefix}topics AS t INNER JOIN {db_prefix}messages AS m1 ON (m1.id_msg = t.id_first_msg) INNER JOIN {db_prefix}messages AS m2 ON (m2.id_msg = t.id_last_msg) LEFT JOIN {db_prefix}members AS mem1 ON (mem1.id_member = m1.id_member) LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = m2.id_member) WHERE t.id_topic IN ({array_int:topic_list}) ORDER BY t.id_first_msg LIMIT ' . count($topics), array('topic_list' => $topics)); if ($db->num_rows($request) < 2) { fatal_lang_error('no_topic_id'); } $num_views = 0; $is_sticky = 0; $boardTotals = array(); $topic_data = array(); $boards = array(); $polls = array(); $firstTopic = 0; while ($row = $db->fetch_assoc($request)) { // Make a note for the board counts... if (!isset($boardTotals[$row['id_board']])) { $boardTotals[$row['id_board']] = array('num_posts' => 0, 'num_topics' => 0, 'unapproved_posts' => 0, 'unapproved_topics' => 0); } // We can't see unapproved topics here? if ($modSettings['postmod_active'] && !$row['approved'] && $can_approve_boards != array(0) && in_array($row['id_board'], $can_approve_boards)) { continue; } elseif (!$row['approved']) { $boardTotals[$row['id_board']]['unapproved_topics']++; } else { $boardTotals[$row['id_board']]['num_topics']++; } $boardTotals[$row['id_board']]['unapproved_posts'] += $row['unapproved_posts']; $boardTotals[$row['id_board']]['num_posts'] += $row['num_replies'] + ($row['approved'] ? 1 : 0); $topic_data[$row['id_topic']] = array('id' => $row['id_topic'], 'board' => $row['id_board'], 'poll' => $row['id_poll'], 'num_views' => $row['num_views'], 'subject' => $row['subject'], 'started' => array('time' => standardTime($row['time_started']), 'html_time' => htmlTime($row['time_started']), 'timestamp' => forum_time(true, $row['time_started']), 'href' => empty($row['id_member_started']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_started'], 'link' => empty($row['id_member_started']) ? $row['name_started'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_started'] . '">' . $row['name_started'] . '</a>'), 'updated' => array('time' => standardTime($row['time_updated']), 'html_time' => htmlTime($row['time_updated']), 'timestamp' => forum_time(true, $row['time_updated']), 'href' => empty($row['id_member_updated']) ? '' : $scripturl . '?action=profile;u=' . $row['id_member_updated'], 'link' => empty($row['id_member_updated']) ? $row['name_updated'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_updated'] . '">' . $row['name_updated'] . '</a>')); $num_views += $row['num_views']; $boards[] = $row['id_board']; // If there's no poll, id_poll == 0... if ($row['id_poll'] > 0) { $polls[] = $row['id_poll']; } // Store the id_topic with the lowest id_first_msg. if (empty($firstTopic)) { $firstTopic = $row['id_topic']; } $is_sticky = max($is_sticky, $row['is_sticky']); } $db->free_result($request); // If we didn't get any topics then they've been messing with unapproved stuff. if (empty($topic_data)) { fatal_lang_error('no_topic_id'); } $boards = array_values(array_unique($boards)); // The parameters of action_mergeExecute were set, so this must've been an internal call. if (!empty($topics)) { isAllowedTo('merge_any', $boards); loadTemplate('MergeTopics'); } // Get the boards a user is allowed to merge in. $merge_boards = boardsAllowedTo('merge_any'); if (empty($merge_boards)) { fatal_lang_error('cannot_merge_any', 'user'); } require_once SUBSDIR . '/Boards.subs.php'; // Make sure they can see all boards.... $query_boards = array('boards' => $boards); if (!in_array(0, $merge_boards)) { $query_boards['boards'] = array_merge($query_boards['boards'], $merge_boards); } // Saved in a variable to (potentially) save a query later $boards_info = fetchBoardsInfo($query_boards); // This happens when a member is moderator of a board he cannot see foreach ($boards as $board) { if (!isset($boards_info[$board])) { fatal_lang_error('no_board'); } } if (empty($_REQUEST['sa']) || $_REQUEST['sa'] == 'options') { if (count($polls) > 1) { $request = $db->query('', ' SELECT t.id_topic, t.id_poll, m.subject, p.question FROM {db_prefix}polls AS p INNER JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll) INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) WHERE p.id_poll IN ({array_int:polls}) LIMIT ' . count($polls), array('polls' => $polls)); while ($row = $db->fetch_assoc($request)) { $context['polls'][] = array('id' => $row['id_poll'], 'topic' => array('id' => $row['id_topic'], 'subject' => $row['subject']), 'question' => $row['question'], 'selected' => $row['id_topic'] == $firstTopic); } $db->free_result($request); } if (count($boards) > 1) { foreach ($boards_info as $row) { $context['boards'][] = array('id' => $row['id_board'], 'name' => $row['name'], 'selected' => $row['id_board'] == $topic_data[$firstTopic]['board']); } } $context['topics'] = $topic_data; foreach ($topic_data as $id => $topic) { $context['topics'][$id]['selected'] = $topic['id'] == $firstTopic; } $context['page_title'] = $txt['merge']; $context['sub_template'] = 'merge_extra_options'; return; } // Determine target board. $target_board = count($boards) > 1 ? (int) $_REQUEST['board'] : $boards[0]; if (!in_array($target_board, $boards)) { fatal_lang_error('no_board'); } // Determine which poll will survive and which polls won't. $target_poll = count($polls) > 1 ? (int) $_POST['poll'] : (count($polls) == 1 ? $polls[0] : 0); if ($target_poll > 0 && !in_array($target_poll, $polls)) { fatal_lang_error('no_access', false); } $deleted_polls = empty($target_poll) ? $polls : array_diff($polls, array($target_poll)); // Determine the subject of the newly merged topic - was a custom subject specified? if (empty($_POST['subject']) && isset($_POST['custom_subject']) && $_POST['custom_subject'] != '') { $target_subject = strtr(Util::htmltrim(Util::htmlspecialchars($_POST['custom_subject'])), array("\r" => '', "\n" => '', "\t" => '')); // Keep checking the length. if (Util::strlen($target_subject) > 100) { $target_subject = Util::substr($target_subject, 0, 100); } // Nothing left - odd but pick the first topics subject. if ($target_subject == '') { $target_subject = $topic_data[$firstTopic]['subject']; } } elseif (!empty($topic_data[(int) $_POST['subject']]['subject'])) { $target_subject = $topic_data[(int) $_POST['subject']]['subject']; } else { $target_subject = $topic_data[$firstTopic]['subject']; } // Get the first and last message and the number of messages.... $request = $db->query('', ' SELECT approved, MIN(id_msg) AS first_msg, MAX(id_msg) AS last_msg, COUNT(*) AS message_count FROM {db_prefix}messages WHERE id_topic IN ({array_int:topics}) GROUP BY approved ORDER BY approved DESC', array('topics' => $topics)); $topic_approved = 1; $first_msg = 0; while ($row = $db->fetch_assoc($request)) { // If this is approved, or is fully unapproved. if ($row['approved'] || !isset($first_msg)) { $first_msg = $row['first_msg']; $last_msg = $row['last_msg']; if ($row['approved']) { $num_replies = $row['message_count'] - 1; $num_unapproved = 0; } else { $topic_approved = 0; $num_replies = 0; $num_unapproved = $row['message_count']; } } else { // If this has a lower first_msg then the first post is not approved and hence the number of replies was wrong! if ($first_msg > $row['first_msg']) { $first_msg = $row['first_msg']; $num_replies++; $topic_approved = 0; } $num_unapproved = $row['message_count']; } } $db->free_result($request); // Ensure we have a board stat for the target board. if (!isset($boardTotals[$target_board])) { $boardTotals[$target_board] = array('num_posts' => 0, 'num_topics' => 0, 'unapproved_posts' => 0, 'unapproved_topics' => 0); } // Fix the topic count stuff depending on what the new one counts as. if ($topic_approved) { $boardTotals[$target_board]['num_topics']--; } else { $boardTotals[$target_board]['unapproved_topics']--; } $boardTotals[$target_board]['unapproved_posts'] -= $num_unapproved; $boardTotals[$target_board]['num_posts'] -= $topic_approved ? $num_replies + 1 : $num_replies; // Get the member ID of the first and last message. $request = $db->query('', ' SELECT id_member FROM {db_prefix}messages WHERE id_msg IN ({int:first_msg}, {int:last_msg}) ORDER BY id_msg LIMIT 2', array('first_msg' => $first_msg, 'last_msg' => $last_msg)); list($member_started) = $db->fetch_row($request); list($member_updated) = $db->fetch_row($request); // First and last message are the same, so only row was returned. if ($member_updated === null) { $member_updated = $member_started; } $db->free_result($request); // Obtain all the message ids we are going to affect. $affected_msgs = messagesInTopics($topics); // Assign the first topic ID to be the merged topic. $id_topic = min($topics); // Grab the response prefix (like 'Re: ') in the default forum language. $context['response_prefix'] = response_prefix(); $enforce_subject = isset($_POST['enforce_subject']) ? Util::htmlspecialchars(trim($_POST['enforce_subject'])) : ''; // Merge topic notifications. $notifications = isset($_POST['notifications']) && is_array($_POST['notifications']) ? array_intersect($topics, $_POST['notifications']) : array(); fixMergedTopics($first_msg, $topics, $id_topic, $target_board, $target_subject, $enforce_subject, $notifications); // Asssign the properties of the newly merged topic. $db->query('', ' UPDATE {db_prefix}topics SET id_board = {int:id_board}, id_member_started = {int:id_member_started}, id_member_updated = {int:id_member_updated}, id_first_msg = {int:id_first_msg}, id_last_msg = {int:id_last_msg}, id_poll = {int:id_poll}, num_replies = {int:num_replies}, unapproved_posts = {int:unapproved_posts}, num_views = {int:num_views}, is_sticky = {int:is_sticky}, approved = {int:approved} WHERE id_topic = {int:id_topic}', array('id_board' => $target_board, 'is_sticky' => $is_sticky, 'approved' => $topic_approved, 'id_topic' => $id_topic, 'id_member_started' => $member_started, 'id_member_updated' => $member_updated, 'id_first_msg' => $first_msg, 'id_last_msg' => $last_msg, 'id_poll' => $target_poll, 'num_replies' => $num_replies, 'unapproved_posts' => $num_unapproved, 'num_views' => $num_views)); // Get rid of the redundant polls. if (!empty($deleted_polls)) { require_once SUBSDIR . '/Poll.subs.php'; removePoll($deleted_polls); } // Cycle through each board... foreach ($boardTotals as $id_board => $stats) { decrementBoard($id_board, $stats); } // Determine the board the final topic resides in $topic_info = getTopicInfo($id_topic); $id_board = $topic_info['id_board']; // Update all the statistics. updateStats('topic'); updateStats('subject', $id_topic, $target_subject); updateLastMessages($boards); logAction('merge', array('topic' => $id_topic, 'board' => $id_board)); // Notify people that these topics have been merged? require_once SUBSDIR . '/Notification.subs.php'; sendNotifications($id_topic, 'merge'); // If there's a search index that needs updating, update it... require_once SUBSDIR . '/Search.subs.php'; $searchAPI = findSearchAPI(); if (is_callable(array($searchAPI, 'topicMerge'))) { $searchAPI->topicMerge($id_topic, $topics, $affected_msgs, empty($enforce_subject) ? null : array($context['response_prefix'], $target_subject)); } // Send them to the all done page. redirectexit('action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board); }