Completed
Push — release-2.1 ( 6f6d35...abeae7 )
by Mathias
08:46
created

Search.php ➔ searchSort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
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 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
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 4',
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);
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);
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);
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);
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
	}
715
	$searchArray = array_slice(array_unique($searchArray), 0, 10);
716
717
	// Create an array of replacements for highlighting.
718
	$context['mark'] = array();
719
	foreach ($searchArray as $word)
720
		$context['mark'][$word] = '<strong class="highlight">' . $word . '</strong>';
721
722
	// Initialize two arrays storing the words that have to be searched for.
723
	$orParts = array();
724
	$searchWords = array();
725
726
	// Make sure at least one word is being searched for.
727
	if (empty($searchArray))
728
		$context['search_errors']['invalid_search_string' . (!empty($foundBlackListedWords) ? '_blacklist' : '')] = true;
729
	// All words/sentences must match.
730
	elseif (empty($search_params['searchtype']))
731
		$orParts[0] = $searchArray;
732
	// Any word/sentence must match.
733
	else
734
		foreach ($searchArray as $index => $value)
735
			$orParts[$index] = array($value);
736
737
	// Don't allow duplicate error messages if one string is too short.
738
	if (isset($context['search_errors']['search_string_small_words'], $context['search_errors']['invalid_search_string']))
739
		unset($context['search_errors']['invalid_search_string']);
740
	// Make sure the excluded words are in all or-branches.
741
	foreach ($orParts as $orIndex => $andParts)
742
		foreach ($excludedWords as $word)
743
			$orParts[$orIndex][] = $word;
744
745
	// Determine the or-branches and the fulltext search words.
746
	foreach ($orParts as $orIndex => $andParts)
747
	{
748
		$searchWords[$orIndex] = array(
749
			'indexed_words' => array(),
750
			'words' => array(),
751
			'subject_words' => array(),
752
			'all_words' => array(),
753
			'complex_words' => array(),
754
		);
755
756
		// Sort the indexed words (large words -> small words -> excluded words).
757
		if ($searchAPI->supportsMethod('searchSort'))
758
			usort($orParts[$orIndex], 'searchSort');
759
760
		foreach ($orParts[$orIndex] as $word)
761
		{
762
			$is_excluded = in_array($word, $excludedWords);
763
764
			$searchWords[$orIndex]['all_words'][] = $word;
765
766
			$subjectWords = text2words($word);
767
			if (!$is_excluded || count($subjectWords) === 1)
768
			{
769
				$searchWords[$orIndex]['subject_words'] = array_merge($searchWords[$orIndex]['subject_words'], $subjectWords);
770
				if ($is_excluded)
771
					$excludedSubjectWords = array_merge($excludedSubjectWords, $subjectWords);
772
			}
773
			else
774
				$excludedPhrases[] = $word;
775
776
			// Have we got indexes to prepare?
777
			if ($searchAPI->supportsMethod('prepareIndexes'))
778
				$searchAPI->prepareIndexes($word, $searchWords[$orIndex], $excludedIndexWords, $is_excluded);
779
		}
780
781
		// Search_force_index requires all AND parts to have at least one fulltext word.
782
		if (!empty($modSettings['search_force_index']) && empty($searchWords[$orIndex]['indexed_words']))
783
		{
784
			$context['search_errors']['query_not_specific_enough'] = true;
785
			break;
786
		}
787
		elseif ($search_params['subject_only'] && empty($searchWords[$orIndex]['subject_words']) && empty($excludedSubjectWords))
788
		{
789
			$context['search_errors']['query_not_specific_enough'] = true;
790
			break;
791
		}
792
793
		// Make sure we aren't searching for too many indexed words.
794
		else
795
		{
796
			$searchWords[$orIndex]['indexed_words'] = array_slice($searchWords[$orIndex]['indexed_words'], 0, 7);
797
			$searchWords[$orIndex]['subject_words'] = array_slice($searchWords[$orIndex]['subject_words'], 0, 7);
798
			$searchWords[$orIndex]['words'] = array_slice($searchWords[$orIndex]['words'], 0, 4);
799
		}
800
	}
801
802
	// *** Spell checking
803
	$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && (function_exists('pspell_new') || (function_exists('enchant_broker_init') && ($txt['lang_charset'] == 'UTF-8' || function_exists('iconv'))));
804
	if ($context['show_spellchecking'])
805
	{
806
		require_once($sourcedir . '/Subs-Post.php');
807
808
		// Don't hardcode spellchecking functions!
809
		$link = spell_init();
810
811
		$did_you_mean = array('search' => array(), 'display' => array());
812
		$found_misspelling = false;
813
		foreach ($searchArray as $word)
814
		{
815
			if (empty($link))
816
				continue;
817
818
			// Don't check phrases.
819
			if (preg_match('~^\w+$~', $word) === 0)
820
			{
821
				$did_you_mean['search'][] = '"' . $word . '"';
822
				$did_you_mean['display'][] = '&quot;' . $smcFunc['htmlspecialchars']($word) . '&quot;';
823
				continue;
824
			}
825
			// For some strange reason spell check can crash PHP on decimals.
826
			elseif (preg_match('~\d~', $word) === 1)
827
			{
828
				$did_you_mean['search'][] = $word;
829
				$did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word);
830
				continue;
831
			}
832 View Code Duplication
			elseif (spell_check($link, $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...
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...
833
			{
834
				$did_you_mean['search'][] = $word;
835
				$did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word);
836
				continue;
837
			}
838
839
			$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...
840
			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...
841
			{
842
				// Search is case insensitive.
843
				if ($smcFunc['strtolower']($s) == $smcFunc['strtolower']($word))
844
					unset($suggestions[$i]);
845
				// Plus, don't suggest something the user thinks is rude!
846
				elseif ($suggestions[$i] != censorText($s))
847
					unset($suggestions[$i]);
848
			}
849
850
			// Anything found?  If so, correct it!
851
			if (!empty($suggestions))
852
			{
853
				$suggestions = array_values($suggestions);
854
				$did_you_mean['search'][] = $suggestions[0];
855
				$did_you_mean['display'][] = '<em><strong>' . $smcFunc['htmlspecialchars']($suggestions[0]) . '</strong></em>';
856
				$found_misspelling = true;
857
			}
858 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...
859
			{
860
				$did_you_mean['search'][] = $word;
861
				$did_you_mean['display'][] = $smcFunc['htmlspecialchars']($word);
862
			}
863
		}
864
865
		if ($found_misspelling)
866
		{
867
			// Don't spell check excluded words, but add them still...
868
			$temp_excluded = array('search' => array(), 'display' => array());
869
			foreach ($excludedWords as $word)
870
			{
871
				if (preg_match('~^\w+$~', $word) == 0)
872
				{
873
					$temp_excluded['search'][] = '-"' . $word . '"';
874
					$temp_excluded['display'][] = '-&quot;' . $smcFunc['htmlspecialchars']($word) . '&quot;';
875
				}
876
				else
877
				{
878
					$temp_excluded['search'][] = '-' . $word;
879
					$temp_excluded['display'][] = '-' . $smcFunc['htmlspecialchars']($word);
880
				}
881
			}
882
883
			$did_you_mean['search'] = array_merge($did_you_mean['search'], $temp_excluded['search']);
884
			$did_you_mean['display'] = array_merge($did_you_mean['display'], $temp_excluded['display']);
885
886
			$temp_params = $search_params;
887
			$temp_params['search'] = implode(' ', $did_you_mean['search']);
888
			if (isset($temp_params['brd']))
889
				$temp_params['brd'] = implode(',', $temp_params['brd']);
890
			$context['params'] = array();
891
			foreach ($temp_params as $k => $v)
892
				$context['did_you_mean_params'][] = $k . '|\'|' . $v;
893
			$context['did_you_mean_params'] = base64_encode(implode('|"|', $context['did_you_mean_params']));
894
			$context['did_you_mean'] = implode(' ', $did_you_mean['display']);
895
		}
896
	}
897
898
	// Let the user adjust the search query, should they wish?
899
	$context['search_params'] = $search_params;
900 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...
901
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
902 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...
903
		$context['search_params']['userspec'] = $smcFunc['htmlspecialchars']($context['search_params']['userspec']);
904
905
	// Do we have captcha enabled?
906
	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']))
907
	{
908
		// If we come from another search box tone down the error...
909
		if (!isset($_REQUEST['search_vv']))
910
			$context['search_errors']['need_verification_code'] = true;
911
		else
912
		{
913
			require_once($sourcedir . '/Subs-Editor.php');
914
			$verificationOptions = array(
915
				'id' => 'search',
916
			);
917
			$context['require_verification'] = create_control_verification($verificationOptions, true);
918
919
			if (is_array($context['require_verification']))
920
			{
921
				foreach ($context['require_verification'] as $error)
922
					$context['search_errors'][$error] = true;
923
			}
924
			// Don't keep asking for it - they've proven themselves worthy.
925
			else
926
				$_SESSION['ss_vv_passed'] = true;
927
		}
928
	}
929
930
	// *** Encode all search params
931
932
	// All search params have been checked, let's compile them to a single string... made less simple by PHP 4.3.9 and below.
933
	$temp_params = $search_params;
934
	if (isset($temp_params['brd']))
935
		$temp_params['brd'] = implode(',', $temp_params['brd']);
936
	$context['params'] = array();
937
	foreach ($temp_params as $k => $v)
938
		$context['params'][] = $k . '|\'|' . $v;
939
940
	if (!empty($context['params']))
941
	{
942
		// Due to old IE's 2083 character limit, we have to compress long search strings
943
		$params = @gzcompress(implode('|"|', $context['params']));
944
		// Gzcompress failed, use try non-gz
945
		if (empty($params))
946
			$params = implode('|"|', $context['params']);
947
		// Base64 encode, then replace +/= with uri safe ones that can be reverted
948
		$context['params'] = str_replace(array('+', '/', '='), array('-', '_', '.'), base64_encode($params));
949
	}
950
951
	// ... and add the links to the link tree.
952
	$context['linktree'][] = array(
953
		'url' => $scripturl . '?action=search;params=' . $context['params'],
954
		'name' => $txt['search']
955
	);
956
	$context['linktree'][] = array(
957
		'url' => $scripturl . '?action=search2;params=' . $context['params'],
958
		'name' => $txt['search_results']
959
	);
960
961
	// *** A last error check
962
	call_integration_hook('integrate_search_errors');
963
964
	// One or more search errors? Go back to the first search screen.
965 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...
966
	{
967
		$_REQUEST['params'] = $context['params'];
968
		return PlushSearch1();
969
	}
970
971
	// Spam me not, Spam-a-lot?
972
	if (empty($_SESSION['last_ss']) || $_SESSION['last_ss'] != $search_params['search'])
973
		spamProtection('search');
974
	// Store the last search string to allow pages of results to be browsed.
975
	$_SESSION['last_ss'] = $search_params['search'];
976
977
	// *** Reserve an ID for caching the search results.
978
	$query_params = array_merge($search_params, array(
979
		'min_msg_id' => isset($minMsgID) ? (int) $minMsgID : 0,
980
		'max_msg_id' => isset($maxMsgID) ? (int) $maxMsgID : 0,
981
		'memberlist' => !empty($memberlist) ? $memberlist : array(),
982
	));
983
984
	// Can this search rely on the API given the parameters?
985
	if ($searchAPI->supportsMethod('searchQuery', $query_params))
986
	{
987
		$participants = array();
988
		$searchArray = array();
989
990
		$num_results = $searchAPI->searchQuery($query_params, $searchWords, $excludedIndexWords, $participants, $searchArray);
991
	}
992
993
	// Update the cache if the current search term is not yet cached.
994
	else
995
	{
996
		$update_cache = empty($_SESSION['search_cache']) || ($_SESSION['search_cache']['params'] != $context['params']);
997
		if ($update_cache)
998
		{
999
			// Increase the pointer...
1000
			$modSettings['search_pointer'] = empty($modSettings['search_pointer']) ? 0 : (int) $modSettings['search_pointer'];
1001
			// ...and store it right off.
1002
			updateSettings(array('search_pointer' => $modSettings['search_pointer'] >= 255 ? 0 : $modSettings['search_pointer'] + 1));
1003
			// As long as you don't change the parameters, the cache result is yours.
1004
			$_SESSION['search_cache'] = array(
1005
				'id_search' => $modSettings['search_pointer'],
1006
				'num_results' => -1,
1007
				'params' => $context['params'],
1008
			);
1009
1010
			// Clear the previous cache of the final results cache.
1011
			$smcFunc['db_search_query']('delete_log_search_results', '
1012
				DELETE FROM {db_prefix}log_search_results
1013
				WHERE id_search = {int:search_id}',
1014
				array(
1015
					'search_id' => $_SESSION['search_cache']['id_search'],
1016
				)
1017
			);
1018
1019
			if ($search_params['subject_only'])
1020
			{
1021
				// We do this to try and avoid duplicate keys on databases not supporting INSERT IGNORE.
1022
				$inserts = array();
1023
				foreach ($searchWords as $orIndex => $words)
1024
				{
1025
					$subject_query_params = array();
1026
					$subject_query = array(
1027
						'from' => '{db_prefix}topics AS t',
1028
						'inner_join' => array(),
1029
						'left_join' => array(),
1030
						'where' => array(),
1031
					);
1032
1033
					if ($modSettings['postmod_active'])
1034
						$subject_query['where'][] = 't.approved = {int:is_approved}';
1035
1036
					$numTables = 0;
1037
					$prev_join = 0;
1038
					$numSubjectResults = 0;
1039
					foreach ($words['subject_words'] as $subjectWord)
1040
					{
1041
						$numTables++;
1042
						if (in_array($subjectWord, $excludedSubjectWords))
1043
						{
1044
							$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)';
1045
							$subject_query['where'][] = '(subj' . $numTables . '.word IS NULL)';
1046
						}
1047
						else
1048
						{
1049
							$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)';
1050
							$subject_query['where'][] = 'subj' . $numTables . '.word ' . (empty($modSettings['search_match_words']) ? 'LIKE {string:subject_words_' . $numTables . '_wild}' : '= {string:subject_words_' . $numTables . '}');
1051
							$prev_join = $numTables;
1052
						}
1053
						$subject_query_params['subject_words_' . $numTables] = $subjectWord;
1054
						$subject_query_params['subject_words_' . $numTables . '_wild'] = '%' . $subjectWord . '%';
1055
					}
1056
1057
					if (!empty($userQuery))
1058
					{
1059
						if ($subject_query['from'] != '{db_prefix}messages AS m')
1060
						{
1061
							$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_topic = t.id_topic)';
1062
						}
1063
						$subject_query['where'][] = $userQuery;
1064
					}
1065
					if (!empty($search_params['topic']))
1066
						$subject_query['where'][] = 't.id_topic = ' . $search_params['topic'];
1067
					if (!empty($minMsgID))
1068
						$subject_query['where'][] = 't.id_first_msg >= ' . $minMsgID;
1069
					if (!empty($maxMsgID))
1070
						$subject_query['where'][] = 't.id_last_msg <= ' . $maxMsgID;
1071
					if (!empty($boardQuery))
1072
						$subject_query['where'][] = 't.id_board ' . $boardQuery;
1073
					if (!empty($excludedPhrases))
1074
					{
1075
						if ($subject_query['from'] != '{db_prefix}messages AS m')
1076
						{
1077
							$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1078
						}
1079
						$count = 0;
1080
						foreach ($excludedPhrases as $phrase)
1081
						{
1082
							$subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:excluded_phrases_' . $count . '}';
1083
							$subject_query_params['excluded_phrases_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
1084
						}
1085
					}
1086
					call_integration_hook('integrate_subject_only_search_query', array(&$subject_query, &$subject_query_params));
1087
1088
					$relevance = '1000 * (';
1089 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...
1090
					{
1091
						$relevance .= $weight[$type];
1092
						if (!empty($value['results']))
1093
							$relevance .= ' * ' . $value['results'];
1094
						$relevance .= ' + ';
1095
					}
1096
					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
1097
1098
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_subject',
1099
						($smcFunc['db_support_ignore'] ? '
1100
						INSERT IGNORE INTO {db_prefix}log_search_results
1101
							(id_search, id_topic, relevance, id_msg, num_matches)' : '') . '
1102
						SELECT
1103
							{int:id_search},
1104
							t.id_topic,
1105
							' . $relevance. ',
1106
							' . (empty($userQuery) ? 't.id_first_msg' : 'm.id_msg') . ',
1107
							1
1108
						FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : '
1109
							INNER JOIN ' . implode('
1110
							INNER JOIN ', $subject_query['inner_join'])) . (empty($subject_query['left_join']) ? '' : '
1111
							LEFT JOIN ' . implode('
1112
							LEFT JOIN ', $subject_query['left_join'])) . '
1113
						WHERE ' . implode('
1114
							AND ', $subject_query['where']) . (empty($modSettings['search_max_results']) ? '' : '
1115
						LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults)),
1116
						array_merge($subject_query_params, array(
1117
							'id_search' => $_SESSION['search_cache']['id_search'],
1118
							'min_msg' => $minMsg,
1119
							'recent_message' => $recentMsg,
1120
							'huge_topic_posts' => $humungousTopicPosts,
1121
							'is_approved' => 1,
1122
						))
1123
					);
1124
1125
					// If the database doesn't support IGNORE to make this fast we need to do some tracking.
1126 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...
1127
					{
1128
						while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1129
						{
1130
							// No duplicates!
1131
							if (isset($inserts[$row[1]]))
1132
								continue;
1133
1134
							foreach ($row as $key => $value)
1135
								$inserts[$row[1]][] = (int) $row[$key];
1136
						}
1137
						$smcFunc['db_free_result']($ignoreRequest);
1138
						$numSubjectResults = count($inserts);
1139
					}
1140
					else
1141
						$numSubjectResults += $smcFunc['db_affected_rows']();
1142
1143
					if (!empty($modSettings['search_max_results']) && $numSubjectResults >= $modSettings['search_max_results'])
1144
						break;
1145
				}
1146
1147
				// If there's data to be inserted for non-IGNORE databases do it here!
1148 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...
1149
				{
1150
					$smcFunc['db_insert']('',
1151
						'{db_prefix}log_search_results',
1152
						array('id_search' => 'int', 'id_topic' => 'int', 'relevance' => 'int', 'id_msg' => 'int', 'num_matches' => 'int'),
1153
						$inserts,
1154
						array('id_search', 'id_topic')
1155
					);
1156
				}
1157
1158
				$_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...
1159
			}
1160
			else
1161
			{
1162
				$main_query = array(
1163
					'select' => array(
1164
						'id_search' => $_SESSION['search_cache']['id_search'],
1165
						'relevance' => '0',
1166
					),
1167
					'weights' => array(),
1168
					'from' => '{db_prefix}topics AS t',
1169
					'inner_join' => array(
1170
						'{db_prefix}messages AS m ON (m.id_topic = t.id_topic)'
1171
					),
1172
					'left_join' => array(),
1173
					'where' => array(),
1174
					'group_by' => array(),
1175
					'parameters' => array(
1176
						'min_msg' => $minMsg,
1177
						'recent_message' => $recentMsg,
1178
						'huge_topic_posts' => $humungousTopicPosts,
1179
						'is_approved' => 1,
1180
					),
1181
				);
1182
1183
				if (empty($search_params['topic']) && empty($search_params['show_complete']))
1184
				{
1185
					$main_query['select']['id_topic'] = 't.id_topic';
1186
					$main_query['select']['id_msg'] = 'MAX(m.id_msg) AS id_msg';
1187
					$main_query['select']['num_matches'] = 'COUNT(*) AS num_matches';
1188
1189
					$main_query['weights'] = $weight_factors;
1190
1191
					$main_query['group_by'][] = 't.id_topic';
1192
				}
1193
				else
1194
				{
1195
					// This is outrageous!
1196
					$main_query['select']['id_topic'] = 'm.id_msg AS id_topic';
1197
					$main_query['select']['id_msg'] = 'm.id_msg';
1198
					$main_query['select']['num_matches'] = '1 AS num_matches';
1199
1200
					$main_query['weights'] = array(
1201
						'age' => array(
1202
							'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)',
1203
						),
1204
						'first_message' => array(
1205
							'search' => 'CASE WHEN m.id_msg = t.id_first_msg THEN 1 ELSE 0 END',
1206
						),
1207
					);
1208
1209 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...
1210
					{
1211
						$main_query['where'][] = 't.id_topic = {int:topic}';
1212
						$main_query['parameters']['topic'] = $search_params['topic'];
1213
					}
1214
					if (!empty($search_params['show_complete']))
1215
						$main_query['group_by'][] = 'm.id_msg, t.id_first_msg, t.id_last_msg';
1216
				}
1217
1218
				// *** Get the subject results.
1219
				$numSubjectResults = 0;
1220
				if (empty($search_params['topic']))
1221
				{
1222
					$inserts = array();
1223
					// Create a temporary table to store some preliminary results in.
1224
					$smcFunc['db_search_query']('drop_tmp_log_search_topics', '
1225
						DROP TABLE IF EXISTS {db_prefix}tmp_log_search_topics',
1226
						array(
1227
						)
1228
					);
1229
					$createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_topics', '
1230
						CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_topics (
1231
							id_topic int NOT NULL default {string:string_zero},
1232
							PRIMARY KEY (id_topic)
1233
						) ENGINE=MEMORY',
1234
						array(
1235
							'string_zero' => '0',
1236
						)
1237
					) !== false;
1238
1239
					// Clean up some previous cache.
1240
					if (!$createTemporary)
1241
						$smcFunc['db_search_query']('delete_log_search_topics', '
1242
							DELETE FROM {db_prefix}log_search_topics
1243
							WHERE id_search = {int:search_id}',
1244
							array(
1245
								'search_id' => $_SESSION['search_cache']['id_search'],
1246
							)
1247
						);
1248
1249
					foreach ($searchWords as $orIndex => $words)
1250
					{
1251
						$subject_query = array(
1252
							'from' => '{db_prefix}topics AS t',
1253
							'inner_join' => array(),
1254
							'left_join' => array(),
1255
							'where' => array(),
1256
							'params' => array(),
1257
						);
1258
1259
						$numTables = 0;
1260
						$prev_join = 0;
1261
						$count = 0;
1262
						$excluded = false;
1263
						foreach ($words['subject_words'] as $subjectWord)
1264
						{
1265
							$numTables++;
1266
							if (in_array($subjectWord, $excludedSubjectWords))
1267
							{
1268
								if (($subject_query['from'] != '{db_prefix}messages AS m') && !$excluded)
1269
								{
1270
									$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1271
									$excluded = true;
1272
								}
1273
								$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)';
1274
								$subject_query['params']['subject_not_' . $count] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord;
1275
1276
								$subject_query['where'][] = '(subj' . $numTables . '.word IS NULL)';
1277
								$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:body_not_' . $count . '}';
1278
								$subject_query['params']['body_not_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($subjectWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $subjectWord), '\\\'') . '[[:>:]]';
1279
							}
1280
							else
1281
							{
1282
								$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)';
1283
								$subject_query['where'][] = 'subj' . $numTables . '.word LIKE {string:subject_like_' . $count . '}';
1284
								$subject_query['params']['subject_like_' . $count++] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord;
1285
								$prev_join = $numTables;
1286
							}
1287
						}
1288
1289
						if (!empty($userQuery))
1290
						{
1291
							if ($subject_query['from'] != '{db_prefix}messages AS m')
1292
							{
1293
								$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1294
							}
1295
							$subject_query['where'][] = '{raw:user_query}';
1296
							$subject_query['params']['user_query'] = $userQuery;
1297
						}
1298 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...
1299
						{
1300
							$subject_query['where'][] = 't.id_topic = {int:topic}';
1301
							$subject_query['params']['topic'] = $search_params['topic'];
1302
						}
1303
						if (!empty($minMsgID))
1304
						{
1305
							$subject_query['where'][] = 't.id_first_msg >= {int:min_msg_id}';
1306
							$subject_query['params']['min_msg_id'] = $minMsgID;
1307
						}
1308
						if (!empty($maxMsgID))
1309
						{
1310
							$subject_query['where'][] = 't.id_last_msg <= {int:max_msg_id}';
1311
							$subject_query['params']['max_msg_id'] = $maxMsgID;
1312
						}
1313
						if (!empty($boardQuery))
1314
						{
1315
							$subject_query['where'][] = 't.id_board {raw:board_query}';
1316
							$subject_query['params']['board_query'] = $boardQuery;
1317
						}
1318
						if (!empty($excludedPhrases))
1319
						{
1320
							if ($subject_query['from'] != '{db_prefix}messages AS m')
1321
							{
1322
								$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1323
							}
1324
							$count = 0;
1325
							foreach ($excludedPhrases as $phrase)
1326
							{
1327
								$subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
1328
								$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
1329
								$subject_query['params']['exclude_phrase_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
1330
							}
1331
						}
1332
						call_integration_hook('integrate_subject_search_query', array(&$subject_query));
1333
1334
						// Nothing to search for?
1335
						if (empty($subject_query['where']))
1336
							continue;
1337
1338
						$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_topics', ($smcFunc['db_support_ignore'] ? ( '
1339
							INSERT IGNORE INTO {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics
1340
								(' . ($createTemporary ? '' : 'id_search, ') . 'id_topic)') : '') . '
1341
							SELECT ' . ($createTemporary ? '' : $_SESSION['search_cache']['id_search'] . ', ') . 't.id_topic
1342
							FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : '
1343
								INNER JOIN ' . implode('
1344
								INNER JOIN ', $subject_query['inner_join'])) . (empty($subject_query['left_join']) ? '' : '
1345
								LEFT JOIN ' . implode('
1346
								LEFT JOIN ', $subject_query['left_join'])) . '
1347
							WHERE ' . implode('
1348
								AND ', $subject_query['where']) . (empty($modSettings['search_max_results']) ? '' : '
1349
							LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults)),
1350
							$subject_query['params']
1351
						);
1352
						// Don't do INSERT IGNORE? Manually fix this up!
1353 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...
1354
						{
1355
							while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1356
							{
1357
								$ind = $createTemporary ? 0 : 1;
1358
								// No duplicates!
1359
								if (isset($inserts[$row[$ind]]))
1360
									continue;
1361
1362
								$inserts[$row[$ind]] = $row;
1363
							}
1364
							$smcFunc['db_free_result']($ignoreRequest);
1365
							$numSubjectResults = count($inserts);
1366
						}
1367
						else
1368
							$numSubjectResults += $smcFunc['db_affected_rows']();
1369
1370
						if (!empty($modSettings['search_max_results']) && $numSubjectResults >= $modSettings['search_max_results'])
1371
							break;
1372
					}
1373
1374
					// Got some non-MySQL data to plonk in?
1375 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...
1376
					{
1377
						$smcFunc['db_insert']('',
1378
							('{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics'),
1379
							$createTemporary ? array('id_topic' => 'int') : array('id_search' => 'int', 'id_topic' => 'int'),
1380
							$inserts,
1381
							$createTemporary ? array('id_topic') : array('id_search', 'id_topic')
1382
						);
1383
					}
1384
1385
					if ($numSubjectResults !== 0)
1386
					{
1387
						$main_query['weights']['subject']['search'] = 'CASE WHEN MAX(lst.id_topic) IS NULL THEN 0 ELSE 1 END';
1388
						$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)';
1389
						if (!$createTemporary)
1390
							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
1391
					}
1392
				}
1393
1394
				$indexedResults = 0;
1395
				// We building an index?
1396
				if ($searchAPI->supportsMethod('indexedWordQuery', $query_params))
1397
				{
1398
					$inserts = array();
1399
					$smcFunc['db_search_query']('drop_tmp_log_search_messages', '
1400
						DROP TABLE IF EXISTS {db_prefix}tmp_log_search_messages',
1401
						array(
1402
						)
1403
					);
1404
1405
					$createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_messages', '
1406
						CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_messages (
1407
							id_msg int NOT NULL default {string:string_zero},
1408
							PRIMARY KEY (id_msg)
1409
						) ENGINE=MEMORY',
1410
						array(
1411
							'string_zero' => '0',
1412
						)
1413
					) !== false;
1414
1415
					// Clear, all clear!
1416
					if (!$createTemporary)
1417
						$smcFunc['db_search_query']('delete_log_search_messages', '
1418
							DELETE FROM {db_prefix}log_search_messages
1419
							WHERE id_search = {int:id_search}',
1420
							array(
1421
								'id_search' => $_SESSION['search_cache']['id_search'],
1422
							)
1423
						);
1424
1425
					foreach ($searchWords as $orIndex => $words)
1426
					{
1427
						// Search for this word, assuming we have some words!
1428
						if (!empty($words['indexed_words']))
1429
						{
1430
							// Variables required for the search.
1431
							$search_data = array(
1432
								'insert_into' => ($createTemporary ? 'tmp_' : '') . 'log_search_messages',
1433
								'no_regexp' => $no_regexp,
1434
								'max_results' => $maxMessageResults,
1435
								'indexed_results' => $indexedResults,
1436
								'params' => array(
1437
									'id_search' => !$createTemporary ? $_SESSION['search_cache']['id_search'] : 0,
1438
									'excluded_words' => $excludedWords,
1439
									'user_query' => !empty($userQuery) ? $userQuery : '',
1440
									'board_query' => !empty($boardQuery) ? $boardQuery : '',
1441
									'topic' => !empty($search_params['topic']) ? $search_params['topic'] : 0,
1442
									'min_msg_id' => !empty($minMsgID) ? $minMsgID : 0,
1443
									'max_msg_id' => !empty($maxMsgID) ? $maxMsgID : 0,
1444
									'excluded_phrases' => !empty($excludedPhrases) ? $excludedPhrases : array(),
1445
									'excluded_index_words' => !empty($excludedIndexWords) ? $excludedIndexWords : array(),
1446
									'excluded_subject_words' => !empty($excludedSubjectWords) ? $excludedSubjectWords : array(),
1447
								),
1448
							);
1449
1450
							$ignoreRequest = $searchAPI->indexedWordQuery($words, $search_data);
1451
1452
							if (!$smcFunc['db_support_ignore'])
1453
							{
1454 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...
1455
								{
1456
									// No duplicates!
1457
									if (isset($inserts[$row[0]]))
1458
										continue;
1459
1460
									$inserts[$row[0]] = $row;
1461
								}
1462
								$smcFunc['db_free_result']($ignoreRequest);
1463
								$indexedResults = count($inserts);
1464
							}
1465
							else
1466
								$indexedResults += $smcFunc['db_affected_rows']();
1467
1468
							if (!empty($maxMessageResults) && $indexedResults >= $maxMessageResults)
1469
								break;
1470
						}
1471
					}
1472
1473
					// More non-MySQL stuff needed?
1474 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...
1475
					{
1476
						$smcFunc['db_insert']('',
1477
							'{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages',
1478
							$createTemporary ? array('id_msg' => 'int') : array('id_msg' => 'int', 'id_search' => 'int'),
1479
							$inserts,
1480
							$createTemporary ? array('id_msg') : array('id_msg', 'id_search')
1481
						);
1482
					}
1483
1484
					if (empty($indexedResults) && empty($numSubjectResults) && !empty($modSettings['search_force_index']))
1485
					{
1486
						$context['search_errors']['query_not_specific_enough'] = true;
1487
						$_REQUEST['params'] = $context['params'];
1488
						return PlushSearch1();
1489
					}
1490
					elseif (!empty($indexedResults))
1491
					{
1492
						$main_query['inner_join'][] = '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages AS lsm ON (lsm.id_msg = m.id_msg)';
1493
						if (!$createTemporary)
1494
						{
1495
							$main_query['where'][] = 'lsm.id_search = {int:id_search}';
1496
							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
1497
						}
1498
					}
1499
				}
1500
1501
				// Not using an index? All conditions have to be carried over.
1502
				else
1503
				{
1504
					$orWhere = array();
1505
					$count = 0;
1506
					foreach ($searchWords as $orIndex => $words)
1507
					{
1508
						$where = array();
1509
						foreach ($words['all_words'] as $regularWord)
1510
						{
1511
							$where[] = 'm.body' . (in_array($regularWord, $excludedWords) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
1512
							if (in_array($regularWord, $excludedWords))
1513
								$where[] = 'm.subject NOT' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
1514
							$main_query['parameters']['all_word_body_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]';
1515
						}
1516 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...
1517
							$orWhere[] = count($where) > 1 ? '(' . implode(' AND ', $where) . ')' : $where[0];
1518
					}
1519 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...
1520
						$main_query['where'][] = count($orWhere) > 1 ? '(' . implode(' OR ', $orWhere) . ')' : $orWhere[0];
1521
1522
					if (!empty($userQuery))
1523
					{
1524
						$main_query['where'][] = '{raw:user_query}';
1525
						$main_query['parameters']['user_query'] = $userQuery;
1526
					}
1527 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...
1528
					{
1529
						$main_query['where'][] = 'm.id_topic = {int:topic}';
1530
						$main_query['parameters']['topic'] = $search_params['topic'];
1531
					}
1532
					if (!empty($minMsgID))
1533
					{
1534
						$main_query['where'][] = 'm.id_msg >= {int:min_msg_id}';
1535
						$main_query['parameters']['min_msg_id'] = $minMsgID;
1536
					}
1537
					if (!empty($maxMsgID))
1538
					{
1539
						$main_query['where'][] = 'm.id_msg <= {int:max_msg_id}';
1540
						$main_query['parameters']['max_msg_id'] = $maxMsgID;
1541
					}
1542
					if (!empty($boardQuery))
1543
					{
1544
						$main_query['where'][] = 'm.id_board {raw:board_query}';
1545
						$main_query['parameters']['board_query'] = $boardQuery;
1546
					}
1547
				}
1548
				call_integration_hook('integrate_main_search_query', array(&$main_query));
1549
1550
				// Did we either get some indexed results, or otherwise did not do an indexed query?
1551
				if (!empty($indexedResults) || !$searchAPI->supportsMethod('indexedWordQuery', $query_params))
1552
				{
1553
					$relevance = '1000 * (';
1554
					$new_weight_total = 0;
1555
					foreach ($main_query['weights'] as $type => $value)
1556
					{
1557
						$relevance .= $weight[$type];
1558
						if (!empty($value['search']))
1559
							$relevance .= ' * ' . $value['search'];
1560
						$relevance .= ' + ';
1561
						$new_weight_total += $weight[$type];
1562
					}
1563
					$main_query['select']['relevance'] = substr($relevance, 0, -3) . ') / ' . $new_weight_total . ' AS relevance';
1564
1565
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_no_index', ($smcFunc['db_support_ignore'] ? ( '
1566
						INSERT IGNORE INTO ' . '{db_prefix}log_search_results
1567
							(' . implode(', ', array_keys($main_query['select'])) . ')') : '') . '
1568
						SELECT
1569
							' . implode(',
1570
							', $main_query['select']) . '
1571
						FROM ' . $main_query['from'] . (empty($main_query['inner_join']) ? '' : '
1572
							INNER JOIN ' . implode('
1573
							INNER JOIN ', $main_query['inner_join'])) . (empty($main_query['left_join']) ? '' : '
1574
							LEFT JOIN ' . implode('
1575
							LEFT JOIN ', $main_query['left_join'])) . (!empty($main_query['where']) ? '
1576
						WHERE ' : '') . implode('
1577
							AND ', $main_query['where']) . (empty($main_query['group_by']) ? '' : '
1578
						GROUP BY ' . implode(', ', $main_query['group_by'])) . (empty($modSettings['search_max_results']) ? '' : '
1579
						LIMIT ' . $modSettings['search_max_results']),
1580
						$main_query['parameters']
1581
					);
1582
1583
					// We love to handle non-good databases that don't support our ignore!
1584
					if (!$smcFunc['db_support_ignore'])
1585
					{
1586
						$inserts = array();
1587
						while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1588
						{
1589
							// No duplicates!
1590
							if (isset($inserts[$row[2]]))
1591
								continue;
1592
1593
							foreach ($row as $key => $value)
1594
								$inserts[$row[2]][] = (int) $row[$key];
1595
						}
1596
						$smcFunc['db_free_result']($ignoreRequest);
1597
1598
						// Now put them in!
1599
						if (!empty($inserts))
1600
						{
1601
							$query_columns = array();
1602
							foreach ($main_query['select'] as $k => $v)
1603
								$query_columns[$k] = 'int';
1604
1605
							$smcFunc['db_insert']('',
1606
								'{db_prefix}log_search_results',
1607
								$query_columns,
1608
								$inserts,
1609
								array('id_search', 'id_topic')
1610
							);
1611
						}
1612
						$_SESSION['search_cache']['num_results'] += count($inserts);
1613
					}
1614
					else
1615
						$_SESSION['search_cache']['num_results'] = $smcFunc['db_affected_rows']();
1616
				}
1617
1618
				// Insert subject-only matches.
1619
				if ($_SESSION['search_cache']['num_results'] < $modSettings['search_max_results'] && $numSubjectResults !== 0)
1620
				{
1621
					$relevance = '1000 * (';
1622 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...
1623
						if (isset($value['results']))
1624
						{
1625
							$relevance .= $weight[$type];
1626
							if (!empty($value['results']))
1627
								$relevance .= ' * ' . $value['results'];
1628
							$relevance .= ' + ';
1629
						}
1630
					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
1631
1632
					$usedIDs = array_flip(empty($inserts) ? array() : array_keys($inserts));
1633
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_sub_only', ($smcFunc['db_support_ignore'] ? ( '
1634
						INSERT IGNORE INTO {db_prefix}log_search_results
1635
							(id_search, id_topic, relevance, id_msg, num_matches)') : '') . '
1636
						SELECT
1637
							{int:id_search},
1638
							t.id_topic,
1639
							' . $relevance . ',
1640
							t.id_first_msg,
1641
							1
1642
						FROM {db_prefix}topics AS t
1643
							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...
1644
						. ($createTemporary ? '' : ' WHERE lst.id_search = {int:id_search}')
1645
						. (empty($modSettings['search_max_results']) ? '' : '
1646
						LIMIT ' . ($modSettings['search_max_results'] - $_SESSION['search_cache']['num_results'])),
1647
						array(
1648
							'id_search' => $_SESSION['search_cache']['id_search'],
1649
							'min_msg' => $minMsg,
1650
							'recent_message' => $recentMsg,
1651
							'huge_topic_posts' => $humungousTopicPosts,
1652
						)
1653
					);
1654
					// Once again need to do the inserts if the database don't support ignore!
1655
					if (!$smcFunc['db_support_ignore'])
1656
					{
1657
						$inserts = array();
1658 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...
1659
						{
1660
							// No duplicates!
1661
							if (isset($usedIDs[$row[1]]))
1662
								continue;
1663
1664
							$usedIDs[$row[1]] = true;
1665
							$inserts[] = $row;
1666
						}
1667
						$smcFunc['db_free_result']($ignoreRequest);
1668
1669
						// Now put them in!
1670 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...
1671
						{
1672
							$smcFunc['db_insert']('',
1673
								'{db_prefix}log_search_results',
1674
								array('id_search' => 'int', 'id_topic' => 'int', 'relevance' => 'float', 'id_msg' => 'int', 'num_matches' => 'int'),
1675
								$inserts,
1676
								array('id_search', 'id_topic')
1677
							);
1678
						}
1679
						$_SESSION['search_cache']['num_results'] += count($inserts);
1680
					}
1681
					else
1682
						$_SESSION['search_cache']['num_results'] += $smcFunc['db_affected_rows']();
1683
				}
1684
				elseif ($_SESSION['search_cache']['num_results'] == -1)
1685
					$_SESSION['search_cache']['num_results'] = 0;
1686
			}
1687
		}
1688
1689
		// *** Retrieve the results to be shown on the page
1690
		$participants = array();
1691
		$request = $smcFunc['db_search_query']('', '
1692
			SELECT ' . (empty($search_params['topic']) ? 'lsr.id_topic' : $search_params['topic'] . ' AS id_topic') . ', lsr.id_msg, lsr.relevance, lsr.num_matches
1693
			FROM {db_prefix}log_search_results AS lsr' . ($search_params['sort'] == 'num_replies' ? '
1694
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = lsr.id_topic)' : '') . '
1695
			WHERE lsr.id_search = {int:id_search}
1696
			ORDER BY {raw:sort} {raw:sort_dir}
1697
			LIMIT {int:start}, {int:max}',
1698
			array(
1699
				'id_search' => $_SESSION['search_cache']['id_search'],
1700
				'sort' => $search_params['sort'],
1701
				'sort_dir' => $search_params['sort_dir'],
1702
				'start' => $_REQUEST['start'],
1703
				'max' => $modSettings['search_results_per_page'],
1704
			)
1705
		);
1706
		while ($row = $smcFunc['db_fetch_assoc']($request))
1707
		{
1708
			$context['topics'][$row['id_msg']] = array(
1709
				'relevance' => round($row['relevance'] / 10, 1) . '%',
1710
				'num_matches' => $row['num_matches'],
1711
				'matches' => array(),
1712
			);
1713
			// By default they didn't participate in the topic!
1714
			$participants[$row['id_topic']] = false;
1715
		}
1716
		$smcFunc['db_free_result']($request);
1717
1718
		$num_results = $_SESSION['search_cache']['num_results'];
1719
	}
1720
1721
	if (!empty($context['topics']))
1722
	{
1723
		// Create an array for the permissions.
1724
		$boards_can = boardsAllowedTo(array('post_reply_own', 'post_reply_any'), true, false);
1725
1726
		// How's about some quick moderation?
1727
		if (!empty($options['display_quick_mod']))
1728
		{
1729
			$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));
1730
1731
			$context['can_lock'] = in_array(0, $boards_can['lock_any']);
1732
			$context['can_sticky'] = in_array(0, $boards_can['make_sticky']);
1733
			$context['can_move'] = in_array(0, $boards_can['move_any']);
1734
			$context['can_remove'] = in_array(0, $boards_can['remove_any']);
1735
			$context['can_merge'] = in_array(0, $boards_can['merge_any']);
1736
		}
1737
1738
		// What messages are we using?
1739
		$msg_list = array_keys($context['topics']);
1740
1741
		// Load the posters...
1742
		$request = $smcFunc['db_query']('', '
1743
			SELECT id_member
1744
			FROM {db_prefix}messages
1745
			WHERE id_member != {int:no_member}
1746
				AND id_msg IN ({array_int:message_list})
1747
			LIMIT {int:limit}',
1748
			array(
1749
				'message_list' => $msg_list,
1750
				'no_member' => 0,
1751
				'limit' => count($context['topics']),
1752
			)
1753
		);
1754
		$posters = array();
1755
		while ($row = $smcFunc['db_fetch_assoc']($request))
1756
			$posters[] = $row['id_member'];
1757
		$smcFunc['db_free_result']($request);
1758
1759
		call_integration_hook('integrate_search_message_list', array(&$msg_list, &$posters));
1760
1761
		if (!empty($posters))
1762
			loadMemberData(array_unique($posters));
1763
1764
		// PG optimization to evade FIND_IN_SET
1765
		if ($smcFunc['db_title'] == 'PostgreSQL')
1766
		{
1767
			$orderJoin = '';
1768
			$msg_list_size = count($msg_list);
1769
			for ($i = 0; $i < $msg_list_size; $i++)
1770
			{
1771
				if ($i > 0)
1772
					$orderJoin .= ',';
1773
				$orderJoin .= '(' . $i . ',' . $msg_list[$i] . ')';
1774
			}
1775
1776
			$orderJoin = 'JOIN ( VALUES ' . $orderJoin . ') as sort(ordering, id) on m.id_msg = sort.id';
1777
		}
1778
1779
		// Get the messages out for the callback - select enough that it can be made to look just like Display.
1780
		$messages_request = $smcFunc['db_query']('', '
1781
			SELECT
1782
				m.id_msg, m.subject, m.poster_name, m.poster_email, m.poster_time, m.id_member,
1783
				m.icon, m.poster_ip, m.body, m.smileys_enabled, m.modified_time, m.modified_name,
1784
				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,
1785
				first_mem.id_member AS first_member_id, COALESCE(first_mem.real_name, first_m.poster_name) AS first_member_name,
1786
				last_m.id_msg AS last_msg, last_m.poster_time AS last_poster_time, last_mem.id_member AS last_member_id,
1787
				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,
1788
				t.id_topic, t.is_sticky, t.locked, t.id_poll, t.num_replies, t.num_views,
1789
				b.id_board, b.name AS board_name, c.id_cat, c.name AS cat_name
1790
			FROM {db_prefix}messages AS m
1791
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1792
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1793
				INNER JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1794
				INNER JOIN {db_prefix}messages AS first_m ON (first_m.id_msg = t.id_first_msg)
1795
				INNER JOIN {db_prefix}messages AS last_m ON (last_m.id_msg = t.id_last_msg)
1796
				LEFT JOIN {db_prefix}members AS first_mem ON (first_mem.id_member = first_m.id_member)
1797
				LEFT JOIN {db_prefix}members AS last_mem ON (last_mem.id_member = first_m.id_member)
1798
				' . (isset($orderJoin) ? $orderJoin : '') . '
1799
			WHERE m.id_msg IN ({array_int:message_list})' . ($modSettings['postmod_active'] ? '
1800
				AND m.approved = {int:is_approved}' : '') . '
1801
			ORDER BY ' . (isset($orderJoin) ? ' sort.ordering' : ' FIND_IN_SET(m.id_msg, {string:message_list_in_set})') . '
1802
			LIMIT {int:limit}',
1803
			array(
1804
				'message_list' => $msg_list,
1805
				'is_approved' => 1,
1806
				'message_list_in_set' => implode(',', $msg_list),
1807
				'limit' => count($context['topics']),
1808
			)
1809
		);
1810
1811
		// If there are no results that means the things in the cache got deleted, so pretend we have no topics anymore.
1812
		if ($smcFunc['db_num_rows']($messages_request) == 0)
1813
			$context['topics'] = array();
1814
1815
		// If we want to know who participated in what then load this now.
1816
		if (!empty($modSettings['enableParticipation']) && !$user_info['is_guest'])
1817
		{
1818
			$result = $smcFunc['db_query']('', '
1819
				SELECT id_topic
1820
				FROM {db_prefix}messages
1821
				WHERE id_topic IN ({array_int:topic_list})
1822
					AND id_member = {int:current_member}
1823
				GROUP BY id_topic
1824
				LIMIT {int:limit}',
1825
				array(
1826
					'current_member' => $user_info['id'],
1827
					'topic_list' => array_keys($participants),
1828
					'limit' => count($participants),
1829
				)
1830
			);
1831
			while ($row = $smcFunc['db_fetch_assoc']($result))
1832
				$participants[$row['id_topic']] = true;
1833
			$smcFunc['db_free_result']($result);
1834
		}
1835
	}
1836
1837
	// Now that we know how many results to expect we can start calculating the page numbers.
1838
	$context['page_index'] = constructPageIndex($scripturl . '?action=search2;params=' . $context['params'], $_REQUEST['start'], $num_results, $modSettings['search_results_per_page'], false);
1839
1840
	// Consider the search complete!
1841
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1842
		cache_put_data('search_start:' . ($user_info['is_guest'] ? $user_info['ip'] : $user_info['id']), null, 90);
1843
1844
	$context['key_words'] = &$searchArray;
1845
1846
	// Setup the default topic icons... for checking they exist and the like!
1847
	$context['icon_sources'] = array();
1848
	foreach ($context['stable_icons'] as $icon)
1849
		$context['icon_sources'][$icon] = 'images_url';
1850
1851
	$context['sub_template'] = 'results';
1852
	$context['page_title'] = $txt['search_results'];
1853
	$context['get_topics'] = 'prepareSearchContext';
1854
	$context['can_restore_perm'] = allowedTo('move_any') && !empty($modSettings['recycle_enable']);
1855
	$context['can_restore'] = false; // We won't know until we handle the context later whether we can actually restore...
1856
1857
	$context['jump_to'] = array(
1858
		'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
1859
		'board_name' => addslashes(un_htmlspecialchars($txt['select_destination'])),
1860
	);
1861
}
1862
1863
/**
1864
 * Callback to return messages - saves memory.
1865
 *
1866
 * What it does:
1867
 * - callback function for the results sub template.
1868
 * - loads the necessary contextual data to show a search result.
1869
 *
1870
 * @param bool $reset Whether to reset the counter
1871
 * @return array An array of contextual info related to this search
1872
 */
1873
function prepareSearchContext($reset = false)
1874
{
1875
	global $txt, $modSettings, $scripturl, $user_info;
1876
	global $memberContext, $context, $settings, $options, $messages_request;
1877
	global $boards_can, $participants, $smcFunc;
1878
	static $recycle_board = null;
1879
1880
	if ($recycle_board === null)
1881
		$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
1882
1883
	// Remember which message this is.  (ie. reply #83)
1884
	static $counter = null;
1885
	if ($counter == null || $reset)
1886
		$counter = $_REQUEST['start'] + 1;
1887
1888
	// If the query returned false, bail.
1889
	if ($messages_request == false)
1890
		return false;
1891
1892
	// Start from the beginning...
1893
	if ($reset)
1894
		return @$smcFunc['db_data_seek']($messages_request, 0);
1895
1896
	// Attempt to get the next message.
1897
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1898
	if (!$message)
1899
		return false;
1900
1901
	// Can't have an empty subject can we?
1902
	$message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
1903
1904
	$message['first_subject'] = $message['first_subject'] != '' ? $message['first_subject'] : $txt['no_subject'];
1905
	$message['last_subject'] = $message['last_subject'] != '' ? $message['last_subject'] : $txt['no_subject'];
1906
1907
	// If it couldn't load, or the user was a guest.... someday may be done with a guest table.
1908
	if (!loadMemberContext($message['id_member']))
1909
	{
1910
		// Notice this information isn't used anywhere else.... *cough guest table cough*.
1911
		$memberContext[$message['id_member']]['name'] = $message['poster_name'];
1912
		$memberContext[$message['id_member']]['id'] = 0;
1913
		$memberContext[$message['id_member']]['group'] = $txt['guest_title'];
1914
		$memberContext[$message['id_member']]['link'] = $message['poster_name'];
1915
		$memberContext[$message['id_member']]['email'] = $message['poster_email'];
1916
	}
1917
	$memberContext[$message['id_member']]['ip'] = inet_dtop($message['poster_ip']);
1918
1919
	// Do the censor thang...
1920
	censorText($message['body']);
1921
	censorText($message['subject']);
1922
1923
	censorText($message['first_subject']);
1924
	censorText($message['last_subject']);
1925
1926
	// Shorten this message if necessary.
1927
	if ($context['compact'])
1928
	{
1929
		// Set the number of characters before and after the searched keyword.
1930
		$charLimit = 50;
1931
1932
		$message['body'] = strtr($message['body'], array("\n" => ' ', '<br>' => "\n"));
1933
		$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1934
		$message['body'] = strip_tags(strtr($message['body'], array('</div>' => '<br>', '</li>' => '<br>')), '<br>');
1935
1936
		if ($smcFunc['strlen']($message['body']) > $charLimit)
1937
		{
1938
			if (empty($context['key_words']))
1939
				$message['body'] = $smcFunc['substr']($message['body'], 0, $charLimit) . '<strong>...</strong>';
1940
			else
1941
			{
1942
				$matchString = '';
1943
				$force_partial_word = false;
1944
				foreach ($context['key_words'] as $keyword)
1945
				{
1946
					$keyword = un_htmlspecialchars($keyword);
1947
					$keyword = preg_replace_callback('~(&amp;#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', strtr($keyword, array('\\\'' => '\'', '&' => '&amp;')));
1948
1949
					if (preg_match('~[\'\.,/@%&;:(){}\[\]_\-+\\\\]$~', $keyword) != 0 || preg_match('~^[\'\.,/@%&;:(){}\[\]_\-+\\\\]~', $keyword) != 0)
1950
						$force_partial_word = true;
1951
					$matchString .= strtr(preg_quote($keyword, '/'), array('\*' => '.+?')) . '|';
1952
				}
1953
				$matchString = un_htmlspecialchars(substr($matchString, 0, -1));
1954
1955
				$message['body'] = un_htmlspecialchars(strtr($message['body'], array('&nbsp;' => ' ', '<br>' => "\n", '&#91;' => '[', '&#93;' => ']', '&#58;' => ':', '&#64;' => '@')));
1956
1957
				if (empty($modSettings['search_method']) || $force_partial_word)
1958
					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);
1959
				else
1960
					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);
1961
1962
				$message['body'] = '';
1963
				foreach ($matches[0] as $index => $match)
1964
				{
1965
					$match = strtr($smcFunc['htmlspecialchars']($match, ENT_QUOTES), array("\n" => '&nbsp;'));
1966
					$message['body'] .= '<strong>......</strong>&nbsp;' . $match . '&nbsp;<strong>......</strong>';
1967
				}
1968
			}
1969
1970
			// Re-fix the international characters.
1971
			$message['body'] = preg_replace_callback('~(&amp;#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $message['body']);
1972
		}
1973
	}
1974
	else
1975
	{
1976
		// Run BBC interpreter on the message.
1977
		$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1978
	}
1979
1980
	// Make sure we don't end up with a practically empty message body.
1981
	$message['body'] = preg_replace('~^(?:&nbsp;)+$~', '', $message['body']);
1982
1983
	if (!empty($recycle_board) && $message['id_board'] == $recycle_board)
1984
	{
1985
		$message['first_icon'] = 'recycled';
1986
		$message['last_icon'] = 'recycled';
1987
		$message['icon'] = 'recycled';
1988
	}
1989
1990
	// Sadly, we need to check the icon ain't broke.
1991
	if (!empty($modSettings['messageIconChecks_enable']))
1992
	{
1993 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...
1994
			$context['icon_sources'][$message['first_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['first_icon'] . '.png') ? 'images_url' : 'default_images_url';
1995 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...
1996
			$context['icon_sources'][$message['last_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['last_icon'] . '.png') ? 'images_url' : 'default_images_url';
1997 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...
1998
			$context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.png') ? 'images_url' : 'default_images_url';
1999
	}
2000
	else
2001
	{
2002 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...
2003
			$context['icon_sources'][$message['first_icon']] = 'images_url';
2004 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...
2005
			$context['icon_sources'][$message['last_icon']] = 'images_url';
2006 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...
2007
			$context['icon_sources'][$message['icon']] = 'images_url';
2008
	}
2009
2010
	// Do we have quote tag enabled?
2011
	$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
2012
2013
	// Reference the main color class.
2014
	$colorClass = 'windowbg';
2015
2016
	// Sticky topics should get a different color, too.
2017
	if ($message['is_sticky'])
2018
		$colorClass .= ' sticky';
2019
2020
	// Locked topics get special treatment as well.
2021
	if ($message['locked'])
2022
		$colorClass .= ' locked';
2023
2024
	$output = array_merge($context['topics'][$message['id_msg']], array(
2025
		'id' => $message['id_topic'],
2026
		'is_sticky' => !empty($message['is_sticky']),
2027
		'is_locked' => !empty($message['locked']),
2028
		'css_class' => $colorClass,
2029
		'is_poll' => $modSettings['pollMode'] == '1' && $message['id_poll'] > 0,
2030
		'posted_in' => !empty($participants[$message['id_topic']]),
2031
		'views' => $message['num_views'],
2032
		'replies' => $message['num_replies'],
2033
		'can_reply' => in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any']),
2034
		'can_quote' => (in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any'])) && $quote_enabled,
2035
		'first_post' => array(
2036
			'id' => $message['first_msg'],
2037
			'time' => timeformat($message['first_poster_time']),
2038
			'timestamp' => forum_time(true, $message['first_poster_time']),
2039
			'subject' => $message['first_subject'],
2040
			'href' => $scripturl . '?topic=' . $message['id_topic'] . '.0',
2041
			'link' => '<a href="' . $scripturl . '?topic=' . $message['id_topic'] . '.0">' . $message['first_subject'] . '</a>',
2042
			'icon' => $message['first_icon'],
2043
			'icon_url' => $settings[$context['icon_sources'][$message['first_icon']]] . '/post/' . $message['first_icon'] . '.png',
2044
			'member' => array(
2045
				'id' => $message['first_member_id'],
2046
				'name' => $message['first_member_name'],
2047
				'href' => !empty($message['first_member_id']) ? $scripturl . '?action=profile;u=' . $message['first_member_id'] : '',
2048
				'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']
2049
			)
2050
		),
2051
		'last_post' => array(
2052
			'id' => $message['last_msg'],
2053
			'time' => timeformat($message['last_poster_time']),
2054
			'timestamp' => forum_time(true, $message['last_poster_time']),
2055
			'subject' => $message['last_subject'],
2056
			'href' => $scripturl . '?topic=' . $message['id_topic'] . ($message['num_replies'] == 0 ? '.0' : '.msg' . $message['last_msg']) . '#msg' . $message['last_msg'],
2057
			'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>',
2058
			'icon' => $message['last_icon'],
2059
			'icon_url' => $settings[$context['icon_sources'][$message['last_icon']]] . '/post/' . $message['last_icon'] . '.png',
2060
			'member' => array(
2061
				'id' => $message['last_member_id'],
2062
				'name' => $message['last_member_name'],
2063
				'href' => !empty($message['last_member_id']) ? $scripturl . '?action=profile;u=' . $message['last_member_id'] : '',
2064
				'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']
2065
			)
2066
		),
2067
		'board' => array(
2068
			'id' => $message['id_board'],
2069
			'name' => $message['board_name'],
2070
			'href' => $scripturl . '?board=' . $message['id_board'] . '.0',
2071
			'link' => '<a href="' . $scripturl . '?board=' . $message['id_board'] . '.0">' . $message['board_name'] . '</a>'
2072
		),
2073
		'category' => array(
2074
			'id' => $message['id_cat'],
2075
			'name' => $message['cat_name'],
2076
			'href' => $scripturl . '#c' . $message['id_cat'],
2077
			'link' => '<a href="' . $scripturl . '#c' . $message['id_cat'] . '">' . $message['cat_name'] . '</a>'
2078
		)
2079
	));
2080
2081
	$body_highlighted = $message['body'];
2082
	$subject_highlighted = $message['subject'];
2083
2084
	if (!empty($options['display_quick_mod']))
2085
	{
2086
		$started = $output['first_post']['member']['id'] == $user_info['id'];
2087
2088
		$output['quick_mod'] = array(
2089
			'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']))),
2090
			'sticky' => (in_array(0, $boards_can['make_sticky']) || in_array($output['board']['id'], $boards_can['make_sticky'])),
2091
			'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']))),
2092
			'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']))),
2093
			'restore' => $context['can_restore_perm'] && ($modSettings['recycle_board'] == $output['board']['id']),
2094
		);
2095
2096
		$context['can_lock'] |= $output['quick_mod']['lock'];
2097
		$context['can_sticky'] |= $output['quick_mod']['sticky'];
2098
		$context['can_move'] |= $output['quick_mod']['move'];
2099
		$context['can_remove'] |= $output['quick_mod']['remove'];
2100
		$context['can_merge'] |= in_array($output['board']['id'], $boards_can['merge_any']);
2101
		$context['can_restore'] |= $output['quick_mod']['restore'];
2102
		$context['can_markread'] = $context['user']['is_logged'];
2103
2104
		$context['qmod_actions'] = array('remove', 'lock', 'sticky', 'move', 'merge', 'restore', 'markread');
2105
		call_integration_hook('integrate_quick_mod_actions_search');
2106
	}
2107
2108
	foreach ($context['key_words'] as $query)
2109
	{
2110
		// Fix the international characters in the keyword too.
2111
		$query = un_htmlspecialchars($query);
2112
		$query = trim($query, "\*+");
2113
		$query = strtr($smcFunc['htmlspecialchars']($query), array('\\\'' => '\''));
2114
2115
		$body_highlighted = preg_replace_callback('/((<[^>]*)|' . preg_quote(strtr($query, array('\'' => '&#039;')), '/') . ')/i' . ($context['utf8'] ? 'u' : ''), function ($m)
2116
		{
2117
			return isset($m[2]) && "$m[2]" == "$m[1]" ? stripslashes("$m[1]") : "<strong class=\"highlight\">$m[1]</strong>";
2118
		}, $body_highlighted);
2119
		$subject_highlighted = preg_replace('/(' . preg_quote($query, '/') . ')/i' . ($context['utf8'] ? 'u' : ''), '<strong class="highlight">$1</strong>', $subject_highlighted);
2120
	}
2121
2122
	$output['matches'][] = array(
2123
		'id' => $message['id_msg'],
2124
		'attachment' => array(),
2125
		'member' => &$memberContext[$message['id_member']],
2126
		'icon' => $message['icon'],
2127
		'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.png',
2128
		'subject' => $message['subject'],
2129
		'subject_highlighted' => $subject_highlighted,
2130
		'time' => timeformat($message['poster_time']),
2131
		'timestamp' => forum_time(true, $message['poster_time']),
2132
		'counter' => $counter,
2133
		'modified' => array(
2134
			'time' => timeformat($message['modified_time']),
2135
			'timestamp' => forum_time(true, $message['modified_time']),
2136
			'name' => $message['modified_name']
2137
		),
2138
		'body' => $message['body'],
2139
		'body_highlighted' => $body_highlighted,
2140
		'start' => 'msg' . $message['id_msg']
2141
	);
2142
	$counter++;
2143
2144
	call_integration_hook('integrate_search_message_context', array(&$output, &$message, $counter));
2145
2146
	return $output;
2147
}
2148
2149
/**
2150
 * Creates a search API and returns the object.
2151
 *
2152
 * @return search_api_interface An instance of the search API interface
2153
 */
2154
function findSearchAPI()
2155
{
2156
	global $sourcedir, $modSettings, $search_versions, $searchAPI, $txt;
2157
2158
	require_once($sourcedir . '/Subs-Package.php');
2159
	require_once($sourcedir . '/Class-SearchAPI.php');
2160
2161
	// Search has a special database set.
2162
	db_extend('search');
2163
2164
	// Load up the search API we are going to use.
2165
	$modSettings['search_index'] = empty($modSettings['search_index']) ? 'standard' : $modSettings['search_index'];
2166
	if (!file_exists($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php'))
2167
		fatal_lang_error('search_api_missing');
2168
	require_once($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php');
2169
2170
	// Create an instance of the search API and check it is valid for this version of SMF.
2171
	$search_class_name = $modSettings['search_index'] . '_search';
2172
	$searchAPI = new $search_class_name();
2173
2174
	// An invalid Search API.
2175
	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...
2176
	{
2177
		// Log the error.
2178
		loadLanguage('Errors');
2179
		log_error(sprintf($txt['search_api_not_compatible'], 'SearchAPI-' . ucwords($modSettings['search_index']) . '.php'), 'critical');
2180
2181
		require_once($sourcedir . '/SearchAPI-Standard.php');
2182
		$searchAPI = new standard_search();
2183
	}
2184
2185
	return $searchAPI;
2186
}
2187
2188
/**
2189
 * This function compares the length of two strings plus a little.
2190
 * What it does:
2191
 * - callback function for usort used to sort the fulltext results.
2192
 * - passes sorting duty to the current API.
2193
 *
2194
 * @param string $a
2195
 * @param string $b
2196
 * @return int
2197
 */
2198
function searchSort($a, $b)
2199
{
2200
	global $searchAPI;
2201
2202
	return $searchAPI->searchSort($a, $b);
2203
}
2204
2205
?>
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...