ManageMembers::_multiMembersAction()   F
last analyzed

Complexity

Conditions 24
Paths 207

Size

Total Lines 110
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 600

Importance

Changes 0
Metric Value
cc 24
eloc 55
nc 207
nop 0
dl 0
loc 110
ccs 0
cts 77
cp 0
crap 600
rs 3.2458
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Show a list of members or a selection of members.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 Beta 1
14
 *
15
 */
16
17
namespace ElkArte\AdminController;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Action;
21
use ElkArte\Cache\Cache;
22
use ElkArte\Helper\Util;
23
use ElkArte\Languages\Txt;
24
use ElkArte\User;
25
26
/**
27
 * ManageMembers controller deals with members administration, approval,
28
 * admin-visible list, and search in it.
29
 *
30
 * @package Members
31
 */
32
class ManageMembers extends AbstractController
33
{
34
	/** @var array Holds various setting conditions for the current action */
35
	protected array $conditions;
36
37
	/** @var array Holds the members that the action is being applied to */
38
	protected array $member_info;
39
40
	/**
41
	 * The main entrance point for the Manage Members screen.
42
	 *
43
	 * What it does:
44
	 *
45
	 * - As everywhere else, it calls a function based on the given sub-action.
46
	 * - Called by ?action=admin;area=viewmembers.
47
	 * - Requires the moderate_forum permission.
48
	 *
49
	 * @event integrate_manage_members used to add subactions and tabs
50
	 * @uses ManageMembers template
51
	 * @uses ManageMembers language file.
52
	 * @see  AbstractCowntroller::action_index()
53
	 */
54
	public function action_index()
55
	{
56
		global $txt, $context, $modSettings;
57
58
		// Load the essentials.
59
		Txt::load('ManageMembers');
60
		theme()->getTemplates()->load('ManageMembers');
61
62
		$subActions = [
63
			'all' => [
64
				'controller' => $this,
65
				'function' => 'action_list',
66
				'permission' => 'moderate_forum'],
67
			'approve' => [
68
				'controller' => $this,
69
				'function' => 'action_approve',
70
				'permission' => 'moderate_forum'],
71
			'browse' => [
72
				'controller' => $this,
73
				'function' => 'action_browse',
74
				'permission' => 'moderate_forum'],
75
			'search' => [
76
				'controller' => $this,
77
				'function' => 'action_search',
78
				'permission' => 'moderate_forum'],
79
			'query' => [
80
				'controller' => $this,
81
				'function' => 'action_list',
82
				'permission' => 'moderate_forum'],
83
		];
84
85
		// Prepare our action control
86
		$action = new Action();
87
88
		// Default to sub action 'all', needed for the tabs array below
89
		$subAction = $action->initialize($subActions, 'all');
90
91
		// Get counts on every type of activation - for sections and filtering alike.
92
		require_once(SUBSDIR . '/Members.subs.php');
93
94
		$context['awaiting_activation'] = 0;
95
		$context['awaiting_approval'] = 0;
96
		$context['activation_numbers'] = countInactiveMembers();
97
98
		foreach ($context['activation_numbers'] as $activation_type => $total_members)
99
		{
100
			if (in_array($activation_type, [0, 2], true))
101
			{
102
				$context['awaiting_activation'] += $total_members;
103
			}
104
			elseif (in_array($activation_type, [3, 4, 5], true))
105
			{
106
				$context['awaiting_approval'] += $total_members;
107
			}
108
		}
109
110
		// Last items for the template
111
		$context['page_title'] = $txt['admin_members'];
112
		$context['sub_action'] = $subAction;
113
114
		// For the page header... do we show activation?
115
		$context['show_activate'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1) || !empty($context['awaiting_activation']);
116
117
		// What about approval?
118
		$context['show_approve'] = (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($context['awaiting_approval']) || !empty($modSettings['approveAccountDeletion']);
119
120
		// Set up the admin tabs.
121
		$context[$context['admin_menu_name']]['object']->prepareTabData([
122
			'title' => 'admin_members',
123
			'help' => 'view_members',
124
			'description' => 'admin_members_list',
125
			'tabs' => [
126
				'viewmembers' => [
127
					'label' => $txt['view_all_members'],
128
					'description' => $txt['admin_members_list'],
129
					'url' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'all']),
130
					'selected' => $subAction === 'all',
131
				],
132
				'search' => [
133
					'label' => $txt['mlist_search'],
134
					'description' => $txt['admin_members_list'],
135
					'url' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'search']),
136
					'selected' => $subAction === 'search' || $subAction === 'query',
137
				],
138
				'approve' => [
139
					'label' => sprintf($txt['admin_browse_awaiting_approval'], $context['awaiting_approval']),
140
					'description' => $txt['admin_browse_approve_desc'],
141
					'url' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'browse', 'type' => 'approve']),
142
					'disabled' => !$context['show_approve'] && ($subAction !== 'browse' || $this->_req->getQuery('type') !== 'approve'),
143
					'selected' => $subAction === 'browse' && $this->_req->getQuery('type') === 'approve',
144
				],
145
				'activate' => [
146
					'label' => sprintf($txt['admin_browse_awaiting_activate'], $context['awaiting_activation']),
147
					'description' => $txt['admin_browse_activate_desc'],
148
					'url' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'browse', 'type' => 'activate']),
149
					'disabled' => !$context['show_activate'] && ($subAction !== 'browse' || $this->_req->getQuery('type', 'trim|strval', '') !== 'activate'),
150
					'selected' => $subAction === 'browse' && $this->_req->getQuery('type') === 'activate',
151
				],
152
			]
153
		]);
154
155
		// Call integrate_manage_members
156
		call_integration_hook('integrate_manage_members', [&$subActions]);
157
158
		// Off we go
159
		$action->dispatch($subAction);
160
	}
161
162
	/**
163
	 * View all members list. It allows sorting on several columns and deletion of
164
	 * selected members.
165
	 *
166
	 * - It also handles the search query sent by ?action=admin;area=viewmembers;sa=search.
167
	 * - Called by ?action=admin;area=viewmembers;sa=all or ?action=admin;area=viewmembers;sa=query.
168
	 * - Requires the moderate_forum permission.
169
	 *
170
	 * @event integrate_list_member_list
171
	 * @event integrate_view_members_params passed $params
172
	 * @uses the view_members sub template of the ManageMembers template.
173
	 */
174
	public function action_list(): void
175
	{
176
		global $txt, $context, $modSettings;
177
178
		// Set the current sub action.
179
		$context['sub_action'] = $this->_req->getQuery('sa', 'strval', $this->_req->getPost('sa', 'strval', 'all'));
180
181
		// Are we performing a mass action?
182
		if (isset($this->_req->post->maction_on_members, $this->_req->post->maction) && !empty($this->_req->post->members))
183
		{
184
			$this->_multiMembersAction();
185
		}
186
187
		// Check input after a member search has been submitted.
188
		if ($context['sub_action'] === 'query')
189
		{
190
			// Retrieving the membergroups and postgroups.
191
			require_once(SUBSDIR . '/Membergroups.subs.php');
192
			$groups = getBasicMembergroupData([], ['moderator'], null, true);
193
194
			$context['membergroups'] = $groups['membergroups'];
195
			$context['postgroups'] = $groups['groups'];
196
			unset($groups);
197
198
			// Some data about the form fields and how they are linked to the database.
199
			$params = [
200
				'mem_id' => [
201
					'db_fields' => ['id_member'],
202
					'type' => 'int',
203
					'range' => true
204
				],
205
				'age' => [
206
					'db_fields' => ['birthdate'],
207
					'type' => 'age',
208
					'range' => true
209
				],
210
				'posts' => [
211
					'db_fields' => ['posts'],
212
					'type' => 'int',
213
					'range' => true
214
				],
215
				'reg_date' => [
216
					'db_fields' => ['date_registered'],
217
					'type' => 'date',
218
					'range' => true
219
				],
220
				'last_online' => [
221
					'db_fields' => ['last_login'],
222
					'type' => 'date',
223
					'range' => true
224
				],
225
				'activated' => [
226
					'db_fields' => ['is_activated'],
227
					'type' => 'checkbox',
228
					'values' => ['0', '1', '11'],
229
				],
230
				'membername' => [
231
					'db_fields' => ['member_name', 'real_name'],
232
					'type' => 'string'
233
				],
234
				'email' => [
235
					'db_fields' => ['email_address'],
236
					'type' => 'string'
237
				],
238
				'website' => [
239
					'db_fields' => ['website_title', 'website_url'],
240
					'type' => 'string'
241
				],
242
				'ip' => [
243
					'db_fields' => ['member_ip'],
244
					'type' => 'string'
245
				]
246
			];
247
248
			$range_trans = [
249
				'--' => '<',
250
				'-' => '<=',
251
				'=' => '=',
252
				'+' => '>=',
253
				'++' => '>'
254
			];
255
256
			call_integration_hook('integrate_view_members_params', [&$params]);
257
258
			$search_params = [];
259
			if ($context['sub_action'] === 'query' && $this->_req->getQuery('params', 'trim|strval', '') !== '' && empty($this->_req->post->types))
260
			{
261
				$encoded = $this->_req->getQuery('params', 'trim|strval', '');
262
				$search_params = @json_decode(base64_decode($encoded), true);
263
			}
264
			elseif (!empty($this->_req->post))
265
			{
266
				$search_params['types'] = $this->_req->post->types;
267
				foreach (array_keys($params) as $param_name)
268
				{
269
					if (isset($this->_req->post->{$param_name}))
270
					{
271
						$search_params[$param_name] = $this->_req->post->{$param_name};
272
					}
273
				}
274
			}
275
276
			$search_url_params = isset($search_params) ? base64_encode(json_encode($search_params)) : null;
277
278
			// @todo Validate a little more.
279
			// Loop through every field of the form.
280
			$query_parts = [];
281
			$where_params = [];
282
			foreach ($params as $param_name => $param_info)
283
			{
284
				// Not filled in?
285
				if (!isset($search_params[$param_name]) || $search_params[$param_name] === '')
286
				{
287
					continue;
288
				}
289
290
				// Make sure numeric values are really numeric.
291
				if (in_array($param_info['type'], ['int', 'age']))
292
				{
293
					$search_params[$param_name] = (int) $search_params[$param_name];
294
				}
295
				// Date values have to match the specified format.
296
				elseif ($param_info['type'] === 'date')
297
				{
298
					// Check if this date format is valid.
299
					if (preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $search_params[$param_name]) == 0)
300
					{
301
						continue;
302
					}
303
304
					$search_params[$param_name] = strtotime($search_params[$param_name]);
305
				}
306
307
				// Those values that are in some kind of range (<, <=, =, >=, >).
308
				if (!empty($param_info['range']))
309
				{
310
					// Default to '=', just in case...
311
					if (empty($range_trans[$search_params['types'][$param_name]]))
312
					{
313
						$search_params['types'][$param_name] = '=';
314
					}
315
316
					// Handle special case 'age'.
317
					if ($param_info['type'] === 'age')
318
					{
319
						// All people that were born between $lowerlimit and $upperlimit are currently the specified age.
320
						$datearray = getdate(forum_time());
321
						$upperlimit = sprintf('%04d-%02d-%02d', $datearray['year'] - $search_params[$param_name], $datearray['mon'], $datearray['mday']);
322
						$lowerlimit = sprintf('%04d-%02d-%02d', $datearray['year'] - $search_params[$param_name] - 1, $datearray['mon'], $datearray['mday']);
323
324
						if (in_array($search_params['types'][$param_name], ['-', '--', '=']))
325
						{
326
							$query_parts[] = ($param_info['db_fields'][0]) . ' > {string:' . $param_name . '_minlimit}';
327
							$where_params[$param_name . '_minlimit'] = ($search_params['types'][$param_name] === '--' ? $upperlimit : $lowerlimit);
328
						}
329
330
						if (in_array($search_params['types'][$param_name], ['+', '++', '=']))
331
						{
332
							$query_parts[] = ($param_info['db_fields'][0]) . ' <= {string:' . $param_name . '_pluslimit}';
333
							$where_params[$param_name . '_pluslimit'] = ($search_params['types'][$param_name] === '++' ? $lowerlimit : $upperlimit);
334
335
							// Make sure that members that didn't set their birth year are not queried.
336
							$query_parts[] = ($param_info['db_fields'][0]) . ' > {date:dec_zero_date}';
337
							$where_params['dec_zero_date'] = '0004-12-31';
338
						}
339
					}
340
					// Special case - equals a date.
341
					elseif ($param_info['type'] === 'date' && $search_params['types'][$param_name] === '=')
342
					{
343
						$query_parts[] = $param_info['db_fields'][0] . ' > ' . $search_params[$param_name] . ' AND ' . $param_info['db_fields'][0] . ' < ' . ($search_params[$param_name] + 86400);
344
					}
345
					else
346
					{
347
						$query_parts[] = $param_info['db_fields'][0] . ' ' . $range_trans[$search_params['types'][$param_name]] . ' ' . $search_params[$param_name];
348
					}
349
				}
350
				// Checkboxes.
351
				elseif ($param_info['type'] === 'checkbox')
352
				{
353
					// Each checkbox or no checkbox at all is checked -> ignore.
354
					if (!is_array($search_params[$param_name]) || $search_params[$param_name] === [] || count($search_params[$param_name]) === count($param_info['values']))
355
					{
356
						continue;
357
					}
358
359
					$query_parts[] = ($param_info['db_fields'][0]) . ' IN ({array_string:' . $param_name . '_check})';
360
					$where_params[$param_name . '_check'] = $search_params[$param_name];
361
				}
362
				else
363
				{
364
					// Replace the wildcard characters ('*' and '?') into MySQL ones.
365
					$parameter = strtolower(strtr(Util::htmlspecialchars($search_params[$param_name], ENT_QUOTES), ['%' => '\%', '_' => '\_', '*' => '%', '?' => '_']));
366
367
					$query_parts[] = '({column_case_insensitive:' . implode('} LIKE {string_case_insensitive:' . $param_name . '_normal} OR {column_case_insensitive:', $param_info['db_fields']) . '} LIKE {string_case_insensitive:' . $param_name . '_normal})';
368
369
					$where_params[$param_name . '_normal'] = '%' . $parameter . '%';
370
				}
371
			}
372
373
			// Set up the membergroup query part.
374
			$mg_query_parts = [];
375
376
			// Primary membergroups, but only if at least was not selected.
377
			if (!empty($search_params['membergroups'][1]) && count($context['membergroups']) !== count($search_params['membergroups'][1]))
378
			{
379
				$mg_query_parts[] = 'mem.id_group IN ({array_int:group_check})';
380
				$where_params['group_check'] = $search_params['membergroups'][1];
381
			}
382
383
			// Additional membergroups (these are only relevant if not all primary groups where selected!).
384
			if (!empty($search_params['membergroups'][2]) && (empty($search_params['membergroups'][1]) || count($context['membergroups']) !== count($search_params['membergroups'][1])))
385
			{
386
				foreach ($search_params['membergroups'][2] as $mg)
387
				{
388
					$mg_query_parts[] = 'FIND_IN_SET({int:add_group_' . $mg . '}, mem.additional_groups) != 0';
389
					$where_params['add_group_' . $mg] = $mg;
390
				}
391
			}
392
393
			// Combine the one or two membergroup parts into one query part linked with an OR.
394
			if ($mg_query_parts !== [])
395
			{
396
				$query_parts[] = '(' . implode(' OR ', $mg_query_parts) . ')';
397
			}
398
399
			// Get all selected post-count-related membergroups.
400
			if (!empty($search_params['postgroups']) && count($search_params['postgroups']) !== count($context['postgroups']))
401
			{
402
				$query_parts[] = 'id_post_group IN ({array_int:post_groups})';
403
				$where_params['post_groups'] = $search_params['postgroups'];
404
			}
405
406
			// Construct the where part of the query.
407
			$where = $query_parts === [] ? '1=1' : implode('
408
				AND ', $query_parts);
409
		}
410
		else
411
		{
412
			$search_url_params = null;
413
		}
414
415
		// Construct the additional URL part with the query info in it.
416
		$context['params_url'] = $context['sub_action'] === 'query' ? ['sa' => 'query', 'params' => $search_url_params] : [];
417
418
		// Get the title and sub template ready.
419
		$context['page_title'] = $txt['admin_members'];
420
		$where_params = $where_params ?? [];
421
422
		$listOptions = [
423
			'id' => 'member_list',
424
			'title' => $txt['members_list'],
425
			'items_per_page' => $modSettings['defaultMaxMembers'],
426
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers'] + $context['params_url']),
427
			'default_sort_col' => 'user_name',
428
			'get_items' => [
429
				'file' => SUBSDIR . '/Members.subs.php',
430
				'function' => 'list_getMembers',
431
				'params' => [
432
					$where ?? '1=1',
433
					$where_params,
434
				],
435
			],
436
			'get_count' => [
437
				'file' => SUBSDIR . '/Members.subs.php',
438
				'function' => 'list_getNumMembers',
439
				'params' => [
440
					$where ?? '1=1',
441
					$where_params,
442
				],
443
			],
444
			'columns' => [
445
				'id_member' => [
446
					'header' => [
447
						'value' => $txt['member_id'],
448
					],
449
					'data' => [
450
						'db' => 'id_member',
451
					],
452
					'sort' => [
453
						'default' => 'id_member',
454
						'reverse' => 'id_member DESC',
455
					],
456
				],
457
				'user_name' => [
458
					'header' => [
459
						'value' => $txt['username'],
460
					],
461
					'data' => [
462
						'sprintf' => [
463
							'format' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => '%1$d', 'name' => '%2$s']) . '">%2$s</a>',
464
							'params' => [
465
								'id_member' => false,
466
								'member_name' => false,
467
							],
468
						],
469
					],
470
					'sort' => [
471
						'default' => 'member_name',
472
						'reverse' => 'member_name DESC',
473
					],
474
				],
475
				'display_name' => [
476
					'header' => [
477
						'value' => $txt['display_name'],
478
					],
479
					'data' => [
480
						'sprintf' => [
481
							'format' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => '%1$d']) . '">%2$s</a>',
482
							'params' => [
483
								'id_member' => false,
484
								'real_name' => false,
485
							],
486
						],
487
					],
488
					'sort' => [
489
						'default' => 'real_name',
490
						'reverse' => 'real_name DESC',
491
					],
492
				],
493
				'email' => [
494
					'header' => [
495
						'value' => $txt['email_address'],
496
					],
497
					'data' => [
498
						'sprintf' => [
499
							'format' => '<a href="mailto:%1$s">%1$s</a>',
500
							'params' => [
501
								'email_address' => true,
502
							],
503
						],
504
					],
505
					'sort' => [
506
						'default' => 'email_address',
507
						'reverse' => 'email_address DESC',
508
					],
509
				],
510
				'ip' => [
511
					'header' => [
512
						'value' => $txt['ip_address'],
513
					],
514
					'data' => [
515
						'sprintf' => [
516
							'format' => '<a href="' . getUrl('action', ['action' => 'trackip', 'searchip' => '%1$s']) . '">%1$s</a>',
517
							'params' => [
518
								'member_ip' => false,
519
							],
520
						],
521
					],
522
					'sort' => [
523
						'default' => 'member_ip',
524
						'reverse' => 'member_ip DESC',
525
					],
526
				],
527
				'last_active' => [
528
					'header' => [
529
						'value' => $txt['viewmembers_online'],
530
					],
531
					'data' => [
532
						'function' => static function ($rowData) {
533
							global $txt;
534
535
							require_once(SUBSDIR . '/Members.subs.php');
536
537
							// Calculate the number of days since last online.
538
							$difference = empty($rowData['last_login']) ? $txt['never'] : htmlTime($rowData['last_login']);
539
540
							// Show it in italics if they're not activated...
541
							if ($rowData['is_activated'] % 10 !== 1)
542
							{
543
								return sprintf('<em title="%1$s">%2$s</em>', $txt['not_activated'], $difference);
544
							}
545
546
							return $difference;
547
						},
548
					],
549
					'sort' => [
550
						'default' => 'last_login DESC',
551
						'reverse' => 'last_login',
552
					],
553
				],
554
				'posts' => [
555
					'header' => [
556
						'value' => $txt['member_postcount'],
557
					],
558
					'data' => [
559
						'db' => 'posts',
560
					],
561
					'sort' => [
562
						'default' => 'posts',
563
						'reverse' => 'posts DESC',
564
					],
565
				],
566
				'check' => [
567
					'header' => [
568
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
569
						'class' => 'centertext',
570
					],
571
					'data' => [
572
						'function' => static fn($rowData) => '<input type="checkbox" name="members[]" value="' . $rowData['id_member'] . '" class="input_check" ' . ($rowData['id_member'] === User::$info->id || $rowData['id_group'] == 1 || in_array(1, explode(',', $rowData['additional_groups'])) ? 'disabled="disabled"' : '') . ' />',
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...
573
						'class' => 'centertext',
574
					],
575
				],
576
			],
577
			'form' => [
578
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers'] + $context['params_url']),
579
				'include_start' => true,
580
				'include_sort' => true,
581
			],
582
			'additional_rows' => [
583
				[
584
					'position' => 'below_table_data',
585
					'value' => template_users_multiactions($this->_getGroups()),
586
					'class' => 'flow_flex_additional_row',
587
				],
588
			],
589
		];
590
591
		// Without enough permissions, don't show 'delete members' checkboxes.
592
		if (!allowedTo('profile_remove_any'))
593
		{
594
			unset($listOptions['cols']['check'], $listOptions['form'], $listOptions['additional_rows']);
595
		}
596
597
		createList($listOptions);
598
599
		$context['sub_template'] = 'show_list';
600
		$context['default_list'] = 'member_list';
601
	}
602
603
	/**
604
	 * Handle mass action processing on a group of members
605
	 *
606
	 * - Deleting members
607
	 * - Group changes
608
	 * - Banning
609
	 */
610
	protected function _multiMembersAction(): void
611
	{
612
		global $txt;
613
614
		// @todo add a token too?
615
		checkSession();
616
617
		// Clean the input.
618
		$members = [];
619
		foreach ($this->_req->post->members as $value)
620
		{
621
			// Don't delete yourself, idiot.
622
			if ($this->_req->post->maction === 'delete' && $value == $this->user->id)
623
			{
624
				continue;
625
			}
626
627
			$members[] = (int) $value;
628
		}
629
630
		$members = array_filter($members);
631
632
		// No members, nothing to do.
633
		if (empty($members))
634
		{
635
			return;
636
		}
637
638
		// Are we performing a deletion?
639
		if ($this->_req->post->maction === 'delete' && allowedTo('profile_remove_any'))
640
		{
641
			// Delete all the selected members.
642
			require_once(SUBSDIR . '/Members.subs.php');
643
			deleteMembers($members, true);
644
		}
645
646
		// Are we changing groups?
647
		if (in_array($this->_req->post->maction, ['pgroup', 'agroup']) && allowedTo('manage_membergroups'))
648
		{
649
			require_once(SUBSDIR . '/Membergroups.subs.php');
650
651
			$groups = ['p', 'a'];
652
			foreach ($groups as $group)
653
			{
654
				if ($this->_req->post->maction == $group . 'group' && !empty($this->_req->post->new_membergroup))
655
				{
656
					$type = $group === 'p' ? 'force_primary' : 'only_additional';
657
658
					// Change all the selected members' group.
659
					if ($this->_req->post->new_membergroup != -1)
660
					{
661
						addMembersToGroup($members, $this->_req->post->new_membergroup, $type, true);
662
					}
663
					else
664
					{
665
						removeMembersFromGroups($members, null, true);
666
					}
667
				}
668
			}
669
		}
670
671
		// Are we banning?
672
		if (in_array($this->_req->post->maction, ['ban_names', 'ban_mails', 'ban_ips', 'ban_names_mails']) && allowedTo('manage_bans'))
673
		{
674
			require_once(SUBSDIR . '/Bans.subs.php');
675
			require_once(SUBSDIR . '/Members.subs.php');
676
677
			$ban_group_id = insertBanGroup([
678
				'name' => $txt['admin_ban_name'],
679
				'cannot' => [
680
					'access' => 1,
681
					'register' => 0,
682
					'post' => 0,
683
					'login' => 0,
684
				],
685
				'db_expiration' => 'NULL',
686
				'reason' => '',
687
				'notes' => '',
688
			]);
689
690
			$ban_name = in_array($this->_req->post->maction, ['ban_names', 'ban_names_mails']);
691
			$ban_email = in_array($this->_req->post->maction, ['ban_mails', 'ban_names_mails']);
692
			$ban_ips = $this->_req->post->maction === 'ban_ips';
693
			$suggestions = [];
694
695
			if ($ban_email)
696
			{
697
				$suggestions[] = 'email';
698
			}
699
700
			if ($ban_name)
701
			{
702
				$suggestions[] = 'user';
703
			}
704
705
			if ($ban_ips)
706
			{
707
				$suggestions[] = 'main_ip';
708
			}
709
710
			$members_data = getBasicMemberData($members, ['moderation' => true]);
711
			foreach ($members_data as $member)
712
			{
713
				saveTriggers([
714
					'main_ip' => $ban_ips ? $member['member_ip'] : '',
715
					'hostname' => '',
716
					'email' => $ban_email ? $member['email_address'] : '',
717
					'user' => $ban_name ? $member['member_name'] : '',
718
					'ban_suggestions' => $suggestions,
719
				], $ban_group_id, $ban_name ? $member['id_member'] : 0);
720
			}
721
		}
722
	}
723
724
	/**
725
	 * Prepares the list of groups to be used in the dropdown for "mass actions".
726
	 *
727
	 * @return array
728
	 */
729
	protected function _getGroups(): array
730
	{
731
		global $txt;
732
733
		require_once(SUBSDIR . '/Membergroups.subs.php');
734
735
		$member_groups = getGroupsList();
736
737
		// Better remove the admin membergroup...and set it to a "remove all"
738
		$member_groups[1] = [
739
			'id' => -1,
740
			'name' => $txt['remove_groups'],
741
			'is_primary' => 0,
742
		];
743
744
		// no primary is tricky...
745
		$member_groups[0] = [
746
			'id' => 0,
747
			'name' => '',
748
			'is_primary' => 1,
749
		];
750
751
		return $member_groups;
752
	}
753
754
	/**
755
	 * Search the member list, using one or more criteria.
756
	 *
757
	 * What it does:
758
	 *
759
	 * - Called by ?action=admin;area=viewmembers;sa=search.
760
	 * - Requires the moderate_forum permission.
761
	 * - form is submitted to action=admin;area=viewmembers;sa=query.
762
	 *
763
	 * @uses the search_members sub template of the ManageMembers template.
764
	 */
765
	public function action_search(): void
766
	{
767
		global $context, $txt;
768
769
		// Get a list of all the membergroups and postgroups that can be selected.
770
		require_once(SUBSDIR . '/Membergroups.subs.php');
771
		$groups = getBasicMembergroupData([], ['moderator'], null, true);
772
773
		$context['membergroups'] = $groups['membergroups'];
774
		$context['postgroups'] = $groups['postgroups'];
775
		$context['page_title'] = $txt['admin_members'];
776
		$context['sub_template'] = 'search_members';
777
778
		unset($groups);
779
	}
780
781
	/**
782
	 * List all members who are awaiting approval / activation, sortable on different columns.
783
	 *
784
	 * What it does:
785
	 *
786
	 * - It allows instant approval or activation of (a selection of) members.
787
	 * - Called by ?action=admin;area=viewmembers;sa=browse;type=approve
788
	 * or ?action=admin;area=viewmembers;sa=browse;type=activate.
789
	 * - The form submits to ?action=admin;area=viewmembers;sa=approve.
790
	 * - Requires the moderate_forum permission.
791
	 *
792
	 * @event integrate_list_approve_list
793
	 * @uses the admin_browse sub template of the ManageMembers template.
794
	 */
795
	public function action_browse(): void
796
	{
797
		global $txt, $context, $modSettings;
798
799
		// Not a lot here!
800
		$context['page_title'] = $txt['admin_members'];
801
		$context['sub_template'] = 'admin_browse';
802
		$context['browse_type'] = $this->_req->getQuery('type', 'trim|strval', (!empty($modSettings['registration_method']) && (int) $modSettings['registration_method'] === 1 ? 'activate' : 'approve'));
803
804
		if (isset($context['tabs'][$context['browse_type']]))
805
		{
806
			$context['tabs'][$context['browse_type']]['is_selected'] = true;
807
		}
808
809
		// Allowed filters are those we can have, in theory.
810
		$context['allowed_filters'] = $context['browse_type'] === 'approve' ? [3, 4, 5] : [0, 2];
811
		$filterQuery = $this->_req->hasQuery('filter') ? $this->_req->getQuery('filter', 'intval', null) : null;
812
		$context['current_filter'] = ($filterQuery !== null && in_array($filterQuery, $context['allowed_filters'], true) && !empty($context['activation_numbers'][$filterQuery])) ? (int) $filterQuery : -1;
813
814
		// Sort out the different subareas that we can actually filter by.
815
		$context['available_filters'] = [];
816
		foreach ($context['activation_numbers'] as $type => $amount)
817
		{
818
			// We have some of these...
819
			if (!in_array($type, $context['allowed_filters']))
820
			{
821
				continue;
822
			}
823
824
			if ($amount <= 0)
825
			{
826
				continue;
827
			}
828
829
			$context['available_filters'][] = [
830
				'type' => $type,
831
				'amount' => $amount,
832
				'desc' => $txt['admin_browse_filter_type_' . $type] ?? '?',
833
				'selected' => $type === $context['current_filter']
834
			];
835
		}
836
837
		// If the filter was not sent, set it to whatever has people in it!
838
		if ($context['current_filter'] == -1 && !empty($context['available_filters'][0]['amount']))
839
		{
840
			$context['current_filter'] = $context['available_filters'][0]['type'];
841
			$context['available_filters'][0]['selected'] = true;
842
		}
843
844
		// This little variable is used to determine if we should flag where we are looking.
845
		$context['show_filter'] = ($context['current_filter'] != 0 && $context['current_filter'] != 3) || count($context['available_filters']) > 1;
846
847
		// The columns that can be sorted.
848
		$context['columns'] = [
849
			'id_member' => ['label' => $txt['admin_browse_id']],
850
			'member_name' => ['label' => $txt['admin_browse_username']],
851
			'email_address' => ['label' => $txt['admin_browse_email']],
852
			'member_ip' => ['label' => $txt['admin_browse_ip']],
853
			'date_registered' => ['label' => $txt['admin_browse_registered']],
854
		];
855
856
		// Are we showing duplicate information?
857
		if ($this->_req->hasQuery('showdupes'))
858
		{
859
			$_SESSION['showdupes'] = $this->_req->getQuery('showdupes', 'intval', 0);
860
		}
861
862
		$context['show_duplicates'] = !empty($_SESSION['showdupes']);
863
864
		// Determine which actions we should allow on this page.
865
		$context['allowed_actions'] = [];
866
		if ($context['browse_type'] === 'approve')
867
		{
868
			// If we are approving deleted accounts, we have a slightly different list... actually a mirror ;)
869
			if ($context['current_filter'] == 4)
870
			{
871
				$context['allowed_actions'] = [
872
					'reject' => $txt['admin_browse_w_approve_deletion'],
873
					'ok' => $txt['admin_browse_w_reject_delete'],
874
				];
875
			}
876
			else
877
			{
878
				$context['allowed_actions'] = [
879
					'ok' => $txt['admin_browse_w_approve'],
880
					'okemail' => $txt['admin_browse_w_approve'] . ' ' . $txt['admin_browse_w_email'],
881
					'require_activation' => $txt['admin_browse_w_approve_require_activate'],
882
					'reject' => $txt['admin_browse_w_reject'],
883
					'rejectemail' => $txt['admin_browse_w_reject'] . ' ' . $txt['admin_browse_w_email'],
884
				];
885
			}
886
		}
887
		elseif ($context['browse_type'] === 'activate')
888
		{
889
			$context['allowed_actions'] = [
890
				'ok' => $txt['admin_browse_w_activate'],
891
				'okemail' => $txt['admin_browse_w_activate'] . ' ' . $txt['admin_browse_w_email'],
892
				'delete' => $txt['admin_browse_w_delete'],
893
				'deleteemail' => $txt['admin_browse_w_delete'] . ' ' . $txt['admin_browse_w_email'],
894
				'remind' => $txt['admin_browse_w_remind'] . ' ' . $txt['admin_browse_w_email'],
895
			];
896
		}
897
898
		// Create an option list for actions allowed to be done with selected members.
899
		$allowed_actions = '
900
			<option selected="selected" value="">' . $txt['admin_browse_with_selected'] . ':</option>
901
			<option value="" disabled="disabled">' . str_repeat('&#8212;', strlen($txt['admin_browse_with_selected'])) . '</option>';
902
903
		foreach ($context['allowed_actions'] as $key => $desc)
904
		{
905
			$allowed_actions .= '
906
				<option value="' . $key . '">' . '&#10148;&nbsp;' . $desc . '</option>';
907
		}
908
909
		// Set up the JavaScript function for selecting an action for the list.
910
		$javascript = '
911
			function onSelectChange()
912
			{
913
				if (document.forms.postForm.todo.value === "")
914
					return;
915
916
				var message = "";';
917
918
		// We have special messages for approving deletion of accounts - it's surprisingly logical - honest.
919
		if ($context['current_filter'] == 4)
920
		{
921
			$javascript .= '
922
				if (document.forms.postForm.todo.value.indexOf("reject") !== -1)
923
					message = "' . $txt['admin_browse_w_delete'] . '";
924
				else
925
					message = "' . $txt['admin_browse_w_reject'] . '";';
926
		}
927
		// Otherwise a nice standard message.
928
		else
929
		{
930
			$javascript .= '
931
				if (document.forms.postForm.todo.value.indexOf("delete") !== -1)
932
					message = "' . $txt['admin_browse_w_delete'] . '";
933
				else if (document.forms.postForm.todo.value.indexOf("reject") !== -1)
934
					message = "' . $txt['admin_browse_w_reject'] . '";
935
				else if (document.forms.postForm.todo.value == "remind")
936
					message = "' . $txt['admin_browse_w_remind'] . '";
937
				else
938
					message = "' . ($context['browse_type'] === 'approve' ? $txt['admin_browse_w_approve'] : $txt['admin_browse_w_activate']) . '";';
939
		}
940
941
		$javascript .= '
942
				if (confirm(message + " ' . $txt['admin_browse_warn'] . '"))
943
					document.forms.postForm.submit();
944
			}';
945
946
		$listOptions = [
947
			'id' => 'approve_list',
948
			'items_per_page' => $modSettings['defaultMaxMembers'],
949
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'browse', 'type' => $context['browse_type'] . (empty($context['show_filter']) ? '' : ", 'filter' =>" . $context['current_filter'])]),
950
			'default_sort_col' => 'date_registered',
951
			'get_items' => [
952
				'file' => SUBSDIR . '/Members.subs.php',
953
				'function' => 'list_getMembers',
954
				'params' => [
955
					'is_activated = {int:activated_status}',
956
					['activated_status' => $context['current_filter']],
957
					$context['show_duplicates'],
958
				],
959
			],
960
			'get_count' => [
961
				'file' => SUBSDIR . '/Members.subs.php',
962
				'function' => 'list_getNumMembers',
963
				'params' => [
964
					'is_activated = {int:activated_status}',
965
					['activated_status' => $context['current_filter']],
966
				],
967
			],
968
			'columns' => [
969
				'id_member' => [
970
					'header' => [
971
						'value' => $txt['member_id'],
972
					],
973
					'data' => [
974
						'db' => 'id_member',
975
					],
976
					'sort' => [
977
						'default' => 'id_member',
978
						'reverse' => 'id_member DESC',
979
					],
980
				],
981
				'user_name' => [
982
					'header' => [
983
						'value' => $txt['username'],
984
					],
985
					'data' => [
986
						'sprintf' => [
987
							'format' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => '%1$d']) . '">%2$s</a>',
988
							'params' => [
989
								'id_member' => false,
990
								'member_name' => false,
991
							],
992
						],
993
					],
994
					'sort' => [
995
						'default' => 'member_name',
996
						'reverse' => 'member_name DESC',
997
					],
998
				],
999
				'email' => [
1000
					'header' => [
1001
						'value' => $txt['email_address'],
1002
					],
1003
					'data' => [
1004
						'sprintf' => [
1005
							'format' => '<a href="mailto:%1$s">%1$s</a>',
1006
							'params' => [
1007
								'email_address' => true,
1008
							],
1009
						],
1010
					],
1011
					'sort' => [
1012
						'default' => 'email_address',
1013
						'reverse' => 'email_address DESC',
1014
					],
1015
				],
1016
				'ip' => [
1017
					'header' => [
1018
						'value' => $txt['ip_address'],
1019
					],
1020
					'data' => [
1021
						'sprintf' => [
1022
							'format' => '<a href="' . getUrl('profile', ['action' => 'trackip', 'searchip' => '%1$s']) . '">%1$s</a>',
1023
							'params' => [
1024
								'member_ip' => false,
1025
							],
1026
						],
1027
					],
1028
					'sort' => [
1029
						'default' => 'member_ip',
1030
						'reverse' => 'member_ip DESC',
1031
					],
1032
				],
1033
				'hostname' => [
1034
					'header' => [
1035
						'value' => $txt['hostname'],
1036
					],
1037
					'data' => [
1038
						'function' => static fn($rowData) => host_from_ip($rowData['member_ip']),
1039
						'class' => 'smalltext',
1040
					],
1041
				],
1042
				'date_registered' => [
1043
					'header' => [
1044
						'value' => $context['current_filter'] == 4 ? $txt['viewmembers_online'] : $txt['date_registered'],
1045
					],
1046
					'data' => [
1047
						'function' => static function ($rowData) {
1048
							global $context;
1049
							return standardTime($rowData[($context['current_filter'] == 4 ? 'last_login' : 'date_registered')]);
1050
						},
1051
					],
1052
					'sort' => [
1053
						'default' => $context['current_filter'] == 4 ? 'mem.last_login DESC' : 'date_registered DESC',
1054
						'reverse' => $context['current_filter'] == 4 ? 'mem.last_login' : 'date_registered',
1055
					],
1056
				],
1057
				'duplicates' => [
1058
					'header' => [
1059
						'value' => $txt['duplicates'],
1060
						// Make sure it doesn't go too wide.
1061
						'style' => 'width: 20%;',
1062
					],
1063
					'data' => [
1064
						'function' => static function ($rowData) {
1065
							global $txt;
1066
1067
							$member_links = [];
1068
							foreach ($rowData['duplicate_members'] as $member)
1069
							{
1070
								if ($member['id'])
1071
								{
1072
									$member_links[] = '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $member['id'], 'name' => $member['name']]) . '" ' . (empty($member['is_banned']) ? '' : 'class="alert"') . '>' . $member['name'] . '</a>';
1073
								}
1074
								else
1075
								{
1076
									$member_links[] = $member['name'] . ' (' . $txt['guest'] . ')';
1077
								}
1078
							}
1079
1080
							return implode(', ', $member_links);
1081
						},
1082
						'class' => 'smalltext',
1083
					],
1084
				],
1085
				'check' => [
1086
					'header' => [
1087
						'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
1088
						'class' => 'centertext',
1089
					],
1090
					'data' => [
1091
						'sprintf' => [
1092
							'format' => '<input type="checkbox" name="todoAction[]" value="%1$d" class="input_check" />',
1093
							'params' => [
1094
								'id_member' => false,
1095
							],
1096
						],
1097
						'class' => 'centertext',
1098
					],
1099
				],
1100
			],
1101
			'javascript' => $javascript,
1102
			'form' => [
1103
				'href' => getUrl('action', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'approve', 'type' => $context['browse_type']]),
1104
				'name' => 'postForm',
1105
				'include_start' => true,
1106
				'include_sort' => true,
1107
				'hidden_fields' => [
1108
					'orig_filter' => $context['current_filter'],
1109
				],
1110
			],
1111
			'additional_rows' => [
1112
				[
1113
					'position' => 'below_table_data',
1114
					'class' => 'flow_flex_additional_row',
1115
					'value' => '
1116
						<div class="submitbutton">
1117
							<a class="linkbutton" href="' . getUrl('action', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'browse', 'showdupes' => $context['show_duplicates'] ? 0 : 1, 'type' => $context['browse_type'], '{session_data}'] + (empty($context['show_filter']) ? [] : ['filter' => $context['current_filter']])) . '">' . ($context['show_duplicates'] ? $txt['dont_check_for_duplicate'] : $txt['check_for_duplicate']) . '</a>
1118
							<select name="todo" onchange="onSelectChange();">
1119
								' . $allowed_actions . '
1120
							</select>
1121
							<noscript>
1122
								<input type="submit" value="' . $txt['go'] . '" />
1123
							</noscript>
1124
						</div>
1125
					',
1126
				],
1127
			],
1128
		];
1129
1130
		// Pick what column to actually include if we're showing duplicates.
1131
		if ($context['show_duplicates'])
1132
		{
1133
			unset($listOptions['columns']['email']);
1134
		}
1135
		else
1136
		{
1137
			unset($listOptions['columns']['duplicates']);
1138
		}
1139
1140
		// Only show hostname on duplicates as it takes a lot of time.
1141
		if (!$context['show_duplicates'] || !empty($modSettings['disableHostnameLookup']))
1142
		{
1143
			unset($listOptions['columns']['hostname']);
1144
		}
1145
1146
		// Is there any need to show filters?
1147
		if (isset($context['available_filters']))
1148
		{
1149
			$listOptions['list_menu'] = [
1150
				'show_on' => 'top',
1151
				'links' => []
1152
			];
1153
1154
			foreach ($context['available_filters'] as $filter)
1155
			{
1156
				$listOptions['list_menu']['links'][] = [
1157
					'is_selected' => $filter['selected'],
1158
					'href' => getUrl('action', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'browse', 'type' => $context['browse_type'], 'filter' => $filter['type']]),
1159
					'label' => $filter['desc'] . ' - ' . $filter['amount'] . ' ' . ($filter['amount'] == 1 ? $txt['user'] : $txt['users'])
1160
				];
1161
			}
1162
		}
1163
1164
		// Now that we have all the options, create the list.
1165
		createList($listOptions);
1166
	}
1167
1168
	/**
1169
	 * This function handles the approval, rejection, activation, or deletion of members.
1170
	 *
1171
	 * What it does:
1172
	 *
1173
	 * - Called by ?action=admin;area=viewmembers;sa=approve.
1174
	 * - Requires the moderate_forum permission.
1175
	 * - Redirects to ?action=admin;area=viewmembers;sa=browse
1176
	 * with the same parameters as the calling page.
1177
	 */
1178
	public function action_approve(): void
1179
	{
1180
		global $modSettings;
1181
1182
		// First, check our session.
1183
		checkSession();
1184
1185
		require_once(SUBSDIR . '/Mail.subs.php');
1186
		require_once(SUBSDIR . '/Members.subs.php');
1187
1188
		// We also need to the login languages here - for emails.
1189
		Txt::load('Login');
1190
1191
		// Start off clean
1192
		$this->conditions = [];
1193
1194
		// Sort out where we are going...
1195
		$original_filter = $this->_req->getPost('orig_filter', 'intval', null);
1196
		$current_filter = $this->conditions['activated_status'] = $original_filter;
1197
1198
		$type = $this->_req->getQuery('type', 'trim', '');
1199
		$filter = $this->_req->getPost('filter', 'trim', null);
1200
		$sort = $this->_req->getRequest('sort', 'trim', '');
1201
		$start = $this->_req->getRequest('start', 'intval', 0);
1202
		$todoAction = $this->_req->getPost('todoAction');
1203
		$time_passed = $this->_req->getPost('$time_passed', 'intval');
1204
		$todo = $this->_req->getPost('todo', 'trim');
1205
1206
		// If we are applying a filter, do just that - then redirect.
1207
		if (isset($filter) && $filter !== $original_filter)
1208
		{
1209
			redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $type . ';sort=' . $sort . ';filter=' . $filter . ';start=' . $start);
1210
		}
1211
1212
		// Nothing to do?
1213
		if (!isset($todoAction) && !isset($time_passed))
1214
		{
1215
			redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $type . ';sort=' . $sort . ';filter=' . $current_filter . ';start=' . $start);
1216
		}
1217
1218
		// Are we dealing with members who have been waiting for > set amount of time?
1219
		if (isset($time_passed))
1220
		{
1221
			$this->conditions['time_before'] = time() - 86400 * $time_passed;
1222
		}
1223
		// Coming from checkboxes - validate the members passed through to us.
1224
		else
1225
		{
1226
			$this->conditions['members'] = [];
1227
			foreach ($todoAction as $id)
1228
			{
1229
				$this->conditions['members'][] = (int) $id;
1230
			}
1231
		}
1232
1233
		$data = retrieveMemberData($this->conditions);
1234
		if ($data['member_count'] === 0)
1235
		{
1236
			redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $type . ';sort=' . $sort . ';filter=' . $current_filter . ';start=' . $start);
1237
		}
1238
1239
		$this->member_info = $data['member_info'];
1240
		$this->conditions['members'] = $data['members'];
1241
1242
		// What do we want to do with this application?
1243
		switch ($todo)
1244
		{
1245
			// Are we activating or approving the members?
1246
			case 'ok':
1247
			case 'okemail':
1248
				$this->_okMember();
1249
				break;
1250
			// Maybe we're sending it off for activation?
1251
			case 'require_activation':
1252
				$this->_requireMember();
1253
				break;
1254
			// Are we rejecting them?
1255
			case 'reject':
1256
			case 'rejectemail':
1257
				$this->_rejectMember();
1258
				break;
1259
			// A simple deletion?
1260
			case 'delete':
1261
			case 'deleteemail':
1262
				$this->_deleteMember();
1263
				break;
1264
			// Remind them to activate their account?
1265
			case 'remind':
1266
				$this->_remindMember();
1267
				break;
1268
		}
1269
1270
		// Log what we did? Core features Moderation Logging must be enabled
1271
		if (featureEnabled('ml') && in_array($todo, ['ok', 'okemail', 'require_activation', 'remind']))
1272
		{
1273
			$log_action = $todo === 'remind' ? 'remind_member' : 'approve_member';
1274
1275
			foreach ($this->member_info as $member)
1276
			{
1277
				logAction($log_action, ['member' => $member['id']], 'admin');
1278
			}
1279
		}
1280
1281
		// Although updateMemberStats *may* catch this, best to do it manually just in case (Doesn't always sort out unapprovedMembers).
1282
		if (in_array($current_filter, [3, 4, 5]))
1283
		{
1284
			updateSettings(['unapprovedMembers' => ($modSettings['unapprovedMembers'] > $data['member_count'] ? $modSettings['unapprovedMembers'] - $data['member_count'] : 0)]);
1285
		}
1286
1287
		// Update the member's stats. (but we know the member didn't change their name.)
1288
		require_once(SUBSDIR . '/Members.subs.php');
1289
		updateMemberStats();
1290
1291
		// If they haven't been deleted, update the post-group statistics on them...
1292
		if (!in_array($todo, ['delete', 'deleteemail', 'reject', 'rejectemail', 'remind']))
1293
		{
1294
			require_once(SUBSDIR . '/Membergroups.subs.php');
1295
			updatePostGroupStats($this->conditions['members']);
1296
		}
1297
1298
		redirectexit('action=admin;area=viewmembers;sa=browse;type=' . $type . ';sort=' . $sort . ';filter=' . $current_filter . ';start=' . $start);
1299
	}
1300
1301
	/**
1302
	 * Approve a member application
1303
	 */
1304
	private function _okMember(): void
1305
	{
1306
		// Approve / activate this member.
1307
		approveMembers($this->conditions);
1308
1309
		// Check for email.
1310
		if ($this->_req->post->todo === 'okemail')
1311
		{
1312
			foreach ($this->member_info as $member)
1313
			{
1314
				$replacements = [
1315
					'NAME' => $member['name'],
1316
					'USERNAME' => $member['username'],
1317
					'PROFILELINK' => getUrl('profile', ['action' => 'profile', 'u' => $member['id'], 'name' => $member['name']]),
1318
					'FORGOTPASSWORDLINK' => getUrl('action', ['action' => 'reminder']),
1319
				];
1320
1321
				$emaildata = loadEmailTemplate('admin_approve_accept', $replacements, $member['language']);
1322
				sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
1323
			}
1324
		}
1325
1326
		// Update the menu action cache so it's forced to refresh
1327
		Cache::instance()->remove('num_menu_errors');
1328
	}
1329
1330
	/**
1331
	 * Tell some members that they require activation of their account
1332
	 */
1333
	private function _requireMember(): void
1334
	{
1335
		require_once(SUBSDIR . '/Auth.subs.php');
1336
1337
		// We have to do this for each member, I'm afraid.
1338
		foreach ($this->member_info as $member)
1339
		{
1340
			$this->conditions['selected_member'] = $member['id'];
1341
1342
			// Generate a random activation code.
1343
			$this->conditions['validation_code'] = generateValidationCode(14);
1344
1345
			// Set these members for activation - I know this includes two id_member checks, but it's safer than bodging $condition ;).
1346
			enforceReactivation($this->conditions);
1347
1348
			$replacements = [
1349
				'USERNAME' => $member['name'],
1350
				'ACTIVATIONLINK' => getUrl('action', ['action' => 'register', 'sa' => 'activate', 'u' => $member['id'], 'code' => $this->conditions['validation_code']]),
1351
				'ACTIVATIONLINKWITHOUTCODE' => getUrl('action', ['action' => 'register', 'sa' => 'activate', 'u' => $member['id']]),
1352
				'ACTIVATIONCODE' => $this->conditions['validation_code'],
1353
			];
1354
1355
			$emaildata = loadEmailTemplate('admin_approve_activation', $replacements, $member['language']);
1356
			sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
1357
		}
1358
	}
1359
1360
	/**
1361
	 * Reject a set a member applications, maybe even tell them
1362
	 */
1363
	private function _rejectMember(): void
1364
	{
1365
		deleteMembers($this->conditions['members']);
1366
1367
		// Send the email telling them they aren't welcome?
1368
		if ($this->_req->post->todo === 'rejectemail')
1369
		{
1370
			foreach ($this->member_info as $member)
1371
			{
1372
				$replacements = [
1373
					'USERNAME' => $member['name'],
1374
				];
1375
1376
				$emaildata = loadEmailTemplate('admin_approve_reject', $replacements, $member['language']);
1377
				sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1);
1378
			}
1379
		}
1380
	}
1381
1382
	/**
1383
	 * Deletes the members specified in the conditions array.
1384
	 *
1385
	 * What it does:
1386
	 *
1387
	 * - Called by the action_approve method.
1388
	 * - Deletes the members specified in the conditions array.
1389
	 * - Optionally sends email to notify the deleted members.
1390
	 *
1391
	 * @return void
1392
	 */
1393
	private function _deleteMember(): void
1394
	{
1395
		deleteMembers($this->conditions['members']);
1396
1397
		// Send the email telling them they aren't welcome?
1398
		if ($this->_req->post->todo === 'deleteemail')
1399
		{
1400
			foreach ($this->member_info as $member)
1401
			{
1402
				$replacements = [
1403
					'USERNAME' => $member['name'],
1404
				];
1405
1406
				$emaildata = loadEmailTemplate('admin_approve_delete', $replacements, $member['language']);
1407
				sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1);
1408
			}
1409
		}
1410
	}
1411
1412
	/**
1413
	 * Remind a set of members that they have an activation email waiting
1414
	 */
1415
	private function _remindMember(): void
1416
	{
1417
		require_once(SUBSDIR . '/Auth.subs.php');
1418
1419
		foreach ($this->member_info as $member)
1420
		{
1421
			$this->conditions['selected_member'] = $member['id'];
1422
			$this->conditions['validation_code'] = generateValidationCode(14);
1423
1424
			enforceReactivation($this->conditions);
1425
1426
			$replacements = [
1427
				'USERNAME' => $member['name'],
1428
				'ACTIVATIONLINK' => getUrl('action', ['action' => 'register', 'sa' => 'activate', 'u' => $member['id'], 'code' => $this->conditions['validation_code']]),
1429
				'ACTIVATIONLINKWITHOUTCODE' => getUrl('action', ['action' => 'register', 'sa' => 'activate', 'u' => $member['id']]),
1430
				'ACTIVATIONCODE' => $this->conditions['validation_code'],
1431
			];
1432
1433
			$emaildata = loadEmailTemplate('admin_approve_remind', $replacements, $member['language']);
1434
			sendmail($member['email'], $emaildata['subject'], $emaildata['body'], null, null, false, 1);
1435
		}
1436
	}
1437
}
1438