Memberlist   F
last analyzed

Complexity

Total Complexity 91

Size/Duplication

Total Lines 599
Duplicated Lines 0 %

Test Coverage

Coverage 80%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 294
c 1
b 0
f 0
dl 0
loc 599
ccs 196
cts 245
cp 0.8
rs 2
wmc 91

4 Methods

Rating   Name   Duplication   Size   Complexity  
A pre_dispatch() 0 29 4
F action_index() 0 176 17
F action_mlsearch() 0 183 36
F action_mlall() 0 168 34

How to fix   Complexity   

Complex Class

Complex classes like Memberlist often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Memberlist, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file contains the functions for displaying and searching in the
5
 * members list.
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 Beta 1
15
 *
16
 */
17
18
namespace ElkArte\Controller;
19
20
use ElkArte\AbstractController;
21
use ElkArte\Exceptions\Exception;
22
use ElkArte\Helper\Util;
23
24
/**
25
 * Memberlist Controller class
26
 */
27
class Memberlist extends AbstractController
28
{
29
	/** @var array The fields that we can search */
30
	public $_search_fields;
31
32
	/**
33
	 * Entry point function, called before all others
34
	 */
35
	public function pre_dispatch()
36
	{
37
		global $context, $txt;
38
39 4
		$context['can_send_email'] = allowedTo('send_email_to_members') && showEmailAddress(0);
40
41 4
		// These are all the possible fields.
42
		$this->_search_fields = [
43
			'name' => $txt['mlist_search_name'],
44 4
			'website' => $txt['mlist_search_website'],
45 4
			'group' => $txt['mlist_search_group'],
46 4
		];
47 4
48 4
		if ($context['can_send_email'])
49
		{
50
			$this->_search_fields['email'] = $txt['mlist_search_email'];
51
		}
52 4
53 4
		// Are there custom fields they can search?
54
		require_once(SUBSDIR . '/Memberlist.subs.php');
55
		ml_findSearchableCustomFields();
56 4
57 4
		// These are handy later
58
		$context['old_search_value'] = '';
59 4
		$context['in_search'] = $this->_req->hasPost('search');
60
61
		foreach ($context['custom_search_fields'] as $field)
62
		{
63 4
			$this->_search_fields['cust_' . $field['colname']] = sprintf($txt['mlist_search_by'], $field['name']);
64
		}
65
	}
66
67
	/**
68
	 * Sets up the context for showing a listing of registered members.
69
	 * For the handlers in this file, it requires the view_mlist permission.
70
	 *
71
	 * - Accessed by ?action_memberlist.
72
	 *
73
	 * @uses Memberlist template, main sub-template.
74
	 *
75 4
	 * @see AbstractController::action_index
76
	 */
77 4
	public function action_index()
78
	{
79
		global $scripturl, $txt, $modSettings, $context;
80 4
81
		// Make sure they can view the memberlist.
82 4
		isAllowedTo('view_mlist');
83 4
84 4
		theme()->getTemplates()->load('Memberlist');
85
		$context['sub_template'] = 'memberlist';
86 4
		theme()->getLayers()->add('mlsearch');
87
88
		$context['listing_by'] = $this->_req->getQuery('sa', 'trim', 'all');
89
90
		// $subActions array format:
91 4
		// 'subaction' => array('label', 'function', 'is_selected')
92 4
		$subActions = [
93
			'all' => [$txt['view_all_members'], 'action_mlall', $context['listing_by'] === 'all'],
94
			'search' => [$txt['mlist_search'], 'action_mlsearch', $context['listing_by'] === 'search'],
95
		];
96 4
97 4
		// Set up the sort links.
98
		$context['sort_links'] = [];
99 4
		foreach ($subActions as $act => $text)
100 4
		{
101 4
			$context['sort_links'][] = [
102 4
				'label' => $text[0],
103
				'action' => $act,
104
				'selected' => $text[2],
105
			];
106 4
		}
107
108
		$context['num_members'] = $modSettings['totalMembers'];
109 4
110 2
		// Set up the standard columns...
111 2
		$context['columns'] = [
112
			'avatar' => [
113
				'label' => '',
114
				'class' => 'avatar',
115 4
			],
116 4
			'real_name' => [
117
				'label' => $txt['username'],
118
				'class' => 'username',
119
				'sort' => [
120
					'down' => 'mem.real_name DESC',
121
					'up' => 'mem.real_name ASC'
122
				],
123 4
			],
124 4
			'online' => [
125
				'label' => $txt['status'],
126 4
				'class' => 'status',
127 4
				'sort' => [
128
					'down' => allowedTo('moderate_forum') ? 'COALESCE(lo.log_time, 1) ASC, real_name ASC' : 'CASE WHEN mem.show_online THEN COALESCE(lo.log_time, 1) ELSE 1 END ASC, real_name ASC',
129
					'up' => allowedTo('moderate_forum') ? 'COALESCE(lo.log_time, 1) DESC, real_name DESC' : 'CASE WHEN mem.show_online THEN COALESCE(lo.log_time, 1) ELSE 1 END DESC, real_name DESC'
130
				],
131 4
			],
132 4
			'email_address' => [
133
				'label' => $txt['email'],
134 4
				'class' => 'email',
135 4
				'sort' => [
136
					'down' => allowedTo('moderate_forum') ? 'mem.email_address DESC' : '',
137
					'up' => allowedTo('moderate_forum') ? 'mem.email_address ASC' : ''
138
				],
139 4
			],
140 4
			'website_url' => [
141 4
				'label' => $txt['website'],
142
				'class' => 'website',
143
				'link_with' => 'website',
144
				'sort' => [
145
					'down' => 'LENGTH(mem.website_url) > 0 ASC, COALESCE(mem.website_url, 1=1) DESC, mem.website_url DESC',
146
					'up' => 'LENGTH(mem.website_url) > 0 DESC, COALESCE(mem.website_url, 1=1) ASC, mem.website_url ASC'
147
				],
148 4
			],
149 4
			'id_group' => [
150
				'label' => $txt['position'],
151
				'class' => 'group',
152
				'sort' => [
153
					'down' => 'COALESCE(mg.group_name, 1=1) DESC, mg.group_name DESC',
154
					'up' => 'COALESCE(mg.group_name, 1=1) ASC, mg.group_name ASC'
155
				],
156 4
			],
157 4
			'date_registered' => [
158
				'label' => $txt['date_registered'],
159
				'class' => 'date_registered',
160
				'sort' => [
161
					'down' => 'mem.date_registered DESC',
162
					'up' => 'mem.date_registered ASC'
163
				],
164 4
			],
165 4
			'posts' => [
166
				'label' => $txt['posts'],
167
				'class' => 'posts',
168
				'default_sort_rev' => true,
169
				'sort' => [
170
					'down' => 'mem.posts DESC',
171
					'up' => 'mem.posts ASC'
172
				],
173
			]
174
		];
175 4
176
		// Add in any custom profile columns
177
		if (ml_CustomProfile())
178
		{
179
			$context['columns'] += $context['custom_profile_fields']['columns'];
180
		}
181 4
182 4
		// The template may appreciate how many columns it needs to display
183 4
		$context['colspan'] = 0;
184
		$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : [];
185 4
		foreach ($context['columns'] as $key => $column)
186
		{
187
			if (isset($context['disabled_fields'][$key]) || (isset($column['link_with'], $context['disabled_fields'][$column['link_with']])))
188
			{
189
				unset($context['columns'][$key]);
190
				continue;
191 4
			}
192
193
			$context['colspan'] += $column['colspan'] ?? 1;
194 4
		}
195 4
196 4
		$context['breadcrumbs'][] = [
197
			'url' => $scripturl . '?action=memberlist',
198
			'name' => $txt['members_list']
199 4
		];
200 4
201
		$context['can_send_pm'] = allowedTo('pm_send');
202
203 4
		// Build the memberlist button array.
204
		if ($context['in_search'])
205
		{
206
			$context['memberlist_buttons'] = [
207
				'view_all_members' => [
208
					'text' => 'view_all_members',
209
					'lang' => true,
210
					'url' => $scripturl . '?action=memberlist;sa=all',
211 4
					'active' => true],
212
			];
213
		}
214
		else
215 4
		{
216
			$context['memberlist_buttons'] = [];
217
		}
218 4
219
		// Make fields available to the template
220
		$context['search_fields'] = $this->_search_fields;
221 4
222
		// What do we search for by default?
223 4
		$context['search_defaults'] = [
224
			'name',
225
			$context['can_send_email'] ? 'email' : ''
226
		];
227 4
228
		// Allow mods to add additional buttons here
229
		call_integration_hook('integrate_memberlist_buttons');
230
231 4
		// Drop columns they do not have permissions to see/search
232
		if (!$context['can_send_email'])
233
		{
234
			unset($context['columns']['email_address']);
235
		}
236
		if (isset($context['disabled_fields']['website']))
237 4
		{
238
			unset($context['columns']['website']);
239 4
		}
240
		if (isset($context['disabled_fields']['posts']))
241
		{
242
			unset($context['columns']['posts']);
243
		}
244
245 4
		// Jump to the sub action.
246
		if (isset($subActions[$context['listing_by']]))
247
		{
248
			$this->{$subActions[$context['listing_by']][1]}();
249
		}
250
		else
251
		{
252
			$this->{$subActions['all'][1]}();
253
		}
254 2
	}
255
256 2
	/**
257
	 * List all members, page by page, with sorting.
258
	 *
259 2
	 * - Called from MemberList().
260 2
	 * - Can be passed a sort parameter, to order the display of members.
261
	 * - Calls printMemberListRows to retrieve the results of the query.
262 2
	 */
263
	public function action_mlall(): void
264
	{
265 2
		global $txt, $scripturl, $modSettings, $context;
266 2
267 2
		// The chunk size for the cached index.
268
		$cache_step_size = 500;
269
		$memberlist_cache = '';
270
271
		require_once(SUBSDIR . '/Memberlist.subs.php');
272
273 2
		// Some handy shortcuts
274 2
		$start = $this->_req->getQuery('start', 'intval');
275 2
		$desc = $this->_req->getQuery('desc', 'trim');
276 2
		$sort = $this->_req->getQuery('sort', 'trim');
277
278 2
		// Only use caching if:
279
		// 1. There are at least 2k members.
280
		// 2. The default sorting method (real_name) is being used.
281
		// 3. The page shown is high enough to make a DB file sort unprofitable.
282
		$use_cache = $modSettings['totalMembers'] > 2000
283
			&& (!isset($sort) || $sort === 'real_name')
284
			&& isset($start)
285
			&& $start > $cache_step_size;
286
287
		if ($use_cache)
288
		{
289
			// Maybe there's something cached already.
290
			if (!empty($modSettings['memberlist_cache']))
291
			{
292
				$memberlist_cache = Util::unserialize($modSettings['memberlist_cache']);
293
			}
294
295
			// The chunk size for the cached index.
296
			$cache_step_size = 500;
297
298
			// Only update the cache if something changed or no cache existed yet.
299
			if (empty($memberlist_cache) || empty($modSettings['memberlist_updated']) || $memberlist_cache['last_update'] < $modSettings['memberlist_updated'])
300 2
			{
301
				$memberlist_cache = ml_memberCache($cache_step_size);
302
			}
303
304 2
			$context['num_members'] = $memberlist_cache['num_members'];
305
		}
306 2
		// Without cache, we need an extra query to get the number of members.
307
		else
308
		{
309
			$context['num_members'] = ml_memberCount();
310 2
		}
311
312
		// Set defaults for sort (real_name)
313
		if (!isset($sort, $context['columns'][$sort]['sort']))
314
		{
315
			$sort = 'real_name';
316
		}
317
318
		// Looking at a specific rolodex letter?
319
		if (!is_numeric($start))
320
		{
321 2
			if (preg_match('~^[^\'\\\\/]~u', Util::strtolower($start), $match) === 0)
322 2
			{
323
				throw new Exception('Hacker?', false);
324 2
			}
325
326
			$start = ml_alphaStart($match[0]);
327
		}
328 2
329
		// Build out the letter selection link bar
330 2
		$context['letter_links'] = '';
331
		for ($i = 97; $i < 123; $i++)
332 2
		{
333 2
			$context['letter_links'] .= '<a href="' . $scripturl . '?action=memberlist;sa=all;start=' . chr($i) . '#letter' . chr($i) . '">' . chr($i - 32) . '</a> ';
334
		}
335 2
336
		// Sort out the column information.
337
		foreach ($context['columns'] as $col => $column_details)
338 2
		{
339 2
			$context['columns'][$col]['href'] = $scripturl . '?action=memberlist;sort=' . $col . ';start=0';
340 2
341
			if ((!isset($desc) && $col === $sort)
342 2
				|| ($col !== $sort && !empty($column_details['default_sort_rev'])))
343
			{
344
				$context['columns'][$col]['href'] .= ';desc';
345
			}
346
347 2
			$context['columns'][$col]['link'] = '<a href="' . $context['columns'][$col]['href'] . '" rel="nofollow">' . $context['columns'][$col]['label'] . '</a>';
348 2
			$context['columns'][$col]['selected'] = $sort === $col;
349
			if ($context['columns'][$col]['selected'])
350
			{
351 2
				$context['columns'][$col]['class'] .= ' selected';
352
			}
353
		}
354 2
355 2
		// Are we sorting the results?
356 2
		$context['sort_by'] = $sort;
357 2
		$context['sort_direction'] = isset($desc) ? 'down' : 'up';
358 2
359 2
		// Construct the page index.
360 2
		$context['page_index'] = constructPageIndex('{scripturl}?action=memberlist;sort=' . $sort . (isset($desc) ? ';desc' : ''), $start, $context['num_members'], $modSettings['defaultMaxMembers']);
361 2
362
		// Send the data to the template.
363
		$context['start'] = $start + 1;
364 2
		$context['end'] = min($start + $modSettings['defaultMaxMembers'], $context['num_members']);
365 2
		$context['can_moderate_forum'] = allowedTo('moderate_forum');
366
		$context['page_title'] = sprintf($txt['viewing_members'], $context['start'], $context['end']);
367 2
		$context['breadcrumbs'][] = [
368 2
			'url' => $scripturl . '?action=memberlist;sort=' . $sort . ';start=' . $start,
369 2
			'name' => &$context['page_title'],
370
			'extra_after' => ' (' . sprintf($txt['of_total_members'], $context['num_members']) . ')'
371
		];
372
373 2
		$limit = $start;
374
		$where = '';
375
		$query_parameters = [
376
			'regular_id_group' => 0,
377
			'is_activated' => 1,
378
			'sort' => $context['columns'][$sort]['sort'][$context['sort_direction']],
379
		];
380
381
		// Using cache allows narrowing down the list to be retrieved.
382
		if ($use_cache && $sort === 'real_name' && !isset($desc))
383
		{
384
			$first_offset = $start - ($start % $cache_step_size);
385 2
			$second_offset = ceil(($start + $modSettings['defaultMaxMembers']) / $cache_step_size) * $cache_step_size;
386
			$second_offset = min(max(array_keys($memberlist_cache['index'])), $second_offset);
387
388
			$where = 'mem.real_name BETWEEN {string:real_name_low} AND {string:real_name_high}';
389
			$query_parameters['real_name_low'] = $memberlist_cache['index'][$first_offset];
390
			$query_parameters['real_name_high'] = $memberlist_cache['index'][$second_offset];
391
			$limit -= $first_offset;
392
		}
393
		// Reverse sorting is a bit more complicated...
394
		elseif ($use_cache && $sort === 'real_name')
395
		{
396
			$first_offset = floor(($memberlist_cache['num_members'] - $modSettings['defaultMaxMembers'] - $start) / $cache_step_size) * $cache_step_size;
397
			if ($first_offset < 0)
398
			{
399
				$first_offset = 0;
400
			}
401 2
402
			$second_offset = ceil(($memberlist_cache['num_members'] - $start) / $cache_step_size) * $cache_step_size;
403
404
			$where = 'mem.real_name BETWEEN {string:real_name_low} AND {string:real_name_high}';
405
			$query_parameters['real_name_low'] = $memberlist_cache['index'][$first_offset];
406
			$query_parameters['real_name_high'] = $memberlist_cache['index'][$second_offset];
407 2
			$limit = $second_offset - ($memberlist_cache['num_members'] - $start) - ($second_offset > $memberlist_cache['num_members'] ? $cache_step_size - ($memberlist_cache['num_members'] % $cache_step_size) : 0);
408
		}
409
410 2
		// Add custom fields parameters too.
411
		if (!empty($context['custom_profile_fields']['parameters']))
412 2
		{
413 2
			$query_parameters += $context['custom_profile_fields']['parameters'];
414
		}
415 2
416
		// Select the members from the database.
417 2
		ml_selectMembers($query_parameters, $where, $limit, $sort);
418
419 2
		// Add anchors at the start of each letter.
420 2
		if ($sort === 'real_name')
421
		{
422
			$last_letter = '';
423
			foreach ($context['members'] as $i => $dummy)
424 2
			{
425
				$this_letter = Util::strtolower(Util::substr($context['members'][$i]['name'], 0, 1));
426
427
				if ($this_letter !== $last_letter && preg_match('~[a-z]~', $this_letter) === 1)
428
				{
429
					$context['members'][$i]['sort_letter'] = Util::htmlspecialchars($this_letter);
430
					$last_letter = $this_letter;
431
				}
432
			}
433 2
		}
434
	}
435 2
436
	/**
437 2
	 * Search for members or display search results.
438 2
	 *
439
	 * - If variable $_REQUEST['search'] is empty, displays a search dialog box,
440
	 * using the search sub-template.
441 2
	 * - Calls printMemberListRows to retrieve the results of the query.
442 2
	 */
443
	public function action_mlsearch(): void
444
	{
445 2
		global $txt, $scripturl, $context, $modSettings;
446 2
447 2
		$context['page_title'] = $txt['mlist_search'];
448 2
		$context['can_moderate_forum'] = allowedTo('moderate_forum');
449 2
450
		// They're searching..
451 2
		if (($this->_req->hasQuery('search') && $this->_req->hasQuery('fields'))
452 2
			|| ($this->_req->hasPost('search') && $this->_req->hasPost('fields')))
453 2
		{
454
			// Some handy shortcuts
455 2
			$start = $this->_req->getQuery('start', 'intval', 0);
456
			$has_desc = $this->_req->hasQuery('desc');
457 2
			$sort = $this->_req->getQuery('sort', 'trim|strval', 'real_name');
458
			$search = Util::htmlspecialchars($this->_req->getRequest('search', 'trim|strval', ''), ENT_QUOTES);
459
			$input_fields = [];
460 2
			if ($this->_req->hasQuery('fields'))
461
			{
462
				$fields_csv = $this->_req->getQuery('fields', 'trim|strval', '');
463 2
				$input_fields = $fields_csv === '' ? [] : explode(',', $fields_csv);
464
			}
465
			elseif ($this->_req->hasPost('fields'))
466
			{
467
				$input_fields = is_array($this->_req->post->fields) ? $this->_req->post->fields : [];
468
			}
469 2
470
			$fields_key = array_keys($this->_search_fields);
471 2
			$context['search_defaults'] = [];
472
			foreach ($input_fields as $val)
473
			{
474
				if (in_array($val, $fields_key, true))
475 2
				{
476
					$context['search_defaults'] = $input_fields;
477 2
				}
478
			}
479 2
480
			$context['old_search_value'] = $search;
481 2
482
			// No fields?  Use default...
483
			if (empty($input_fields))
484 2
			{
485 2
				$input_fields = [
486 2
					'name',
487
					$context['can_send_email'] ? 'email' : ''
488
				];
489
			}
490 2
491 2
			// Set defaults for how the results are sorted
492 2
			if (!isset($context['columns'][$sort]['sort']))
493 2
			{
494
				$sort = 'real_name';
495
			}
496
497 2
			// Build the column link / sort information.
498 2
			foreach ($context['columns'] as $col => $column_details)
499 2
			{
500 2
				$context['columns'][$col]['href'] = $scripturl . '?action=memberlist;sa=search;start=0;sort=' . $col;
501 2
502
				if ((!$has_desc && $col === $sort) || ($col !== $sort && !empty($column_details['default_sort_rev'])))
503
				{
504
					$context['columns'][$col]['href'] .= ';desc';
505 2
				}
506
507 2
				$context['columns'][$col]['href'] .= ';search=' . $search . ';fields=' . implode(',', $input_fields);
508
				$context['columns'][$col]['link'] = '<a href="' . $context['columns'][$col]['href'] . '" rel="nofollow">' . $context['columns'][$col]['label'] . '</a>';
509
				$context['columns'][$col]['selected'] = $sort === $col;
510
			}
511
512
			// set up some things for use in the template
513
			$context['sort_direction'] = $has_desc ? 'down' : 'up';
514
			$context['sort_by'] = $sort;
515 2
			$context['memberlist_buttons'] = [
516
				'view_all_members' => ['text' => 'view_all_members',
517
					'lang' => true,
518
					'url' => $scripturl . '?action=memberlist;sa=all',
519
					'active' => true],
520
			];
521 2
522
			$query_parameters = [
523
				'regular_id_group' => 0,
524
				'is_activated' => 1,
525
				'blank_string' => '',
526
				'search' => '%' . strtr($search, ['_' => '\\_', '%' => '\\%', '*' => '%']) . '%',
527 2
				'sort' => $context['columns'][$sort]['sort'][$context['sort_direction']],
528
			];
529
530
			// Search for a name
531
			if (in_array('name', $input_fields, true))
532
			{
533
				$fields = allowedTo('moderate_forum') ? ['member_name', 'real_name'] : ['real_name'];
534 2
			}
535
			else
536
			{
537 2
				$fields = [];
538
			}
539 2
540
			// Search for websites.
541
			if (in_array('website', $input_fields, true))
542 2
			{
543 2
				$fields += [7 => 'website_title', 'website_url'];
544 2
			}
545
546
			// Search for groups.
547 2
			if (in_array('group', $input_fields, true))
548
			{
549 2
				$fields += [9 => 'COALESCE(group_name, {string:blank_string})'];
550 2
			}
551
552
			// Search for an email address?
553
			if (in_array('email', $input_fields, true))
554
			{
555 1
				$fields += [2 => $context['can_send_email'] ? 'email_address' : ''];
556
				$condition = allowedTo('moderate_forum') ? '' : ')';
557
			}
558 2
			else
559 2
			{
560 2
				$condition = '';
561
			}
562
563
			foreach ($fields as $key => $field)
564
			{
565
				if ($key === 9)
566
				{
567 2
					$fields[$key] = 'COALESCE({column_case_insensitive:group_name}, {string:blank_string})';
568
					continue;
569
				}
570
571
				$fields[$key] = '{column_case_insensitive:' . $field . '}';
572 2
			}
573 2
574 2
			$customJoin = [];
575
			$customCount = 10;
576
			$validFields = $input_fields ?? [];
577 2
578 2
			// Any custom fields to search for - these being tricky?
579 2
			foreach ($input_fields as $field)
580
			{
581
				$curField = substr($field, 5);
582
				if (isset($context['custom_search_fields'][$curField]) && str_starts_with($field, 'cust_'))
583
				{
584
					$customJoin[] = 'LEFT JOIN {db_prefix}custom_fields_data AS cfd' . $field . ' ON (cfd' . $field . '.variable = {string:cfd' . $field . '} AND cfd' . $field . '.id_member = mem.id_member)';
585
					$query_parameters['cfd' . $field] = $curField;
586 2
					$fields += [$customCount++ => 'COALESCE(cfd' . $field . '.value, {string:blank_string})'];
587 2
					$validFields[] = $field;
588 2
				}
589
			}
590
591
			$field = $sort;
592 2
			$curField = substr($field, 5);
593 2
			if (isset($context['custom_search_fields'][$curField]) && str_starts_with($field, 'cust_'))
594
			{
595
				$customJoin[] = 'LEFT JOIN {db_prefix}custom_fields_data AS cfd' . $field . ' ON (cfd' . $field . '.variable = {string:cfd' . $field . '} AND cfd' . $field . '.id_member = mem.id_member)';
596
				$query_parameters['cfd' . $field] = $curField;
597
				$validFields[] = $field;
598
			}
599
600
			if (empty($fields))
601
			{
602
				redirectexit('action=memberlist');
603
			}
604
605
			$validFields = array_unique($validFields);
606
			$query = $search === '' ? '= {string:blank_string}' : ('LIKE {string_case_insensitive:search}');
607
			$where = implode(' ' . $query . ' OR ', $fields) . ' ' . $query . $condition;
608
609
			// Find the members from the database.
610
			$numResults = ml_searchMembers($query_parameters, array_unique($customJoin), $where, $start);
611
			$context['letter_links'] = '';
612
			$context['page_index'] = constructPageIndex('{scripturl}?action=memberlist;sa=search;search=' . $search . ';fields=' . implode(',', $validFields), $start, $numResults, $modSettings['defaultMaxMembers']);
613
		}
614
		else
615
		{
616
			redirectexit('action=memberlist');
617
		}
618
619
		$context['breadcrumbs'][] = [
620
			'url' => $scripturl . '?action=memberlist;sa=search',
621
			'name' => &$context['page_title']
622
		];
623
624
		// Highlight the correct button, too!
625
		unset($context['memberlist_buttons']['view_all_members']['active']);
626
	}
627
}
628