Passed
Pull Request — development (#3540)
by Emanuele
07:12
created

ManageLanguages::_settings()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 0
dl 0
loc 22
ccs 0
cts 11
cp 0
crap 6
rs 9.9332
c 0
b 0
f 0
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;
0 ignored issues
show
Bug introduced by
The type ElkArte\Languages\Editor was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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');
0 ignored issues
show
Unused Code introduced by
ElkArte\Languages\Txt::load('ManageSettings') is not reachable.

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 or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

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.

Loading history...
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);
0 ignored issues
show
Bug introduced by
$matches of type string is incompatible with the type string[] expected by parameter $matches of preg_match(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

717
		preg_match('~([A-Za-z0-9_-]+)~', $file_id, /** @scrutinizer ignore-type */ $matches);
Loading history...
718
		$file_id = ucfirst($matches[1] ?? '');
719
720
		// Get all the theme data.
721
		require_once(SUBSDIR . '/Themes.subs.php');
722
		$themes = getCustomThemes();
0 ignored issues
show
Unused Code introduced by
The assignment to $themes is dead and can be removed.
Loading history...
723
724
		// This will be where we look
725
		$lang_dirs = glob($base_lang_dir . '/*', GLOB_ONLYDIR);
726
		$images_dirs = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $images_dirs is dead and can be removed.
Loading history...
727
728
		$current_file = $file_id ? $base_lang_dir . '/' . $file_id . '/' . ucfirst($context['lang_id']) . '.php' : '';
729
730
		// Now for every theme get all the files and stick them in context!
731
		$context['possible_files'] =  array_map(function($file) use ($file_id, $txt) {
732
			return [
733
				'id' => basename($file, '.php'),
734
				'name' => $txt['lang_file_desc_' . basename($file)] ?? basename($file),
735
				'path' => $file,
736
				'selected' => $file_id == basename($file),
737
			];
738
		}, $lang_dirs);
739
740
		if ($context['lang_id'] != 'english')
741
		{
742
			$possiblePackage = findPossiblePackages($context['lang_id']);
743
			if ($possiblePackage !== false)
0 ignored issues
show
introduced by
The condition $possiblePackage !== false is always true.
Loading history...
744
			{
745
				$context['langpack_uninstall_link'] = getUrl('admin', ['action' => 'admin', 'area' => 'packages', 'sa' => 'uninstall', 'package' => $possiblePackage[1], 'pid' => $possiblePackage[0]]);
746
			}
747
		}
748
749
		// Saving primary settings?
750
		$madeSave = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $madeSave is dead and can be removed.
Loading history...
751
		if (!empty($this->_req->post->save_main) && !$current_file)
752
		{
753
			checkSession();
754
			validateToken('admin-mlang');
755
756
			// Read in the current file.
757
			$current_data = implode('', file($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php'));
758
759
			// These are the replacements. old => new
760
			$replace_array = array(
761
				'~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . addslashes($this->_req->post->locale) . '\';',
762
				'~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . addslashes($this->_req->post->dictionary) . '\';',
763
				'~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . addslashes($this->_req->post->spelling) . '\';',
764
				'~\$txt\[\'lang_rtl\'\]\s=\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($this->_req->post->rtl) ? 'true' : 'false') . ';',
765
			);
766
			$current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data);
767
			$fp = fopen($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php', 'w+');
768
			fwrite($fp, $current_data);
769
			fclose($fp);
770
771
			if ($this->_checkOpcache())
772
			{
773
				opcache_invalidate($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php');
774
			}
775
776
			$madeSave = true;
777
		}
778
779
		// Quickly load index language entries.
780
		$mtxt = [];
781
		$new_lang = new LangLoader($context['lang_id'], $mtxt, database());
782
		$new_lang->load('Index', true);
783
784
		// Setup the primary settings context.
785
		$context['primary_settings'] = array(
786
			'name' => Util::ucwords(strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))),
787
			'locale' => $mtxt['lang_locale'],
788
			'dictionary' => $mtxt['lang_dictionary'],
789
			'spelling' => $mtxt['lang_spelling'],
790
			'rtl' => $mtxt['lang_rtl'],
791
		);
792
793
		// Quickly load index language entries.
794
		$edit_lang = new LangEditor($context['lang_id'], database());
795
		$edit_lang->load($file_id, true);
796
797
		$context['file_entries'] = $edit_lang->getForEditing();
798
799
		// Are we saving?
800
		$save_strings = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $save_strings is dead and can be removed.
Loading history...
801
		if (isset($this->_req->post->save_entries) && !empty($this->_req->post->entry))
802
		{
803
			checkSession();
804
			validateToken('admin-mlang');
805
806
			$edit_lang->save($file_id, $this->_req->post->entry);
807
808
			redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']);
809
		}
810
811
		createToken('admin-mlang');
812
	}
813
814
	/**
815
	 * Checks if the Zend Opcahce is installed, active and cmd functions available.
816
	 *
817
	 * @return bool
818
	 */
819
	private function _checkOpcache()
820
	{
821
		return (extension_loaded('Zend OPcache') && ini_get('opcache.enable') &&
822
			(ini_get('opcache.restrict_api') === '' || stripos(BOARDDIR, ini_get('opcache.restrict_api')) !== 0));
823
	}
824
825
	/**
826
	 * Edit language related settings.
827
	 *
828
	 * - Accessed by ?action=admin;area=languages;sa=settings
829
	 * - This method handles the display, allows to edit, and saves the result
830
	 * for the _languageSettings form.
831
	 *
832
	 * @event integrate_save_language_settings
833
	 */
834
	public function action_languageSettings_display()
835
	{
836
		global $context, $txt;
837
838
		// Initialize the form
839
		$settingsForm = new SettingsForm(SettingsForm::FILE_ADAPTER);
840
841
		// Initialize it with our settings
842
		$settingsForm->setConfigVars($this->_settings());
843
844
		// Warn the user if the backup of Settings.php failed.
845
		$settings_not_writable = !is_writable(BOARDDIR . '/Settings.php');
846
		$settings_backup_fail = !@is_writable(BOARDDIR . '/Settings_bak.php') || !@copy(BOARDDIR . '/Settings.php', BOARDDIR . '/Settings_bak.php');
847
848
		// Saving settings?
849
		if (isset($this->_req->query->save))
850
		{
851
			checkSession();
852
853
			call_integration_hook('integrate_save_language_settings');
854
855
			$settingsForm->setConfigValues((array) $this->_req->post);
856
			$settingsForm->save();
857
			redirectexit('action=admin;area=languages;sa=settings');
858
		}
859
860
		// Setup the template stuff.
861
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'languages', 'sa' => 'settings', 'save']);
862
		$context['settings_title'] = $txt['language_settings'];
863
		$context['save_disabled'] = $settings_not_writable;
864
865
		if ($settings_not_writable)
866
		{
867
			$context['error_type'] = 'notice';
868
			$context['settings_message'] = $txt['settings_not_writable'];
869
		}
870
		elseif ($settings_backup_fail)
871
		{
872
			$context['error_type'] = 'notice';
873
			$context['settings_message'] = $txt['admin_backup_fail'];
874
		}
875
876
		// Fill the config array in contextual data for the template.
877
		$settingsForm->prepare();
878
	}
879
880
	/**
881
	 * Load up all of the language settings
882
	 *
883
	 * @event integrate_modify_language_settings Use to add new config options
884
	 */
885
	private function _settings()
886
	{
887
		global $txt;
888
889
		// Warn the user if the backup of Settings.php failed.
890
		$settings_not_writable = !is_writable(BOARDDIR . '/Settings.php');
891
892
		$config_vars = array(
893
			'language' => array('language', $txt['default_language'], 'file', 'select', array(), null, 'disabled' => $settings_not_writable),
894
			array('userLanguage', $txt['userLanguage'], 'db', 'check', null, 'userLanguage'),
895
		);
896
897
		call_integration_hook('integrate_modify_language_settings', array(&$config_vars));
898
899
		// Get our languages. No cache.
900
		$languages = getLanguages(false);
901
		foreach ($languages as $lang)
902
		{
903
			$config_vars['language'][4][] = array($lang['filename'], strtr($lang['name'], array('-utf8' => ' (UTF-8)')));
904
		}
905
906
		return $config_vars;
907
	}
908
909
	/**
910
	 * Return the form settings for use in admin search
911
	 */
912
	public function settings_search()
913
	{
914
		return $this->_settings();
915
	}
916
}
917