ManageThemes::action_remove_api()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 79
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 38
c 0
b 0
f 0
nc 6
nop 0
dl 0
loc 79
ccs 0
cts 44
cp 0
crap 42
rs 8.6897

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
 * @package   ElkArte Forum
9
 * @copyright ElkArte Forum contributors
10
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
11
 *
12
 * This file contains code covered by:
13
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
14
 *
15
 * @version 2.0 dev
16
 *
17
 *
18
 * @todo Update this for the new package manager?
19
 *
20
 * Creating and distributing theme packages:
21
 * There isn't that much required to package and distribute your own themes...
22
 * just do the following:
23
 *
24
 *  - create a theme_info.xml file, with the root element theme-info.
25
 *  - its name should go in a name element, just like description.
26
 *  - your name should go in author. (email in the email attribute.)
27
 *  - any support website for the theme should be in website.
28
 *  - layers and templates (non-default) should go in those elements ;).
29
 *  - if the images dir isn't images, specify in the images element.
30
 *  - any extra rows for themes should go in extra, serialized. (as in array(variable => value).)
31
 *  - tar and gzip the directory - and you're done!
32
 *  - please include any special license in a license.txt file.
33
 */
34
35
namespace ElkArte\AdminController;
36
37
use ElkArte\AbstractController;
38
use ElkArte\Action;
39
use ElkArte\Cache\Cache;
40
use ElkArte\Exceptions\Exception;
41
use ElkArte\Helper\FileFunctions;
42
use ElkArte\Helper\Util;
43
use ElkArte\Languages\Txt;
44
use ElkArte\Profile\Profile;
45
use ElkArte\Themes\ThemeLoader;
46
use ElkArte\User;
47
use ElkArte\XmlArray;
48
49
/**
50
 * Class to deal with theme administration.
51
 *
52
 * Its tasks include changing theme settings, installing and removing
53
 * themes, choosing the current theme, and editing themes.
54
 *
55
 * @package Themes
56
 */
57
class ManageThemes extends AbstractController
58
{
59
	/** @var string Name of the theme */
60
	private $theme_name;
61
62
	/** @var string Full path to the theme */
63
	private $theme_dir;
64
65
	/** @var string|null The themes image url if any */
66
	private $images_url;
67
68
	/**
69
	 * {@inheritDoc}
70
	 */
71
	public function trackStats($action = '')
72
	{
73
		if ($action === 'action_jsoption')
74
		{
75
			return false;
76
		}
77
78
		return parent::trackStats($action);
79
	}
80
81
	/**
82
	 * Subaction handler - manages the action and delegates control to the proper
83
	 * sub-action.
84
	 *
85
	 * What it does:
86
	 *
87
	 * - It loads both the Themes and Settings language files.
88
	 * - Checks the session by GET or POST to verify the data.
89
	 * - Requires the user to not be a guest.
90
	 * - Accessed via ?action=admin;area=theme.
91
	 *
92
	 * @see AbstractController::action_index()
93
	 */
94
	public function action_index()
95
	{
96
		global $txt, $context;
97
98
		if (isset($this->_req->query->api))
99
		{
100
			$this->action_index_api();
101
102
			return;
103
		}
104
105
		// Load the important language files...
106
		Txt::load('ManageThemes+Settings');
107
108
		// No guests in here.
109
		is_not_guest();
110
111
		// Theme administration, removal, choice, or installation...
112
		$subActions = array(
113
			'admin' => array($this, 'action_admin', 'permission' => 'admin_forum'),
114
			'list' => array($this, 'action_list', 'permission' => 'admin_forum'),
115
			'reset' => array($this, 'action_options', 'permission' => 'admin_forum'),
116
			'options' => array($this, 'action_options', 'permission' => 'admin_forum'),
117
			'install' => array($this, 'action_install', 'permission' => 'admin_forum'),
118
			'remove' => array($this, 'action_remove', 'permission' => 'admin_forum'),
119
			'pick' => array($this, 'action_pick', 'permission' => 'admin_forum'),
120
		);
121
122
		// Action controller
123
		$action = new Action('manage_themes');
124
125
		if (!empty($context['admin_menu_name']))
126
		{
127
			$context[$context['admin_menu_name']]['object']->prepareTabData([
128
				'title' => 'themeadmin_title',
129
				'description' => 'themeadmin_description',
130
				'prefix' => 'themeadmin',
131
			]);
132
		}
133
134
		// Follow the sa or just go to administration, call integrate_sa_manage_themes
135
		$subAction = $action->initialize($subActions, 'admin');
136
137
		// Default the page title to Theme Administration by default.
138
		$context['page_title'] = $txt['themeadmin_title'];
139
		$context['sub_action'] = $subAction;
140
141
		// Go to the action, if you have permissions
142
		$action->dispatch($subAction);
143
	}
144
145
	/**
146
	 * Responds to an ajax button request, currently only for remove
147
	 *
148
	 * @uses generic_xml_buttons sub template
149
	 */
150
	public function action_index_api()
151
	{
152
		global $txt, $context;
153
154
		theme()->getTemplates()->load('Xml');
155
156
		// Remove any template layers that may have been created, this is XML!
157
		theme()->getLayers()->removeAll();
158
		$context['sub_template'] = 'generic_xml_buttons';
159
160
		// No guests in here.
161
		if ($this->user->is_guest)
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
162
		{
163
			Txt::load('Errors');
164
			$context['xml_data'] = array(
165
				'error' => 1,
166
				'text' => $txt['not_guests']
167
			);
168
169
			return;
170
		}
171
172
		// Theme administration, removal, choice, or installation...
173
		// Of all the actions we currently know only this
174
		$subActions = array(
175
			// 'admin' => 'action_admin',
176
			// 'list' => 'action_list',
177
			// 'reset' => 'action_options',
178
			// 'options' => 'action_options',
179
			// 'install' => 'action_install',
180
			'remove' => 'action_remove_api',
181
			// 'pick' => 'action_pick',
182
		);
183
184
		// Follow the sa or just go to administration.
185
		if (isset($this->_req->query->sa, $subActions[$this->_req->query->sa]) && $subActions[$this->_req->query->sa] !== '' && $subActions[$this->_req->query->sa] !== '0')
186
		{
187
			$this->{$subActions[$this->_req->query->sa]}();
188
		}
189
		else
190
		{
191
			Txt::load('Errors');
192
			$context['xml_data'] = array(
193
				'error' => 1,
194
				'text' => $txt['error_sa_not_set']
195
			);
196
		}
197
	}
198
199
	/**
200
	 * This function lists the available themes and provides an interface
201
	 * to reset the paths of all the installed themes.
202
	 *
203
	 * @uses sub template list_themes, template ManageThemes
204
	 */
205
	public function action_list()
206
	{
207
		global $context, $boardurl, $txt;
208
209
		// Load in the helpers we need
210
		require_once(SUBSDIR . '/Themes.subs.php');
211
		Txt::load('Admin');
212
		$fileFunc = FileFunctions::instance();
213
214
		if (isset($this->_req->query->th))
215
		{
216
			$this->action_setthemesettings();
217
			return;
218
		}
219
220
		// Saving?
221
		if (isset($this->_req->post->save))
222
		{
223
			checkSession();
224
			validateToken('admin-tl');
225
226
			$themes = installedThemes();
227
			$setValues = array();
228
229
			foreach ($themes as $id => $theme)
230
			{
231
				if ($fileFunc->isDir($this->_req->post->reset_dir . '/' . basename($theme['theme_dir'])))
232
				{
233
					$setValues[] = array($id, 0, 'theme_dir', realpath($this->_req->post->reset_dir . '/' . basename($theme['theme_dir'])));
234
					$setValues[] = array($id, 0, 'theme_url', $this->_req->post->reset_url . '/' . basename($theme['theme_dir']));
235
					$setValues[] = array($id, 0, 'images_url', $this->_req->post->reset_url . '/' . basename($theme['theme_dir']) . '/' . basename($theme['images_url']));
236
				}
237
238
				if (isset($theme['base_theme_dir']) && $fileFunc->isDir($this->_req->post->reset_dir . '/' . basename($theme['base_theme_dir'])))
239
				{
240
					$setValues[] = array($id, 0, 'base_theme_dir', realpath($this->_req->post->reset_dir . '/' . basename($theme['base_theme_dir'])));
241
					$setValues[] = array($id, 0, 'base_theme_url', $this->_req->post->reset_url . '/' . basename($theme['base_theme_dir']));
242
					$setValues[] = array($id, 0, 'base_images_url', $this->_req->post->reset_url . '/' . basename($theme['base_theme_dir']) . '/' . basename($theme['base_images_url']));
243
				}
244
245
				Cache::instance()->remove('theme_settings-' . $id);
246
			}
247
248
			updateThemeOptions($setValues);
249
250
			redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']);
251
		}
252
253
		theme()->getTemplates()->load('ManageThemes');
254
255
		$context['themes'] = installedThemes();
256
257
		// For each theme, make sure the directory exists, and try to fetch the theme version
258
		foreach ($context['themes'] as $i => $theme)
259
		{
260
			$context['themes'][$i]['theme_dir'] = realpath($context['themes'][$i]['theme_dir']);
261
262
			if ($fileFunc->fileExists($context['themes'][$i]['theme_dir'] . '/index.template.php'))
263
			{
264
				// Fetch the header... a good 256 bytes should be more than enough.
265
				$fp = fopen($context['themes'][$i]['theme_dir'] . '/index.template.php', 'rb');
266
				$header = fread($fp, 256);
267
				fclose($fp);
268
269
				// Can we find a version comment, at all?
270
				if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
271
				{
272
					$context['themes'][$i]['version'] = $match[1];
273
				}
274
			}
275
276
			$context['themes'][$i]['valid_path'] = $fileFunc->isDir($context['themes'][$i]['theme_dir']);
277
		}
278
279
		// Off to the template we go
280
		$context['sub_template'] = 'list_themes';
281
		theme()->addJavascriptVar(array('txt_theme_remove_confirm' => $txt['theme_remove_confirm']), true);
282
		$context['reset_dir'] = realpath(BOARDDIR . '/themes');
283
		$context['reset_url'] = $boardurl . '/themes';
284
285
		createToken('admin-tl');
286
		createToken('admin-tr', 'request');
287
	}
288
289
	/**
290
	 * Administrative global settings.
291
	 *
292
	 * What it does:
293
	 *
294
	 * - Saves and requests global theme settings. ($settings)
295
	 * - Loads the Admin language file.
296
	 * - Calls action_admin() if no theme is specified. (the theme center.)
297
	 * - Requires admin_forum permission.
298
	 * - Accessed with ?action=admin;area=theme;sa=list&th=xx.
299
	 *
300
	 * @event integrate_init_theme
301
	 */
302
	public function action_setthemesettings()
303
	{
304
		global $txt, $context, $settings, $modSettings;
305
306
		require_once(SUBSDIR . '/Themes.subs.php');
307
		$fileFunc = FileFunctions::instance();
308
309
		// Nothing chosen, back to the start you go
310
		$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0));
311
		if (empty($theme))
312
		{
313
			redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
314
		}
315
316
		// The theme's ID is needed
317
		$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0));
318
319
		// Validate inputs/user.
320
		if (empty($theme))
321
		{
322
			throw new Exception('no_theme', false);
323
		}
324
325
		// Select the best fitting tab.
326
		$context[$context['admin_menu_name']]['current_subsection'] = 'list';
327
		Txt::load('Admin');
328
329
		// Fetch the smiley sets...
330
		$sets = explode(',', 'none,' . $modSettings['smiley_sets_known']);
331
		$set_names = explode("\n", $txt['smileys_none'] . "\n" . $modSettings['smiley_sets_names']);
332
		$context['smiley_sets'] = array('' => $txt['smileys_no_default']);
333
		foreach ($sets as $i => $set)
334
		{
335
			$context['smiley_sets'][$set] = htmlspecialchars($set_names[$i], ENT_COMPAT);
336
		}
337
338
		$old_id = $settings['theme_id'];
339
		$old_settings = $settings;
340
		$old_js_inline = $context['js_inline'];
341
342
		new ThemeLoader($theme, false);
343
344
		// Also load the actual themes language file - in case of special settings.
345
		Txt::load('Settings', false, true);
346
347
		// And the custom language strings...
348
		Txt::load('ThemeStrings', false);
349
350
		// Let the theme take care of the settings.
351
		theme()->getTemplates()->load('Settings');
352
		theme()->getTemplates()->loadSubTemplate('settings');
353
354
		// Load the variants separately...
355
		if ($fileFunc->fileExists($settings['theme_dir'] . '/index.template.php'))
356
		{
357
			$variants = theme()->getSettings();
358
			$settings['theme_variants'] = $variants['theme_variants'] ?? array();
359
			call_integration_hook('integrate_init_theme', array($theme, &$settings));
360
		}
361
362
		// Submitting!
363
		if (isset($this->_req->post->save))
364
		{
365
			// Allowed?
366
			checkSession();
367
			validateToken('admin-sts');
368
369
			$options = array();
370
			$options['options'] = empty($this->_req->post->options) ? array() : (array) $this->_req->post->options;
371
			$options['default_options'] = empty($this->_req->post->default_options) ? array() : (array) $this->_req->post->default_options;
372
373
			// Make sure items are cast correctly.
374
			foreach ($context['theme_settings'] as $item)
375
			{
376
				// Unwatch this item if this is just a separator.
377
				if (!is_array($item))
378
				{
379
					continue;
380
				}
381
382
				// Clean them up for the database
383
				foreach (array('options', 'default_options') as $option)
384
				{
385
					if (!isset($options[$option][$item['id']]))
386
					{
387
						continue;
388
					}
389
390
					// Checkbox.
391
					if (empty($item['type']))
392
					{
393
						$options[$option][$item['id']] = $options[$option][$item['id']] ? 1 : 0;
394
					}
395
396
					// Number
397
					elseif ($item['type'] === 'number')
398
					{
399
						$options[$option][$item['id']] = (int) $options[$option][$item['id']];
400
					}
401
				}
402
			}
403
404
			// Set up the sql query.
405
			$inserts = array();
406
			foreach ($options['options'] as $opt => $val)
407
			{
408
				$inserts[] = array($theme, 0, $opt, is_array($val) ? implode(',', $val) : $val);
409
			}
410
411
			foreach ($options['default_options'] as $opt => $val)
412
			{
413
				$inserts[] = array(1, 0, $opt, is_array($val) ? implode(',', $val) : $val);
414
			}
415
416
			// If we're actually inserting something..
417
			if (!empty($inserts))
418
			{
419
				updateThemeOptions($inserts);
420
			}
421
422
			// Clear and Invalidate the cache.
423
			Cache::instance()->remove('theme_settings-' . $theme);
424
			Cache::instance()->remove('theme_settings-1');
425
			updateSettings(array('settings_updated' => time()));
426
427
			redirectexit('action=admin;area=theme;sa=list;th=' . $theme . ';' . $context['session_var'] . '=' . $context['session_id']);
428
		}
429
430
		$context['sub_template'] = 'set_settings';
431
		$context['page_title'] = $txt['theme_settings'];
432
433
		foreach ($settings as $setting => $set)
434
		{
435
			if (!in_array($setting, array('theme_url', 'theme_dir', 'images_url', 'template_dirs')))
436
			{
437
				$settings[$setting] = Util::htmlspecialchars__recursive($set);
438
			}
439
		}
440
441
		$context['settings'] = $context['theme_settings'];
442
		$context['theme_settings'] = $settings;
443
444
		foreach ($context['settings'] as $i => $setting)
445
		{
446
			// Separators are dummies, so leave them alone.
447
			if (!is_array($setting))
448
			{
449
				continue;
450
			}
451
452
			// Create the right input fields for the data
453
			if (!isset($setting['type']) || $setting['type'] === 'bool')
454
			{
455
				$context['settings'][$i]['type'] = 'checkbox';
456
			}
457
			elseif ($setting['type'] === 'int' || $setting['type'] === 'integer')
458
			{
459
				$context['settings'][$i]['type'] = 'number';
460
			}
461
			elseif ($setting['type'] === 'string')
462
			{
463
				$context['settings'][$i]['type'] = 'text';
464
			}
465
466
			if (isset($setting['options']))
467
			{
468
				$context['settings'][$i]['type'] = 'list';
469
			}
470
471
			$context['settings'][$i]['value'] = $settings[$setting['id']] ?? '';
472
		}
473
474
		// Do we support variants?
475
		if (!empty($settings['theme_variants']))
476
		{
477
			$context['theme_variants'] = array();
478
			foreach ($settings['theme_variants'] as $variant)
479
			{
480
				// Have any text, old chap?
481
				$context['theme_variants'][$variant] = array(
482
					'label' => $txt['variant_' . $variant] ?? $variant,
483
					'thumbnail' => !$fileFunc->fileExists($settings['theme_dir'] . '/images/thumbnail.png') || $fileFunc->fileExists($settings['theme_dir'] . '/images/thumbnail_' . $variant . '.png') ? $settings['images_url'] . '/thumbnail_' . $variant . '.png' : ($settings['images_url'] . '/thumbnail.png'),
484
				);
485
			}
486
487
			$context['default_variant'] = !empty($settings['default_variant']) && isset($context['theme_variants'][$settings['default_variant']]) ? $settings['default_variant'] : $settings['theme_variants'][0];
488
		}
489
490
		// Restore the current theme.
491
		new ThemeLoader($old_id, true);
492
493
		$settings = $old_settings;
494
		$context['js_inline'] = $old_js_inline;
495
496
		// Reinit just incase.
497
		theme()->getSettings();
498
499
		theme()->getTemplates()->load('ManageThemes');
500
501
		createToken('admin-sts');
502
	}
503
504
	/**
505
	 * This function allows administration of themes and their settings,
506
	 * as well as global theme settings.
507
	 *
508
	 * What it does:
509
	 *
510
	 * - sets the settings theme_allow, theme_guests, and knownThemes.
511
	 * - requires the admin_forum permission.
512
	 * - accessed with ?action=admin;area=theme;sa=admin.
513
	 *
514
	 * @uses Themes template
515
	 * @uses Admin language file
516
	 */
517
	public function action_admin()
518
	{
519
		global $context, $modSettings;
520
521
		Txt::load('Admin');
522
523
		// Saving?
524
		if (isset($this->_req->post->save))
525
		{
526
			checkSession();
527
			validateToken('admin-tm');
528
529
			// What themes are being made as known to the members
530
			if (isset($this->_req->post->options['known_themes']))
531
			{
532
				foreach ($this->_req->post->options['known_themes'] as $key => $id)
533
				{
534
					$this->_req->post->options['known_themes'][$key] = (int) $id;
535
				}
536
			}
537
			else
538
			{
539
				throw new Exception('themes_none_selectable', false);
540
			}
541
542
			if (!in_array($this->_req->post->options['theme_guests'], $this->_req->post->options['known_themes']))
543
			{
544
				throw new Exception('themes_default_selectable', false);
545
			}
546
547
			// Commit the new settings.
548
			updateSettings(array(
549
				'theme_allow' => !empty($this->_req->post->options['theme_allow']),
550
				'theme_guests' => $this->_req->post->options['theme_guests'],
551
				'knownThemes' => implode(',', $this->_req->post->options['known_themes']),
552
			));
553
554
			if ((int) $this->_req->post->theme_reset === 0 || in_array($this->_req->post->theme_reset, $this->_req->post->options['known_themes']))
555
			{
556
				require_once(SUBSDIR . '/Members.subs.php');
557
				updateMemberData(null, array('id_theme' => (int) $this->_req->post->theme_reset));
558
			}
559
560
			redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=admin');
561
		}
562
		// If we aren't submitting - that is, if we are about to...
563
		else
564
		{
565
			$fileFunc = FileFunctions::instance();
566
567
			theme()->getTemplates()->load('ManageThemes');
568
			$context['sub_template'] = 'manage_themes';
569
570
			// Make our known themes a little easier to work with.
571
			$knownThemes = empty($modSettings['knownThemes']) ? array() : explode(',', $modSettings['knownThemes']);
572
573
			// Load up all the themes.
574
			require_once(SUBSDIR . '/Themes.subs.php');
575
			$context['themes'] = loadThemes($knownThemes);
0 ignored issues
show
Bug introduced by
It seems like $knownThemes can also be of type string[]; however, parameter $knownThemes of loadThemes() does only seem to accept integer[], maybe add an additional type check? ( Ignorable by Annotation )

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

575
			$context['themes'] = loadThemes(/** @scrutinizer ignore-type */ $knownThemes);
Loading history...
576
577
			// Can we create a new theme?
578
			$context['can_create_new'] = $fileFunc->isWritable(BOARDDIR . '/themes');
579
			$context['new_theme_dir'] = substr(realpath(BOARDDIR . '/themes/default'), 0, -7);
580
581
			// Look for a nonexistent theme directory. (ie theme87.)
582
			$theme_dir = BOARDDIR . '/themes/theme';
583
			$i = 1;
584
			while ($fileFunc->isDir($theme_dir . $i))
585
			{
586
				$i++;
587
			}
588
589
			$context['new_theme_name'] = 'theme' . $i;
590
591
			createToken('admin-tm');
592
		}
593
	}
594
595
	/**
596
	 * Administrative global settings.
597
	 *
598
	 * - Accessed by ?action=admin;area=theme;sa=reset;
599
	 *
600
	 * @uses sub template set_options, template file Settings
601
	 * @uses template file ManageThemes
602
	 */
603
	public function action_options()
604
	{
605
		global $txt, $context, $settings, $modSettings;
606
607
		require_once(SUBSDIR . '/Themes.subs.php');
608
		$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0));
609
610
		// No theme selected, so show the theme list with theme option count and member count not using default
611
		if (empty($theme))
612
		{
613
			$context['themes'] = installedThemes();
614
615
			// How many options do we have set for guests?
616
			$guestOptions = countConfiguredGuestOptions();
617
			foreach ($guestOptions as $guest_option)
618
			{
619
				$context['themes'][$guest_option['id_theme']]['num_default_options'] = $guest_option['value'];
620
			}
621
622
			// How many options do we have set for members?
623
			$memberOptions = countConfiguredMemberOptions();
624
			foreach ($memberOptions as $member_option)
625
			{
626
				$context['themes'][$member_option['id_theme']]['num_members'] = $member_option['value'];
627
			}
628
629
			// There has to be a Settings template!
630
			$fileFunc = FileFunctions::instance();
631
			foreach ($context['themes'] as $k => $v)
632
			{
633
				if (empty($v['theme_dir']) || (!$fileFunc->fileExists($v['theme_dir'] . '/Settings.template.php') && empty($v['num_members'])))
634
				{
635
					unset($context['themes'][$k]);
636
				}
637
			}
638
639
			theme()->getTemplates()->load('ManageThemes');
640
			$context['sub_template'] = 'reset_list';
641
642
			createToken('admin-stor', 'request');
643
644
			return;
645
		}
646
647
		// Submit?
648
		$who = $this->_req->getPost('who', 'intval', 0);
649
		if (isset($this->_req->post->submit) && empty($who))
650
		{
651
			checkSession();
652
			validateToken('admin-sto');
653
654
			$_options = $this->_req->getPost('options', '', array());
655
			$_default_options = $this->_req->getPost('default_options', '', array());
656
657
			// Set up the query values.
658
			$setValues = array();
659
			foreach ($_options as $opt => $val)
660
			{
661
				$setValues[] = array($theme, -1, $opt, is_array($val) ? implode(',', $val) : $val);
662
			}
663
664
			$old_settings = array();
665
			foreach ($_default_options as $opt => $val)
666
			{
667
				$old_settings[] = $opt;
668
				$setValues[] = array(1, -1, $opt, is_array($val) ? implode(',', $val) : $val);
669
			}
670
671
			// If we're actually inserting something..
672
			if (!empty($setValues))
673
			{
674
				// Are there options in non-default themes set that should be cleared?
675
				if (!empty($old_settings))
676
				{
677
					removeThemeOptions('custom', 'guests', $old_settings);
678
				}
679
680
				updateThemeOptions($setValues);
681
			}
682
683
			// Cache the theme settings
684
			Cache::instance()->remove('theme_settings-' . $theme);
685
			Cache::instance()->remove('theme_settings-1');
686
687
			redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
688
		}
689
690
		// Changing the current options for all members using this theme
691
		if (isset($this->_req->post->submit) && $who === 1)
692
		{
693
			checkSession();
694
			validateToken('admin-sto');
695
696
			$_options = $this->_req->getPost('options', '', array());
697
			$_options_master = $this->_req->getPost('options_master', '', array());
698
699
			$_default_options = $this->_req->getPost('default_options', '', array());
700
			$_default_options_master = $this->_req->getPost('default_options_master', '', array());
701
702
			$old_settings = array();
703
			foreach ($_default_options as $opt => $val)
704
			{
705
				if ($_default_options_master[$opt] == 0)
706
				{
707
					continue;
708
				}
709
710
				if ($_default_options_master[$opt] == 1)
711
				{
712
					// Delete then insert for ease of database compatibility!
713
					removeThemeOptions('default', 'members', $opt);
714
					addThemeOptions(1, $opt, $val);
715
716
					$old_settings[] = $opt;
717
				}
718
				elseif ($_default_options_master[$opt] == 2)
719
				{
720
					removeThemeOptions('all', 'members', $opt);
721
				}
722
			}
723
724
			// Delete options from other themes.
725
			if (!empty($old_settings))
726
			{
727
				removeThemeOptions('custom', 'members', $old_settings);
728
			}
729
730
			foreach ($_options as $opt => $val)
731
			{
732
				if ($_options_master[$opt] == 0)
733
				{
734
					continue;
735
				}
736
737
				if ($_options_master[$opt] == 1)
738
				{
739
					// Delete then insert for ease of database compatibility - again!
740
					removeThemeOptions($theme, 'non_default', $opt);
741
					addThemeOptions($theme, $opt, $val);
742
				}
743
				elseif ($_options_master[$opt] == 2)
744
				{
745
					removeThemeOptions($theme, 'all', $opt);
746
				}
747
			}
748
749
			redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
750
		}
751
752
		// Remove all members options and use the defaults
753
		if (!empty($this->_req->query->who) && $who === 2)
754
		{
755
			checkSession('get');
756
			validateToken('admin-stor', 'request');
757
758
			removeThemeOptions($theme, 'members');
759
760
			redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset');
761
		}
762
763
		$old_id = $settings['theme_id'];
764
		$old_settings = $settings;
765
766
		new ThemeLoader($theme, false);
767
		Txt::load('Profile');
768
769
		// @todo Should we just move these options so they are no longer theme dependant?
770
		Txt::load('PersonalMessage');
771
772
		// Let the theme take care of the settings.
773
		theme()->getTemplates()->load('Settings');
774
		theme()->getTemplates()->loadSubTemplate('options');
775
776
		// Set up for the template
777
		$context['sub_template'] = 'set_options';
778
		$context['page_title'] = $txt['theme_settings'];
779
		$context['options'] = $context['theme_options'];
780
		$context['theme_settings'] = $settings;
781
782
		// Load the options for these theme
783
		if (empty($this->_req->query->who))
784
		{
785
			$context['theme_options'] = loadThemeOptionsInto(array(1, $theme), -1, $context['theme_options']);
786
			$context['theme_options_reset'] = false;
787
		}
788
		else
789
		{
790
			$context['theme_options'] = array();
791
			$context['theme_options_reset'] = true;
792
		}
793
794
		// Prepare the options for the template
795
		foreach ($context['options'] as $i => $setting)
796
		{
797
			// Is this disabled?
798
			if ($setting['id'] === 'calendar_start_day' && empty($modSettings['cal_enabled']))
799
			{
800
				unset($context['options'][$i]);
801
				continue;
802
			}
803
			if (($setting['id'] === 'topics_per_page' || $setting['id'] === 'messages_per_page') && !empty($modSettings['disableCustomPerPage']))
804
			{
805
				unset($context['options'][$i]);
806
				continue;
807
			}
808
809
			// Type of field so we display the right input field
810
			if (!isset($setting['type']) || $setting['type'] === 'bool')
811
			{
812
				$context['options'][$i]['type'] = 'checkbox';
813
			}
814
			elseif ($setting['type'] === 'int' || $setting['type'] === 'integer')
815
			{
816
				$context['options'][$i]['type'] = 'number';
817
			}
818
			elseif ($setting['type'] === 'string')
819
			{
820
				$context['options'][$i]['type'] = 'text';
821
			}
822
823
			if (isset($setting['options']))
824
			{
825
				$context['options'][$i]['type'] = 'list';
826
			}
827
828
			$context['options'][$i]['value'] = $context['theme_options'][$setting['id']] ?? '';
829
		}
830
831
		// Restore the existing theme and its settings.
832
		new ThemeLoader($old_id, true);
833
		$settings = $old_settings;
834
835
		theme()->getTemplates()->load('ManageThemes');
836
		createToken('admin-sto');
837
	}
838
839
	/**
840
	 * Remove a theme from the database.
841
	 *
842
	 * What it does:
843
	 *
844
	 * - Removes an installed theme.
845
	 * - Requires an administrator.
846
	 * - Accessed with ?action=admin;area=theme;sa=remove.
847
	 * - Does not remove files
848
	 */
849
	public function action_remove()
850
	{
851
		global $modSettings, $context;
852
853
		require_once(SUBSDIR . '/Themes.subs.php');
854
855
		checkSession('get');
856
		validateToken('admin-tr', 'request');
857
858
		// The theme's ID must be an integer.
859
		$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0));
860
861
		// You can't delete the default theme!
862
		if ($theme === 1)
863
		{
864
			throw new Exception('no_access', false);
865
		}
866
867
		// Its no longer known
868
		$known = $this->_knownTheme($theme);
869
870
		// Remove it as an option everywhere
871
		deleteTheme($theme);
872
873
		// Fix it if the theme was the overall default theme.
874
		if ($modSettings['theme_guests'] === $theme)
875
		{
876
			updateSettings(array('theme_guests' => '1', 'knownThemes' => $known));
877
		}
878
		else
879
		{
880
			updateSettings(array('knownThemes' => $known));
881
		}
882
883
		redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']);
884
	}
885
886
	/**
887
	 * Small helper to return the list of known themes other than the current
888
	 *
889
	 * @param string $theme current theme
890
	 * @return string
891
	 */
892
	private function _knownTheme($theme)
893
	{
894
		global $modSettings;
895
896
		$known = explode(',', $modSettings['knownThemes']);
897
		foreach ($known as $i => $knew)
898
		{
899
			if ($knew === $theme)
900
			{
901
				// I knew them at one time
902
				unset($known[$i]);
903
			}
904
		}
905
906
		return strtr(implode(',', $known), array(',,' => ','));
907
	}
908
909
	/**
910
	 * Remove a theme from the database in response to an ajax api request
911
	 *
912
	 * What it does:
913
	 *
914
	 * - Removes an installed theme.
915
	 * - Requires an administrator.
916
	 * - Accessed with ?action=admin;area=theme;sa=remove;api
917
	 */
918
	public function action_remove_api()
919
	{
920
		global $modSettings, $context, $txt;
921
922
		require_once(SUBSDIR . '/Themes.subs.php');
923
924
		// Validate what was sent
925
		if (checkSession('get', '', false))
926
		{
927
			Txt::load('Errors');
928
			$context['xml_data'] = array(
929
				'error' => 1,
930
				'text' => $txt['session_verify_fail'],
931
			);
932
933
			return;
934
		}
935
936
		// Not just any John Smith can send in a api request
937
		if (!allowedTo('admin_forum'))
938
		{
939
			Txt::load('Errors');
940
			$context['xml_data'] = array(
941
				'error' => 1,
942
				'text' => $txt['cannot_admin_forum'],
943
			);
944
945
			return;
946
		}
947
948
		// Even if you are John Smith, you still need a ticket
949
		if (!validateToken('admin-tr', 'request', true, false))
950
		{
951
			Txt::load('Errors');
952
			$context['xml_data'] = array(
953
				'error' => 1,
954
				'text' => $txt['token_verify_fail'],
955
			);
956
957
			return;
958
		}
959
960
		// The theme's ID must be an integer.
961
		$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0));
962
963
		// You can't delete the default theme!
964
		if ($theme === 1)
965
		{
966
			Txt::load('Errors');
967
			$context['xml_data'] = array(
968
				'error' => 1,
969
				'text' => $txt['no_access'],
970
			);
971
972
			return;
973
		}
974
975
		// It is a theme we know about?
976
		$known = $this->_knownTheme($theme);
977
978
		// Finally, remove it
979
		deleteTheme($theme);
980
981
		// Fix it if the theme was the overall default theme.
982
		if ($modSettings['theme_guests'] === $theme)
983
		{
984
			updateSettings(array('theme_guests' => '1', 'knownThemes' => $known));
985
		}
986
		else
987
		{
988
			updateSettings(array('knownThemes' => $known));
989
		}
990
991
		// Let them know it worked, all without a page refresh
992
		createToken('admin-tr', 'request');
993
		$context['xml_data'] = array(
994
			'success' => 1,
995
			'token_var' => $context['admin-tr_token_var'],
996
			'token' => $context['admin-tr_token'],
997
		);
998
	}
999
1000
	/**
1001
	 * Choose a theme from a list.
1002
	 * Allows a user or administrator to pick a new theme with an interface.
1003
	 *
1004
	 * What it does:
1005
	 *
1006
	 * - Can edit everyone's (u = 0) or guests' (u = -1).
1007
	 * - Uses the Themes template. (pick sub template.)
1008
	 * - Accessed with ?action=admin;area=theme;sa=pick.
1009
	 *
1010
	 * @uses Profile language text
1011
	 * @uses ManageThemes template
1012
	 * with centralized admin permissions on ManageThemes.
1013
	 */
1014
	public function action_pick()
1015
	{
1016
		global $txt, $context, $modSettings;
1017
1018
		require_once(SUBSDIR . '/Themes.subs.php');
1019
1020
		theme()->getTemplates()->load('ManageThemes');
1021
1022
		// 0 is reset all members, -1 is set forum default
1023
		$u = $this->_req->getQuery('u', 'intval');
1024
		$id = $this->_req->getQuery('id', 'intval');
1025
		$save = $this->_req->getPost('save');
1026
		$themePicked = $this->_req->getQuery('th', 'intval');
1027
		$variant = $this->_req->getQuery('vrt', 'cleanhtml');
1028
1029
		$context['default_theme_id'] = $modSettings['theme_default'];
1030
1031
		$_SESSION['theme'] = 0;
1032
1033
		if (isset($id))
1034
		{
1035
			$themePicked = $id;
1036
		}
1037
1038
		// Saving a variant cause JS doesn't work - pretend it did ;)
1039
		if (isset($save))
1040
		{
1041
			// Which theme?
1042
			foreach ($save as $k => $v)
1043
			{
1044
				$themePicked = (int) $k;
1045
			}
1046
1047
			if (isset($this->_req->post->vrt[$k]))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $k seems to be defined by a foreach iteration on line 1042. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1048
			{
1049
				$variant = $this->_req->post->vrt[$k];
1050
			}
1051
		}
1052
1053
		// Have we made a decision, or are we just browsing?
1054
		if (isset($themePicked))
1055
		{
1056
			checkSession('get');
1057
1058
			//$th = $this->_req->getQuery('th', 'intval');
1059
			//$vrt = $this->_req->getQuery('vrt', 'cleanhtml');
1060
1061
			// If changing members or guests - and there's a variant - assume changing default variant.
1062
			if (!empty($variant) && ($u === 0 || $u === -1))
1063
			{
1064
				updateThemeOptions(array($themePicked, 0, 'default_variant', $variant));
1065
1066
				// Make it obvious that it's changed
1067
				Cache::instance()->remove('theme_settings-' . $themePicked);
1068
			}
1069
1070
			// For everyone.
1071
			if ($u === 0)
1072
			{
1073
				require_once(SUBSDIR . '/Members.subs.php');
1074
				updateMemberData(null, array('id_theme' => $themePicked));
1075
1076
				// Remove any custom variants.
1077
				if (!empty($variant))
1078
				{
1079
					deleteVariants($themePicked);
1080
				}
1081
1082
				redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
1083
			}
1084
			// Change the default/guest theme.
1085
			elseif ($u === -1)
1086
			{
1087
				updateSettings(array('theme_guests' => $themePicked));
1088
1089
				redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
1090
			}
1091
		}
1092
1093
		$current_theme = 0;
1094
		if ($u === 0)
1095
		{
1096
			$context['current_member'] = 0;
1097
		}
1098
		// Guests and such...
1099
		elseif ($u === -1)
1100
		{
1101
			$context['current_member'] = -1;
1102
		}
1103
1104
		// Get the theme name and descriptions.
1105
		[$context['available_themes'], $guest_theme] = availableThemes($current_theme, $context['current_member']);
1106
1107
		// As long as we're not doing the default theme...
1108
		if (!isset($u) || $u >= 0)
1109
		{
1110
			if ($guest_theme !== 0)
1111
			{
1112
				$context['available_themes'][0] = $context['available_themes'][$guest_theme];
1113
			}
1114
1115
			$context['available_themes'][0]['id'] = 0;
1116
			$context['available_themes'][0]['name'] = $txt['theme_forum_default'];
1117
			$context['available_themes'][0]['selected'] = $current_theme === 0;
1118
			$context['available_themes'][0]['description'] = $txt['theme_global_description'];
1119
		}
1120
1121
		ksort($context['available_themes']);
1122
1123
		$context['page_title'] = $txt['theme_pick'];
1124
		$context['sub_template'] = 'pick';
1125
	}
1126
1127
	/**
1128
	 * Installs new themes, either from a gzip or copy of the default.
1129
	 *
1130
	 * What it does:
1131
	 *
1132
	 * - Puts themes in $boardurl/themes.
1133
	 * - Assumes the gzip has a root directory in it. (ie default.)
1134
	 * - Requires admin_forum.
1135
	 * - Accessed with ?action=admin;area=theme;sa=install.
1136
	 *
1137
	 * @uses ManageThemes template
1138
	 */
1139
	public function action_install()
1140
	{
1141
		global $boardurl, $txt, $context, $settings, $modSettings;
1142
1143
		checkSession('request');
1144
1145
		require_once(SUBSDIR . '/Themes.subs.php');
1146
		require_once(SUBSDIR . '/Package.subs.php');
1147
		$fileFunc = FileFunctions::instance();
1148
1149
		theme()->getTemplates()->load('ManageThemes');
1150
1151
		// Passed an ID, then the install is complete, lets redirect and show them
1152
		if (isset($this->_req->query->theme_id))
1153
		{
1154
			$this->_req->query->theme_id = (int) $this->_req->query->theme_id;
1155
1156
			$context['sub_template'] = 'installed';
1157
			$context['page_title'] = $txt['theme_installed'];
1158
			$context['installed_theme'] = array(
1159
				'id' => $this->_req->query->theme_id,
1160
				'name' => getThemeName($this->_req->query->theme_id),
1161
			);
1162
1163
			return null;
1164
		}
1165
1166
		// How are we going to install this theme, from a dir, zip, copy of default?
1167
		if ((!empty($_FILES['theme_gz']) && (!isset($_FILES['theme_gz']['error']) || $_FILES['theme_gz']['error'] != 4)) || !empty($this->_req->query->theme_gz))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($_FILES['theme_...>_req->query->theme_gz), Probably Intended Meaning: ! empty($_FILES['theme_g..._req->query->theme_gz))
Loading history...
1168
		{
1169
			$method = 'upload';
1170
		}
1171
		elseif (isset($this->_req->post->theme_dir) && rtrim(realpath($this->_req->post->theme_dir), '/\\') != realpath(BOARDDIR . '/themes') && $fileFunc->isDir($this->_req->post->theme_dir))
1172
		{
1173
			$method = 'path';
1174
		}
1175
		else
1176
		{
1177
			$method = 'copy';
1178
		}
1179
1180
		// Copy the default theme?
1181
		if (!empty($this->_req->post->copy) && $method === 'copy')
1182
		{
1183
			$this->copyDefault();
1184
		}
1185
		// Install from another directory
1186
		elseif (isset($this->_req->post->theme_dir) && $method === 'path')
1187
		{
1188
			$this->installFromDir();
1189
		}
1190
		// Uploaded a zip file to install from
1191
		elseif ($method === 'upload')
1192
		{
1193
			$this->installFromZip();
1194
		}
1195
		else
1196
		{
1197
			throw new Exception('theme_install_general', false);
1198
		}
1199
1200
		// Something go wrong?
1201
		if ($this->theme_dir !== '' && basename($this->theme_dir) !== 'themes')
1202
		{
1203
			// Defaults.
1204
			$install_info = array(
1205
				'theme_url' => $boardurl . '/themes/' . basename($this->theme_dir),
1206
				'images_url' => $this->images_url ?? $boardurl . '/themes/' . basename($this->theme_dir) . '/images',
1207
				'theme_dir' => $this->theme_dir,
1208
				'name' => $this->theme_name
1209
			);
1210
			$explicit_images = false;
1211
1212
			if ($fileFunc->fileExists($this->theme_dir . '/theme_info.xml'))
1213
			{
1214
				$theme_info = file_get_contents($this->theme_dir . '/theme_info.xml');
1215
1216
				// Parse theme-info.xml into an \ElkArte\XmlArray.
1217
				$theme_info_xml = new XmlArray($theme_info);
1218
1219
				// @todo Error message of some sort?
1220
				if (!$theme_info_xml->exists('theme-info[0]'))
1221
				{
1222
					return 'package_get_error_packageinfo_corrupt';
1223
				}
1224
1225
				$theme_info_xml = $theme_info_xml->path('theme-info[0]');
1226
				$theme_info_xml = $theme_info_xml->to_array();
1227
1228
				$xml_elements = array(
1229
					'name' => 'name',
1230
					'theme_layers' => 'layers',
1231
					'theme_templates' => 'templates',
1232
					'based_on' => 'based-on',
1233
				);
1234
				foreach ($xml_elements as $var => $name)
1235
				{
1236
					if (!empty($theme_info_xml[$name]))
1237
					{
1238
						$install_info[$var] = $theme_info_xml[$name];
1239
					}
1240
				}
1241
1242
				if (!empty($theme_info_xml['images']))
1243
				{
1244
					$install_info['images_url'] = $install_info['theme_url'] . '/' . $theme_info_xml['images'];
1245
					$explicit_images = true;
1246
				}
1247
1248
				if (!empty($theme_info_xml['extra']))
1249
				{
1250
					$install_info += Util::unserialize($theme_info_xml['extra']);
1251
				}
1252
			}
1253
1254
			if (isset($install_info['based_on']))
1255
			{
1256
				if ($install_info['based_on'] === 'default')
1257
				{
1258
					$install_info['theme_url'] = $settings['default_theme_url'];
1259
					$install_info['images_url'] = $settings['default_images_url'];
1260
				}
1261
				elseif ($install_info['based_on'] != '')
1262
				{
1263
					$install_info['based_on'] = preg_replace('~[^A-Za-z0-9\-_ ]~', '', $install_info['based_on']);
1264
1265
					$temp = loadBasedOnTheme($install_info['based_on'], $explicit_images);
1266
1267
					// @todo An error otherwise?
1268
					if (is_array($temp))
1269
					{
1270
						$install_info = $temp + $install_info;
1271
1272
						if ($explicit_images === false && !empty($install_info['base_theme_url']))
1273
						{
1274
							$install_info['theme_url'] = $install_info['base_theme_url'];
1275
						}
1276
					}
1277
				}
1278
1279
				unset($install_info['based_on']);
1280
			}
1281
1282
			// Find the newest id_theme.
1283
			$id_theme = nextTheme();
1284
1285
			$inserts = array();
1286
			foreach ($install_info as $var => $val)
1287
			{
1288
				$inserts[] = array($id_theme, $var, $val);
1289
			}
1290
1291
			if (!empty($inserts))
1292
			{
1293
				addTheme($inserts);
1294
			}
1295
1296
			updateSettings(array('knownThemes' => strtr($modSettings['knownThemes'] . ',' . $id_theme, array(',,' => ','))));
1297
1298
			redirectexit('action=admin;area=theme;sa=install;theme_id=' . $id_theme . ';' . $context['session_var'] . '=' . $context['session_id']);
1299
		}
1300
1301
		redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']);
1302
	}
1303
1304
	/**
1305
	 * Make a copy of the default theme in a new directory
1306
	 */
1307
	public function copyDefault()
1308
	{
1309
		global $boardurl, $modSettings, $settings;
1310
1311
		$fileFunc = FileFunctions::instance();
1312
1313
		// Hopefully the theme directory is writable, or we might have a problem.
1314
		if (!$fileFunc->chmod(BOARDDIR . '/themes'))
1315
		{
1316
			throw new Exception('theme_install_write_error', 'critical');
1317
		}
1318
1319
		// Make the new directory, standard characters only
1320
		$new_theme_name = preg_replace('~[^A-Za-z0-9_\- ]~', '', $this->_req->post->copy);
1321
		$this->theme_dir = BOARDDIR . '/themes/' . $new_theme_name;
1322
		$fileFunc->createDirectory($this->theme_dir, false);
1323
1324
		// Get some more time if we can
1325
		detectServer()->setTimeLimit(600);
1326
1327
		// Create the subdirectories for css, javascript and font files.
1328
		$fileFunc->createDirectory($this->theme_dir . '/css', false);
1329
		$fileFunc->createDirectory($this->theme_dir . '/scripts', false);
1330
		$fileFunc->createDirectory($this->theme_dir . '/webfonts', false);
1331
1332
		// Copy over the default non-theme files.
1333
		$to_copy = array('/index.php', '/index.template.php', '/scripts/theme.js', '/Theme.php');
1334
		foreach ($to_copy as $file)
1335
		{
1336
			copy($settings['default_theme_dir'] . $file, $this->theme_dir . $file);
1337
			$fileFunc->chmod($this->theme_dir . $file);
1338
		}
1339
1340
		// And now the entire css, images and webfonts directories!
1341
		copytree($settings['default_theme_dir'] . '/css', $this->theme_dir . '/css');
1342
		copytree($settings['default_theme_dir'] . '/images', $this->theme_dir . '/images');
1343
		copytree($settings['default_theme_dir'] . '/webfonts', $this->theme_dir . '/webfonts');
1344
		package_flush_cache();
1345
1346
		$this->theme_name = $this->_req->post->copy;
1347
		$this->images_url = $boardurl . '/themes/' . basename($this->theme_dir) . '/images';
1348
		$this->theme_dir = realpath($this->theme_dir);
1349
1350
		// Lets get some data for the new theme (default theme (1), default settings (0)).
1351
		$theme_values = loadThemeOptionsInto(1, 0, array(), array('theme_templates', 'theme_layers'));
1352
1353
		// Lets add a theme_info.xml to this theme.
1354
		write_theme_info($this->_req->post->copy, $modSettings['elkVersion'], $this->theme_dir, $theme_values);
1355
1356
		// Finish by setting the namespace
1357
		$theme = file_get_contents($this->theme_dir . '/Theme.php');
1358
		$theme = str_replace('namespace ElkArte\Themes\DefaultTheme;', 'namespace ElkArte\Themes\\' . $new_theme_name . ';', $theme);
1359
		file_put_contents($this->theme_dir . '/Theme.php', $theme);
1360
	}
1361
1362
	/**
1363
	 * Install a theme from a directory on the server
1364
	 *
1365
	 * - Expects the directory is properly loaded with theme files
1366
	 */
1367
	public function installFromDir()
1368
	{
1369
		$fileFunc = FileFunctions::instance();
1370
1371
		if (!$fileFunc->isDir($this->_req->post->theme_dir) || !$fileFunc->fileExists($this->_req->post->theme_dir . '/theme_info.xml'))
1372
		{
1373
			throw new Exception('theme_install_error', false);
1374
		}
1375
1376
		$this->theme_name = basename($this->_req->post->theme_dir);
1377
		$this->theme_dir = $this->_req->post->theme_dir;
1378
	}
1379
1380
	/**
1381
	 * Install a new theme from an uploaded zip archive
1382
	 */
1383
	public function installFromZip()
1384
	{
1385
		$fileFunc = FileFunctions::instance();
1386
1387
		// Hopefully the theme directory is writable, or we might have a problem.
1388
		if (!$fileFunc->chmod(BOARDDIR . '/themes'))
1389
		{
1390
			throw new Exception('theme_install_write_error', 'critical');
1391
		}
1392
1393
		// This happens when the admin session is gone and the user has to login again
1394
		if (empty($_FILES['theme_gz']) && empty($this->_req->post->theme_gz))
1395
		{
1396
			return;
1397
		}
1398
1399
		// Set the default settings...
1400
		$this->theme_name = strtok(basename(isset($_FILES['theme_gz']) ? $_FILES['theme_gz']['name'] : $this->_req->post->theme_gz), '.');
1401
		$this->theme_name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $this->theme_name);
1402
1403
		$this->theme_dir = BOARDDIR . '/themes/' . $this->theme_name;
1404
1405
		if (isset($_FILES['theme_gz']) && is_uploaded_file($_FILES['theme_gz']['tmp_name']) && (ini_get('open_basedir') != '' || $fileFunc->fileExists($_FILES['theme_gz']['tmp_name'])))
1406
		{
1407
			read_tgz_file($_FILES['theme_gz']['tmp_name'], BOARDDIR . '/themes/' . $this->theme_name, false, true);
1408
		}
1409
		elseif (isset($this->_req->post->theme_gz))
1410
		{
1411
			read_tgz_file($this->_req->post->theme_gz, BOARDDIR . '/themes/' . $this->theme_name, false, true);
1412
		}
1413
	}
1414
1415
	/**
1416
	 * Set a theme option via javascript.
1417
	 *
1418
	 * What it does:
1419
	 *
1420
	 * - sets a theme option without outputting anything.
1421
	 * - can be used with javascript, via a dummy image... (which doesn't require
1422
	 *   the page to reload.)
1423
	 * - requires someone who is logged in.
1424
	 * - accessed via ?action=jsoption;var=variable;val=value;session_var=sess_id.
1425
	 * - optionally contains &th=theme id
1426
	 * - does not log access to the Who's Online log. (in index.php..)
1427
	 */
1428
	public function action_jsoption()
1429
	{
1430
		global $settings, $options;
1431
1432
		// Check the session id.
1433
		checkSession('get');
1434
1435
		// This good-for-nothing pixel is being used to keep the session alive.
1436
		if (empty($this->_req->query->var) || !isset($this->_req->query->val))
1437
		{
1438
			redirectexit($settings['images_url'] . '/blank.png');
1439
		}
1440
1441
		// Sorry, guests can't go any further than this..
1442
		if ($this->user->is_guest || $this->user->id == 0)
1443
		{
1444
			obExit(false);
1445
		}
1446
1447
		$reservedVars = array(
1448
			'actual_theme_url',
1449
			'actual_images_url',
1450
			'base_theme_dir',
1451
			'base_theme_url',
1452
			'default_images_url',
1453
			'default_theme_dir',
1454
			'default_theme_url',
1455
			'default_template',
1456
			'images_url',
1457
			'number_recent_posts',
1458
			'smiley_sets_default',
1459
			'theme_dir',
1460
			'theme_id',
1461
			'theme_layers',
1462
			'theme_templates',
1463
			'theme_url',
1464
			'name',
1465
		);
1466
1467
		// Can't change reserved vars.
1468
		if (in_array(strtolower($this->_req->query->var), $reservedVars))
1469
		{
1470
			redirectexit($settings['images_url'] . '/blank.png');
1471
		}
1472
1473
		// Use a specific theme?
1474
		if (isset($this->_req->query->th) || isset($this->_req->query->id))
1475
		{
1476
			// Invalidate the current themes cache too.
1477
			Cache::instance()->remove('theme_settings-' . $settings['theme_id'] . ':' . $this->user->id);
1478
1479
			$settings['theme_id'] = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval'));
1480
		}
1481
1482
		// If this is the admin preferences the passed value will just be an element of it.
1483
		if ($this->_req->query->var === 'admin_preferences')
1484
		{
1485
			if (!empty($options['admin_preferences']))
1486
			{
1487
				$options['admin_preferences'] = serializeToJson($options['admin_preferences'], static function ($array_form) {
1488
					global $context;
1489
1490
					$context['admin_preferences'] = $array_form;
1491
					require_once(SUBSDIR . '/Admin.subs.php');
1492
					updateAdminPreferences();
1493
				});
1494
			}
1495
			else
1496
			{
1497
				$options['admin_preferences'] = array();
1498
			}
1499
1500
			// New thingy...
1501
			if (isset($this->_req->query->admin_key) && strlen($this->_req->query->admin_key) < 5)
1502
			{
1503
				$options['admin_preferences'][$this->_req->query->admin_key] = $this->_req->query->val;
1504
			}
1505
1506
			// Change the value to be something nice,
1507
			$this->_req->query->val = json_encode($options['admin_preferences']);
1508
		}
1509
		// If this is the window min/max settings, the passed window name will just be an element of it.
1510
		elseif ($this->_req->query->var === 'minmax_preferences')
1511
		{
1512
			if (!empty($options['minmax_preferences']))
1513
			{
1514
				$minmax_preferences = serializeToJson($options['minmax_preferences'], static function ($array_form) use ($settings) {
1515
					// Update the option.
1516
					require_once(SUBSDIR . '/Themes.subs.php');
1517
					updateThemeOptions(array($settings['theme_id'], User::$info->id, 'minmax_preferences', json_encode($array_form)));
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1518
				});
1519
			}
1520
			else
1521
			{
1522
				$minmax_preferences = array();
1523
			}
1524
1525
			// New value for them
1526
			if (isset($this->_req->query->minmax_key) && strlen($this->_req->query->minmax_key) < 10)
1527
			{
1528
				$minmax_preferences[$this->_req->query->minmax_key] = $this->_req->query->val;
1529
			}
1530
1531
			// Change the value to be something nice,
1532
			$this->_req->query->val = json_encode($minmax_preferences);
1533
		}
1534
1535
		// Update the option.
1536
		require_once(SUBSDIR . '/Themes.subs.php');
1537
		updateThemeOptions(array($settings['theme_id'], $this->user->id, $this->_req->query->var, is_array($this->_req->query->val) ? implode(',', $this->_req->query->val) : $this->_req->query->val));
1538
1539
		Cache::instance()->remove('theme_settings-' . $settings['theme_id'] . ':' . $this->user->id);
1540
1541
		// Don't output anything...
1542
		redirectexit($settings['images_url'] . '/blank.png');
1543
	}
1544
}
1545