Issues (1065)

Sources/ManageServer.php (1 issue)

1
<?php
2
3
/**
4
 * Contains all the functionality required to be able to edit the core server
5
 * settings. This includes anything from which an error may result in the forum
6
 * destroying itself in a firey fury.
7
 *
8
 * Adding options to one of the setting screens isn't hard. Call prepareDBSettingsContext;
9
 * The basic format for a checkbox is:
10
 * 		array('check', 'nameInModSettingsAndSQL'),
11
 * And for a text box:
12
 * 		array('text', 'nameInModSettingsAndSQL')
13
 * (NOTE: You have to add an entry for this at the bottom!)
14
 *
15
 * In these cases, it will look for $txt['nameInModSettingsAndSQL'] as the description,
16
 * and $helptxt['nameInModSettingsAndSQL'] as the help popup description.
17
 *
18
 * Here's a quick explanation of how to add a new item:
19
 *
20
 * - A text input box.  For textual values.
21
 * 		array('text', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
22
 * - A text input box.  For numerical values.
23
 * 		array('int', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
24
 * - A text input box.  For floating point values.
25
 * 		array('float', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
26
 * - A large text input box. Used for textual values spanning multiple lines.
27
 * 		array('large_text', 'nameInModSettingsAndSQL', 'OptionalNumberOfRows'),
28
 * - A check box.  Either one or zero. (boolean)
29
 * 		array('check', 'nameInModSettingsAndSQL'),
30
 * - A selection box.  Used for the selection of something from a list.
31
 * 		array('select', 'nameInModSettingsAndSQL', array('valueForSQL' => $txt['displayedValue'])),
32
 * 		Note that just saying array('first', 'second') will put 0 in the SQL for 'first'.
33
 * - A password input box. Used for passwords, no less!
34
 * 		array('password', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
35
 * - A permission - for picking groups who have a permission.
36
 * 		array('permissions', 'manage_groups'),
37
 * - A BBC selection box.
38
 * 		array('bbc', 'sig_bbc'),
39
 * - A list of boards to choose from
40
 *  	array('boards', 'likes_boards'),
41
 *  	Note that the storage in the database is as 1,2,3,4
42
 *
43
 * For each option:
44
 * 	- type (see above), variable name, size/possible values.
45
 * 	  OR make type '' for an empty string for a horizontal rule.
46
 *  - SET preinput - to put some HTML prior to the input box.
47
 *  - SET postinput - to put some HTML following the input box.
48
 *  - SET invalid - to mark the data as invalid.
49
 *  - PLUS you can override label and help parameters by forcing their keys in the array, for example:
50
 *  	array('text', 'invalidlabel', 3, 'label' => 'Actual Label')
51
 *
52
 * Simple Machines Forum (SMF)
53
 *
54
 * @package SMF
55
 * @author Simple Machines https://www.simplemachines.org
56
 * @copyright 2022 Simple Machines and individual contributors
57
 * @license https://www.simplemachines.org/about/smf/license.php BSD
58
 *
59
 * @version 2.1.3
60
 */
61
62
use SMF\Cache\CacheApi;
63
use SMF\Cache\CacheApiInterface;
64
65
if (!defined('SMF'))
66
	die('No direct access...');
67
68
/**
69
 * This is the main dispatcher. Sets up all the available sub-actions, all the tabs and selects
70
 * the appropriate one based on the sub-action.
71
 *
72
 * Requires the admin_forum permission.
73
 * Redirects to the appropriate function based on the sub-action.
74
 *
75
 * Uses edit_settings adminIndex.
76
 */
77
function ModifySettings()
78
{
79
	global $context, $txt, $boarddir;
80
81
	// This is just to keep the database password more secure.
82
	isAllowedTo('admin_forum');
83
84
	// Load up all the tabs...
85
	$context[$context['admin_menu_name']]['tab_data'] = array(
86
		'title' => $txt['admin_server_settings'],
87
		'help' => 'serversettings',
88
		'description' => $txt['admin_basic_settings'],
89
	);
90
91
	checkSession('request');
92
93
	// The settings are in here, I swear!
94
	loadLanguage('ManageSettings');
95
96
	$context['page_title'] = $txt['admin_server_settings'];
97
	$context['sub_template'] = 'show_settings';
98
99
	$subActions = array(
100
		'general' => 'ModifyGeneralSettings',
101
		'database' => 'ModifyDatabaseSettings',
102
		'cookie' => 'ModifyCookieSettings',
103
		'security' => 'ModifyGeneralSecuritySettings',
104
		'cache' => 'ModifyCacheSettings',
105
		'export' => 'ModifyExportSettings',
106
		'loads' => 'ModifyLoadBalancingSettings',
107
		'phpinfo' => 'ShowPHPinfoSettings',
108
	);
109
110
	// Warn the user if there's any relevant information regarding Settings.php.
111
	$settings_not_writable = !is_writable($boarddir . '/Settings.php');
112
	$settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php');
113
114
	if ($settings_backup_fail)
115
		$context['settings_message'] = array(
116
			'label' => $txt['admin_backup_fail'],
117
			'tag' => 'div',
118
			'class' => 'centertext strong'
119
		);
120
121
	$context['settings_not_writable'] = $settings_not_writable;
122
123
	call_integration_hook('integrate_server_settings', array(&$subActions));
124
125
	// By default we're editing the core settings
126
	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'general';
127
128
	$context['sub_action'] = $_REQUEST['sa'];
129
130
	// Call the right function for this sub-action.
131
	call_helper($subActions[$_REQUEST['sa']]);
132
}
133
134
/**
135
 * General forum settings - forum name, maintenance mode, etc.
136
 * Practically, this shows an interface for the settings in Settings.php to be changed.
137
 *
138
 * - Requires the admin_forum permission.
139
 * - Uses the edit_settings administration area.
140
 * - Contains the actual array of settings to show from Settings.php.
141
 * - Accessed from ?action=admin;area=serversettings;sa=general.
142
 *
143
 * @param bool $return_config Whether to return the $config_vars array (for pagination purposes)
144
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
145
 */
146
function ModifyGeneralSettings($return_config = false)
147
{
148
	global $scripturl, $context, $txt, $modSettings, $boardurl, $sourcedir, $smcFunc;
149
150
	/* If you're writing a mod, it's a bad idea to add things here....
151
	For each option:
152
		variable name, description, type (constant), size/possible values, helptext, optional 'min' (minimum value for float/int, defaults to 0), optional 'max' (maximum value for float/int), optional 'step' (amount to increment/decrement value for float/int)
153
	OR	an empty string for a horizontal rule.
154
	OR	a string for a titled section. */
155
	$config_vars = array(
156
		array('mbname', $txt['admin_title'], 'file', 'text', 30),
157
		'',
158
		array('maintenance', $txt['admin_maintain'], 'file', 'check'),
159
		array('mtitle', $txt['maintenance_subject'], 'file', 'text', 36),
160
		array('mmessage', $txt['maintenance_message'], 'file', 'text', 36),
161
		'',
162
		array('webmaster_email', $txt['admin_webmaster_email'], 'file', 'text', 30),
163
		'',
164
		array('enableCompressedOutput', $txt['enableCompressedOutput'], 'db', 'check', null, 'enableCompressedOutput'),
165
		array('disableHostnameLookup', $txt['disableHostnameLookup'], 'db', 'check', null, 'disableHostnameLookup'),
166
		'',
167
		'force_ssl' => array('force_ssl', $txt['force_ssl'], 'db', 'select', array($txt['force_ssl_off'], $txt['force_ssl_complete']), 'force_ssl'),
168
		array('image_proxy_enabled', $txt['image_proxy_enabled'], 'file', 'check', null, 'image_proxy_enabled'),
169
		array('image_proxy_secret', $txt['image_proxy_secret'], 'file', 'text', 30, 'image_proxy_secret'),
170
		array('image_proxy_maxsize', $txt['image_proxy_maxsize'], 'file', 'int', null, 'image_proxy_maxsize'),
171
		'',
172
		array('enable_sm_stats', $txt['enable_sm_stats'], 'db', 'check', null, 'enable_sm_stats'),
173
	);
174
175
	call_integration_hook('integrate_general_settings', array(&$config_vars));
176
177
	if ($return_config)
178
		return $config_vars;
179
180
	// If no cert, force_ssl must remain 0 (The admin search doesn't require this)
181
	$config_vars['force_ssl']['disabled'] = empty($modSettings['force_ssl']) && !ssl_cert_found($boardurl);
182
183
	// Setup the template stuff.
184
	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=general;save';
185
	$context['settings_title'] = $txt['general_settings'];
186
	$context['save_disabled'] = $context['settings_not_writable'];
187
188
	// Saving settings?
189
	if (isset($_REQUEST['save']))
190
	{
191
		call_integration_hook('integrate_save_general_settings');
192
193
		foreach ($config_vars as $config_var)
194
		{
195
			if (is_array($config_var) && isset($config_var[3]) && $config_var[3] == 'text' && !empty($_POST[$config_var[0]]))
196
				$_POST[$config_var[0]] = $smcFunc['normalize']($_POST[$config_var[0]]);
197
		}
198
199
		// Are we saving the stat collection?
200
		if (!empty($_POST['enable_sm_stats']) && empty($modSettings['sm_stats_key']))
201
		{
202
			$registerSMStats = registerSMStats();
203
204
			// Failed to register, disable it again.
205
			if (empty($registerSMStats))
206
				$_POST['enable_sm_stats'] = 0;
207
		}
208
209
		// Ensure all URLs are aligned with the new force_ssl setting
210
		// Treat unset like 0
211
		if (isset($_POST['force_ssl']))
212
			AlignURLsWithSSLSetting($_POST['force_ssl']);
213
		else
214
			AlignURLsWithSSLSetting(0);
215
216
		saveSettings($config_vars);
217
		$_SESSION['adm-save'] = true;
218
		redirectexit('action=admin;area=serversettings;sa=general;' . $context['session_var'] . '=' . $context['session_id']);
219
	}
220
221
	// Fill the config array.
222
	prepareServerSettingsContext($config_vars);
223
224
	// Some javascript for SSL
225
	if (empty($context['settings_not_writable']))
226
		addInlineJavaScript('
227
$(function()
228
{
229
	$("#force_ssl").change(function()
230
	{
231
		var mode = $(this).val() == 1 ? false : true;
232
		$("#image_proxy_enabled").prop("disabled", mode);
233
		$("#image_proxy_secret").prop("disabled", mode);
234
		$("#image_proxy_maxsize").prop("disabled", mode);
235
	}).change();
236
});', true);
237
}
238
239
/**
240
 * Align URLs with SSL Setting.
241
 *
242
 * If force_ssl has changed, ensure all URLs are aligned with the new setting.
243
 * This includes:
244
 *     - $boardurl
245
 *     - $modSettings['smileys_url']
246
 *     - $modSettings['avatar_url']
247
 *     - $modSettings['custom_avatar_url'] - if found
248
 *     - theme_url - all entries in the themes table
249
 *     - images_url - all entries in the themes table
250
 *
251
 * This function will NOT overwrite URLs that are not subfolders of $boardurl.
252
 * The admin must have pointed those somewhere else on purpose, so they must be updated manually.
253
 *
254
 * A word of caution: You can't trust the http/https scheme reflected for these URLs in $globals
255
 * (e.g., $boardurl) or in $modSettings.  This is because SMF may change them in memory to comply
256
 * with the force_ssl setting - a soft redirect may be in effect...  Thus, conditional updates
257
 * to these values do not work.  You gotta just brute force overwrite them based on force_ssl.
258
 *
259
 * @param int $new_force_ssl is the current force_ssl setting.
260
 * @return void Returns nothing, just does its job
261
 */
262
function AlignURLsWithSSLSetting($new_force_ssl = 0)
263
{
264
	global $boardurl, $modSettings, $sourcedir, $smcFunc;
265
	require_once($sourcedir . '/Subs-Admin.php');
266
267
	// Check $boardurl
268
	if (!empty($new_force_ssl))
269
		$newval = strtr($boardurl, array('http://' => 'https://'));
270
	else
271
		$newval = strtr($boardurl, array('https://' => 'http://'));
272
	updateSettingsFile(array('boardurl' => $newval));
273
274
	$new_settings = array();
275
276
	// Check $smileys_url, but only if it points to a subfolder of $boardurl
277
	if (BoardurlMatch($modSettings['smileys_url']))
278
	{
279
		if (!empty($new_force_ssl))
280
			$newval = strtr($modSettings['smileys_url'], array('http://' => 'https://'));
281
		else
282
			$newval = strtr($modSettings['smileys_url'], array('https://' => 'http://'));
283
		$new_settings['smileys_url'] = $newval;
284
	}
285
286
	// Check $avatar_url, but only if it points to a subfolder of $boardurl
287
	if (BoardurlMatch($modSettings['avatar_url']))
288
	{
289
		if (!empty($new_force_ssl))
290
			$newval = strtr($modSettings['avatar_url'], array('http://' => 'https://'));
291
		else
292
			$newval = strtr($modSettings['avatar_url'], array('https://' => 'http://'));
293
		$new_settings['avatar_url'] = $newval;
294
	}
295
296
	// Check $custom_avatar_url, but only if it points to a subfolder of $boardurl
297
	// This one had been optional in the past, make sure it is set first
298
	if (isset($modSettings['custom_avatar_url']) && BoardurlMatch($modSettings['custom_avatar_url']))
299
	{
300
		if (!empty($new_force_ssl))
301
			$newval = strtr($modSettings['custom_avatar_url'], array('http://' => 'https://'));
302
		else
303
			$newval = strtr($modSettings['custom_avatar_url'], array('https://' => 'http://'));
304
		$new_settings['custom_avatar_url'] = $newval;
305
	}
306
307
	// Save updates to the settings table
308
	if (!empty($new_settings))
309
		updateSettings($new_settings, true);
310
311
	// Now we move onto the themes.
312
	// First, get a list of theme URLs...
313
	$request = $smcFunc['db_query']('', '
314
		SELECT id_theme, variable, value
315
		FROM {db_prefix}themes
316
		WHERE variable in ({string:themeurl}, {string:imagesurl})
317
			AND id_member = {int:zero}',
318
		array(
319
			'themeurl' => 'theme_url',
320
			'imagesurl' => 'images_url',
321
			'zero' => 0,
322
		)
323
	);
324
325
	while ($row = $smcFunc['db_fetch_assoc']($request))
326
	{
327
		// First check to see if it points to a subfolder of $boardurl
328
		if (BoardurlMatch($row['value']))
329
		{
330
			if (!empty($new_force_ssl))
331
				$newval = strtr($row['value'], array('http://' => 'https://'));
332
			else
333
				$newval = strtr($row['value'], array('https://' => 'http://'));
334
335
			$smcFunc['db_query']('', '
336
				UPDATE {db_prefix}themes
337
				SET value = {string:theme_val}
338
				WHERE variable = {string:theme_var}
339
					AND id_theme = {string:theme_id}
340
					AND id_member = {int:zero}',
341
				array(
342
					'theme_val' => $newval,
343
					'theme_var' => $row['variable'],
344
					'theme_id' => $row['id_theme'],
345
					'zero' => 0,
346
				)
347
			);
348
		}
349
	}
350
	$smcFunc['db_free_result']($request);
351
}
352
353
/**
354
 * $boardurl Match.
355
 *
356
 * Helper function to see if the url being checked is based off of $boardurl.
357
 * If not, it was overridden by the admin to some other value on purpose, and should not
358
 * be stepped on by SMF when aligning URLs with the force_ssl setting.
359
 * The site admin must change URLs that are not aligned with $boardurl manually.
360
 *
361
 * @param string $url is the url to check.
362
 * @return bool Returns true if the url is based off of $boardurl (without the scheme), false if not
363
 */
364
function BoardurlMatch($url = '')
365
{
366
	global $boardurl;
367
368
	// Strip the schemes
369
	$urlpath = strtr($url, array('http://' => '', 'https://' => ''));
370
	$boardurlpath = strtr($boardurl, array('http://' => '', 'https://' => ''));
371
372
	// If leftmost portion of path matches boardurl, return true
373
	$result = strpos($urlpath, $boardurlpath);
374
	if ($result === false || $result != 0)
375
		return false;
376
	else
377
		return true;
378
}
379
380
/**
381
 * Basic database and paths settings - database name, host, etc.
382
 *
383
 * - It shows an interface for the settings in Settings.php to be changed.
384
 * - It contains the actual array of settings to show from Settings.php.
385
 * - Requires the admin_forum permission.
386
 * - Uses the edit_settings administration area.
387
 * - Accessed from ?action=admin;area=serversettings;sa=database.
388
 *
389
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
390
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
391
 */
392
function ModifyDatabaseSettings($return_config = false)
393
{
394
	global $scripturl, $context, $txt, $smcFunc;
395
	db_extend('extra');
396
397
	/* If you're writing a mod, it's a bad idea to add things here....
398
		For each option:
399
		variable name, description, type (constant), size/possible values, helptext, optional 'min' (minimum value for float/int, defaults to 0), optional 'max' (maximum value for float/int), optional 'step' (amount to increment/decrement value for float/int)
400
		OR an empty string for a horizontal rule.
401
		OR a string for a titled section. */
402
	$config_vars = array(
403
		array('db_persist', $txt['db_persist'], 'file', 'check', null, 'db_persist'),
404
		array('db_error_send', $txt['db_error_send'], 'file', 'check'),
405
		array('ssi_db_user', $txt['ssi_db_user'], 'file', 'text', null, 'ssi_db_user'),
406
		array('ssi_db_passwd', $txt['ssi_db_passwd'], 'file', 'password'),
407
		'',
408
		array('autoFixDatabase', $txt['autoFixDatabase'], 'db', 'check', false, 'autoFixDatabase')
409
	);
410
411
	// Add PG Stuff
412
	if ($smcFunc['db_title'] === POSTGRE_TITLE)
413
	{
414
		$request = $smcFunc['db_query']('', 'SELECT cfgname FROM pg_ts_config', array());
415
		$fts_language = array();
416
417
		while ($row = $smcFunc['db_fetch_assoc']($request))
418
			$fts_language[$row['cfgname']] = $row['cfgname'];
419
420
		$config_vars = array_merge($config_vars, array(
421
				'',
422
				array('search_language', $txt['search_language'], 'db', 'select', $fts_language, 'pgFulltextSearch')
423
			)
424
		);
425
	}
426
427
	call_integration_hook('integrate_database_settings', array(&$config_vars));
428
429
	if ($return_config)
430
		return $config_vars;
431
432
	// Setup the template stuff.
433
	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=database;save';
434
	$context['settings_title'] = $txt['database_settings'];
435
	$context['save_disabled'] = $context['settings_not_writable'];
436
437
	if (!$smcFunc['db_allow_persistent']())
438
		addInlineJavaScript('
439
			$(function()
440
			{
441
				$("#db_persist").prop("disabled", true);
442
			});', true);
443
444
	// Saving settings?
445
	if (isset($_REQUEST['save']))
446
	{
447
		call_integration_hook('integrate_save_database_settings');
448
449
		saveSettings($config_vars);
450
		$_SESSION['adm-save'] = true;
451
		redirectexit('action=admin;area=serversettings;sa=database;' . $context['session_var'] . '=' . $context['session_id']);
452
	}
453
454
	// Fill the config array.
455
	prepareServerSettingsContext($config_vars);
456
}
457
458
/**
459
 * This function handles cookies settings modifications.
460
 *
461
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
462
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
463
 */
464
function ModifyCookieSettings($return_config = false)
465
{
466
	global $context, $scripturl, $txt, $sourcedir, $modSettings, $cookiename, $user_settings, $boardurl, $smcFunc;
467
468
	// Define the variables we want to edit.
469
	$config_vars = array(
470
		// Cookies...
471
		array('cookiename', $txt['cookie_name'], 'file', 'text', 20),
472
		array('cookieTime', $txt['cookieTime'], 'db', 'select', array_filter(array_map(
473
			function ($str) use ($txt)
474
			{
475
				return isset($txt[$str]) ? $txt[$str] : '';
476
			},
477
			$context['login_cookie_times']
478
		))),
479
		array('localCookies', $txt['localCookies'], 'db', 'check', false, 'localCookies'),
480
		array('globalCookies', $txt['globalCookies'], 'db', 'check', false, 'globalCookies'),
481
		array('globalCookiesDomain', $txt['globalCookiesDomain'], 'db', 'text', false, 'globalCookiesDomain'),
482
		array('secureCookies', $txt['secureCookies'], 'db', 'check', false, 'secureCookies', 'disabled' => !httpsOn()),
483
		array('httponlyCookies', $txt['httponlyCookies'], 'db', 'check', false, 'httponlyCookies'),
484
		array('samesiteCookies', $txt['samesiteCookies'], 'db', 'select', array(
485
				'none' 		=> $txt['samesiteNone'],
486
				'lax' 		=> $txt['samesiteLax'],
487
				'strict' 	=> $txt['samesiteStrict']
488
			),
489
			'samesiteCookies'),
490
		'',
491
		// Sessions
492
		array('databaseSession_enable', $txt['databaseSession_enable'], 'db', 'check', false, 'databaseSession_enable'),
493
		array('databaseSession_loose', $txt['databaseSession_loose'], 'db', 'check', false, 'databaseSession_loose'),
494
		array('databaseSession_lifetime', $txt['databaseSession_lifetime'], 'db', 'int', false, 'databaseSession_lifetime', 'postinput' => $txt['seconds']),
495
		'',
496
		// 2FA
497
		array('tfa_mode', $txt['tfa_mode'], 'db', 'select', array(
498
			0 => $txt['tfa_mode_disabled'],
499
			1 => $txt['tfa_mode_enabled'],
500
		) + (empty($user_settings['tfa_secret']) ? array() : array(
501
			2 => $txt['tfa_mode_forced'],
502
		)) + (empty($user_settings['tfa_secret']) ? array() : array(
503
			3 => $txt['tfa_mode_forcedall'],
504
		)), 'subtext' => $txt['tfa_mode_subtext'] . (empty($user_settings['tfa_secret']) ? '<br><strong>' . $txt['tfa_mode_forced_help'] . '</strong>' : ''), 'tfa_mode'),
505
	);
506
507
	addInlineJavaScript('
508
	function hideGlobalCookies()
509
	{
510
		var usingLocal = $("#localCookies").prop("checked");
511
		$("#setting_globalCookies").closest("dt").toggle(!usingLocal);
512
		$("#globalCookies").closest("dd").toggle(!usingLocal);
513
514
		var usingGlobal = !usingLocal && $("#globalCookies").prop("checked");
515
		$("#setting_globalCookiesDomain").closest("dt").toggle(usingGlobal);
516
		$("#globalCookiesDomain").closest("dd").toggle(usingGlobal);
517
	};
518
	hideGlobalCookies();
519
520
	$("#localCookies, #globalCookies").click(function() {
521
		hideGlobalCookies();
522
	});
523
	', true);
524
525
	if (empty($user_settings['tfa_secret']))
526
		addInlineJavaScript('');
527
528
	call_integration_hook('integrate_cookie_settings', array(&$config_vars));
529
530
	if ($return_config)
531
		return $config_vars;
532
533
	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cookie;save';
534
	$context['settings_title'] = $txt['cookies_sessions_settings'];
535
	$context['save_disabled'] = $context['settings_not_writable'];
536
537
	// Saving settings?
538
	if (isset($_REQUEST['save']))
539
	{
540
		call_integration_hook('integrate_save_cookie_settings');
541
542
		$_POST['cookiename'] = $smcFunc['normalize']($_POST['cookiename']);
543
544
		// Local and global do not play nicely together.
545
		if (!empty($_POST['localCookies']) && empty($_POST['globalCookies']))
546
			unset ($_POST['globalCookies']);
547
548
		if (empty($modSettings['localCookies']) != empty($_POST['localCookies']) || empty($modSettings['globalCookies']) != empty($_POST['globalCookies']))
549
			$scope_changed = true;
550
551
		if (!empty($_POST['globalCookiesDomain']))
552
		{
553
			$_POST['globalCookiesDomain'] = parse_iri(normalize_iri((strpos($_POST['globalCookiesDomain'], '//') === false ? 'http://' : '') . ltrim($_POST['globalCookiesDomain'], '.')), PHP_URL_HOST);
554
555
			if (!preg_match('/(?:^|\.)' . preg_quote($_POST['globalCookiesDomain'], '/') . '$/u', parse_iri($boardurl, PHP_URL_HOST)))
556
				fatal_lang_error('invalid_cookie_domain', false);
557
		}
558
559
		// Per spec, if samesite setting is 'none', cookies MUST be secure. Thems the rules. Else you lock everyone out...
560
		if (!empty($_POST['samesiteCookies']) && ($_POST['samesiteCookies'] === 'none') && empty($_POST['secureCookies']))
561
			fatal_lang_error('samesiteSecureRequired', false);
562
563
		saveSettings($config_vars);
564
565
		// If the cookie name or scope were changed, reset the cookie.
566
		if ($cookiename != $_POST['cookiename'] || !empty($scope_changed))
567
		{
568
			$original_session_id = $context['session_id'];
569
			include_once($sourcedir . '/Subs-Auth.php');
570
571
			// Remove the old cookie.
572
			setLoginCookie(-3600, 0);
573
574
			// Set the new one.
575
			$cookiename = !empty($_POST['cookiename']) ? $_POST['cookiename'] : $cookiename;
576
			setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], hash_salt($user_settings['passwd'], $user_settings['password_salt']));
577
578
			redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $original_session_id, $context['server']['needs_login_fix']);
579
		}
580
581
		//If we disabled 2FA, reset all members and membergroups settings.
582
		if (isset($_POST['tfa_mode']) && empty($_POST['tfa_mode']))
583
		{
584
			$smcFunc['db_query']('', '
585
				UPDATE {db_prefix}membergroups
586
				SET tfa_required = {int:zero}',
587
				array(
588
					'zero' => 0,
589
				)
590
			);
591
			$smcFunc['db_query']('', '
592
				UPDATE {db_prefix}members
593
				SET tfa_secret = {string:empty}, tfa_backup = {string:empty}',
594
				array(
595
					'empty' => '',
596
				)
597
			);
598
		}
599
600
		$_SESSION['adm-save'] = true;
601
		redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $context['session_id']);
602
	}
603
604
	// Fill the config array.
605
	prepareServerSettingsContext($config_vars);
606
}
607
608
/**
609
 * Settings really associated with general security aspects.
610
 *
611
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
612
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
613
 */
614
function ModifyGeneralSecuritySettings($return_config = false)
615
{
616
	global $txt, $scripturl, $context;
617
618
	$config_vars = array(
619
		array('int', 'failed_login_threshold'),
620
		array('int', 'loginHistoryDays', 'subtext' => $txt['zero_to_disable']),
621
		'',
622
623
		array('check', 'securityDisable'),
624
		array('check', 'securityDisable_moderate'),
625
		'',
626
627
		// Reactive on email, and approve on delete
628
		array('check', 'send_validation_onChange'),
629
		array('check', 'approveAccountDeletion'),
630
		'',
631
632
		// Password strength.
633
		array(
634
			'select',
635
			'password_strength',
636
			array(
637
				$txt['setting_password_strength_low'],
638
				$txt['setting_password_strength_medium'],
639
				$txt['setting_password_strength_high']
640
			)
641
		),
642
		array('check', 'enable_password_conversion'),
643
		'',
644
645
		// Reporting of personal messages?
646
		array('check', 'enableReportPM'),
647
		'',
648
649
		array('check', 'allow_cors'),
650
		array('check', 'allow_cors_credentials'),
651
		array('text', 'cors_domains'),
652
		array('text', 'cors_headers'),
653
		'',
654
655
		array(
656
			'select',
657
			'frame_security',
658
			array(
659
				'SAMEORIGIN' => $txt['setting_frame_security_SAMEORIGIN'],
660
				'DENY' => $txt['setting_frame_security_DENY'],
661
				'DISABLE' => $txt['setting_frame_security_DISABLE']
662
			)
663
		),
664
		'',
665
666
		array(
667
			'select',
668
			'proxy_ip_header',
669
			array(
670
				'disabled' => $txt['setting_proxy_ip_header_disabled'],
671
				'autodetect' => $txt['setting_proxy_ip_header_autodetect'],
672
				'HTTP_X_FORWARDED_FOR' => 'X-Forwarded-For',
673
				'HTTP_CLIENT_IP' => 'Client-IP',
674
				'HTTP_X_REAL_IP' => 'X-Real-IP',
675
				'HTTP_CF_CONNECTING_IP' => 'CF-Connecting-IP'
676
			)
677
		),
678
		array('text', 'proxy_ip_servers'),
679
	);
680
681
	call_integration_hook('integrate_general_security_settings', array(&$config_vars));
682
683
	if ($return_config)
684
		return $config_vars;
685
686
	// Saving?
687
	if (isset($_GET['save']))
688
	{
689
		if (!empty($_POST['cors_domains']))
690
		{
691
			$cors_domains = explode(',', $_POST['cors_domains']);
692
693
			foreach ($cors_domains as &$cors_domain)
694
			{
695
				if (strpos($cors_domain, '//') === false)
696
					$cors_domain = '//' . $cors_domain;
697
698
				$temp = parse_iri(normalize_iri($cors_domain));
699
700
				if (strpos($temp['host'], '*') !== false)
701
					$temp['host'] = substr($temp['host'], strrpos($temp['host'], '*'));
702
703
				$cors_domain = (!empty($temp['scheme']) ? $temp['scheme'] . '://' : '') . $temp['host'] . (!empty($temp['port']) ? ':' . $temp['port'] : '');
704
			}
705
706
			$_POST['cors_domains'] = implode(',', $cors_domains);
707
		}
708
709
		saveDBSettings($config_vars);
710
		$_SESSION['adm-save'] = true;
711
712
		call_integration_hook('integrate_save_general_security_settings');
713
714
		writeLog();
715
		redirectexit('action=admin;area=serversettings;sa=security;' . $context['session_var'] . '=' . $context['session_id']);
716
	}
717
718
	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;save;sa=security';
719
	$context['settings_title'] = $txt['security_settings'];
720
721
	prepareDBSettingContext($config_vars);
722
}
723
724
/**
725
 * Simply modifying cache functions
726
 *
727
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
728
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
729
 */
730
function ModifyCacheSettings($return_config = false)
731
{
732
	global $context, $scripturl, $txt, $cacheAPI, $cache_enable, $cache_accelerator;
733
734
	// Detect all available optimizers
735
	$detectedCacheApis = loadCacheAPIs();
736
	$apis_names = array();
737
738
	/* @var CacheApiInterface $cache_api */
739
	foreach ($detectedCacheApis as $class_name => $cache_api)
740
	{
741
		$class_name_txt_key = strtolower($cache_api->getImplementationClassKeyName());
742
743
		$apis_names[$class_name] = isset($txt[$class_name_txt_key . '_cache']) ?
744
			$txt[$class_name_txt_key . '_cache'] : $class_name;
745
	}
746
747
	// set our values to show what, if anything, we found
748
	if (empty($detectedCacheApis))
749
	{
750
		$txt['cache_settings_message'] = '<strong class="alert">' . $txt['detected_no_caching'] . '</strong>';
751
		$cache_level = array($txt['cache_off']);
752
		$apis_names['none'] = $txt['cache_off'];
753
	}
754
755
	else
756
	{
757
		$txt['cache_settings_message'] = '<strong class="success">' .
758
			sprintf($txt['detected_accelerators'], implode(', ', $apis_names)) . '</strong>';
759
760
		$cache_level = array($txt['cache_off'], $txt['cache_level1'], $txt['cache_level2'], $txt['cache_level3']);
761
	}
762
763
	// Define the variables we want to edit.
764
	$config_vars = array(
765
		// Only a few settings, but they are important
766
		array('', $txt['cache_settings_message'], '', 'desc'),
767
		array('cache_enable', $txt['cache_enable'], 'file', 'select', $cache_level, 'cache_enable'),
768
		array('cache_accelerator', $txt['cache_accelerator'], 'file', 'select', $apis_names),
769
	);
770
771
	// some javascript to enable / disable certain settings if the option is not selected
772
	$context['settings_post_javascript'] = '
773
		$(document).ready(function() {
774
			$("#cache_accelerator").change();
775
		});';
776
777
	call_integration_hook('integrate_modify_cache_settings', array(&$config_vars));
778
779
	// Maybe we have some additional settings from the selected accelerator.
780
	if (!empty($detectedCacheApis))
781
		/* @var CacheApiInterface $cache_api */
782
		foreach ($detectedCacheApis as $class_name_txt_key => $cache_api)
783
			if (is_callable(array($cache_api, 'cacheSettings')))
784
				$cache_api->cacheSettings($config_vars);
785
786
	if ($return_config)
787
		return $config_vars;
788
789
	// Saving again?
790
	if (isset($_GET['save']))
791
	{
792
		call_integration_hook('integrate_save_cache_settings');
793
794
		if (is_callable(array($cacheAPI, 'cleanCache')) && ((int) $_POST['cache_enable'] < $cache_enable || $_POST['cache_accelerator'] != $cache_accelerator))
795
		{
796
			$cacheAPI->cleanCache();
797
		}
798
799
		saveSettings($config_vars);
800
		$_SESSION['adm-save'] = true;
801
802
		// We need to save the $cache_enable to $modSettings as well
803
		updateSettings(array('cache_enable' => (int) $_POST['cache_enable']));
804
805
		// exit so we reload our new settings on the page
806
		redirectexit('action=admin;area=serversettings;sa=cache;' . $context['session_var'] . '=' . $context['session_id']);
807
	}
808
809
	loadLanguage('ManageMaintenance');
810
	createToken('admin-maint');
811
	$context['template_layers'][] = 'clean_cache_button';
812
813
	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cache;save';
814
	$context['settings_title'] = $txt['caching_settings'];
815
816
	// Changing cache settings won't have any effect if Settings.php is not writable.
817
	$context['save_disabled'] = $context['settings_not_writable'];
818
819
	// Decide what message to show.
820
	if (!$context['save_disabled'])
821
		$context['settings_message'] = $txt['caching_information'];
822
823
	// Prepare the template.
824
	prepareServerSettingsContext($config_vars);
825
}
826
827
/**
828
 * Controls settings for data export functionality
829
 *
830
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
831
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
832
 */
833
function ModifyExportSettings($return_config = false)
834
{
835
	global $context, $scripturl, $txt, $modSettings, $boarddir, $sourcedir;
836
837
	// Fill in a default value for this if it is missing.
838
	if (empty($modSettings['export_dir']))
839
		$modSettings['export_dir'] = $boarddir . DIRECTORY_SEPARATOR . 'exports';
840
841
	/*
842
		Some paranoid hosts worry that the disk space functions pose a security
843
		risk. Usually these hosts just disable the functions and move on, which
844
		is fine. A rare few, however, are not only paranoid, but also think it'd
845
		be a "clever" security move to overload the disk space functions with
846
		custom code that intentionally delivers false information, which is
847
		idiotic and evil. At any rate, if the functions are unavailable or if
848
		they report obviously insane values, it's not possible to track disk
849
		usage correctly.
850
	 */
851
	$diskspace_disabled = (!function_exists('disk_free_space') || !function_exists('disk_total_space') || intval(@disk_total_space(file_exists($modSettings['export_dir']) ? $modSettings['export_dir'] : $boarddir)) < 1440);
852
853
	$context['settings_message'] = $txt['export_settings_description'];
854
855
	$config_vars = array(
856
		array('text', 'export_dir', 40),
857
		array('int', 'export_expiry', 'subtext' => $txt['zero_to_disable'], 'postinput' => $txt['days_word']),
858
		array('int', 'export_min_diskspace_pct', 'postinput' => '%', 'max' => 80, 'disabled' => $diskspace_disabled),
859
		array('int', 'export_rate', 'min' => 5, 'max' => 500, 'step' => 5, 'subtext' => $txt['export_rate_desc']),
860
	);
861
862
	call_integration_hook('integrate_export_settings', array(&$config_vars));
863
864
	if ($return_config)
865
		return $config_vars;
866
867
	if (isset($_REQUEST['save']))
868
	{
869
		$prev_export_dir = is_dir($modSettings['export_dir']) ? rtrim($modSettings['export_dir'], '/\\') : '';
870
871
		if (!empty($_POST['export_dir']))
872
			$_POST['export_dir'] = rtrim($_POST['export_dir'], '/\\');
873
874
		if ($diskspace_disabled)
875
			$_POST['export_min_diskspace_pct'] = 0;
876
877
		$_POST['export_rate'] = max(5, min($_POST['export_rate'], 500));
878
879
		saveDBSettings($config_vars);
880
881
		// Create the new directory, but revert to the previous one if anything goes wrong.
882
		require_once($sourcedir . '/Profile-Export.php');
883
		create_export_dir($prev_export_dir);
884
885
		// Ensure we don't lose track of any existing export files.
886
		if (!empty($prev_export_dir) && $prev_export_dir != $modSettings['export_dir'])
887
		{
888
			$export_files = glob($prev_export_dir . DIRECTORY_SEPARATOR . '*');
889
890
			foreach ($export_files as $export_file)
891
			{
892
				if (!in_array(basename($export_file), array('index.php', '.htaccess')))
893
				{
894
					rename($export_file, $modSettings['export_dir'] . DIRECTORY_SEPARATOR . basename($export_file));
895
				}
896
			}
897
		}
898
899
		call_integration_hook('integrate_save_export_settings');
900
901
		$_SESSION['adm-save'] = true;
902
		redirectexit('action=admin;area=serversettings;sa=export;' . $context['session_var'] . '=' . $context['session_id']);
903
	}
904
905
	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=export;save';
906
	$context['settings_title'] = $txt['export_settings'];
907
908
	prepareDBSettingContext($config_vars);
909
}
910
911
/**
912
 * Allows to edit load balancing settings.
913
 *
914
 * @param bool $return_config Whether or not to return the config_vars array
915
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
916
 */
917
function ModifyLoadBalancingSettings($return_config = false)
918
{
919
	global $txt, $scripturl, $context, $modSettings;
920
921
	// Setup a warning message, but disabled by default.
922
	$disabled = true;
923
	$context['settings_message'] = array('label' => $txt['loadavg_disabled_conf'], 'class' => 'error');
924
925
	if (DIRECTORY_SEPARATOR === '\\')
926
	{
927
		$context['settings_message']['label'] = $txt['loadavg_disabled_windows'];
928
		if (isset($_GET['save']))
929
			$_SESSION['adm-save'] = $context['settings_message']['label'];
930
	}
931
	elseif (stripos(PHP_OS, 'darwin') === 0)
932
	{
933
		$context['settings_message']['label'] = $txt['loadavg_disabled_osx'];
934
		if (isset($_GET['save']))
935
			$_SESSION['adm-save'] = $context['settings_message']['label'];
936
	}
937
	else
938
	{
939
		$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
940
		if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) !== 0)
941
			$modSettings['load_average'] = (float) $matches[1];
942
		elseif (($modSettings['load_average'] = @`uptime`) !== null && preg_match('~load averages?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) !== 0)
943
			$modSettings['load_average'] = (float) $matches[1];
944
		else
945
			unset($modSettings['load_average']);
946
947
		if (!empty($modSettings['load_average']) || (isset($modSettings['load_average']) && $modSettings['load_average'] === 0.0))
948
		{
949
			$context['settings_message']['label'] = sprintf($txt['loadavg_warning'], $modSettings['load_average']);
950
			$disabled = false;
951
		}
952
	}
953
954
	// Start with a simple checkbox.
955
	$config_vars = array(
956
		array('check', 'loadavg_enable', 'disabled' => $disabled),
957
	);
958
959
	// Set the default values for each option.
960
	$default_values = array(
961
		'loadavg_auto_opt' => 1.0,
962
		'loadavg_search' => 2.5,
963
		'loadavg_allunread' => 2.0,
964
		'loadavg_unreadreplies' => 3.5,
965
		'loadavg_show_posts' => 2.0,
966
		'loadavg_userstats' => 10.0,
967
		'loadavg_bbc' => 30.0,
968
		'loadavg_forum' => 40.0,
969
	);
970
971
	// Loop through the settings.
972
	foreach ($default_values as $name => $value)
973
	{
974
		// Use the default value if the setting isn't set yet.
975
		$value = !isset($modSettings[$name]) ? $value : $modSettings[$name];
976
		$config_vars[] = array('float', $name, 'value' => $value, 'disabled' => $disabled);
977
	}
978
979
	call_integration_hook('integrate_loadavg_settings', array(&$config_vars));
980
981
	if ($return_config)
982
		return $config_vars;
983
984
	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=loads;save';
985
	$context['settings_title'] = $txt['load_balancing_settings'];
986
987
	// Saving?
988
	if (isset($_GET['save']))
989
	{
990
		// Stupidity is not allowed.
991
		foreach ($_POST as $key => $value)
992
		{
993
			if (strpos($key, 'loadavg') === 0 || $key === 'loadavg_enable' || !in_array($key, array_keys($default_values)))
994
				continue;
995
			else
996
				$_POST[$key] = (float) $value;
997
998
			if ($key == 'loadavg_auto_opt' && $value <= 1)
999
				$_POST['loadavg_auto_opt'] = 1.0;
1000
			elseif ($key == 'loadavg_forum' && $value < 10)
1001
				$_POST['loadavg_forum'] = 10.0;
1002
			elseif ($value < 2)
1003
				$_POST[$key] = 2.0;
1004
		}
1005
1006
		call_integration_hook('integrate_save_loadavg_settings');
1007
1008
		saveDBSettings($config_vars);
1009
		if (!isset($_SESSION['adm-save']))
1010
			$_SESSION['adm-save'] = true;
1011
		redirectexit('action=admin;area=serversettings;sa=loads;' . $context['session_var'] . '=' . $context['session_id']);
1012
	}
1013
1014
	prepareDBSettingContext($config_vars);
1015
}
1016
1017
/**
1018
 * Helper function, it sets up the context for the manage server settings.
1019
 * - The basic usage of the six numbered key fields are
1020
 * - array (0 ,1, 2, 3, 4, 5
1021
 *		0 variable name - the name of the saved variable
1022
 *		1 label - the text to show on the settings page
1023
 *		2 saveto - file or db, where to save the variable name - value pair
1024
 *		3 type - type of data to save, int, float, text, check
1025
 *		4 size - false or field size
1026
 *		5 help - '' or helptxt variable name
1027
 *	)
1028
 *
1029
 * the following named keys are also permitted
1030
 * 'disabled' => A string of code that will determine whether or not the setting should be disabled
1031
 * 'postinput' => Text to display after the input field
1032
 * 'preinput' => Text to display before the input field
1033
 * 'subtext' => Additional descriptive text to display under the field's label
1034
 * 'min' => minimum allowed value (for int/float). Defaults to 0 if not set.
1035
 * 'max' => maximum allowed value (for int/float)
1036
 * 'step' => how much to increment/decrement the value by (only for int/float - mostly used for float values).
1037
 *
1038
 * @param array $config_vars An array of configuration variables
1039
 */
1040
function prepareServerSettingsContext(&$config_vars)
1041
{
1042
	global $context, $modSettings, $smcFunc, $txt;
1043
1044
	if (!empty($context['settings_not_writable']))
1045
		$context['settings_message'] = array(
1046
			'label' => $txt['settings_not_writable'],
1047
			'tag' => 'div',
1048
			'class' => 'centertext strong'
1049
		);
1050
1051
	if (isset($_SESSION['adm-save']))
1052
	{
1053
		if ($_SESSION['adm-save'] === true)
1054
			$context['saved_successful'] = true;
1055
		else
1056
			$context['saved_failed'] = $_SESSION['adm-save'];
1057
1058
		unset($_SESSION['adm-save']);
1059
	}
1060
1061
	$context['config_vars'] = array();
1062
	foreach ($config_vars as $identifier => $config_var)
1063
	{
1064
		if (!is_array($config_var) || !isset($config_var[1]))
1065
			$context['config_vars'][] = $config_var;
1066
		else
1067
		{
1068
			$varname = $config_var[0];
1069
			global $$varname;
1070
1071
			// Set the subtext in case it's part of the label.
1072
			// @todo Temporary. Preventing divs inside label tags.
1073
			$divPos = strpos($config_var[1], '<div');
1074
			$subtext = '';
1075
			if ($divPos !== false)
1076
			{
1077
				$subtext = preg_replace('~</?div[^>]*>~', '', substr($config_var[1], $divPos));
1078
				$config_var[1] = substr($config_var[1], 0, $divPos);
1079
			}
1080
1081
			$context['config_vars'][$config_var[0]] = array(
1082
				'label' => $config_var[1],
1083
				'help' => isset($config_var[5]) ? $config_var[5] : '',
1084
				'type' => $config_var[3],
1085
				'size' => !empty($config_var[4]) && !is_array($config_var[4]) ? $config_var[4] : 0,
1086
				'data' => isset($config_var[4]) && is_array($config_var[4]) && $config_var[3] != 'select' ? $config_var[4] : array(),
1087
				'name' => $config_var[0],
1088
				'value' => $config_var[2] == 'file' ? $smcFunc['htmlspecialchars']($$varname) : (isset($modSettings[$config_var[0]]) ? $smcFunc['htmlspecialchars']($modSettings[$config_var[0]]) : (in_array($config_var[3], array('int', 'float')) ? 0 : '')),
1089
				'disabled' => !empty($context['settings_not_writable']) || !empty($config_var['disabled']),
1090
				'invalid' => false,
1091
				'subtext' => !empty($config_var['subtext']) ? $config_var['subtext'] : $subtext,
1092
				'javascript' => '',
1093
				'preinput' => !empty($config_var['preinput']) ? $config_var['preinput'] : '',
1094
				'postinput' => !empty($config_var['postinput']) ? $config_var['postinput'] : '',
1095
			);
1096
1097
			// Handle min/max/step if necessary
1098
			if ($config_var[3] == 'int' || $config_var[3] == 'float')
1099
			{
1100
				// Default to a min of 0 if one isn't set
1101
				if (isset($config_var['min']))
1102
					$context['config_vars'][$config_var[0]]['min'] = $config_var['min'];
1103
				else
1104
					$context['config_vars'][$config_var[0]]['min'] = 0;
1105
1106
				if (isset($config_var['max']))
1107
					$context['config_vars'][$config_var[0]]['max'] = $config_var['max'];
1108
1109
				if (isset($config_var['step']))
1110
					$context['config_vars'][$config_var[0]]['step'] = $config_var['step'];
1111
			}
1112
1113
			// If this is a select box handle any data.
1114
			if (!empty($config_var[4]) && is_array($config_var[4]))
1115
			{
1116
				// If it's associative
1117
				$config_values = array_values($config_var[4]);
1118
				if (isset($config_values[0]) && is_array($config_values[0]))
1119
					$context['config_vars'][$config_var[0]]['data'] = $config_var[4];
1120
				else
1121
				{
1122
					foreach ($config_var[4] as $key => $item)
1123
						$context['config_vars'][$config_var[0]]['data'][] = array($key, $item);
1124
				}
1125
			}
1126
		}
1127
	}
1128
1129
	// Two tokens because saving these settings requires both saveSettings and saveDBSettings
1130
	createToken('admin-ssc');
1131
	createToken('admin-dbsc');
1132
}
1133
1134
/**
1135
 * Helper function, it sets up the context for database settings.
1136
 *
1137
 * @todo see rev. 10406 from 2.1-requests
1138
 *
1139
 * @param array $config_vars An array of configuration variables
1140
 */
1141
function prepareDBSettingContext(&$config_vars)
1142
{
1143
	global $txt, $helptxt, $context, $modSettings, $sourcedir, $smcFunc;
1144
1145
	loadLanguage('Help');
1146
1147
	if (isset($_SESSION['adm-save']))
1148
	{
1149
		if ($_SESSION['adm-save'] === true)
1150
			$context['saved_successful'] = true;
1151
		else
1152
			$context['saved_failed'] = $_SESSION['adm-save'];
1153
1154
		unset($_SESSION['adm-save']);
1155
	}
1156
1157
	$context['config_vars'] = array();
1158
	$inlinePermissions = array();
1159
	$bbcChoice = array();
1160
	$board_list = false;
1161
	foreach ($config_vars as $config_var)
1162
	{
1163
		// HR?
1164
		if (!is_array($config_var))
1165
			$context['config_vars'][] = $config_var;
1166
		else
1167
		{
1168
			// If it has no name it doesn't have any purpose!
1169
			if (empty($config_var[1]))
1170
				continue;
1171
1172
			// Special case for inline permissions
1173
			if ($config_var[0] == 'permissions' && allowedTo('manage_permissions'))
1174
				$inlinePermissions[] = $config_var[1];
1175
1176
			elseif ($config_var[0] == 'permissions')
1177
				continue;
1178
1179
			if ($config_var[0] == 'boards')
1180
				$board_list = true;
1181
1182
			// Are we showing the BBC selection box?
1183
			if ($config_var[0] == 'bbc')
1184
				$bbcChoice[] = $config_var[1];
1185
1186
			// We need to do some parsing of the value before we pass it in.
1187
			if (isset($modSettings[$config_var[1]]))
1188
			{
1189
				switch ($config_var[0])
1190
				{
1191
					case 'select':
1192
						$value = $modSettings[$config_var[1]];
1193
						break;
1194
					case 'json':
1195
						$value = $smcFunc['htmlspecialchars']($smcFunc['json_encode']($modSettings[$config_var[1]]));
1196
						break;
1197
					case 'boards':
1198
						$value = explode(',', $modSettings[$config_var[1]]);
1199
						break;
1200
					default:
1201
						$value = $smcFunc['htmlspecialchars']($modSettings[$config_var[1]]);
1202
				}
1203
			}
1204
			else
1205
			{
1206
				// Darn, it's empty. What type is expected?
1207
				switch ($config_var[0])
1208
				{
1209
					case 'int':
1210
					case 'float':
1211
						$value = 0;
1212
						break;
1213
					case 'select':
1214
						$value = !empty($config_var['multiple']) ? $smcFunc['json_encode'](array()) : '';
1215
						break;
1216
					case 'boards':
1217
						$value = array();
1218
						break;
1219
					default:
1220
						$value = '';
1221
				}
1222
			}
1223
1224
			$context['config_vars'][$config_var[1]] = array(
1225
				'label' => isset($config_var['text_label']) ? $config_var['text_label'] : (isset($txt[$config_var[1]]) ? $txt[$config_var[1]] : (isset($config_var[3]) && !is_array($config_var[3]) ? $config_var[3] : '')),
1226
				'help' => isset($helptxt[$config_var[1]]) ? $config_var[1] : '',
1227
				'type' => $config_var[0],
1228
				'size' => !empty($config_var['size']) ? $config_var['size'] : (!empty($config_var[2]) && !is_array($config_var[2]) ? $config_var[2] : (in_array($config_var[0], array('int', 'float')) ? 6 : 0)),
1229
				'data' => array(),
1230
				'name' => $config_var[1],
1231
				'value' => $value,
1232
				'disabled' => false,
1233
				'invalid' => !empty($config_var['invalid']),
1234
				'javascript' => '',
1235
				'var_message' => !empty($config_var['message']) && isset($txt[$config_var['message']]) ? $txt[$config_var['message']] : '',
1236
				'preinput' => isset($config_var['preinput']) ? $config_var['preinput'] : '',
1237
				'postinput' => isset($config_var['postinput']) ? $config_var['postinput'] : '',
1238
			);
1239
1240
			// Handle min/max/step if necessary
1241
			if ($config_var[0] == 'int' || $config_var[0] == 'float')
1242
			{
1243
				// Default to a min of 0 if one isn't set
1244
				if (isset($config_var['min']))
1245
					$context['config_vars'][$config_var[1]]['min'] = $config_var['min'];
1246
1247
				else
1248
					$context['config_vars'][$config_var[1]]['min'] = 0;
1249
1250
				if (isset($config_var['max']))
1251
					$context['config_vars'][$config_var[1]]['max'] = $config_var['max'];
1252
1253
				if (isset($config_var['step']))
1254
					$context['config_vars'][$config_var[1]]['step'] = $config_var['step'];
1255
			}
1256
1257
			// If this is a select box handle any data.
1258
			if (!empty($config_var[2]) && is_array($config_var[2]))
1259
			{
1260
				// If we allow multiple selections, we need to adjust a few things.
1261
				if ($config_var[0] == 'select' && !empty($config_var['multiple']))
1262
				{
1263
					$context['config_vars'][$config_var[1]]['name'] .= '[]';
1264
					$context['config_vars'][$config_var[1]]['value'] = !empty($context['config_vars'][$config_var[1]]['value']) ? $smcFunc['json_decode']($context['config_vars'][$config_var[1]]['value'], true) : array();
1265
				}
1266
1267
				// If it's associative
1268
				if (isset($config_var[2][0]) && is_array($config_var[2][0]))
1269
					$context['config_vars'][$config_var[1]]['data'] = $config_var[2];
1270
1271
				else
1272
				{
1273
					foreach ($config_var[2] as $key => $item)
1274
						$context['config_vars'][$config_var[1]]['data'][] = array($key, $item);
1275
				}
1276
				if (empty($config_var['size']) && !empty($config_var['multiple']))
1277
					$context['config_vars'][$config_var[1]]['size'] = max(4, count($config_var[2]));
1278
			}
1279
1280
			// Finally allow overrides - and some final cleanups.
1281
			foreach ($config_var as $k => $v)
1282
			{
1283
				if (!is_numeric($k))
1284
				{
1285
					if (substr($k, 0, 2) == 'on')
1286
						$context['config_vars'][$config_var[1]]['javascript'] .= ' ' . $k . '="' . $v . '"';
1287
					else
1288
						$context['config_vars'][$config_var[1]][$k] = $v;
1289
				}
1290
1291
				// See if there are any other labels that might fit?
1292
				if (isset($txt['setting_' . $config_var[1]]))
1293
					$context['config_vars'][$config_var[1]]['label'] = $txt['setting_' . $config_var[1]];
1294
1295
				elseif (isset($txt['groups_' . $config_var[1]]))
1296
					$context['config_vars'][$config_var[1]]['label'] = $txt['groups_' . $config_var[1]];
1297
			}
1298
1299
			// Set the subtext in case it's part of the label.
1300
			// @todo Temporary. Preventing divs inside label tags.
1301
			$divPos = strpos($context['config_vars'][$config_var[1]]['label'], '<div');
1302
			if ($divPos !== false)
1303
			{
1304
				$context['config_vars'][$config_var[1]]['subtext'] = preg_replace('~</?div[^>]*>~', '', substr($context['config_vars'][$config_var[1]]['label'], $divPos));
1305
				$context['config_vars'][$config_var[1]]['label'] = substr($context['config_vars'][$config_var[1]]['label'], 0, $divPos);
1306
			}
1307
		}
1308
	}
1309
1310
	// If we have inline permissions we need to prep them.
1311
	if (!empty($inlinePermissions) && allowedTo('manage_permissions'))
1312
	{
1313
		require_once($sourcedir . '/ManagePermissions.php');
1314
		init_inline_permissions($inlinePermissions);
1315
	}
1316
1317
	if ($board_list)
1318
	{
1319
		require_once($sourcedir . '/Subs-MessageIndex.php');
1320
		$context['board_list'] = getBoardList();
1321
	}
1322
1323
	// What about any BBC selection boxes?
1324
	if (!empty($bbcChoice))
1325
	{
1326
		// What are the options, eh?
1327
		$temp = parse_bbc(false);
1328
		$bbcTags = array();
1329
		foreach ($temp as $tag)
1330
			if (!isset($tag['require_parents']))
1331
				$bbcTags[] = $tag['tag'];
1332
1333
		$bbcTags = array_unique($bbcTags);
1334
1335
		// The number of columns we want to show the BBC tags in.
1336
		$numColumns = isset($context['num_bbc_columns']) ? $context['num_bbc_columns'] : 3;
1337
1338
		// Now put whatever BBC options we may have into context too!
1339
		$context['bbc_sections'] = array();
1340
		foreach ($bbcChoice as $bbcSection)
1341
		{
1342
			$context['bbc_sections'][$bbcSection] = array(
1343
				'title' => isset($txt['bbc_title_' . $bbcSection]) ? $txt['bbc_title_' . $bbcSection] : $txt['enabled_bbc_select'],
1344
				'disabled' => empty($modSettings['bbc_disabled_' . $bbcSection]) ? array() : $modSettings['bbc_disabled_' . $bbcSection],
1345
				'all_selected' => empty($modSettings['bbc_disabled_' . $bbcSection]),
1346
				'columns' => array(),
1347
			);
1348
1349
			if ($bbcSection == 'legacyBBC')
1350
				$sectionTags = array_intersect($context['legacy_bbc'], $bbcTags);
1351
			else
1352
				$sectionTags = array_diff($bbcTags, $context['legacy_bbc']);
1353
1354
			$totalTags = count($sectionTags);
1355
			$tagsPerColumn = ceil($totalTags / $numColumns);
1356
1357
			$col = 0;
1358
			$i = 0;
1359
			foreach ($sectionTags as $tag)
1360
			{
1361
				if ($i % $tagsPerColumn == 0 && $i != 0)
1362
					$col++;
1363
1364
				$context['bbc_sections'][$bbcSection]['columns'][$col][] = array(
1365
					'tag' => $tag,
1366
					'show_help' => isset($helptxt['tag_' . $tag]),
1367
				);
1368
1369
				$i++;
1370
			}
1371
		}
1372
	}
1373
1374
	call_integration_hook('integrate_prepare_db_settings', array(&$config_vars));
1375
	createToken('admin-dbsc');
1376
}
1377
1378
/**
1379
 * Helper function. Saves settings by putting them in Settings.php or saving them in the settings table.
1380
 *
1381
 * - Saves those settings set from ?action=admin;area=serversettings.
1382
 * - Requires the admin_forum permission.
1383
 * - Contains arrays of the types of data to save into Settings.php.
1384
 *
1385
 * @param array $config_vars An array of configuration variables
1386
 */
1387
function saveSettings(&$config_vars)
1388
{
1389
	global $sourcedir, $context;
1390
1391
	validateToken('admin-ssc');
1392
1393
	// Fix the darn stupid cookiename! (more may not be allowed, but these for sure!)
1394
	if (isset($_POST['cookiename']))
1395
		$_POST['cookiename'] = preg_replace('~[,;\s\.$]+~' . ($context['utf8'] ? 'u' : ''), '', $_POST['cookiename']);
1396
1397
	// Fix the forum's URL if necessary.
1398
	if (isset($_POST['boardurl']))
1399
	{
1400
		if (substr($_POST['boardurl'], -10) == '/index.php')
1401
			$_POST['boardurl'] = substr($_POST['boardurl'], 0, -10);
1402
		elseif (substr($_POST['boardurl'], -1) == '/')
1403
			$_POST['boardurl'] = substr($_POST['boardurl'], 0, -1);
1404
		if (substr($_POST['boardurl'], 0, 7) != 'http://' && substr($_POST['boardurl'], 0, 7) != 'file://' && substr($_POST['boardurl'], 0, 8) != 'https://')
1405
			$_POST['boardurl'] = 'http://' . $_POST['boardurl'];
1406
1407
		$_POST['boardurl'] = normalize_iri($_POST['boardurl']);
1408
	}
1409
1410
	require_once($sourcedir . '/Subs-Admin.php');
1411
1412
	// Any passwords?
1413
	$config_passwords = array();
1414
1415
	// All the numeric variables.
1416
	$config_nums = array();
1417
1418
	// All the checkboxes
1419
	$config_bools = array();
1420
1421
	// Ones that accept multiple types (should be rare)
1422
	$config_multis = array();
1423
1424
	// Get all known setting definitions and assign them to our groups above.
1425
	$settings_defs = get_settings_defs();
1426
	foreach ($settings_defs as $var => $def)
1427
	{
1428
		if (!is_string($var))
1429
			continue;
1430
1431
		if (!empty($def['is_password']))
1432
		{
1433
			$config_passwords[] = $var;
1434
		}
1435
		else
1436
		{
1437
			// Special handling if multiple types are allowed.
1438
			if (is_array($def['type']))
1439
			{
1440
				// Obviously, we don't need null here.
1441
				$def['type'] = array_filter(
1442
					$def['type'],
1443
					function ($type)
1444
					{
1445
						return $type !== 'NULL';
1446
					}
1447
				);
1448
1449
				$type = count($def['type']) == 1 ? reset($def['type']) : 'multiple';
1450
			}
1451
			else
1452
				$type = $def['type'];
1453
1454
			switch ($type)
1455
			{
1456
				case 'multiple':
1457
					$config_multis[$var] = $def['type'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
1458
1459
				case 'double':
1460
					$config_nums[] = $var;
1461
					break;
1462
1463
				case 'integer':
1464
					// Some things saved as integers are presented as booleans
1465
					foreach ($config_vars as $config_var)
1466
					{
1467
						if (is_array($config_var) && $config_var[0] == $var)
1468
						{
1469
							if ($config_var[3] == 'check')
1470
							{
1471
								$config_bools[] = $var;
1472
								break 2;
1473
							}
1474
							else
1475
								break;
1476
						}
1477
					}
1478
					$config_nums[] = $var;
1479
					break;
1480
1481
				case 'boolean':
1482
					$config_bools[] = $var;
1483
					break;
1484
1485
				default:
1486
					break;
1487
			}
1488
		}
1489
	}
1490
1491
	// Now sort everything into a big array, and figure out arrays and etc.
1492
	$new_settings = array();
1493
	// Figure out which config vars we're saving here...
1494
	foreach ($config_vars as $config_var)
1495
	{
1496
		if (!is_array($config_var) || $config_var[2] != 'file')
1497
			continue;
1498
1499
		$var_name = $config_var[0];
1500
1501
		// Unknown setting?
1502
		if (!isset($settings_defs[$var_name]) && isset($config_var[3]))
1503
		{
1504
			switch ($config_var[3])
1505
			{
1506
				case 'int':
1507
				case 'float':
1508
					$config_nums[] = $var_name;
1509
					break;
1510
1511
				case 'check':
1512
					$config_bools[] = $var_name;
1513
					break;
1514
1515
				default:
1516
					break;
1517
			}
1518
		}
1519
1520
		if (!in_array($var_name, $config_bools) && !isset($_POST[$var_name]))
1521
			continue;
1522
1523
		if (in_array($var_name, $config_passwords))
1524
		{
1525
			if (isset($_POST[$var_name][1]) && $_POST[$var_name][0] == $_POST[$var_name][1])
1526
				$new_settings[$var_name] = $_POST[$var_name][0];
1527
		}
1528
		elseif (in_array($var_name, $config_nums))
1529
		{
1530
			$new_settings[$var_name] = (int) $_POST[$var_name];
1531
1532
			// If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
1533
			$min = isset($config_var['min']) ? $config_var['min'] : 0;
1534
			$new_settings[$var_name] = max($min, $new_settings[$var_name]);
1535
1536
			// Is there a max value for this as well?
1537
			if (isset($config_var['max']))
1538
				$new_settings[$var_name] = min($config_var['max'], $new_settings[$var_name]);
1539
		}
1540
		elseif (in_array($var_name, $config_bools))
1541
		{
1542
			$new_settings[$var_name] = !empty($_POST[$var_name]);
1543
		}
1544
		elseif (isset($config_multis[$var_name]))
1545
		{
1546
			$is_acceptable_type = false;
1547
1548
			foreach ($config_multis[$var_name] as $type)
1549
			{
1550
				$temp = $_POST[$var_name];
1551
				settype($temp, $type);
1552
1553
				if ($temp == $_POST[$var_name])
1554
				{
1555
					$new_settings[$var_name] = $temp;
1556
					$is_acceptable_type = true;
1557
					break;
1558
				}
1559
			}
1560
1561
			if (!$is_acceptable_type)
1562
				fatal_error('Invalid config_var \'' . $var_name . '\'');
1563
		}
1564
		else
1565
		{
1566
			$new_settings[$var_name] = $_POST[$var_name];
1567
		}
1568
	}
1569
1570
	// Save the relevant settings in the Settings.php file.
1571
	updateSettingsFile($new_settings);
1572
1573
	// Now loop through the remaining (database-based) settings.
1574
	$new_settings = array();
1575
	foreach ($config_vars as $config_var)
1576
	{
1577
		// We just saved the file-based settings, so skip their definitions.
1578
		if (!is_array($config_var) || $config_var[2] == 'file')
1579
			continue;
1580
1581
		$new_setting = array($config_var[3], $config_var[0]);
1582
1583
		// Select options need carried over, too.
1584
		if (isset($config_var[4]))
1585
			$new_setting[] = $config_var[4];
1586
1587
		// Include min and max if necessary
1588
		if (isset($config_var['min']))
1589
			$new_setting['min'] = $config_var['min'];
1590
1591
		if (isset($config_var['max']))
1592
			$new_setting['max'] = $config_var['max'];
1593
1594
		// Rewrite the definition a bit.
1595
		$new_settings[] = $new_setting;
1596
	}
1597
1598
	// Save the new database-based settings, if any.
1599
	if (!empty($new_settings))
1600
		saveDBSettings($new_settings);
1601
}
1602
1603
/**
1604
 * Helper function for saving database settings.
1605
 *
1606
 * @todo see rev. 10406 from 2.1-requests
1607
 *
1608
 * @param array $config_vars An array of configuration variables
1609
 */
1610
function saveDBSettings(&$config_vars)
1611
{
1612
	global $sourcedir, $smcFunc;
1613
	static $board_list = null;
1614
1615
	validateToken('admin-dbsc');
1616
1617
	$inlinePermissions = array();
1618
	foreach ($config_vars as $var)
1619
	{
1620
		if (!isset($var[1]) || (!isset($_POST[$var[1]]) && $var[0] != 'check' && $var[0] != 'permissions' && $var[0] != 'boards' && ($var[0] != 'bbc' || !isset($_POST[$var[1] . '_enabledTags']))))
1621
			continue;
1622
1623
		// Checkboxes!
1624
		elseif ($var[0] == 'check')
1625
			$setArray[$var[1]] = !empty($_POST[$var[1]]) ? '1' : '0';
1626
		// Select boxes!
1627
		elseif ($var[0] == 'select' && in_array($_POST[$var[1]], array_keys($var[2])))
1628
			$setArray[$var[1]] = $_POST[$var[1]];
1629
		elseif ($var[0] == 'select' && !empty($var['multiple']) && array_intersect($_POST[$var[1]], array_keys($var[2])) != array())
1630
		{
1631
			// For security purposes we validate this line by line.
1632
			$lOptions = array();
1633
			foreach ($_POST[$var[1]] as $invar)
1634
				if (in_array($invar, array_keys($var[2])))
1635
					$lOptions[] = $invar;
1636
1637
			$setArray[$var[1]] = $smcFunc['json_encode']($lOptions);
1638
		}
1639
		// List of boards!
1640
		elseif ($var[0] == 'boards')
1641
		{
1642
			// We just need a simple list of valid boards, nothing more.
1643
			if ($board_list === null)
1644
			{
1645
				$board_list = array();
1646
				$request = $smcFunc['db_query']('', '
1647
					SELECT id_board
1648
					FROM {db_prefix}boards');
1649
1650
				while ($row = $smcFunc['db_fetch_row']($request))
1651
					$board_list[$row[0]] = true;
1652
1653
				$smcFunc['db_free_result']($request);
1654
			}
1655
1656
			$lOptions = array();
1657
1658
			if (!empty($_POST[$var[1]]))
1659
				foreach ($_POST[$var[1]] as $invar => $dummy)
1660
					if (isset($board_list[$invar]))
1661
						$lOptions[] = $invar;
1662
1663
			$setArray[$var[1]] = !empty($lOptions) ? implode(',', $lOptions) : '';
1664
		}
1665
		// Integers!
1666
		elseif ($var[0] == 'int')
1667
		{
1668
			$setArray[$var[1]] = (int) $_POST[$var[1]];
1669
1670
			// If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
1671
			$min = isset($var['min']) ? $var['min'] : 0;
1672
			$setArray[$var[1]] = max($min, $setArray[$var[1]]);
1673
1674
			// Do we have a max value for this as well?
1675
			if (isset($var['max']))
1676
				$setArray[$var[1]] = min($var['max'], $setArray[$var[1]]);
1677
		}
1678
		// Floating point!
1679
		elseif ($var[0] == 'float')
1680
		{
1681
			$setArray[$var[1]] = (float) $_POST[$var[1]];
1682
1683
			// If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
1684
			$min = isset($var['min']) ? $var['min'] : 0;
1685
			$setArray[$var[1]] = max($min, $setArray[$var[1]]);
1686
1687
			// Do we have a max value for this as well?
1688
			if (isset($var['max']))
1689
				$setArray[$var[1]] = min($var['max'], $setArray[$var[1]]);
1690
		}
1691
		// Text!
1692
		elseif (in_array($var[0], array('text', 'large_text', 'color', 'date', 'datetime', 'datetime-local', 'email', 'month', 'time')))
1693
			$setArray[$var[1]] = $_POST[$var[1]];
1694
		// Passwords!
1695
		elseif ($var[0] == 'password')
1696
		{
1697
			if (isset($_POST[$var[1]][1]) && $_POST[$var[1]][0] == $_POST[$var[1]][1])
1698
				$setArray[$var[1]] = $_POST[$var[1]][0];
1699
		}
1700
		// BBC.
1701
		elseif ($var[0] == 'bbc')
1702
		{
1703
			$bbcTags = array();
1704
			foreach (parse_bbc(false) as $tag)
1705
				$bbcTags[] = $tag['tag'];
1706
1707
			if (!isset($_POST[$var[1] . '_enabledTags']))
1708
				$_POST[$var[1] . '_enabledTags'] = array();
1709
			elseif (!is_array($_POST[$var[1] . '_enabledTags']))
1710
				$_POST[$var[1] . '_enabledTags'] = array($_POST[$var[1] . '_enabledTags']);
1711
1712
			$setArray[$var[1]] = implode(',', array_diff($bbcTags, $_POST[$var[1] . '_enabledTags']));
1713
		}
1714
		// Permissions?
1715
		elseif ($var[0] == 'permissions')
1716
			$inlinePermissions[] = $var[1];
1717
	}
1718
1719
	if (!empty($setArray))
1720
		updateSettings($setArray);
1721
1722
	// If we have inline permissions we need to save them.
1723
	if (!empty($inlinePermissions) && allowedTo('manage_permissions'))
1724
	{
1725
		require_once($sourcedir . '/ManagePermissions.php');
1726
		save_inline_permissions($inlinePermissions);
1727
	}
1728
}
1729
1730
/**
1731
 * Allows us to see the servers php settings
1732
 *
1733
 * - loads the settings into an array for display in a template
1734
 * - drops cookie values just in case
1735
 */
1736
function ShowPHPinfoSettings()
1737
{
1738
	global $context, $txt;
1739
1740
	$category = $txt['phpinfo_settings'];
1741
1742
	// get the data
1743
	ob_start();
1744
	phpinfo();
1745
1746
	// We only want it for its body, pigs that we are
1747
	$info_lines = preg_replace('~^.*<body>(.*)</body>.*$~', '$1', ob_get_contents());
1748
	$info_lines = explode("\n", strip_tags($info_lines, "<tr><td><h2>"));
1749
	ob_end_clean();
1750
1751
	// remove things that could be considered sensitive
1752
	$remove = '_COOKIE|Cookie|_GET|_REQUEST|REQUEST_URI|QUERY_STRING|REQUEST_URL|HTTP_REFERER';
1753
1754
	// put all of it into an array
1755
	foreach ($info_lines as $line)
1756
	{
1757
		if (preg_match('~(' . $remove . ')~', $line))
1758
			continue;
1759
1760
		// new category?
1761
		if (strpos($line, '<h2>') !== false)
1762
			$category = preg_match('~<h2>(.*)</h2>~', $line, $title) ? $category = $title[1] : $category;
1763
1764
		// load it as setting => value or the old setting local master
1765
		if (preg_match('~<tr><td[^>]+>([^<]*)</td><td[^>]+>([^<]*)</td></tr>~', $line, $val))
1766
			$pinfo[$category][$val[1]] = $val[2];
1767
		elseif (preg_match('~<tr><td[^>]+>([^<]*)</td><td[^>]+>([^<]*)</td><td[^>]+>([^<]*)</td></tr>~', $line, $val))
1768
			$pinfo[$category][$val[1]] = array($txt['phpinfo_localsettings'] => $val[2], $txt['phpinfo_defaultsettings'] => $val[3]);
1769
	}
1770
1771
	// load it in to context and display it
1772
	$context['pinfo'] = $pinfo;
1773
	$context['page_title'] = $txt['admin_server_settings'];
1774
	$context['sub_template'] = 'php_info';
1775
	return;
1776
}
1777
1778
/**
1779
 * Get the installed Cache API implementations.
1780
 *
1781
 */
1782
function loadCacheAPIs()
1783
{
1784
	global $sourcedir;
1785
1786
	$cacheAPIdir = $sourcedir . '/Cache';
1787
1788
	$loadedApis = array();
1789
	$apis_dir = $cacheAPIdir .'/'. CacheApi::APIS_FOLDER;
1790
1791
	$api_classes = new GlobIterator($apis_dir . '/*.php', FilesystemIterator::NEW_CURRENT_AND_KEY);
1792
1793
	foreach ($api_classes as $file_path => $file_info)
1794
	{
1795
		require_once($apis_dir . '/' . $file_path);
1796
1797
		$class_name = $file_info->getBasename('.php');
1798
		$fully_qualified_class_name = CacheApi::APIS_NAMESPACE . $class_name;
1799
1800
		/* @var CacheApiInterface $cache_api */
1801
		$cache_api = new $fully_qualified_class_name();
1802
1803
		// Deal with it!
1804
		if (!($cache_api instanceof CacheApiInterface) || !($cache_api instanceof CacheApi))
1805
			continue;
1806
1807
		// No Support?  NEXT!
1808
		if (!$cache_api->isSupported(true))
1809
			continue;
1810
1811
		$loadedApis[$class_name] = $cache_api;
1812
	}
1813
1814
	call_integration_hook('integrate_load_cache_apis', array(&$loadedApis));
1815
1816
	return $loadedApis;
1817
}
1818
1819
/**
1820
 * Registers the site with the Simple Machines Stat collection. This function
1821
 * purposely does not use updateSettings.php as it will be called shortly after
1822
 * this process completes by the saveSettings() function.
1823
 *
1824
 * @see SMStats() for more information.
1825
 * @link https://www.simplemachines.org/about/stats.php for more info.
1826
 *
1827
 */
1828
function registerSMStats()
1829
{
1830
	global $modSettings, $boardurl, $smcFunc;
1831
1832
	// Already have a key?  Can't register again.
1833
	if (!empty($modSettings['sm_stats_key']))
1834
		return true;
1835
1836
	$fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr);
1837
	if (!$fp)
1838
		$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1839
	if ($fp)
1840
	{
1841
		$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1842
		$out .= 'Host: www.simplemachines.org' . "\r\n";
1843
		$out .= 'Connection: Close' . "\r\n\r\n";
1844
		fwrite($fp, $out);
1845
1846
		$return_data = '';
1847
		while (!feof($fp))
1848
			$return_data .= fgets($fp, 128);
1849
1850
		fclose($fp);
1851
1852
		// Get the unique site ID.
1853
		preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1854
1855
		if (!empty($ID[1]))
1856
		{
1857
			$smcFunc['db_insert']('replace',
1858
				'{db_prefix}settings',
1859
				array('variable' => 'string', 'value' => 'string'),
1860
				array('sm_stats_key', $ID[1]),
1861
				array('variable')
1862
			);
1863
			return true;
1864
		}
1865
	}
1866
1867
	return false;
1868
}
1869
1870
?>