Issues (1686)

sources/subs/RepairBoards.subs.php (3 issues)

1
<?php
2
3
/**
4
 * This file contains functions for dealing with messages.
5
 * Low-level functions, i.e. database operations needed to perform.
6
 * These functions (probably) do NOT make permissions checks. (they assume
7
 * those were already made).
8
 *
9
 * @package   ElkArte Forum
10
 * @copyright ElkArte Forum contributors
11
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
12
 *
13
 * This file contains code covered by:
14
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
15
 *
16
 * @version 2.0 dev
17
 *
18
 */
19
20
use ElkArte\Debug;
21
use ElkArte\Themes\ThemeLoader;
22
use ElkArte\Languages\Loader as LangLoader;
23
24
/**
25
 * Load up all the tests we might want to do ;)
26
 *
27
 * @return mixed[]
28
 */
29
function loadForumTests()
30
{
31
	/**
32
	 * This array is defined like so:
33
	 *
34
	 * string check_query: Query to be executed when testing if errors exist.
35
	 * string check_type: Defines how it knows if a problem was found. If set to
36
	 *                    count looks for the first variable from check_query
37
	 *                    being > 0. Anything else it looks for some results.
38
	 *                    If not set assumes you want results.
39
	 * string fix_it_query: When doing fixes if an error was detected this query
40
	 *                      is executed to "fix" it.
41
	 * string fix_query: The query to execute to get data when doing a fix.
42
	 *                   If not set check_query is used again.
43
	 * array fix_collect: This array is used if the fix is basically gathering
44
	 *                    all broken ids and then doing something with it.
45
	 *                     - string index: The value returned from the main query
46
	 *                                     and passed to the processing function.
47
	 *                     - process: A function passed an array of ids to
48
	 *                                execute the fix on.
49
	 * function fix_processing: Function called for each row returned from fix_query
50
	 *                          to execute whatever fixes are required.
51
	 * function fix_full_processing: As above but does the while loop and everything
52
	 *                               itself - except the freeing.
53
	 * array force_fix: If this is set then the error types included within this
54
	 *                  array will also be assumed broken.
55
	 *                  Note: At the moment only processes these if they occur after
56
	 *                  the primary error in the array.
57
	 */
58
59
	// This great array contains all of our error checks, fixes, etc etc etc.
60
	return array(
61
		// Make a last-ditch-effort check to get rid of topics with zeros..
62
		'zero_topics' => array(
63
			'check_query' => '
64
				SELECT COUNT(*)
65
				FROM {db_prefix}topics
66
				WHERE id_topic = 0',
67
			'check_type' => 'count',
68
			'fix_it_query' => '
69
				UPDATE {db_prefix}topics
70
				SET id_topic = NULL
71
				WHERE id_topic = 0',
72
			'message' => 'repair_zero_ids',
73
		),
74
		// ... and same with messages.
75
		'zero_messages' => array(
76
			'check_query' => '
77
				SELECT COUNT(*)
78
				FROM {db_prefix}messages
79
				WHERE id_msg = 0',
80
			'check_type' => 'count',
81
			'fix_it_query' => '
82
				UPDATE {db_prefix}messages
83
				SET id_msg = NULL
84
				WHERE id_msg = 0',
85
			'message' => 'repair_zero_ids',
86
		),
87
		// Find messages that don't have existing topics.
88
		'missing_topics' => array(
89
			'substeps' => array(
90
				'step_size' => 1000,
91
				'step_max' => '
92
					SELECT MAX(id_topic)
93
					FROM {db_prefix}messages'
94
			),
95
			'check_query' => '
96
				SELECT m.id_topic, m.id_msg
97
				FROM {db_prefix}messages AS m
98
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
99
				WHERE m.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
100
					AND t.id_topic IS NULL
101
				ORDER BY m.id_topic, m.id_msg',
102
			'fix_query' => '
103
				SELECT
104
					m.id_board, m.id_topic, MIN(m.id_msg) AS myid_first_msg, MAX(m.id_msg) AS myid_last_msg,
105
					COUNT(*) - 1 AS my_num_replies
106
				FROM {db_prefix}messages AS m
107
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
108
				WHERE t.id_topic IS NULL
109
				GROUP BY m.id_topic, m.id_board',
110
			'fix_processing' => function ($row) {
111
				$db = database();
112
113
				// Only if we don't have a reasonable idea of where to put it.
114
				if ($row['id_board'] == 0)
115
				{
116
					$row['id_board'] = createSalvageBoard();
117
				}
118
119
				// Make sure that no topics claim the first/last message as theirs.
120
				$db->query('', '
121
					UPDATE {db_prefix}topics
122
					SET 
123
						id_first_msg = 0
124
					WHERE id_first_msg = {int:id_first_msg}',
125
					array(
126
						'id_first_msg' => $row['myid_first_msg'],
127
					)
128
				);
129
				$db->query('', '
130
					UPDATE {db_prefix}topics
131
					SET 
132
						id_last_msg = 0
133
					WHERE id_last_msg = {int:id_last_msg}',
134
					array(
135
						'id_last_msg' => $row['myid_last_msg'],
136
					)
137
				);
138
139
				$memberStartedID = getMsgMemberID($row['myid_first_msg']);
140
				$memberUpdatedID = getMsgMemberID($row['myid_last_msg']);
141
142
				$db->insert('',
143
					'{db_prefix}topics',
144
					array(
145
						'id_board' => 'int',
146
						'id_member_started' => 'int',
147
						'id_member_updated' => 'int',
148
						'id_first_msg' => 'int',
149
						'id_last_msg' => 'int',
150
						'num_replies' => 'int'
151
					),
152
					array(
153
						$row['id_board'],
154
						$memberStartedID,
155
						$memberUpdatedID,
156
						$row['myid_first_msg'],
157
						$row['myid_last_msg'],
158
						$row['my_num_replies']
159
					),
160
					array('id_topic')
161
				);
162
163
				$newTopicID = $db->insert_id('{db_prefix}topics');
164
165
				$db->query('', '
166
					UPDATE {db_prefix}messages
167
					SET 
168
						id_topic = {int:newTopicID}, id_board = {int:board_id}
169
					WHERE id_topic = {int:topic_id}',
170
					array(
171
						'board_id' => $row['id_board'],
172
						'topic_id' => $row['id_topic'],
173
						'newTopicID' => $newTopicID,
174
					)
175
				);
176
			},
177
			'force_fix' => array('stats_topics'),
178
			'messages' => array('repair_missing_topics', 'id_msg', 'id_topic'),
179
		),
180
		// Find topics with no messages.
181
		'missing_messages' => array(
182
			'substeps' => array(
183
				'step_size' => 1000,
184
				'step_max' => '
185
					SELECT MAX(id_topic)
186
					FROM {db_prefix}topics'
187
			),
188
			'check_query' => '
189
				SELECT 
190
					t.id_topic, COUNT(m.id_msg) AS num_msg
191
				FROM {db_prefix}topics AS t
192
					LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic)
193
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
194
				GROUP BY t.id_topic
195
				HAVING COUNT(m.id_msg) = 0',
196
			// Remove all topics that have zero messages in the messages table.
197
			'fix_collect' => array(
198
				'index' => 'id_topic',
199
				'process' => function ($topics) {
200
					$db = database();
201
202
					$db->query('', '
203
						DELETE FROM {db_prefix}topics
204
						WHERE id_topic IN ({array_int:topics})',
205
						array(
206
							'topics' => $topics,
207
						)
208
					);
209
					$db->query('', '
210
						DELETE FROM {db_prefix}log_topics
211
						WHERE id_topic IN ({array_int:topics})',
212
						array(
213
							'topics' => $topics,
214
						)
215
					);
216
				},
217
			),
218
			'messages' => array('repair_missing_messages', 'id_topic'),
219
		),
220
		'polls_missing_topics' => array(
221
			'substeps' => array(
222
				'step_size' => 500,
223
				'step_max' => '
224
					SELECT MAX(id_poll)
225
					FROM {db_prefix}polls'
226
			),
227
			'check_query' => '
228
				SELECT 
229
					p.id_poll, p.id_member, p.poster_name, t.id_board
230
				FROM {db_prefix}polls AS p
231
					LEFT JOIN {db_prefix}topics AS t ON (t.id_poll = p.id_poll)
232
				WHERE p.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
233
					AND t.id_poll IS NULL',
234
			'fix_processing' => function ($row) {
235
				global $txt;
236
237
				$db = database();
238
239
				// Only if we don't have a reasonable idea of where to put it.
240
				if ($row['id_board'] == 0)
241
				{
242
					$row['id_board'] = createSalvageBoard();
243
				}
244
245
				$row['poster_name'] = !empty($row['poster_name']) ? $row['poster_name'] : $txt['guest'];
246
247
				$db->insert('',
248
					'{db_prefix}messages',
249
					array(
250
						'id_board' => 'int',
251
						'id_topic' => 'int',
252
						'poster_time' => 'int',
253
						'id_member' => 'int',
254
						'subject' => 'string-255',
255
						'poster_name' => 'string-255',
256
						'poster_email' => 'string-255',
257
						'poster_ip' => 'string-16',
258
						'smileys_enabled' => 'int',
259
						'body' => 'string-65534',
260
						'icon' => 'string-16',
261
						'approved' => 'int',
262
					),
263
					array(
264
						$row['id_board'],
265
						0,
266
						time(),
267
						$row['id_member'],
268
						$txt['salvaged_poll_topic_name'],
269
						$row['poster_name'],
270
						'',
271
						'127.0.0.1',
272
						1,
273
						$txt['salvaged_poll_message_body'],
274
						'xx',
275
						1,
276
					),
277
					array('id_topic')
278
				);
279
280
				$newMessageID = $db->insert_id('{db_prefix}messages');
281
282
				$db->insert('',
283
					'{db_prefix}topics',
284
					array(
285
						'id_board' => 'int',
286
						'id_poll' => 'int',
287
						'id_member_started' => 'int',
288
						'id_member_updated' => 'int',
289
						'id_first_msg' => 'int',
290
						'id_last_msg' => 'int',
291
						'num_replies' => 'int',
292
					),
293
					array(
294
						$row['id_board'],
295
						$row['id_poll'],
296
						$row['id_member'],
297
						$row['id_member'],
298
						$newMessageID,
299
						$newMessageID,
300
						0,
301
					),
302
					array('id_topic')
303
				);
304
305
				$newTopicID = $db->insert_id('{db_prefix}topics');
306
307
				$db->query('', '
308
					UPDATE {db_prefix}messages
309
					SET 
310
						id_topic = {int:newTopicID}, id_board = {int:id_board}
311
					WHERE id_msg = {int:newMessageID}',
312
					array(
313
						'id_board' => $row['id_board'],
314
						'newTopicID' => $newTopicID,
315
						'newMessageID' => $newMessageID,
316
					)
317
				);
318
319
				require_once(SUBSDIR . '/Messages.subs.php');
320
				updateSubjectStats($newTopicID, $txt['salvaged_poll_topic_name']);
0 ignored issues
show
It seems like $newTopicID can also be of type boolean; however, parameter $id_topic of updateSubjectStats() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

320
				updateSubjectStats(/** @scrutinizer ignore-type */ $newTopicID, $txt['salvaged_poll_topic_name']);
Loading history...
321
			},
322
			'force_fix' => array('stats_topics'),
323
			'messages' => array('repair_polls_missing_topics', 'id_poll', 'id_topic'),
324
		),
325
		'stats_topics' => array(
326
			'substeps' => array(
327
				'step_size' => 200,
328
				'step_max' => '
329
					SELECT MAX(id_topic)
330
					FROM {db_prefix}topics'
331
			),
332
			'check_query' => '
333
				SELECT
334
					t.id_topic, t.id_first_msg, t.id_last_msg,
335
					CASE WHEN MIN(ma.id_msg) > 0 THEN
336
						CASE WHEN MIN(mu.id_msg) > 0 THEN
337
							CASE WHEN MIN(mu.id_msg) < MIN(ma.id_msg) THEN MIN(mu.id_msg) ELSE MIN(ma.id_msg) END ELSE
338
						MIN(ma.id_msg) END ELSE
339
					MIN(mu.id_msg) END AS myid_first_msg,
340
					CASE WHEN MAX(ma.id_msg) > 0 THEN MAX(ma.id_msg) ELSE MIN(mu.id_msg) END AS myid_last_msg,
341
					t.approved, mf.approved, mf.approved AS firstmsg_approved
342
				FROM {db_prefix}topics AS t
343
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1)
344
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0)
345
					LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
346
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
347
				GROUP BY t.id_topic, t.id_first_msg, t.id_last_msg, t.approved, mf.approved
348
				ORDER BY t.id_topic',
349
			'fix_processing' => function ($row) {
350
				$row['firstmsg_approved'] = (int) $row['firstmsg_approved'];
351
				$row['myid_first_msg'] = (int) $row['myid_first_msg'];
352
				$row['myid_last_msg'] = (int) $row['myid_last_msg'];
353
354
				// Not really a problem?
355
				if ($row['id_first_msg'] == $row['myid_first_msg'] && $row['id_last_msg'] == $row['myid_last_msg'] && $row['approved'] == $row['firstmsg_approved'])
356
				{
357
					return false;
358
				}
359
360
				$memberStartedID = getMsgMemberID($row['myid_first_msg']);
361
				$memberUpdatedID = getMsgMemberID($row['myid_last_msg']);
362
363
				require_once(SUBSDIR . '/Topic.subs.php');
364
				setTopicAttribute($row['id_topic'], array(
365
					'id_first_msg' => $row['myid_first_msg'],
366
					'id_member_started' => $memberStartedID,
367
					'id_last_msg' => $row['myid_last_msg'],
368
					'id_member_updated' => $memberUpdatedID,
369
					'approved' => $row['firstmsg_approved'],
370
				));
371
			},
372
			'message_function' => function ($row) {
373
				global $txt, $context;
374
375
				// A pretend error?
376
				if ($row['id_first_msg'] == $row['myid_first_msg'] && $row['id_last_msg'] == $row['myid_last_msg'] && $row['approved'] == $row['firstmsg_approved'])
377
				{
378
					return false;
379
				}
380
381
				if ($row['id_first_msg'] != $row['myid_first_msg'])
382
				{
383
					$context['repair_errors'][] = sprintf($txt['repair_stats_topics_1'], $row['id_topic'], $row['id_first_msg']);
384
				}
385
386
				if ($row['id_last_msg'] != $row['myid_last_msg'])
387
				{
388
					$context['repair_errors'][] = sprintf($txt['repair_stats_topics_2'], $row['id_topic'], $row['id_last_msg']);
389
				}
390
391
				if ($row['approved'] != $row['firstmsg_approved'])
392
				{
393
					$context['repair_errors'][] = sprintf($txt['repair_stats_topics_5'], $row['id_topic']);
394
				}
395
396
				return true;
397
			},
398
		),
399
		// Find topics with incorrect num_replies.
400
		'stats_topics2' => array(
401
			'substeps' => array(
402
				'step_size' => 300,
403
				'step_max' => '
404
					SELECT MAX(id_topic)
405
					FROM {db_prefix}topics'
406
			),
407
			'check_query' => '
408
				SELECT
409
					t.id_topic, t.num_replies, mf.approved,
410
					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
411
				FROM {db_prefix}topics AS t
412
					LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = 1)
413
					LEFT JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
414
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
415
				GROUP BY t.id_topic, t.num_replies, mf.approved
416
				ORDER BY t.id_topic',
417
			'fix_processing' => function ($row) {
418
				$row['my_num_replies'] = (int) $row['my_num_replies'];
419
420
				// Not really a problem?
421
				if ($row['my_num_replies'] == $row['num_replies'])
422
				{
423
					return false;
424
				}
425
426
				require_once(SUBSDIR . '/Topic.subs.php');
427
				setTopicAttribute($row['id_topic'], array(
428
					'num_replies' => $row['my_num_replies'],
429
				));
430
			},
431
			'message_function' => function ($row) {
432
				global $txt, $context;
433
434
				// Just joking?
435
				if ($row['my_num_replies'] == $row['num_replies'])
436
				{
437
					return false;
438
				}
439
440
				if ($row['num_replies'] != $row['my_num_replies'])
441
				{
442
					$context['repair_errors'][] = sprintf($txt['repair_stats_topics_3'], $row['id_topic'], $row['num_replies']);
443
				}
444
445
				return true;
446
			},
447
		),
448
		// Find topics with incorrect unapproved_posts.
449
		'stats_topics3' => array(
450
			'substeps' => array(
451
				'step_size' => 1000,
452
				'step_max' => '
453
					SELECT MAX(id_topic)
454
					FROM {db_prefix}topics'
455
			),
456
			'check_query' => '
457
				SELECT
458
					t.id_topic, t.unapproved_posts, COUNT(mu.id_msg) AS my_unapproved_posts
459
				FROM {db_prefix}topics AS t
460
					LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = 0)
461
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
462
				GROUP BY t.id_topic, t.unapproved_posts
463
				HAVING unapproved_posts != COUNT(mu.id_msg)
464
				ORDER BY t.id_topic',
465
			'fix_processing' => function ($row) {
466
				$row['my_unapproved_posts'] = (int) $row['my_unapproved_posts'];
467
468
				setTopicAttribute($row['id_topic'], array(
469
					'unapproved_posts' => $row['my_unapproved_posts'],
470
				));
471
			},
472
			'messages' => array('repair_stats_topics_4', 'id_topic', 'unapproved_posts'),
473
		),
474
		// Find topics with nonexistent boards.
475
		'missing_boards' => array(
476
			'substeps' => array(
477
				'step_size' => 1000,
478
				'step_max' => '
479
					SELECT MAX(id_topic)
480
					FROM {db_prefix}topics'
481
			),
482
			'check_query' => '
483
				SELECT 
484
					t.id_topic, t.id_board
485
				FROM {db_prefix}topics AS t
486
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
487
				WHERE b.id_board IS NULL
488
					AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
489
				ORDER BY t.id_board, t.id_topic',
490
			'fix_query' => '
491
				SELECT 
492
					t.id_board, COUNT(*) AS my_num_topics, COUNT(m.id_msg) AS my_num_posts
493
				FROM {db_prefix}topics AS t
494
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
495
					LEFT JOIN {db_prefix}messages AS m ON (m.id_topic = t.id_topic)
496
				WHERE b.id_board IS NULL
497
					AND t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
498
				GROUP BY t.id_board',
499
			'fix_processing' => function ($row) {
500
				global $txt;
501
502
				$db = database();
503
				$salvageCatID = createSalvageCategory();
504
505
				$row['my_num_topics'] = (int) $row['my_num_topics'];
506
				$row['my_num_posts'] = (int) $row['my_num_posts'];
507
508
				$db->insert('',
509
					'{db_prefix}boards',
510
					array('id_cat' => 'int', 'name' => 'string', 'description' => 'string', 'num_topics' => 'int', 'num_posts' => 'int', 'member_groups' => 'string'),
511
					array($salvageCatID, $txt['salvaged_board_name'], $txt['salvaged_board_description'], $row['my_num_topics'], $row['my_num_posts'], '1'),
512
					array('id_board')
513
				);
514
				$newBoardID = $db->insert_id('{db_prefix}boards');
515
516
				$db->query('', '
517
					UPDATE {db_prefix}topics
518
					SET 
519
						id_board = {int:newBoardID}
520
					WHERE id_board = {int:board_id}',
521
					array(
522
						'newBoardID' => $newBoardID,
523
						'board_id' => $row['id_board'],
524
					)
525
				);
526
				$db->query('', '
527
					UPDATE {db_prefix}messages
528
					SET 
529
						id_board = {int:newBoardID}
530
					WHERE id_board = {int:board_id}',
531
					array(
532
						'newBoardID' => $newBoardID,
533
						'board_id' => $row['id_board'],
534
					)
535
				);
536
			},
537
			'messages' => array('repair_missing_boards', 'id_topic', 'id_board'),
538
		),
539
		// Find boards with nonexistent categories.
540
		'missing_categories' => array(
541
			'check_query' => '
542
				SELECT 
543
					b.id_board, b.id_cat
544
				FROM {db_prefix}boards AS b
545
					LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
546
				WHERE c.id_cat IS NULL
547
				ORDER BY b.id_cat, b.id_board',
548
			'fix_collect' => array(
549
				'index' => 'id_cat',
550
				'process' => function ($cats) {
551
					$db = database();
552
					$salvageCatID = createSalvageCategory();
553
					$db->query('', '
554
						UPDATE {db_prefix}boards
555
						SET id_cat = {int:salvageCatID}
556
						WHERE id_cat IN ({array_int:categories})',
557
						array(
558
							'salvageCatID' => $salvageCatID,
559
							'categories' => $cats,
560
						)
561
					);
562
				},
563
			),
564
			'messages' => array('repair_missing_categories', 'id_board', 'id_cat'),
565
		),
566
		// Find messages with nonexistent members.
567
		'missing_posters' => array(
568
			'substeps' => array(
569
				'step_size' => 2000,
570
				'step_max' => '
571
					SELECT MAX(id_msg)
572
					FROM {db_prefix}messages'
573
			),
574
			'check_query' => '
575
				SELECT 
576
					m.id_msg, m.id_member
577
				FROM {db_prefix}messages AS m
578
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
579
				WHERE mem.id_member IS NULL
580
					AND m.id_member != 0
581
					AND m.id_msg BETWEEN {STEP_LOW} AND {STEP_HIGH}
582
				ORDER BY m.id_msg',
583
			// Last step-make sure all non-guest posters still exist.
584
			'fix_collect' => array(
585
				'index' => 'id_msg',
586
				'process' => function ($msgs) {
587
					$db = database();
588
589
					$db->query('', '
590
						UPDATE {db_prefix}messages
591
						SET 
592
							id_member = {int:guest_id}
593
						WHERE id_msg IN ({array_int:msgs})',
594
						array(
595
							'msgs' => $msgs,
596
							'guest_id' => 0,
597
						)
598
					);
599
				},
600
			),
601
			'messages' => array('repair_missing_posters', 'id_msg', 'id_member'),
602
		),
603
		// Find boards with nonexistent parents.
604
		'missing_parents' => array(
605
			'check_query' => '
606
				SELECT 
607
					b.id_board, b.id_parent
608
				FROM {db_prefix}boards AS b
609
					LEFT JOIN {db_prefix}boards AS p ON (p.id_board = b.id_parent)
610
				WHERE b.id_parent != 0
611
					AND (p.id_board IS NULL OR p.id_board = b.id_board)
612
				ORDER BY b.id_parent, b.id_board',
613
			'fix_collect' => array(
614
				'index' => 'id_parent',
615
				'process' => function ($parents) {
616
					$db = database();
617
					$salvageCatID = createSalvageCategory();
618
					$salvageBoardID = createSalvageBoard();
619
					$db->query('', '
620
						UPDATE {db_prefix}boards
621
						SET 
622
							id_parent = {int:salvageBoardID}, id_cat = {int:salvageCatID}, child_level = 1
623
						WHERE id_parent IN ({array_int:parents})',
624
						array(
625
							'salvageBoardID' => $salvageBoardID,
626
							'salvageCatID' => $salvageCatID,
627
							'parents' => $parents,
628
						)
629
					);
630
				},
631
			),
632
			'messages' => array('repair_missing_parents', 'id_board', 'id_parent'),
633
		),
634
		'missing_polls' => array(
635
			'substeps' => array(
636
				'step_size' => 500,
637
				'step_max' => '
638
					SELECT 
639
						MAX(id_poll)
640
					FROM {db_prefix}topics'
641
			),
642
			'check_query' => '
643
				SELECT 
644
					t.id_poll, t.id_topic
645
				FROM {db_prefix}topics AS t
646
					LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = t.id_poll)
647
				WHERE t.id_poll != 0
648
					AND t.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
649
					AND p.id_poll IS NULL',
650
			'fix_collect' => array(
651
				'index' => 'id_poll',
652
				'process' => function ($polls) {
653
					$db = database();
654
655
					$db->query('', '
656
						UPDATE {db_prefix}topics
657
						SET id_poll = 0
658
						WHERE id_poll IN ({array_int:polls})',
659
						array(
660
							'polls' => $polls,
661
						)
662
					);
663
				},
664
			),
665
			'messages' => array('repair_missing_polls', 'id_topic', 'id_poll'),
666
		),
667
		'missing_calendar_topics' => array(
668
			'substeps' => array(
669
				'step_size' => 1000,
670
				'step_max' => '
671
					SELECT 
672
						MAX(id_topic)
673
					FROM {db_prefix}calendar'
674
			),
675
			'check_query' => '
676
				SELECT 
677
					cal.id_topic, cal.id_event
678
				FROM {db_prefix}calendar AS cal
679
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic)
680
				WHERE cal.id_topic != 0
681
					AND cal.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
682
					AND t.id_topic IS NULL
683
				ORDER BY cal.id_topic',
684
			'fix_collect' => array(
685
				'index' => 'id_topic',
686
				'process' => function ($events) {
687
					$db = database();
688
689
					$db->query('', '
690
						UPDATE {db_prefix}calendar
691
						SET id_topic = 0, id_board = 0
692
						WHERE id_topic IN ({array_int:events})',
693
						array(
694
							'events' => $events,
695
						)
696
					);
697
				},
698
			),
699
			'messages' => array('repair_missing_calendar_topics', 'id_event', 'id_topic'),
700
		),
701
		'missing_log_topics' => array(
702
			'substeps' => array(
703
				'step_size' => 150,
704
				'step_max' => '
705
					SELECT MAX(id_member)
706
					FROM {db_prefix}log_topics'
707
			),
708
			'check_query' => '
709
				SELECT 
710
					lt.id_topic
711
				FROM {db_prefix}log_topics AS lt
712
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lt.id_topic)
713
				WHERE t.id_topic IS NULL
714
					AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
715
			'fix_collect' => array(
716
				'index' => 'id_topic',
717
				'process' => function ($topics) {
718
					$db = database();
719
720
					$db->query('', '
721
						DELETE FROM {db_prefix}log_topics
722
						WHERE id_topic IN ({array_int:topics})',
723
						array(
724
							'topics' => $topics,
725
						)
726
					);
727
				},
728
			),
729
			'messages' => array('repair_missing_log_topics', 'id_topic'),
730
		),
731
		'missing_log_topics_members' => array(
732
			'substeps' => array(
733
				'step_size' => 150,
734
				'step_max' => '
735
					SELECT MAX(id_member)
736
					FROM {db_prefix}log_topics'
737
			),
738
			'check_query' => '
739
				SELECT DISTINCT lt.id_member
740
				FROM {db_prefix}log_topics AS lt
741
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lt.id_member)
742
				WHERE mem.id_member IS NULL
743
					AND lt.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
744
			'fix_collect' => array(
745
				'index' => 'id_member',
746
				'process' => function ($members) {
747
					$db = database();
748
749
					$db->query('', '
750
						DELETE FROM {db_prefix}log_topics
751
						WHERE id_member IN ({array_int:members})',
752
						array(
753
							'members' => $members,
754
						)
755
					);
756
				},
757
			),
758
			'messages' => array('repair_missing_log_topics_members', 'id_member'),
759
		),
760
		'missing_log_boards' => array(
761
			'substeps' => array(
762
				'step_size' => 500,
763
				'step_max' => '
764
					SELECT MAX(id_member)
765
					FROM {db_prefix}log_boards'
766
			),
767
			'check_query' => '
768
				SELECT DISTINCT lb.id_board
769
				FROM {db_prefix}log_boards AS lb
770
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lb.id_board)
771
				WHERE b.id_board IS NULL
772
					AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
773
			'fix_collect' => array(
774
				'index' => 'id_board',
775
				'process' => function ($boards) {
776
					$db = database();
777
778
					$db->query('', '
779
						DELETE FROM {db_prefix}log_boards
780
						WHERE id_board IN ({array_int:boards})',
781
						array(
782
							'boards' => $boards,
783
						)
784
					);
785
				},
786
			),
787
			'messages' => array('repair_missing_log_boards', 'id_board'),
788
		),
789
		'missing_log_boards_members' => array(
790
			'substeps' => array(
791
				'step_size' => 500,
792
				'step_max' => '
793
					SELECT MAX(id_member)
794
					FROM {db_prefix}log_boards'
795
			),
796
			'check_query' => '
797
				SELECT DISTINCT lb.id_member
798
				FROM {db_prefix}log_boards AS lb
799
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lb.id_member)
800
				WHERE mem.id_member IS NULL
801
					AND lb.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
802
			'fix_collect' => array(
803
				'index' => 'id_member',
804
				'process' => function ($members) {
805
					$db = database();
806
807
					$db->query('', '
808
						DELETE FROM {db_prefix}log_boards
809
						WHERE id_member IN ({array_int:members})',
810
						array(
811
							'members' => $members,
812
						)
813
					);
814
				},
815
			),
816
			'messages' => array('repair_missing_log_boards_members', 'id_member'),
817
		),
818
		'missing_log_mark_read' => array(
819
			'substeps' => array(
820
				'step_size' => 500,
821
				'step_max' => '
822
					SELECT MAX(id_member)
823
					FROM {db_prefix}log_mark_read'
824
			),
825
			'check_query' => '
826
				SELECT DISTINCT lmr.id_board
827
				FROM {db_prefix}log_mark_read AS lmr
828
					LEFT JOIN {db_prefix}boards AS b ON (b.id_board = lmr.id_board)
829
				WHERE b.id_board IS NULL
830
					AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
831
			'fix_collect' => array(
832
				'index' => 'id_board',
833
				'process' => function ($boards) {
834
					$db = database();
835
836
					$db->query('', '
837
						DELETE FROM {db_prefix}log_mark_read
838
						WHERE id_board IN ({array_int:boards})',
839
						array(
840
							'boards' => $boards,
841
						)
842
					);
843
				},
844
			),
845
			'messages' => array('repair_missing_log_mark_read', 'id_board'),
846
		),
847
		'missing_log_mark_read_members' => array(
848
			'substeps' => array(
849
				'step_size' => 500,
850
				'step_max' => '
851
					SELECT MAX(id_member)
852
					FROM {db_prefix}log_mark_read'
853
			),
854
			'check_query' => '
855
				SELECT DISTINCT lmr.id_member
856
				FROM {db_prefix}log_mark_read AS lmr
857
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lmr.id_member)
858
				WHERE mem.id_member IS NULL
859
					AND lmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}',
860
			'fix_collect' => array(
861
				'index' => 'id_member',
862
				'process' => function ($members) {
863
					$db = database();
864
865
					$db->query('', '
866
						DELETE FROM {db_prefix}log_mark_read
867
						WHERE id_member IN ({array_int:members})',
868
						array(
869
							'members' => $members,
870
						)
871
					);
872
				},
873
			),
874
			'messages' => array('repair_missing_log_mark_read_members', 'id_member'),
875
		),
876
		'missing_pms' => array(
877
			'substeps' => array(
878
				'step_size' => 500,
879
				'step_max' => '
880
					SELECT MAX(id_pm)
881
					FROM {db_prefix}pm_recipients'
882
			),
883
			'check_query' => '
884
				SELECT 
885
					pmr.id_pm
886
				FROM {db_prefix}pm_recipients AS pmr
887
					LEFT JOIN {db_prefix}personal_messages AS pm ON (pm.id_pm = pmr.id_pm)
888
				WHERE pm.id_pm IS NULL
889
					AND pmr.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH}
890
				GROUP BY pmr.id_pm',
891
			'fix_collect' => array(
892
				'index' => 'id_pm',
893
				'process' => function ($pms) {
894
					$db = database();
895
896
					$db->query('', '
897
						DELETE FROM {db_prefix}pm_recipients
898
						WHERE id_pm IN ({array_int:pms})',
899
						array(
900
							'pms' => $pms,
901
						)
902
					);
903
				},
904
			),
905
			'messages' => array('repair_missing_pms', 'id_pm'),
906
		),
907
		'missing_recipients' => array(
908
			'substeps' => array(
909
				'step_size' => 500,
910
				'step_max' => '
911
					SELECT MAX(id_member)
912
					FROM {db_prefix}pm_recipients'
913
			),
914
			'check_query' => '
915
				SELECT DISTINCT pmr.id_member
916
				FROM {db_prefix}pm_recipients AS pmr
917
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pmr.id_member)
918
				WHERE pmr.id_member != 0
919
					AND pmr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
920
					AND mem.id_member IS NULL',
921
			'fix_collect' => array(
922
				'index' => 'id_member',
923
				'process' => function ($members) {
924
					$db = database();
925
926
					$db->query('', '
927
						DELETE FROM {db_prefix}pm_recipients
928
						WHERE id_member IN ({array_int:members})',
929
						array(
930
							'members' => $members,
931
						)
932
					);
933
				},
934
			),
935
			'messages' => array('repair_missing_recipients', 'id_member'),
936
		),
937
		'missing_senders' => array(
938
			'substeps' => array(
939
				'step_size' => 500,
940
				'step_max' => '
941
					SELECT MAX(id_pm)
942
					FROM {db_prefix}personal_messages'
943
			),
944
			'check_query' => '
945
				SELECT 
946
					pm.id_pm, pm.id_member_from
947
				FROM {db_prefix}personal_messages AS pm
948
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = pm.id_member_from)
949
				WHERE pm.id_member_from != 0
950
					AND pm.id_pm BETWEEN {STEP_LOW} AND {STEP_HIGH}
951
					AND mem.id_member IS NULL',
952
			'fix_collect' => array(
953
				'index' => 'id_pm',
954
				'process' => function ($guestMessages) {
955
					$db = database();
956
957
					$db->query('', '
958
						UPDATE {db_prefix}personal_messages
959
						SET id_member_from = 0
960
						WHERE id_pm IN ({array_int:guestMessages})',
961
						array(
962
							'guestMessages' => $guestMessages,
963
						));
964
				},
965
			),
966
			'messages' => array('repair_missing_senders', 'id_pm', 'id_member_from'),
967
		),
968
		'missing_notify_members' => array(
969
			'substeps' => array(
970
				'step_size' => 500,
971
				'step_max' => '
972
					SELECT MAX(id_member)
973
					FROM {db_prefix}log_notify'
974
			),
975
			'check_query' => '
976
				SELECT DISTINCT ln.id_member
977
				FROM {db_prefix}log_notify AS ln
978
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ln.id_member)
979
				WHERE ln.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
980
					AND mem.id_member IS NULL',
981
			'fix_collect' => array(
982
				'index' => 'id_member',
983
				'process' => function ($members) {
984
					$db = database();
985
986
					$db->query('', '
987
						DELETE FROM {db_prefix}log_notify
988
						WHERE id_member IN ({array_int:members})',
989
						array(
990
							'members' => $members,
991
						)
992
					);
993
				},
994
			),
995
			'messages' => array('repair_missing_notify_members', 'id_member'),
996
		),
997
		'missing_cached_subject' => array(
998
			'substeps' => array(
999
				'step_size' => 100,
1000
				'step_max' => '
1001
					SELECT MAX(id_topic)
1002
					FROM {db_prefix}topics'
1003
			),
1004
			'check_query' => '
1005
				SELECT 
1006
					t.id_topic, fm.subject
1007
				FROM {db_prefix}topics AS t
1008
					INNER JOIN {db_prefix}messages AS fm ON (fm.id_msg = t.id_first_msg)
1009
					LEFT JOIN {db_prefix}log_search_subjects AS lss ON (lss.id_topic = t.id_topic)
1010
				WHERE t.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
1011
					AND lss.id_topic IS NULL',
1012
			'fix_full_processing' => function ($result) {
1013
1014
				$db = database();
1015
1016
				$inserts = array();
1017
				while (($row = $result->fetch_assoc()))
1018
				{
1019
					foreach (text2words($row['subject']) as $word)
1020
					{
1021
						$inserts[] = array($word, $row['id_topic']);
1022
					}
1023
					if (count($inserts) > 500)
1024
					{
1025
						$db->insert('ignore',
1026
							'{db_prefix}log_search_subjects',
1027
							array('word' => 'string', 'id_topic' => 'int'),
1028
							$inserts,
1029
							array('word', 'id_topic')
1030
						);
1031
						$inserts = array();
1032
					}
1033
1034
				}
1035
1036
				if (!empty($inserts))
1037
				{
1038
					$db->insert('ignore',
1039
						'{db_prefix}log_search_subjects',
1040
						array('word' => 'string', 'id_topic' => 'int'),
1041
						$inserts,
1042
						array('word', 'id_topic')
1043
					);
1044
				}
1045
			},
1046
			'message_function' => function ($row) {
1047
				global $txt, $context;
1048
1049
				if (count(text2words($row['subject'])) != 0)
1050
				{
1051
					$context['repair_errors'][] = sprintf($txt['repair_missing_cached_subject'], $row['id_topic']);
1052
1053
					return true;
1054
				}
1055
1056
				return false;
1057
			},
1058
		),
1059
		'missing_topic_for_cache' => array(
1060
			'substeps' => array(
1061
				'step_size' => 50,
1062
				'step_max' => '
1063
					SELECT MAX(id_topic)
1064
					FROM {db_prefix}log_search_subjects'
1065
			),
1066
			'check_query' => '
1067
				SELECT 
1068
					lss.id_topic, lss.word
1069
				FROM {db_prefix}log_search_subjects AS lss
1070
					LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = lss.id_topic)
1071
				WHERE lss.id_topic BETWEEN {STEP_LOW} AND {STEP_HIGH}
1072
					AND t.id_topic IS NULL',
1073
			'fix_collect' => array(
1074
				'index' => 'id_topic',
1075
				'process' => function ($deleteTopics) {
1076
					$db = database();
1077
1078
					$db->query('', '
1079
						DELETE FROM {db_prefix}log_search_subjects
1080
						WHERE id_topic IN ({array_int:deleteTopics})',
1081
						array(
1082
							'deleteTopics' => $deleteTopics,
1083
						)
1084
					);
1085
				},
1086
			),
1087
			'messages' => array('repair_missing_topic_for_cache', 'word'),
1088
		),
1089
		'missing_member_vote' => array(
1090
			'substeps' => array(
1091
				'step_size' => 500,
1092
				'step_max' => '
1093
					SELECT MAX(id_member)
1094
					FROM {db_prefix}log_polls'
1095
			),
1096
			'check_query' => '
1097
				SELECT 
1098
					lp.id_poll, lp.id_member
1099
				FROM {db_prefix}log_polls AS lp
1100
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lp.id_member)
1101
				WHERE lp.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1102
					AND lp.id_member > 0
1103
					AND mem.id_member IS NULL',
1104
			'fix_collect' => array(
1105
				'index' => 'id_member',
1106
				'process' => function ($members) {
1107
					$db = database();
1108
1109
					$db->query('', '
1110
						DELETE FROM {db_prefix}log_polls
1111
						WHERE id_member IN ({array_int:members})',
1112
						array(
1113
							'members' => $members,
1114
						)
1115
					);
1116
				},
1117
			),
1118
			'messages' => array('repair_missing_log_poll_member', 'id_poll', 'id_member'),
1119
		),
1120
		'missing_log_poll_vote' => array(
1121
			'substeps' => array(
1122
				'step_size' => 500,
1123
				'step_max' => '
1124
					SELECT MAX(id_poll)
1125
					FROM {db_prefix}log_polls'
1126
			),
1127
			'check_query' => '
1128
				SELECT 
1129
					lp.id_poll, lp.id_member
1130
				FROM {db_prefix}log_polls AS lp
1131
					LEFT JOIN {db_prefix}polls AS p ON (p.id_poll = lp.id_poll)
1132
				WHERE lp.id_poll BETWEEN {STEP_LOW} AND {STEP_HIGH}
1133
					AND p.id_poll IS NULL',
1134
			'fix_collect' => array(
1135
				'index' => 'id_poll',
1136
				'process' => function ($polls) {
1137
					$db = database();
1138
1139
					$db->query('', '
1140
						DELETE FROM {db_prefix}log_polls
1141
						WHERE id_poll IN ({array_int:polls})',
1142
						array(
1143
							'polls' => $polls,
1144
						)
1145
					);
1146
				},
1147
			),
1148
			'messages' => array('repair_missing_log_poll_vote', 'id_member', 'id_poll'),
1149
		),
1150
		'report_missing_comments' => array(
1151
			'substeps' => array(
1152
				'step_size' => 500,
1153
				'step_max' => '
1154
					SELECT MAX(id_report)
1155
					FROM {db_prefix}log_reported'
1156
			),
1157
			'check_query' => '
1158
				SELECT 
1159
					lr.id_report, lr.subject
1160
				FROM {db_prefix}log_reported AS lr
1161
					LEFT JOIN {db_prefix}log_reported_comments AS lrc ON (lrc.id_report = lr.id_report)
1162
				WHERE lr.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH}
1163
					AND lrc.id_report IS NULL',
1164
			'fix_collect' => array(
1165
				'index' => 'id_report',
1166
				'process' => function ($reports) {
1167
					$db = database();
1168
1169
					$db->query('', '
1170
						DELETE FROM {db_prefix}log_reported
1171
						WHERE id_report IN ({array_int:reports})',
1172
						array(
1173
							'reports' => $reports,
1174
						)
1175
					);
1176
				},
1177
			),
1178
			'messages' => array('repair_report_missing_comments', 'id_report', 'subject'),
1179
		),
1180
		'comments_missing_report' => array(
1181
			'substeps' => array(
1182
				'step_size' => 200,
1183
				'step_max' => '
1184
					SELECT MAX(id_report)
1185
					FROM {db_prefix}log_reported_comments'
1186
			),
1187
			'check_query' => '
1188
				SELECT 
1189
					lrc.id_report, lrc.membername
1190
				FROM {db_prefix}log_reported_comments AS lrc
1191
					LEFT JOIN {db_prefix}log_reported AS lr ON (lr.id_report = lrc.id_report)
1192
				WHERE lrc.id_report BETWEEN {STEP_LOW} AND {STEP_HIGH}
1193
					AND lr.id_report IS NULL',
1194
			'fix_collect' => array(
1195
				'index' => 'id_report',
1196
				'process' => function ($reports) {
1197
					$db = database();
1198
1199
					$db->query('', '
1200
						DELETE FROM {db_prefix}log_reported_comments
1201
						WHERE id_report IN ({array_int:reports})',
1202
						array(
1203
							'reports' => $reports,
1204
						)
1205
					);
1206
				},
1207
			),
1208
			'messages' => array('repair_comments_missing_report', 'id_report', 'membername'),
1209
		),
1210
		'group_request_missing_member' => array(
1211
			'substeps' => array(
1212
				'step_size' => 200,
1213
				'step_max' => '
1214
					SELECT MAX(id_member)
1215
					FROM {db_prefix}log_group_requests'
1216
			),
1217
			'check_query' => '
1218
				SELECT DISTINCT lgr.id_member
1219
				FROM {db_prefix}log_group_requests AS lgr
1220
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
1221
				WHERE lgr.id_member BETWEEN {STEP_LOW} AND {STEP_HIGH}
1222
					AND mem.id_member IS NULL',
1223
			'fix_collect' => array(
1224
				'index' => 'id_member',
1225
				'process' => function ($members) {
1226
					$db = database();
1227
1228
					$db->query('', '
1229
						DELETE FROM {db_prefix}log_group_requests
1230
						WHERE id_member IN ({array_int:members})',
1231
						array(
1232
							'members' => $members,
1233
						)
1234
					);
1235
				},
1236
			),
1237
			'messages' => array('repair_group_request_missing_member', 'id_member'),
1238
		),
1239
		'group_request_missing_group' => array(
1240
			'substeps' => array(
1241
				'step_size' => 200,
1242
				'step_max' => '
1243
					SELECT MAX(id_group)
1244
					FROM {db_prefix}log_group_requests'
1245
			),
1246
			'check_query' => '
1247
				SELECT DISTINCT lgr.id_group
1248
				FROM {db_prefix}log_group_requests AS lgr
1249
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
1250
				WHERE lgr.id_group BETWEEN {STEP_LOW} AND {STEP_HIGH}
1251
					AND mg.id_group IS NULL',
1252
			'fix_collect' => array(
1253
				'index' => 'id_group',
1254
				'process' => function ($groups) {
1255
					$db = database();
1256
1257
					$db->query('', '
1258
						DELETE FROM {db_prefix}log_group_requests
1259
						WHERE id_group IN ({array_int:groups})',
1260
						array(
1261
							'groups' => $groups,
1262
						)
1263
					);
1264
				},
1265
			),
1266
			'messages' => array('repair_group_request_missing_group', 'id_group'),
1267
		),
1268
	);
1269
}
1270
1271
/**
1272
 * Create a salvage area for repair purposes, if one doesn't already exist.
1273
 * Uses the forum's default language, and checks based on that name.
1274
 *
1275
 * @throws \ElkArte\Exceptions\Exception salvaged_board_error
1276
 */
1277
function createSalvageBoard()
1278
{
1279
	global $language;
1280
	static $salvageBoardID = null;
1281
1282
	if ($salvageBoardID !== null)
1283
	{
1284
		return $salvageBoardID;
1285
	}
1286
1287
	$db = database();
1288
1289
	// Back to the forum's default language.
1290
	$mtxt = [];
1291
	$lang_loader = new LangLoader($language, $mtxt, database());
1292
	$lang_loader->load('Admin');
1293
	$salvageCatID = createSalvageCategory();
1294
1295
	// Check to see if a 'Salvage Board' exists, if not => insert one.
1296
	$result = $db->query('', '
1297
		SELECT 
1298
			id_board
1299
		FROM {db_prefix}boards
1300
		WHERE id_cat = {int:id_cat}
1301
			AND name = {string:board_name}
1302
		LIMIT 1',
1303
		array(
1304
			'id_cat' => $salvageCatID,
1305
			'board_name' => $mtxt['salvaged_board_name'],
1306
		)
1307
	);
1308
	if ($result->num_rows() != 0)
1309
	{
1310
		list ($salvageBoardID) = $result->fetch_row();
1311
	}
1312
	$result->free_result();
1313
1314
	if (empty($salvageBoardID))
1315
	{
1316
		$result = $db->insert('',
1317
			'{db_prefix}boards',
1318
			array('name' => 'string-255', 'description' => 'string-255', 'id_cat' => 'int', 'member_groups' => 'string', 'board_order' => 'int', 'redirect' => 'string'),
1319
			array($mtxt['salvaged_board_name'], $mtxt['salvaged_board_description'], $salvageCatID, '1', -1, ''),
1320
			array('id_board')
1321
		);
1322
1323
		if ($result->affected_rows() <= 0)
1324
		{
1325
			throw new \ElkArte\Exceptions\Exception('Admin.salvaged_board_error', false);
1326
		}
1327
1328
		$salvageBoardID = $result->insert_id();
1329
	}
1330
1331
	// Restore the user's language.
1332
	return (int) $salvageBoardID;
1333
}
1334
1335
/**
1336
 * Create a salvage area for repair purposes, if one doesn't already exist.
1337
 * Uses the forum's default language, and checks based on that name.
1338
 *
1339
 * @throws \ElkArte\Exceptions\Exception salvaged_category_error
1340
 */
1341
function createSalvageCategory()
1342
{
1343
	global $language;
1344
	static $salvageCatID = null;
1345
1346
	if ($salvageCatID !== null)
1347
	{
1348
		return $salvageCatID;
1349
	}
1350
1351
	$db = database();
1352
1353
	// Back to the forum's default language.
1354
	$mtxt = [];
1355
	$lang_loader = new LangLoader($language, $mtxt, database());
1356
	$lang_loader->load('Admin');
1357
1358
	// Check to see if a 'Salvage Category' exists, if not => insert one.
1359
	$result = $db->query('', '
1360
		SELECT 
1361
			id_cat
1362
		FROM {db_prefix}categories
1363
		WHERE name = {string:cat_name}
1364
		LIMIT 1',
1365
		array(
1366
			'cat_name' => $mtxt['salvaged_category_name'],
1367
		)
1368
	);
1369
	if ($result->num_rows() != 0)
1370
	{
1371
		list ($salvageCatID) = $result->fetch_row();
1372
	}
1373
	$result->free_result();
1374
1375
	if (empty($salvageCatID))
1376
	{
1377
		$result = $db->insert('',
1378
			'{db_prefix}categories',
1379
			array('name' => 'string-255', 'cat_order' => 'int'),
1380
			array($mtxt['salvaged_category_name'], -1),
1381
			array('id_cat')
1382
		);
1383
1384
		if ($result->affected_rows() <= 0)
1385
		{
1386
			throw new \ElkArte\Exceptions\Exception('Admin.salvaged_category_error', false);
1387
		}
1388
1389
		$salvageCatID = $result->insert_id();
1390
	}
1391
1392
	// Restore the user's language.
1393
	$salvageCatID = (int) $salvageCatID;
1394
	$_SESSION['redirect_to_recount'] = true;
1395
1396
	return $salvageCatID;
1397
}
1398
1399
/**
1400
 * Show the not_done template to avoid CGI timeouts and similar.
1401
 * Called when 3 or more seconds have passed while searching for errors.
1402
 * If max_substep is set, $_GET['substep'] / $max_substep is the percent
1403
 * done this step is.
1404
 *
1405
 * @param mixed[] $to_fix
1406
 * @param string $current_step_description
1407
 * @param int $max_substep = none
1408
 * @param bool $force = false
1409
 */
1410
function pauseRepairProcess($to_fix, $current_step_description, $max_substep = 0, $force = false)
1411
{
1412
	global $context, $txt, $time_start, $db_show_debug;
1413
1414
	// More time, I need more time!
1415
	detectServer()->setTimeLimit(600);
1416
1417
	// Errr, wait.  How much time has this taken already?
1418
	if (!$force && microtime(true) - $time_start < 3000)
1419
	{
1420
		return;
1421
	}
1422
1423
	// Restore the query cache if interested.
1424
	if ($db_show_debug === true)
1425
	{
1426
		Debug::instance()->on();
1427
	}
1428
1429
	$context['continue_get_data'] = '?action=admin;area=repairboards' . (isset($_GET['fixErrors']) ? ';fixErrors' : '') . ';step=' . $_GET['step'] . ';substep=' . $_GET['substep'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1430
	$context['page_title'] = $txt['not_done_title'];
1431
	$context['continue_post_data'] = '';
1432
	$context['continue_countdown'] = '2';
1433
	$context['sub_template'] = 'not_done';
1434
1435
	// Change these two if more steps are added!
1436
	if (empty($max_substep))
1437
	{
1438
		$context['continue_percent'] = round(($_GET['step'] * 100) / $context['total_steps']);
1439
	}
1440
	else
1441
	{
1442
		$context['continue_percent'] = round((($_GET['step'] + ($_GET['substep'] / $max_substep)) * 100) / $context['total_steps']);
1443
	}
1444
1445
	// Never more than 100%!
1446
	$context['continue_percent'] = min($context['continue_percent'], 100);
1447
1448
	// What about substeps?
1449
	$context['substep_enabled'] = $max_substep != 0;
1450
	$context['substep_title'] = sprintf($txt['repair_currently_' . (isset($_GET['fixErrors']) ? 'fixing' : 'checking')], ($txt['repair_operation_' . $current_step_description] ?? $current_step_description));
1451
	$context['substep_continue_percent'] = $max_substep == 0 ? 0 : round(($_GET['substep'] * 100) / $max_substep, 1);
1452
1453
	$_SESSION['repairboards_to_fix'] = $to_fix;
1454
	$_SESSION['repairboards_to_fix2'] = $context['repair_errors'];
1455
1456
	obExit();
1457
}
1458
1459
/**
1460
 * Checks for errors in steps, until 5 seconds have passed.
1461
 *
1462
 * - It keeps track of the errors it did find, so that the actual repair
1463
 * won't have to recheck everything.
1464
 * - returns the errors found.
1465
 *
1466
 * @param bool $do_fix
1467
 * @return mixed[]
1468
 */
1469
function findForumErrors($do_fix = false)
1470
{
1471
	global $context, $txt, $db_show_debug;
1472
1473
	$db = database();
1474
1475
	// This may take some time...
1476
	detectServer()->setTimeLimit(600);
1477
1478
	$to_fix = !empty($_SESSION['repairboards_to_fix']) ? $_SESSION['repairboards_to_fix'] : array();
1479
	$context['repair_errors'] = $_SESSION['repairboards_to_fix2'] ?? array();
1480
1481
	$_GET['step'] = empty($_GET['step']) ? 0 : (int) $_GET['step'];
1482
	$_GET['substep'] = empty($_GET['substep']) ? 0 : (int) $_GET['substep'];
1483
1484
	// Don't allow the cache to get too full.
1485
	if ($db_show_debug === true)
1486
	{
1487
		Debug::instance()->off();
1488
	}
1489
1490
	// Will want this.
1491
	$errorTests = loadForumTests();
1492
	$context['total_steps'] = count($errorTests);
1493
1494
	// For all the defined error types do the necessary tests.
1495
	$current_step = -1;
1496
	$total_queries = 0;
1497
1498
	foreach ($errorTests as $error_type => $test)
1499
	{
1500
		$current_step++;
1501
1502
		// Already done this?
1503
		if ($_GET['step'] > $current_step)
1504
		{
1505
			continue;
1506
		}
1507
1508
		// If we're fixing it but it ain't broke why try?
1509
		if ($do_fix && !in_array($error_type, $to_fix))
1510
		{
1511
			$_GET['step']++;
1512
			continue;
1513
		}
1514
1515
		// Has it got substeps?
1516
		if (isset($test['substeps']))
1517
		{
1518
			$step_size = $test['substeps']['step_size'] ?? 100;
1519
			$request = $db->query('',
1520
				$test['substeps']['step_max'],
1521
				array()
1522
			);
1523
			list ($step_max) = $request->fetch_row();
1524
1525
			$total_queries++;
1526
			$request->free_result();
1527
		}
1528
1529
		// We in theory keep doing this... the substeps.
1530
		$done = false;
1531
		while (!$done)
1532
		{
1533
			// Make sure there's at least one ID to test.
1534
			if (isset($test['substeps']) && empty($step_max))
1535
			{
1536
				break;
1537
			}
1538
1539
			// What is the testing query (Changes if we are testing or fixing)
1540
			if (!$do_fix)
1541
			{
1542
				$test_query = 'check_query';
1543
			}
1544
			else
1545
			{
1546
				$test_query = isset($test['fix_query']) ? 'fix_query' : 'check_query';
1547
			}
1548
1549
			// Do the test...
1550
			$request = $db->query('',
1551
				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...
1552
				array()
1553
			);
1554
1555
			// Does it need a fix?
1556
			if (!empty($test['check_type']) && $test['check_type'] == 'count')
1557
			{
1558
				list ($needs_fix) = $request->fetch_row();
1559
			}
1560
			else
1561
			{
1562
				$needs_fix = $request->num_rows();
1563
			}
1564
1565
			$total_queries++;
1566
1567
			if ($needs_fix)
1568
			{
1569
				// What about a message to the user?
1570
				if (!$do_fix)
1571
				{
1572
					// Assume need to fix.
1573
					$found_errors = true;
1574
1575
					if (isset($test['message']))
1576
					{
1577
						$context['repair_errors'][] = $txt[$test['message']];
1578
					}
1579
1580
					// One per row!
1581
					elseif (isset($test['messages']))
1582
					{
1583
						while (($row = $request->fetch_assoc()))
1584
						{
1585
							$variables = $test['messages'];
1586
							foreach ($variables as $k => $v)
1587
							{
1588
								if ($k == 0 && isset($txt[$v]))
1589
								{
1590
									$variables[$k] = $txt[$v];
1591
								}
1592
								elseif ($k > 0 && isset($row[$v]))
1593
								{
1594
									$variables[$k] = $row[$v];
1595
								}
1596
							}
1597
							$context['repair_errors'][] = call_user_func_array('sprintf', $variables);
1598
						}
1599
					}
1600
1601
					// A function to process?
1602
					elseif (isset($test['message_function']))
1603
					{
1604
						// Find out if there are actually errors.
1605
						$found_errors = false;
1606
						while (($row = $request->fetch_assoc()))
1607
						{
1608
							$found_errors |= $test['message_function']($row);
1609
						}
1610
					}
1611
1612
					// Actually have something to fix?
1613
					if ($found_errors)
1614
					{
1615
						$to_fix[] = $error_type;
1616
					}
1617
				}
1618
1619
				// We want to fix, we need to fix - so work out what exactly to do!
1620
				else
1621
				{
1622
					// Are we simply getting a collection of ids?
1623
					if (isset($test['fix_collect']))
1624
					{
1625
						$ids = array();
1626
						while (($row = $request->fetch_assoc()))
1627
						{
1628
							$ids[] = $row[$test['fix_collect']['index']];
1629
						}
1630
						if (!empty($ids))
1631
						{
1632
							// Fix it!
1633
							$test['fix_collect']['process']($ids);
1634
						}
1635
					}
1636
1637
					// Simply executing a fix it query?
1638
					elseif (isset($test['fix_it_query']))
1639
					{
1640
						$db->query('',
1641
							$test['fix_it_query'],
1642
							array()
1643
						);
1644
					}
1645
1646
					// Do we have some processing to do?
1647
					elseif (isset($test['fix_processing']))
1648
					{
1649
						while (($row = $request->fetch_assoc()))
1650
						{
1651
							$test['fix_processing']($row);
1652
						}
1653
					}
1654
1655
					// What about the full set of processing?
1656
					elseif (isset($test['fix_full_processing']))
1657
					{
1658
						$test['fix_full_processing']($request);
1659
					}
1660
1661
					// Do we have other things we need to fix as a result?
1662
					if (!empty($test['force_fix']))
1663
					{
1664
						foreach ($test['force_fix'] as $item)
1665
						{
1666
							if (!in_array($item, $to_fix))
1667
							{
1668
								$to_fix[] = $item;
1669
							}
1670
						}
1671
					}
1672
				}
1673
			}
1674
1675
			// Free the result.
1676
			$request->free_result();
1677
1678
			// Are we done yet?
1679
			if (isset($test['substeps']))
1680
			{
1681
				$_GET['substep'] += $step_size;
1682
				// Not done?
1683
				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...
1684
				{
1685
					pauseRepairProcess($to_fix, $error_type, $step_max);
1686
				}
1687
				else
1688
				{
1689
					$done = true;
1690
				}
1691
			}
1692
			else
1693
			{
1694
				$done = true;
1695
			}
1696
1697
			// Don't allow more than 1000 queries at a time.
1698
			if ($total_queries >= 1000)
1699
			{
1700
				pauseRepairProcess($to_fix, $error_type, $step_max, true);
1701
			}
1702
		}
1703
1704
		// Keep going.
1705
		$_GET['step']++;
1706
		$_GET['substep'] = 0;
1707
1708
		$to_fix = array_unique($to_fix);
1709
1710
		// If we're doing fixes and this needed a fix and we're all done then don't do it again.
1711
		if ($do_fix)
1712
		{
1713
			$key = array_search($error_type, $to_fix);
1714
			if ($key !== false && isset($to_fix[$key]))
1715
			{
1716
				unset($to_fix[$key]);
1717
			}
1718
		}
1719
1720
		// Are we done?
1721
		pauseRepairProcess($to_fix, $error_type);
1722
	}
1723
1724
	return $to_fix;
1725
}
1726