Passed
Pull Request — development (#3442)
by Elk
12:13 queued 06:23
created

ManageSearch::connectSphinxApi()   A

Complexity

Conditions 6
Paths 17

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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

314
			$fulltext_index = /** @scrutinizer ignore-call */ detectFulltextIndex();
Loading history...
315
		}
316
317
		// Creating index, removing or simply changing the one in use?
318
		$sa = $this->_req->getQuery('sa', 'trim', '');
319
		if ($sa === 'createfulltext')
320
		{
321
			checkSession('get');
322
			validateToken('admin-msm', 'get');
323
324
			alterFullTextIndex('{db_prefix}messages', 'body', true);
0 ignored issues
show
Bug introduced by
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

324
			/** @scrutinizer ignore-call */ 
325
   alterFullTextIndex('{db_prefix}messages', 'body', true);
Loading history...
325
			$fulltext_index = true;
326
		}
327
		elseif ($sa === 'removefulltext' && !empty($fulltext_index))
328
		{
329
			checkSession('get');
330
			validateToken('admin-msm', 'get');
331
332
			alterFullTextIndex('{db_prefix}messages', $fulltext_index);
333
			$fulltext_index = false;
334
335
			// Go back to the default search method.
336
			if (!empty($modSettings['search_index']) && $modSettings['search_index'] === 'fulltext')
337
			{
338
				updateSettings(array(
339
					'search_index' => '',
340
				));
341
			}
342
		}
343
		elseif ($sa === 'removecustom')
344
		{
345
			checkSession('get');
346
			validateToken('admin-msm', 'get');
347
348
			drop_log_search_words();
0 ignored issues
show
Bug introduced by
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

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

548
			list ($context['start'], $context['step'], $context['percentage']) = /** @scrutinizer ignore-call */ createSearchIndex($context['start'], $messages_per_batch, $index_properties[$context['index_settings']['bytes_per_word']]['column_definition'], $context['index_settings']);
Loading history...
549
		}
550
551
		// Step 2: removing the words that occur too often and are of no use.
552
		if ($context['step'] === 2)
553
		{
554
			if ($context['index_settings']['bytes_per_word'] < 4)
555
			{
556
				$context['step'] = 3;
557
			}
558
			else
559
			{
560
				list ($context['start'], $complete) = removeCommonWordsFromIndex($context['start'], $index_properties[$context['index_settings']['bytes_per_word']]);
0 ignored issues
show
Bug introduced by
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

560
				list ($context['start'], $complete) = /** @scrutinizer ignore-call */ removeCommonWordsFromIndex($context['start'], $index_properties[$context['index_settings']['bytes_per_word']]);
Loading history...
561
				if ($complete)
562
				{
563
					$context['step'] = 3;
564
				}
565
566
				$context['sub_template'] = 'create_index_progress';
567
568
				$context['percentage'] = 80 + round($context['start'] / $index_properties[$context['index_settings']['bytes_per_word']]['max_size'], 3) * 20;
569
			}
570
		}
571
572
		// Restore previous debug state
573
		$db_show_debug = $old_db_show_debug;
574
575
		// Step 3: everything done.
576
		if ($context['step'] === 3)
577
		{
578
			$context['sub_template'] = 'create_index_done';
579
580
			updateSettings(array('search_index' => 'custom', 'search_custom_index_config' => serialize($context['index_settings'])));
581
			removeSettings('search_custom_index_resume');
582
		}
583
	}
584
585
	/**
586
	 * Edit settings related to the sphinx or sphinxQL search function.
587
	 *
588
	 * - Called by ?action=admin;area=managesearch;sa=sphinx.
589
	 * - Checks if connection to search daemon is possible
590
	 */
591
	public function action_managesphinx()
592
	{
593
		global $txt, $context, $modSettings;
594
595
		// Saving the settings
596
		if (isset($this->_req->post->save))
597
		{
598
			checkSession();
599
			validateToken('admin-mssphinx');
600
			$this->_saveSphinxConfig();
601
		}
602
		// Checking if we can connect?
603
		elseif (isset($this->_req->post->checkconnect))
604
		{
605
			checkSession();
606
			validateToken('admin-mssphinx');
607
608
			// If they have not picked sphinx yet, let them know, but we can still check connections
609
			if (empty($modSettings['search_index']) || stripos($modSettings['search_index'], 'sphinx') === false)
610
			{
611
				$context['settings_message'][] = $txt['sphinx_test_not_selected'];
612
				$context['error_type'] = 'notice';
613
			}
614
615
			// Try to connect via Sphinx API?
616
			if (empty($modSettings['search_index']) || $modSettings['search_index'] === 'Sphinx')
617
			{
618
				$this->connectSphinxApi();
619
			}
620
621
			// Try to connect via SphinxQL
622
			if (empty($modSettings['search_index']) || $modSettings['search_index'] === 'Sphinxql')
623
			{
624
				$this->connectSphinxQL();
625
			}
626
		}
627
		elseif (isset($this->_req->post->createconfig))
628
		{
629
			checkSession();
630
			validateToken('admin-mssphinx');
631
			$this->_saveSphinxConfig();
632
633
			require_once(SUBSDIR . '/ManageSearch.subs.php');
634
635
			createSphinxConfig();
0 ignored issues
show
Bug introduced by
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

635
			/** @scrutinizer ignore-call */ 
636
   createSphinxConfig();
Loading history...
636
		}
637
638
		// Setup for the template
639
		$context[$context['admin_menu_name']]['current_subsection'] = 'managesphinx';
640
		$context['page_title'] = $txt['search_sphinx'];
641
		$context['page_description'] = $txt['sphinx_description'];
642
		$context['sub_template'] = 'manage_sphinx';
643
		createToken('admin-mssphinx');
644
	}
645
646
	/**
647
	 * Save the form values in modsettings
648
	 *
649
	 * @throws \Elk_Exception
650
	 */
651
	private function _saveSphinxConfig()
652
	{
653
		updateSettings(array(
654
			'sphinx_index_prefix' => rtrim($this->_req->post->sphinx_index_prefix, '/'),
655
			'sphinx_data_path' => rtrim($this->_req->post->sphinx_data_path, '/'),
656
			'sphinx_log_path' => rtrim($this->_req->post->sphinx_log_path, '/'),
657
			'sphinx_stopword_path' => $this->_req->post->sphinx_stopword_path,
658
			'sphinx_indexer_mem' => (int) $this->_req->post->sphinx_indexer_mem,
659
			'sphinx_searchd_server' => $this->_req->post->sphinx_searchd_server,
660
			'sphinx_searchd_port' => (int) $this->_req->post->sphinx_searchd_port,
661
			'sphinxql_searchd_port' => (int) $this->_req->post->sphinxql_searchd_port,
662
			'sphinx_max_results' => (int) $this->_req->post->sphinx_max_results,
663
		));
664
	}
665
666
	/**
667
	 * Attempt to connect to sphinx using the API methods
668
	 */
669
	public function connectSphinxApi()
670
	{
671
		global $txt, $modSettings, $context;
672
673
		// This is included with sphinx and not distrubuted with ElkArte
674
		if (file_exists(SOURCEDIR . '/sphinxapi.php'))
675
		{
676
			include_once(SOURCEDIR . '/sphinxapi.php');
677
			$server = !empty($modSettings['sphinx_searchd_server']) ? $modSettings['sphinx_searchd_server'] : 'localhost';
678
			$port = !empty($modSettings['sphinx_searchd_port']) ? $modSettings['sphinx_searchd_port'] : 9312;
679
680
			$mySphinx = new \SphinxClient();
0 ignored issues
show
Bug introduced by
The type SphinxClient was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
681
			$mySphinx->SetServer($server, (int) $port);
682
			$mySphinx->SetLimits(0, 25, 1);
683
684
			$index = (!empty($modSettings['sphinx_index_prefix']) ? $modSettings['sphinx_index_prefix'] : 'elkarte') . '_index';
685
			$request = $mySphinx->Query('ElkArte', $index);
686
687
			if ($request === false)
688
			{
689
				$context['settings_message'][] = $txt['sphinx_test_connect_failed'];
690
				$context['error_type'] = 'serious';
691
			}
692
			else
693
			{
694
				updateSettings(array('sphinx_searchd_server' => $server, 'sphinx_searchd_port' => $port));
695
				$context['settings_message'][] = $txt['sphinx_test_passed'];
696
			}
697
698
			return;
699
		}
700
701
		$context['settings_message'][] = $txt['sphinx_test_api_missing'];
702
		$context['error_type'] = 'serious';
703
	}
704
705
	/**
706
	 * Attempt to connect to Sphinx using the preferred QL way
707
	 */
708
	public function connectSphinxQL()
709
	{
710
		global $txt, $modSettings, $context;
711
712
		$server = !empty($modSettings['sphinx_searchd_server']) ? $modSettings['sphinx_searchd_server'] : 'localhost';
713
		$server = $server === 'localhost' ? '127.0.0.1' : $server;
714
		$port = !empty($modSettings['sphinxql_searchd_port']) ? $modSettings['sphinxql_searchd_port'] : '9306';
715
716
		set_error_handler(function () { /* ignore errors */ });
717
		try
718
		{
719
			$result = mysqli_connect($server, '', '', '', (int) $port);
0 ignored issues
show
Bug introduced by
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

719
			$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...
720
		}
721
		catch (\Exception $e)
722
		{
723
			$result = false;
724
		}
725
		finally
726
		{
727
			restore_error_handler();
728
		}
729
730
		if ($result === false)
731
		{
732
			$context['settings_message'][] = $txt['sphinxql_test_connect_failed'];
733
			$context['error_type'] = 'serious';
734
735
			return;
736
		}
737
738
		updateSettings(array('sphinx_searchd_server' => $server, 'sphinxql_searchd_port' => $port));
739
		$context['settings_message'][] = $txt['sphinxql_test_passed'];
740
	}
741
}
742