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
![]() |
|||||
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
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
![]() |
|||||
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
|
|||||
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
|
|||||
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
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
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 |