Issues (1014)

Sources/RepairBoards.php (3 issues)

1
<?php
2
3
/**
4
 * This is here for the "repair any errors" feature in the admin center.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2022 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.0
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Finds or repairs errors in the database to fix possible problems.
21
 * Requires the admin_forum permission.
22
 * Calls createSalvageArea() to create a new board, if necessary.
23
 * Accessed by ?action=admin;area=repairboards.
24
 *
25
 * @uses template_repair_boards()
26
 */
27
function RepairBoards()
28
{
29
	global $txt, $context, $sourcedir, $salvageBoardID;
30
31
	isAllowedTo('admin_forum');
32
33
	// Try secure more memory.
34
	setMemoryLimit('128M');
35
36
	// Print out the top of the webpage.
37
	$context['page_title'] = $txt['admin_repair'];
38
	$context['sub_template'] = 'repair_boards';
39
	$context[$context['admin_menu_name']]['current_subsection'] = 'general';
40
41
	// Load the language file.
42
	loadLanguage('ManageMaintenance');
43
44
	// Make sure the tabs stay nice.
45
	$context[$context['admin_menu_name']]['tab_data'] = array(
46
		'title' => $txt['maintain_title'],
47
		'help' => '',
48
		'description' => $txt['maintain_info'],
49
		'tabs' => array(),
50
	);
51
52
	// Start displaying errors without fixing them.
53
	if (isset($_GET['fixErrors']))
54
		checkSession('get');
55
56
	// Will want this.
57
	loadForumTests();
58
59
	// Giant if/else. The first displays the forum errors if a variable is not set and asks
60
	// if you would like to continue, the other fixes the errors.
61
	if (!isset($_GET['fixErrors']))
62
	{
63
		$context['error_search'] = true;
64
		$context['repair_errors'] = array();
65
		$context['to_fix'] = findForumErrors();
66
67
		if (!empty($context['to_fix']))
68
		{
69
			$_SESSION['repairboards_to_fix'] = $context['to_fix'];
70
			$_SESSION['repairboards_to_fix2'] = null;
71
72
			if (empty($context['repair_errors']))
73
				$context['repair_errors'][] = '???';
74
		}
75
76
		// Need a token here.
77
		createToken('admin-repairboards', 'request');
78
	}
79
	else
80
	{
81
		// Validate the token, create a new one and tell the not done template.
82
		validateToken('admin-repairboards', 'request');
83
		createToken('admin-repairboards', 'request');
84
		$context['not_done_token'] = 'admin-repairboards';
85
86
		$context['error_search'] = false;
87
		$context['to_fix'] = isset($_SESSION['repairboards_to_fix']) ? $_SESSION['repairboards_to_fix'] : array();
88
89
		require_once($sourcedir . '/Subs-Boards.php');
90
91
		// Actually do the fix.
92
		findForumErrors(true);
93
94
		// Note that we've changed everything possible ;)
95
		updateSettings(array(
96
			'settings_updated' => time(),
97
		));
98
		updateStats('message');
99
		updateStats('topic');
100
		updateSettings(array(
101
			'calendar_updated' => time(),
102
		));
103
104
		// If we created a salvage area, we may need to recount stats properly.
105
		if (!empty($salvageBoardID) || !empty($_SESSION['salvageBoardID']))
106
		{
107
			unset($_SESSION['salvageBoardID']);
108
			$context['redirect_to_recount'] = true;
109
			createToken('admin-maint');
110
		}
111
112
		$_SESSION['repairboards_to_fix'] = null;
113
		$_SESSION['repairboards_to_fix2'] = null;
114
115
		// We are done at this point, dump the token,
116
		validateToken('admin-repairboards', 'request', false);
117
	}
118
}
119
120
/**
121
 * Show the not_done template to avoid CGI timeouts and similar.
122
 * Called when 3 or more seconds have passed while searching for errors.
123
 * If max_substep is set, $_GET['substep'] / $max_substep is the percent
124
 * done this step is.
125
 *
126
 * @param array $to_fix An array of information about what to fix
127
 * @param string $current_step_description The description of the current step
128
 * @param int $max_substep The maximum substep to reach before pausing
129
 * @param bool $force Whether to force pausing even if we don't really need to
130
 */
131
function pauseRepairProcess($to_fix, $current_step_description, $max_substep = 0, $force = false)
132
{
133
	global $context, $txt, $db_temp_cache, $db_cache;
134
	static $loops = 0;
135
	++$loops;
136
137
	// More time, I need more time!
138
	@set_time_limit(600);
139
	if (function_exists('apache_reset_timeout'))
140
		@apache_reset_timeout();
141
142
	$return = true;
143
144
	// If we are from a SSI/cron job, we can allow this through, if enabled.
145
	if ((SMF === 'SSI' || SMF === 'BACKGROUND') && php_sapi_name() == 'cli' && !empty($context['no_pause_process']))
0 ignored issues
show
The condition SMF === 'SSI' is always true.
Loading history...
146
		$return = true;
147
	elseif ($force)
148
		$return = false;
149
	// Try to stay under our memory limit.
150
	elseif ((memory_get_usage() + 65536) > memoryReturnBytes(ini_get('memory_limit')))
151
		$return = false;
152
	// Errr, wait.  How much time has this taken already?
153
	elseif ((time() - TIME_START) > 3)
154
		$return = false;
155
	// If we have a lot of errors, lets do smaller batches, to save on memory needs.
156
	elseif (count($context['repair_errors']) > 100000 && $loops > 50)
157
		$return = false;
158
159
	// If we can return, lets do so.
160
	if ($return)
161
		return;
162
163
	// Restore the query cache if interested.
164
	if (!empty($db_temp_cache))
165
		$db_cache = $db_temp_cache;
166
167
	$context['continue_get_data'] = '?action=admin;area=repairboards' . (isset($_GET['fixErrors']) ? ';fixErrors' : '') . ';step=' . $_GET['step'] . ';substep=' . $_GET['substep'] . ';' . $context['session_var'] . '=' . $context['session_id'];
168
	$context['page_title'] = $txt['not_done_title'];
169
	$context['continue_post_data'] = '';
170
	$context['continue_countdown'] = '2';
171
	$context['sub_template'] = 'not_done';
172
173
	// Change these two if more steps are added!
174
	if (empty($max_substep))
175
		$context['continue_percent'] = round(($_GET['step'] * 100) / $context['total_steps']);
176
	else
177
		$context['continue_percent'] = round((($_GET['step'] + ($_GET['substep'] / $max_substep)) * 100) / $context['total_steps']);
178
179
	// Never more than 100%!
180
	$context['continue_percent'] = min($context['continue_percent'], 100);
181
182
	// What about substeps?
183
	$context['substep_enabled'] = $max_substep != 0;
184
	$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));
185
	$context['substep_continue_percent'] = $max_substep == 0 ? 0 : round(($_GET['substep'] * 100) / $max_substep, 1);
186
187
	$_SESSION['repairboards_to_fix'] = $to_fix;
188
	$_SESSION['repairboards_to_fix2'] = $context['repair_errors'];
189
190
	obExit();
191
}
192
193
/**
194
 * Load up all the tests we might want to do ;)
195
 */
196
function loadForumTests()
197
{
198
	global $errorTests, $smcFunc, $txt, $context;
199
200
	/* Here this array is defined like so:
201
		string check_query:	Query to be executed when testing if errors exist.
202
		string check_type:	Defines how it knows if a problem was found. If set to count looks for the first variable from check_query
203
					being > 0. Anything else it looks for some results. If not set assumes you want results.
204
		string fix_it_query:	When doing fixes if an error was detected this query is executed to "fix" it.
205
		string fix_query:	The query to execute to get data when doing a fix. If not set check_query is used again.
206
		array fix_collect:	This array is used if the fix is basically gathering all broken ids and then doing something with it.
207
			- string index:		The value returned from the main query and passed to the processing function.
208
			- process:		A function passed an array of ids to execute the fix on.
209
		function fix_processing:
210
					Function called for each row returned from fix_query to execute whatever fixes are required.
211
		function fix_full_processing:
212
					As above but does the while loop and everything itself - except the freeing.
213
		array force_fix:	If this is set then the error types included within this array will also be assumed broken.
214
					Note: At the moment only processes these if they occur after the primary error in the array.
215
	*/
216
217
	// This great array contains all of our error checks, fixes, etc etc etc.
218
	$errorTests = array(
219
		// Make a last-ditch-effort check to get rid of topics with zeros..
220
		'zero_topics' => array(
221
			'check_query' => '
222
				SELECT COUNT(*)
223
				FROM {db_prefix}topics
224
				WHERE id_topic = 0',
225
			'check_type' => 'count',
226
			'fix_it_query' => '
227
				UPDATE {db_prefix}topics
228
				SET id_topic = NULL
229
				WHERE id_topic = 0',
230
			'message' => 'repair_zero_ids',
231
		),
232
		// ... and same with messages.
233
		'zero_messages' => array(
234
			'check_query' => '
235
				SELECT COUNT(*)
236
				FROM {db_prefix}messages
237
				WHERE id_msg = 0',
238
			'check_type' => 'count',
239
			'fix_it_query' => '
240
				UPDATE {db_prefix}messages
241
				SET id_msg = NULL
242
				WHERE id_msg = 0',
243
			'message' => 'repair_zero_ids',
244
		),
245
		// Find messages that don't have existing topics.
246
		'missing_topics' => array(
247
			'substeps' => array(
248
				'step_size' => 1000,
249
				'step_max' => '
250
					SELECT MAX(id_topic)
251
					FROM {db_prefix}messages'
252
			),
253
			'check_query' => '
254
				SELECT m.id_topic, m.id_msg
255
				FROM {db_prefix}messages AS m
256
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
257
				WHERE m.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
258
					AND t.id_topic IS NULL
259
				ORDER BY m.id_topic, m.id_msg',
260
			'fix_query' => '
261
				SELECT
262
					m.id_board, m.id_topic, MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg,
263
					COUNT(*) - 1 AS my_num_replies
264
				FROM {db_prefix}messages AS m
265
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
266
				WHERE t.id_topic IS NULL
267
				GROUP BY m.id_topic, m.id_board',
268
			'fix_processing' => function($row) use ($smcFunc)
269
			{
270
				global $salvageBoardID;
271
272
				// Only if we don't have a reasonable idea of where to put it.
273
				if ($row['id_board'] == 0)
274
				{
275
					createSalvageArea();
276
					$row['id_board'] = $_SESSION['salvageBoardID'] = (int) $salvageBoardID;
277
				}
278
279
				// Make sure that no topics claim the first/last message as theirs.
280
				$smcFunc['db_query']('', '
281
					UPDATE {db_prefix}topics
282
					SET id_first_msg = 0
283
					WHERE id_first_msg = {int:id_first_msg}',
284
					array(
285
						'id_first_msg' => $row['myid_first_msg'],
286
					)
287
				);
288
				$smcFunc['db_query']('', '
289
					UPDATE {db_prefix}topics
290
					SET id_last_msg = 0
291
					WHERE id_last_msg = {int:id_last_msg}',
292
					array(
293
						'id_last_msg' => $row['myid_last_msg'],
294
					)
295
				);
296
297
				$memberStartedID = (int) getMsgMemberID($row['myid_first_msg']);
298
				$memberUpdatedID = (int) getMsgMemberID($row['myid_last_msg']);
299
300
				$newTopicID = $smcFunc['db_insert']('',
301
					'{db_prefix}topics',
302
					array(
303
						'id_board' => 'int',
304
						'id_member_started' => 'int',
305
						'id_member_updated' => 'int',
306
						'id_first_msg' => 'int',
307
						'id_last_msg' => 'int',
308
						'num_replies' => 'int'
309
					),
310
					array(
311
						$row['id_board'],
312
						$memberStartedID,
313
						$memberUpdatedID,
314
						$row['myid_first_msg'],
315
						$row['myid_last_msg'],
316
						$row['my_num_replies']
317
					),
318
					array('id_topic'),
319
					1
320
				);
321
322
				$smcFunc['db_query']('', '
323
					UPDATE {db_prefix}messages
324
					SET id_topic = {int:newTopicID}, id_board = {int:board_id}
325
					WHERE id_topic = {int:topic_id}',
326
					array(
327
						'board_id' => $row['id_board'],
328
						'topic_id' => $row['id_topic'],
329
						'newTopicID' => $newTopicID,
330
					)
331
				);
332
			},
333
			'force_fix' => array('stats_topics'),
334
			'messages' => array('repair_missing_topics', 'id_msg', 'id_topic'),
335
		),
336
		// Find topics with no messages.
337
		'missing_messages' => array(
338
			'substeps' => array(
339
				'step_size' => 1000,
340
				'step_max' => '
341
					SELECT MAX(id_topic)
342
					FROM {db_prefix}topics'
343
			),
344
			'check_query' => '
345
				SELECT t.id_topic, COUNT(m.id_msg) AS num_msg
346
				FROM {db_prefix}topics AS t
347
					LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic)
348
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
349
				GROUP BY t.id_topic
350
				HAVING COUNT(m.id_msg) = 0',
351
			// Remove all topics that have zero messages in the messages table.
352
			'fix_collect' => array(
353
				'index' => 'id_topic',
354
				'process' => function($topics) use ($smcFunc)
355
				{
356
					$smcFunc['db_query']('', '
357
						DELETE FROM {db_prefix}topics
358
						WHERE id_topic IN ({array_int:topics})',
359
						array(
360
							'topics' => $topics,
361
						)
362
					);
363
					$smcFunc['db_query']('', '
364
						DELETE FROM {db_prefix}log_topics
365
						WHERE id_topic IN ({array_int:topics})',
366
						array(
367
							'topics' => $topics,
368
						)
369
					);
370
				},
371
			),
372
			'messages' => array('repair_missing_messages', 'id_topic'),
373
		),
374
		'poll_options_missing_poll' => array(
375
			'substeps' => array(
376
				'step_size' => 500,
377
				'step_max' => '
378
					SELECT MAX(id_poll)
379
					FROM {db_prefix}poll_choices'
380
			),
381
			'check_query' => '
382
				SELECT o.id_poll, count(*) as amount, t.id_topic, t.id_board, t.id_member_started AS id_poster, m.member_name AS poster_name
383
				FROM {db_prefix}poll_choices AS o
384
					LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = o.id_poll)
385
					LEFT JOIN {db_prefix}topics AS t ON (t.id_poll = o.id_poll)
386
					LEFT JOIN {db_prefix}members AS m ON (m.id_member = t.id_member_started)
387
				WHERE o.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
388
					AND p.id_poll IS NULL
389
				GROUP BY o.id_poll, t.id_topic, t.id_board, t.id_member_started, m.member_name',
390
			'fix_processing' => function($row) use ($smcFunc, $txt)
391
			{
392
				global $salvageBoardID;
393
394
				$row['poster_name'] = !empty($row['poster_name']) ? $row['poster_name'] : $txt['guest'];
395
				$row['id_poster'] = !empty($row['id_poster']) ? $row['id_poster'] : 0;
396
397
				if (empty($row['id_board']))
398
				{
399
					// Only if we don't have a reasonable idea of where to put it.
400
					createSalvageArea();
401
					$row['id_board'] = $_SESSION['salvageBoardID'] = (int) $salvageBoardID;
402
				}
403
404
				if (empty($row['id_topic']))
405
				{
406
					$newMessageID = $smcFunc['db_insert']('',
407
						'{db_prefix}messages',
408
						array(
409
							'id_board' => 'int',
410
							'id_topic' => 'int',
411
							'poster_time' => 'int',
412
							'id_member' => 'int',
413
							'subject' => 'string-255',
414
							'poster_name' => 'string-255',
415
							'poster_email' => 'string-255',
416
							'poster_ip' => 'inet',
417
							'smileys_enabled' => 'int',
418
							'body' => 'string-65534',
419
							'icon' => 'string-16',
420
							'approved' => 'int',
421
						),
422
						array(
423
							$row['id_board'],
424
							0,
425
							time(),
426
							$row['id_poster'],
427
							$txt['salvaged_poll_topic_name'],
428
							$row['poster_name'],
429
							$txt['salvaged_poll_topic_name'],
430
							'127.0.0.1',
431
							1,
432
							$txt['salvaged_poll_message_body'],
433
							'xx',
434
							1,
435
						),
436
						array('id_msg'),
437
						1
438
					);
439
440
					$row['id_topic'] = $smcFunc['db_insert']('',
441
						'{db_prefix}topics',
442
						array(
443
							'id_board' => 'int',
444
							'id_poll' => 'int',
445
							'id_member_started' => 'int',
446
							'id_member_updated' => 'int',
447
							'id_first_msg' => 'int',
448
							'id_last_msg' => 'int',
449
							'num_replies' => 'int',
450
						),
451
						array(
452
							$row['id_board'],
453
							$row['id_poll'],
454
							$row['id_poster'],
455
							$row['id_poster'],
456
							$newMessageID,
457
							$newMessageID,
458
							0,
459
						),
460
						array('id_topic'),
461
						1
462
					);
463
464
					$smcFunc['db_query']('', '
465
						UPDATE {db_prefix}messages
466
						SET id_topic = {int:newTopicID}, id_board = {int:id_board}
467
						WHERE id_msg = {int:newMessageID}',
468
						array(
469
							'id_board' => $row['id_board'],
470
							'newTopicID' => $row['id_topic'],
471
							'newMessageID' => $newMessageID,
472
						)
473
					);
474
475
					updateStats('subject', $row['id_topic'], $txt['salvaged_poll_topic_name']);
476
				}
477
478
				$smcFunc['db_insert']('',
479
					'{db_prefix}polls',
480
					array(
481
						'id_poll' => 'int',
482
						'question' => 'string-255',
483
						'voting_locked' => 'int',
484
						'max_votes' => 'int',
485
						'expire_time' => 'int',
486
						'hide_results' => 'int',
487
						'change_vote' => 'int',
488
						'guest_vote' => 'int',
489
						'num_guest_voters' => 'int',
490
						'reset_poll' => 'int',
491
						'id_member' => 'int',
492
						'poster_name' => 'string-255',
493
					),
494
					array(
495
						$row['id_poll'],
496
						$txt['salvaged_poll_question'],
497
						1,
498
						0,
499
						0,
500
						0,
501
						0,
502
						0,
503
						0,
504
						0,
505
						$row['id_poster'],
506
						$row['poster_name'],
507
					),
508
					array()
509
				);
510
			},
511
			'force_fix' => array('stats_topics'),
512
			'messages' => array('repair_poll_options_missing_poll', 'id_poll', 'amount'),
513
		),
514
		'polls_missing_topics' => array(
515
			'substeps' => array(
516
				'step_size' => 500,
517
				'step_max' => '
518
					SELECT MAX(id_poll)
519
					FROM {db_prefix}polls'
520
			),
521
			'check_query' => '
522
				SELECT p.id_poll, p.id_member, p.poster_name, t.id_board
523
				FROM {db_prefix}polls AS p
524
					LEFT JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll)
525
				WHERE p.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
526
					AND t.id_poll IS NULL',
527
			'fix_processing' => function($row) use ($smcFunc, $txt)
528
			{
529
				global $salvageBoardID;
530
531
				// Only if we don't have a reasonable idea of where to put it.
532
				if ($row['id_board'] == 0)
533
				{
534
					createSalvageArea();
535
					$row['id_board'] = $_SESSION['salvageBoardID'] = (int) $salvageBoardID;
536
				}
537
538
				$row['poster_name'] = !empty($row['poster_name']) ? $row['poster_name'] : $txt['guest'];
539
540
				$newMessageID = $smcFunc['db_insert']('',
541
					'{db_prefix}messages',
542
					array(
543
						'id_board' => 'int',
544
						'id_topic' => 'int',
545
						'poster_time' => 'int',
546
						'id_member' => 'int',
547
						'subject' => 'string-255',
548
						'poster_name' => 'string-255',
549
						'poster_email' => 'string-255',
550
						'poster_ip' => 'inet',
551
						'smileys_enabled' => 'int',
552
						'body' => 'string-65534',
553
						'icon' => 'string-16',
554
						'approved' => 'int',
555
					),
556
					array(
557
						$row['id_board'],
558
						0,
559
						time(),
560
						$row['id_member'],
561
						$txt['salvaged_poll_topic_name'],
562
						$row['poster_name'],
563
						'',
564
						'127.0.0.1',
565
						1,
566
						$txt['salvaged_poll_message_body'],
567
						'xx',
568
						1,
569
					),
570
					array('id_msg'),
571
					1
572
				);
573
574
				$newTopicID = $smcFunc['db_insert']('',
575
					'{db_prefix}topics',
576
					array(
577
						'id_board' => 'int',
578
						'id_poll' => 'int',
579
						'id_member_started' => 'int',
580
						'id_member_updated' => 'int',
581
						'id_first_msg' => 'int',
582
						'id_last_msg' => 'int',
583
						'num_replies' => 'int',
584
					),
585
					array(
586
						$row['id_board'],
587
						$row['id_poll'],
588
						$row['id_member'],
589
						$row['id_member'],
590
						$newMessageID,
591
						$newMessageID,
592
						0,
593
					),
594
					array('id_topic'),
595
					1
596
				);
597
598
				$smcFunc['db_query']('', '
599
					UPDATE {db_prefix}messages
600
					SET id_topic = {int:newTopicID}, id_board = {int:id_board}
601
					WHERE id_msg = {int:newMessageID}',
602
					array(
603
						'id_board' => $row['id_board'],
604
						'newTopicID' => $newTopicID,
605
						'newMessageID' => $newMessageID,
606
					)
607
				);
608
609
				updateStats('subject', $newTopicID, $txt['salvaged_poll_topic_name']);
610
			},
611
			'force_fix' => array('stats_topics'),
612
			'messages' => array('repair_polls_missing_topics', 'id_poll', 'id_topic'),
613
		),
614
		'stats_topics' => array(
615
			'substeps' => array(
616
				'step_size' => 200,
617
				'step_max' => '
618
					SELECT MAX(id_topic)
619
					FROM {db_prefix}topics'
620
			),
621
			'check_query' => '
622
				SELECT
623
					t.id_topic, t.id_first_msg, t.id_last_msg,
624
					CASE WHEN MIN(ma.id_msg) > 0 THEN
625
						CASE WHEN MIN(mu.id_msg) > 0 THEN
626
							CASE WHEN MIN(mu.id_msg) < MIN(ma.id_msg) THEN MIN(mu.id_msg) ELSE MIN(ma.id_msg) END ELSE
627
						MIN(ma.id_msg) END ELSE
628
					MIN(mu.id_msg) END AS myid_first_msg,
629
					CASE WHEN MAX(ma.id_msg) > 0 THEN MAX(ma.id_msg) ELSE MIN(mu.id_msg) END AS myid_last_msg,
630
					t.approved, mf.approved, mf.approved AS firstmsg_approved
631
				FROM {db_prefix}topics AS t
632
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1)
633
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0)
634
					LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
635
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
636
				GROUP BY t.id_topic, t.id_first_msg, t.id_last_msg, t.approved, mf.approved
637
				ORDER BY t.id_topic',
638
			'fix_processing' => function($row) use ($smcFunc)
639
			{
640
				$row['firstmsg_approved'] = (int) $row['firstmsg_approved'];
641
				$row['myid_first_msg'] = (int) $row['myid_first_msg'];
642
				$row['myid_last_msg'] = (int) $row['myid_last_msg'];
643
644
				// Not really a problem?
645
				if ($row['id_first_msg'] == $row['myid_first_msg'] && $row['id_last_msg'] == $row['myid_last_msg'] && $row['approved'] == $row['firstmsg_approved'])
646
					return false;
647
648
				$memberStartedID = (int) getMsgMemberID($row['myid_first_msg']);
649
				$memberUpdatedID = (int) getMsgMemberID($row['myid_last_msg']);
650
651
				$smcFunc['db_query']('', '
652
					UPDATE {db_prefix}topics
653
					SET id_first_msg = {int:myid_first_msg},
654
						id_member_started = {int:memberStartedID}, id_last_msg = {int:myid_last_msg},
655
						id_member_updated = {int:memberUpdatedID}, approved = {int:firstmsg_approved}
656
					WHERE id_topic = {int:topic_id}',
657
					array(
658
						'myid_first_msg' => $row['myid_first_msg'],
659
						'memberStartedID' => $memberStartedID,
660
						'myid_last_msg' => $row['myid_last_msg'],
661
						'memberUpdatedID' => $memberUpdatedID,
662
						'firstmsg_approved' => $row['firstmsg_approved'],
663
						'topic_id' => $row['id_topic'],
664
					)
665
				);
666
			},
667
			'message_function' => function($row) use ($txt, &$context)
668
			{
669
				// A pretend error?
670
				if ($row['id_first_msg'] == $row['myid_first_msg'] && $row['id_last_msg'] == $row['myid_last_msg'] && $row['approved'] == $row['firstmsg_approved'])
671
					return false;
672
673
				if ($row['id_first_msg'] != $row['myid_first_msg'])
674
					$context['repair_errors'][] = sprintf($txt['repair_topic_wrong_first_id'], $row['id_topic'], $row['id_first_msg']);
675
				if ($row['id_last_msg'] != $row['myid_last_msg'])
676
					$context['repair_errors'][] = sprintf($txt['repair_topic_wrong_last_id'], $row['id_topic'], $row['id_last_msg']);
677
				if ($row['approved'] != $row['firstmsg_approved'])
678
					$context['repair_errors'][] = sprintf($txt['repair_topic_wrong_approval'], $row['id_topic']);
679
680
				return true;
681
			},
682
		),
683
		// Find topics with incorrect num_replies.
684
		'stats_topics2' => array(
685
			'substeps' => array(
686
				'step_size' => 300,
687
				'step_max' => '
688
					SELECT MAX(id_topic)
689
					FROM {db_prefix}topics'
690
			),
691
			'check_query' => '
692
				SELECT
693
					t.id_topic, t.num_replies, mf.approved,
694
					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
695
				FROM {db_prefix}topics AS t
696
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1)
697
					LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
698
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
699
				GROUP BY t.id_topic, t.num_replies, mf.approved
700
				ORDER BY t.id_topic',
701
			'fix_processing' => function($row)
702
			{
703
				global $smcFunc;
704
				$row['my_num_replies'] = (int) $row['my_num_replies'];
705
706
				// Not really a problem?
707
				if ($row['my_num_replies'] == $row['num_replies'])
708
					return false;
709
710
				$smcFunc['db_query']('', '
711
					UPDATE {db_prefix}topics
712
					SET num_replies = {int:my_num_replies}
713
					WHERE id_topic = {int:topic_id}',
714
					array(
715
						'my_num_replies' => $row['my_num_replies'],
716
						'topic_id' => $row['id_topic'],
717
					)
718
				);
719
			},
720
			'message_function' => function($row)
721
			{
722
				global $txt, $context;
723
724
				// Just joking?
725
				if ($row['my_num_replies'] == $row['num_replies'])
726
					return false;
727
728
				if ($row['num_replies'] != $row['my_num_replies'])
729
					$context['repair_errors'][] = sprintf($txt['repair_topic_wrong_replies'], $row['id_topic'], $row['num_replies']);
730
731
				return true;
732
			},
733
		),
734
		// Find topics with incorrect unapproved_posts.
735
		'stats_topics3' => array(
736
			'substeps' => array(
737
				'step_size' => 1000,
738
				'step_max' => '
739
					SELECT MAX(id_topic)
740
					FROM {db_prefix}topics'
741
			),
742
			'check_query' => '
743
				SELECT
744
					t.id_topic, t.unapproved_posts, COUNT(mu.id_msg) AS my_unapproved_posts
745
				FROM {db_prefix}topics AS t
746
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0)
747
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
748
				GROUP BY t.id_topic, t.unapproved_posts
749
				HAVING unapproved_posts != COUNT(mu.id_msg)
750
				ORDER BY t.id_topic',
751
			'fix_processing' => function($row)
752
			{
753
				global $smcFunc;
754
				$row['my_unapproved_posts'] = (int) $row['my_unapproved_posts'];
755
756
				$smcFunc['db_query']('', '
757
					UPDATE {db_prefix}topics
758
					SET unapproved_posts = {int:my_unapproved_posts}
759
					WHERE id_topic = {int:topic_id}',
760
					array(
761
						'my_unapproved_posts' => $row['my_unapproved_posts'],
762
						'topic_id' => $row['id_topic'],
763
					)
764
				);
765
			},
766
			'messages' => array('repair_topic_wrong_unapproved_number', 'id_topic', 'unapproved_posts'),
767
		),
768
		// Find topics with nonexistent boards.
769
		'missing_boards' => array(
770
			'substeps' => array(
771
				'step_size' => 1000,
772
				'step_max' => '
773
					SELECT MAX(id_topic)
774
					FROM {db_prefix}topics'
775
			),
776
			'check_query' => '
777
				SELECT t.id_topic, t.id_board
778
				FROM {db_prefix}topics AS t
779
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
780
				WHERE b.id_board IS NULL
781
					AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
782
				ORDER BY t.id_board, t.id_topic',
783
			'fix_query' => '
784
				SELECT t.id_board, COUNT(*) AS my_num_topics, COUNT(m.id_msg) AS my_num_posts
785
				FROM {db_prefix}topics AS t
786
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
787
					LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic)
788
				WHERE b.id_board IS NULL
789
					AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
790
				GROUP BY t.id_board',
791
			'fix_processing' => function($row)
792
			{
793
				global $smcFunc, $salvageCatID, $txt;
794
				createSalvageArea();
795
796
				$row['my_num_topics'] = (int) $row['my_num_topics'];
797
				$row['my_num_posts'] = (int) $row['my_num_posts'];
798
799
				$newBoardID = $smcFunc['db_insert']('',
800
					'{db_prefix}boards',
801
					array('id_cat' => 'int', 'name' => 'string', 'description' => 'string', 'num_topics' => 'int', 'num_posts' => 'int', 'member_groups' => 'string'),
802
					array($salvageCatID, $txt['salvaged_board_name'], $txt['salvaged_board_description'], $row['my_num_topics'], $row['my_num_posts'], '1'),
803
					array('id_board'),
804
					1
805
				);
806
807
				$smcFunc['db_query']('', '
808
					UPDATE {db_prefix}topics
809
					SET id_board = {int:newBoardID}
810
					WHERE id_board = {int:board_id}',
811
					array(
812
						'newBoardID' => $newBoardID,
813
						'board_id' => $row['id_board'],
814
					)
815
				);
816
				$smcFunc['db_query']('', '
817
					UPDATE {db_prefix}messages
818
					SET id_board = {int:newBoardID}
819
					WHERE id_board = {int:board_id}',
820
					array(
821
						'newBoardID' => $newBoardID,
822
						'board_id' => $row['id_board'],
823
					)
824
				);
825
			},
826
			'messages' => array('repair_missing_boards', 'id_topic', 'id_board'),
827
		),
828
		// Find boards with nonexistent categories.
829
		'missing_categories' => array(
830
			'check_query' => '
831
				SELECT b.id_board, b.id_cat
832
				FROM {db_prefix}boards AS b
833
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
834
				WHERE c.id_cat IS NULL
835
				ORDER BY b.id_cat, b.id_board',
836
			'fix_collect' => array(
837
				'index' => 'id_cat',
838
				'process' => function($cats)
839
				{
840
					global $smcFunc, $salvageCatID;
841
					createSalvageArea();
842
					$smcFunc['db_query']('', '
843
						UPDATE {db_prefix}boards
844
						SET id_cat = {int:salvageCatID}
845
						WHERE id_cat IN ({array_int:categories})',
846
						array(
847
							'salvageCatID' => $salvageCatID,
848
							'categories' => $cats,
849
						)
850
					);
851
				},
852
			),
853
			'messages' => array('repair_missing_categories', 'id_board', 'id_cat'),
854
		),
855
		// Find messages with nonexistent members.
856
		'missing_posters' => array(
857
			'substeps' => array(
858
				'step_size' => 2000,
859
				'step_max' => '
860
					SELECT MAX(id_msg)
861
					FROM {db_prefix}messages'
862
			),
863
			'check_query' => '
864
				SELECT m.id_msg, m.id_member
865
				FROM {db_prefix}messages AS m
866
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
867
				WHERE mem.id_member IS NULL
868
					AND m.id_member != 0
869
					AND m.id_msg BETWEEN {STEP_LOW} AND {STEP_HIGH}
870
				ORDER BY m.id_msg',
871
			// Last step-make sure all non-guest posters still exist.
872
			'fix_collect' => array(
873
				'index' => 'id_msg',
874
				'process' => function($msgs)
875
				{
876
					global $smcFunc;
877
					$smcFunc['db_query']('', '
878
						UPDATE {db_prefix}messages
879
						SET id_member = {int:guest_id}
880
						WHERE id_msg IN ({array_int:msgs})',
881
						array(
882
							'msgs' => $msgs,
883
							'guest_id' => 0,
884
						)
885
					);
886
				},
887
			),
888
			'messages' => array('repair_missing_posters', 'id_msg', 'id_member'),
889
		),
890
		// Find boards with nonexistent parents.
891
		'missing_parents' => array(
892
			'check_query' => '
893
				SELECT b.id_board, b.id_parent
894
				FROM {db_prefix}boards AS b
895
					LEFT JOIN {db_prefix}boards AS p ON (p.id_board = b.id_parent)
896
				WHERE b.id_parent != 0
897
					AND (p.id_board IS NULL OR p.id_board = b.id_board)
898
				ORDER BY b.id_parent, b.id_board',
899
			'fix_collect' => array(
900
				'index' => 'id_parent',
901
				'process' => function($parents)
902
				{
903
					global $smcFunc, $salvageBoardID, $salvageCatID;
904
905
					createSalvageArea();
906
					$_SESSION['salvageBoardID'] = (int) $salvageBoardID;
907
908
					$smcFunc['db_query']('', '
909
						UPDATE {db_prefix}boards
910
						SET id_parent = {int:salvageBoardID}, id_cat = {int:salvageCatID}, child_level = 1
911
						WHERE id_parent IN ({array_int:parents})',
912
						array(
913
							'salvageBoardID' => $salvageBoardID,
914
							'salvageCatID' => $salvageCatID,
915
							'parents' => $parents,
916
						)
917
					);
918
				},
919
			),
920
			'messages' => array('repair_missing_parents', 'id_board', 'id_parent'),
921
		),
922
		'missing_polls' => array(
923
			'substeps' => array(
924
				'step_size' => 500,
925
				'step_max' => '
926
					SELECT MAX(id_poll)
927
					FROM {db_prefix}topics'
928
			),
929
			'check_query' => '
930
				SELECT t.id_poll, t.id_topic
931
				FROM {db_prefix}topics AS t
932
					LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll)
933
				WHERE t.id_poll != 0
934
					AND t.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
935
					AND p.id_poll IS NULL',
936
			'fix_collect' => array(
937
				'index' => 'id_poll',
938
				'process' => function($polls)
939
				{
940
					global $smcFunc;
941
					$smcFunc['db_query']('', '
942
						UPDATE {db_prefix}topics
943
						SET id_poll = 0
944
						WHERE id_poll IN ({array_int:polls})',
945
						array(
946
							'polls' => $polls,
947
						)
948
					);
949
				},
950
			),
951
			'messages' => array('repair_missing_polls', 'id_topic', 'id_poll'),
952
		),
953
		'missing_calendar_topics' => array(
954
			'substeps' => array(
955
				'step_size' => 1000,
956
				'step_max' => '
957
					SELECT MAX(id_topic)
958
					FROM {db_prefix}calendar'
959
			),
960
			'check_query' => '
961
				SELECT cal.id_topic, cal.id_event
962
				FROM {db_prefix}calendar AS cal
963
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic)
964
				WHERE cal.id_topic != 0
965
					AND cal.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
966
					AND t.id_topic IS NULL
967
				ORDER BY cal.id_topic',
968
			'fix_collect' => array(
969
				'index' => 'id_topic',
970
				'process' => function($events)
971
				{
972
					global $smcFunc;
973
					$smcFunc['db_query']('', '
974
						UPDATE {db_prefix}calendar
975
						SET id_topic = 0, id_board = 0
976
						WHERE id_topic IN ({array_int:events})',
977
						array(
978
							'events' => $events,
979
						)
980
					);
981
				},
982
			),
983
			'messages' => array('repair_missing_calendar_topics', 'id_event', 'id_topic'),
984
		),
985
		'missing_log_topics' => array(
986
			'substeps' => array(
987
				'step_size' => 150,
988
				'step_max' => '
989
					SELECT MAX(id_member)
990
					FROM {db_prefix}log_topics'
991
			),
992
			'check_query' => '
993
				SELECT lt.id_topic
994
				FROM {db_prefix}log_topics AS lt
995
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lt.id_topic)
996
				WHERE t.id_topic IS NULL
997
					AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
998
			'fix_collect' => array(
999
				'index' => 'id_topic',
1000
				'process' => function($topics)
1001
				{
1002
					global $smcFunc;
1003
					$smcFunc['db_query']('', '
1004
						DELETE FROM {db_prefix}log_topics
1005
						WHERE id_topic IN ({array_int:topics})',
1006
						array(
1007
							'topics' => $topics,
1008
						)
1009
					);
1010
				},
1011
			),
1012
			'messages' => array('repair_missing_log_topics', 'id_topic'),
1013
		),
1014
		'missing_log_topics_members' => array(
1015
			'substeps' => array(
1016
				'step_size' => 150,
1017
				'step_max' => '
1018
					SELECT MAX(id_member)
1019
					FROM {db_prefix}log_topics'
1020
			),
1021
			'check_query' => '
1022
				SELECT lt.id_member
1023
				FROM {db_prefix}log_topics AS lt
1024
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lt.id_member)
1025
				WHERE mem.id_member IS NULL
1026
					AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1027
				GROUP BY lt.id_member',
1028
			'fix_collect' => array(
1029
				'index' => 'id_member',
1030
				'process' => function($members)
1031
				{
1032
					global $smcFunc;
1033
					$smcFunc['db_query']('', '
1034
						DELETE FROM {db_prefix}log_topics
1035
						WHERE id_member IN ({array_int:members})',
1036
						array(
1037
							'members' => $members,
1038
						)
1039
					);
1040
				},
1041
			),
1042
			'messages' => array('repair_missing_log_topics_members', 'id_member'),
1043
		),
1044
		'missing_log_boards' => array(
1045
			'substeps' => array(
1046
				'step_size' => 500,
1047
				'step_max' => '
1048
					SELECT MAX(id_member)
1049
					FROM {db_prefix}log_boards'
1050
			),
1051
			'check_query' => '
1052
				SELECT lb.id_board
1053
				FROM {db_prefix}log_boards AS lb
1054
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lb.id_board)
1055
				WHERE b.id_board IS NULL
1056
					AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1057
				GROUP BY lb.id_board',
1058
			'fix_collect' => array(
1059
				'index' => 'id_board',
1060
				'process' => function($boards)
1061
				{
1062
					global $smcFunc;
1063
					$smcFunc['db_query']('', '
1064
						DELETE FROM {db_prefix}log_boards
1065
						WHERE id_board IN ({array_int:boards})',
1066
						array(
1067
							'boards' => $boards,
1068
						)
1069
					);
1070
				},
1071
			),
1072
			'messages' => array('repair_missing_log_boards', 'id_board'),
1073
		),
1074
		'missing_log_boards_members' => array(
1075
			'substeps' => array(
1076
				'step_size' => 500,
1077
				'step_max' => '
1078
					SELECT MAX(id_member)
1079
					FROM {db_prefix}log_boards'
1080
			),
1081
			'check_query' => '
1082
				SELECT lb.id_member
1083
				FROM {db_prefix}log_boards AS lb
1084
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member)
1085
				WHERE mem.id_member IS NULL
1086
					AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1087
				GROUP BY lb.id_member',
1088
			'fix_collect' => array(
1089
				'index' => 'id_member',
1090
				'process' => function($members) use ($smcFunc)
1091
				{
1092
					$smcFunc['db_query']('', '
1093
						DELETE FROM {db_prefix}log_boards
1094
						WHERE id_member IN ({array_int:members})',
1095
						array(
1096
							'members' => $members,
1097
						)
1098
					);
1099
				},
1100
			),
1101
			'messages' => array('repair_missing_log_boards_members', 'id_member'),
1102
		),
1103
		'missing_log_mark_read' => array(
1104
			'substeps' => array(
1105
				'step_size' => 500,
1106
				'step_max' => '
1107
					SELECT MAX(id_member)
1108
					FROM {db_prefix}log_mark_read'
1109
			),
1110
			'check_query' => '
1111
				SELECT lmr.id_board
1112
				FROM {db_prefix}log_mark_read AS lmr
1113
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lmr.id_board)
1114
				WHERE b.id_board IS NULL
1115
					AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1116
				GROUP BY lmr.id_board',
1117
			'fix_collect' => array(
1118
				'index' => 'id_board',
1119
				'process' => function($boards) use ($smcFunc)
1120
				{
1121
					$smcFunc['db_query']('', '
1122
						DELETE FROM {db_prefix}log_mark_read
1123
						WHERE id_board IN ({array_int:boards})',
1124
						array(
1125
							'boards' => $boards,
1126
						)
1127
					);
1128
				},
1129
			),
1130
			'messages' => array('repair_missing_log_mark_read', 'id_board'),
1131
		),
1132
		'missing_log_mark_read_members' => array(
1133
			'substeps' => array(
1134
				'step_size' => 500,
1135
				'step_max' => '
1136
					SELECT MAX(id_member)
1137
					FROM {db_prefix}log_mark_read'
1138
			),
1139
			'check_query' => '
1140
				SELECT lmr.id_member
1141
				FROM {db_prefix}log_mark_read AS lmr
1142
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lmr.id_member)
1143
				WHERE mem.id_member IS NULL
1144
					AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1145
				GROUP BY lmr.id_member',
1146
			'fix_collect' => array(
1147
				'index' => 'id_member',
1148
				'process' => function($members) use ($smcFunc)
1149
				{
1150
					$smcFunc['db_query']('', '
1151
						DELETE FROM {db_prefix}log_mark_read
1152
						WHERE id_member IN ({array_int:members})',
1153
						array(
1154
							'members' => $members,
1155
						)
1156
					);
1157
				},
1158
			),
1159
			'messages' => array('repair_missing_log_mark_read_members', 'id_member'),
1160
		),
1161
		'missing_pms' => array(
1162
			'substeps' => array(
1163
				'step_size' => 500,
1164
				'step_max' => '
1165
					SELECT MAX(id_pm)
1166
					FROM {db_prefix}pm_recipients'
1167
			),
1168
			'check_query' => '
1169
				SELECT pmr.id_pm
1170
				FROM {db_prefix}pm_recipients AS pmr
1171
					LEFT JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
1172
				WHERE pm.id_pm IS NULL
1173
					AND pmr.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH}
1174
				GROUP BY pmr.id_pm',
1175
			'fix_collect' => array(
1176
				'index' => 'id_pm',
1177
				'process' => function($pms) use ($smcFunc)
1178
				{
1179
					$smcFunc['db_query']('', '
1180
						DELETE FROM {db_prefix}pm_recipients
1181
						WHERE id_pm IN ({array_int:pms})',
1182
						array(
1183
							'pms' => $pms,
1184
						)
1185
					);
1186
				},
1187
			),
1188
			'messages' => array('repair_missing_pms', 'id_pm'),
1189
		),
1190
		'missing_recipients' => array(
1191
			'substeps' => array(
1192
				'step_size' => 500,
1193
				'step_max' => '
1194
					SELECT MAX(id_member)
1195
					FROM {db_prefix}pm_recipients'
1196
			),
1197
			'check_query' => '
1198
				SELECT pmr.id_member
1199
				FROM {db_prefix}pm_recipients AS pmr
1200
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
1201
				WHERE pmr.id_member != 0
1202
					AND pmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1203
					AND mem.id_member IS NULL
1204
				GROUP BY pmr.id_member',
1205
			'fix_collect' => array(
1206
				'index' => 'id_member',
1207
				'process' => function($members)
1208
				{
1209
					global $smcFunc;
1210
					$smcFunc['db_query']('', '
1211
						DELETE FROM {db_prefix}pm_recipients
1212
						WHERE id_member IN ({array_int:members})',
1213
						array(
1214
							'members' => $members,
1215
						)
1216
					);
1217
				},
1218
			),
1219
			'messages' => array('repair_missing_recipients', 'id_member'),
1220
		),
1221
		'missing_senders' => array(
1222
			'substeps' => array(
1223
				'step_size' => 500,
1224
				'step_max' => '
1225
					SELECT MAX(id_pm)
1226
					FROM {db_prefix}personal_messages'
1227
			),
1228
			'check_query' => '
1229
				SELECT pm.id_pm, pm.id_member_from
1230
				FROM {db_prefix}personal_messages AS pm
1231
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
1232
				WHERE pm.id_member_from != 0
1233
					AND pm.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH}
1234
					AND mem.id_member IS NULL',
1235
			'fix_collect' => array(
1236
				'index' => 'id_pm',
1237
				'process' => function($guestMessages)
1238
				{
1239
					global $smcFunc;
1240
					$smcFunc['db_query']('', '
1241
						UPDATE {db_prefix}personal_messages
1242
						SET id_member_from = 0
1243
						WHERE id_pm IN ({array_int:guestMessages})',
1244
						array(
1245
							'guestMessages' => $guestMessages,
1246
						)
1247
					);
1248
				},
1249
			),
1250
			'messages' => array('repair_missing_senders', 'id_pm', 'id_member_from'),
1251
		),
1252
		'missing_notify_members' => array(
1253
			'substeps' => array(
1254
				'step_size' => 500,
1255
				'step_max' => '
1256
					SELECT MAX(id_member)
1257
					FROM {db_prefix}log_notify'
1258
			),
1259
			'check_query' => '
1260
				SELECT ln.id_member
1261
				FROM {db_prefix}log_notify AS ln
1262
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
1263
				WHERE ln.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1264
					AND mem.id_member IS NULL
1265
				GROUP BY ln.id_member',
1266
			'fix_collect' => array(
1267
				'index' => 'id_member',
1268
				'process' => function($members) use ($smcFunc)
1269
				{
1270
					$smcFunc['db_query']('', '
1271
						DELETE FROM {db_prefix}log_notify
1272
						WHERE id_member IN ({array_int:members})',
1273
						array(
1274
							'members' => $members,
1275
						)
1276
					);
1277
				},
1278
			),
1279
			'messages' => array('repair_missing_notify_members', 'id_member'),
1280
		),
1281
		'missing_cached_subject' => array(
1282
			'substeps' => array(
1283
				'step_size' => 100,
1284
				'step_max' => '
1285
					SELECT MAX(id_topic)
1286
					FROM {db_prefix}topics'
1287
			),
1288
			'check_query' => '
1289
				SELECT t.id_topic, fm.subject
1290
				FROM {db_prefix}topics AS t
1291
					INNER JOIN {db_prefix}messages AS fm ON (fm.id_msg = t.id_first_msg)
1292
					LEFT JOIN {db_prefix}log_search_subjects AS lss ON (lss.id_topic = t.id_topic)
1293
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
1294
					AND lss.id_topic IS NULL',
1295
			'fix_full_processing' => function($result)
1296
			{
1297
				global $smcFunc;
1298
1299
				$inserts = array();
1300
				while ($row = $smcFunc['db_fetch_assoc']($result))
1301
				{
1302
					foreach (text2words($row['subject']) as $word)
1303
						$inserts[] = array($word, $row['id_topic']);
1304
					if (count($inserts) > 500)
1305
					{
1306
						$smcFunc['db_insert']('ignore',
1307
							'{db_prefix}log_search_subjects',
1308
							array('word' => 'string', 'id_topic' => 'int'),
1309
							$inserts,
1310
							array('word', 'id_topic')
1311
						);
1312
						$inserts = array();
1313
					}
1314
				}
1315
1316
				if (!empty($inserts))
1317
					$smcFunc['db_insert']('ignore',
1318
						'{db_prefix}log_search_subjects',
1319
						array('word' => 'string', 'id_topic' => 'int'),
1320
						$inserts,
1321
						array('word', 'id_topic')
1322
					);
1323
			},
1324
			'message_function' => function($row)
1325
			{
1326
				global $txt, $context;
1327
1328
				if (count(text2words($row['subject'])) != 0)
1329
				{
1330
					$context['repair_errors'][] = sprintf($txt['repair_missing_cached_subject'], $row['id_topic']);
1331
					return true;
1332
				}
1333
1334
				return false;
1335
			},
1336
		),
1337
		'missing_topic_for_cache' => array(
1338
			'substeps' => array(
1339
				'step_size' => 50,
1340
				'step_max' => '
1341
					SELECT MAX(id_topic)
1342
					FROM {db_prefix}log_search_subjects'
1343
			),
1344
			'check_query' => '
1345
				SELECT lss.id_topic, lss.word
1346
				FROM {db_prefix}log_search_subjects AS lss
1347
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lss.id_topic)
1348
				WHERE lss.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
1349
					AND t.id_topic IS NULL',
1350
			'fix_collect' => array(
1351
				'index' => 'id_topic',
1352
				'process' => function($deleteTopics)
1353
				{
1354
					global $smcFunc;
1355
					$smcFunc['db_query']('', '
1356
						DELETE FROM {db_prefix}log_search_subjects
1357
						WHERE id_topic IN ({array_int:deleteTopics})',
1358
						array(
1359
							'deleteTopics' => $deleteTopics,
1360
						)
1361
					);
1362
				},
1363
			),
1364
			'messages' => array('repair_missing_topic_for_cache', 'word'),
1365
		),
1366
		'missing_member_vote' => array(
1367
			'substeps' => array(
1368
				'step_size' => 500,
1369
				'step_max' => '
1370
					SELECT MAX(id_member)
1371
					FROM {db_prefix}log_polls'
1372
			),
1373
			'check_query' => '
1374
				SELECT lp.id_poll, lp.id_member
1375
				FROM {db_prefix}log_polls AS lp
1376
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lp.id_member)
1377
				WHERE lp.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1378
					AND lp.id_member > 0
1379
					AND mem.id_member IS NULL',
1380
			'fix_collect' => array(
1381
				'index' => 'id_member',
1382
				'process' => function($members)
1383
				{
1384
					global $smcFunc;
1385
					$smcFunc['db_query']('', '
1386
						DELETE FROM {db_prefix}log_polls
1387
						WHERE id_member IN ({array_int:members})',
1388
						array(
1389
							'members' => $members,
1390
						)
1391
					);
1392
				},
1393
			),
1394
			'messages' => array('repair_missing_log_poll_member', 'id_poll', 'id_member'),
1395
		),
1396
		'missing_log_poll_vote' => array(
1397
			'substeps' => array(
1398
				'step_size' => 500,
1399
				'step_max' => '
1400
					SELECT MAX(id_poll)
1401
					FROM {db_prefix}log_polls'
1402
			),
1403
			'check_query' => '
1404
				SELECT lp.id_poll, lp.id_member
1405
				FROM {db_prefix}log_polls AS lp
1406
					LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = lp.id_poll)
1407
				WHERE lp.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
1408
					AND p.id_poll IS NULL',
1409
			'fix_collect' => array(
1410
				'index' => 'id_poll',
1411
				'process' => function($polls)
1412
				{
1413
					global $smcFunc;
1414
					$smcFunc['db_query']('', '
1415
						DELETE FROM {db_prefix}log_polls
1416
						WHERE id_poll IN ({array_int:polls})',
1417
						array(
1418
							'polls' => $polls,
1419
						)
1420
					);
1421
				},
1422
			),
1423
			'messages' => array('repair_missing_log_poll_vote', 'id_member', 'id_poll'),
1424
		),
1425
		'report_missing_comments' => array(
1426
			'substeps' => array(
1427
				'step_size' => 500,
1428
				'step_max' => '
1429
					SELECT MAX(id_report)
1430
					FROM {db_prefix}log_reported'
1431
			),
1432
			'check_query' => '
1433
				SELECT lr.id_report, lr.subject
1434
				FROM {db_prefix}log_reported AS lr
1435
					LEFT JOIN {db_prefix}log_reported_comments AS lrc ON (lrc.id_report = lr.id_report)
1436
				WHERE lr.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH}
1437
					AND lrc.id_report IS NULL',
1438
			'fix_collect' => array(
1439
				'index' => 'id_report',
1440
				'process' => function($reports)
1441
				{
1442
					global $smcFunc;
1443
					$smcFunc['db_query']('', '
1444
						DELETE FROM {db_prefix}log_reported
1445
						WHERE id_report IN ({array_int:reports})',
1446
						array(
1447
							'reports' => $reports,
1448
						)
1449
					);
1450
				},
1451
			),
1452
			'messages' => array('repair_report_missing_comments', 'id_report', 'subject'),
1453
		),
1454
		'comments_missing_report' => array(
1455
			'substeps' => array(
1456
				'step_size' => 200,
1457
				'step_max' => '
1458
					SELECT MAX(id_report)
1459
					FROM {db_prefix}log_reported_comments'
1460
			),
1461
			'check_query' => '
1462
				SELECT lrc.id_report, lrc.membername
1463
				FROM {db_prefix}log_reported_comments AS lrc
1464
					LEFT JOIN {db_prefix}log_reported AS lr ON (lr.id_report = lrc.id_report)
1465
				WHERE lrc.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH}
1466
					AND lr.id_report IS NULL',
1467
			'fix_collect' => array(
1468
				'index' => 'id_report',
1469
				'process' => function($reports)
1470
				{
1471
					global $smcFunc;
1472
					$smcFunc['db_query']('', '
1473
						DELETE FROM {db_prefix}log_reported_comments
1474
						WHERE id_report IN ({array_int:reports})',
1475
						array(
1476
							'reports' => $reports,
1477
						)
1478
					);
1479
				},
1480
			),
1481
			'messages' => array('repair_comments_missing_report', 'id_report', 'membername'),
1482
		),
1483
		'group_request_missing_member' => array(
1484
			'substeps' => array(
1485
				'step_size' => 200,
1486
				'step_max' => '
1487
					SELECT MAX(id_member)
1488
					FROM {db_prefix}log_group_requests'
1489
			),
1490
			'check_query' => '
1491
				SELECT lgr.id_member
1492
				FROM {db_prefix}log_group_requests AS lgr
1493
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
1494
				WHERE lgr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1495
					AND mem.id_member IS NULL
1496
				GROUP BY lgr.id_member',
1497
			'fix_collect' => array(
1498
				'index' => 'id_member',
1499
				'process' => function($members)
1500
				{
1501
					global $smcFunc;
1502
					$smcFunc['db_query']('', '
1503
						DELETE FROM {db_prefix}log_group_requests
1504
						WHERE id_member IN ({array_int:members})',
1505
						array(
1506
							'members' => $members,
1507
						)
1508
					);
1509
				},
1510
			),
1511
			'messages' => array('repair_group_request_missing_member', 'id_member'),
1512
		),
1513
		'group_request_missing_group' => array(
1514
			'substeps' => array(
1515
				'step_size' => 200,
1516
				'step_max' => '
1517
					SELECT MAX(id_group)
1518
					FROM {db_prefix}log_group_requests'
1519
			),
1520
			'check_query' => '
1521
				SELECT lgr.id_group
1522
				FROM {db_prefix}log_group_requests AS lgr
1523
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
1524
				WHERE lgr.id_group BETWEEN {STEP_LOW} AND {STEP_HIGH}
1525
					AND mg.id_group IS NULL
1526
				GROUP BY lgr.id_group',
1527
			'fix_collect' => array(
1528
				'index' => 'id_group',
1529
				'process' => function($groups)
1530
				{
1531
					global $smcFunc;
1532
					$smcFunc['db_query']('', '
1533
						DELETE FROM {db_prefix}log_group_requests
1534
						WHERE id_group IN ({array_int:groups})',
1535
						array(
1536
							'groups' => $groups,
1537
						)
1538
					);
1539
				},
1540
			),
1541
			'messages' => array('repair_group_request_missing_group', 'id_group'),
1542
		),
1543
	);
1544
}
1545
1546
/**
1547
 * Checks for errors in steps, until 5 seconds have passed.
1548
 * It keeps track of the errors it did find, so that the actual repair
1549
 * won't have to recheck everything.
1550
 *
1551
 * @param bool $do_fix Whether to actually fix the errors or just return the info
1552
 * @return array the errors found.
1553
 */
1554
function findForumErrors($do_fix = false)
1555
{
1556
	global $context, $txt, $smcFunc, $errorTests, $db_cache, $db_temp_cache;
1557
1558
	// This may take some time...
1559
	@set_time_limit(600);
1560
1561
	$to_fix = !empty($_SESSION['repairboards_to_fix']) ? $_SESSION['repairboards_to_fix'] : array();
1562
	$context['repair_errors'] = isset($_SESSION['repairboards_to_fix2']) ? $_SESSION['repairboards_to_fix2'] : array();
1563
1564
	$_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step'];
1565
	$_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep'];
1566
1567
	// Don't allow the cache to get too full.
1568
	$db_temp_cache = $db_cache;
1569
	$db_cache = array();
1570
1571
	$context['total_steps'] = count($errorTests);
1572
1573
	// For all the defined error types do the necessary tests.
1574
	$current_step = -1;
1575
	$total_queries = 0;
1576
	foreach ($errorTests as $error_type => $test)
1577
	{
1578
		$current_step++;
1579
1580
		// Already done this?
1581
		if ($_GET['step'] > $current_step)
1582
			continue;
1583
1584
		// If we're fixing it but it ain't broke why try?
1585
		if ($do_fix && !in_array($error_type, $to_fix))
1586
		{
1587
			$_GET['step']++;
1588
			continue;
1589
		}
1590
1591
		// Has it got substeps?
1592
		if (isset($test['substeps']))
1593
		{
1594
			$step_size = isset($test['substeps']['step_size']) ? $test['substeps']['step_size'] : 100;
1595
			$request = $smcFunc['db_query']('',
1596
				$test['substeps']['step_max'],
1597
				array(
1598
				)
1599
			);
1600
			list ($step_max) = $smcFunc['db_fetch_row']($request);
1601
1602
			$total_queries++;
1603
			$smcFunc['db_free_result']($request);
1604
		}
1605
1606
		// We in theory keep doing this... the substeps.
1607
		$done = false;
1608
		while (!$done)
1609
		{
1610
			// Make sure there's at least one ID to test.
1611
			if (isset($test['substeps']) && empty($step_max))
1612
				break;
1613
1614
			// What is the testing query (Changes if we are testing or fixing)
1615
			if (!$do_fix)
1616
				$test_query = 'check_query';
1617
			else
1618
				$test_query = isset($test['fix_query']) ? 'fix_query' : 'check_query';
1619
1620
			// Do the test...
1621
			$request = $smcFunc['db_query']('',
1622
				isset($test['substeps']) ? strtr($test[$test_query], array('{STEP_LOW}' => $_GET['substep'], '{STEP_HIGH}' => $_GET['substep'] + $step_size - 1)) : $test[$test_query],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $step_size does not seem to be defined for all execution paths leading up to this point.
Loading history...
1623
				array(
1624
				)
1625
			);
1626
1627
			// Does it need a fix?
1628
			if (!empty($test['check_type']) && $test['check_type'] == 'count')
1629
				list ($needs_fix) = $smcFunc['db_fetch_row']($request);
1630
			else
1631
				$needs_fix = $smcFunc['db_num_rows']($request);
1632
1633
			$total_queries++;
1634
1635
			if ($needs_fix)
1636
			{
1637
				// What about a message to the user?
1638
				if (!$do_fix)
1639
				{
1640
					// Assume need to fix.
1641
					$found_errors = true;
1642
1643
					if (isset($test['message']))
1644
						$context['repair_errors'][] = $txt[$test['message']];
1645
1646
					// One per row!
1647
					elseif (isset($test['messages']))
1648
					{
1649
						while ($row = $smcFunc['db_fetch_assoc']($request))
1650
						{
1651
							$variables = $test['messages'];
1652
							foreach ($variables as $k => $v)
1653
							{
1654
								if ($k == 0 && isset($txt[$v]))
1655
									$variables[$k] = $txt[$v];
1656
								elseif ($k > 0 && isset($row[$v]))
1657
									$variables[$k] = $row[$v];
1658
							}
1659
							$context['repair_errors'][] = call_user_func_array('sprintf', $variables);
1660
						}
1661
					}
1662
1663
					// A function to process?
1664
					elseif (isset($test['message_function']))
1665
					{
1666
						// Find out if there are actually errors.
1667
						$found_errors = false;
1668
						while ($row = $smcFunc['db_fetch_assoc']($request))
1669
							$found_errors |= $test['message_function']($row);
1670
					}
1671
1672
					// Actually have something to fix?
1673
					if ($found_errors)
1674
						$to_fix[] = $error_type;
1675
				}
1676
1677
				// We want to fix, we need to fix - so work out what exactly to do!
1678
				else
1679
				{
1680
					// Are we simply getting a collection of ids?
1681
					if (isset($test['fix_collect']))
1682
					{
1683
						$ids = array();
1684
						while ($row = $smcFunc['db_fetch_assoc']($request))
1685
							$ids[] = $row[$test['fix_collect']['index']];
1686
						if (!empty($ids))
1687
						{
1688
							// Fix it!
1689
							$test['fix_collect']['process']($ids);
1690
						}
1691
					}
1692
1693
					// Simply executing a fix it query?
1694
					elseif (isset($test['fix_it_query']))
1695
						$smcFunc['db_query']('',
1696
							$test['fix_it_query'],
1697
							array(
1698
							)
1699
						);
1700
1701
					// Do we have some processing to do?
1702
					elseif (isset($test['fix_processing']))
1703
					{
1704
						while ($row = $smcFunc['db_fetch_assoc']($request))
1705
							$test['fix_processing']($row);
1706
					}
1707
1708
					// What about the full set of processing?
1709
					elseif (isset($test['fix_full_processing']))
1710
						$test['fix_full_processing']($request);
1711
1712
					// Do we have other things we need to fix as a result?
1713
					if (!empty($test['force_fix']))
1714
					{
1715
						foreach ($test['force_fix'] as $item)
1716
							if (!in_array($item, $to_fix))
1717
								$to_fix[] = $item;
1718
					}
1719
				}
1720
			}
1721
1722
			// Free the result.
1723
			$smcFunc['db_free_result']($request);
1724
			// Keep memory down.
1725
			$db_cache = array();
1726
1727
			// Are we done yet?
1728
			if (isset($test['substeps']))
1729
			{
1730
				$_GET['substep'] += $step_size;
1731
				// Not done?
1732
				if ($_GET['substep'] <= $step_max)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $step_max does not seem to be defined for all execution paths leading up to this point.
Loading history...
1733
				{
1734
					pauseRepairProcess($to_fix, $error_type, $step_max);
1735
				}
1736
				else
1737
					$done = true;
1738
			}
1739
			else
1740
				$done = true;
1741
1742
			// Don't allow more than 1000 queries at a time.
1743
			if ($total_queries >= 1000)
1744
				pauseRepairProcess($to_fix, $error_type, $step_max, true);
1745
		}
1746
1747
		// Keep going.
1748
		$_GET['step']++;
1749
		$_GET['substep'] = 0;
1750
1751
		$to_fix = array_unique($to_fix);
1752
1753
		// If we're doing fixes and this needed a fix and we're all done then don't do it again.
1754
		if ($do_fix)
1755
		{
1756
			$key = array_search($error_type, $to_fix);
1757
			if ($key !== false && isset($to_fix[$key]))
1758
				unset($to_fix[$key]);
1759
		}
1760
1761
		// Are we done?
1762
		pauseRepairProcess($to_fix, $error_type);
1763
	}
1764
1765
	// Restore the cache.
1766
	$db_cache = $db_temp_cache;
1767
1768
	return $to_fix;
1769
}
1770
1771
/**
1772
 * Create a salvage area for repair purposes, if one doesn't already exist.
1773
 * Uses the forum's default language, and checks based on that name.
1774
 */
1775
function createSalvageArea()
1776
{
1777
	global $txt, $language, $salvageBoardID, $salvageCatID, $smcFunc;
1778
	static $createOnce = false;
1779
1780
	// Have we already created it?
1781
	if ($createOnce)
1782
		return;
1783
	else
1784
		$createOnce = true;
1785
1786
	// Back to the forum's default language.
1787
	loadLanguage('Admin', $language);
1788
1789
	// Check to see if a 'Salvage Category' exists, if not => insert one.
1790
	$result = $smcFunc['db_query']('', '
1791
		SELECT id_cat
1792
		FROM {db_prefix}categories
1793
		WHERE name = {string:cat_name}
1794
		LIMIT 1',
1795
		array(
1796
			'cat_name' => $txt['salvaged_category_name'],
1797
		)
1798
	);
1799
	if ($smcFunc['db_num_rows']($result) != 0)
1800
		list ($salvageCatID) = $smcFunc['db_fetch_row']($result);
1801
	$smcFunc['db_free_result']($result);
1802
1803
	if (empty($salvageCatID))
1804
	{
1805
		$salvageCatID = $smcFunc['db_insert']('',
1806
			'{db_prefix}categories',
1807
			array('name' => 'string-255', 'cat_order' => 'int', 'description' => 'string-255'),
1808
			array($txt['salvaged_category_name'], -1, $txt['salvaged_category_description']),
1809
			array('id_cat'),
1810
			1
1811
		);
1812
1813
		if ($smcFunc['db_affected_rows']() <= 0)
1814
		{
1815
			loadLanguage('Admin');
1816
			fatal_lang_error('salvaged_category_error', false);
1817
		}
1818
	}
1819
1820
	// Check to see if a 'Salvage Board' exists, if not => insert one.
1821
	$result = $smcFunc['db_query']('', '
1822
		SELECT id_board
1823
		FROM {db_prefix}boards
1824
		WHERE id_cat = {int:id_cat}
1825
			AND name = {string:board_name}
1826
		LIMIT 1',
1827
		array(
1828
			'id_cat' => $salvageCatID,
1829
			'board_name' => $txt['salvaged_board_name'],
1830
		)
1831
	);
1832
	if ($smcFunc['db_num_rows']($result) != 0)
1833
		list ($salvageBoardID) = $smcFunc['db_fetch_row']($result);
1834
	$smcFunc['db_free_result']($result);
1835
1836
	if (empty($salvageBoardID))
1837
	{
1838
		$salvageBoardID = $smcFunc['db_insert']('',
1839
			'{db_prefix}boards',
1840
			array('name' => 'string-255', 'description' => 'string-255', 'id_cat' => 'int', 'member_groups' => 'string', 'board_order' => 'int', 'redirect' => 'string'),
1841
			array($txt['salvaged_board_name'], $txt['salvaged_board_description'], $salvageCatID, '1', -1, ''),
1842
			array('id_board'),
1843
			1
1844
		);
1845
1846
		if ($smcFunc['db_affected_rows']() <= 0)
1847
		{
1848
			loadLanguage('Admin');
1849
			fatal_lang_error('salvaged_board_error', false);
1850
		}
1851
	}
1852
1853
	// Restore the user's language.
1854
	loadLanguage('Admin');
1855
}
1856
1857
?>