getIDMemberFromGroupModerators()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 16
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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