Issues (1061)

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