PageRenderTime 67ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/forum/Sources/ManageServer.php

https://github.com/leftnode/nooges.com
PHP | 2077 lines | 1440 code | 231 blank | 406 comment | 294 complexity | e12e53f3741bd317a721096a624b5f0e MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**********************************************************************************
  3. * ManageServer.php *
  4. ***********************************************************************************
  5. * SMF: Simple Machines Forum *
  6. * Open-Source Project Inspired by Zef Hemel (zef@zefhemel.com) *
  7. * =============================================================================== *
  8. * Software Version: SMF 2.0 RC2 *
  9. * Software by: Simple Machines (http://www.simplemachines.org) *
  10. * Copyright 2006-2009 by: Simple Machines LLC (http://www.simplemachines.org) *
  11. * 2001-2006 by: Lewis Media (http://www.lewismedia.com) *
  12. * Support, News, Updates at: http://www.simplemachines.org *
  13. ***********************************************************************************
  14. * This program is free software; you may redistribute it and/or modify it under *
  15. * the terms of the provided license as published by Simple Machines LLC. *
  16. * *
  17. * This program is distributed in the hope that it is and will be useful, but *
  18. * WITHOUT ANY WARRANTIES; without even any implied warranty of MERCHANTABILITY *
  19. * or FITNESS FOR A PARTICULAR PURPOSE. *
  20. * *
  21. * See the "license.txt" file for details of the Simple Machines license. *
  22. * The latest version can always be found at http://www.simplemachines.org. *
  23. **********************************************************************************/
  24. if (!defined('SMF'))
  25. die('Hacking attempt...');
  26. /* This file contains all the functionality required to be able to edit the
  27. core server settings. This includes anything from which an error may result
  28. in the forum destroying itself in a firey fury.
  29. void ModifySettings()
  30. // !!!
  31. void ModifyGeneralSettings()
  32. - shows an interface for the settings in Settings.php to be changed.
  33. - uses the rawdata sub template (not theme-able.)
  34. - requires the admin_forum permission.
  35. - uses the edit_settings administration area.
  36. - contains the actual array of settings to show from Settings.php.
  37. - accessed from ?action=admin;area=serversettings;sa=general.
  38. void ModifyDatabaseSettings()
  39. - shows an interface for the settings in Settings.php to be changed.
  40. - uses the rawdata sub template (not theme-able.)
  41. - requires the admin_forum permission.
  42. - uses the edit_settings administration area.
  43. - contains the actual array of settings to show from Settings.php.
  44. - accessed from ?action=admin;area=serversettings;sa=database.
  45. void ModifyCookieSettings()
  46. // !!!
  47. void ModifyCacheSettings()
  48. // !!!
  49. void ModifyLoadBalancingSettings()
  50. // !!!
  51. void AddLanguage()
  52. // !!!
  53. void DownloadLanguage()
  54. // !!!
  55. void ManageLanguages()
  56. // !!!
  57. void ModifyLanguages()
  58. // !!!
  59. int list_getNumLanguages()
  60. // !!!
  61. array list_getLanguages()
  62. // !!!
  63. void ModifyLanguageSettings()
  64. // !!!
  65. void ModifyLanguage()
  66. // !!!
  67. void prepareServerSettingsContext(array config_vars)
  68. // !!!
  69. void prepareDBSettingContext(array config_vars)
  70. // !!!
  71. void saveSettings(array config_vars)
  72. - saves those settings set from ?action=admin;area=serversettings to the
  73. Settings.php file and the database.
  74. - requires the admin_forum permission.
  75. - contains arrays of the types of data to save into Settings.php.
  76. void saveDBSettings(array config_vars)
  77. // !!!
  78. */
  79. /* Adding options to one of the setting screens isn't hard. Call prepareDBSettingsContext;
  80. The basic format for a checkbox is:
  81. array('check', 'nameInModSettingsAndSQL'),
  82. And for a text box:
  83. array('text', 'nameInModSettingsAndSQL')
  84. (NOTE: You have to add an entry for this at the bottom!)
  85. In these cases, it will look for $txt['nameInModSettingsAndSQL'] as the description,
  86. and $helptxt['nameInModSettingsAndSQL'] as the help popup description.
  87. Here's a quick explanation of how to add a new item:
  88. * A text input box. For textual values.
  89. ie. array('text', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
  90. * A text input box. For numerical values.
  91. ie. array('int', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
  92. * A text input box. For floating point values.
  93. ie. array('float', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
  94. * A large text input box. Used for textual values spanning multiple lines.
  95. ie. array('large_text', 'nameInModSettingsAndSQL', 'OptionalNumberOfRows'),
  96. * A check box. Either one or zero. (boolean)
  97. ie. array('check', 'nameInModSettingsAndSQL'),
  98. * A selection box. Used for the selection of something from a list.
  99. ie. array('select', 'nameInModSettingsAndSQL', array('valueForSQL' => $txt['displayedValue'])),
  100. Note that just saying array('first', 'second') will put 0 in the SQL for 'first'.
  101. * A password input box. Used for passwords, no less!
  102. ie. array('password', 'nameInModSettingsAndSQL', 'OptionalInputBoxWidth'),
  103. * A permission - for picking groups who have a permission.
  104. ie. array('permissions', 'manage_groups'),
  105. * A BBC selection box.
  106. ie. array('bbc', 'sig_bbc'),
  107. For each option:
  108. type (see above), variable name, size/possible values.
  109. OR make type '' for an empty string for a horizontal rule.
  110. SET preinput - to put some HTML prior to the input box.
  111. SET postinput - to put some HTML following the input box.
  112. SET invalid - to mark the data as invalid.
  113. PLUS You can override label and help parameters by forcing their keys in the array, for example:
  114. array('text', 'invalidlabel', 3, 'label' => 'Actual Label') */
  115. // This is the main pass through function, it creates tabs and the like.
  116. function ModifySettings()
  117. {
  118. global $context, $txt, $scripturl, $boarddir;
  119. // This is just to keep the database password more secure.
  120. isAllowedTo('admin_forum');
  121. // Load up all the tabs...
  122. $context[$context['admin_menu_name']]['tab_data'] = array(
  123. 'title' => $txt['admin_server_settings'],
  124. 'help' => 'serversettings',
  125. 'description' => $txt['admin_basic_settings'],
  126. );
  127. checkSession('request');
  128. // The settings are in here, I swear!
  129. loadLanguage('ManageSettings');
  130. $context['page_title'] = $txt['admin_server_settings'];
  131. $context['sub_template'] = 'show_settings';
  132. $subActions = array(
  133. 'general' => 'ModifyGeneralSettings',
  134. 'database' => 'ModifyDatabaseSettings',
  135. 'cookie' => 'ModifyCookieSettings',
  136. 'cache' => 'ModifyCacheSettings',
  137. 'loads' => 'ModifyLoadBalancingSettings',
  138. );
  139. // By default we're editing the core settings
  140. $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'general';
  141. $context['sub_action'] = $_REQUEST['sa'];
  142. // Warn the user if there's any relevant information regarding Settings.php.
  143. if ($_REQUEST['sa'] != 'cache')
  144. {
  145. // Warn the user if the backup of Settings.php failed.
  146. $settings_not_writable = !is_writable($boarddir . '/Settings.php');
  147. $settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php');
  148. if ($settings_not_writable)
  149. $context['settings_message'] = '<div class="centertext"><strong>' . $txt['settings_not_writable'] . '</strong></div><br />';
  150. elseif ($settings_backup_fail)
  151. $context['settings_message'] = '<div class="centertext"><strong>' . $txt['admin_backup_fail'] . '</strong></div><br />';
  152. $context['settings_not_writable'] = $settings_not_writable;
  153. }
  154. // Call the right function for this sub-action.
  155. $subActions[$_REQUEST['sa']]();
  156. }
  157. // General forum settings - forum name, maintenance mode, etc.
  158. function ModifyGeneralSettings($return_config = false)
  159. {
  160. global $scripturl, $context, $txt;
  161. /* If you're writing a mod, it's a bad idea to add things here....
  162. For each option:
  163. variable name, description, type (constant), size/possible values, helptext.
  164. OR an empty string for a horizontal rule.
  165. OR a string for a titled section. */
  166. $config_vars = array(
  167. array('mbname', $txt['admin_title'], 'file', 'text', 30),
  168. '',
  169. array('maintenance', $txt['admin_maintain'], 'file', 'check'),
  170. array('mtitle', $txt['maintenance_subject'], 'file', 'text', 36),
  171. array('mmessage', $txt['maintenance_message'], 'file', 'text', 36),
  172. '',
  173. array('webmaster_email', $txt['admin_webmaster_email'], 'file', 'text', 30),
  174. '',
  175. array('enableCompressedOutput', $txt['enableCompressedOutput'], 'db', 'check', null, 'enableCompressedOutput'),
  176. array('disableTemplateEval', $txt['disableTemplateEval'], 'db', 'check', null, 'disableTemplateEval'),
  177. array('disableHostnameLookup', $txt['disableHostnameLookup'], 'db', 'check', null, 'disableHostnameLookup'),
  178. );
  179. if ($return_config)
  180. return $config_vars;
  181. // Setup the template stuff.
  182. $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=general;save';
  183. $context['settings_title'] = $txt['general_settings'];
  184. // Saving settings?
  185. if (isset($_REQUEST['save']))
  186. {
  187. saveSettings($config_vars);
  188. redirectexit('action=admin;area=serversettings;sa=general;' . $context['session_var'] . '=' . $context['session_id']);
  189. }
  190. // Fill the config array.
  191. prepareServerSettingsContext($config_vars);
  192. }
  193. // Basic database and paths settings - database name, host, etc.
  194. function ModifyDatabaseSettings($return_config = false)
  195. {
  196. global $scripturl, $context, $settings, $txt, $boarddir;
  197. /* If you're writing a mod, it's a bad idea to add things here....
  198. For each option:
  199. variable name, description, type (constant), size/possible values, helptext.
  200. OR an empty string for a horizontal rule.
  201. OR a string for a titled section. */
  202. $config_vars = array(
  203. array('db_server', $txt['database_server'], 'file', 'text'),
  204. array('db_user', $txt['database_user'], 'file', 'text'),
  205. array('db_passwd', $txt['database_password'], 'file', 'password'),
  206. array('db_name', $txt['database_name'], 'file', 'text'),
  207. array('db_prefix', $txt['database_prefix'], 'file', 'text'),
  208. array('db_persist', $txt['db_persist'], 'file', 'check', null, 'db_persist'),
  209. array('db_error_send', $txt['db_error_send'], 'file', 'check'),
  210. array('ssi_db_user', $txt['ssi_db_user'], 'file', 'text', null, 'ssi_db_user'),
  211. array('ssi_db_passwd', $txt['ssi_db_passwd'], 'file', 'password'),
  212. '',
  213. array('autoFixDatabase', $txt['autoFixDatabase'], 'db', 'check'),
  214. array('autoOptMaxOnline', $txt['autoOptMaxOnline'], 'db', 'int'),
  215. '',
  216. array('boardurl', $txt['admin_url'], 'file', 'text', 36),
  217. array('boarddir', $txt['boarddir'], 'file', 'text', 36),
  218. array('sourcedir', $txt['sourcesdir'], 'file', 'text', 36),
  219. array('cachedir', $txt['cachedir'], 'file', 'text', 36),
  220. );
  221. if ($return_config)
  222. return $config_vars;
  223. // Setup the template stuff.
  224. $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=database;save';
  225. $context['settings_title'] = $txt['database_paths_settings'];
  226. $context['save_disabled'] = $context['settings_not_writable'];
  227. // Saving settings?
  228. if (isset($_REQUEST['save']))
  229. {
  230. saveSettings($config_vars);
  231. redirectexit('action=admin;area=serversettings;sa=database;' . $context['session_var'] . '=' . $context['session_id']);
  232. }
  233. // Fill the config array.
  234. prepareServerSettingsContext($config_vars);
  235. }
  236. // This function basically edits anything which is configuration and stored in the database, except for caching.
  237. function ModifyCookieSettings($return_config = false)
  238. {
  239. global $context, $scripturl, $txt, $sourcedir, $modSettings, $cookiename, $user_settings;
  240. // Define the variables we want to edit.
  241. $config_vars = array(
  242. // Cookies...
  243. array('cookiename', $txt['cookie_name'], 'file', 'text', 20),
  244. array('cookieTime', $txt['cookieTime'], 'db', 'int'),
  245. array('localCookies', $txt['localCookies'], 'db', 'check'),
  246. array('globalCookies', $txt['globalCookies'], 'db', 'check'),
  247. array('secureCookies', $txt['secureCookies'], 'db', 'check', 'disabled' => !isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) != 'on'),
  248. '',
  249. // Sessions
  250. array('databaseSession_enable', $txt['databaseSession_enable'], 'db', 'check'),
  251. array('databaseSession_loose', $txt['databaseSession_loose'], 'db', 'check'),
  252. array('databaseSession_lifetime', $txt['databaseSession_lifetime'], 'db', 'int'),
  253. );
  254. if ($return_config)
  255. return $config_vars;
  256. $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cookie;save';
  257. $context['settings_title'] = $txt['cookies_sessions_settings'];
  258. // Saving settings?
  259. if (isset($_REQUEST['save']))
  260. {
  261. saveSettings($config_vars);
  262. // If the cookie name was changed, reset the cookie.
  263. if ($cookiename != $_POST['cookiename'])
  264. {
  265. $original_session_id = $context['session_id'];
  266. include_once($sourcedir . '/Subs-Auth.php');
  267. // Remove the old cookie.
  268. setLoginCookie(-3600, 0);
  269. // Set the new one.
  270. $cookiename = $_POST['cookiename'];
  271. setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], sha1($user_settings['passwd'] . $user_settings['password_salt']));
  272. redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $original_session_id, $context['server']['needs_login_fix']);
  273. }
  274. redirectexit('action=admin;area=serversettings;sa=cookie;' . $context['session_var'] . '=' . $context['session_id']);
  275. }
  276. // Fill the config array.
  277. prepareServerSettingsContext($config_vars);
  278. }
  279. // Simply modifying cache functions
  280. function ModifyCacheSettings($return_config = false)
  281. {
  282. global $context, $scripturl, $txt, $helptxt, $modSettings;
  283. // Define the variables we want to edit.
  284. $config_vars = array(
  285. // Only a couple of settings, but they are important
  286. array('select', 'cache_enable', array($txt['cache_off'], $txt['cache_level1'], $txt['cache_level2'], $txt['cache_level3'])),
  287. array('text', 'cache_memcached'),
  288. );
  289. if ($return_config)
  290. return $config_vars;
  291. // Saving again?
  292. if (isset($_GET['save']))
  293. {
  294. saveDBSettings($config_vars);
  295. // We have to manually force the clearing of the cache otherwise the changed settings might not get noticed.
  296. $modSettings['cache_enable'] = 1;
  297. cache_put_data('modSettings', null, 90);
  298. redirectexit('action=admin;area=serversettings;sa=cache;' . $context['session_var'] . '=' . $context['session_id']);
  299. }
  300. $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=cache;save';
  301. $context['settings_title'] = $txt['caching_settings'];
  302. $context['settings_message'] = $txt['caching_information'];
  303. // Detect an optimizer?
  304. if (function_exists('eaccelerator_put'))
  305. $detected = 'eAccelerator';
  306. elseif (function_exists('mmcache_put'))
  307. $detected = 'MMCache';
  308. elseif (function_exists('apc_store'))
  309. $detected = 'APC';
  310. elseif (function_exists('output_cache_put'))
  311. $detected = 'Zend';
  312. elseif (function_exists('memcache_set'))
  313. $detected = 'Memcached';
  314. elseif (function_exists('xcache_set'))
  315. $detected = 'XCache';
  316. else
  317. $detected = 'no_caching';
  318. $context['settings_message'] = sprintf($context['settings_message'], $txt['detected_' . $detected]);
  319. // Prepare the template.
  320. prepareDBSettingContext($config_vars);
  321. }
  322. function ModifyLoadBalancingSettings($return_config = false)
  323. {
  324. global $txt, $scripturl, $context, $settings, $modSettings;
  325. // Setup a warning message.
  326. $context['settings_message'] = $txt['loadavg_warning'];
  327. // Start with a simple checkbox.
  328. $config_vars = array(
  329. array('check', 'loadavg_enable'),
  330. );
  331. // Set the default values for each option.
  332. $default_values = array(
  333. 'loadavg_auto_opt' => '1.0',
  334. 'loadavg_search' => '2.5',
  335. 'loadavg_allunread' => '2.0',
  336. 'loadavg_unreadreplies' => '3.5',
  337. 'loadavg_show_posts' => '2.0',
  338. 'loadavg_forum' => '40.0',
  339. );
  340. // Loop through the settings.
  341. foreach ($default_values as $name => $value)
  342. {
  343. // Use the default value if the setting isn't set yet.
  344. $value = !isset($modSettings[$name]) ? $value : $modSettings[$name];
  345. $config_vars[] = array('text', $name, 'value' => $value);
  346. }
  347. if ($return_config)
  348. return $config_vars;
  349. $context['post_url'] = $scripturl . '?action=admin;area=serversettings;sa=loads;save';
  350. $context['settings_title'] = $txt['load_balancing_settings'];
  351. // Saving?
  352. if (isset($_GET['save']))
  353. {
  354. // Stupidity is not allowed.
  355. foreach ($_POST as $key => $value)
  356. {
  357. if (substr($key, 0, 7) != 'loadavg' || $key == 'loadavg_enable')
  358. continue;
  359. elseif ($key == 'loadavg_auto_opt' && $value <= 1)
  360. $_POST['loadavg_auto_opt'] = '1.0';
  361. elseif ($key == 'loadavg_forum' && $value < 10)
  362. $_POST['loadavg_forum'] = '10.0';
  363. elseif ($value < 2)
  364. $_POST[$key] = '2.0';
  365. }
  366. saveDBSettings($config_vars);
  367. redirectexit('action=admin;area=serversettings;sa=loads;' . $context['session_var'] . '=' . $context['session_id']);
  368. }
  369. prepareDBSettingContext($config_vars);
  370. }
  371. // Interface for adding a new language
  372. function AddLanguage()
  373. {
  374. global $context, $sourcedir, $forum_version, $boarddir, $txt, $smcFunc, $scripturl;
  375. // Are we searching for new languages courtesy of Simple Machines?
  376. if (!empty($_POST['smf_add_sub']))
  377. {
  378. // Need fetch_web_data.
  379. require_once($sourcedir . '/Subs-Package.php');
  380. $context['smf_search_term'] = trim($_POST['smf_add']);
  381. // We're going to use this URL.
  382. $url = 'http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => '')));
  383. // Load the class file and stick it into an array.
  384. loadClassFile('Class-Package.php');
  385. $language_list = new xmlArray(fetch_web_data($url), true);
  386. // Check it exists.
  387. if (!$language_list->exists('languages'))
  388. $context['smf_error'] = 'no_response';
  389. else
  390. {
  391. $language_list = $language_list->path('languages[0]');
  392. $lang_files = $language_list->set('language');
  393. $context['smf_languages'] = array();
  394. foreach ($lang_files as $file)
  395. {
  396. // Were we searching?
  397. if (!empty($context['smf_search_term']) && strpos($file->fetch('name'), $smcFunc['strtolower']($context['smf_search_term'])) === false)
  398. continue;
  399. $context['smf_languages'][] = array(
  400. 'id' => $file->fetch('id'),
  401. 'name' => $smcFunc['ucwords']($file->fetch('name')),
  402. 'version' => $file->fetch('version'),
  403. 'utf8' => $file->fetch('utf8'),
  404. 'description' => $file->fetch('description'),
  405. 'link' => $scripturl . '?action=admin;area=languages;sa=downloadlang;did=' . $file->fetch('id') . ';' . $context['session_var'] . '=' . $context['session_id'],
  406. );
  407. }
  408. if (empty($context['smf_languages']))
  409. $context['smf_error'] = 'no_files';
  410. }
  411. }
  412. $context['sub_template'] = 'add_language';
  413. }
  414. // Download a language file from the Simple Machines website.
  415. function DownloadLanguage()
  416. {
  417. global $context, $sourcedir, $forum_version, $boarddir, $txt, $smcFunc, $scripturl, $modSettings;
  418. loadLanguage('ManageSettings');
  419. require_once($sourcedir . '/Subs-Package.php');
  420. // Clearly we need to know what to request.
  421. if (!isset($_GET['did']))
  422. fatal_lang_error('no_access', false);
  423. // Some lovely context.
  424. $context['download_id'] = $_GET['did'];
  425. $context['sub_template'] = 'download_language';
  426. $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'add';
  427. // Can we actually do the installation - and do they want to?
  428. if (!empty($_POST['do_install']) && !empty($_POST['copy_file']))
  429. {
  430. $chmod_files = array();
  431. $install_files = array();
  432. // Check writable status.
  433. foreach ($_POST['copy_file'] as $file)
  434. {
  435. // Check it's not very bad.
  436. if (strpos($file, '..') !== false || (substr($file, 0, 6) != 'Themes' && !preg_match('~agreement\.[A-Za-z-_0-9]+\.txt$~', $file)))
  437. fatal_error($txt['languages_download_illegal_paths']);
  438. $chmod_files[] = $boarddir . '/' . $file;
  439. $install_files[] = $file;
  440. }
  441. // Call this in case we have work to do.
  442. $file_status = create_chmod_control($chmod_files);
  443. $files_left = $file_status['files']['notwritable'];
  444. // Something not writable?
  445. if (!empty($files_left))
  446. $context['error_message'] = $txt['languages_download_not_chmod'];
  447. // Otherwise, go go go!
  448. elseif (!empty($install_files))
  449. {
  450. $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);
  451. // Make sure the files aren't stuck in the cache.
  452. package_flush_cache();
  453. $context['install_complete'] = sprintf($txt['languages_download_complete_desc'], $scripturl . '?action=admin;area=languages;' . $context['session_var'] . '=' . $context['session_id']);
  454. clean_cache('lang');
  455. return;
  456. }
  457. }
  458. // Open up the old china.
  459. if (!isset($archive_content))
  460. $archive_content = read_tgz_file('http://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))) . ';fetch=' . urlencode($_GET['did']), null);
  461. if (empty($archive_content))
  462. fatal_error($txt['add_language_error_no_response']);
  463. // Now for each of the files, let's do some *stuff*
  464. $context['files'] = array(
  465. 'lang' => array(),
  466. 'other' => array(),
  467. );
  468. $context['make_writable'] = array();
  469. foreach ($archive_content as $file)
  470. {
  471. $dirname = dirname($file['filename']);
  472. $filename = basename($file['filename']);
  473. $extension = substr($filename, strrpos($filename, '.') + 1);
  474. // Don't do anything with files we don't understand.
  475. if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt')))
  476. continue;
  477. // Basic data.
  478. $context_data = array(
  479. 'name' => $filename,
  480. 'destination' => $boarddir . '/' . $file['filename'],
  481. 'generaldest' => $file['filename'],
  482. 'size' => $file['size'],
  483. // Does chmod status allow the copy?
  484. 'writable' => false,
  485. // Should we suggest they copy this file?
  486. 'default_copy' => true,
  487. // Does the file already exist, if so is it same or different?
  488. 'exists' => false,
  489. );
  490. // Does the file exist, is it different and can we overwrite?
  491. if (file_exists($boarddir . '/' . $file['filename']))
  492. {
  493. if (is_writable($boarddir . '/' . $file['filename']))
  494. $context_data['writable'] = true;
  495. // Finally, do we actually think the content has changed?
  496. if ($file['size'] == filesize($boarddir . '/' . $file['filename']) && $file['md5'] == md5_file($boarddir . '/' . $file['filename']))
  497. {
  498. $context_data['exists'] = 'same';
  499. $context_data['default_copy'] = false;
  500. }
  501. // Attempt to discover newline character differences.
  502. elseif ($file['md5'] == md5(preg_replace("~[\r]?\n~", "\r\n", file_get_contents($boarddir . '/' . $file['filename']))))
  503. {
  504. $context_data['exists'] = 'same';
  505. $context_data['default_copy'] = false;
  506. }
  507. else
  508. $context_data['exists'] = 'different';
  509. }
  510. // No overwrite?
  511. else
  512. {
  513. // Can we at least stick it in the directory...
  514. if (is_writable($boarddir . '/' . $dirname))
  515. $context_data['writable'] = true;
  516. }
  517. // 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...
  518. if ($extension == 'php' && preg_match('~\w+\.\w+\.php~', $filename))
  519. {
  520. $context_data += array(
  521. 'version' => '??',
  522. 'cur_version' => false,
  523. 'version_compare' => 'newer',
  524. );
  525. list ($name, $language) = explode('.', $filename);
  526. // Let's get the new version, I like versions, they tell me that I'm up to date.
  527. if (preg_match('~\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '~i', $file['preview'], $match) == 1)
  528. $context_data['version'] = $match[1];
  529. // Now does the old file exist - if so what is it's version?
  530. if (file_exists($boarddir . '/' . $file['filename']))
  531. {
  532. // OK - what is the current version?
  533. $fp = fopen($boarddir . '/' . $file['filename'], 'rb');
  534. $header = fread($fp, 768);
  535. fclose($fp);
  536. // Find the version.
  537. if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1)
  538. {
  539. $context_data['cur_version'] = $match[1];
  540. // How does this compare?
  541. if ($context_data['cur_version'] == $context_data['version'])
  542. $context_data['version_compare'] = 'same';
  543. elseif ($context_data['cur_version'] > $context_data['version'])
  544. $context_data['version_compare'] = 'older';
  545. // Don't recommend copying if the version is the same.
  546. if ($context_data['version_compare'] != 'newer')
  547. $context_data['default_copy'] = false;
  548. }
  549. }
  550. // Add the context data to the main set.
  551. $context['files']['lang'][] = $context_data;
  552. }
  553. else
  554. {
  555. // If we think it's a theme thing work out what the theme is.
  556. if (substr($dirname, 0, 6) == 'Themes' && preg_match('~Themes[\\/]([^\\/]+)[\\/]~', $dirname, $match))
  557. $theme_name = $match[1];
  558. else
  559. $theme_name = 'misc';
  560. // Assume it's an image, could be an acceptance note etc but rare.
  561. $context['files']['images'][$theme_name][] = $context_data;
  562. }
  563. // Collect together all non-writable areas.
  564. if (!$context_data['writable'])
  565. $context['make_writable'][] = $context_data['destination'];
  566. }
  567. // So, I'm a perfectionist - let's get the theme names.
  568. $theme_indexes = array();
  569. foreach ($context['files']['images'] as $k => $dummy)
  570. {
  571. $indexes[] = $k;
  572. }
  573. $context['theme_names'] = array();
  574. if (!empty($indexes))
  575. {
  576. $value_data = array(
  577. 'query' => array(),
  578. 'params' => array(),
  579. );
  580. foreach ($indexes as $k => $index)
  581. {
  582. $value_data['query'][] = 'value LIKE {string:value_' . $k . '}';
  583. $value_data['params']['value_' . $k] = '%' . $index;
  584. }
  585. $request = $smcFunc['db_query']('', '
  586. SELECT id_theme, value
  587. FROM {db_prefix}themes
  588. WHERE id_member = {int:no_member}
  589. AND variable = {string:theme_dir}
  590. AND (' . implode(' OR ', $value_data['query']) . ')',
  591. array_merge($value_data['params'], array(
  592. 'no_member' => 0,
  593. 'theme_dir' => 'theme_dir',
  594. 'index_compare_explode' => 'value LIKE \'%' . implode('\' OR value LIKE \'%', $indexes) . '\'',
  595. ))
  596. );
  597. $themes = array();
  598. while ($row = $smcFunc['db_fetch_assoc']($request))
  599. {
  600. // Find the right one.
  601. foreach ($indexes as $index)
  602. if (strpos($row['value'], $index) !== false)
  603. $themes[$row['id_theme']] = $index;
  604. }
  605. $smcFunc['db_free_result']($request);
  606. if (!empty($themes))
  607. {
  608. // Now we have the id_theme we can get the pretty description.
  609. $request = $smcFunc['db_query']('', '
  610. SELECT id_theme, value
  611. FROM {db_prefix}themes
  612. WHERE id_member = {int:no_member}
  613. AND variable = {string:name}
  614. AND id_theme IN ({array_int:theme_list})',
  615. array(
  616. 'theme_list' => array_keys($themes),
  617. 'no_member' => 0,
  618. 'name' => 'name',
  619. )
  620. );
  621. while ($row = $smcFunc['db_fetch_assoc']($request))
  622. {
  623. // Now we have it...
  624. $context['theme_names'][$themes[$row['id_theme']]] = $row['value'];
  625. }
  626. $smcFunc['db_free_result']($request);
  627. }
  628. }
  629. // Before we go to far can we make anything writable, eh, eh?
  630. if (!empty($context['make_writable']))
  631. {
  632. // What is left to be made writable?
  633. $file_status = create_chmod_control($context['make_writable']);
  634. $context['still_not_writable'] = $file_status['files']['notwritable'];
  635. // Mark those which are now writable as such.
  636. foreach ($context['files'] as $type => $data)
  637. {
  638. if ($type == 'lang')
  639. {
  640. foreach ($data as $k => $file)
  641. if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable']))
  642. $context['files'][$type][$k]['writable'] = true;
  643. }
  644. else
  645. {
  646. foreach ($data as $theme => $files)
  647. foreach ($files as $k => $file)
  648. if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable']))
  649. $context['files'][$type][$theme][$k]['writable'] = true;
  650. }
  651. }
  652. // Are we going to need more language stuff?
  653. if (!empty($context['still_not_writable']))
  654. loadLanguage('Packages');
  655. }
  656. // This is the list for the main files.
  657. $listOptions = array(
  658. 'id' => 'lang_main_files_list',
  659. 'title' => $txt['languages_download_main_files'],
  660. 'get_items' => array(
  661. 'function' => create_function('', '
  662. global $context;
  663. return $context[\'files\'][\'lang\'];
  664. '),
  665. ),
  666. 'columns' => array(
  667. 'name' => array(
  668. 'header' => array(
  669. 'value' => $txt['languages_download_filename'],
  670. ),
  671. 'data' => array(
  672. 'function' => create_function('$rowData', '
  673. global $context, $txt;
  674. 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\'] : \'\');
  675. '),
  676. ),
  677. ),
  678. 'writable' => array(
  679. 'header' => array(
  680. 'value' => $txt['languages_download_writable'],
  681. ),
  682. 'data' => array(
  683. 'function' => create_function('$rowData', '
  684. global $txt;
  685. return \'<span style="color: \' . ($rowData[\'writable\'] ? \'green\' : \'red\') . \';">\' . ($rowData[\'writable\'] ? $txt[\'yes\'] : $txt[\'no\']) . \'</span>\';
  686. '),
  687. 'style' => 'text-align: center',
  688. ),
  689. ),
  690. 'version' => array(
  691. 'header' => array(
  692. 'value' => $txt['languages_download_version'],
  693. ),
  694. 'data' => array(
  695. 'function' => create_function('$rowData', '
  696. global $txt;
  697. return \'<span style="color: \' . ($rowData[\'version_compare\'] == \'older\' ? \'red\' : ($rowData[\'version_compare\'] == \'same\' ? \'orange\' : \'green\')) . \';">\' . $rowData[\'version\'] . \'</span>\';
  698. '),
  699. ),
  700. ),
  701. 'exists' => array(
  702. 'header' => array(
  703. 'value' => $txt['languages_download_exists'],
  704. ),
  705. 'data' => array(
  706. 'function' => create_function('$rowData', '
  707. global $txt;
  708. return $rowData[\'exists\'] ? ($rowData[\'exists\'] == \'same\' ? $txt[\'languages_download_exists_same\'] : $txt[\'languages_download_exists_different\']) : $txt[\'no\'];
  709. '),
  710. ),
  711. ),
  712. 'copy' => array(
  713. 'header' => array(
  714. 'value' => $txt['languages_download_copy'],
  715. ),
  716. 'data' => array(
  717. 'function' => create_function('$rowData', '
  718. return \'<input type="checkbox" name="copy_file[]" value="\' . $rowData[\'generaldest\'] . \'" \' . ($rowData[\'default_copy\'] ? \'checked="checked"\' : \'\') . \' class="input_check" />\';
  719. '),
  720. 'style' => 'text-align: center; width: 4%;',
  721. ),
  722. ),
  723. ),
  724. );
  725. // Kill the cache, as it is now invalid..
  726. if (!empty($modSettings['cache_enable']))
  727. cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
  728. require_once($sourcedir . '/Subs-List.php');
  729. createList($listOptions);
  730. $context['default_list'] = 'lang_main_files_list';
  731. }
  732. // This is the main function for the language area.
  733. function ManageLanguages()
  734. {
  735. global $context, $txt, $scripturl, $modSettings;
  736. loadLanguage('ManageSettings');
  737. $context['page_title'] = $txt['edit_languages'];
  738. $context['sub_template'] = 'show_settings';
  739. $subActions = array(
  740. 'edit' => 'ModifyLanguages',
  741. 'add' => 'AddLanguage',
  742. 'settings' => 'ModifyLanguageSettings',
  743. 'downloadlang' => 'DownloadLanguage',
  744. 'editlang' => 'ModifyLanguage',
  745. );
  746. // By default we're managing languages.
  747. $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'edit';
  748. $context['sub_action'] = $_REQUEST['sa'];
  749. // Load up all the tabs...
  750. $context[$context['admin_menu_name']]['tab_data'] = array(
  751. 'title' => $txt['language_configuration'],
  752. 'description' => $txt['language_description'],
  753. );
  754. // Call the right function for this sub-acton.
  755. $subActions[$_REQUEST['sa']]();
  756. }
  757. // This lists all the current languages and allows editing of them.
  758. function ModifyLanguages()
  759. {
  760. global $txt, $context, $scripturl;
  761. global $user_info, $smcFunc, $sourcedir, $language, $boarddir, $forum_version;
  762. // Setting a new default?
  763. if (!empty($_POST['set_default']) && !empty($_POST['def_language']))
  764. {
  765. if ($_POST['def_language'] != $language)
  766. {
  767. require_once($sourcedir . '/Subs-Admin.php');
  768. updateSettingsFile(array('language' => '\'' . $_POST['def_language'] . '\''));
  769. $language = $_POST['def_language'];
  770. }
  771. }
  772. $listOptions = array(
  773. 'id' => 'language_list',
  774. 'items_per_page' => 20,
  775. 'base_href' => $scripturl . '?action=admin;area=languages;' . $context['session_var'] . '=' . $context['session_id'],
  776. 'title' => $txt['edit_languages'],
  777. 'get_items' => array(
  778. 'function' => 'list_getLanguages',
  779. ),
  780. 'get_count' => array(
  781. 'function' => 'list_getNumLanguages',
  782. ),
  783. 'columns' => array(
  784. 'default' => array(
  785. 'header' => array(
  786. 'value' => $txt['languages_default'],
  787. ),
  788. 'data' => array(
  789. 'function' => create_function('$rowData', '
  790. return \'<input type="radio" name="def_language" value="\' . $rowData[\'id\'] . \'" \' . ($rowData[\'default\'] ? \'checked="checked"\' : \'\') . \' onclick="highlightSelected(\\\'list_language_list_\' . $rowData[\'id\'] . \'\\\');" class="input_radio" />\';
  791. '),
  792. 'style' => 'text-align: center; width: 4%;',
  793. ),
  794. ),
  795. 'name' => array(
  796. 'header' => array(
  797. 'value' => $txt['languages_lang_name'],
  798. ),
  799. 'data' => array(
  800. 'function' => create_function('$rowData', '
  801. global $scripturl, $context;
  802. return sprintf(\'<a href="%1$s?action=admin;area=languages;sa=editlang;lid=%2$s;%3$s=%4$s">%5$s</a>\', $scripturl, $rowData[\'id\'], $context[\'session_var\'], $context[\'session_id\'], $rowData[\'name\']);
  803. '),
  804. ),
  805. ),
  806. 'character_set' => array(
  807. 'header' => array(
  808. 'value' => $txt['languages_character_set'],
  809. ),
  810. 'data' => array(
  811. 'db_htmlsafe' => 'char_set',
  812. ),
  813. ),
  814. 'count' => array(
  815. 'header' => array(
  816. 'value' => $txt['languages_users'],
  817. ),
  818. 'data' => array(
  819. 'db_htmlsafe' => 'count',
  820. 'style' => 'text-align: center',
  821. ),
  822. ),
  823. 'locale' => array(
  824. 'header' => array(
  825. 'value' => $txt['languages_locale'],
  826. ),
  827. 'data' => array(
  828. 'db_htmlsafe' => 'locale',
  829. ),
  830. ),
  831. ),
  832. 'form' => array(
  833. 'href' => $scripturl . '?action=admin;area=languages;' . $context['session_var'] . '=' . $context['session_id'],
  834. ),
  835. 'additional_rows' => array(
  836. array(
  837. 'position' => 'below_table_data',
  838. 'value' => '<input type="submit" name="set_default" value="' . $txt['save'] . '"' . (is_writable($boarddir . '/Settings.php') ? '' : ' disabled="disabled"') . ' class="button_submit" />',
  839. 'class' => 'titlebg',
  840. 'style' => 'text-align: right;',
  841. ),
  842. ),
  843. // For highlighting the default.
  844. 'javascript' => '
  845. var prevClass = "";
  846. var prevDiv = "";
  847. function highlightSelected(box)
  848. {
  849. if (prevClass != "")
  850. {
  851. prevDiv.className = prevClass;
  852. }
  853. prevDiv = document.getElementById(box);
  854. prevClass = prevDiv.className;
  855. prevDiv.className = "highlight2";
  856. }
  857. highlightSelected("list_language_list_' . ($language == '' ? 'english' : $language). '");
  858. ',
  859. );
  860. // Display a warning if we cannot edit the default setting.
  861. if (!is_writable($boarddir . '/Settings.php'))
  862. $listOptions['additional_rows'][] = array(
  863. 'position' => 'after_title',
  864. 'value' => '<span class="smalltext alert">' . $txt['language_settings_writable'] . '</span>',
  865. 'class' => 'windowbg',
  866. );
  867. require_once($sourcedir . '/Subs-List.php');
  868. createList($listOptions);
  869. $context['sub_template'] = 'show_list';
  870. $context['default_list'] = 'language_list';
  871. }
  872. // How many languages?
  873. function list_getNumLanguages()
  874. {
  875. global $settings;
  876. // Return how many we have.
  877. return count(getLanguages());
  878. }
  879. // Fetch the actual language information.
  880. function list_getLanguages()
  881. {
  882. global $settings, $smcFunc, $language, $context, $txt;
  883. $languages = array();
  884. // Keep our old entries.
  885. $old_txt = $txt;
  886. $backup_actual_theme_dir = $settings['actual_theme_dir'];
  887. $backup_base_theme_dir = !empty($settings['base_theme_dir']) ? $settings['base_theme_dir'] : '';
  888. // Override these for now.
  889. $settings['actual_theme_dir'] = $settings['base_theme_dir'] = $settings['default_theme_dir'];
  890. getLanguages(true, false);
  891. // Put them back.
  892. $settings['actual_theme_dir'] = $backup_actual_theme_dir;
  893. if (!empty($backup_base_theme_dir))
  894. $settings['base_theme_dir'] = $backup_base_theme_dir;
  895. // Get the language files and data...
  896. foreach ($context['languages'] as $lang)
  897. {
  898. // Load the file to get the character set.
  899. require_once($settings['default_theme_dir'] . '/languages/index.' . $lang['filename'] . '.php');
  900. $languages[$lang['filename']] = array(
  901. 'id' => $lang['filename'],
  902. 'count' => 0,
  903. 'char_set' => $txt['lang_character_set'],
  904. 'default' => $language == $lang['filename'] || ($language == '' && $lang['filename'] == 'english'),
  905. 'locale' => $txt['lang_locale'],
  906. 'name' => $smcFunc['ucwords'](strtr($lang['filename'], array('_' => ' ', '-utf8' => ''))),
  907. );
  908. }
  909. // Work out how many people are using each language.
  910. $request = $smcFunc['db_query']('', '
  911. SELECT lngfile, COUNT(*) AS num_users
  912. FROM {db_prefix}members
  913. GROUP BY lngfile',
  914. array(
  915. )
  916. );
  917. while ($row = $smcFunc['db_fetch_assoc']($request))
  918. {
  919. // Default?
  920. if (empty($row['lngfile']) || !isset($languages[$row['lngfile']]))
  921. $row['lngfile'] = $language;
  922. if (!isset($languages[$row['lngfile']]) && isset($languages['english']))
  923. $languages['english']['count'] += $row['num_users'];
  924. elseif (isset($languages[$row['lngfile']]))
  925. $languages[$row['lngfile']]['count'] += $row['num_users'];
  926. }
  927. $smcFunc['db_free_result']($request);
  928. // Restore the current users language.
  929. $txt = $old_txt;
  930. // Return how many we have.
  931. return $languages;
  932. }
  933. // Edit language related settings.
  934. function ModifyLanguageSettings($return_config = false)
  935. {
  936. global $scripturl, $context, $txt, $boarddir, $settings, $smcFunc;
  937. // Warn the user if the backup of Settings.php failed.
  938. $settings_not_writable = !is_writable($boarddir . '/Settings.php');
  939. $settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php');
  940. /* If you're writing a mod, it's a bad idea to add things here....
  941. For each option:
  942. variable name, description, type (constant), size/possible values, helptext.
  943. OR an empty string for a horizontal rule.
  944. OR a string for a titled section. */
  945. $config_vars = array(
  946. 'language' => array('language', $txt['default_language'], 'file', 'select', array(), null, 'disabled' => $settings_not_writable),
  947. array('userLanguage', $txt['userLanguage'], 'db', 'check', null, 'userLanguage'),
  948. );
  949. if ($return_config)
  950. return $config_vars;
  951. // Get our languages. No cache and use utf8.
  952. getLanguages(false, false);
  953. foreach ($context['languages'] as $lang)
  954. $config_vars['language'][4][$lang['filename']] = array($lang['filename'], $lang['name']);
  955. // Saving settings?
  956. if (isset($_REQUEST['save']))
  957. {
  958. saveSettings($config_vars);
  959. redirectexit('action=admin;area=languages;sa=settings;' . $context['session_var'] . '=' . $context['session_id']);
  960. }
  961. // Setup the template stuff.
  962. $context['post_url'] = $scripturl . '?action=admin;area=languages;sa=settings;save';
  963. $context['settings_title'] = $txt['language_settings'];
  964. $context['save_disabled'] = $settings_not_writable;
  965. if ($settings_not_writable)
  966. $context['settings_message'] = '<div class="centertext"><strong>' . $txt['settings_not_writable'] . '</strong></div><br />';
  967. elseif ($settings_backup_fail)
  968. $context['settings_message'] = '<div class="centertext"><strong>' . $txt['admin_backup_fail'] . '</strong></div><br />';
  969. // Fill the config array.
  970. prepareServerSettingsContext($config_vars);
  971. }
  972. // Edit a particular set of language entries.
  973. function ModifyLanguage()
  974. {
  975. global $settings, $context, $smcFunc, $txt, $modSettings, $boarddir, $sourcedir, $language;
  976. loadLanguage('ManageSettings');
  977. // Select the languages tab.
  978. $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit';
  979. $context['page_title'] = $txt['edit_languages'];
  980. $context['sub_template'] = 'modify_language_entries';
  981. $context['lang_id'] = $_GET['lid'];
  982. list($theme_id, $file_id) = empty($_REQUEST['tfid']) || strpos($_REQUEST['tfid'], '+') === false ? array(1, '') : explode('+', $_REQUEST['tfid']);
  983. // Clean the ID - just in case.
  984. preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches);
  985. $context['lang_id'] = $matches[1];
  986. // Get all the theme data.
  987. $request = $smcFunc['db_query']('', '
  988. SELECT id_theme, variable, value
  989. FROM {db_prefix}themes
  990. WHERE id_theme != {int:default_theme}
  991. AND id_member = {int:no_member}
  992. AND variable IN ({string:name}, {string:theme_dir})',
  993. array(
  994. 'default_theme' => 1,
  995. 'no_member' => 0,
  996. 'name' => 'name',
  997. 'theme_dir' => 'theme_dir',
  998. )
  999. );
  1000. $themes = array(
  1001. 1 => array(
  1002. 'name' => $txt['dvc_default'],
  1003. 'theme_dir' => $settings['default_theme_dir'],
  1004. ),
  1005. );
  1006. while ($row = $smcFunc['db_fetch_assoc']($request))
  1007. $themes[$row['id_theme']][$row['variable']] = $row['value'];
  1008. $smcFunc['db_free_result']($request);
  1009. // This will be where we look
  1010. $lang_dirs = array();
  1011. // Check we have themes with a path and a name - just in case - and add the path.
  1012. foreach ($themes as $id => $data)
  1013. {
  1014. if (count($data) != 2)
  1015. unset($themes[$id]);
  1016. elseif (is_dir($data['theme_dir'] . '/languages'))
  1017. $lang_dirs[$id] = $data['theme_dir'] . '/languages';
  1018. // How about image directories?
  1019. if (is_dir($data['theme_dir'] . '/images/' . $context['lang_id']))
  1020. $images_dirs[$id] = $data['theme_dir'] . '/images/' . $context['lang_id'];
  1021. }
  1022. $current_file = $file_id ? $lang_dirs[$theme_id] . '/' . $file_id . '.' . $context['lang_id'] . '.php' : '';
  1023. // Now for every theme get all the files and stick them in context!
  1024. $context['possible_files'] = array();
  1025. foreach ($lang_dirs as $theme => $theme_dir)
  1026. {
  1027. // Open it up.
  1028. $dir = dir($theme_dir);
  1029. while ($entry = $dir->read())
  1030. {
  1031. // We're only after the files for this language.
  1032. if (preg_match('~^([A-Za-z]+)\.' . $context['lang_id'] . '\.php$~', $entry, $matches) == 0)
  1033. continue;
  1034. //!!! Temp!
  1035. if ($matches[1] == 'EmailTemplates')
  1036. continue;
  1037. if (!isset($context['possible_files'][$theme]))
  1038. $context['possible_files'][$theme] = array(
  1039. 'id' => $theme,
  1040. 'name' => $themes[$theme]['name'],
  1041. 'files' => array(),
  1042. );
  1043. $context['possible_files'][$theme]['files'][] = array(
  1044. 'id' => $matches[1],
  1045. 'name' => isset($txt['lang_file_desc_' . $matches[1]]) ? $txt['lang_file_desc_' . $matches[1]] : $matches[1],
  1046. 'selected' => $theme_id == $theme && $file_id == $matches[1],
  1047. );
  1048. }
  1049. $dir->close();
  1050. }
  1051. // We no longer wish to speak this language.
  1052. if (!empty($_POST['delete_main']) && $context['lang_id'] != 'english')
  1053. {
  1054. // !!! Todo: FTP Controls?
  1055. require_once($sourcedir . '/Subs-Package.php');
  1056. // First, Make a backup?
  1057. if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['lang_id'] .'$$$'))
  1058. {
  1059. $_SESSION['last_backup_for'] = $context['lang_id'] .'$$$';
  1060. package_create_backup('backup_lang_' . $context['lang_id']);
  1061. }
  1062. // Second, loop through the array to remove the files.
  1063. foreach ($lang_dirs as $curPath)
  1064. {
  1065. foreach ($context['possible_files'][1]['files'] as $lang)
  1066. if (file_exists($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php'))
  1067. unlink($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php');
  1068. // Check for the email template.
  1069. if (file_exists($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php'))
  1070. unlink($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php');
  1071. }
  1072. // Third, the agreement file.
  1073. if (file_exists($boarddir . '/agreement.' . $context['lang_id'] . '.txt'))
  1074. unlink($boarddir . '/agreement.' . $context['lang_id'] . '.txt');
  1075. // Fourth, a related images folder?
  1076. foreach ($images_dirs as $curPath)
  1077. if (is_dir($curPath))
  1078. deltree($curPath);
  1079. // Fifth, update getLanguages() cache.
  1080. if (!empty($modSettings['cache_enable']))
  1081. cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
  1082. // Sixth, if we deleted the default language, set us back to english?
  1083. if ($context['lang_id'] == $language)
  1084. {
  1085. require_once($sourcedir . '/Subs-Admin.php');
  1086. $language = 'english';
  1087. updateSettingsFile(array('language' => '\'' . $language . '\''));
  1088. }
  1089. // Seventh, get out of here.
  1090. redirectexit('action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id']);
  1091. }
  1092. // Saving primary settings?
  1093. $madeSave = false;
  1094. if (!empty($_POST['save_main']) && !$current_file)
  1095. {
  1096. // Read in the current file.
  1097. $current_data = implode('', file($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php'));
  1098. // These are the replacements. old => new
  1099. $replace_array = array(
  1100. '~\$txt\[\'lang_character_set\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_character_set\'] = \'' . addslashes($_POST['character_set']) . '\';',
  1101. '~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . addslashes($_POST['locale']) . '\';',
  1102. '~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . addslashes($_POST['dictionary']) . '\';',
  1103. '~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . addslashes($_POST['spelling']) . '\';',
  1104. '~\$txt\[\'lang_rtl\'\]\s=\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($_POST['rtl']) ? 'true' : 'false') . ';',
  1105. );
  1106. $current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data);
  1107. $fp = fopen($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php', 'w+');
  1108. fwrite($fp, $current_data);
  1109. fclose($fp);
  1110. $madeSave = true;
  1111. }
  1112. // Quickly load index language entries.
  1113. $old_txt = $txt;
  1114. require($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php');
  1115. $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');
  1116. // Setup the primary settings context.
  1117. $context['primary_settings'] = array(
  1118. 'name' => $smcFunc['ucwords'](strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))),
  1119. 'character_set' => $txt['lang_character_set'],
  1120. 'locale' => $txt['lang_locale'],
  1121. 'dictionary' => $txt['lang_dictionary'],
  1122. 'spelling' => $txt['lang_spelling'],
  1123. 'rtl' => $txt['lang_rtl'],
  1124. );
  1125. // Restore normal service.
  1126. $txt = $old_txt;
  1127. // Are we saving?
  1128. $save_strings = array();
  1129. if (isset($_POST['save_entries']) && !empty($_POST['entry']))
  1130. {
  1131. // Clean each entry!
  1132. foreach ($_POST['entry'] as $k => $v)
  1133. {
  1134. // Only try to save if it's changed!
  1135. if ($_POST['entry'][$k] != $_POST['comp'][$k])
  1136. $save_strings[$k] = cleanLangString($v, false);
  1137. }
  1138. }
  1139. // If we are editing a file work away at that.
  1140. if ($current_file)
  1141. {
  1142. $context['entries_not_writable_message'] = is_writable($current_file) ? '' : sprintf($txt['lang_entries_not_writable'], $current_file);
  1143. $entries = array();
  1144. // We can't just require it I'm afraid - otherwise we pass in all kinds of variables!
  1145. $multiline_cache = '';
  1146. foreach (file($current_file) as $line)
  1147. {
  1148. // Got a new entry?
  1149. if ($line{0} == '$' && !empty($multiline_cache))
  1150. {
  1151. preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s=\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
  1152. if (!empty($matches[3]))
  1153. {
  1154. $entries[$matches[2]] = array(
  1155. 'type' => $matches[1],
  1156. 'full' => $matches[0],
  1157. 'entry' => $matches[3],
  1158. );
  1159. $multiline_cache = '';
  1160. }
  1161. }
  1162. $multiline_cache .= $line . "\n";
  1163. }
  1164. // Last entry to add?
  1165. if ($multiline_cache)
  1166. {
  1167. preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s=\s(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
  1168. if (!empty($matches[3]))
  1169. $entries[$matches[2]] = array(
  1170. 'type' => $matches[1],
  1171. 'full' => $matches[0],
  1172. 'entry' => $matches[3],
  1173. );
  1174. }
  1175. // These are the entries we can definitely save.
  1176. $final_saves = array();
  1177. $context['file_entries'] = array();
  1178. foreach ($entries as $entryKey => $entryValue)
  1179. {
  1180. // Ignore some things we set separately.
  1181. $ignore_files = array('lang_character_set', 'lang_locale', 'lang_dictionary', 'lang_spelling', 'lang_rtl');
  1182. if (in_array($entryKey, $ignore_files))
  1183. continue;
  1184. // These are arrays that need breaking out.
  1185. $arrays = array('days', 'days_short', 'months', 'months_titles', 'months_short');
  1186. if (in_array($entryKey, $arrays))
  1187. {
  1188. // Get off the first bits.
  1189. $entryValue['entry'] = substr($entryValue['entry'], strpos($entryValue['entry'], '(') + 1, strrpos($entryValue['entry'], ')') - strpos($entryValue['entry'], '('));
  1190. $entryValue['entry'] = explode(',', strtr($entryValue['entry'], array(' ' => '')));
  1191. // Now create an entry for each item.
  1192. $cur_index = 0;
  1193. $save_cache = array(
  1194. 'enabled' => false,
  1195. 'entries' => array(),
  1196. );
  1197. foreach ($entryValue['entry'] as $id => $subValue)
  1198. {
  1199. // Is this a new index?
  1200. if (preg_match('~^(\d+)~', $subValue, $matches))
  1201. {
  1202. $cur_index = $matches[1];
  1203. $subValue = substr($subValue, strpos($subValue, '\''));
  1204. }
  1205. // Clean up some bits.
  1206. $subValue = strtr($subValue, array('"' => '', '\'' => '', ')' => ''));
  1207. // Can we save?
  1208. if (isset($save_strings[$entryKey . '-+- ' . $cur_index]))
  1209. {
  1210. $save_cache['entries'][$cur_index] = strtr($save_strings[$entryKey . '-+- ' . $cur_index], array

Large files files are truncated, but you can click here to view the full file