Completed
Push — patch_1-1-4 ( 567711...ded5b7 )
by Emanuele
17s
created

ManageLanguages_Controller::_checkOpcache()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 2
nc 3
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file handles the administration of languages tasks.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1.4
15
 *
16
 */
17
18
/**
19
 * Manage languages controller class.
20
 *
21
 * @package Languages
22
 */
23
class ManageLanguages_Controller extends Action_Controller
24
{
25
	/**
26
	 * This is the main function for the languages area.
27
	 *
28
	 * What it does:
29
	 *
30
	 * - It dispatches the requests.
31
	 * - Loads the ManageLanguages template. (sub-actions will use it)
32
	 *
33
	 * @event integrate_sa_manage_languages Used to add more sub actions
34
	 * @uses ManageSettings language file
35
	 * @see Action_Controller::action_index()
36
	 */
37
	public function action_index()
38
	{
39
		global $context, $txt;
40
41
		loadTemplate('ManageLanguages');
42
		loadLanguage('ManageSettings');
43
44
		$subActions = array(
45
			'edit' => array($this, 'action_edit', 'permission' => 'admin_forum'),
46
			'settings' => array($this, 'action_languageSettings_display', 'permission' => 'admin_forum'),
47
			'downloadlang' => array($this, 'action_downloadlang', 'permission' => 'admin_forum'),
48
			'editlang' => array($this, 'action_editlang', 'permission' => 'admin_forum'),
49
		);
50
51
		// Get ready for action
52
		$action = new Action('manage_languages');
53
54
		// Load up all the tabs...
55
		$context[$context['admin_menu_name']]['tab_data'] = array(
56
			'title' => $txt['language_configuration'],
57
			'description' => $txt['language_description'],
58
		);
59
60
		// By default we're managing languages, call integrate_sa_manage_languages
61
		$subAction = $action->initialize($subActions, 'edit');
62
63
		// Some final bits
64
		$context['sub_action'] = $subAction;
65
		$context['page_title'] = $txt['edit_languages'];
66
		$context['sub_template'] = 'show_settings';
67
68
		// Call the right function for this sub-action.
69
		$action->dispatch($subAction);
70
	}
71
72
	/**
73
	 * Interface for adding a new language.
74
	 *
75
	 * @uses ManageLanguages template, add_language sub-template.
76
	 */
77
	public function action_add()
78
	{
79
		global $context, $txt;
80
81
		// Are we searching for new languages on the site?
82
		if (!empty($this->_req->post->lang_add_sub))
83
		{
84
			// Need fetch_web_data.
85
			require_once(SUBSDIR . '/Package.subs.php');
86
			require_once(SUBSDIR . '/Language.subs.php');
87
88
			$context['elk_search_term'] = $this->_req->getPost('lang_add', 'trim|htmlspecialchars[ENT_COMPAT]');
89
90
			$listOptions = array(
91
				'id' => 'languages',
92
				'get_items' => array(
93
					'function' => 'list_getLanguagesList',
94
				),
95
				'columns' => array(
96
					'name' => array(
97
						'header' => array(
98
							'value' => $txt['name'],
99
						),
100
						'data' => array(
101
							'db' => 'name',
102
						),
103
					),
104
					'description' => array(
105
						'header' => array(
106
							'value' => $txt['add_language_elk_desc'],
107
						),
108
						'data' => array(
109
							'db' => 'description',
110
						),
111
					),
112
					'version' => array(
113
						'header' => array(
114
							'value' => $txt['add_language_elk_version'],
115
						),
116
						'data' => array(
117
							'db' => 'version',
118
						),
119
					),
120
					'utf8' => array(
121
						'header' => array(
122
							'value' => $txt['add_language_elk_utf8'],
123
						),
124
						'data' => array(
125
							'db' => 'utf8',
126
						),
127
					),
128
					'install_link' => array(
129
						'header' => array(
130
							'value' => $txt['add_language_elk_install'],
131
							'class' => 'centertext',
132
						),
133
						'data' => array(
134
							'db' => 'install_link',
135
							'class' => 'centertext',
136
						),
137
					),
138
				),
139
			);
140
141
			createList($listOptions);
142
		}
143
144
		$context['sub_template'] = 'add_language';
145
	}
146
147
	/**
148
	 * This lists all the current languages and allows editing of them.
149
	 */
150
	public function action_edit()
151
	{
152
		global $txt, $context, $scripturl, $language;
153
154
		require_once(SUBSDIR . '/Language.subs.php');
155
156
		// Setting a new default?
157
		if (!empty($this->_req->post->set_default) && !empty($this->_req->post->def_language))
158
		{
159
			checkSession();
160
			validateToken('admin-lang');
161
162
			$lang_exists = false;
163
			$available_langs = getLanguages();
164
			foreach ($available_langs as $lang)
165
			{
166
				if ($this->_req->post->def_language == $lang['filename'])
167
				{
168
					$lang_exists = true;
169
					break;
170
				}
171
			}
172
173
			if ($this->_req->post->def_language != $language && $lang_exists)
174
			{
175
				$language = $this->_req->post->def_language;
176
				$this->updateLanguage($language);
177
				redirectexit('action=admin;area=languages;sa=edit');
178
			}
179
		}
180
181
		// Create another one time token here.
182
		createToken('admin-lang');
183
		createToken('admin-ssc');
184
185
		$listOptions = array(
186
			'id' => 'language_list',
187
			'items_per_page' => 20,
188
			'base_href' => $scripturl . '?action=admin;area=languages',
189
			'title' => $txt['edit_languages'],
190
			'data_check' => array(
191
				'class' => function ($rowData) {
192
					if ($rowData['default'])
193
						return 'highlight2';
194
					else
195
						return '';
196
				},
197
			),
198
			'get_items' => array(
199
				'function' => 'list_getLanguages',
200
			),
201
			'get_count' => array(
202
				'function' => 'list_getNumLanguages',
203
			),
204
			'columns' => array(
205
				'default' => array(
206
					'header' => array(
207
						'value' => $txt['languages_default'],
208
						'class' => 'centertext',
209
					),
210
					'data' => array(
211
						'function' => function ($rowData) {
212
							return '<input type="radio" name="def_language" value="' . $rowData['id'] . '" ' . ($rowData['default'] ? 'checked="checked"' : '') . ' class="input_radio" />';
213
						},
214
						'style' => 'width: 8%;',
215
						'class' => 'centertext',
216
					),
217
				),
218
				'name' => array(
219
					'header' => array(
220
						'value' => $txt['languages_lang_name'],
221
					),
222
					'data' => array(
223
						'function' => function ($rowData) {
224
							global $scripturl;
225
226
							return sprintf('<a href="%1$s?action=admin;area=languages;sa=editlang;lid=%2$s">%3$s<i class="icon icon-small i-modify"></i></a>', $scripturl, $rowData['id'], $rowData['name']);
227
						},
228
					),
229
				),
230
				'count' => array(
231
					'header' => array(
232
						'value' => $txt['languages_users'],
233
					),
234
					'data' => array(
235
						'db_htmlsafe' => 'count',
236
					),
237
				),
238
				'locale' => array(
239
					'header' => array(
240
						'value' => $txt['languages_locale'],
241
					),
242
					'data' => array(
243
						'db_htmlsafe' => 'locale',
244
					),
245
				),
246
			),
247
			'form' => array(
248
				'href' => $scripturl . '?action=admin;area=languages',
249
				'token' => 'admin-lang',
250
			),
251
			'additional_rows' => array(
252
				array(
253
					'class' => 'submitbutton',
254
					'position' => 'bottom_of_list',
255
					'value' => '
256
						<input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" />
257
						<input type="submit" name="set_default" value="' . $txt['save'] . '"' . (is_writable(BOARDDIR . '/Settings.php') ? '' : ' disabled="disabled"') . ' />
258
						<input type="hidden" name="' . $context['admin-ssc_token_var'] . '" value="' . $context['admin-ssc_token'] . '" />',
259
				),
260
			),
261
			// For highlighting the default.
262
			'javascript' => '
263
				initHighlightSelection(\'language_list\');
264
			',
265
		);
266
267
		// Display a warning if we cannot edit the default setting.
268
		if (!is_writable(BOARDDIR . '/Settings.php'))
269
			$listOptions['additional_rows'][] = array(
270
				'position' => 'after_title',
271
				'value' => $txt['language_settings_writable'],
272
				'class' => 'smalltext alert',
273
			);
274
275
		createList($listOptions);
276
277
		$context['sub_template'] = 'show_list';
278
		$context['default_list'] = 'language_list';
279
	}
280
281
	/**
282
	 * Download a language file from the website.
283
	 *
284
	 * What it does:
285
	 *
286
	 * - Requires a valid download ID ("did") in the URL.
287
	 * - Also handles installing language files.
288
	 * - Attempts to chmod things as needed.
289
	 * - Uses a standard list to display information about all the files and where they'll be put.
290
	 *
291
	 * @uses ManageLanguages template, download_language sub-template.
292
	 * @uses Admin template, show_list sub-template.
293
	 */
294
	public function action_downloadlang()
295
	{
296
		// @todo for the moment there is no facility to download packages, so better kill it here
297
		throw new Elk_Exception('no_access', false);
298
299
		loadLanguage('ManageSettings');
0 ignored issues
show
Unused Code introduced by
loadLanguage('ManageSettings'); does not seem to be 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...
300
		require_once(SUBSDIR . '/Package.subs.php');
301
302
		// Clearly we need to know what to request.
303
		if (!isset($this->_req->query->did))
304
			throw new Elk_Exception('no_access', false);
305
306
		// Some lovely context.
307
		$context['download_id'] = $this->_req->query->did;
308
		$context['sub_template'] = 'download_language';
309
		$context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'add';
310
311
		// Can we actually do the installation - and do they want to?
312
		if (!empty($this->_req->post->do_install) && !empty($this->_req->post->copy_file))
313
		{
314
			checkSession('get');
315
			validateToken('admin-dlang');
316
317
			$chmod_files = array();
318
			$install_files = array();
319
320
			// Check writable status.
321
			foreach ($this->_req->post->copy_file as $file)
322
			{
323
				// Check it's not very bad.
324
				if (strpos($file, '..') !== false || (strpos($file, 'themes') !== 0 && !preg_match('~agreement\.[A-Za-z-_0-9]+\.txt$~', $file)))
325
					throw new Elk_Exception($txt['languages_download_illegal_paths']);
326
327
				$chmod_files[] = BOARDDIR . '/' . $file;
328
				$install_files[] = $file;
329
			}
330
331
			// Call this in case we have work to do.
332
			$file_status = create_chmod_control($chmod_files);
333
			$files_left = $file_status['files']['notwritable'];
334
335
			// Something not writable?
336
			if (!empty($files_left))
337
				$context['error_message'] = $txt['languages_download_not_chmod'];
338
			// Otherwise, go go go!
339
			elseif (!empty($install_files))
340
			{
341
				// @todo retrieve the language pack per naming pattern from our sites
342
				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);
343
344
				// Make sure the files aren't stuck in the cache.
345
				package_flush_cache();
346
				$context['install_complete'] = sprintf($txt['languages_download_complete_desc'], $scripturl . '?action=admin;area=languages');
347
348
				return;
349
			}
350
		}
351
352
		// @todo Open up the old china.
353
		$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);
354
355
		if (empty($archive_content))
356
			throw new Elk_Exception($txt['add_language_error_no_response']);
357
358
		// Now for each of the files, let's do some *stuff*
359
		$context['files'] = array(
360
			'lang' => array(),
361
			'other' => array(),
362
		);
363
		$context['make_writable'] = array();
364
		foreach ($archive_content as $file)
365
		{
366
			$dirname = dirname($file['filename']);
367
			$filename = basename($file['filename']);
368
			$extension = substr($filename, strrpos($filename, '.') + 1);
369
370
			// Don't do anything with files we don't understand.
371
			if (!in_array($extension, array('php', 'jpg', 'gif', 'jpeg', 'png', 'txt')))
372
				continue;
373
374
			// Basic data.
375
			$context_data = array(
376
				'name' => $filename,
377
				'destination' => BOARDDIR . '/' . $file['filename'],
378
				'generaldest' => $file['filename'],
379
				'size' => $file['size'],
380
				// Does chmod status allow the copy?
381
				'writable' => false,
382
				// Should we suggest they copy this file?
383
				'default_copy' => true,
384
				// Does the file already exist, if so is it same or different?
385
				'exists' => false,
386
			);
387
388
			// Does the file exist, is it different and can we overwrite?
389
			if (file_exists(BOARDDIR . '/' . $file['filename']))
390
			{
391 View Code Duplication
				if (is_writable(BOARDDIR . '/' . $file['filename']))
392
					$context_data['writable'] = true;
393
394
				// Finally, do we actually think the content has changed?
395
				if ($file['size'] == filesize(BOARDDIR . '/' . $file['filename']) && $file['md5'] === md5_file(BOARDDIR . '/' . $file['filename']))
396
				{
397
					$context_data['exists'] = 'same';
398
					$context_data['default_copy'] = false;
399
				}
400
				// Attempt to discover newline character differences.
401
				elseif ($file['md5'] === md5(preg_replace("~[\r]?\n~", "\r\n", file_get_contents(BOARDDIR . '/' . $file['filename']))))
402
				{
403
					$context_data['exists'] = 'same';
404
					$context_data['default_copy'] = false;
405
				}
406
				else
407
					$context_data['exists'] = 'different';
408
			}
409
			// No overwrite?
410 View Code Duplication
			else
411
			{
412
				// Can we at least stick it in the directory...
413
				if (is_writable(BOARDDIR . '/' . $dirname))
414
					$context_data['writable'] = true;
415
			}
416
417
			// 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...
418
			if ($extension == 'php' && preg_match('~\w+\.\w+(?:-utf8)?\.php~', $filename))
419
			{
420
				$context_data += array(
421
					'version' => '??',
422
					'cur_version' => false,
423
					'version_compare' => 'newer',
424
				);
425
426
				list ($name,) = explode('.', $filename);
427
428
				// Let's get the new version, I like versions, they tell me that I'm up to date.
429
				if (preg_match('~\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '~i', $file['preview'], $match) == 1)
430
					$context_data['version'] = $match[1];
431
432
				// Now does the old file exist - if so what is it's version?
433
				if (file_exists(BOARDDIR . '/' . $file['filename']))
434
				{
435
					// OK - what is the current version?
436
					$fp = fopen(BOARDDIR . '/' . $file['filename'], 'rb');
437
					$header = fread($fp, 768);
438
					fclose($fp);
439
440
					// Find the version.
441
					if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1)
442
					{
443
						$context_data['cur_version'] = $match[1];
444
445
						// How does this compare?
446
						if ($context_data['cur_version'] == $context_data['version'])
447
							$context_data['version_compare'] = 'same';
448
						elseif ($context_data['cur_version'] > $context_data['version'])
449
							$context_data['version_compare'] = 'older';
450
451
						// Don't recommend copying if the version is the same.
452
						if ($context_data['version_compare'] != 'newer')
453
							$context_data['default_copy'] = false;
454
					}
455
				}
456
457
				// Add the context data to the main set.
458
				$context['files']['lang'][] = $context_data;
459
			}
460
			else
461
			{
462
				// If we think it's a theme thing, work out what the theme is.
463 View Code Duplication
				if (strpos($dirname, 'themes') === 0 && preg_match('~themes[\\/]([^\\/]+)[\\/]~', $dirname, $match))
464
					$theme_name = $match[1];
465
				else
466
					$theme_name = 'misc';
467
468
				// Assume it's an image, could be an acceptance note etc but rare.
469
				$context['files']['images'][$theme_name][] = $context_data;
470
			}
471
472
			// Collect together all non-writable areas.
473
			if (!$context_data['writable'])
474
				$context['make_writable'][] = $context_data['destination'];
475
		}
476
477
		// So, I'm a perfectionist - let's get the theme names.
478
		$indexes = array();
479
		foreach ($context['files']['images'] as $k => $dummy)
480
			$indexes[] = $k;
481
482
		$context['theme_names'] = array();
483
		if (!empty($indexes))
484
		{
485
			require_once(SUBSDIR . '/Themes.subs.php');
486
			$value_data = array(
487
				'query' => array(),
488
				'params' => array(),
489
			);
490
491
			foreach ($indexes as $k => $index)
492
			{
493
				$value_data['query'][] = 'value LIKE {string:value_' . $k . '}';
494
				$value_data['params']['value_' . $k] = '%' . $index;
495
			}
496
497
			$themes = validateThemeName($indexes, $value_data);
498
499
			// Now we have the id_theme we can get the pretty description.
500
			if (!empty($themes))
501
				$context['theme_names'] = getBasicThemeInfos($themes);
502
		}
503
504
		// Before we go to far can we make anything writable, eh, eh?
505
		if (!empty($context['make_writable']))
506
		{
507
			// What is left to be made writable?
508
			$file_status = create_chmod_control($context['make_writable']);
509
			$context['still_not_writable'] = $file_status['files']['notwritable'];
510
511
			// Mark those which are now writable as such.
512
			foreach ($context['files'] as $type => $data)
513
			{
514
				if ($type == 'lang')
515
				{
516 View Code Duplication
					foreach ($data as $k => $file)
517
						if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable']))
518
							$context['files'][$type][$k]['writable'] = true;
519
				}
520
				else
521
				{
522
					foreach ($data as $theme => $files)
523 View Code Duplication
						foreach ($files as $k => $file)
524
							if (!$file['writable'] && !in_array($file['destination'], $context['still_not_writable']))
525
								$context['files'][$type][$theme][$k]['writable'] = true;
526
				}
527
			}
528
529
			// Are we going to need more language stuff?
530
			if (!empty($context['still_not_writable']))
531
				loadLanguage('Packages');
532
		}
533
534
		// This is the list for the main files.
535
		$listOptions = array(
536
			'id' => 'lang_main_files_list',
537
			'title' => $txt['languages_download_main_files'],
538
			'get_items' => array(
539
				'function' => function () {
540
					global $context;
541
					return $context['files']['lang'];
542
				},
543
			),
544
			'columns' => array(
545
				'name' => array(
546
					'header' => array(
547
						'value' => $txt['languages_download_filename'],
548
					),
549
					'data' => array(
550
						'function' => function ($rowData) {
551
							global $txt;
552
553
							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'] : '');
554
						},
555
					),
556
				),
557
				'writable' => array(
558
					'header' => array(
559
						'value' => $txt['languages_download_writable'],
560
					),
561
					'data' => array(
562
						'function' => function ($rowData) {
563
							global $txt;
564
565
							return '<span class="' . ($rowData['writable'] ? 'success' : 'error') . ';">' . ($rowData['writable'] ? $txt['yes'] : $txt['no']) . '</span>';
566
						},
567
					),
568
				),
569
				'version' => array(
570
					'header' => array(
571
						'value' => $txt['languages_download_version'],
572
					),
573
					'data' => array(
574
						'function' => function ($rowData) {
575
							return '<span class="' . ($rowData['version_compare'] == 'older' ? 'error' : ($rowData['version_compare'] == 'same' ? 'softalert' : 'success')) . ';">' . $rowData['version'] . '</span>';
576
						},
577
					),
578
				),
579
				'exists' => array(
580
					'header' => array(
581
						'value' => $txt['languages_download_exists'],
582
					),
583
					'data' => array(
584
						'function' => function ($rowData) {
585
							global $txt;
586
587
							return $rowData['exists'] ? ($rowData['exists'] == 'same' ? $txt['languages_download_exists_same'] : $txt['languages_download_exists_different']) : $txt['no'];
588
						},
589
					),
590
				),
591
				'copy' => array(
592
					'header' => array(
593
						'value' => $txt['languages_download_copy'],
594
						'class' => 'centertext',
595
					),
596
					'data' => array(
597
						'function' => function ($rowData) {
598
							return '<input type="checkbox" name="copy_file[]" value="' . $rowData['generaldest'] . '" ' . ($rowData['default_copy'] ? 'checked="checked"' : '') . ' class="input_check" />';
599
						},
600
						'style' => 'width: 4%;',
601
						'class' => 'centertext',
602
					),
603
				),
604
			),
605
		);
606
607
		// Kill the cache, as it is now invalid..
608
		$cache = Cache::instance();
609
		$cache->put('known_languages', null, $cache->maxLevel(1) ? 86400 : 3600);
610
611
		createList($listOptions);
612
613
		createToken('admin-dlang');
614
	}
615
616
	/**
617
	 * Edit a particular set of language entries.
618
	 */
619
	public function action_editlang()
620
	{
621
		global $settings, $context, $txt, $modSettings, $language, $scripturl;
622
623
		require_once(SUBSDIR . '/Language.subs.php');
624
		loadLanguage('ManageSettings');
625
626
		// Select the languages tab.
627
		$context['menu_data_' . $context['admin_menu_id']]['current_subsection'] = 'edit';
628
		$context['page_title'] = $txt['edit_languages'];
629
		$context['sub_template'] = 'modify_language_entries';
630
631
		$context['lang_id'] = $this->_req->query->lid;
632
		list ($theme_id, $file_id) = empty($this->_req->post->tfid) || strpos($this->_req->post->tfid, '+') === false ? array(1, '') : explode('+', $this->_req->post->tfid);
633
634
		// Clean the ID - just in case.
635
		preg_match('~([A-Za-z0-9_-]+)~', $context['lang_id'], $matches);
636
		$context['lang_id'] = $matches[1];
637
638
		// Get all the theme data.
639
		require_once(SUBSDIR . '/Themes.subs.php');
640
		$themes = getCustomThemes();
641
642
		// This will be where we look
643
		$lang_dirs = array();
644
		$images_dirs = array();
645
646
		// Check we have themes with a path and a name - just in case - and add the path.
647
		foreach ($themes as $id => $data)
648
		{
649
			if (count($data) != 2)
650
				unset($themes[$id]);
651 View Code Duplication
			elseif (is_dir($data['theme_dir'] . '/languages/' . $context['lang_id']))
652
				$lang_dirs[$id] = $data['theme_dir'] . '/languages/' . $context['lang_id'];
653
654
			// How about image directories?
655 View Code Duplication
			if (is_dir($data['theme_dir'] . '/images/' . $context['lang_id']))
656
				$images_dirs[$id] = $data['theme_dir'] . '/images/' . $context['lang_id'];
657
		}
658
659
		$current_file = $file_id ? $lang_dirs[$theme_id] . '/' . $file_id . '.' . $context['lang_id'] . '.php' : '';
660
661
		// Now for every theme get all the files and stick them in context!
662
		$context['possible_files'] = array();
663
		foreach ($lang_dirs as $theme => $theme_dir)
664
		{
665
			// Open it up.
666
			$dir = dir($theme_dir);
667
			while ($entry = $dir->read())
668
			{
669
				// We're only after the files for this language.
670
				if (preg_match('~^([A-Za-z]+)\.' . $context['lang_id'] . '\.php$~', $entry, $matches) == 0)
671
					continue;
672
673
				if (!isset($context['possible_files'][$theme]))
674
					$context['possible_files'][$theme] = array(
675
						'id' => $theme,
676
						'name' => $themes[$theme]['name'],
677
						'files' => array(),
678
					);
679
680
				$context['possible_files'][$theme]['files'][] = array(
681
					'id' => $matches[1],
682
					'name' => isset($txt['lang_file_desc_' . $matches[1]]) ? $txt['lang_file_desc_' . $matches[1]] : $matches[1],
683
					'selected' => $theme_id == $theme && $file_id == $matches[1],
684
				);
685
			}
686
			$dir->close();
687
			usort($context['possible_files'][$theme]['files'], function ($val1, $val2) {
688
				return strcmp($val1['name'], $val2['name']);
689
			});
690
		}
691
692
		if ($context['lang_id'] != 'english')
693
		{
694
			$possiblePackage = findPossiblePackages($context['lang_id']);
695
			if ($possiblePackage !== false)
696
			{
697
				$context['langpack_uninstall_link'] = $scripturl . '?action=admin;area=packages;sa=uninstall;package=' . $possiblePackage[1] . ';pid=' . $possiblePackage[0];
698
			}
699
		}
700
701
		// We no longer wish to speak this language.
702
		// @todo - languages have been moved to packages
703
		// this may or may not be used in the future, for now it's not used at all
704
		// @deprecated since 1.0
705
		if (!empty($this->_req->post->delete_main) && $context['lang_id'] != 'english')
706
		{
707
			checkSession();
708
			validateToken('admin-mlang');
709
710
			// @todo FTP Controls?
711
			require_once(SUBSDIR . '/Package.subs.php');
712
713
			// First, Make a backup?
714
			if (!empty($modSettings['package_make_backups']) && (!isset($this->_req->session->last_backup_for) || $this->_req->session->last_backup_for != $context['lang_id'] . '$$$'))
715
			{
716
				$_SESSION['last_backup_for'] = $context['lang_id'] . '$$$';
717
				package_create_backup('backup_lang_' . $context['lang_id']);
718
			}
719
720
			// Second, loop through the array to remove the files.
721
			foreach ($lang_dirs as $curPath)
722
			{
723
				foreach ($context['possible_files'][1]['files'] as $lang)
724
					if (file_exists($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php'))
725
						unlink($curPath . '/' . $lang['id'] . '.' . $context['lang_id'] . '.php');
726
727
				// Check for the email template.
728
				if (file_exists($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php'))
729
					unlink($curPath . '/EmailTemplates.' . $context['lang_id'] . '.php');
730
			}
731
732
			// Third, the agreement file.
733
			if (file_exists(BOARDDIR . '/agreement.' . $context['lang_id'] . '.txt'))
734
				unlink(BOARDDIR . '/agreement.' . $context['lang_id'] . '.txt');
735
736
			// Fourth, a related images folder?
737
			if (!empty($images_dirs))
738
			{
739
				foreach ($images_dirs as $curPath)
740
				{
741
					if (is_dir($curPath))
742
						deltree($curPath);
743
				}
744
			}
745
746
			// Members can no longer use this language.
747
			removeLanguageFromMember($context['lang_id']);
748
749
			// Fifth, update getLanguages() cache.
750
			$cache = Cache::instance();
751
			$cache->put('known_languages', null, $cache->maxLevel(1) ? 86400 : 3600);
752
753
			// Sixth, if we deleted the default language, set us back to english.
754
			if ($context['lang_id'] == $language)
755
			{
756
				$language = 'english';
757
				$this->updateLanguage($language);
758
			}
759
760
			// Seventh, get out of here.
761
			redirectexit('action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id']);
762
		}
763
764
		// Saving primary settings?
765
		$madeSave = false;
766
		if (!empty($this->_req->post->save_main) && !$current_file)
767
		{
768
			checkSession();
769
			validateToken('admin-mlang');
770
771
			// Read in the current file.
772
			$current_data = implode('', file($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php'));
773
774
			// These are the replacements. old => new
775
			$replace_array = array(
776
				'~\$txt\[\'lang_locale\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_locale\'] = \'' . addslashes($this->_req->post->locale) . '\';',
777
				'~\$txt\[\'lang_dictionary\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_dictionary\'] = \'' . addslashes($this->_req->post->dictionary) . '\';',
778
				'~\$txt\[\'lang_spelling\'\]\s=\s(\'|")[^\r\n]+~' => '$txt[\'lang_spelling\'] = \'' . addslashes($this->_req->post->spelling) . '\';',
779
				'~\$txt\[\'lang_rtl\'\]\s=\s[A-Za-z0-9]+;~' => '$txt[\'lang_rtl\'] = ' . (!empty($this->_req->post->rtl) ? 'true' : 'false') . ';',
780
			);
781
			$current_data = preg_replace(array_keys($replace_array), array_values($replace_array), $current_data);
782
			$fp = fopen($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php', 'w+');
783
			fwrite($fp, $current_data);
784
			fclose($fp);
785
786
			if ($this->_checkOpcache())
787
				opcache_invalidate($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php');
788
789
			$madeSave = true;
790
		}
791
792
		// Quickly load index language entries.
793
		$old_txt = $txt;
794
		require($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php');
795
		$context['lang_file_not_writable_message'] = is_writable($settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php') ? '' : sprintf($txt['lang_file_not_writable'], $settings['default_theme_dir'] . '/languages/' . $context['lang_id'] . '/index.' . $context['lang_id'] . '.php');
796
797
		// Setup the primary settings context.
798
		$context['primary_settings'] = array(
799
			'name' => Util::ucwords(strtr($context['lang_id'], array('_' => ' ', '-utf8' => ''))),
800
			'locale' => $txt['lang_locale'],
801
			'dictionary' => $txt['lang_dictionary'],
802
			'spelling' => $txt['lang_spelling'],
803
			'rtl' => $txt['lang_rtl'],
804
		);
805
806
		// Restore normal service.
807
		$txt = $old_txt;
808
809
		// Are we saving?
810
		$save_strings = array();
811
		if (isset($this->_req->post->save_entries) && !empty($this->_req->post->entry))
812
		{
813
			checkSession();
814
			validateToken('admin-mlang');
815
816
			// Clean each entry!
817
			foreach ($this->_req->post->entry as $k => $v)
818
			{
819
				// Only try to save if it's changed!
820
				if ($this->_req->post->entry[$k] != $this->_req->post->comp[$k])
821
					$save_strings[$k] = cleanLangString($v, false);
822
			}
823
		}
824
825
		// If we are editing a file work away at that.
826
		if ($current_file)
827
		{
828
			$context['entries_not_writable_message'] = is_writable($current_file) ? '' : sprintf($txt['lang_entries_not_writable'], $current_file);
829
830
			$entries = array();
831
832
			// We can't just require it I'm afraid - otherwise we pass in all kinds of variables!
833
			$multiline_cache = '';
834
			foreach (file($current_file) as $line)
835
			{
836
				// Got a new entry?
837
				if ($line[0] == '$' && !empty($multiline_cache))
838
				{
839
					preg_match('~\$(helptxt|txt|editortxt)\[\'(.+)\'\]\s?=\s?(.+);~ms', strtr($multiline_cache, array("\r" => '')), $matches);
840 View Code Duplication
					if (!empty($matches[3]))
841
					{
842
						$entries[$matches[2]] = array(
843
							'type' => $matches[1],
844
							'full' => $matches[0],
845
							'entry' => $matches[3],
846
						);
847
						$multiline_cache = '';
848
					}
849
				}
850
				$multiline_cache .= $line;
851
			}
852
853
			// Last entry to add?
854
			if ($multiline_cache)
855
			{
856
				preg_match('~\$(helptxt|txt|editortxt)\[\'(.+)\'\]\s?=\s?(.+);~ms', strtr($multiline_cache, array("\r" => '')), $matches);
857 View Code Duplication
				if (!empty($matches[3]))
858
					$entries[$matches[2]] = array(
859
						'type' => $matches[1],
860
						'full' => $matches[0],
861
						'entry' => $matches[3],
862
					);
863
			}
864
865
			// These are the entries we can definitely save.
866
			$final_saves = array();
867
868
			$context['file_entries'] = array();
869
			foreach ($entries as $entryKey => $entryValue)
870
			{
871
				// Nowadays some entries have fancy keys, so better use something "portable" for the form
872
				$md5EntryKey = md5($entryKey);
873
874
				// Ignore some things we set separately.
875
				$ignore_files = array('lang_character_set', 'lang_locale', 'lang_dictionary', 'lang_spelling', 'lang_rtl');
876
				if (in_array($entryKey, $ignore_files))
877
					continue;
878
879
				// These are arrays that need breaking out.
880
				$arrays = array('days', 'days_short', 'months', 'months_titles', 'months_short', 'happy_birthday_author', 'karlbenson1_author', 'nite0859_author', 'zwaldowski_author', 'geezmo_author', 'karlbenson2_author');
881
				if (in_array($entryKey, $arrays))
882
				{
883
					// Get off the first bits.
884
					$entryValue['entry'] = substr($entryValue['entry'], strpos($entryValue['entry'], '(') + 1, strrpos($entryValue['entry'], ')') - strpos($entryValue['entry'], '('));
885
					$entryValue['entry'] = explode(',', strtr($entryValue['entry'], array(' ' => '')));
886
887
					// Now create an entry for each item.
888
					$cur_index = 0;
889
					$save_cache = array(
890
						'enabled' => false,
891
						'entries' => array(),
892
					);
893
					foreach ($entryValue['entry'] as $id => $subValue)
894
					{
895
						// Is this a new index?
896
						if (preg_match('~^(\d+)~', $subValue, $matches))
897
						{
898
							$cur_index = $matches[1];
899
							$subValue = substr($subValue, strpos($subValue, '\''));
900
						}
901
902
						// Clean up some bits.
903
						$subValue = strtr($subValue, array('"' => '', '\'' => '', ')' => ''));
904
905
						// Can we save?
906
						if (isset($save_strings[$md5EntryKey . '-+- ' . $cur_index]))
907
						{
908
							$save_cache['entries'][$cur_index] = strtr($save_strings[$md5EntryKey . '-+- ' . $cur_index], array('\'' => ''));
909
							$save_cache['enabled'] = true;
910
						}
911
						else
912
							$save_cache['entries'][$cur_index] = $subValue;
913
914
						$context['file_entries'][] = array(
915
							'key' => $entryKey . '-+- ' . $cur_index,
916
							'display_key' => $entryKey . '-+- ' . $cur_index,
917
							'value' => $subValue,
918
							'rows' => 1,
919
						);
920
						$cur_index++;
921
					}
922
923
					// Do we need to save?
924
					if ($save_cache['enabled'])
925
					{
926
						// Format the string, checking the indexes first.
927
						$items = array();
928
						$cur_index = 0;
929
						foreach ($save_cache['entries'] as $k2 => $v2)
930
						{
931
							// Manually show the custom index.
932
							if ($k2 != $cur_index)
933
							{
934
								$items[] = $k2 . ' => \'' . $v2 . '\'';
935
								$cur_index = $k2;
936
							}
937
							else
938
								$items[] = '\'' . $v2 . '\'';
939
940
							$cur_index++;
941
						}
942
943
						// Now create the string!
944
						$final_saves[$entryKey] = array(
945
							'find' => $entryValue['full'],
946
							'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = array(' . implode(', ', $items) . ');',
947
						);
948
					}
949
				}
950
				else
951
				{
952
					// Saving?
953
					if (isset($save_strings[$md5EntryKey]) && $save_strings[$md5EntryKey] != $entryValue['entry'])
954
					{
955
						// @todo Fix this properly.
956
						if ($save_strings[$md5EntryKey] == '')
957
							$save_strings[$md5EntryKey] = '\'\'';
958
959
						// Set the new value.
960
						$entryValue['entry'] = $save_strings[$md5EntryKey];
961
962
						// And we know what to save now!
963
						$final_saves[$entryKey] = array(
964
							'find' => $entryValue['full'],
965
							'replace' => '$' . $entryValue['type'] . '[\'' . $entryKey . '\'] = ' . $save_strings[$md5EntryKey] . ';',
966
						);
967
					}
968
969
					$editing_string = cleanLangString($entryValue['entry'], true);
970
					$context['file_entries'][] = array(
971
						'key' => $md5EntryKey,
972
						'display_key' => $entryKey,
973
						'value' => $editing_string,
974
						'rows' => (int) (strlen($editing_string) / 38) + substr_count($editing_string, "\n") + 1,
975
					);
976
				}
977
			}
978
979
			// Any saves to make?
980
			if (!empty($final_saves))
981
			{
982
				checkSession();
983
984
				$file_contents = implode('', file($current_file));
985
				foreach ($final_saves as $save)
986
					$file_contents = strtr($file_contents, array($save['find'] => $save['replace']));
987
988
				// Save the actual changes.
989
				$fp = fopen($current_file, 'w+');
990
				fwrite($fp, strtr($file_contents, array("\r" => '')));
991
				fclose($fp);
992
993
				if ($this->_checkOpcache())
994
					opcache_invalidate($current_file);
995
996
				$madeSave = true;
997
			}
998
999
			// Another restore.
1000
			$txt = $old_txt;
1001
		}
1002
1003
		// If we saved, redirect.
1004
		if ($madeSave)
1005
			redirectexit('action=admin;area=languages;sa=editlang;lid=' . $context['lang_id']);
1006
1007
		createToken('admin-mlang');
1008
	}
1009
1010
	/**
1011
	 * Edit language related settings.
1012
	 *
1013
	 * - Accessed by ?action=admin;area=languages;sa=settings
1014
	 * - This method handles the display, allows to edit, and saves the result
1015
	 * for the _languageSettings form.
1016
	 *
1017
	 * @event integrate_save_language_settings
1018
	 */
1019
	public function action_languageSettings_display()
1020
	{
1021
		global $scripturl, $context, $txt;
1022
1023
		// Initialize the form
1024
		$settingsForm = new Settings_Form(Settings_Form::FILE_ADAPTER);
1025
1026
		// Initialize it with our settings
1027
		$settingsForm->setConfigVars($this->_settings());
1028
1029
		// Warn the user if the backup of Settings.php failed.
1030
		$settings_not_writable = !is_writable(BOARDDIR . '/Settings.php');
1031
		$settings_backup_fail = !@is_writable(BOARDDIR . '/Settings_bak.php') || !@copy(BOARDDIR . '/Settings.php', BOARDDIR . '/Settings_bak.php');
1032
1033
		// Saving settings?
1034
		if (isset($this->_req->query->save))
1035
		{
1036
			checkSession();
1037
1038
			call_integration_hook('integrate_save_language_settings');
1039
1040
			$settingsForm->setConfigValues((array) $this->_req->post);
1041
			$settingsForm->save();
1042
			redirectexit('action=admin;area=languages;sa=settings');
1043
		}
1044
1045
		// Setup the template stuff.
1046
		$context['post_url'] = $scripturl . '?action=admin;area=languages;sa=settings;save';
1047
		$context['settings_title'] = $txt['language_settings'];
1048
		$context['save_disabled'] = $settings_not_writable;
1049
1050 View Code Duplication
		if ($settings_not_writable)
1051
		{
1052
			$context['error_type'] = 'notice';
1053
			$context['settings_message'] = $txt['settings_not_writable'];
1054
		}
1055
		elseif ($settings_backup_fail)
1056
		{
1057
			$context['error_type'] = 'notice';
1058
			$context['settings_message'] = $txt['admin_backup_fail'];
1059
		}
1060
1061
		// Fill the config array in contextual data for the template.
1062
		$settingsForm->prepare();
1063
	}
1064
1065
	/**
1066
	 * Checks if the Zend Opcahce is installed, active and cmd functions available.
1067
	 *
1068
	 * @return bool
1069
	 */
1070
	private function _checkOpcache()
1071
	{
1072
		return (extension_loaded('Zend OPcache') && ini_get('opcache.enable') && stripos(BOARDDIR, ini_get('opcache.restrict_api')) !== 0);
1073
	}
1074
1075
	/**
1076
	 * Load up all of the language settings
1077
	 *
1078
	 * @event integrate_modify_language_settings Use to add new config options
1079
	 */
1080 1
	private function _settings()
1081
	{
1082 1
		global $txt;
1083
1084
		// Warn the user if the backup of Settings.php failed.
1085 1
		$settings_not_writable = !is_writable(BOARDDIR . '/Settings.php');
1086
1087
		$config_vars = array(
1088 1
			'language' => array('language', $txt['default_language'], 'file', 'select', array(), null, 'disabled' => $settings_not_writable),
1089 1
			array('userLanguage', $txt['userLanguage'], 'db', 'check', null, 'userLanguage'),
1090 1
		);
1091
1092 1
		call_integration_hook('integrate_modify_language_settings', array(&$config_vars));
1093
1094
		// Get our languages. No cache.
1095 1
		$languages = getLanguages(false);
1096 1
		foreach ($languages as $lang)
1097 1
			$config_vars['language'][4][] = array($lang['filename'], strtr($lang['name'], array('-utf8' => ' (UTF-8)')));
1098
1099 1
		return $config_vars;
1100
	}
1101
1102
	/**
1103
	 * Return the form settings for use in admin search
1104
	 */
1105 1
	public function settings_search()
1106
	{
1107 1
		return $this->_settings();
1108
	}
1109
1110
	/**
1111
	 * Update the language in use
1112
	 *
1113
	 * @param string $language
1114
	 */
1115
	private function updateLanguage($language)
1116
	{
1117
		$configVars = array(
1118
			array('language', '', 'file')
1119
		);
1120
		$configValues = array(
1121
			'language' => $language
1122
		);
1123
		$settingsForm = new Settings_Form(Settings_Form::FILE_ADAPTER);
1124
		$settingsForm->setConfigVars($configVars);
1125
		$settingsForm->setConfigValues((array) $configValues);
1126
		$settingsForm->save();
1127
	}
1128
}
1129