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
![]() |
|||
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('—', strlen($txt['admin_browse_with_selected'])) . '</option>'; |
||
900 | |||
901 | foreach ($context['allowed_actions'] as $key => $desc) |
||
902 | { |
||
903 | $allowed_actions .= ' |
||
904 | <option value="' . $key . '">' . '➤ ' . $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 |