-
Notifications
You must be signed in to change notification settings - Fork 58
/
Memberlist.controller.php
464 lines (390 loc) · 17.5 KB
/
Memberlist.controller.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
<?php
/**
* @name ElkArte Forum
* @copyright ElkArte Forum contributors
* @license BSD http://opensource.org/licenses/BSD-3-Clause
*
* This software is a derived product, based on:
*
* Simple Machines Forum (SMF)
* copyright: 2011 Simple Machines (http://www.simplemachines.org)
* license: BSD, See included LICENSE.TXT for terms and conditions.
*
* @version 1.0 Alpha
*
* This file contains the functions for displaying and searching in the
* members list.
* Accessed by ?action_memberlist.
* The default action is to list all registered members.
*
*/
if (!defined('ELK'))
die('No access...');
/**
* Memberlist Controller
*/
class Memberlist_Controller extends Action_Controller
{
/**
* Sets up the context for showing a listing of registered members.
* For the handlers in this file, it requires the view_mlist permission.
*
* @uses Memberlist template, main sub-template.
*
* @see Action_Controller::action_index()
*/
public function action_index()
{
global $scripturl, $txt, $modSettings, $context;
// Make sure they can view the memberlist.
isAllowedTo('view_mlist');
loadTemplate('Memberlist');
$context['listing_by'] = !empty($_GET['sa']) ? $_GET['sa'] : 'all';
// $subActions array format:
// 'subaction' => array('label', 'function', 'is_selected')
$subActions = array(
'all' => array($txt['view_all_members'], 'action_mlall', $context['listing_by'] == 'all'),
'search' => array($txt['mlist_search'], 'action_mlsearch', $context['listing_by'] == 'search'),
);
// Set up the sort links.
$context['sort_links'] = array();
foreach ($subActions as $act => $text)
{
$context['sort_links'][] = array(
'label' => $text[0],
'action' => $act,
'selected' => $text[2],
);
}
$context['num_members'] = $modSettings['totalMembers'];
// Set up the standard columns...
$context['columns'] = array(
'online' => array(
'label' => $txt['status'],
'width' => 60,
'class' => 'first_th centertext',
'sort' => array(
'down' => allowedTo('moderate_forum') ? 'IFNULL(lo.log_time, 1) ASC, real_name ASC' : 'CASE WHEN mem.show_online THEN IFNULL(lo.log_time, 1) ELSE 1 END ASC, real_name ASC',
'up' => allowedTo('moderate_forum') ? 'IFNULL(lo.log_time, 1) DESC, real_name DESC' : 'CASE WHEN mem.show_online THEN IFNULL(lo.log_time, 1) ELSE 1 END DESC, real_name DESC'
),
),
'real_name' => array(
'label' => $txt['username'],
'sort' => array(
'down' => 'mem.real_name DESC',
'up' => 'mem.real_name ASC'
),
),
'email_address' => array(
'label' => $txt['email'],
'class' => "centertext",
'sort' => array(
'down' => allowedTo('moderate_forum') ? 'mem.email_address DESC' : 'mem.hide_email DESC, mem.email_address DESC',
'up' => allowedTo('moderate_forum') ? 'mem.email_address ASC' : 'mem.hide_email ASC, mem.email_address ASC'
),
),
'website_url' => array(
'label' => $txt['website'],
'class' => "centertext",
'link_with' => 'website',
'sort' => array(
'down' => 'LENGTH(mem.website_url) > 0 ASC, IFNULL(mem.website_url, 1=1) DESC, mem.website_url DESC',
'up' => 'LENGTH(mem.website_url) > 0 DESC, IFNULL(mem.website_url, 1=1) ASC, mem.website_url ASC'
),
),
'id_group' => array(
'label' => $txt['position'],
'sort' => array(
'down' => 'IFNULL(mg.group_name, 1=1) DESC, mg.group_name DESC',
'up' => 'IFNULL(mg.group_name, 1=1) ASC, mg.group_name ASC'
),
),
'date_registered' => array(
'label' => $txt['date_registered'],
'sort' => array(
'down' => 'mem.date_registered DESC',
'up' => 'mem.date_registered ASC'
),
),
'posts' => array(
'label' => $txt['posts'],
'default_sort_rev' => true,
'sort' => array(
'down' => 'mem.posts DESC',
'up' => 'mem.posts ASC'
),
)
);
// Add in any custom profile columns
require_once(SUBSDIR . '/Memberlist.subs.php');
if (ml_CustomProfile())
$context['columns'] += $context['custom_profile_fields']['columns'];
// The template may appreciate how many columns it needs to display
$context['colspan'] = 0;
$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
foreach ($context['columns'] as $key => $column)
{
if (isset($context['disabled_fields'][$key]) || (isset($column['link_with']) && isset($context['disabled_fields'][$column['link_with']])))
{
unset($context['columns'][$key]);
continue;
}
$context['colspan'] += isset($column['colspan']) ? $column['colspan'] : 1;
}
// Define the last column of those that remain
end($context['columns']);
$context['columns'][key($context['columns'])]['class'] = 'last_th';
$context['linktree'][] = array(
'url' => $scripturl . '?action=memberlist',
'name' => $txt['members_list']
);
$context['can_send_pm'] = allowedTo('pm_send');
// Build the memberlist button array.
$context['memberlist_buttons'] = array(
'view_all_members' => array('text' => 'view_all_members', 'image' => 'mlist.png', 'lang' => true, 'url' => $scripturl . '?action=memberlist' . ';sa=all', 'active'=> true),
);
// Are there custom fields they can search?
ml_findSearchableCustomFields();
// These are all the possible fields.
$context['search_fields'] = array(
'name' => $txt['mlist_search_name'],
'email' => $txt['mlist_search_email'],
'website' => $txt['mlist_search_website'],
'group' => $txt['mlist_search_group'],
);
foreach ($context['custom_search_fields'] as $field)
$context['search_fields']['cust_' . $field['colname']] = sprintf($txt['mlist_search_by'], $field['name']);
// What do we search for by default?
$context['search_defaults'] = array('name', 'email');
// Allow mods to add additional buttons here
call_integration_hook('integrate_memberlist_buttons');
if (!allowedTo('send_email_to_members'))
unset($context['columns']['email_address']);
if (isset($context['disabled_fields']['website']))
unset($context['columns']['website']);
if (isset($context['disabled_fields']['posts']))
unset($context['columns']['posts']);
// Jump to the sub action.
if (isset($subActions[$context['listing_by']]))
$this->{$subActions[$context['listing_by']][1]}();
else
$this->{$subActions['all'][1]}();
}
/**
* List all members, page by page, with sorting.
* Called from MemberList().
* Can be passed a sort parameter, to order the display of members.
* Calls printMemberListRows to retrieve the results of the query.
*/
public function action_mlall()
{
global $txt, $scripturl, $modSettings, $context;
// The chunk size for the cached index.
$cache_step_size = 500;
require_once(SUBSDIR . '/Memberlist.subs.php');
// Only use caching if:
// 1. there are at least 2k members,
// 2. the default sorting method (real_name) is being used,
// 3. the page shown is high enough to make a DB filesort unprofitable.
$use_cache = $modSettings['totalMembers'] > 2000 && (!isset($_REQUEST['sort']) || $_REQUEST['sort'] === 'real_name') && isset($_REQUEST['start']) && $_REQUEST['start'] > $cache_step_size;
if ($use_cache)
{
// Maybe there's something cached already.
if (!empty($modSettings['memberlist_cache']))
$memberlist_cache = @unserialize($modSettings['memberlist_cache']);
// The chunk size for the cached index.
$cache_step_size = 500;
// Only update the cache if something changed or no cache existed yet.
if (empty($memberlist_cache) || empty($modSettings['memberlist_updated']) || $memberlist_cache['last_update'] < $modSettings['memberlist_updated'])
$memberlist_cache = ml_memberCache($cache_step_size);
$context['num_members'] = $memberlist_cache['num_members'];
}
// Without cache we need an extra query to get the amount of members.
else
$context['num_members'] = ml_memberCount();
// Set defaults for sort (real_name) and start. (0)
if (!isset($_REQUEST['sort']) || !isset($context['columns'][$_REQUEST['sort']]))
$_REQUEST['sort'] = 'real_name';
if (!is_numeric($_REQUEST['start']))
{
if (preg_match('~^[^\'\\\\/]~u', Util::strtolower($_REQUEST['start']), $match) === 0)
fatal_error('Hacker?', false);
$_REQUEST['start'] = ml_alphaStart($match[0]);
}
// Build out the letter selection link bar
$context['letter_links'] = '';
for ($i = 97; $i < 123; $i++)
$context['letter_links'] .= '<a href="' . $scripturl . '?action=memberlist;sa=all;start=' . chr($i) . '#letter' . chr($i) . '">' . chr($i - 32) . '</a> ';
// Sort out the column information.
foreach ($context['columns'] as $col => $column_details)
{
$context['columns'][$col]['href'] = $scripturl . '?action=memberlist;sort=' . $col . ';start=0';
if ((!isset($_REQUEST['desc']) && $col == $_REQUEST['sort']) || ($col != $_REQUEST['sort'] && !empty($column_details['default_sort_rev'])))
$context['columns'][$col]['href'] .= ';desc';
$context['columns'][$col]['link'] = '<a href="' . $context['columns'][$col]['href'] . '" rel="nofollow">' . $context['columns'][$col]['label'] . '</a>';
$context['columns'][$col]['selected'] = $_REQUEST['sort'] == $col;
}
// Are we sorting the results
$context['sort_by'] = $_REQUEST['sort'];
$context['sort_direction'] = !isset($_REQUEST['desc']) ? 'up' : 'down';
// Construct the page index.
$context['page_index'] = constructPageIndex($scripturl . '?action=memberlist;sort=' . $_REQUEST['sort'] . (isset($_REQUEST['desc']) ? ';desc' : ''), $_REQUEST['start'], $context['num_members'], $modSettings['defaultMaxMembers']);
// Send the data to the template.
$context['start'] = $_REQUEST['start'] + 1;
$context['end'] = min($_REQUEST['start'] + $modSettings['defaultMaxMembers'], $context['num_members']);
$context['can_moderate_forum'] = allowedTo('moderate_forum');
$context['page_title'] = sprintf($txt['viewing_members'], $context['start'], $context['end']);
$context['linktree'][] = array(
'url' => $scripturl . '?action=memberlist;sort=' . $_REQUEST['sort'] . ';start=' . $_REQUEST['start'],
'name' => &$context['page_title'],
'extra_after' => ' (' . sprintf($txt['of_total_members'], $context['num_members']) . ')'
);
$limit = $_REQUEST['start'];
$where = '';
$query_parameters = array(
'regular_id_group' => 0,
'is_activated' => 1,
'sort' => $context['columns'][$_REQUEST['sort']]['sort'][$context['sort_direction']],
);
// Using cache allows to narrow down the list to be retrieved.
if ($use_cache && $_REQUEST['sort'] === 'real_name' && !isset($_REQUEST['desc']))
{
$first_offset = $_REQUEST['start'] - ($_REQUEST['start'] % $cache_step_size);
$second_offset = ceil(($_REQUEST['start'] + $modSettings['defaultMaxMembers']) / $cache_step_size) * $cache_step_size;
$where = 'mem.real_name BETWEEN {string:real_name_low} AND {string:real_name_high}';
$query_parameters['real_name_low'] = $memberlist_cache['index'][$first_offset];
$query_parameters['real_name_high'] = $memberlist_cache['index'][$second_offset];
$limit -= $first_offset;
}
// Reverse sorting is a bit more complicated...
elseif ($use_cache && $_REQUEST['sort'] === 'real_name')
{
$first_offset = floor(($memberlist_cache['num_members'] - $modSettings['defaultMaxMembers'] - $_REQUEST['start']) / $cache_step_size) * $cache_step_size;
if ($first_offset < 0)
$first_offset = 0;
$second_offset = ceil(($memberlist_cache['num_members'] - $_REQUEST['start']) / $cache_step_size) * $cache_step_size;
$where = 'mem.real_name BETWEEN {string:real_name_low} AND {string:real_name_high}';
$query_parameters['real_name_low'] = $memberlist_cache['index'][$first_offset];
$query_parameters['real_name_high'] = $memberlist_cache['index'][$second_offset];
$limit = $second_offset - ($memberlist_cache['num_members'] - $_REQUEST['start']) - ($second_offset > $memberlist_cache['num_members'] ? $cache_step_size - ($memberlist_cache['num_members'] % $cache_step_size) : 0);
}
// Add custom fields parameters too.
if (!empty($context['custom_profile_fields']['parameters']))
$query_parameters += $context['custom_profile_fields']['parameters'];
// Select the members from the database.
ml_selectMembers($query_parameters, $where, $limit, $_REQUEST['sort']);
// Add anchors at the start of each letter.
if ($_REQUEST['sort'] === 'real_name')
{
$last_letter = '';
foreach ($context['members'] as $i => $dummy)
{
$this_letter = Util::strtolower(Util::substr($context['members'][$i]['name'], 0, 1));
if ($this_letter != $last_letter && preg_match('~[a-z]~', $this_letter) === 1)
{
$context['members'][$i]['sort_letter'] = htmlspecialchars($this_letter);
$last_letter = $this_letter;
}
}
}
}
/**
* Search for members, or display search results.
* If variable $_REQUEST['search'] is empty displays search dialog box, using the search sub-template.
* Calls printMemberListRows to retrieve the results of the query.
*/
public function action_mlsearch()
{
global $txt, $scripturl, $context, $modSettings;
$db = database();
$context['page_title'] = $txt['mlist_search'];
$context['can_moderate_forum'] = allowedTo('moderate_forum');
// Are there custom fields they can search?
ml_findSearchableCustomFields();
// They're searching..
if (isset($_REQUEST['search']) && isset($_REQUEST['fields']))
{
$_POST['search'] = trim(isset($_GET['search']) ? $_GET['search'] : $_POST['search']);
$_POST['fields'] = isset($_GET['fields']) ? explode(',', $_GET['fields']) : $_POST['fields'];
$context['old_search'] = $_REQUEST['search'];
$context['old_search_value'] = urlencode($_REQUEST['search']);
// No fields? Use default...
if (empty($_POST['fields']))
$_POST['fields'] = array('name');
// Set defaults for how the results are sorted
if (!isset($_REQUEST['sort']) || !isset($context['columns'][$_REQUEST['sort']]))
$_REQUEST['sort'] = 'real_name';
// Build the column link / sort information.
foreach ($context['columns'] as $col => $column_details)
{
$context['columns'][$col]['href'] = $scripturl . '?action=memberlist;sa=search;start=0;sort=' . $col;
if ((!isset($_REQUEST['desc']) && $col == $_REQUEST['sort']) || ($col != $_REQUEST['sort'] && !empty($column_details['default_sort_rev'])))
$context['columns'][$col]['href'] .= ';desc';
if (isset($_POST['search']) && isset($_POST['fields']))
$context['columns'][$col]['href'] .= ';search=' . $_POST['search'] . ';fields=' . implode(',', $_POST['fields']);
$context['columns'][$col]['link'] = '<a href="' . $context['columns'][$col]['href'] . '" rel="nofollow">' . $context['columns'][$col]['label'] . '</a>';
$context['columns'][$col]['selected'] = $_REQUEST['sort'] == $col;
}
// set up some things for use in the template
$context['sort_direction'] = !isset($_REQUEST['desc']) ? 'up' : 'down';
$context['sort_by'] = $_REQUEST['sort'];
$query_parameters = array(
'regular_id_group' => 0,
'is_activated' => 1,
'blank_string' => '',
'search' => '%' . strtr(Util::htmlspecialchars($_POST['search'], ENT_QUOTES), array('_' => '\\_', '%' => '\\%', '*' => '%')) . '%',
'sort' => $context['columns'][$_REQUEST['sort']]['sort'][$context['sort_direction']],
);
// Search for a name
if (in_array('name', $_POST['fields']))
$fields = allowedTo('moderate_forum') ? array('member_name', 'real_name') : array('real_name');
else
$fields = array();
// Search for websites.
if (in_array('website', $_POST['fields']))
$fields += array(7 => 'website_title', 'website_url');
// Search for groups.
if (in_array('group', $_POST['fields']))
$fields += array(9 => 'IFNULL(group_name, {string:blank_string})');
// Search for an email address?
if (in_array('email', $_POST['fields']))
{
$fields += array(2 => allowedTo('moderate_forum') ? 'email_address' : '(hide_email = 0 AND email_address');
$condition = allowedTo('moderate_forum') ? '' : ')';
}
else
$condition = '';
if ($db->db_case_sensitive())
{
foreach ($fields as $key => $field)
$fields[$key] = 'LOWER(' . $field . ')';
}
$customJoin = array();
$customCount = 10;
// Any custom fields to search for - these being tricky?
foreach ($_POST['fields'] as $field)
{
$curField = substr($field, 5);
if (substr($field, 0, 5) === 'cust_' && isset($context['custom_search_fields'][$curField]))
{
$customJoin[] = 'LEFT JOIN {db_prefix}themes AS t' . $curField . ' ON (t' . $curField . '.variable = {string:t' . $curField . '} AND t' . $curField . '.id_theme = 1 AND t' . $curField . '.id_member = mem.id_member)';
$query_parameters['t' . $curField] = $curField;
$fields += array($customCount++ => 'IFNULL(t' . $curField . '.value, {string:blank_string})');
}
}
$query = $_POST['search'] == '' ? '= {string:blank_string}' : ($db->db_case_sensitive() ? 'LIKE LOWER({string:search})' : 'LIKE {string:search}');
$where = implode(' ' . $query . ' OR ', $fields) . ' ' . $query . $condition;
// Find the members from the database.
$numResults = ml_searchMembers($query_parameters, $customJoin, $where, $_REQUEST['start']);
$context['page_index'] = constructPageIndex($scripturl . '?action=memberlist;sa=search;search=' . $_POST['search'] . ';fields=' . implode(',', $_POST['fields']), $_REQUEST['start'], $numResults, $modSettings['defaultMaxMembers']);
}
else
redirectexit('action=memberlist');
$context['linktree'][] = array(
'url' => $scripturl . '?action=memberlist;sa=search',
'name' => &$context['page_title']
);
// Highlight the correct button, too!
unset($context['memberlist_buttons']['view_all_members']['active']);
}
}