Issues (1696)

sources/ElkArte/AdminController/ManageSearch.php (9 issues)

1
<?php
2
3
/**
4
 * The admin screen to change the search settings.  Allows for the creation \
5
 * of search indexes and search weights
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 dev
15
 *
16
 */
17
18
namespace ElkArte\AdminController;
19
20
use ElkArte\AbstractController;
21
use ElkArte\Action;
22
use ElkArte\Helper\Util;
23
use ElkArte\Languages\Txt;
24
use ElkArte\Search\SearchApiWrapper;
25
use ElkArte\SettingsForm\SettingsForm;
26
use Exception;
27
use FilesystemIterator;
28
use GlobIterator;
29
use UnexpectedValueException;
30
31
/**
32
 * ManageSearch controller admin class.
33
 *
34
 * @package Search
35
 */
36
class ManageSearch extends AbstractController
37
{
38
	/**
39
	 * Main entry point for the admin search settings screen.
40
	 *
41
	 * What it does:
42
	 *
43
	 * - It checks permissions, and it forwards to the appropriate function based on
44
	 * the given sub-action.
45
	 * - Defaults to sub-action 'settings'.
46
	 * - Called by ?action=admin;area=managesearch.
47
	 * - Requires the admin_forum permission.
48
	 *
49
	 * @event integrate_sa_manage_search add new search actions
50
	 * @uses ManageSearch template.
51
	 * @uses Search language file.
52
	 * @see  AbstractController::action_index()
53
	 */
54
	public function action_index()
55
	{
56
		global $context, $txt;
57
58
		Txt::load('Search');
59
		theme()->getTemplates()->load('ManageSearch');
60
61
		$subActions = [
62
			'settings' => [$this, 'action_searchSettings_display', 'permission' => 'admin_forum'],
63
			'weights' => [$this, 'action_weight', 'permission' => 'admin_forum'],
64
			'method' => [$this, 'action_edit', 'permission' => 'admin_forum'],
65
			'createfulltext' => [$this, 'action_edit', 'permission' => 'admin_forum'],
66
			'removecustom' => [$this, 'action_edit', 'permission' => 'admin_forum'],
67
			'removefulltext' => [$this, 'action_edit', 'permission' => 'admin_forum'],
68
			'createmsgindex' => [$this, 'action_create', 'permission' => 'admin_forum'],
69
			'managemanticore' => [$this, 'action_managemanticore', 'permission' => 'admin_forum'],
70
			'managesphinxql' => [$this, 'action_managesphinx', 'permission' => 'admin_forum'],
71
		];
72
73
		// Control for actions
74
		$action = new Action('manage_search');
75
76
		// Default the sub-action to 'edit search method'.  Call integrate_sa_manage_search
77
		$subAction = $action->initialize($subActions, 'method');
78
79
		// Final bits
80
		$context['sub_action'] = $subAction;
81
		$context['page_title'] = $txt['search_settings_title'];
82
83
		// Create the tabs
84
		$context[$context['admin_menu_name']]['object']->prepareTabData([
85
			'title' => 'manage_search',
86
			'description' => 'search_settings_desc',
87
			'prefix' => 'search',
88
			'help' => 'search']
89
		);
90
91
		// Call the right function for this sub-action.
92
		$action->dispatch($subAction);
93
	}
94
95
	/**
96
	 * Edit some general settings related to the search function.
97
	 *
98
	 * - Called by ?action=admin;area=managesearch;sa=settings.
99
	 * - Requires the admin_forum permission.
100
	 *
101
	 * @event integrate_save_search_settings
102
	 * @uses ManageSearch template, 'modify_settings' sub-template.
103
	 */
104
	public function action_searchSettings_display(): void
105
	{
106
		global $txt, $context, $modSettings;
107
108
		// Initialize the form
109
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
110
111
		// Initialize it with our settings
112
		$settingsForm->setConfigVars($this->_settings());
113
114
		$context['page_title'] = $txt['search_settings_title'];
115
		$context['sub_template'] = 'show_settings';
116
117
		$context['search_engines'] = [];
118
		if (!empty($modSettings['additional_search_engines']))
119
		{
120
			$context['search_engines'] = Util::unserialize($modSettings['additional_search_engines']);
121
			$context['search_engines'] = $context['search_engines'] === false ? [] : $context['search_engines'];
122
		}
123
124
		for ($count = 0; $count < 3; $count++)
125
		{
126
			$context['search_engines'][] = [
127
				'name' => '',
128
				'url' => '',
129
				'separator' => '',
130
			];
131
		}
132
133
		// A form was submitted.
134
		if (isset($this->_req->query->save))
135
		{
136
			checkSession();
137
138
			call_integration_hook('integrate_save_search_settings');
139
140
			if (empty($this->_req->post->search_results_per_page))
141
			{
142
				$this->_req->post->search_results_per_page = empty($modSettings['search_results_per_page']) ? $modSettings['defaultMaxMessages'] : $modSettings['search_results_per_page'];
143
			}
144
145
			$new_engines = [];
146
			foreach ($this->_req->post->engine_name as $id => $searchengine)
147
			{
148
				$url = trim(str_replace(['"', '<', '>'], ['&quot;', '&lt;', '&gt;'], $this->_req->post->engine_url[$id]));
149
				// If no url, forget it
150
				if (empty($searchengine))
151
				{
152
					continue;
153
				}
154
155
				if (empty($url))
156
				{
157
					continue;
158
				}
159
160
				if (!filter_var($url, FILTER_VALIDATE_URL))
161
				{
162
					continue;
163
				}
164
165
				$new_engines[] = [
166
					'name' => trim(Util::htmlspecialchars($searchengine, ENT_COMPAT)),
167
					'url' => $url,
168
					'separator' => trim(Util::htmlspecialchars(empty($this->_req->post->engine_separator[$id]) ? '+' : $this->_req->post->engine_separator[$id], ENT_COMPAT)),
169
				];
170
			}
171
172
			updateSettings([
173
				'additional_search_engines' => $new_engines === [] ? '' : serialize($new_engines)
174
			]);
175
176
			$settingsForm->setConfigValues((array) $this->_req->post);
177
			$settingsForm->save();
178
			redirectexit('action=admin;area=managesearch;sa=settings;' . $context['session_var'] . '=' . $context['session_id']);
179
		}
180
181
		// Prep the template!
182
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'managesearch', 'save', 'sa' => 'settings']);
183
		$context['settings_title'] = $txt['search_settings_title'];
184 2
185
		$settingsForm->prepare();
186 2
	}
187
188
	/**
189
	 * Retrieve admin search settings
190
	 *
191 2
	 * @event integrate_modify_search_settings
192
	 */
193
	private function _settings()
194
	{
195 2
		global $txt, $modSettings;
196 2
197
		// What are we editing anyway?
198 2
		$config_vars = [
199
			// Permission...
200
			['permissions', 'search_posts', 'collapsed' => 'true'],
201
			// Some simple settings.
202
			['check', 'search_dropdown'],
203
			['int', 'search_results_per_page'],
204 2
			['int', 'search_max_results', 'subtext' => $txt['search_max_results_disable']],
205 2
			// Some limitations.
206
			['int', 'search_floodcontrol_time', 'subtext' => $txt['search_floodcontrol_time_desc'], 6, 'postinput' => $txt['seconds']],
207
			['title', 'additional_search_engines'],
208 2
			['callback', 'external_search_engines'],
209
		];
210 2
211
		// Perhaps the search method wants to add some settings?
212
		$searchAPI = new SearchApiWrapper(empty($modSettings['search_index']) ? '' : $modSettings['search_index']);
213
		$searchAPI->searchSettings($config_vars);
214
215
		// Add new settings with a nice hook, makes them available for admin settings search as well
216 2
		call_integration_hook('integrate_modify_search_settings', [&$config_vars]);
217
218 2
		return $config_vars;
219
	}
220
221
	/**
222
	 * Return the search settings for use in admin search
223
	 */
224
	public function settings_search()
225
	{
226
		return $this->_settings();
227
	}
228
229
	/**
230
	 * Edit the relative weight of the search factors.
231
	 *
232
	 * - Called by ?action=admin;area=managesearch;sa=weights.
233
	 * - Requires the admin_forum permission.
234
	 *
235
	 * @event integrate_modify_search_weights
236
	 * @event integrate_save_search_weights
237
	 * @uses ManageSearch template, 'modify_weights' sub-template.
238
	 */
239
	public function action_weight(): void
240
	{
241
		global $txt, $context, $modSettings;
242
243
		$context['page_title'] = $txt['search_weights_title'];
244
		$context['sub_template'] = 'modify_weights';
245
246
		$factors = [
247
			'search_weight_frequency',
248
			'search_weight_age',
249
			'search_weight_length',
250
			'search_weight_subject',
251
			'search_weight_first_message',
252
			'search_weight_sticky',
253
			'search_weight_likes',
254
		];
255
256
		call_integration_hook('integrate_modify_search_weights', [&$factors]);
257
258
		// A form was submitted.
259
		if (isset($this->_req->post->save))
260
		{
261
			checkSession();
262
			validateToken('admin-msw');
263
264
			call_integration_hook('integrate_save_search_weights');
265
266
			$changes = [];
267
			foreach ($factors as $factor)
268
			{
269
				$changes[$factor] = (int) $this->_req->post->{$factor};
270
			}
271
272
			updateSettings($changes);
273
		}
274
275
		$context['relative_weights'] = ['total' => 0];
276
		foreach ($factors as $factor)
277
		{
278
			$context['relative_weights']['total'] += $modSettings[$factor] ?? 0;
279
		}
280
281
		foreach ($factors as $factor)
282
		{
283
			$context['relative_weights'][$factor] = round(100 * ($modSettings[$factor] ?? 0) / $context['relative_weights']['total'], 1);
284
		}
285
286
		createToken('admin-msw');
287
	}
288
289
	/**
290
	 * Edit the search method and search index used.
291
	 *
292
	 * What it does:
293
	 *
294
	 * - Calculates the size of the current search indexes in use.
295
	 * - Allows to create and delete a fulltext index on the messages table.
296
	 * - Allows to delete a custom index (that action_create() created).
297
	 * - Called by ?action=admin;area=managesearch;sa=method.
298
	 * - Requires the admin_forum permission.
299
	 *
300
	 * @uses ManageSearch template, 'select_search_method' sub-template.
301
	 */
302
	public function action_edit(): void
303
	{
304
		global $txt, $context, $modSettings;
305
306
		// Need to work with some db search stuffs
307
		$db_search = db_search();
308
		require_once(SUBSDIR . '/ManageSearch.subs.php');
309
310
		$context[$context['admin_menu_name']]['current_subsection'] = 'method';
311
		$context['page_title'] = $txt['search_method_title'];
312
		$context['sub_template'] = 'select_search_method';
313
		$context['supports_fulltext'] = $db_search->search_support('fulltext');
314
		$context['fulltext_index'] = false;
315
316
		// Load any apis.
317
		$context['search_apis'] = $this->loadSearchAPIs();
318
319
		// Detect whether a fulltext index is set.
320
		if ($context['supports_fulltext'])
321
		{
322
			$fulltext_index = detectFulltextIndex();
0 ignored issues
show
The function detectFulltextIndex was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

322
			$fulltext_index = /** @scrutinizer ignore-call */ detectFulltextIndex();
Loading history...
323
		}
324
325
		// Creating index, removing or simply changing the one in use?
326
		$sa = $this->_req->getQuery('sa', 'trim', '');
327
		if ($sa === 'createfulltext')
328
		{
329
			checkSession('get');
330
			validateToken('admin-msm', 'get');
331
332
			alterFullTextIndex('{db_prefix}messages', ['body', 'subject', 'body,subject'], true);
0 ignored issues
show
The function alterFullTextIndex was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

332
			/** @scrutinizer ignore-call */ 
333
   alterFullTextIndex('{db_prefix}messages', ['body', 'subject', 'body,subject'], true);
Loading history...
333
			$fulltext_index = true;
334
		}
335
		elseif ($sa === 'removefulltext' && !empty($fulltext_index))
336
		{
337
			checkSession('get');
338
			validateToken('admin-msm', 'get');
339
340
			alterFullTextIndex('{db_prefix}messages', $fulltext_index);
341
			$fulltext_index = false;
342
343
			// Go back to the default search method.
344
			if (!empty($modSettings['search_index']) && $modSettings['search_index'] === 'fulltext')
345
			{
346
				updateSettings([
347
					'search_index' => '',
348
				]);
349
			}
350
		}
351
		elseif ($sa === 'removecustom')
352
		{
353
			checkSession('get');
354
			validateToken('admin-msm', 'get');
355
356
			drop_log_search_words();
0 ignored issues
show
The function drop_log_search_words was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

356
			/** @scrutinizer ignore-call */ 
357
   drop_log_search_words();
Loading history...
357
358
			updateSettings([
359
				'search_custom_index_config' => '',
360
				'search_custom_index_resume' => '',
361
			]);
362
363
			// Go back to the default search method.
364
			if (!empty($modSettings['search_index']) && $modSettings['search_index'] === 'custom')
365
			{
366
				updateSettings([
367
					'search_index' => '',
368
				]);
369
			}
370
		}
371
		elseif (isset($this->_req->post->save))
372
		{
373
			checkSession();
374
			validateToken('admin-msmpost');
375
376
			updateSettings([
377
				'search_index' => empty($this->_req->post->search_index) || (!in_array($this->_req->post->search_index, ['fulltext', 'custom']) && !isset($context['search_apis'][$this->_req->post->search_index])) ? '' : $this->_req->post->search_index,
378
				'search_force_index' => isset($this->_req->post->search_force_index) ? '1' : '0',
379
				'search_match_words' => isset($this->_req->post->search_match_words) ? '1' : '0',
380
			]);
381
		}
382
383
		$table_info_defaults = [
384
			'data_length' => 0,
385
			'index_length' => 0,
386
			'fulltext_length' => 0,
387
			'custom_index_length' => 0,
388
		];
389
390
		// Get some info about the messages table, to show its size and index size.
391
		if (method_exists($db_search, 'membersTableInfo'))
392
		{
393
			$context['table_info'] = array_merge($table_info_defaults, $db_search->membersTableInfo());
394
		}
395
		else
396
		{
397
			// Here may be wolves.
398
			$context['table_info'] = [
399
				'data_length' => $txt['not_applicable'],
400
				'index_length' => $txt['not_applicable'],
401
				'fulltext_length' => $txt['not_applicable'],
402
				'custom_index_length' => $txt['not_applicable'],
403
			];
404
		}
405
406
		// Format the data and index length in human-readable form.
407
		foreach ($context['table_info'] as $type => $size)
408
		{
409
			// If it's not numeric then just break.  This database engine doesn't support size.
410
			if (!is_numeric($size))
411
			{
412
				break;
413
			}
414
415
			$context['table_info'][$type] = byte_format($context['table_info'][$type]);
416
		}
417
418
		$context['custom_index'] = !empty($modSettings['search_custom_index_config']);
419
		$context['partial_custom_index'] = !empty($modSettings['search_custom_index_resume']) && empty($modSettings['search_custom_index_config']);
420
		$context['double_index'] = !empty($context['fulltext_index']) && $context['custom_index'];
421
		$context['fulltext_index'] = !empty($fulltext_index);
422
423
		createToken('admin-msmpost');
424
		createToken('admin-msm', 'get');
425
	}
426
427
	/**
428
	 * Get the installed Search API implementations.
429
	 *
430
	 * - This function checks for patterns in comments on top of the Search-API files!
431
	 * - It loads the search API classes if identified.
432
	 * - This function is used by action_edit to list all installed API implementations.
433
	 */
434
	private function loadSearchAPIs(): array
435
	{
436
		global $txt;
437
438
		$apis = [];
439
		try
440
		{
441
			$files = new GlobIterator(SOURCEDIR . '/ElkArte/Search/API/*.php', FilesystemIterator::SKIP_DOTS);
442
			foreach ($files as $file)
443
			{
444
				if ($file->isFile())
445
				{
446
					$index_name = $file->getBasename('.php');
447
					$common_name = strtolower($index_name);
448
449
					if ($common_name === 'searchapi')
450
					{
451
						continue;
452
					}
453
454
					$apis[$index_name] = [
455
						'filename' => $file->getFilename(),
456
						'setting_index' => $index_name,
457
						'has_template' => in_array($common_name, ['custom', 'fulltext', 'standard']),
458
						'label' => $index_name && isset($txt['search_index_' . $common_name]) ? str_replace('{managesearch_url}', getUrl('admin', ['action' => 'admin', 'area' => 'managesearch', 'sa' => 'manage' . $common_name]), $txt['search_index_' . $common_name]) : '',
459
						'desc' => $index_name && isset($txt['search_index_' . $common_name . '_desc']) ? str_replace('{managesearch_url}', getUrl('admin', ['action' => 'admin', 'area' => 'managesearch', 'sa' => 'manage' . $common_name]), $txt['search_index_' . $common_name . '_desc']) : '',
460
					];
461
				}
462
			}
463
		}
464
		catch (UnexpectedValueException)
465
		{
466
			// @todo for now just passthrough
467
		}
468
469
		return $apis;
470
	}
471
472
	/**
473
	 * Create a custom search index for the messages table.
474
	 *
475
	 * What it does:
476
	 *
477
	 * - Called by ?action=admin;area=managesearch;sa=createmsgindex.
478
	 * - Linked from the action_edit screen.
479
	 * - Requires the admin_forum permission.
480
	 * - Depending on the size of the message table, the process is divided in steps.
481
	 *
482
	 * @uses ManageSearch template, 'create_index_progress', and 'create_index_done'
483
	 * sub-templates.
484
	 */
485
	public function action_create(): void
486
	{
487
		global $modSettings, $context, $txt, $db_show_debug;
488
489
		// Scotty, we need more time...
490
		detectServer()->setTimeLimit(600);
491
492
		$context[$context['admin_menu_name']]['current_subsection'] = 'method';
493
		$context['page_title'] = $txt['search_index_custom'];
494
		$context['sub_template'] = 'create_index_progress';
495
496
		$messages_per_batch = 75;
497
498
		// Resume building an index that was not completed
499
		if (isset($this->_req->query->resume) && !empty($modSettings['search_custom_index_resume']))
500
		{
501
			$context['index_settings'] = Util::unserialize($modSettings['search_custom_index_resume']);
502
			$context['start'] = (int) $context['index_settings']['resume_at'];
503
			unset($context['index_settings']['resume_at']);
504
			$context['step'] = 1;
505
		}
506
		else
507
		{
508
			$context['index_settings'] = [];
509
			$context['start'] = $this->_req->getPost('start', 'intval', 0);
510
			$context['step'] = $this->_req->getPost('step', 'intval', 1);
511
		}
512
513
		checkSession('request');
514
515
		// Admin timeouts are painful when building these long indexes
516
		$_SESSION['admin_time'] = time();
517
518
		require_once(SUBSDIR . '/ManageSearch.subs.php');
519
520
		// Logging may cause session issues with many queries
521
		$old_db_show_debug = $db_show_debug;
522
		$db_show_debug = false;
523
524
		// Step 1: insert all the words.
525
		if ($context['step'] === 1)
526
		{
527
			$context['sub_template'] = 'create_index_progress';
528
			[$context['start'], $context['step'], $context['percentage']] = createSearchIndex($context['start'], $messages_per_batch);
0 ignored issues
show
The function createSearchIndex was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

528
			[$context['start'], $context['step'], $context['percentage']] = /** @scrutinizer ignore-call */ createSearchIndex($context['start'], $messages_per_batch);
Loading history...
529
		}
530
531
		// Step 2: removing the words that occur too often and are of no use.
532
		if ($context['step'] === 2)
533
		{
534
			[$context['start'], $complete, $context['percentage']] = removeCommonWordsFromIndex($context['start']);
0 ignored issues
show
The function removeCommonWordsFromIndex was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

534
			[$context['start'], $complete, $context['percentage']] = /** @scrutinizer ignore-call */ removeCommonWordsFromIndex($context['start']);
Loading history...
535
			if ($complete)
536
			{
537
				$context['step'] = 3;
538
			}
539
540
			$context['sub_template'] = 'create_index_progress';
541
		}
542
543
		// Restore previous debug state
544
		$db_show_debug = $old_db_show_debug;
545
546
		// Step 3: everything done.
547
		if ($context['step'] === 3)
548
		{
549
			$context['sub_template'] = 'create_index_done';
550
551
			updateSettings(['search_index' => 'custom', 'search_custom_index_config' => serialize($context['index_settings'])]);
552
			removeSettings('search_custom_index_resume');
553
		}
554
	}
555
556
	/**
557
	 * Edit settings related to the sphinxQL search function.
558
	 *
559
	 * - Called by ?action=admin;area=managesearch;sa=sphinx.
560
	 * - Checks if connection to search daemon is possible
561
	 */
562
	public function action_managesphinx(): void
563
	{
564
		global $txt, $context, $modSettings;
565
566
		// Saving the settings
567
		if (isset($this->_req->post->save))
568
		{
569
			checkSession();
570
			validateToken('admin-mssphinx');
571
			$this->_saveSphinxConfig();
572
		}
573
		// Checking if we can connect?
574
		elseif (isset($this->_req->post->checkconnect))
575
		{
576
			checkSession();
577
			validateToken('admin-mssphinx');
578
579
			// If they have not picked sphinx yet, let them know, but we can still check connections
580
			if (empty($modSettings['search_index']) || stripos($modSettings['search_index'], 'sphinx') === false)
581
			{
582
				$context['settings_message'][] = $txt['sphinx_test_not_selected'];
583
				$context['error_type'] = 'notice';
584
			}
585
586
			// Try to connect via SphinxQL
587
			if (empty($modSettings['search_index']) || $modSettings['search_index'] === 'Sphinxql')
588
			{
589
				$this->connectSphinxQL();
590
			}
591
		}
592
		elseif (isset($this->_req->post->createconfig))
593
		{
594
			checkSession();
595
			validateToken('admin-mssphinx');
596
			$this->_saveSphinxConfig();
597
598
			require_once(SUBSDIR . '/ManageSearch.subs.php');
599
600
			createSphinxConfig();
0 ignored issues
show
The function createSphinxConfig was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

600
			/** @scrutinizer ignore-call */ 
601
   createSphinxConfig();
Loading history...
601
		}
602
603
		// Setup for the template
604
		$context[$context['admin_menu_name']]['current_subsection'] = 'managesphinx';
605
		$context['page_title'] = $txt['search_sphinx'];
606
		$context['page_description'] = $txt['sphinx_create_config'];
607
		$context['sub_template'] = 'manage_sphinx';
608
		createToken('admin-mssphinx');
609
	}
610
611
	/**
612
	 * Save the form values in modsettings
613
	 */
614
	private function _saveSphinxConfig(): void
615
	{
616
		updateSettings([
617
			'sphinx_index_prefix' => rtrim($this->_req->post->sphinx_index_prefix, '/'),
618
			'sphinx_data_path' => rtrim($this->_req->post->sphinx_data_path, '/'),
619
			'sphinx_log_path' => rtrim($this->_req->post->sphinx_log_path, '/'),
620
			'sphinx_stopword_path' => $this->_req->getPost('sphinx_stopword_path', 'trim', ''),
621
			'sphinx_indexer_mem' => $this->_req->getPost('sphinx_indexer_mem', 'intval', 128),
622
			'sphinx_searchd_server' => $this->_req->getPost('sphinx_searchd_server', 'trim', 'localhost'),
623
			'sphinxql_searchd_port' => $this->_req->getPost('sphinxql_searchd_port', 'intval', 0),
624
			'sphinx_max_results' => $this->_req->getPost('sphinx_max_results', 'intval', 0)
625
		]);
626
	}
627
628
	/**
629
	 * Attempt to connect to Sphinx using the preferred QL way
630
	 */
631
	public function connectSphinxQL(): void
632
	{
633
		global $txt, $modSettings, $context;
634
635
		$server = empty($modSettings['sphinx_searchd_server']) ? 'localhost' : $modSettings['sphinx_searchd_server'];
636
		$server = $server === 'localhost' ? '127.0.0.1' : $server;
637
		$port = empty($modSettings['sphinxql_searchd_port']) ? '9306' : $modSettings['sphinxql_searchd_port'];
638
639
		set_error_handler(static function () { /* ignore errors */ });
640
		try
641
		{
642
			$result = mysqli_connect($server, '', '', '', (int) $port);
0 ignored issues
show
The call to mysqli_connect() has too few arguments starting with socket. ( Ignorable by Annotation )

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

642
			$result = /** @scrutinizer ignore-call */ mysqli_connect($server, '', '', '', (int) $port);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
643
		}
644
		catch (Exception)
645
		{
646
			$result = false;
647
		}
648
		finally
649
		{
650
			restore_error_handler();
651
		}
652
653
		if ($result === false)
654
		{
655
			$context['settings_message'][] = $txt['sphinxql_test_connect_failed'];
656
			$context['error_type'] = 'serious';
657
658
			return;
659
		}
660
661
		updateSettings(['sphinx_searchd_server' => $server, 'sphinxql_searchd_port' => $port]);
662
		$context['settings_message'][] = $txt['sphinxql_test_passed'];
663
	}
664
665
	/**
666
	 * Edit settings related to the Manticore search function.
667
	 *
668
	 * - Called by ?action=admin;area=managesearch;sa=manticore.
669
	 * - Checks if connection to search daemon is possible
670
	 */
671
	public function action_managemanticore(): void
672
	{
673
		global $txt, $context, $modSettings;
674
675
		// Saving the settings
676
		if (isset($this->_req->post->save))
677
		{
678
			checkSession();
679
			validateToken('admin-msmanticore');
680
			$this->_saveManticoreConfig();
681
		}
682
		// Checking if we can connect?
683
		elseif (isset($this->_req->post->checkconnect))
684
		{
685
			checkSession();
686
			validateToken('admin-msmanticore');
687
688
			// If they have not picked manticore yet, let them know, but we can still check connections
689
			if (empty($modSettings['search_index']) || stripos($modSettings['search_index'], 'manticore') === false)
690
			{
691
				$context['settings_message'][] = $txt['manticore_test_not_selected'];
692
				$context['error_type'] = 'notice';
693
			}
694
695
			// Try to connect
696
			//if (empty($modSettings['search_index']) || $modSettings['search_index'] === 'Manticore')
697
			//{
698
				$this->connectManticore();
699
			//}
700
		}
701
		elseif (isset($this->_req->post->createconfig))
702
		{
703
			checkSession();
704
			validateToken('admin-msmanticore');
705
			$this->_saveManticoreConfig();
706
707
			require_once(SUBSDIR . '/ManageSearch.subs.php');
708
709
			createManticoreConfig();
0 ignored issues
show
The function createManticoreConfig was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

709
			/** @scrutinizer ignore-call */ 
710
   createManticoreConfig();
Loading history...
710
		}
711
712
		// Setup for the template
713
		$context[$context['admin_menu_name']]['current_subsection'] = 'managemanticore';
714
		$context['page_title'] = $txt['search_manticore'];
715
		$context['page_description'] = $txt['manticore_create_config'];
716
		$context['sub_template'] = 'manage_manticore';
717
		createToken('admin-msmanticore');
718
	}
719
720
	/**
721
	 * Save the form values
722
	 */
723
	private function _saveManticoreConfig(): void
724
	{
725
		updateSettings([
726
			'manticore_index_prefix' => rtrim($this->_req->post->manticore_index_prefix, '/'),
727
			'manticore_data_path' => rtrim($this->_req->post->manticore_data_path, '/'),
728
			'manticore_log_path' => rtrim($this->_req->post->manticore_log_path, '/'),
729
			'manticore_stopword_path' => $this->_req->getPost('manticore_stopword_path', 'trim', ''),
730
			'manticore_indexer_mem' => $this->_req->getPost('manticore_indexer_mem', 'intval', 128),
731
			'manticore_searchd_server' => $this->_req->getPost('manticore_searchd_server', 'trim', 'localhost'),
732
			'manticore_searchd_port' => $this->_req->getPost('manticore_searchd_port', 'intval', 0),
733
			'manticore_max_results' => $this->_req->getPost('manticore_max_results', 'intval', 0)
734
		]);
735
	}
736
737
	/**
738
	 * Attempt to connect to Sphinx using the preferred QL way
739
	 */
740
	public function connectManticore(): void
741
	{
742
		global $txt, $modSettings, $context;
743
744
		$server = empty($modSettings['manticore_searchd_server']) ? 'localhost' : $modSettings['manticore_searchd_server'];
745
		$server = $server === 'localhost' ? '127.0.0.1' : $server;
746
		$port = empty($modSettings['manticore_searchd_port']) ? '9306' : $modSettings['manticore_searchd_port'];
747
748
		set_error_handler(static function () { /* ignore errors */ });
749
		try
750
		{
751
			$result = mysqli_connect($server, '', '', '', (int) $port);
0 ignored issues
show
The call to mysqli_connect() has too few arguments starting with socket. ( Ignorable by Annotation )

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

751
			$result = /** @scrutinizer ignore-call */ mysqli_connect($server, '', '', '', (int) $port);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
752
		}
753
		catch (Exception)
754
		{
755
			$result = false;
756
		}
757
		finally
758
		{
759
			restore_error_handler();
760
		}
761
762
		if ($result === false)
763
		{
764
			$context['settings_message'][] = $txt['manticore_test_connect_failed'];
765
			$context['error_type'] = 'serious';
766
767
			return;
768
		}
769
770
		updateSettings(['manticore_searchd_server' => $server, 'manticore_searchd_port' => $port]);
771
		$context['settings_message'][] = $txt['manticore_test_passed'];
772
	}
773
}
774