Search   F
last analyzed

Complexity

Total Complexity 84

Size/Duplication

Total Lines 645
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 272
dl 0
loc 645
ccs 0
cts 421
cp 0
rs 2
c 1
b 0
f 0
wmc 84

8 Methods

Rating   Name   Duplication   Size   Complexity  
F action_search() 0 160 31
A _fill_default_search_params() 0 23 2
A _prepareParticipants() 0 13 4
A action_index() 0 4 1
A buildQuickModerationButtons() 0 47 1
B pre_dispatch() 0 26 8
B _controlVerifications() 0 23 8
F action_results() 0 255 29

How to fix   Complexity   

Complex Class

Complex classes like Search 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 Search, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Handle all searching from here.
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\Controller;
18
19
use ElkArte\AbstractController;
20
use ElkArte\Cache\Cache;
21
use ElkArte\Exceptions\Exception;
22
use ElkArte\Helper\Util;
23
use ElkArte\Helper\ValuesContainer;
24
use ElkArte\Languages\Txt;
25
use ElkArte\MembersList;
26
use ElkArte\MessagesCallback\BodyParser\Compact;
27
use ElkArte\MessagesCallback\BodyParser\Normal;
28
use ElkArte\MessagesCallback\SearchRenderer;
29
use ElkArte\MessageTopicIcons;
30
use ElkArte\Search\SearchApiWrapper;
31
use ElkArte\Search\SearchParams;
32
use ElkArte\Search\WeightFactors;
33
use ElkArte\VerificationControls\VerificationControlsIntegrate;
34
35
/**
36
 * Handle the searching for the site
37
 *
38
 * @package Search
39
 */
40
class Search extends AbstractController
41
{
42
	/** @var \ElkArte\Search\Search Holds the search object */
43
	protected $_search;
44
45
	/** @var null|MessageTopicIcons The class that takes care of rendering the message icons */
46
	protected $_icon_sources;
47
48
	/** @var array */
49
	protected $_participants = [];
50
51
	/**
52
	 * Called before any other action method in this class.
53
	 *
54
	 * - If coming from the quick reply allows to route to the proper action
55
	 * - if needed (for example external search engine or members search
56
	 */
57
	public function pre_dispatch()
58
	{
59
		global $modSettings;
60
61
		// Coming from quick search box and going to some custom place?
62
		$search_selection = $this->_req->getRequest('search_selection', 'trim');
63
		$search = $this->_req->getRequest('search', 'trim');
64
		if (isset($search_selection) && !empty($modSettings['additional_search_engines']))
65
		{
66
			$engines = prepareSearchEngines();
67
			if (isset($engines[$search_selection]))
68
			{
69
				$engine = $engines[$search_selection];
70
				redirectexit($engine['url'] . urlencode(implode($engine['separator'], explode(' ', $search))));
71
			}
72
		}
73
74
		// If coming from the quick search box, and we want to search on members, well we need to do that ;)
75
		if (isset($search_selection) && $search_selection === 'members')
76
		{
77
			redirectexit('action=memberlist;sa=search;fields=name,email;search=' . urlencode($search));
78
		}
79
		// If load management is on and the load is high, no need to even show the form.
80
		if (!empty($modSettings['loadavg_search']) && $modSettings['current_load'] >= $modSettings['loadavg_search'])
81
		{
82
			throw new Exception('loadavg_search_disabled', false);
83
		}
84
	}
85
86
	/**
87
	 * Intended entry point for this class.
88
	 *
89
	 * - The default action for no sub-action is... present the search screen
90
	 *
91
	 * @see AbstractController::action_index
92
	 */
93
	public function action_index()
94
	{
95
		// Call the right method.
96
		$this->action_search();
97
	}
98
99
	/**
100
	 * Ask the user what they want to search for.
101
	 *
102
	 * What it does:
103
	 *
104
	 * - Shows the screen to search forum posts (action=search),
105
	 * - Uses the main sub template of the Search template.
106
	 * - Uses the Search language file.
107
	 * - Requires the search_posts permission.
108
	 * - Decodes and loads search parameters given in the URL (if any).
109
	 * - The form redirects to index.php?action=search;sa=results.
110
	 *
111
	 * @throws Exception loadavg_search_disabled
112
	 * @uses Search template, searchform sub template
113
	 *
114
	 * @uses Search language file and Errors language when needed
115
	 */
116
	public function action_search()
117
	{
118
		global $txt, $modSettings, $context;
119
120
		// Is the load average too high to allow searching just now?
121
		if (!empty($modSettings['loadavg_search']) && $modSettings['current_load'] >= $modSettings['loadavg_search'])
122
		{
123
			throw new Exception('loadavg_search_disabled', false);
124
		}
125
126
		Txt::load('Search');
127
128
		// Don't load this in XML mode.
129
		if ($this->getApi() === false)
130
		{
131
			theme()->getTemplates()->load('Search');
132
			$context['sub_template'] = 'searchform';
133
			loadJavascriptFile('suggest.js', array('defer' => true));
134
		}
135
136
		// Check the user's permissions.
137
		isAllowedTo('search_posts');
138
139
		// Link tree....
140
		$context['breadcrumbs'][] = array(
141
			'url' => getUrl('action', ['action' => 'search']),
142
			'name' => $txt['search']
143
		);
144
145
		// This is hard coded maximum string length.
146
		$context['search_string_limit'] = 100;
147
148
		$context['require_verification'] = $this->user->is_guest && !empty($modSettings['search_enable_captcha']) && empty($_SESSION['ss_vv_passed']);
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
149
		if ($context['require_verification'])
150
		{
151
			// Build a verification control for the form
152
			$verificationOptions = array(
153
				'id' => 'search',
154
			);
155
156
			$context['require_verification'] = VerificationControlsIntegrate::create($verificationOptions);
157
			$context['visual_verification_id'] = $verificationOptions['id'];
158
		}
159
160
		// If you got back from search;sa=results by using the breadcrumbs, you get your original search parameters back.
161
		$params = $this->_req->getQuery('params');
162
		if ($this->_search === null && isset($params))
163
		{
164
			$search_params = new SearchParams($params);
165
166
			$context['search_params'] = $search_params->get();
167
		}
168
169
		$search = $this->_req->getRequest('search', 'un_htmlspecialchars|trim');
170
		if (isset($search))
171
		{
172
			$context['search_params']['search'] = $search;
173
		}
174
175
		if (isset($context['search_params']['search']))
176
		{
177
			$context['search_params']['search'] = Util::htmlspecialchars($context['search_params']['search']);
178
		}
179
180
		if (isset($context['search_params']['userspec']))
181
		{
182
			$context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec'], ENT_COMPAT, 'UTF-8');
183
		}
184
185
		if (!empty($context['search_params']['searchtype']))
186
		{
187
			$context['search_params']['searchtype'] = 2;
188
		}
189
190
		if (!empty($context['search_params']['minage']))
191
		{
192
			$context['search_params']['minage'] = date("Y-m-d", strtotime('-' . $context['search_params']['minage'] . ' days'));
193
		}
194
195
		if (!empty($context['search_params']['maxage']))
196
		{
197
			if ($context['search_params']['maxage'] === 9999)
198
			{
199
				$context['search_params']['maxage'] = 0;
200
			}
201
			else
202
			{
203
				$context['search_params']['maxage'] = date("Y-m-d", strtotime('-' . $context['search_params']['maxage'] . ' days'));
204
			}
205
		}
206
207
		$context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
208
		$context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
209
210
		// Load the error text strings if there were errors in the search.
211
		if (!empty($context['search_errors']))
212
		{
213
			Txt::load('Errors');
214
			$context['search_errors']['messages'] = [];
215
			foreach ($context['search_errors'] as $search_error => $dummy)
216
			{
217
				if ($search_error === 'messages')
218
				{
219
					continue;
220
				}
221
222
				if ($search_error === 'string_too_long')
223
				{
224
					$txt['error_string_too_long'] = sprintf($txt['error_string_too_long'], $context['search_string_limit']);
225
				}
226
227
				$context['search_errors']['messages'][] = $txt['error_' . $search_error];
228
			}
229
		}
230
231
		require_once(SUBSDIR . '/Boards.subs.php');
232
		$context += getBoardList(array('not_redirection' => true));
233
234
		$context['boards_in_category'] = array();
235
		foreach ($context['categories'] as $cat => &$category)
236
		{
237
			$context['boards_in_category'][$cat] = count($category['boards']);
238
			$category['child_ids'] = array_keys($category['boards']);
239
			foreach ($category['boards'] as &$board)
240
			{
241
				$board['selected'] = (empty($context['search_params']['brd']) && (empty($modSettings['recycle_enable']) || $board['id'] != $modSettings['recycle_board']) && !in_array($board['id'], (array) $this->user->ignoreboards)) || (!empty($context['search_params']['brd']) && in_array($board['id'], $context['search_params']['brd']));
0 ignored issues
show
Bug Best Practice introduced by
The property ignoreboards does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
242
			}
243
		}
244
245
		$topic = $this->_req->getRequest('topic', 'intval', 0);
246
		if (!empty($topic))
247
		{
248
			$context['search_params']['topic'] = $topic;
249
			$context['search_params']['show_complete'] = true;
250
		}
251
252
		if (!empty($context['search_params']['topic']))
253
		{
254
			$context['search_params']['topic'] = (int) $context['search_params']['topic'];
255
256
			$context['search_topic'] = array(
257
				'id' => $context['search_params']['topic'],
258
				'href' => getUrl('action', ['topic' => $context['search_params']['topic'] . '.0']),
259
			);
260
261
			require_once(SUBSDIR . '/Topic.subs.php');
262
			$context['search_topic']['subject'] = getSubject($context['search_params']['topic']);
263
			$context['search_topic']['link'] = '<a href="' . $context['search_topic']['href'] . '">' . $context['search_topic']['subject'] . '</a>';
264
		}
265
266
		$context['page_title'] = $txt['set_parameters'];
267
		$context['search_params'] = $this->_fill_default_search_params($context['search_params']);
268
269
		// Start guest off collapsed
270
		if ($context['user']['is_guest'] && !isset($context['minmax_preferences']['asearch']))
271
		{
272
			$context['minmax_preferences']['asearch'] = 1;
273
		}
274
275
		call_integration_hook('integrate_search');
276
	}
277
278
	/**
279
	 * Fills the empty spaces in an array with the default values for search params
280
	 *
281
	 * @param array $array
282
	 *
283
	 * @return array
284
	 */
285
	private function _fill_default_search_params($array)
286
	{
287
		$default = array(
288
			'search' => '',
289
			'userspec' => '*',
290
			'searchtype' => 0,
291
			'show_complete' => 0,
292
			'subject_only' => 0,
293
			'minage' => 0,
294
			'maxage' => 9999,
295
			'sort' => 'relevance',
296
		);
297
298
		$array = array_merge($default, $array);
299
		if (empty($array['userspec']))
300
		{
301
			$array['userspec'] = '*';
302
		}
303
304
		$array['show_complete'] = (int) $array['show_complete'];
305
		$array['subject_only'] = (int) $array['subject_only'];
306
307
		return $array;
308
	}
309
310
	/**
311
	 * Gather the results and show them.
312
	 *
313
	 * What it does:
314
	 *
315
	 * - Checks user input and searches the messages table for messages matching the query.
316
	 * - Requires the search_posts permission.
317
	 * - Uses the results sub template of the Search template.
318
	 * - Uses the Search language file.
319
	 * - Stores the results into the search cache.
320
	 * - Show the results of the search query.
321
	 */
322
	public function action_results()
323
	{
324
		global $modSettings, $txt, $settings, $context, $options, $messages_request;
325
326
		// No, no, no... this is a bit hard on the server, so don't you go prefetching it!
327
		stop_prefetching();
328
329
		// These vars don't require an interface, they're just here for tweaking.
330
		$recentPercentage = 0.30;
331
332
		// Message length used to tweak messages relevance of the results
333
		$humungousTopicPosts = 200;
334
		$shortTopicPosts = 5;
335
		$maxMembersToSearch = 500;
336
337
		// Maximum number of results
338
		$maxMessageResults = empty($modSettings['search_max_results']) ? 0 : $modSettings['search_max_results'] * 5;
339
340
		// Start with no errors.
341
		$context['search_errors'] = array();
342
343
		// Number of pages hard maximum - normally not set at all.
344
		$modSettings['search_max_results'] = empty($modSettings['search_max_results']) ? 200 * $modSettings['search_results_per_page'] : (int) $modSettings['search_max_results'];
345
346
		// Maximum length of the string.
347
		$context['search_string_limit'] = 100;
348
349
		Txt::load('Search');
350
		if ($this->getApi() === false)
351
		{
352
			theme()->getTemplates()->load('Search');
353
		}
354
		// If we're doing XML we need to use the results template regardless really.
355
		else
356
		{
357
			$context['sub_template'] = 'results';
358
		}
359
360
		// Are you allowed?
361
		isAllowedTo('search_posts');
362
363
		$this->_search = new \ElkArte\Search\Search();
364
		$this->_search->setWeights(new WeightFactors($modSettings, $this->user->is_admin));
365
366
		$params = $this->_req->getRequest('params', '', '');
367
		$search_params = new SearchParams($params);
368
		$search_params->merge((array) $this->_req->post, $recentPercentage, $maxMembersToSearch);
369
370
		$this->_search->setParams($search_params, !empty($modSettings['search_simple_fulltext']));
371
372
		$context['compact'] = $this->_search->isCompact();
373
374
		// Nothing??
375
		if ($this->_search->param('search') === false || $this->_search->param('search') === '')
376
		{
377
			$context['search_errors']['invalid_search_string'] = true;
378
		}
379
		// Too long?
380
		elseif (Util::strlen($this->_search->param('search')) > $context['search_string_limit'])
381
		{
382
			$context['search_errors']['string_too_long'] = true;
383
		}
384
385
		// Build the search array
386
		// $modSettings ['search_simple_fulltext'] is an hidden setting that will
387
		// do fulltext searching in the most basic way.
388
		$searchArray = $this->_search->getSearchArray();
389
390
		// This is used to remember words that will be ignored (because too short usually)
391
		$context['search_ignored'] = $this->_search->getIgnored();
392
393
		// Make sure at least one word is being searched for.
394
		if (empty($searchArray))
395
		{
396
			if (!empty($context['search_ignored']))
397
			{
398
				$context['search_errors']['search_string_small_words'] = true;
399
			}
400
			else
401
			{
402
				$context['search_errors']['invalid_search_string' . ($this->_search->foundBlockListedWords() ? '_blocklist' : '')] = true;
403
			}
404
405
			// Don't allow duplicate error messages if one string is too short.
406
			if (isset($context['search_errors']['search_string_small_words'], $context['search_errors']['invalid_search_string']))
407
			{
408
				unset($context['search_errors']['invalid_search_string']);
409
			}
410
		}
411
412
		// Let the user adjust the search query, should they wish?
413
		$context['search_params'] = (array) $this->_search->getSearchParams(true);
414
		if (isset($context['search_params']['search']))
415
		{
416
			$context['search_params']['search'] = Util::htmlspecialchars($context['search_params']['search']);
417
		}
418
419
		if (isset($context['search_params']['userspec']))
420
		{
421
			$context['search_params']['userspec'] = Util::htmlspecialchars($context['search_params']['userspec']);
422
		}
423
424
		if (empty($context['search_params']['minage']))
425
		{
426
			$context['search_params']['minage'] = 0;
427
		}
428
429
		if (empty($context['search_params']['maxage']))
430
		{
431
			$context['search_params']['maxage'] = 9999;
432
		}
433
434
		$context['search_params'] = $this->_fill_default_search_params($context['search_params']);
435
436
		$this->_controlVerifications();
437
438
		$context['params'] = $this->_search->compileURLparams();
439
440
		// ... and add the links to the link tree.
441
		$context['breadcrumbs'][] = [
442
			'url' => getUrl('action', ['action' => 'search', 'params' => $context['params']]),
443
			'name' => $txt['search']
444
		];
445
446
		$context['breadcrumbs'][] = [
447
			'url' => getUrl('action', ['action' => 'search', 'sa' => 'results', 'params' => $context['params']]),
448
			'name' => $txt['search_results']
449
		];
450
451
		// Start guest off collapsed
452
		if ($context['user']['is_guest'] && !isset($context['minmax_preferences']['asearch']))
453
		{
454
			$context['minmax_preferences']['asearch'] = 1;
455
		}
456
457
		// *** A last error check
458
		call_integration_hook('integrate_search_errors');
459
460
		// One or more search errors? Go back to the first search screen.
461
		if (!empty($context['search_errors']))
462
		{
463
			return $this->action_search();
464
		}
465
466
		// Spam me not, Spam-a-lot?
467
		if (empty($_SESSION['last_ss']) || $_SESSION['last_ss'] !== $this->_search->param('search'))
468
		{
469
			spamProtection('search');
470
		}
471
472
		// Store the last search string to allow pages of results to be browsed.
473
		$_SESSION['last_ss'] = $this->_search->param('search');
474
475
		try
476
		{
477
			$search_config = new ValuesContainer([
478
				'humungousTopicPosts' => $humungousTopicPosts,
479
				'shortTopicPosts' => $shortTopicPosts,
480
				'maxMessageResults' => $maxMessageResults,
481
				'search_index' => empty($modSettings['search_index']) ? '' : $modSettings['search_index'],
482
				'banned_words' => empty($modSettings['search_banned_words']) ? [] : explode(',', $modSettings['search_banned_words']),
483
			]);
484
			$context['topics'] = $this->_search->searchQuery(
485
				new SearchApiWrapper($search_config, $this->_search->getSearchParams())
486
			);
487
		}
488
		catch (\Exception $exception)
489
		{
490
			$context['search_errors'][$exception->getMessage()] = true;
491
492
			return $this->action_search();
493
		}
494
495
		// Did we find anything?
496
		if (!empty($context['topics']))
497
		{
498
			// Create an array for the permissions.
499
			$boards_can = boardsAllowedTo(array('post_reply_own', 'post_reply_any', 'mark_any_notify'), true, false);
500
501
			// How's about some quick moderation?
502
			if (!empty($options['display_quick_mod']))
503
			{
504
				$boards_can = array_merge($boards_can, boardsAllowedTo(array('lock_any', 'lock_own', 'make_sticky', 'move_any', 'move_own', 'remove_any', 'remove_own', 'merge_any'), true, false));
505
506
				$context['can_lock'] = in_array(0, $boards_can['lock_any']);
507
				$context['can_sticky'] = in_array(0, $boards_can['make_sticky']);
508
				$context['can_move'] = in_array(0, $boards_can['move_any']);
509
				$context['can_remove'] = in_array(0, $boards_can['remove_any']);
510
				$context['can_merge'] = in_array(0, $boards_can['merge_any']);
511
			}
512
513
			// What messages are we using?
514
			$msg_list = array_keys($context['topics']);
515
			$posters = $this->_search->loadPosters($msg_list, count($context['topics']));
516
517
			call_integration_hook('integrate_search_message_list', array(&$msg_list, &$posters));
518
519
			if (!empty($posters))
520
			{
521
				MembersList::load(array_unique($posters));
522
			}
523
524
			// Get the messages out for the callback - select enough that it can be made to look just like Display.
525
			$messages_request = $this->_search->loadMessagesRequest($msg_list, count($context['topics']));
526
527
			// If there are no results that means the things in the cache got deleted, so pretend we have no topics anymore.
528
			if ($this->_search->noMessages($messages_request))
529
			{
530
				$context['topics'] = array();
531
			}
532
533
			$this->_prepareParticipants(!empty($modSettings['enableParticipation']), (int) $this->user->id);
534
		}
535
536
		// Now that we know how many results to expect we can start calculating the page numbers.
537
		$start = $this->_req->getRequest('start', 'intval', 0);
538
		$context['page_index'] = constructPageIndex('{scripturl}?action=search;sa=results;params=' . $context['params'], $start, $this->_search->getNumResults(), $modSettings['search_results_per_page'], false);
539
540
		// Consider the search complete!
541
		Cache::instance()->remove('search_start:' . ($this->user->is_guest ? $this->user->ip : $this->user->id));
542
543
		$context['sub_template'] = 'results';
544
		$context['page_title'] = $txt['search_results'];
545
546
		$this->_icon_sources = new MessageTopicIcons(!empty($modSettings['messageIconChecks_enable']), $settings['theme_dir']);
547
548
		// Set the callback.  (do you REALIZE how much memory all the messages would take?!?)
549
		// This will be called from the template.
550
		if ($this->_search->isCompact())
551
		{
552
			$bodyParser = new Compact($this->_search->getSearchArray(), empty($modSettings['search_method']));
553
		}
554
		else
555
		{
556
			$bodyParser = new Normal($this->_search->getSearchArray(), empty($modSettings['search_method']));
557
		}
558
559
		$opt = new ValuesContainer([
560
			'icon_sources' => $this->_icon_sources,
561
			'show_signatures' => false,
562
			'boards_can' => $boards_can ?? [],
563
		]);
564
		$renderer = new SearchRenderer($messages_request, $this->user, $bodyParser, $opt);
565
		$renderer->setParticipants($this->_participants);
566
567
		$context['topic_starter_id'] = 0;
568
		$context['get_topics'] = [$renderer, 'getContext'];
569
570
		$context['jump_to'] = [
571
			'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
572
			'board_name' => addslashes(un_htmlspecialchars($txt['select_destination'])),
573
		];
574
575
		loadJavascriptFile('topic.js');
576
		$this->buildQuickModerationButtons();
577
	}
578
579
	/**
580
	 * Show an anti-spam verification control
581
	 */
582
	protected function _controlVerifications()
583
	{
584
		global $modSettings, $context;
585
586
		// Do we have captcha enabled?
587
		if ($this->user->is_guest && !empty($modSettings['search_enable_captcha']) && empty($_SESSION['ss_vv_passed']) && (empty($_SESSION['last_ss']) || $_SESSION['last_ss'] !== $this->_search->param('search')))
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
588
		{
589
			$verificationOptions = [
590
				'id' => 'search',
591
			];
592
			$context['require_verification'] = VerificationControlsIntegrate::create($verificationOptions, true);
593
594
			if (is_array($context['require_verification']))
595
			{
596
				foreach ($context['require_verification'] as $error)
597
				{
598
					$context['search_errors'][$error] = true;
599
				}
600
			}
601
			// Don't keep asking for it - they've proven themselves worthy.
602
			else
603
			{
604
				$_SESSION['ss_vv_passed'] = true;
605
			}
606
		}
607
	}
608
609
	/**
610
	 * Prepares the participants data
611
	 *
612
	 * @param bool $participationEnabled Indicates if participation is enabled
613
	 * @param int $user_id The ID of the user
614
	 *
615
	 * @return void
616
	 */
617
	protected function _prepareParticipants($participationEnabled, $user_id)
618
	{
619
		// If we want to know who participated in what then load this now.
620
		if ($participationEnabled === true && $user_id !== 0)
621
		{
622
			$this->_participants = $this->_search->getParticipants();
623
624
			require_once(SUBSDIR . '/MessageIndex.subs.php');
625
			$topics_participated_in = topicsParticipation($user_id, array_keys($this->_participants));
626
627
			foreach ($topics_participated_in as $topic)
628
			{
629
				$this->_participants[$topic['id_topic']] = true;
630
			}
631
		}
632
	}
633
634
	/**
635
	 * Loads into $context the moderation button array for template use.
636
	 * Call integrate_message_index_mod_buttons hook
637
	 */
638
	protected function buildQuickModerationButtons()
639
	{
640
		global $context;
641
642
		// Build the mod button array with buttons that are valid for, at least some, of the messages
643
		$context['mod_buttons'] = [
644
			'move' => [
645
				'test' => 'can_move',
646
				'text' => 'move_topic',
647
				'id' => 'move',
648
				'lang' => true,
649
				'url' => 'javascript:void(0);',
650
			],
651
			'remove' => [
652
				'test' => 'can_remove',
653
				'text' => 'remove_topic',
654
				'id' => 'remove',
655
				'lang' => true,
656
				'url' => 'javascript:void(0);',
657
			],
658
			'lock' => [
659
				'test' => 'can_lock',
660
				'text' => 'set_lock',
661
				'id' => 'lock',
662
				'lang' => true,
663
				'url' => 'javascript:void(0);',
664
			],
665
			'sticky' => [
666
				'test' => 'can_sticky',
667
				'text' => 'set_sticky',
668
				'id' => 'sticky',
669
				'lang' => true,
670
				'url' => 'javascript:void(0);',
671
			],
672
			'markread' => [
673
				'test' => 'can_markread',
674
				'text' => 'mark_read_short',
675
				'id' => 'markread',
676
				'lang' => true,
677
				'url' => 'javascript:void(0);',
678
			],
679
		];
680
681
		// Allow adding new buttons easily.
682
		call_integration_hook('integrate_search_quickmod_buttons');
683
684
		$context['mod_buttons'] = array_reverse($context['mod_buttons']);
685
	}
686
}
687