list_getMembergroups()   F
last analyzed

Complexity

Conditions 28
Paths 216

Size

Total Lines 218
Code Lines 116

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 97.9287

Importance

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

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 = array((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 = array(-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
			array(
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', array('group' => $row['group_name']), 'admin');
90
	}
91
92
	call_integration_hook('integrate_delete_membergroups', array($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
		array(
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
		array(
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
		array(
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
		array(
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
		array(
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
		array(
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
		array(
152
			'group_list' => $groups,
153
			'uninherited' => -2,
154
		)
155
	);
156
157
	// Update the additional groups of members.
158
	$updates = array();
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
		array(
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, array('additional_groups' => implode(',', array_diff(explode(',', $additional_groups), $groups))));
178
	}
179
180
	// No boards can provide access to these membergroups anymore.
181
	$updates = array();
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
		array(
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
			array(
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 = array('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(array('settings_updated' => time()));
257
258
	// Cleaning the input.
259
	if (!is_array($members))
260
	{
261
		$members = array((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 = array();
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
			array(
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', array('member' => $member), 'admin');
320
		}
321
322
		return true;
323
	}
324
	elseif (!is_array($groups))
0 ignored issues
show
introduced by
The condition is_array($groups) is always false.
Loading history...
325
	{
326
		$groups = array((int) $groups);
327
	}
328
	// Make sure all groups are integer.
329
	else
330
	{
331
		$groups = array_unique(array_map('intval', $groups));
332
	}
333
334
	// Fetch a list of groups members cannot be assigned to explicitly, and the group names of the ones we want.
335
	$implicitGroups = array(-1, 0, 3);
336
	$group_names = array();
337
	$group_details = membergroupsById($groups, 0, true);
338
	foreach ($group_details as $key => $row)
339
	{
340
		if ($row['min_posts'] != -1)
341
		{
342
			$implicitGroups[] = $row['id_group'];
343
		}
344
		else
345
		{
346
			$group_names[$row['id_group']] = $row['group_name'];
347
		}
348
	}
349
350
	// Now get rid of those groups.
351
	$groups = array_diff($groups, $implicitGroups);
352
353
	// Don't forget the protected groups.
354
	if (!allowedTo('admin_forum'))
355
	{
356
		$protected_groups = array(1);
357
		$db->fetchQuery('
358
			SELECT 
359
				id_group
360
			FROM {db_prefix}membergroups
361
			WHERE group_type = {int:is_protected}',
362
			array(
363
				'is_protected' => 1,
364
			)
365
		)->fetch_callback(
366
			function ($row) use (&$protected_groups) {
367
				$protected_groups[] = $row['id_group'];
368
			}
369
		);
370
371
		// If you're not an admin yourself, you can't touch protected groups!
372
		$groups = array_diff($groups, array_unique($protected_groups));
373
	}
374
375
	// Only continue if there are still groups and members left.
376
	if (empty($groups) || empty($members))
377
	{
378
		return false;
379
	}
380
381
	// First, reset those who have this as their primary group - this is the easy one.
382
	$log_inserts = $db->fetchQuery('
383
		SELECT 
384
			id_member, id_group
385
		FROM {db_prefix}members AS members
386
		WHERE id_group IN ({array_int:group_list})
387
			AND id_member IN ({array_int:member_list})',
388
		array(
389
			'group_list' => $groups,
390
			'member_list' => $members,
391
		)
392
	)->fetch_callback(
393
		function ($row) use ($group_names) {
394
			return array('group' => $group_names[$row['id_group']], 'member' => $row['id_member']);
395
		}
396
	);
397
398
	$db->query('', '
399
		UPDATE {db_prefix}members
400
		SET id_group = {int:regular_member}
401
		WHERE id_group IN ({array_int:group_list})
402
			AND id_member IN ({array_int:member_list})',
403
		array(
404
			'group_list' => $groups,
405
			'member_list' => $members,
406
			'regular_member' => 0,
407
		)
408
	);
409
410
	// Those who have it as part of their additional group must be updated the long way... sadly.
411
	$updates = array();
412
	$db->fetchQuery('
413
		SELECT 
414
			id_member, additional_groups
415
		FROM {db_prefix}members
416
		WHERE (FIND_IN_SET({raw:additional_groups_implode}, additional_groups) != 0)
417
			AND id_member IN ({array_int:member_list})
418
		LIMIT ' . count($members),
419
		array(
420
			'member_list' => $members,
421
			'additional_groups_implode' => implode(', additional_groups) != 0 OR FIND_IN_SET(', $groups),
422
		)
423
	)->fetch_callback(
424
		function ($row) use (&$updates, $groups, $group_names) {
425
			// What log entries must we make for this one, eh?
426
			foreach (explode(',', $row['additional_groups']) as $group)
427
			{
428
				if (in_array($group, $groups))
429
				{
430
					$log_inserts[] = array(
431
						'group' => $group_names[$group],
432
						'member' => $row['id_member']
433
					);
434
				}
435
			}
436
437
			$updates[$row['additional_groups']][] = $row['id_member'];
438
		}
439
	);
440
441
	require_once(SUBSDIR . '/Members.subs.php');
442
	foreach ($updates as $additional_groups => $memberArray)
443
	{
444
		updateMemberData($memberArray, array('additional_groups' => implode(',', array_diff(explode(',', $additional_groups), $groups))));
445
	}
446
447
	// Their post groups may have changed now...
448
	updatePostGroupStats($members);
449
450
	// Do the log.
451
	if (!empty($log_inserts) && featureEnabled('ml'))
452
	{
453
		foreach ($log_inserts as $extra)
454
		{
455
			logAction('removed_from_group', $extra, 'admin');
456
		}
457
	}
458
459
	// Mission successful.
460
	return true;
461
}
462
463
/**
464
 * Add one or more members to a membergroup.
465
 *
466
 * - Requires the manage_membergroups permission.
467
 * - Function has protection against adding members to implicit groups.
468
 * - Non-admins cannot add members to the admin group, or protected groups.
469
 *
470
 * @param int|int[] $members
471
 * @param int $group
472
 * @param string $type = 'auto' specifies whether the group is added as primary or as additional group.
473
 * Supported types:
474
 * - only_primary    - Assigns a membergroup as primary membergroup, but only
475
 *                     if a member has not yet a primary membergroup assigned,
476
 *                     unless the member is already part of the membergroup.
477
 * - only_additional - Assigns a membergroup to the additional membergroups,
478
 *                     unless the member is already part of the membergroup.
479
 * - force_primary   - Assigns a membergroup as primary membergroup no matter
480
 *                     what the previous primary membergroup was.
481
 * - auto            - Assigns a membergroup to the primary group if it's still
482
 *                     available. If not, assign it to the additional group.
483
 * @param bool $permissionCheckDone = false if true, it checks permission of the current user to add groups ('manage_membergroups')
484
 * @return bool success or failure
485
 * @package Membergroups
486
 */
487
function addMembersToGroup($members, $group, $type = 'auto', $permissionCheckDone = false)
488
{
489
	$db = database();
490
491
	// Show your licence, but only if it hasn't been done yet.
492
	if (!$permissionCheckDone)
493
	{
494
		isAllowedTo('manage_membergroups');
495
	}
496
497
	// Make sure we don't keep old stuff cached.
498
	updateSettings(array('settings_updated' => time()));
499
500
	$members = !is_array($members) ? array((int) $members) : array_unique(array_map('intval', $members));
501
502
	$group = (int) $group;
503
504
	// Some groups just don't like explicitly having members.
505
	$implicitGroups = array(-1, 0, 3);
506
	$group_names = array();
507
	$group_details = membergroupById($group, true);
508
	if ($group_details['min_posts'] != -1)
509
	{
510
		$implicitGroups[] = $group_details['id_group'];
511
	}
512
	else
513
	{
514
		$group_names[$group_details['id_group']] = $group_details['group_name'];
515
	}
516
517
	// Sorry, you can't join an implicit group.
518
	if (in_array($group, $implicitGroups) || empty($members))
519
	{
520
		return false;
521
	}
522
523
	// Only admins can add admins...
524
	if (!allowedTo('admin_forum') && $group == 1)
525
	{
526
		return false;
527
	}
528
	// ... and assign protected groups!
529
	elseif (!allowedTo('admin_forum') && $group_details['group_type'] == 1)
530
	{
531
		return false;
532
	}
533
534
	// Do the actual updates.
535
	if ($type === 'only_additional')
536
	{
537
		$db->query('', '
538
			UPDATE {db_prefix}members
539
			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
540
			WHERE id_member IN ({array_int:member_list})
541
				AND id_group != {int:id_group}
542
				AND FIND_IN_SET({int:id_group}, additional_groups) = 0',
543
			array(
544
				'member_list' => $members,
545
				'id_group' => $group,
546
				'id_group_string' => (string) $group,
547
				'id_group_string_extend' => ',' . $group,
548
				'blank_string' => '',
549
			)
550
		);
551
	}
552
	elseif ($type === 'only_primary' || $type === 'force_primary')
553
	{
554
		$db->query('', '
555
			UPDATE {db_prefix}members
556
			SET id_group = {int:id_group}
557
			WHERE id_member IN ({array_int:member_list})' . ($type === 'force_primary' ? '' : '
558
				AND id_group = {int:regular_group}
559
				AND FIND_IN_SET({int:id_group}, additional_groups) = 0'),
560
			array(
561
				'member_list' => $members,
562
				'id_group' => $group,
563
				'regular_group' => 0,
564
			)
565
		);
566
	}
567
	elseif ($type === 'auto')
568
	{
569
		$db->query('', '
570
			UPDATE {db_prefix}members
571
			SET
572
				id_group = CASE WHEN id_group = {int:regular_group} THEN {int:id_group} ELSE id_group END,
573
				additional_groups = CASE WHEN id_group = {int:id_group} THEN additional_groups
574
					WHEN additional_groups = {string:blank_string} THEN {string:id_group_string}
575
					ELSE CONCAT(additional_groups, {string:id_group_string_extend}) END
576
			WHERE id_member IN ({array_int:member_list})
577
				AND id_group != {int:id_group}
578
				AND FIND_IN_SET({int:id_group}, additional_groups) = 0',
579
			array(
580
				'member_list' => $members,
581
				'regular_group' => 0,
582
				'id_group' => $group,
583
				'blank_string' => '',
584
				'id_group_string' => (string) $group,
585
				'id_group_string_extend' => ',' . $group,
586
			)
587
		);
588
	}
589
	// Ack!!?  What happened?
590
	else
591
	{
592
		trigger_error('addMembersToGroup(): Unknown type \'' . $type . '\'', E_USER_WARNING);
593
	}
594
595
	call_integration_hook('integrate_add_members_to_group', array($members, $group_details, &$group_names));
596
597
	// Update their postgroup statistics.
598
	updatePostGroupStats($members);
599
600
	require_once(SOURCEDIR . '/Logging.php');
601
	foreach ($members as $member)
602
	{
603
		logAction('added_to_group', array('group' => $group_names[$group], 'member' => $member), 'admin');
604
	}
605
606
	return true;
607
}
608
609
/**
610
 * Gets the members of a supplied membergroup.
611
 *
612
 * - Returns them as a link for display.
613
 *
614
 * @param int[] $members
615
 * @param int $membergroup
616
 * @param int|null $limit = null
617
 * @return bool
618
 * @package Membergroups
619
 */
620
function listMembergroupMembers_Href(&$members, $membergroup, $limit = null)
621
{
622
	$db = database();
623
624
	$members = array();
625
	$db->fetchQuery('
626
		SELECT 
627
			id_member, real_name
628
		FROM {db_prefix}members
629
		WHERE id_group = {int:id_group} OR FIND_IN_SET({int:id_group}, additional_groups) != 0' . ($limit === null ? '' : '
630
		LIMIT ' . ($limit + 1)),
631
		array(
632
			'id_group' => $membergroup,
633
		)
634
	)->fetch_callback(
635
		function ($row) use (&$members) {
636
			$members[$row['id_member']] = '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['real_name']]) . '">' . $row['real_name'] . '</a>';
637
		}
638
	);
639
640
	// If there are more than $limit members, add a 'more' link.
641
	if ($limit !== null && count($members) > $limit)
642
	{
643
		array_pop($members);
644
645
		return true;
646
	}
647
648
	return false;
649
}
650
651
/**
652
 * Retrieve a list of (visible) membergroups used by the cache.
653
 *
654
 * @package Membergroups
655
 */
656
function cache_getMembergroupList()
657
{
658
	$db = database();
659
660
	$groupCache = $db->fetchQuery('
661
		SELECT 
662
			id_group, group_name, online_color
663
		FROM {db_prefix}membergroups
664
		WHERE min_posts = {int:min_posts}
665
			AND hidden = {int:not_hidden}
666
			AND id_group != {int:mod_group}
667
			AND online_color != {string:blank_string}
668
		ORDER BY group_name',
669
		array(
670
			'min_posts' => -1,
671
			'not_hidden' => 0,
672
			'mod_group' => 3,
673
			'blank_string' => '',
674
		)
675
	)->fetch_callback(
676
		function ($row) {
677
			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>';
678
		}
679
	);
680
681
	return array(
682
		'data' => $groupCache,
683
		'expires' => time() + 3600,
684
		'refresh_eval' => 'return $GLOBALS[\'modSettings\'][\'settings_updated\'] > ' . time() . ';',
685
	);
686
}
687
688
/**
689
 * Helper function to generate a list of membergroups for display.
690
 *
691
 * @param int $start not used
692
 * @param int $items_per_page not used
693
 * @param string $sort An SQL query indicating how to sort the results
694
 * @param string $membergroup_type Should be 'post_count' for post groups or 'regular' for other groups
695
 * @param int $user_id id of the member making the request
696
 * @param bool $include_hidden If true includes hidden groups if the user has permission
697
 * @param bool $include_all If true includes all groups the user can see
698
 * @param bool $aggregate
699
 * @param bool $count_permissions
700
 * @param int|null $pid - profile id
701
 *
702
 * @return array
703
 * @package Membergroups
704
 *
705
 */
706
function list_getMembergroups($start, $items_per_page, $sort, $membergroup_type, $user_id, $include_hidden, $include_all = false, $aggregate = false, $count_permissions = false, $pid = null)
707
{
708
	global $txt, $context;
709
710
	$db = database();
711
	Txt::load('Admin');
712 2
713
	// Start collecting the data.
714 2
	$groups = array();
715 2
	$group_ids = array();
716
	$parent_groups = array();
717
718 2
	if ($membergroup_type === 'all')
719 2
	{
720 2
		// Determine the number of ungrouped members.
721
		$num_members = countMembersInGroup(0);
722 2
723
		// Fill the context variable with 'Guests' and 'Regular Members'.
724
		$groups = array(
725
			-1 => array(
726
				'id_group' => -1,
727
				'group_name' => $txt['membergroups_guests'],
728
				'group_name_color' => $txt['membergroups_guests'],
729
				'min_posts' => 0,
730
				'desc' => '',
731
				'num_members' => $txt['membergroups_guests_na'],
732
				'icons' => '',
733
				'can_search' => false,
734
				'id_parent' => -2,
735
				'num_permissions' => array(
736
					'allowed' => 0,
737
					'denied' => 0,
738
				)
739
			),
740
			0 => array(
741
				'id_group' => 0,
742
				'group_name' => $txt['membergroups_members'],
743
				'group_name_color' => $txt['membergroups_members'],
744
				'min_posts' => 0,
745
				'desc' => '',
746
				'num_members' => $num_members,
747
				'icons' => '',
748
				'can_search' => true,
749
				'id_parent' => -2,
750
				'num_permissions' => array(
751
					'allowed' => 0,
752
					'denied' => 0,
753
				)
754
			),
755
		);
756
	}
757
758
	$db->fetchQuery('
759
		SELECT 
760
			mg.id_group, mg.group_name, mg.min_posts, mg.description, mg.group_type, mg.online_color,
761
			mg.hidden, mg.id_parent, mg.icons, COALESCE(gm.id_member, 0) AS can_moderate, 0 AS num_members
762 2
		FROM {db_prefix}membergroups AS mg
763
			LEFT JOIN {db_prefix}group_moderators AS gm ON (gm.id_group = mg.id_group AND gm.id_member = {int:current_member})
764
		WHERE mg.min_posts {raw:min_posts}' . ($include_all ? '' : '
765
			AND mg.id_group != {int:mod_group}
766
			AND mg.group_type != {int:is_protected}') . '
767
		ORDER BY {raw:sort}',
768 2
		array(
769
			'current_member' => $user_id,
770 2
			'min_posts' => ($membergroup_type === 'post_count' ? '!= -1' : '= -1'),
771
			'mod_group' => 3,
772
			'is_protected' => 1,
773 2
			'sort' => $sort,
774 2
		)
775 2
	)->fetch_callback(
776 2
		function ($row) use (&$parent_groups, &$groups, &$group_ids, &$include_hidden, $count_permissions, $aggregate) {
777 2
			global $txt;
778
779 2
			// We only list the groups they can see.
780
			if ($row['hidden'] && !$row['can_moderate'] && !$include_hidden)
781 2
			{
782
				return;
783
			}
784 2
785
			if ((int) $row['id_parent'] !== -2)
786
			{
787
				$parent_groups[] = (int) $row['id_parent'];
788
			}
789 2
790
			// If it's inherited, just add it as a child.
791
			if ($aggregate && (int) $row['id_parent'] !== -2)
792
			{
793
				if (isset($groups[$row['id_parent']]))
794
				{
795 2
					$groups[$row['id_parent']]['children'][$row['id_group']] = $row['group_name'];
796
				}
797
798
				return;
799
			}
800
801
			$row['icons'] = explode('#', $row['icons']);
802
			$row['id_group'] = (int) $row['id_group'];
803
			$groups[$row['id_group']] = array(
804
				'id_group' => $row['id_group'],
805 2
				'group_name' => $row['group_name'],
806
				'group_name_color' => empty($row['online_color']) ? $row['group_name'] : '<span style="color: ' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
807 2
				'min_posts' => (int) $row['min_posts'],
808 2
				'desc' => $row['description'],
809 2
				'online_color' => $row['online_color'],
810 2
				'type' => (int) $row['group_type'],
811 2
				'num_members' => (int) $row['num_members'],
812 2
				'moderators' => array(),
813 2
				'icons' => $row['icons'],
814 2
				'can_search' => $row['id_group'] !== 3,
815 2
				'id_parent' => (int) $row['id_parent'],
816
			);
817 2
818 2
			if ($count_permissions)
819 2
			{
820
				$groups[$row['id_group']]['num_permissions'] = array(
821
					'allowed' => $row['id_group'] === 1 ? '(' . $txt['permissions_all'] . ')' : 0,
822 2
					'denied' => $row['id_group'] === 1 ? '(' . $txt['permissions_none'] . ')' : 0,
823
				);
824
			}
825
826
			$include_hidden |= $row['can_moderate'];
827
			$group_ids[] = $row['id_group'];
828
		}
829
	);
830 2
831 2
	// If we found any membergroups, get the amount of members in them.
832 2
	if (!empty($group_ids))
833
	{
834
		if ($membergroup_type === 'post_count')
835
		{
836 2
			$groups_count = membersInGroups($group_ids);
837
		}
838 2
		else
839
		{
840
			$groups_count = membersInGroups(array(), $group_ids, $include_hidden);
841
		}
842
843
		// @todo not sure why += wouldn't = be enough?
844 2
		foreach ($groups_count as $group_id => $num_members)
845
		{
846
			$groups[$group_id]['num_members'] += $num_members;
847
		}
848 2
849
		$db->fetchQuery('
850 2
			SELECT 
851
				mods.id_group, mods.id_member, mem.member_name, mem.real_name
852
			FROM {db_prefix}group_moderators AS mods
853 2
				INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
854
			WHERE mods.id_group IN ({array_int:group_list})',
855
			array(
856
				'group_list' => $group_ids,
857
			)
858
		)->fetch_callback(
859
			function ($row) use (&$groups) {
860 2
				$groups[$row['id_group']]['moderators'][] = '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member'], 'name' => $row['real_name']]) . '">' . $row['real_name'] . '</a>';
861
			}
862 2
		);
863
	}
864
865 2
	if (!empty($parent_groups))
866
	{
867
		$all_group_names = array(
868
			-1 => $txt['membergroups_guests'],
869 2
			0 => $txt['membergroups_members']
870
		);
871
		$db->fetchQuery('
872
			SELECT 
873
				id_group, group_name
874
			FROM {db_prefix}membergroups
875
			WHERE id_group IN ({array_int:groups})',
876
			array(
877
				'groups' => $parent_groups,
878
			)
879
		)->fetch_callback(
880
			function ($row) use (&$all_group_names) {
881
				$all_group_names[$row['id_group']] = $row['group_name'];
882
			}
883
		);
884
	}
885
	foreach ($groups as $key => $group)
886
	{
887
		if ($group['id_parent'] != -2)
888
		{
889 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...
890
		}
891 2
	}
892
893 1
	// Apply manual sorting if the 'number of members' column is selected.
894
	if (substr($sort, 0, 1) === '1' || strpos($sort, ', 1') !== false)
895
	{
896
		$sort_ascending = strpos($sort, 'DESC') === false;
897
		$sort_array = array();
898 2
899
		foreach ($groups as $group)
900
		{
901
			$sort_array[] = $group['id_group'] != 3 ? (int) $group['num_members'] : -1;
902
		}
903
904
		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

904
		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

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

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