Issues (1686)

sources/ElkArte/AdminController/ManageMembers.php (1 issue)

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