Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
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'])
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'])
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)
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']))
96
		$context['search_params']['search'] = un_htmlspecialchars($_REQUEST['search']);
97
98 View Code Duplication
	if (isset($context['search_params']['search']))
99
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
100 View Code Duplication
	if (isset($context['search_params']['userspec']))
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']))
105
		$context['search_params']['minage'] = (int) $context['search_params']['minage'];
106 View Code Duplication
	if (!empty($context['search_params']['maxage']))
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)
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++)
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'])
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')
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)
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']))
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))
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))
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))
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'] != '*'))
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++)
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))
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']))
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?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
619
	 * @todo Maybe only blacklist if they are the only word, or "any" is used?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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)
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
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
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']))
901
		$context['search_params']['search'] = $smcFunc['htmlspecialchars']($context['search_params']['search']);
902 View Code Duplication
	if (isset($context['search_params']['userspec']))
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']))
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), '\\\'') . '[[:>:]]';
0 ignored issues
show
Coding Style introduced by
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
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)
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'])
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))
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']))
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
							'db_error_skip' => true,
1228
						)
1229
					);
1230
					$createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_topics', '
1231
						CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_topics (
1232
							id_topic mediumint(8) unsigned NOT NULL default {string:string_zero},
1233
							PRIMARY KEY (id_topic)
1234
						) ENGINE=MEMORY',
1235
						array(
1236
							'string_zero' => '0',
1237
							'db_error_skip' => true,
1238
						)
1239
					) !== false;
1240
1241
					// Clean up some previous cache.
1242
					if (!$createTemporary)
1243
						$smcFunc['db_search_query']('delete_log_search_topics', '
1244
							DELETE FROM {db_prefix}log_search_topics
1245
							WHERE id_search = {int:search_id}',
1246
							array(
1247
								'search_id' => $_SESSION['search_cache']['id_search'],
1248
							)
1249
						);
1250
1251
					foreach ($searchWords as $orIndex => $words)
1252
					{
1253
						$subject_query = array(
1254
							'from' => '{db_prefix}topics AS t',
1255
							'inner_join' => array(),
1256
							'left_join' => array(),
1257
							'where' => array(),
1258
							'params' => array(),
1259
						);
1260
1261
						$numTables = 0;
1262
						$prev_join = 0;
1263
						$count = 0;
1264
						$excluded = false;
1265
						foreach ($words['subject_words'] as $subjectWord)
1266
						{
1267
							$numTables++;
1268
							if (in_array($subjectWord, $excludedSubjectWords))
1269
							{
1270
								if (($subject_query['from'] != '{db_prefix}messages AS m') && !$excluded)
1271
								{
1272
									$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1273
									$excluded = true;
1274
								}
1275
								$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)';
1276
								$subject_query['params']['subject_not_' . $count] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord;
1277
1278
								$subject_query['where'][] = '(subj' . $numTables . '.word IS NULL)';
1279
								$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:body_not_' . $count . '}';
1280
								$subject_query['params']['body_not_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($subjectWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $subjectWord), '\\\'') . '[[:>:]]';
0 ignored issues
show
Coding Style introduced by
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
1281
							}
1282
							else
1283
							{
1284
								$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)';
1285
								$subject_query['where'][] = 'subj' . $numTables . '.word LIKE {string:subject_like_' . $count . '}';
1286
								$subject_query['params']['subject_like_' . $count++] = empty($modSettings['search_match_words']) ? '%' . $subjectWord . '%' : $subjectWord;
0 ignored issues
show
Coding Style introduced by
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
1287
								$prev_join = $numTables;
1288
							}
1289
						}
1290
1291
						if (!empty($userQuery))
1292
						{
1293
							if ($subject_query['from'] != '{db_prefix}messages AS m')
1294
							{
1295
								$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1296
							}
1297
							$subject_query['where'][] = '{raw:user_query}';
1298
							$subject_query['params']['user_query'] = $userQuery;
1299
						}
1300 View Code Duplication
						if (!empty($search_params['topic']))
1301
						{
1302
							$subject_query['where'][] = 't.id_topic = {int:topic}';
1303
							$subject_query['params']['topic'] = $search_params['topic'];
1304
						}
1305
						if (!empty($minMsgID))
1306
						{
1307
							$subject_query['where'][] = 't.id_first_msg >= {int:min_msg_id}';
1308
							$subject_query['params']['min_msg_id'] = $minMsgID;
1309
						}
1310
						if (!empty($maxMsgID))
1311
						{
1312
							$subject_query['where'][] = 't.id_last_msg <= {int:max_msg_id}';
1313
							$subject_query['params']['max_msg_id'] = $maxMsgID;
1314
						}
1315
						if (!empty($boardQuery))
1316
						{
1317
							$subject_query['where'][] = 't.id_board {raw:board_query}';
1318
							$subject_query['params']['board_query'] = $boardQuery;
1319
						}
1320
						if (!empty($excludedPhrases))
1321
						{
1322
							if ($subject_query['from'] != '{db_prefix}messages AS m')
1323
							{
1324
								$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
1325
							}
1326
							$count = 0;
1327
							foreach ($excludedPhrases as $phrase)
1328
							{
1329
								$subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
1330
								$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
1331
								$subject_query['params']['exclude_phrase_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
0 ignored issues
show
Coding Style introduced by
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
1332
							}
1333
						}
1334
						call_integration_hook('integrate_subject_search_query', array(&$subject_query));
1335
1336
						// Nothing to search for?
1337
						if (empty($subject_query['where']))
1338
							continue;
1339
1340
						$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_topics', ($smcFunc['db_support_ignore'] ? ( '
1341
							INSERT IGNORE INTO {db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics
1342
								(' . ($createTemporary ? '' : 'id_search, ') . 'id_topic)') : '') . '
1343
							SELECT ' . ($createTemporary ? '' : $_SESSION['search_cache']['id_search'] . ', ') . 't.id_topic
1344
							FROM ' . $subject_query['from'] . (empty($subject_query['inner_join']) ? '' : '
1345
								INNER JOIN ' . implode('
1346
								INNER JOIN ', $subject_query['inner_join'])) . (empty($subject_query['left_join']) ? '' : '
1347
								LEFT JOIN ' . implode('
1348
								LEFT JOIN ', $subject_query['left_join'])) . '
1349
							WHERE ' . implode('
1350
								AND ', $subject_query['where']) . (empty($modSettings['search_max_results']) ? '' : '
1351
							LIMIT ' . ($modSettings['search_max_results'] - $numSubjectResults)),
1352
							$subject_query['params']
1353
						);
1354
						// Don't do INSERT IGNORE? Manually fix this up!
1355 View Code Duplication
						if (!$smcFunc['db_support_ignore'])
1356
						{
1357
							while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1358
							{
1359
								$ind = $createTemporary ? 0 : 1;
1360
								// No duplicates!
1361
								if (isset($inserts[$row[$ind]]))
1362
									continue;
1363
1364
								$inserts[$row[$ind]] = $row;
1365
							}
1366
							$smcFunc['db_free_result']($ignoreRequest);
1367
							$numSubjectResults = count($inserts);
1368
						}
1369
						else
1370
							$numSubjectResults += $smcFunc['db_affected_rows']();
1371
1372
						if (!empty($modSettings['search_max_results']) && $numSubjectResults >= $modSettings['search_max_results'])
1373
							break;
1374
					}
1375
1376
					// Got some non-MySQL data to plonk in?
1377 View Code Duplication
					if (!empty($inserts))
1378
					{
1379
						$smcFunc['db_insert']('',
1380
							('{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_topics'),
1381
							$createTemporary ? array('id_topic' => 'int') : array('id_search' => 'int', 'id_topic' => 'int'),
1382
							$inserts,
1383
							$createTemporary ? array('id_topic') : array('id_search', 'id_topic')
1384
						);
1385
					}
1386
1387
					if ($numSubjectResults !== 0)
1388
					{
1389
						$main_query['weights']['subject']['search'] = 'CASE WHEN MAX(lst.id_topic) IS NULL THEN 0 ELSE 1 END';
1390
						$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)';
1391
						if (!$createTemporary)
1392
							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
1393
					}
1394
				}
1395
1396
				$indexedResults = 0;
1397
				// We building an index?
1398
				if ($searchAPI->supportsMethod('indexedWordQuery', $query_params))
1399
				{
1400
					$inserts = array();
1401
					$smcFunc['db_search_query']('drop_tmp_log_search_messages', '
1402
						DROP TABLE IF EXISTS {db_prefix}tmp_log_search_messages',
1403
						array(
1404
							'db_error_skip' => true,
1405
						)
1406
					);
1407
1408
					$createTemporary = $smcFunc['db_search_query']('create_tmp_log_search_messages', '
1409
						CREATE TEMPORARY TABLE {db_prefix}tmp_log_search_messages (
1410
							id_msg int(10) unsigned NOT NULL default {string:string_zero},
1411
							PRIMARY KEY (id_msg)
1412
						) ENGINE=MEMORY',
1413
						array(
1414
							'string_zero' => '0',
1415
							'db_error_skip' => true,
1416
						)
1417
					) !== false;
1418
1419
					// Clear, all clear!
1420
					if (!$createTemporary)
1421
						$smcFunc['db_search_query']('delete_log_search_messages', '
1422
							DELETE FROM {db_prefix}log_search_messages
1423
							WHERE id_search = {int:id_search}',
1424
							array(
1425
								'id_search' => $_SESSION['search_cache']['id_search'],
1426
							)
1427
						);
1428
1429
					foreach ($searchWords as $orIndex => $words)
1430
					{
1431
						// Search for this word, assuming we have some words!
1432
						if (!empty($words['indexed_words']))
1433
						{
1434
							// Variables required for the search.
1435
							$search_data = array(
1436
								'insert_into' => ($createTemporary ? 'tmp_' : '') . 'log_search_messages',
1437
								'no_regexp' => $no_regexp,
1438
								'max_results' => $maxMessageResults,
1439
								'indexed_results' => $indexedResults,
1440
								'params' => array(
1441
									'id_search' => !$createTemporary ? $_SESSION['search_cache']['id_search'] : 0,
1442
									'excluded_words' => $excludedWords,
1443
									'user_query' => !empty($userQuery) ? $userQuery : '',
1444
									'board_query' => !empty($boardQuery) ? $boardQuery : '',
1445
									'topic' => !empty($search_params['topic']) ? $search_params['topic'] : 0,
1446
									'min_msg_id' => !empty($minMsgID) ? $minMsgID : 0,
1447
									'max_msg_id' => !empty($maxMsgID) ? $maxMsgID : 0,
1448
									'excluded_phrases' => !empty($excludedPhrases) ? $excludedPhrases : array(),
1449
									'excluded_index_words' => !empty($excludedIndexWords) ? $excludedIndexWords : array(),
1450
									'excluded_subject_words' => !empty($excludedSubjectWords) ? $excludedSubjectWords : array(),
1451
								),
1452
							);
1453
1454
							$ignoreRequest = $searchAPI->indexedWordQuery($words, $search_data);
1455
1456
							if (!$smcFunc['db_support_ignore'])
1457
							{
1458 View Code Duplication
								while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1459
								{
1460
									// No duplicates!
1461
									if (isset($inserts[$row[0]]))
1462
										continue;
1463
1464
									$inserts[$row[0]] = $row;
1465
								}
1466
								$smcFunc['db_free_result']($ignoreRequest);
1467
								$indexedResults = count($inserts);
1468
							}
1469
							else
1470
								$indexedResults += $smcFunc['db_affected_rows']();
1471
1472
							if (!empty($maxMessageResults) && $indexedResults >= $maxMessageResults)
1473
								break;
1474
						}
1475
					}
1476
1477
					// More non-MySQL stuff needed?
1478 View Code Duplication
					if (!empty($inserts))
1479
					{
1480
						$smcFunc['db_insert']('',
1481
							'{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages',
1482
							$createTemporary ? array('id_msg' => 'int') : array('id_msg' => 'int', 'id_search' => 'int'),
1483
							$inserts,
1484
							$createTemporary ? array('id_msg') : array('id_msg', 'id_search')
1485
						);
1486
					}
1487
1488
					if (empty($indexedResults) && empty($numSubjectResults) && !empty($modSettings['search_force_index']))
1489
					{
1490
						$context['search_errors']['query_not_specific_enough'] = true;
1491
						$_REQUEST['params'] = $context['params'];
1492
						return PlushSearch1();
1493
					}
1494
					elseif (!empty($indexedResults))
1495
					{
1496
						$main_query['inner_join'][] = '{db_prefix}' . ($createTemporary ? 'tmp_' : '') . 'log_search_messages AS lsm ON (lsm.id_msg = m.id_msg)';
1497
						if (!$createTemporary)
1498
						{
1499
							$main_query['where'][] = 'lsm.id_search = {int:id_search}';
1500
							$main_query['parameters']['id_search'] = $_SESSION['search_cache']['id_search'];
1501
						}
1502
					}
1503
				}
1504
1505
				// Not using an index? All conditions have to be carried over.
1506
				else
1507
				{
1508
					$orWhere = array();
1509
					$count = 0;
1510
					foreach ($searchWords as $orIndex => $words)
1511
					{
1512
						$where = array();
1513
						foreach ($words['all_words'] as $regularWord)
1514
						{
1515
							$where[] = 'm.body' . (in_array($regularWord, $excludedWords) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
1516
							if (in_array($regularWord, $excludedWords))
1517
								$where[] = 'm.subject NOT' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
1518
							$main_query['parameters']['all_word_body_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]';
0 ignored issues
show
Coding Style introduced by
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
1519
						}
1520 View Code Duplication
						if (!empty($where))
1521
							$orWhere[] = count($where) > 1 ? '(' . implode(' AND ', $where) . ')' : $where[0];
1522
					}
1523 View Code Duplication
					if (!empty($orWhere))
1524
						$main_query['where'][] = count($orWhere) > 1 ? '(' . implode(' OR ', $orWhere) . ')' : $orWhere[0];
1525
1526
					if (!empty($userQuery))
1527
					{
1528
						$main_query['where'][] = '{raw:user_query}';
1529
						$main_query['parameters']['user_query'] = $userQuery;
1530
					}
1531 View Code Duplication
					if (!empty($search_params['topic']))
1532
					{
1533
						$main_query['where'][] = 'm.id_topic = {int:topic}';
1534
						$main_query['parameters']['topic'] = $search_params['topic'];
1535
					}
1536
					if (!empty($minMsgID))
1537
					{
1538
						$main_query['where'][] = 'm.id_msg >= {int:min_msg_id}';
1539
						$main_query['parameters']['min_msg_id'] = $minMsgID;
1540
					}
1541
					if (!empty($maxMsgID))
1542
					{
1543
						$main_query['where'][] = 'm.id_msg <= {int:max_msg_id}';
1544
						$main_query['parameters']['max_msg_id'] = $maxMsgID;
1545
					}
1546
					if (!empty($boardQuery))
1547
					{
1548
						$main_query['where'][] = 'm.id_board {raw:board_query}';
1549
						$main_query['parameters']['board_query'] = $boardQuery;
1550
					}
1551
				}
1552
				call_integration_hook('integrate_main_search_query', array(&$main_query));
1553
1554
				// Did we either get some indexed results, or otherwise did not do an indexed query?
1555
				if (!empty($indexedResults) || !$searchAPI->supportsMethod('indexedWordQuery', $query_params))
1556
				{
1557
					$relevance = '1000 * (';
1558
					$new_weight_total = 0;
1559
					foreach ($main_query['weights'] as $type => $value)
1560
					{
1561
						$relevance .= $weight[$type];
1562
						if (!empty($value['search']))
1563
							$relevance .= ' * ' . $value['search'];
1564
						$relevance .= ' + ';
1565
						$new_weight_total += $weight[$type];
1566
					}
1567
					$main_query['select']['relevance'] = substr($relevance, 0, -3) . ') / ' . $new_weight_total . ' AS relevance';
1568
1569
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_no_index', ($smcFunc['db_support_ignore'] ? ( '
1570
						INSERT IGNORE INTO ' . '{db_prefix}log_search_results
1571
							(' . implode(', ', array_keys($main_query['select'])) . ')') : '') . '
1572
						SELECT
1573
							' . implode(',
1574
							', $main_query['select']) . '
1575
						FROM ' . $main_query['from'] . (empty($main_query['inner_join']) ? '' : '
1576
							INNER JOIN ' . implode('
1577
							INNER JOIN ', $main_query['inner_join'])) . (empty($main_query['left_join']) ? '' : '
1578
							LEFT JOIN ' . implode('
1579
							LEFT JOIN ', $main_query['left_join'])) . (!empty($main_query['where']) ? '
1580
						WHERE ' : '') . implode('
1581
							AND ', $main_query['where']) . (empty($main_query['group_by']) ? '' : '
1582
						GROUP BY ' . implode(', ', $main_query['group_by'])) . (empty($modSettings['search_max_results']) ? '' : '
1583
						LIMIT ' . $modSettings['search_max_results']),
1584
						$main_query['parameters']
1585
					);
1586
1587
					// We love to handle non-good databases that don't support our ignore!
1588
					if (!$smcFunc['db_support_ignore'])
1589
					{
1590
						$inserts = array();
1591
						while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1592
						{
1593
							// No duplicates!
1594
							if (isset($inserts[$row[2]]))
1595
								continue;
1596
1597
							foreach ($row as $key => $value)
1598
								$inserts[$row[2]][] = (int) $row[$key];
1599
						}
1600
						$smcFunc['db_free_result']($ignoreRequest);
1601
1602
						// Now put them in!
1603
						if (!empty($inserts))
1604
						{
1605
							$query_columns = array();
1606
							foreach ($main_query['select'] as $k => $v)
1607
								$query_columns[$k] = 'int';
1608
1609
							$smcFunc['db_insert']('',
1610
								'{db_prefix}log_search_results',
1611
								$query_columns,
1612
								$inserts,
1613
								array('id_search', 'id_topic')
1614
							);
1615
						}
1616
						$_SESSION['search_cache']['num_results'] += count($inserts);
1617
					}
1618
					else
1619
						$_SESSION['search_cache']['num_results'] = $smcFunc['db_affected_rows']();
1620
				}
1621
1622
				// Insert subject-only matches.
1623
				if ($_SESSION['search_cache']['num_results'] < $modSettings['search_max_results'] && $numSubjectResults !== 0)
1624
				{
1625
					$relevance = '1000 * (';
1626 View Code Duplication
					foreach ($weight_factors as $type => $value)
1627
						if (isset($value['results']))
1628
						{
1629
							$relevance .= $weight[$type];
1630
							if (!empty($value['results']))
1631
								$relevance .= ' * ' . $value['results'];
1632
							$relevance .= ' + ';
1633
						}
1634
					$relevance = substr($relevance, 0, -3) . ') / ' . $weight_total . ' AS relevance';
1635
1636
					$usedIDs = array_flip(empty($inserts) ? array() : array_keys($inserts));
1637
					$ignoreRequest = $smcFunc['db_search_query']('insert_log_search_results_sub_only', ($smcFunc['db_support_ignore'] ? ( '
1638
						INSERT IGNORE INTO {db_prefix}log_search_results
1639
							(id_search, id_topic, relevance, id_msg, num_matches)') : '') . '
1640
						SELECT
1641
							{int:id_search},
1642
							t.id_topic,
1643
							' . $relevance . ',
1644
							t.id_first_msg,
1645
							1
1646
						FROM {db_prefix}topics AS t
1647
							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...
1648
						. ($createTemporary ? '' : ' WHERE lst.id_search = {int:id_search}')
1649
						. (empty($modSettings['search_max_results']) ? '' : '
1650
						LIMIT ' . ($modSettings['search_max_results'] - $_SESSION['search_cache']['num_results'])),
1651
						array(
1652
							'id_search' => $_SESSION['search_cache']['id_search'],
1653
							'min_msg' => $minMsg,
1654
							'recent_message' => $recentMsg,
1655
							'huge_topic_posts' => $humungousTopicPosts,
1656
						)
1657
					);
1658
					// Once again need to do the inserts if the database don't support ignore!
1659
					if (!$smcFunc['db_support_ignore'])
1660
					{
1661
						$inserts = array();
1662 View Code Duplication
						while ($row = $smcFunc['db_fetch_row']($ignoreRequest))
1663
						{
1664
							// No duplicates!
1665
							if (isset($usedIDs[$row[1]]))
1666
								continue;
1667
1668
							$usedIDs[$row[1]] = true;
1669
							$inserts[] = $row;
1670
						}
1671
						$smcFunc['db_free_result']($ignoreRequest);
1672
1673
						// Now put them in!
1674 View Code Duplication
						if (!empty($inserts))
1675
						{
1676
							$smcFunc['db_insert']('',
1677
								'{db_prefix}log_search_results',
1678
								array('id_search' => 'int', 'id_topic' => 'int', 'relevance' => 'float', 'id_msg' => 'int', 'num_matches' => 'int'),
1679
								$inserts,
1680
								array('id_search', 'id_topic')
1681
							);
1682
						}
1683
						$_SESSION['search_cache']['num_results'] += count($inserts);
1684
					}
1685
					else
1686
						$_SESSION['search_cache']['num_results'] += $smcFunc['db_affected_rows']();
1687
				}
1688
				elseif ($_SESSION['search_cache']['num_results'] == -1)
1689
					$_SESSION['search_cache']['num_results'] = 0;
1690
			}
1691
		}
1692
1693
		// *** Retrieve the results to be shown on the page
1694
		$participants = array();
1695
		$request = $smcFunc['db_search_query']('', '
1696
			SELECT ' . (empty($search_params['topic']) ? 'lsr.id_topic' : $search_params['topic'] . ' AS id_topic') . ', lsr.id_msg, lsr.relevance, lsr.num_matches
1697
			FROM {db_prefix}log_search_results AS lsr' . ($search_params['sort'] == 'num_replies' ? '
1698
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = lsr.id_topic)' : '') . '
1699
			WHERE lsr.id_search = {int:id_search}
1700
			ORDER BY {raw:sort} {raw:sort_dir}
1701
			LIMIT {int:start}, {int:max}',
1702
			array(
1703
				'id_search' => $_SESSION['search_cache']['id_search'],
1704
				'sort' => $search_params['sort'],
1705
				'sort_dir' => $search_params['sort_dir'],
1706
				'start' => $_REQUEST['start'],
1707
				'max' => $modSettings['search_results_per_page'],
1708
			)
1709
		);
1710
		while ($row = $smcFunc['db_fetch_assoc']($request))
1711
		{
1712
			$context['topics'][$row['id_msg']] = array(
1713
				'relevance' => round($row['relevance'] / 10, 1) . '%',
1714
				'num_matches' => $row['num_matches'],
1715
				'matches' => array(),
1716
			);
1717
			// By default they didn't participate in the topic!
1718
			$participants[$row['id_topic']] = false;
1719
		}
1720
		$smcFunc['db_free_result']($request);
1721
1722
		$num_results = $_SESSION['search_cache']['num_results'];
1723
	}
1724
1725
	if (!empty($context['topics']))
1726
	{
1727
		// Create an array for the permissions.
1728
		$boards_can = boardsAllowedTo(array('post_reply_own', 'post_reply_any'), true, false);
1729
1730
		// How's about some quick moderation?
1731
		if (!empty($options['display_quick_mod']))
1732
		{
1733
			$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));
1734
1735
			$context['can_lock'] = in_array(0, $boards_can['lock_any']);
1736
			$context['can_sticky'] = in_array(0, $boards_can['make_sticky']);
1737
			$context['can_move'] = in_array(0, $boards_can['move_any']);
1738
			$context['can_remove'] = in_array(0, $boards_can['remove_any']);
1739
			$context['can_merge'] = in_array(0, $boards_can['merge_any']);
1740
		}
1741
1742
		// What messages are we using?
1743
		$msg_list = array_keys($context['topics']);
1744
1745
		// Load the posters...
1746
		$request = $smcFunc['db_query']('', '
1747
			SELECT id_member
1748
			FROM {db_prefix}messages
1749
			WHERE id_member != {int:no_member}
1750
				AND id_msg IN ({array_int:message_list})
1751
			LIMIT {int:limit}',
1752
			array(
1753
				'message_list' => $msg_list,
1754
				'no_member' => 0,
1755
				'limit' => count($context['topics']),
1756
			)
1757
		);
1758
		$posters = array();
1759
		while ($row = $smcFunc['db_fetch_assoc']($request))
1760
			$posters[] = $row['id_member'];
1761
		$smcFunc['db_free_result']($request);
1762
1763
		call_integration_hook('integrate_search_message_list', array(&$msg_list, &$posters));
1764
1765
		if (!empty($posters))
1766
			loadMemberData(array_unique($posters));
1767
1768
		// Get the messages out for the callback - select enough that it can be made to look just like Display.
1769
		$messages_request = $smcFunc['db_query']('', '
1770
			SELECT
1771
				m.id_msg, m.subject, m.poster_name, m.poster_email, m.poster_time, m.id_member,
1772
				m.icon, m.poster_ip, m.body, m.smileys_enabled, m.modified_time, m.modified_name,
1773
				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,
1774
				first_mem.id_member AS first_member_id, COALESCE(first_mem.real_name, first_m.poster_name) AS first_member_name,
1775
				last_m.id_msg AS last_msg, last_m.poster_time AS last_poster_time, last_mem.id_member AS last_member_id,
1776
				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,
1777
				t.id_topic, t.is_sticky, t.locked, t.id_poll, t.num_replies, t.num_views,
1778
				b.id_board, b.name AS board_name, c.id_cat, c.name AS cat_name
1779
			FROM {db_prefix}messages AS m
1780
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic)
1781
				INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
1782
				INNER JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1783
				INNER JOIN {db_prefix}messages AS first_m ON (first_m.id_msg = t.id_first_msg)
1784
				INNER JOIN {db_prefix}messages AS last_m ON (last_m.id_msg = t.id_last_msg)
1785
				LEFT JOIN {db_prefix}members AS first_mem ON (first_mem.id_member = first_m.id_member)
1786
				LEFT JOIN {db_prefix}members AS last_mem ON (last_mem.id_member = first_m.id_member)
1787
			WHERE m.id_msg IN ({array_int:message_list})' . ($modSettings['postmod_active'] ? '
1788
				AND m.approved = {int:is_approved}' : '') . '
1789
			ORDER BY FIND_IN_SET(m.id_msg, {string:message_list_in_set})
1790
			LIMIT {int:limit}',
1791
			array(
1792
				'message_list' => $msg_list,
1793
				'is_approved' => 1,
1794
				'message_list_in_set' => implode(',', $msg_list),
1795
				'limit' => count($context['topics']),
1796
			)
1797
		);
1798
1799
		// If there are no results that means the things in the cache got deleted, so pretend we have no topics anymore.
1800
		if ($smcFunc['db_num_rows']($messages_request) == 0)
1801
			$context['topics'] = array();
1802
1803
		// If we want to know who participated in what then load this now.
1804
		if (!empty($modSettings['enableParticipation']) && !$user_info['is_guest'])
1805
		{
1806
			$result = $smcFunc['db_query']('', '
1807
				SELECT id_topic
1808
				FROM {db_prefix}messages
1809
				WHERE id_topic IN ({array_int:topic_list})
1810
					AND id_member = {int:current_member}
1811
				GROUP BY id_topic
1812
				LIMIT {int:limit}',
1813
				array(
1814
					'current_member' => $user_info['id'],
1815
					'topic_list' => array_keys($participants),
1816
					'limit' => count($participants),
1817
				)
1818
			);
1819
			while ($row = $smcFunc['db_fetch_assoc']($result))
1820
				$participants[$row['id_topic']] = true;
1821
			$smcFunc['db_free_result']($result);
1822
		}
1823
	}
1824
1825
	// Now that we know how many results to expect we can start calculating the page numbers.
1826
	$context['page_index'] = constructPageIndex($scripturl . '?action=search2;params=' . $context['params'], $_REQUEST['start'], $num_results, $modSettings['search_results_per_page'], false);
1827
1828
	// Consider the search complete!
1829
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1830
		cache_put_data('search_start:' . ($user_info['is_guest'] ? $user_info['ip'] : $user_info['id']), null, 90);
1831
1832
	$context['key_words'] = &$searchArray;
1833
1834
	// Setup the default topic icons... for checking they exist and the like!
1835
	$context['icon_sources'] = array();
1836
	foreach ($context['stable_icons'] as $icon)
1837
		$context['icon_sources'][$icon] = 'images_url';
1838
1839
	$context['sub_template'] = 'results';
1840
	$context['page_title'] = $txt['search_results'];
1841
	$context['get_topics'] = 'prepareSearchContext';
1842
	$context['can_restore_perm'] = allowedTo('move_any') && !empty($modSettings['recycle_enable']);
1843
	$context['can_restore'] = false; // We won't know until we handle the context later whether we can actually restore...
1844
1845
	$context['jump_to'] = array(
1846
		'label' => addslashes(un_htmlspecialchars($txt['jump_to'])),
1847
		'board_name' => addslashes(un_htmlspecialchars($txt['select_destination'])),
1848
	);
1849
}
1850
1851
/**
1852
 * Callback to return messages - saves memory.
1853
 *
1854
 * What it does:
1855
 * - callback function for the results sub template.
1856
 * - loads the necessary contextual data to show a search result.
1857
 *
1858
 * @param bool $reset Whether to reset the counter
1859
 * @return array An array of contextual info related to this search
1860
 */
1861
function prepareSearchContext($reset = false)
1862
{
1863
	global $txt, $modSettings, $scripturl, $user_info;
1864
	global $memberContext, $context, $settings, $options, $messages_request;
1865
	global $boards_can, $participants, $smcFunc;
1866
	static $recycle_board = null;
1867
1868
	if ($recycle_board === null)
1869
		$recycle_board = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) ? (int) $modSettings['recycle_board'] : 0;
1870
1871
	// Remember which message this is.  (ie. reply #83)
1872
	static $counter = null;
1873
	if ($counter == null || $reset)
1874
		$counter = $_REQUEST['start'] + 1;
1875
1876
	// If the query returned false, bail.
1877
	if ($messages_request == false)
1878
		return false;
1879
1880
	// Start from the beginning...
1881
	if ($reset)
1882
		return @$smcFunc['db_data_seek']($messages_request, 0);
1883
1884
	// Attempt to get the next message.
1885
	$message = $smcFunc['db_fetch_assoc']($messages_request);
1886
	if (!$message)
1887
		return false;
1888
1889
	// Can't have an empty subject can we?
1890
	$message['subject'] = $message['subject'] != '' ? $message['subject'] : $txt['no_subject'];
1891
1892
	$message['first_subject'] = $message['first_subject'] != '' ? $message['first_subject'] : $txt['no_subject'];
1893
	$message['last_subject'] = $message['last_subject'] != '' ? $message['last_subject'] : $txt['no_subject'];
1894
1895
	// If it couldn't load, or the user was a guest.... someday may be done with a guest table.
1896
	if (!loadMemberContext($message['id_member']))
1897
	{
1898
		// Notice this information isn't used anywhere else.... *cough guest table cough*.
1899
		$memberContext[$message['id_member']]['name'] = $message['poster_name'];
1900
		$memberContext[$message['id_member']]['id'] = 0;
1901
		$memberContext[$message['id_member']]['group'] = $txt['guest_title'];
1902
		$memberContext[$message['id_member']]['link'] = $message['poster_name'];
1903
		$memberContext[$message['id_member']]['email'] = $message['poster_email'];
1904
	}
1905
	$memberContext[$message['id_member']]['ip'] = inet_dtop($message['poster_ip']);
1906
1907
	// Do the censor thang...
1908
	censorText($message['body']);
1909
	censorText($message['subject']);
1910
1911
	censorText($message['first_subject']);
1912
	censorText($message['last_subject']);
1913
1914
	// Shorten this message if necessary.
1915
	if ($context['compact'])
1916
	{
1917
		// Set the number of characters before and after the searched keyword.
1918
		$charLimit = 50;
1919
1920
		$message['body'] = strtr($message['body'], array("\n" => ' ', '<br>' => "\n"));
1921
		$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1922
		$message['body'] = strip_tags(strtr($message['body'], array('</div>' => '<br>', '</li>' => '<br>')), '<br>');
1923
1924
		if ($smcFunc['strlen']($message['body']) > $charLimit)
1925
		{
1926
			if (empty($context['key_words']))
1927
				$message['body'] = $smcFunc['substr']($message['body'], 0, $charLimit) . '<strong>...</strong>';
1928
			else
1929
			{
1930
				$matchString = '';
1931
				$force_partial_word = false;
1932
				foreach ($context['key_words'] as $keyword)
1933
				{
1934
					$keyword = un_htmlspecialchars($keyword);
1935
					$keyword = preg_replace_callback('~(&amp;#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', strtr($keyword, array('\\\'' => '\'', '&' => '&amp;')));
1936
1937
					if (preg_match('~[\'\.,/@%&;:(){}\[\]_\-+\\\\]$~', $keyword) != 0 || preg_match('~^[\'\.,/@%&;:(){}\[\]_\-+\\\\]~', $keyword) != 0)
1938
						$force_partial_word = true;
1939
					$matchString .= strtr(preg_quote($keyword, '/'), array('\*' => '.+?')) . '|';
1940
				}
1941
				$matchString = un_htmlspecialchars(substr($matchString, 0, -1));
1942
1943
				$message['body'] = un_htmlspecialchars(strtr($message['body'], array('&nbsp;' => ' ', '<br>' => "\n", '&#91;' => '[', '&#93;' => ']', '&#58;' => ':', '&#64;' => '@')));
1944
1945
				if (empty($modSettings['search_method']) || $force_partial_word)
1946
					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);
1947
				else
1948
					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);
1949
1950
				$message['body'] = '';
1951
				foreach ($matches[0] as $index => $match)
1952
				{
1953
					$match = strtr($smcFunc['htmlspecialchars']($match, ENT_QUOTES), array("\n" => '&nbsp;'));
1954
					$message['body'] .= '<strong>......</strong>&nbsp;' . $match . '&nbsp;<strong>......</strong>';
1955
				}
1956
			}
1957
1958
			// Re-fix the international characters.
1959
			$message['body'] = preg_replace_callback('~(&amp;#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $message['body']);
1960
		}
1961
	}
1962
	else
1963
	{
1964
		// Run BBC interpreter on the message.
1965
		$message['body'] = parse_bbc($message['body'], $message['smileys_enabled'], $message['id_msg']);
1966
	}
1967
1968
	// Make sure we don't end up with a practically empty message body.
1969
	$message['body'] = preg_replace('~^(?:&nbsp;)+$~', '', $message['body']);
1970
1971
	if (!empty($recycle_board) && $message['id_board'] == $recycle_board)
1972
	{
1973
		$message['first_icon'] = 'recycled';
1974
		$message['last_icon'] = 'recycled';
1975
		$message['icon'] = 'recycled';
1976
	}
1977
1978
	// Sadly, we need to check the icon ain't broke.
1979
	if (!empty($modSettings['messageIconChecks_enable']))
1980
	{
1981 View Code Duplication
		if (!isset($context['icon_sources'][$message['first_icon']]))
1982
			$context['icon_sources'][$message['first_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['first_icon'] . '.png') ? 'images_url' : 'default_images_url';
1983 View Code Duplication
		if (!isset($context['icon_sources'][$message['last_icon']]))
1984
			$context['icon_sources'][$message['last_icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['last_icon'] . '.png') ? 'images_url' : 'default_images_url';
1985 View Code Duplication
		if (!isset($context['icon_sources'][$message['icon']]))
1986
			$context['icon_sources'][$message['icon']] = file_exists($settings['theme_dir'] . '/images/post/' . $message['icon'] . '.png') ? 'images_url' : 'default_images_url';
1987
	}
1988
	else
1989
	{
1990 View Code Duplication
		if (!isset($context['icon_sources'][$message['first_icon']]))
1991
			$context['icon_sources'][$message['first_icon']] = 'images_url';
1992 View Code Duplication
		if (!isset($context['icon_sources'][$message['last_icon']]))
1993
			$context['icon_sources'][$message['last_icon']] = 'images_url';
1994 View Code Duplication
		if (!isset($context['icon_sources'][$message['icon']]))
1995
			$context['icon_sources'][$message['icon']] = 'images_url';
1996
	}
1997
1998
	// Do we have quote tag enabled?
1999
	$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
2000
2001
	// Reference the main color class.
2002
	$colorClass = 'windowbg';
2003
2004
	// Sticky topics should get a different color, too.
2005
	if ($message['is_sticky'])
2006
		$colorClass .= ' sticky';
2007
2008
	// Locked topics get special treatment as well.
2009
	if ($message['locked'])
2010
		$colorClass .= ' locked';
2011
2012
	$output = array_merge($context['topics'][$message['id_msg']], array(
2013
		'id' => $message['id_topic'],
2014
		'is_sticky' => !empty($message['is_sticky']),
2015
		'is_locked' => !empty($message['locked']),
2016
		'css_class' => $colorClass,
2017
		'is_poll' => $modSettings['pollMode'] == '1' && $message['id_poll'] > 0,
2018
		'posted_in' => !empty($participants[$message['id_topic']]),
2019
		'views' => $message['num_views'],
2020
		'replies' => $message['num_replies'],
2021
		'can_reply' => in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any']),
2022
		'can_quote' => (in_array($message['id_board'], $boards_can['post_reply_any']) || in_array(0, $boards_can['post_reply_any'])) && $quote_enabled,
2023
		'first_post' => array(
2024
			'id' => $message['first_msg'],
2025
			'time' => timeformat($message['first_poster_time']),
2026
			'timestamp' => forum_time(true, $message['first_poster_time']),
2027
			'subject' => $message['first_subject'],
2028
			'href' => $scripturl . '?topic=' . $message['id_topic'] . '.0',
2029
			'link' => '<a href="' . $scripturl . '?topic=' . $message['id_topic'] . '.0">' . $message['first_subject'] . '</a>',
2030
			'icon' => $message['first_icon'],
2031
			'icon_url' => $settings[$context['icon_sources'][$message['first_icon']]] . '/post/' . $message['first_icon'] . '.png',
2032
			'member' => array(
2033
				'id' => $message['first_member_id'],
2034
				'name' => $message['first_member_name'],
2035
				'href' => !empty($message['first_member_id']) ? $scripturl . '?action=profile;u=' . $message['first_member_id'] : '',
2036
				'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']
2037
			)
2038
		),
2039
		'last_post' => array(
2040
			'id' => $message['last_msg'],
2041
			'time' => timeformat($message['last_poster_time']),
2042
			'timestamp' => forum_time(true, $message['last_poster_time']),
2043
			'subject' => $message['last_subject'],
2044
			'href' => $scripturl . '?topic=' . $message['id_topic'] . ($message['num_replies'] == 0 ? '.0' : '.msg' . $message['last_msg']) . '#msg' . $message['last_msg'],
2045
			'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>',
2046
			'icon' => $message['last_icon'],
2047
			'icon_url' => $settings[$context['icon_sources'][$message['last_icon']]] . '/post/' . $message['last_icon'] . '.png',
2048
			'member' => array(
2049
				'id' => $message['last_member_id'],
2050
				'name' => $message['last_member_name'],
2051
				'href' => !empty($message['last_member_id']) ? $scripturl . '?action=profile;u=' . $message['last_member_id'] : '',
2052
				'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']
2053
			)
2054
		),
2055
		'board' => array(
2056
			'id' => $message['id_board'],
2057
			'name' => $message['board_name'],
2058
			'href' => $scripturl . '?board=' . $message['id_board'] . '.0',
2059
			'link' => '<a href="' . $scripturl . '?board=' . $message['id_board'] . '.0">' . $message['board_name'] . '</a>'
2060
		),
2061
		'category' => array(
2062
			'id' => $message['id_cat'],
2063
			'name' => $message['cat_name'],
2064
			'href' => $scripturl . '#c' . $message['id_cat'],
2065
			'link' => '<a href="' . $scripturl . '#c' . $message['id_cat'] . '">' . $message['cat_name'] . '</a>'
2066
		)
2067
	));
2068
2069
	$body_highlighted = $message['body'];
2070
	$subject_highlighted = $message['subject'];
2071
2072
	if (!empty($options['display_quick_mod']))
2073
	{
2074
		$started = $output['first_post']['member']['id'] == $user_info['id'];
2075
2076
		$output['quick_mod'] = array(
2077
			'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']))),
2078
			'sticky' => (in_array(0, $boards_can['make_sticky']) || in_array($output['board']['id'], $boards_can['make_sticky'])),
2079
			'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']))),
2080
			'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']))),
2081
			'restore' => $context['can_restore_perm'] && ($modSettings['recycle_board'] == $output['board']['id']),
2082
		);
2083
2084
		$context['can_lock'] |= $output['quick_mod']['lock'];
2085
		$context['can_sticky'] |= $output['quick_mod']['sticky'];
2086
		$context['can_move'] |= $output['quick_mod']['move'];
2087
		$context['can_remove'] |= $output['quick_mod']['remove'];
2088
		$context['can_merge'] |= in_array($output['board']['id'], $boards_can['merge_any']);
2089
		$context['can_restore'] |= $output['quick_mod']['restore'];
2090
		$context['can_markread'] = $context['user']['is_logged'];
2091
2092
		$context['qmod_actions'] = array('remove', 'lock', 'sticky', 'move', 'merge', 'restore', 'markread');
2093
		call_integration_hook('integrate_quick_mod_actions_search');
2094
	}
2095
2096
	foreach ($context['key_words'] as $query)
2097
	{
2098
		// Fix the international characters in the keyword too.
2099
		$query = un_htmlspecialchars($query);
2100
		$query = trim($query, "\*+");
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal \*+ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
2101
		$query = strtr($smcFunc['htmlspecialchars']($query), array('\\\'' => '\''));
2102
2103
		$body_highlighted = preg_replace_callback('/((<[^>]*)|' . preg_quote(strtr($query, array('\'' => '&#039;')), '/') . ')/i' . ($context['utf8'] ? 'u' : ''), function ($m)
2104
		{
2105
			return isset($m[2]) && "$m[2]" == "$m[1]" ? stripslashes("$m[1]") : "<strong class=\"highlight\">$m[1]</strong>";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $m instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
2106
		}, $body_highlighted);
2107
		$subject_highlighted = preg_replace('/(' . preg_quote($query, '/') . ')/i' . ($context['utf8'] ? 'u' : ''), '<strong class="highlight">$1</strong>', $subject_highlighted);
2108
	}
2109
2110
	$output['matches'][] = array(
2111
		'id' => $message['id_msg'],
2112
		'attachment' => array(),
2113
		'member' => &$memberContext[$message['id_member']],
2114
		'icon' => $message['icon'],
2115
		'icon_url' => $settings[$context['icon_sources'][$message['icon']]] . '/post/' . $message['icon'] . '.png',
2116
		'subject' => $message['subject'],
2117
		'subject_highlighted' => $subject_highlighted,
2118
		'time' => timeformat($message['poster_time']),
2119
		'timestamp' => forum_time(true, $message['poster_time']),
2120
		'counter' => $counter,
2121
		'modified' => array(
2122
			'time' => timeformat($message['modified_time']),
2123
			'timestamp' => forum_time(true, $message['modified_time']),
2124
			'name' => $message['modified_name']
2125
		),
2126
		'body' => $message['body'],
2127
		'body_highlighted' => $body_highlighted,
2128
		'start' => 'msg' . $message['id_msg']
2129
	);
2130
	$counter++;
2131
2132
	call_integration_hook('integrate_search_message_context', array(&$output, &$message, $counter));
2133
2134
	return $output;
2135
}
2136
2137
/**
2138
 * Creates a search API and returns the object.
2139
 *
2140
 * @return search_api_interface An instance of the search API interface
2141
 */
2142
function findSearchAPI()
2143
{
2144
	global $sourcedir, $modSettings, $search_versions, $searchAPI, $txt;
2145
2146
	require_once($sourcedir . '/Subs-Package.php');
2147
	require_once($sourcedir . '/Class-SearchAPI.php');
2148
2149
	// Search has a special database set.
2150
	db_extend('search');
2151
2152
	// Load up the search API we are going to use.
2153
	$modSettings['search_index'] = empty($modSettings['search_index']) ? 'standard' : $modSettings['search_index'];
2154
	if (!file_exists($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php'))
2155
		fatal_lang_error('search_api_missing');
2156
	require_once($sourcedir . '/SearchAPI-' . ucwords($modSettings['search_index']) . '.php');
2157
2158
	// Create an instance of the search API and check it is valid for this version of SMF.
2159
	$search_class_name = $modSettings['search_index'] . '_search';
2160
	$searchAPI = new $search_class_name();
2161
2162
	// An invalid Search API.
2163
	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...
2164
	{
2165
		// Log the error.
2166
		loadLanguage('Errors');
2167
		log_error(sprintf($txt['search_api_not_compatible'], 'SearchAPI-' . ucwords($modSettings['search_index']) . '.php'), 'critical');
2168
2169
		require_once($sourcedir . '/SearchAPI-Standard.php');
2170
		$searchAPI = new standard_search();
2171
	}
2172
2173
	return $searchAPI;
2174
}
2175
2176
/**
2177
 * This function compares the length of two strings plus a little.
2178
 * What it does:
2179
 * - callback function for usort used to sort the fulltext results.
2180
 * - passes sorting duty to the current API.
2181
 *
2182
 * @param string $a
2183
 * @param string $b
2184
 * @return int
2185
 */
2186
function searchSort($a, $b)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $b. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2187
{
2188
	global $searchAPI;
2189
2190
	return $searchAPI->searchSort($a, $b);
2191
}
2192
2193
?>