ManageMembers::_okMember()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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