getBasicMembergroupData()   F
last analyzed

Complexity

Conditions 22
Paths > 20000

Size

Total Lines 134
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 65.0544

Importance

Changes 0
Metric Value
cc 22
eloc 69
nc 98304
nop 4
dl 0
loc 134
ccs 31
cts 56
cp 0.5536
crap 65.0544
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains functions regarding manipulation of and information about membergroups.
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 Beta 1
14
 *
15
 */
16
17
use ElkArte\Cache\Cache;
18
use ElkArte\Helper\Util;
19
use ElkArte\Languages\Txt;
20
use ElkArte\User;
21
22
/**
23
 * Delete one of more membergroups.
24
 *
25
 * - Requires the manage_membergroups permission.
26
 * - Returns true on success or false on failure.
27
 * - Has protection against deletion of protected membergroups.
28
 * - Deletes the permissions linked to the membergroup.
29
 * - Takes members out of the deleted membergroups.
30
 *
31
 * @param int[]|int $groups
32
 * @return bool
33
 * @package Membergroups
34
 */
35
function deleteMembergroups($groups)
36
{
37
	global $modSettings;
38
39
	$db = database();
40
41
	// Make sure it's an array.
42
	if (!is_array($groups))
43
	{
44
		$groups = [(int) $groups];
45
	}
46
	else
47
	{
48
		$groups = array_unique($groups);
49
50
		// Make sure all groups are integer.
51
		foreach ($groups as $key => $value)
52
		{
53
			$groups[$key] = (int) $value;
54
		}
55
	}
56
57
	// Some groups are protected (guests, administrators, moderators, newbies).
58
	$protected_groups = [-1, 0, 1, 3, 4];
59
60
	// There maybe some others as well.
61
	if (!allowedTo('admin_forum'))
62
	{
63
		$db->fetchQuery('
64
			SELECT 
65
				id_group
66
			FROM {db_prefix}membergroups
67
			WHERE group_type = {int:is_protected}',
68
			[
69
				'is_protected' => 1,
70
			]
71
		)->fetch_callback(
72
			function ($row) use (&$protected_groups) {
73
				$protected_groups[] = $row['id_group'];
74
			}
75
		);
76
	}
77
78
	// Make sure they don't delete protected groups!
79
	$groups = array_diff($groups, array_unique($protected_groups));
80
	if (empty($groups))
81
	{
82
		return false;
83
	}
84
85
	// Log the deletion.
86
	$groups_to_log = membergroupsById($groups, 0);
87
	foreach ($groups_to_log as $key => $row)
88
	{
89
		logAction('delete_group', ['group' => $row['group_name']], 'admin');
90
	}
91
92
	call_integration_hook('integrate_delete_membergroups', [$groups]);
93
94
	// Remove the membergroups themselves.
95
	$db->query('', '
96
		DELETE FROM {db_prefix}membergroups
97
		WHERE id_group IN ({array_int:group_list})',
98
		[
99
			'group_list' => $groups,
100
		]
101
	);
102
103
	// Remove the permissions of the membergroups.
104
	$db->query('', '
105
		DELETE FROM {db_prefix}permissions
106
		WHERE id_group IN ({array_int:group_list})',
107
		[
108
			'group_list' => $groups,
109
		]
110
	);
111
	$db->query('', '
112
		DELETE FROM {db_prefix}board_permissions
113
		WHERE id_group IN ({array_int:group_list})',
114
		[
115
			'group_list' => $groups,
116
		]
117
	);
118
	$db->query('', '
119
		DELETE FROM {db_prefix}group_moderators
120
		WHERE id_group IN ({array_int:group_list})',
121
		[
122
			'group_list' => $groups,
123
		]
124
	);
125
126
	// Delete any outstanding requests.
127
	$db->query('', '
128
		DELETE FROM {db_prefix}log_group_requests
129
		WHERE id_group IN ({array_int:group_list})',
130
		[
131
			'group_list' => $groups,
132
		]
133
	);
134
135
	// Update the primary groups of members.
136
	$db->query('', '
137
		UPDATE {db_prefix}members
138
		SET id_group = {int:regular_group}
139
		WHERE id_group IN ({array_int:group_list})',
140
		[
141
			'group_list' => $groups,
142
			'regular_group' => 0,
143
		]
144
	);
145
146
	// Update any inherited groups (Lose inheritance).
147
	$db->query('', '
148
		UPDATE {db_prefix}membergroups
149
		SET id_parent = {int:uninherited}
150
		WHERE id_parent IN ({array_int:group_list})',
151
		[
152
			'group_list' => $groups,
153
			'uninherited' => -2,
154
		]
155
	);
156
157
	// Update the additional groups of members.
158
	$updates = [];
159
	$db->fetchQuery('
160
		SELECT 
161
			id_member, additional_groups
162
		FROM {db_prefix}members
163
		WHERE FIND_IN_SET({raw:additional_groups_explode}, additional_groups) != 0',
164
		[
165
			'additional_groups_explode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups),
166
		]
167
	)->fetch_callback(
168
		function ($row) use (&$updates) {
169
			// Update each member information.
170
			$updates[$row['additional_groups']][] = $row['id_member'];
171
		}
172
	);
173
174
	require_once(SUBSDIR . '/Members.subs.php');
175
	foreach ($updates as $additional_groups => $memberArray)
176
	{
177
		updateMemberData($memberArray, ['additional_groups' => implode(',', array_diff(explode(',', $additional_groups), $groups))]);
178
	}
179
180
	// No boards can provide access to these membergroups anymore.
181
	$updates = [];
182
	$db->fetchQuery('
183
		SELECT 
184
			id_board, member_groups
185
		FROM {db_prefix}boards
186
		WHERE FIND_IN_SET({raw:member_groups_explode}, member_groups) != 0',
187
		[
188
			'member_groups_explode' => implode(', member_groups) != 0 OR FIND_IN_SET(', $groups),
189
		]
190
	)->fetch_callback(
191
		function ($row) use (&$updates) {
192
			$updates[$row['member_groups']][] = $row['id_board'];
193
		}
194
	);
195
196
	foreach ($updates as $member_groups => $boardArray)
197
	{
198
		$db->query('', '
199
			UPDATE {db_prefix}boards
200
			SET member_groups = {string:member_groups}
201
			WHERE id_board IN ({array_int:board_lists})',
202
			[
203
				'board_lists' => $boardArray,
204
				'member_groups' => implode(',', array_diff(explode(',', $member_groups), $groups)),
205
			]
206
		);
207
	}
208
209
	// Recalculate the post-groups, as they likely changed.
210
	updatePostGroupStats();
211
212
	// Make a note of the fact that the cache may be wrong.
213
	$settings_update = ['settings_updated' => time()];
214
215
	// Have we deleted the spider group?
216
	// @memo we are lucky that the group 1 and 0 cannot be deleted
217
	// $modSettings['spider_group'] is set to 1 (admin) for regular members (that usually is group 0)
218
	if (isset($modSettings['spider_group']) && in_array($modSettings['spider_group'], $groups))
219
	{
220
		$settings_update['spider_group'] = 0;
221
	}
222
223
	updateSettings($settings_update);
224
225
	// It was a success.
226
	return true;
227
}
228
229
/**
230
 * Remove one or more members from one or more membergroups.
231
 *
232
 * - Requires the manage_membergroups permission.
233
 * - Function includes protection against removing from implicit groups.
234
 * - Non-admins are not able to remove members from the admin group.
235
 *
236
 * @param int[]|int $members
237
 * @param int|null $groups
238
 * @param bool $permissionCheckDone = false
239
 *
240
 * @return bool
241
 * @package Membergroups
242
 */
243
function removeMembersFromGroups($members, $groups = null, $permissionCheckDone = false)
244
{
245
	global $modSettings;
246
247
	$db = database();
248
249
	// You're getting nowhere without this permission, unless of course you are the group's moderator.
250
	if (!$permissionCheckDone)
251
	{
252
		isAllowedTo('manage_membergroups');
253
	}
254
255
	// Assume something will happen.
256
	updateSettings(['settings_updated' => time()]);
257
258
	// Cleaning the input.
259
	if (!is_array($members))
260
	{
261
		$members = [(int) $members];
262
	}
263
	else
264
	{
265
		$members = array_unique($members);
266
267
		// Cast the members to integer.
268
		foreach ($members as $key => $value)
269
		{
270
			$members[$key] = (int) $value;
271
		}
272
	}
273
274
	// Before we get started, let's check we won't leave the admin group empty!
275
	if ($groups === null || $groups == 1 || (is_array($groups) && in_array(1, $groups)))
276
	{
277
		$admins = [];
278
		listMembergroupMembers_Href($admins, 1);
279
280
		// Remove any admins if there are too many.
281
		$non_changing_admins = array_diff(array_keys($admins), $members);
282
283
		if (empty($non_changing_admins))
284
		{
285
			$members = array_diff($members, array_keys($admins));
286
		}
287
	}
288
289
	// Just in case.
290
	if (empty($members))
291
	{
292
		return false;
293
	}
294
295
	// Wanna remove all groups from these members? That's easy.
296
	if ($groups === null)
297
	{
298
		$db->query('', '
299
			UPDATE {db_prefix}members
300
			SET
301
				id_group = {int:regular_member},
302
				additional_groups = {string:blank_string}
303
			WHERE id_member IN ({array_int:member_list})' . (allowedTo('admin_forum') ? '' : '
304
				AND id_group != {int:admin_group}
305
				AND FIND_IN_SET({int:admin_group}, additional_groups) = 0'),
306
			[
307
				'member_list' => $members,
308
				'regular_member' => 0,
309
				'admin_group' => 1,
310
				'blank_string' => '',
311
			]
312
		);
313
314
		updatePostGroupStats($members);
315
316
		// Log what just happened.
317
		foreach ($members as $member)
318
		{
319
			logAction('removed_all_groups', ['member' => $member], 'admin');
320
		}
321
322
		return true;
323
	}
324
325
	if (!is_array($groups))
0 ignored issues
show
introduced by
The condition is_array($groups) is always false.
Loading history...
326
	{
327
		$groups = [(int) $groups];
328
	}
329
	// Make sure all groups are integer.
330
	else
331
	{
332
		$groups = array_unique(array_map('intval', $groups));
333
	}
334
335
	// Fetch a list of groups members cannot be assigned to explicitly, and the group names of the ones we want.
336
	$implicitGroups = [-1, 0, 3];
337
	$group_names = [];
338
	$group_details = membergroupsById($groups, 0, true);
339
	foreach ($group_details as $key => $row)
340
	{
341
		if ((int) $row['min_posts'] !== -1)
342
		{
343
			$implicitGroups[] = $row['id_group'];
344
		}
345
		else
346
		{
347
			$group_names[$row['id_group']] = $row['group_name'];
348
		}
349
	}
350
351
	// Now get rid of those groups.
352
	$groups = array_diff($groups, $implicitGroups);
353
354
	// Remember the protected groups.
355
	if (!allowedTo('admin_forum'))
356
	{
357
		$protected_groups = [1];
358
		$db->fetchQuery('
359
			SELECT 
360
				id_group
361
			FROM {db_prefix}membergroups
362
			WHERE group_type = {int:is_protected}',
363
			[
364
				'is_protected' => 1,
365
			]
366
		)->fetch_callback(
367
			function ($row) use (&$protected_groups) {
368
				$protected_groups[] = $row['id_group'];
369
			}
370
		);
371
372
		// If you're not an admin yourself, you can't touch protected groups!
373
		$groups = array_diff($groups, array_unique($protected_groups));
374
	}
375
376
	// Only continue if there are still groups and members left.
377
	if (empty($groups) || empty($members))
378
	{
379
		return false;
380
	}
381
382
	// First, reset those who have this as their primary group - this is the easy one.
383
	$log_inserts = $db->fetchQuery('
384
		SELECT 
385
			id_member, id_group
386
		FROM {db_prefix}members AS members
387
		WHERE id_group IN ({array_int:group_list})
388
			AND id_member IN ({array_int:member_list})',
389
		[
390
			'group_list' => $groups,
391
			'member_list' => $members,
392
		]
393
	)->fetch_callback(
394
		function ($row) use ($group_names) {
395
			return ['group' => $group_names[$row['id_group']], 'member' => $row['id_member']];
396
		}
397
	);
398
399
	$db->query('', '
400
		UPDATE {db_prefix}members
401
		SET id_group = {int:regular_member}
402
		WHERE id_group IN ({array_int:group_list})
403
			AND id_member IN ({array_int:member_list})',
404
		[
405
			'group_list' => $groups,
406
			'member_list' => $members,
407
			'regular_member' => 0,
408
		]
409
	);
410
411
	// Those who have it as part of their additional group must be updated a long way... sadly.
412
	$updates = [];
413
	$db->fetchQuery('
414
		SELECT 
415
			id_member, additional_groups
416
		FROM {db_prefix}members
417
		WHERE (FIND_IN_SET({raw:additional_groups_implode}, additional_groups) != 0)
418
			AND id_member IN ({array_int:member_list})
419
		LIMIT ' . count($members),
420
		[
421
			'member_list' => $members,
422
			'additional_groups_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups),
423
		]
424
	)->fetch_callback(
425
		function ($row) use (&$updates, &$log_inserts, $groups, $group_names) {
426
			// What log entries must we make for this one, eh?
427
			foreach (explode(',', $row['additional_groups']) as $group)
428
			{
429
				if (in_array($group, $groups))
430
				{
431
					$log_inserts[] = [
432
						'group' => $group_names[$group],
433
						'member' => $row['id_member']
434
					];
435
				}
436
			}
437
438
			$updates[$row['additional_groups']][] = $row['id_member'];
439
		}
440
	);
441
442
	require_once(SUBSDIR . '/Members.subs.php');
443
	foreach ($updates as $additional_groups => $memberArray)
444
	{
445
		updateMemberData($memberArray, ['additional_groups' => implode(',', array_diff(explode(',', $additional_groups), $groups))]);
446
	}
447
448
	// Their post-groups may have changed now...
449
	updatePostGroupStats($members);
450
451
	// Do the log.
452
	if (!empty($log_inserts) && featureEnabled('ml'))
453
	{
454
		foreach ($log_inserts as $extra)
455
		{
456
			logAction('removed_from_group', $extra, 'admin');
457
		}
458
	}
459
460
	// Mission successful.
461
	return true;
462
}
463
464
/**
465
 * Add one or more members to a membergroup.
466
 *
467
 * - Requires the manage_membergroups permission.
468
 * - Function has protection against adding members to implicit groups.
469
 * - Non-admins cannot add members to the admin group, or protected groups.
470
 *
471
 * @param int|int[] $members
472
 * @param int $group
473
 * @param string $type = 'auto' specifies whether the group is added as primary or as additional group.
474
 * Supported types:
475
 * - only_primary    - Assigns a membergroup as primary membergroup, but only
476
 *                     if a member has not yet a primary membergroup assigned,
477
 *                     unless the member is already part of the membergroup.
478
 * - only_additional - Assigns a membergroup to the additional membergroups,
479
 *                     unless the member is already part of the membergroup.
480
 * - force_primary   - Assigns a membergroup as primary membergroup no matter
481
 *                     what the previous primary membergroup was.
482
 * - auto            - Assigns a membergroup to the primary group if it's still
483
 *                     available. If not, assign it to the additional group.
484
 * @param bool $permissionCheckDone = false if true, it checks permission of the current user to add groups ('manage_membergroups')
485
 * @return bool success or failure
486
 * @package Membergroups
487
 */
488
function addMembersToGroup($members, $group, $type = 'auto', $permissionCheckDone = false)
489
{
490
	$db = database();
491
492
	// Show your license, but only if it hasn't been done yet.
493
	if (!$permissionCheckDone)
494
	{
495
		isAllowedTo('manage_membergroups');
496
	}
497
498
	// Make sure we don't keep old stuff cached.
499
	updateSettings(['settings_updated' => time()]);
500
501
	$members = !is_array($members) ? [(int) $members] : array_unique(array_map('intval', $members));
502
503
	$group = (int) $group;
504
505
	// Some groups just don't like explicitly having members.
506
	$implicitGroups = [-1, 0, 3];
507
	$group_names = [];
508
	$group_details = membergroupById($group, true);
509
	if ($group_details['min_posts'] != -1)
510
	{
511
		$implicitGroups[] = $group_details['id_group'];
512
	}
513
	else
514
	{
515
		$group_names[$group_details['id_group']] = $group_details['group_name'];
516
	}
517
518
	// Sorry, you can't join an implicit group.
519
	if (in_array($group, $implicitGroups) || empty($members))
520
	{
521
		return false;
522
	}
523
524
	// Only admins can add admins...
525
	if (!allowedTo('admin_forum') && $group == 1)
526
	{
527
		return false;
528
	}
529
530
	if (!allowedTo('admin_forum') && $group_details['group_type'] == 1)
531
	{
532
		return false;
533
	}
534
	// ... and assign protected groups!
535
536
	// Do the actual updates.
537
	if ($type === 'only_additional')
538
	{
539
		$db->query('', '
540
			UPDATE {db_prefix}members
541
			SET additional_groups = CASE WHEN additional_groups = {string:blank_string} THEN {string:id_group_string} ELSE CONCAT(additional_groups, {string:id_group_string_extend}) END
542
			WHERE id_member IN ({array_int:member_list})
543
				AND id_group != {int:id_group}
544
				AND FIND_IN_SET({int:id_group}, additional_groups) = 0',
545
			[
546
				'member_list' => $members,
547
				'id_group' => $group,
548
				'id_group_string' => (string) $group,
549
				'id_group_string_extend' => ',' . $group,
550
				'blank_string' => '',
551
			]
552
		);
553
	}
554
	elseif ($type === 'only_primary' || $type === 'force_primary')
555
	{
556
		$db->query('', '
557
			UPDATE {db_prefix}members
558
			SET id_group = {int:id_group}
559
			WHERE id_member IN ({array_int:member_list})' . ($type === 'force_primary' ? '' : '
560
				AND id_group = {int:regular_group}
561
				AND FIND_IN_SET({int:id_group}, additional_groups) = 0'),
562
			[
563
				'member_list' => $members,
564
				'id_group' => $group,
565
				'regular_group' => 0,
566
			]
567
		);
568
	}
569
	elseif ($type === 'auto')
570
	{
571
		$db->query('', '
572
			UPDATE {db_prefix}members
573
			SET
574
				id_group = CASE WHEN id_group = {int:regular_group} THEN {int:id_group} ELSE id_group END,
575
				additional_groups = CASE WHEN id_group = {int:id_group} THEN additional_groups
576
					WHEN additional_groups = {string:blank_string} THEN {string:id_group_string}
577
					ELSE CONCAT(additional_groups, {string:id_group_string_extend}) END
578
			WHERE id_member IN ({array_int:member_list})
579
				AND id_group != {int:id_group}
580
				AND FIND_IN_SET({int:id_group}, additional_groups) = 0',
581
			[
582
				'member_list' => $members,
583
				'regular_group' => 0,
584
				'id_group' => $group,
585
				'blank_string' => '',
586
				'id_group_string' => (string) $group,
587
				'id_group_string_extend' => ',' . $group,
588
			]
589
		);
590
	}
591
	// Ack!!?  What happened?
592
	else
593
	{
594
		trigger_error('addMembersToGroup(): Unknown type \'' . $type . '\'', E_USER_WARNING);
595
	}
596
597
	call_integration_hook('integrate_add_members_to_group', [$members, $group_details, &$group_names]);
598
599
	// Update their postgroup statistics.
600
	updatePostGroupStats($members);
601
602
	require_once(SOURCEDIR . '/Logging.php');
603
	foreach ($members as $member)
604
	{
605
		logAction('added_to_group', ['group' => $group_names[$group], 'member' => $member], 'admin');
606
	}
607
608
	return true;
609
}
610
611
/**
612
 * Gets the members of a supplied membergroup.
613
 *
614
 * - Returns them as a link for display.
615
 *
616
 * @param int[] $members
617
 * @param int $membergroup
618
 * @param int|null $limit = null
619
 * @return bool
620
 * @package Membergroups
621
 */
622
function listMembergroupMembers_Href(&$members, $membergroup, $limit = null)
623
{
624
	$db = database();
625
626
	$members = [];
627
	$db->fetchQuery('
628
		SELECT 
629
			id_member, real_name
630
		FROM {db_prefix}members
631
		WHERE id_group = {int:id_group} OR FIND_IN_SET({int:id_group}, additional_groups) != 0' . ($limit === null ? '' : '
632
		LIMIT ' . ($limit + 1)),
633
		[
634
			'id_group' => $membergroup,
635
		]
636
	)->fetch_callback(
637
		function ($row) use (&$members) {
638
			$members[$row['id_member']] = '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['real_name']]) . '">' . $row['real_name'] . '</a>';
639
		}
640
	);
641
642
	// If there are more than $limit members, add a 'more' link.
643
	if ($limit !== null && count($members) > $limit)
644
	{
645
		array_pop($members);
646
647
		return true;
648
	}
649
650
	return false;
651
}
652
653
/**
654
 * Retrieve a list of (visible) membergroups used by the cache.
655
 *
656
 * @package Membergroups
657
 */
658
function cache_getMembergroupList()
659
{
660
	$db = database();
661
662
	$groupCache = $db->fetchQuery('
663
		SELECT 
664
			id_group, group_name, online_color
665
		FROM {db_prefix}membergroups
666
		WHERE min_posts = {int:min_posts}
667
			AND hidden = {int:not_hidden}
668
			AND id_group != {int:mod_group}
669
			AND online_color != {string:blank_string}
670
		ORDER BY group_name',
671
		[
672
			'min_posts' => -1,
673
			'not_hidden' => 0,
674
			'mod_group' => 3,
675
			'blank_string' => '',
676
		]
677
	)->fetch_callback(
678
		function ($row) {
679
			return '<a href="' . getUrl('group', ['action' => 'groups', 'sa' => 'members', 'group' => $row['id_group'], 'name' => $row['group_name']]) . '" ' . ($row['online_color'] ? 'style="color: ' . $row['online_color'] . '"' : '') . '>' . $row['group_name'] . '</a>';
680
		}
681
	);
682
683
	return [
684
		'data' => $groupCache,
685
		'expires' => time() + 3600,
686
		'refresh_eval' => 'return $GLOBALS[\'modSettings\'][\'settings_updated\'] > ' . time() . ';',
687
	];
688
}
689
690
/**
691
 * Helper function to generate a list of membergroups for display.
692
 *
693
 * @param int $start not used
694
 * @param int $items_per_page not used
695
 * @param string $sort An SQL query indicating how to sort the results
696
 * @param string $membergroup_type Should be 'post_count' for post-groups or 'regular' for other groups
697
 * @param int $user_id id of the member making the request
698
 * @param bool $include_hidden If true, includes hidden groups if the user has permission
699
 * @param bool $include_all If true, includes all groups the user can see
700
 * @param bool $aggregate
701
 * @param bool $count_permissions
702
 * @param int|null $pid - profile id
703
 *
704
 * @return array
705
 * @package Membergroups
706
 *
707
 */
708
function list_getMembergroups($start, $items_per_page, $sort, $membergroup_type, $user_id, $include_hidden, $include_all = false, $aggregate = false, $count_permissions = false, $pid = null)
709
{
710
	global $txt, $context;
711
712 2
	$db = database();
713
	Txt::load('Admin');
714 2
715 2
	// Start collecting the data.
716
	$groups = [];
717
	$group_ids = [];
718 2
	$parent_groups = [];
719 2
720 2
	if ($membergroup_type === 'all')
721
	{
722 2
		// Determine the number of ungrouped members.
723
		$num_members = countMembersInGroup();
724
725
		// Fill the context variable with 'Guests' and 'Regular Members'.
726
		$groups = [
727
			-1 => [
728
				'id_group' => -1,
729
				'group_name' => $txt['membergroups_guests'],
730
				'group_name_color' => $txt['membergroups_guests'],
731
				'min_posts' => 0,
732
				'desc' => '',
733
				'num_members' => $txt['membergroups_guests_na'],
734
				'icons' => '',
735
				'can_search' => false,
736
				'id_parent' => -2,
737
				'num_permissions' => [
738
					'allowed' => 0,
739
					'denied' => 0,
740
				]
741
			],
742
			0 => [
743
				'id_group' => 0,
744
				'group_name' => $txt['membergroups_members'],
745
				'group_name_color' => $txt['membergroups_members'],
746
				'min_posts' => 0,
747
				'desc' => '',
748
				'num_members' => $num_members,
749
				'icons' => '',
750
				'can_search' => true,
751
				'id_parent' => -2,
752
				'num_permissions' => [
753
					'allowed' => 0,
754
					'denied' => 0,
755
				]
756
			],
757
		];
758
	}
759
760
	$db->fetchQuery('
761
		SELECT 
762 2
			mg.id_group, mg.group_name, mg.min_posts, mg.description, mg.group_type, mg.online_color,
763
			mg.hidden, mg.id_parent, mg.icons, COALESCE(gm.id_member, 0) AS can_moderate, 0 AS num_members
764
		FROM {db_prefix}membergroups AS mg
765
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
766
		WHERE mg.min_posts {raw:min_posts}' . ($include_all ? '' : '
767
			AND mg.id_group != {int:mod_group}
768 2
			AND mg.group_type != {int:is_protected}') . '
769
		ORDER BY {raw:sort}',
770 2
		[
771
			'current_member' => $user_id,
772
			'min_posts' => ($membergroup_type === 'post_count' ? '!= -1' : '= -1'),
773 2
			'mod_group' => 3,
774 2
			'is_protected' => 1,
775 2
			'sort' => $sort,
776 2
		]
777 2
	)->fetch_callback(
778
		function ($row) use (&$parent_groups, &$groups, &$group_ids, &$include_hidden, $count_permissions, $aggregate) {
779 2
			global $txt;
780
781 2
			// We only list the groups they can see.
782
			if ($row['hidden'] && !$row['can_moderate'] && !$include_hidden)
783
			{
784 2
				return;
785
			}
786
787
			if ((int) $row['id_parent'] !== -2)
788
			{
789 2
				$parent_groups[] = (int) $row['id_parent'];
790
			}
791
792
			// If it's inherited, just add it as a child.
793
			if ($aggregate && (int) $row['id_parent'] !== -2)
794
			{
795 2
				if (isset($groups[$row['id_parent']]))
796
				{
797
					$groups[$row['id_parent']]['children'][$row['id_group']] = $row['group_name'];
798
				}
799
800
				return;
801
			}
802
803
			$row['icons'] = explode('#', $row['icons']);
804
			$row['id_group'] = (int) $row['id_group'];
805 2
			$groups[$row['id_group']] = [
806
				'id_group' => $row['id_group'],
807 2
				'group_name' => $row['group_name'],
808 2
				'group_name_color' => empty($row['online_color']) ? $row['group_name'] : '<span style="color: ' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
809 2
				'min_posts' => (int) $row['min_posts'],
810 2
				'desc' => $row['description'],
811 2
				'online_color' => $row['online_color'],
812 2
				'type' => (int) $row['group_type'],
813 2
				'num_members' => (int) $row['num_members'],
814 2
				'moderators' => [],
815 2
				'icons' => $row['icons'],
816
				'can_search' => $row['id_group'] !== 3,
817 2
				'id_parent' => (int) $row['id_parent'],
818 2
			];
819 2
820
			if ($count_permissions)
821
			{
822 2
				$groups[$row['id_group']]['num_permissions'] = [
823
					'allowed' => $row['id_group'] === 1 ? '(' . $txt['permissions_all'] . ')' : 0,
824
					'denied' => $row['id_group'] === 1 ? '(' . $txt['permissions_none'] . ')' : 0,
825
				];
826
			}
827
828
			$include_hidden |= $row['can_moderate'];
829
			$group_ids[] = $row['id_group'];
830 2
		}
831 2
	);
832 2
833
	// If we found any membergroups, get the number of members in them.
834
	if (!empty($group_ids))
835
	{
836 2
		if ($membergroup_type === 'post_count')
837
		{
838 2
			$groups_count = membersInGroups($group_ids);
839
		}
840
		else
841
		{
842
			$groups_count = membersInGroups([], $group_ids, $include_hidden);
843
		}
844 2
845
		// @todo not sure why += wouldn't = be enough?
846
		foreach ($groups_count as $group_id => $num_members)
847
		{
848 2
			$groups[$group_id]['num_members'] += $num_members;
849
		}
850 2
851
		$db->fetchQuery('
852
			SELECT 
853 2
				mods.id_group, mods.id_member, mem.member_name, mem.real_name
854
			FROM {db_prefix}group_moderators AS mods
855
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
856
			WHERE mods.id_group IN ({array_int:group_list})',
857
			[
858
				'group_list' => $group_ids,
859
			]
860 2
		)->fetch_callback(
861
			function ($row) use (&$groups) {
862 2
				$groups[$row['id_group']]['moderators'][] = '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['real_name']]) . '">' . $row['real_name'] . '</a>';
863
			}
864
		);
865 2
	}
866
867
	if (!empty($parent_groups))
868
	{
869 2
		$all_group_names = [
870
			-1 => $txt['membergroups_guests'],
871
			0 => $txt['membergroups_members']
872
		];
873
		$db->fetchQuery('
874
			SELECT 
875
				id_group, group_name
876
			FROM {db_prefix}membergroups
877
			WHERE id_group IN ({array_int:groups})',
878
			[
879
				'groups' => $parent_groups,
880
			]
881
		)->fetch_callback(
882
			function ($row) use (&$all_group_names) {
883
				$all_group_names[$row['id_group']] = $row['group_name'];
884
			}
885
		);
886
	}
887
	foreach ($groups as $key => $group)
888
	{
889 2
		if ($group['id_parent'] != -2)
890
		{
891 2
			$groups[$key]['parent_name'] = $all_group_names[$group['id_parent']];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $all_group_names does not seem to be defined for all execution paths leading up to this point.
Loading history...
892
		}
893 1
	}
894
895
	// Apply manual sorting if the 'number of members' column is selected.
896
	if (str_starts_with($sort, '1') || str_contains($sort, ', 1'))
897
	{
898 2
		$sort_ascending = !str_contains($sort, 'DESC');
899
		$sort_array = [];
900
901
		foreach ($groups as $group)
902
		{
903
			$sort_array[] = $group['id_group'] != 3 ? (int) $group['num_members'] : -1;
904
		}
905
906
		array_multisort($sort_array, $sort_ascending ? SORT_ASC : SORT_DESC, SORT_REGULAR, $groups);
0 ignored issues
show
Bug introduced by
$sort_ascending ? SORT_ASC : SORT_DESC cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

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

906
		array_multisort($sort_array, /** @scrutinizer ignore-type */ $sort_ascending ? SORT_ASC : SORT_DESC, SORT_REGULAR, $groups);
Loading history...
Bug introduced by
SORT_REGULAR cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

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

906
		array_multisort($sort_array, $sort_ascending ? SORT_ASC : SORT_DESC, /** @scrutinizer ignore-type */ SORT_REGULAR, $groups);
Loading history...
907
	}
908
909
	if ($count_permissions)
910
	{
911 2
		// pid = profile id
912
		if (empty($pid))
913
		{
914
			$groups = countPermissions($groups, $context['hidden_permissions']);
915
916
			// Get the "default" profile permissions too.
917
			$groups = countBoardPermissions($groups, $context['hidden_permissions'], 1);
918
		}
919
		else
920
		{
921
			$groups = countBoardPermissions($groups, null, $pid);
922
		}
923
	}
924
925
	return $groups;
926
}
927 2
928
/**
929
 * Count the number of members in specific groups
930
 *
931
 * @param int[] $postGroups an array of post-based groups' id.
932
 * @param int[] $normalGroups = array() an array of normal groups' id.
933
 * @param bool $include_hidden if true, includes hidden groups in the count (default false).
934
 * @param bool $include_moderators if true, includes board moderators too (default false).
935
 * @param bool $include_non_active if true, includes non active members (default false).
936
 * @return array
937
 * @package Membergroups
938
 */
939
function membersInGroups($postGroups, $normalGroups = [], $include_hidden = false, $include_moderators = false, $include_non_active = false)
940
{
941
	$db = database();
942
943
	$groups = [];
944 2
945
	// If we have post-groups, let's count the number of members...
946 2
	if (!empty($postGroups))
947
	{
948
		$db->fetchQuery('
949 2
			SELECT 
950
				id_post_group AS id_group, COUNT(*) AS member_count
951
			FROM {db_prefix}members
952
			WHERE id_post_group IN ({array_int:post_group_list})' . ($include_non_active ? '' : '
953
				AND is_activated = {int:active_members}') . '
954
			GROUP BY id_post_group',
955
			[
956
				'post_group_list' => $postGroups,
957
				'active_members' => 1,
958
			]
959
		)->fetch_callback(
960
			function ($row) use (&$groups) {
961
				$groups[$row['id_group']] = $row['member_count'];
962
			}
963
		);
964
	}
965
966
	if (!empty($normalGroups))
967
	{
968
		// Find people who are members of this group...
969 2
		$db->fetchQuery('
970
			SELECT 
971
				id_group, COUNT(*) AS member_count
972 2
			FROM {db_prefix}members
973
			WHERE id_group IN ({array_int:normal_group_list})' . ($include_non_active ? '' : '
974
				AND is_activated = {int:active_members}') . '
975
			GROUP BY id_group',
976 2
			[
977 2
				'normal_group_list' => $normalGroups,
978
				'active_members' => 1,
979
			]
980 2
		)->fetch_callback(
981 2
			function ($row) use (&$groups) {
982
				$groups[$row['id_group']] = $row['member_count'];
983 2
			}
984
		);
985 2
986 2
		// Only do additional groups if we can moderate...
987
		if ($include_hidden)
988
		{
989
			// Also do those who have it as an additional membergroup - this one more yucky...
990 2
			$db->fetchQuery('
991
				SELECT 
992
					mg.id_group, COUNT(*) AS member_count
993 2
				FROM {db_prefix}membergroups AS mg
994
					INNER JOIN {db_prefix}members AS mem ON (mem.additional_groups != {string:blank_string}
995
						AND mem.id_group != mg.id_group
996
						AND FIND_IN_SET(mg.id_group, mem.additional_groups) != 0)
997
				WHERE mg.id_group IN ({array_int:normal_group_list})' . ($include_non_active ? '' : '
998
					AND mem.is_activated = {int:active_members}') . '
999
				GROUP BY mg.id_group',
1000 2
				[
1001 2
					'normal_group_list' => $normalGroups,
1002
					'active_members' => 1,
1003
					'blank_string' => '',
1004 2
				]
1005 2
			)->fetch_callback(
1006 2
				function ($row) use (&$groups) {
1007
					if (isset($groups[$row['id_group']]))
1008 2
					{
1009
						$groups[$row['id_group']] += $row['member_count'];
1010
					}
1011
					else
1012
					{
1013
						$groups[$row['id_group']] = $row['member_count'];
1014
					}
1015
				}
1016
			);
1017
		}
1018 2
	}
1019
1020
	if ($include_moderators)
1021
	{
1022
		// Any moderators?
1023 2
		$request = $db->query('', '
1024
			SELECT 
1025
				COUNT(DISTINCT id_member) AS num_distinct_mods
1026
			FROM {db_prefix}moderators
1027
			LIMIT 1',
1028
			[]
1029
		);
1030
		list ($groups[3]) = $request->fetch_row();
1031
		$request->free_result();
1032
	}
1033
1034
	return $groups;
1035
}
1036
1037 2
/**
1038
 * Returns details of membergroups based on the id
1039
 *
1040
 * @param int[]|int $group_ids the IDs of the groups.
1041
 * @param int $limit = 1 the number of results returned (default 1 if null/false/0 returns all).
1042
 * @param bool $detailed = false if true, then it returns more fields (default false).
1043
 *     false returns: id_group, group_name, group_type.
1044
 *     true adds to above: description, min_posts, online_color, max_messages, icons, hidden, id_parent.
1045
 * @param bool $assignable = false determine if the group is assignable or not and return that information.
1046
 * @return array
1047
 * @package Membergroups
1048
 */
1049
function membergroupsById($group_ids, $limit = 1, $detailed = false, $assignable = false)
1050
{
1051
	$db = database();
1052
1053
	if (empty($group_ids))
1054
	{
1055
		return [];
1056
	}
1057
1058
	$group_ids = !is_array($group_ids) ? [$group_ids] : $group_ids;
1059
1060
	$groups = [];
1061
	$group_ids = array_map('intval', $group_ids);
1062
1063
	$db->fetchQuery('
1064
		SELECT 
1065
			id_group, group_name, group_type' . (!$detailed ? '' : ',
1066
			description, min_posts, online_color, max_messages, icons, hidden, id_parent') . (!$assignable ? '' : ',
1067
			CASE WHEN min_posts = {int:min_posts} THEN 1 ELSE 0 END AS assignable,
1068
			CASE WHEN min_posts != {int:min_posts} THEN 1 ELSE 0 END AS is_post_group') . '
1069
		FROM {db_prefix}membergroups
1070
		WHERE id_group IN ({array_int:group_ids})' . (empty($limit) ? '' : '
1071
		LIMIT {int:limit}'),
1072
		[
1073
			'min_posts' => -1,
1074
			'group_ids' => $group_ids,
1075
			'limit' => $limit,
1076
		]
1077
	)->fetch_callback(
1078
		function ($row) use (&$groups, $detailed) {
1079
			$row['id_group'] = (int) $row['id_group'];
1080
			$row['group_type'] = (int) $row['group_type'];
1081
1082
			if ($detailed)
1083
			{
1084
				$row['id_parent'] = (int) $row['id_parent'];
1085
				$row['min_posts'] = (int) $row['min_posts'];
1086
				$row['max_messages'] = (int) $row['max_messages'];
1087
			}
1088
1089
			$groups[$row['id_group']] = $row;
1090
		}
1091
	);
1092
1093
	return $groups;
1094
}
1095
1096
/**
1097
 * Uses membergroupsById to return the group information of a single group
1098
 *
1099
 * @param int $group_id
1100
 * @param bool $detailed
1101
 * @param bool $assignable
1102
 *
1103
 * @return bool|mixed
1104
 * @package Membergroups
1105
 *
1106
 */
1107
function membergroupById($group_id, $detailed = false, $assignable = false)
1108
{
1109
	$groups = membergroupsById([$group_id], 1, $detailed, $assignable);
1110
1111
	return $groups[$group_id] ?? false;
1112
}
1113
1114
/**
1115
 * Gets basic membergroup data
1116
 *
1117
 * - the $includes and $excludes array is used for granular filtering the output.
1118
 * - We need to exclude groups sometimes because they are special ones.
1119
 * Example: getBasicMembergroupData(array('admin', 'mod', 'globalmod'));
1120
 * $includes parameters:
1121
 * - 'admin' includes the admin: id_group = 1
1122
 * - 'mod' includes the local moderator: id_group = 3
1123
 * - 'globalmod' includes the global moderators: id_group = 2
1124
 * - 'member' includes the ungrouped users from id_group = 0
1125
 * - 'postgroups' includes the post based membergroups
1126
 * - 'protected' includes protected groups
1127
 * - 'all' lists all groups
1128
 * $excludes parameters:
1129
 * - 'newbie' excludes the newbie group id_group 4
1130
 * - 'custom' lists only the system based groups (id 0, 1, 2, 3)
1131
 * - 'membergroups' excludes permission groups, lists the post based membergroups
1132
 * - 'hidden' excludes hidden groups
1133
 *
1134
 * @param string[]|string $includes
1135
 * @param string[] $excludes
1136
 * @param string|null $sort_order
1137
 * @param bool|null $split splits postgroups and membergroups
1138
 * @return array
1139
 * @package Membergroups
1140
 */
1141
function getBasicMembergroupData($includes = [], $excludes = [], $sort_order = null, $split = null)
1142
{
1143
	global $txt, $modSettings;
1144
1145
	$db = database();
1146 2
1147
	// No $includes parameters given? Let's set some default values
1148 2
	if (empty($includes))
1149
	{
1150
		$includes = ['globalmod', 'member', 'postgroups'];
1151 2
	}
1152
	elseif (!is_array($includes))
1153
	{
1154
		$includes = [$includes];
1155 2
	}
1156
1157
	$groups = [];
1158
1159
	$where = '';
1160 2
	$sort_order = $sort_order ?? 'min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name';
1161
1162 2
	// Do we need the post-based membergroups?
1163 2
	$where .= !empty($modSettings['permission_enable_postgroups']) || in_array('postgroups', $includes) ? '' : 'AND min_posts = {int:min_posts}';
0 ignored issues
show
Bug introduced by
It seems like $includes can also be of type string; however, parameter $haystack of in_array() does only seem to accept array, 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

1163
	$where .= !empty($modSettings['permission_enable_postgroups']) || in_array('postgroups', /** @scrutinizer ignore-type */ $includes) ? '' : 'AND min_posts = {int:min_posts}';
Loading history...
1164
	// Include protected groups?
1165
	$where .= allowedTo('admin_forum') || in_array('protected', $includes) ? '' : ' AND group_type != {int:is_protected}';
1166 2
	// Include the global moderators?
1167
	$where .= in_array('globalmod', $includes) ? '' : ' AND id_group != {int:global_mod_group}';
1168 2
	// Include the admins?
1169
	$where .= in_array('admin', $includes) ? '' : ' AND id_group != {int:admin_group}';
1170 2
	// Local Moderators?
1171
	$where .= in_array('mod', $includes) ? '' : ' AND id_group != {int:moderator_group}';
1172 2
	// Ignore the first post-based group?
1173
	$where .= !in_array('newbie', $excludes) ? '' : ' AND id_group != {int:newbie_group}';
1174 2
	// Exclude custom groups?
1175
	$where .= !in_array('custom', $excludes) ? '' : ' AND id_group < {int:newbie_group}';
1176 2
	// Exclude hidden?
1177
	$where .= !in_array('hidden', $excludes) ? '' : ' AND hidden != {int:hidden_group}';
1178 2
1179
	// Only the post-based membergroups? We can safely overwrite the $where.
1180 2
	if (in_array('membergroups', $excludes))
1181
	{
1182
		$where = ' AND min_posts != {int:min_posts}';
1183 2
	}
1184
1185
	// Simply all of them?
1186
	if (in_array('all', $includes))
1187
	{
1188
		$where = '';
1189 2
	}
1190
1191
	$request = $db->query('', '
1192
		SELECT 
1193
			id_group, group_name, min_posts, online_color
1194 2
		FROM {db_prefix}membergroups
1195
		WHERE 1 = 1
1196
			' . $where . '
1197
		ORDER BY ' . $sort_order,
1198
		[
1199 2
			'admin_group' => 1,
1200 2
			'moderator_group' => 3,
1201
			'global_mod_group' => 2,
1202 2
			'min_posts' => -1,
1203
			'is_protected' => 1,
1204
			'newbie_group' => 4,
1205
			'hidden_group' => 2,
1206
		]
1207
	);
1208
1209
	// Include the default membergroup? the ones with id_member = 0
1210
	if (in_array('member', $includes) && !isset($split))
1211
	{
1212
		$groups[] = [
1213 2
			'id' => 0,
1214
			'name' => $txt['membergroups_members']
1215 2
		];
1216 2
	}
1217 2
1218
	if (!empty($split))
1219
	{
1220
		if (empty($modSettings['permission_enable_postgroups']))
1221 2
		{
1222
			$groups['groups'][0] = [
1223
				'id' => 0,
1224
				'name' => $txt['membergroups_members'],
1225
				'can_be_additional' => false,
1226
				'member_count' => 0,
1227
			];
1228
			$groups['membergroups'][0] = [
1229
				'id' => 0,
1230
				'name' => $txt['membergroups_members'],
1231
				'can_be_additional' => false,
1232
				'member_count' => 0,
1233
			];
1234
		}
1235
		while (($row = $request->fetch_assoc()))
1236
		{
1237
			$groups['groups'][$row['id_group']] = [
1238
				'id' => $row['id_group'],
1239
				'name' => $row['group_name'],
1240
				'member_count' => 0,
1241
			];
1242
1243
			if ($row['min_posts'] == -1)
1244
			{
1245
				$groups['membergroups'][] = [
1246
					'id' => $row['id_group'],
1247
					'name' => $row['group_name'],
1248
					'can_be_additional' => true,
1249
				];
1250
			}
1251
			else
1252
			{
1253
				$groups['postgroups'][] = [
1254
					'id' => $row['id_group'],
1255
					'name' => $row['group_name'],
1256
				];
1257
			}
1258
		}
1259
	}
1260
	else
1261
	{
1262
		while (($row = $request->fetch_assoc()))
1263
		{
1264
			$groups[] = [
1265 2
				'id' => $row['id_group'],
1266
				'name' => $row['group_name'],
1267 2
				'online_color' => $row['online_color'],
1268 2
			];
1269 2
		}
1270 2
	}
1271
1272
	$request->free_result();
1273
1274
	return $groups;
1275 2
}
1276
1277 2
/**
1278
 * Retrieve groups and their number of members.
1279
 *
1280
 * @param int[] $groupList
1281
 * @return array with ('id', 'name', 'member_count')
1282
 * @package Membergroups
1283
 */
1284
function getGroups($groupList)
1285
{
1286
	global $txt;
1287
1288
	$db = database();
1289
1290 2
	$groups = [];
1291
	if (in_array(0, $groupList))
1292 2
	{
1293
		$groups[0] = [
1294 2
			'id' => 0,
1295 2
			'name' => $txt['announce_regular_members'],
1296
			'member_count' => 'n/a',
1297 2
		];
1298 2
	}
1299 2
1300 2
	// Get all membergroups that have access to the board the announcement was made on.
1301
	$db->fetchQuery('
1302
		SELECT 
1303
			mg.id_group, mg.group_name, COUNT(mem.id_member) AS num_members
1304
		FROM {db_prefix}membergroups AS mg
1305 2
			LEFT JOIN {db_prefix}members AS mem ON (mem.id_group = mg.id_group OR FIND_IN_SET(mg.id_group, mem.additional_groups) != 0 OR mg.id_group = mem.id_post_group)
1306
		WHERE mg.id_group IN ({array_int:group_list})
1307
		GROUP BY mg.id_group, mg.group_name',
1308
		[
1309
			'group_list' => $groupList,
1310
		]
1311
	)->fetch_callback(
1312
		function ($row) use (&$groups) {
1313 2
			$groups[$row['id_group']] = [
1314
				'id' => $row['id_group'],
1315 2
				'name' => $row['group_name'],
1316
				'member_count' => $row['num_members'],
1317 2
			];
1318 2
		}
1319 2
	);
1320 2
1321
	return $groups;
1322 2
}
1323
1324
/**
1325 2
 * Gets the last assigned group id.
1326
 *
1327
 * @return int $id_group
1328
 * @package Membergroups
1329
 */
1330
function getMaxGroupID()
1331
{
1332
	$db = database();
1333
1334
	$request = $db->query('', '
1335
		SELECT 
1336
			MAX(id_group)
1337
		FROM {db_prefix}membergroups',
1338
		[]
1339
	);
1340
	list ($id_group) = $request->fetch_row();
1341
	$request->free_result();
1342
1343
	return $id_group;
1344
}
1345
1346
/**
1347
 * Adds a new group to the membergroups table.
1348
 *
1349
 * @param string $groupname
1350
 * @param int $minposts
1351
 * @param string $type
1352
 * @package Membergroups
1353
 */
1354
function createMembergroup($groupname, $minposts, $type)
1355
{
1356
	$db = database();
1357
1358
	$db->insert('',
1359
		'{db_prefix}membergroups',
1360
		[
1361
			'description' => 'string', 'group_name' => 'string-80', 'min_posts' => 'int',
1362
			'icons' => 'string', 'online_color' => 'string', 'group_type' => 'int',
1363
		],
1364
		[
1365
			'', Util::htmlspecialchars($groupname, ENT_QUOTES), $minposts,
1366
			'1#icon.png', '', $type,
1367
		],
1368
		['id_group']
1369
	);
1370
1371
	return $db->insert_id('{db_prefix}membergroups');
1372
}
1373
1374
/**
1375
 * Copies permissions from a given membergroup.
1376
 *
1377
 * @param int $id_group
1378
 * @param int $copy_from
1379
 * @param string[]|null $illegal_permissions
1380
 * @todo another function with the same name in ManagePermissions.subs.php
1381
 * @package Membergroups
1382
 */
1383
function copyPermissions($id_group, $copy_from, $illegal_permissions)
1384
{
1385
	$db = database();
1386
1387
	$inserts = [];
1388
1389
	$db->fetchQuery('
1390
		SELECT 
1391
			permission, add_deny
1392
		FROM {db_prefix}permissions
1393
		WHERE id_group = {int:copy_from}',
1394
		[
1395
			'copy_from' => $copy_from,
1396
		]
1397
	)->fetch_callback(
1398
		function ($row) use (&$inserts, $illegal_permissions, $id_group) {
1399
			if (empty($illegal_permissions) || !in_array($row['permission'], $illegal_permissions))
1400
			{
1401
				$inserts[] = [$id_group, $row['permission'], $row['add_deny']];
1402
			}
1403
		}
1404
	);
1405
1406
	if (!empty($inserts))
1407
	{
1408
		$db->insert('insert',
1409
			'{db_prefix}permissions',
1410
			['id_group' => 'int', 'permission' => 'string', 'add_deny' => 'int'],
1411
			$inserts,
1412
			['id_group', 'permission']
1413
		);
1414
	}
1415
}
1416
1417
/**
1418
 * Copies the board permissions from a given membergroup.
1419
 *
1420
 * @param int $id_group
1421
 * @param int $copy_from
1422
 * @package Membergroups
1423
 */
1424
function copyBoardPermissions($id_group, $copy_from)
1425
{
1426
	$db = database();
1427
1428
	$inserts = $db->fetchQuery('
1429
		SELECT 
1430
			id_profile, permission, add_deny
1431
		FROM {db_prefix}board_permissions
1432
		WHERE id_group = {int:copy_from}',
1433
		[
1434
			'copy_from' => $copy_from,
1435
		]
1436
	)->fetch_callback(
1437
		function ($row) use ($id_group) {
1438
			return [$id_group, $row['id_profile'], $row['permission'], $row['add_deny']];
1439
		}
1440
	);
1441
1442
	if (!empty($inserts))
1443
	{
1444
		$db->insert('insert',
1445
			'{db_prefix}board_permissions',
1446
			['id_group' => 'int', 'id_profile' => 'int', 'permission' => 'string', 'add_deny' => 'int'],
1447
			$inserts,
1448
			['id_group', 'id_profile', 'permission']
1449
		);
1450
	}
1451
}
1452
1453
/**
1454
 * Updates the properties of a copied membergroup.
1455
 *
1456
 * @param int $id_group
1457
 * @param int $copy_from
1458
 * @package Membergroups
1459
 */
1460
function updateCopiedGroup($id_group, $copy_from)
1461
{
1462
	$db = database();
1463
1464
	require_once(SUBSDIR . '/Membergroups.subs.php');
1465
	$group_info = membergroupById($copy_from, true);
1466
1467
	// update the new membergroup
1468
	$db->query('', '
1469
		UPDATE {db_prefix}membergroups
1470
		SET
1471
			online_color = {string:online_color},
1472
			max_messages = {int:max_messages},
1473
			icons = {string:icons}
1474
			WHERE id_group = {int:current_group}',
1475
		[
1476
			'max_messages' => $group_info['max_messages'],
1477
			'current_group' => $id_group,
1478
			'online_color' => $group_info['online_color'],
1479
			'icons' => $group_info['icons'],
1480
		]
1481
	);
1482
}
1483
1484
/**
1485
 * Updates the properties of an inherited membergroup.
1486
 *
1487
 * @param int $id_group
1488
 * @param int $copy_id
1489
 * @package Membergroups
1490
 */
1491
function updateInheritedGroup($id_group, $copy_id)
1492
{
1493
	$db = database();
1494
1495
	$db->query('', '
1496
		UPDATE {db_prefix}membergroups
1497
		SET id_parent = {int:copy_from}
1498
		WHERE id_group = {int:current_group}',
1499
		[
1500
			'copy_from' => $copy_id,
1501
			'current_group' => $id_group,
1502
		]
1503
	);
1504
}
1505
1506
/**
1507
 * This function updates the membergroup with the given information.
1508
 *
1509
 * - It's passed an associative array $properties, with 'current_group' holding
1510
 * the group to update. The rest of the keys are details to update it with.
1511
 *
1512
 * @param array $properties
1513
 * @package Membergroups
1514
 */
1515
function updateMembergroupProperties($properties)
1516
{
1517
	$db = database();
1518
1519
	$known_properties = [
1520
		'max_messages' => ['type' => 'int'],
1521
		'min_posts' => ['type' => 'int'],
1522
		'group_type' => ['type' => 'int'],
1523
		'hidden' => ['type' => 'int'],
1524
		'id_parent' => ['type' => 'int'],
1525
		'group_name' => ['type' => 'string'],
1526
		'online_color' => ['type' => 'string'],
1527
		'icons' => ['type' => 'string'],
1528
		'description' => ['type' => 'string'],
1529
	];
1530
1531
	$values = ['current_group' => $properties['current_group']];
1532
	$updates = [];
1533
	foreach ($properties as $name => $value)
1534
	{
1535
		if (isset($known_properties[$name]))
1536
		{
1537
			$updates[] = $name . '={' . $known_properties[$name]['type'] . ':subs_' . $name . '}';
1538
			$values['subs_' . $name] = match ($known_properties[$name]['type'])
1539
			{
1540
				'string' => Util::htmlspecialchars((string) $value),
1541
				default => (int) $value,
1542
			};
1543
		}
1544
	}
1545
1546
	if (empty($values))
1547
	{
1548
		return;
1549
	}
1550
1551
	$db->query('', '
1552
		UPDATE {db_prefix}membergroups
1553
		SET ' . implode(', ', $updates) . '
1554
		WHERE id_group = {int:current_group}',
1555
		$values
1556
	);
1557
}
1558
1559
/**
1560
 * Detaches a membergroup from the boards listed in $boards.
1561
 *
1562
 * @param int $id_group
1563
 * @param array $boards
1564
 * @param string $access_list ('allow', 'deny')
1565
 * @package Membergroups
1566
 */
1567
function detachGroupFromBoards($id_group, $boards, $access_list)
1568
{
1569
	$db = database();
1570
1571
	// Find all boards in whose access list this group is in, but shouldn't be.
1572
	$db->fetchQuery('
1573
		SELECT 
1574
			id_board, {raw:column}
1575
		FROM {db_prefix}boards
1576
		WHERE FIND_IN_SET({string:current_group}, {raw:column}) != 0' . (empty($boards[$access_list]) ? '' : '
1577
			AND id_board NOT IN ({array_int:board_access_list})'),
1578
		[
1579
			'current_group' => $id_group,
1580
			'board_access_list' => $boards[$access_list],
1581
			'column' => $access_list === 'allow' ? 'member_groups' : 'deny_member_groups',
1582
		]
1583
	)->fetch_callback(
1584
		function ($row) use ($id_group, $access_list, $db) {
1585
			$db->query('', '
1586
				UPDATE {db_prefix}boards
1587
				SET {raw:column} = {string:member_group_access}
1588
				WHERE id_board = {int:current_board}',
1589
				[
1590
					'current_board' => $row['id_board'],
1591
					'member_group_access' => implode(',', array_diff(explode(',', $row['member_groups']), [$id_group])),
1592
					'column' => $access_list === 'allow' ? 'member_groups' : 'deny_member_groups',
1593
				]
1594
			);
1595
		}
1596
	);
1597
}
1598
1599
/**
1600
 * Assigns the given group $id_group to the boards specified, for
1601
 * the 'allow' or 'deny' list.
1602
 *
1603
 * @param int $id_group
1604
 * @param array $boards
1605
 * @param string $access_list ('allow', 'deny')
1606
 * @package Membergroups
1607
 */
1608
function assignGroupToBoards($id_group, $boards, $access_list)
1609
{
1610
	$db = database();
1611
1612
	$db->query('', '
1613
		UPDATE {db_prefix}boards
1614
		SET {raw:column} = CASE WHEN {raw:column} = {string:blank_string} THEN {string:group_id_string} ELSE CONCAT({raw:column}, {string:comma_group}) END
1615
		WHERE id_board IN ({array_int:board_list})
1616
			AND FIND_IN_SET({int:current_group}, {raw:column}) = 0',
1617
		[
1618
			'board_list' => $boards[$access_list],
1619
			'blank_string' => '',
1620
			'current_group' => $id_group,
1621
			'group_id_string' => (string) $id_group,
1622
			'comma_group' => ',' . $id_group,
1623
			'column' => $access_list === 'allow' ? 'member_groups' : 'deny_member_groups',
1624
		]
1625
	);
1626
}
1627
1628
/**
1629
 * Membergroup was deleted? We need to detach that group from our members, too...
1630
 *
1631
 * @param int $id_group
1632
 * @package Membergroups
1633
 */
1634
function detachDeletedGroupFromMembers($id_group)
1635
{
1636
	$db = database();
1637
1638
	$updates = [];
1639
1640
	$db->query('', '
1641
		UPDATE {db_prefix}members
1642
		SET id_group = {int:regular_member}
1643
		WHERE id_group = {int:current_group}',
1644
		[
1645
			'regular_member' => 0,
1646
			'current_group' => $id_group,
1647
		]
1648
	);
1649
1650
	$db->fetchQuery('
1651
		SELECT 
1652
			id_member, additional_groups
1653
		FROM {db_prefix}members
1654
		WHERE FIND_IN_SET({string:current_group}, additional_groups) != 0',
1655
		[
1656
			'current_group' => $id_group,
1657
		]
1658
	)->fetch_callback(
1659
		function ($row) use (&$updates) {
1660
			$updates[$row['additional_groups']][] = $row['id_member'];
1661
		}
1662
	);
1663
1664
	require_once(SUBSDIR . '/Members.subs.php');
1665
	foreach ($updates as $additional_groups => $memberArray)
1666
	{
1667
		updateMemberData($memberArray, ['additional_groups' => implode(',', array_diff(explode(',', $additional_groups), [$id_group]))]);
1668
	}
1669
1670
}
1671
1672
/**
1673
 * Make the given group hidden. Hidden groups are stored in the additional_groups.
1674
 *
1675
 * @param int $id_group
1676
 * @package Membergroups
1677
 */
1678
function setGroupToHidden($id_group)
1679
{
1680
	$db = database();
1681
1682
	$updates = [];
1683
1684
	$db->fetchQuery('
1685
		SELECT 
1686
			id_member, additional_groups
1687
		FROM {db_prefix}members
1688
		WHERE id_group = {int:current_group}
1689
			AND FIND_IN_SET({int:current_group}, additional_groups) = 0',
1690
		[
1691
			'current_group' => $id_group,
1692
		]
1693
	)->fetch_callback(
1694
		function ($row) use (&$updates) {
1695
			$updates[$row['additional_groups']][] = $row['id_member'];
1696
		}
1697
	);
1698
1699
	require_once(SUBSDIR . '/Members.subs.php');
1700
	foreach ($updates as $additional_groups => $memberArray)
1701
	{
1702
		updateMemberData($memberArray, ['additional_groups' => implode(',', array_merge(explode(',', $additional_groups), [$id_group]))]);
1703
	}
1704
1705
	$db->query('', '
1706
		UPDATE {db_prefix}members
1707
		SET id_group = {int:regular_member}
1708
		WHERE id_group = {int:current_group}',
1709
		[
1710
			'regular_member' => 0,
1711
			'current_group' => $id_group,
1712
		]
1713
	);
1714
}
1715
1716
/**
1717
 * Make sure the setting to display membergroup key on the board index is valid.
1718
 * It updates the setting if necessary.
1719
 *
1720
 * @package Membergroups
1721
 */
1722
function validateShowGroupMembership()
1723
{
1724
	global $modSettings;
1725
1726
	$db = database();
1727
1728
	$request = $db->query('', '
1729
		SELECT
1730
		 	COUNT(*)
1731
		FROM {db_prefix}membergroups
1732
		WHERE group_type > {int:non_joinable}',
1733
		[
1734
			'non_joinable' => 1,
1735
		]
1736
	);
1737
	list ($have_joinable) = $request->fetch_row();
1738
	$request->free_result();
1739
1740
	// Do we need to update the setting?
1741
	if ((empty($modSettings['show_group_membership']) && $have_joinable) || (!empty($modSettings['show_group_membership']) && !$have_joinable))
1742
	{
1743
		updateSettings(['show_group_membership' => $have_joinable ? 1 : 0]);
1744
	}
1745
}
1746
1747
/**
1748
 * Detaches group moderators from a deleted group.
1749
 *
1750
 * @param int $id_group
1751
 * @package Membergroups
1752
 */
1753
function detachGroupModerators($id_group)
1754
{
1755
	$db = database();
1756
1757
	$db->query('', '
1758
		DELETE FROM {db_prefix}group_moderators
1759
		WHERE id_group = {int:current_group}',
1760
		[
1761
			'current_group' => $id_group,
1762
		]
1763
	);
1764
}
1765
1766
/**
1767
 * Get the id_member from the membergroup moderators.
1768
 *
1769
 * @param string[] $moderators
1770
 *
1771
 * @return int[]
1772
 * @package Membergroups
1773
 */
1774
function getIDMemberFromGroupModerators($moderators)
1775
{
1776
	$db = database();
1777
1778
	return $db->fetchQuery('
1779
		SELECT 
1780
			id_member
1781
		FROM {db_prefix}members
1782
		WHERE member_name IN ({array_string:moderators}) OR real_name IN ({array_string:moderators})
1783
		LIMIT ' . count($moderators),
1784
		[
1785
			'moderators' => $moderators,
1786
		]
1787
	)->fetch_callback(
1788
		function ($row) {
1789
			return $row['id_member'];
1790
		}
1791
	);
1792
}
1793
1794
/**
1795
 * Assign members to the membergroup moderators.
1796
 *
1797
 * @param int $id_group
1798
 * @param int[] $group_moderators
1799
 * @package Membergroups
1800
 */
1801
function assignGroupModerators($id_group, $group_moderators)
1802
{
1803
	$db = database();
1804
1805
	$mod_insert = [];
1806
	foreach ($group_moderators as $moderator)
1807
	{
1808
		$mod_insert[] = [$id_group, $moderator];
1809
	}
1810
1811
	$db->insert('insert',
1812
		'{db_prefix}group_moderators',
1813
		['id_group' => 'int', 'id_member' => 'int'],
1814
		$mod_insert,
1815
		['id_group', 'id_member']
1816
	);
1817
}
1818
1819
/**
1820
 * List moderators from a given membergroup.
1821
 *
1822
 * @param int $id_group
1823
 * @return array moderators as array(id => name)
1824
 * @package Membergroups
1825
 */
1826
function getGroupModerators($id_group)
1827
{
1828
	$db = database();
1829
1830
	$moderators = [];
1831
1832
	$db->fetchQuery('
1833
		SELECT 
1834
			mem.id_member, mem.real_name
1835
		FROM {db_prefix}group_moderators AS mods
1836
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1837
		WHERE mods.id_group = {int:current_group}',
1838
		[
1839
			'current_group' => $id_group,
1840
		]
1841
	)->fetch_callback(
1842
		function ($row) use (&$moderators) {
1843
			$moderators[$row['id_member']] = $row['real_name'];
1844
		}
1845
	);
1846
1847
	return $moderators;
1848
}
1849
1850
/**
1851
 * Lists all groups that inherit permission profiles from the given group.
1852
 *
1853
 * - If no group is specified, it will list any group that can be used
1854
 *
1855
 * @param int|bool $id_group
1856
 * @return array
1857
 * @package Membergroups
1858
 */
1859
function getInheritableGroups($id_group = false)
1860
{
1861
	global $modSettings;
1862
1863
	$db = database();
1864
1865
	$inheritable_groups = [];
1866
1867
	$db->fetchQuery('
1868
		SELECT 
1869
			id_group, group_name
1870
		FROM {db_prefix}membergroups
1871
		WHERE id_parent = {int:not_inherited}' . ($id_group === false ? '' : '
1872
			AND id_group != {int:current_group}') .
1873
		(empty($modSettings['permission_enable_postgroups']) ? '
1874
			AND min_posts = {int:min_posts}' : '') . (allowedTo('admin_forum') ? '' : '
1875
			AND group_type != {int:is_protected}') . '
1876
			AND id_group NOT IN (1, 3)',
1877
		[
1878
			'current_group' => $id_group,
1879
			'min_posts' => -1,
1880
			'not_inherited' => -2,
1881
			'is_protected' => 1,
1882
		]
1883
	)->fetch_callback(
1884
		function ($row) use (&$inheritable_groups) {
1885
			$inheritable_groups[$row['id_group']] = $row['group_name'];
1886
		}
1887
	);
1888
1889
	return $inheritable_groups;
1890
}
1891
1892
/**
1893
 * List all membergroups and prepares them to assign permissions to.
1894
 *
1895
 * @return array
1896
 * @package Membergroups
1897
 */
1898
function prepareMembergroupPermissions()
1899
{
1900
	global $modSettings, $txt;
1901
1902
	$db = database();
1903
1904
	// Start this with the guests/members.
1905
	$profile_groups = [
1906
		-1 => [
1907
			'id' => -1,
1908
			'name' => $txt['membergroups_guests'],
1909
			'color' => '',
1910
			'new_topic' => 'disallow',
1911
			'replies_own' => 'disallow',
1912
			'replies_any' => 'disallow',
1913
			'attachment' => 'disallow',
1914
			'children' => [],
1915
		],
1916
		0 => [
1917
			'id' => 0,
1918
			'name' => $txt['membergroups_members'],
1919
			'color' => '',
1920
			'new_topic' => 'disallow',
1921
			'replies_own' => 'disallow',
1922
			'replies_any' => 'disallow',
1923
			'attachment' => 'disallow',
1924
			'children' => [],
1925
		],
1926
	];
1927
1928
	$db->fetchQuery('
1929
		SELECT 
1930
			id_group, group_name, online_color, id_parent
1931
		FROM {db_prefix}membergroups
1932
		WHERE id_group != {int:admin_group}
1933
			' . (empty($modSettings['permission_enable_postgroups']) ? ' AND min_posts = {int:min_posts}' : '') . '
1934
		ORDER BY id_parent ASC',
1935
		[
1936
			'admin_group' => 1,
1937
			'min_posts' => -1,
1938
		]
1939
	)->fetch_callback(
1940
		function ($row) use (&$profile_groups) {
1941
			if ($row['id_parent'] == -2)
1942
			{
1943
				$profile_groups[$row['id_group']] = [
1944
					'id' => $row['id_group'],
1945
					'name' => $row['group_name'],
1946
					'color' => $row['online_color'],
1947
					'new_topic' => 'disallow',
1948
					'replies_own' => 'disallow',
1949
					'replies_any' => 'disallow',
1950
					'attachment' => 'disallow',
1951
					'children' => [],
1952
				];
1953
			}
1954
			elseif (isset($profile_groups[$row['id_parent']]))
1955
			{
1956
				$profile_groups[$row['id_parent']]['children'][] = $row['group_name'];
1957
			}
1958
		}
1959
	);
1960
1961
	return $profile_groups;
1962
}
1963
1964
/**
1965
 * Returns the groups that a user could see.
1966
 *
1967
 * - Ask and it will give you.
1968
 *
1969
 * @param int $id_member the id of a member
1970
 * @param bool $show_hidden true if hidden groups (that the user can moderate) should be loaded (default false)
1971
 * @param int $min_posts minimum number of posts for the group (-1 for non-post-based groups)
1972
 *
1973
 * @return array
1974
 * @package Membergroups
1975
 */
1976
function loadGroups($id_member, $show_hidden = false, $min_posts = -1)
1977
{
1978
	$db = database();
1979
1980
	$groups = [];
1981
	$db->fetchQuery('
1982
		SELECT 
1983
			mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
1984
		FROM {db_prefix}membergroups AS mg
1985
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
1986
		WHERE mg.min_posts = {int:min_posts}
1987
			AND mg.id_group != {int:moderator_group}' . ($show_hidden ? '' : '
1988
			AND mg.hidden = {int:not_hidden}') . '
1989
		ORDER BY mg.group_name',
1990
		[
1991
			'current_member' => $id_member,
1992
			'min_posts' => $min_posts,
1993
			'moderator_group' => 3,
1994
			'not_hidden' => 0,
1995
		]
1996
	)->fetch_callback(
1997
		function ($row) use (&$groups, $show_hidden) {
1998
			// Hide hidden groups!
1999
			if ($show_hidden && $row['hidden'] && !$row['can_moderate'])
2000
			{
2001
				return;
2002
			}
2003
2004
			$groups[$row['id_group']] = $row['group_name'];
2005
		}
2006
	);
2007
2008
	return $groups;
2009
}
2010
2011
/**
2012
 * Returns the groups that the current user can see.
2013
 *
2014
 * - Uses User::$info and allowedTo().
2015
 * - Does not include post-count based groups
2016
 *
2017
 * @return array
2018
 * @package Membergroups
2019
 */
2020
function accessibleGroups()
2021
{
2022
	$db = database();
2023
2024
	$groups = [];
2025
	$db->fetchQuery('
2026
		SELECT 
2027
			mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
2028
		FROM {db_prefix}membergroups AS mg
2029
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
2030
		WHERE mg.min_posts = {int:min_posts}
2031
			AND mg.id_group != {int:moderator_group}',
2032
		[
2033
			'current_member' => User::$info->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
2034
			'min_posts' => -1,
2035
			'moderator_group' => 3,
2036
		]
2037
	)->fetch_callback(
2038
		function ($row) use (&$groups) {
2039
			// Hide hidden groups!
2040
			if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
2041
			{
2042
				return;
2043
			}
2044
2045
			$groups[$row['id_group']] = $row['group_name'];
2046
		}
2047
	);
2048
2049
	asort($groups);
2050
2051
	return $groups;
2052
}
2053
2054
/**
2055
 * Finds the number of group requests in the system
2056
 *
2057
 * - Callback function for createList().
2058
 *
2059
 * @param string $where
2060
 * @param string[] $where_parameters
2061
 * @return int the count of group requests
2062
 * @package Membergroups
2063
 */
2064
function list_getGroupRequestCount($where, $where_parameters)
2065
{
2066
	$db = database();
2067
2068
	$request = $db->query('', '
2069
		SELECT 
2070
			COUNT(*)
2071
		FROM {db_prefix}log_group_requests AS lgr
2072
		WHERE ' . $where,
2073
		array_merge($where_parameters, [])
2074
	);
2075
	list ($totalRequests) = $request->fetch_row();
2076
	$request->free_result();
2077
2078
	return $totalRequests;
2079
}
2080
2081
/**
2082
 * Find the details of pending group requests
2083
 *
2084
 * - Callback function for createList()
2085
 *
2086
 * @param int $start The item to start with (for pagination purposes)
2087
 * @param int $items_per_page The number of items to show per page
2088
 * @param string $sort A string indicating how to sort the results
2089
 * @param string $where
2090
 * @param string[] $where_parameters
2091
 * @return array an array of group requests
2092
 * Each group request has:
2093
 *   'id'
2094
 *   'member_link'
2095
 *   'group_link'
2096
 *   'reason'
2097
 *   'time_submitted'
2098
 * @package Membergroups
2099
 */
2100
function list_getGroupRequests($start, $items_per_page, $sort, $where, $where_parameters)
2101
{
2102
	$db = database();
2103
2104
	return $db->fetchQuery('
2105
		SELECT 
2106
			lgr.id_request, lgr.id_member, lgr.id_group, lgr.time_applied, lgr.reason,
2107
			mem.member_name, mg.group_name, mg.online_color, mem.real_name
2108
		FROM {db_prefix}log_group_requests AS lgr
2109
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
2110
			INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
2111
		WHERE ' . $where . '
2112
		ORDER BY {raw:sort}
2113
		LIMIT ' . $items_per_page . '  OFFSET ' . $start,
2114
		array_merge($where_parameters, [
2115
			'sort' => $sort,
2116
		])
2117
	)->fetch_callback(
2118
		function ($row) {
2119
			return [
2120
				'id' => $row['id_request'],
2121
				'member_link' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['real_name']]) . '">' . $row['real_name'] . '</a>',
2122
				'group_link' => '<span style="color: ' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
2123
				'reason' => censor($row['reason']),
2124
				'time_submitted' => standardTime($row['time_applied']),
2125
			];
2126
		}
2127
	);
2128
}
2129
2130
/**
2131
 * Deletes old group requests.
2132
 *
2133
 * @param int[] $groups
2134
 * @package Membergroups
2135
 */
2136
function deleteGroupRequests($groups)
2137
{
2138
	$db = database();
2139
2140
	// Remove the evidence...
2141
	$db->query('', '
2142
		DELETE FROM {db_prefix}log_group_requests
2143
		WHERE id_request IN ({array_int:request_list})',
2144
		[
2145
			'request_list' => $groups,
2146
		]
2147
	);
2148
}
2149
2150
/**
2151
 * This function updates those members who match post-based
2152
 * membergroups in the database (restricted by parameter $members).
2153
 *
2154
 * @param int[]|null $members = null The members to update, null if all
2155
 * @param string[]|null $parameter2 = null
2156
 * @package Membergroups
2157
 */
2158
function updatePostGroupStats($members = null, $parameter2 = null)
2159
{
2160
	$db = database();
2161
2162
	// Parameter two is the updated columns: we should check to see if we base groups off any of these.
2163
	if ($parameter2 !== null && !in_array('posts', $parameter2))
2164
	{
2165
		return;
2166
	}
2167
2168
	$postgroups = Cache::instance()->get('updatePostGroupStats', 360);
2169
	if ($postgroups === null || $members === null)
2170
	{
2171
		// Fetch the postgroups!
2172
		$postgroups = [];
2173
		$db->fetchQuery('
2174
			SELECT 
2175
			 	id_group, min_posts
2176
			FROM {db_prefix}membergroups
2177
			WHERE min_posts != {int:min_posts}',
2178
			[
2179
				'min_posts' => -1,
2180
			]
2181
		)->fetch_callback(
2182
			function ($row) use (&$postgroups) {
2183
				$postgroups[$row['id_group']] = $row['min_posts'];
2184
			}
2185
		);
2186
2187
		// Sort them this way because if it's done with MySQL, it causes a filesort :(.
2188
		arsort($postgroups);
2189
2190 28
		Cache::instance()->put('updatePostGroupStats', $postgroups, 360);
2191
	}
2192
2193 28
	// Oh great, they've screwed their post-groups.
2194
	if (empty($postgroups))
2195 14
	{
2196
		return;
2197
	}
2198 16
2199 16
	// Set all membergroups from most posts to the least post.
2200
	$conditions = '';
2201
	$lastMin = 0;
2202 16
	foreach ($postgroups as $id => $min_posts)
2203 16
	{
2204
		$conditions .= '
2205
				WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
2206
		$lastMin = $min_posts;
2207
	}
2208
2209 16
	// A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
2210
	$db->query('', '
2211 16
		UPDATE {db_prefix}members
2212
		SET id_post_group = CASE ' . $conditions . '
2213 16
				ELSE 0
2214 16
			END' . ($members !== null ? '
2215
		WHERE id_member IN ({array_int:members})' : ''),
2216
		[
2217
			'members' => is_array($members) ? $members : [$members],
2218 16
		]
2219
	);
2220 16
}
2221
2222
/**
2223
 * Get the ids of the groups that are unassignable
2224 16
 *
2225
 * @param bool $ignore_protected To ignore protected groups
2226
 * @return int[]
2227
 */
2228
function getUnassignableGroups($ignore_protected)
2229
{
2230 16
	$db = database();
2231 16
2232 16
	return $db->fetchQuery('
2233
		SELECT 
2234
			id_group
2235 16
		FROM {db_prefix}membergroups
2236 16
		WHERE min_posts != {int:min_posts}' . ($ignore_protected ? '' : '
2237
			OR group_type = {int:is_protected}'),
2238
		[
2239
			'min_posts' => -1,
2240 16
			'is_protected' => 1,
2241
		]
2242 16
	)->fetch_callback(
2243
		function ($row) {
2244 16
			return $row['id_group'];
2245 16
		},
2246
		[-1, 3]
2247 16
	);
2248
}
2249
2250 16
/**
2251
 * Returns a list of groups that a member can be assigned to
2252
 *
2253
 * @return array
2254
 */
2255
function getGroupsList()
2256
{
2257
	global $txt;
2258
2259
	Txt::load('Profile');
2260
2261 5
	$db = database();
2262
	$member_groups = [
2263 5
		0 => [
2264
			'id' => 0,
2265
			'name' => $txt['no_primary_membergroup'],
2266
			'is_primary' => false,
2267 5
			'can_be_additional' => false,
2268
			'can_be_primary' => true,
2269
		]
2270 5
	];
2271
2272
	// Load membergroups, but only those groups the user can assign.
2273 5
	$db->fetchQuery('
2274
		SELECT 
2275 5
			group_name, id_group, hidden
2276 5
		FROM {db_prefix}membergroups
2277 5
		WHERE id_group != {int:moderator_group}
2278
			AND min_posts = {int:min_posts}' . (allowedTo('admin_forum') ? '' : '
2279
			AND group_type != {int:is_protected}') . '
2280
		ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name',
2281
		[
2282
			'moderator_group' => 3,
2283
			'min_posts' => -1,
2284
			'is_protected' => 1,
2285
			'newbie_group' => 4,
2286
		]
2287
	)->fetch_callback(
2288
		function ($row) use (&$member_groups) {
2289 2
			// We should skip the administrator group if they don't have the admin_forum permission!
2290
			if ($row['id_group'] == 1 && !allowedTo('admin_forum'))
2291 2
			{
2292
				return;
2293 2
			}
2294
2295 1
			$member_groups[$row['id_group']] = [
2296 2
				'id' => $row['id_group'],
2297 2
				'name' => $row['group_name'],
2298
				'hidden' => $row['hidden'],
2299
				'is_primary' => false,
2300
				'can_be_primary' => $row['hidden'] != 2,
2301
			];
2302
		}
2303
	);
2304
2305 2
	return $member_groups;
2306
}
2307