$txt['displayedValue'])),
* Note that just saying array('first', 'second') will put 0 in the SQL for 'first'.
* - A password input box. Used for passwords, no less!
* array('password', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
* - A permission - for picking groups who have a permission.
* array('permissions', 'manage_groups'),
* - A BBC selection box.
* array('bbc', 'sig_bbc'),
* - A list of boards to choose from
* array('boards', 'likes_boards'),
* Note that the storage in the database is as 1,2,3,4
*
* For each option:
* - type (see above), variable name, size/possible values.
* OR make type '' for an empty string for a horizontal rule.
* - SET preinput - to put some HTML prior to the input box.
* - SET postinput - to put some HTML following the input box.
* - SET invalid - to mark the data as invalid.
* - PLUS you can override label and help parameters by forcing their keys in the array, for example:
* array('text', 'invalidlabel', 3, 'label' => 'Actual Label')
*
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1.3
*/
use SMF\Cache\CacheApi;
use SMF\Cache\CacheApiInterface;
if (!defined('SMF'))
die('No direct access...');
/**
* This is the main dispatcher. Sets up all the available sub-actions, all the tabs and selects
* the appropriate one based on the sub-action.
*
* Requires the admin_forum permission.
* Redirects to the appropriate function based on the sub-action.
*
* Uses edit_settings adminIndex.
*/
function ModifySettings()
{
global $context, $txt, $boarddir;
// This is just to keep the database password more secure.
isAllowedTo('admin_forum');
// Load up all the tabs...
$context[$context['admin_menu_name']]['tab_data'] = array(
'title' => $txt['admin_server_settings'],
'help' => 'serversettings',
'description' => $txt['admin_basic_settings'],
);
checkSession('request');
// The settings are in here, I swear!
loadLanguage('ManageSettings');
$context['page_title'] = $txt['admin_server_settings'];
$context['sub_template'] = 'show_settings';
$subActions = array(
'general' => 'ModifyGeneralSettings',
'database' => 'ModifyDatabaseSettings',
'cookie' => 'ModifyCookieSettings',
'security' => 'ModifyGeneralSecuritySettings',
'cache' => 'ModifyCacheSettings',
'export' => 'ModifyExportSettings',
'loads' => 'ModifyLoadBalancingSettings',
'phpinfo' => 'ShowPHPinfoSettings',
);
// Warn the user if there's any relevant information regarding Settings.php.
$settings_not_writable = !is_writable($boarddir . '/Settings.php');
$settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php');
if ($settings_backup_fail)
$context['settings_message'] = array(
'label' => $txt['admin_backup_fail'],
'tag' => 'div',
'class' => 'centertext strong'
);
$context['settings_not_writable'] = $settings_not_writable;
call_integration_hook('integrate_server_settings', array(&$subActions));
// By default we're editing the core settings
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'general';
$context['sub_action'] = $_REQUEST['sa'];
// Call the right function for this sub-action.
call_helper($subActions[$_REQUEST['sa']]);
}
/**
* General forum settings - forum name, maintenance mode, etc.
* Practically, this shows an interface for the settings in Settings.php to be changed.
*
* - Requires the admin_forum permission.
* - Uses the edit_settings administration area.
* - Contains the actual array of settings to show from Settings.php.
* - Accessed from ?action=admin;area=serversettings;sa=general.
*
* @param bool $return_config Whether to return the $config_vars array (for pagination purposes)
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
*/
function ModifyGeneralSettings($return_config = false)
{
global $scripturl, $context, $txt, $modSettings, $boardurl, $sourcedir, $smcFunc;
/* If you're writing a mod, it's a bad idea to add things here....
For each option:
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)
OR an empty string for a horizontal rule.
OR a string for a titled section. */
$config_vars = array(
array('mbname', $txt['admin_title'], 'file', 'text', 30),
'',
array('maintenance', $txt['admin_maintain'], 'file', 'check'),
array('mtitle', $txt['maintenance_subject'], 'file', 'text', 36),
array('mmessage', $txt['maintenance_message'], 'file', 'text', 36),
'',
array('webmaster_email', $txt['admin_webmaster_email'], 'file', 'text', 30),
'',
array('enableCompressedOutput', $txt['enableCompressedOutput'], 'db', 'check', null, 'enableCompressedOutput'),
array('disableHostnameLookup', $txt['disableHostnameLookup'], 'db', 'check', null, 'disableHostnameLookup'),
'',
'force_ssl' => array('force_ssl', $txt['force_ssl'], 'db', 'select', array($txt['force_ssl_off'], $txt['force_ssl_complete']), 'force_ssl'),
array('image_proxy_enabled', $txt['image_proxy_enabled'], 'file', 'check', null, 'image_proxy_enabled'),
array('image_proxy_secret', $txt['image_proxy_secret'], 'file', 'text', 30, 'image_proxy_secret'),
array('image_proxy_maxsize', $txt['image_proxy_maxsize'], 'file', 'int', null, 'image_proxy_maxsize'),
'',
array('enable_sm_stats', $txt['enable_sm_stats'], 'db', 'check', null, 'enable_sm_stats'),
);
call_integration_hook('integrate_general_settings', array(&$config_vars));
if ($return_config)
return $config_vars;
// If no cert, force_ssl must remain 0 (The admin search doesn't require this)
$config_vars['force_ssl']['disabled'] = empty($modSettings['force_ssl']) && !ssl_cert_found($boardurl);
// Setup the template stuff.
$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=general;save';
$context['settings_title'] = $txt['general_settings'];
$context['save_disabled'] = $context['settings_not_writable'];
// Saving settings?
if (isset($_REQUEST['save']))
{
call_integration_hook('integrate_save_general_settings');
foreach ($config_vars as $config_var)
{
if (is_array($config_var) && isset($config_var[3]) && $config_var[3] == 'text' && !empty($_POST[$config_var[0]]))
$_POST[$config_var[0]] = $smcFunc['normalize']($_POST[$config_var[0]]);
}
// Are we saving the stat collection?
if (!empty($_POST['enable_sm_stats']) && empty($modSettings['sm_stats_key']))
{
$registerSMStats = registerSMStats();
// Failed to register, disable it again.
if (empty($registerSMStats))
$_POST['enable_sm_stats'] = 0;
}
// Ensure all URLs are aligned with the new force_ssl setting
// Treat unset like 0
if (isset($_POST['force_ssl']))
AlignURLsWithSSLSetting($_POST['force_ssl']);
else
AlignURLsWithSSLSetting(0);
saveSettings($config_vars);
$_SESSION['adm-save'] = true;
redirectexit('action=admin;area=serversettings;sa=general;' . $context['session_var'] . '=' . $context['session_id']);
}
// Fill the config array.
prepareServerSettingsContext($config_vars);
// Some javascript for SSL
if (empty($context['settings_not_writable']))
addInlineJavaScript('
$(function()
{
$("#force_ssl").change(function()
{
var mode = $(this).val() == 1 ? false : true;
$("#image_proxy_enabled").prop("disabled", mode);
$("#image_proxy_secret").prop("disabled", mode);
$("#image_proxy_maxsize").prop("disabled", mode);
}).change();
});', true);
}
/**
* Align URLs with SSL Setting.
*
* If force_ssl has changed, ensure all URLs are aligned with the new setting.
* This includes:
* - $boardurl
* - $modSettings['smileys_url']
* - $modSettings['avatar_url']
* - $modSettings['custom_avatar_url'] - if found
* - theme_url - all entries in the themes table
* - images_url - all entries in the themes table
*
* This function will NOT overwrite URLs that are not subfolders of $boardurl.
* The admin must have pointed those somewhere else on purpose, so they must be updated manually.
*
* A word of caution: You can't trust the http/https scheme reflected for these URLs in $globals
* (e.g., $boardurl) or in $modSettings. This is because SMF may change them in memory to comply
* with the force_ssl setting - a soft redirect may be in effect... Thus, conditional updates
* to these values do not work. You gotta just brute force overwrite them based on force_ssl.
*
* @param int $new_force_ssl is the current force_ssl setting.
* @return void Returns nothing, just does its job
*/
function AlignURLsWithSSLSetting($new_force_ssl = 0)
{
global $boardurl, $modSettings, $sourcedir, $smcFunc;
require_once($sourcedir . '/Subs-Admin.php');
// Check $boardurl
if (!empty($new_force_ssl))
$newval = strtr($boardurl, array('http://' => 'https://'));
else
$newval = strtr($boardurl, array('https://' => 'http://'));
updateSettingsFile(array('boardurl' => $newval));
$new_settings = array();
// Check $smileys_url, but only if it points to a subfolder of $boardurl
if (BoardurlMatch($modSettings['smileys_url']))
{
if (!empty($new_force_ssl))
$newval = strtr($modSettings['smileys_url'], array('http://' => 'https://'));
else
$newval = strtr($modSettings['smileys_url'], array('https://' => 'http://'));
$new_settings['smileys_url'] = $newval;
}
// Check $avatar_url, but only if it points to a subfolder of $boardurl
if (BoardurlMatch($modSettings['avatar_url']))
{
if (!empty($new_force_ssl))
$newval = strtr($modSettings['avatar_url'], array('http://' => 'https://'));
else
$newval = strtr($modSettings['avatar_url'], array('https://' => 'http://'));
$new_settings['avatar_url'] = $newval;
}
// Check $custom_avatar_url, but only if it points to a subfolder of $boardurl
// This one had been optional in the past, make sure it is set first
if (isset($modSettings['custom_avatar_url']) && BoardurlMatch($modSettings['custom_avatar_url']))
{
if (!empty($new_force_ssl))
$newval = strtr($modSettings['custom_avatar_url'], array('http://' => 'https://'));
else
$newval = strtr($modSettings['custom_avatar_url'], array('https://' => 'http://'));
$new_settings['custom_avatar_url'] = $newval;
}
// Save updates to the settings table
if (!empty($new_settings))
updateSettings($new_settings, true);
// Now we move onto the themes.
// First, get a list of theme URLs...
$request = $smcFunc['db_query']('', '
SELECT id_theme, variable, value
FROM {db_prefix}themes
WHERE variable in ({string:themeurl}, {string:imagesurl})
AND id_member = {int:zero}',
array(
'themeurl' => 'theme_url',
'imagesurl' => 'images_url',
'zero' => 0,
)
);
while ($row = $smcFunc['db_fetch_assoc']($request))
{
// First check to see if it points to a subfolder of $boardurl
if (BoardurlMatch($row['value']))
{
if (!empty($new_force_ssl))
$newval = strtr($row['value'], array('http://' => 'https://'));
else
$newval = strtr($row['value'], array('https://' => 'http://'));
$smcFunc['db_query']('', '
UPDATE {db_prefix}themes
SET value = {string:theme_val}
WHERE variable = {string:theme_var}
AND id_theme = {string:theme_id}
AND id_member = {int:zero}',
array(
'theme_val' => $newval,
'theme_var' => $row['variable'],
'theme_id' => $row['id_theme'],
'zero' => 0,
)
);
}
}
$smcFunc['db_free_result']($request);
}
/**
* $boardurl Match.
*
* Helper function to see if the url being checked is based off of $boardurl.
* If not, it was overridden by the admin to some other value on purpose, and should not
* be stepped on by SMF when aligning URLs with the force_ssl setting.
* The site admin must change URLs that are not aligned with $boardurl manually.
*
* @param string $url is the url to check.
* @return bool Returns true if the url is based off of $boardurl (without the scheme), false if not
*/
function BoardurlMatch($url = '')
{
global $boardurl;
// Strip the schemes
$urlpath = strtr($url, array('http://' => '', 'https://' => ''));
$boardurlpath = strtr($boardurl, array('http://' => '', 'https://' => ''));
// If leftmost portion of path matches boardurl, return true
$result = strpos($urlpath, $boardurlpath);
if ($result === false || $result != 0)
return false;
else
return true;
}
/**
* Basic database and paths settings - database name, host, etc.
*
* - It shows an interface for the settings in Settings.php to be changed.
* - It contains the actual array of settings to show from Settings.php.
* - Requires the admin_forum permission.
* - Uses the edit_settings administration area.
* - Accessed from ?action=admin;area=serversettings;sa=database.
*
* @param bool $return_config Whether or not to return the config_vars array (used for admin search)
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
*/
function ModifyDatabaseSettings($return_config = false)
{
global $scripturl, $context, $txt, $smcFunc;
db_extend('extra');
/* If you're writing a mod, it's a bad idea to add things here....
For each option:
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)
OR an empty string for a horizontal rule.
OR a string for a titled section. */
$config_vars = array(
array('db_persist', $txt['db_persist'], 'file', 'check', null, 'db_persist'),
array('db_error_send', $txt['db_error_send'], 'file', 'check'),
array('ssi_db_user', $txt['ssi_db_user'], 'file', 'text', null, 'ssi_db_user'),
array('ssi_db_passwd', $txt['ssi_db_passwd'], 'file', 'password'),
'',
array('autoFixDatabase', $txt['autoFixDatabase'], 'db', 'check', false, 'autoFixDatabase')
);
// Add PG Stuff
if ($smcFunc['db_title'] === POSTGRE_TITLE)
{
$request = $smcFunc['db_query']('', 'SELECT cfgname FROM pg_ts_config', array());
$fts_language = array();
while ($row = $smcFunc['db_fetch_assoc']($request))
$fts_language[$row['cfgname']] = $row['cfgname'];
$config_vars = array_merge($config_vars, array(
'',
array('search_language', $txt['search_language'], 'db', 'select', $fts_language, 'pgFulltextSearch')
)
);
}
call_integration_hook('integrate_database_settings', array(&$config_vars));
if ($return_config)
return $config_vars;
// Setup the template stuff.
$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=database;save';
$context['settings_title'] = $txt['database_settings'];
$context['save_disabled'] = $context['settings_not_writable'];
if (!$smcFunc['db_allow_persistent']())
addInlineJavaScript('
$(function()
{
$("#db_persist").prop("disabled", true);
});', true);
// Saving settings?
if (isset($_REQUEST['save']))
{
call_integration_hook('integrate_save_database_settings');
saveSettings($config_vars);
$_SESSION['adm-save'] = true;
redirectexit('action=admin;area=serversettings;sa=database;' . $context['session_var'] . '=' . $context['session_id']);
}
// Fill the config array.
prepareServerSettingsContext($config_vars);
}
/**
* This function handles cookies settings modifications.
*
* @param bool $return_config Whether or not to return the config_vars array (used for admin search)
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
*/
function ModifyCookieSettings($return_config = false)
{
global $context, $scripturl, $txt, $sourcedir, $modSettings, $cookiename, $user_settings, $boardurl, $smcFunc;
// Define the variables we want to edit.
$config_vars = array(
// Cookies...
array('cookiename', $txt['cookie_name'], 'file', 'text', 20),
array('cookieTime', $txt['cookieTime'], 'db', 'select', array_filter(array_map(
function ($str) use ($txt)
{
return isset($txt[$str]) ? $txt[$str] : '';
},
$context['login_cookie_times']
))),
array('localCookies', $txt['localCookies'], 'db', 'check', false, 'localCookies'),
array('globalCookies', $txt['globalCookies'], 'db', 'check', false, 'globalCookies'),
array('globalCookiesDomain', $txt['globalCookiesDomain'], 'db', 'text', false, 'globalCookiesDomain'),
array('secureCookies', $txt['secureCookies'], 'db', 'check', false, 'secureCookies', 'disabled' => !httpsOn()),
array('httponlyCookies', $txt['httponlyCookies'], 'db', 'check', false, 'httponlyCookies'),
array('samesiteCookies', $txt['samesiteCookies'], 'db', 'select', array(
'none' => $txt['samesiteNone'],
'lax' => $txt['samesiteLax'],
'strict' => $txt['samesiteStrict']
),
'samesiteCookies'),
'',
// Sessions
array('databaseSession_enable', $txt['databaseSession_enable'], 'db', 'check', false, 'databaseSession_enable'),
array('databaseSession_loose', $txt['databaseSession_loose'], 'db', 'check', false, 'databaseSession_loose'),
array('databaseSession_lifetime', $txt['databaseSession_lifetime'], 'db', 'int', false, 'databaseSession_lifetime', 'postinput' => $txt['seconds']),
'',
// 2FA
array('tfa_mode', $txt['tfa_mode'], 'db', 'select', array(
0 => $txt['tfa_mode_disabled'],
1 => $txt['tfa_mode_enabled'],
) + (empty($user_settings['tfa_secret']) ? array() : array(
2 => $txt['tfa_mode_forced'],
)) + (empty($user_settings['tfa_secret']) ? array() : array(
3 => $txt['tfa_mode_forcedall'],
)), 'subtext' => $txt['tfa_mode_subtext'] . (empty($user_settings['tfa_secret']) ? '
' . $txt['tfa_mode_forced_help'] . '' : ''), 'tfa_mode'),
);
addInlineJavaScript('
function hideGlobalCookies()
{
var usingLocal = $("#localCookies").prop("checked");
$("#setting_globalCookies").closest("dt").toggle(!usingLocal);
$("#globalCookies").closest("dd").toggle(!usingLocal);
var usingGlobal = !usingLocal && $("#globalCookies").prop("checked");
$("#setting_globalCookiesDomain").closest("dt").toggle(usingGlobal);
$("#globalCookiesDomain").closest("dd").toggle(usingGlobal);
};
hideGlobalCookies();
$("#localCookies, #globalCookies").click(function() {
hideGlobalCookies();
});
', true);
if (empty($user_settings['tfa_secret']))
addInlineJavaScript('');
call_integration_hook('integrate_cookie_settings', array(&$config_vars));
if ($return_config)
return $config_vars;
$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cookie;save';
$context['settings_title'] = $txt['cookies_sessions_settings'];
$context['save_disabled'] = $context['settings_not_writable'];
// Saving settings?
if (isset($_REQUEST['save']))
{
call_integration_hook('integrate_save_cookie_settings');
$_POST['cookiename'] = $smcFunc['normalize']($_POST['cookiename']);
// Local and global do not play nicely together.
if (!empty($_POST['localCookies']) && empty($_POST['globalCookies']))
unset ($_POST['globalCookies']);
if (empty($modSettings['localCookies']) != empty($_POST['localCookies']) || empty($modSettings['globalCookies']) != empty($_POST['globalCookies']))
$scope_changed = true;
if (!empty($_POST['globalCookiesDomain']))
{
$_POST['globalCookiesDomain'] = parse_iri(normalize_iri((strpos($_POST['globalCookiesDomain'], '//') === false ? 'http://' : '') . ltrim($_POST['globalCookiesDomain'], '.')), PHP_URL_HOST);
if (!preg_match('/(?:^|\.)' . preg_quote($_POST['globalCookiesDomain'], '/') . '$/u', parse_iri($boardurl, PHP_URL_HOST)))
fatal_lang_error('invalid_cookie_domain', false);
}
// Per spec, if samesite setting is 'none', cookies MUST be secure. Thems the rules. Else you lock everyone out...
if (!empty($_POST['samesiteCookies']) && ($_POST['samesiteCookies'] === 'none') && empty($_POST['secureCookies']))
fatal_lang_error('samesiteSecureRequired', false);
saveSettings($config_vars);
// If the cookie name or scope were changed, reset the cookie.
if ($cookiename != $_POST['cookiename'] || !empty($scope_changed))
{
$original_session_id = $context['session_id'];
include_once($sourcedir . '/Subs-Auth.php');
// Remove the old cookie.
setLoginCookie(-3600, 0);
// Set the new one.
$cookiename = !empty($_POST['cookiename']) ? $_POST['cookiename'] : $cookiename;
setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], hash_salt($user_settings['passwd'], $user_settings['password_salt']));
redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $original_session_id, $context['server']['needs_login_fix']);
}
//If we disabled 2FA, reset all members and membergroups settings.
if (isset($_POST['tfa_mode']) && empty($_POST['tfa_mode']))
{
$smcFunc['db_query']('', '
UPDATE {db_prefix}membergroups
SET tfa_required = {int:zero}',
array(
'zero' => 0,
)
);
$smcFunc['db_query']('', '
UPDATE {db_prefix}members
SET tfa_secret = {string:empty}, tfa_backup = {string:empty}',
array(
'empty' => '',
)
);
}
$_SESSION['adm-save'] = true;
redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $context['session_id']);
}
// Fill the config array.
prepareServerSettingsContext($config_vars);
}
/**
* Settings really associated with general security aspects.
*
* @param bool $return_config Whether or not to return the config_vars array (used for admin search)
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
*/
function ModifyGeneralSecuritySettings($return_config = false)
{
global $txt, $scripturl, $context;
$config_vars = array(
array('int', 'failed_login_threshold'),
array('int', 'loginHistoryDays', 'subtext' => $txt['zero_to_disable']),
'',
array('check', 'securityDisable'),
array('check', 'securityDisable_moderate'),
'',
// Reactive on email, and approve on delete
array('check', 'send_validation_onChange'),
array('check', 'approveAccountDeletion'),
'',
// Password strength.
array(
'select',
'password_strength',
array(
$txt['setting_password_strength_low'],
$txt['setting_password_strength_medium'],
$txt['setting_password_strength_high']
)
),
array('check', 'enable_password_conversion'),
'',
// Reporting of personal messages?
array('check', 'enableReportPM'),
'',
array('check', 'allow_cors'),
array('check', 'allow_cors_credentials'),
array('text', 'cors_domains'),
array('text', 'cors_headers'),
'',
array(
'select',
'frame_security',
array(
'SAMEORIGIN' => $txt['setting_frame_security_SAMEORIGIN'],
'DENY' => $txt['setting_frame_security_DENY'],
'DISABLE' => $txt['setting_frame_security_DISABLE']
)
),
'',
array(
'select',
'proxy_ip_header',
array(
'disabled' => $txt['setting_proxy_ip_header_disabled'],
'autodetect' => $txt['setting_proxy_ip_header_autodetect'],
'HTTP_X_FORWARDED_FOR' => 'X-Forwarded-For',
'HTTP_CLIENT_IP' => 'Client-IP',
'HTTP_X_REAL_IP' => 'X-Real-IP',
'HTTP_CF_CONNECTING_IP' => 'CF-Connecting-IP'
)
),
array('text', 'proxy_ip_servers'),
);
call_integration_hook('integrate_general_security_settings', array(&$config_vars));
if ($return_config)
return $config_vars;
// Saving?
if (isset($_GET['save']))
{
if (!empty($_POST['cors_domains']))
{
$cors_domains = explode(',', $_POST['cors_domains']);
foreach ($cors_domains as &$cors_domain)
{
if (strpos($cors_domain, '//') === false)
$cors_domain = '//' . $cors_domain;
$temp = parse_iri(normalize_iri($cors_domain));
if (strpos($temp['host'], '*') !== false)
$temp['host'] = substr($temp['host'], strrpos($temp['host'], '*'));
$cors_domain = (!empty($temp['scheme']) ? $temp['scheme'] . '://' : '') . $temp['host'] . (!empty($temp['port']) ? ':' . $temp['port'] : '');
}
$_POST['cors_domains'] = implode(',', $cors_domains);
}
saveDBSettings($config_vars);
$_SESSION['adm-save'] = true;
call_integration_hook('integrate_save_general_security_settings');
writeLog();
redirectexit('action=admin;area=serversettings;sa=security;' . $context['session_var'] . '=' . $context['session_id']);
}
$context['post_url'] = $scripturl . '?action=admin;area=serversettings;save;sa=security';
$context['settings_title'] = $txt['security_settings'];
prepareDBSettingContext($config_vars);
}
/**
* Simply modifying cache functions
*
* @param bool $return_config Whether or not to return the config_vars array (used for admin search)
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
*/
function ModifyCacheSettings($return_config = false)
{
global $context, $scripturl, $txt, $cacheAPI, $cache_enable, $cache_accelerator;
// Detect all available optimizers
$detectedCacheApis = loadCacheAPIs();
$apis_names = array();
/* @var CacheApiInterface $cache_api */
foreach ($detectedCacheApis as $class_name => $cache_api)
{
$class_name_txt_key = strtolower($cache_api->getImplementationClassKeyName());
$apis_names[$class_name] = isset($txt[$class_name_txt_key . '_cache']) ?
$txt[$class_name_txt_key . '_cache'] : $class_name;
}
// set our values to show what, if anything, we found
if (empty($detectedCacheApis))
{
$txt['cache_settings_message'] = '' . $txt['detected_no_caching'] . '';
$cache_level = array($txt['cache_off']);
$apis_names['none'] = $txt['cache_off'];
}
else
{
$txt['cache_settings_message'] = '' .
sprintf($txt['detected_accelerators'], implode(', ', $apis_names)) . '';
$cache_level = array($txt['cache_off'], $txt['cache_level1'], $txt['cache_level2'], $txt['cache_level3']);
}
// Define the variables we want to edit.
$config_vars = array(
// Only a few settings, but they are important
array('', $txt['cache_settings_message'], '', 'desc'),
array('cache_enable', $txt['cache_enable'], 'file', 'select', $cache_level, 'cache_enable'),
array('cache_accelerator', $txt['cache_accelerator'], 'file', 'select', $apis_names),
);
// some javascript to enable / disable certain settings if the option is not selected
$context['settings_post_javascript'] = '
$(document).ready(function() {
$("#cache_accelerator").change();
});';
call_integration_hook('integrate_modify_cache_settings', array(&$config_vars));
// Maybe we have some additional settings from the selected accelerator.
if (!empty($detectedCacheApis))
/* @var CacheApiInterface $cache_api */
foreach ($detectedCacheApis as $class_name_txt_key => $cache_api)
if (is_callable(array($cache_api, 'cacheSettings')))
$cache_api->cacheSettings($config_vars);
if ($return_config)
return $config_vars;
// Saving again?
if (isset($_GET['save']))
{
call_integration_hook('integrate_save_cache_settings');
if (is_callable(array($cacheAPI, 'cleanCache')) && ((int) $_POST['cache_enable'] < $cache_enable || $_POST['cache_accelerator'] != $cache_accelerator))
{
$cacheAPI->cleanCache();
}
saveSettings($config_vars);
$_SESSION['adm-save'] = true;
// We need to save the $cache_enable to $modSettings as well
updateSettings(array('cache_enable' => (int) $_POST['cache_enable']));
// exit so we reload our new settings on the page
redirectexit('action=admin;area=serversettings;sa=cache;' . $context['session_var'] . '=' . $context['session_id']);
}
loadLanguage('ManageMaintenance');
createToken('admin-maint');
$context['template_layers'][] = 'clean_cache_button';
$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cache;save';
$context['settings_title'] = $txt['caching_settings'];
// Changing cache settings won't have any effect if Settings.php is not writable.
$context['save_disabled'] = $context['settings_not_writable'];
// Decide what message to show.
if (!$context['save_disabled'])
$context['settings_message'] = $txt['caching_information'];
// Prepare the template.
prepareServerSettingsContext($config_vars);
}
/**
* Controls settings for data export functionality
*
* @param bool $return_config Whether or not to return the config_vars array (used for admin search)
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
*/
function ModifyExportSettings($return_config = false)
{
global $context, $scripturl, $txt, $modSettings, $boarddir, $sourcedir;
// Fill in a default value for this if it is missing.
if (empty($modSettings['export_dir']))
$modSettings['export_dir'] = $boarddir . DIRECTORY_SEPARATOR . 'exports';
/*
Some paranoid hosts worry that the disk space functions pose a security
risk. Usually these hosts just disable the functions and move on, which
is fine. A rare few, however, are not only paranoid, but also think it'd
be a "clever" security move to overload the disk space functions with
custom code that intentionally delivers false information, which is
idiotic and evil. At any rate, if the functions are unavailable or if
they report obviously insane values, it's not possible to track disk
usage correctly.
*/
$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);
$context['settings_message'] = $txt['export_settings_description'];
$config_vars = array(
array('text', 'export_dir', 40),
array('int', 'export_expiry', 'subtext' => $txt['zero_to_disable'], 'postinput' => $txt['days_word']),
array('int', 'export_min_diskspace_pct', 'postinput' => '%', 'max' => 80, 'disabled' => $diskspace_disabled),
array('int', 'export_rate', 'min' => 5, 'max' => 500, 'step' => 5, 'subtext' => $txt['export_rate_desc']),
);
call_integration_hook('integrate_export_settings', array(&$config_vars));
if ($return_config)
return $config_vars;
if (isset($_REQUEST['save']))
{
$prev_export_dir = is_dir($modSettings['export_dir']) ? rtrim($modSettings['export_dir'], '/\\') : '';
if (!empty($_POST['export_dir']))
$_POST['export_dir'] = rtrim($_POST['export_dir'], '/\\');
if ($diskspace_disabled)
$_POST['export_min_diskspace_pct'] = 0;
$_POST['export_rate'] = max(5, min($_POST['export_rate'], 500));
saveDBSettings($config_vars);
// Create the new directory, but revert to the previous one if anything goes wrong.
require_once($sourcedir . '/Profile-Export.php');
create_export_dir($prev_export_dir);
// Ensure we don't lose track of any existing export files.
if (!empty($prev_export_dir) && $prev_export_dir != $modSettings['export_dir'])
{
$export_files = glob($prev_export_dir . DIRECTORY_SEPARATOR . '*');
foreach ($export_files as $export_file)
{
if (!in_array(basename($export_file), array('index.php', '.htaccess')))
{
rename($export_file, $modSettings['export_dir'] . DIRECTORY_SEPARATOR . basename($export_file));
}
}
}
call_integration_hook('integrate_save_export_settings');
$_SESSION['adm-save'] = true;
redirectexit('action=admin;area=serversettings;sa=export;' . $context['session_var'] . '=' . $context['session_id']);
}
$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=export;save';
$context['settings_title'] = $txt['export_settings'];
prepareDBSettingContext($config_vars);
}
/**
* Allows to edit load balancing settings.
*
* @param bool $return_config Whether or not to return the config_vars array
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
*/
function ModifyLoadBalancingSettings($return_config = false)
{
global $txt, $scripturl, $context, $modSettings;
// Setup a warning message, but disabled by default.
$disabled = true;
$context['settings_message'] = array('label' => $txt['loadavg_disabled_conf'], 'class' => 'error');
if (DIRECTORY_SEPARATOR === '\\')
{
$context['settings_message']['label'] = $txt['loadavg_disabled_windows'];
if (isset($_GET['save']))
$_SESSION['adm-save'] = $context['settings_message']['label'];
}
elseif (stripos(PHP_OS, 'darwin') === 0)
{
$context['settings_message']['label'] = $txt['loadavg_disabled_osx'];
if (isset($_GET['save']))
$_SESSION['adm-save'] = $context['settings_message']['label'];
}
else
{
$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) !== 0)
$modSettings['load_average'] = (float) $matches[1];
elseif (($modSettings['load_average'] = @`uptime`) !== null && preg_match('~load averages?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) !== 0)
$modSettings['load_average'] = (float) $matches[1];
else
unset($modSettings['load_average']);
if (!empty($modSettings['load_average']) || (isset($modSettings['load_average']) && $modSettings['load_average'] === 0.0))
{
$context['settings_message']['label'] = sprintf($txt['loadavg_warning'], $modSettings['load_average']);
$disabled = false;
}
}
// Start with a simple checkbox.
$config_vars = array(
array('check', 'loadavg_enable', 'disabled' => $disabled),
);
// Set the default values for each option.
$default_values = array(
'loadavg_auto_opt' => 1.0,
'loadavg_search' => 2.5,
'loadavg_allunread' => 2.0,
'loadavg_unreadreplies' => 3.5,
'loadavg_show_posts' => 2.0,
'loadavg_userstats' => 10.0,
'loadavg_bbc' => 30.0,
'loadavg_forum' => 40.0,
);
// Loop through the settings.
foreach ($default_values as $name => $value)
{
// Use the default value if the setting isn't set yet.
$value = !isset($modSettings[$name]) ? $value : $modSettings[$name];
$config_vars[] = array('float', $name, 'value' => $value, 'disabled' => $disabled);
}
call_integration_hook('integrate_loadavg_settings', array(&$config_vars));
if ($return_config)
return $config_vars;
$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=loads;save';
$context['settings_title'] = $txt['load_balancing_settings'];
// Saving?
if (isset($_GET['save']))
{
// Stupidity is not allowed.
foreach ($_POST as $key => $value)
{
if (strpos($key, 'loadavg') === 0 || $key === 'loadavg_enable' || !in_array($key, array_keys($default_values)))
continue;
else
$_POST[$key] = (float) $value;
if ($key == 'loadavg_auto_opt' && $value <= 1)
$_POST['loadavg_auto_opt'] = 1.0;
elseif ($key == 'loadavg_forum' && $value < 10)
$_POST['loadavg_forum'] = 10.0;
elseif ($value < 2)
$_POST[$key] = 2.0;
}
call_integration_hook('integrate_save_loadavg_settings');
saveDBSettings($config_vars);
if (!isset($_SESSION['adm-save']))
$_SESSION['adm-save'] = true;
redirectexit('action=admin;area=serversettings;sa=loads;' . $context['session_var'] . '=' . $context['session_id']);
}
prepareDBSettingContext($config_vars);
}
/**
* Helper function, it sets up the context for the manage server settings.
* - The basic usage of the six numbered key fields are
* - array (0 ,1, 2, 3, 4, 5
* 0 variable name - the name of the saved variable
* 1 label - the text to show on the settings page
* 2 saveto - file or db, where to save the variable name - value pair
* 3 type - type of data to save, int, float, text, check
* 4 size - false or field size
* 5 help - '' or helptxt variable name
* )
*
* the following named keys are also permitted
* 'disabled' => A string of code that will determine whether or not the setting should be disabled
* 'postinput' => Text to display after the input field
* 'preinput' => Text to display before the input field
* 'subtext' => Additional descriptive text to display under the field's label
* 'min' => minimum allowed value (for int/float). Defaults to 0 if not set.
* 'max' => maximum allowed value (for int/float)
* 'step' => how much to increment/decrement the value by (only for int/float - mostly used for float values).
*
* @param array $config_vars An array of configuration variables
*/
function prepareServerSettingsContext(&$config_vars)
{
global $context, $modSettings, $smcFunc, $txt;
if (!empty($context['settings_not_writable']))
$context['settings_message'] = array(
'label' => $txt['settings_not_writable'],
'tag' => 'div',
'class' => 'centertext strong'
);
if (isset($_SESSION['adm-save']))
{
if ($_SESSION['adm-save'] === true)
$context['saved_successful'] = true;
else
$context['saved_failed'] = $_SESSION['adm-save'];
unset($_SESSION['adm-save']);
}
$context['config_vars'] = array();
foreach ($config_vars as $identifier => $config_var)
{
if (!is_array($config_var) || !isset($config_var[1]))
$context['config_vars'][] = $config_var;
else
{
$varname = $config_var[0];
global $$varname;
// Set the subtext in case it's part of the label.
// @todo Temporary. Preventing divs inside label tags.
$divPos = strpos($config_var[1], '