list_getMembergroups()   F
last analyzed

Complexity

Conditions 28
Paths 216

Size

Total Lines 218
Code Lines 113

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 97.9287

Importance

Changes 0
Metric Value
cc 28
eloc 113
nc 216
nop 10
dl 0
loc 218
ccs 52
cts 94
cp 0.5532
crap 97.9287
rs 2.5066
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 dev
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 a 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 ($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
	// Don't forget 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 the 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, $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 licence, 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(0);
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 amount 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 ones 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 a 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
			switch ($known_properties[$name]['type'])
1539
			{
1540
				case 'string':
1541
					$values['subs_' . $name] = Util::htmlspecialchars((string) $value);
1542
					break;
1543
				default:
1544
					$values['subs_' . $name] = (int) $value;
1545
			}
1546
		}
1547
	}
1548
1549
	if (empty($values))
1550
	{
1551
		return;
1552
	}
1553
1554
	$db->query('', '
1555
		UPDATE {db_prefix}membergroups
1556
		SET ' . implode(', ', $updates) . '
1557
		WHERE id_group = {int:current_group}',
1558
		$values
1559
	);
1560
}
1561
1562
/**
1563
 * Detaches a membergroup from the boards listed in $boards.
1564
 *
1565
 * @param int $id_group
1566
 * @param array $boards
1567
 * @param string $access_list ('allow', 'deny')
1568
 * @package Membergroups
1569
 */
1570
function detachGroupFromBoards($id_group, $boards, $access_list)
1571
{
1572
	$db = database();
1573
1574
	// Find all boards in whose access list this group is in, but shouldn't be.
1575
	$db->fetchQuery('
1576
		SELECT 
1577
			id_board, {raw:column}
1578
		FROM {db_prefix}boards
1579
		WHERE FIND_IN_SET({string:current_group}, {raw:column}) != 0' . (empty($boards[$access_list]) ? '' : '
1580
			AND id_board NOT IN ({array_int:board_access_list})'),
1581
		[
1582
			'current_group' => $id_group,
1583
			'board_access_list' => $boards[$access_list],
1584
			'column' => $access_list === 'allow' ? 'member_groups' : 'deny_member_groups',
1585
		]
1586
	)->fetch_callback(
1587
		function ($row) use ($id_group, $access_list, $db) {
1588
			$db->query('', '
1589
				UPDATE {db_prefix}boards
1590
				SET {raw:column} = {string:member_group_access}
1591
				WHERE id_board = {int:current_board}',
1592
				[
1593
					'current_board' => $row['id_board'],
1594
					'member_group_access' => implode(',', array_diff(explode(',', $row['member_groups']), [$id_group])),
1595
					'column' => $access_list === 'allow' ? 'member_groups' : 'deny_member_groups',
1596
				]
1597
			);
1598
		}
1599
	);
1600
}
1601
1602
/**
1603
 * Assigns the given group $id_group to the boards specified, for
1604
 * the 'allow' or 'deny' list.
1605
 *
1606
 * @param int $id_group
1607
 * @param array $boards
1608
 * @param string $access_list ('allow', 'deny')
1609
 * @package Membergroups
1610
 */
1611
function assignGroupToBoards($id_group, $boards, $access_list)
1612
{
1613
	$db = database();
1614
1615
	$db->query('', '
1616
		UPDATE {db_prefix}boards
1617
		SET {raw:column} = CASE WHEN {raw:column} = {string:blank_string} THEN {string:group_id_string} ELSE CONCAT({raw:column}, {string:comma_group}) END
1618
		WHERE id_board IN ({array_int:board_list})
1619
			AND FIND_IN_SET({int:current_group}, {raw:column}) = 0',
1620
		[
1621
			'board_list' => $boards[$access_list],
1622
			'blank_string' => '',
1623
			'current_group' => $id_group,
1624
			'group_id_string' => (string) $id_group,
1625
			'comma_group' => ',' . $id_group,
1626
			'column' => $access_list === 'allow' ? 'member_groups' : 'deny_member_groups',
1627
		]
1628
	);
1629
}
1630
1631
/**
1632
 * Membergroup was deleted? We need to detach that group from our members, too...
1633
 *
1634
 * @param int $id_group
1635
 * @package Membergroups
1636
 */
1637
function detachDeletedGroupFromMembers($id_group)
1638
{
1639
	$db = database();
1640
1641
	$updates = [];
1642
1643
	$db->query('', '
1644
		UPDATE {db_prefix}members
1645
		SET id_group = {int:regular_member}
1646
		WHERE id_group = {int:current_group}',
1647
		[
1648
			'regular_member' => 0,
1649
			'current_group' => $id_group,
1650
		]
1651
	);
1652
1653
	$db->fetchQuery('
1654
		SELECT 
1655
			id_member, additional_groups
1656
		FROM {db_prefix}members
1657
		WHERE FIND_IN_SET({string:current_group}, additional_groups) != 0',
1658
		[
1659
			'current_group' => $id_group,
1660
		]
1661
	)->fetch_callback(
1662
		function ($row) use (&$updates) {
1663
			$updates[$row['additional_groups']][] = $row['id_member'];
1664
		}
1665
	);
1666
1667
	require_once(SUBSDIR . '/Members.subs.php');
1668
	foreach ($updates as $additional_groups => $memberArray)
1669
	{
1670
		updateMemberData($memberArray, ['additional_groups' => implode(',', array_diff(explode(',', $additional_groups), [$id_group]))]);
1671
	}
1672
1673
}
1674
1675
/**
1676
 * Make the given group hidden. Hidden groups are stored in the additional_groups.
1677
 *
1678
 * @param int $id_group
1679
 * @package Membergroups
1680
 */
1681
function setGroupToHidden($id_group)
1682
{
1683
	$db = database();
1684
1685
	$updates = [];
1686
1687
	$db->fetchQuery('
1688
		SELECT 
1689
			id_member, additional_groups
1690
		FROM {db_prefix}members
1691
		WHERE id_group = {int:current_group}
1692
			AND FIND_IN_SET({int:current_group}, additional_groups) = 0',
1693
		[
1694
			'current_group' => $id_group,
1695
		]
1696
	)->fetch_callback(
1697
		function ($row) use (&$updates) {
1698
			$updates[$row['additional_groups']][] = $row['id_member'];
1699
		}
1700
	);
1701
1702
	require_once(SUBSDIR . '/Members.subs.php');
1703
	foreach ($updates as $additional_groups => $memberArray)
1704
	{
1705
		updateMemberData($memberArray, ['additional_groups' => implode(',', array_merge(explode(',', $additional_groups), [$id_group]))]);
1706
	}
1707
1708
	$db->query('', '
1709
		UPDATE {db_prefix}members
1710
		SET id_group = {int:regular_member}
1711
		WHERE id_group = {int:current_group}',
1712
		[
1713
			'regular_member' => 0,
1714
			'current_group' => $id_group,
1715
		]
1716
	);
1717
}
1718
1719
/**
1720
 * Make sure the setting to display membergroup key on the board index is valid.
1721
 * It updates the setting if necessary.
1722
 *
1723
 * @package Membergroups
1724
 */
1725
function validateShowGroupMembership()
1726
{
1727
	global $modSettings;
1728
1729
	$db = database();
1730
1731
	$request = $db->query('', '
1732
		SELECT
1733
		 	COUNT(*)
1734
		FROM {db_prefix}membergroups
1735
		WHERE group_type > {int:non_joinable}',
1736
		[
1737
			'non_joinable' => 1,
1738
		]
1739
	);
1740
	list ($have_joinable) = $request->fetch_row();
1741
	$request->free_result();
1742
1743
	// Do we need to update the setting?
1744
	if ((empty($modSettings['show_group_membership']) && $have_joinable) || (!empty($modSettings['show_group_membership']) && !$have_joinable))
1745
	{
1746
		updateSettings(['show_group_membership' => $have_joinable ? 1 : 0]);
1747
	}
1748
}
1749
1750
/**
1751
 * Detaches group moderators from a deleted group.
1752
 *
1753
 * @param int $id_group
1754
 * @package Membergroups
1755
 */
1756
function detachGroupModerators($id_group)
1757
{
1758
	$db = database();
1759
1760
	$db->query('', '
1761
		DELETE FROM {db_prefix}group_moderators
1762
		WHERE id_group = {int:current_group}',
1763
		[
1764
			'current_group' => $id_group,
1765
		]
1766
	);
1767
}
1768
1769
/**
1770
 * Get the id_member from the membergroup moderators.
1771
 *
1772
 * @param string[] $moderators
1773
 *
1774
 * @return int[]
1775
 * @package Membergroups
1776
 */
1777
function getIDMemberFromGroupModerators($moderators)
1778
{
1779
	$db = database();
1780
1781
	return $db->fetchQuery('
1782
		SELECT 
1783
			id_member
1784
		FROM {db_prefix}members
1785
		WHERE member_name IN ({array_string:moderators}) OR real_name IN ({array_string:moderators})
1786
		LIMIT ' . count($moderators),
1787
		[
1788
			'moderators' => $moderators,
1789
		]
1790
	)->fetch_callback(
1791
		function ($row) {
1792
			return $row['id_member'];
1793
		}
1794
	);
1795
}
1796
1797
/**
1798
 * Assign members to the membergroup moderators.
1799
 *
1800
 * @param int $id_group
1801
 * @param int[] $group_moderators
1802
 * @package Membergroups
1803
 */
1804
function assignGroupModerators($id_group, $group_moderators)
1805
{
1806
	$db = database();
1807
1808
	$mod_insert = [];
1809
	foreach ($group_moderators as $moderator)
1810
	{
1811
		$mod_insert[] = [$id_group, $moderator];
1812
	}
1813
1814
	$db->insert('insert',
1815
		'{db_prefix}group_moderators',
1816
		['id_group' => 'int', 'id_member' => 'int'],
1817
		$mod_insert,
1818
		['id_group', 'id_member']
1819
	);
1820
}
1821
1822
/**
1823
 * List moderators from a given membergroup.
1824
 *
1825
 * @param int $id_group
1826
 * @return array moderators as array(id => name)
1827
 * @package Membergroups
1828
 */
1829
function getGroupModerators($id_group)
1830
{
1831
	$db = database();
1832
1833
	$moderators = [];
1834
1835
	$db->fetchQuery('
1836
		SELECT 
1837
			mem.id_member, mem.real_name
1838
		FROM {db_prefix}group_moderators AS mods
1839
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1840
		WHERE mods.id_group = {int:current_group}',
1841
		[
1842
			'current_group' => $id_group,
1843
		]
1844
	)->fetch_callback(
1845
		function ($row) use (&$moderators) {
1846
			$moderators[$row['id_member']] = $row['real_name'];
1847
		}
1848
	);
1849
1850
	return $moderators;
1851
}
1852
1853
/**
1854
 * Lists all groups which inherit permission profiles from the given group.
1855
 *
1856
 * - If no group is specified it will list any group that can be used
1857
 *
1858
 * @param int|bool $id_group
1859
 * @return array
1860
 * @package Membergroups
1861
 */
1862
function getInheritableGroups($id_group = false)
1863
{
1864
	global $modSettings;
1865
1866
	$db = database();
1867
1868
	$inheritable_groups = [];
1869
1870
	$db->fetchQuery('
1871
		SELECT 
1872
			id_group, group_name
1873
		FROM {db_prefix}membergroups
1874
		WHERE id_parent = {int:not_inherited}' . ($id_group === false ? '' : '
1875
			AND id_group != {int:current_group}') .
1876
		(empty($modSettings['permission_enable_postgroups']) ? '
1877
			AND min_posts = {int:min_posts}' : '') . (allowedTo('admin_forum') ? '' : '
1878
			AND group_type != {int:is_protected}') . '
1879
			AND id_group NOT IN (1, 3)',
1880
		[
1881
			'current_group' => $id_group,
1882
			'min_posts' => -1,
1883
			'not_inherited' => -2,
1884
			'is_protected' => 1,
1885
		]
1886
	)->fetch_callback(
1887
		function ($row) use (&$inheritable_groups) {
1888
			$inheritable_groups[$row['id_group']] = $row['group_name'];
1889
		}
1890
	);
1891
1892
	return $inheritable_groups;
1893
}
1894
1895
/**
1896
 * List all membergroups and prepares them to assign permissions to..
1897
 *
1898
 * @return array
1899
 * @package Membergroups
1900
 */
1901
function prepareMembergroupPermissions()
1902
{
1903
	global $modSettings, $txt;
1904
1905
	$db = database();
1906
1907
	// Start this with the guests/members.
1908
	$profile_groups = [
1909
		-1 => [
1910
			'id' => -1,
1911
			'name' => $txt['membergroups_guests'],
1912
			'color' => '',
1913
			'new_topic' => 'disallow',
1914
			'replies_own' => 'disallow',
1915
			'replies_any' => 'disallow',
1916
			'attachment' => 'disallow',
1917
			'children' => [],
1918
		],
1919
		0 => [
1920
			'id' => 0,
1921
			'name' => $txt['membergroups_members'],
1922
			'color' => '',
1923
			'new_topic' => 'disallow',
1924
			'replies_own' => 'disallow',
1925
			'replies_any' => 'disallow',
1926
			'attachment' => 'disallow',
1927
			'children' => [],
1928
		],
1929
	];
1930
1931
	$db->fetchQuery('
1932
		SELECT 
1933
			id_group, group_name, online_color, id_parent
1934
		FROM {db_prefix}membergroups
1935
		WHERE id_group != {int:admin_group}
1936
			' . (empty($modSettings['permission_enable_postgroups']) ? ' AND min_posts = {int:min_posts}' : '') . '
1937
		ORDER BY id_parent ASC',
1938
		[
1939
			'admin_group' => 1,
1940
			'min_posts' => -1,
1941
		]
1942
	)->fetch_callback(
1943
		function ($row) use (&$profile_groups) {
1944
			if ($row['id_parent'] == -2)
1945
			{
1946
				$profile_groups[$row['id_group']] = [
1947
					'id' => $row['id_group'],
1948
					'name' => $row['group_name'],
1949
					'color' => $row['online_color'],
1950
					'new_topic' => 'disallow',
1951
					'replies_own' => 'disallow',
1952
					'replies_any' => 'disallow',
1953
					'attachment' => 'disallow',
1954
					'children' => [],
1955
				];
1956
			}
1957
			elseif (isset($profile_groups[$row['id_parent']]))
1958
			{
1959
				$profile_groups[$row['id_parent']]['children'][] = $row['group_name'];
1960
			}
1961
		}
1962
	);
1963
1964
	return $profile_groups;
1965
}
1966
1967
/**
1968
 * Returns the groups that a user could see.
1969
 *
1970
 * - Ask and it will give you.
1971
 *
1972
 * @param int $id_member the id of a member
1973
 * @param bool $show_hidden true if hidden groups (that the user can moderate) should be loaded (default false)
1974
 * @param int $min_posts minimum number of posts for the group (-1 for non-post based groups)
1975
 *
1976
 * @return array
1977
 * @package Membergroups
1978
 */
1979
function loadGroups($id_member, $show_hidden = false, $min_posts = -1)
1980
{
1981
	$db = database();
1982
1983
	$groups = [];
1984
	$db->fetchQuery('
1985
		SELECT 
1986
			mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
1987
		FROM {db_prefix}membergroups AS mg
1988
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
1989
		WHERE mg.min_posts = {int:min_posts}
1990
			AND mg.id_group != {int:moderator_group}' . ($show_hidden ? '' : '
1991
			AND mg.hidden = {int:not_hidden}') . '
1992
		ORDER BY mg.group_name',
1993
		[
1994
			'current_member' => $id_member,
1995
			'min_posts' => $min_posts,
1996
			'moderator_group' => 3,
1997
			'not_hidden' => 0,
1998
		]
1999
	)->fetch_callback(
2000
		function ($row) use (&$groups, $show_hidden) {
2001
			// Hide hidden groups!
2002
			if ($show_hidden && $row['hidden'] && !$row['can_moderate'])
2003
			{
2004
				return;
2005
			}
2006
2007
			$groups[$row['id_group']] = $row['group_name'];
2008
		}
2009
	);
2010
2011
	return $groups;
2012
}
2013
2014
/**
2015
 * Returns the groups that the current user can see.
2016
 *
2017
 * - uses User::$info and allowedTo().
2018
 * - does not include post count based groups
2019
 *
2020
 * @return array
2021
 * @package Membergroups
2022
 */
2023
function accessibleGroups()
2024
{
2025
	$db = database();
2026
2027
	$groups = [];
2028
	$db->fetchQuery('
2029
		SELECT 
2030
			mg.id_group, mg.group_name, COALESCE(gm.id_member, 0) AS can_moderate, mg.hidden
2031
		FROM {db_prefix}membergroups AS mg
2032
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
2033
		WHERE mg.min_posts = {int:min_posts}
2034
			AND mg.id_group != {int:moderator_group}',
2035
		[
2036
			'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...
2037
			'min_posts' => -1,
2038
			'moderator_group' => 3,
2039
		]
2040
	)->fetch_callback(
2041
		function ($row) use (&$groups) {
2042
			// Hide hidden groups!
2043
			if ($row['hidden'] && !$row['can_moderate'] && !allowedTo('manage_membergroups'))
2044
			{
2045
				return;
2046
			}
2047
2048
			$groups[$row['id_group']] = $row['group_name'];
2049
		}
2050
	);
2051
2052
	asort($groups);
2053
2054
	return $groups;
2055
}
2056
2057
/**
2058
 * Finds the number of group requests in the system
2059
 *
2060
 * - Callback function for createList().
2061
 *
2062
 * @param string $where
2063
 * @param string[] $where_parameters
2064
 * @return int the count of group requests
2065
 * @package Membergroups
2066
 */
2067
function list_getGroupRequestCount($where, $where_parameters)
2068
{
2069
	$db = database();
2070
2071
	$request = $db->query('', '
2072
		SELECT 
2073
			COUNT(*)
2074
		FROM {db_prefix}log_group_requests AS lgr
2075
		WHERE ' . $where,
2076
		array_merge($where_parameters, [])
2077
	);
2078
	list ($totalRequests) = $request->fetch_row();
2079
	$request->free_result();
2080
2081
	return $totalRequests;
2082
}
2083
2084
/**
2085
 * Find the details of pending group requests
2086
 *
2087
 * - Callback function for createList()
2088
 *
2089
 * @param int $start The item to start with (for pagination purposes)
2090
 * @param int $items_per_page The number of items to show per page
2091
 * @param string $sort A string indicating how to sort the results
2092
 * @param string $where
2093
 * @param string[] $where_parameters
2094
 * @return array an array of group requests
2095
 * Each group request has:
2096
 *   'id'
2097
 *   'member_link'
2098
 *   'group_link'
2099
 *   'reason'
2100
 *   'time_submitted'
2101
 * @package Membergroups
2102
 */
2103
function list_getGroupRequests($start, $items_per_page, $sort, $where, $where_parameters)
2104
{
2105
	$db = database();
2106
2107
	return $db->fetchQuery('
2108
		SELECT 
2109
			lgr.id_request, lgr.id_member, lgr.id_group, lgr.time_applied, lgr.reason,
2110
			mem.member_name, mg.group_name, mg.online_color, mem.real_name
2111
		FROM {db_prefix}log_group_requests AS lgr
2112
			INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
2113
			INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
2114
		WHERE ' . $where . '
2115
		ORDER BY {raw:sort}
2116
		LIMIT ' . $items_per_page . '  OFFSET ' . $start,
2117
		array_merge($where_parameters, [
2118
			'sort' => $sort,
2119
		])
2120
	)->fetch_callback(
2121
		function ($row) {
2122
			return [
2123
				'id' => $row['id_request'],
2124
				'member_link' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['real_name']]) . '">' . $row['real_name'] . '</a>',
2125
				'group_link' => '<span style="color: ' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
2126
				'reason' => censor($row['reason']),
2127
				'time_submitted' => standardTime($row['time_applied']),
2128
			];
2129
		}
2130
	);
2131
}
2132
2133
/**
2134
 * Deletes old group requests.
2135
 *
2136
 * @param int[] $groups
2137
 * @package Membergroups
2138
 */
2139
function deleteGroupRequests($groups)
2140
{
2141
	$db = database();
2142
2143
	// Remove the evidence...
2144
	$db->query('', '
2145
		DELETE FROM {db_prefix}log_group_requests
2146
		WHERE id_request IN ({array_int:request_list})',
2147
		[
2148
			'request_list' => $groups,
2149
		]
2150
	);
2151
}
2152
2153
/**
2154
 * This function updates those members who match post-based
2155
 * membergroups in the database (restricted by parameter $members).
2156
 *
2157
 * @param int[]|null $members = null The members to update, null if all
2158
 * @param string[]|null $parameter2 = null
2159
 * @package Membergroups
2160
 */
2161
function updatePostGroupStats($members = null, $parameter2 = null)
2162
{
2163
	$db = database();
2164
2165
	// Parameter two is the updated columns: we should check to see if we base groups off any of these.
2166
	if ($parameter2 !== null && !in_array('posts', $parameter2))
2167
	{
2168
		return;
2169
	}
2170
2171
	$postgroups = Cache::instance()->get('updatePostGroupStats', 360);
2172
	if ($postgroups === null || $members === null)
2173
	{
2174
		// Fetch the postgroups!
2175
		$postgroups = [];
2176
		$db->fetchQuery('
2177
			SELECT 
2178
			 	id_group, min_posts
2179
			FROM {db_prefix}membergroups
2180
			WHERE min_posts != {int:min_posts}',
2181
			[
2182
				'min_posts' => -1,
2183
			]
2184
		)->fetch_callback(
2185
			function ($row) use (&$postgroups) {
2186
				$postgroups[$row['id_group']] = $row['min_posts'];
2187
			}
2188
		);
2189
2190 28
		// Sort them this way because if it's done with MySQL it causes a filesort :(.
2191
		arsort($postgroups);
2192
2193 28
		Cache::instance()->put('updatePostGroupStats', $postgroups, 360);
2194
	}
2195 14
2196
	// Oh great, they've screwed their post groups.
2197
	if (empty($postgroups))
2198 16
	{
2199 16
		return;
2200
	}
2201
2202 16
	// Set all membergroups from most posts to the least posts.
2203 16
	$conditions = '';
2204
	$lastMin = 0;
2205
	foreach ($postgroups as $id => $min_posts)
2206
	{
2207
		$conditions .= '
2208
				WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
2209 16
		$lastMin = $min_posts;
2210
	}
2211 16
2212
	// A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
2213 16
	$db->query('', '
2214 16
		UPDATE {db_prefix}members
2215
		SET id_post_group = CASE ' . $conditions . '
2216
				ELSE 0
2217
			END' . ($members !== null ? '
2218 16
		WHERE id_member IN ({array_int:members})' : ''),
2219
		[
2220 16
			'members' => is_array($members) ? $members : [$members],
2221
		]
2222
	);
2223
}
2224 16
2225
/**
2226
 * Get the ids of the groups that are unassignable
2227
 *
2228
 * @param bool $ignore_protected To ignore protected groups
2229
 * @return int[]
2230 16
 */
2231 16
function getUnassignableGroups($ignore_protected)
2232 16
{
2233
	$db = database();
2234
2235 16
	return $db->fetchQuery('
2236 16
		SELECT 
2237
			id_group
2238
		FROM {db_prefix}membergroups
2239
		WHERE min_posts != {int:min_posts}' . ($ignore_protected ? '' : '
2240 16
			OR group_type = {int:is_protected}'),
2241
		[
2242 16
			'min_posts' => -1,
2243
			'is_protected' => 1,
2244 16
		]
2245 16
	)->fetch_callback(
2246
		function ($row) {
2247 16
			return $row['id_group'];
2248
		},
2249
		[-1, 3]
2250 16
	);
2251
}
2252
2253
/**
2254
 * Returns a list of groups that a member can be assigned to
2255
 *
2256
 * @return array
2257
 */
2258
function getGroupsList()
2259
{
2260
	global $txt;
2261 5
2262
	Txt::load('Profile');
2263 5
2264
	$db = database();
2265
	$member_groups = [
2266
		0 => [
2267 5
			'id' => 0,
2268
			'name' => $txt['no_primary_membergroup'],
2269
			'is_primary' => false,
2270 5
			'can_be_additional' => false,
2271
			'can_be_primary' => true,
2272
		]
2273 5
	];
2274
2275 5
	// Load membergroups, but only those groups the user can assign.
2276 5
	$db->fetchQuery('
2277 5
		SELECT 
2278
			group_name, id_group, hidden
2279
		FROM {db_prefix}membergroups
2280
		WHERE id_group != {int:moderator_group}
2281
			AND min_posts = {int:min_posts}' . (allowedTo('admin_forum') ? '' : '
2282
			AND group_type != {int:is_protected}') . '
2283
		ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name',
2284
		[
2285
			'moderator_group' => 3,
2286
			'min_posts' => -1,
2287
			'is_protected' => 1,
2288
			'newbie_group' => 4,
2289 2
		]
2290
	)->fetch_callback(
2291 2
		function ($row) use (&$member_groups) {
2292
			// We should skip the administrator group if they don't have the admin_forum permission!
2293 2
			if ($row['id_group'] == 1 && !allowedTo('admin_forum'))
2294
			{
2295 1
				return;
2296 2
			}
2297 2
2298
			$member_groups[$row['id_group']] = [
2299
				'id' => $row['id_group'],
2300
				'name' => $row['group_name'],
2301
				'hidden' => $row['hidden'],
2302
				'is_primary' => false,
2303
				'can_be_primary' => $row['hidden'] != 2,
2304
			];
2305 2
		}
2306
	);
2307
2308
	return $member_groups;
2309
}
2310