diff forum/Sources/RepairBoards.php @ 76:e3e11437ecea website

Add forum code
author Chris Cannam
date Sun, 07 Jul 2013 11:25:48 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/forum/Sources/RepairBoards.php	Sun Jul 07 11:25:48 2013 +0200
@@ -0,0 +1,1644 @@
+<?php
+
+/**
+ * Simple Machines Forum (SMF)
+ *
+ * @package SMF
+ * @author Simple Machines http://www.simplemachines.org
+ * @copyright 2011 Simple Machines
+ * @license http://www.simplemachines.org/about/smf/license.php BSD
+ *
+ * @version 2.0
+ */
+
+if (!defined('SMF'))
+	die('Hacking attempt...');
+
+/*	This is here for the "repair any errors" feature in the admin center.  It
+	uses just two simple functions:
+
+	void RepairBoards()
+		- finds or repairs errors in the database to fix possible problems.
+		- requires the admin_forum permission.
+		- uses the raw_data sub template.
+		- calls createSalvageArea() to create a new board, if necesary.
+		- accessed by ?action=admin;area=repairboards.
+
+	void pauseRepairProcess(array to_fix, string current_step_desc, int max_substep = none, force = false)
+		- show the not_done template to avoid CGI timeouts and similar.
+		- called when 3 or more seconds have passed while searching for errors.
+		- if max_substep is set, $_GET['substep'] / $max_substep is the percent
+		  done this step is.
+
+	array findForumErrors()
+		- checks for errors in steps, until 5 seconds have passed.
+		- keeps track of the errors it did find, so that the actual repair
+		  won't have to recheck everything.
+		- returns the array of errors found.
+
+	void createSalvageArea()
+		- creates a salvage board/category if one doesn't already exist.
+		- uses the forum's default language, and checks based on that name.
+*/
+
+function RepairBoards()
+{
+	global $txt, $scripturl, $db_connection, $context, $sourcedir;
+	global $salvageCatID, $salvageBoardID, $smcFunc, $errorTests;
+
+	isAllowedTo('admin_forum');
+
+	// Try secure more memory.
+	@ini_set('memory_limit', '128M');
+
+	// Print out the top of the webpage.
+	$context['page_title'] = $txt['admin_repair'];
+	$context['sub_template'] = 'repair_boards';
+	$context[$context['admin_menu_name']]['current_subsection'] = 'general';
+
+	// Load the language file.
+	loadLanguage('ManageMaintenance');
+
+	// Make sure the tabs stay nice.
+	$context[$context['admin_menu_name']]['tab_data'] = array(
+		'title' => $txt['maintain_title'],
+		'help' => '',
+		'description' => $txt['maintain_info'],
+		'tabs' => array(),
+	);
+
+	// Start displaying errors without fixing them.
+	if (isset($_GET['fixErrors']))
+		checkSession('get');
+
+	// Will want this.
+	loadForumTests();
+
+	// Giant if/else. The first displays the forum errors if a variable is not set and asks
+	// if you would like to continue, the other fixes the errors.
+	if (!isset($_GET['fixErrors']))
+	{
+		$context['error_search'] = true;
+		$context['repair_errors'] = array();
+		$context['to_fix'] = findForumErrors();
+
+		if (!empty($context['to_fix']))
+		{
+			$_SESSION['repairboards_to_fix'] = $context['to_fix'];
+			$_SESSION['repairboards_to_fix2'] = null;
+
+			if (empty($context['repair_errors']))
+				$context['repair_errors'][] = '???';
+		}
+	}
+	else
+	{
+		$context['error_search'] = false;
+		$context['to_fix'] = isset($_SESSION['repairboards_to_fix']) ? $_SESSION['repairboards_to_fix'] : array();
+
+		require_once($sourcedir . '/Subs-Boards.php');
+
+		// Get the MySQL version for future reference.
+		$mysql_version = $smcFunc['db_server_info']($db_connection);
+
+		// Actually do the fix.
+		findForumErrors(true);
+
+		// Note that we've changed everything possible ;)
+		updateSettings(array(
+			'settings_updated' => time(),
+		));
+		updateStats('message');
+		updateStats('topic');
+		updateSettings(array(
+			'calendar_updated' => time(),
+		));
+
+		if (!empty($salvageBoardID))
+		{
+			$context['redirect_to_recount'] = true;
+		}
+
+		$_SESSION['repairboards_to_fix'] = null;
+		$_SESSION['repairboards_to_fix2'] = null;
+	}
+}
+
+function pauseRepairProcess($to_fix, $current_step_description, $max_substep = 0, $force = false)
+{
+	global $context, $txt, $time_start, $db_temp_cache, $db_cache;
+
+	// More time, I need more time!
+	@set_time_limit(600);
+	if (function_exists('apache_reset_timeout'))
+		@apache_reset_timeout();
+
+	// Errr, wait.  How much time has this taken already?
+	if (!$force && time() - array_sum(explode(' ', $time_start)) < 3)
+		return;
+
+	// Restore the query cache if interested.
+	if (!empty($db_temp_cache))
+		$db_cache = $db_temp_cache;
+
+	$context['continue_get_data'] = '?action=admin;area=repairboards' . (isset($_GET['fixErrors']) ? ';fixErrors' : '') . ';step=' . $_GET['step'] . ';substep=' . $_GET['substep'] . ';' . $context['session_var'] . '=' . $context['session_id'];
+	$context['page_title'] = $txt['not_done_title'];
+	$context['continue_post_data'] = '';
+	$context['continue_countdown'] = '2';
+	$context['sub_template'] = 'not_done';
+
+	// Change these two if more steps are added!
+	if (empty($max_substep))
+		$context['continue_percent'] = round(($_GET['step'] * 100) / $context['total_steps']);
+	else
+		$context['continue_percent'] = round((($_GET['step'] + ($_GET['substep'] / $max_substep)) * 100) / $context['total_steps']);
+
+	// Never more than 100%!
+	$context['continue_percent'] = min($context['continue_percent'], 100);
+
+	// What about substeps?
+	$context['substep_enabled'] = $max_substep != 0;
+	$context['substep_title'] = sprintf($txt['repair_currently_' . (isset($_GET['fixErrors']) ? 'fixing' : 'checking')], (isset($txt['repair_operation_' . $current_step_description]) ? $txt['repair_operation_' . $current_step_description] : $current_step_description));
+	$context['substep_continue_percent'] = $max_substep == 0 ? 0 : round(($_GET['substep'] * 100) / $max_substep, 1);
+
+	$_SESSION['repairboards_to_fix'] = $to_fix;
+	$_SESSION['repairboards_to_fix2'] = $context['repair_errors'];
+
+	obExit();
+}
+
+// Load up all the tests we might want to do ;)
+function loadForumTests()
+{
+	global $smcFunc, $errorTests;
+
+	/* Here this array is defined like so:
+		string check_query:	Query to be executed when testing if errors exist.
+		string check_type:	Defines how it knows if a problem was found. If set to count looks for the first variable from check_query
+					being > 0. Anything else it looks for some results. If not set assumes you want results.
+		string fix_it_query:	When doing fixes if an error was detected this query is executed to "fix" it.
+		string fix_query:	The query to execute to get data when doing a fix. If not set check_query is used again.
+		array fix_collect:	This array is used if the fix is basically gathering all broken ids and then doing something with it.
+			- string index:		The value returned from the main query and passed to the processing function.
+			- process:		A function passed an array of ids to execute the fix on.
+		function fix_processing:
+					Function called for each row returned from fix_query to execute whatever fixes are required.
+		function fix_full_processing:
+					As above but does the while loop and everything itself - except the freeing.
+		array force_fix:	If this is set then the error types included within this array will also be assumed broken.
+					Note: At the moment only processes these if they occur after the primary error in the array.
+	*/
+
+	// This great array contains all of our error checks, fixes, etc etc etc.
+	$errorTests = array(
+		// Make a last-ditch-effort check to get rid of topics with zeros..
+		'zero_topics' => array(
+			'check_query' => '
+				SELECT COUNT(*)
+				FROM {db_prefix}topics
+				WHERE id_topic = 0',
+			'check_type' => 'count',
+			'fix_it_query' => '
+				UPDATE {db_prefix}topics
+				SET id_topic = NULL
+				WHERE id_topic = 0',
+			'message' => 'repair_zero_ids',
+		),
+		// ... and same with messages.
+		'zero_messages' => array(
+			'check_query' => '
+				SELECT COUNT(*)
+				FROM {db_prefix}messages
+				WHERE id_msg = 0',
+			'check_type' => 'count',
+			'fix_it_query' => '
+				UPDATE {db_prefix}messages
+				SET id_msg = NULL
+				WHERE id_msg = 0',
+			'message' => 'repair_zero_ids',
+		),
+		// Find messages that don't have existing topics.
+		'missing_topics' => array(
+			'substeps' => array(
+				'step_size' => 1000,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}messages'
+			),
+			'check_query' => '
+				SELECT m.id_topic, m.id_msg
+				FROM {db_prefix}messages AS m
+					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
+				WHERE m.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND t.id_topic IS NULL
+				ORDER BY m.id_topic, m.id_msg',
+			'fix_query' => '
+				SELECT
+					m.id_board, m.id_topic, MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg,
+					COUNT(*) - 1 AS my_num_replies
+				FROM {db_prefix}messages AS m
+					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
+				WHERE t.id_topic IS NULL
+				GROUP BY m.id_topic, m.id_board',
+			'fix_processing' => create_function('$row', '
+				global $smcFunc, $salvageBoardID;
+
+				// Only if we don\'t have a reasonable idea of where to put it.
+				if ($row[\'id_board\'] == 0)
+				{
+					createSalvageArea();
+					$row[\'id_board\'] = (int) $salvageBoardID;
+				}
+
+				// Make sure that no topics claim the first/last message as theirs.
+				$smcFunc[\'db_query\'](\'\', \'
+					UPDATE {db_prefix}topics
+					SET id_first_msg = 0
+					WHERE id_first_msg = {int:id_first_msg}\',
+					array(
+						\'id_first_msg\' => $row[\'myid_first_msg\'],
+					)
+				);
+				$smcFunc[\'db_query\'](\'\', \'
+					UPDATE {db_prefix}topics
+					SET id_last_msg = 0
+					WHERE id_last_msg = {int:id_last_msg}\',
+					array(
+						\'id_last_msg\' => $row[\'myid_last_msg\'],
+					)
+				);
+
+				$memberStartedID = (int) getMsgMemberID($row[\'myid_first_msg\']);
+				$memberUpdatedID = (int) getMsgMemberID($row[\'myid_last_msg\']);
+
+				$smcFunc[\'db_insert\'](\'\',
+					\'{db_prefix}topics\',
+					array(
+						\'id_board\' => \'int\',
+						\'id_member_started\' => \'int\',
+						\'id_member_updated\' => \'int\',
+						\'id_first_msg\' => \'int\',
+						\'id_last_msg\' => \'int\',
+						\'num_replies\' => \'int\'
+					),
+					array(
+						$row[\'id_board\'],
+						$memberStartedID,
+						$memberUpdatedID,
+						$row[\'myid_first_msg\'],
+						$row[\'myid_last_msg\'],
+						$row[\'my_num_replies\']
+					),
+					array(\'id_topic\')
+				);
+
+				$newTopicID = $smcFunc[\'db_insert_id\']("{db_prefix}topics", \'id_topic\');
+
+				$smcFunc[\'db_query\'](\'\', "
+					UPDATE {db_prefix}messages
+					SET id_topic = $newTopicID, id_board = $row[id_board]
+					WHERE id_topic = $row[id_topic]",
+					array(
+					)
+				);
+				'),
+			'force_fix' => array('stats_topics'),
+			'messages' => array('repair_missing_topics', 'id_msg', 'id_topic'),
+		),
+		// Find topics with no messages.
+		'missing_messages' => array(
+			'substeps' => array(
+				'step_size' => 1000,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}topics'
+			),
+			'check_query' => '
+				SELECT t.id_topic, COUNT(m.id_msg) AS num_msg
+				FROM {db_prefix}topics AS t
+					LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic)
+				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY t.id_topic
+				HAVING COUNT(m.id_msg) = 0',
+			// Remove all topics that have zero messages in the messages table.
+			'fix_collect' => array(
+				'index' => 'id_topic',
+				'process' => create_function('$topics', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}topics
+						WHERE id_topic IN ({array_int:topics})",
+						array(
+							\'topics\' => $topics
+						)
+					);
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_topics
+						WHERE id_topic IN ({array_int:topics})",
+						array(
+							\'topics\' => $topics
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_messages', 'id_topic'),
+		),
+		'polls_missing_topics' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_poll)
+					FROM {db_prefix}polls'
+			),
+			'check_query' => '
+				SELECT p.id_poll, p.id_member, p.poster_name, t.id_board
+				FROM {db_prefix}polls AS p
+					LEFT JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll)
+				WHERE p.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND t.id_poll IS NULL',
+			'fix_processing' => create_function('$row', '
+				global $smcFunc, $salvageBoardID, $txt;
+
+				// Only if we don\'t have a reasonable idea of where to put it.
+				if ($row[\'id_board\'] == 0)
+				{
+					createSalvageArea();
+					$row[\'id_board\'] = (int) $salvageBoardID;
+				}
+
+				$row[\'poster_name\'] = !empty($row[\'poster_name\']) ? $row[\'poster_name\'] : $txt[\'guest\'];
+
+				$smcFunc[\'db_insert\'](\'\',
+					\'{db_prefix}messages\',
+					array(
+						\'id_board\' => \'int\',
+						\'id_topic\' => \'int\',
+						\'poster_time\' => \'int\',
+						\'id_member\' => \'int\',
+						\'subject\' => \'string-255\',
+						\'poster_name\' => \'string-255\',
+						\'poster_email\' => \'string-255\',
+						\'poster_ip\' => \'string-16\',
+						\'smileys_enabled\' => \'int\',
+						\'body\' => \'string-65534\',
+						\'icon\' => \'string-16\',
+						\'approved\' => \'int\',
+					),
+					array(
+						$row[\'id_board\'],
+						0,
+						time(),
+						$row[\'id_member\'],
+						$txt[\'salvaged_poll_topic_name\'],
+						$row[\'poster_name\'],
+						\'\',
+						\'127.0.0.1\',
+						1,
+						$txt[\'salvaged_poll_message_body\'],
+						\'xx\',
+						1,
+					),
+					array(\'id_topic\')
+				);
+
+				$newMessageID = $smcFunc[\'db_insert_id\']("{db_prefix}messages", \'id_msg\');
+
+				$smcFunc[\'db_insert\'](\'\',
+					\'{db_prefix}topics\',
+					array(
+						\'id_board\' => \'int\',
+						\'id_poll\' => \'int\',
+						\'id_member_started\' => \'int\',
+						\'id_member_updated\' => \'int\',
+						\'id_first_msg\' => \'int\',
+						\'id_last_msg\' => \'int\',
+						\'num_replies\' => \'int\',
+					),
+					array(
+						$row[\'id_board\'],
+						$row[\'id_poll\'],
+						$row[\'id_member\'],
+						$row[\'id_member\'],
+						$newMessageID,
+						$newMessageID,
+						0,
+					),
+					array(\'id_topic\')
+				);
+
+				$newTopicID = $smcFunc[\'db_insert_id\']("{db_prefix}topics", \'id_topic\');
+
+				$smcFunc[\'db_query\'](\'\', "
+					UPDATE {db_prefix}messages
+					SET id_topic = $newTopicID, id_board = $row[id_board]
+					WHERE id_msg = $newMessageID",
+					array(
+					)
+				);
+
+				updateStats(\'subject\', $newTopicID, $txt[\'salvaged_poll_topic_name\']);
+
+				'),
+			'force_fix' => array('stats_topics'),
+			'messages' => array('repair_polls_missing_topics', 'id_poll', 'id_topic'),
+		),
+		'stats_topics' => array(
+			'substeps' => array(
+				'step_size' => 200,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}topics'
+			),
+			'check_query' => '
+				SELECT
+					t.id_topic, t.id_first_msg, t.id_last_msg,
+					CASE WHEN MIN(ma.id_msg) > 0 THEN
+						CASE WHEN MIN(mu.id_msg) > 0 THEN
+							CASE WHEN MIN(mu.id_msg) < MIN(ma.id_msg) THEN MIN(mu.id_msg) ELSE MIN(ma.id_msg) END ELSE
+						MIN(ma.id_msg) END ELSE
+					MIN(mu.id_msg) END AS myid_first_msg,
+					CASE WHEN MAX(ma.id_msg) > 0 THEN MAX(ma.id_msg) ELSE MIN(mu.id_msg) END AS myid_last_msg,
+					t.approved, mf.approved, mf.approved AS firstmsg_approved
+				FROM {db_prefix}topics AS t
+					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1)
+					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0)
+					LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
+				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY t.id_topic, t.id_first_msg, t.id_last_msg, t.approved, mf.approved
+				ORDER BY t.id_topic',
+			'fix_processing' => create_function('$row', '
+				global $smcFunc;
+				$row[\'firstmsg_approved\'] = (int) $row[\'firstmsg_approved\'];
+				$row[\'myid_first_msg\'] = (int) $row[\'myid_first_msg\'];
+				$row[\'myid_last_msg\'] = (int) $row[\'myid_last_msg\'];
+
+				// Not really a problem?
+				if ($row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'approved\'] == $row[\'firstmsg_approved\'])
+					return false;
+
+				$memberStartedID = (int) getMsgMemberID($row[\'myid_first_msg\']);
+				$memberUpdatedID = (int) getMsgMemberID($row[\'myid_last_msg\']);
+
+				$smcFunc[\'db_query\'](\'\', "
+					UPDATE {db_prefix}topics
+					SET id_first_msg = $row[myid_first_msg],
+						id_member_started = $memberStartedID, id_last_msg = $row[myid_last_msg],
+						id_member_updated = $memberUpdatedID, approved = $row[firstmsg_approved]
+					WHERE id_topic = $row[id_topic]",
+					array(
+					)
+				);
+			'),
+			'message_function' => create_function('$row', '
+				global $txt, $context;
+
+				// A pretend error?
+				if ($row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'myid_first_msg\'] == $row[\'myid_first_msg\'] && $row[\'approved\'] == $row[\'firstmsg_approved\'])
+					return false;
+
+				if ($row[\'id_first_msg\'] != $row[\'myid_first_msg\'])
+					$context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_1\'], $row[\'id_topic\'], $row[\'id_first_msg\']);
+				if ($row[\'id_last_msg\'] != $row[\'myid_last_msg\'])
+					$context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_2\'], $row[\'id_topic\'], $row[\'id_last_msg\']);
+				if ($row[\'approved\'] != $row[\'firstmsg_approved\'])
+					$context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_5\'], $row[\'id_topic\']);
+
+				return true;
+			'),
+		),
+		// Find topics with incorrect num_replies.
+		'stats_topics2' => array(
+			'substeps' => array(
+				'step_size' => 300,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}topics'
+			),
+			'check_query' => '
+				SELECT
+					t.id_topic, t.num_replies, mf.approved,
+					CASE WHEN COUNT(ma.id_msg) > 0 THEN CASE WHEN mf.approved > 0 THEN COUNT(ma.id_msg) - 1 ELSE COUNT(ma.id_msg) END ELSE 0 END AS my_num_replies
+				FROM {db_prefix}topics AS t
+					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1)
+					LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
+				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY t.id_topic, t.num_replies, mf.approved
+				ORDER BY t.id_topic',
+			'fix_processing' => create_function('$row', '
+				global $smcFunc;
+				$row[\'my_num_replies\'] = (int) $row[\'my_num_replies\'];
+
+				// Not really a problem?
+				if ($row[\'my_num_replies\'] == $row[\'num_replies\'])
+					return false;
+
+				$smcFunc[\'db_query\'](\'\', "
+					UPDATE {db_prefix}topics
+					SET num_replies = $row[my_num_replies]
+					WHERE id_topic = $row[id_topic]",
+					array(
+					)
+				);
+			'),
+			'message_function' => create_function('$row', '
+				global $txt, $context;
+
+				// Just joking?
+				if ($row[\'my_num_replies\'] == $row[\'num_replies\'])
+					return false;
+
+				if ($row[\'num_replies\'] != $row[\'my_num_replies\'])
+					$context[\'repair_errors\'][] = sprintf($txt[\'repair_stats_topics_3\'], $row[\'id_topic\'], $row[\'num_replies\']);
+
+				return true;
+			'),
+		),
+		// Find topics with incorrect unapproved_posts.
+		'stats_topics3' => array(
+			'substeps' => array(
+				'step_size' => 1000,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}topics'
+			),
+			'check_query' => '
+				SELECT
+					t.id_topic, t.unapproved_posts, COUNT(mu.id_msg) AS my_unapproved_posts
+				FROM {db_prefix}topics AS t
+					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0)
+				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY t.id_topic, t.unapproved_posts
+				HAVING unapproved_posts != COUNT(mu.id_msg)
+				ORDER BY t.id_topic',
+			'fix_processing' => create_function('$row', '
+				global $smcFunc;
+				$row[\'my_unapproved_posts\'] = (int) $row[\'my_unapproved_posts\'];
+
+				$smcFunc[\'db_query\'](\'\', "
+					UPDATE {db_prefix}topics
+					SET unapproved_posts = $row[my_unapproved_posts]
+					WHERE id_topic = $row[id_topic]",
+					array(
+					)
+				);
+			'),
+			'messages' => array('repair_stats_topics_4', 'id_topic', 'unapproved_posts'),
+		),
+		// Find topics with nonexistent boards.
+		'missing_boards' => array(
+			'substeps' => array(
+				'step_size' => 1000,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}topics'
+			),
+			'check_query' => '
+				SELECT t.id_topic, t.id_board
+				FROM {db_prefix}topics AS t
+					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
+				WHERE b.id_board IS NULL
+					AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				ORDER BY t.id_board, t.id_topic',
+			'fix_query' => '
+				SELECT t.id_board, COUNT(*) AS my_num_topics, COUNT(m.id_msg) AS my_num_posts
+				FROM {db_prefix}topics AS t
+					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
+					LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic)
+				WHERE b.id_board IS NULL
+					AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY t.id_board',
+			'fix_processing' => create_function('$row', '
+				global $smcFunc, $salvageCatID;
+				createSalvageArea();
+
+				$row[\'my_num_topics\'] = (int) $row[\'my_num_topics\'];
+				$row[\'my_num_posts\'] = (int) $row[\'my_num_posts\'];
+
+				$smcFunc[\'db_insert\'](\'\',
+					\'{db_prefix}boards\',
+					array(\'id_cat\' => \'int\', \'name\' => \'string\', \'description\' => \'string\', \'num_topics\' => \'int\', \'num_posts\' => \'int\', \'member_groups\' => \'string\'),
+					array($salvageCatID, \'Salvaged board\', \'\', $row[\'my_num_topics\'], $row[\'my_num_posts\'], \'1\'),
+					array(\'id_board\')
+				);
+				$newBoardID = $smcFunc[\'db_insert_id\'](\'{db_prefix}boards\', \'id_board\');
+
+				$smcFunc[\'db_query\'](\'\', "
+					UPDATE {db_prefix}topics
+					SET id_board = $newBoardID
+					WHERE id_board = $row[id_board]",
+					array(
+					)
+				);
+				$smcFunc[\'db_query\'](\'\', "
+					UPDATE {db_prefix}messages
+					SET id_board = $newBoardID
+					WHERE id_board = $row[id_board]",
+					array(
+					)
+				);
+			'),
+			'messages' => array('repair_missing_boards', 'id_topic', 'id_board'),
+		),
+		// Find boards with nonexistent categories.
+		'missing_categories' => array(
+			'check_query' => '
+				SELECT b.id_board, b.id_cat
+				FROM {db_prefix}boards AS b
+					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
+				WHERE c.id_cat IS NULL
+				ORDER BY b.id_cat, b.id_board',
+			'fix_collect' => array(
+				'index' => 'id_cat',
+				'process' => create_function('$cats', '
+					global $smcFunc, $salvageCatID;
+					createSalvageArea();
+					$smcFunc[\'db_query\'](\'\', "
+						UPDATE {db_prefix}boards
+						SET id_cat = $salvageCatID
+						WHERE id_cat IN ({array_int:categories})",
+						array(
+							\'categories\' => $cats
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_categories', 'id_board', 'id_cat'),
+		),
+		// Find messages with nonexistent members.
+		'missing_posters' => array(
+			'substeps' => array(
+				'step_size' => 2000,
+				'step_max' => '
+					SELECT MAX(id_msg)
+					FROM {db_prefix}messages'
+			),
+			'check_query' => '
+				SELECT m.id_msg, m.id_member
+				FROM {db_prefix}messages AS m
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
+				WHERE mem.id_member IS NULL
+					AND m.id_member != 0
+					AND m.id_msg BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				ORDER BY m.id_msg',
+			// Last step-make sure all non-guest posters still exist.
+			'fix_collect' => array(
+				'index' => 'id_msg',
+				'process' => create_function('$msgs', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						UPDATE {db_prefix}messages
+						SET id_member = 0
+						WHERE id_msg IN ({array_int:msgs})",
+						array(
+							\'msgs\' => $msgs
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_posters', 'id_msg', 'id_member'),
+		),
+		// Find boards with nonexistent parents.
+		'missing_parents' => array(
+			'check_query' => '
+				SELECT b.id_board, b.id_parent
+				FROM {db_prefix}boards AS b
+					LEFT JOIN {db_prefix}boards AS p ON (p.id_board = b.id_parent)
+				WHERE b.id_parent != 0
+					AND (p.id_board IS NULL OR p.id_board = b.id_board)
+				ORDER BY b.id_parent, b.id_board',
+			'fix_collect' => array(
+				'index' => 'id_parent',
+				'process' => create_function('$parents', '
+					global $smcFunc, $salvageBoardID, $salvageCatID;
+					createSalvageArea();
+					$smcFunc[\'db_query\'](\'\', "
+						UPDATE {db_prefix}boards
+						SET id_parent = $salvageBoardID, id_cat = $salvageCatID, child_level = 1
+						WHERE id_parent IN ({array_int:parents})",
+						array(
+							\'parents\' => $parents
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_parents', 'id_board', 'id_parent'),
+		),
+		'missing_polls' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_poll)
+					FROM {db_prefix}topics'
+			),
+			'check_query' => '
+				SELECT t.id_poll, t.id_topic
+				FROM {db_prefix}topics AS t
+					LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll)
+				WHERE t.id_poll != 0
+					AND t.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND p.id_poll IS NULL',
+			'fix_collect' => array(
+				'index' => 'id_poll',
+				'process' => create_function('$polls', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						UPDATE {db_prefix}topics
+						SET id_poll = 0
+						WHERE id_poll IN ({array_int:polls})",
+						array(
+							\'polls\' => $polls
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_polls', 'id_topic', 'id_poll'),
+		),
+		'missing_calendar_topics' => array(
+			'substeps' => array(
+				'step_size' => 1000,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}calendar'
+			),
+			'check_query' => '
+				SELECT cal.id_topic, cal.id_event
+				FROM {db_prefix}calendar AS cal
+					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic)
+				WHERE cal.id_topic != 0
+					AND cal.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND t.id_topic IS NULL
+				ORDER BY cal.id_topic',
+			'fix_collect' => array(
+				'index' => 'id_topic',
+				'process' => create_function('$events', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', \'
+						UPDATE {db_prefix}calendar
+						SET id_topic = 0, id_board = 0
+						WHERE id_topic IN ({array_int:events})\',
+						array(
+							\'events\' => $events
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_calendar_topics', 'id_event', 'id_topic'),
+		),
+		'missing_log_topics' => array(
+			'substeps' => array(
+				'step_size' => 150,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_topics'
+			),
+			'check_query' => '
+				SELECT lt.id_topic
+				FROM {db_prefix}log_topics AS lt
+					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lt.id_topic)
+				WHERE t.id_topic IS NULL
+					AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
+			'fix_collect' => array(
+				'index' => 'id_topic',
+				'process' => create_function('$topics', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_topics
+						WHERE id_topic IN ({array_int:topics})",
+						array(
+							\'topics\' => $topics
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_topics', 'id_topic'),
+		),
+		'missing_log_topics_members' => array(
+			'substeps' => array(
+				'step_size' => 150,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_topics'
+			),
+			'check_query' => '
+				SELECT lt.id_member
+				FROM {db_prefix}log_topics AS lt
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lt.id_member)
+				WHERE mem.id_member IS NULL
+					AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY lt.id_member',
+			'fix_collect' => array(
+				'index' => 'id_member',
+				'process' => create_function('$members', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_topics
+						WHERE id_member IN ({array_int:members})",
+						array(
+							\'members\' => $members
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_topics_members', 'id_member'),
+		),
+		'missing_log_boards' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_boards'
+			),
+			'check_query' => '
+				SELECT lb.id_board
+				FROM {db_prefix}log_boards AS lb
+					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lb.id_board)
+				WHERE b.id_board IS NULL
+					AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY lb.id_board',
+			'fix_collect' => array(
+				'index' => 'id_board',
+				'process' => create_function('$boards', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_boards
+						WHERE id_board IN ({array_int:boards})",
+						array(
+							\'boards\' => $boards
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_boards', 'id_board'),
+		),
+		'missing_log_boards_members' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_boards'
+			),
+			'check_query' => '
+				SELECT lb.id_member
+				FROM {db_prefix}log_boards AS lb
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member)
+				WHERE mem.id_member IS NULL
+					AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY lb.id_member',
+			'fix_collect' => array(
+				'index' => 'id_member',
+				'process' => create_function('$members', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_boards
+						WHERE id_member IN ({array_int:members})",
+						array(
+							\'members\' => $members
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_boards_members', 'id_member'),
+		),
+		'missing_log_mark_read' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_mark_read'
+			),
+			'check_query' => '
+				SELECT lmr.id_board
+				FROM {db_prefix}log_mark_read AS lmr
+					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lmr.id_board)
+				WHERE b.id_board IS NULL
+					AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY lmr.id_board',
+			'fix_collect' => array(
+				'index' => 'id_board',
+				'process' => create_function('$boards', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_mark_read
+						WHERE id_board IN ({array_int:boards})",
+						array(
+							\'boards\' => $boards
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_mark_read', 'id_board'),
+		),
+		'missing_log_mark_read_members' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_mark_read'
+			),
+			'check_query' => '
+				SELECT lmr.id_member
+				FROM {db_prefix}log_mark_read AS lmr
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lmr.id_member)
+				WHERE mem.id_member IS NULL
+					AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY lmr.id_member',
+			'fix_collect' => array(
+				'index' => 'id_member',
+				'process' => create_function('$members', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_mark_read
+						WHERE id_member IN ({array_int:members})",
+						array(
+							\'members\' => $members
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_mark_read_members', 'id_member'),
+		),
+		'missing_pms' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_pm)
+					FROM {db_prefix}pm_recipients'
+			),
+			'check_query' => '
+				SELECT pmr.id_pm
+				FROM {db_prefix}pm_recipients AS pmr
+					LEFT JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
+				WHERE pm.id_pm IS NULL
+					AND pmr.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH}
+				GROUP BY pmr.id_pm',
+			'fix_collect' => array(
+				'index' => 'id_pm',
+				'process' => create_function('$pms', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}pm_recipients
+						WHERE id_pm IN ({array_int:pms})",
+						array(
+							\'pms\' => $pms
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_pms', 'id_pm'),
+		),
+		'missing_recipients' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}pm_recipients'
+			),
+			'check_query' => '
+				SELECT pmr.id_member
+				FROM {db_prefix}pm_recipients AS pmr
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
+				WHERE pmr.id_member != 0
+					AND pmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND mem.id_member IS NULL
+				GROUP BY pmr.id_member',
+			'fix_collect' => array(
+				'index' => 'id_member',
+				'process' => create_function('$members', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}pm_recipients
+						WHERE id_member IN ({array_int:members})",
+						array(
+							\'members\' => $members
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_recipients', 'id_member'),
+		),
+		'missing_senders' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_pm)
+					FROM {db_prefix}personal_messages'
+			),
+			'check_query' => '
+				SELECT pm.id_pm, pm.id_member_from
+				FROM {db_prefix}personal_messages AS pm
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
+				WHERE pm.id_member_from != 0
+					AND pm.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND mem.id_member IS NULL',
+			'fix_collect' => array(
+				'index' => 'id_pm',
+				'process' => create_function('$guestMessages', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						UPDATE {db_prefix}personal_messages
+						SET id_member_from = 0
+						WHERE id_pm IN ({array_int:guestMessages})",
+						array(
+							\'guestMessages\' => $guestMessages
+						));
+				'),
+			),
+			'messages' => array('repair_missing_senders', 'id_pm', 'id_member_from'),
+		),
+		'missing_notify_members' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_notify'
+			),
+			'check_query' => '
+				SELECT ln.id_member
+				FROM {db_prefix}log_notify AS ln
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
+				WHERE ln.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND mem.id_member IS NULL
+				GROUP BY ln.id_member',
+			'fix_collect' => array(
+				'index' => 'id_member',
+				'process' => create_function('$members', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_notify
+						WHERE id_member IN ({array_int:members})",
+						array(
+							\'members\' => $members
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_notify_members', 'id_member'),
+		),
+		'missing_cached_subject' => array(
+			'substeps' => array(
+				'step_size' => 100,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}topics'
+			),
+			'check_query' => '
+				SELECT t.id_topic, fm.subject
+				FROM {db_prefix}topics AS t
+					INNER JOIN {db_prefix}messages AS fm ON (fm.id_msg = t.id_first_msg)
+					LEFT JOIN {db_prefix}log_search_subjects AS lss ON (lss.id_topic = t.id_topic)
+				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND lss.id_topic IS NULL',
+			'fix_full_processing' => create_function('$result', '
+				global $smcFunc;
+
+				$inserts = array();
+				while ($row = $smcFunc[\'db_fetch_assoc\']($result))
+				{
+					foreach (text2words($row[\'subject\']) as $word)
+						$inserts[] = array($word, $row[\'id_topic\']);
+					if (count($inserts) > 500)
+					{
+						$smcFunc[\'db_insert\'](\'ignore\',
+							"{db_prefix}log_search_subjects",
+							array(\'word\' => \'string\', \'id_topic\' => \'int\'),
+							$inserts,
+							array(\'word\', \'id_topic\')
+						);
+						$inserts = array();
+					}
+
+				}
+
+				if (!empty($inserts))
+					$smcFunc[\'db_insert\'](\'ignore\',
+						"{db_prefix}log_search_subjects",
+						array(\'word\' => \'string\', \'id_topic\' => \'int\'),
+						$inserts,
+						array(\'word\', \'id_topic\')
+					);
+			'),
+			'message_function' => create_function('$row', '
+				global $txt, $context;
+
+				if (count(text2words($row[\'subject\'])) != 0)
+				{
+					$context[\'repair_errors\'][] = sprintf($txt[\'repair_missing_cached_subject\'], $row[\'id_topic\']);
+					return true;
+				}
+
+				return false;
+			'),
+		),
+		'missing_topic_for_cache' => array(
+			'substeps' => array(
+				'step_size' => 50,
+				'step_max' => '
+					SELECT MAX(id_topic)
+					FROM {db_prefix}log_search_subjects'
+			),
+			'check_query' => '
+				SELECT lss.id_topic, lss.word
+				FROM {db_prefix}log_search_subjects AS lss
+					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lss.id_topic)
+				WHERE lss.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND t.id_topic IS NULL',
+			'fix_collect' => array(
+				'index' => 'id_topic',
+				'process' => create_function('$deleteTopics', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_search_subjects
+						WHERE id_topic IN ({array_int:deleteTopics})",
+						array(
+							\'deleteTopics\' => $deleteTopics
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_topic_for_cache', 'word'),
+		),
+		'missing_member_vote' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_polls'
+			),
+			'check_query' => '
+				SELECT lp.id_poll, lp.id_member
+				FROM {db_prefix}log_polls AS lp
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lp.id_member)
+				WHERE lp.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND lp.id_member > 0
+					AND mem.id_member IS NULL',
+			'fix_collect' => array(
+				'index' => 'id_member',
+				'process' => create_function('$members', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_polls
+						WHERE id_member IN ({array_int:members})",
+						array(
+							\'members\' => $members
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_poll_member', 'id_poll', 'id_member'),
+		),
+		'missing_log_poll_vote' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_poll)
+					FROM {db_prefix}log_polls'
+			),
+			'check_query' => '
+				SELECT lp.id_poll, lp.id_member
+				FROM {db_prefix}log_polls AS lp
+					LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = lp.id_poll)
+				WHERE lp.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND p.id_poll IS NULL',
+			'fix_collect' => array(
+				'index' => 'id_poll',
+				'process' => create_function('$polls', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_polls
+						WHERE id_poll IN ({array_int:polls})",
+						array(
+							\'polls\' => $polls
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_missing_log_poll_vote', 'id_member', 'id_poll'),
+		),
+		'report_missing_comments' => array(
+			'substeps' => array(
+				'step_size' => 500,
+				'step_max' => '
+					SELECT MAX(id_report)
+					FROM {db_prefix}log_reported'
+			),
+			'check_query' => '
+				SELECT lr.id_report, lr.subject
+				FROM {db_prefix}log_reported AS lr
+					LEFT JOIN {db_prefix}log_reported_comments AS lrc ON (lrc.id_report = lr.id_report)
+				WHERE lr.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND lrc.id_report IS NULL',
+			'fix_collect' => array(
+				'index' => 'id_report',
+				'process' => create_function('$reports', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_reported
+						WHERE id_report IN ({array_int:reports})",
+						array(
+							\'reports\' => $reports
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_report_missing_comments', 'id_report', 'subject'),
+		),
+		'comments_missing_report' => array(
+			'substeps' => array(
+				'step_size' => 200,
+				'step_max' => '
+					SELECT MAX(id_report)
+					FROM {db_prefix}log_reported_comments'
+			),
+			'check_query' => '
+				SELECT lrc.id_report, lrc.membername
+				FROM {db_prefix}log_reported_comments AS lrc
+					LEFT JOIN {db_prefix}log_reported AS lr ON (lr.id_report = lrc.id_report)
+				WHERE lrc.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND lr.id_report IS NULL',
+			'fix_collect' => array(
+				'index' => 'id_report',
+				'process' => create_function('$reports', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_reported_comments
+						WHERE id_report IN ({array_int:reports})",
+						array(
+							\'reports\' => $reports
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_comments_missing_report', 'id_report', 'membername'),
+		),
+		'group_request_missing_member' => array(
+			'substeps' => array(
+				'step_size' => 200,
+				'step_max' => '
+					SELECT MAX(id_member)
+					FROM {db_prefix}log_group_requests'
+			),
+			'check_query' => '
+				SELECT lgr.id_member
+				FROM {db_prefix}log_group_requests AS lgr
+					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
+				WHERE lgr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND mem.id_member IS NULL
+				GROUP BY lgr.id_member',
+			'fix_collect' => array(
+				'index' => 'id_member',
+				'process' => create_function('$members', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', "
+						DELETE FROM {db_prefix}log_group_requests
+						WHERE id_member IN ({array_int:members})",
+						array(
+							\'members\' => $members
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_group_request_missing_member', 'id_member'),
+		),
+		'group_request_missing_group' => array(
+			'substeps' => array(
+				'step_size' => 200,
+				'step_max' => '
+					SELECT MAX(id_group)
+					FROM {db_prefix}log_group_requests'
+			),
+			'check_query' => '
+				SELECT lgr.id_group
+				FROM {db_prefix}log_group_requests AS lgr
+					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
+				WHERE lgr.id_group BETWEEN {STEP_LOW} AND {STEP_HIGH}
+					AND mg.id_group IS NULL
+				GROUP BY lgr.id_group',
+			'fix_collect' => array(
+				'index' => 'id_group',
+				'process' => create_function('$groups', '
+					global $smcFunc;
+					$smcFunc[\'db_query\'](\'\', \'
+						DELETE FROM {db_prefix}log_group_requests
+						WHERE id_group IN ({array_int:groups})\',
+						array(
+							\'groups\' => $groups
+						)
+					);
+				'),
+			),
+			'messages' => array('repair_group_request_missing_group', 'id_group'),
+		),
+	);
+}
+
+function findForumErrors($do_fix = false)
+{
+	global $context, $txt, $smcFunc, $errorTests, $db_cache, $db_temp_cache;
+
+	// This may take some time...
+	@set_time_limit(600);
+
+	$to_fix = !empty($_SESSION['repairboards_to_fix']) ? $_SESSION['repairboards_to_fix'] : array();
+	$context['repair_errors'] = isset($_SESSION['repairboards_to_fix2']) ? $_SESSION['repairboards_to_fix2'] : array();
+
+	$_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step'];
+	$_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep'];
+
+	// Don't allow the cache to get too full.
+	$db_temp_cache = $db_cache;
+	$db_cache = '';
+
+	$context['total_steps'] = count($errorTests);
+
+	// For all the defined error types do the necessary tests.
+	$current_step = -1;
+	$total_queries = 0;
+	foreach ($errorTests as $error_type => $test)
+	{
+		$current_step++;
+
+		// Already done this?
+		if ($_GET['step'] > $current_step)
+			continue;
+
+		// If we're fixing it but it ain't broke why try?
+		if ($do_fix && !in_array($error_type, $to_fix))
+		{
+			$_GET['step']++;
+			continue;
+		}
+
+		// Has it got substeps?
+		if (isset($test['substeps']))
+		{
+			$step_size = isset($test['substeps']['step_size']) ? $test['substeps']['step_size'] : 100;
+			$request = $smcFunc['db_query']('',
+				$test['substeps']['step_max'],
+				array(
+				)
+			);
+			list ($step_max) = $smcFunc['db_fetch_row']($request);
+
+			$total_queries++;
+			$smcFunc['db_free_result']($request);
+		}
+
+		// We in theory keep doing this... the substeps.
+		$done = false;
+		while (!$done)
+		{
+			// Make sure there's at least one ID to test.
+			if (isset($test['substeps']) && empty($step_max))
+				break;
+
+			// What is the testing query (Changes if we are testing or fixing)
+			if (!$do_fix)
+				$test_query = 'check_query';
+			else
+				$test_query = isset($test['fix_query']) ? 'fix_query' : 'check_query';
+
+			// Do the test...
+			$request = $smcFunc['db_query']('',
+				isset($test['substeps']) ? strtr($test[$test_query], array('{STEP_LOW}' => $_GET['substep'], '{STEP_HIGH}' => $_GET['substep'] + $step_size - 1)) : $test[$test_query],
+				array(
+				)
+			);
+			$needs_fix = false;
+
+			// Does it need a fix?
+			if (!empty($test['check_type']) && $test['check_type'] == 'count')
+				list ($needs_fix) = $smcFunc['db_fetch_row']($request);
+			else
+				$needs_fix = $smcFunc['db_num_rows']($request);
+
+			$total_queries++;
+
+			if ($needs_fix)
+			{
+				// What about a message to the user?
+				if (!$do_fix)
+				{
+					// Assume need to fix.
+					$found_errors = true;
+
+					if (isset($test['message']))
+						$context['repair_errors'][] = $txt[$test['message']];
+
+					// One per row!
+					elseif (isset($test['messages']))
+					{
+						while ($row = $smcFunc['db_fetch_assoc']($request))
+						{
+							$variables = $test['messages'];
+							foreach ($variables as $k => $v)
+							{
+								if ($k == 0 && isset($txt[$v]))
+									$variables[$k] = $txt[$v];
+								elseif ($k > 0 && isset($row[$v]))
+									$variables[$k] = $row[$v];
+							}
+							$context['repair_errors'][] = call_user_func_array('sprintf', $variables);
+						}
+					}
+
+					// A function to process?
+					elseif (isset($test['message_function']))
+					{
+						// Find out if there are actually errors.
+						$found_errors = false;
+						while ($row = $smcFunc['db_fetch_assoc']($request))
+							$found_errors |= $test['message_function']($row);
+					}
+
+					// Actually have something to fix?
+					if ($found_errors)
+						$to_fix[] = $error_type;
+				}
+
+				// We want to fix, we need to fix - so work out what exactly to do!
+				else
+				{
+					// Are we simply getting a collection of ids?
+					if (isset($test['fix_collect']))
+					{
+						$ids = array();
+						while ($row = $smcFunc['db_fetch_assoc']($request))
+							$ids[] = $row[$test['fix_collect']['index']];
+						if (!empty($ids))
+						{
+							// Fix it!
+							$test['fix_collect']['process']($ids);
+						}
+					}
+
+					// Simply executing a fix it query?
+					elseif (isset($test['fix_it_query']))
+						$smcFunc['db_query']('',
+							$test['fix_it_query'],
+							array(
+							)
+						);
+
+					// Do we have some processing to do?
+					elseif (isset($test['fix_processing']))
+					{
+						while ($row = $smcFunc['db_fetch_assoc']($request))
+							$test['fix_processing']($row);
+					}
+
+					// What about the full set of processing?
+					elseif (isset($test['fix_full_processing']))
+						$test['fix_full_processing']($request);
+
+					// Do we have other things we need to fix as a result?
+					if (!empty($test['force_fix']))
+					{
+						foreach ($test['force_fix'] as $item)
+							if (!in_array($item, $to_fix))
+								$to_fix[] = $item;
+					}
+				}
+			}
+
+			// Free the result.
+			$smcFunc['db_free_result']($request);
+			// Keep memory down.
+			$db_cache = '';
+
+			// Are we done yet?
+			if (isset($test['substeps']))
+			{
+				$_GET['substep'] += $step_size;
+				// Not done?
+				if ($_GET['substep'] <= $step_max)
+				{
+					pauseRepairProcess($to_fix, $error_type, $step_max);
+				}
+				else
+					$done = true;
+			}
+			else
+				$done = true;
+
+			// Don't allow more than 1000 queries at a time.
+			if ($total_queries >= 1000)
+				pauseRepairProcess($to_fix, $error_type, $step_max, true);
+		}
+
+		// Keep going.
+		$_GET['step']++;
+		$_GET['substep'] = 0;
+
+		$to_fix = array_unique($to_fix);
+
+		// If we're doing fixes and this needed a fix and we're all done then don't do it again.
+		if ($do_fix)
+		{
+			$key = array_search($error_type, $to_fix);
+			if ($key !== false && isset($to_fix[$key]))
+				unset($to_fix[$key]);
+		}
+
+		// Are we done?
+		pauseRepairProcess($to_fix, $error_type);
+	}
+
+	// Restore the cache.
+	$db_cache = $db_temp_cache;
+
+	return $to_fix;
+}
+
+// Create a salvage area for repair purposes.
+function createSalvageArea()
+{
+	global $txt, $language, $salvageBoardID, $salvageCatID, $smcFunc;
+	static $createOnce = false;
+
+	// Have we already created it?
+	if ($createOnce)
+		return;
+	else
+		$createOnce = true;
+
+	// Back to the forum's default language.
+	loadLanguage('Admin', $language);
+
+	// Check to see if a 'Salvage Category' exists, if not => insert one.
+	$result = $smcFunc['db_query']('', '
+		SELECT id_cat
+		FROM {db_prefix}categories
+		WHERE name = {string:cat_name}
+		LIMIT 1',
+		array(
+			'cat_name' => $txt['salvaged_category_name'],
+		)
+	);
+	if ($smcFunc['db_num_rows']($result) != 0)
+		list ($salvageCatID) = $smcFunc['db_fetch_row']($result);
+	$smcFunc['db_free_result']($result);
+
+	if (empty($salveageCatID))
+	{
+		$smcFunc['db_insert']('',
+			'{db_prefix}categories',
+			array('name' => 'string-255', 'cat_order' => 'int'),
+			array($txt['salvaged_category_name'], -1),
+			array('id_cat')
+		);
+
+		if ($smcFunc['db_affected_rows']() <= 0)
+		{
+			loadLanguage('Admin');
+			fatal_lang_error('salvaged_category_error', false);
+		}
+
+		$salvageCatID = $smcFunc['db_insert_id']('{db_prefix}categories', 'id_cat');
+	}
+
+	// Check to see if a 'Salvage Board' exists, if not => insert one.
+	$result = $smcFunc['db_query']('', '
+		SELECT id_board
+		FROM {db_prefix}boards
+		WHERE id_cat = {int:id_cat}
+			AND name = {string:board_name}
+		LIMIT 1',
+		array(
+			'id_cat' => $salvageCatID,
+			'board_name' => $txt['salvaged_board_name'],
+		)
+	);
+	if ($smcFunc['db_num_rows']($result) != 0)
+		list ($salvageBoardID) = $smcFunc['db_fetch_row']($result);
+	$smcFunc['db_free_result']($result);
+
+	if (empty($salvageBoardID))
+	{
+		$smcFunc['db_insert']('',
+			'{db_prefix}boards',
+			array('name' => 'string-255', 'description' => 'string-255', 'id_cat' => 'int', 'member_groups' => 'string', 'board_order' => 'int', 'redirect' => 'string'),
+			array($txt['salvaged_board_name'], $txt['salvaged_board_description'], $salvageCatID, '1', -1, ''),
+			array('id_board')
+		);
+
+		if ($smcFunc['db_affected_rows']() <= 0)
+		{
+			loadLanguage('Admin');
+			fatal_lang_error('salvaged_board_error', false);
+		}
+
+		$salvageBoardID = $smcFunc['db_insert_id']('{db_prefix}boards', 'id_board');
+	}
+
+	$smcFunc['db_query']('alter_table_boards', '
+		ALTER TABLE {db_prefix}boards
+		ORDER BY board_order',
+		array(
+		)
+	);
+
+	// Restore the user's language.
+	loadLanguage('Admin');
+}
+
+?>
\ No newline at end of file