1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file handles the administration of languages tasks. |
5
|
|
|
* |
6
|
|
|
* @package ElkArte Forum |
7
|
|
|
* @copyright ElkArte Forum contributors |
8
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
9
|
|
|
* |
10
|
|
|
* This file contains code covered by: |
11
|
|
|
* copyright: 2011 Simple Machines (http://www.simplemachines.org) |
12
|
|
|
* |
13
|
|
|
* @version 2.0 dev |
14
|
|
|
* |
15
|
|
|
*/ |
16
|
|
|
|
17
|
|
|
namespace ElkArte\AdminController; |
18
|
|
|
|
19
|
|
|
use ElkArte\AbstractController; |
20
|
|
|
use ElkArte\Action; |
21
|
|
|
use ElkArte\Cache\Cache; |
22
|
|
|
use ElkArte\Exceptions\Exception; |
23
|
|
|
use ElkArte\SettingsForm\SettingsForm; |
24
|
|
|
use ElkArte\Languages\Txt; |
25
|
|
|
use ElkArte\Util; |
26
|
|
|
use ElkArte\Languages\Editor as LangEditor; |
27
|
|
|
use ElkArte\Languages\Loader as LangLoader; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Manage languages controller class. |
31
|
|
|
* |
32
|
|
|
* @package Languages |
33
|
|
|
*/ |
34
|
|
|
class ManageLanguages extends AbstractController |
35
|
|
|
{ |
36
|
|
|
/** |
37
|
|
|
* This is the main function for the languages area. |
38
|
|
|
* |
39
|
|
|
* What it does: |
40
|
|
|
* |
41
|
|
|
* - It dispatches the requests. |
42
|
|
|
* - Loads the ManageLanguages template. (sub-actions will use it) |
43
|
|
|
* |
44
|
|
|
* @event integrate_sa_manage_languages Used to add more sub actions |
45
|
|
|
* @uses ManageSettings language file |
46
|
|
|
* @see \ElkArte\AbstractController::action_index() |
47
|
|
|
*/ |
48
|
|
|
public function action_index() |
49
|
|
|
{ |
50
|
|
|
global $context, $txt; |
51
|
|
|
|
52
|
|
|
theme()->getTemplates()->load('ManageLanguages'); |
53
|
|
|
Txt::load('ManageSettings'); |
54
|
|
|
|
55
|
|
|
$subActions = array( |
56
|
|
|
'edit' => array($this, 'action_edit', 'permission' => 'admin_forum'), |
57
|
|
|
'settings' => array($this, 'action_languageSettings_display', 'permission' => 'admin_forum'), |
58
|
|
|
'downloadlang' => array($this, 'action_downloadlang', 'permission' => 'admin_forum'), |
59
|
|
|
'editlang' => array($this, 'action_editlang', 'permission' => 'admin_forum'), |
60
|
|
|
); |
61
|
|
|
|
62
|
|
|
// Get ready for action |
63
|
|
|
$action = new Action('manage_languages'); |
64
|
|
|
|
65
|
|
|
// Load up all the tabs... |
66
|
|
|
$context[$context['admin_menu_name']]['tab_data'] = array( |
67
|
|
|
'title' => $txt['language_configuration'], |
68
|
|
|
'description' => $txt['language_description'], |
69
|
|
|
); |
70
|
|
|
|
71
|
|
|
// By default we're managing languages, call integrate_sa_manage_languages |
72
|
|
|
$subAction = $action->initialize($subActions, 'edit'); |
73
|
|
|
|
74
|
|
|
// Some final bits |
75
|
|
|
$context['sub_action'] = $subAction; |
76
|
|
|
$context['page_title'] = $txt['edit_languages']; |
77
|
|
|
$context['sub_template'] = 'show_settings'; |
78
|
|
|
|
79
|
|
|
// Call the right function for this sub-action. |
80
|
|
|
$action->dispatch($subAction); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Interface for adding a new language. |
85
|
|
|
* |
86
|
|
|
* @uses ManageLanguages template, add_language sub-template. |
87
|
|
|
*/ |
88
|
|
|
public function action_add() |
89
|
|
|
{ |
90
|
|
|
global $context, $txt; |
91
|
|
|
|
92
|
|
|
// Are we searching for new languages on the site? |
93
|
|
|
if (!empty($this->_req->post->lang_add_sub)) |
94
|
|
|
{ |
95
|
|
|
// Need fetch_web_data. |
96
|
|
|
require_once(SUBSDIR . '/Package.subs.php'); |
97
|
|
|
require_once(SUBSDIR . '/Language.subs.php'); |
98
|
|
|
|
99
|
|
|
$context['elk_search_term'] = $this->_req->getPost('lang_add', 'trim|htmlspecialchars[ENT_COMPAT]'); |
100
|
|
|
|
101
|
|
|
$listOptions = array( |
102
|
|
|
'id' => 'languages', |
103
|
|
|
'get_items' => array( |
104
|
|
|
'function' => 'list_getLanguagesList', |
105
|
|
|
), |
106
|
|
|
'columns' => array( |
107
|
|
|
'name' => array( |
108
|
|
|
'header' => array( |
109
|
|
|
'value' => $txt['name'], |
110
|
|
|
), |
111
|
|
|
'data' => array( |
112
|
|
|
'db' => 'name', |
113
|
|
|
), |
114
|
|
|
), |
115
|
|
|
'description' => array( |
116
|
|
|
'header' => array( |
117
|
|
|
'value' => $txt['add_language_elk_desc'], |
118
|
|
|
), |
119
|
|
|
'data' => array( |
120
|
|
|
'db' => 'description', |
121
|
|
|
), |
122
|
|
|
), |
123
|
|
|
'version' => array( |
124
|
|
|
'header' => array( |
125
|
|
|
'value' => $txt['add_language_elk_version'], |
126
|
|
|
), |
127
|
|
|
'data' => array( |
128
|
|
|
'db' => 'version', |
129
|
|
|
), |
130
|
|
|
), |
131
|
|
|
'utf8' => array( |
132
|
|
|
'header' => array( |
133
|
|
|
'value' => $txt['add_language_elk_utf8'], |
134
|
|
|
), |
135
|
|
|
'data' => array( |
136
|
|
|
'db' => 'utf8', |
137
|
|
|
), |
138
|
|
|
), |
139
|
|
|
'install_link' => array( |
140
|
|
|
'header' => array( |
141
|
|
|
'value' => $txt['add_language_elk_install'], |
142
|
|
|
'class' => 'centertext', |
143
|
|
|
), |
144
|
|
|
'data' => array( |
145
|
|
|
'db' => 'install_link', |
146
|
|
|
'class' => 'centertext', |
147
|
|
|
), |
148
|
|
|
), |
149
|
|
|
), |
150
|
|
|
); |
151
|
|
|
|
152
|
|
|
createList($listOptions); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$context['sub_template'] = 'add_language'; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* This lists all the current languages and allows editing of them. |
160
|
|
|
*/ |
161
|
|
|
public function action_edit() |
162
|
|
|
{ |
163
|
|
|
global $txt, $context, $language; |
164
|
|
|
|
165
|
|
|
require_once(SUBSDIR . '/Language.subs.php'); |
166
|
|
|
|
167
|
|
|
// Setting a new default? |
168
|
|
|
if (!empty($this->_req->post->set_default) && !empty($this->_req->post->def_language)) |
169
|
|
|
{ |
170
|
|
|
checkSession(); |
171
|
|
|
validateToken('admin-lang'); |
172
|
|
|
|
173
|
|
|
$lang_exists = false; |
174
|
|
|
$available_langs = getLanguages(); |
175
|
|
|
foreach ($available_langs as $lang) |
176
|
|
|
{ |
177
|
|
|
if ($this->_req->post->def_language === $lang['filename']) |
178
|
|
|
{ |
179
|
|
|
$lang_exists = true; |
180
|
|
|
break; |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
if ($this->_req->post->def_language !== $language && $lang_exists) |
185
|
|
|
{ |
186
|
|
|
$language = $this->_req->post->def_language; |
187
|
|
|
$this->updateLanguage($language); |
188
|
|
|
redirectexit('action=admin;area=languages;sa=edit'); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
// Create another one time token here. |
193
|
|
|
createToken('admin-lang'); |
194
|
|
|
createToken('admin-ssc'); |
195
|
|
|
|
196
|
|
|
$listOptions = array( |
197
|
|
|
'id' => 'language_list', |
198
|
|
|
'items_per_page' => 20, |
199
|
|
|
'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'languages']), |
200
|
|
|
'title' => $txt['edit_languages'], |
201
|
|
|
'data_check' => array( |
202
|
|
|
'class' => function ($rowData) { |
203
|
|
|
if ($rowData['default']) |
204
|
|
|
{ |
205
|
|
|
return 'highlight2'; |
206
|
|
|
} |
207
|
|
|
else |
208
|
|
|
{ |
209
|
|
|
return ''; |
210
|
|
|
} |
211
|
|
|
}, |
212
|
|
|
), |
213
|
|
|
'get_items' => array( |
214
|
|
|
'function' => 'list_getLanguages', |
215
|
|
|
), |
216
|
|
|
'get_count' => array( |
217
|
|
|
'function' => 'list_getNumLanguages', |
218
|
|
|
), |
219
|
|
|
'columns' => array( |
220
|
|
|
'default' => array( |
221
|
|
|
'header' => array( |
222
|
|
|
'value' => $txt['languages_default'], |
223
|
|
|
'class' => 'centertext', |
224
|
|
|
), |
225
|
|
|
'data' => array( |
226
|
|
|
'function' => function ($rowData) { |
227
|
|
|
return '<input type="radio" name="def_language" value="' . $rowData['id'] . '" ' . ($rowData['default'] ? 'checked="checked"' : '') . ' class="input_radio" />'; |
228
|
|
|
}, |
229
|
|
|
'style' => 'width: 8%;', |
230
|
|
|
'class' => 'centertext', |
231
|
|
|
), |
232
|
|
|
), |
233
|
|
|
'name' => array( |
234
|
|
|
'header' => array( |
235
|
|
|
'value' => $txt['languages_lang_name'], |
236
|
|
|
), |
237
|
|
|
'data' => array( |
238
|
|
|
'function' => function ($rowData) { |
239
|
|
|
return sprintf('<a href="%1$s">%2$s<i class="icon icon-small i-modify"></i></a>', getUrl('admin', ['action' => 'admin', 'area' => 'languages', 'sa' => 'editlang', 'lid' => $rowData['id']]), $rowData['name']); |
240
|
|
|
}, |
241
|
|
|
), |
242
|
|
|
), |
243
|
|
|
'count' => array( |
244
|
|
|
'header' => array( |
245
|
|
|
'value' => $txt['languages_users'], |
246
|
|
|
), |
247
|
|
|
'data' => array( |
248
|
|
|
'db_htmlsafe' => 'count', |
249
|
|
|
), |
250
|
|
|
), |
251
|
|
|
'locale' => array( |
252
|
|
|
'header' => array( |
253
|
|
|
'value' => $txt['languages_locale'], |
254
|
|
|
), |
255
|
|
|
'data' => array( |
256
|
|
|
'db_htmlsafe' => 'locale', |
257
|
|
|
), |
258
|
|
|
), |
259
|
|
|
), |
260
|
|
|
'form' => array( |
261
|
|
|
'href' => getUrl('admin', ['action' => 'admin', 'area' => 'languages']), |
262
|
|
|
'token' => 'admin-lang', |
263
|
|
|
), |
264
|
|
|
'additional_rows' => array( |
265
|
|
|
array( |
266
|
|
|
'class' => 'submitbutton', |
267
|
|
|
'position' => 'bottom_of_list', |
268
|
|
|
'value' => ' |
269
|
|
|
<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" /> |
270
|
|
|
<input type="submit" name="set_default" value="' . $txt['save'] . '"' . (is_writable(BOARDDIR . '/Settings.php') ? '' : ' disabled="disabled"') . ' /> |
271
|
|
|
<input type="hidden" name="' . $context['admin-ssc_token_var'] . '" value="' . $context['admin-ssc_token'] . '" />', |
272
|
|
|
), |
273
|
|
|
), |
274
|
|
|
// For highlighting the default. |
275
|
|
|
'javascript' => ' |
276
|
|
|
initHighlightSelection(\'language_list\'); |
277
|
|
|
', |
278
|
|
|
); |
279
|
|
|
|
280
|
|
|
// Display a warning if we cannot edit the default setting. |
281
|
|
|
if (!is_writable(BOARDDIR . '/Settings.php')) |
282
|
|
|
{ |
283
|
|
|
$listOptions['additional_rows'][] = array( |
284
|
|
|
'position' => 'after_title', |
285
|
|
|
'value' => $txt['language_settings_writable'], |
286
|
|
|
'class' => 'smalltext alert', |
287
|
|
|
); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
createList($listOptions); |
291
|
|
|
|
292
|
|
|
$context['sub_template'] = 'show_list'; |
293
|
|
|
$context['default_list'] = 'language_list'; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Update the language in use |
298
|
|
|
* |
299
|
|
|
* @param string $language |
300
|
|
|
*/ |
301
|
|
|
private function updateLanguage($language) |
302
|
|
|
{ |
303
|
|
|
$configVars = array( |
304
|
|
|
array('language', '', 'file') |
305
|
|
|
); |
306
|
|
|
$configValues = array( |
307
|
|
|
'language' => $language |
308
|
|
|
); |
309
|
|
|
$settingsForm = new SettingsForm(SettingsForm::FILE_ADAPTER); |
310
|
|
|
$settingsForm->setConfigVars($configVars); |
311
|
|
|
$settingsForm->setConfigValues((array) $configValues); |
312
|
|
|
$settingsForm->save(); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Download a language file from the website. |
317
|
|
|
* |
318
|
|
|
* What it does: |
319
|
|
|
* |
320
|
|
|
* - Requires a valid download ID ("did") in the URL. |
321
|
|
|
* - Also handles installing language files. |
322
|
|
|
* - Attempts to chmod things as needed. |
323
|
|
|
* - Uses a standard list to display information about all the files and where they'll be put. |
324
|
|
|
* |
325
|
|
|
* @uses ManageLanguages template, download_language sub-template. |
326
|
|
|
* @uses Admin template, show_list sub-template. |
327
|
|
|
*/ |
328
|
|
|
public function action_downloadlang() |
329
|
|
|
{ |
330
|
|
|
// @todo for the moment there is no facility to download packages, so better kill it here |
331
|
|
|
throw new Exception('no_access', false); |
332
|
|
|
|
333
|
|
|
Txt::load('ManageSettings'); |
|
|
|
|
334
|
|
|
require_once(SUBSDIR . '/Package.subs.php'); |
335
|
|
|
|
336
|
|
|
// Clearly we need to know what to request. |
337
|
|
|
if (!isset($this->_req->query->did)) |
338
|
|
|
{ |
339
|
|
|
throw new Exception('no_access', false); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
// Some lovely context. |
343
|
|
|
$context['download_id'] = $this->_req->query->did; |
344
|
|
|
$context['sub_template'] = 'download_language'; |
345
|
|
|
$context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'add'; |
346
|
|
|
|
347
|
|
|
// Can we actually do the installation - and do they want to? |
348
|
|
|
if (!empty($this->_req->post->do_install) && !empty($this->_req->post->copy_file)) |
349
|
|
|
{ |
350
|
|
|
checkSession('get'); |
351
|
|
|
validateToken('admin-dlang'); |
352
|
|
|
|
353
|
|
|
$chmod_files = array(); |
354
|
|
|
$install_files = array(); |
355
|
|
|
|
356
|
|
|
// Check writable status. |
357
|
|
|
foreach ($this->_req->post->copy_file as $file) |
358
|
|
|
{ |
359
|
|
|
// Check it's not very bad. |
360
|
|
|
if (strpos($file, '..') !== false || (strpos($file, 'themes') !== 0 && !preg_match('~agreement\.[A-Za-z-_0-9]+\.txt$~', $file))) |
361
|
|
|
{ |
362
|
|
|
throw new Exception($txt['languages_download_illegal_paths']); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
$chmod_files[] = BOARDDIR . '/' . $file; |
366
|
|
|
$install_files[] = $file; |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
// Call this in case we have work to do. |
370
|
|
|
$file_status = create_chmod_control($chmod_files); |
371
|
|
|
$files_left = $file_status['files']['notwritable']; |
372
|
|
|
|
373
|
|
|
// Something not writable? |
374
|
|
|
if (!empty($files_left)) |
375
|
|
|
{ |
376
|
|
|
$context['error_message'] = $txt['languages_download_not_chmod']; |
377
|
|
|
} |
378
|
|
|
// Otherwise, go go go! |
379
|
|
|
elseif (!empty($install_files)) |
380
|
|
|
{ |
381
|
|
|
// @todo retrieve the language pack per naming pattern from our sites |
382
|
|
|
read_tgz_file('http://download.elkarte.net/fetch_language.php?version=' . urlencode(strtr(FORUM_VERSION, array('ElkArte ' => ''))) . ';fetch=' . urlencode($this->_req->query->did), BOARDDIR, false, true, $install_files); |
383
|
|
|
|
384
|
|
|
// Make sure the files aren't stuck in the cache. |
385
|
|
|
package_flush_cache(); |
386
|
|
|
$context['install_complete'] = sprintf($txt['languages_download_complete_desc'], getUrl('admin', ['action' => 'admin', 'area' => 'languages'])); |
387
|
|
|
|
388
|
|
|
return; |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
// @todo Open up the old china. |
393
|
|
|
$archive_content = read_tgz_file('http://download.elkarte.net/fetch_language.php?version=' . urlencode(strtr(FORUM_VERSION, array('ElkArte ' => ''))) . ';fetch=' . urlencode($this->_req->query->did), null); |
394
|
|
|
|
395
|
|
|
if (empty($archive_content)) |
396
|
|
|
{ |
397
|
|
|
throw new Exception($txt['add_language_error_no_response']); |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
// Now for each of the files, let's do some *stuff* |
401
|
|
|
$context['files'] = array( |
402
|
|
|
'lang' => array(), |
403
|
|
|
'other' => array(), |
404
|
|
|
); |
405
|
|
|
$context['make_writable'] = array(); |
406
|
|
|
foreach ($archive_content as $file) |
407
|
|
|
{ |
408
|
|
|
$dirname = dirname($file['filename']); |
409
|
|
|
$filename = basename($file['filename']); |
410
|
|
|
$extension = substr($filename, strrpos($filename, '.') + 1); |
411
|
|
|
|
412
|
|
|
// Don't do anything with files we don't understand. |
413
|
|
|
if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt'))) |
414
|
|
|
{ |
415
|
|
|
continue; |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
// Basic data. |
419
|
|
|
$context_data = array( |
420
|
|
|
'name' => $filename, |
421
|
|
|
'destination' => BOARDDIR . '/' . $file['filename'], |
422
|
|
|
'generaldest' => $file['filename'], |
423
|
|
|
'size' => $file['size'], |
424
|
|
|
// Does chmod status allow the copy? |
425
|
|
|
'writable' => false, |
426
|
|
|
// Should we suggest they copy this file? |
427
|
|
|
'default_copy' => true, |
428
|
|
|
// Does the file already exist, if so is it same or different? |
429
|
|
|
'exists' => false, |
430
|
|
|
); |
431
|
|
|
|
432
|
|
|
// Does the file exist, is it different and can we overwrite? |
433
|
|
|
if (file_exists(BOARDDIR . '/' . $file['filename'])) |
434
|
|
|
{ |
435
|
|
|
if (is_writable(BOARDDIR . '/' . $file['filename'])) |
436
|
|
|
{ |
437
|
|
|
$context_data['writable'] = true; |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
// Finally, do we actually think the content has changed? |
441
|
|
|
if ($file['size'] == filesize(BOARDDIR . '/' . $file['filename']) && $file['md5'] === md5_file(BOARDDIR . '/' . $file['filename'])) |
442
|
|
|
{ |
443
|
|
|
$context_data['exists'] = 'same'; |
444
|
|
|
$context_data['default_copy'] = false; |
445
|
|
|
} |
446
|
|
|
// Attempt to discover newline character differences. |
447
|
|
|
elseif ($file['md5'] === md5(preg_replace("~[\r]?\n~", "\r\n", file_get_contents(BOARDDIR . '/' . $file['filename'])))) |
448
|
|
|
{ |
449
|
|
|
$context_data['exists'] = 'same'; |
450
|
|
|
$context_data['default_copy'] = false; |
451
|
|
|
} |
452
|
|
|
else |
453
|
|
|
{ |
454
|
|
|
$context_data['exists'] = 'different'; |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
// No overwrite? |
458
|
|
|
elseif (is_writable(BOARDDIR . '/' . $dirname)) |
459
|
|
|
{ |
460
|
|
|
// Can we at least stick it in the directory... |
461
|
|
|
$context_data['writable'] = true; |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
// 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... |
465
|
|
|
if ($extension === 'php' && preg_match('~\w+\.\w+(?:-utf8)?\.php~', $filename)) |
466
|
|
|
{ |
467
|
|
|
$context_data += array( |
468
|
|
|
'version' => '??', |
469
|
|
|
'cur_version' => false, |
470
|
|
|
'version_compare' => 'newer', |
471
|
|
|
); |
472
|
|
|
|
473
|
|
|
list ($name,) = explode('.', $filename); |
474
|
|
|
|
475
|
|
|
// Let's get the new version, I like versions, they tell me that I'm up to date. |
476
|
|
|
if (preg_match('~\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '~i', $file['preview'], $match) == 1) |
477
|
|
|
{ |
478
|
|
|
$context_data['version'] = $match[1]; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
// Now does the old file exist - if so what is it's version? |
482
|
|
|
if (file_exists(BOARDDIR . '/' . $file['filename'])) |
483
|
|
|
{ |
484
|
|
|
// OK - what is the current version? |
485
|
|
|
$fp = fopen(BOARDDIR . '/' . $file['filename'], 'rb'); |
486
|
|
|
$header = fread($fp, 768); |
487
|
|
|
fclose($fp); |
488
|
|
|
|
489
|
|
|
// Find the version. |
490
|
|
|
if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1) |
491
|
|
|
{ |
492
|
|
|
$context_data['cur_version'] = $match[1]; |
493
|
|
|
|
494
|
|
|
// How does this compare? |
495
|
|
|
if ($context_data['cur_version'] === $context_data['version']) |
496
|
|
|
{ |
497
|
|
|
$context_data['version_compare'] = 'same'; |
498
|
|
|
} |
499
|
|
|
elseif ($context_data['cur_version'] > $context_data['version']) |
500
|
|
|
{ |
501
|
|
|
$context_data['version_compare'] = 'older'; |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
// Don't recommend copying if the version is the same. |
505
|
|
|
if ($context_data['version_compare'] != 'newer') |
506
|
|
|
{ |
507
|
|
|
$context_data['default_copy'] = false; |
508
|
|
|
} |
509
|
|
|
} |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
// Add the context data to the main set. |
513
|
|
|
$context['files']['lang'][] = $context_data; |
514
|
|
|
} |
515
|
|
|
else |
516
|
|
|
{ |
517
|
|
|
// If we think it's a theme thing, work out what the theme is. |
518
|
|
|
if (strpos($dirname, 'themes') === 0 && preg_match('~themes[\\/]([^\\/]+)[\\/]~', $dirname, $match)) |
519
|
|
|
{ |
520
|
|
|
$theme_name = $match[1]; |
521
|
|
|
} |
522
|
|
|
else |
523
|
|
|
{ |
524
|
|
|
$theme_name = 'misc'; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
// Assume it's an image, could be an acceptance note etc but rare. |
528
|
|
|
$context['files']['images'][$theme_name][] = $context_data; |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
// Collect together all non-writable areas. |
532
|
|
|
if (!$context_data['writable']) |
533
|
|
|
{ |
534
|
|
|
$context['make_writable'][] = $context_data['destination']; |
535
|
|
|
} |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
// So, I'm a perfectionist - let's get the theme names. |
539
|
|
|
$indexes = array(); |
540
|
|
|
foreach ($context['files']['images'] as $k => $dummy) |
541
|
|
|
{ |
542
|
|
|
$indexes[] = $k; |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
$context['theme_names'] = array(); |
546
|
|
|
if (!empty($indexes)) |
547
|
|
|
{ |
548
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
549
|
|
|
$value_data = array( |
550
|
|
|
'query' => array(), |
551
|
|
|
'params' => array(), |
552
|
|
|
); |
553
|
|
|
|
554
|
|
|
foreach ($indexes as $k => $index) |
555
|
|
|
{ |
556
|
|
|
$value_data['query'][] = 'value LIKE {string:value_' . $k . '}'; |
557
|
|
|
$value_data['params']['value_' . $k] = '%' . $index; |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
$themes = validateThemeName($indexes, $value_data); |
561
|
|
|
|
562
|
|
|
// Now we have the id_theme we can get the pretty description. |
563
|
|
|
if (!empty($themes)) |
564
|
|
|
{ |
565
|
|
|
$context['theme_names'] = getBasicThemeInfos($themes); |
566
|
|
|
} |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
// Before we go to far can we make anything writable, eh, eh? |
570
|
|
|
if (!empty($context['make_writable'])) |
571
|
|
|
{ |
572
|
|
|
// What is left to be made writable? |
573
|
|
|
$file_status = create_chmod_control($context['make_writable']); |
574
|
|
|
$context['still_not_writable'] = $file_status['files']['notwritable']; |
575
|
|
|
|
576
|
|
|
// Mark those which are now writable as such. |
577
|
|
|
foreach ($context['files'] as $type => $data) |
578
|
|
|
{ |
579
|
|
|
if ($type == 'lang') |
580
|
|
|
{ |
581
|
|
|
foreach ($data as $k => $file) |
582
|
|
|
{ |
583
|
|
|
if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) |
584
|
|
|
{ |
585
|
|
|
$context['files'][$type][$k]['writable'] = true; |
586
|
|
|
} |
587
|
|
|
} |
588
|
|
|
} |
589
|
|
|
else |
590
|
|
|
{ |
591
|
|
|
foreach ($data as $theme => $files) |
592
|
|
|
{ |
593
|
|
|
foreach ($files as $k => $file) |
594
|
|
|
{ |
595
|
|
|
if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable'])) |
596
|
|
|
{ |
597
|
|
|
$context['files'][$type][$theme][$k]['writable'] = true; |
598
|
|
|
} |
599
|
|
|
} |
600
|
|
|
} |
601
|
|
|
} |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
// Are we going to need more language stuff? |
605
|
|
|
if (!empty($context['still_not_writable'])) |
606
|
|
|
{ |
607
|
|
|
Txt::load('Packages'); |
608
|
|
|
} |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
// This is the list for the main files. |
612
|
|
|
$listOptions = array( |
613
|
|
|
'id' => 'lang_main_files_list', |
614
|
|
|
'title' => $txt['languages_download_main_files'], |
615
|
|
|
'get_items' => array( |
616
|
|
|
'function' => function () { |
617
|
|
|
global $context; |
618
|
|
|
|
619
|
|
|
return $context['files']['lang']; |
620
|
|
|
}, |
621
|
|
|
), |
622
|
|
|
'columns' => array( |
623
|
|
|
'name' => array( |
624
|
|
|
'header' => array( |
625
|
|
|
'value' => $txt['languages_download_filename'], |
626
|
|
|
), |
627
|
|
|
'data' => array( |
628
|
|
|
'function' => function ($rowData) { |
629
|
|
|
global $txt; |
630
|
|
|
|
631
|
|
|
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'] : ''); |
632
|
|
|
}, |
633
|
|
|
), |
634
|
|
|
), |
635
|
|
|
'writable' => array( |
636
|
|
|
'header' => array( |
637
|
|
|
'value' => $txt['languages_download_writable'], |
638
|
|
|
), |
639
|
|
|
'data' => array( |
640
|
|
|
'function' => function ($rowData) { |
641
|
|
|
global $txt; |
642
|
|
|
|
643
|
|
|
return '<span class="' . ($rowData['writable'] ? 'success' : 'error') . ';">' . ($rowData['writable'] ? $txt['yes'] : $txt['no']) . '</span>'; |
644
|
|
|
}, |
645
|
|
|
), |
646
|
|
|
), |
647
|
|
|
'version' => array( |
648
|
|
|
'header' => array( |
649
|
|
|
'value' => $txt['languages_download_version'], |
650
|
|
|
), |
651
|
|
|
'data' => array( |
652
|
|
|
'function' => function ($rowData) { |
653
|
|
|
return '<span class="' . ($rowData['version_compare'] == 'older' ? 'error' : ($rowData['version_compare'] == 'same' ? 'softalert' : 'success')) . ';">' . $rowData['version'] . '</span>'; |
654
|
|
|
}, |
655
|
|
|
), |
656
|
|
|
), |
657
|
|
|
'exists' => array( |
658
|
|
|
'header' => array( |
659
|
|
|
'value' => $txt['languages_download_exists'], |
660
|
|
|
), |
661
|
|
|
'data' => array( |
662
|
|
|
'function' => function ($rowData) { |
663
|
|
|
global $txt; |
664
|
|
|
|
665
|
|
|
return $rowData['exists'] ? ($rowData['exists'] == 'same' ? $txt['languages_download_exists_same'] : $txt['languages_download_exists_different']) : $txt['no']; |
666
|
|
|
}, |
667
|
|
|
), |
668
|
|
|
), |
669
|
|
|
'copy' => array( |
670
|
|
|
'header' => array( |
671
|
|
|
'value' => $txt['languages_download_copy'], |
672
|
|
|
'class' => 'centertext', |
673
|
|
|
), |
674
|
|
|
'data' => array( |
675
|
|
|
'function' => function ($rowData) { |
676
|
|
|
return '<input type="checkbox" name="copy_file[]" value="' . $rowData['generaldest'] . '" ' . ($rowData['default_copy'] ? 'checked="checked"' : '') . ' class="input_check" />'; |
677
|
|
|
}, |
678
|
|
|
'style' => 'width: 4%;', |
679
|
|
|
'class' => 'centertext', |
680
|
|
|
), |
681
|
|
|
), |
682
|
|
|
), |
683
|
|
|
); |
684
|
|
|
|
685
|
|
|
// Kill the cache, as it is now invalid.. |
686
|
|
|
$cache = Cache::instance(); |
687
|
|
|
$cache->put('known_languages', null, $cache->maxLevel(1) ? 86400 : 3600); |
688
|
|
|
|
689
|
|
|
createList($listOptions); |
690
|
|
|
|
691
|
|
|
createToken('admin-dlang'); |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
/** |
695
|
|
|
* Edit a particular set of language entries. |
696
|
|
|
*/ |
697
|
|
|
public function action_editlang() |
698
|
|
|
{ |
699
|
|
|
global $settings, $context, $txt; |
700
|
|
|
|
701
|
|
|
$base_lang_dir = SOURCEDIR . '/ElkArte/Languages'; |
702
|
|
|
require_once(SUBSDIR . '/Language.subs.php'); |
703
|
|
|
Txt::load('ManageSettings'); |
704
|
|
|
|
705
|
|
|
// Select the languages tab. |
706
|
|
|
$context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit'; |
707
|
|
|
$context['page_title'] = $txt['edit_languages']; |
708
|
|
|
$context['sub_template'] = 'modify_language_entries'; |
709
|
|
|
|
710
|
|
|
$context['lang_id'] = $this->_req->query->lid; |
711
|
|
|
$file_id = !empty($this->_req->post->tfid) ? $this->_req->post->tfid : ''; |
712
|
|
|
|
713
|
|
|
// Clean the ID - just in case. |
714
|
|
|
preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches); |
715
|
|
|
$context['lang_id'] = $matches[1]; |
716
|
|
|
$matches = ''; |
717
|
|
|
preg_match('~([A-Za-z0-9_-]+)~', $file_id, $matches); |
|
|
|
|
718
|
|
|
$file_id = ucfirst($matches[1] ?? ''); |
719
|
|
|
|
720
|
|
|
// This will be where we look |
721
|
|
|
$lang_dirs = glob($base_lang_dir . '/*', GLOB_ONLYDIR); |
722
|
|
|
$images_dirs = array(); |
|
|
|
|
723
|
|
|
|
724
|
|
|
$current_file = $file_id ? $base_lang_dir . '/' . $file_id . '/' . ucfirst($context['lang_id']) . '.php' : ''; |
|
|
|
|
725
|
|
|
|
726
|
|
|
// Now for every theme get all the files and stick them in context! |
727
|
|
|
$context['possible_files'] = array_map(function($file) use ($file_id, $txt) { |
728
|
|
|
return [ |
729
|
|
|
'id' => basename($file, '.php'), |
730
|
|
|
'name' => $txt['lang_file_desc_' . basename($file)] ?? basename($file), |
731
|
|
|
'path' => $file, |
732
|
|
|
'selected' => $file_id == basename($file), |
733
|
|
|
]; |
734
|
|
|
}, $lang_dirs); |
735
|
|
|
|
736
|
|
|
if ($context['lang_id'] != 'english') |
737
|
|
|
{ |
738
|
|
|
$possiblePackage = findPossiblePackages($context['lang_id']); |
739
|
|
|
if ($possiblePackage !== false) |
|
|
|
|
740
|
|
|
{ |
741
|
|
|
$context['langpack_uninstall_link'] = getUrl('admin', ['action' => 'admin', 'area' => 'packages', 'sa' => 'uninstall', 'package' => $possiblePackage[1], 'pid' => $possiblePackage[0]]); |
742
|
|
|
} |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
// Quickly load index language entries. |
746
|
|
|
$mtxt = []; |
747
|
|
|
$new_lang = new LangLoader($context['lang_id'], $mtxt, database()); |
748
|
|
|
$new_lang->load('Index', true); |
749
|
|
|
|
750
|
|
|
// Setup the primary settings context. |
751
|
|
|
$context['primary_settings'] = array( |
752
|
|
|
'name' => Util::ucwords(strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))), |
753
|
|
|
'locale' => $mtxt['lang_locale'], |
754
|
|
|
'dictionary' => $mtxt['lang_dictionary'], |
755
|
|
|
'spelling' => $mtxt['lang_spelling'], |
756
|
|
|
'rtl' => $mtxt['lang_rtl'], |
757
|
|
|
); |
758
|
|
|
|
759
|
|
|
// Quickly load index language entries. |
760
|
|
|
$edit_lang = new LangEditor($context['lang_id'], database()); |
761
|
|
|
$edit_lang->load($file_id, true); |
|
|
|
|
762
|
|
|
|
763
|
|
|
$context['file_entries'] = $edit_lang->getForEditing(); |
764
|
|
|
|
765
|
|
|
// Are we saving? |
766
|
|
|
$save_strings = array(); |
|
|
|
|
767
|
|
|
if (isset($this->_req->post->save_entries) && !empty($this->_req->post->entry)) |
768
|
|
|
{ |
769
|
|
|
checkSession(); |
770
|
|
|
validateToken('admin-mlang'); |
771
|
|
|
|
772
|
|
|
$edit_lang->save($file_id, $this->_req->post->entry); |
773
|
|
|
|
774
|
|
|
redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']); |
775
|
|
|
} |
776
|
|
|
|
777
|
|
|
createToken('admin-mlang'); |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
/** |
781
|
|
|
* Edit language related settings. |
782
|
|
|
* |
783
|
|
|
* - Accessed by ?action=admin;area=languages;sa=settings |
784
|
|
|
* - This method handles the display, allows to edit, and saves the result |
785
|
|
|
* for the _languageSettings form. |
786
|
|
|
* |
787
|
|
|
* @event integrate_save_language_settings |
788
|
|
|
*/ |
789
|
|
|
public function action_languageSettings_display() |
790
|
|
|
{ |
791
|
|
|
global $context, $txt; |
792
|
|
|
|
793
|
|
|
// Initialize the form |
794
|
|
|
$settingsForm = new SettingsForm(SettingsForm::FILE_ADAPTER); |
795
|
|
|
|
796
|
|
|
// Initialize it with our settings |
797
|
|
|
$settingsForm->setConfigVars($this->_settings()); |
798
|
|
|
|
799
|
|
|
// Warn the user if the backup of Settings.php failed. |
800
|
|
|
$settings_not_writable = !is_writable(BOARDDIR . '/Settings.php'); |
801
|
|
|
$settings_backup_fail = !@is_writable(BOARDDIR . '/Settings_bak.php') || !@copy(BOARDDIR . '/Settings.php', BOARDDIR . '/Settings_bak.php'); |
802
|
|
|
|
803
|
|
|
// Saving settings? |
804
|
|
|
if (isset($this->_req->query->save)) |
805
|
|
|
{ |
806
|
|
|
checkSession(); |
807
|
|
|
|
808
|
|
|
call_integration_hook('integrate_save_language_settings'); |
809
|
|
|
|
810
|
|
|
$settingsForm->setConfigValues((array) $this->_req->post); |
811
|
|
|
$settingsForm->save(); |
812
|
|
|
redirectexit('action=admin;area=languages;sa=settings'); |
813
|
|
|
} |
814
|
|
|
|
815
|
|
|
// Setup the template stuff. |
816
|
|
|
$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'languages', 'sa' => 'settings', 'save']); |
817
|
|
|
$context['settings_title'] = $txt['language_settings']; |
818
|
|
|
$context['save_disabled'] = $settings_not_writable; |
819
|
|
|
|
820
|
|
|
if ($settings_not_writable) |
821
|
|
|
{ |
822
|
|
|
$context['error_type'] = 'notice'; |
823
|
|
|
$context['settings_message'] = $txt['settings_not_writable']; |
824
|
|
|
} |
825
|
|
|
elseif ($settings_backup_fail) |
826
|
|
|
{ |
827
|
|
|
$context['error_type'] = 'notice'; |
828
|
|
|
$context['settings_message'] = $txt['admin_backup_fail']; |
829
|
|
|
} |
830
|
|
|
|
831
|
|
|
// Fill the config array in contextual data for the template. |
832
|
|
|
$settingsForm->prepare(); |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
/** |
836
|
|
|
* Load up all of the language settings |
837
|
|
|
* |
838
|
|
|
* @event integrate_modify_language_settings Use to add new config options |
839
|
|
|
*/ |
840
|
|
|
private function _settings() |
841
|
|
|
{ |
842
|
|
|
global $txt; |
843
|
|
|
|
844
|
|
|
// Warn the user if the backup of Settings.php failed. |
845
|
|
|
$settings_not_writable = !is_writable(BOARDDIR . '/Settings.php'); |
846
|
|
|
|
847
|
|
|
$config_vars = array( |
848
|
|
|
'language' => array('language', $txt['default_language'], 'file', 'select', array(), null, 'disabled' => $settings_not_writable), |
849
|
|
|
array('userLanguage', $txt['userLanguage'], 'db', 'check', null, 'userLanguage'), |
850
|
|
|
); |
851
|
|
|
|
852
|
|
|
call_integration_hook('integrate_modify_language_settings', array(&$config_vars)); |
853
|
|
|
|
854
|
|
|
// Get our languages. No cache. |
855
|
|
|
$languages = getLanguages(false); |
856
|
|
|
foreach ($languages as $lang) |
857
|
|
|
{ |
858
|
|
|
$config_vars['language'][4][] = array($lang['filename'], strtr($lang['name'], array('-utf8' => ' (UTF-8)'))); |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
return $config_vars; |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
/** |
865
|
|
|
* Return the form settings for use in admin search |
866
|
|
|
*/ |
867
|
|
|
public function settings_search() |
868
|
|
|
{ |
869
|
|
|
return $this->_settings(); |
870
|
|
|
} |
871
|
|
|
} |
872
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.