Completed
Push — release-2.1 ( 5876a2...f30e04 )
by Mert
06:50
created

Search.php ➔ PlushSearch1()   F

Complexity

Conditions 41
Paths > 20000

Size

Total Lines 204
Code Lines 113

Duplication

Lines 45
Ratio 22.06 %
Metric Value
cc 41
eloc 113
nc 429496.7295
nop 0
dl 45
loc 204
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Handle all of the searching from here.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
// This defines two version types for checking the API's are compatible with this version of SMF.
20
$GLOBALS['search_versions'] = array(
21
	// This is the forum version but is repeated due to some people rewriting $forum_version.
22
	'forum_version' => 'SMF 2.1 Beta 3',
23
	// This is the minimum version of SMF that an API could have been written for to work. (strtr to stop accidentally updating version on release)
24
	'search_version' => strtr('SMF 2+1=Alpha=1', array('+' => '.', '=' => ' ')),
25
);
26
27
/**
28
 * Ask the user what they want to search for.
29
 * What it does:
30
 * - shows the screen to search forum posts (action=search)
31
 * - uses the main sub template of the Search template.
32
 * - uses the Search language file.
33
 * - requires the search_posts permission.
34
 * - decodes and loads search parameters given in the URL (if any).
35
 * - the form redirects to index.php?action=search2.
36
 */
37
function PlushSearch1()
38
{
39
	global $txt, $scripturl, $modSettings, $user_info, $context, $smcFunc, $sourcedir;
40
41
	// Is the load average too high to allow searching just now?
42 View Code Duplication
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
43
		fatal_lang_error('loadavg_search_disabled', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
44
45
	loadLanguage('Search');
46
	// Don't load this in XML mode.
47
	if (!isset($_REQUEST['xml']))
48
	{
49
		loadTemplate('Search');
50
		loadJavascriptFile('suggest.js', array('defer' => false), 'smf_suggest');
51
	}
52
53
	// Check the user's permissions.
54
	isAllowedTo('search_posts');
55
56
	// Link tree....
57
	$context['linktree'][] = array(
58
		'url' => $scripturl . '?action=search',
59
		'name' => $txt['search']
60
	);
61
62
	// This is hard coded maximum string length.
63
	$context['search_string_limit'] = 100;
64
65
	$context['require_verification'] = $user_info['is_guest'] && !empty($modSettings['search_enable_captcha']) && empty($_SESSION['ss_vv_passed']);
66 View Code Duplication
	if ($context['require_verification'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
	{
68
		require_once($sourcedir . '/Subs-Editor.php');
69
		$verificationOptions = array(
70
			'id' => 'search',
71
		);
72
		$context['require_verification'] = create_control_verification($verificationOptions);
73
		$context['visual_verification_id'] = $verificationOptions['id'];
74
	}
75
76
	// If you got back from search2 by using the linktree, you get your original search parameters back.
77
	if (isset($_REQUEST['params']))
78
	{
79
		// Due to IE's 2083 character limit, we have to compress long search strings
80
		$temp_params = base64_decode(str_replace(array('-', '_', '.'), array('+', '/', '='), $_REQUEST['params']));
81
		// Test for gzuncompress failing
82
		$temp_params2 = @gzuncompress($temp_params);
83
		$temp_params = explode('|"|', !empty($temp_params2) ? $temp_params2 : $temp_params);
84
85
		$context['search_params'] = array();
86 View Code Duplication
		foreach ($temp_params as $i => $data)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
		{
88
			@list ($k, $v) = explode('|\'|', $data);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
89
			$context['search_params'][$k] = $v;
90
		}
91
		if (isset($context['search_params']['brd']))
92
			$context['search_params']['brd'] = $context['search_params']['brd'] == '' ? array() : explode(',', $context['search_params']['brd']);
93
	}
94
95 View Code Duplication
	if (isset($_REQUEST['search']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
96
		$context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']);
97
98 View Code Duplication
	if (isset($context['search_params']['search']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
99
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
100 View Code Duplication
	if (isset($context['search_params']['userspec']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
102
	if (!empty($context['search_params']['searchtype']))
103
		$context['search_params']['searchtype'] = 2;
104 View Code Duplication
	if (!empty($context['search_params']['minage']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
		$context['search_params']['minage'] = (int) $context['search_params']['minage'];
106 View Code Duplication
	if (!empty($context['search_params']['maxage']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
		$context['search_params']['maxage'] = (int) $context['search_params']['maxage'];
108
109
	$context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']);
110
	$context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']);
111
112
	// Load the error text strings if there were errors in the search.
113
	if (!empty($context['search_errors']))
114
	{
115
		loadLanguage('Errors');
116
		$context['search_errors']['messages'] = array();
117
		foreach ($context['search_errors'] as $search_error => $dummy)
118
		{
119
			if ($search_error === 'messages')
120
				continue;
121
122
			if ($search_error == 'string_too_long')
123
				$txt['error_string_too_long'] = sprintf($txt['error_string_too_long'], $context['search_string_limit']);
124
125
			$context['search_errors']['messages'][] = $txt['error_' . $search_error];
126
		}
127
	}
128
129
	// Find all the boards this user is allowed to see.
130
	$request = $smcFunc['db_query']('order_by_board_order', '
131
		SELECT b.id_cat, c.name AS cat_name, b.id_board, b.name, b.child_level
132
		FROM {db_prefix}boards AS b
133
			LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
134
		WHERE {query_see_board}
135
			AND redirect = {string:empty_string}',
136
		array(
137
			'empty_string' => '',
138
		)
139
	);
140
	$context['num_boards'] = $smcFunc['db_num_rows']($request);
141
	$context['boards_check_all'] = true;
142
	$context['categories'] = array();
143
	while ($row = $smcFunc['db_fetch_assoc']($request))
144
	{
145
		// This category hasn't been set up yet..
146
		if (!isset($context['categories'][$row['id_cat']]))
147
			$context['categories'][$row['id_cat']] = array(
148
				'id' => $row['id_cat'],
149
				'name' => $row['cat_name'],
150
				'boards' => array()
151
			);
152
153
		// Set this board up, and let the template know when it's a child.  (indent them..)
154
		$context['categories'][$row['id_cat']]['boards'][$row['id_board']] = array(
155
			'id' => $row['id_board'],
156
			'name' => $row['name'],
157
			'child_level' => $row['child_level'],
158
			'selected' => (empty($context['search_params']['brd']) && (empty($modSettings['recycle_enable']) || $row['id_board'] != $modSettings['recycle_board']) && !in_array($row['id_board'], $user_info['ignoreboards'])) || (!empty($context['search_params']['brd']) && in_array($row['id_board'], $context['search_params']['brd']))
159
		);
160
161
		// If a board wasn't checked that probably should have been ensure the board selection is selected, yo!
162
		if (!$context['categories'][$row['id_cat']]['boards'][$row['id_board']]['selected'] && (empty($modSettings['recycle_enable']) || $row['id_board'] != $modSettings['recycle_board']))
163
			$context['boards_check_all'] = false;
164
	}
165
	$smcFunc['db_free_result']($request);
166
167
	require_once($sourcedir . '/Subs-Boards.php');
168
	sortCategories($context['categories']);
169
170
	// Now, let's sort the list of categories into the boards for templates that like that.
171
	$temp_boards = array();
172 View Code Duplication
	foreach ($context['categories'] as $category)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173
	{
174
		$temp_boards[] = array(
175
			'name' => $category['name'],
176
			'child_ids' => array_keys($category['boards'])
177
		);
178
		$temp_boards = array_merge($temp_boards, array_values($category['boards']));
179
180
		// Include a list of boards per category for easy toggling.
181
		$context['categories'][$category['id']]['child_ids'] = array_keys($category['boards']);
182
	}
183
184
	$max_boards = ceil(count($temp_boards) / 2);
185
	if ($max_boards == 1)
186
		$max_boards = 2;
187
188
	// Now, alternate them so they can be shown left and right ;).
189
	$context['board_columns'] = array();
190 View Code Duplication
	for ($i = 0; $i < $max_boards; $i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
	{
192
		$context['board_columns'][] = $temp_boards[$i];
193
		if (isset($temp_boards[$i + $max_boards]))
194
			$context['board_columns'][] = $temp_boards[$i + $max_boards];
195
		else
196
			$context['board_columns'][] = array();
197
	}
198
199
	if (!empty($_REQUEST['topic']))
200
	{
201
		$context['search_params']['topic'] = (int) $_REQUEST['topic'];
202
		$context['search_params']['show_complete'] = true;
203
	}
204
	if (!empty($context['search_params']['topic']))
205
	{
206
		$context['search_params']['topic'] = (int) $context['search_params']['topic'];
207
208
		$context['search_topic'] = array(
209
			'id' => $context['search_params']['topic'],
210
			'href' => $scripturl . '?topic=' . $context['search_params']['topic'] . '.0',
211
		);
212
213
		$request = $smcFunc['db_query']('', '
214
			SELECT ms.subject
215
			FROM {db_prefix}topics AS t
216
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
217
				INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
218
			WHERE t.id_topic = {int:search_topic_id}
219
				AND {query_see_board}' . ($modSettings['postmod_active'] ? '
220
				AND t.approved = {int:is_approved_true}' : '') . '
221
			LIMIT 1',
222
			array(
223
				'is_approved_true' => 1,
224
				'search_topic_id' => $context['search_params']['topic'],
225
			)
226
		);
227
228
		if ($smcFunc['db_num_rows']($request) == 0)
229
			fatal_lang_error('topic_gone', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
230
231
		list ($context['search_topic']['subject']) = $smcFunc['db_fetch_row']($request);
232
		$smcFunc['db_free_result']($request);
233
234
		$context['search_topic']['link'] = '<a href="' . $context['search_topic']['href'] . '">' . $context['search_topic']['subject'] . '</a>';
235
	}
236
237
	$context['page_title'] = $txt['set_parameters'];
238
239
	call_integration_hook('integrate_search');
240
}
241
242
/**
243
 * Gather the results and show them.
244
 * What it does:
245
 * - checks user input and searches the messages table for messages matching the query.
246
 * - requires the search_posts permission.
247
 * - uses the results sub template of the Search template.
248
 * - uses the Search language file.
249
 * - stores the results into the search cache.
250
 * - show the results of the search query.
251
 */
252
function PlushSearch2()
253
{
254
	global $scripturl, $modSettings, $sourcedir, $txt;
255
	global $user_info, $context, $options, $messages_request, $boards_can;
256
	global $excludedWords, $participants, $smcFunc;
257
258
	// if comming from the quick search box, and we want to search on members, well we need to do that ;)
259
	if (isset($_REQUEST['search_selection']) && $_REQUEST['search_selection'] === 'members')
260
		redirectexit($scripturl . '?action=mlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']));
261
262 View Code Duplication
	if (!empty($context['load_average']) && !empty($modSettings['loadavg_search']) && $context['load_average'] >= $modSettings['loadavg_search'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
		fatal_lang_error('loadavg_search_disabled', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
264
265
	// No, no, no... this is a bit hard on the server, so don't you go prefetching it!
266 View Code Duplication
	if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267
	{
268
		ob_end_clean();
269
		header('HTTP/1.1 403 Forbidden');
270
		die;
271
	}
272
273
	$weight_factors = array(
274
		'frequency' => array(
275
			'search' => 'COUNT(*) / (MAX(t.num_replies) + 1)',
276
			'results' => '(t.num_replies + 1)',
277
		),
278
		'age' => array(
279
			'search' => 'CASE WHEN MAX(m.id_msg) < {int:min_msg} THEN 0 ELSE (MAX(m.id_msg) - {int:min_msg}) / {int:recent_message} END',
280
			'results' => 'CASE WHEN t.id_first_msg < {int:min_msg} THEN 0 ELSE (t.id_first_msg - {int:min_msg}) / {int:recent_message} END',
281
		),
282
		'length' => array(
283
			'search' => 'CASE WHEN MAX(t.num_replies) < {int:huge_topic_posts} THEN MAX(t.num_replies) / {int:huge_topic_posts} ELSE 1 END',
284
			'results' => 'CASE WHEN t.num_replies < {int:huge_topic_posts} THEN t.num_replies / {int:huge_topic_posts} ELSE 1 END',
285
		),
286
		'subject' => array(
287
			'search' => 0,
288
			'results' => 0,
289
		),
290
		'first_message' => array(
291
			'search' => 'CASE WHEN MIN(m.id_msg) = MAX(t.id_first_msg) THEN 1 ELSE 0 END',
292
		),
293
		'sticky' => array(
294
			'search' => 'MAX(t.is_sticky)',
295
			'results' => 't.is_sticky',
296
		),
297
	);
298
299
	call_integration_hook('integrate_search_weights', array(&$weight_factors));
300
301
	$weight = array();
302
	$weight_total = 0;
303
	foreach ($weight_factors as $weight_factor => $value)
304
	{
305
		$weight[$weight_factor] = empty($modSettings['search_weight_' . $weight_factor]) ? 0 : (int) $modSettings['search_weight_' . $weight_factor];
306
		$weight_total += $weight[$weight_factor];
307
	}
308
309
	// Zero weight.  Weightless :P.
310
	if (empty($weight_total))
311
		fatal_lang_error('search_invalid_weights');
312
313
	// These vars don't require an interface, they're just here for tweaking.
314
	$recentPercentage = 0.30;
315
	$humungousTopicPosts = 200;
316
	$maxMembersToSearch = 500;
317
	$maxMessageResults = empty($modSettings['search_max_results']) ? 0 : $modSettings['search_max_results'] * 5;
318
319
	// Start with no errors.
320
	$context['search_errors'] = array();
321
322
	// Number of pages hard maximum - normally not set at all.
323
	$modSettings['search_max_results'] = empty($modSettings['search_max_results']) ? 200 * $modSettings['search_results_per_page'] : (int) $modSettings['search_max_results'];
324
325
	// Maximum length of the string.
326
	$context['search_string_limit'] = 100;
327
328
	loadLanguage('Search');
329
	if (!isset($_REQUEST['xml']))
330
		loadTemplate('Search');
331
	//If we're doing XML we need to use the results template regardless really.
332
	else
333
		$context['sub_template'] = 'results';
334
335
	// Are you allowed?
336
	isAllowedTo('search_posts');
337
338
	require_once($sourcedir . '/Display.php');
339
	require_once($sourcedir . '/Subs-Package.php');
340
341
	// Search has a special database set.
342
	db_extend('search');
343
344
	// Load up the search API we are going to use.
345
	$searchAPI = findSearchAPI();
346
347
	// $search_params will carry all settings that differ from the default search parameters.
348
	// That way, the URLs involved in a search page will be kept as short as possible.
349
	$search_params = array();
350
351
	if (isset($_REQUEST['params']))
352
	{
353
		// Due to IE's 2083 character limit, we have to compress long search strings
354
		$temp_params = base64_decode(str_replace(array('-', '_', '.'), array('+', '/', '='), $_REQUEST['params']));
355
356
		// Test for gzuncompress failing
357
		$temp_params2 = @gzuncompress($temp_params);
358
		$temp_params = explode('|"|', (!empty($temp_params2) ? $temp_params2 : $temp_params));
359
360 View Code Duplication
		foreach ($temp_params as $i => $data)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
361
		{
362
			@list($k, $v) = explode('|\'|', $data);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
363
			$search_params[$k] = $v;
364
		}
365
366
		if (isset($search_params['brd']))
367
			$search_params['brd'] = empty($search_params['brd']) ? array() : explode(',', $search_params['brd']);
368
	}
369
370
	// Store whether simple search was used (needed if the user wants to do another query).
371 View Code Duplication
	if (!isset($search_params['advanced']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
		$search_params['advanced'] = empty($_REQUEST['advanced']) ? 0 : 1;
373
374
	// 1 => 'allwords' (default, don't set as param) / 2 => 'anywords'.
375 View Code Duplication
	if (!empty($search_params['searchtype']) || (!empty($_REQUEST['searchtype']) && $_REQUEST['searchtype'] == 2))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
		$search_params['searchtype'] = 2;
377
378
	// Minimum age of messages. Default to zero (don't set param in that case).
379 View Code Duplication
	if (!empty($search_params['minage']) || (!empty($_REQUEST['minage']) && $_REQUEST['minage'] > 0))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
380
		$search_params['minage'] = !empty($search_params['minage']) ? (int) $search_params['minage'] : (int) $_REQUEST['minage'];
381
382
	// Maximum age of messages. Default to infinite (9999 days: param not set).
383 View Code Duplication
	if (!empty($search_params['maxage']) || (!empty($_REQUEST['maxage']) && $_REQUEST['maxage'] < 9999))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
		$search_params['maxage'] = !empty($search_params['maxage']) ? (int) $search_params['maxage'] : (int) $_REQUEST['maxage'];
385
386
	// Searching a specific topic?
387
	if (!empty($_REQUEST['topic']) || (!empty($_REQUEST['search_selection']) && $_REQUEST['search_selection'] == 'topic'))
388
	{
389
		$search_params['topic'] = empty($_REQUEST['search_selection']) ? (int) $_REQUEST['topic'] : (isset($_REQUEST['sd_topic']) ? (int) $_REQUEST['sd_topic'] : '');
390
		$search_params['show_complete'] = true;
391
	}
392
	elseif (!empty($search_params['topic']))
393
		$search_params['topic'] = (int) $search_params['topic'];
394
395
	if (!empty($search_params['minage']) || !empty($search_params['maxage']))
396
	{
397
		$request = $smcFunc['db_query']('', '
398
			SELECT ' . (empty($search_params['maxage']) ? '0, ' : 'COALESCE(MIN(id_msg), -1), ') . (empty($search_params['minage']) ? '0' : 'COALESCE(MAX(id_msg), -1)') . '
399
			FROM {db_prefix}messages
400
			WHERE 1=1' . ($modSettings['postmod_active'] ? '
401
				AND approved = {int:is_approved_true}' : '') . (empty($search_params['minage']) ? '' : '
402
				AND poster_time <= {int:timestamp_minimum_age}') . (empty($search_params['maxage']) ? '' : '
403
				AND poster_time >= {int:timestamp_maximum_age}'),
404
			array(
405
				'timestamp_minimum_age' => empty($search_params['minage']) ? 0 : time() - 86400 * $search_params['minage'],
406
				'timestamp_maximum_age' => empty($search_params['maxage']) ? 0 : time() - 86400 * $search_params['maxage'],
407
				'is_approved_true' => 1,
408
			)
409
		);
410
		list ($minMsgID, $maxMsgID) = $smcFunc['db_fetch_row']($request);
411
		if ($minMsgID < 0 || $maxMsgID < 0)
412
			$context['search_errors']['no_messages_in_time_frame'] = true;
413
		$smcFunc['db_free_result']($request);
414
	}
415
416
	// Default the user name to a wildcard matching every user (*).
417 View Code Duplication
	if (!empty($search_params['userspec']) || (!empty($_REQUEST['userspec']) && $_REQUEST['userspec'] != '*'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
418
		$search_params['userspec'] = isset($search_params['userspec']) ? $search_params['userspec'] : $_REQUEST['userspec'];
419
420
	// If there's no specific user, then don't mention it in the main query.
421
	if (empty($search_params['userspec']))
422
		$userQuery = '';
423
	else
424
	{
425
		$userString = strtr($smcFunc['htmlspecialchars']($search_params['userspec'], ENT_QUOTES), array('&quot;' => '"'));
426
		$userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_'));
427
428
		preg_match_all('~"([^"]+)"~', $userString, $matches);
429
		$possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString)));
430
431 View Code Duplication
		for ($k = 0, $n = count($possible_users); $k < $n; $k++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
432
		{
433
			$possible_users[$k] = trim($possible_users[$k]);
434
435
			if (strlen($possible_users[$k]) == 0)
436
				unset($possible_users[$k]);
437
		}
438
439
		// Create a list of database-escaped search names.
440
		$realNameMatches = array();
441
		foreach ($possible_users as $possible_user)
442
			$realNameMatches[] = $smcFunc['db_quote'](
443
				'{string:possible_user}',
444
				array(
445
					'possible_user' => $possible_user
446
				)
447
			);
448
449
		// Retrieve a list of possible members.
450
		$request = $smcFunc['db_query']('', '
451
			SELECT id_member
452
			FROM {db_prefix}members
453
			WHERE {raw:match_possible_users}',
454
			array(
455
				'match_possible_users' => 'real_name LIKE ' . implode(' OR real_name LIKE ', $realNameMatches),
456
			)
457
		);
458
		// Simply do nothing if there're too many members matching the criteria.
459
		if ($smcFunc['db_num_rows']($request) > $maxMembersToSearch)
460
			$userQuery = '';
461
		elseif ($smcFunc['db_num_rows']($request) == 0)
462
		{
463
			$userQuery = $smcFunc['db_quote'](
464
				'm.id_member = {int:id_member_guest} AND ({raw:match_possible_guest_names})',
465
				array(
466
					'id_member_guest' => 0,
467
					'match_possible_guest_names' => 'm.poster_name LIKE ' . implode(' OR m.poster_name LIKE ', $realNameMatches),
468
				)
469
			);
470
		}
471
		else
472
		{
473
			$memberlist = array();
474
			while ($row = $smcFunc['db_fetch_assoc']($request))
475
				$memberlist[] = $row['id_member'];
476
			$userQuery = $smcFunc['db_quote'](
477
				'(m.id_member IN ({array_int:matched_members}) OR (m.id_member = {int:id_member_guest} AND ({raw:match_possible_guest_names})))',
478
				array(
479
					'matched_members' => $memberlist,
480
					'id_member_guest' => 0,
481
					'match_possible_guest_names' => 'm.poster_name LIKE ' . implode(' OR m.poster_name LIKE ', $realNameMatches),
482
				)
483
			);
484
		}
485
		$smcFunc['db_free_result']($request);
486
	}
487
488
	// If the boards were passed by URL (params=), temporarily put them back in $_REQUEST.
489
	if (!empty($search_params['brd']) && is_array($search_params['brd']))
490
		$_REQUEST['brd'] = $search_params['brd'];
491
492
	// Ensure that brd is an array.
493
	if ((!empty($_REQUEST['brd']) && !is_array($_REQUEST['brd'])) || (!empty($_REQUEST['search_selection']) && $_REQUEST['search_selection'] == 'board'))
494
	{
495
		if (!empty($_REQUEST['brd']))
496
			$_REQUEST['brd'] = strpos($_REQUEST['brd'], ',') !== false ? explode(',', $_REQUEST['brd']) : array($_REQUEST['brd']);
497
		else
498
			$_REQUEST['brd'] = isset($_REQUEST['sd_brd']) ? array($_REQUEST['sd_brd']) : array();
499
	}
500
501
	// Make sure all boards are integers.
502
	if (!empty($_REQUEST['brd']))
503
		foreach ($_REQUEST['brd'] as $id => $brd)
504
			$_REQUEST['brd'][$id] = (int) $brd;
505
506
	// Special case for boards: searching just one topic?
507
	if (!empty($search_params['topic']))
508
	{
509
		$request = $smcFunc['db_query']('', '
510
			SELECT b.id_board
511
			FROM {db_prefix}topics AS t
512
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
513
			WHERE t.id_topic = {int:search_topic_id}
514
				AND {query_see_board}' . ($modSettings['postmod_active'] ? '
515
				AND t.approved = {int:is_approved_true}' : '') . '
516
			LIMIT 1',
517
			array(
518
				'search_topic_id' => $search_params['topic'],
519
				'is_approved_true' => 1,
520
			)
521
		);
522
523
		if ($smcFunc['db_num_rows']($request) == 0)
524
			fatal_lang_error('topic_gone', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
525
526
		$search_params['brd'] = array();
527
		list ($search_params['brd'][0]) = $smcFunc['db_fetch_row']($request);
528
		$smcFunc['db_free_result']($request);
529
	}
530
	// Select all boards you've selected AND are allowed to see.
531
	elseif ($user_info['is_admin'] && (!empty($search_params['advanced']) || !empty($_REQUEST['brd'])))
532
		$search_params['brd'] = empty($_REQUEST['brd']) ? array() : $_REQUEST['brd'];
533
	else
534
	{
535
		$see_board = empty($search_params['advanced']) ? 'query_wanna_see_board' : 'query_see_board';
536
		$request = $smcFunc['db_query']('', '
537
			SELECT b.id_board
538
			FROM {db_prefix}boards AS b
539
			WHERE {raw:boards_allowed_to_see}
540
				AND redirect = {string:empty_string}' . (empty($_REQUEST['brd']) ? (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
541
				AND b.id_board != {int:recycle_board_id}' : '') : '
542
				AND b.id_board IN ({array_int:selected_search_boards})'),
543
			array(
544
				'boards_allowed_to_see' => $user_info[$see_board],
545
				'empty_string' => '',
546
				'selected_search_boards' => empty($_REQUEST['brd']) ? array() : $_REQUEST['brd'],
547
				'recycle_board_id' => $modSettings['recycle_board'],
548
			)
549
		);
550
		$search_params['brd'] = array();
551 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
552
			$search_params['brd'][] = $row['id_board'];
553
		$smcFunc['db_free_result']($request);
554
555
		// This error should pro'bly only happen for hackers.
556
		if (empty($search_params['brd']))
557
			$context['search_errors']['no_boards_selected'] = true;
558
	}
559
560
	if (count($search_params['brd']) != 0)
561
	{
562
		foreach ($search_params['brd'] as $k => $v)
563
			$search_params['brd'][$k] = (int) $v;
564
565
		// If we've selected all boards, this parameter can be left empty.
566
		$request = $smcFunc['db_query']('', '
567
			SELECT COUNT(*)
568
			FROM {db_prefix}boards
569
			WHERE redirect = {string:empty_string}',
570
			array(
571
				'empty_string' => '',
572
			)
573
		);
574
		list ($num_boards) = $smcFunc['db_fetch_row']($request);
575
		$smcFunc['db_free_result']($request);
576
577
		if (count($search_params['brd']) == $num_boards)
578
			$boardQuery = '';
579
		elseif (count($search_params['brd']) == $num_boards - 1 && !empty($modSettings['recycle_board']) && !in_array($modSettings['recycle_board'], $search_params['brd']))
580
			$boardQuery = '!= ' . $modSettings['recycle_board'];
581
		else
582
			$boardQuery = 'IN (' . implode(', ', $search_params['brd']) . ')';
583
	}
584
	else
585
		$boardQuery = '';
586
587
	$search_params['show_complete'] = !empty($search_params['show_complete']) || !empty($_REQUEST['show_complete']);
588
	$search_params['subject_only'] = !empty($search_params['subject_only']) || !empty($_REQUEST['subject_only']);
589
590
	$context['compact'] = !$search_params['show_complete'];
591
592
	// Get the sorting parameters right. Default to sort by relevance descending.
593
	$sort_columns = array(
594
		'relevance',
595
		'num_replies',
596
		'id_msg',
597
	);
598
	call_integration_hook('integrate_search_sort_columns', array(&$sort_columns));
599 View Code Duplication
	if (empty($search_params['sort']) && !empty($_REQUEST['sort']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
600
		list ($search_params['sort'], $search_params['sort_dir']) = array_pad(explode('|', $_REQUEST['sort']), 2, '');
601
	$search_params['sort'] = !empty($search_params['sort']) && in_array($search_params['sort'], $sort_columns) ? $search_params['sort'] : 'relevance';
602
	if (!empty($search_params['topic']) && $search_params['sort'] === 'num_replies')
603
		$search_params['sort'] = 'id_msg';
604
605
	// Sorting direction: descending unless stated otherwise.
606
	$search_params['sort_dir'] = !empty($search_params['sort_dir']) && $search_params['sort_dir'] == 'asc' ? 'asc' : 'desc';
607
608
	// Determine some values needed to calculate the relevance.
609
	$minMsg = (int) ((1 - $recentPercentage) * $modSettings['maxMsgID']);
610
	$recentMsg = $modSettings['maxMsgID'] - $minMsg;
611
612
	// *** Parse the search query
613
	call_integration_hook('integrate_search_params', array(&$search_params));
614
615
	/*
616
	 * Unfortunately, searching for words like this is going to be slow, so we're blacklisting them.
617
	 *
618
	 * @todo Setting to add more here?
619
	 * @todo Maybe only blacklist if they are the only word, or "any" is used?
620
	 */
621
	$blacklisted_words = array('img', 'url', 'quote', 'www', 'http', 'the', 'is', 'it', 'are', 'if');
622
	call_integration_hook('integrate_search_blacklisted_words', array(&$blacklisted_words));
623
624
	// What are we searching for?
625
	if (empty($search_params['search']))
626
	{
627
		if (isset($_GET['search']))
628
			$search_params['search'] = un_htmlspecialchars($_GET['search']);
629
		elseif (isset($_POST['search']))
630
			$search_params['search'] = $_POST['search'];
631
		else
632
			$search_params['search'] = '';
633
	}
634
635
	// Nothing??
636
	if (!isset($search_params['search']) || $search_params['search'] == '')
637
		$context['search_errors']['invalid_search_string'] = true;
638
	// Too long?
639
	elseif ($smcFunc['strlen']($search_params['search']) > $context['search_string_limit'])
640
	{
641
		$context['search_errors']['string_too_long'] = true;
642
	}
643
644
	// Change non-word characters into spaces.
645
	$stripped_query = preg_replace('~(?:[\x0B\0' . ($context['utf8'] ? '\x{A0}' : '\xA0') . '\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']);
646
647
	// Make the query lower case. It's gonna be case insensitive anyway.
648
	$stripped_query = un_htmlspecialchars($smcFunc['strtolower']($stripped_query));
649
650
	// This (hidden) setting will do fulltext searching in the most basic way.
651
	if (!empty($modSettings['search_simple_fulltext']))
652
		$stripped_query = strtr($stripped_query, array('"' => ''));
653
654
	$no_regexp = preg_match('~&#(?:\d{1,7}|x[0-9a-fA-F]{1,6});~', $stripped_query) === 1;
655
656
	// Extract phrase parts first (e.g. some words "this is a phrase" some more words.)
657
	preg_match_all('/(?:^|\s)([-]?)"([^"]+)"(?:$|\s)/', $stripped_query, $matches, PREG_PATTERN_ORDER);
658
	$phraseArray = $matches[2];
659
660
	// Remove the phrase parts and extract the words.
661
	$wordArray = preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']);
662
	$wordArray = explode(' ',  $smcFunc['htmlspecialchars'](un_htmlspecialchars($wordArray), ENT_QUOTES));
663
664
	// A minus sign in front of a word excludes the word.... so...
665
	$excludedWords = array();
666
	$excludedIndexWords = array();
667
	$excludedSubjectWords = array();
668
	$excludedPhrases = array();
669
670
	// .. first, we check for things like -"some words", but not "-some words".
671 View Code Duplication
	foreach ($matches[1] as $index => $word)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
672
	{
673
		if ($word === '-')
674
		{
675
			if (($word = trim($phraseArray[$index], '-_\' ')) !== '' && !in_array($word, $blacklisted_words))
676
				$excludedWords[] = $word;
677
			unset($phraseArray[$index]);
678
		}
679
	}
680
681
	// Now we look for -test, etc.... normaller.
682
	foreach ($wordArray as $index => $word)
683
	{
684
		if (strpos(trim($word), '-') === 0)
685
		{
686
			if (($word = trim($word, '-_\' ')) !== '' && !in_array($word, $blacklisted_words))
687
				$excludedWords[] = $word;
688
			unset($wordArray[$index]);
689
		}
690
	}
691
692
	// The remaining words and phrases are all included.
693
	$searchArray = array_merge($phraseArray, $wordArray);
694
695
	$context['search_ignored'] = array();
696
	// Trim everything and make sure there are no words that are the same.
697
	foreach ($searchArray as $index => $value)
698
	{
699
		// Skip anything practically empty.
700
		if (($searchArray[$index] = trim($value, '-_\' ')) === '')
701
			unset($searchArray[$index]);
702
		// Skip blacklisted words. Make sure to note we skipped them in case we end up with nothing.
703
		elseif (in_array($searchArray[$index], $blacklisted_words))
704
		{
705
			$foundBlackListedWords = true;
706
			unset($searchArray[$index]);
707
		}
708
		// Don't allow very, very short words.
709
		elseif ($smcFunc['strlen']($value) < 2)
710
		{
711
			$context['search_ignored'][] = $value;
712
			unset($searchArray[$index]);
713
		}
714
		else
715
			$searchArray[$index] = $searchArray[$index];
716
	}
717
	$searchArray = array_slice(array_unique($searchArray), 0, 10);
718
719
	// Create an array of replacements for highlighting.
720
	$context['mark'] = array();
721
	foreach ($searchArray as $word)
722
		$context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
723
724
	// Initialize two arrays storing the words that have to be searched for.
725
	$orParts = array();
726
	$searchWords = array();
727
728
	// Make sure at least one word is being searched for.
729
	if (empty($searchArray))
730
		$context['search_errors']['invalid_search_string' . (!empty($foundBlackListedWords) ? '_blacklist' : '')] = true;
731
	// All words/sentences must match.
732
	elseif (empty($search_params['searchtype']))
733
		$orParts[0] = $searchArray;
734
	// Any word/sentence must match.
735
	else
736
		foreach ($searchArray as $index => $value)
737
			$orParts[$index] = array($value);
738
739
	// Don't allow duplicate error messages if one string is too short.
740
	if (isset($context['search_errors']['search_string_small_words'], $context['search_errors']['invalid_search_string']))
741
		unset($context['search_errors']['invalid_search_string']);
742
	// Make sure the excluded words are in all or-branches.
743
	foreach ($orParts as $orIndex => $andParts)
744
		foreach ($excludedWords as $word)
745
			$orParts[$orIndex][] = $word;
746
747
	// Determine the or-branches and the fulltext search words.
748
	foreach ($orParts as $orIndex => $andParts)
749
	{
750
		$searchWords[$orIndex] = array(
751
			'indexed_words' => array(),
752
			'words' => array(),
753
			'subject_words' => array(),
754
			'all_words' => array(),
755
			'complex_words' => array(),
756
		);
757
758
		// Sort the indexed words (large words -> small words -> excluded words).
759
		if ($searchAPI->supportsMethod('searchSort'))
760
			usort($orParts[$orIndex], 'searchSort');
761
762
		foreach ($orParts[$orIndex] as $word)
763
		{
764
			$is_excluded = in_array($word, $excludedWords);
765
766
			$searchWords[$orIndex]['all_words'][] = $word;
767
768
			$subjectWords = text2words($word);
769
			if (!$is_excluded || count($subjectWords) === 1)
770
			{
771
				$searchWords[$orIndex]['subject_words'] = array_merge($searchWords[$orIndex]['subject_words'], $subjectWords);
772
				if ($is_excluded)
773
					$excludedSubjectWords = array_merge($excludedSubjectWords, $subjectWords);
774
			}
775
			else
776
				$excludedPhrases[] = $word;
777
778
			// Have we got indexes to prepare?
779
			if ($searchAPI->supportsMethod('prepareIndexes'))
780
				$searchAPI->prepareIndexes($word, $searchWords[$orIndex], $excludedIndexWords, $is_excluded);
781
		}
782
783
		// Search_force_index requires all AND parts to have at least one fulltext word.
784
		if (!empty($modSettings['search_force_index']) && empty($searchWords[$orIndex]['indexed_words']))
785
		{
786
			$context['search_errors']['query_not_specific_enough'] = true;
787
			break;
788
		}
789
		elseif ($search_params['subject_only'] && empty($searchWords[$orIndex]['subject_words']) && empty($excludedSubjectWords))
790
		{
791
			$context['search_errors']['query_not_specific_enough'] = true;
792
			break;
793
		}
794
795
		// Make sure we aren't searching for too many indexed words.
796
		else
797
		{
798
			$searchWords[$orIndex]['indexed_words'] = array_slice($searchWords[$orIndex]['indexed_words'], 0, 7);
799
			$searchWords[$orIndex]['subject_words'] = array_slice($searchWords[$orIndex]['subject_words'], 0, 7);
800
			$searchWords[$orIndex]['words'] = array_slice($searchWords[$orIndex]['words'], 0, 4);
801
		}
802
	}
803
804
	// *** Spell checking
805
	$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && (function_exists('pspell_new') || (function_exists('enchant_broker_init') && ($txt['lang_charset'] == 'UTF-8' || function_exists('iconv'))));
806
	if ($context['show_spellchecking'])
807
	{
808
		require_once($sourcedir . '/Subs-Post.php');
809
810
		// Don't hardcode spellchecking functions!
811
		$link = spell_init();
812
813
		$did_you_mean = array('search' => array(), 'display' => array());
814
		$found_misspelling = false;
815
		foreach ($searchArray as $word)
816
		{
817
			if (empty($link))
818
				continue;
819
820
			// Don't check phrases.
821
			if (preg_match('~^\w+$~', $word) === 0)
822
			{
823
				$did_you_mean['search'][] = '"' . $word . '"';
824
				$did_you_mean['display'][] = '&quot;' . $smcFunc['htmlspecialchars']($word) . '&quot;';
825
				continue;
826
			}
827
			// For some strange reason spell check can crash PHP on decimals.
828
			elseif (preg_match('~\d~', $word) === 1)
829
			{
830
				$did_you_mean['search'][] = $word;
831
				$did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word);
832
				continue;
833
			}
834 View Code Duplication
			elseif (spell_check($link, $word))
0 ignored issues
show
Documentation introduced by
$link is of type integer, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
835
			{
836
				$did_you_mean['search'][] = $word;
837
				$did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word);
838
				continue;
839
			}
840
841
			$suggestions = spell_suggest($link, $word);
0 ignored issues
show
Documentation introduced by
$link is of type integer, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
842
			foreach ($suggestions as $i => $s)
0 ignored issues
show
Bug introduced by
The expression $suggestions of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
843
			{
844
				// Search is case insensitive.
845
				if ($smcFunc['strtolower']($s) == $smcFunc['strtolower']($word))
846
					unset($suggestions[$i]);
847
				// Plus, don't suggest something the user thinks is rude!
848
				elseif ($suggestions[$i] != censorText($s))
849
					unset($suggestions[$i]);
850
			}
851
852
			// Anything found?  If so, correct it!
853
			if (!empty($suggestions))
854
			{
855
				$suggestions = array_values($suggestions);
856
				$did_you_mean['search'][] = $suggestions[0];
857
				$did_you_mean['display'][] = '<em><strong>' . $smcFunc['htmlspecialchars']($suggestions[0]) . '</strong></em>';
858
				$found_misspelling = true;
859
			}
860 View Code Duplication
			else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
861
			{
862
				$did_you_mean['search'][] = $word;
863
				$did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word);
864
			}
865
		}
866
867
		if ($found_misspelling)
868
		{
869
			// Don't spell check excluded words, but add them still...
870
			$temp_excluded = array('search' => array(), 'display' => array());
871
			foreach ($excludedWords as $word)
872
			{
873
				if (preg_match('~^\w+$~', $word) == 0)
874
				{
875
					$temp_excluded['search'][] = '-"' . $word . '"';
876
					$temp_excluded['display'][] = '-&quot;' . $smcFunc['htmlspecialchars']($word) . '&quot;';
877
				}
878
				else
879
				{
880
					$temp_excluded['search'][] = '-' . $word;
881
					$temp_excluded['display'][] = '-' . $smcFunc['htmlspecialchars']($word);
882
				}
883
			}
884
885
			$did_you_mean['search'] = array_merge($did_you_mean['search'], $temp_excluded['search']);
886
			$did_you_mean['display'] = array_merge($did_you_mean['display'], $temp_excluded['display']);
887
888
			$temp_params = $search_params;
889
			$temp_params['search'] = implode(' ', $did_you_mean['search']);
890
			if (isset($temp_params['brd']))
891
				$temp_params['brd'] = implode(',', $temp_params['brd']);
892
			$context['params'] = array();
893
			foreach ($temp_params as $k => $v)
894
				$context['did_you_mean_params'][] = $k . '|\'|' . $v;
895
			$context['did_you_mean_params'] = base64_encode(implode('|"|', $context['did_you_mean_params']));
896
			$context['did_you_mean'] = implode(' ', $did_you_mean['display']);
897
		}
898
	}
899
900
	// Let the user adjust the search query, should they wish?
901
	$context['search_params'] = $search_params;
902 View Code Duplication
	if (isset($context['search_params']['search']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
903
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
904 View Code Duplication
	if (isset($context['search_params']['userspec']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
905
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
906
907
	// Do we have captcha enabled?
908
	if ($user_info['is_guest'] && !empty($modSettings['search_enable_captcha']) && empty($_SESSION['ss_vv_passed']) && (empty($_SESSION['last_ss']) || $_SESSION['last_ss'] != $search_params['search']))
909
	{
910
		// If we come from another search box tone down the error...
911
		if (!isset($_REQUEST['search_vv']))
912
			$context['search_errors']['need_verification_code'] = true;
913
		else
914
		{
915
			require_once($sourcedir . '/Subs-Editor.php');
916
			$verificationOptions = array(
917
				'id' => 'search',
918
			);
919
			$context['require_verification'] = create_control_verification($verificationOptions, true);
920
921
			if (is_array($context['require_verification']))
922
			{
923
				foreach ($context['require_verification'] as $error)
924
					$context['search_errors'][$error] = true;
925
			}
926
			// Don't keep asking for it - they've proven themselves worthy.
927
			else
928
				$_SESSION['ss_vv_passed'] = true;
929
		}
930
	}
931
932
	// *** Encode all search params
933
934
	// All search params have been checked, let's compile them to a single string... made less simple by PHP 4.3.9 and below.
935
	$temp_params = $search_params;
936
	if (isset($temp_params['brd']))
937
		$temp_params['brd'] = implode(',', $temp_params['brd']);
938
	$context['params'] = array();
939
	foreach ($temp_params as $k => $v)
940
		$context['params'][] = $k . '|\'|' . $v;
941
942
	if (!empty($context['params']))
943
	{
944
		// Due to old IE's 2083 character limit, we have to compress long search strings
945
		$params = @gzcompress(implode('|"|', $context['params']));
946
		// Gzcompress failed, use try non-gz
947
		if (empty($params))
948
			$params = implode('|"|', $context['params']);
949
		// Base64 encode, then replace +/= with uri safe ones that can be reverted
950
		$context['params'] = str_replace(array('+', '/', '='), array('-', '_', '.'), base64_encode($params));
951
	}
952
953
	// ... and add the links to the link tree.
954
	$context['linktree'][] = array(
955
		'url' => $scripturl . '?action=search;params=' . $context['params'],
956
		'name' => $txt['search']
957
	);
958
	$context['linktree'][] = array(
959
		'url' => $scripturl . '?action=search2;params=' . $context['params'],
960
		'name' => $txt['search_results']
961
	);
962
963
	// *** A last error check
964
	call_integration_hook('integrate_search_errors');
965
966
	// One or more search errors? Go back to the first search screen.
967 View Code Duplication
	if (!empty($context['search_errors']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
968
	{
969
		$_REQUEST['params'] = $context['params'];
970
		return PlushSearch1();
971
	}
972
973
	// Spam me not, Spam-a-lot?
974
	if (empty($_SESSION['last_ss']) || $_SESSION['last_ss'] != $search_params['search'])
975
		spamProtection('search');
976
	// Store the last search string to allow pages of results to be browsed.
977
	$_SESSION['last_ss'] = $search_params['search'];
978
979
	// *** Reserve an ID for caching the search results.
980
	$query_params = array_merge($search_params, array(
981
		'min_msg_id' => isset($minMsgID) ? (int) $minMsgID : 0,
982
		'max_msg_id' => isset($maxMsgID) ? (int) $maxMsgID : 0,
983
		'memberlist' => !empty($memberlist) ? $memberlist : array(),
984
	));
985
986
	// Can this search rely on the API given the parameters?
987
	if ($searchAPI->supportsMethod('searchQuery', $query_params))
988
	{
989
		$participants = array();
990
		$searchArray = array();
991
992
		$num_results = $searchAPI->searchQuery($query_params, $searchWords, $excludedIndexWords, $participants, $searchArray);
993
	}
994
995
	// Update the cache if the current search term is not yet cached.
996
	else
997
	{
998
		$update_cache = empty($_SESSION['search_cache']) || ($_SESSION['search_cache']['params'] != $context['params']);
999
		if ($update_cache)
1000
		{
1001
			// Increase the pointer...
1002
			$modSettings['search_pointer'] = empty($modSettings['search_pointer']) ? 0 : (int) $modSettings['search_pointer'];
1003
			// ...and store it right off.
1004
			updateSettings(array('search_pointer' => $modSettings['search_pointer'] >= 255 ? 0 : $modSettings['search_pointer'] + 1));
1005
			// As long as you don't change the parameters, the cache result is yours.
1006
			$_SESSION['search_cache'] = array(
1007
				'id_search' => $modSettings['search_pointer'],
1008
				'num_results' => -1,
1009
				'params' => $context['params'],
1010
			);
1011
1012
			// Clear the previous cache of the final results cache.
1013
			$smcFunc['db_search_query']('delete_log_search_results', '
1014
				DELETE FROM {db_prefix}log_search_results
1015
				WHERE id_search = {int:search_id}',
1016
				array(
1017
					'search_id' => $_SESSION['search_cache']['id_search'],
1018
				)
1019
			);
1020
1021
			if ($search_params['subject_only'])
1022
			{
1023
				// We do this to try and avoid duplicate keys on databases not supporting INSERT IGNORE.
1024
				$inserts = array();
1025
				foreach ($searchWords as $orIndex => $words)
1026
				{
1027
					$subject_query_params = array();
1028
					$subject_query = array(
1029
						'from' => '{db_prefix}topics AS t',
1030
						'inner_join' => array(),
1031
						'left_join' => array(),
1032
						'where' => array(),
1033
					);
1034
1035
					if ($modSettings['postmod_active'])
1036
						$subject_query['where'][] = 't.approved = {int:is_approved}';
1037
1038
					$numTables = 0;
1039
					$prev_join = 0;
1040
					$numSubjectResults = 0;
1041
					foreach ($words['subject_words'] as $subjectWord)
1042
					{
1043
						$numTables++;
1044
						if (in_array($subjectWord, $excludedSubjectWords))
1045
						{
1046
							$subject_query['left_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.word ' . (empty($modSettings['search_match_words']) ? 'LIKE {string:subject_words_' . $numTables . '_wild}' : '= {string:subject_words_' . $numTables . '}') . ' AND subj' . $numTables . '.id_topic = t.id_topic)';
1047
							$subject_query['where'][] = '(subj' . $numTables . '.word IS NULL)';
1048
						}
1049
						else
1050
						{
1051
							$subject_query['inner_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.id_topic = ' . ($prev_join === 0 ? 't' : 'subj' . $prev_join) . '.id_topic)';
1052
							$subject_query['where'][] = 'subj' . $numTables . '.word ' . (empty($modSettings['search_match_words']) ? 'LIKE {string:subject_words_' . $numTables . '_wild}' : '= {string:subject_words_' . $numTables . '}');
1053
							$prev_join = $numTables;
1054
						}
1055
						$subject_query_params['subject_words_' . $numTables] = $subjectWord;
1056
						$subject_query_params['subject_words_' . $numTables . '_wild'] = '%' . $subjectWord . '%';
1057
					}
1058
1059
					if (!empty($userQuery))
1060
					{
1061
						if ($subject_query['from'] != '{db_prefix}messages AS m')
1062
						{
1063
							$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_topic = t.id_topic)';
1064
						}
1065
						$subject_query['where'][] = $userQuery;
1066
					}
1067
					if (!empty($search_params['topic']))
1068
						$subject_query['where'][] = 't.id_topic = ' . $search_params['topic'];
1069
					if (!empty($minMsgID))
1070
						$subject_query['where'][] = 't.id_first_msg >= ' . $minMsgID;
1071
					if (!empty($maxMsgID))
1072
						$subject_query['where'][] = 't.id_last_msg <= ' . $maxMsgID;
1073
					if (!empty($boardQuery))
1074
						$subject_query['where'][] = 't.id_board ' . $boardQuery;
1075
					if (!empty($excludedPhrases))
1076
					{
1077
						if ($subject_query['from'] != '{db_prefix}messages AS m')
1078
						{
1079
							$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1080
						}
1081
						$count = 0;
1082
						foreach ($excludedPhrases as $phrase)
1083
						{
1084
							$subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:excluded_phrases_' . $count . '}';
1085
							$subject_query_params['excluded_phrases_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
1086
						}
1087
					}
1088
					call_integration_hook('integrate_subject_only_search_query', array(&$subject_query, &$subject_query_params));
1089
1090
					$relevance = '1000 * (';
1091 View Code Duplication
					foreach ($weight_factors as $type => $value)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1092
					{
1093
						$relevance .= $weight[$type];
1094
						if (!empty($value['results']))
1095
							$relevance .= ' * ' . $value['results'];
1096
						$relevance .= ' + ';
1097
					}
1098
					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
1099
1100
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_subject',
1101
						($smcFunc['db_support_ignore'] ? '
1102
						INSERT IGNORE INTO {db_prefix}log_search_results
1103
							(id_search, id_topic, relevance, id_msg, num_matches)' : '') . '
1104
						SELECT
1105
							{int:id_search},
1106
							t.id_topic,
1107
							' . $relevance. ',
1108
							' . (empty($userQuery) ? 't.id_first_msg' : 'm.id_msg') . ',
1109
							1
1110
						FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : '
1111
							INNER JOIN ' . implode('
1112
							INNER JOIN ', $subject_query['inner_join'])) . (empty($subject_query['left_join']) ? '' : '
1113
							LEFT JOIN ' . implode('
1114
							LEFT JOIN ', $subject_query['left_join'])) . '
1115
						WHERE ' . implode('
1116
							AND ', $subject_query['where']) . (empty($modSettings['search_max_results']) ? '' : '
1117
						LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults)),
1118
						array_merge($subject_query_params, array(
1119
							'id_search' => $_SESSION['search_cache']['id_search'],
1120
							'min_msg' => $minMsg,
1121
							'recent_message' => $recentMsg,
1122
							'huge_topic_posts' => $humungousTopicPosts,
1123
							'is_approved' => 1,
1124
						))
1125
					);
1126
1127
					// If the database doesn't support IGNORE to make this fast we need to do some tracking.
1128 View Code Duplication
					if (!$smcFunc['db_support_ignore'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1129
					{
1130
						while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1131
						{
1132
							// No duplicates!
1133
							if (isset($inserts[$row[1]]))
1134
								continue;
1135
1136
							foreach ($row as $key => $value)
1137
								$inserts[$row[1]][] = (int) $row[$key];
1138
						}
1139
						$smcFunc['db_free_result']($ignoreRequest);
1140
						$numSubjectResults = count($inserts);
1141
					}
1142
					else
1143
						$numSubjectResults += $smcFunc['db_affected_rows']();
1144
1145
					if (!empty($modSettings['search_max_results']) && $numSubjectResults >= $modSettings['search_max_results'])
1146
						break;
1147
				}
1148
1149
				// If there's data to be inserted for non-IGNORE databases do it here!
1150 View Code Duplication
				if (!empty($inserts))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1151
				{
1152
					$smcFunc['db_insert']('',
1153
						'{db_prefix}log_search_results',
1154
						array('id_search' => 'int', 'id_topic' => 'int', 'relevance' => 'int', 'id_msg' => 'int', 'num_matches' => 'int'),
1155
						$inserts,
1156
						array('id_search', 'id_topic')
1157
					);
1158
				}
1159
1160
				$_SESSION['search_cache']['num_results'] = $numSubjectResults;
0 ignored issues
show
Bug introduced by
The variable $numSubjectResults does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1161
			}
1162
			else
1163
			{
1164
				$main_query = array(
1165
					'select' => array(
1166
						'id_search' => $_SESSION['search_cache']['id_search'],
1167
						'relevance' => '0',
1168
					),
1169
					'weights' => array(),
1170
					'from' => '{db_prefix}topics AS t',
1171
					'inner_join' => array(
1172
						'{db_prefix}messages AS m ON (m.id_topic = t.id_topic)'
1173
					),
1174
					'left_join' => array(),
1175
					'where' => array(),
1176
					'group_by' => array(),
1177
					'parameters' => array(
1178
						'min_msg' => $minMsg,
1179
						'recent_message' => $recentMsg,
1180
						'huge_topic_posts' => $humungousTopicPosts,
1181
						'is_approved' => 1,
1182
					),
1183
				);
1184
1185
				if (empty($search_params['topic']) && empty($search_params['show_complete']))
1186
				{
1187
					$main_query['select']['id_topic'] = 't.id_topic';
1188
					$main_query['select']['id_msg'] = 'MAX(m.id_msg) AS id_msg';
1189
					$main_query['select']['num_matches'] = 'COUNT(*) AS num_matches';
1190
1191
					$main_query['weights'] = $weight_factors;
1192
1193
					$main_query['group_by'][] = 't.id_topic';
1194
				}
1195
				else
1196
				{
1197
					// This is outrageous!
1198
					$main_query['select']['id_topic'] = 'm.id_msg AS id_topic';
1199
					$main_query['select']['id_msg'] = 'm.id_msg';
1200
					$main_query['select']['num_matches'] = '1 AS num_matches';
1201
1202
					$main_query['weights'] = array(
1203
						'age' => array(
1204
							'search' => '((m.id_msg - t.id_first_msg) / CASE WHEN t.id_last_msg = t.id_first_msg THEN 1 ELSE t.id_last_msg - t.id_first_msg END)',
1205
						),
1206
						'first_message' => array(
1207
							'search' => 'CASE WHEN m.id_msg = t.id_first_msg THEN 1 ELSE 0 END',
1208
						),
1209
					);
1210
1211 View Code Duplication
					if (!empty($search_params['topic']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1212
					{
1213
						$main_query['where'][] = 't.id_topic = {int:topic}';
1214
						$main_query['parameters']['topic'] = $search_params['topic'];
1215
					}
1216
					if (!empty($search_params['show_complete']))
1217
						$main_query['group_by'][] = 'm.id_msg, t.id_first_msg, t.id_last_msg';
1218
				}
1219
1220
				// *** Get the subject results.
1221
				$numSubjectResults = 0;
1222
				if (empty($search_params['topic']))
1223
				{
1224
					$inserts = array();
1225
					// Create a temporary table to store some preliminary results in.
1226
					$smcFunc['db_search_query']('drop_tmp_log_search_topics', '
1227
						DROP TABLE IF EXISTS {db_prefix}tmp_log_search_topics',
1228
						array(
1229
							'db_error_skip' => true,
1230
						)
1231
					);
1232
					$createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_topics', '
1233
						CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_topics (
1234
							id_topic mediumint(8) unsigned NOT NULL default {string:string_zero},
1235
							PRIMARY KEY (id_topic)
1236
						) ENGINE=MEMORY',
1237
						array(
1238
							'string_zero' => '0',
1239
							'db_error_skip' => true,
1240
						)
1241
					) !== false;
1242
1243
					// Clean up some previous cache.
1244
					if (!$createTemporary)
1245
						$smcFunc['db_search_query']('delete_log_search_topics', '
1246
							DELETE FROM {db_prefix}log_search_topics
1247
							WHERE id_search = {int:search_id}',
1248
							array(
1249
								'search_id' => $_SESSION['search_cache']['id_search'],
1250
							)
1251
						);
1252
1253
					foreach ($searchWords as $orIndex => $words)
1254
					{
1255
						$subject_query = array(
1256
							'from' => '{db_prefix}topics AS t',
1257
							'inner_join' => array(),
1258
							'left_join' => array(),
1259
							'where' => array(),
1260
							'params' => array(),
1261
						);
1262
1263
						$numTables = 0;
1264
						$prev_join = 0;
1265
						$count = 0;
1266
						$excluded = false;
1267
						foreach ($words['subject_words'] as $subjectWord)
1268
						{
1269
							$numTables++;
1270
							if (in_array($subjectWord, $excludedSubjectWords))
1271
							{
1272
								if (($subject_query['from'] != '{db_prefix}messages AS m') && !$excluded)
1273
								{
1274
									$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1275
									$excluded = true;
1276
								}
1277
								$subject_query['left_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.word ' . (empty($modSettings['search_match_words']) ? 'LIKE {string:subject_not_' . $count . '}' : '= {string:subject_not_' . $count . '}') . ' AND subj' . $numTables . '.id_topic = t.id_topic)';
1278
								$subject_query['params']['subject_not_' . $count] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord;
1279
1280
								$subject_query['where'][] = '(subj' . $numTables . '.word IS NULL)';
1281
								$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:body_not_' . $count . '}';
1282
								$subject_query['params']['body_not_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($subjectWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $subjectWord), '\\\'') . '[[:>:]]';
1283
							}
1284
							else
1285
							{
1286
								$subject_query['inner_join'][] = '{db_prefix}log_search_subjects AS subj' . $numTables . ' ON (subj' . $numTables . '.id_topic = ' . ($prev_join === 0 ? 't' : 'subj' . $prev_join) . '.id_topic)';
1287
								$subject_query['where'][] = 'subj' . $numTables . '.word LIKE {string:subject_like_' . $count . '}';
1288
								$subject_query['params']['subject_like_' . $count++] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord;
1289
								$prev_join = $numTables;
1290
							}
1291
						}
1292
1293
						if (!empty($userQuery))
1294
						{
1295
							if ($subject_query['from'] != '{db_prefix}messages AS m')
1296
							{
1297
								$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1298
							}
1299
							$subject_query['where'][] = '{raw:user_query}';
1300
							$subject_query['params']['user_query'] = $userQuery;
1301
						}
1302 View Code Duplication
						if (!empty($search_params['topic']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1303
						{
1304
							$subject_query['where'][] = 't.id_topic = {int:topic}';
1305
							$subject_query['params']['topic'] = $search_params['topic'];
1306
						}
1307
						if (!empty($minMsgID))
1308
						{
1309
							$subject_query['where'][] = 't.id_first_msg >= {int:min_msg_id}';
1310
							$subject_query['params']['min_msg_id'] = $minMsgID;
1311
						}
1312
						if (!empty($maxMsgID))
1313
						{
1314
							$subject_query['where'][] = 't.id_last_msg <= {int:max_msg_id}';
1315
							$subject_query['params']['max_msg_id'] = $maxMsgID;
1316
						}
1317
						if (!empty($boardQuery))
1318
						{
1319
							$subject_query['where'][] = 't.id_board {raw:board_query}';
1320
							$subject_query['params']['board_query'] = $boardQuery;
1321
						}
1322
						if (!empty($excludedPhrases))
1323
						{
1324
							if ($subject_query['from'] != '{db_prefix}messages AS m')
1325
							{
1326
								$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1327
							}
1328
							$count = 0;
1329
							foreach ($excludedPhrases as $phrase)
1330
							{
1331
								$subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
1332
								$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
1333
								$subject_query['params']['exclude_phrase_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
1334
							}
1335
						}
1336
						call_integration_hook('integrate_subject_search_query', array(&$subject_query));
1337
1338
						// Nothing to search for?
1339
						if (empty($subject_query['where']))
1340
							continue;
1341
1342
						$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_topics', ($smcFunc['db_support_ignore'] ? ( '
1343
							INSERT IGNORE INTO {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics
1344
								(' . ($createTemporary ? '' : 'id_search, ') . 'id_topic)') : '') . '
1345
							SELECT ' . ($createTemporary ? '' : $_SESSION['search_cache']['id_search'] . ', ') . 't.id_topic
1346
							FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : '
1347
								INNER JOIN ' . implode('
1348
								INNER JOIN ', $subject_query['inner_join'])) . (empty($subject_query['left_join']) ? '' : '
1349
								LEFT JOIN ' . implode('
1350
								LEFT JOIN ', $subject_query['left_join'])) . '
1351
							WHERE ' . implode('
1352
								AND ', $subject_query['where']) . (empty($modSettings['search_max_results']) ? '' : '
1353
							LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults)),
1354
							$subject_query['params']
1355
						);
1356
						// Don't do INSERT IGNORE? Manually fix this up!
1357 View Code Duplication
						if (!$smcFunc['db_support_ignore'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1358
						{
1359
							while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1360
							{
1361
								$ind = $createTemporary ? 0 : 1;
1362
								// No duplicates!
1363
								if (isset($inserts[$row[$ind]]))
1364
									continue;
1365
1366
								$inserts[$row[$ind]] = $row;
1367
							}
1368
							$smcFunc['db_free_result']($ignoreRequest);
1369
							$numSubjectResults = count($inserts);
1370
						}
1371
						else
1372
							$numSubjectResults += $smcFunc['db_affected_rows']();
1373
1374
						if (!empty($modSettings['search_max_results']) && $numSubjectResults >= $modSettings['search_max_results'])
1375
							break;
1376
					}
1377
1378
					// Got some non-MySQL data to plonk in?
1379 View Code Duplication
					if (!empty($inserts))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1380
					{
1381
						$smcFunc['db_insert']('',
1382
							('{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics'),
1383
							$createTemporary ? array('id_topic' => 'int') : array('id_search' => 'int', 'id_topic' => 'int'),
1384
							$inserts,
1385
							$createTemporary ? array('id_topic') : array('id_search', 'id_topic')
1386
						);
1387
					}
1388
1389
					if ($numSubjectResults !== 0)
1390
					{
1391
						$main_query['weights']['subject']['search'] = 'CASE WHEN MAX(lst.id_topic) IS NULL THEN 0 ELSE 1 END';
1392
						$main_query['left_join'][] = '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics AS lst ON (' . ($createTemporary ? '' : 'lst.id_search = {int:id_search} AND ') . 'lst.id_topic = t.id_topic)';
1393
						if (!$createTemporary)
1394
							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
1395
					}
1396
				}
1397
1398
				$indexedResults = 0;
1399
				// We building an index?
1400
				if ($searchAPI->supportsMethod('indexedWordQuery', $query_params))
1401
				{
1402
					$inserts = array();
1403
					$smcFunc['db_search_query']('drop_tmp_log_search_messages', '
1404
						DROP TABLE IF EXISTS {db_prefix}tmp_log_search_messages',
1405
						array(
1406
							'db_error_skip' => true,
1407
						)
1408
					);
1409
1410
					$createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_messages', '
1411
						CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_messages (
1412
							id_msg int(10) unsigned NOT NULL default {string:string_zero},
1413
							PRIMARY KEY (id_msg)
1414
						) ENGINE=MEMORY',
1415
						array(
1416
							'string_zero' => '0',
1417
							'db_error_skip' => true,
1418
						)
1419
					) !== false;
1420
1421
					// Clear, all clear!
1422
					if (!$createTemporary)
1423
						$smcFunc['db_search_query']('delete_log_search_messages', '
1424
							DELETE FROM {db_prefix}log_search_messages
1425
							WHERE id_search = {int:id_search}',
1426
							array(
1427
								'id_search' => $_SESSION['search_cache']['id_search'],
1428
							)
1429
						);
1430
1431
					foreach ($searchWords as $orIndex => $words)
1432
					{
1433
						// Search for this word, assuming we have some words!
1434
						if (!empty($words['indexed_words']))
1435
						{
1436
							// Variables required for the search.
1437
							$search_data = array(
1438
								'insert_into' => ($createTemporary ? 'tmp_' : '') . 'log_search_messages',
1439
								'no_regexp' => $no_regexp,
1440
								'max_results' => $maxMessageResults,
1441
								'indexed_results' => $indexedResults,
1442
								'params' => array(
1443
									'id_search' => !$createTemporary ? $_SESSION['search_cache']['id_search'] : 0,
1444
									'excluded_words' => $excludedWords,
1445
									'user_query' => !empty($userQuery) ? $userQuery : '',
1446
									'board_query' => !empty($boardQuery) ? $boardQuery : '',
1447
									'topic' => !empty($search_params['topic']) ? $search_params['topic'] : 0,
1448
									'min_msg_id' => !empty($minMsgID) ? $minMsgID : 0,
1449
									'max_msg_id' => !empty($maxMsgID) ? $maxMsgID : 0,
1450
									'excluded_phrases' => !empty($excludedPhrases) ? $excludedPhrases : array(),
1451
									'excluded_index_words' => !empty($excludedIndexWords) ? $excludedIndexWords : array(),
1452
									'excluded_subject_words' => !empty($excludedSubjectWords) ? $excludedSubjectWords : array(),
1453
								),
1454
							);
1455
1456
							$ignoreRequest = $searchAPI->indexedWordQuery($words, $search_data);
1457
1458
							if (!$smcFunc['db_support_ignore'])
1459
							{
1460 View Code Duplication
								while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1461
								{
1462
									// No duplicates!
1463
									if (isset($inserts[$row[0]]))
1464
										continue;
1465
1466
									$inserts[$row[0]] = $row;
1467
								}
1468
								$smcFunc['db_free_result']($ignoreRequest);
1469
								$indexedResults = count($inserts);
1470
							}
1471
							else
1472
								$indexedResults += $smcFunc['db_affected_rows']();
1473
1474
							if (!empty($maxMessageResults) && $indexedResults >= $maxMessageResults)
1475
								break;
1476
						}
1477
					}
1478
1479
					// More non-MySQL stuff needed?
1480 View Code Duplication
					if (!empty($inserts))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1481
					{
1482
						$smcFunc['db_insert']('',
1483
							'{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages',
1484
							$createTemporary ? array('id_msg' => 'int') : array('id_msg' => 'int', 'id_search' => 'int'),
1485
							$inserts,
1486
							$createTemporary ? array('id_msg') : array('id_msg', 'id_search')
1487
						);
1488
					}
1489
1490
					if (empty($indexedResults) && empty($numSubjectResults) && !empty($modSettings['search_force_index']))
1491
					{
1492
						$context['search_errors']['query_not_specific_enough'] = true;
1493
						$_REQUEST['params'] = $context['params'];
1494
						return PlushSearch1();
1495
					}
1496
					elseif (!empty($indexedResults))
1497
					{
1498
						$main_query['inner_join'][] = '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages AS lsm ON (lsm.id_msg = m.id_msg)';
1499
						if (!$createTemporary)
1500
						{
1501
							$main_query['where'][] = 'lsm.id_search = {int:id_search}';
1502
							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
1503
						}
1504
					}
1505
				}
1506
1507
				// Not using an index? All conditions have to be carried over.
1508
				else
1509
				{
1510
					$orWhere = array();
1511
					$count = 0;
1512
					foreach ($searchWords as $orIndex => $words)
1513
					{
1514
						$where = array();
1515
						foreach ($words['all_words'] as $regularWord)
1516
						{
1517
							$where[] = 'm.body' . (in_array($regularWord, $excludedWords) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
1518
							if (in_array($regularWord, $excludedWords))
1519
								$where[] = 'm.subject NOT' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
1520
							$main_query['parameters']['all_word_body_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]';
1521
						}
1522 View Code Duplication
						if (!empty($where))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1523
							$orWhere[] = count($where) > 1 ? '(' . implode(' AND ', $where) . ')' : $where[0];
1524
					}
1525 View Code Duplication
					if (!empty($orWhere))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1526
						$main_query['where'][] = count($orWhere) > 1 ? '(' . implode(' OR ', $orWhere) . ')' : $orWhere[0];
1527
1528
					if (!empty($userQuery))
1529
					{
1530
						$main_query['where'][] = '{raw:user_query}';
1531
						$main_query['parameters']['user_query'] = $userQuery;
1532
					}
1533 View Code Duplication
					if (!empty($search_params['topic']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1534
					{
1535
						$main_query['where'][] = 'm.id_topic = {int:topic}';
1536
						$main_query['parameters']['topic'] = $search_params['topic'];
1537
					}
1538
					if (!empty($minMsgID))
1539
					{
1540
						$main_query['where'][] = 'm.id_msg >= {int:min_msg_id}';
1541
						$main_query['parameters']['min_msg_id'] = $minMsgID;
1542
					}
1543
					if (!empty($maxMsgID))
1544
					{
1545
						$main_query['where'][] = 'm.id_msg <= {int:max_msg_id}';
1546
						$main_query['parameters']['max_msg_id'] = $maxMsgID;
1547
					}
1548
					if (!empty($boardQuery))
1549
					{
1550
						$main_query['where'][] = 'm.id_board {raw:board_query}';
1551
						$main_query['parameters']['board_query'] = $boardQuery;
1552
					}
1553
				}
1554
				call_integration_hook('integrate_main_search_query', array(&$main_query));
1555
1556
				// Did we either get some indexed results, or otherwise did not do an indexed query?
1557
				if (!empty($indexedResults) || !$searchAPI->supportsMethod('indexedWordQuery', $query_params))
1558
				{
1559
					$relevance = '1000 * (';
1560
					$new_weight_total = 0;
1561
					foreach ($main_query['weights'] as $type => $value)
1562
					{
1563
						$relevance .= $weight[$type];
1564
						if (!empty($value['search']))
1565
							$relevance .= ' * ' . $value['search'];
1566
						$relevance .= ' + ';
1567
						$new_weight_total += $weight[$type];
1568
					}
1569
					$main_query['select']['relevance'] = substr($relevance, 0, -3) . ') / ' . $new_weight_total . ' AS relevance';
1570
1571
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_no_index', ($smcFunc['db_support_ignore'] ? ( '
1572
						INSERT IGNORE INTO ' . '{db_prefix}log_search_results
1573
							(' . implode(', ', array_keys($main_query['select'])) . ')') : '') . '
1574
						SELECT
1575
							' . implode(',
1576
							', $main_query['select']) . '
1577
						FROM ' . $main_query['from'] . (empty($main_query['inner_join']) ? '' : '
1578
							INNER JOIN ' . implode('
1579
							INNER JOIN ', $main_query['inner_join'])) . (empty($main_query['left_join']) ? '' : '
1580
							LEFT JOIN ' . implode('
1581
							LEFT JOIN ', $main_query['left_join'])) . (!empty($main_query['where']) ? '
1582
						WHERE ' : '') . implode('
1583
							AND ', $main_query['where']) . (empty($main_query['group_by']) ? '' : '
1584
						GROUP BY ' . implode(', ', $main_query['group_by'])) . (empty($modSettings['search_max_results']) ? '' : '
1585
						LIMIT ' . $modSettings['search_max_results']),
1586
						$main_query['parameters']
1587
					);
1588
1589
					// We love to handle non-good databases that don't support our ignore!
1590
					if (!$smcFunc['db_support_ignore'])
1591
					{
1592
						$inserts = array();
1593
						while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1594
						{
1595
							// No duplicates!
1596
							if (isset($inserts[$row[2]]))
1597
								continue;
1598
1599
							foreach ($row as $key => $value)
1600
								$inserts[$row[2]][] = (int) $row[$key];
1601
						}
1602
						$smcFunc['db_free_result']($ignoreRequest);
1603
1604
						// Now put them in!
1605
						if (!empty($inserts))
1606
						{
1607
							$query_columns = array();
1608
							foreach ($main_query['select'] as $k => $v)
1609
								$query_columns[$k] = 'int';
1610
1611
							$smcFunc['db_insert']('',
1612
								'{db_prefix}log_search_results',
1613
								$query_columns,
1614
								$inserts,
1615
								array('id_search', 'id_topic')
1616
							);
1617
						}
1618
						$_SESSION['search_cache']['num_results'] += count($inserts);
1619
					}
1620
					else
1621
						$_SESSION['search_cache']['num_results'] = $smcFunc['db_affected_rows']();
1622
				}
1623
1624
				// Insert subject-only matches.
1625
				if ($_SESSION['search_cache']['num_results'] < $modSettings['search_max_results'] && $numSubjectResults !== 0)
1626
				{
1627
					$relevance = '1000 * (';
1628 View Code Duplication
					foreach ($weight_factors as $type => $value)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1629
						if (isset($value['results']))
1630
						{
1631
							$relevance .= $weight[$type];
1632
							if (!empty($value['results']))
1633
								$relevance .= ' * ' . $value['results'];
1634
							$relevance .= ' + ';
1635
						}
1636
					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
1637
1638
					$usedIDs = array_flip(empty($inserts) ? array() : array_keys($inserts));
1639
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_sub_only', ($smcFunc['db_support_ignore'] ? ( '
1640
						INSERT IGNORE INTO {db_prefix}log_search_results
1641
							(id_search, id_topic, relevance, id_msg, num_matches)') : '') . '
1642
						SELECT
1643
							{int:id_search},
1644
							t.id_topic,
1645
							' . $relevance . ',
1646
							t.id_first_msg,
1647
							1
1648
						FROM {db_prefix}topics AS t
1649
							INNER JOIN {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics AS lst ON (lst.id_topic = t.id_topic)'
0 ignored issues
show
Bug introduced by
The variable $createTemporary does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1650
						. ($createTemporary ? '' : ' WHERE lst.id_search = {int:id_search}')
1651
						. (empty($modSettings['search_max_results']) ? '' : '
1652
						LIMIT ' . ($modSettings['search_max_results'] - $_SESSION['search_cache']['num_results'])),
1653
						array(
1654
							'id_search' => $_SESSION['search_cache']['id_search'],
1655
							'min_msg' => $minMsg,
1656
							'recent_message' => $recentMsg,
1657
							'huge_topic_posts' => $humungousTopicPosts,
1658
						)
1659
					);
1660
					// Once again need to do the inserts if the database don't support ignore!
1661
					if (!$smcFunc['db_support_ignore'])
1662
					{
1663
						$inserts = array();
1664 View Code Duplication
						while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1665
						{
1666
							// No duplicates!
1667
							if (isset($usedIDs[$row[1]]))
1668
								continue;
1669
1670
							$usedIDs[$row[1]] = true;
1671
							$inserts[] = $row;
1672
						}
1673
						$smcFunc['db_free_result']($ignoreRequest);
1674
1675
						// Now put them in!
1676 View Code Duplication
						if (!empty($inserts))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1677
						{
1678
							$smcFunc['db_insert']('',
1679
								'{db_prefix}log_search_results',
1680
								array('id_search' => 'int', 'id_topic' => 'int', 'relevance' => 'float', 'id_msg' => 'int', 'num_matches' => 'int'),
1681
								$inserts,
1682
								array('id_search', 'id_topic')
1683
							);
1684
						}
1685
						$_SESSION['search_cache']['num_results'] += count($inserts);
1686
					}
1687
					else
1688
						$_SESSION['search_cache']['num_results'] += $smcFunc['db_affected_rows']();
1689
				}
1690
				elseif ($_SESSION['search_cache']['num_results'] == -1)
1691
					$_SESSION['search_cache']['num_results'] = 0;
1692
			}
1693
		}
1694
1695
		// *** Retrieve the results to be shown on the page
1696
		$participants = array();
1697
		$request = $smcFunc['db_search_query']('', '
1698
			SELECT ' . (empty($search_params['topic']) ? 'lsr.id_topic' : $search_params['topic'] . ' AS id_topic') . ', lsr.id_msg, lsr.relevance, lsr.num_matches
1699
			FROM {db_prefix}log_search_results AS lsr' . ($search_params['sort'] == 'num_replies' ? '
1700
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = lsr.id_topic)' : '') . '
1701
			WHERE lsr.id_search = {int:id_search}
1702
			ORDER BY {raw:sort} {raw:sort_dir}
1703
			LIMIT {int:start}, {int:max}',
1704
			array(
1705
				'id_search' => $_SESSION['search_cache']['id_search'],
1706
				'sort' => $search_params['sort'],
1707
				'sort_dir' => $search_params['sort_dir'],
1708
				'start' => $_REQUEST['start'],
1709
				'max' => $modSettings['search_results_per_page'],
1710
			)
1711
		);
1712
		while ($row = $smcFunc['db_fetch_assoc']($request))
1713
		{
1714
			$context['topics'][$row['id_msg']] = array(
1715
				'relevance' => round($row['relevance'] / 10, 1) . '%',
1716
				'num_matches' => $row['num_matches'],
1717
				'matches' => array(),
1718
			);
1719
			// By default they didn't participate in the topic!
1720
			$participants[$row['id_topic']] = false;
1721
		}
1722
		$smcFunc['db_free_result']($request);
1723
1724
		$num_results = $_SESSION['search_cache']['num_results'];
1725
	}
1726
1727
	if (!empty($context['topics']))
1728
	{
1729
		// Create an array for the permissions.
1730
		$boards_can = boardsAllowedTo(array('post_reply_own', 'post_reply_any'), true, false);
1731
1732
		// How's about some quick moderation?
1733
		if (!empty($options['display_quick_mod']))
1734
		{
1735
			$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));
1736
1737
			$context['can_lock'] = in_array(0, $boards_can['lock_any']);
1738
			$context['can_sticky'] = in_array(0, $boards_can['make_sticky']);
1739
			$context['can_move'] = in_array(0, $boards_can['move_any']);
1740
			$context['can_remove'] = in_array(0, $boards_can['remove_any']);
1741
			$context['can_merge'] = in_array(0, $boards_can['merge_any']);
1742
		}
1743
1744
		// What messages are we using?
1745
		$msg_list = array_keys($context['topics']);
1746
1747
		// Load the posters...
1748
		$request = $smcFunc['db_query']('', '
1749
			SELECT id_member
1750
			FROM {db_prefix}messages
1751
			WHERE id_member != {int:no_member}
1752
				AND id_msg IN ({array_int:message_list})
1753
			LIMIT {int:limit}',
1754
			array(
1755
				'message_list' => $msg_list,
1756
				'no_member' => 0,
1757
				'limit' => count($context['topics']),
1758
			)
1759
		);
1760
		$posters = array();
1761
		while ($row = $smcFunc['db_fetch_assoc']($request))
1762
			$posters[] = $row['id_member'];
1763
		$smcFunc['db_free_result']($request);
1764
1765
		call_integration_hook('integrate_search_message_list', array(&$msg_list, &$posters));
1766
1767
		if (!empty($posters))
1768
			loadMemberData(array_unique($posters));
1769
1770
		// Get the messages out for the callback - select enough that it can be made to look just like Display.
1771
		$messages_request = $smcFunc['db_query']('', '
1772
			SELECT
1773
				m.id_msg, m.subject, m.poster_name, m.poster_email, m.poster_time, m.id_member,
1774
				m.icon, m.poster_ip, m.body, m.smileys_enabled, m.modified_time, m.modified_name,
1775
				first_m.id_msg AS first_msg, first_m.subject AS first_subject, first_m.icon AS first_icon, first_m.poster_time AS first_poster_time,
1776
				first_mem.id_member AS first_member_id, COALESCE(first_mem.real_name, first_m.poster_name) AS first_member_name,
1777
				last_m.id_msg AS last_msg, last_m.poster_time AS last_poster_time, last_mem.id_member AS last_member_id,
1778
				COALESCE(last_mem.real_name, last_m.poster_name) AS last_member_name, last_m.icon AS last_icon, last_m.subject AS last_subject,
1779
				t.id_topic, t.is_sticky, t.locked, t.id_poll, t.num_replies, t.num_views,
1780
				b.id_board, b.name AS board_name, c.id_cat, c.name AS cat_name
1781
			FROM {db_prefix}messages AS m
1782
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1783
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1784
				INNER JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1785
				INNER JOIN {db_prefix}messages AS first_m ON (first_m.id_msg = t.id_first_msg)
1786
				INNER JOIN {db_prefix}messages AS last_m ON (last_m.id_msg = t.id_last_msg)
1787
				LEFT JOIN {db_prefix}members AS first_mem ON (first_mem.id_member = first_m.id_member)
1788
				LEFT JOIN {db_prefix}members AS last_mem ON (last_mem.id_member = first_m.id_member)
1789
			WHERE m.id_msg IN ({array_int:message_list})' . ($modSettings['postmod_active'] ? '
1790
				AND m.approved = {int:is_approved}' : '') . '
1791
			ORDER BY FIND_IN_SET(m.id_msg, {string:message_list_in_set})
1792
			LIMIT {int:limit}',
1793
			array(
1794
				'message_list' => $msg_list,
1795
				'is_approved' => 1,
1796
				'message_list_in_set' => implode(',', $msg_list),
1797
				'limit' => count($context['topics']),
1798
			)
1799
		);
1800
1801
		// If there are no results that means the things in the cache got deleted, so pretend we have no topics anymore.
1802
		if ($smcFunc['db_num_rows']($messages_request) == 0)
1803
			$context['topics'] = array();
1804
1805
		// If we want to know who participated in what then load this now.
1806
		if (!empty($modSettings['enableParticipation']) && !$user_info['is_guest'])
1807
		{
1808
			$result = $smcFunc['db_query']('', '
1809
				SELECT id_topic
1810
				FROM {db_prefix}messages
1811
				WHERE id_topic IN ({array_int:topic_list})
1812
					AND id_member = {int:current_member}
1813
				GROUP BY id_topic
1814
				LIMIT {int:limit}',
1815
				array(
1816
					'current_member' => $user_info['id'],
1817
					'topic_list' => array_keys($participants),
1818
					'limit' => count($participants),
1819
				)
1820
			);
1821
			while ($row = $smcFunc['db_fetch_assoc']($result))
1822
				$participants[$row['id_topic']] = true;
1823
			$smcFunc['db_free_result']($result);
1824
		}
1825
	}
1826
1827
	// Now that we know how many results to expect we can start calculating the page numbers.
1828
	$context['page_index'] = constructPageIndex($scripturl . '?action=search2;params=' . $context['params'], $_REQUEST['start'], $num_results, $modSettings['search_results_per_page'], false);
1829
1830
	// Consider the search complete!
1831
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1832
		cache_put_data('search_start:' . ($user_info['is_guest'] ? $user_info['ip'] : $user_info['id']), null, 90);
1833
1834
	$context['key_words'] = &$searchArray;
1835
1836
	// Setup the default topic icons... for checking they exist and the like!
1837
	$context['icon_sources'] = array();
1838
	foreach ($context['stable_icons'] as $icon)
1839
		$context['icon_sources'][$icon] = 'images_url';
1840
1841
	$context['sub_template'] = 'results';
1842
	$context['page_title'] = $txt['search_results'];
1843
	$context['get_topics'] = 'prepareSearchContext';
1844
	$context['can_restore_perm'] = allowedTo('move_any') && !empty($modSettings['recycle_enable']);
1845
	$context['can_restore'] = false; // We won't know until we handle the context later whether we can actually restore...
1846
1847
	$context['jump_to'] = array(
1848
		'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
1849
		'board_name' => addslashes(un_htmlspecialchars($txt['select_destination'])),
1850
	);
1851
}
1852
1853
/**
1854
 * Callback to return messages - saves memory.
1855
 *
1856
 * What it does:
1857
 * - callback function for the results sub template.
1858
 * - loads the necessary contextual data to show a search result.
1859
 *
1860
 * @param bool $reset Whether to reset the counter
1861
 * @return array An array of contextual info related to this search
1862
 */
1863
function prepareSearchContext($reset = false)
1864
{
1865
	global $txt, $modSettings, $scripturl, $user_info;
1866
	global $memberContext, $context, $settings, $options, $messages_request;
1867
	global $boards_can, $participants, $smcFunc;
1868
	static $recycle_board = null;
1869
1870
	if ($recycle_board === null)
1871
		$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
1872
1873
	// Remember which message this is.  (ie. reply #83)
1874
	static $counter = null;
1875
	if ($counter == null || $reset)
1876
		$counter = $_REQUEST['start'] + 1;
1877
1878
	// If the query returned false, bail.
1879
	if ($messages_request == false)
1880
		return false;
1881
1882
	// Start from the beginning...
1883
	if ($reset)
1884
		return @$smcFunc['db_data_seek']($messages_request, 0);
1885
1886
	// Attempt to get the next message.
1887
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1888
	if (!$message)
1889
		return false;
1890
1891
	// Can't have an empty subject can we?
1892
	$message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
1893
1894
	$message['first_subject'] = $message['first_subject'] != '' ? $message['first_subject'] : $txt['no_subject'];
1895
	$message['last_subject'] = $message['last_subject'] != '' ? $message['last_subject'] : $txt['no_subject'];
1896
1897
	// If it couldn't load, or the user was a guest.... someday may be done with a guest table.
1898
	if (!loadMemberContext($message['id_member']))
1899
	{
1900
		// Notice this information isn't used anywhere else.... *cough guest table cough*.
1901
		$memberContext[$message['id_member']]['name'] = $message['poster_name'];
1902
		$memberContext[$message['id_member']]['id'] = 0;
1903
		$memberContext[$message['id_member']]['group'] = $txt['guest_title'];
1904
		$memberContext[$message['id_member']]['link'] = $message['poster_name'];
1905
		$memberContext[$message['id_member']]['email'] = $message['poster_email'];
1906
	}
1907
	$memberContext[$message['id_member']]['ip'] = inet_dtop($message['poster_ip']);
1908
1909
	// Do the censor thang...
1910
	censorText($message['body']);
1911
	censorText($message['subject']);
1912
1913
	censorText($message['first_subject']);
1914
	censorText($message['last_subject']);
1915
1916
	// Shorten this message if necessary.
1917
	if ($context['compact'])
1918
	{
1919
		// Set the number of characters before and after the searched keyword.
1920
		$charLimit = 50;
1921
1922
		$message['body'] = strtr($message['body'], array("\n" => ' ', '<br>' => "\n"));
1923
		$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1924
		$message['body'] = strip_tags(strtr($message['body'], array('</div>' => '<br>', '</li>' => '<br>')), '<br>');
1925
1926
		if ($smcFunc['strlen']($message['body']) > $charLimit)
1927
		{
1928
			if (empty($context['key_words']))
1929
				$message['body'] = $smcFunc['substr']($message['body'], 0, $charLimit) . '<strong>...</strong>';
1930
			else
1931
			{
1932
				$matchString = '';
1933
				$force_partial_word = false;
1934
				foreach ($context['key_words'] as $keyword)
1935
				{
1936
					$keyword = un_htmlspecialchars($keyword);
1937
					$keyword = preg_replace_callback('~(&amp;#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', strtr($keyword, array('\\\'' => '\'', '&' => '&amp;')));
1938
1939
					if (preg_match('~[\'\.,/@%&;:(){}\[\]_\-+\\\\]$~', $keyword) != 0 || preg_match('~^[\'\.,/@%&;:(){}\[\]_\-+\\\\]~', $keyword) != 0)
1940
						$force_partial_word = true;
1941
					$matchString .= strtr(preg_quote($keyword, '/'), array('\*' => '.+?')) . '|';
1942
				}
1943
				$matchString = un_htmlspecialchars(substr($matchString, 0, -1));
1944
1945
				$message['body'] = un_htmlspecialchars(strtr($message['body'], array('&nbsp;' => ' ', '<br>' => "\n", '&#91;' => '[', '&#93;' => ']', '&#58;' => ':', '&#64;' => '@')));
1946
1947
				if (empty($modSettings['search_method']) || $force_partial_word)
1948
					preg_match_all('/([^\s\W]{' . $charLimit . '}[\s\W]|[\s\W].{0,' . $charLimit . '}?|^)(' . $matchString . ')(.{0,' . $charLimit . '}[\s\W]|[^\s\W]{0,' . $charLimit . '})/is' . ($context['utf8'] ? 'u' : ''), $message['body'], $matches);
1949
				else
1950
					preg_match_all('/([^\s\W]{' . $charLimit . '}[\s\W]|[\s\W].{0,' . $charLimit . '}?[\s\W]|^)(' . $matchString . ')([\s\W].{0,' . $charLimit . '}[\s\W]|[\s\W][^\s\W]{0,' . $charLimit . '})/is' . ($context['utf8'] ? 'u' : ''), $message['body'], $matches);
1951
1952
				$message['body'] = '';
1953
				foreach ($matches[0] as $index => $match)
1954
				{
1955
					$match = strtr($smcFunc['htmlspecialchars']($match, ENT_QUOTES), array("\n" => '&nbsp;'));
1956
					$message['body'] .= '<strong>......</strong>&nbsp;' . $match . '&nbsp;<strong>......</strong>';
1957
				}
1958
			}
1959
1960
			// Re-fix the international characters.
1961
			$message['body'] = preg_replace_callback('~(&amp;#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $message['body']);
1962
		}
1963
	}
1964
	else
1965
	{
1966
		// Run BBC interpreter on the message.
1967
		$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1968
	}
1969
1970
	// Make sure we don't end up with a practically empty message body.
1971
	$message['body'] = preg_replace('~^(?:&nbsp;)+$~', '', $message['body']);
1972
1973
	if (!empty($recycle_board) && $message['id_board'] == $recycle_board)
1974
	{
1975
		$message['first_icon'] = 'recycled';
1976
		$message['last_icon'] = 'recycled';
1977
		$message['icon'] = 'recycled';
1978
	}
1979
1980
	// Sadly, we need to check the icon ain't broke.
1981
	if (!empty($modSettings['messageIconChecks_enable']))
1982
	{
1983 View Code Duplication
		if (!isset($context['icon_sources'][$message['first_icon']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1984
			$context['icon_sources'][$message['first_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['first_icon'] . '.png') ? 'images_url' : 'default_images_url';
1985 View Code Duplication
		if (!isset($context['icon_sources'][$message['last_icon']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1986
			$context['icon_sources'][$message['last_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['last_icon'] . '.png') ? 'images_url' : 'default_images_url';
1987 View Code Duplication
		if (!isset($context['icon_sources'][$message['icon']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1988
			$context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.png') ? 'images_url' : 'default_images_url';
1989
	}
1990
	else
1991
	{
1992 View Code Duplication
		if (!isset($context['icon_sources'][$message['first_icon']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1993
			$context['icon_sources'][$message['first_icon']] = 'images_url';
1994 View Code Duplication
		if (!isset($context['icon_sources'][$message['last_icon']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1995
			$context['icon_sources'][$message['last_icon']] = 'images_url';
1996 View Code Duplication
		if (!isset($context['icon_sources'][$message['icon']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1997
			$context['icon_sources'][$message['icon']] = 'images_url';
1998
	}
1999
2000
	// Do we have quote tag enabled?
2001
	$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
2002
2003
	// Reference the main color class.
2004
	$colorClass = 'windowbg';
2005
2006
	// Sticky topics should get a different color, too.
2007
	if ($message['is_sticky'])
2008
		$colorClass .= ' sticky';
2009
2010
	// Locked topics get special treatment as well.
2011
	if ($message['locked'])
2012
		$colorClass .= ' locked';
2013
2014
	$output = array_merge($context['topics'][$message['id_msg']], array(
2015
		'id' => $message['id_topic'],
2016
		'is_sticky' => !empty($message['is_sticky']),
2017
		'is_locked' => !empty($message['locked']),
2018
		'css_class' => $colorClass,
2019
		'is_poll' => $modSettings['pollMode'] == '1' && $message['id_poll'] > 0,
2020
		'posted_in' => !empty($participants[$message['id_topic']]),
2021
		'views' => $message['num_views'],
2022
		'replies' => $message['num_replies'],
2023
		'can_reply' => in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any']),
2024
		'can_quote' => (in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any'])) && $quote_enabled,
2025
		'first_post' => array(
2026
			'id' => $message['first_msg'],
2027
			'time' => timeformat($message['first_poster_time']),
2028
			'timestamp' => forum_time(true, $message['first_poster_time']),
2029
			'subject' => $message['first_subject'],
2030
			'href' => $scripturl . '?topic=' . $message['id_topic'] . '.0',
2031
			'link' => '<a href="' . $scripturl . '?topic=' . $message['id_topic'] . '.0">' . $message['first_subject'] . '</a>',
2032
			'icon' => $message['first_icon'],
2033
			'icon_url' => $settings[$context['icon_sources'][$message['first_icon']]] . '/post/' . $message['first_icon'] . '.png',
2034
			'member' => array(
2035
				'id' => $message['first_member_id'],
2036
				'name' => $message['first_member_name'],
2037
				'href' => !empty($message['first_member_id']) ? $scripturl . '?action=profile;u=' . $message['first_member_id'] : '',
2038
				'link' => !empty($message['first_member_id']) ? '<a href="' . $scripturl . '?action=profile;u=' . $message['first_member_id'] . '" title="' . $txt['profile_of'] . ' ' . $message['first_member_name'] . '">' . $message['first_member_name'] . '</a>' : $message['first_member_name']
2039
			)
2040
		),
2041
		'last_post' => array(
2042
			'id' => $message['last_msg'],
2043
			'time' => timeformat($message['last_poster_time']),
2044
			'timestamp' => forum_time(true, $message['last_poster_time']),
2045
			'subject' => $message['last_subject'],
2046
			'href' => $scripturl . '?topic=' . $message['id_topic'] . ($message['num_replies'] == 0 ? '.0' : '.msg' . $message['last_msg']) . '#msg' . $message['last_msg'],
2047
			'link' => '<a href="' . $scripturl . '?topic=' . $message['id_topic'] . ($message['num_replies'] == 0 ? '.0' : '.msg' . $message['last_msg']) . '#msg' . $message['last_msg'] . '">' . $message['last_subject'] . '</a>',
2048
			'icon' => $message['last_icon'],
2049
			'icon_url' => $settings[$context['icon_sources'][$message['last_icon']]] . '/post/' . $message['last_icon'] . '.png',
2050
			'member' => array(
2051
				'id' => $message['last_member_id'],
2052
				'name' => $message['last_member_name'],
2053
				'href' => !empty($message['last_member_id']) ? $scripturl . '?action=profile;u=' . $message['last_member_id'] : '',
2054
				'link' => !empty($message['last_member_id']) ? '<a href="' . $scripturl . '?action=profile;u=' . $message['last_member_id'] . '" title="' . $txt['profile_of'] . ' ' . $message['last_member_name'] . '">' . $message['last_member_name'] . '</a>' : $message['last_member_name']
2055
			)
2056
		),
2057
		'board' => array(
2058
			'id' => $message['id_board'],
2059
			'name' => $message['board_name'],
2060
			'href' => $scripturl . '?board=' . $message['id_board'] . '.0',
2061
			'link' => '<a href="' . $scripturl . '?board=' . $message['id_board'] . '.0">' . $message['board_name'] . '</a>'
2062
		),
2063
		'category' => array(
2064
			'id' => $message['id_cat'],
2065
			'name' => $message['cat_name'],
2066
			'href' => $scripturl . '#c' . $message['id_cat'],
2067
			'link' => '<a href="' . $scripturl . '#c' . $message['id_cat'] . '">' . $message['cat_name'] . '</a>'
2068
		)
2069
	));
2070
2071
	$body_highlighted = $message['body'];
2072
	$subject_highlighted = $message['subject'];
2073
2074
	if (!empty($options['display_quick_mod']))
2075
	{
2076
		$started = $output['first_post']['member']['id'] == $user_info['id'];
2077
2078
		$output['quick_mod'] = array(
2079
			'lock' => in_array(0, $boards_can['lock_any']) || in_array($output['board']['id'], $boards_can['lock_any']) || ($started && (in_array(0, $boards_can['lock_own']) || in_array($output['board']['id'], $boards_can['lock_own']))),
2080
			'sticky' => (in_array(0, $boards_can['make_sticky']) || in_array($output['board']['id'], $boards_can['make_sticky'])),
2081
			'move' => in_array(0, $boards_can['move_any']) || in_array($output['board']['id'], $boards_can['move_any']) || ($started && (in_array(0, $boards_can['move_own']) || in_array($output['board']['id'], $boards_can['move_own']))),
2082
			'remove' => in_array(0, $boards_can['remove_any']) || in_array($output['board']['id'], $boards_can['remove_any']) || ($started && (in_array(0, $boards_can['remove_own']) || in_array($output['board']['id'], $boards_can['remove_own']))),
2083
			'restore' => $context['can_restore_perm'] && ($modSettings['recycle_board'] == $output['board']['id']),
2084
		);
2085
2086
		$context['can_lock'] |= $output['quick_mod']['lock'];
2087
		$context['can_sticky'] |= $output['quick_mod']['sticky'];
2088
		$context['can_move'] |= $output['quick_mod']['move'];
2089
		$context['can_remove'] |= $output['quick_mod']['remove'];
2090
		$context['can_merge'] |= in_array($output['board']['id'], $boards_can['merge_any']);
2091
		$context['can_restore'] |= $output['quick_mod']['restore'];
2092
		$context['can_markread'] = $context['user']['is_logged'];
2093
2094
		$context['qmod_actions'] = array('remove', 'lock', 'sticky', 'move', 'merge', 'restore', 'markread');
2095
		call_integration_hook('integrate_quick_mod_actions_search');
2096
	}
2097
2098
	foreach ($context['key_words'] as $query)
2099
	{
2100
		// Fix the international characters in the keyword too.
2101
		$query = un_htmlspecialchars($query);
2102
		$query = trim($query, "\*+");
2103
		$query = strtr($smcFunc['htmlspecialchars']($query), array('\\\'' => '\''));
2104
2105
		$body_highlighted = preg_replace_callback('/((<[^>]*)|' . preg_quote(strtr($query, array('\'' => '&#039;')), '/') . ')/i' . ($context['utf8'] ? 'u' : ''), function ($m)
2106
		{
2107
			return isset($m[2]) && "$m[2]" == "$m[1]" ? stripslashes("$m[1]") : "<strong class=\"highlight\">$m[1]</strong>";
2108
		}, $body_highlighted);
2109
		$subject_highlighted = preg_replace('/(' . preg_quote($query, '/') . ')/i' . ($context['utf8'] ? 'u' : ''), '<strong class="highlight">$1</strong>', $subject_highlighted);
2110
	}
2111
2112
	$output['matches'][] = array(
2113
		'id' => $message['id_msg'],
2114
		'attachment' => array(),
2115
		'member' => &$memberContext[$message['id_member']],
2116
		'icon' => $message['icon'],
2117
		'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.png',
2118
		'subject' => $message['subject'],
2119
		'subject_highlighted' => $subject_highlighted,
2120
		'time' => timeformat($message['poster_time']),
2121
		'timestamp' => forum_time(true, $message['poster_time']),
2122
		'counter' => $counter,
2123
		'modified' => array(
2124
			'time' => timeformat($message['modified_time']),
2125
			'timestamp' => forum_time(true, $message['modified_time']),
2126
			'name' => $message['modified_name']
2127
		),
2128
		'body' => $message['body'],
2129
		'body_highlighted' => $body_highlighted,
2130
		'start' => 'msg' . $message['id_msg']
2131
	);
2132
	$counter++;
2133
2134
	call_integration_hook('integrate_search_message_context', array(&$output, &$message, $counter));
2135
2136
	return $output;
2137
}
2138
2139
/**
2140
 * Creates a search API and returns the object.
2141
 *
2142
 * @return search_api_interface An instance of the search API interface
2143
 */
2144
function findSearchAPI()
2145
{
2146
	global $sourcedir, $modSettings, $search_versions, $searchAPI, $txt;
2147
2148
	require_once($sourcedir . '/Subs-Package.php');
2149
	require_once($sourcedir . '/Class-SearchAPI.php');
2150
2151
	// Search has a special database set.
2152
	db_extend('search');
2153
2154
	// Load up the search API we are going to use.
2155
	$modSettings['search_index'] = empty($modSettings['search_index']) ? 'standard' : $modSettings['search_index'];
2156
	if (!file_exists($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php'))
2157
		fatal_lang_error('search_api_missing');
2158
	require_once($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php');
2159
2160
	// Create an instance of the search API and check it is valid for this version of SMF.
2161
	$search_class_name = $modSettings['search_index'] . '_search';
2162
	$searchAPI = new $search_class_name();
2163
2164
	// An invalid Search API.
2165
	if (!$searchAPI || !($searchAPI instanceof search_api_interface) || ($searchAPI->supportsMethod('isValid') && !$searchAPI->isValid()) || !matchPackageVersion($search_versions['forum_version'], $searchAPI->min_smf_version . '-' . $searchAPI->version_compatible))
0 ignored issues
show
Bug introduced by
Accessing min_smf_version on the interface search_api_interface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing version_compatible on the interface search_api_interface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2166
	{
2167
		// Log the error.
2168
		loadLanguage('Errors');
2169
		log_error(sprintf($txt['search_api_not_compatible'], 'SearchAPI-' . ucwords($modSettings['search_index']) . '.php'), 'critical');
2170
2171
		require_once($sourcedir . '/SearchAPI-Standard.php');
2172
		$searchAPI = new standard_search();
2173
	}
2174
2175
	return $searchAPI;
2176
}
2177
2178
/**
2179
 * This function compares the length of two strings plus a little.
2180
 * What it does:
2181
 * - callback function for usort used to sort the fulltext results.
2182
 * - passes sorting duty to the current API.
2183
 *
2184
 * @param string $a
2185
 * @param string $b
2186
 * @return int
2187
 */
2188
function searchSort($a, $b)
2189
{
2190
	global $searchAPI;
2191
2192
	return $searchAPI->searchSort($a, $b);
2193
}
2194
2195
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...