PageRenderTime 118ms CodeModel.GetById 3ms app.highlight 99ms RepoModel.GetById 0ms app.codeStats 1ms

/php/Sources/ManageServer.php

https://github.com/dekoza/openshift-smf-2.0.7
PHP | 2135 lines | 1486 code | 243 blank | 406 comment | 306 complexity | aa00efa6940adad770b8a422ba9d9198 MD5 | raw file
   1<?php
   2
   3/**
   4 * Simple Machines Forum (SMF)
   5 *
   6 * @package SMF
   7 * @author Simple Machines http://www.simplemachines.org
   8 * @copyright 2011 Simple Machines
   9 * @license http://www.simplemachines.org/about/smf/license.php BSD
  10 *
  11 * @version 2.0.5
  12 */
  13
  14if (!defined('SMF'))
  15	die('Hacking attempt...');
  16
  17/*	This file contains all the functionality required to be able to edit the
  18	core server settings. This includes anything from which an error may result
  19	in the forum destroying itself in a firey fury.
  20
  21	void ModifySettings()
  22		- Sets up all the available sub-actions.
  23		- Requires the admin_forum permission.
  24		- Uses the edit_settings adminIndex.
  25		- Sets up all the tabs and selects the appropriate one based on the sub-action.
  26		- Redirects to the appropriate function based on the sub-action.
  27
  28	void ModifyGeneralSettings()
  29		- shows an interface for the settings in Settings.php to be changed.
  30		- uses the rawdata sub template (not theme-able.)
  31		- requires the admin_forum permission.
  32		- uses the edit_settings administration area.
  33		- contains the actual array of settings to show from Settings.php.
  34		- accessed from ?action=admin;area=serversettings;sa=general.
  35
  36	void ModifyDatabaseSettings()
  37		- shows an interface for the settings in Settings.php to be changed.
  38		- uses the rawdata sub template (not theme-able.)
  39		- requires the admin_forum permission.
  40		- uses the edit_settings administration area.
  41		- contains the actual array of settings to show from Settings.php.
  42		- accessed from ?action=admin;area=serversettings;sa=database.
  43
  44	void ModifyCookieSettings()
  45		// !!!
  46
  47	void ModifyCacheSettings()
  48		// !!!
  49
  50	void ModifyLoadBalancingSettings()
  51		// !!!
  52
  53	void AddLanguage()
  54		// !!!
  55
  56	void DownloadLanguage()
  57		- Uses the ManageSettings template and the download_language sub-template.
  58		- Requires a valid download ID ("did") in the URL.
  59		- Also handles installing language files.
  60		- Attempts to chmod things as needed.
  61		- Uses a standard list to display information about all the files and where they'll be put.
  62
  63	void ManageLanguages()
  64		// !!!
  65
  66	void ModifyLanguages()
  67		// !!!
  68
  69	int list_getNumLanguages()
  70		// !!!
  71
  72	array list_getLanguages()
  73		- Callback for $listOptions['get_items']['function'] in ManageLanguageSettings.
  74		- Determines which languages are available by looking for the "index.{language}.php" file.
  75		- Also figures out how many users are using a particular language.
  76
  77	void ModifyLanguageSettings()
  78		// !!!
  79
  80	void ModifyLanguage()
  81		// !!!
  82
  83	void prepareServerSettingsContext(array config_vars)
  84		// !!!
  85
  86	void prepareDBSettingContext(array config_vars)
  87		// !!!
  88
  89	void saveSettings(array config_vars)
  90		- saves those settings set from ?action=admin;area=serversettings to the
  91		  Settings.php file and the database.
  92		- requires the admin_forum permission.
  93		- contains arrays of the types of data to save into Settings.php.
  94
  95	void saveDBSettings(array config_vars)
  96		// !!!
  97
  98*/
  99
 100/*	Adding options to one of the setting screens isn't hard. Call prepareDBSettingsContext;
 101	The basic format for a checkbox is:
 102		array('check', 'nameInModSettingsAndSQL'),
 103
 104	   And for a text box:
 105		array('text', 'nameInModSettingsAndSQL')
 106	   (NOTE: You have to add an entry for this at the bottom!)
 107
 108	   In these cases, it will look for $txt['nameInModSettingsAndSQL'] as the description,
 109	   and $helptxt['nameInModSettingsAndSQL'] as the help popup description.
 110
 111	Here's a quick explanation of how to add a new item:
 112
 113	* A text input box.  For textual values.
 114	ie.	array('text', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
 115
 116	* A text input box.  For numerical values.
 117	ie.	array('int', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
 118
 119	* A text input box.  For floating point values.
 120	ie.	array('float', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
 121
 122	* A large text input box. Used for textual values spanning multiple lines.
 123	ie.	array('large_text', 'nameInModSettingsAndSQL', 'OptionalNumberOfRows'),
 124
 125	* A check box.  Either one or zero. (boolean)
 126	ie.	array('check', 'nameInModSettingsAndSQL'),
 127
 128	* A selection box.  Used for the selection of something from a list.
 129	ie.	array('select', 'nameInModSettingsAndSQL', array('valueForSQL' => $txt['displayedValue'])),
 130	Note that just saying array('first', 'second') will put 0 in the SQL for 'first'.
 131
 132	* A password input box. Used for passwords, no less!
 133	ie.	array('password', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
 134
 135	* A permission - for picking groups who have a permission.
 136	ie.	array('permissions', 'manage_groups'),
 137
 138	* A BBC selection box.
 139	ie.	array('bbc', 'sig_bbc'),
 140
 141	For each option:
 142		type (see above), variable name, size/possible values.
 143	OR	make type '' for an empty string for a horizontal rule.
 144	SET	preinput - to put some HTML prior to the input box.
 145	SET	postinput - to put some HTML following the input box.
 146	SET	invalid - to mark the data as invalid.
 147	PLUS	You can override label and help parameters by forcing their keys in the array, for example:
 148		array('text', 'invalidlabel', 3, 'label' => 'Actual Label') */
 149
 150// This is the main pass through function, it creates tabs and the like.
 151function ModifySettings()
 152{
 153	global $context, $txt, $scripturl, $boarddir;
 154
 155	// This is just to keep the database password more secure.
 156	isAllowedTo('admin_forum');
 157
 158	// Load up all the tabs...
 159	$context[$context['admin_menu_name']]['tab_data'] = array(
 160		'title' => $txt['admin_server_settings'],
 161		'help' => 'serversettings',
 162		'description' => $txt['admin_basic_settings'],
 163	);
 164
 165	checkSession('request');
 166
 167	// The settings are in here, I swear!
 168	loadLanguage('ManageSettings');
 169
 170	$context['page_title'] = $txt['admin_server_settings'];
 171	$context['sub_template'] = 'show_settings';
 172
 173	$subActions = array(
 174		'general' => 'ModifyGeneralSettings',
 175		'database' => 'ModifyDatabaseSettings',
 176		'cookie' => 'ModifyCookieSettings',
 177		'cache' => 'ModifyCacheSettings',
 178		'loads' => 'ModifyLoadBalancingSettings',
 179	);
 180
 181	// By default we're editing the core settings
 182	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'general';
 183	$context['sub_action'] = $_REQUEST['sa'];
 184
 185	// Warn the user if there's any relevant information regarding Settings.php.
 186	if ($_REQUEST['sa'] != 'cache')
 187	{
 188		// Warn the user if the backup of Settings.php failed.
 189		$settings_not_writable = !is_writable($boarddir . '/Settings.php');
 190		$settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php');
 191
 192		if ($settings_not_writable)
 193			$context['settings_message'] = '<div class="centertext"><strong>' . $txt['settings_not_writable'] . '</strong></div><br />';
 194		elseif ($settings_backup_fail)
 195			$context['settings_message'] = '<div class="centertext"><strong>' . $txt['admin_backup_fail'] . '</strong></div><br />';
 196
 197		$context['settings_not_writable'] = $settings_not_writable;
 198	}
 199
 200	// Call the right function for this sub-action.
 201	$subActions[$_REQUEST['sa']]();
 202}
 203
 204// General forum settings - forum name, maintenance mode, etc.
 205function ModifyGeneralSettings($return_config = false)
 206{
 207	global $scripturl, $context, $txt;
 208
 209	/* If you're writing a mod, it's a bad idea to add things here....
 210	For each option:
 211		variable name, description, type (constant), size/possible values, helptext.
 212	OR	an empty string for a horizontal rule.
 213	OR	a string for a titled section. */
 214	$config_vars = array(
 215		array('mbname', $txt['admin_title'], 'file', 'text', 30),
 216		'',
 217		array('maintenance', $txt['admin_maintain'], 'file', 'check'),
 218		array('mtitle', $txt['maintenance_subject'], 'file', 'text', 36),
 219		array('mmessage', $txt['maintenance_message'], 'file', 'text', 36),
 220		'',
 221		array('webmaster_email', $txt['admin_webmaster_email'], 'file', 'text', 30),
 222		'',
 223		array('enableCompressedOutput', $txt['enableCompressedOutput'], 'db', 'check', null, 'enableCompressedOutput'),
 224		array('disableTemplateEval', $txt['disableTemplateEval'], 'db', 'check', null, 'disableTemplateEval'),
 225		array('disableHostnameLookup', $txt['disableHostnameLookup'], 'db', 'check', null, 'disableHostnameLookup'),
 226	);
 227
 228	if ($return_config)
 229		return $config_vars;
 230
 231	// Setup the template stuff.
 232	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=general;save';
 233	$context['settings_title'] = $txt['general_settings'];
 234
 235	// Saving settings?
 236	if (isset($_REQUEST['save']))
 237	{
 238		saveSettings($config_vars);
 239		redirectexit('action=admin;area=serversettings;sa=general;' . $context['session_var'] . '=' . $context['session_id']);
 240	}
 241
 242	// Fill the config array.
 243	prepareServerSettingsContext($config_vars);
 244}
 245
 246// Basic database and paths settings - database name, host, etc.
 247function ModifyDatabaseSettings($return_config = false)
 248{
 249	global $scripturl, $context, $settings, $txt, $boarddir;
 250
 251	/* If you're writing a mod, it's a bad idea to add things here....
 252	For each option:
 253		variable name, description, type (constant), size/possible values, helptext.
 254	OR	an empty string for a horizontal rule.
 255	OR	a string for a titled section. */
 256	$config_vars = array(
 257		array('db_server', $txt['database_server'], 'file', 'text'),
 258		array('db_user', $txt['database_user'], 'file', 'text'),
 259		array('db_passwd', $txt['database_password'], 'file', 'password'),
 260		array('db_name', $txt['database_name'], 'file', 'text'),
 261		array('db_prefix', $txt['database_prefix'], 'file', 'text'),
 262		array('db_persist', $txt['db_persist'], 'file', 'check', null, 'db_persist'),
 263		array('db_error_send', $txt['db_error_send'], 'file', 'check'),
 264		array('ssi_db_user', $txt['ssi_db_user'], 'file', 'text', null, 'ssi_db_user'),
 265		array('ssi_db_passwd', $txt['ssi_db_passwd'], 'file', 'password'),
 266		'',
 267		array('autoFixDatabase', $txt['autoFixDatabase'], 'db', 'check', false, 'autoFixDatabase'),
 268		array('autoOptMaxOnline', $txt['autoOptMaxOnline'], 'db', 'int'),
 269		'',
 270		array('boardurl', $txt['admin_url'], 'file', 'text', 36),
 271		array('boarddir', $txt['boarddir'], 'file', 'text', 36),
 272		array('sourcedir', $txt['sourcesdir'], 'file', 'text', 36),
 273		array('cachedir', $txt['cachedir'], 'file', 'text', 36),
 274	);
 275
 276	if ($return_config)
 277		return $config_vars;
 278
 279	// Setup the template stuff.
 280	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=database;save';
 281	$context['settings_title'] = $txt['database_paths_settings'];
 282	$context['save_disabled'] = $context['settings_not_writable'];
 283
 284	// Saving settings?
 285	if (isset($_REQUEST['save']))
 286	{
 287		saveSettings($config_vars);
 288		redirectexit('action=admin;area=serversettings;sa=database;' . $context['session_var'] . '=' . $context['session_id']);
 289	}
 290
 291	// Fill the config array.
 292	prepareServerSettingsContext($config_vars);
 293}
 294
 295// This function basically edits anything which is configuration and stored in the database, except for caching.
 296function ModifyCookieSettings($return_config = false)
 297{
 298	global $context, $scripturl, $txt, $sourcedir, $modSettings, $cookiename, $user_settings;
 299
 300	// Define the variables we want to edit.
 301	$config_vars = array(
 302		// Cookies...
 303		array('cookiename', $txt['cookie_name'], 'file', 'text', 20),
 304		array('cookieTime', $txt['cookieTime'], 'db', 'int'),
 305		array('localCookies', $txt['localCookies'], 'db', 'check', false, 'localCookies'),
 306		array('globalCookies', $txt['globalCookies'], 'db', 'check', false, 'globalCookies'),
 307		array('secureCookies', $txt['secureCookies'], 'db', 'check', false, 'secureCookies',  'disabled' => !isset($_SERVER['HTTPS']) || !(strtolower($_SERVER['HTTPS']) == 'on' || strtolower($_SERVER['HTTPS']) == '1')),
 308		'',
 309		// Sessions
 310		array('databaseSession_enable', $txt['databaseSession_enable'], 'db', 'check', false, 'databaseSession_enable'),
 311		array('databaseSession_loose', $txt['databaseSession_loose'], 'db', 'check', false, 'databaseSession_loose'),
 312		array('databaseSession_lifetime', $txt['databaseSession_lifetime'], 'db', 'int', false, 'databaseSession_lifetime'),
 313	);
 314
 315	if ($return_config)
 316		return $config_vars;
 317
 318	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cookie;save';
 319	$context['settings_title'] = $txt['cookies_sessions_settings'];
 320
 321	// Saving settings?
 322	if (isset($_REQUEST['save']))
 323	{
 324		saveSettings($config_vars);
 325
 326		// If the cookie name was changed, reset the cookie.
 327		if ($cookiename != $_POST['cookiename'])
 328		{
 329			$original_session_id = $context['session_id'];
 330			include_once($sourcedir . '/Subs-Auth.php');
 331
 332			// Remove the old cookie.
 333			setLoginCookie(-3600, 0);
 334
 335			// Set the new one.
 336			$cookiename = $_POST['cookiename'];
 337			setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], sha1($user_settings['passwd'] . $user_settings['password_salt']));
 338
 339			redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $original_session_id, $context['server']['needs_login_fix']);
 340		}
 341
 342		redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $context['session_id']);
 343	}
 344
 345	// Fill the config array.
 346	prepareServerSettingsContext($config_vars);
 347}
 348
 349// Simply modifying cache functions
 350function ModifyCacheSettings($return_config = false)
 351{
 352	global $context, $scripturl, $txt, $helptxt, $modSettings;
 353
 354	// Define the variables we want to edit.
 355	$config_vars = array(
 356		// Only a couple of settings, but they are important
 357		array('select', 'cache_enable', array($txt['cache_off'], $txt['cache_level1'], $txt['cache_level2'], $txt['cache_level3'])),
 358		array('text', 'cache_memcached'),
 359	);
 360
 361	if ($return_config)
 362		return $config_vars;
 363
 364	// Saving again?
 365	if (isset($_GET['save']))
 366	{
 367		saveDBSettings($config_vars);
 368
 369		// We have to manually force the clearing of the cache otherwise the changed settings might not get noticed.
 370		$modSettings['cache_enable'] = 1;
 371		cache_put_data('modSettings', null, 90);
 372
 373		redirectexit('action=admin;area=serversettings;sa=cache;' . $context['session_var'] . '=' . $context['session_id']);
 374	}
 375
 376	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cache;save';
 377	$context['settings_title'] = $txt['caching_settings'];
 378	$context['settings_message'] = $txt['caching_information'];
 379
 380	// Detect an optimizer?
 381	if (function_exists('eaccelerator_put'))
 382		$detected = 'eAccelerator';
 383	elseif (function_exists('mmcache_put'))
 384		$detected = 'MMCache';
 385	elseif (function_exists('apc_store'))
 386		$detected = 'APC';
 387	elseif (function_exists('output_cache_put'))
 388		$detected = 'Zend';
 389	elseif (function_exists('memcache_set'))
 390		$detected = 'Memcached';
 391	elseif (function_exists('xcache_set'))
 392		$detected = 'XCache';
 393	else
 394		$detected = 'no_caching';
 395
 396	$context['settings_message'] = sprintf($context['settings_message'], $txt['detected_' . $detected]);
 397
 398	// Prepare the template.
 399	prepareDBSettingContext($config_vars);
 400}
 401
 402function ModifyLoadBalancingSettings($return_config = false)
 403{
 404	global $txt, $scripturl, $context, $settings, $modSettings;
 405
 406	// Setup a warning message, but disabled by default.
 407	$disabled = true;
 408	$context['settings_message'] = $txt['loadavg_disabled_conf'];
 409
 410	if (strpos(strtolower(PHP_OS), 'win') === 0)
 411		$context['settings_message'] = $txt['loadavg_disabled_windows'];
 412	else
 413	{
 414		$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
 415		if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) !== 0)
 416			$modSettings['load_average'] = (float) $matches[1];
 417		elseif (($modSettings['load_average'] = @`uptime`) !== null && preg_match('~load averages?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) !== 0)
 418			$modSettings['load_average'] = (float) $matches[1];
 419		else
 420			unset($modSettings['load_average']);
 421
 422		if (!empty($modSettings['load_average']))
 423		{
 424			$context['settings_message'] = sprintf($txt['loadavg_warning'], $modSettings['load_average']);
 425			$disabled = false;
 426		}
 427	}
 428
 429	// Start with a simple checkbox.
 430	$config_vars = array(
 431		array('check', 'loadavg_enable'),
 432	);
 433
 434	// Set the default values for each option.
 435	$default_values = array(
 436		'loadavg_auto_opt' => '1.0',
 437		'loadavg_search' => '2.5',
 438		'loadavg_allunread' => '2.0',
 439		'loadavg_unreadreplies' => '3.5',
 440		'loadavg_show_posts' => '2.0',
 441		'loadavg_forum' => '40.0',
 442	);
 443
 444	// Loop through the settings.
 445	foreach ($default_values as $name => $value)
 446	{
 447		// Use the default value if the setting isn't set yet.
 448		$value = !isset($modSettings[$name]) ? $value : $modSettings[$name];
 449		$config_vars[] = array('text', $name, 'value' => $value, 'disabled' => $disabled);
 450	}
 451
 452	if ($return_config)
 453		return $config_vars;
 454
 455	$context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=loads;save';
 456	$context['settings_title'] = $txt['load_balancing_settings'];
 457
 458	// Saving?
 459	if (isset($_GET['save']))
 460	{
 461		// Stupidity is not allowed.
 462		foreach ($_POST as $key => $value)
 463		{
 464			if (strpos($key, 'loadavg') === 0 || $key === 'loadavg_enable')
 465				continue;
 466			elseif ($key == 'loadavg_auto_opt' && $value <= 1)
 467				$_POST['loadavg_auto_opt'] = '1.0';
 468			elseif ($key == 'loadavg_forum' && $value < 10)
 469				$_POST['loadavg_forum'] = '10.0';
 470			elseif ($value < 2)
 471				$_POST[$key] = '2.0';
 472		}
 473
 474		saveDBSettings($config_vars);
 475		redirectexit('action=admin;area=serversettings;sa=loads;' . $context['session_var'] . '=' . $context['session_id']);
 476	}
 477
 478	prepareDBSettingContext($config_vars);
 479}
 480
 481// This is the main function for the language area.
 482function ManageLanguages()
 483{
 484	global $context, $txt, $scripturl, $modSettings;
 485
 486	loadLanguage('ManageSettings');
 487
 488	$context['page_title'] = $txt['edit_languages'];
 489	$context['sub_template'] = 'show_settings';
 490
 491	$subActions = array(
 492		'edit' => 'ModifyLanguages',
 493		'add' => 'AddLanguage',
 494		'settings' => 'ModifyLanguageSettings',
 495		'downloadlang' => 'DownloadLanguage',
 496		'editlang' => 'ModifyLanguage',
 497	);
 498
 499	// By default we're managing languages.
 500	$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'edit';
 501	$context['sub_action'] = $_REQUEST['sa'];
 502
 503	// Load up all the tabs...
 504	$context[$context['admin_menu_name']]['tab_data'] = array(
 505		'title' => $txt['language_configuration'],
 506		'description' => $txt['language_description'],
 507	);
 508
 509	// Call the right function for this sub-acton.
 510	$subActions[$_REQUEST['sa']]();
 511}
 512
 513// Interface for adding a new language
 514function AddLanguage()
 515{
 516	global $context, $sourcedir, $forum_version, $boarddir, $txt, $smcFunc, $scripturl;
 517
 518	// Are we searching for new languages courtesy of Simple Machines?
 519	if (!empty($_POST['smf_add_sub']))
 520	{
 521		// Need fetch_web_data.
 522		require_once($sourcedir . '/Subs-Package.php');
 523
 524		$context['smf_search_term'] = htmlspecialchars(trim($_POST['smf_add']));
 525
 526		// We're going to use this URL.
 527		$url = 'http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => '')));
 528
 529		// Load the class file and stick it into an array.
 530		loadClassFile('Class-Package.php');
 531		$language_list = new xmlArray(fetch_web_data($url), true);
 532
 533		// Check it exists.
 534		if (!$language_list->exists('languages'))
 535			$context['smf_error'] = 'no_response';
 536		else
 537		{
 538			$language_list = $language_list->path('languages[0]');
 539			$lang_files = $language_list->set('language');
 540			$context['smf_languages'] = array();
 541			foreach ($lang_files as $file)
 542			{
 543				// Were we searching?
 544				if (!empty($context['smf_search_term']) && strpos($file->fetch('name'), $smcFunc['strtolower']($context['smf_search_term'])) === false)
 545					continue;
 546
 547				$context['smf_languages'][] = array(
 548					'id' => $file->fetch('id'),
 549					'name' => $smcFunc['ucwords']($file->fetch('name')),
 550					'version' => $file->fetch('version'),
 551					'utf8' => $file->fetch('utf8'),
 552					'description' => $file->fetch('description'),
 553					'link' => $scripturl . '?action=admin;area=languages;sa=downloadlang;did=' . $file->fetch('id') . ';' . $context['session_var'] . '=' . $context['session_id'],
 554				);
 555			}
 556			if (empty($context['smf_languages']))
 557				$context['smf_error'] = 'no_files';
 558		}
 559	}
 560
 561	$context['sub_template'] = 'add_language';
 562}
 563
 564// Download a language file from the Simple Machines website.
 565function DownloadLanguage()
 566{
 567	global $context, $sourcedir, $forum_version, $boarddir, $txt, $smcFunc, $scripturl, $modSettings;
 568
 569	loadLanguage('ManageSettings');
 570	require_once($sourcedir . '/Subs-Package.php');
 571
 572	// Clearly we need to know what to request.
 573	if (!isset($_GET['did']))
 574		fatal_lang_error('no_access', false);
 575
 576	// Some lovely context.
 577	$context['download_id'] = $_GET['did'];
 578	$context['sub_template'] = 'download_language';
 579	$context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'add';
 580
 581	// Can we actually do the installation - and do they want to?
 582	if (!empty($_POST['do_install']) && !empty($_POST['copy_file']))
 583	{
 584		checkSession('get');
 585
 586		$chmod_files = array();
 587		$install_files = array();
 588		// Check writable status.
 589		foreach ($_POST['copy_file'] as $file)
 590		{
 591			// Check it's not very bad.
 592			if (strpos($file, '..') !== false || (substr($file, 0, 6) != 'Themes' && !preg_match('~agreement\.[A-Za-z-_0-9]+\.txt$~', $file)))
 593				fatal_error($txt['languages_download_illegal_paths']);
 594
 595			$chmod_files[] = $boarddir . '/' . $file;
 596			$install_files[] = $file;
 597		}
 598
 599		// Call this in case we have work to do.
 600		$file_status = create_chmod_control($chmod_files);
 601		$files_left = $file_status['files']['notwritable'];
 602
 603		// Something not writable?
 604		if (!empty($files_left))
 605			$context['error_message'] = $txt['languages_download_not_chmod'];
 606		// Otherwise, go go go!
 607		elseif (!empty($install_files))
 608		{
 609			$archive_content = read_tgz_file('http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))) . ';fetch=' . urlencode($_GET['did']), $boarddir, false, true, $install_files);
 610			// Make sure the files aren't stuck in the cache.
 611			package_flush_cache();
 612			$context['install_complete'] = sprintf($txt['languages_download_complete_desc'], $scripturl . '?action=admin;area=languages');
 613
 614			return;
 615		}
 616	}
 617
 618	// Open up the old china.
 619	if (!isset($archive_content))
 620		$archive_content = read_tgz_file('http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))) . ';fetch=' . urlencode($_GET['did']), null);
 621
 622	if (empty($archive_content))
 623		fatal_error($txt['add_language_error_no_response']);
 624
 625	// Now for each of the files, let's do some *stuff*
 626	$context['files'] = array(
 627		'lang' => array(),
 628		'other' => array(),
 629	);
 630	$context['make_writable'] = array();
 631	foreach ($archive_content as $file)
 632	{
 633		$dirname = dirname($file['filename']);
 634		$filename = basename($file['filename']);
 635		$extension = substr($filename, strrpos($filename, '.') + 1);
 636
 637		// Don't do anything with files we don't understand.
 638		if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt')))
 639			continue;
 640
 641		// Basic data.
 642		$context_data = array(
 643			'name' => $filename,
 644			'destination' => $boarddir . '/' . $file['filename'],
 645			'generaldest' => $file['filename'],
 646			'size' => $file['size'],
 647			// Does chmod status allow the copy?
 648			'writable' => false,
 649			// Should we suggest they copy this file?
 650			'default_copy' => true,
 651			// Does the file already exist, if so is it same or different?
 652			'exists' => false,
 653		);
 654
 655		// Does the file exist, is it different and can we overwrite?
 656		if (file_exists($boarddir . '/' . $file['filename']))
 657		{
 658			if (is_writable($boarddir . '/' . $file['filename']))
 659				$context_data['writable'] = true;
 660
 661			// Finally, do we actually think the content has changed?
 662			if ($file['size'] == filesize($boarddir . '/' . $file['filename']) && $file['md5'] == md5_file($boarddir . '/' . $file['filename']))
 663			{
 664				$context_data['exists'] = 'same';
 665				$context_data['default_copy'] = false;
 666			}
 667			// Attempt to discover newline character differences.
 668			elseif ($file['md5'] == md5(preg_replace("~[\r]?\n~", "\r\n", file_get_contents($boarddir . '/' . $file['filename']))))
 669			{
 670				$context_data['exists'] = 'same';
 671				$context_data['default_copy'] = false;
 672			}
 673			else
 674				$context_data['exists'] = 'different';
 675		}
 676		// No overwrite?
 677		else
 678		{
 679			// Can we at least stick it in the directory...
 680			if (is_writable($boarddir . '/' . $dirname))
 681				$context_data['writable'] = true;
 682		}
 683
 684		// I love PHP files, that's why I'm a developer and not an artistic type spending my time drinking absinth and living a life of sin...
 685		if ($extension == 'php' && preg_match('~\w+\.\w+(?:-utf8)?\.php~', $filename))
 686		{
 687			$context_data += array(
 688				'version' => '??',
 689				'cur_version' => false,
 690				'version_compare' => 'newer',
 691			);
 692
 693			list ($name, $language) = explode('.', $filename);
 694
 695			// Let's get the new version, I like versions, they tell me that I'm up to date.
 696			if (preg_match('~\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '~i', $file['preview'], $match) == 1)
 697				$context_data['version'] = $match[1];
 698
 699			// Now does the old file exist - if so what is it's version?
 700			if (file_exists($boarddir . '/' . $file['filename']))
 701			{
 702				// OK - what is the current version?
 703				$fp = fopen($boarddir . '/' . $file['filename'], 'rb');
 704				$header = fread($fp, 768);
 705				fclose($fp);
 706
 707				// Find the version.
 708				if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1)
 709				{
 710					$context_data['cur_version'] = $match[1];
 711
 712					// How does this compare?
 713					if ($context_data['cur_version'] == $context_data['version'])
 714						$context_data['version_compare'] = 'same';
 715					elseif ($context_data['cur_version'] > $context_data['version'])
 716						$context_data['version_compare'] = 'older';
 717
 718					// Don't recommend copying if the version is the same.
 719					if ($context_data['version_compare'] != 'newer')
 720						$context_data['default_copy'] = false;
 721				}
 722			}
 723
 724			// Add the context data to the main set.
 725			$context['files']['lang'][] = $context_data;
 726		}
 727		else
 728		{
 729			// If we think it's a theme thing, work out what the theme is.
 730			if (substr($dirname, 0, 6) == 'Themes' && preg_match('~Themes[\\/]([^\\/]+)[\\/]~', $dirname, $match))
 731				$theme_name = $match[1];
 732			else
 733				$theme_name = 'misc';
 734
 735			// Assume it's an image, could be an acceptance note etc but rare.
 736			$context['files']['images'][$theme_name][] = $context_data;
 737		}
 738
 739		// Collect together all non-writable areas.
 740		if (!$context_data['writable'])
 741			$context['make_writable'][] = $context_data['destination'];
 742	}
 743
 744	// So, I'm a perfectionist - let's get the theme names.
 745	$theme_indexes = array();
 746	foreach ($context['files']['images'] as $k => $dummy)
 747		$indexes[] = $k;
 748
 749	$context['theme_names'] = array();
 750	if (!empty($indexes))
 751	{
 752		$value_data = array(
 753			'query' => array(),
 754			'params' => array(),
 755		);
 756
 757		foreach ($indexes as $k => $index)
 758		{
 759			$value_data['query'][] = 'value LIKE {string:value_' . $k . '}';
 760			$value_data['params']['value_' . $k] = '%' . $index;
 761		}
 762
 763		$request = $smcFunc['db_query']('', '
 764			SELECT id_theme, value
 765			FROM {db_prefix}themes
 766			WHERE id_member = {int:no_member}
 767				AND variable = {string:theme_dir}
 768				AND (' . implode(' OR ', $value_data['query']) . ')',
 769			array_merge($value_data['params'], array(
 770				'no_member' => 0,
 771				'theme_dir' => 'theme_dir',
 772				'index_compare_explode' => 'value LIKE \'%' . implode('\' OR value LIKE \'%', $indexes) . '\'',
 773			))
 774		);
 775		$themes = array();
 776		while ($row = $smcFunc['db_fetch_assoc']($request))
 777		{
 778			// Find the right one.
 779			foreach ($indexes as $index)
 780				if (strpos($row['value'], $index) !== false)
 781					$themes[$row['id_theme']] = $index;
 782		}
 783		$smcFunc['db_free_result']($request);
 784
 785		if (!empty($themes))
 786		{
 787			// Now we have the id_theme we can get the pretty description.
 788			$request = $smcFunc['db_query']('', '
 789				SELECT id_theme, value
 790				FROM {db_prefix}themes
 791				WHERE id_member = {int:no_member}
 792					AND variable = {string:name}
 793					AND id_theme IN ({array_int:theme_list})',
 794				array(
 795					'theme_list' => array_keys($themes),
 796					'no_member' => 0,
 797					'name' => 'name',
 798				)
 799			);
 800			while ($row = $smcFunc['db_fetch_assoc']($request))
 801			{
 802				// Now we have it...
 803				$context['theme_names'][$themes[$row['id_theme']]] = $row['value'];
 804			}
 805			$smcFunc['db_free_result']($request);
 806		}
 807	}
 808
 809	// Before we go to far can we make anything writable, eh, eh?
 810	if (!empty($context['make_writable']))
 811	{
 812		// What is left to be made writable?
 813		$file_status = create_chmod_control($context['make_writable']);
 814		$context['still_not_writable'] = $file_status['files']['notwritable'];
 815
 816		// Mark those which are now writable as such.
 817		foreach ($context['files'] as $type => $data)
 818		{
 819			if ($type == 'lang')
 820			{
 821				foreach ($data as $k => $file)
 822					if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable']))
 823						$context['files'][$type][$k]['writable'] = true;
 824			}
 825			else
 826			{
 827				foreach ($data as $theme => $files)
 828					foreach ($files as $k => $file)
 829						if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable']))
 830							$context['files'][$type][$theme][$k]['writable'] = true;
 831			}
 832		}
 833
 834		// Are we going to need more language stuff?
 835		if (!empty($context['still_not_writable']))
 836			loadLanguage('Packages');
 837	}
 838
 839	// This is the list for the main files.
 840	$listOptions = array(
 841		'id' => 'lang_main_files_list',
 842		'title' => $txt['languages_download_main_files'],
 843		'get_items' => array(
 844			'function' => create_function('', '
 845				global $context;
 846				return $context[\'files\'][\'lang\'];
 847			'),
 848		),
 849		'columns' => array(
 850			'name' => array(
 851				'header' => array(
 852					'value' => $txt['languages_download_filename'],
 853				),
 854				'data' => array(
 855					'function' => create_function('$rowData', '
 856						global $context, $txt;
 857
 858						return \'<strong>\' . $rowData[\'name\'] . \'</strong><br /><span class="smalltext">\' . $txt[\'languages_download_dest\'] . \': \' . $rowData[\'destination\'] . \'</span>\' . ($rowData[\'version_compare\'] == \'older\' ? \'<br />\' . $txt[\'languages_download_older\'] : \'\');
 859					'),
 860				),
 861			),
 862			'writable' => array(
 863				'header' => array(
 864					'value' => $txt['languages_download_writable'],
 865				),
 866				'data' => array(
 867					'function' => create_function('$rowData', '
 868						global $txt;
 869
 870						return \'<span style="color: \' . ($rowData[\'writable\'] ? \'green\' : \'red\') . \';">\' . ($rowData[\'writable\'] ? $txt[\'yes\'] : $txt[\'no\']) . \'</span>\';
 871					'),
 872					'style' => 'text-align: center',
 873				),
 874			),
 875			'version' => array(
 876				'header' => array(
 877					'value' => $txt['languages_download_version'],
 878				),
 879				'data' => array(
 880					'function' => create_function('$rowData', '
 881						global $txt;
 882
 883						return \'<span style="color: \' . ($rowData[\'version_compare\'] == \'older\' ? \'red\' : ($rowData[\'version_compare\'] == \'same\' ? \'orange\' : \'green\')) . \';">\' . $rowData[\'version\'] . \'</span>\';
 884					'),
 885				),
 886			),
 887			'exists' => array(
 888				'header' => array(
 889					'value' => $txt['languages_download_exists'],
 890				),
 891				'data' => array(
 892					'function' => create_function('$rowData', '
 893						global $txt;
 894
 895						return $rowData[\'exists\'] ? ($rowData[\'exists\'] == \'same\' ? $txt[\'languages_download_exists_same\'] : $txt[\'languages_download_exists_different\']) : $txt[\'no\'];
 896					'),
 897				),
 898			),
 899			'copy' => array(
 900				'header' => array(
 901					'value' => $txt['languages_download_copy'],
 902				),
 903				'data' => array(
 904					'function' => create_function('$rowData', '
 905						return \'<input type="checkbox" name="copy_file[]" value="\' . $rowData[\'generaldest\'] . \'" \' . ($rowData[\'default_copy\'] ? \'checked="checked"\' : \'\') . \' class="input_check" />\';
 906					'),
 907					'style' => 'text-align: center; width: 4%;',
 908				),
 909			),
 910		),
 911	);
 912
 913	// Kill the cache, as it is now invalid..
 914	if (!empty($modSettings['cache_enable']))
 915	{
 916		cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
 917		cache_put_data('known_languages_all', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
 918	}
 919
 920	require_once($sourcedir . '/Subs-List.php');
 921	createList($listOptions);
 922
 923	$context['default_list'] = 'lang_main_files_list';
 924}
 925
 926// This lists all the current languages and allows editing of them.
 927function ModifyLanguages()
 928{
 929	global $txt, $context, $scripturl;
 930	global $user_info, $smcFunc, $sourcedir, $language, $boarddir, $forum_version;
 931
 932	// Setting a new default?
 933	if (!empty($_POST['set_default']) && !empty($_POST['def_language']))
 934	{
 935		checkSession();
 936
 937		getLanguages(true, false);
 938		$lang_exists = false;
 939		foreach ($context['languages'] as $lang)
 940		{
 941			if ($_POST['def_language'] == $lang['filename'])
 942			{
 943				$lang_exists = true;
 944				break;
 945			}
 946		}
 947
 948		if ($_POST['def_language'] != $language && $lang_exists)
 949		{
 950			require_once($sourcedir . '/Subs-Admin.php');
 951			updateSettingsFile(array('language' => '\'' . $_POST['def_language'] . '\''));
 952			$language = $_POST['def_language'];
 953		}
 954	}
 955
 956	$listOptions = array(
 957		'id' => 'language_list',
 958		'items_per_page' => 20,
 959		'base_href' => $scripturl . '?action=admin;area=languages',
 960		'title' => $txt['edit_languages'],
 961		'get_items' => array(
 962			'function' => 'list_getLanguages',
 963		),
 964		'get_count' => array(
 965			'function' => 'list_getNumLanguages',
 966		),
 967		'columns' => array(
 968			'default' => array(
 969				'header' => array(
 970					'value' => $txt['languages_default'],
 971				),
 972				'data' => array(
 973					'function' => create_function('$rowData', '
 974						return \'<input type="radio" name="def_language" value="\' . $rowData[\'id\'] . \'" \' . ($rowData[\'default\'] ? \'checked="checked"\' : \'\') . \' onclick="highlightSelected(\\\'list_language_list_\' . $rowData[\'id\'] . \'\\\');" class="input_radio" />\';
 975					'),
 976					'style' => 'text-align: center; width: 8%;',
 977				),
 978			),
 979			'name' => array(
 980				'header' => array(
 981					'value' => $txt['languages_lang_name'],
 982				),
 983				'data' => array(
 984					'function' => create_function('$rowData', '
 985						global $scripturl, $context;
 986
 987						return sprintf(\'<a href="%1$s?action=admin;area=languages;sa=editlang;lid=%2$s">%3$s</a>\', $scripturl, $rowData[\'id\'], $rowData[\'name\']);
 988					'),
 989				),
 990			),
 991			'character_set' => array(
 992				'header' => array(
 993					'value' => $txt['languages_character_set'],
 994				),
 995				'data' => array(
 996					'db_htmlsafe' => 'char_set',
 997				),
 998			),
 999			'count' => array(
1000				'header' => array(
1001					'value' => $txt['languages_users'],
1002				),
1003				'data' => array(
1004					'db_htmlsafe' => 'count',
1005					'style' => 'text-align: center',
1006				),
1007			),
1008			'locale' => array(
1009				'header' => array(
1010					'value' => $txt['languages_locale'],
1011				),
1012				'data' => array(
1013					'db_htmlsafe' => 'locale',
1014				),
1015			),
1016		),
1017		'form' => array(
1018			'href' => $scripturl . '?action=admin;area=languages',
1019		),
1020		'additional_rows' => array(
1021			array(
1022				'position' => 'below_table_data',
1023				'value' => '<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" /><input type="submit" name="set_default" value="' . $txt['save'] . '"' . (is_writable($boarddir . '/Settings.php') ? '' : ' disabled="disabled"') . ' class="button_submit" />',
1024				'style' => 'text-align: right;',
1025			),
1026		),
1027		// For highlighting the default.
1028		'javascript' => '
1029					var prevClass = "";
1030					var prevDiv = "";
1031					function highlightSelected(box)
1032					{
1033						if (prevClass != "")
1034							prevDiv.className = prevClass;
1035
1036						prevDiv = document.getElementById(box);
1037						prevClass = prevDiv.className;
1038
1039						prevDiv.className = "highlight2";
1040					}
1041					highlightSelected("list_language_list_' . ($language == '' ? 'english' : $language). '");
1042		',
1043	);
1044
1045	// Display a warning if we cannot edit the default setting.
1046	if (!is_writable($boarddir . '/Settings.php'))
1047		$listOptions['additional_rows'][] = array(
1048				'position' => 'after_title',
1049				'value' => $txt['language_settings_writable'],
1050				'class' => 'smalltext alert',
1051			);
1052
1053	require_once($sourcedir . '/Subs-List.php');
1054	createList($listOptions);
1055
1056	$context['sub_template'] = 'show_list';
1057	$context['default_list'] = 'language_list';
1058}
1059
1060// How many languages?
1061function list_getNumLanguages()
1062{
1063	global $settings;
1064
1065	// Return how many we have.
1066	return count(getLanguages(true, false));
1067}
1068
1069// Fetch the actual language information.
1070function list_getLanguages()
1071{
1072	global $settings, $smcFunc, $language, $context, $txt;
1073
1074	$languages = array();
1075	// Keep our old entries.
1076	$old_txt = $txt;
1077	$backup_actual_theme_dir = $settings['actual_theme_dir'];
1078	$backup_base_theme_dir = !empty($settings['base_theme_dir']) ? $settings['base_theme_dir'] : '';
1079
1080	// Override these for now.
1081	$settings['actual_theme_dir'] = $settings['base_theme_dir'] = $settings['default_theme_dir'];
1082	getLanguages(true, false);
1083
1084	// Put them back.
1085	$settings['actual_theme_dir'] = $backup_actual_theme_dir;
1086	if (!empty($backup_base_theme_dir))
1087		$settings['base_theme_dir'] = $backup_base_theme_dir;
1088	else
1089		unset($settings['base_theme_dir']);
1090
1091	// Get the language files and data...
1092	foreach ($context['languages'] as $lang)
1093	{
1094		// Load the file to get the character set.
1095		require($settings['default_theme_dir'] . '/languages/index.' . $lang['filename'] . '.php');
1096
1097		$languages[$lang['filename']] = array(
1098			'id' => $lang['filename'],
1099			'count' => 0,
1100			'char_set' => $txt['lang_character_set'],
1101			'default' => $language == $lang['filename'] || ($language == '' && $lang['filename'] == 'english'),
1102			'locale' => $txt['lang_locale'],
1103			'name' => $smcFunc['ucwords'](strtr($lang['filename'], array('_' => ' ', '-utf8' => ''))),
1104		);
1105	}
1106
1107	// Work out how many people are using each language.
1108	$request = $smcFunc['db_query']('', '
1109		SELECT lngfile, COUNT(*) AS num_users
1110		FROM {db_prefix}members
1111		GROUP BY lngfile',
1112		array(
1113		)
1114	);
1115	while ($row = $smcFunc['db_fetch_assoc']($request))
1116	{
1117		// Default?
1118		if (empty($row['lngfile']) || !isset($languages[$row['lngfile']]))
1119			$row['lngfile'] = $language;
1120
1121		if (!isset($languages[$row['lngfile']]) && isset($languages['english']))
1122			$languages['english']['count'] += $row['num_users'];
1123		elseif (isset($languages[$row['lngfile']]))
1124			$languages[$row['lngfile']]['count'] += $row['num_users'];
1125	}
1126	$smcFunc['db_free_result']($request);
1127
1128	// Restore the current users language.
1129	$txt = $old_txt;
1130
1131	// Return how many we have.
1132	return $languages;
1133}
1134
1135// Edit language related settings.
1136function ModifyLanguageSettings($return_config = false)
1137{
1138	global $scripturl, $context, $txt, $boarddir, $settings, $smcFunc;
1139
1140	// Warn the user if the backup of Settings.php failed.
1141	$settings_not_writable = !is_writable($boarddir . '/Settings.php');
1142	$settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php');
1143
1144	/* If you're writing a mod, it's a bad idea to add things here....
1145	For each option:
1146		variable name, description, type (constant), size/possible values, helptext.
1147	OR	an empty string for a horizontal rule.
1148	OR	a string for a titled section. */
1149	$config_vars = array(
1150		'language' => array('language', $txt['default_language'], 'file', 'select', array(), null, 'disabled' => $settings_not_writable),
1151		array('userLanguage', $txt['userLanguage'], 'db', 'check', null, 'userLanguage'),
1152	);
1153
1154	if ($return_config)
1155		return $config_vars;
1156
1157	// Get our languages. No cache and use utf8.
1158	getLanguages(false, false);
1159	foreach ($context['languages'] as $lang)
1160		$config_vars['language'][4][$lang['filename']] = array($lang['filename'], strtr($lang['name'], array('-utf8' => ' (UTF-8)')));
1161
1162	// Saving settings?
1163	if (isset($_REQUEST['save']))
1164	{
1165		checkSession();
1166		saveSettings($config_vars);
1167		redirectexit('action=admin;area=languages;sa=settings');
1168	}
1169
1170	// Setup the template stuff.
1171	$context['post_url'] = $scripturl . '?action=admin;area=languages;sa=settings;save';
1172	$context['settings_title'] = $txt['language_settings'];
1173	$context['save_disabled'] = $settings_not_writable;
1174
1175	if ($settings_not_writable)
1176		$context['settings_message'] = '<div class="centertext"><strong>' . $txt['settings_not_writable'] . '</strong></div><br />';
1177	elseif ($settings_backup_fail)
1178		$context['settings_message'] = '<div class="centertext"><strong>' . $txt['admin_backup_fail'] . '</strong></div><br />';
1179
1180	// Fill the config array.
1181	prepareServerSettingsContext($config_vars);
1182}
1183
1184// Edit a particular set of language entries.
1185function ModifyLanguage()
1186{
1187	global $settings, $context, $smcFunc, $txt, $modSettings, $boarddir, $sourcedir, $language;
1188
1189	loadLanguage('ManageSettings');
1190
1191	// Select the languages tab.
1192	$context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit';
1193	$context['page_title'] = $txt['edit_languages'];
1194	$context['sub_template'] = 'modify_language_entries';
1195
1196	$context['lang_id'] = $_GET['lid'];
1197	list($theme_id, $file_id) = empty($_REQUEST['tfid']) || strpos($_REQUEST['tfid'], '+') === false ? array(1, '') : explode('+', $_REQUEST['tfid']);
1198
1199	// Clean the ID - just in case.
1200	preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches);
1201	$context['lang_id'] = $matches[1];
1202
1203	// Get all the theme data.
1204	$request = $smcFunc['db_query']('', '
1205		SELECT id_theme, variable, value
1206		FROM {db_prefix}themes
1207		WHERE id_theme != {int:default_theme}
1208			AND id_member = {int:no_member}
1209			AND variable IN ({string:name}, {string:theme_dir})',
1210		array(
1211			'default_theme' => 1,
1212			'no_member' => 0,
1213			'name' => 'name',
1214			'theme_dir' => 'theme_dir',
1215		)
1216	);
1217	$themes = array(
1218		1 => array(
1219			'name' => $txt['dvc_default'],
1220			'theme_dir' => $settings['default_theme_dir'],
1221		),
1222	);
1223	while ($row = $smcFunc['db_fetch_assoc']($request))
1224		$themes[$row['id_theme']][$row['variable']] = $row['value'];
1225	$smcFunc['db_free_result']($request);
1226
1227	// This will be where we look
1228	$lang_dirs = array();
1229	// Check we have themes with a path and a name - just in case - and add the path.
1230	foreach ($themes as $id => $data)
1231	{
1232		if (count($data) != 2)
1233			unset($themes[$id]);
1234		elseif (is_dir($data['theme_dir'] . '/languages'))
1235			$lang_dirs[$id] = $data['theme_dir'] . '/languages';
1236
1237		// How about image directories?
1238		if (is_dir($data['theme_dir'] . '/images/' . $context['lang_id']))
1239			$images_dirs[$id] = $data['theme_dir'] . '/images/' . $context['lang_id'];
1240	}
1241
1242	$current_file = $file_id ? $lang_dirs[$theme_id] . '/' . $file_id . '.' . $context['lang_id'] . '.php' : '';
1243
1244	// Now for every theme get all the files and stick them in context!
1245	$context['possible_files'] = array();
1246	foreach ($lang_dirs as $theme => $theme_dir)
1247	{
1248		// Open it up.
1249		$dir = dir($theme_dir);
1250		while ($entry = $dir->read())
1251		{
1252			// We're only after the files for this language.
1253			if (preg_match('~^([A-Za-z]+)\.' . $context['lang_id'] . '\.php$~', $entry, $matches) == 0)
1254				continue;
1255
1256			//!!! Temp!
1257			if ($matches[1] == 'EmailTemplates')
1258				continue;
1259
1260			if (!isset($context['possible_files'][$theme]))
1261				$context['possible_files'][$theme] = array(
1262					'id' => $theme,
1263					'name' => $themes[$theme]['name'],
1264					'files' => array(),
1265				);
1266
1267			$context['possible_files'][$theme]['files'][] = array(
1268				'id' => $matches[1],
1269				'name' => isset($txt['lang_file_desc_' . $matches[1]]) ? $txt['lang_file_desc_' . $matches[1]] : $matches[1],
1270				'selected' => $theme_id == $theme && $file_id == $matches[1],
1271			);
1272		}
1273		$dir->close();
1274	}
1275
1276	// We no longer wish to speak this language.
1277	if (!empty($_POST['delete_main']) && $context['lang_id'] != 'english')
1278	{
1279		checkSession();
1280
1281		// !!! Todo: FTP Controls?
1282		require_once($sourcedir . '/Subs-Package.php');
1283
1284		// First, Make a backup?
1285		if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['lang_id'] . '$$$'))
1286		{
1287			$_SESSION['last_backup_for'] = $context['lang_id'] . '$$$';
1288			package_create_backup('backup_lang_' . $context['lang_id']);
1289		}
1290
1291		// Second, loop through the array to remove the files.
1292		foreach ($lang_dirs as $curPath)
1293		{
1294			foreach ($context['possible_files'][1]['files'] as $lang)
1295				if (file_exists($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php'))
1296					unlink($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php');
1297
1298			// Check for the email template.
1299			if (file_exists($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php'))
1300				unlink($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php');
1301		}
1302
1303		// Third, the agreement file.
1304		if (file_exists($boarddir . '/agreement.' . $context['lang_id'] . '.txt'))
1305			unlink($boarddir . '/agreement.' . $context['lang_id'] . '.txt');
1306
1307		// Fourth, a related images folder?
1308		foreach ($images_dirs as $curPath)
1309			if (is_dir($curPath))
1310				deltree($curPath);
1311
1312		// Members can no longer use this language.
1313		$smcFunc['db_query']('', '
1314			UPDATE {db_prefix}members
1315			SET lngfile = {string:empty_string}
1316			WHERE lngfile = {string:current_language}',
1317			array(
1318				'empty_string' => '',
1319				'current_language' => $context['lang_id'],
1320			)
1321		);
1322
1323		// Fifth, update getLanguages() cache.
1324		if (!empty($modSettings['cache_enable']))
1325		{
1326			cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
1327			cache_put_data('known_languages_all', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
1328		}
1329
1330		// Sixth, if we deleted the default language, set us back to english?
1331		if ($context['lang_id'] == $language)
1332		{
1333			require_once($sourcedir . '/Subs-Admin.php');
1334			$language = 'english';
1335			updateSettingsFile(array('language' => '\'' . $language . '\''));
1336		}
1337
1338		// Seventh, get out of here.
1339		redirectexit('action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id']);
1340	}
1341
1342	// Saving primary settings?
1343	$madeSave = false;
1344	if (!empty($_POST['save_main']) && !$current_file)
1345	{
1346		checkSession();
1347
1348		// Read in the current file.
1349		$current_data = implode('', file($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php'));
1350		// These are the replacements. old => new
1351		$replace_array = array(
1352			'~\$txt\[\'lang_character_set\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_character_set\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['character_set']) . '\';',
1353			'~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['locale']) . '\';',
1354			'~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['dictionary']) . '\';',
1355			'~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['spelling']) . '\';',
1356			'~\$txt\[\'lang_rtl\'\]\s=\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($_POST['rtl']) ? 'true' : 'false') . ';',
1357		);
1358		$current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data);
1359		$fp = fopen($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php', 'w+');
1360		fwrite($fp, $current_data);
1361		fclose($fp);
1362
1363		$madeSave = true;
1364	}
1365
1366	// Quickly load index language entries.
1367	$old_txt = $txt;
1368	require($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php');
1369	$context['lang_file_not_writable_message'] = is_writable($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php') ? '' : sprintf($txt['lang_file_not_writable'], $settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php');
1370	// Setup the primary settings context.
1371	$context['primary_settings'] = array(
1372		'name' => $smcFunc['ucwords'](strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))),
1373		'character_set' => $txt['lang_character_set'],
1374		'locale' => $txt['lang_locale'],
1375		'dictionary' => $txt['lang_dictionary'],
1376		'spelling' => $txt['lang_spelling'],
1377		'rtl' => $txt['lang_rtl'],
1378	);
1379
1380	// Restore normal service.
1381	$txt = $old_txt;
1382
1383	// Are we saving?
1384	$save_strings = array();
1385	if (isset($_POST['save_entries']) && !empty($_POST['entry']))
1386	{
1387		checkSession();
1388
1389		// Clean each entry!
1390		foreach ($_POST['entry'] as $k => $v)
1391		{
1392			// Only try to save if it's changed!
1393			if ($_POST['entry'][$k] != $_POST['comp'][$k])
1394				$save_strings[$k] = cleanLangString($v, false);
1395		}
1396	}
1397
1398	// If we are editing a file work away at that.
1399	if ($current_file)
1400	{
1401		$context['entries_not_writable_message'] = is_writable($current_file) ? '' : sprintf($txt['lang_entries_not_writable'], $current_file);
1402
1403		$entries = array();
1404		// We can't just require it I'm afraid - otherwise we pass in all kinds of variables!
1405		$multiline_cache = '';
1406		foreach (file($current_file) as $line)
1407		{
1408			// Got a new entry?
1409			if ($line[0] == '$' && !empty($multiline_cache))
1410			{
1411				preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s=\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
1412				if (!empty($matches[3]))
1413				{
1414					$entries[$matches[2]] = array(
1415						'type' => $matches[1],
1416						'full' => $matches[0],
1417						'entry' => $matches[3],
1418					);
1419					$multiline_cache = '';
1420				}
1421			}
1422			$multiline_cache .= $line . "\n";
1423		}
1424		// Last entry to add?
1425		if ($multiline_cache)
1426		{
1427			preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s=\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
1428			if (!empty($matches[3]))
1429				$entries[$matches[2]] = array(
1430					'type' => $matches[1],
1431					'full' => $matches[0],
1432					'entry' => $matches[3],
1433				);
1434		}
1435
1436		// These are the entries we can definitely save.
1437		$final_saves = array();
1438
1439		$context['file_entries'] = array();
1440		foreach ($entries as $entryKey => $entryValue)
1441		{
1442			// Ignore some things we set separately.
1443			$ignore_files = array('lang_character_set', 'lang_locale', 'lang_dictionary', 'lang_spelling', 'lang_rtl');
1444			if (in_array($entryKey, $ignore_files))
1445				continue;
1446
1447			// These are arrays that need breaking out.
1448			$arrays = array('days', 'days_short', 'months', 'months_titles', 'months_short');
1449			if (in_array($entryKey, $arrays))
1450			{
1451				// Get off the first bits.
1452				$entryValue['entry'] = substr($entryValue['entry'], strpos($entryValue['entry'], '(') + 1, strrpos($entryValue['entry'], ')') - strpos($entryValue['entry'], '('));
1453				$entryValue['entry'] = explode(',', strtr($entryValue['entry'], array(' ' => '')));
1454
1455				// Now create an entry for each item.
1456				$cur_index = 0;
1457				$save_cache = array(
1458					'enabled' => false,
1459					'entries' => array(),
1460				);
1461				foreach ($entryValue['entry'] as $id => $subValue)
1462				{
1463					// Is this a new index?
1464					if (preg_match('~^(\d+)~', $subValue, $matches))
1465					{
1466						$cur_index = $matches[1];
1467						$subValue = substr($subValue, strpos($subValue, '\''));
1468					}
1469
1470					// Clean up some bits.
1471					$subValue = strtr($subValue, array('"' => '', '\'' => '', ')' => ''));
1472
1473					// Can we save?
1474					if (isset($save_strings[$entryKey . '-+- ' . $cur_index]))
1475					{
1476						$save_cache['entries'][$cur_index] = strtr($save_strings[$entryKey . '-+- ' . $cur_index], array('\'' => ''));
1477						$save_cache['enabled'] = true;
1478					}
1479					else
1480						$save_cache['entries'][$cur_index] = $subValue;
1481
1482					$context['file_entries'][] = array(
1483						'key' => $entryKey . '-+- ' . $cur_index,
1484						'value' => $subValue,
1485						'rows' => 1,
1486					);
1487					$cur_index++;
1488				}
1489
1490				// Do we need to save?
1491				if ($save_cache['enabled'])
1492				{
1493					// Format the string, checking the indexes first.
1494					$items = array();
1495					$cur_index = 0;
1496					foreach ($save_cache['entries'] as $k2 => $v2)
1497					{
1498						// Manually show the custom index.
1499						if ($k2 != $cur_index)
1500						{
1501							$items[] = $k2 . ' => \'' . $v2 . '\'';
1502							$cur_index = $k2;
1503						}
1504						else
1505							$items[] = '\'' . $v2 . '\'';
1506
1507						$cur_index++;
1508					}
1509					// Now create the string!
1510					$final_saves[$entryKey] = array(
1511						'find' => $entryValue['full'],
1512						'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = array(' . implode(', ', $items) . ');',
1513					);
1514				}
1515			}
1516			else
1517			{
1518				// Saving?
1519				if (isset($save_strings[$entryKey]) && $save_strings[$entryKey] != $entryValue['entry'])
1520				{
1521					// !!! Fix this properly.
1522					if ($save_strings[$entryKey] == '')
1523						$save_strings[$entryKey] = '\'\'';
1524
1525					// Set the new value.
1526					$entryValue['entry'] = $save_strings[$entryKey];
1527					// And we know what to save now!
1528					$final_saves[$entryKey] = array(
1529						'find' => $entryValue['full'],
1530						'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = ' . $save_strings[$entryKey] . ';',
1531					);
1532				}
1533
1534				$editing_string = cleanLangString($entryValue['entry'], true);
1535				$context['file_entries'][] = array(
1536					'key' => $entryKey,
1537					'value' => $editing_string,
1538					'rows' => (int) (strlen($editing_string) / 38) + substr_count($editing_string, "\n") + 1,
1539				);
1540			}
1541		}
1542
1543		// Any saves to make?
1544		if (!empty($final_saves))
1545		{
1546			checkSession();
1547
1548			$file_contents = implode('', file($current_file));
1549			foreach ($final_saves as $save)
1550				$file_contents = strtr($file_contents, array($save['find'] => $save['replace']));
1551
1552			// Save the actual changes.
1553			$fp = fopen($current_file, 'w+');
1554			fwrite($fp, $file_contents);
1555			fclose($fp);
1556
1557			$madeSave = true;
1558		}
1559
1560		// Another restore.
1561		$txt = $old_txt;
1562	}
1563
1564	// If we saved, redirect.
1565	if ($madeSave)
1566		redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']);
1567}
1568
1569// This function could be two functions - either way it cleans language entries to/from display.
1570function cleanLangString($string, $to_display = true)
1571{
1572	global $smcFunc;
1573
1574	// If going to display we make sure it doesn't have any HTML in it - etc.
1575	$new_string = '';
1576	if ($to_display)
1577	{
1578		// Are we in a string (0 = no, 1 = single quote, 2 = parsed)
1579		$in_string = 0;
1580		$is_escape = false;
1581		for ($i = 0; $i < strlen($string); $i++)
1582		{
1583			// Handle ecapes first.
1584			if ($string{$i} == '\\')
1585			{
1586				// Toggle the escape.
1587				$is_escape = !$is_escape;
1588				// If we're now escaped don't add this string.
1589				if ($is_escape)
1590					continue;
1591			}
1592			// Special case - parsed string with line break etc?
1593			elseif (($string{$i} == 'n' || $string{$i} == 't') && $in_string == 2 && $is_escape)
1594			{
1595				// Put the escape back...
1596				$new_string .= $string{$i} == 'n' ? "\n" : "\t";
1597				$is_escape = false;
1598				continue;
1599			}
1600			// Have we got a single quote?
1601			elseif ($string{$i} == '\'')
1602			{
1603				// Already in a parsed string, or escaped in a linear string, means we print it - otherwise something special.
1604				if ($in_string != 2 && ($in_string != 1 || !$is_escape))
1605				{
1606					// Is it the end of a single quote string?
1607					if ($in_string == 1)
1608						$in_string = 0;
1609					// Otherwise it's the start!
1610					else
1611						$in_string = 1;
1612
1613					// Don't actually include this character!
1614					continue;
1615				}
1616			}
1617			// Otherwise a double quote?
1618			elseif ($string{$i} == '"')
1619			{
1620				// Already in a single quote string, or escaped in a parsed string, means we print it - otherwise something special.
1621				if ($in_string != 1 && ($in_string != 2 || !$is_escape))
1622				{
1623					// Is it the end of a double quote string?
1624					if ($in_string == 2)
1625						$in_string = 0;
1626					// Otherwise it's the start!
1627					else
1628						$in_string = 2;
1629
1630					// Don't actually include this character!
1631					continue;
1632				}
1633			}
1634			// A join/space outside of a string is simply removed.
1635			elseif ($in_string == 0 && (empty($string{$i}) || $string{$i} == '.'))
1636				continue;
1637			// Start of a variable?
1638			elseif ($in_string == 0 && $string{$i} == '$')
1639			{
1640				// Find the whole of it!
1641				preg_match('~([\$A-Za-z0-9\'\[\]_-]+)~', substr($string, $i), $matches);
1642				if (!empty($matches[1]))
1643				{
1644					// Come up with some pseudo thing to indicate this is a var.
1645					//!!! Do better than this, please!
1646					$new_string .= '{%' . $matches[1] . '%}';
1647
1648					// We're not going to reparse this.
1649					$i += strlen($matches[1]) - 1;
1650				}
1651
1652				continue;
1653			}
1654			// Right, if we're outside of a string we have DANGER, DANGER!
1655			elseif ($in_string == 0)
1656			{
1657				continue;
1658			}
1659
1660			// Actually add the character to the string!
1661			$new_string .= $string{$i};
1662			// If anything was escaped it ain't any longer!
1663			$is_escape = false;
1664		}
1665
1666		// Unhtml then rehtml the whole thing!
1667		$new_string = htmlspecialchars(un_htmlspecialchars($new_string));
1668	}
1669	else
1670	{
1671		// Keep track of what we're doing...
1672		$in_string = 0;
1673		// This is for deciding whether to HTML a quote.
1674		$in_html = false;
1675		for ($i = 0; $i < strlen($string); $i++)
1676		{
1677			// Handle line breaks!
1678			if ($string{$i} == "\n" || $string{$i} == "\t")
1679			{
1680				// Are we in a string? Is it the right type?
1681				if ($in_string == 1)
1682				{
1683					// Change type!
1684					$new_string .= '\' . "\\' . ($string{$i} == "\n" ? 'n' : 't');
1685					$in_string = 2;
1686				}
1687				elseif ($in_string == 2)
1688					$new_string .= '\\' . ($string{$i} == "\n" ? 'n' : 't');
1689				// Otherwise start one off - joining if required.
1690				else
1691					$new_string .= ($new_string ? ' . ' : '') . '"\\' . ($string{$i} == "\n" ? 'n' : 't');
1692
1693				continue;
1694			}
1695			// We don't do parsed strings apart from for breaks.
1696			elseif ($in_string == 2)
1697			{
1698				$in_string = 0;
1699				$new_string .= '"';
1700			}
1701
1702			// Not in a string yet?
1703			if ($in_string != 1)
1704			{
1705				$in_string = 1;
1706				$new_string .= ($new_string ? ' . ' : '') . '\'';
1707			}
1708
1709			// Is this a variable?
1710			if ($string{$i} == '{' && $string{$i + 1} == '%' && $string{$i + 2} == '$')
1711			{
1712				// Grab the variable.
1713				preg_match('~\{%([\$A-Za-z0-9\'\[\]_-]+)%\}~', substr($string, $i), $matches);
1714				if (!empty($matches[1]))
1715				{
1716					if ($in_string == 1)
1717						$new_string .= '\' . ';
1718					elseif ($new_string)
1719						$new_string .= ' . ';
1720
1721					$new_string .= $matches[1];
1722					$i += strlen($matches[1]) + 3;
1723					$in_string = 0;
1724				}
1725
1726				continue;
1727			}
1728			// Is this a lt sign?
1729			elseif ($string{$i} == '<')
1730			{
1731				// Probably HTML?
1732				if ($string{$i + 1} != ' ')
1733					$in_html = true;
1734				// Assume we need an entity...
1735				else
1736				{
1737					$new_string .= '&lt;';
1738					continue;
1739				}
1740			}
1741			// What about gt?
1742			elseif ($string{$i} == '>')
1743			{
1744				// Will it be HTML?
1745				if ($in_html)
1746					$in_html = false;
1747				// Otherwise we need an entity...
1748				else
1749				{
1750					$new_string .= '&gt;';
1751					continue;
1752				}
1753			}
1754			// Is it a slash? If so escape it...
1755			if ($string{$i} == '\\')
1756				$new_string .= '\\';
1757			// The infamous double quote?
1758			elseif ($string{$i} == '"')
1759			{
1760				// If we're in HTML we leave it as a quote - otherwise we entity it.
1761				if (!$in_html)
1762				{
1763					$new_string .= '&quot;';
1764					continue;
1765				}
1766			}
1767			// A single quote?
1768			elseif ($string{$i} == '\'')
1769			{
1770				// Must be in a string so escape it.
1771				$new_string .= '\\';
1772			}
1773
1774			// Finally add the character to the string!
1775			$new_string .= $string{$i};
1776		}
1777
1778		// If we ended as a string then close it off.
1779		if ($in_string == 1)
1780			$new_string .= '\'';
1781		elseif ($in_string == 2)
1782			$new_string .= '"';
1783	}
1784
1785	return $new_string;
1786}
1787
1788// Helper function, it sets up the context for the manage server settings.
1789function prepareServerSettingsContext(&$config_vars)
1790{
1791	global $context, $modSettings;
1792
1793	$context['config_vars'] = array();
1794	foreach ($config_vars as $identifier => $config_var)
1795	{
1796		if (!is_array($config_var) || !isset($config_var[1]))
1797			$context['config_vars'][] = $config_var;
1798		else
1799		{
1800			$varname = $config_var[0];
1801			global $$varname;
1802
1803			$context['config_vars'][] = array(
1804				'label' => $config_var[1],
1805				'help' => isset($config_var[5]) ? $config_var[5] : '',
1806				'type' => $config_var[3],
1807				'size' => empty($config_var[4]) ? 0 : $config_var[4],
1808				'data' => isset($config_var[4]) && is_array($config_var[4]) ? $config_var[4] : array(),
1809				'name' => $config_var[0],
1810				'value' => $config_var[2] == 'file' ? htmlspecialchars($$varname) : (isset($modSettings[$config_var[0]]) ? htmlspecialchars($modSettings[$config_var[0]]) : (in_array($config_var[3], array('int', 'float')) ? 0 : '')),
1811				'disabled' => !empty($context['settings_not_writable']) || !empty($config_var['disabled']),
1812				'invalid' => false,
1813				'javascript' => '',
1814				'preinput' => '',
1815				'postinput' => '',
1816			);
1817		}
1818	}
1819}
1820
1821// Helper function, it sets up the context for database settings.
1822function prepareDBSettingContext(&$config_vars)
1823{
1824	global $txt, $helptxt, $context, $modSettings, $sourcedir;
1825
1826	loadLanguage('Help');
1827
1828	$context['config_vars'] = array();
1829	$inlinePermissions = array();
1830	$bbcChoice = array();
1831	foreach ($config_vars as $config_var)
1832	{
1833		// HR?
1834		if (!is_array($config_var))
1835			$context['config_vars'][] = $config_var;
1836		else
1837		{
1838			// If it has no name it doesn't have any purpose!
1839			if (empty($config_var[1]))
1840				continue;
1841
1842			// Special case for inline permissions
1843			if ($config_var[0] == 'permissions' && allowedTo('manage_permissions'))
1844				$inlinePermissions[] = $config_var[1];
1845			elseif ($config_var[0] == 'permissions')
1846				continue;
1847
1848			// Are we showing the BBC selection box?
1849			if ($config_var[0] == 'bbc')
1850				$bbcChoice[] = $config_var[1];
1851
1852			$context['config_vars'][$config_var[1]] = array(
1853				'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] : '')),
1854				'help' => isset($helptxt[$config_var[1]]) ? $config_var[1] : '',
1855				'type' => $config_var[0],
1856				'size' => !empty($config_var[2]) && !is_array($config_var[2]) ? $config_var[2] : (in_array($config_var[0], array('int', 'float')) ? 6 : 0),
1857				'data' => array(),
1858				'name' => $config_var[1],
1859				'value' => isset($modSettings[$config_var[1]]) ? ($config_var[0] == 'select' ? $modSettings[$config_var[1]] : htmlspecialchars($modSettings[$config_var[1]])) : (in_array($config_var[0], array('int', 'float')) ? 0 : ''),
1860				'disabled' => false,
1861				'invalid' => !empty($config_var['invalid']),
1862				'javascript' => '',
1863				'var_message' => !empty($config_var['message']) && isset($txt[$config_var['message']]) ? $txt[$config_var['message']] : '',
1864				'preinput' => isset($config_var['preinput']) ? $config_var['preinput'] : '',
1865				'postinput' => isset($config_var['postinput']) ? $config_var['postinput'] : '',
1866			);
1867
1868			// If this is a select box handle any data.
1869			if (!empty($config_var[2]) && is_array($config_var[2]))
1870			{
1871				// If we allow multiple selections, we need to adjust a few things.
1872				if ($config_var[0] == 'select' && !empty($config_var['multiple']))
1873				{
1874					$context['config_vars'][$config_var[1]]['name'] .= '[]';
1875					$context['config_vars'][$config_var[1]]['value'] = unserialize($context['config_vars'][$config_var[1]]['value']);
1876				}
1877
1878				// If it's associative
1879				if (isset($config_var[2][0]) && is_array($config_var[2][0]))
1880					$context['config_vars'][$config_var[1]]['data'] = $config_var[2];
1881				else
1882				{
1883					foreach ($config_var[2] as $key => $item)
1884						$context['config_vars'][$config_var[1]]['data'][] = array($key, $item);
1885				}
1886			}
1887
1888			// Finally allow overrides - and some final cleanups.
1889			foreach ($config_var as $k => $v)
1890			{
1891				if (!is_numeric($k))
1892				{
1893					if (substr($k, 0, 2) == 'on')
1894						$context['config_vars'][$config_var[1]]['javascript'] .= ' ' . $k . '="' . $v . '"';
1895					else
1896						$context['config_vars'][$config_var[1]][$k] = $v;
1897				}
1898
1899				// See if there are any other labels that might fit?
1900				if (isset($txt['setting_' . $config_var[1]]))
1901					$context['config_vars'][$config_var[1]]['label'] = $txt['setting_' . $config_var[1]];
1902				elseif (isset($txt['groups_' . $config_var[1]]))
1903					$context['config_vars'][$config_var[1]]['label'] = $txt['groups_' . $config_var[1]];
1904			}
1905
1906			// Set the subtext in case it's part of the label.
1907			// !!! Temporary. Preventing divs inside label tags.
1908			$divPos = strpos($context['config_vars'][$config_var[1]]['label'], '<div');
1909			if ($divPos !== false)
1910			{
1911				$context['config_vars'][$config_var[1]]['subtext'] = preg_replace('~</?div[^>]*>~', '', substr($context['config_vars'][$config_var[1]]['label'], $divPos));
1912				$context['config_vars'][$config_var[1]]['label'] = substr($context['config_vars'][$config_var[1]]['label'], 0, $divPos);
1913			}
1914		}
1915	}
1916
1917	// If we have inline permissions we need to prep them.
1918	if (!empty($inlinePermissions) && allowedTo('manage_permissions'))
1919	{
1920		require_once($sourcedir . '/ManagePermissions.php');
1921		init_inline_permissions($inlinePermissions, isset($context['permissions_excluded']) ? $context['permissions_excluded'] : array());
1922	}
1923
1924	// What about any BBC selection boxes?
1925	if (!empty($bbcChoice))
1926	{
1927		// What are the options, eh?
1928		$temp = parse_bbc(false);
1929		$bbcTags = array();
1930		foreach ($temp as $tag)
1931			$bbcTags[] = $tag['tag'];
1932
1933		$bbcTags = array_unique($bbcTags);
1934		$totalTags = count($bbcTags);
1935
1936		// The number of columns we want to show the BBC tags in.
1937		$numColumns = isset($context['num_bbc_columns']) ? $context['num_bbc_columns'] : 3;
1938
1939		// Start working out the context stuff.
1940		$context['bbc_columns'] = array();
1941		$tagsPerColumn = ceil($totalTags / $numColumns);
1942
1943		$col = 0; $i = 0;
1944		foreach ($bbcTags as $tag)
1945		{
1946			if ($i % $tagsPerColumn == 0 && $i != 0)
1947				$col++;
1948
1949			$context['bbc_columns'][$col][] = array(
1950				'tag' => $tag,
1951				// !!! 'tag_' . ?
1952				'show_help' => isset($helptxt[$tag]),
1953			);
1954
1955			$i++;
1956		}
1957
1958		// Now put whatever BBC options we may have into context too!
1959		$context['bbc_sections'] = array();
1960		foreach ($bbcChoice as $bbc)
1961		{
1962			$context['bbc_sections'][$bbc] = array(
1963				'title' => isset($txt['bbc_title_' . $bbc]) ? $txt['bbc_title_' . $bbc] : $txt['bbcTagsToUse_select'],
1964				'disabled' => empty($modSettings['bbc_disabled_' . $bbc]) ? array() : $modSettings['bbc_disabled_' . $bbc],
1965				'all_selected' => empty($modSettings['bbc_disabled_' . $bbc]),
1966			);
1967		}
1968	}
1969}
1970
1971// Helper function. Saves settings by putting them in Settings.php or saving them in the settings table.
1972function saveSettings(&$config_vars)
1973{
1974	global $boarddir, $sc, $cookiename, $modSettings, $user_settings;
1975	global $sourcedir, $context, $cachedir;
1976
1977	// Fix the darn stupid cookiename! (more may not be allowed, but these for sure!)
1978	if (isset($_POST['cookiename']))
1979		$_POST['cookiename'] = preg_replace('~[,;\s\.$]+~' . ($context['utf8'] ? 'u' : ''), '', $_POST['cookiename']);
1980
1981	// Fix the forum's URL if necessary.
1982	if (isset($_POST['boardurl']))
1983	{
1984		if (substr($_POST['boardurl'], -10) == '/index.php')
1985			$_POST['boardurl'] = substr($_POST['boardurl'], 0, -10);
1986		elseif (substr($_POST['boardurl'], -1) == '/')
1987			$_POST['boardurl'] = substr($_POST['boardurl'], 0, -1);
1988		if (substr($_POST['boardurl'], 0, 7) != 'http://' && substr($_POST['boardurl'], 0, 7) != 'file://' && substr($_POST['boardurl'], 0, 8) != 'https://')
1989			$_POST['boardurl'] = 'http://' . $_POST['boardurl'];
1990	}
1991
1992	// Any passwords?
1993	$config_passwords = array(
1994		'db_passwd',
1995		'ssi_db_passwd',
1996	);
1997
1998	// All the strings to write.
1999	$config_strs = array(
2000		'mtitle', 'mmessage',
2001		'language', 'mbname', 'boardurl',
2002		'cookiename',
2003		'webmaster_email',
2004		'db_name', 'db_user', 'db_server', 'db_prefix', 'ssi_db_user',
2005		'boarddir', 'sourcedir', 'cachedir',
2006	);
2007	// All the numeric variables.
2008	$config_ints = array(
2009	);
2010	// All the checkboxes.
2011	$config_bools = array(
2012		'db_persist', 'db_error_send',
2013		'maintenance',
2014	);
2015
2016	// Now sort everything into a big array, and figure out arrays and etc.
2017	$new_settings = array();
2018	foreach ($config_passwords as $config_var)
2019	{
2020		if (isset($_POST[$config_var][1]) && $_POST[$config_var][0] == $_POST[$config_var][1])
2021			$new_settings[$config_var] = '\'' . addcslashes($_POST[$config_var][0], '\'\\') . '\'';
2022	}
2023	foreach ($config_strs as $config_var)
2024	{
2025		if (isset($_POST[$config_var]))
2026			$new_settings[$config_var] = '\'' . addcslashes($_POST[$config_var], '\'\\') . '\'';
2027	}
2028	foreach ($config_ints as $config_var)
2029	{
2030		if (isset($_POST[$config_var]))
2031			$new_settings[$config_var] = (int) $_POST[$config_var];
2032	}
2033	foreach ($config_bools as $key)
2034	{
2035		if (!empty($_POST[$key]))
2036			$new_settings[$key] = '1';
2037		else
2038			$new_settings[$key] = '0';
2039	}
2040
2041	// Save the relevant settings in the Settings.php file.
2042	require_once($sourcedir . '/Subs-Admin.php');
2043	updateSettingsFile($new_settings);
2044
2045	// Now loopt through the remaining (database-based) settings.
2046	$new_settings = array();
2047	foreach ($config_vars as $config_var)
2048	{
2049		// We just saved the file-based settings, so skip their definitions.
2050		if (!is_array($config_var) || $config_var[2] == 'file')
2051			continue;
2052
2053		// Rewrite the definition a bit.
2054		$new_settings[] = array($config_var[3], $config_var[0]);
2055	}
2056
2057	// Save the new database-based settings, if any.
2058	if (!empty($new_settings))
2059		saveDBSettings($new_settings);
2060}
2061
2062// Helper function for saving database settings.
2063function saveDBSettings(&$config_vars)
2064{
2065	global $sourcedir, $context;
2066
2067	$inlinePermissions = array();
2068	foreach ($config_vars as $var)
2069	{
2070		if (!isset($var[1]) || (!isset($_POST[$var[1]]) && $var[0] != 'check' && $var[0] != 'permissions' && ($var[0] != 'bbc' || !isset($_POST[$var[1] . '_enabledTags']))))
2071			continue;
2072
2073		// Checkboxes!
2074		elseif ($var[0] == 'check')
2075			$setArray[$var[1]] = !empty($_POST[$var[1]]) ? '1' : '0';
2076		// Select boxes!
2077		elseif ($var[0] == 'select' && in_array($_POST[$var[1]], array_keys($var[2])))
2078			$setArray[$var[1]] = $_POST[$var[1]];
2079		elseif ($var[0] == 'select' && !empty($var['multiple']) && array_intersect($_POST[$var[1]], array_keys($var[2])) != array())
2080		{
2081			// For security purposes we validate this line by line.
2082			$options = array();
2083			foreach ($_POST[$var[1]] as $invar)
2084				if (in_array($invar, array_keys($var[2])))
2085					$options[] = $invar;
2086
2087			$setArray[$var[1]] = serialize($options);
2088		}
2089		// Integers!
2090		elseif ($var[0] == 'int')
2091			$setArray[$var[1]] = (int) $_POST[$var[1]];
2092		// Floating point!
2093		elseif ($var[0] == 'float')
2094			$setArray[$var[1]] = (float) $_POST[$var[1]];
2095		// Text!
2096		elseif ($var[0] == 'text' || $var[0] == 'large_text')
2097			$setArray[$var[1]] = $_POST[$var[1]];
2098		// Passwords!
2099		elseif ($var[0] == 'password')
2100		{
2101			if (isset($_POST[$var[1]][1]) && $_POST[$var[1]][0] == $_POST[$var[1]][1])
2102				$setArray[$var[1]] = $_POST[$var[1]][0];
2103		}
2104		// BBC.
2105		elseif ($var[0] == 'bbc')
2106		{
2107
2108			$bbcTags = array();
2109			foreach (parse_bbc(false) as $tag)
2110				$bbcTags[] = $tag['tag'];
2111
2112			if (!isset($_POST[$var[1] . '_enabledTags']))
2113				$_POST[$var[1] . '_enabledTags'] = array();
2114			elseif (!is_array($_POST[$var[1] . '_enabledTags']))
2115				$_POST[$var[1] . '_enabledTags'] = array($_POST[$var[1] . '_enabledTags']);
2116
2117			$setArray[$var[1]] = implode(',', array_diff($bbcTags, $_POST[$var[1] . '_enabledTags']));
2118		}
2119		// Permissions?
2120		elseif ($var[0] == 'permissions')
2121			$inlinePermissions[] = $var[1];
2122	}
2123
2124	if (!empty($setArray))
2125		updateSettings($setArray);
2126
2127	// If we have inline permissions we need to save them.
2128	if (!empty($inlinePermissions) && allowedTo('manage_permissions'))
2129	{
2130		require_once($sourcedir . '/ManagePermissions.php');
2131		save_inline_permissions($inlinePermissions);
2132	}
2133}
2134
2135?>