Passed
Push — release-2.1 ( 0c2197...207d2d )
by Jeremy
05:47
created

ManageSearch.php ➔ EditSearchMethod()   F

Complexity

Conditions 38
Paths 4560

Size

Total Lines 270

Duplication

Lines 59
Ratio 21.85 %

Importance

Changes 0
Metric Value
cc 38
nc 4560
nop 0
dl 59
loc 270
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * The admin screen to change the search settings.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2018 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
/**
20
 * Main entry point for the admin search settings screen.
21
 * It checks permissions, and it forwards to the appropriate function based on
22
 * the given sub-action.
23
 * Defaults to sub-action 'settings'.
24
 * Called by ?action=admin;area=managesearch.
25
 * Requires the admin_forum permission.
26
 *
27
 * @uses ManageSearch template.
28
 * @uses Search language file.
29
 */
30
function ManageSearch()
31
{
32
	global $context, $txt;
33
34
	isAllowedTo('admin_forum');
35
36
	loadLanguage('Search');
37
	loadTemplate('ManageSearch');
38
39
	db_extend('search');
40
41
	$subActions = array(
42
		'settings' => 'EditSearchSettings',
43
		'weights' => 'EditWeights',
44
		'method' => 'EditSearchMethod',
45
		'createfulltext' => 'EditSearchMethod',
46
		'removecustom' => 'EditSearchMethod',
47
		'removefulltext' => 'EditSearchMethod',
48
		'createmsgindex' => 'CreateMessageIndex',
49
	);
50
51
	// Default the sub-action to 'edit search settings'.
52
	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'weights';
53
54
	$context['sub_action'] = $_REQUEST['sa'];
55
56
	// Create the tabs for the template.
57
	$context[$context['admin_menu_name']]['tab_data'] = array(
58
		'title' => $txt['manage_search'],
59
		'help' => 'search',
60
		'description' => $txt['search_settings_desc'],
61
		'tabs' => array(
62
			'weights' => array(
63
				'description' => $txt['search_weights_desc'],
64
			),
65
			'method' => array(
66
				'description' => $txt['search_method_desc'],
67
			),
68
			'settings' => array(
69
				'description' => $txt['search_settings_desc'],
70
			),
71
		),
72
	);
73
74
	call_integration_hook('integrate_manage_search', array(&$subActions));
75
76
	// Call the right function for this sub-action.
77
	call_helper($subActions[$_REQUEST['sa']]);
78
}
79
80
/**
81
 * Edit some general settings related to the search function.
82
 * Called by ?action=admin;area=managesearch;sa=settings.
83
 * Requires the admin_forum permission.
84
 *
85
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
86
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
87
 * @uses ManageSearch template, 'modify_settings' sub-template.
88
 */
89
function EditSearchSettings($return_config = false)
90
{
91
	global $txt, $context, $scripturl, $sourcedir, $modSettings;
92
93
	// What are we editing anyway?
94
	$config_vars = array(
95
			// Permission...
96
			array('permissions', 'search_posts'),
97
			// Some simple settings.
98
			array('int', 'search_results_per_page'),
99
			array('int', 'search_max_results', 'subtext' => $txt['search_max_results_disable']),
100
		'',
101
			// Some limitations.
102
			array('int', 'search_floodcontrol_time', 'subtext' => $txt['search_floodcontrol_time_desc'], 6, 'postinput' => $txt['seconds']),
103
	);
104
105
	call_integration_hook('integrate_modify_search_settings', array(&$config_vars));
106
107
	// Perhaps the search method wants to add some settings?
108
	require_once($sourcedir . '/Search.php');
109
	$searchAPI = findSearchAPI();
110
	if (is_callable(array($searchAPI, 'searchSettings')))
111
		call_user_func_array(array($searchAPI, 'searchSettings'), array(&$config_vars));
112
113
	if ($return_config)
114
		return $config_vars;
115
116
	$context['page_title'] = $txt['search_settings_title'];
117
	$context['sub_template'] = 'show_settings';
118
119
	// We'll need this for the settings.
120
	require_once($sourcedir . '/ManageServer.php');
121
122
	// A form was submitted.
123
	if (isset($_REQUEST['save']))
124
	{
125
		checkSession();
126
127
		call_integration_hook('integrate_save_search_settings');
128
129
		if (empty($_POST['search_results_per_page']))
130
			$_POST['search_results_per_page'] = !empty($modSettings['search_results_per_page']) ? $modSettings['search_results_per_page'] : $modSettings['defaultMaxMessages'];
131
		saveDBSettings($config_vars);
132
		$_SESSION['adm-save'] = true;
133
		redirectexit('action=admin;area=managesearch;sa=settings;' . $context['session_var'] . '=' . $context['session_id']);
134
	}
135
136
	// Prep the template!
137
	$context['post_url'] = $scripturl . '?action=admin;area=managesearch;save;sa=settings';
138
	$context['settings_title'] = $txt['search_settings_title'];
139
140
	// We need this for the in-line permissions
141
	createToken('admin-mp');
142
143
	prepareDBSettingContext($config_vars);
144
}
145
146
/**
147
 * Edit the relative weight of the search factors.
148
 * Called by ?action=admin;area=managesearch;sa=weights.
149
 * Requires the admin_forum permission.
150
 *
151
 * @uses ManageSearch template, 'modify_weights' sub-template.
152
 */
153
function EditWeights()
154
{
155
	global $txt, $context, $modSettings;
156
157
	$context['page_title'] = $txt['search_weights_title'];
158
	$context['sub_template'] = 'modify_weights';
159
160
	$factors = array(
161
		'search_weight_frequency',
162
		'search_weight_age',
163
		'search_weight_length',
164
		'search_weight_subject',
165
		'search_weight_first_message',
166
		'search_weight_sticky',
167
	);
168
169
	call_integration_hook('integrate_modify_search_weights', array(&$factors));
170
171
	// A form was submitted.
172
	if (isset($_POST['save']))
173
	{
174
		checkSession();
175
		validateToken('admin-msw');
176
177
		call_integration_hook('integrate_save_search_weights');
178
179
		$changes = array();
180
		foreach ($factors as $factor)
181
			$changes[$factor] = (int) $_POST[$factor];
182
		updateSettings($changes);
183
	}
184
185
	$context['relative_weights'] = array('total' => 0);
186
	foreach ($factors as $factor)
187
		$context['relative_weights']['total'] += isset($modSettings[$factor]) ? $modSettings[$factor] : 0;
188
189
	foreach ($factors as $factor)
190
		$context['relative_weights'][$factor] = round(100 * (isset($modSettings[$factor]) ? $modSettings[$factor] : 0) / $context['relative_weights']['total'], 1);
191
192
	createToken('admin-msw');
193
}
194
195
/**
196
 * Edit the search method and search index used.
197
 * Calculates the size of the current search indexes in use.
198
 * Allows to create and delete a fulltext index on the messages table.
199
 * Allows to delete a custom index (that CreateMessageIndex() created).
200
 * Called by ?action=admin;area=managesearch;sa=method.
201
 * Requires the admin_forum permission.
202
 *
203
 * @uses ManageSearch template, 'select_search_method' sub-template.
204
 */
205
function EditSearchMethod()
206
{
207
	global $txt, $context, $modSettings, $smcFunc, $db_type, $db_prefix;
208
209
	$context[$context['admin_menu_name']]['current_subsection'] = 'method';
210
	$context['page_title'] = $txt['search_method_title'];
211
	$context['sub_template'] = 'select_search_method';
212
	$context['supports_fulltext'] = $smcFunc['db_search_support']('fulltext');
213
214
	// Load any apis.
215
	$context['search_apis'] = loadSearchAPIs();
216
217
	// Detect whether a fulltext index is set.
218
	if ($context['supports_fulltext'])
219
		detectFulltextIndex();
220
221
	if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'createfulltext')
222
	{
223
		checkSession('get');
224
		validateToken('admin-msm', 'get');
225
226
		if ($db_type == 'postgresql') {
227
			$smcFunc['db_query']('', '
228
				DROP INDEX IF EXISTS {db_prefix}messages_ftx',
229
				array(
230
					'db_error_skip' => true,
231
				)
232
			);
233
234
			$language_ftx = $smcFunc['db_search_language']();
235
236
			$smcFunc['db_query']('', '
237
				CREATE INDEX {db_prefix}messages_ftx ON {db_prefix}messages
238
				USING gin(to_tsvector({string:language},body))',
239
				array(
240
					'language' => $language_ftx
241
				)
242
			);
243
		}
244
		else
245
		{
246
			// Make sure it's gone before creating it.
247
			$smcFunc['db_query']('', '
248
				ALTER TABLE {db_prefix}messages
249
				DROP INDEX body',
250
				array(
251
					'db_error_skip' => true,
252
				)
253
			);
254
255
			$smcFunc['db_query']('', '
256
				ALTER TABLE {db_prefix}messages
257
				ADD FULLTEXT body (body)',
258
				array(
259
				)
260
			);
261
		}
262
	}
263
	elseif (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'removefulltext' && !empty($context['fulltext_index']))
264
	{
265
		checkSession('get');
266
		validateToken('admin-msm', 'get');
267
268
		$smcFunc['db_query']('', '
269
			ALTER TABLE {db_prefix}messages
270
			DROP INDEX ' . implode(',
271
			DROP INDEX ', $context['fulltext_index']),
272
			array(
273
				'db_error_skip' => true,
274
			)
275
		);
276
277
		$context['fulltext_index'] = array();
278
279
		// Go back to the default search method.
280
		if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'fulltext')
281
			updateSettings(array(
282
				'search_index' => '',
283
			));
284
	}
285
	elseif (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'removecustom')
286
	{
287
		checkSession('get');
288
		validateToken('admin-msm', 'get');
289
290
		db_extend();
291
		$tables = $smcFunc['db_list_tables'](false, $db_prefix . 'log_search_words');
292
		if (!empty($tables))
293
		{
294
			$smcFunc['db_search_query']('drop_words_table', '
295
				DROP TABLE {db_prefix}log_search_words',
296
				array(
297
				)
298
			);
299
		}
300
301
		updateSettings(array(
302
			'search_custom_index_config' => '',
303
			'search_custom_index_resume' => '',
304
		));
305
306
		// Go back to the default search method.
307
		if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'custom')
308
			updateSettings(array(
309
				'search_index' => '',
310
			));
311
	}
312
	elseif (isset($_POST['save']))
313
	{
314
		checkSession();
315
		validateToken('admin-msmpost');
316
317
		updateSettings(array(
318
			'search_index' => empty($_POST['search_index']) || (!in_array($_POST['search_index'], array('fulltext', 'custom')) && !isset($context['search_apis'][$_POST['search_index']])) ? '' : $_POST['search_index'],
319
			'search_force_index' => isset($_POST['search_force_index']) ? '1' : '0',
320
			'search_match_words' => isset($_POST['search_match_words']) ? '1' : '0',
321
		));
322
	}
323
324
	$context['table_info'] = array(
325
		'data_length' => 0,
326
		'index_length' => 0,
327
		'fulltext_length' => 0,
328
		'custom_index_length' => 0,
329
	);
330
331
	// Get some info about the messages table, to show its size and index size.
332
	if ($db_type == 'mysql')
333
	{
334
		if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
335
			$request = $smcFunc['db_query']('', '
336
				SHOW TABLE STATUS
337
				FROM {string:database_name}
338
				LIKE {string:table_name}',
339
				array(
340
					'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
341
					'table_name' => str_replace('_', '\_', $match[2]) . 'messages',
342
				)
343
			);
344
		else
345
			$request = $smcFunc['db_query']('', '
346
				SHOW TABLE STATUS
347
				LIKE {string:table_name}',
348
				array(
349
					'table_name' => str_replace('_', '\_', $db_prefix) . 'messages',
350
				)
351
			);
352
		if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
353
		{
354
			// Only do this if the user has permission to execute this query.
355
			$row = $smcFunc['db_fetch_assoc']($request);
356
			$context['table_info']['data_length'] = $row['Data_length'];
357
			$context['table_info']['index_length'] = $row['Index_length'];
358
			$context['table_info']['fulltext_length'] = $row['Index_length'];
359
			$smcFunc['db_free_result']($request);
360
		}
361
362
		// Now check the custom index table, if it exists at all.
363
		if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
364
			$request = $smcFunc['db_query']('', '
365
				SHOW TABLE STATUS
366
				FROM {string:database_name}
367
				LIKE {string:table_name}',
368
				array(
369
					'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
370
					'table_name' => str_replace('_', '\_', $match[2]) . 'log_search_words',
371
				)
372
			);
373
		else
374
			$request = $smcFunc['db_query']('', '
375
				SHOW TABLE STATUS
376
				LIKE {string:table_name}',
377
				array(
378
					'table_name' => str_replace('_', '\_', $db_prefix) . 'log_search_words',
379
				)
380
			);
381
		if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
382
		{
383
			// Only do this if the user has permission to execute this query.
384
			$row = $smcFunc['db_fetch_assoc']($request);
385
			$context['table_info']['index_length'] += $row['Data_length'] + $row['Index_length'];
386
			$context['table_info']['custom_index_length'] = $row['Data_length'] + $row['Index_length'];
387
			$smcFunc['db_free_result']($request);
388
		}
389
	}
390
	elseif ($db_type == 'postgresql')
391
	{
392
		// In order to report the sizes correctly we need to perform vacuum (optimize) on the tables we will be using.
393
		//db_extend();
394
		//$temp_tables = $smcFunc['db_list_tables']();
395
		//foreach ($temp_tables as $table)
396
		//	if ($table == $db_prefix. 'messages' || $table == $db_prefix. 'log_search_words')
397
		//		$smcFunc['db_optimize_table']($table);
398
399
		// PostGreSql has some hidden sizes.
400
		$request = $smcFunc['db_query']('', '
401
			SELECT
402
				indexname,
403
				pg_relation_size(quote_ident(t.tablename)::text) AS table_size,
404
				pg_relation_size(quote_ident(indexrelname)::text) AS index_size
405
			FROM pg_tables t
406
			LEFT OUTER JOIN pg_class c ON t.tablename=c.relname
407
			LEFT OUTER JOIN
408
				( SELECT c.relname AS ctablename, ipg.relname AS indexname, indexrelname FROM pg_index x
409
						JOIN pg_class c ON c.oid = x.indrelid
410
						JOIN pg_class ipg ON ipg.oid = x.indexrelid
411
						JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid )
412
				AS foo
413
				ON t.tablename = foo.ctablename
414
			WHERE t.schemaname= {string:schema} and (
415
				indexname = {string:messages_ftx} OR indexname = {string:log_search_words} )',
416
			array(
417
				'messages_ftx' => $db_prefix . 'messages_ftx',
418
				'log_search_words' => $db_prefix . 'log_search_words',
419
				'schema' => 'public',
420
			)
421
		);
422
423
		if ($request !== false && $smcFunc['db_num_rows']($request) > 0)
424
		{
425
			while ($row = $smcFunc['db_fetch_assoc']($request))
426
			{
427
				if ($row['indexname'] == $db_prefix . 'messages_ftx')
428
				{
429
					$context['table_info']['data_length'] = (int) $row['table_size'];
430
					$context['table_info']['index_length'] = (int) $row['index_size'];
431
					$context['table_info']['fulltext_length'] = (int) $row['index_size'];
432
				}
433
				elseif ($row['indexname'] == $db_prefix . 'log_search_words')
434
				{
435
					$context['table_info']['index_length'] = (int) $row['index_size'];
436
					$context['table_info']['custom_index_length'] = (int) $row['index_size'];
437
				}
438
			}
439
			$smcFunc['db_free_result']($request);
440
		}
441
		else
442
			// Didn't work for some reason...
443
			$context['table_info'] = array(
444
				'data_length' => $txt['not_applicable'],
445
				'index_length' => $txt['not_applicable'],
446
				'fulltext_length' => $txt['not_applicable'],
447
				'custom_index_length' => $txt['not_applicable'],
448
			);
449
	}
450
	else
451
		$context['table_info'] = array(
452
			'data_length' => $txt['not_applicable'],
453
			'index_length' => $txt['not_applicable'],
454
			'fulltext_length' => $txt['not_applicable'],
455
			'custom_index_length' => $txt['not_applicable'],
456
		);
457
458
	// Format the data and index length in kilobytes.
459
	foreach ($context['table_info'] as $type => $size)
460
	{
461
		// If it's not numeric then just break.  This database engine doesn't support size.
462
		if (!is_numeric($size))
463
			break;
464
465
		$context['table_info'][$type] = comma_format($context['table_info'][$type] / 1024) . ' ' . $txt['search_method_kilobytes'];
466
	}
467
468
	$context['custom_index'] = !empty($modSettings['search_custom_index_config']);
469
	$context['partial_custom_index'] = !empty($modSettings['search_custom_index_resume']) && empty($modSettings['search_custom_index_config']);
470
	$context['double_index'] = !empty($context['fulltext_index']) && $context['custom_index'];
471
472
	createToken('admin-msmpost');
473
	createToken('admin-msm', 'get');
474
}
475
476
/**
477
 * Create a custom search index for the messages table.
478
 * Called by ?action=admin;area=managesearch;sa=createmsgindex.
479
 * Linked from the EditSearchMethod screen.
480
 * Requires the admin_forum permission.
481
 * Depending on the size of the message table, the process is divided in steps.
482
 *
483
 * @uses ManageSearch template, 'create_index', 'create_index_progress', and 'create_index_done'
484
 *  sub-templates.
485
 */
486
function CreateMessageIndex()
487
{
488
	global $modSettings, $context, $smcFunc, $db_prefix, $txt;
489
490
	// Scotty, we need more time...
491
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

491
	/** @scrutinizer ignore-unhandled */ @set_time_limit(600);

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...
492
	if (function_exists('apache_reset_timeout'))
493
		@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for apache_reset_timeout(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

493
		/** @scrutinizer ignore-unhandled */ @apache_reset_timeout();

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...
494
495
	$context[$context['admin_menu_name']]['current_subsection'] = 'method';
496
	$context['page_title'] = $txt['search_index_custom'];
497
498
	$messages_per_batch = 50;
499
500
	$index_properties = array(
501
		2 => array(
502
			'column_definition' => 'small',
503
			'step_size' => 1000000,
504
		),
505
		4 => array(
506
			'column_definition' => 'medium',
507
			'step_size' => 1000000,
508
			'max_size' => 16777215,
509
		),
510
		5 => array(
511
			'column_definition' => 'large',
512
			'step_size' => 100000000,
513
			'max_size' => 2000000000,
514
		),
515
	);
516
517
	if (isset($_REQUEST['resume']) && !empty($modSettings['search_custom_index_resume']))
518
	{
519
		$context['index_settings'] = $smcFunc['json_decode']($modSettings['search_custom_index_resume'], true);
520
		$context['start'] = (int) $context['index_settings']['resume_at'];
521
		unset($context['index_settings']['resume_at']);
522
		$context['step'] = 1;
523
	}
524
	else
525
	{
526
		$context['index_settings'] = array(
527
			'bytes_per_word' => isset($_REQUEST['bytes_per_word']) && isset($index_properties[$_REQUEST['bytes_per_word']]) ? (int) $_REQUEST['bytes_per_word'] : 2,
528
		);
529
		$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
530
		$context['step'] = isset($_REQUEST['step']) ? (int) $_REQUEST['step'] : 0;
531
532
		// admin timeouts are painful when building these long indexes - but only if we actually have such things enabled
533
		if (empty($modSettings['securityDisable']) && $_SESSION['admin_time'] + 3300 < time() && $context['step'] >= 1)
534
			$_SESSION['admin_time'] = time();
535
	}
536
537
	if ($context['step'] !== 0)
538
		checkSession('request');
539
540
	// Step 0: let the user determine how they like their index.
541
	if ($context['step'] === 0)
542
	{
543
		$context['sub_template'] = 'create_index';
544
	}
545
546
	// Step 1: insert all the words.
547
	if ($context['step'] === 1)
548
	{
549
		$context['sub_template'] = 'create_index_progress';
550
551
		if ($context['start'] === 0)
552
		{
553
			db_extend();
554
			$tables = $smcFunc['db_list_tables'](false, $db_prefix . 'log_search_words');
555
			if (!empty($tables))
556
			{
557
				$smcFunc['db_search_query']('drop_words_table', '
558
					DROP TABLE {db_prefix}log_search_words',
559
					array(
560
					)
561
				);
562
			}
563
564
			$smcFunc['db_create_word_search']($index_properties[$context['index_settings']['bytes_per_word']]['column_definition']);
565
566
			// Temporarily switch back to not using a search index.
567
			if (!empty($modSettings['search_index']) && $modSettings['search_index'] == 'custom')
568
				updateSettings(array('search_index' => ''));
569
570
			// Don't let simultanious processes be updating the search index.
571
			if (!empty($modSettings['search_custom_index_config']))
572
				updateSettings(array('search_custom_index_config' => ''));
573
		}
574
575
		$num_messages = array(
576
			'done' => 0,
577
			'todo' => 0,
578
		);
579
580
		$request = $smcFunc['db_query']('', '
581
			SELECT id_msg >= {int:starting_id} AS todo, COUNT(*) AS num_messages
582
			FROM {db_prefix}messages
583
			GROUP BY todo',
584
			array(
585
				'starting_id' => $context['start'],
586
			)
587
		);
588
		while ($row = $smcFunc['db_fetch_assoc']($request))
589
			$num_messages[empty($row['todo']) ? 'done' : 'todo'] = $row['num_messages'];
590
591
		if (empty($num_messages['todo']))
592
		{
593
			$context['step'] = 2;
594
			$context['percentage'] = 80;
595
			$context['start'] = 0;
596
		}
597
		else
598
		{
599
			// Number of seconds before the next step.
600
			$stop = time() + 3;
601
			while (time() < $stop)
602
			{
603
				$inserts = array();
604
				$request = $smcFunc['db_query']('', '
605
					SELECT id_msg, body
606
					FROM {db_prefix}messages
607
					WHERE id_msg BETWEEN {int:starting_id} AND {int:ending_id}
608
					LIMIT {int:limit}',
609
					array(
610
						'starting_id' => $context['start'],
611
						'ending_id' => $context['start'] + $messages_per_batch - 1,
612
						'limit' => $messages_per_batch,
613
					)
614
				);
615
				$forced_break = false;
616
				$number_processed = 0;
617
				while ($row = $smcFunc['db_fetch_assoc']($request))
618
				{
619
					// In theory it's possible for one of these to take friggin ages so add more timeout protection.
620
					if ($stop < time())
621
					{
622
						$forced_break = true;
623
						break;
624
					}
625
626
					$number_processed++;
627
					foreach (text2words($row['body'], $context['index_settings']['bytes_per_word'], true) as $id_word)
628
					{
629
						$inserts[] = array($id_word, $row['id_msg']);
630
					}
631
				}
632
				$num_messages['done'] += $number_processed;
633
				$num_messages['todo'] -= $number_processed;
634
				$smcFunc['db_free_result']($request);
635
636
				$context['start'] += $forced_break ? $number_processed : $messages_per_batch;
637
638
				if (!empty($inserts))
639
					$smcFunc['db_insert']('ignore',
640
						'{db_prefix}log_search_words',
641
						array('id_word' => 'int', 'id_msg' => 'int'),
642
						$inserts,
643
						array('id_word', 'id_msg')
644
					);
645
				if ($num_messages['todo'] === 0)
646
				{
647
					$context['step'] = 2;
648
					$context['start'] = 0;
649
					break;
650
				}
651
				else
652
					updateSettings(array('search_custom_index_resume' => $smcFunc['json_encode'](array_merge($context['index_settings'], array('resume_at' => $context['start'])))));
653
			}
654
655
			// Since there are still two steps to go, 80% is the maximum here.
656
			$context['percentage'] = round($num_messages['done'] / ($num_messages['done'] + $num_messages['todo']), 3) * 80;
657
		}
658
	}
659
660
	// Step 2: removing the words that occur too often and are of no use.
661
	elseif ($context['step'] === 2)
662
	{
663
		if ($context['index_settings']['bytes_per_word'] < 4)
664
			$context['step'] = 3;
665
		else
666
		{
667
			$stop_words = $context['start'] === 0 || empty($modSettings['search_stopwords']) ? array() : explode(',', $modSettings['search_stopwords']);
668
			$stop = time() + 3;
669
			$context['sub_template'] = 'create_index_progress';
670
			$max_messages = ceil(60 * $modSettings['totalMessages'] / 100);
671
672
			while (time() < $stop)
673
			{
674
				$request = $smcFunc['db_query']('', '
675
					SELECT id_word, COUNT(id_word) AS num_words
676
					FROM {db_prefix}log_search_words
677
					WHERE id_word BETWEEN {int:starting_id} AND {int:ending_id}
678
					GROUP BY id_word
679
					HAVING COUNT(id_word) > {int:minimum_messages}',
680
					array(
681
						'starting_id' => $context['start'],
682
						'ending_id' => $context['start'] + $index_properties[$context['index_settings']['bytes_per_word']]['step_size'] - 1,
683
						'minimum_messages' => $max_messages,
684
					)
685
				);
686
				while ($row = $smcFunc['db_fetch_assoc']($request))
687
					$stop_words[] = $row['id_word'];
688
				$smcFunc['db_free_result']($request);
689
690
				updateSettings(array('search_stopwords' => implode(',', $stop_words)));
691
692
				if (!empty($stop_words))
693
					$smcFunc['db_query']('', '
694
						DELETE FROM {db_prefix}log_search_words
695
						WHERE id_word in ({array_int:stop_words})',
696
						array(
697
							'stop_words' => $stop_words,
698
						)
699
					);
700
701
				$context['start'] += $index_properties[$context['index_settings']['bytes_per_word']]['step_size'];
702
				if ($context['start'] > $index_properties[$context['index_settings']['bytes_per_word']]['max_size'])
703
				{
704
					$context['step'] = 3;
705
					break;
706
				}
707
			}
708
			$context['percentage'] = 80 + round($context['start'] / $index_properties[$context['index_settings']['bytes_per_word']]['max_size'], 3) * 20;
709
		}
710
	}
711
712
	// Step 3: remove words not distinctive enough.
713
	if ($context['step'] === 3)
714
	{
715
		$context['sub_template'] = 'create_index_done';
716
717
		updateSettings(array('search_index' => 'custom', 'search_custom_index_config' => $smcFunc['json_encode']($context['index_settings'])));
718
		$smcFunc['db_query']('', '
719
			DELETE FROM {db_prefix}settings
720
			WHERE variable = {string:search_custom_index_resume}',
721
			array(
722
				'search_custom_index_resume' => 'search_custom_index_resume',
723
			)
724
		);
725
	}
726
}
727
728
/**
729
 * Get the installed Search API implementations.
730
 * This function checks for patterns in comments on top of the Search-API files!
731
 * In addition to filenames pattern.
732
 * It loads the search API classes if identified.
733
 * This function is used by EditSearchMethod to list all installed API implementations.
734
 */
735
function loadSearchAPIs()
736
{
737
	global $sourcedir, $txt;
738
739
	$apis = array();
740
	if ($dh = opendir($sourcedir))
741
	{
742
		while (($file = readdir($dh)) !== false)
743
		{
744
			if (is_file($sourcedir . '/' . $file) && preg_match('~^SearchAPI-([A-Za-z\d_]+)\.php$~', $file, $matches))
745
			{
746
				// Check this is definitely a valid API!
747
				$fp = fopen($sourcedir . '/' . $file, 'rb');
748
				$header = fread($fp, 4096);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

748
				$header = fread(/** @scrutinizer ignore-type */ $fp, 4096);
Loading history...
749
				fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

749
				fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
750
751
				if (strpos($header, '* SearchAPI-' . $matches[1] . '.php') !== false)
752
				{
753
					require_once($sourcedir . '/' . $file);
754
755
					$index_name = strtolower($matches[1]);
756
					$search_class_name = $index_name . '_search';
757
					$searchAPI = new $search_class_name();
758
759
					// No Support?  NEXT!
760
					if (!$searchAPI->is_supported)
761
						continue;
762
763
					$apis[$index_name] = array(
764
						'filename' => $file,
765
						'setting_index' => $index_name,
766
						'has_template' => in_array($index_name, array('custom', 'fulltext', 'standard')),
767
						'label' => $index_name && isset($txt['search_index_' . $index_name]) ? $txt['search_index_' . $index_name] : '',
768
						'desc' => $index_name && isset($txt['search_index_' . $index_name . '_desc']) ? $txt['search_index_' . $index_name . '_desc'] : '',
769
					);
770
				}
771
			}
772
		}
773
	}
774
	closedir($dh);
0 ignored issues
show
Bug introduced by
It seems like $dh can also be of type false; however, parameter $dir_handle of closedir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

774
	closedir(/** @scrutinizer ignore-type */ $dh);
Loading history...
775
776
	return $apis;
777
}
778
779
/**
780
 * Checks if the message table already has a fulltext index created and returns the key name
781
 * Determines if a db is capable of creating a fulltext index
782
 */
783
function detectFulltextIndex()
784
{
785
	global $smcFunc, $context, $db_prefix;
786
787
	// We need this for db_get_version
788
	db_extend();
789
790
	if ($smcFunc['db_title'] == 'PostgreSQL') {
791
		$request = $smcFunc['db_query']('', '
792
			SELECT
793
				indexname
794
			FROM pg_tables t
795
			LEFT OUTER JOIN
796
				( SELECT c.relname AS ctablename, ipg.relname AS indexname, indexrelname FROM pg_index x
797
						JOIN pg_class c ON c.oid = x.indrelid
798
						JOIN pg_class ipg ON ipg.oid = x.indexrelid
799
						JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid )
800
				AS foo
801
				ON t.tablename = foo.ctablename
802
			WHERE t.schemaname= {string:schema} and indexname = {string:messages_ftx}',
803
			array(
804
				'schema' => 'public',
805
				'messages_ftx' => $db_prefix . 'messages_ftx',
806
			)
807
		);
808
		while ($row = $smcFunc['db_fetch_assoc']($request))
809
			$context['fulltext_index'][] = $row['indexname'];
810
	}
811
	else
812
	{
813
		$request = $smcFunc['db_query']('', '
814
			SHOW INDEX
815
			FROM {db_prefix}messages',
816
			array(
817
			)
818
		);
819
		$context['fulltext_index'] = array();
820
		if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
821
		{
822
			while ($row = $smcFunc['db_fetch_assoc']($request))
823
			if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
824
				$context['fulltext_index'][] = $row['Key_name'];
825
			$smcFunc['db_free_result']($request);
826
827
			if (is_array($context['fulltext_index']))
828
				$context['fulltext_index'] = array_unique($context['fulltext_index']);
829
		}
830
831
		if (preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) !== 0)
832
			$request = $smcFunc['db_query']('', '
833
			SHOW TABLE STATUS
834
			FROM {string:database_name}
835
			LIKE {string:table_name}',
836
			array(
837
				'database_name' => '`' . strtr($match[1], array('`' => '')) . '`',
838
				'table_name' => str_replace('_', '\_', $match[2]) . 'messages',
839
			)
840
			);
841
		else
842
			$request = $smcFunc['db_query']('', '
843
			SHOW TABLE STATUS
844
			LIKE {string:table_name}',
845
			array(
846
				'table_name' => str_replace('_', '\_', $db_prefix) . 'messages',
847
			)
848
			);
849
850
		if ($request !== false)
851
		{
852
			while ($row = $smcFunc['db_fetch_assoc']($request))
853
			if (isset($row['Engine']) && strtolower($row['Engine']) != 'myisam' && !(strtolower($row['Engine']) == 'innodb' && version_compare($smcFunc['db_get_version'](), '5.6.4', '>=')))
854
				$context['cannot_create_fulltext'] = true;
855
			$smcFunc['db_free_result']($request);
856
		}
857
	}
858
}
859
860
?>