Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

Themes.php ➔ ThemesMain()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 68
Code Lines 39

Duplication

Lines 5
Ratio 7.35 %

Importance

Changes 0
Metric Value
cc 4
eloc 39
nc 4
nop 0
dl 5
loc 68
rs 8.7864
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file concerns itself almost completely with theme administration.
5
 * Its tasks include changing theme settings, installing and removing
6
 * themes, choosing the current theme, and editing themes.
7
 *
8
 * @todo Update this for the new package manager?
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
9
 *
10
 * Creating and distributing theme packages:
11
 * 	There isn't that much required to package and distribute your own themes...
12
 * just do the following:
13
 * - create a theme_info.xml file, with the root element theme-info.
14
 * - its name should go in a name element, just like description.
15
 * - your name should go in author. (email in the email attribute.)
16
 * - any support website for the theme should be in website.
17
 * - layers and templates (non-default) should go in those elements ;).
18
 * - if the images dir isn't images, specify in the images element.
19
 * - any extra rows for themes should go in extra, serialized. (as in array(variable => value).)
20
 * - tar and gzip the directory - and you're done!
21
 * - please include any special license in a license.txt file.
22
 *
23
 * Simple Machines Forum (SMF)
24
 *
25
 * @package SMF
26
 * @author Simple Machines http://www.simplemachines.org
27
 * @copyright 2017 Simple Machines and individual contributors
28
 * @license http://www.simplemachines.org/about/smf/license.php BSD
29
 *
30
 * @version 2.1 Beta 4
31
 */
32
33
if (!defined('SMF'))
34
	die('No direct access...');
35
36
/**
37
 * Subaction handler - manages the action and delegates control to the proper
38
 * sub-action.
39
 * It loads both the Themes and Settings language files.
40
 * Checks the session by GET or POST to verify the sent data.
41
 * Requires the user not be a guest. (@todo what?)
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
42
 * Accessed via ?action=admin;area=theme.
43
 */
44
function ThemesMain()
45
{
46
	global $txt, $context, $sourcedir;
47
48
	// Load the important language files...
49
	loadLanguage('Themes');
50
	loadLanguage('Settings');
51
	loadLanguage('Drafts');
52
53
	// No funny business - guests only.
54
	is_not_guest();
55
56
	require_once($sourcedir . '/Subs-Themes.php');
57
58
	// Default the page title to Theme Administration by default.
59
	$context['page_title'] = $txt['themeadmin_title'];
60
61
	// Theme administration, removal, choice, or installation...
62
	$subActions = array(
63
		'admin' => 'ThemeAdmin',
64
		'list' => 'ThemeList',
65
		'reset' => 'SetThemeOptions',
66
		'options' => 'SetThemeOptions',
67
		'install' => 'ThemeInstall',
68
		'remove' => 'RemoveTheme',
69
		'pick' => 'PickTheme',
70
		'edit' => 'EditTheme',
71
		'enable' => 'EnableTheme',
72
		'copy' => 'CopyTemplate',
73
	);
74
75
	// @todo Layout Settings?  huh?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
76
	if (!empty($context['admin_menu_name']))
77
	{
78
		$context[$context['admin_menu_name']]['tab_data'] = array(
79
			'title' => $txt['themeadmin_title'],
80
			'help' => 'themes',
81
			'description' => $txt['themeadmin_description'],
82
			'tabs' => array(
83
				'admin' => array(
84
					'description' => $txt['themeadmin_admin_desc'],
85
				),
86
				'list' => array(
87
					'description' => $txt['themeadmin_list_desc'],
88
				),
89
				'reset' => array(
90
					'description' => $txt['themeadmin_reset_desc'],
91
				),
92
				'edit' => array(
93
					'description' => $txt['themeadmin_edit_desc'],
94
				),
95
			),
96
		);
97
	}
98
99
	// CRUD $subActions as needed.
100
	call_integration_hook('integrate_manage_themes', array(&$subActions));
101
102
	// Whatever you decide to do, clean the minify cache.
103
	cache_put_data('minimized_css', null);
104
105
	// Follow the sa or just go to administration.
106 View Code Duplication
	if (isset($_GET['sa']) && !empty($subActions[$_GET['sa']]))
107
		call_helper($subActions[$_GET['sa']]);
108
109
	else
110
		call_helper($subActions['admin']);
111
}
112
113
/**
114
 * This function allows administration of themes and their settings,
115
 * as well as global theme settings.
116
 *  - sets the settings theme_allow, theme_guests, and knownThemes.
117
 *  - requires the admin_forum permission.
118
 *  - accessed with ?action=admin;area=theme;sa=admin.
119
 *
120
 *  @uses Themes template
121
 *  @uses Admin language file
122
 */
123
function ThemeAdmin()
124
{
125
	global $context, $boarddir;
126
127
	// Are handling any settings?
128
	if (isset($_POST['save']))
129
	{
130
		checkSession();
131
		validateToken('admin-tm');
132
133
		if (isset($_POST['options']['known_themes']))
134
			foreach ($_POST['options']['known_themes'] as $key => $id)
135
				$_POST['options']['known_themes'][$key] = (int) $id;
136
137
		else
138
			fatal_lang_error('themes_none_selectable', false);
139
140
		if (!in_array($_POST['options']['theme_guests'], $_POST['options']['known_themes']))
141
			fatal_lang_error('themes_default_selectable', false);
142
143
		// Commit the new settings.
144
		updateSettings(array(
145
			'theme_allow' => $_POST['options']['theme_allow'],
146
			'theme_guests' => $_POST['options']['theme_guests'],
147
			'knownThemes' => implode(',', $_POST['options']['known_themes']),
148
		));
149
		if ((int) $_POST['theme_reset'] == 0 || in_array($_POST['theme_reset'], $_POST['options']['known_themes']))
150
			updateMemberData(null, array('id_theme' => (int) $_POST['theme_reset']));
151
152
		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=admin');
153
	}
154
155
	loadLanguage('Admin');
156
	isAllowedTo('admin_forum');
157
	loadTemplate('Themes');
158
159
	// List all installed and enabled themes.
160
	get_all_themes(true);
161
162
	// Can we create a new theme?
163
	$context['can_create_new'] = is_writable($boarddir . '/Themes');
164
	$context['new_theme_dir'] = substr(realpath($boarddir . '/Themes/default'), 0, -7);
165
166
	// Look for a non existent theme directory. (ie theme87.)
167
	$theme_dir = $boarddir . '/Themes/theme';
168
	$i = 1;
169
	while (file_exists($theme_dir . $i))
170
		$i++;
171
172
	$context['new_theme_name'] = 'theme' . $i;
173
174
	// A bunch of tokens for a bunch of forms.
175
	createToken('admin-tm');
176
	createToken('admin-t-file');
177
	createToken('admin-t-copy');
178
	createToken('admin-t-dir');
179
}
180
181
/**
182
 * This function lists the available themes and provides an interface to reset
183
 * the paths of all the installed themes.
184
 */
185
function ThemeList()
186
{
187
	global $context, $boarddir, $boardurl, $smcFunc;
188
189
	loadLanguage('Admin');
190
	isAllowedTo('admin_forum');
191
192
	if (isset($_REQUEST['th']))
193
		return SetThemeSettings();
194
195
	if (isset($_POST['save']))
196
	{
197
		checkSession();
198
		validateToken('admin-tl');
199
200
		// Calling the almighty power of global vars!
201
		get_all_themes(false);
202
203
		$setValues = array();
204
		foreach ($context['themes'] as $id => $theme)
205
		{
206 View Code Duplication
			if (file_exists($_POST['reset_dir'] . '/' . basename($theme['theme_dir'])))
207
			{
208
				$setValues[] = array($id, 0, 'theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['theme_dir'])));
209
				$setValues[] = array($id, 0, 'theme_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir']));
210
				$setValues[] = array($id, 0, 'images_url', $_POST['reset_url'] . '/' . basename($theme['theme_dir']) . '/' . basename($theme['images_url']));
211
			}
212
213 View Code Duplication
			if (isset($theme['base_theme_dir']) && file_exists($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir'])))
214
			{
215
				$setValues[] = array($id, 0, 'base_theme_dir', realpath($_POST['reset_dir'] . '/' . basename($theme['base_theme_dir'])));
216
				$setValues[] = array($id, 0, 'base_theme_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir']));
217
				$setValues[] = array($id, 0, 'base_images_url', $_POST['reset_url'] . '/' . basename($theme['base_theme_dir']) . '/' . basename($theme['base_images_url']));
218
			}
219
220
			cache_put_data('theme_settings-' . $id, null, 90);
221
		}
222
223 View Code Duplication
		if (!empty($setValues))
224
		{
225
			$smcFunc['db_insert']('replace',
226
				'{db_prefix}themes',
227
				array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
228
				$setValues,
229
				array('id_theme', 'variable', 'id_member')
230
			);
231
		}
232
233
		redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']);
234
	}
235
236
	loadTemplate('Themes');
237
238
	// Get all installed themes.
239
	get_all_themes(false);
240
241
	$context['reset_dir'] = realpath($boarddir . '/Themes');
242
	$context['reset_url'] = $boardurl . '/Themes';
243
244
	$context['sub_template'] = 'list_themes';
245
	createToken('admin-tl');
246
	createToken('admin-tr', 'request');
247
	createToken('admin-tre', 'request');
248
}
249
250
/**
251
 * Administrative global settings.
252
 */
253
function SetThemeOptions()
254
{
255
	global $txt, $context, $settings, $modSettings, $smcFunc;
256
257
	$_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (isset($_GET['id']) ? (int) $_GET['id'] : 0);
258
259
	isAllowedTo('admin_forum');
260
261
	if (empty($_GET['th']) && empty($_GET['id']))
262
	{
263
		$request = $smcFunc['db_query']('', '
264
			SELECT id_theme, variable, value
265
			FROM {db_prefix}themes
266
			WHERE variable IN ({string:name}, {string:theme_dir})
267
				AND id_member = {int:no_member}',
268
			array(
269
				'no_member' => 0,
270
				'name' => 'name',
271
				'theme_dir' => 'theme_dir',
272
			)
273
		);
274
		$context['themes'] = array();
275
		while ($row = $smcFunc['db_fetch_assoc']($request))
276
		{
277
			if (!isset($context['themes'][$row['id_theme']]))
278
				$context['themes'][$row['id_theme']] = array(
279
					'id' => $row['id_theme'],
280
					'num_default_options' => 0,
281
					'num_members' => 0,
282
				);
283
			$context['themes'][$row['id_theme']][$row['variable']] = $row['value'];
284
		}
285
		$smcFunc['db_free_result']($request);
286
287
		$request = $smcFunc['db_query']('', '
288
			SELECT id_theme, COUNT(*) AS value
289
			FROM {db_prefix}themes
290
			WHERE id_member = {int:guest_member}
291
			GROUP BY id_theme',
292
			array(
293
				'guest_member' => -1,
294
			)
295
		);
296
		while ($row = $smcFunc['db_fetch_assoc']($request))
297
			$context['themes'][$row['id_theme']]['num_default_options'] = $row['value'];
298
		$smcFunc['db_free_result']($request);
299
300
		// Need to make sure we don't do custom fields.
301
		$request = $smcFunc['db_query']('', '
302
			SELECT col_name
303
			FROM {db_prefix}custom_fields',
304
			array(
305
			)
306
		);
307
		$customFields = array();
308
		while ($row = $smcFunc['db_fetch_assoc']($request))
309
			$customFields[] = $row['col_name'];
310
		$smcFunc['db_free_result']($request);
311
		$customFieldsQuery = empty($customFields) ? '' : ('AND variable NOT IN ({array_string:custom_fields})');
312
313
		$request = $smcFunc['db_query']('themes_count', '
314
			SELECT COUNT(DISTINCT id_member) AS value, id_theme
315
			FROM {db_prefix}themes
316
			WHERE id_member > {int:no_member}
317
				' . $customFieldsQuery . '
318
			GROUP BY id_theme',
319
			array(
320
				'no_member' => 0,
321
				'custom_fields' => empty($customFields) ? array() : $customFields,
322
			)
323
		);
324
		while ($row = $smcFunc['db_fetch_assoc']($request))
325
			$context['themes'][$row['id_theme']]['num_members'] = $row['value'];
326
		$smcFunc['db_free_result']($request);
327
328
		// There has to be a Settings template!
329
		foreach ($context['themes'] as $k => $v)
330
			if (empty($v['theme_dir']) || (!file_exists($v['theme_dir'] . '/Settings.template.php') && empty($v['num_members'])))
331
				unset($context['themes'][$k]);
332
333
		loadTemplate('Themes');
334
		$context['sub_template'] = 'reset_list';
335
336
		createToken('admin-stor', 'request');
337
		return;
338
	}
339
340
	// Submit?
341
	if (isset($_POST['submit']) && empty($_POST['who']))
342
	{
343
		checkSession();
344
		validateToken('admin-sto');
345
346
		if (empty($_POST['options']))
347
			$_POST['options'] = array();
348
		if (empty($_POST['default_options']))
349
			$_POST['default_options'] = array();
350
351
		// Set up the sql query.
352
		$setValues = array();
353
354
		foreach ($_POST['options'] as $opt => $val)
355
			$setValues[] = array(-1, $_GET['th'], $opt, is_array($val) ? implode(',', $val) : $val);
356
357
		$old_settings = array();
358 View Code Duplication
		foreach ($_POST['default_options'] as $opt => $val)
359
		{
360
			$old_settings[] = $opt;
361
362
			$setValues[] = array(-1, 1, $opt, is_array($val) ? implode(',', $val) : $val);
363
		}
364
365
		// If we're actually inserting something..
366
		if (!empty($setValues))
367
		{
368
			// Are there options in non-default themes set that should be cleared?
369 View Code Duplication
			if (!empty($old_settings))
370
				$smcFunc['db_query']('', '
371
					DELETE FROM {db_prefix}themes
372
					WHERE id_theme != {int:default_theme}
373
						AND id_member = {int:guest_member}
374
						AND variable IN ({array_string:old_settings})',
375
					array(
376
						'default_theme' => 1,
377
						'guest_member' => -1,
378
						'old_settings' => $old_settings,
379
					)
380
				);
381
382
			$smcFunc['db_insert']('replace',
383
				'{db_prefix}themes',
384
				array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
385
				$setValues,
386
				array('id_theme', 'variable', 'id_member')
387
			);
388
		}
389
390
		cache_put_data('theme_settings-' . $_GET['th'], null, 90);
391
		cache_put_data('theme_settings-1', null, 90);
392
393
		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
394
	}
395
	elseif (isset($_POST['submit']) && $_POST['who'] == 1)
396
	{
397
		checkSession();
398
		validateToken('admin-sto');
399
400
		$_POST['options'] = empty($_POST['options']) ? array() : $_POST['options'];
401
		$_POST['options_master'] = empty($_POST['options_master']) ? array() : $_POST['options_master'];
402
		$_POST['default_options'] = empty($_POST['default_options']) ? array() : $_POST['default_options'];
403
		$_POST['default_options_master'] = empty($_POST['default_options_master']) ? array() : $_POST['default_options_master'];
404
405
		$old_settings = array();
406
		foreach ($_POST['default_options'] as $opt => $val)
407
		{
408
			if ($_POST['default_options_master'][$opt] == 0)
409
				continue;
410 View Code Duplication
			elseif ($_POST['default_options_master'][$opt] == 1)
411
			{
412
				// Delete then insert for ease of database compatibility!
413
				$smcFunc['db_query']('substring', '
414
					DELETE FROM {db_prefix}themes
415
					WHERE id_theme = {int:default_theme}
416
						AND id_member != {int:no_member}
417
						AND variable = SUBSTRING({string:option}, 1, 255)',
418
					array(
419
						'default_theme' => 1,
420
						'no_member' => 0,
421
						'option' => $opt,
422
					)
423
				);
424
				$smcFunc['db_query']('substring', '
425
					INSERT INTO {db_prefix}themes
426
						(id_member, id_theme, variable, value)
427
					SELECT id_member, 1, SUBSTRING({string:option}, 1, 255), SUBSTRING({string:value}, 1, 65534)
428
					FROM {db_prefix}members',
429
					array(
430
						'option' => $opt,
431
						'value' => (is_array($val) ? implode(',', $val) : $val),
432
					)
433
				);
434
435
				$old_settings[] = $opt;
436
			}
437
			elseif ($_POST['default_options_master'][$opt] == 2)
438
			{
439
				$smcFunc['db_query']('', '
440
					DELETE FROM {db_prefix}themes
441
					WHERE variable = {string:option_name}
442
						AND id_member > {int:no_member}',
443
					array(
444
						'no_member' => 0,
445
						'option_name' => $opt,
446
					)
447
				);
448
			}
449
		}
450
451
		// Delete options from other themes.
452 View Code Duplication
		if (!empty($old_settings))
453
			$smcFunc['db_query']('', '
454
				DELETE FROM {db_prefix}themes
455
				WHERE id_theme != {int:default_theme}
456
					AND id_member > {int:no_member}
457
					AND variable IN ({array_string:old_settings})',
458
				array(
459
					'default_theme' => 1,
460
					'no_member' => 0,
461
					'old_settings' => $old_settings,
462
				)
463
			);
464
465
		foreach ($_POST['options'] as $opt => $val)
466
		{
467
			if ($_POST['options_master'][$opt] == 0)
468
				continue;
469 View Code Duplication
			elseif ($_POST['options_master'][$opt] == 1)
470
			{
471
				// Delete then insert for ease of database compatibility - again!
472
				$smcFunc['db_query']('substring', '
473
					DELETE FROM {db_prefix}themes
474
					WHERE id_theme = {int:current_theme}
475
						AND id_member != {int:no_member}
476
						AND variable = SUBSTRING({string:option}, 1, 255)',
477
					array(
478
						'current_theme' => $_GET['th'],
479
						'no_member' => 0,
480
						'option' => $opt,
481
					)
482
				);
483
				$smcFunc['db_query']('substring', '
484
					INSERT INTO {db_prefix}themes
485
						(id_member, id_theme, variable, value)
486
					SELECT id_member, {int:current_theme}, SUBSTRING({string:option}, 1, 255), SUBSTRING({string:value}, 1, 65534)
487
					FROM {db_prefix}members',
488
					array(
489
						'current_theme' => $_GET['th'],
490
						'option' => $opt,
491
						'value' => (is_array($val) ? implode(',', $val) : $val),
492
					)
493
				);
494
			}
495
			elseif ($_POST['options_master'][$opt] == 2)
496
			{
497
				$smcFunc['db_query']('', '
498
					DELETE FROM {db_prefix}themes
499
					WHERE variable = {string:option}
500
						AND id_member > {int:no_member}
501
						AND id_theme = {int:current_theme}',
502
					array(
503
						'no_member' => 0,
504
						'current_theme' => $_GET['th'],
505
						'option' => $opt,
506
					)
507
				);
508
			}
509
		}
510
511
		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
512
	}
513
	elseif (!empty($_GET['who']) && $_GET['who'] == 2)
514
	{
515
		checkSession('get');
516
		validateToken('admin-stor', 'request');
517
518
		// Don't delete custom fields!!
519
		if ($_GET['th'] == 1)
520
		{
521
			$request = $smcFunc['db_query']('', '
522
				SELECT col_name
523
				FROM {db_prefix}custom_fields',
524
				array(
525
				)
526
			);
527
			$customFields = array();
528
			while ($row = $smcFunc['db_fetch_assoc']($request))
529
				$customFields[] = $row['col_name'];
530
			$smcFunc['db_free_result']($request);
531
		}
532
		$customFieldsQuery = empty($customFields) ? '' : ('AND variable NOT IN ({array_string:custom_fields})');
533
534
		$smcFunc['db_query']('', '
535
			DELETE FROM {db_prefix}themes
536
			WHERE id_member > {int:no_member}
537
				AND id_theme = {int:current_theme}
538
				' . $customFieldsQuery,
539
			array(
540
				'no_member' => 0,
541
				'current_theme' => $_GET['th'],
542
				'custom_fields' => empty($customFields) ? array() : $customFields,
543
			)
544
		);
545
546
		redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
547
	}
548
549
	$old_id = $settings['theme_id'];
550
	$old_settings = $settings;
551
552
	loadTheme($_GET['th'], false);
553
554
	loadLanguage('Profile');
555
	// @todo Should we just move these options so they are no longer theme dependant?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
556
	loadLanguage('PersonalMessage');
557
558
	// Let the theme take care of the settings.
559
	loadTemplate('Settings');
560
	loadSubTemplate('options');
561
562
	$context['sub_template'] = 'set_options';
563
	$context['page_title'] = $txt['theme_settings'];
564
565
	$context['options'] = $context['theme_options'];
566
	$context['theme_settings'] = $settings;
567
568
	if (empty($_REQUEST['who']))
569
	{
570
		$request = $smcFunc['db_query']('', '
571
			SELECT variable, value
572
			FROM {db_prefix}themes
573
			WHERE id_theme IN (1, {int:current_theme})
574
				AND id_member = {int:guest_member}',
575
			array(
576
				'current_theme' => $_GET['th'],
577
				'guest_member' => -1,
578
			)
579
		);
580
		$context['theme_options'] = array();
581
		while ($row = $smcFunc['db_fetch_assoc']($request))
582
			$context['theme_options'][$row['variable']] = $row['value'];
583
		$smcFunc['db_free_result']($request);
584
585
		$context['theme_options_reset'] = false;
586
	}
587
	else
588
	{
589
		$context['theme_options'] = array();
590
		$context['theme_options_reset'] = true;
591
	}
592
593
	foreach ($context['options'] as $i => $setting)
594
	{
595
		// Just skip separators
596
		if (!is_array($setting))
597
			continue;
598
599
		// Is this disabled?
600
		if ($setting['id'] == 'calendar_start_day' && empty($modSettings['cal_enabled']))
601
		{
602
			unset($context['options'][$i]);
603
			continue;
604
		}
605
		elseif (($setting['id'] == 'topics_per_page' || $setting['id'] == 'messages_per_page') && !empty($modSettings['disableCustomPerPage']))
606
		{
607
			unset($context['options'][$i]);
608
			continue;
609
		}
610
611 View Code Duplication
		if (!isset($setting['type']) || $setting['type'] == 'bool')
612
			$context['options'][$i]['type'] = 'checkbox';
613
		elseif ($setting['type'] == 'int' || $setting['type'] == 'integer')
614
			$context['options'][$i]['type'] = 'number';
615
		elseif ($setting['type'] == 'string')
616
			$context['options'][$i]['type'] = 'text';
617
618
		if (isset($setting['options']))
619
			$context['options'][$i]['type'] = 'list';
620
621
		$context['options'][$i]['value'] = !isset($context['theme_options'][$setting['id']]) ? '' : $context['theme_options'][$setting['id']];
622
	}
623
624
	// Restore the existing theme.
625
	loadTheme($old_id, false);
626
	$settings = $old_settings;
627
628
	loadTemplate('Themes');
629
	createToken('admin-sto');
630
}
631
632
/**
633
 * Administrative global settings.
634
 * - saves and requests global theme settings. ($settings)
635
 * - loads the Admin language file.
636
 * - calls ThemeAdmin() if no theme is specified. (the theme center.)
637
 * - requires admin_forum permission.
638
 * - accessed with ?action=admin;area=theme;sa=list&th=xx.
639
 */
640
function SetThemeSettings()
641
{
642
	global $txt, $context, $settings, $modSettings, $smcFunc;
643
644
	if (empty($_GET['th']) && empty($_GET['id']))
645
		return ThemeAdmin();
646
647
	$_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
648
649
	// Select the best fitting tab.
650
	$context[$context['admin_menu_name']]['current_subsection'] = 'list';
651
652
	loadLanguage('Admin');
653
	isAllowedTo('admin_forum');
654
655
	// Validate inputs/user.
656
	if (empty($_GET['th']))
657
		fatal_lang_error('no_theme', false);
658
659
	// Fetch the smiley sets...
660
	$sets = explode(',', 'none,' . $modSettings['smiley_sets_known']);
661
	$set_names = explode("\n", $txt['smileys_none'] . "\n" . $modSettings['smiley_sets_names']);
662
	$context['smiley_sets'] = array(
663
		'' => $txt['smileys_no_default']
664
	);
665
	foreach ($sets as $i => $set)
666
		$context['smiley_sets'][$set] = $smcFunc['htmlspecialchars']($set_names[$i]);
667
668
	$old_id = $settings['theme_id'];
669
	$old_settings = $settings;
670
671
	loadTheme($_GET['th'], false);
672
673
	// Sadly we really do need to init the template.
674
	loadSubTemplate('init', 'ignore');
0 ignored issues
show
Documentation introduced by
'ignore' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
675
676
	// Also load the actual themes language file - in case of special settings.
677
	loadLanguage('Settings', '', true, true);
678
679
	// And the custom language strings...
680
	loadLanguage('ThemeStrings', '', false, true);
681
682
	// Let the theme take care of the settings.
683
	loadTemplate('Settings');
684
	loadSubTemplate('settings');
685
686
	// Load the variants separately...
687
	$settings['theme_variants'] = array();
688
	if (file_exists($settings['theme_dir'] . '/index.template.php'))
689
	{
690
		$file_contents = implode('', file($settings['theme_dir'] . '/index.template.php'));
691
		if (preg_match('~\$settings\[\'theme_variants\'\]\s*=(.+?);~', $file_contents, $matches))
692
				eval('global $settings;' . $matches[0]);
0 ignored issues
show
Coding Style introduced by
The function SetThemeSettings() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
693
	}
694
695
	// Submitting!
696
	if (isset($_POST['save']))
697
	{
698
		checkSession();
699
		validateToken('admin-sts');
700
701
		if (empty($_POST['options']))
702
			$_POST['options'] = array();
703
		if (empty($_POST['default_options']))
704
			$_POST['default_options'] = array();
705
706
		// Make sure items are cast correctly.
707
		foreach ($context['theme_settings'] as $item)
708
		{
709
			// Disregard this item if this is just a separator.
710
			if (!is_array($item))
711
				continue;
712
713
			foreach (array('options', 'default_options') as $option)
714
			{
715
				if (!isset($_POST[$option][$item['id']]))
716
					continue;
717
				// Checkbox.
718 View Code Duplication
				elseif (empty($item['type']))
719
					$_POST[$option][$item['id']] = $_POST[$option][$item['id']] ? 1 : 0;
720
				// Number
721 View Code Duplication
				elseif ($item['type'] == 'number')
722
					$_POST[$option][$item['id']] = (int) $_POST[$option][$item['id']];
723
			}
724
		}
725
726
		// Set up the sql query.
727
		$inserts = array();
728 View Code Duplication
		foreach ($_POST['options'] as $opt => $val)
729
			$inserts[] = array(0, $_GET['th'], $opt, is_array($val) ? implode(',', $val) : $val);
730 View Code Duplication
		foreach ($_POST['default_options'] as $opt => $val)
731
			$inserts[] = array(0, 1, $opt, is_array($val) ? implode(',', $val) : $val);
732
		// If we're actually inserting something..
733 View Code Duplication
		if (!empty($inserts))
734
		{
735
			$smcFunc['db_insert']('replace',
736
				'{db_prefix}themes',
737
				array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
738
				$inserts,
739
				array('id_member', 'id_theme', 'variable')
740
			);
741
		}
742
743
		cache_put_data('theme_settings-' . $_GET['th'], null, 90);
744
		cache_put_data('theme_settings-1', null, 90);
745
746
		// Invalidate the cache.
747
		updateSettings(array('settings_updated' => time()));
748
749
		redirectexit('action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id']);
750
	}
751
752
	$context['sub_template'] = 'set_settings';
753
	$context['page_title'] = $txt['theme_settings'];
754
755
	foreach ($settings as $setting => $dummy)
756
	{
757
		if (!in_array($setting, array('theme_url', 'theme_dir', 'images_url', 'template_dirs')))
758
			$settings[$setting] = htmlspecialchars__recursive($settings[$setting]);
759
	}
760
761
	$context['settings'] = $context['theme_settings'];
762
	$context['theme_settings'] = $settings;
763
764
	foreach ($context['settings'] as $i => $setting)
765
	{
766
		// Separators are dummies, so leave them alone.
767
		if (!is_array($setting))
768
			continue;
769
770 View Code Duplication
		if (!isset($setting['type']) || $setting['type'] == 'bool')
771
			$context['settings'][$i]['type'] = 'checkbox';
772
		elseif ($setting['type'] == 'int' || $setting['type'] == 'integer')
773
			$context['settings'][$i]['type'] = 'number';
774
		elseif ($setting['type'] == 'string')
775
			$context['settings'][$i]['type'] = 'text';
776
777
		if (isset($setting['options']))
778
			$context['settings'][$i]['type'] = 'list';
779
780
		$context['settings'][$i]['value'] = !isset($settings[$setting['id']]) ? '' : $settings[$setting['id']];
781
	}
782
783
	// Do we support variants?
784
	if (!empty($settings['theme_variants']))
785
	{
786
		$context['theme_variants'] = array();
787 View Code Duplication
		foreach ($settings['theme_variants'] as $variant)
0 ignored issues
show
Bug introduced by
The expression $settings['theme_variants'] of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
788
		{
789
			// Have any text, old chap?
790
			$context['theme_variants'][$variant] = array(
791
				'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant,
792
				'thumbnail' => !file_exists($settings['theme_dir'] . '/images/thumbnail.png') || file_exists($settings['theme_dir'] . '/images/thumbnail_' . $variant . '.png') ? $settings['images_url'] . '/thumbnail_' . $variant . '.png' : ($settings['images_url'] . '/thumbnail.png'),
793
			);
794
		}
795
		$context['default_variant'] = !empty($settings['default_variant']) && isset($context['theme_variants'][$settings['default_variant']]) ? $settings['default_variant'] : $settings['theme_variants'][0];
796
	}
797
798
	// Restore the current theme.
799
	loadTheme($old_id, false);
800
801
	// Reinit just incase.
802
	loadSubTemplate('init', 'ignore');
0 ignored issues
show
Documentation introduced by
'ignore' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
803
804
	$settings = $old_settings;
805
806
	loadTemplate('Themes');
807
808
	// We like Kenny better than Token.
809
	createToken('admin-sts');
810
}
811
812
/**
813
 * Remove a theme from the database.
814
 * - removes an installed theme.
815
 * - requires an administrator.
816
 * - accessed with ?action=admin;area=theme;sa=remove.
817
 */
818
function RemoveTheme()
819
{
820
	global $context;
821
822
	checkSession('get');
823
824
	isAllowedTo('admin_forum');
825
	validateToken('admin-tr', 'request');
826
827
	// The theme's ID must be an integer.
828
	$themeID = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
829
830
	// You can't delete the default theme!
831
	if ($themeID == 1)
832
		fatal_lang_error('no_access', false);
833
834
	$theme_info = get_single_theme($themeID);
835
836
	// Remove it from the DB.
837
	remove_theme($themeID);
838
839
	// And remove all its files and folders too.
840
	if (!empty($theme_info) && !empty($theme_info['theme_dir']))
841
		remove_dir($theme_info['theme_dir']);
842
843
	// Go back to the list page.
844
	redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'] . ';done=removing');
845
}
846
847
/**
848
 * Handles enabling/disabling a theme from the admin center
849
 */
850
function EnableTheme()
851
{
852
	global $modSettings, $context;
853
854
	checkSession('get');
855
856
	isAllowedTo('admin_forum');
857
	validateToken('admin-tre', 'request');
858
859
	// The theme's ID must be an string.
860
	$themeID = isset($_GET['th']) ? (string) trim($_GET['th']) : (string) trim($_GET['id']);
861
862
	// Get the current list.
863
	$enableThemes = explode(',', $modSettings['enableThemes']);
864
865
	// Are we disabling it?
866
	if (isset($_GET['disabled']))
867
		$enableThemes = array_diff($enableThemes, array($themeID));
868
869
	// Nope? then enable it!
870
	else
871
		$enableThemes[] = (string) $themeID;
872
873
	// Update the setting.
874
	$enableThemes = strtr(implode(',', $enableThemes), array(',,' => ','));
875
	updateSettings(array('enableThemes' => $enableThemes));
876
877
	// Done!
878
	redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'] . ';done=' . (isset($_GET['disabled']) ? 'disabling' : 'enabling'));
879
}
880
881
/**
882
 * Choose a theme from a list.
883
 * allows an user or administrator to pick a new theme with an interface.
884
 * - can edit everyone's (u = 0), guests' (u = -1), or a specific user's.
885
 * - uses the Themes template. (pick sub template.)
886
 * - accessed with ?action=admin;area=theme;sa=pick.
887
 * @todo thought so... Might be better to split this file in ManageThemes and Themes,
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
888
 * with centralized admin permissions on ManageThemes.
889
 */
890
function PickTheme()
891
{
892
	global $txt, $context, $modSettings, $user_info, $language, $smcFunc, $settings, $scripturl;
893
894
	loadLanguage('Profile');
895
	loadTemplate('Themes');
896
897
	// Build the link tree.
898
	$context['linktree'][] = array(
899
		'url' => $scripturl . '?action=theme;sa=pick;u=' . (!empty($_REQUEST['u']) ? (int) $_REQUEST['u'] : 0),
900
		'name' => $txt['theme_pick'],
901
	);
902
	$context['default_theme_id'] = $modSettings['theme_default'];
903
904
	$_SESSION['id_theme'] = 0;
905
906
	if (isset($_GET['id']))
907
		$_GET['th'] = $_GET['id'];
908
909
	// Saving a variant cause JS doesn't work - pretend it did ;)
910
	if (isset($_POST['save']))
911
	{
912
		// Which theme?
913
		foreach ($_POST['save'] as $k => $v)
914
			$_GET['th'] = (int) $k;
915
916
		if (isset($_POST['vrt'][$k]))
917
			$_GET['vrt'] = $_POST['vrt'][$k];
0 ignored issues
show
Bug introduced by
The variable $k seems to be defined by a foreach iteration on line 913. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
918
	}
919
920
	// Have we made a decision, or are we just browsing?
921
	if (isset($_GET['th']))
922
	{
923
		checkSession('get');
924
925
		$_GET['th'] = (int) $_GET['th'];
926
927
		// Save for this user.
928
		if (!isset($_REQUEST['u']) || !allowedTo('admin_forum'))
929
		{
930
			updateMemberData($user_info['id'], array('id_theme' => (int) $_GET['th']));
931
932
			// A variants to save for the user?
933
			if (!empty($_GET['vrt']))
934
			{
935
				$smcFunc['db_insert']('replace',
936
					'{db_prefix}themes',
937
					array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
938
					array($_GET['th'], $user_info['id'], 'theme_variant', $_GET['vrt']),
939
					array('id_theme', 'id_member', 'variable')
940
				);
941
				cache_put_data('theme_settings-' . $_GET['th'] . ':' . $user_info['id'], null, 90);
942
943
				$_SESSION['id_variant'] = 0;
944
			}
945
946
			redirectexit('action=profile;area=theme');
947
		}
948
949
		// If changing members or guests - and there's a variant - assume changing default variant.
950
		if (!empty($_GET['vrt']) && ($_REQUEST['u'] == '0' || $_REQUEST['u'] == '-1'))
951
		{
952
			$smcFunc['db_insert']('replace',
953
				'{db_prefix}themes',
954
				array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
955
				array($_GET['th'], 0, 'default_variant', $_GET['vrt']),
956
				array('id_theme', 'id_member', 'variable')
957
			);
958
959
			// Make it obvious that it's changed
960
			cache_put_data('theme_settings-' . $_GET['th'], null, 90);
961
		}
962
963
		// For everyone.
964
		if ($_REQUEST['u'] == '0')
965
		{
966
			updateMemberData(null, array('id_theme' => (int) $_GET['th']));
967
968
			// Remove any custom variants.
969
			if (!empty($_GET['vrt']))
970
			{
971
				$smcFunc['db_query']('', '
972
					DELETE FROM {db_prefix}themes
973
					WHERE id_theme = {int:current_theme}
974
						AND variable = {string:theme_variant}',
975
					array(
976
						'current_theme' => (int) $_GET['th'],
977
						'theme_variant' => 'theme_variant',
978
					)
979
				);
980
			}
981
982
			redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
983
		}
984
		// Change the default/guest theme.
985
		elseif ($_REQUEST['u'] == '-1')
986
		{
987
			updateSettings(array('theme_guests' => (int) $_GET['th']));
988
989
			redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
990
		}
991
		// Change a specific member's theme.
992
		else
993
		{
994
			// The forum's default theme is always 0 and we
995
			if (isset($_GET['th']) && $_GET['th'] == 0)
996
					$_GET['th'] = $modSettings['theme_guests'];
997
998
			updateMemberData((int) $_REQUEST['u'], array('id_theme' => (int) $_GET['th']));
999
1000
			if (!empty($_GET['vrt']))
1001
			{
1002
				$smcFunc['db_insert']('replace',
1003
					'{db_prefix}themes',
1004
					array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
1005
					array($_GET['th'], (int) $_REQUEST['u'], 'theme_variant', $_GET['vrt']),
1006
					array('id_theme', 'id_member', 'variable')
1007
				);
1008
				cache_put_data('theme_settings-' . $_GET['th'] . ':' . (int) $_REQUEST['u'], null, 90);
1009
1010
				if ($user_info['id'] == $_REQUEST['u'])
1011
					$_SESSION['id_variant'] = 0;
1012
			}
1013
1014
			redirectexit('action=profile;u=' . (int) $_REQUEST['u'] . ';area=theme');
1015
		}
1016
	}
1017
1018
	// Figure out who the member of the minute is, and what theme they've chosen.
1019
	if (!isset($_REQUEST['u']) || !allowedTo('admin_forum'))
1020
	{
1021
		$context['current_member'] = $user_info['id'];
1022
		$context['current_theme'] = $user_info['theme'];
1023
	}
1024
	// Everyone can't chose just one.
1025
	elseif ($_REQUEST['u'] == '0')
1026
	{
1027
		$context['current_member'] = 0;
1028
		$context['current_theme'] = 0;
1029
	}
1030
	// Guests and such...
1031
	elseif ($_REQUEST['u'] == '-1')
1032
	{
1033
		$context['current_member'] = -1;
1034
		$context['current_theme'] = $modSettings['theme_guests'];
1035
	}
1036
	// Someones else :P.
1037 View Code Duplication
	else
1038
	{
1039
		$context['current_member'] = (int) $_REQUEST['u'];
1040
1041
		$request = $smcFunc['db_query']('', '
1042
			SELECT id_theme
1043
			FROM {db_prefix}members
1044
			WHERE id_member = {int:current_member}
1045
			LIMIT 1',
1046
			array(
1047
				'current_member' => $context['current_member'],
1048
			)
1049
		);
1050
		list ($context['current_theme']) = $smcFunc['db_fetch_row']($request);
1051
		$smcFunc['db_free_result']($request);
1052
	}
1053
1054
	// Get the theme name and descriptions.
1055
	$context['available_themes'] = array();
1056
	if (!empty($modSettings['knownThemes']))
1057
	{
1058
		$request = $smcFunc['db_query']('', '
1059
			SELECT id_theme, variable, value
1060
			FROM {db_prefix}themes
1061
			WHERE variable IN ({string:name}, {string:theme_url}, {string:theme_dir}, {string:images_url}, {string:disable_user_variant})' . (!allowedTo('admin_forum') ? '
1062
				AND id_theme IN ({array_string:known_themes})' : '') . '
1063
				AND id_theme != {int:default_theme}
1064
				AND id_member = {int:no_member}
1065
				AND id_theme IN ({array_string:enable_themes})',
1066
			array(
1067
				'default_theme' => 0,
1068
				'name' => 'name',
1069
				'no_member' => 0,
1070
				'theme_url' => 'theme_url',
1071
				'theme_dir' => 'theme_dir',
1072
				'images_url' => 'images_url',
1073
				'disable_user_variant' => 'disable_user_variant',
1074
				'known_themes' => explode(',', $modSettings['knownThemes']),
1075
				'enable_themes' => explode(',', $modSettings['enableThemes']),
1076
			)
1077
		);
1078
		while ($row = $smcFunc['db_fetch_assoc']($request))
1079
		{
1080
			if (!isset($context['available_themes'][$row['id_theme']]))
1081
				$context['available_themes'][$row['id_theme']] = array(
1082
					'id' => $row['id_theme'],
1083
					'selected' => $context['current_theme'] == $row['id_theme'],
1084
					'num_users' => 0
1085
				);
1086
			$context['available_themes'][$row['id_theme']][$row['variable']] = $row['value'];
1087
		}
1088
		$smcFunc['db_free_result']($request);
1089
	}
1090
1091
	// Okay, this is a complicated problem: the default theme is 1, but they aren't allowed to access 1!
1092
	if (!isset($context['available_themes'][$modSettings['theme_guests']]))
1093
	{
1094
		$context['available_themes'][0] = array(
1095
			'num_users' => 0
1096
		);
1097
		$guest_theme = 0;
1098
	}
1099
	else
1100
		$guest_theme = $modSettings['theme_guests'];
1101
1102
	$request = $smcFunc['db_query']('', '
1103
		SELECT id_theme, COUNT(*) AS the_count
1104
		FROM {db_prefix}members
1105
		GROUP BY id_theme
1106
		ORDER BY id_theme DESC',
1107
		array(
1108
		)
1109
	);
1110
	while ($row = $smcFunc['db_fetch_assoc']($request))
1111
	{
1112
		// Figure out which theme it is they are REALLY using.
1113
		if (!empty($modSettings['knownThemes']) && !in_array($row['id_theme'], explode(',', $modSettings['knownThemes'])))
1114
			$row['id_theme'] = $guest_theme;
1115
		elseif (empty($modSettings['theme_allow']))
1116
			$row['id_theme'] = $guest_theme;
1117
1118
		if (isset($context['available_themes'][$row['id_theme']]))
1119
			$context['available_themes'][$row['id_theme']]['num_users'] += $row['the_count'];
1120
		else
1121
			$context['available_themes'][$guest_theme]['num_users'] += $row['the_count'];
1122
	}
1123
	$smcFunc['db_free_result']($request);
1124
1125
	// Get any member variant preferences.
1126
	$variant_preferences = array();
1127
	if ($context['current_member'] > 0)
1128
	{
1129
		$request = $smcFunc['db_query']('', '
1130
			SELECT id_theme, value
1131
			FROM {db_prefix}themes
1132
			WHERE variable = {string:theme_variant}
1133
				AND id_member IN ({array_int:id_member})
1134
			ORDER BY id_member ASC',
1135
			array(
1136
				'theme_variant' => 'theme_variant',
1137
				'id_member' => isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? array(-1, $context['current_member']) : array(-1),
1138
			)
1139
		);
1140
		while ($row = $smcFunc['db_fetch_assoc']($request))
1141
			$variant_preferences[$row['id_theme']] = $row['value'];
1142
		$smcFunc['db_free_result']($request);
1143
	}
1144
1145
	// Save the setting first.
1146
	$current_images_url = $settings['images_url'];
1147
	$current_theme_variants = !empty($settings['theme_variants']) ? $settings['theme_variants'] : array();
1148
1149
	foreach ($context['available_themes'] as $id_theme => $theme_data)
1150
	{
1151
		// Don't try to load the forum or board default theme's data... it doesn't have any!
1152
		if ($id_theme == 0)
1153
			continue;
1154
1155
		// The thumbnail needs the correct path.
1156
		$settings['images_url'] = &$theme_data['images_url'];
1157
1158
		if (file_exists($theme_data['theme_dir'] . '/languages/Settings.' . $user_info['language'] . '.php'))
1159
			include($theme_data['theme_dir'] . '/languages/Settings.' . $user_info['language'] . '.php');
1160
		elseif (file_exists($theme_data['theme_dir'] . '/languages/Settings.' . $language . '.php'))
1161
			include($theme_data['theme_dir'] . '/languages/Settings.' . $language . '.php');
1162
		else
1163
		{
1164
			$txt['theme_thumbnail_href'] = $theme_data['images_url'] . '/thumbnail.png';
1165
			$txt['theme_description'] = '';
1166
		}
1167
1168
		$context['available_themes'][$id_theme]['thumbnail_href'] = $txt['theme_thumbnail_href'];
1169
		$context['available_themes'][$id_theme]['description'] = $txt['theme_description'];
1170
1171
		// Are there any variants?
1172
		if (file_exists($theme_data['theme_dir'] . '/index.template.php') && (empty($theme_data['disable_user_variant']) || allowedTo('admin_forum')))
1173
		{
1174
			$file_contents = implode('', file($theme_data['theme_dir'] . '/index.template.php'));
1175
			if (preg_match('~\$settings\[\'theme_variants\'\]\s*=(.+?);~', $file_contents, $matches))
1176
			{
1177
				$settings['theme_variants'] = array();
1178
1179
				// Fill settings up.
1180
				eval('global $settings;' . $matches[0]);
0 ignored issues
show
Coding Style introduced by
The function PickTheme() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
1181
1182
				if (!empty($settings['theme_variants']))
1183
				{
1184
					loadLanguage('Settings');
1185
1186
					$context['available_themes'][$id_theme]['variants'] = array();
1187 View Code Duplication
					foreach ($settings['theme_variants'] as $variant)
1188
						$context['available_themes'][$id_theme]['variants'][$variant] = array(
1189
							'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant,
1190
							'thumbnail' => !file_exists($theme_data['theme_dir'] . '/images/thumbnail.png') || file_exists($theme_data['theme_dir'] . '/images/thumbnail_' . $variant . '.png') ? $theme_data['images_url'] . '/thumbnail_' . $variant . '.png' : ($theme_data['images_url'] . '/thumbnail.png'),
1191
						);
1192
1193
					$context['available_themes'][$id_theme]['selected_variant'] = isset($_GET['vrt']) ? $_GET['vrt'] : (!empty($variant_preferences[$id_theme]) ? $variant_preferences[$id_theme] : (!empty($settings['default_variant']) ? $settings['default_variant'] : $settings['theme_variants'][0]));
1194
					if (!isset($context['available_themes'][$id_theme]['variants'][$context['available_themes'][$id_theme]['selected_variant']]['thumbnail']))
1195
						$context['available_themes'][$id_theme]['selected_variant'] = $settings['theme_variants'][0];
1196
1197
					$context['available_themes'][$id_theme]['thumbnail_href'] = $context['available_themes'][$id_theme]['variants'][$context['available_themes'][$id_theme]['selected_variant']]['thumbnail'];
1198
					// Allow themes to override the text.
1199
					$context['available_themes'][$id_theme]['pick_label'] = isset($txt['variant_pick']) ? $txt['variant_pick'] : $txt['theme_pick_variant'];
1200
				}
1201
			}
1202
		}
1203
	}
1204
	// Then return it.
1205
	$settings['images_url'] = $current_images_url;
1206
	$settings['theme_variants'] = $current_theme_variants;
1207
1208
	// As long as we're not doing the default theme...
1209
	if (!isset($_REQUEST['u']) || $_REQUEST['u'] >= 0)
1210
	{
1211
		if ($guest_theme != 0)
1212
			$context['available_themes'][0] = $context['available_themes'][$guest_theme];
1213
1214
		$context['available_themes'][0]['id'] = 0;
1215
		$context['available_themes'][0]['name'] = $txt['theme_forum_default'];
1216
		$context['available_themes'][0]['selected'] = $context['current_theme'] == 0;
1217
		$context['available_themes'][0]['description'] = $txt['theme_global_description'];
1218
	}
1219
1220
	ksort($context['available_themes']);
1221
1222
	$context['page_title'] = $txt['theme_pick'];
1223
	$context['sub_template'] = 'pick';
1224
}
1225
1226
/**
1227
 * Installs new themes, calls the respective function according to the install type.
1228
 * - puts themes in $boardurl/Themes.
1229
 * - assumes the gzip has a root directory in it. (ie default.)
1230
 * Requires admin_forum.
1231
 * Accessed with ?action=admin;area=theme;sa=install.
1232
 */
1233
function ThemeInstall()
1234
{
1235
	global $sourcedir, $txt, $context, $boarddir, $boardurl;
1236
	global $themedir, $themeurl, $smcFunc;
1237
1238
	checkSession('request');
1239
	isAllowedTo('admin_forum');
1240
1241
	require_once($sourcedir . '/Subs-Package.php');
1242
1243
	// Make it easier to change the path and url.
1244
	$themedir = $boarddir . '/Themes';
1245
	$themeurl = $boardurl . '/Themes';
1246
1247
	loadTemplate('Themes');
1248
1249
	$subActions = array(
1250
		'file' => 'InstallFile',
1251
		'copy' => 'InstallCopy',
1252
		'dir' => 'InstallDir',
1253
	);
1254
1255
	// Is there a function to call?
1256
	if (isset($_GET['do']) && !empty($_GET['do']) && isset($subActions[$_GET['do']]))
1257
	{
1258
		$action = $smcFunc['htmlspecialchars'](trim($_GET['do']));
1259
1260
		// Got any info from the specific form?
1261
		if (!isset($_POST['save_' . $action]))
1262
			fatal_lang_error('theme_install_no_action', false);
1263
1264
		validateToken('admin-t-' . $action);
1265
1266
		// Hopefully the themes directory is writable, or we might have a problem.
1267
		if (!is_writable($themedir))
1268
			fatal_lang_error('theme_install_write_error', 'critical');
1269
1270
		// Call the function and handle the result.
1271
		$result = $subActions[$action]();
1272
1273
		// Everything went better than expected!
1274
		if (!empty($result))
1275
		{
1276
			$context['sub_template'] = 'installed';
1277
			$context['page_title'] = $txt['theme_installed'];
1278
			$context['installed_theme'] = $result;
1279
		}
1280
	}
1281
1282
	// Nope, show a nice error.
1283
	else
1284
		fatal_lang_error('theme_install_no_action', false);
1285
}
1286
1287
/**
1288
 * Installs a theme from a theme package.
1289
 *
1290
 * Stores the theme files on a temp dir, on success it renames the dir to the new theme's name. Ends execution with fatal_lang_error() on any error.
1291
 * @return array The newly created theme's info.
1292
 */
1293
function InstallFile()
1294
{
1295
	global $themedir, $themeurl, $context;
1296
1297
	// Set a temp dir for dumping all required files on it.
1298
	$dirtemp = $themedir . '/temp';
1299
1300
	// Make sure the temp dir doesn't already exist
1301
	if (file_exists($dirtemp))
1302
		remove_dir($dirtemp);
1303
1304
	// Create the temp dir.
1305
	mkdir($dirtemp, 0777);
1306
1307
	// Hopefully the temp directory is writable, or we might have a problem.
1308
	if (!is_writable($dirtemp))
1309
	{
1310
		// Lets give it a try.
1311
		smf_chmod($dirtemp, '0755');
1312
1313
		// How about now?
1314
		if (!is_writable($dirtemp))
1315
			fatal_lang_error('theme_install_write_error', 'critical');
1316
	}
1317
1318
	// This happens when the admin session is gone and the user has to login again.
1319
	if (!isset($_FILES) || !isset($_FILES['theme_gz']) || empty($_FILES['theme_gz']))
1320
		redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
1321
1322
	// Another error check layer, something went wrong with the upload.
1323
	if (isset($_FILES['theme_gz']['error']) && $_FILES['theme_gz']['error'] != 0)
1324
		fatal_lang_error('theme_install_error_file_' . $_FILES['theme_gz']['error'], false);
1325
1326
	// Get the theme's name.
1327
	$name = pathinfo($_FILES['theme_gz']['name'], PATHINFO_FILENAME);
1328
	$name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $name);
1329
1330
	// Start setting some vars.
1331
	$context['to_install'] = array(
1332
		'theme_dir' => $themedir . '/' . $name,
1333
		'theme_url' => $themeurl . '/' . $name,
1334
		'images_url' => $themeurl . '/' . $name . '/images',
1335
		'name' => $name,
1336
	);
1337
1338
	// Extract the file on the proper themes dir.
1339
	$extracted = read_tgz_file($_FILES['theme_gz']['tmp_name'], $dirtemp, false, true);
1340
1341
	if ($extracted)
1342
	{
1343
		// Read its info form the XML file.
1344
		$theme_info = get_theme_info($dirtemp);
1345
		$context['to_install'] += $theme_info;
1346
1347
		// Install the theme. theme_install() will return the new installed ID.
1348
		$context['to_install']['id'] = theme_install($context['to_install']);
1349
1350
		// Rename the temp dir to the actual theme name.
1351
		rename($dirtemp, $context['to_install']['theme_dir']);
1352
1353
		// return all the info.
1354
		return $context['to_install'];
1355
	}
1356
1357
	else
1358
		fatal_lang_error('theme_install_error_title', false);
1359
}
1360
1361
/**
1362
 * Makes a copy from the default theme, assigns a name for it and installs it.
1363
 *
1364
 * Creates a new .xml file containing all the theme's info.
1365
 * @return array The newly created theme's info.
1366
 */
1367
function InstallCopy()
1368
{
1369
	global $themedir, $themeurl, $settings, $smcFunc, $context;
1370
	global $forum_version;
1371
1372
	// There's gotta be something to work with.
1373
	if (!isset($_REQUEST['copy']) || empty($_REQUEST['copy']))
1374
		fatal_lang_error('theme_install_error_title', false);
1375
1376
	// Get a cleaner version.
1377
	$name = preg_replace('~[^A-Za-z0-9_\- ]~', '', $_REQUEST['copy']);
1378
1379
	// Is there a theme already named like this?
1380
	if (file_exists($themedir . '/' . $name))
1381
		fatal_lang_error('theme_install_already_dir', false);
1382
1383
	// This is a brand new theme so set all possible values.
1384
	$context['to_install'] = array(
1385
		'theme_dir' => $themedir . '/' . $name,
1386
		'theme_url' => $themeurl . '/' . $name,
1387
		'name' => $name,
1388
		'images_url' => $themeurl . '/' . $name . '/images',
1389
		'version' => '1.0',
1390
		'install_for' => '2.1 - 2.1.99, ' . strtr($forum_version, array('SMF ' => '')),
1391
		'based_on' => '',
1392
		'based_on_dir' => $themedir . '/default',
1393
	);
1394
1395
	// Create the specific dir.
1396
	umask(0);
1397
	mkdir($context['to_install']['theme_dir'], 0777);
1398
1399
	// Buy some time.
1400
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1401
	if (function_exists('apache_reset_timeout'))
1402
		@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1403
1404
	// Create subdirectories for css and javascript files.
1405
	mkdir($context['to_install']['theme_dir'] . '/css', 0777);
1406
	mkdir($context['to_install']['theme_dir'] . '/scripts', 0777);
1407
1408
	// Copy over the default non-theme files.
1409
	$to_copy = array('/index.php', '/index.template.php', '/css/index.css', '/css/responsive.css', '/css/slider.min.css', '/css/rtl.css', '/css/calendar.css', '/css/calendar.rtl.css', '/css/admin.css', '/scripts/theme.js');
1410
1411
	foreach ($to_copy as $file)
1412
	{
1413
		copy($settings['default_theme_dir'] . $file, $context['to_install']['theme_dir'] . $file);
1414
		smf_chmod($context['to_install']['theme_dir'] . $file, 0777);
1415
	}
1416
1417
	// And now the entire images directory!
1418
	copytree($settings['default_theme_dir'] . '/images', $context['to_install']['theme_dir'] . '/images');
1419
	package_flush_cache();
1420
1421
	// Lets get some data for the new theme.
1422
	$request = $smcFunc['db_query']('', '
1423
		SELECT variable, value
1424
		FROM {db_prefix}themes
1425
		WHERE variable IN ({string:theme_templates}, {string:theme_layers})
1426
			AND id_member = {int:no_member}
1427
			AND id_theme = {int:default_theme}',
1428
		array(
1429
			'no_member' => 0,
1430
			'default_theme' => 1,
1431
			'theme_templates' => 'theme_templates',
1432
			'theme_layers' => 'theme_layers',
1433
		)
1434
	);
1435
1436
	while ($row = $smcFunc['db_fetch_assoc']($request))
1437
	{
1438
		if ($row['variable'] == 'theme_templates')
1439
			$theme_templates = $row['value'];
1440
		elseif ($row['variable'] == 'theme_layers')
1441
			$theme_layers = $row['value'];
1442
		else
1443
			continue;
1444
	}
1445
1446
	$smcFunc['db_free_result']($request);
1447
1448
	$context['to_install'] += array(
1449
		'theme_layers' => empty($theme_layers) ? 'html,body' : $theme_layers,
1450
		'theme_templates' => empty($theme_templates) ? 'index' : $theme_templates,
1451
	);
1452
1453
	// Lets add a theme_info.xml to this theme.
1454
	$xml_info = '<' . '?xml version="1.0"?' . '>
1455
<theme-info xmlns="http://www.simplemachines.org/xml/theme-info" xmlns:smf="http://www.simplemachines.org/">
1456
<!-- For the id, always use something unique - put your name, a colon, and then the package name. -->
1457
<id>smf:' . $smcFunc['strtolower']($context['to_install']['name']) . '</id>
1458
<!-- The theme\'s version, please try to use semantic versioning. -->
1459
<version>1.0</version>
1460
<!-- Install for, the SMF versions this theme was designed for. Uses the same wildcards used in the packager manager. This field is mandatory. -->
1461
<install for="'. $context['to_install']['install_for'] . '" />
1462
<!-- Theme name, used purely for aesthetics. -->
1463
<name>' . $context['to_install']['name'] . '</name>
1464
<!-- Author: your email address or contact information. The name attribute is optional. -->
1465
<author name="Simple Machines">[email protected]</author>
1466
<!-- Website... where to get updates and more information. -->
1467
<website>https://www.simplemachines.org/</website>
1468
<!-- Template layers to use, defaults to "html,body". -->
1469
<layers>' . $context['to_install']['theme_layers'] . '</layers>
1470
<!-- Templates to load on startup. Default is "index". -->
1471
<templates>' . $context['to_install']['theme_templates'] . '</templates>
1472
<!-- Base this theme off another? Default is blank, or no. It could be "default". -->
1473
<based-on></based-on>
1474
</theme-info>';
1475
1476
	// Now write it.
1477
	$fp = @fopen($context['to_install']['theme_dir'] . '/theme_info.xml', 'w+');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1478
	if ($fp)
1479
	{
1480
		fwrite($fp, $xml_info);
1481
		fclose($fp);
1482
	}
1483
1484
	// Install the theme. theme_install() will take care of possible errors.
1485
	$context['to_install']['id'] = theme_install($context['to_install']);
1486
1487
	// return the info.
1488
	return $context['to_install'];
1489
}
1490
1491
/**
1492
 * Install a theme from a specific dir
1493
 *
1494
 * Assumes the dir is located on the main Themes dir. Ends execution with fatal_lang_error() on any error.
1495
 * @return array The newly created theme's info.
1496
 */
1497
function InstallDir()
1498
{
1499
	global $themedir, $themeurl, $context;
1500
1501
	// Cannot use the theme dir as a theme dir.
1502
	if (!isset($_REQUEST['theme_dir']) || empty($_REQUEST['theme_dir']) || rtrim(realpath($_REQUEST['theme_dir']), '/\\') == realpath($themedir))
1503
		fatal_lang_error('theme_install_invalid_dir', false);
1504
1505
	// Check is there is "something" on the dir.
1506 View Code Duplication
	elseif (!is_dir($_REQUEST['theme_dir']) || !file_exists($_REQUEST['theme_dir'] . '/theme_info.xml'))
1507
		fatal_lang_error('theme_install_error', false);
1508
1509
	$name = basename($_REQUEST['theme_dir']);
1510
	$name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $name);
1511
1512
	// All good! set some needed vars.
1513
	$context['to_install'] = array(
1514
		'theme_dir' => $_REQUEST['theme_dir'],
1515
		'theme_url' => $themeurl . '/' . $name,
1516
		'name' => $name,
1517
		'images_url' => $themeurl . '/' . $name . '/images',
1518
	);
1519
1520
	// Read its info form the XML file.
1521
	$theme_info = get_theme_info($context['to_install']['theme_dir']);
1522
	$context['to_install'] += $theme_info;
1523
1524
	// Install the theme. theme_install() will take care of possible errors.
1525
	$context['to_install']['id'] = theme_install($context['to_install']);
1526
1527
	// return the info.
1528
	return $context['to_install'];
1529
}
1530
1531
/**
1532
 * Possibly the simplest and best example of how to use the template system.
1533
 *  - allows the theme to take care of actions.
1534
 *  - happens if $settings['catch_action'] is set and action isn't found
1535
 *   in the action array.
1536
 *  - can use a template, layers, sub_template, filename, and/or function.
1537
 */
1538
function WrapAction()
1539
{
1540
	global $context, $settings;
1541
1542
	// Load any necessary template(s)?
1543
	if (isset($settings['catch_action']['template']))
1544
	{
1545
		// Load both the template and language file. (but don't fret if the language file isn't there...)
1546
		loadTemplate($settings['catch_action']['template']);
1547
		loadLanguage($settings['catch_action']['template'], '', false);
1548
	}
1549
1550
	// Any special layers?
1551
	if (isset($settings['catch_action']['layers']))
1552
		$context['template_layers'] = $settings['catch_action']['layers'];
1553
1554
	// Any function to call?
1555
	if (isset($settings['catch_action']['function']))
1556
	{
1557
		$hook = $settings['catch_action']['function'];
1558
1559
		if (!isset($settings['catch_action']['filename']))
1560
			$settings['catch_action']['filename'] = '';
1561
1562
		add_integration_function('integrate_wrap_action', $hook, false, $settings['catch_action']['filename'], false);
1563
		call_integration_hook('integrate_wrap_action');
1564
	}
1565
	// And finally, the main sub template ;).
1566
	if (isset($settings['catch_action']['sub_template']))
1567
		$context['sub_template'] = $settings['catch_action']['sub_template'];
1568
}
1569
1570
/**
1571
 * Set an option via javascript.
1572
 * - sets a theme option without outputting anything.
1573
 * - can be used with javascript, via a dummy image... (which doesn't require
1574
 * the page to reload.)
1575
 * - requires someone who is logged in.
1576
 * - accessed via ?action=jsoption;var=variable;val=value;session_var=sess_id.
1577
 * - does not log access to the Who's Online log. (in index.php..)
1578
 */
1579
function SetJavaScript()
1580
{
1581
	global $settings, $user_info, $smcFunc, $options;
1582
1583
	// Check the session id.
1584
	checkSession('get');
1585
1586
	// This good-for-nothing pixel is being used to keep the session alive.
1587
	if (empty($_GET['var']) || !isset($_GET['val']))
1588
		redirectexit($settings['images_url'] . '/blank.png');
1589
1590
	// Sorry, guests can't go any further than this.
1591
	if ($user_info['is_guest'] || $user_info['id'] == 0)
1592
		obExit(false);
1593
1594
	$reservedVars = array(
1595
		'actual_theme_url',
1596
		'actual_images_url',
1597
		'base_theme_dir',
1598
		'base_theme_url',
1599
		'default_images_url',
1600
		'default_theme_dir',
1601
		'default_theme_url',
1602
		'default_template',
1603
		'images_url',
1604
		'number_recent_posts',
1605
		'smiley_sets_default',
1606
		'theme_dir',
1607
		'theme_id',
1608
		'theme_layers',
1609
		'theme_templates',
1610
		'theme_url',
1611
		'name',
1612
	);
1613
1614
	// Can't change reserved vars.
1615
	if (in_array(strtolower($_GET['var']), $reservedVars))
1616
		redirectexit($settings['images_url'] . '/blank.png');
1617
1618
	// Use a specific theme?
1619
	if (isset($_GET['th']) || isset($_GET['id']))
1620
	{
1621
		// Invalidate the current themes cache too.
1622
		cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 60);
1623
1624
		$settings['theme_id'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
1625
	}
1626
1627
	// If this is the admin preferences the passed value will just be an element of it.
1628
	if ($_GET['var'] == 'admin_preferences')
1629
	{
1630
		$options['admin_preferences'] = !empty($options['admin_preferences']) ? $smcFunc['json_decode']($options['admin_preferences'], true) : array();
1631
		// New thingy...
1632
		if (isset($_GET['admin_key']) && strlen($_GET['admin_key']) < 5)
1633
			$options['admin_preferences'][$_GET['admin_key']] = $_GET['val'];
1634
1635
		// Change the value to be something nice,
1636
		$_GET['val'] = $smcFunc['json_encode']($options['admin_preferences']);
1637
	}
1638
1639
	// Update the option.
1640
	$smcFunc['db_insert']('replace',
1641
		'{db_prefix}themes',
1642
		array('id_theme' => 'int', 'id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
1643
		array($settings['theme_id'], $user_info['id'], $_GET['var'], is_array($_GET['val']) ? implode(',', $_GET['val']) : $_GET['val']),
1644
		array('id_theme', 'id_member', 'variable')
1645
	);
1646
1647
	cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 60);
1648
1649
	// Don't output anything...
1650
	redirectexit($settings['images_url'] . '/blank.png');
1651
}
1652
1653
/**
1654
 * Shows an interface for editing the templates.
1655
 * - uses the Themes template and edit_template/edit_style sub template.
1656
 * - accessed via ?action=admin;area=theme;sa=edit
1657
 */
1658
function EditTheme()
1659
{
1660
	global $context, $scripturl, $boarddir, $smcFunc, $txt;
1661
1662
	// @todo Should this be removed?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1663
	if (isset($_REQUEST['preview']))
1664
		die('die() with fire');
1665
1666
	isAllowedTo('admin_forum');
1667
	loadTemplate('Themes');
1668
1669
	$_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) @$_GET['id'];
1670
1671
	if (empty($_GET['th']))
1672
	{
1673
		get_all_themes();
1674
1675
		foreach ($context['themes'] as $key => $theme)
1676
		{
1677
			// There has to be a Settings template!
1678
			if (!file_exists($theme['theme_dir'] . '/index.template.php') && !file_exists($theme['theme_dir'] . '/css/index.css'))
1679
				unset($context['themes'][$key]);
1680
1681
			else
1682
				$context['themes'][$key]['can_edit_style'] = file_exists($theme['theme_dir'] . '/css/index.css');
1683
		}
1684
1685
		$context['sub_template'] = 'edit_list';
1686
1687
		return 'no_themes';
1688
	}
1689
1690
	$context['session_error'] = false;
1691
1692
	// Get the directory of the theme we are editing.
1693
	$currentTheme = get_single_theme($_GET['th']);
1694
	$context['theme_id'] = $currentTheme['id'];
1695
	$context['browse_title'] = sprintf($txt['themeadmin_browsing_theme'], $currentTheme['name']);
1696
1697 View Code Duplication
	if (!file_exists($currentTheme['theme_dir'] . '/index.template.php') && !file_exists($currentTheme['theme_dir'] . '/css/index.css'))
1698
		fatal_lang_error('theme_edit_missing', false);
1699
1700
	if (!isset($_REQUEST['filename']))
1701
	{
1702 View Code Duplication
		if (isset($_GET['directory']))
1703
		{
1704
			if (substr($_GET['directory'], 0, 1) == '.')
1705
				$_GET['directory'] = '';
1706
			else
1707
			{
1708
				$_GET['directory'] = preg_replace(array('~^[\./\\:\0\n\r]+~', '~[\\\\]~', '~/[\./]+~'), array('', '/', '/'), $_GET['directory']);
1709
1710
				$temp = realpath($currentTheme['theme_dir'] . '/' . $_GET['directory']);
1711
				if (empty($temp) || substr($temp, 0, strlen(realpath($currentTheme['theme_dir']))) != realpath($currentTheme['theme_dir']))
1712
					$_GET['directory'] = '';
1713
			}
1714
		}
1715
1716
		if (isset($_GET['directory']) && $_GET['directory'] != '')
1717
		{
1718
			$context['theme_files'] = get_file_listing($currentTheme['theme_dir'] . '/' . $_GET['directory'], $_GET['directory'] . '/');
1719
1720
			$temp = dirname($_GET['directory']);
1721
			array_unshift($context['theme_files'], array(
1722
				'filename' => $temp == '.' || $temp == '' ? '/ (..)' : $temp . ' (..)',
1723
				'is_writable' => is_writable($currentTheme['theme_dir'] . '/' . $temp),
1724
				'is_directory' => true,
1725
				'is_template' => false,
1726
				'is_image' => false,
1727
				'is_editable' => false,
1728
				'href' => $scripturl . '?action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . $temp,
1729
				'size' => '',
1730
			));
1731
		}
1732
		else
1733
			$context['theme_files'] = get_file_listing($currentTheme['theme_dir'], '');
1734
1735
		$context['sub_template'] = 'edit_browse';
1736
1737
		return;
1738
	}
1739
	else
1740
	{
1741 View Code Duplication
		if (substr($_REQUEST['filename'], 0, 1) == '.')
1742
			$_REQUEST['filename'] = '';
1743
		else
1744
		{
1745
			$_REQUEST['filename'] = preg_replace(array('~^[\./\\:\0\n\r]+~', '~[\\\\]~', '~/[\./]+~'), array('', '/', '/'), $_REQUEST['filename']);
1746
1747
			$temp = realpath($currentTheme['theme_dir'] . '/' . $_REQUEST['filename']);
1748
			if (empty($temp) || substr($temp, 0, strlen(realpath($currentTheme['theme_dir']))) != realpath($currentTheme['theme_dir']))
1749
				$_REQUEST['filename'] = '';
1750
		}
1751
1752
		if (empty($_REQUEST['filename']))
1753
			fatal_lang_error('theme_edit_missing', false);
1754
	}
1755
1756
	if (isset($_POST['save']))
1757
	{
1758
		if (checkSession('post', '', false) == '' && validateToken('admin-te-' . md5($_GET['th'] . '-' . $_REQUEST['filename']), 'post', false) == true)
1759
		{
1760
			if (is_array($_POST['entire_file']))
1761
				$_POST['entire_file'] = implode("\n", $_POST['entire_file']);
1762
1763
			$_POST['entire_file'] = rtrim(strtr($_POST['entire_file'], array("\r" => '', '   ' => "\t")));
1764
1765
			// Check for a parse error!
1766
			if (substr($_REQUEST['filename'], -13) == '.template.php' && is_writable($currentTheme['theme_dir']) && ini_get('display_errors'))
1767
			{
1768
				$fp = fopen($currentTheme['theme_dir'] . '/tmp_' . session_id() . '.php', 'w');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1769
				fwrite($fp, $_POST['entire_file']);
1770
				fclose($fp);
1771
1772
				$error = @file_get_contents($currentTheme['theme_url'] . '/tmp_' . session_id() . '.php');
1773
				if (preg_match('~ <b>(\d+)</b><br( /)?' . '>$~i', $error) != 0)
1774
					$error_file = $currentTheme['theme_dir'] . '/tmp_' . session_id() . '.php';
1775
				else
1776
					unlink($currentTheme['theme_dir'] . '/tmp_' . session_id() . '.php');
1777
			}
1778
1779
			if (!isset($error_file))
1780
			{
1781
				$fp = fopen($currentTheme['theme_dir'] . '/' . $_REQUEST['filename'], 'w');
1782
				fwrite($fp, $_POST['entire_file']);
1783
				fclose($fp);
1784
1785
				redirectexit('action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename']));
1786
			}
1787
		}
1788
		// Session timed out.
1789
		else
1790
		{
1791
			loadLanguage('Errors');
1792
1793
			$context['session_error'] = true;
1794
			$context['sub_template'] = 'edit_file';
1795
1796
			// Recycle the submitted data.
1797
			if (is_array($_POST['entire_file']))
1798
				$context['entire_file'] = $smcFunc['htmlspecialchars'](implode("\n", $_POST['entire_file']));
1799
			else
1800
				$context['entire_file'] = $smcFunc['htmlspecialchars']($_POST['entire_file']);
1801
1802
			$context['edit_filename'] = $smcFunc['htmlspecialchars']($_POST['filename']);
1803
1804
			// You were able to submit it, so it's reasonable to assume you are allowed to save.
1805
			$context['allow_save'] = true;
1806
1807
			// Re-create the token so that it can be used
1808
			createToken('admin-te-' . md5($_GET['th'] . '-' . $_REQUEST['filename']));
1809
1810
			return;
1811
		}
1812
	}
1813
1814
	$context['allow_save'] = is_writable($currentTheme['theme_dir'] . '/' . $_REQUEST['filename']);
1815
	$context['allow_save_filename'] = strtr($currentTheme['theme_dir'] . '/' . $_REQUEST['filename'], array($boarddir => '...'));
1816
	$context['edit_filename'] = $smcFunc['htmlspecialchars']($_REQUEST['filename']);
1817
1818
	if (substr($_REQUEST['filename'], -4) == '.css')
1819
	{
1820
		$context['sub_template'] = 'edit_style';
1821
1822
		$context['entire_file'] = $smcFunc['htmlspecialchars'](strtr(file_get_contents($currentTheme['theme_dir'] . '/' . $_REQUEST['filename']), array("\t" => '   ')));
1823
	}
1824
	elseif (substr($_REQUEST['filename'], -13) == '.template.php')
1825
	{
1826
		$context['sub_template'] = 'edit_template';
1827
1828
		if (!isset($error_file))
1829
			$file_data = file($currentTheme['theme_dir'] . '/' . $_REQUEST['filename']);
1830
		else
1831
		{
1832
			if (preg_match('~(<b>.+?</b>:.+?<b>).+?(</b>.+?<b>\d+</b>)<br( /)?' . '>$~i', $error, $match) != 0)
0 ignored issues
show
Bug introduced by
The variable $error does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1833
				$context['parse_error'] = $match[1] . $_REQUEST['filename'] . $match[2];
1834
			$file_data = file($error_file);
1835
			unlink($error_file);
1836
		}
1837
1838
		$j = 0;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $j. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1839
		$context['file_parts'] = array(array('lines' => 0, 'line' => 1, 'data' => ''));
1840
		for ($i = 0, $n = count($file_data); $i < $n; $i++)
1841
		{
1842
			if (isset($file_data[$i + 1]) && substr($file_data[$i + 1], 0, 9) == 'function ')
1843
			{
1844
				// Try to format the functions a little nicer...
1845
				$context['file_parts'][$j]['data'] = trim($context['file_parts'][$j]['data']) . "\n";
1846
1847
				if (empty($context['file_parts'][$j]['lines']))
1848
					unset($context['file_parts'][$j]);
1849
				$context['file_parts'][++$j] = array('lines' => 0, 'line' => $i + 1, 'data' => '');
1850
			}
1851
1852
			$context['file_parts'][$j]['lines']++;
1853
			$context['file_parts'][$j]['data'] .= $smcFunc['htmlspecialchars'](strtr($file_data[$i], array("\t" => '   ')));
1854
		}
1855
1856
		$context['entire_file'] = $smcFunc['htmlspecialchars'](strtr(implode('', $file_data), array("\t" => '   ')));
1857
	}
1858
	else
1859
	{
1860
		$context['sub_template'] = 'edit_file';
1861
1862
		$context['entire_file'] = $smcFunc['htmlspecialchars'](strtr(file_get_contents($currentTheme['theme_dir'] . '/' . $_REQUEST['filename']), array("\t" => '   ')));
1863
	}
1864
1865
	// Create a special token to allow editing of multiple files.
1866
	createToken('admin-te-' . md5($_GET['th'] . '-' . $_REQUEST['filename']));
1867
}
1868
1869
/**
1870
 * Makes a copy of a template file in a new location
1871
 * @uses Themes template, copy_template sub-template.
1872
 */
1873
function CopyTemplate()
1874
{
1875
	global $context, $settings;
1876
1877
	isAllowedTo('admin_forum');
1878
	loadTemplate('Themes');
1879
1880
	$context[$context['admin_menu_name']]['current_subsection'] = 'edit';
1881
1882
	$_GET['th'] = isset($_GET['th']) ? (int) $_GET['th'] : (int) $_GET['id'];
1883
1884
	if (empty($_GET['th']))
1885
		fatal_lang_error('theme_install_invalid_id');
1886
1887
	// Get the theme info.
1888
	$theme = get_single_theme($_GET['th']);
1889
	$context['theme_id'] = $theme['id'];
1890
1891
	if (isset($_REQUEST['template']) && preg_match('~[\./\\\\:\0]~', $_REQUEST['template']) == 0)
1892
	{
1893 View Code Duplication
		if (file_exists($settings['default_theme_dir'] . '/' . $_REQUEST['template'] . '.template.php'))
1894
			$filename = $settings['default_theme_dir'] . '/' . $_REQUEST['template'] . '.template.php';
1895
1896
		else
1897
			fatal_lang_error('no_access', false);
1898
1899
		$fp = fopen($theme['theme_dir'] . '/' . $_REQUEST['template'] . '.template.php', 'w');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1900
		fwrite($fp, file_get_contents($filename));
0 ignored issues
show
Bug introduced by
The variable $filename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1901
		fclose($fp);
1902
1903
		redirectexit('action=admin;area=theme;th=' . $context['theme_id'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=copy');
1904
	}
1905
	elseif (isset($_REQUEST['lang_file']) && preg_match('~^[^\./\\\\:\0]\.[^\./\\\\:\0]$~', $_REQUEST['lang_file']) != 0)
1906
	{
1907 View Code Duplication
		if (file_exists($settings['default_theme_dir'] . '/languages/' . $_REQUEST['template'] . '.php'))
1908
			$filename = $settings['default_theme_dir'] . '/languages/' . $_REQUEST['template'] . '.php';
1909
1910
		else
1911
			fatal_lang_error('no_access', false);
1912
1913
		$fp = fopen($theme['theme_dir'] . '/languages/' . $_REQUEST['lang_file'] . '.php', 'w');
1914
		fwrite($fp, file_get_contents($filename));
1915
		fclose($fp);
1916
1917
		redirectexit('action=admin;area=theme;th=' . $context['theme_id'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=copy');
1918
	}
1919
1920
	$templates = array();
1921
	$lang_files = array();
1922
1923
	$dir = dir($settings['default_theme_dir']);
1924
	while ($entry = $dir->read())
1925
	{
1926
		if (substr($entry, -13) == '.template.php')
1927
			$templates[] = substr($entry, 0, -13);
1928
	}
1929
	$dir->close();
1930
1931
	$dir = dir($settings['default_theme_dir'] . '/languages');
1932
	while ($entry = $dir->read())
1933
	{
1934
		if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches))
1935
			$lang_files[] = $matches[1];
1936
	}
1937
	$dir->close();
1938
1939
	natcasesort($templates);
1940
	natcasesort($lang_files);
1941
1942
	$context['available_templates'] = array();
1943
	foreach ($templates as $template)
1944
		$context['available_templates'][$template] = array(
1945
			'filename' => $template . '.template.php',
1946
			'value' => $template,
1947
			'already_exists' => false,
1948
			'can_copy' => is_writable($theme['theme_dir']),
1949
		);
1950
	$context['available_language_files'] = array();
1951
	foreach ($lang_files as $file)
1952
		$context['available_language_files'][$file] = array(
1953
			'filename' => $file . '.php',
1954
			'value' => $file,
1955
			'already_exists' => false,
1956
			'can_copy' => file_exists($theme['theme_dir'] . '/languages') ? is_writable($theme['theme_dir'] . '/languages') : is_writable($theme['theme_dir']),
1957
		);
1958
1959
	$dir = dir($theme['theme_dir']);
1960
	while ($entry = $dir->read())
1961
	{
1962
		if (substr($entry, -13) == '.template.php' && isset($context['available_templates'][substr($entry, 0, -13)]))
1963
		{
1964
			$context['available_templates'][substr($entry, 0, -13)]['already_exists'] = true;
1965
			$context['available_templates'][substr($entry, 0, -13)]['can_copy'] = is_writable($theme['theme_dir'] . '/' . $entry);
1966
		}
1967
	}
1968
	$dir->close();
1969
1970
	if (file_exists($theme['theme_dir'] . '/languages'))
1971
	{
1972
		$dir = dir($theme['theme_dir'] . '/languages');
1973
		while ($entry = $dir->read())
1974
		{
1975
			if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches) && isset($context['available_language_files'][$matches[1]]))
1976
			{
1977
				$context['available_language_files'][$matches[1]]['already_exists'] = true;
1978
				$context['available_language_files'][$matches[1]]['can_copy'] = is_writable($theme['theme_dir'] . '/languages/' . $entry);
1979
			}
1980
		}
1981
		$dir->close();
1982
	}
1983
1984
	$context['sub_template'] = 'copy_template';
1985
}
1986
1987
?>