Issues (1686)

sources/subs/Maintenance.subs.php (2 issues)

1
<?php
2
3
/**
4
 * Forum maintenance support functions. Important stuff.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
/**
18
 * Counts the total number of messages
19
 *
20
 * @return int
21
 * @package Maintenance
22
 */
23
function countMessages()
24
{
25
	$db = database();
26
27
	$request = $db->query('', '
28
		SELECT 
29
			COUNT(*)
30
		FROM {db_prefix}messages',
31
		array()
32
	);
33
	list ($messages) = $request->fetch_row();
34
	$request->free_result();
35
36
	return $messages;
37
}
38
39
/**
40
 * Flushes all log tables
41
 *
42
 * @package Maintenance
43
 */
44
function flushLogTables()
45
{
46
	$db = database();
47
48
	$db->query('', '
49
		DELETE FROM {db_prefix}log_online');
50
51
	// Dump the banning logs.
52
	$db->query('', '
53
		DELETE FROM {db_prefix}log_banned');
54
55
	// Start id_error back at 0 and dump the error log.
56
	$db->truncate('{db_prefix}log_errors');
57
58
	// Clear out the spam log.
59
	$db->query('', '
60
		DELETE FROM {db_prefix}log_floodcontrol');
61
62
	// Clear out the karma actions.
63
	$db->query('', '
64
		DELETE FROM {db_prefix}log_karma');
65
66
	// Last but not least, the search logs!
67
	$db->truncate('{db_prefix}log_search_topics');
68
69
	$db->truncate('{db_prefix}log_search_messages');
70
71
	$db->truncate('{db_prefix}log_search_results');
72
}
73
74
/**
75
 * Gets the table columns from the messages table, just a wrapper function
76
 *
77
 * @return array
78
 * @package Maintenance
79
 */
80
function getMessageTableColumns()
81
{
82
	$table = db_table();
83
84
	return $table->list_columns('{db_prefix}messages', true);
85
}
86
87
/**
88
 * Retrieve information about the body column of the messages table
89
 * Used in action_database
90
 *
91
 * @package Maintenance
92
 */
93
function fetchBodyType()
94
{
95
	$table = db_table();
96
97
	$colData = $table->list_columns('{db_prefix}messages', true);
98
	foreach ($colData as $column)
99
	{
100
		if ($column['name'] == 'body')
101
		{
102
			$body_type = $column['type'];
103
		}
104
	}
105
106
	return $body_type;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $body_type does not seem to be defined for all execution paths leading up to this point.
Loading history...
107
}
108
109
/**
110
 * Resizes the body column from the messages table
111
 *
112
 * @param string $type
113
 * @package Maintenance
114
 */
115
function resizeMessageTableBody($type)
116
{
117
	$table = db_table();
118
	$table->change_column('{db_prefix}messages', 'body', array('type' => $type));
119
}
120
121
/**
122
 * Detects messages, which exceed the max message size
123
 *
124
 * @param int $start The item to start with (for pagination purposes)
125
 * @param int $increment
126
 *
127
 * @return array
128
 * @package Maintenance
129
 *
130
 */
131
function detectExceedingMessages($start, $increment)
132
{
133
	$db = database();
134
135
	return $db->fetchQuery('
136
		SELECT 
137
			/*!40001 SQL_NO_CACHE */ id_msg
138
		FROM {db_prefix}messages
139
		WHERE id_msg BETWEEN {int:start} AND {int:start} + {int:increment}
140
			AND LENGTH(body) > 65535',
141
		array(
142
			'start' => $start,
143
			'increment' => $increment - 1,
144
		)
145
	)->fetch_callback(
146
		function ($row) {
147
			return $row['id_msg'];
148
		}
149
	);
150
}
151
152
/**
153
 * loads messages, which exceed the length that will fit in the col field
154
 *
155
 * - Used by maintenance when convert the column "body" of the table from TEXT
156
 * to MEDIUMTEXT and vice versa.
157
 *
158
 * @param int[] $msg
159
 * @return array
160
 * @package Maintenance
161
 */
162
function getExceedingMessages($msg)
163
{
164
	global $scripturl;
165
166
	$db = database();
167
168
	return $db->fetchQuery('
169
		SELECT 
170
			id_msg, id_topic, subject
171
		FROM {db_prefix}messages
172
		WHERE id_msg IN ({array_int:messages})',
173
		array(
174
			'messages' => $msg,
175
		)
176
	)->fetch_callback(
177
		function ($row) use ($scripturl) {
178
			return '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['id_msg'] . '#msg' . $row['id_msg'] . '">' . $row['subject'] . '</a>';
179
		}
180
	);
181
}
182
183
/**
184
 * Lists all the tables from our ElkArte installation.
185
 *
186
 * - Additional tables from addons are also included.
187
 *
188
 * @return array
189
 * @package Maintenance
190
 */
191
function getElkTables()
192
{
193
	global $db_prefix;
194
195
	$db = database();
196
197
	$tables = array();
198
199
	// Only optimize the tables related to this installation, not all the tables in the db
200
	$real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
201
202
	// Get a list of tables, as well as how many there are.
203
	$temp_tables = $db->list_tables(false, $real_prefix . '%');
0 ignored issues
show
The method list_tables() does not exist on ElkArte\Database\QueryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to ElkArte\Database\QueryInterface. ( Ignorable by Annotation )

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

203
	/** @scrutinizer ignore-call */ 
204
 $temp_tables = $db->list_tables(false, $real_prefix . '%');
Loading history...
204
	foreach ($temp_tables as $table)
205
	{
206
		$tables[] = array('table_name' => $table);
207
	}
208
209
	return $tables;
210
}
211
212
/**
213
 * Gets the last topics id.
214
 *
215
 * @return int
216
 * @package Maintenance
217
 */
218
function getMaxTopicID()
219
{
220
	$db = database();
221
222
	$request = $db->query('', '
223
		SELECT 
224
			MAX(id_topic)
225
		FROM {db_prefix}topics',
226
		array()
227
	);
228
	list ($id_topic) = $request->fetch_row();
229
	$request->free_result();
230
231
	return $id_topic;
232
}
233
234
/**
235
 * Recounts all approved messages
236
 *
237
 * @param int $start The item to start with (for pagination purposes)
238
 * @param int $increment
239
 * @package Maintenance
240
 */
241
function recountApprovedMessages($start, $increment)
242
{
243
	$db = database();
244
245
	// Recount approved messages
246
	$db->fetchQuery('
247
		SELECT 
248
			/*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.num_replies) AS num_replies,
249
			CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END AS real_num_replies
250
		FROM {db_prefix}topics AS t
251
			LEFT JOIN {db_prefix}messages AS ma ON (ma.id_topic = t.id_topic AND ma.approved = {int:is_approved})
252
		WHERE t.id_topic > {int:start}
253
			AND t.id_topic <= {int:max_id}
254
		GROUP BY t.id_topic
255
		HAVING CASE WHEN COUNT(ma.id_msg) >= 1 THEN COUNT(ma.id_msg) - 1 ELSE 0 END != MAX(t.num_replies)',
256
		array(
257
			'is_approved' => 1,
258
			'start' => $start,
259
			'max_id' => $start + $increment,
260
		)
261
	)->fetch_callback(
262
		function ($row) {
263
			setTopicAttribute($row['id_topic'], array('num_replies' => $row['real_num_replies']));
264
		}
265
	);
266
}
267
268
/**
269
 * Recounts all unapproved messages
270
 *
271
 * @param int $start The item to start with (for pagination purposes)
272
 * @param int $increment
273
 * @package Maintenance
274
 */
275
function recountUnapprovedMessages($start, $increment)
276
{
277
	$db = database();
278
279
	// Recount unapproved messages
280
	$db->fetchQuery('
281
		SELECT 
282
			/*!40001 SQL_NO_CACHE */ t.id_topic, MAX(t.unapproved_posts) AS unapproved_posts,
283
			COUNT(mu.id_msg) AS real_unapproved_posts
284
		FROM {db_prefix}topics AS t
285
			LEFT JOIN {db_prefix}messages AS mu ON (mu.id_topic = t.id_topic AND mu.approved = {int:not_approved})
286
		WHERE t.id_topic > {int:start}
287
			AND t.id_topic <= {int:max_id}
288
		GROUP BY t.id_topic
289
		HAVING COUNT(mu.id_msg) != MAX(t.unapproved_posts)',
290
		array(
291
			'not_approved' => 0,
292
			'start' => $start,
293
			'max_id' => $start + $increment,
294
		)
295
	)->fetch_callback(
296
		function ($row) {
297
			setTopicAttribute($row['id_topic'], array('unapproved_posts' => $row['real_unapproved_posts']));
298
		}
299
	);
300
}
301
302
/**
303
 * Reset the boards table's counter for posts, topics, unapproved posts and
304
 * unapproved topics
305
 *
306
 * - Allowed parameters: num_posts, num_topics, unapproved_posts, unapproved_topics
307
 *
308
 * @param string $column
309
 *
310
 * @return bool
311
 * @package Maintenance
312
 *
313
 */
314
function resetBoardsCounter($column)
315
{
316
	$db = database();
317
318
	$allowed_columns = array('num_posts', 'num_topics', 'unapproved_posts', 'unapproved_topics');
319
320
	if (!in_array($column, $allowed_columns))
321
	{
322
		return false;
323
	}
324
325
	$db->query('', '
326
		UPDATE {db_prefix}boards
327
		SET 
328
			' . $column . ' = {int:counter}
329
		WHERE redirect = {string:redirect}',
330
		array(
331
			'counter' => 0,
332
			'redirect' => '',
333
		)
334
	);
335
}
336
337
/**
338
 * Recalculates the boards table's counter
339
 *
340
 * @param string $type - can be 'posts', 'topic', 'unapproved_posts', 'unapproved_topics'
341
 * @param int $start The item to start with (for pagination purposes)
342
 * @param int $increment
343
 * @package Maintenance
344
 */
345
function updateBoardsCounter($type, $start, $increment)
346
{
347
	$db = database();
348
349
	switch ($type)
350
	{
351
		case 'posts':
352
			$db->fetchQuery('
353
				SELECT 
354
					/*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_num_posts
355
				FROM {db_prefix}messages AS m
356
				WHERE m.id_topic > {int:id_topic_min}
357
					AND m.id_topic <= {int:id_topic_max}
358
					AND m.approved = {int:is_approved}
359
				GROUP BY m.id_board',
360
				array(
361
					'id_topic_min' => $start,
362
					'id_topic_max' => $start + $increment,
363
					'is_approved' => 1,
364
				)
365
			)->fetch_callback(
366
				function ($row) use ($db) {
367
					$db->query('', '
368
					UPDATE {db_prefix}boards
369
					SET 
370
						num_posts = num_posts + {int:real_num_posts}
371
					WHERE id_board = {int:id_board}',
372
						array(
373
							'id_board' => $row['id_board'],
374
							'real_num_posts' => $row['real_num_posts'],
375
						)
376
					);
377
				}
378
			);
379
			break;
380
381
		case 'topics':
382
			$db->fetchQuery('
383
				SELECT 
384
					/*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_num_topics
385
				FROM {db_prefix}topics AS t
386
				WHERE t.approved = {int:is_approved}
387
					AND t.id_topic > {int:id_topic_min}
388
					AND t.id_topic <= {int:id_topic_max}
389
				GROUP BY t.id_board',
390
				array(
391
					'is_approved' => 1,
392
					'id_topic_min' => $start,
393
					'id_topic_max' => $start + $increment,
394
				)
395
			)->fetch_callback(
396
				function ($row) use ($db) {
397
					$db->query('', '
398
					UPDATE {db_prefix}boards
399
					SET 
400
						num_topics = num_topics + {int:real_num_topics}
401
					WHERE id_board = {int:id_board}',
402
						array(
403
							'id_board' => $row['id_board'],
404
							'real_num_topics' => $row['real_num_topics'],
405
						)
406
					);
407
				}
408
			);
409
			break;
410
411
		case 'unapproved_posts':
412
			$db->fetchQuery('
413
				SELECT 
414
					/*!40001 SQL_NO_CACHE */ m.id_board, COUNT(*) AS real_unapproved_posts
415
				FROM {db_prefix}messages AS m
416
				WHERE m.id_topic > {int:id_topic_min}
417
					AND m.id_topic <= {int:id_topic_max}
418
					AND m.approved = {int:is_approved}
419
				GROUP BY m.id_board',
420
				array(
421
					'id_topic_min' => $start,
422
					'id_topic_max' => $start + $increment,
423
					'is_approved' => 0,
424
				)
425
			)->fetch_callback(
426
				function ($row) use ($db) {
427
					$db->query('', '
428
					UPDATE {db_prefix}boards
429
					SET unapproved_posts = unapproved_posts + {int:unapproved_posts}
430
					WHERE id_board = {int:id_board}',
431
						array(
432
							'id_board' => $row['id_board'],
433
							'unapproved_posts' => $row['real_unapproved_posts'],
434
						)
435
					);
436
				}
437
			);
438
			break;
439
440
		case 'unapproved_topics':
441
			$db->fetchQuery('
442
				SELECT 
443
					/*!40001 SQL_NO_CACHE */ t.id_board, COUNT(*) AS real_unapproved_topics
444
				FROM {db_prefix}topics AS t
445
				WHERE t.approved = {int:is_approved}
446
					AND t.id_topic > {int:id_topic_min}
447
					AND t.id_topic <= {int:id_topic_max}
448
				GROUP BY t.id_board',
449
				array(
450
					'is_approved' => 0,
451
					'id_topic_min' => $start,
452
					'id_topic_max' => $start + $increment,
453
				)
454
			)->fetch_callback(
455
				function ($row) use ($db) {
456
					$db->query('', '
457
					UPDATE {db_prefix}boards
458
					SET unapproved_topics = unapproved_topics + {int:real_unapproved_topics}
459
					WHERE id_board = {int:id_board}',
460
						array(
461
							'id_board' => $row['id_board'],
462
							'real_unapproved_topics' => $row['real_unapproved_topics'],
463
						)
464
					);
465
				}
466
			);
467
			break;
468
469
		default:
470
			trigger_error('updateBoardsCounter(): Invalid counter type \'' . $type . '\'', E_USER_NOTICE);
471
	}
472
}
473
474
/**
475
 * Update the personal messages counter
476
 *
477
 * @package Maintenance
478
 */
479
function updatePersonalMessagesCounter()
480
{
481
	$db = database();
482
483
	require_once(SUBSDIR . '/Members.subs.php');
484
485
	$db->fetchQuery('
486
		SELECT 
487
			/*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
488
			MAX(mem.personal_messages) AS personal_messages
489
		FROM {db_prefix}members AS mem
490
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted})
491
		GROUP BY mem.id_member
492
		HAVING COUNT(pmr.id_pm) != MAX(mem.personal_messages)',
493
		array(
494
			'is_not_deleted' => 0,
495
		)
496
	)->fetch_callback(
497
		function ($row) {
498
			updateMemberData($row['id_member'], array('personal_messages' => $row['real_num']));
499
		}
500
	);
501
502
	$db->fetchQuery('
503
		SELECT 
504
			/*!40001 SQL_NO_CACHE */ mem.id_member, COUNT(pmr.id_pm) AS real_num,
505
			MAX(mem.unread_messages) AS unread_messages
506
		FROM {db_prefix}members AS mem
507
			LEFT JOIN {db_prefix}pm_recipients AS pmr ON (mem.id_member = pmr.id_member AND pmr.deleted = {int:is_not_deleted} AND pmr.is_read = {int:is_not_read})
508
		GROUP BY mem.id_member
509
		HAVING COUNT(pmr.id_pm) != MAX(mem.unread_messages)',
510
		array(
511
			'is_not_deleted' => 0,
512
			'is_not_read' => 0,
513
		)
514
	)->fetch_callback(
515
		function ($row) {
516
			updateMemberData($row['id_member'], array('unread_messages' => $row['real_num']));
517
		}
518
	);
519
}
520
521
/**
522
 * Fixes the column id_board from the messages table.
523
 *
524
 * @param int $start The item to start with (for pagination purposes)
525
 * @param int $increment
526
 * @package Maintenance
527
 */
528
function updateMessagesBoardID($start, $increment)
529
{
530
	$db = database();
531
532
	$boards = array();
533
	$db->fetchQuery('
534
		SELECT 
535
			/*!40001 SQL_NO_CACHE */ t.id_board, m.id_msg
536
		FROM {db_prefix}messages AS m
537
			INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic AND t.id_board != m.id_board)
538
		WHERE m.id_msg > {int:id_msg_min}
539
			AND m.id_msg <= {int:id_msg_max}',
540
		array(
541
			'id_msg_min' => $start,
542
			'id_msg_max' => $start + $increment,
543
		)
544
	)->fetch_callback(
545
		function ($row) use (&$boards) {
546
			$boards[$row['id_board']][] = $row['id_msg'];
547
		}
548
	);
549
550
	foreach ($boards as $board_id => $messages)
551
	{
552
		$db->query('', '
553
			UPDATE {db_prefix}messages
554
			SET id_board = {int:id_board}
555
			WHERE id_msg IN ({array_int:id_msg_array})',
556
			array(
557
				'id_msg_array' => $messages,
558
				'id_board' => $board_id,
559
			)
560
		);
561
	}
562
}
563
564
/**
565
 * Updates the latest message of each board.
566
 *
567
 * @package Maintenance
568
 */
569
function updateBoardsLastMessage()
570
{
571
	$db = database();
572
573
	// Update the latest message of each board.
574
	$realBoardCounts = array();
575
	$db->fetchQuery('
576
		SELECT 
577
			m.id_board, MAX(m.id_msg) AS local_last_msg
578
		FROM {db_prefix}messages AS m
579
		WHERE m.approved = {int:is_approved}
580
		GROUP BY m.id_board',
581
		array(
582
			'is_approved' => 1,
583
		)
584
	)->fetch_callback(
585
		function ($row) use (&$realBoardCounts) {
586
			$realBoardCounts[$row['id_board']] = $row['local_last_msg'];
587
		}
588
	);
589
590
	$resort_me = array();
591
	$db->fetchQuery('
592
		SELECT 
593
			/*!40001 SQL_NO_CACHE */ id_board, id_parent, id_last_msg, child_level, id_msg_updated
594
		FROM {db_prefix}boards',
595
		array()
596
	)->fetch_callback(
597
		function ($row) use (&$resort_me, $realBoardCounts) {
598
			$row['local_last_msg'] = $realBoardCounts[$row['id_board']] ?? 0;
599
			$resort_me[$row['child_level']][] = $row;
600
		}
601
	);
602
603
	krsort($resort_me);
604
605
	$lastModifiedMsg = array();
606
	foreach ($resort_me as $rows)
607
	{
608
		foreach ($rows as $row)
609
		{
610
			// The latest message is the latest of the current board and its children.
611
			if (isset($lastModifiedMsg[$row['id_board']]))
612
			{
613
				$curLastModifiedMsg = max($row['local_last_msg'], $lastModifiedMsg[$row['id_board']]);
614
			}
615
			else
616
			{
617
				$curLastModifiedMsg = $row['local_last_msg'];
618
			}
619
			// If what is and what should be the latest message differ, an update is necessary.
620
			if ($row['local_last_msg'] != $row['id_last_msg'] || $curLastModifiedMsg != $row['id_msg_updated'])
621
			{
622
				$db->query('', '
623
					UPDATE {db_prefix}boards
624
					SET id_last_msg = {int:id_last_msg}, id_msg_updated = {int:id_msg_updated}
625
					WHERE id_board = {int:id_board}',
626
					array(
627
						'id_last_msg' => $row['local_last_msg'],
628
						'id_msg_updated' => $curLastModifiedMsg,
629
						'id_board' => $row['id_board'],
630
					)
631
				);
632
			}
633
634
			// Parent boards inherit the latest modified message of their children.
635
			if (isset($lastModifiedMsg[$row['id_parent']]))
636
			{
637
				$lastModifiedMsg[$row['id_parent']] = max($row['local_last_msg'], $lastModifiedMsg[$row['id_parent']]);
638
			}
639
			else
640
			{
641
				$lastModifiedMsg[$row['id_parent']] = $row['local_last_msg'];
642
			}
643
		}
644
	}
645
}
646
647
/**
648
 * Counts topics from a given board.
649
 *
650
 * @param int $id_board
651
 * @return int
652
 * @package Maintenance
653
 */
654
function countTopicsFromBoard($id_board)
655
{
656
	$db = database();
657
658
	$request = $db->query('', '
659
		SELECT 
660
			COUNT(*)
661
		FROM {db_prefix}topics
662
		WHERE id_board = {int:id_board}',
663
		array(
664
			'id_board' => $id_board,
665
		)
666
	);
667
	list ($total_topics) = $request->fetch_row();
668
	$request->free_result();
669
670
	return $total_topics;
671
}
672
673
/**
674
 * Gets a list of the next 10 topics which should be moved to a different board.
675
 *
676
 * @param int $id_board
677
 *
678
 * @return int[]
679
 * @package Maintenance
680
 */
681
function getTopicsToMove($id_board)
682
{
683
	$db = database();
684
685
	// Lets get the topics.
686
	return $db->fetchQuery('
687
		SELECT 
688
			id_topic
689
		FROM {db_prefix}topics
690
		WHERE id_board = {int:id_board}
691
		LIMIT 10',
692
		array(
693
			'id_board' => $id_board,
694
		)
695
	)->fetch_callback(
696
		function ($row) {
697
			return $row['id_topic'];
698
		}
699
	);
700
}
701
702
/**
703
 * Counts members with posts > 0, we name them contributors
704
 *
705
 * @return int
706
 * @package Maintenance
707
 */
708
function countContributors()
709
{
710
	$db = database();
711
712
	$request = $db->query('', '
713
		SELECT 
714
			COUNT(DISTINCT m.id_member)
715
		FROM ({db_prefix}messages AS m, {db_prefix}boards AS b)
716
		WHERE m.id_member != 0
717
			AND b.count_posts = 0
718
			AND m.id_board = b.id_board',
719
		array()
720
	);
721
722
	// save it so we don't do this again for this task
723
	list ($total_members) = $request->fetch_row();
724
	$request->free_result();
725
726
	return $total_members;
727
}
728
729
/**
730
 * Recount the members posts.
731
 *
732
 * @param int $start The item to start with (for pagination purposes)
733
 * @param int $increment
734
 * @return int
735
 * @package Maintenance
736
 */
737
function updateMembersPostCount($start, $increment)
738
{
739
	global $modSettings;
740
741
	$db = database();
742
	require_once(SUBSDIR . '/Members.subs.php');
743
744
	$request = $db->query('', '
745
		SELECT 
746
			/*!40001 SQL_NO_CACHE */ m.id_member, COUNT(m.id_member) AS posts
747
		FROM {db_prefix}messages AS m
748
			INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
749
		WHERE m.id_member != {int:zero}
750
			AND b.count_posts = {int:zero}' . (!empty($modSettings['recycle_enable']) ? '
751
			AND b.id_board != {int:recycle}' : '') . '
752
		GROUP BY m.id_member
753
		LIMIT {int:start}, {int:number}',
754
		array(
755
			'start' => $start,
756
			'number' => $increment,
757
			'recycle' => $modSettings['recycle_board'],
758
			'zero' => 0,
759
		)
760
	);
761
	$total_rows = $request->num_rows();
762
	// Update the post count for this group
763
	while (($row = $request->fetch_assoc()))
764
	{
765
		updateMemberData($row['id_member'], array('posts' => $row['posts']));
766
	}
767
	$request->free_result();
768
769
	return $total_rows;
770
}
771
772
/**
773
 * Used to find members who have a post count >0 that should not.
774
 *
775
 * @package Maintenance
776
 */
777
function updateZeroPostMembers()
778
{
779
	global $modSettings;
780
781
	$db = database();
782
783
	// Sub select all member ids that have posts on post count enabled boards and
784
	// right join the members table on that result with members who have a non zero
785
	// post count.  result set will be members who do not exist in the sub select group.
786
	$members = $db->fetchQuery('
787
		SELECT 
788
			mem.id_member, mem.posts
789
		FROM (	
790
			SELECT 
791
				m.id_member
792
			FROM {db_prefix}messages AS m
793
				INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
794
			WHERE m.id_member != {int:zero}
795
				AND b.count_posts = {int:zero}' . (!empty($modSettings['recycle_enable']) ? '
796
				AND b.id_board != {int:recycle}' : '') . '
797
			GROUP BY m.id_member
798
		) AS temp
799
		RIGHT JOIN {db_prefix}members AS mem ON (temp.id_member = mem.id_member)
800
		WHERE temp.id_member IS NULL
801
			AND mem.posts != {int:zero}',
802
		array(
803
			'zero' => 0,
804
			'recycle' => $modSettings['recycle_board']
805
		)
806
	)->fetch_callback(
807
		function ($row) {
808
			// Set the post count to zero for any delinquents we may have found
809
			return $row['id_member'];
810
		}
811
	);
812
813
	if (!empty($members))
814
	{
815
		require_once(SUBSDIR . '/Members.subs.php');
816
		updateMemberData($members, array('posts' => 0));
817
	}
818
}
819
820
/**
821
 * Removing old and inactive members
822
 *
823
 * @param string $type
824
 * @param int[] $groups
825
 * @param int $time_limit
826
 * @return array
827
 * @package Maintenance
828
 */
829
function purgeMembers($type, $groups, $time_limit)
830
{
831
	$db = database();
832
833
	$where_vars = array(
834
		'time_limit' => $time_limit,
835
	);
836
	if ($type === 'activated')
837
	{
838
		$where = 'mem.date_registered < {int:time_limit} AND mem.is_activated = {int:is_activated}';
839
		$where_vars['is_activated'] = 0;
840
	}
841
	else
842
	{
843
		$where = 'mem.last_login < {int:time_limit} AND (mem.last_login != 0 OR mem.date_registered < {int:time_limit})';
844
	}
845
846
	// Need to get *all* groups then work out which (if any) we avoid.
847
	$db->fetchQuery('
848
		SELECT 
849
			id_group, group_name, min_posts
850
		FROM {db_prefix}membergroups',
851
		array()
852
	)->fetch_callback(
853
		function ($row) use (&$where, &$where_vars, $groups) {
854
			// Avoid this one?
855
			if (!in_array($row['id_group'], $groups))
856
			{
857
				// Post group?
858
				if ($row['min_posts'] != -1)
859
				{
860
					$where .= ' AND mem.id_post_group != {int:id_post_group_' . $row['id_group'] . '}';
861
					$where_vars['id_post_group_' . $row['id_group']] = $row['id_group'];
862
				}
863
				else
864
				{
865
					$where .= ' AND mem.id_group != {int:id_group_' . $row['id_group'] . '} AND FIND_IN_SET({int:id_group_' . $row['id_group'] . '}, mem.additional_groups) = 0';
866
					$where_vars['id_group_' . $row['id_group']] = $row['id_group'];
867
				}
868
			}
869
		}
870
	);
871
872
	// If we have ungrouped unselected we need to avoid those guys.
873
	if (!in_array(0, $groups))
874
	{
875
		$where .= ' AND (mem.id_group != 0 OR mem.additional_groups != {string:blank_add_groups})';
876
		$where_vars['blank_add_groups'] = '';
877
	}
878
879
	// Select all the members we're about to remove...
880
	$members = array();
881
	$db->fetchQuery('
882
		SELECT 
883
			mem.id_member, COALESCE(m.id_member, 0) AS is_mod
884
		FROM {db_prefix}members AS mem
885
			LEFT JOIN {db_prefix}moderators AS m ON (m.id_member = mem.id_member)
886
		WHERE ' . $where,
887
		$where_vars
888
	)->fetch_callback(
889
		function ($row) use (&$members, $groups) {
890
			if (!$row['is_mod'] || !in_array(3, $groups))
891
			{
892
				$members[] = $row['id_member'];
893
			}
894
		}
895
	);
896
897
	return $members;
898
}
899