Yoshi2889 /
SMF2.1
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This file handles the administration of languages tasks. |
||
| 5 | * |
||
| 6 | * Simple Machines Forum (SMF) |
||
| 7 | * |
||
| 8 | * @package SMF |
||
| 9 | * @author Simple Machines http://www.simplemachines.org |
||
| 10 | * @copyright 2017 Simple Machines and individual contributors |
||
| 11 | * @license http://www.simplemachines.org/about/smf/license.php BSD |
||
| 12 | * |
||
| 13 | * @version 2.1 Beta 4 |
||
| 14 | */ |
||
| 15 | |||
| 16 | if (!defined('SMF')) |
||
| 17 | die('No direct access...'); |
||
| 18 | |||
| 19 | /** |
||
| 20 | * This is the main function for the languages area. |
||
| 21 | * It dispatches the requests. |
||
| 22 | * Loads the ManageLanguages template. (sub-actions will use it) |
||
| 23 | * @todo lazy loading. |
||
| 24 | * |
||
| 25 | * @uses ManageSettings language file |
||
| 26 | */ |
||
| 27 | View Code Duplication | function ManageLanguages() |
|
| 28 | { |
||
| 29 | global $context, $txt; |
||
| 30 | |||
| 31 | loadTemplate('ManageLanguages'); |
||
| 32 | loadLanguage('ManageSettings'); |
||
| 33 | |||
| 34 | $context['page_title'] = $txt['edit_languages']; |
||
| 35 | $context['sub_template'] = 'show_settings'; |
||
| 36 | |||
| 37 | $subActions = array( |
||
| 38 | 'edit' => 'ModifyLanguages', |
||
| 39 | 'add' => 'AddLanguage', |
||
| 40 | 'settings' => 'ModifyLanguageSettings', |
||
| 41 | 'downloadlang' => 'DownloadLanguage', |
||
| 42 | 'editlang' => 'ModifyLanguage', |
||
| 43 | ); |
||
| 44 | |||
| 45 | // By default we're managing languages. |
||
| 46 | $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'edit'; |
||
| 47 | $context['sub_action'] = $_REQUEST['sa']; |
||
| 48 | |||
| 49 | // Load up all the tabs... |
||
| 50 | $context[$context['admin_menu_name']]['tab_data'] = array( |
||
| 51 | 'title' => $txt['language_configuration'], |
||
| 52 | 'description' => $txt['language_description'], |
||
| 53 | ); |
||
| 54 | |||
| 55 | call_integration_hook('integrate_manage_languages', array(&$subActions)); |
||
| 56 | |||
| 57 | // Call the right function for this sub-action. |
||
| 58 | call_helper($subActions[$_REQUEST['sa']]); |
||
| 59 | } |
||
| 60 | |||
| 61 | /** |
||
| 62 | * Interface for adding a new language |
||
| 63 | * |
||
| 64 | * @uses ManageLanguages template, add_language sub-template. |
||
| 65 | */ |
||
| 66 | function AddLanguage() |
||
| 67 | { |
||
| 68 | global $context, $sourcedir, $txt, $smcFunc; |
||
| 69 | |||
| 70 | // Are we searching for new languages courtesy of Simple Machines? |
||
| 71 | if (!empty($_POST['smf_add_sub'])) |
||
| 72 | { |
||
| 73 | // Need fetch_web_data. |
||
| 74 | require_once($sourcedir . '/Subs-Package.php'); |
||
| 75 | |||
| 76 | $context['smf_search_term'] = $smcFunc['htmlspecialchars'](trim($_POST['smf_add'])); |
||
| 77 | |||
| 78 | $listOptions = array( |
||
| 79 | 'id' => 'smf_languages', |
||
| 80 | 'get_items' => array( |
||
| 81 | 'function' => 'list_getLanguagesList', |
||
| 82 | ), |
||
| 83 | 'columns' => array( |
||
| 84 | 'name' => array( |
||
| 85 | 'header' => array( |
||
| 86 | 'value' => $txt['name'], |
||
| 87 | ), |
||
| 88 | 'data' => array( |
||
| 89 | 'db' => 'name', |
||
| 90 | ), |
||
| 91 | ), |
||
| 92 | 'description' => array( |
||
| 93 | 'header' => array( |
||
| 94 | 'value' => $txt['add_language_smf_desc'], |
||
| 95 | ), |
||
| 96 | 'data' => array( |
||
| 97 | 'db' => 'description', |
||
| 98 | ), |
||
| 99 | ), |
||
| 100 | 'version' => array( |
||
| 101 | 'header' => array( |
||
| 102 | 'value' => $txt['add_language_smf_version'], |
||
| 103 | ), |
||
| 104 | 'data' => array( |
||
| 105 | 'db' => 'version', |
||
| 106 | ), |
||
| 107 | ), |
||
| 108 | 'utf8' => array( |
||
| 109 | 'header' => array( |
||
| 110 | 'value' => $txt['add_language_smf_utf8'], |
||
| 111 | ), |
||
| 112 | 'data' => array( |
||
| 113 | 'db' => 'utf8', |
||
| 114 | ), |
||
| 115 | ), |
||
| 116 | 'install_link' => array( |
||
| 117 | 'header' => array( |
||
| 118 | 'value' => $txt['add_language_smf_install'], |
||
| 119 | 'class' => 'centercol', |
||
| 120 | ), |
||
| 121 | 'data' => array( |
||
| 122 | 'db' => 'install_link', |
||
| 123 | 'class' => 'centercol', |
||
| 124 | ), |
||
| 125 | ), |
||
| 126 | ), |
||
| 127 | ); |
||
| 128 | |||
| 129 | require_once($sourcedir . '/Subs-List.php'); |
||
| 130 | createList($listOptions); |
||
| 131 | |||
| 132 | $context['default_list'] = 'smf_languages'; |
||
| 133 | } |
||
| 134 | |||
| 135 | $context['sub_template'] = 'add_language'; |
||
| 136 | } |
||
| 137 | |||
| 138 | /** |
||
| 139 | * Gets a list of available languages from the mother ship |
||
| 140 | * Will return a subset if searching, otherwise all avaialble |
||
| 141 | * |
||
| 142 | * @return array An array containing information about each available language |
||
| 143 | */ |
||
| 144 | function list_getLanguagesList() |
||
| 145 | { |
||
| 146 | global $forum_version, $context, $sourcedir, $smcFunc, $txt, $scripturl; |
||
| 147 | |||
| 148 | // We're going to use this URL. |
||
| 149 | $url = 'https://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))); |
||
| 150 | |||
| 151 | // Load the class file and stick it into an array. |
||
| 152 | require_once($sourcedir . '/Class-Package.php'); |
||
| 153 | $language_list = new xmlArray(fetch_web_data($url), true); |
||
|
0 ignored issues
–
show
Security
Bug
introduced
by
Loading history...
|
|||
| 154 | |||
| 155 | // Check that the site responded and that the language exists. |
||
| 156 | if (!$language_list->exists('languages')) |
||
| 157 | $context['smf_error'] = 'no_response'; |
||
| 158 | elseif (!$language_list->exists('languages/language')) |
||
| 159 | $context['smf_error'] = 'no_files'; |
||
| 160 | else |
||
| 161 | { |
||
| 162 | $language_list = $language_list->path('languages[0]'); |
||
| 163 | $lang_files = $language_list->set('language'); |
||
| 164 | $smf_languages = array(); |
||
| 165 | foreach ($lang_files as $file) |
||
| 166 | { |
||
| 167 | // Were we searching? |
||
| 168 | if (!empty($context['smf_search_term']) && strpos($file->fetch('name'), $smcFunc['strtolower']($context['smf_search_term'])) === false) |
||
| 169 | continue; |
||
| 170 | |||
| 171 | $smf_languages[] = array( |
||
| 172 | 'id' => $file->fetch('id'), |
||
| 173 | 'name' => $smcFunc['ucwords']($file->fetch('name')), |
||
| 174 | 'version' => $file->fetch('version'), |
||
| 175 | 'utf8' => $file->fetch('utf8') ? $txt['yes'] : $txt['no'], |
||
| 176 | 'description' => $file->fetch('description'), |
||
| 177 | 'install_link' => '<a href="' . $scripturl . '?action=admin;area=languages;sa=downloadlang;did=' . $file->fetch('id') . ';' . $context['session_var'] . '=' . $context['session_id'] . '">' . $txt['add_language_smf_install'] . '</a>', |
||
| 178 | ); |
||
| 179 | } |
||
| 180 | if (empty($smf_languages)) |
||
| 181 | $context['smf_error'] = 'no_files'; |
||
| 182 | else |
||
| 183 | return $smf_languages; |
||
| 184 | } |
||
| 185 | } |
||
| 186 | |||
| 187 | /** |
||
| 188 | * Download a language file from the Simple Machines website. |
||
| 189 | * Requires a valid download ID ("did") in the URL. |
||
| 190 | * Also handles installing language files. |
||
| 191 | * Attempts to chmod things as needed. |
||
| 192 | * Uses a standard list to display information about all the files and where they'll be put. |
||
| 193 | * |
||
| 194 | * @uses ManageLanguages template, download_language sub-template. |
||
| 195 | * @uses Admin template, show_list sub-template. |
||
| 196 | */ |
||
| 197 | function DownloadLanguage() |
||
| 198 | { |
||
| 199 | global $context, $sourcedir, $forum_version, $boarddir, $txt, $scripturl, $modSettings; |
||
| 200 | |||
| 201 | loadLanguage('ManageSettings'); |
||
| 202 | require_once($sourcedir . '/Subs-Package.php'); |
||
| 203 | |||
| 204 | // Clearly we need to know what to request. |
||
| 205 | if (!isset($_GET['did'])) |
||
| 206 | fatal_lang_error('no_access', false); |
||
| 207 | |||
| 208 | // Some lovely context. |
||
| 209 | $context['download_id'] = $_GET['did']; |
||
| 210 | $context['sub_template'] = 'download_language'; |
||
| 211 | $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'add'; |
||
| 212 | |||
| 213 | // Can we actually do the installation - and do they want to? |
||
| 214 | if (!empty($_POST['do_install']) && !empty($_POST['copy_file'])) |
||
| 215 | { |
||
| 216 | checkSession('get'); |
||
| 217 | validateToken('admin-dlang'); |
||
| 218 | |||
| 219 | $chmod_files = array(); |
||
| 220 | $install_files = array(); |
||
| 221 | |||
| 222 | // Check writable status. |
||
| 223 | foreach ($_POST['copy_file'] as $file) |
||
| 224 | { |
||
| 225 | // Check it's not very bad. |
||
| 226 | if (strpos($file, '..') !== false || (strpos($file, 'Themes') !== 0 && !preg_match('~agreement\.[A-Za-z-_0-9]+\.txt$~', $file))) |
||
| 227 | fatal_error($txt['languages_download_illegal_paths']); |
||
| 228 | |||
| 229 | $chmod_files[] = $boarddir . '/' . $file; |
||
| 230 | $install_files[] = $file; |
||
| 231 | } |
||
| 232 | |||
| 233 | // Call this in case we have work to do. |
||
| 234 | $file_status = create_chmod_control($chmod_files); |
||
| 235 | $files_left = $file_status['files']['notwritable']; |
||
| 236 | |||
| 237 | // Something not writable? |
||
| 238 | if (!empty($files_left)) |
||
| 239 | $context['error_message'] = $txt['languages_download_not_chmod']; |
||
| 240 | // Otherwise, go go go! |
||
| 241 | elseif (!empty($install_files)) |
||
| 242 | { |
||
| 243 | read_tgz_file('https://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))) . ';fetch=' . urlencode($_GET['did']), $boarddir, false, true, $install_files); |
||
| 244 | |||
| 245 | // Make sure the files aren't stuck in the cache. |
||
| 246 | package_flush_cache(); |
||
| 247 | $context['install_complete'] = sprintf($txt['languages_download_complete_desc'], $scripturl . '?action=admin;area=languages'); |
||
| 248 | |||
| 249 | return; |
||
| 250 | } |
||
| 251 | } |
||
| 252 | |||
| 253 | // Open up the old china. |
||
| 254 | if (!isset($archive_content)) |
||
|
0 ignored issues
–
show
The variable
$archive_content seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.
This check marks calls to This is likely the result of code being shifted around. Consider removing these calls. Loading history...
|
|||
| 255 | $archive_content = read_tgz_file('https://download.simplemachines.org/fetch_language.php?version=' . urlencode(strtr($forum_version, array('SMF ' => ''))) . ';fetch=' . urlencode($_GET['did']), null); |
||
| 256 | |||
| 257 | if (empty($archive_content)) |
||
| 258 | fatal_error($txt['add_language_error_no_response']); |
||
| 259 | |||
| 260 | // Now for each of the files, let's do some *stuff* |
||
| 261 | $context['files'] = array( |
||
| 262 | 'lang' => array(), |
||
| 263 | 'other' => array(), |
||
| 264 | ); |
||
| 265 | $context['make_writable'] = array(); |
||
| 266 | foreach ($archive_content as $file) |
||
| 267 | { |
||
| 268 | $dirname = dirname($file['filename']); |
||
| 269 | $filename = basename($file['filename']); |
||
| 270 | $extension = substr($filename, strrpos($filename, '.') + 1); |
||
| 271 | |||
| 272 | // Don't do anything with files we don't understand. |
||
| 273 | if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt'))) |
||
| 274 | continue; |
||
| 275 | |||
| 276 | // Basic data. |
||
| 277 | $context_data = array( |
||
| 278 | 'name' => $filename, |
||
| 279 | 'destination' => $boarddir . '/' . $file['filename'], |
||
| 280 | 'generaldest' => $file['filename'], |
||
| 281 | 'size' => $file['size'], |
||
| 282 | // Does chmod status allow the copy? |
||
| 283 | 'writable' => false, |
||
| 284 | // Should we suggest they copy this file? |
||
| 285 | 'default_copy' => true, |
||
| 286 | // Does the file already exist, if so is it same or different? |
||
| 287 | 'exists' => false, |
||
| 288 | ); |
||
| 289 | |||
| 290 | // Does the file exist, is it different and can we overwrite? |
||
| 291 | if (file_exists($boarddir . '/' . $file['filename'])) |
||
| 292 | { |
||
| 293 | if (is_writable($boarddir . '/' . $file['filename'])) |
||
| 294 | $context_data['writable'] = true; |
||
| 295 | |||
| 296 | // Finally, do we actually think the content has changed? |
||
| 297 | if ($file['size'] == filesize($boarddir . '/' . $file['filename']) && $file['md5'] == md5_file($boarddir . '/' . $file['filename'])) |
||
| 298 | { |
||
| 299 | $context_data['exists'] = 'same'; |
||
| 300 | $context_data['default_copy'] = false; |
||
| 301 | } |
||
| 302 | // Attempt to discover newline character differences. |
||
| 303 | elseif ($file['md5'] == md5(preg_replace("~[\r]?\n~", "\r\n", file_get_contents($boarddir . '/' . $file['filename'])))) |
||
| 304 | { |
||
| 305 | $context_data['exists'] = 'same'; |
||
| 306 | $context_data['default_copy'] = false; |
||
| 307 | } |
||
| 308 | else |
||
| 309 | $context_data['exists'] = 'different'; |
||
| 310 | } |
||
| 311 | // No overwrite? |
||
| 312 | else |
||
| 313 | { |
||
| 314 | // Can we at least stick it in the directory... |
||
| 315 | if (is_writable($boarddir . '/' . $dirname)) |
||
| 316 | $context_data['writable'] = true; |
||
| 317 | } |
||
| 318 | |||
| 319 | // 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... |
||
| 320 | if ($extension == 'php' && preg_match('~\w+\.\w+(?:-utf8)?\.php~', $filename)) |
||
| 321 | { |
||
| 322 | $context_data += array( |
||
| 323 | 'version' => '??', |
||
| 324 | 'cur_version' => false, |
||
| 325 | 'version_compare' => 'newer', |
||
| 326 | ); |
||
| 327 | |||
| 328 | list ($name, $language) = explode('.', $filename); |
||
| 329 | |||
| 330 | // Let's get the new version, I like versions, they tell me that I'm up to date. |
||
| 331 | if (preg_match('~\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '~i', $file['preview'], $match) == 1) |
||
| 332 | $context_data['version'] = $match[1]; |
||
| 333 | |||
| 334 | // Now does the old file exist - if so what is it's version? |
||
| 335 | if (file_exists($boarddir . '/' . $file['filename'])) |
||
| 336 | { |
||
| 337 | // OK - what is the current version? |
||
| 338 | $fp = fopen($boarddir . '/' . $file['filename'], 'rb'); |
||
| 339 | $header = fread($fp, 768); |
||
| 340 | fclose($fp); |
||
| 341 | |||
| 342 | // Find the version. |
||
| 343 | if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1) |
||
| 344 | { |
||
| 345 | $context_data['cur_version'] = $match[1]; |
||
| 346 | |||
| 347 | // How does this compare? |
||
| 348 | if ($context_data['cur_version'] == $context_data['version']) |
||
| 349 | $context_data['version_compare'] = 'same'; |
||
| 350 | elseif ($context_data['cur_version'] > $context_data['version']) |
||
| 351 | $context_data['version_compare'] = 'older'; |
||
| 352 | |||
| 353 | // Don't recommend copying if the version is the same. |
||
| 354 | if ($context_data['version_compare'] != 'newer') |
||
| 355 | $context_data['default_copy'] = false; |
||
| 356 | } |
||
| 357 | } |
||
| 358 | |||
| 359 | // Add the context data to the main set. |
||
| 360 | $context['files']['lang'][] = $context_data; |
||
| 361 | } |
||
| 362 | elseif ($extension == '.txt' && stripos($filename, 'agreement') !== false) |
||
| 363 | { |
||
| 364 | // Registration agreement is a primary file |
||
| 365 | $context['files']['lang'][] = $context_data; |
||
| 366 | } |
||
| 367 | else |
||
| 368 | { |
||
| 369 | // There shouldn't be anything else, but load this into "other" in case we decide to handle it in the future |
||
| 370 | $context['files']['other'][] = $context_data; |
||
| 371 | } |
||
| 372 | |||
| 373 | // Collect together all non-writable areas. |
||
| 374 | if (!$context_data['writable']) |
||
| 375 | $context['make_writable'][] = $context_data['destination']; |
||
| 376 | } |
||
| 377 | |||
| 378 | // Before we go to far can we make anything writable, eh, eh? |
||
| 379 | if (!empty($context['make_writable'])) |
||
| 380 | { |
||
| 381 | // What is left to be made writable? |
||
| 382 | $file_status = create_chmod_control($context['make_writable']); |
||
| 383 | $context['still_not_writable'] = $file_status['files']['notwritable']; |
||
| 384 | |||
| 385 | // Mark those which are now writable as such. |
||
| 386 | foreach ($context['files'] as $type => $data) |
||
| 387 | { |
||
| 388 | if ($type == 'lang') |
||
| 389 | { |
||
| 390 | View Code Duplication | foreach ($data as $k => $file) |
|
| 391 | if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) |
||
| 392 | $context['files'][$type][$k]['writable'] = true; |
||
| 393 | } |
||
| 394 | else |
||
| 395 | { |
||
| 396 | foreach ($data as $theme => $files) |
||
| 397 | View Code Duplication | foreach ($files as $k => $file) |
|
| 398 | if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) |
||
| 399 | $context['files'][$type][$theme][$k]['writable'] = true; |
||
| 400 | } |
||
| 401 | } |
||
| 402 | |||
| 403 | // Are we going to need more language stuff? |
||
| 404 | if (!empty($context['still_not_writable'])) |
||
| 405 | loadLanguage('Packages'); |
||
| 406 | } |
||
| 407 | |||
| 408 | // This is the list for the main files. |
||
| 409 | $listOptions = array( |
||
| 410 | 'id' => 'lang_main_files_list', |
||
| 411 | 'title' => $txt['languages_download_main_files'], |
||
| 412 | 'get_items' => array( |
||
| 413 | 'function' => function() use ($context) |
||
| 414 | { |
||
| 415 | return $context['files']['lang']; |
||
| 416 | }, |
||
| 417 | ), |
||
| 418 | 'columns' => array( |
||
| 419 | 'name' => array( |
||
| 420 | 'header' => array( |
||
| 421 | 'value' => $txt['languages_download_filename'], |
||
| 422 | ), |
||
| 423 | 'data' => array( |
||
| 424 | 'function' => function($rowData) use ($txt) |
||
| 425 | { |
||
| 426 | 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'] : ''); |
||
| 427 | }, |
||
| 428 | ), |
||
| 429 | ), |
||
| 430 | 'writable' => array( |
||
| 431 | 'header' => array( |
||
| 432 | 'value' => $txt['languages_download_writable'], |
||
| 433 | ), |
||
| 434 | 'data' => array( |
||
| 435 | View Code Duplication | 'function' => function($rowData) use ($txt) |
|
| 436 | { |
||
| 437 | return '<span style="color: ' . ($rowData['writable'] ? 'green' : 'red') . ';">' . ($rowData['writable'] ? $txt['yes'] : $txt['no']) . '</span>'; |
||
| 438 | }, |
||
| 439 | ), |
||
| 440 | ), |
||
| 441 | 'version' => array( |
||
| 442 | 'header' => array( |
||
| 443 | 'value' => $txt['languages_download_version'], |
||
| 444 | ), |
||
| 445 | 'data' => array( |
||
| 446 | 'function' => function($rowData) use ($txt) |
||
| 447 | { |
||
| 448 | return '<span style="color: ' . ($rowData['version_compare'] == 'older' ? 'red' : ($rowData['version_compare'] == 'same' ? 'orange' : 'green')) . ';">' . $rowData['version'] . '</span>'; |
||
| 449 | }, |
||
| 450 | ), |
||
| 451 | ), |
||
| 452 | 'exists' => array( |
||
| 453 | 'header' => array( |
||
| 454 | 'value' => $txt['languages_download_exists'], |
||
| 455 | ), |
||
| 456 | 'data' => array( |
||
| 457 | 'function' => function($rowData) use ($txt) |
||
| 458 | { |
||
| 459 | return $rowData['exists'] ? ($rowData['exists'] == 'same' ? $txt['languages_download_exists_same'] : $txt['languages_download_exists_different']) : $txt['no']; |
||
| 460 | }, |
||
| 461 | ), |
||
| 462 | ), |
||
| 463 | 'copy' => array( |
||
| 464 | 'header' => array( |
||
| 465 | 'value' => $txt['languages_download_copy'], |
||
| 466 | 'class' => 'centercol', |
||
| 467 | ), |
||
| 468 | 'data' => array( |
||
| 469 | 'function' => function($rowData) |
||
| 470 | { |
||
| 471 | return '<input type="checkbox" name="copy_file[]" value="' . $rowData['generaldest'] . '"' . ($rowData['default_copy'] ? ' checked' : '') . ' class="input_check">'; |
||
| 472 | }, |
||
| 473 | 'style' => 'width: 4%;', |
||
| 474 | 'class' => 'centercol', |
||
| 475 | ), |
||
| 476 | ), |
||
| 477 | ), |
||
| 478 | ); |
||
| 479 | |||
| 480 | // Kill the cache, as it is now invalid.. |
||
| 481 | if (!empty($modSettings['cache_enable'])) |
||
| 482 | { |
||
| 483 | cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); |
||
| 484 | cache_put_data('known_languages_all', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); |
||
| 485 | } |
||
| 486 | |||
| 487 | require_once($sourcedir . '/Subs-List.php'); |
||
| 488 | createList($listOptions); |
||
| 489 | |||
| 490 | $context['default_list'] = 'lang_main_files_list'; |
||
| 491 | createToken('admin-dlang'); |
||
| 492 | } |
||
| 493 | |||
| 494 | /** |
||
| 495 | * This lists all the current languages and allows editing of them. |
||
| 496 | */ |
||
| 497 | function ModifyLanguages() |
||
| 498 | { |
||
| 499 | global $txt, $context, $scripturl, $modSettings; |
||
| 500 | global $sourcedir, $language, $boarddir; |
||
| 501 | |||
| 502 | // Setting a new default? |
||
| 503 | if (!empty($_POST['set_default']) && !empty($_POST['def_language'])) |
||
| 504 | { |
||
| 505 | checkSession(); |
||
| 506 | validateToken('admin-lang'); |
||
| 507 | |||
| 508 | getLanguages(); |
||
| 509 | $lang_exists = false; |
||
| 510 | foreach ($context['languages'] as $lang) |
||
| 511 | { |
||
| 512 | if ($_POST['def_language'] == $lang['filename']) |
||
| 513 | { |
||
| 514 | $lang_exists = true; |
||
| 515 | break; |
||
| 516 | } |
||
| 517 | } |
||
| 518 | |||
| 519 | if ($_POST['def_language'] != $language && $lang_exists) |
||
| 520 | { |
||
| 521 | require_once($sourcedir . '/Subs-Admin.php'); |
||
| 522 | updateSettingsFile(array('language' => '\'' . $_POST['def_language'] . '\'')); |
||
| 523 | $language = $_POST['def_language']; |
||
| 524 | } |
||
| 525 | } |
||
| 526 | |||
| 527 | // Create another one time token here. |
||
| 528 | createToken('admin-lang'); |
||
| 529 | |||
| 530 | $listOptions = array( |
||
| 531 | 'id' => 'language_list', |
||
| 532 | 'items_per_page' => $modSettings['defaultMaxListItems'], |
||
| 533 | 'base_href' => $scripturl . '?action=admin;area=languages', |
||
| 534 | 'title' => $txt['edit_languages'], |
||
| 535 | 'get_items' => array( |
||
| 536 | 'function' => 'list_getLanguages', |
||
| 537 | ), |
||
| 538 | 'get_count' => array( |
||
| 539 | 'function' => 'list_getNumLanguages', |
||
| 540 | ), |
||
| 541 | 'columns' => array( |
||
| 542 | 'default' => array( |
||
| 543 | 'header' => array( |
||
| 544 | 'value' => $txt['languages_default'], |
||
| 545 | 'class' => 'centercol', |
||
| 546 | ), |
||
| 547 | 'data' => array( |
||
| 548 | 'function' => function($rowData) |
||
| 549 | { |
||
| 550 | return '<input type="radio" name="def_language" value="' . $rowData['id'] . '"' . ($rowData['default'] ? ' checked' : '') . ' onclick="highlightSelected(\'list_language_list_' . $rowData['id'] . '\');" class="input_radio">'; |
||
| 551 | }, |
||
| 552 | 'style' => 'width: 8%;', |
||
| 553 | 'class' => 'centercol', |
||
| 554 | ), |
||
| 555 | ), |
||
| 556 | 'name' => array( |
||
| 557 | 'header' => array( |
||
| 558 | 'value' => $txt['languages_lang_name'], |
||
| 559 | ), |
||
| 560 | 'data' => array( |
||
| 561 | 'function' => function($rowData) use ($scripturl) |
||
| 562 | { |
||
| 563 | return sprintf('<a href="%1$s?action=admin;area=languages;sa=editlang;lid=%2$s">%3$s</a>', $scripturl, $rowData['id'], $rowData['name']); |
||
| 564 | }, |
||
| 565 | ), |
||
| 566 | ), |
||
| 567 | 'character_set' => array( |
||
| 568 | 'header' => array( |
||
| 569 | 'value' => $txt['languages_character_set'], |
||
| 570 | ), |
||
| 571 | 'data' => array( |
||
| 572 | 'db_htmlsafe' => 'char_set', |
||
| 573 | ), |
||
| 574 | ), |
||
| 575 | 'count' => array( |
||
| 576 | 'header' => array( |
||
| 577 | 'value' => $txt['languages_users'], |
||
| 578 | ), |
||
| 579 | 'data' => array( |
||
| 580 | 'db_htmlsafe' => 'count', |
||
| 581 | ), |
||
| 582 | ), |
||
| 583 | 'locale' => array( |
||
| 584 | 'header' => array( |
||
| 585 | 'value' => $txt['languages_locale'], |
||
| 586 | ), |
||
| 587 | 'data' => array( |
||
| 588 | 'db_htmlsafe' => 'locale', |
||
| 589 | ), |
||
| 590 | ), |
||
| 591 | ), |
||
| 592 | 'form' => array( |
||
| 593 | 'href' => $scripturl . '?action=admin;area=languages', |
||
| 594 | 'token' => 'admin-lang', |
||
| 595 | ), |
||
| 596 | 'additional_rows' => array( |
||
| 597 | array( |
||
| 598 | 'position' => 'top_of_list', |
||
| 599 | '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') . ' class="button_submit">', |
||
| 600 | ), |
||
| 601 | array( |
||
| 602 | 'position' => 'bottom_of_list', |
||
| 603 | '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') . ' class="button_submit">', |
||
| 604 | ), |
||
| 605 | ), |
||
| 606 | ); |
||
| 607 | |||
| 608 | // We want to highlight the selected language. Need some Javascript for this. |
||
| 609 | addInlineJavaScript(' |
||
| 610 | function highlightSelected(box) |
||
| 611 | { |
||
| 612 | $("tr.highlight2").removeClass("highlight2"); |
||
| 613 | $("#" + box).addClass("highlight2"); |
||
| 614 | } |
||
| 615 | highlightSelected("list_language_list_' . ($language == '' ? 'english' : $language) . '");', true); |
||
| 616 | |||
| 617 | // Display a warning if we cannot edit the default setting. |
||
| 618 | if (!is_writable($boarddir . '/Settings.php')) |
||
| 619 | $listOptions['additional_rows'][] = array( |
||
| 620 | 'position' => 'after_title', |
||
| 621 | 'value' => $txt['language_settings_writable'], |
||
| 622 | 'class' => 'smalltext alert', |
||
| 623 | ); |
||
| 624 | |||
| 625 | require_once($sourcedir . '/Subs-List.php'); |
||
| 626 | createList($listOptions); |
||
| 627 | |||
| 628 | $context['sub_template'] = 'show_list'; |
||
| 629 | $context['default_list'] = 'language_list'; |
||
| 630 | } |
||
| 631 | |||
| 632 | /** |
||
| 633 | * How many languages? |
||
| 634 | * Callback for the list in ManageLanguageSettings(). |
||
| 635 | * @return int The number of available languages |
||
| 636 | */ |
||
| 637 | function list_getNumLanguages() |
||
| 638 | { |
||
| 639 | return count(getLanguages()); |
||
| 640 | } |
||
| 641 | |||
| 642 | /** |
||
| 643 | * Fetch the actual language information. |
||
| 644 | * Callback for $listOptions['get_items']['function'] in ManageLanguageSettings. |
||
| 645 | * Determines which languages are available by looking for the "index.{language}.php" file. |
||
| 646 | * Also figures out how many users are using a particular language. |
||
| 647 | * @return array An array of information about currenty installed languages |
||
| 648 | */ |
||
| 649 | function list_getLanguages() |
||
| 650 | { |
||
| 651 | global $settings, $smcFunc, $language, $context, $txt; |
||
| 652 | |||
| 653 | $languages = array(); |
||
| 654 | // Keep our old entries. |
||
| 655 | $old_txt = $txt; |
||
| 656 | $backup_actual_theme_dir = $settings['actual_theme_dir']; |
||
| 657 | $backup_base_theme_dir = !empty($settings['base_theme_dir']) ? $settings['base_theme_dir'] : ''; |
||
| 658 | |||
| 659 | // Override these for now. |
||
| 660 | $settings['actual_theme_dir'] = $settings['base_theme_dir'] = $settings['default_theme_dir']; |
||
| 661 | getLanguages(); |
||
| 662 | |||
| 663 | // Put them back. |
||
| 664 | $settings['actual_theme_dir'] = $backup_actual_theme_dir; |
||
| 665 | if (!empty($backup_base_theme_dir)) |
||
| 666 | $settings['base_theme_dir'] = $backup_base_theme_dir; |
||
| 667 | else |
||
| 668 | unset($settings['base_theme_dir']); |
||
| 669 | |||
| 670 | // Get the language files and data... |
||
| 671 | foreach ($context['languages'] as $lang) |
||
| 672 | { |
||
| 673 | // Load the file to get the character set. |
||
| 674 | require($settings['default_theme_dir'] . '/languages/index.' . $lang['filename'] . '.php'); |
||
| 675 | |||
| 676 | $languages[$lang['filename']] = array( |
||
| 677 | 'id' => $lang['filename'], |
||
| 678 | 'count' => 0, |
||
| 679 | 'char_set' => $txt['lang_character_set'], |
||
| 680 | 'default' => $language == $lang['filename'] || ($language == '' && $lang['filename'] == 'english'), |
||
| 681 | 'locale' => $txt['lang_locale'], |
||
| 682 | 'name' => $smcFunc['ucwords'](strtr($lang['filename'], array('_' => ' ', '-utf8' => ''))), |
||
| 683 | ); |
||
| 684 | } |
||
| 685 | |||
| 686 | // Work out how many people are using each language. |
||
| 687 | $request = $smcFunc['db_query']('', ' |
||
| 688 | SELECT lngfile, COUNT(*) AS num_users |
||
| 689 | FROM {db_prefix}members |
||
| 690 | GROUP BY lngfile', |
||
| 691 | array( |
||
| 692 | ) |
||
| 693 | ); |
||
| 694 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
| 695 | { |
||
| 696 | // Default? |
||
| 697 | if (empty($row['lngfile']) || !isset($languages[$row['lngfile']])) |
||
| 698 | $row['lngfile'] = $language; |
||
| 699 | |||
| 700 | if (!isset($languages[$row['lngfile']]) && isset($languages['english'])) |
||
| 701 | $languages['english']['count'] += $row['num_users']; |
||
| 702 | elseif (isset($languages[$row['lngfile']])) |
||
| 703 | $languages[$row['lngfile']]['count'] += $row['num_users']; |
||
| 704 | } |
||
| 705 | $smcFunc['db_free_result']($request); |
||
| 706 | |||
| 707 | // Restore the current users language. |
||
| 708 | $txt = $old_txt; |
||
| 709 | |||
| 710 | // Return how many we have. |
||
| 711 | return $languages; |
||
| 712 | } |
||
| 713 | |||
| 714 | /** |
||
| 715 | * Edit language related settings. |
||
| 716 | * |
||
| 717 | * @param bool $return_config Whether to return the $config_vars array (used in admin search) |
||
| 718 | * @return void|array Returns nothing or the $config_vars array if $return_config is true |
||
| 719 | */ |
||
| 720 | function ModifyLanguageSettings($return_config = false) |
||
| 721 | { |
||
| 722 | global $scripturl, $context, $txt, $boarddir, $sourcedir; |
||
| 723 | |||
| 724 | // We'll want to save them someday. |
||
| 725 | require_once $sourcedir . '/ManageServer.php'; |
||
| 726 | |||
| 727 | // Warn the user if the backup of Settings.php failed. |
||
| 728 | $settings_not_writable = !is_writable($boarddir . '/Settings.php'); |
||
| 729 | $settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php'); |
||
| 730 | |||
| 731 | /* If you're writing a mod, it's a bad idea to add things here.... |
||
| 732 | For each option: |
||
| 733 | variable name, description, type (constant), size/possible values, helptext. |
||
| 734 | OR an empty string for a horizontal rule. |
||
| 735 | OR a string for a titled section. */ |
||
| 736 | $config_vars = array( |
||
| 737 | 'language' => array('language', $txt['default_language'], 'file', 'select', array(), null, 'disabled' => $settings_not_writable), |
||
| 738 | array('userLanguage', $txt['userLanguage'], 'db', 'check', null, 'userLanguage'), |
||
| 739 | ); |
||
| 740 | |||
| 741 | call_integration_hook('integrate_language_settings', array(&$config_vars)); |
||
| 742 | |||
| 743 | if ($return_config) |
||
| 744 | return $config_vars; |
||
| 745 | |||
| 746 | // Get our languages. No cache |
||
| 747 | getLanguages(false); |
||
| 748 | foreach ($context['languages'] as $lang) |
||
| 749 | $config_vars['language'][4][$lang['filename']] = array($lang['filename'], $lang['name']); |
||
| 750 | |||
| 751 | // Saving settings? |
||
| 752 | View Code Duplication | if (isset($_REQUEST['save'])) |
|
| 753 | { |
||
| 754 | checkSession(); |
||
| 755 | |||
| 756 | call_integration_hook('integrate_save_language_settings', array(&$config_vars)); |
||
| 757 | |||
| 758 | saveSettings($config_vars); |
||
| 759 | if (!$settings_not_writable && !$settings_backup_fail) |
||
| 760 | $_SESSION['adm-save'] = true; |
||
| 761 | redirectexit('action=admin;area=languages;sa=settings'); |
||
| 762 | } |
||
| 763 | |||
| 764 | // Setup the template stuff. |
||
| 765 | $context['post_url'] = $scripturl . '?action=admin;area=languages;sa=settings;save'; |
||
| 766 | $context['settings_title'] = $txt['language_settings']; |
||
| 767 | $context['save_disabled'] = $settings_not_writable; |
||
| 768 | |||
| 769 | View Code Duplication | if ($settings_not_writable) |
|
| 770 | $context['settings_message'] = '<div class="centertext"><strong>' . $txt['settings_not_writable'] . '</strong></div><br>'; |
||
| 771 | elseif ($settings_backup_fail) |
||
| 772 | $context['settings_message'] = '<div class="centertext"><strong>' . $txt['admin_backup_fail'] . '</strong></div><br>'; |
||
| 773 | |||
| 774 | // Fill the config array. |
||
| 775 | prepareServerSettingsContext($config_vars); |
||
| 776 | } |
||
| 777 | |||
| 778 | /** |
||
| 779 | * Edit a particular set of language entries. |
||
| 780 | */ |
||
| 781 | function ModifyLanguage() |
||
| 782 | { |
||
| 783 | global $settings, $context, $smcFunc, $txt, $modSettings, $boarddir, $sourcedir, $language; |
||
| 784 | |||
| 785 | loadLanguage('ManageSettings'); |
||
| 786 | |||
| 787 | // Select the languages tab. |
||
| 788 | $context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit'; |
||
| 789 | $context['page_title'] = $txt['edit_languages']; |
||
| 790 | $context['sub_template'] = 'modify_language_entries'; |
||
| 791 | |||
| 792 | $context['lang_id'] = $_GET['lid']; |
||
| 793 | list($theme_id, $file_id) = empty($_REQUEST['tfid']) || strpos($_REQUEST['tfid'], '+') === false ? array(1, '') : explode('+', $_REQUEST['tfid']); |
||
| 794 | |||
| 795 | // Clean the ID - just in case. |
||
| 796 | preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches); |
||
| 797 | $context['lang_id'] = $matches[1]; |
||
| 798 | |||
| 799 | // Get all the theme data. |
||
| 800 | $request = $smcFunc['db_query']('', ' |
||
| 801 | SELECT id_theme, variable, value |
||
| 802 | FROM {db_prefix}themes |
||
| 803 | WHERE id_theme != {int:default_theme} |
||
| 804 | AND id_member = {int:no_member} |
||
| 805 | AND variable IN ({string:name}, {string:theme_dir})', |
||
| 806 | array( |
||
| 807 | 'default_theme' => 1, |
||
| 808 | 'no_member' => 0, |
||
| 809 | 'name' => 'name', |
||
| 810 | 'theme_dir' => 'theme_dir', |
||
| 811 | ) |
||
| 812 | ); |
||
| 813 | $themes = array( |
||
| 814 | 1 => array( |
||
| 815 | 'name' => $txt['dvc_default'], |
||
| 816 | 'theme_dir' => $settings['default_theme_dir'], |
||
| 817 | ), |
||
| 818 | ); |
||
| 819 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
| 820 | $themes[$row['id_theme']][$row['variable']] = $row['value']; |
||
| 821 | $smcFunc['db_free_result']($request); |
||
| 822 | |||
| 823 | // This will be where we look |
||
| 824 | $lang_dirs = array(); |
||
| 825 | |||
| 826 | // Does a hook need to add in some additional places to look for languages? |
||
| 827 | call_integration_hook('integrate_modifylanguages', array(&$themes, &$lang_dirs)); |
||
| 828 | |||
| 829 | // Check we have themes with a path and a name - just in case - and add the path. |
||
| 830 | foreach ($themes as $id => $data) |
||
| 831 | { |
||
| 832 | if (count($data) != 2) |
||
| 833 | unset($themes[$id]); |
||
| 834 | elseif (is_dir($data['theme_dir'] . '/languages')) |
||
| 835 | $lang_dirs[$id] = $data['theme_dir'] . '/languages'; |
||
| 836 | |||
| 837 | // How about image directories? |
||
| 838 | if (is_dir($data['theme_dir'] . '/images/' . $context['lang_id'])) |
||
| 839 | $images_dirs[$id] = $data['theme_dir'] . '/images/' . $context['lang_id']; |
||
| 840 | } |
||
| 841 | |||
| 842 | $current_file = $file_id ? $lang_dirs[$theme_id] . '/' . $file_id . '.' . $context['lang_id'] . '.php' : ''; |
||
| 843 | |||
| 844 | // Now for every theme get all the files and stick them in context! |
||
| 845 | $context['possible_files'] = array(); |
||
| 846 | foreach ($lang_dirs as $theme => $theme_dir) |
||
| 847 | { |
||
| 848 | // Open it up. |
||
| 849 | $dir = dir($theme_dir); |
||
| 850 | while ($entry = $dir->read()) |
||
| 851 | { |
||
| 852 | // We're only after the files for this language. |
||
| 853 | if (preg_match('~^([A-Za-z]+)\.' . $context['lang_id'] . '\.php$~', $entry, $matches) == 0) |
||
| 854 | continue; |
||
| 855 | |||
| 856 | if (!isset($context['possible_files'][$theme])) |
||
| 857 | $context['possible_files'][$theme] = array( |
||
| 858 | 'id' => $theme, |
||
| 859 | 'name' => $themes[$theme]['name'], |
||
| 860 | 'files' => array(), |
||
| 861 | ); |
||
| 862 | |||
| 863 | $context['possible_files'][$theme]['files'][] = array( |
||
| 864 | 'id' => $matches[1], |
||
| 865 | 'name' => isset($txt['lang_file_desc_' . $matches[1]]) ? $txt['lang_file_desc_' . $matches[1]] : $matches[1], |
||
| 866 | 'selected' => $theme_id == $theme && $file_id == $matches[1], |
||
| 867 | ); |
||
| 868 | } |
||
| 869 | $dir->close(); |
||
| 870 | usort($context['possible_files'][$theme]['files'], function($val1, $val2) |
||
| 871 | { |
||
| 872 | return strcmp($val1['name'], $val2['name']); |
||
| 873 | }); |
||
| 874 | } |
||
| 875 | |||
| 876 | // We no longer wish to speak this language. |
||
| 877 | if (!empty($_POST['delete_main']) && $context['lang_id'] != 'english') |
||
| 878 | { |
||
| 879 | checkSession(); |
||
| 880 | validateToken('admin-mlang'); |
||
| 881 | |||
| 882 | // @todo Todo: FTP Controls? |
||
| 883 | require_once($sourcedir . '/Subs-Package.php'); |
||
| 884 | |||
| 885 | // First, Make a backup? |
||
| 886 | if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['lang_id'] . '$$$')) |
||
| 887 | { |
||
| 888 | $_SESSION['last_backup_for'] = $context['lang_id'] . '$$$'; |
||
| 889 | $result = package_create_backup('backup_lang_' . $context['lang_id']); |
||
| 890 | if (!$result) |
||
| 891 | fatal_lang_error('could_not_language_backup', false); |
||
| 892 | } |
||
| 893 | |||
| 894 | // Second, loop through the array to remove the files. |
||
| 895 | foreach ($lang_dirs as $curPath) |
||
| 896 | { |
||
| 897 | foreach ($context['possible_files'][1]['files'] as $lang) |
||
| 898 | if (file_exists($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php')) |
||
| 899 | unlink($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php'); |
||
| 900 | |||
| 901 | // Check for the email template. |
||
| 902 | View Code Duplication | if (file_exists($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php')) |
|
| 903 | unlink($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php'); |
||
| 904 | } |
||
| 905 | |||
| 906 | // Third, the agreement file. |
||
| 907 | View Code Duplication | if (file_exists($boarddir . '/agreement.' . $context['lang_id'] . '.txt')) |
|
| 908 | unlink($boarddir . '/agreement.' . $context['lang_id'] . '.txt'); |
||
| 909 | |||
| 910 | // Fourth, a related images folder, if it exists... |
||
| 911 | if (!empty($images_dirs)) |
||
| 912 | foreach ($images_dirs as $curPath) |
||
| 913 | if (is_dir($curPath)) |
||
| 914 | deltree($curPath); |
||
| 915 | |||
| 916 | // Members can no longer use this language. |
||
| 917 | $smcFunc['db_query']('', ' |
||
| 918 | UPDATE {db_prefix}members |
||
| 919 | SET lngfile = {empty} |
||
| 920 | WHERE lngfile = {string:current_language}', |
||
| 921 | array( |
||
| 922 | 'empty_string' => '', |
||
| 923 | 'current_language' => $context['lang_id'], |
||
| 924 | ) |
||
| 925 | ); |
||
| 926 | |||
| 927 | // Fifth, update getLanguages() cache. |
||
| 928 | View Code Duplication | if (!empty($modSettings['cache_enable'])) |
|
| 929 | { |
||
| 930 | cache_put_data('known_languages', null, !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600); |
||
| 931 | } |
||
| 932 | |||
| 933 | // Sixth, if we deleted the default language, set us back to english? |
||
| 934 | if ($context['lang_id'] == $language) |
||
| 935 | { |
||
| 936 | require_once($sourcedir . '/Subs-Admin.php'); |
||
| 937 | $language = 'english'; |
||
| 938 | updateSettingsFile(array('language' => '\'' . $language . '\'')); |
||
| 939 | } |
||
| 940 | |||
| 941 | // Seventh, get out of here. |
||
| 942 | redirectexit('action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id']); |
||
| 943 | } |
||
| 944 | |||
| 945 | // Saving primary settings? |
||
| 946 | $madeSave = false; |
||
| 947 | if (!empty($_POST['save_main']) && !$current_file) |
||
| 948 | { |
||
| 949 | checkSession(); |
||
| 950 | validateToken('admin-mlang'); |
||
| 951 | |||
| 952 | // Read in the current file. |
||
| 953 | $current_data = implode('', file($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php')); |
||
| 954 | // These are the replacements. old => new |
||
| 955 | $replace_array = array( |
||
| 956 | '~\$txt\[\'lang_character_set\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_character_set\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['character_set']) . '\';', |
||
| 957 | '~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['locale']) . '\';', |
||
| 958 | '~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['dictionary']) . '\';', |
||
| 959 | '~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . preg_replace('~[^\w-]~i', '', $_POST['spelling']) . '\';', |
||
| 960 | '~\$txt\[\'lang_rtl\'\]\s=\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($_POST['rtl']) ? 'true' : 'false') . ';', |
||
| 961 | ); |
||
| 962 | $current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data); |
||
| 963 | $fp = fopen($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php', 'w+'); |
||
| 964 | fwrite($fp, $current_data); |
||
| 965 | fclose($fp); |
||
| 966 | |||
| 967 | $madeSave = true; |
||
| 968 | } |
||
| 969 | |||
| 970 | // Quickly load index language entries. |
||
| 971 | $old_txt = $txt; |
||
| 972 | require($settings['default_theme_dir'] . '/languages/index.' . $context['lang_id'] . '.php'); |
||
| 973 | $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'); |
||
| 974 | // Setup the primary settings context. |
||
| 975 | $context['primary_settings'] = array( |
||
| 976 | 'name' => $smcFunc['ucwords'](strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))), |
||
| 977 | 'character_set' => $txt['lang_character_set'], |
||
| 978 | 'locale' => $txt['lang_locale'], |
||
| 979 | 'dictionary' => $txt['lang_dictionary'], |
||
| 980 | 'spelling' => $txt['lang_spelling'], |
||
| 981 | 'rtl' => $txt['lang_rtl'], |
||
| 982 | ); |
||
| 983 | |||
| 984 | // Restore normal service. |
||
| 985 | $txt = $old_txt; |
||
| 986 | |||
| 987 | // Are we saving? |
||
| 988 | $save_strings = array(); |
||
| 989 | if (isset($_POST['save_entries']) && !empty($_POST['entry'])) |
||
| 990 | { |
||
| 991 | checkSession(); |
||
| 992 | validateToken('admin-mlang'); |
||
| 993 | |||
| 994 | // Clean each entry! |
||
| 995 | foreach ($_POST['entry'] as $k => $v) |
||
| 996 | { |
||
| 997 | // Only try to save if it's changed! |
||
| 998 | if ($_POST['entry'][$k] != $_POST['comp'][$k]) |
||
| 999 | $save_strings[$k] = cleanLangString($v, false); |
||
| 1000 | } |
||
| 1001 | } |
||
| 1002 | |||
| 1003 | // If we are editing a file work away at that. |
||
| 1004 | if ($current_file) |
||
| 1005 | { |
||
| 1006 | $context['entries_not_writable_message'] = is_writable($current_file) ? '' : sprintf($txt['lang_entries_not_writable'], $current_file); |
||
| 1007 | |||
| 1008 | $entries = array(); |
||
| 1009 | // We can't just require it I'm afraid - otherwise we pass in all kinds of variables! |
||
| 1010 | $multiline_cache = ''; |
||
| 1011 | foreach (file($current_file) as $line) |
||
| 1012 | { |
||
| 1013 | // Got a new entry? |
||
| 1014 | if ($line[0] == '$' && !empty($multiline_cache)) |
||
| 1015 | { |
||
| 1016 | preg_match('~\$(helptxt|txt|editortxt)\[\'(.+)\'\]\s?=\s?(.+);~ms', strtr($multiline_cache, array("\r" => '')), $matches); |
||
| 1017 | View Code Duplication | if (!empty($matches[3])) |
|
| 1018 | { |
||
| 1019 | $entries[$matches[2]] = array( |
||
| 1020 | 'type' => $matches[1], |
||
| 1021 | 'full' => $matches[0], |
||
| 1022 | 'entry' => $matches[3], |
||
| 1023 | ); |
||
| 1024 | $multiline_cache = ''; |
||
| 1025 | } |
||
| 1026 | } |
||
| 1027 | $multiline_cache .= $line; |
||
| 1028 | } |
||
| 1029 | // Last entry to add? |
||
| 1030 | if ($multiline_cache) |
||
| 1031 | { |
||
| 1032 | preg_match('~\$(helptxt|txt|editortxt)\[\'(.+)\'\]\s?=\s?(.+);~ms', strtr($multiline_cache, array("\r" => '')), $matches); |
||
| 1033 | View Code Duplication | if (!empty($matches[3])) |
|
| 1034 | $entries[$matches[2]] = array( |
||
| 1035 | 'type' => $matches[1], |
||
| 1036 | 'full' => $matches[0], |
||
| 1037 | 'entry' => $matches[3], |
||
| 1038 | ); |
||
| 1039 | } |
||
| 1040 | |||
| 1041 | // These are the entries we can definitely save. |
||
| 1042 | $final_saves = array(); |
||
| 1043 | |||
| 1044 | $context['file_entries'] = array(); |
||
| 1045 | foreach ($entries as $entryKey => $entryValue) |
||
| 1046 | { |
||
| 1047 | // Ignore some things we set separately. |
||
| 1048 | $ignore_files = array('lang_character_set', 'lang_locale', 'lang_dictionary', 'lang_spelling', 'lang_rtl'); |
||
| 1049 | if (in_array($entryKey, $ignore_files)) |
||
| 1050 | continue; |
||
| 1051 | |||
| 1052 | // These are arrays that need breaking out. |
||
| 1053 | $arrays = array('days', 'days_short', 'months', 'months_titles', 'months_short', 'happy_birthday_author', 'karlbenson1_author', 'nite0859_author', 'zwaldowski_author', 'geezmo_author', 'karlbenson2_author'); |
||
| 1054 | if (in_array($entryKey, $arrays)) |
||
| 1055 | { |
||
| 1056 | // Get off the first bits. |
||
| 1057 | $entryValue['entry'] = substr($entryValue['entry'], strpos($entryValue['entry'], '(') + 1, strrpos($entryValue['entry'], ')') - strpos($entryValue['entry'], '(')); |
||
| 1058 | $entryValue['entry'] = explode(',', strtr($entryValue['entry'], array(' ' => ''))); |
||
| 1059 | |||
| 1060 | // Now create an entry for each item. |
||
| 1061 | $cur_index = 0; |
||
| 1062 | $save_cache = array( |
||
| 1063 | 'enabled' => false, |
||
| 1064 | 'entries' => array(), |
||
| 1065 | ); |
||
| 1066 | foreach ($entryValue['entry'] as $id => $subValue) |
||
| 1067 | { |
||
| 1068 | // Is this a new index? |
||
| 1069 | if (preg_match('~^(\d+)~', $subValue, $matches)) |
||
| 1070 | { |
||
| 1071 | $cur_index = $matches[1]; |
||
| 1072 | $subValue = substr($subValue, strpos($subValue, '\'')); |
||
| 1073 | } |
||
| 1074 | |||
| 1075 | // Clean up some bits. |
||
| 1076 | $subValue = strtr($subValue, array('"' => '', '\'' => '', ')' => '')); |
||
| 1077 | |||
| 1078 | // Can we save? |
||
| 1079 | if (isset($save_strings[$entryKey . '-+- ' . $cur_index])) |
||
| 1080 | { |
||
| 1081 | $save_cache['entries'][$cur_index] = strtr($save_strings[$entryKey . '-+- ' . $cur_index], array('\'' => '')); |
||
| 1082 | $save_cache['enabled'] = true; |
||
| 1083 | } |
||
| 1084 | else |
||
| 1085 | $save_cache['entries'][$cur_index] = $subValue; |
||
| 1086 | |||
| 1087 | $context['file_entries'][] = array( |
||
| 1088 | 'key' => $entryKey . '-+- ' . $cur_index, |
||
| 1089 | 'value' => $subValue, |
||
| 1090 | 'rows' => 1, |
||
| 1091 | ); |
||
| 1092 | $cur_index++; |
||
| 1093 | } |
||
| 1094 | |||
| 1095 | // Do we need to save? |
||
| 1096 | if ($save_cache['enabled']) |
||
| 1097 | { |
||
| 1098 | // Format the string, checking the indexes first. |
||
| 1099 | $items = array(); |
||
| 1100 | $cur_index = 0; |
||
| 1101 | foreach ($save_cache['entries'] as $k2 => $v2) |
||
| 1102 | { |
||
| 1103 | // Manually show the custom index. |
||
| 1104 | if ($k2 != $cur_index) |
||
| 1105 | { |
||
| 1106 | $items[] = $k2 . ' => \'' . $v2 . '\''; |
||
| 1107 | $cur_index = $k2; |
||
| 1108 | } |
||
| 1109 | else |
||
| 1110 | $items[] = '\'' . $v2 . '\''; |
||
| 1111 | |||
| 1112 | $cur_index++; |
||
| 1113 | } |
||
| 1114 | // Now create the string! |
||
| 1115 | $final_saves[$entryKey] = array( |
||
| 1116 | 'find' => $entryValue['full'], |
||
| 1117 | 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = array(' . implode(', ', $items) . ');', |
||
| 1118 | ); |
||
| 1119 | } |
||
| 1120 | } |
||
| 1121 | else |
||
| 1122 | { |
||
| 1123 | // Saving? |
||
| 1124 | if (isset($save_strings[$entryKey]) && $save_strings[$entryKey] != $entryValue['entry']) |
||
| 1125 | { |
||
| 1126 | // @todo Fix this properly. |
||
| 1127 | if ($save_strings[$entryKey] == '') |
||
| 1128 | $save_strings[$entryKey] = '\'\''; |
||
| 1129 | |||
| 1130 | // Set the new value. |
||
| 1131 | $entryValue['entry'] = $save_strings[$entryKey]; |
||
| 1132 | // And we know what to save now! |
||
| 1133 | $final_saves[$entryKey] = array( |
||
| 1134 | 'find' => $entryValue['full'], |
||
| 1135 | 'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = ' . $save_strings[$entryKey] . ';', |
||
| 1136 | ); |
||
| 1137 | } |
||
| 1138 | |||
| 1139 | $editing_string = cleanLangString($entryValue['entry'], true); |
||
| 1140 | $context['file_entries'][] = array( |
||
| 1141 | 'key' => $entryKey, |
||
| 1142 | 'value' => $editing_string, |
||
| 1143 | 'rows' => (int) (strlen($editing_string) / 38) + substr_count($editing_string, "\n") + 1, |
||
| 1144 | ); |
||
| 1145 | } |
||
| 1146 | } |
||
| 1147 | |||
| 1148 | // Any saves to make? |
||
| 1149 | if (!empty($final_saves)) |
||
| 1150 | { |
||
| 1151 | checkSession(); |
||
| 1152 | |||
| 1153 | $file_contents = implode('', file($current_file)); |
||
| 1154 | foreach ($final_saves as $save) |
||
| 1155 | $file_contents = strtr($file_contents, array($save['find'] => $save['replace'])); |
||
| 1156 | |||
| 1157 | // Save the actual changes. |
||
| 1158 | $fp = fopen($current_file, 'w+'); |
||
| 1159 | fwrite($fp, strtr($file_contents, array("\r" => ''))); |
||
| 1160 | fclose($fp); |
||
| 1161 | |||
| 1162 | $madeSave = true; |
||
| 1163 | } |
||
| 1164 | |||
| 1165 | // Another restore. |
||
| 1166 | $txt = $old_txt; |
||
| 1167 | } |
||
| 1168 | |||
| 1169 | // If we saved, redirect. |
||
| 1170 | if ($madeSave) |
||
| 1171 | redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']); |
||
| 1172 | |||
| 1173 | createToken('admin-mlang'); |
||
| 1174 | } |
||
| 1175 | |||
| 1176 | /** |
||
| 1177 | * This function cleans language entries to/from display. |
||
| 1178 | * @todo This function could be two functions? |
||
| 1179 | * |
||
| 1180 | * @param string $string The language string |
||
| 1181 | * @param bool $to_display Whether or not this is going to be displayed |
||
| 1182 | * @return string The cleaned string |
||
| 1183 | */ |
||
| 1184 | function cleanLangString($string, $to_display = true) |
||
| 1185 | { |
||
| 1186 | global $smcFunc; |
||
| 1187 | |||
| 1188 | // If going to display we make sure it doesn't have any HTML in it - etc. |
||
| 1189 | $new_string = ''; |
||
| 1190 | if ($to_display) |
||
| 1191 | { |
||
| 1192 | // Are we in a string (0 = no, 1 = single quote, 2 = parsed) |
||
| 1193 | $in_string = 0; |
||
| 1194 | $is_escape = false; |
||
| 1195 | for ($i = 0; $i < strlen($string); $i++) |
||
| 1196 | { |
||
| 1197 | // Handle escapes first. |
||
| 1198 | if ($string{$i} == '\\') |
||
| 1199 | { |
||
| 1200 | // Toggle the escape. |
||
| 1201 | $is_escape = !$is_escape; |
||
| 1202 | // If we're now escaped don't add this string. |
||
| 1203 | if ($is_escape) |
||
| 1204 | continue; |
||
| 1205 | } |
||
| 1206 | // Special case - parsed string with line break etc? |
||
| 1207 | elseif (($string{$i} == 'n' || $string{$i} == 't') && $in_string == 2 && $is_escape) |
||
| 1208 | { |
||
| 1209 | // Put the escape back... |
||
| 1210 | $new_string .= $string{$i} == 'n' ? "\n" : "\t"; |
||
| 1211 | $is_escape = false; |
||
| 1212 | continue; |
||
| 1213 | } |
||
| 1214 | // Have we got a single quote? |
||
| 1215 | View Code Duplication | elseif ($string{$i} == '\'') |
|
| 1216 | { |
||
| 1217 | // Already in a parsed string, or escaped in a linear string, means we print it - otherwise something special. |
||
| 1218 | if ($in_string != 2 && ($in_string != 1 || !$is_escape)) |
||
| 1219 | { |
||
| 1220 | // Is it the end of a single quote string? |
||
| 1221 | if ($in_string == 1) |
||
| 1222 | $in_string = 0; |
||
| 1223 | // Otherwise it's the start! |
||
| 1224 | else |
||
| 1225 | $in_string = 1; |
||
| 1226 | |||
| 1227 | // Don't actually include this character! |
||
| 1228 | continue; |
||
| 1229 | } |
||
| 1230 | } |
||
| 1231 | // Otherwise a double quote? |
||
| 1232 | View Code Duplication | elseif ($string{$i} == '"') |
|
| 1233 | { |
||
| 1234 | // Already in a single quote string, or escaped in a parsed string, means we print it - otherwise something special. |
||
| 1235 | if ($in_string != 1 && ($in_string != 2 || !$is_escape)) |
||
| 1236 | { |
||
| 1237 | // Is it the end of a double quote string? |
||
| 1238 | if ($in_string == 2) |
||
| 1239 | $in_string = 0; |
||
| 1240 | // Otherwise it's the start! |
||
| 1241 | else |
||
| 1242 | $in_string = 2; |
||
| 1243 | |||
| 1244 | // Don't actually include this character! |
||
| 1245 | continue; |
||
| 1246 | } |
||
| 1247 | } |
||
| 1248 | // A join/space outside of a string is simply removed. |
||
| 1249 | elseif ($in_string == 0 && (empty($string{$i}) || $string{$i} == '.')) |
||
| 1250 | continue; |
||
| 1251 | // Start of a variable? |
||
| 1252 | elseif ($in_string == 0 && $string{$i} == '$') |
||
| 1253 | { |
||
| 1254 | // Find the whole of it! |
||
| 1255 | preg_match('~([\$A-Za-z0-9\'\[\]_-]+)~', substr($string, $i), $matches); |
||
| 1256 | if (!empty($matches[1])) |
||
| 1257 | { |
||
| 1258 | // Come up with some pseudo thing to indicate this is a var. |
||
| 1259 | /** |
||
| 1260 | * @todo Do better than this, please! |
||
| 1261 | */ |
||
| 1262 | $new_string .= '{%' . $matches[1] . '%}'; |
||
| 1263 | |||
| 1264 | // We're not going to reparse this. |
||
| 1265 | $i += strlen($matches[1]) - 1; |
||
| 1266 | } |
||
| 1267 | |||
| 1268 | continue; |
||
| 1269 | } |
||
| 1270 | // Right, if we're outside of a string we have DANGER, DANGER! |
||
| 1271 | elseif ($in_string == 0) |
||
| 1272 | { |
||
| 1273 | continue; |
||
| 1274 | } |
||
| 1275 | |||
| 1276 | // Actually add the character to the string! |
||
| 1277 | $new_string .= $string{$i}; |
||
| 1278 | // If anything was escaped it ain't any longer! |
||
| 1279 | $is_escape = false; |
||
| 1280 | } |
||
| 1281 | |||
| 1282 | // Unhtml then rehtml the whole thing! |
||
| 1283 | $new_string = $smcFunc['htmlspecialchars'](un_htmlspecialchars($new_string)); |
||
| 1284 | } |
||
| 1285 | else |
||
| 1286 | { |
||
| 1287 | // Keep track of what we're doing... |
||
| 1288 | $in_string = 0; |
||
| 1289 | // This is for deciding whether to HTML a quote. |
||
| 1290 | $in_html = false; |
||
| 1291 | for ($i = 0; $i < strlen($string); $i++) |
||
| 1292 | { |
||
| 1293 | // We don't do parsed strings apart from for breaks. |
||
| 1294 | if ($in_string == 2) |
||
| 1295 | { |
||
| 1296 | $in_string = 0; |
||
| 1297 | $new_string .= '"'; |
||
| 1298 | } |
||
| 1299 | |||
| 1300 | // Not in a string yet? |
||
| 1301 | if ($in_string != 1) |
||
| 1302 | { |
||
| 1303 | $in_string = 1; |
||
| 1304 | $new_string .= ($new_string ? ' . ' : '') . '\''; |
||
| 1305 | } |
||
| 1306 | |||
| 1307 | // Is this a variable? |
||
| 1308 | if ($string{$i} == '{' && $string{$i + 1} == '%' && $string{$i + 2} == '$') |
||
| 1309 | { |
||
| 1310 | // Grab the variable. |
||
| 1311 | preg_match('~\{%([\$A-Za-z0-9\'\[\]_-]+)%\}~', substr($string, $i), $matches); |
||
| 1312 | if (!empty($matches[1])) |
||
| 1313 | { |
||
| 1314 | if ($in_string == 1) |
||
| 1315 | $new_string .= '\' . '; |
||
| 1316 | elseif ($new_string) |
||
| 1317 | $new_string .= ' . '; |
||
| 1318 | |||
| 1319 | $new_string .= $matches[1]; |
||
| 1320 | $i += strlen($matches[1]) + 3; |
||
| 1321 | $in_string = 0; |
||
| 1322 | } |
||
| 1323 | |||
| 1324 | continue; |
||
| 1325 | } |
||
| 1326 | // Is this a lt sign? |
||
| 1327 | View Code Duplication | elseif ($string{$i} == '<') |
|
| 1328 | { |
||
| 1329 | // Probably HTML? |
||
| 1330 | if ($string{$i + 1} != ' ') |
||
| 1331 | $in_html = true; |
||
| 1332 | // Assume we need an entity... |
||
| 1333 | else |
||
| 1334 | { |
||
| 1335 | $new_string .= '<'; |
||
| 1336 | continue; |
||
| 1337 | } |
||
| 1338 | } |
||
| 1339 | // What about gt? |
||
| 1340 | View Code Duplication | elseif ($string{$i} == '>') |
|
| 1341 | { |
||
| 1342 | // Will it be HTML? |
||
| 1343 | if ($in_html) |
||
| 1344 | $in_html = false; |
||
| 1345 | // Otherwise we need an entity... |
||
| 1346 | else |
||
| 1347 | { |
||
| 1348 | $new_string .= '>'; |
||
| 1349 | continue; |
||
| 1350 | } |
||
| 1351 | } |
||
| 1352 | // Is it a slash? If so escape it... |
||
| 1353 | if ($string{$i} == '\\') |
||
| 1354 | $new_string .= '\\'; |
||
| 1355 | // The infamous double quote? |
||
| 1356 | elseif ($string{$i} == '"') |
||
| 1357 | { |
||
| 1358 | // If we're in HTML we leave it as a quote - otherwise we entity it. |
||
| 1359 | if (!$in_html) |
||
| 1360 | { |
||
| 1361 | $new_string .= '"'; |
||
| 1362 | continue; |
||
| 1363 | } |
||
| 1364 | } |
||
| 1365 | // A single quote? |
||
| 1366 | elseif ($string{$i} == '\'') |
||
| 1367 | { |
||
| 1368 | // Must be in a string so escape it. |
||
| 1369 | $new_string .= '\\'; |
||
| 1370 | } |
||
| 1371 | |||
| 1372 | // Finally add the character to the string! |
||
| 1373 | $new_string .= $string{$i}; |
||
| 1374 | } |
||
| 1375 | |||
| 1376 | // If we ended as a string then close it off. |
||
| 1377 | if ($in_string == 1) |
||
| 1378 | $new_string .= '\''; |
||
| 1379 | elseif ($in_string == 2) |
||
| 1380 | $new_string .= '"'; |
||
| 1381 | } |
||
| 1382 | |||
| 1383 | return $new_string; |
||
| 1384 | } |
||
| 1385 | |||
| 1386 | ?> |