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
|
|
|
* @name ElkArte Forum |
9
|
|
|
* @copyright ElkArte Forum contributors |
10
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause |
11
|
|
|
* |
12
|
|
|
* This software is a derived product, based on: |
13
|
|
|
* |
14
|
|
|
* Simple Machines Forum (SMF) |
15
|
|
|
* copyright: 2011 Simple Machines (http://www.simplemachines.org) |
16
|
|
|
* license: BSD, See included LICENSE.TXT for terms and conditions. |
17
|
|
|
* |
18
|
|
|
* @version 1.1 dev |
19
|
|
|
* |
20
|
|
|
* |
21
|
|
|
* @todo Update this for the new package manager? |
22
|
|
|
* |
23
|
|
|
* Creating and distributing theme packages: |
24
|
|
|
* There isn't that much required to package and distribute your own themes... |
25
|
|
|
* just do the following: |
26
|
|
|
* - create a theme_info.xml file, with the root element theme-info. |
27
|
|
|
* - its name should go in a name element, just like description. |
28
|
|
|
* - your name should go in author. (email in the email attribute.) |
29
|
|
|
* - any support website for the theme should be in website. |
30
|
|
|
* - layers and templates (non-default) should go in those elements ;). |
31
|
|
|
* - if the images dir isn't images, specify in the images element. |
32
|
|
|
* - any extra rows for themes should go in extra, serialized. (as in array(variable => value).) |
33
|
|
|
* - tar and gzip the directory - and you're done! |
34
|
|
|
* - please include any special license in a license.txt file. |
35
|
|
|
*/ |
36
|
|
|
|
37
|
|
|
if (!defined('ELK')) |
38
|
|
|
die('No access...'); |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Class to deal with theme administration. |
42
|
|
|
* |
43
|
|
|
* Its tasks include changing theme settings, installing and removing |
44
|
|
|
* themes, choosing the current theme, and editing themes. |
45
|
|
|
* |
46
|
|
|
* @package Themes |
47
|
|
|
*/ |
48
|
|
|
class ManageThemes_Controller extends Action_Controller |
49
|
|
|
{ |
50
|
|
|
/** |
51
|
|
|
* Holds the selected theme options |
52
|
|
|
* @var mixed[] |
53
|
|
|
*/ |
54
|
|
|
private $_options; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Holds the selected default theme options |
58
|
|
|
* @var mixed[] |
59
|
|
|
*/ |
60
|
|
|
private $_default_options; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Holds the selected master options for a theme |
64
|
|
|
* @var mixed[] |
65
|
|
|
*/ |
66
|
|
|
private $_options_master; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Holds the selected default master options for a theme |
70
|
|
|
* @var mixed[] |
71
|
|
|
*/ |
72
|
|
|
private $_default_options_master; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Name of the theme |
76
|
|
|
* @var string |
77
|
|
|
*/ |
78
|
|
|
private $theme_name; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Full path to the theme |
82
|
|
|
* @var string |
83
|
|
|
*/ |
84
|
|
|
private $theme_dir; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* The themes images url if any |
88
|
|
|
* @var string|null |
89
|
|
|
*/ |
90
|
|
|
private $images_url; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Subaction handler - manages the action and delegates control to the proper |
94
|
|
|
* sub-action. |
95
|
|
|
* |
96
|
|
|
* What it does: |
97
|
|
|
* - It loads both the Themes and Settings language files. |
98
|
|
|
* - Checks the session by GET or POST to verify the sent data. |
99
|
|
|
* - Requires the user to not be a guest. |
100
|
|
|
* - Accessed via ?action=admin;area=theme. |
101
|
|
|
* |
102
|
|
|
* @see Action_Controller::action_index() |
103
|
|
|
*/ |
104
|
|
|
public function action_index() |
105
|
|
|
{ |
106
|
|
|
global $txt, $context; |
107
|
|
|
|
108
|
|
|
if (isset($this->_req->query->api)) |
109
|
|
|
return $this->action_index_api(); |
110
|
|
|
|
111
|
|
|
// Load the important language files... |
112
|
|
|
loadLanguage('ManageThemes'); |
113
|
|
|
loadLanguage('Settings'); |
114
|
|
|
|
115
|
|
|
// No guests in here. |
116
|
|
|
is_not_guest(); |
117
|
|
|
|
118
|
|
|
// Theme administration, removal, choice, or installation... |
119
|
|
|
$subActions = array( |
120
|
|
|
'admin' => array($this, 'action_admin', 'permission' => 'admin_forum'), |
121
|
|
|
'list' => array($this, 'action_list', 'permission' => 'admin_forum'), |
122
|
|
|
'reset' => array($this, 'action_options', 'permission' => 'admin_forum'), |
123
|
|
|
'options' => array($this, 'action_options', 'permission' => 'admin_forum'), |
124
|
|
|
'install' => array($this, 'action_install', 'permission' => 'admin_forum'), |
125
|
|
|
'remove' => array($this, 'action_remove', 'permission' => 'admin_forum'), |
126
|
|
|
'pick' => array($this, 'action_pick'), // @todo ugly having that in this controller |
127
|
|
|
'edit' => array($this, 'action_edit', 'permission' => 'admin_forum'), |
128
|
|
|
'copy' => array($this, 'action_copy', 'permission' => 'admin_forum'), |
129
|
|
|
'themelist' => array($this, 'action_themelist', 'permission' => 'admin_forum'), |
130
|
|
|
'browse' => array($this, 'action_browse', 'permission' => 'admin_forum'), |
131
|
|
|
); |
132
|
|
|
|
133
|
|
|
// Action controller |
134
|
|
|
$action = new Action('manage_themes'); |
135
|
|
|
|
136
|
|
|
// @todo Layout Settings? |
137
|
|
|
if (!empty($context['admin_menu_name'])) |
138
|
|
|
{ |
139
|
|
|
$context[$context['admin_menu_name']]['tab_data'] = array( |
140
|
|
|
'title' => $txt['themeadmin_title'], |
141
|
|
|
'description' => $txt['themeadmin_description'], |
142
|
|
|
'tabs' => array( |
143
|
|
|
'admin' => array( |
144
|
|
|
'description' => $txt['themeadmin_admin_desc'], |
145
|
|
|
), |
146
|
|
|
'list' => array( |
147
|
|
|
'description' => $txt['themeadmin_list_desc'], |
148
|
|
|
), |
149
|
|
|
'reset' => array( |
150
|
|
|
'description' => $txt['themeadmin_reset_desc'], |
151
|
|
|
), |
152
|
|
|
'edit' => array( |
153
|
|
|
'description' => $txt['themeadmin_edit_desc'], |
154
|
|
|
), |
155
|
|
|
'themelist' => array( |
156
|
|
|
'description' => $txt['themeadmin_edit_desc'], |
157
|
|
|
), |
158
|
|
|
'browse' => array( |
159
|
|
|
'description' => $txt['themeadmin_edit_desc'], |
160
|
|
|
), |
161
|
|
|
), |
162
|
|
|
); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
// Follow the sa or just go to administration, call integrate_sa_manage_themes |
166
|
|
|
$subAction = $action->initialize($subActions, 'admin'); |
167
|
|
|
|
168
|
|
|
// Default the page title to Theme Administration by default. |
169
|
|
|
$context['page_title'] = $txt['themeadmin_title']; |
170
|
|
|
$context['sub_action'] = $subAction; |
171
|
|
|
|
172
|
|
|
// Go to the action, if you have permissions |
173
|
|
|
$action->dispatch($subAction); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Responds to an ajax button request, currently only for remove |
178
|
|
|
* |
179
|
|
|
* @uses generic_xml_buttons sub template |
180
|
|
|
*/ |
181
|
|
|
public function action_index_api() |
182
|
|
|
{ |
183
|
|
|
global $txt, $context, $user_info; |
184
|
|
|
|
185
|
|
|
loadTemplate('Xml'); |
186
|
|
|
|
187
|
|
|
// Remove any template layers that may have been created, this is XML! |
188
|
|
|
Template_Layers::getInstance()->removeAll(); |
189
|
|
|
$context['sub_template'] = 'generic_xml_buttons'; |
190
|
|
|
|
191
|
|
|
// No guests in here. |
192
|
|
|
if ($user_info['is_guest']) |
193
|
|
|
{ |
194
|
|
|
loadLanguage('Errors'); |
195
|
|
|
$context['xml_data'] = array( |
196
|
|
|
'error' => 1, |
197
|
|
|
'text' => $txt['not_guests'] |
198
|
|
|
); |
199
|
|
|
|
200
|
|
|
return; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
// Theme administration, removal, choice, or installation... |
204
|
|
|
// Of all the actions we currently know only this |
205
|
|
|
$subActions = array( |
206
|
|
|
// 'admin' => 'action_admin', |
|
|
|
|
207
|
|
|
// 'list' => 'action_list', |
|
|
|
|
208
|
|
|
// 'reset' => 'action_options', |
|
|
|
|
209
|
|
|
// 'options' => 'action_options', |
|
|
|
|
210
|
|
|
// 'install' => 'action_install', |
|
|
|
|
211
|
|
|
'remove' => 'action_remove_api', |
212
|
|
|
// 'pick' => 'action_pick', |
|
|
|
|
213
|
|
|
// 'edit' => 'action_edit', |
|
|
|
|
214
|
|
|
// 'copy' => 'action_copy', |
|
|
|
|
215
|
|
|
// 'themelist' => 'action_themelist', |
|
|
|
|
216
|
|
|
// 'browse' => 'action_browse', |
|
|
|
|
217
|
|
|
); |
218
|
|
|
|
219
|
|
|
// Follow the sa or just go to administration. |
220
|
|
|
if (isset($this->_req->query->sa) && !empty($subActions[$this->_req->query->sa])) |
221
|
|
|
$this->{$subActions[$this->_req->query->sa]}(); |
222
|
|
|
else |
223
|
|
|
{ |
224
|
|
|
loadLanguage('Errors'); |
225
|
|
|
$context['xml_data'] = array( |
226
|
|
|
'error' => 1, |
227
|
|
|
'text' => $txt['error_sa_not_set'] |
228
|
|
|
); |
229
|
|
|
return; |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* This function allows administration of themes and their settings, |
235
|
|
|
* as well as global theme settings. |
236
|
|
|
* |
237
|
|
|
* What it does: |
238
|
|
|
* - sets the settings theme_allow, theme_guests, and knownThemes. |
239
|
|
|
* - requires the admin_forum permission. |
240
|
|
|
* - accessed with ?action=admin;area=theme;sa=admin. |
241
|
|
|
* |
242
|
|
|
* @uses Themes template |
243
|
|
|
* @uses Admin language file |
244
|
|
|
*/ |
245
|
|
|
public function action_admin() |
246
|
|
|
{ |
247
|
|
|
global $context, $modSettings; |
248
|
|
|
|
249
|
|
|
loadLanguage('Admin'); |
250
|
|
|
|
251
|
|
|
// Saving? |
252
|
|
|
if (isset($this->_req->post->save)) |
253
|
|
|
{ |
254
|
|
|
checkSession(); |
255
|
|
|
validateToken('admin-tm'); |
256
|
|
|
|
257
|
|
|
// What themes are being made as known to the members |
258
|
|
|
if (isset($this->_req->post->options['known_themes'])) |
259
|
|
|
{ |
260
|
|
|
foreach ($this->_req->post->options['known_themes'] as $key => $id) |
261
|
|
|
$this->_req->post->options['known_themes'][$key] = (int) $id; |
262
|
|
|
} |
263
|
|
|
else |
264
|
|
|
Errors::instance()->fatal_lang_error('themes_none_selectable', false); |
265
|
|
|
|
266
|
|
|
if (!in_array($this->_req->post->options['theme_guests'], $this->_req->post->options['known_themes'])) |
267
|
|
|
Errors::instance()->fatal_lang_error('themes_default_selectable', false); |
268
|
|
|
|
269
|
|
|
// Commit the new settings. |
270
|
|
|
updateSettings(array( |
271
|
|
|
'theme_allow' => !empty($this->_req->post->options['theme_allow']), |
272
|
|
|
'theme_guests' => $this->_req->post->options['theme_guests'], |
273
|
|
|
'knownThemes' => implode(',', $this->_req->post->options['known_themes']), |
274
|
|
|
)); |
275
|
|
|
|
276
|
|
|
if ((int) $this->_req->post->theme_reset == 0 || in_array($this->_req->post->theme_reset, $this->_req->post->options['known_themes'])) |
277
|
|
|
{ |
278
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
279
|
|
|
updateMemberData(null, array('id_theme' => (int) $this->_req->post->theme_reset)); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=admin'); |
283
|
|
|
} |
284
|
|
|
// If we aren't submitting - that is, if we are about to... |
285
|
|
|
else |
286
|
|
|
{ |
287
|
|
|
loadTemplate('ManageThemes'); |
288
|
|
|
$context['sub_template'] = 'manage_themes'; |
289
|
|
|
|
290
|
|
|
// Make our known themes a little easier to work with. |
291
|
|
|
$knownThemes = !empty($modSettings['knownThemes']) ? explode(',', $modSettings['knownThemes']) : array(); |
292
|
|
|
|
293
|
|
|
// Load up all the themes. |
294
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
295
|
|
|
$context['themes'] = loadThemes($knownThemes); |
296
|
|
|
|
297
|
|
|
// Can we create a new theme? |
298
|
|
|
$context['can_create_new'] = is_writable(BOARDDIR . '/themes'); |
299
|
|
|
$context['new_theme_dir'] = substr(realpath(BOARDDIR . '/themes/default'), 0, -7); |
300
|
|
|
|
301
|
|
|
// Look for a non existent theme directory. (ie theme87.) |
302
|
|
|
$theme_dir = BOARDDIR . '/themes/theme'; |
303
|
|
|
$i = 1; |
304
|
|
|
while (file_exists($theme_dir . $i)) |
305
|
|
|
$i++; |
306
|
|
|
$context['new_theme_name'] = 'theme' . $i; |
307
|
|
|
|
308
|
|
|
createToken('admin-tm'); |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* This function lists the available themes and provides an interface |
314
|
|
|
* to reset the paths of all the installed themes. |
315
|
|
|
* |
316
|
|
|
* @uses sub template list_themes, template ManageThemes |
317
|
|
|
*/ |
318
|
|
|
public function action_list() |
319
|
|
|
{ |
320
|
|
|
global $context, $boardurl, $txt; |
321
|
|
|
|
322
|
|
|
// Load in the helpers we need |
323
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
324
|
|
|
loadLanguage('Admin'); |
325
|
|
|
|
326
|
|
|
if (isset($this->_req->query->th)) |
327
|
|
|
return $this->action_setthemesettings(); |
328
|
|
|
|
329
|
|
|
// Saving? |
330
|
|
|
if (isset($this->_req->post->save)) |
331
|
|
|
{ |
332
|
|
|
checkSession(); |
333
|
|
|
validateToken('admin-tl'); |
334
|
|
|
|
335
|
|
|
$themes = installedThemes(); |
336
|
|
|
|
337
|
|
|
$setValues = array(); |
338
|
|
|
foreach ($themes as $id => $theme) |
339
|
|
|
{ |
340
|
|
|
if (file_exists($this->_req->post->reset_dir . '/' . basename($theme['theme_dir']))) |
341
|
|
|
{ |
342
|
|
|
$setValues[] = array($id, 0, 'theme_dir', realpath($this->_req->post->reset_dir . '/' . basename($theme['theme_dir']))); |
343
|
|
|
$setValues[] = array($id, 0, 'theme_url', $this->_req->post->reset_url . '/' . basename($theme['theme_dir'])); |
344
|
|
|
$setValues[] = array($id, 0, 'images_url', $this->_req->post->reset_url . '/' . basename($theme['theme_dir']) . '/' . basename($theme['images_url'])); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
if (isset($theme['base_theme_dir']) && file_exists($this->_req->post->reset_dir . '/' . basename($theme['base_theme_dir']))) |
348
|
|
|
{ |
349
|
|
|
$setValues[] = array($id, 0, 'base_theme_dir', realpath($this->_req->post->reset_dir . '/' . basename($theme['base_theme_dir']))); |
350
|
|
|
$setValues[] = array($id, 0, 'base_theme_url', $this->_req->post->reset_url . '/' . basename($theme['base_theme_dir'])); |
351
|
|
|
$setValues[] = array($id, 0, 'base_images_url', $this->_req->post->reset_url . '/' . basename($theme['base_theme_dir']) . '/' . basename($theme['base_images_url'])); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
Cache::instance()->remove('theme_settings-' . $id); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
if (!empty($setValues)) |
358
|
|
|
updateThemeOptions($setValues); |
359
|
|
|
|
360
|
|
|
redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
loadTemplate('ManageThemes'); |
364
|
|
|
|
365
|
|
|
$context['themes'] = installedThemes(); |
366
|
|
|
|
367
|
|
|
// For each theme, make sure the directory exists, and try to fetch the theme version |
368
|
|
|
foreach ($context['themes'] as $i => $theme) |
369
|
|
|
{ |
370
|
|
|
$context['themes'][$i]['theme_dir'] = realpath($context['themes'][$i]['theme_dir']); |
371
|
|
|
|
372
|
|
|
if (file_exists($context['themes'][$i]['theme_dir'] . '/index.template.php')) |
373
|
|
|
{ |
374
|
|
|
// Fetch the header... a good 256 bytes should be more than enough. |
375
|
|
|
$fp = fopen($context['themes'][$i]['theme_dir'] . '/index.template.php', 'rb'); |
376
|
|
|
$header = fread($fp, 256); |
377
|
|
|
fclose($fp); |
378
|
|
|
|
379
|
|
|
// Can we find a version comment, at all? |
380
|
|
|
if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) |
381
|
|
|
$context['themes'][$i]['version'] = $match[1]; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
$context['themes'][$i]['valid_path'] = file_exists($context['themes'][$i]['theme_dir']) && is_dir($context['themes'][$i]['theme_dir']); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
// Off to the template we go |
388
|
|
|
$context['sub_template'] = 'list_themes'; |
389
|
|
|
addJavascriptVar(array('txt_theme_remove_confirm' => $txt['theme_remove_confirm']), true); |
390
|
|
|
$context['reset_dir'] = realpath(BOARDDIR . '/themes'); |
391
|
|
|
$context['reset_url'] = $boardurl . '/themes'; |
392
|
|
|
|
393
|
|
|
createToken('admin-tl'); |
394
|
|
|
createToken('admin-tr', 'request'); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Administrative global settings. |
399
|
|
|
* |
400
|
|
|
* - Accessed by ?action=admin;area=theme;sa=reset; |
401
|
|
|
* |
402
|
|
|
* @uses sub template set_options, template file Settings |
403
|
|
|
* @uses template file ManageThemes |
404
|
|
|
*/ |
405
|
|
|
public function action_options() |
406
|
|
|
{ |
407
|
|
|
global $txt, $context, $settings, $modSettings; |
408
|
|
|
|
409
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
410
|
|
|
$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
411
|
|
|
|
412
|
|
|
if (empty($theme) && empty($this->_req->query->id)) |
413
|
|
|
{ |
414
|
|
|
$context['themes'] = installedThemes(); |
415
|
|
|
|
416
|
|
|
// How many options do we have setup for guests? |
417
|
|
|
$guestOptions = countConfiguredGuestOptions(); |
418
|
|
|
foreach ($guestOptions as $guest_option) |
419
|
|
|
$context['themes'][$guest_option['id_theme']]['num_default_options'] = $guest_option['value']; |
420
|
|
|
|
421
|
|
|
// How many options do we have setup for members? |
422
|
|
|
$memberOptions = countConfiguredMemberOptions(); |
423
|
|
|
foreach ($memberOptions as $member_option) |
424
|
|
|
$context['themes'][$member_option['id_theme']]['num_members'] = $member_option['value']; |
425
|
|
|
|
426
|
|
|
// There has to be a Settings template! |
427
|
|
|
foreach ($context['themes'] as $k => $v) |
428
|
|
|
if (empty($v['theme_dir']) || (!file_exists($v['theme_dir'] . '/Settings.template.php') && empty($v['num_members']))) |
429
|
|
|
unset($context['themes'][$k]); |
430
|
|
|
|
431
|
|
|
loadTemplate('ManageThemes'); |
432
|
|
|
$context['sub_template'] = 'reset_list'; |
433
|
|
|
|
434
|
|
|
createToken('admin-stor', 'request'); |
435
|
|
|
return; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
// Submit? |
439
|
|
|
if (isset($this->_req->post->submit) && empty($this->_req->post->who)) |
440
|
|
|
{ |
441
|
|
|
checkSession(); |
442
|
|
|
validateToken('admin-sto'); |
443
|
|
|
|
444
|
|
|
if (empty($this->_req->post->options)) |
445
|
|
|
$this->_options = array(); |
446
|
|
|
|
447
|
|
|
if (empty($this->_req->post->default_options)) |
448
|
|
|
$this->_default_options = array(); |
449
|
|
|
|
450
|
|
|
// Set up the query values. |
451
|
|
|
$setValues = array(); |
452
|
|
|
foreach ($this->_options as $opt => $val) |
453
|
|
|
$setValues[] = array($theme, -1, $opt, is_array($val) ? implode(',', $val) : $val); |
454
|
|
|
|
455
|
|
|
$old_settings = array(); |
456
|
|
|
foreach ($this->_default_options as $opt => $val) |
457
|
|
|
{ |
458
|
|
|
$old_settings[] = $opt; |
459
|
|
|
$setValues[] = array(1, -1, $opt, is_array($val) ? implode(',', $val) : $val); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
// If we're actually inserting something.. |
463
|
|
|
if (!empty($setValues)) |
464
|
|
|
{ |
465
|
|
|
// Are there options in non-default themes set that should be cleared? |
466
|
|
|
if (!empty($old_settings)) |
467
|
|
|
removeThemeOptions('custom', 'guests', $old_settings); |
468
|
|
|
|
469
|
|
|
updateThemeOptions($setValues); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
// Cache the theme settings |
473
|
|
|
Cache::instance()->remove('theme_settings-' . $theme); |
474
|
|
|
Cache::instance()->remove('theme_settings-1'); |
475
|
|
|
|
476
|
|
|
redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset'); |
477
|
|
|
} |
478
|
|
|
// Changing the current options for all members using this theme |
479
|
|
|
elseif (isset($this->_req->post->submit) && $this->_req->post->who == 1) |
480
|
|
|
{ |
481
|
|
|
checkSession(); |
482
|
|
|
validateToken('admin-sto'); |
483
|
|
|
|
484
|
|
|
$this->_options = empty($this->_req->post->options) ? array() : $this->_req->post->options; |
485
|
|
|
$this->_options_master = empty($this->_req->post->options_master) ? array() : $this->_req->post->options_master; |
486
|
|
|
$this->_default_options = empty($this->_req->post->default_options) ? array() : $this->_req->post->default_options; |
487
|
|
|
$this->_default_options_master = empty($this->_req->post->default_options_master) ? array() : $this->_req->post->default_options_master; |
488
|
|
|
|
489
|
|
|
$old_settings = array(); |
490
|
|
|
foreach ($this->_default_options as $opt => $val) |
491
|
|
|
{ |
492
|
|
|
if ($this->_default_options_master[$opt] == 0) |
493
|
|
|
continue; |
494
|
|
|
elseif ($this->_default_options_master[$opt] == 1) |
495
|
|
|
{ |
496
|
|
|
// Delete then insert for ease of database compatibility! |
497
|
|
|
removeThemeOptions('default', 'members', $opt); |
498
|
|
|
addThemeOptions(1, $opt, $val); |
499
|
|
|
|
500
|
|
|
$old_settings[] = $opt; |
501
|
|
|
} |
502
|
|
|
elseif ($this->_default_options_master[$opt] == 2) |
503
|
|
|
removeThemeOptions('all', 'members', $opt); |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
// Delete options from other themes. |
507
|
|
|
if (!empty($old_settings)) |
508
|
|
|
removeThemeOptions('custom', 'members', $old_settings); |
509
|
|
|
|
510
|
|
|
foreach ($this->_options as $opt => $val) |
511
|
|
|
{ |
512
|
|
|
if ($this->_options_master[$opt] == 0) |
513
|
|
|
continue; |
514
|
|
|
elseif ($this->_options_master[$opt] == 1) |
515
|
|
|
{ |
516
|
|
|
// Delete then insert for ease of database compatibility - again! |
517
|
|
|
removeThemeOptions($theme, 'non_default', $opt); |
518
|
|
|
addThemeOptions($theme, $opt, $val); |
519
|
|
|
} |
520
|
|
|
elseif ($this->_options_master[$opt] == 2) |
521
|
|
|
removeThemeOptions($theme, 'all', $opt); |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset'); |
525
|
|
|
} |
526
|
|
|
// Remove all members options and use the defaults |
527
|
|
|
elseif (!empty($this->_req->query->who) && $this->_req->query->who == 2) |
528
|
|
|
{ |
529
|
|
|
checkSession('get'); |
530
|
|
|
validateToken('admin-stor', 'request'); |
531
|
|
|
|
532
|
|
|
removeThemeOptions($theme, 'members'); |
533
|
|
|
|
534
|
|
|
redirectexit('action=admin;area=theme;' . $context['session_var'] . '=' . $context['session_id'] . ';sa=reset'); |
535
|
|
|
} |
536
|
|
|
|
537
|
|
|
$old_id = $settings['theme_id']; |
538
|
|
|
$old_settings = $settings; |
539
|
|
|
|
540
|
|
|
loadTheme($theme, false); |
541
|
|
|
loadLanguage('Profile'); |
542
|
|
|
|
543
|
|
|
// @todo Should we just move these options so they are no longer theme dependant? |
544
|
|
|
loadLanguage('PersonalMessage'); |
545
|
|
|
|
546
|
|
|
// Let the theme take care of the settings. |
547
|
|
|
loadTemplate('Settings'); |
548
|
|
|
loadSubTemplate('options'); |
549
|
|
|
|
550
|
|
|
// Set up for the template |
551
|
|
|
$context['sub_template'] = 'set_options'; |
552
|
|
|
$context['page_title'] = $txt['theme_settings']; |
553
|
|
|
$context['options'] = $context['theme_options']; |
554
|
|
|
$context['theme_settings'] = $settings; |
555
|
|
|
|
556
|
|
|
// Load the options for these theme |
557
|
|
|
if (empty($this->_req->query->who)) |
558
|
|
|
{ |
559
|
|
|
$context['theme_options'] = loadThemeOptionsInto(array(1, $theme), -1, $context['theme_options']); |
560
|
|
|
$context['theme_options_reset'] = false; |
561
|
|
|
} |
562
|
|
|
else |
563
|
|
|
{ |
564
|
|
|
$context['theme_options'] = array(); |
565
|
|
|
$context['theme_options_reset'] = true; |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
// Prepare the options for the template |
569
|
|
|
foreach ($context['options'] as $i => $setting) |
570
|
|
|
{ |
571
|
|
|
// Is this disabled? |
572
|
|
|
if ($setting['id'] == 'calendar_start_day' && empty($modSettings['cal_enabled'])) |
573
|
|
|
{ |
574
|
|
|
unset($context['options'][$i]); |
575
|
|
|
continue; |
576
|
|
|
} |
577
|
|
|
elseif (($setting['id'] == 'topics_per_page' || $setting['id'] == 'messages_per_page') && !empty($modSettings['disableCustomPerPage'])) |
578
|
|
|
{ |
579
|
|
|
unset($context['options'][$i]); |
580
|
|
|
continue; |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
// Type of field so we display the right input field |
584
|
|
|
if (!isset($setting['type']) || $setting['type'] == 'bool') |
585
|
|
|
$context['options'][$i]['type'] = 'checkbox'; |
586
|
|
|
elseif ($setting['type'] == 'int' || $setting['type'] == 'integer') |
587
|
|
|
$context['options'][$i]['type'] = 'number'; |
588
|
|
|
elseif ($setting['type'] == 'string') |
589
|
|
|
$context['options'][$i]['type'] = 'text'; |
590
|
|
|
|
591
|
|
|
if (isset($setting['options'])) |
592
|
|
|
$context['options'][$i]['type'] = 'list'; |
593
|
|
|
|
594
|
|
|
$context['options'][$i]['value'] = !isset($context['theme_options'][$setting['id']]) ? '' : $context['theme_options'][$setting['id']]; |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
// Restore the existing theme. |
598
|
|
|
loadTheme($old_id, false); |
599
|
|
|
$settings = $old_settings; |
600
|
|
|
|
601
|
|
|
loadTemplate('ManageThemes'); |
602
|
|
|
createToken('admin-sto'); |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* Administrative global settings. |
607
|
|
|
* |
608
|
|
|
* What it does: |
609
|
|
|
* - Saves and requests global theme settings. ($settings) |
610
|
|
|
* - Loads the Admin language file. |
611
|
|
|
* - Calls action_admin() if no theme is specified. (the theme center.) |
612
|
|
|
* - Requires admin_forum permission. |
613
|
|
|
* - Accessed with ?action=admin;area=theme;sa=list&th=xx. |
614
|
|
|
*/ |
615
|
|
|
public function action_setthemesettings() |
616
|
|
|
{ |
617
|
|
|
global $txt, $context, $settings, $modSettings; |
618
|
|
|
|
619
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
620
|
|
|
|
621
|
|
|
// Nothing chosen, back to the start you go |
622
|
|
|
if (empty($this->_req->query->th) && empty($this->_req->query->id)) |
623
|
|
|
return $this->action_admin(); |
624
|
|
|
|
625
|
|
|
// The theme's ID is needed |
626
|
|
|
$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
627
|
|
|
|
628
|
|
|
// Validate inputs/user. |
629
|
|
|
if (empty($theme)) |
630
|
|
|
Errors::instance()->fatal_lang_error('no_theme', false); |
631
|
|
|
|
632
|
|
|
// Select the best fitting tab. |
633
|
|
|
$context[$context['admin_menu_name']]['current_subsection'] = 'list'; |
634
|
|
|
loadLanguage('Admin'); |
635
|
|
|
|
636
|
|
|
// Fetch the smiley sets... |
637
|
|
|
$sets = explode(',', 'none,' . $modSettings['smiley_sets_known']); |
638
|
|
|
$set_names = explode("\n", $txt['smileys_none'] . "\n" . $modSettings['smiley_sets_names']); |
639
|
|
|
$context['smiley_sets'] = array( |
640
|
|
|
'' => $txt['smileys_no_default'] |
641
|
|
|
); |
642
|
|
|
foreach ($sets as $i => $set) |
643
|
|
|
$context['smiley_sets'][$set] = htmlspecialchars($set_names[$i], ENT_COMPAT, 'UTF-8'); |
644
|
|
|
|
645
|
|
|
$old_id = $settings['theme_id']; |
646
|
|
|
$old_settings = $settings; |
647
|
|
|
|
648
|
|
|
loadTheme($theme, false); |
649
|
|
|
|
650
|
|
|
// Also load the actual themes language file - in case of special settings. |
651
|
|
|
loadLanguage('Settings', '', true, true); |
652
|
|
|
|
653
|
|
|
// And the custom language strings... |
654
|
|
|
loadLanguage('ThemeStrings', '', false, true); |
655
|
|
|
|
656
|
|
|
// Let the theme take care of the settings. |
657
|
|
|
loadTemplate('Settings'); |
658
|
|
|
loadSubTemplate('settings'); |
659
|
|
|
|
660
|
|
|
// Load the variants separately... |
661
|
|
|
$settings['theme_variants'] = array(); |
662
|
|
|
if (file_exists($settings['theme_dir'] . '/index.template.php')) |
663
|
|
|
{ |
664
|
|
|
$file_contents = implode("\n", file($settings['theme_dir'] . '/index.template.php')); |
665
|
|
|
if (preg_match('~\'theme_variants\'\s*=>(.+?\)),$~sm', $file_contents, $matches)) |
666
|
|
|
eval('global $settings; $settings[\'theme_variants\'] = ' . $matches[1] . ';'); |
|
|
|
|
667
|
|
|
|
668
|
|
|
call_integration_hook('integrate_init_theme', array($theme, &$settings)); |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
// Submitting! |
672
|
|
|
if (isset($this->_req->post->save)) |
673
|
|
|
{ |
674
|
|
|
// Allowed? |
675
|
|
|
checkSession(); |
676
|
|
|
validateToken('admin-sts'); |
677
|
|
|
|
678
|
|
|
$options = array(); |
679
|
|
|
$options['options'] = empty($this->_req->post->options) ? array() : (array) $this->_req->post->options; |
680
|
|
|
$options['default_options'] = empty($this->_req->post->default_options) ? array() : (array) $this->_req->post->default_options; |
681
|
|
|
|
682
|
|
|
// Make sure items are cast correctly. |
683
|
|
|
foreach ($context['theme_settings'] as $item) |
684
|
|
|
{ |
685
|
|
|
// Unwatch this item if this is just a separator. |
686
|
|
|
if (!is_array($item)) |
687
|
|
|
continue; |
688
|
|
|
|
689
|
|
|
// Clean them up for the database |
690
|
|
|
foreach (array('options', 'default_options') as $option) |
691
|
|
|
{ |
692
|
|
|
if (!isset($options[$option][$item['id']])) |
693
|
|
|
continue; |
694
|
|
|
// Checkbox. |
695
|
|
|
elseif (empty($item['type'])) |
696
|
|
|
$options[$option][$item['id']] = $options[$option][$item['id']] ? 1 : 0; |
697
|
|
|
// Number |
698
|
|
|
elseif ($item['type'] == 'number') |
699
|
|
|
$options[$option][$item['id']] = (int) $options[$option][$item['id']]; |
700
|
|
|
} |
701
|
|
|
} |
702
|
|
|
|
703
|
|
|
// Set up the sql query. |
704
|
|
|
$inserts = array(); |
705
|
|
|
foreach ($options['options'] as $opt => $val) |
706
|
|
|
$inserts[] = array($theme, 0, $opt, is_array($val) ? implode(',', $val) : $val); |
707
|
|
|
|
708
|
|
|
foreach ($options['default_options'] as $opt => $val) |
709
|
|
|
$inserts[] = array(1, 0, $opt, is_array($val) ? implode(',', $val) : $val); |
710
|
|
|
|
711
|
|
|
// If we're actually inserting something.. |
712
|
|
|
if (!empty($inserts)) |
713
|
|
|
updateThemeOptions($inserts); |
714
|
|
|
|
715
|
|
|
// Clear and Invalidate the cache. |
716
|
|
|
Cache::instance()->remove('theme_settings-' . $theme); |
717
|
|
|
Cache::instance()->remove('theme_settings-1'); |
718
|
|
|
updateSettings(array('settings_updated' => time())); |
719
|
|
|
|
720
|
|
|
redirectexit('action=admin;area=theme;sa=list;th=' . $theme . ';' . $context['session_var'] . '=' . $context['session_id']); |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
$context['sub_template'] = 'set_settings'; |
724
|
|
|
$context['page_title'] = $txt['theme_settings']; |
725
|
|
|
|
726
|
|
|
foreach ($settings as $setting => $dummy) |
727
|
|
|
{ |
728
|
|
|
if (!in_array($setting, array('theme_url', 'theme_dir', 'images_url', 'template_dirs'))) |
729
|
|
|
$settings[$setting] = htmlspecialchars__recursive($settings[$setting]); |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
$context['settings'] = $context['theme_settings']; |
733
|
|
|
$context['theme_settings'] = $settings; |
734
|
|
|
|
735
|
|
|
foreach ($context['settings'] as $i => $setting) |
736
|
|
|
{ |
737
|
|
|
// Separators are dummies, so leave them alone. |
738
|
|
|
if (!is_array($setting)) |
739
|
|
|
continue; |
740
|
|
|
|
741
|
|
|
// Create the right input fields for the data |
742
|
|
|
if (!isset($setting['type']) || $setting['type'] == 'bool') |
743
|
|
|
$context['settings'][$i]['type'] = 'checkbox'; |
744
|
|
|
elseif ($setting['type'] == 'int' || $setting['type'] == 'integer') |
745
|
|
|
$context['settings'][$i]['type'] = 'number'; |
746
|
|
|
elseif ($setting['type'] == 'string') |
747
|
|
|
$context['settings'][$i]['type'] = 'text'; |
748
|
|
|
|
749
|
|
|
if (isset($setting['options'])) |
750
|
|
|
$context['settings'][$i]['type'] = 'list'; |
751
|
|
|
|
752
|
|
|
$context['settings'][$i]['value'] = !isset($settings[$setting['id']]) ? '' : $settings[$setting['id']]; |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
// Do we support variants? |
756
|
|
|
if (!empty($settings['theme_variants'])) |
757
|
|
|
{ |
758
|
|
|
$context['theme_variants'] = array(); |
759
|
|
|
foreach ($settings['theme_variants'] as $variant) |
|
|
|
|
760
|
|
|
{ |
761
|
|
|
// Have any text, old chap? |
762
|
|
|
$context['theme_variants'][$variant] = array( |
763
|
|
|
'label' => isset($txt['variant_' . $variant]) ? $txt['variant_' . $variant] : $variant, |
764
|
|
|
'thumbnail' => !file_exists($settings['theme_dir'] . '/images/thumbnail.png') || file_exists($settings['theme_dir'] . '/images/thumbnail_' . $variant . '.png') ? $settings['images_url'] . '/thumbnail_' . $variant . '.png' : ($settings['images_url'] . '/thumbnail.png'), |
765
|
|
|
); |
766
|
|
|
} |
767
|
|
|
$context['default_variant'] = !empty($settings['default_variant']) && isset($context['theme_variants'][$settings['default_variant']]) ? $settings['default_variant'] : $settings['theme_variants'][0]; |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
// Restore the current theme. |
771
|
|
|
loadTheme($old_id, false); |
772
|
|
|
|
773
|
|
|
$settings = $old_settings; |
774
|
|
|
|
775
|
|
|
// Reinit just incase. |
776
|
|
|
if (function_exists('template_init')) |
777
|
|
|
$settings += template_init(); |
778
|
|
|
|
779
|
|
|
loadTemplate('ManageThemes'); |
780
|
|
|
|
781
|
|
|
// We like Kenny better than Token. |
782
|
|
|
createToken('admin-sts'); |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Remove a theme from the database. |
787
|
|
|
* |
788
|
|
|
* What it does: |
789
|
|
|
* - Removes an installed theme. |
790
|
|
|
* - Requires an administrator. |
791
|
|
|
* - Accessed with ?action=admin;area=theme;sa=remove. |
792
|
|
|
*/ |
793
|
|
|
public function action_remove() |
794
|
|
|
{ |
795
|
|
|
global $modSettings, $context; |
796
|
|
|
|
797
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
798
|
|
|
|
799
|
|
|
checkSession('get'); |
800
|
|
|
validateToken('admin-tr', 'request'); |
801
|
|
|
|
802
|
|
|
// The theme's ID must be an integer. |
803
|
|
|
$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
804
|
|
|
|
805
|
|
|
// You can't delete the default theme! |
806
|
|
|
if ($theme == 1) |
807
|
|
|
Errors::instance()->fatal_lang_error('no_access', false); |
808
|
|
|
|
809
|
|
|
// Its no longer known |
810
|
|
|
$known = explode(',', $modSettings['knownThemes']); |
811
|
|
|
for ($i = 0, $n = count($known); $i < $n; $i++) |
812
|
|
|
{ |
813
|
|
|
if ($known[$i] == $theme) |
814
|
|
|
unset($known[$i]); |
815
|
|
|
} |
816
|
|
|
$known = strtr(implode(',', $known), array(',,' => ',')); |
817
|
|
|
|
818
|
|
|
// Remove it as an option everywhere |
819
|
|
|
deleteTheme($theme); |
820
|
|
|
|
821
|
|
|
// Fix it if the theme was the overall default theme. |
822
|
|
|
if ($modSettings['theme_guests'] == $theme) |
823
|
|
|
updateSettings(array('theme_guests' => '1', 'knownThemes' => $known)); |
824
|
|
|
else |
825
|
|
|
updateSettings(array('knownThemes' => $known)); |
826
|
|
|
|
827
|
|
|
redirectexit('action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id']); |
828
|
|
|
} |
829
|
|
|
|
830
|
|
|
/** |
831
|
|
|
* Remove a theme from the database in response to an ajax api request |
832
|
|
|
* |
833
|
|
|
* What it does: |
834
|
|
|
* - Removes an installed theme. |
835
|
|
|
* - Requires an administrator. |
836
|
|
|
* - Accessed with ?action=admin;area=theme;sa=remove;api |
837
|
|
|
*/ |
838
|
|
|
public function action_remove_api() |
839
|
|
|
{ |
840
|
|
|
global $modSettings, $context, $txt; |
841
|
|
|
|
842
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
843
|
|
|
|
844
|
|
|
// Validate what was sent |
845
|
|
|
if (checkSession('get', '', false)) |
846
|
|
|
{ |
847
|
|
|
loadLanguage('Errors'); |
848
|
|
|
$context['xml_data'] = array( |
849
|
|
|
'error' => 1, |
850
|
|
|
'text' => $txt['session_verify_fail'], |
851
|
|
|
); |
852
|
|
|
|
853
|
|
|
return; |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
// Not just any John Smith can send in a api request |
857
|
|
|
if (!allowedTo('admin_forum')) |
858
|
|
|
{ |
859
|
|
|
loadLanguage('Errors'); |
860
|
|
|
$context['xml_data'] = array( |
861
|
|
|
'error' => 1, |
862
|
|
|
'text' => $txt['cannot_admin_forum'], |
863
|
|
|
); |
864
|
|
|
return; |
865
|
|
|
} |
866
|
|
|
|
867
|
|
|
// Even if you are John Smith, you still need a ticket |
868
|
|
|
if (!validateToken('admin-tr', 'request', true, false)) |
869
|
|
|
{ |
870
|
|
|
loadLanguage('Errors'); |
871
|
|
|
$context['xml_data'] = array( |
872
|
|
|
'error' => 1, |
873
|
|
|
'text' => $txt['token_verify_fail'], |
874
|
|
|
); |
875
|
|
|
|
876
|
|
|
return; |
877
|
|
|
} |
878
|
|
|
|
879
|
|
|
// The theme's ID must be an integer. |
880
|
|
|
$theme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
881
|
|
|
|
882
|
|
|
// You can't delete the default theme! |
883
|
|
|
if ($theme == 1) |
884
|
|
|
{ |
885
|
|
|
loadLanguage('Errors'); |
886
|
|
|
$context['xml_data'] = array( |
887
|
|
|
'error' => 1, |
888
|
|
|
'text' => $txt['no_access'], |
889
|
|
|
); |
890
|
|
|
return; |
891
|
|
|
} |
892
|
|
|
|
893
|
|
|
// It is a theme we know about? |
894
|
|
|
$known = explode(',', $modSettings['knownThemes']); |
895
|
|
|
for ($i = 0, $n = count($known); $i < $n; $i++) |
896
|
|
|
{ |
897
|
|
|
if ($known[$i] == $theme) |
898
|
|
|
unset($known[$i]); |
899
|
|
|
} |
900
|
|
|
|
901
|
|
|
// Finally, remove it |
902
|
|
|
deleteTheme($theme); |
903
|
|
|
|
904
|
|
|
$known = strtr(implode(',', $known), array(',,' => ',')); |
905
|
|
|
|
906
|
|
|
// Fix it if the theme was the overall default theme. |
907
|
|
|
if ($modSettings['theme_guests'] == $theme) |
908
|
|
|
updateSettings(array('theme_guests' => '1', 'knownThemes' => $known)); |
909
|
|
|
else |
910
|
|
|
updateSettings(array('knownThemes' => $known)); |
911
|
|
|
|
912
|
|
|
// Let them know it worked, all without a page refresh |
913
|
|
|
createToken('admin-tr', 'request'); |
914
|
|
|
$context['xml_data'] = array( |
915
|
|
|
'success' => 1, |
916
|
|
|
'token_var' => $context['admin-tr_token_var'], |
917
|
|
|
'token' => $context['admin-tr_token'], |
918
|
|
|
); |
919
|
|
|
} |
920
|
|
|
|
921
|
|
|
/** |
922
|
|
|
* Choose a theme from a list. |
923
|
|
|
* Allows a user or administrator to pick a new theme with an interface. |
924
|
|
|
* |
925
|
|
|
* What it does: |
926
|
|
|
* - Can edit everyone's (u = 0), guests' (u = -1), or a specific user's. |
927
|
|
|
* - Uses the Themes template. (pick sub template.) |
928
|
|
|
* - Accessed with ?action=admin;area=theme;sa=pick. |
929
|
|
|
* |
930
|
|
|
* @uses Profile language text |
931
|
|
|
* @uses ManageThemes template |
932
|
|
|
* @todo thought so... Might be better to split this file in ManageThemes and Themes, |
933
|
|
|
* with centralized admin permissions on ManageThemes. |
934
|
|
|
*/ |
935
|
|
|
public function action_pick() |
|
|
|
|
936
|
|
|
{ |
937
|
|
|
global $txt, $context, $modSettings, $user_info, $scripturl, $settings; |
938
|
|
|
|
939
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
940
|
|
|
|
941
|
|
|
if (!$modSettings['theme_allow'] && $settings['disable_user_variant'] && !allowedTo('admin_forum')) |
942
|
|
|
Errors::instance()->fatal_lang_error('no_access', false); |
943
|
|
|
|
944
|
|
|
loadLanguage('Profile'); |
945
|
|
|
loadTemplate('ManageThemes'); |
946
|
|
|
|
947
|
|
|
// Build the link tree. |
948
|
|
|
$context['linktree'][] = array( |
949
|
|
|
'url' => $scripturl . '?action=theme;sa=pick;u=' . (!empty($this->_req->query->u) ? (int) $this->_req->query->u : 0), |
950
|
|
|
'name' => $txt['theme_pick'], |
951
|
|
|
); |
952
|
|
|
$context['default_theme_id'] = $modSettings['theme_default']; |
953
|
|
|
|
954
|
|
|
$_SESSION['id_theme'] = 0; |
955
|
|
|
|
956
|
|
|
if (isset($this->_req->query->id)) |
957
|
|
|
$this->_req->query->th = $this->_req->query->id; |
958
|
|
|
|
959
|
|
|
// Saving a variant cause JS doesn't work - pretend it did ;) |
960
|
|
|
if (isset($this->_req->post->save)) |
961
|
|
|
{ |
962
|
|
|
// Which theme? |
963
|
|
|
foreach ($this->_req->post->save as $k => $v) |
964
|
|
|
$this->_req->query->th = (int) $k; |
965
|
|
|
|
966
|
|
|
if (isset($this->_req->post->vrt[$k])) |
967
|
|
|
$this->_req->query->vrt = $this->_req->post->vrt[$k]; |
|
|
|
|
968
|
|
|
} |
969
|
|
|
|
970
|
|
|
// Have we made a decision, or are we just browsing? |
971
|
|
|
if (isset($this->_req->query->th)) |
972
|
|
|
{ |
973
|
|
|
checkSession('get'); |
974
|
|
|
|
975
|
|
|
$th = $this->_req->getQuery('th', 'intval'); |
976
|
|
|
$vrt = $this->_req->getQuery('vrt', 'cleanhtml'); |
977
|
|
|
$u = $this->_req->getQuery('u', 'intval'); |
978
|
|
|
|
979
|
|
|
// Save for this user. |
980
|
|
|
if (!isset($u) || !allowedTo('admin_forum')) |
981
|
|
|
{ |
982
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
983
|
|
|
updateMemberData($user_info['id'], array('id_theme' => $th)); |
984
|
|
|
|
985
|
|
|
// A variants to save for the user? |
986
|
|
|
if (!empty($vrt)) |
987
|
|
|
{ |
988
|
|
|
updateThemeOptions(array($th, $user_info['id'], 'theme_variant', $vrt)); |
989
|
|
|
|
990
|
|
|
Cache::instance()->remove('theme_settings-' . $th . ':' . $user_info['id']); |
991
|
|
|
|
992
|
|
|
$_SESSION['id_variant'] = 0; |
993
|
|
|
} |
994
|
|
|
|
995
|
|
|
redirectexit('action=profile;area=theme'); |
996
|
|
|
} |
997
|
|
|
|
998
|
|
|
// If changing members or guests - and there's a variant - assume changing default variant. |
999
|
|
|
if (!empty($vrt) && ($u === 0 || $u === -1)) |
1000
|
|
|
{ |
1001
|
|
|
updateThemeOptions(array($th, 0, 'default_variant', $vrt)); |
1002
|
|
|
|
1003
|
|
|
// Make it obvious that it's changed |
1004
|
|
|
Cache::instance()->remove('theme_settings-' . $th); |
1005
|
|
|
} |
1006
|
|
|
|
1007
|
|
|
// For everyone. |
1008
|
|
|
if ($u === 0) |
1009
|
|
|
{ |
1010
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1011
|
|
|
updateMemberData(null, array('id_theme' => $th)); |
1012
|
|
|
|
1013
|
|
|
// Remove any custom variants. |
1014
|
|
|
if (!empty($vrt)) |
1015
|
|
|
deleteVariants($th); |
1016
|
|
|
|
1017
|
|
|
redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']); |
1018
|
|
|
} |
1019
|
|
|
// Change the default/guest theme. |
1020
|
|
|
elseif ($u === -1) |
1021
|
|
|
{ |
1022
|
|
|
updateSettings(array('theme_guests' => $th)); |
1023
|
|
|
|
1024
|
|
|
redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']); |
1025
|
|
|
} |
1026
|
|
|
// Change a specific member's theme. |
1027
|
|
|
else |
1028
|
|
|
{ |
1029
|
|
|
// The forum's default theme is always 0 and we |
1030
|
|
|
if (isset($th) && $th == 0) |
1031
|
|
|
$th = $modSettings['theme_guests']; |
1032
|
|
|
|
1033
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1034
|
|
|
updateMemberData($u, array('id_theme' => $th)); |
1035
|
|
|
|
1036
|
|
|
if (!empty($vrt)) |
1037
|
|
|
{ |
1038
|
|
|
updateThemeOptions(array($th, $u, 'theme_variant', $vrt)); |
1039
|
|
|
Cache::instance()->remove('theme_settings-' . $th . ':' . $u); |
1040
|
|
|
|
1041
|
|
|
if ($user_info['id'] == $u) |
1042
|
|
|
$_SESSION['id_variant'] = 0; |
1043
|
|
|
} |
1044
|
|
|
|
1045
|
|
|
redirectexit('action=profile;u=' . $u . ';area=theme'); |
1046
|
|
|
} |
1047
|
|
|
} |
1048
|
|
|
|
1049
|
|
|
$u = $this->_req->getQuery('u', 'intval'); |
1050
|
|
|
|
1051
|
|
|
// Figure out who the member of the minute is, and what theme they've chosen. |
1052
|
|
|
if (!isset($u) || !allowedTo('admin_forum')) |
1053
|
|
|
{ |
1054
|
|
|
$context['current_member'] = $user_info['id']; |
1055
|
|
|
$current_theme = $user_info['theme']; |
1056
|
|
|
} |
1057
|
|
|
// Everyone can't chose just one. |
1058
|
|
|
elseif ($u === 0) |
1059
|
|
|
{ |
1060
|
|
|
$context['current_member'] = 0; |
1061
|
|
|
$current_theme = 0; |
1062
|
|
|
} |
1063
|
|
|
// Guests and such... |
1064
|
|
|
elseif ($u === -1) |
1065
|
|
|
{ |
1066
|
|
|
$context['current_member'] = -1; |
1067
|
|
|
$current_theme = $modSettings['theme_guests']; |
1068
|
|
|
} |
1069
|
|
|
// Someones else :P. |
1070
|
|
|
else |
1071
|
|
|
{ |
1072
|
|
|
$context['current_member'] = $u; |
1073
|
|
|
|
1074
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1075
|
|
|
$member = getBasicMemberData($context['current_member']); |
1076
|
|
|
|
1077
|
|
|
$current_theme = $member['id_theme']; |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
// Get the theme name and descriptions. |
1081
|
|
|
list ($context['available_themes'], $guest_theme) = availableThemes($current_theme, $context['current_member']); |
1082
|
|
|
|
1083
|
|
|
// As long as we're not doing the default theme... |
1084
|
|
|
if (!isset($u) || $u >= 0) |
1085
|
|
|
{ |
1086
|
|
|
if ($guest_theme != 0) |
1087
|
|
|
$context['available_themes'][0] = $context['available_themes'][$guest_theme]; |
1088
|
|
|
|
1089
|
|
|
$context['available_themes'][0]['id'] = 0; |
1090
|
|
|
$context['available_themes'][0]['name'] = $txt['theme_forum_default']; |
1091
|
|
|
$context['available_themes'][0]['selected'] = $current_theme == 0; |
1092
|
|
|
$context['available_themes'][0]['description'] = $txt['theme_global_description']; |
1093
|
|
|
} |
1094
|
|
|
|
1095
|
|
|
ksort($context['available_themes']); |
1096
|
|
|
|
1097
|
|
|
$context['page_title'] = $txt['theme_pick']; |
1098
|
|
|
$context['sub_template'] = 'pick'; |
1099
|
|
|
} |
1100
|
|
|
|
1101
|
|
|
/** |
1102
|
|
|
* Installs new themes, either from a gzip or copy of the default. |
1103
|
|
|
* |
1104
|
|
|
* What it does: |
1105
|
|
|
* - Puts themes in $boardurl/themes. |
1106
|
|
|
* - Assumes the gzip has a root directory in it. (ie default.) |
1107
|
|
|
* - Requires admin_forum. |
1108
|
|
|
* - Accessed with ?action=admin;area=theme;sa=install. |
1109
|
|
|
* |
1110
|
|
|
* @uses ManageThemes template |
1111
|
|
|
*/ |
1112
|
|
|
public function action_install() |
|
|
|
|
1113
|
|
|
{ |
1114
|
|
|
global $boardurl, $txt, $context, $settings, $modSettings; |
1115
|
|
|
|
1116
|
|
|
checkSession('request'); |
1117
|
|
|
|
1118
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
1119
|
|
|
require_once(SUBSDIR . '/Package.subs.php'); |
1120
|
|
|
|
1121
|
|
|
loadTemplate('ManageThemes'); |
1122
|
|
|
|
1123
|
|
|
// Passed an ID, then the install is complete, lets redirect and show them |
1124
|
|
|
if (isset($this->_req->query->theme_id)) |
1125
|
|
|
{ |
1126
|
|
|
$this->_req->query->theme_id = (int) $this->_req->query->theme_id; |
1127
|
|
|
|
1128
|
|
|
$context['sub_template'] = 'installed'; |
1129
|
|
|
$context['page_title'] = $txt['theme_installed']; |
1130
|
|
|
$context['installed_theme'] = array( |
1131
|
|
|
'id' => $this->_req->query->theme_id, |
1132
|
|
|
'name' => getThemeName($this->_req->query->theme_id), |
1133
|
|
|
); |
1134
|
|
|
|
1135
|
|
|
return; |
1136
|
|
|
} |
1137
|
|
|
|
1138
|
|
|
// How are we going to install this theme, from a dir, zip, copy of default? |
1139
|
|
|
if ((!empty($_FILES['theme_gz']) && (!isset($_FILES['theme_gz']['error']) || $_FILES['theme_gz']['error'] != 4)) || !empty($this->_req->query->theme_gz)) |
1140
|
|
|
$method = 'upload'; |
1141
|
|
|
elseif (isset($this->_req->query->theme_dir) && rtrim(realpath($this->_req->query->theme_dir), '/\\') != realpath(BOARDDIR . '/themes') && file_exists($this->_req->query->theme_dir)) |
1142
|
|
|
$method = 'path'; |
1143
|
|
|
else |
1144
|
|
|
$method = 'copy'; |
1145
|
|
|
|
1146
|
|
|
// Copy the default theme? |
1147
|
|
|
if (!empty($this->_req->post->copy) && $method == 'copy') |
1148
|
|
|
$this->copyDefault(); |
1149
|
|
|
// Install from another directory |
1150
|
|
|
elseif (isset($this->_req->post->theme_dir) && $method == 'path') |
1151
|
|
|
$this->installFromDir(); |
1152
|
|
|
// Uploaded a zip file to install from |
1153
|
|
|
elseif ($method == 'upload') |
1154
|
|
|
$this->InstallFromZip(); |
1155
|
|
|
else |
1156
|
|
|
Errors::instance()->fatal_lang_error('theme_install_general', false); |
1157
|
|
|
|
1158
|
|
|
// Something go wrong? |
1159
|
|
|
if ($this->theme_dir != '' && basename($this->theme_dir) != 'themes') |
1160
|
|
|
{ |
1161
|
|
|
// Defaults. |
1162
|
|
|
$install_info = array( |
1163
|
|
|
'theme_url' => $boardurl . '/themes/' . basename($this->theme_dir), |
1164
|
|
|
'images_url' => isset($this->images_url) ? $this->images_url : $boardurl . '/themes/' . basename($this->theme_dir) . '/images', |
1165
|
|
|
'theme_dir' => $this->theme_dir, |
1166
|
|
|
'name' => $this->theme_name |
1167
|
|
|
); |
1168
|
|
|
$explicit_images = false; |
1169
|
|
|
|
1170
|
|
|
if (file_exists($this->theme_dir . '/theme_info.xml')) |
1171
|
|
|
{ |
1172
|
|
|
$theme_info = file_get_contents($this->theme_dir . '/theme_info.xml'); |
1173
|
|
|
|
1174
|
|
|
// Parse theme-info.xml into an Xml_Array. |
1175
|
|
|
$theme_info_xml = new Xml_Array($theme_info); |
1176
|
|
|
|
1177
|
|
|
// @todo Error message of some sort? |
1178
|
|
|
if (!$theme_info_xml->exists('theme-info[0]')) |
1179
|
|
|
return 'package_get_error_packageinfo_corrupt'; |
1180
|
|
|
|
1181
|
|
|
$theme_info_xml = $theme_info_xml->path('theme-info[0]'); |
1182
|
|
|
$theme_info_xml = $theme_info_xml->to_array(); |
1183
|
|
|
|
1184
|
|
|
$xml_elements = array( |
1185
|
|
|
'name' => 'name', |
1186
|
|
|
'theme_layers' => 'layers', |
1187
|
|
|
'theme_templates' => 'templates', |
1188
|
|
|
'based_on' => 'based-on', |
1189
|
|
|
); |
1190
|
|
|
foreach ($xml_elements as $var => $name) |
1191
|
|
|
{ |
1192
|
|
|
if (!empty($theme_info_xml[$name])) |
1193
|
|
|
$install_info[$var] = $theme_info_xml[$name]; |
1194
|
|
|
} |
1195
|
|
|
|
1196
|
|
|
if (!empty($theme_info_xml['images'])) |
1197
|
|
|
{ |
1198
|
|
|
$install_info['images_url'] = $install_info['theme_url'] . '/' . $theme_info_xml['images']; |
1199
|
|
|
$explicit_images = true; |
1200
|
|
|
} |
1201
|
|
|
|
1202
|
|
|
if (!empty($theme_info_xml['extra'])) |
1203
|
|
|
$install_info += unserialize($theme_info_xml['extra']); |
|
|
|
|
1204
|
|
|
} |
1205
|
|
|
|
1206
|
|
|
if (isset($install_info['based_on'])) |
1207
|
|
|
{ |
1208
|
|
|
if ($install_info['based_on'] == 'default') |
1209
|
|
|
{ |
1210
|
|
|
$install_info['theme_url'] = $settings['default_theme_url']; |
1211
|
|
|
$install_info['images_url'] = $settings['default_images_url']; |
1212
|
|
|
} |
1213
|
|
|
elseif ($install_info['based_on'] != '') |
1214
|
|
|
{ |
1215
|
|
|
$install_info['based_on'] = preg_replace('~[^A-Za-z0-9\-_ ]~', '', $install_info['based_on']); |
1216
|
|
|
|
1217
|
|
|
$temp = loadBasedOnTheme($install_info['based_on'], $explicit_images); |
1218
|
|
|
|
1219
|
|
|
// @todo An error otherwise? |
1220
|
|
|
if (is_array($temp)) |
1221
|
|
|
{ |
1222
|
|
|
$install_info = $temp + $install_info; |
1223
|
|
|
|
1224
|
|
|
if (empty($explicit_images) && !empty($install_info['base_theme_url'])) |
1225
|
|
|
$install_info['theme_url'] = $install_info['base_theme_url']; |
1226
|
|
|
} |
1227
|
|
|
} |
1228
|
|
|
|
1229
|
|
|
unset($install_info['based_on']); |
1230
|
|
|
} |
1231
|
|
|
|
1232
|
|
|
// Find the newest id_theme. |
1233
|
|
|
$id_theme = nextTheme(); |
1234
|
|
|
|
1235
|
|
|
$inserts = array(); |
1236
|
|
|
foreach ($install_info as $var => $val) |
1237
|
|
|
$inserts[] = array($id_theme, $var, $val); |
1238
|
|
|
|
1239
|
|
|
if (!empty($inserts)) |
1240
|
|
|
addTheme($inserts); |
1241
|
|
|
|
1242
|
|
|
updateSettings(array('knownThemes' => strtr($modSettings['knownThemes'] . ',' . $id_theme, array(',,' => ',')))); |
1243
|
|
|
} |
1244
|
|
|
|
1245
|
|
|
redirectexit('action=admin;area=theme;sa=install;theme_id=' . $id_theme . ';' . $context['session_var'] . '=' . $context['session_id']); |
|
|
|
|
1246
|
|
|
} |
1247
|
|
|
|
1248
|
|
|
/** |
1249
|
|
|
* Install a new theme from an uploaded zip archive |
1250
|
|
|
*/ |
1251
|
|
|
public function installFromZip() |
|
|
|
|
1252
|
|
|
{ |
1253
|
|
|
global $context; |
1254
|
|
|
|
1255
|
|
|
// Hopefully the themes directory is writable, or we might have a problem. |
1256
|
|
|
if (!is_writable(BOARDDIR . '/themes')) |
1257
|
|
|
Errors::instance()->fatal_lang_error('theme_install_write_error', 'critical'); |
1258
|
|
|
|
1259
|
|
|
// This happens when the admin session is gone and the user has to login again |
1260
|
|
|
if (empty($_FILES['theme_gz']) && empty($this->_req->post->theme_gz)) |
1261
|
|
|
redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']); |
1262
|
|
|
|
1263
|
|
|
// Set the default settings... |
1264
|
|
|
$this->theme_name = strtok(basename(isset($_FILES['theme_gz']) ? $_FILES['theme_gz']['name'] : $this->_req->post->theme_gz), '.'); |
1265
|
|
|
$this->theme_name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $this->theme_name); |
1266
|
|
|
$this->theme_dir = BOARDDIR . '/themes/' . $this->theme_name; |
1267
|
|
|
|
1268
|
|
|
if (isset($_FILES['theme_gz']) && is_uploaded_file($_FILES['theme_gz']['tmp_name']) && (ini_get('open_basedir') != '' || file_exists($_FILES['theme_gz']['tmp_name']))) |
1269
|
|
|
read_tgz_file($_FILES['theme_gz']['tmp_name'], BOARDDIR . '/themes/' . $this->theme_name, false, true); |
1270
|
|
|
elseif (isset($this->_req->post->theme_gz)) |
1271
|
|
|
{ |
1272
|
|
|
if (!isAuthorizedServer($this->_req->post->theme_gz)) |
1273
|
|
|
Errors::instance()->fatal_lang_error('not_valid_server'); |
1274
|
|
|
|
1275
|
|
|
read_tgz_file($this->_req->post->theme_gz, BOARDDIR . '/themes/' . $this->theme_name, false, true); |
1276
|
|
|
} |
1277
|
|
|
else |
1278
|
|
|
redirectexit('action=admin;area=theme;sa=admin;' . $context['session_var'] . '=' . $context['session_id']); |
1279
|
|
|
} |
1280
|
|
|
|
1281
|
|
|
/** |
1282
|
|
|
* Install a theme from a directory on the server |
1283
|
|
|
* |
1284
|
|
|
* - Expects the directory is properly loaded with theme files |
1285
|
|
|
*/ |
1286
|
|
|
public function installFromDir() |
1287
|
|
|
{ |
1288
|
|
|
if (!is_dir($this->_req->post->theme_dir) || !file_exists($this->_req->post->theme_dir . '/theme_info.xml')) |
1289
|
|
|
Errors::instance()->fatal_lang_error('theme_install_error', false); |
1290
|
|
|
|
1291
|
|
|
$this->theme_name = basename($this->_req->post->theme_dir); |
1292
|
|
|
$this->theme_dir = $this->_req->post->theme_dir; |
1293
|
|
|
} |
1294
|
|
|
|
1295
|
|
|
/** |
1296
|
|
|
* Make a copy of the default theme in a new directory |
1297
|
|
|
*/ |
1298
|
|
|
public function copyDefault() |
1299
|
|
|
{ |
1300
|
|
|
global $boardurl, $modSettings, $settings; |
1301
|
|
|
|
1302
|
|
|
// Hopefully the themes directory is writable, or we might have a problem. |
1303
|
|
|
if (!is_writable(BOARDDIR . '/themes')) |
1304
|
|
|
Errors::instance()->fatal_lang_error('theme_install_write_error', 'critical'); |
1305
|
|
|
|
1306
|
|
|
// Make the new directory, standard characters only |
1307
|
|
|
$this->theme_dir = BOARDDIR . '/themes/' . preg_replace('~[^A-Za-z0-9_\- ]~', '', $this->_req->post->copy); |
1308
|
|
|
umask(0); |
1309
|
|
|
mkdir($this->theme_dir, 0777); |
1310
|
|
|
|
1311
|
|
|
// Get some more time if we can |
1312
|
|
|
setTimeLimit(600); |
1313
|
|
|
|
1314
|
|
|
// Create the subdirectories for css, javascript and font files. |
1315
|
|
|
mkdir($this->theme_dir . '/css', 0777); |
1316
|
|
|
mkdir($this->theme_dir . '/scripts', 0777); |
1317
|
|
|
mkdir($this->theme_dir . '/webfonts', 0777); |
1318
|
|
|
|
1319
|
|
|
// Copy over the default non-theme files. |
1320
|
|
|
$to_copy = array('/index.php', '/index.template.php', '/scripts/theme.js'); |
1321
|
|
|
foreach ($to_copy as $file) |
1322
|
|
|
{ |
1323
|
|
|
copy($settings['default_theme_dir'] . $file, $this->theme_dir . $file); |
1324
|
|
|
@chmod($this->theme_dir . $file, 0777); |
|
|
|
|
1325
|
|
|
} |
1326
|
|
|
|
1327
|
|
|
// And now the entire css, images and webfonts directories! |
1328
|
|
|
copytree($settings['default_theme_dir'] . '/css', $this->theme_dir . '/css'); |
1329
|
|
|
copytree($settings['default_theme_dir'] . '/images', $this->theme_dir . '/images'); |
1330
|
|
|
copytree($settings['default_theme_dir'] . '/webfonts', $this->theme_dir . '/webfonts'); |
1331
|
|
|
package_flush_cache(); |
1332
|
|
|
|
1333
|
|
|
$this->theme_name = $this->_req->post->copy; |
1334
|
|
|
$this->images_url = $boardurl . '/themes/' . basename($this->theme_dir) . '/images'; |
1335
|
|
|
$this->theme_dir = realpath($this->theme_dir); |
1336
|
|
|
|
1337
|
|
|
// Lets get some data for the new theme (default theme (1), default settings (0)). |
|
|
|
|
1338
|
|
|
$theme_values = loadThemeOptionsInto(1, 0, array(), array('theme_templates', 'theme_layers')); |
1339
|
|
|
|
1340
|
|
|
// Lets add a theme_info.xml to this theme. |
1341
|
|
|
write_theme_info($this->_req->query->copy, $modSettings['elkVersion'], $this->theme_dir, $theme_values); |
1342
|
|
|
} |
1343
|
|
|
|
1344
|
|
|
/** |
1345
|
|
|
* Set a theme option via javascript. |
1346
|
|
|
* |
1347
|
|
|
* What it does: |
1348
|
|
|
* - sets a theme option without outputting anything. |
1349
|
|
|
* - can be used with javascript, via a dummy image... (which doesn't require |
1350
|
|
|
* the page to reload.) |
1351
|
|
|
* - requires someone who is logged in. |
1352
|
|
|
* - accessed via ?action=jsoption;var=variable;val=value;session_var=sess_id. |
1353
|
|
|
* - optionally contains &th=theme id |
1354
|
|
|
* - does not log access to the Who's Online log. (in index.php..) |
1355
|
|
|
*/ |
1356
|
|
|
public function action_jsoption() |
1357
|
|
|
{ |
1358
|
|
|
global $settings, $user_info, $options, $modSettings; |
1359
|
|
|
|
1360
|
|
|
// Check the session id. |
1361
|
|
|
checkSession('get'); |
1362
|
|
|
|
1363
|
|
|
// This good-for-nothing pixel is being used to keep the session alive. |
1364
|
|
|
if (empty($this->_req->query->var) || !isset($this->_req->query->val)) |
1365
|
|
|
redirectexit($settings['images_url'] . '/blank.png'); |
1366
|
|
|
|
1367
|
|
|
// Sorry, guests can't go any further than this.. |
1368
|
|
|
if ($user_info['is_guest'] || $user_info['id'] == 0) |
1369
|
|
|
obExit(false); |
1370
|
|
|
|
1371
|
|
|
$reservedVars = array( |
1372
|
|
|
'actual_theme_url', |
1373
|
|
|
'actual_images_url', |
1374
|
|
|
'base_theme_dir', |
1375
|
|
|
'base_theme_url', |
1376
|
|
|
'default_images_url', |
1377
|
|
|
'default_theme_dir', |
1378
|
|
|
'default_theme_url', |
1379
|
|
|
'default_template', |
1380
|
|
|
'images_url', |
1381
|
|
|
'number_recent_posts', |
1382
|
|
|
'smiley_sets_default', |
1383
|
|
|
'theme_dir', |
1384
|
|
|
'theme_id', |
1385
|
|
|
'theme_layers', |
1386
|
|
|
'theme_templates', |
1387
|
|
|
'theme_url', |
1388
|
|
|
'name', |
1389
|
|
|
); |
1390
|
|
|
|
1391
|
|
|
// Can't change reserved vars. |
1392
|
|
|
if (in_array(strtolower($this->_req->query->var), $reservedVars)) |
1393
|
|
|
redirectexit($settings['images_url'] . '/blank.png'); |
1394
|
|
|
|
1395
|
|
|
// Use a specific theme? |
1396
|
|
|
if (isset($this->_req->query->th) || isset($this->_req->query->id)) |
1397
|
|
|
{ |
1398
|
|
|
// Invalidate the current themes cache too. |
1399
|
|
|
Cache::instance()->remove('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id']); |
1400
|
|
|
|
1401
|
|
|
$settings['theme_id'] = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval')); |
1402
|
|
|
} |
1403
|
|
|
|
1404
|
|
|
// If this is the admin preferences the passed value will just be an element of it. |
1405
|
|
|
if ($this->_req->query->var == 'admin_preferences') |
1406
|
|
|
{ |
1407
|
|
|
$options['admin_preferences'] = !empty($options['admin_preferences']) ? unserialize($options['admin_preferences']) : array(); |
1408
|
|
|
|
1409
|
|
|
// New thingy... |
1410
|
|
|
if (isset($this->_req->query->admin_key) && strlen($this->_req->query->admin_key) < 5) |
1411
|
|
|
$options['admin_preferences'][$this->_req->query->admin_key] = $this->_req->query->val; |
1412
|
|
|
|
1413
|
|
|
// Change the value to be something nice, |
1414
|
|
|
$this->_req->query->val = serialize($options['admin_preferences']); |
1415
|
|
|
} |
1416
|
|
|
// If this is the window min/max settings, the passed window name will just be an element of it. |
1417
|
|
|
elseif ($this->_req->query->var == 'minmax_preferences') |
1418
|
|
|
{ |
1419
|
|
|
$options['minmax_preferences'] = !empty($options['minmax_preferences']) ? unserialize($options['minmax_preferences']) : array(); |
1420
|
|
|
|
1421
|
|
|
// New value for them |
1422
|
|
|
if (isset($this->_req->query->minmax_key) && strlen($this->_req->query->minmax_key) < 10) |
1423
|
|
|
$options['minmax_preferences'][$this->_req->query->minmax_key] = $this->_req->query->val; |
1424
|
|
|
|
1425
|
|
|
// Change the value to be something nice, |
1426
|
|
|
$this->_req->query->val = serialize($options['minmax_preferences']); |
1427
|
|
|
} |
1428
|
|
|
|
1429
|
|
|
// Update the option. |
1430
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
1431
|
|
|
updateThemeOptions(array($settings['theme_id'], $user_info['id'], $this->_req->query->var, is_array($this->_req->query->val) ? implode(',', $this->_req->query->val) : $this->_req->query->val)); |
1432
|
|
|
|
1433
|
|
|
Cache::instance()->remove('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id']); |
1434
|
|
|
|
1435
|
|
|
// Don't output anything... |
1436
|
|
|
redirectexit($settings['images_url'] . '/blank.png'); |
1437
|
|
|
} |
1438
|
|
|
|
1439
|
|
|
/** |
1440
|
|
|
* Allows choosing, browsing, and editing a themes files. |
1441
|
|
|
* |
1442
|
|
|
* What it does: |
1443
|
|
|
* - Its subactions handle several features: |
1444
|
|
|
* - edit_template: display and edit a PHP template file |
1445
|
|
|
* - edit_style: display and edit a CSS file |
1446
|
|
|
* - edit_file: display and edit other files in the theme |
1447
|
|
|
* - accessed via ?action=admin;area=theme;sa=edit |
1448
|
|
|
* |
1449
|
|
|
* @uses the ManageThemes template |
1450
|
|
|
*/ |
1451
|
|
|
public function action_edit() |
1452
|
|
|
{ |
1453
|
|
|
global $context; |
1454
|
|
|
|
1455
|
|
|
loadTemplate('ManageThemes'); |
1456
|
|
|
|
1457
|
|
|
// We'll work hard with them themes! |
1458
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
1459
|
|
|
|
1460
|
|
|
$selectedTheme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
1461
|
|
|
|
1462
|
|
|
// Unfortunately we cannot edit an unkwown theme.. redirect. |
1463
|
|
|
if (empty($selectedTheme)) |
1464
|
|
|
redirectexit('action=admin;area=theme;sa=themelist'); |
1465
|
|
|
// You're browsing around, aren't you |
1466
|
|
|
elseif (!isset($this->_req->query->filename) && !isset($this->_req->post->save)) |
1467
|
|
|
redirectexit('action=admin;area=theme;sa=browse;th=' . $selectedTheme); |
1468
|
|
|
|
1469
|
|
|
// We don't have errors. Yet. |
1470
|
|
|
$context['session_error'] = false; |
1471
|
|
|
|
1472
|
|
|
// We're editing a theme file. |
1473
|
|
|
// Get the directory of the theme we are editing. |
1474
|
|
|
$context['theme_id'] = $selectedTheme; |
1475
|
|
|
$this->theme_dir = themeDirectory($context['theme_id']); |
1476
|
|
|
|
1477
|
|
|
$this->prepareThemeEditContext(); |
1478
|
|
|
|
1479
|
|
|
// Saving? |
1480
|
|
|
if (isset($this->_req->post->save)) |
1481
|
|
|
{ |
1482
|
|
|
$this->_action_edit_submit(); |
1483
|
|
|
|
1484
|
|
|
// Now lets get out of here! |
1485
|
|
|
return; |
1486
|
|
|
} |
1487
|
|
|
|
1488
|
|
|
// We're editing .css, .template.php, .{language}.php or others. |
1489
|
|
|
// Note: we're here sending $theme_dir as parameter to action_() |
1490
|
|
|
// controller functions, which isn't cool. To be refactored. |
1491
|
|
|
if (substr($this->_req->query->filename, -4) == '.css') |
1492
|
|
|
$this->_action_edit_style(); |
1493
|
|
|
elseif (substr($this->_req->query->filename, -13) == '.template.php') |
1494
|
|
|
$this->_action_edit_template(); |
1495
|
|
|
else |
1496
|
|
|
$this->_action_edit_file(); |
1497
|
|
|
|
1498
|
|
|
// Create a special token to allow editing of multiple files. |
1499
|
|
|
createToken('admin-te-' . md5($selectedTheme . '-' . $this->_req->query->filename)); |
1500
|
|
|
} |
1501
|
|
|
|
1502
|
|
|
/** |
1503
|
|
|
* Displays for editing in admin panel a css file. |
1504
|
|
|
* |
1505
|
|
|
* This function is forwarded to, from |
1506
|
|
|
* ?action=admin;area=theme;sa=edit |
1507
|
|
|
*/ |
1508
|
|
|
private function _action_edit_style() |
1509
|
|
|
{ |
1510
|
|
|
global $context, $settings; |
1511
|
|
|
|
1512
|
|
|
addJavascriptVar(array( |
1513
|
|
|
'previewData' => '', |
1514
|
|
|
'previewTimeout' => '', |
1515
|
|
|
'refreshPreviewCache' => '', |
1516
|
|
|
'editFilename' => $context['edit_filename'], |
1517
|
|
|
'theme_id' => $settings['theme_id'], |
1518
|
|
|
), true); |
1519
|
|
|
|
1520
|
|
|
// pick the template and send it the file |
1521
|
|
|
$context['sub_template'] = 'edit_style'; |
1522
|
|
|
$context['entire_file'] = htmlspecialchars(strtr(file_get_contents($this->theme_dir . '/' . $this->_req->query->filename), array("\t" => ' ')), ENT_COMPAT, 'UTF-8'); |
1523
|
|
|
} |
1524
|
|
|
|
1525
|
|
|
/** |
1526
|
|
|
* Displays for editing in the admin panel a template file. |
1527
|
|
|
* |
1528
|
|
|
* This function is forwarded to, from |
1529
|
|
|
* ?action=admin;area=theme;sa=edit |
1530
|
|
|
*/ |
1531
|
|
|
private function _action_edit_template() |
1532
|
|
|
{ |
1533
|
|
|
global $context; |
1534
|
|
|
|
1535
|
|
|
// Make sure the sub-template is set |
1536
|
|
|
$context['sub_template'] = 'edit_template'; |
1537
|
|
|
|
1538
|
|
|
// Retrieve the contents of the file |
1539
|
|
|
$file_data = file($this->theme_dir . '/' . $this->_req->query->filename); |
1540
|
|
|
|
1541
|
|
|
// For a PHP template file, we display each function in separate boxes. |
1542
|
|
|
$j = 0; |
1543
|
|
|
$context['file_parts'] = array(array('lines' => 0, 'line' => 1, 'data' => '', 'function' => '')); |
1544
|
|
|
for ($i = 0, $n = count($file_data); $i < $n; $i++) |
1545
|
|
|
{ |
1546
|
|
|
// @todo refactor this so the docblocks are in the function content window |
1547
|
|
|
if (substr($file_data[$i], 0, 9) === 'function ') |
1548
|
|
|
{ |
1549
|
|
|
// Try to format the functions a little nicer... |
1550
|
|
|
$context['file_parts'][$j]['data'] = trim($context['file_parts'][$j]['data']); |
1551
|
|
|
|
1552
|
|
|
if (empty($context['file_parts'][$j]['lines'])) |
1553
|
|
|
unset($context['file_parts'][$j]); |
1554
|
|
|
|
1555
|
|
|
// Start a new function block |
1556
|
|
|
$context['file_parts'][++$j] = array('lines' => 0, 'line' => $i, 'data' => ''); |
1557
|
|
|
} |
1558
|
|
|
|
1559
|
|
|
$context['file_parts'][$j]['lines']++; |
1560
|
|
|
$context['file_parts'][$j]['data'] .= htmlspecialchars(strtr($file_data[$i], array("\t" => ' ')), ENT_COMPAT, 'UTF-8'); |
1561
|
|
|
} |
1562
|
|
|
|
1563
|
|
|
$context['entire_file'] = htmlspecialchars(strtr(implode('', $file_data), array("\t" => ' ')), ENT_COMPAT, 'UTF-8'); |
1564
|
|
|
} |
1565
|
|
|
|
1566
|
|
|
/** |
1567
|
|
|
* Handles editing in admin of other types of files from a theme, |
1568
|
|
|
* except templates and css. |
1569
|
|
|
* |
1570
|
|
|
* This function is forwarded to, from |
1571
|
|
|
* ?action=admin;area=theme;sa=edit |
1572
|
|
|
*/ |
1573
|
|
|
private function _action_edit_file() |
1574
|
|
|
{ |
1575
|
|
|
global $context; |
1576
|
|
|
|
1577
|
|
|
// Simply set the template and the file contents. |
1578
|
|
|
$context['sub_template'] = 'edit_file'; |
1579
|
|
|
$context['entire_file'] = htmlspecialchars(strtr(file_get_contents($this->theme_dir . '/' . $this->_req->query->filename), array("\t" => ' ')), ENT_COMPAT, 'UTF-8'); |
1580
|
|
|
} |
1581
|
|
|
|
1582
|
|
|
/** |
1583
|
|
|
* This function handles submission of a template file. |
1584
|
|
|
* It checks the file for syntax errors, and if it passes, it saves it. |
1585
|
|
|
* |
1586
|
|
|
* This function is forwarded to, from |
1587
|
|
|
* ?action=admin;area=theme;sa=edit |
1588
|
|
|
*/ |
1589
|
|
|
private function _action_edit_submit() |
|
|
|
|
1590
|
|
|
{ |
1591
|
|
|
global $context, $settings, $user_info; |
1592
|
|
|
|
1593
|
|
|
$selectedTheme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
1594
|
|
|
if (empty($selectedTheme)) |
1595
|
|
|
{ |
1596
|
|
|
// This should never be happening. Never I say. But... in case it does :P |
1597
|
|
|
Errors::instance()->fatal_lang_error('theme_edit_missing'); |
1598
|
|
|
} |
1599
|
|
|
|
1600
|
|
|
$theme_dir = themeDirectory($context['theme_id']); |
1601
|
|
|
$file = isset($this->_req->post->entire_file) ? $this->_req->post->entire_file : ''; |
1602
|
|
|
|
1603
|
|
|
// You did submit *something*, didn't you? |
1604
|
|
|
if (empty($file)) |
1605
|
|
|
{ |
1606
|
|
|
// @todo a better error message |
1607
|
|
|
Errors::instance()->fatal_lang_error('theme_edit_missing'); |
1608
|
|
|
} |
1609
|
|
|
|
1610
|
|
|
// Checking PHP syntax on css files is not a most constructive use of processing power :P |
1611
|
|
|
// We need to know what kind of file we have |
1612
|
|
|
$is_php = substr($this->_req->post->filename, -4) == '.php'; |
1613
|
|
|
$is_template = substr($this->_req->post->filename, -13) == '.template.php'; |
1614
|
|
|
$is_css = substr($this->_req->post->filename, -4) == '.css'; |
1615
|
|
|
|
1616
|
|
|
// Check you up |
1617
|
|
|
if (checkSession('post', '', false) == '' && validateToken('admin-te-' . md5($selectedTheme . '-' . $this->_req->post->filename), 'post', false) == true) |
|
|
|
|
1618
|
|
|
{ |
1619
|
|
|
// Consolidate the format in which we received the file contents |
1620
|
|
|
if (is_array($file)) |
1621
|
|
|
$entire_file = implode("\n", $file); |
1622
|
|
|
else |
1623
|
|
|
$entire_file = $file; |
1624
|
|
|
|
1625
|
|
|
// Convert our tabs back to tabs! |
1626
|
|
|
$entire_file = rtrim(strtr($entire_file, array("\r" => '', ' ' => "\t"))); |
1627
|
|
|
|
1628
|
|
|
// Errors? No errors! |
1629
|
|
|
$errors = array(); |
1630
|
|
|
|
1631
|
|
|
// For PHP files, we check the syntax. |
1632
|
|
|
if ($is_php) |
1633
|
|
|
{ |
1634
|
|
|
require_once(SUBSDIR . '/Modlog.subs.php'); |
1635
|
|
|
|
1636
|
|
|
// Since we are running php code, let's track it, but only once in a while. |
1637
|
|
|
if (!recentlyLogged('editing_theme', 60)) |
1638
|
|
|
{ |
1639
|
|
|
logAction('editing_theme', array('member' => $user_info['id']), 'admin'); |
1640
|
|
|
|
1641
|
|
|
// But the email only once every 60 minutes should be fine |
1642
|
|
|
if (!recentlyLogged('editing_theme', 3600)) |
1643
|
|
|
{ |
1644
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
1645
|
|
|
require_once(SUBSDIR . '/Admin.subs.php'); |
1646
|
|
|
|
1647
|
|
|
$theme_info = getBasicThemeInfos($context['theme_id']); |
1648
|
|
|
emailAdmins('editing_theme', array( |
1649
|
|
|
'EDIT_REALNAME' => $user_info['name'], |
1650
|
|
|
'FILE_EDITED' => $this->_req->post->filename, |
1651
|
|
|
'THEME_NAME' => $theme_info[$context['theme_id']], |
1652
|
|
|
)); |
1653
|
|
|
} |
1654
|
|
|
} |
1655
|
|
|
|
1656
|
|
|
$validator = new Data_Validator(); |
1657
|
|
|
$validator->validation_rules(array( |
1658
|
|
|
'entire_file' => 'php_syntax' |
1659
|
|
|
)); |
1660
|
|
|
$validator->validate(array('entire_file' => $entire_file)); |
1661
|
|
|
|
1662
|
|
|
// Retrieve the errors |
1663
|
|
|
$errors = $validator->validation_errors(); |
1664
|
|
|
} |
1665
|
|
|
|
1666
|
|
|
// If successful so far, we'll take the plunge and save this piece of art. |
1667
|
|
|
if (empty($errors)) |
1668
|
|
|
{ |
1669
|
|
|
// Try to save the new file contents |
1670
|
|
|
$fp = fopen($theme_dir . '/' . $this->_req->post->filename, 'w'); |
1671
|
|
|
fwrite($fp, $entire_file); |
1672
|
|
|
fclose($fp); |
1673
|
|
|
|
1674
|
|
|
if (function_exists('opcache_invalidate')) |
1675
|
|
|
opcache_invalidate($theme_dir . '/' . $_REQUEST['filename']); |
1676
|
|
|
|
1677
|
|
|
// We're done here. |
1678
|
|
|
redirectexit('action=admin;area=theme;th=' . $selectedTheme . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=browse;directory=' . dirname($this->_req->post->filename)); |
1679
|
|
|
} |
1680
|
|
|
// I can't let you off the hook yet: syntax errors are a nasty beast. |
1681
|
|
|
else |
1682
|
|
|
{ |
1683
|
|
|
// Pick the right sub-template for the next try |
1684
|
|
|
if ($is_template) |
1685
|
|
|
$context['sub_template'] = 'edit_template'; |
1686
|
|
|
else |
1687
|
|
|
$context['sub_template'] = 'edit_file'; |
1688
|
|
|
|
1689
|
|
|
// Fill contextual data for the template, the errors to show |
1690
|
|
|
foreach ($errors as $error) |
1691
|
|
|
$context['parse_error'][] = $error; |
1692
|
|
|
|
1693
|
|
|
// The format of the data depends on template/non-template file. |
1694
|
|
|
if (!is_array($file)) |
1695
|
|
|
$file = array($file); |
1696
|
|
|
|
1697
|
|
|
// Send back the file contents |
1698
|
|
|
$context['entire_file'] = htmlspecialchars(strtr(implode('', $file), array("\t" => ' ')), ENT_COMPAT, 'UTF-8'); |
1699
|
|
|
|
1700
|
|
|
foreach ($file as $i => $file_part) |
1701
|
|
|
{ |
1702
|
|
|
$context['file_parts'][$i]['lines'] = strlen($file_part); |
1703
|
|
|
$context['file_parts'][$i]['data'] = $file_part; |
1704
|
|
|
} |
1705
|
|
|
|
1706
|
|
|
// Re-create token for another try |
1707
|
|
|
createToken('admin-te-' . md5($selectedTheme . '-' . $this->_req->post->filename)); |
1708
|
|
|
|
1709
|
|
|
return; |
1710
|
|
|
} |
1711
|
|
|
} |
1712
|
|
|
// Session timed out. |
1713
|
|
|
else |
1714
|
|
|
{ |
1715
|
|
|
loadLanguage('Errors'); |
1716
|
|
|
|
1717
|
|
|
// Notify the template of trouble |
1718
|
|
|
$context['session_error'] = true; |
1719
|
|
|
|
1720
|
|
|
// Recycle the submitted data. |
1721
|
|
|
if (is_array($file)) |
1722
|
|
|
$context['entire_file'] = htmlspecialchars(implode("\n", $file), ENT_COMPAT, 'UTF-8'); |
1723
|
|
|
else |
1724
|
|
|
$context['entire_file'] = htmlspecialchars($file, ENT_COMPAT, 'UTF-8'); |
1725
|
|
|
|
1726
|
|
|
$context['edit_filename'] = htmlspecialchars($this->_req->post->filename, ENT_COMPAT, 'UTF-8'); |
1727
|
|
|
|
1728
|
|
|
// Choose sub-template |
1729
|
|
|
if ($is_template) |
1730
|
|
|
$context['sub_template'] = 'edit_template'; |
1731
|
|
|
elseif ($is_css) |
1732
|
|
|
{ |
1733
|
|
|
addJavascriptVar(array( |
1734
|
|
|
'previewData' => '\'\'', |
1735
|
|
|
'previewTimeout' => '\'\'', |
1736
|
|
|
'refreshPreviewCache' => '\'\'', |
1737
|
|
|
'editFilename' => JavaScriptEscape($context['edit_filename']), |
1738
|
|
|
'theme_id' => $settings['theme_id'], |
1739
|
|
|
)); |
1740
|
|
|
$context['sub_template'] = 'edit_style'; |
1741
|
|
|
} |
1742
|
|
|
else |
1743
|
|
|
$context['sub_template'] = 'edit_file'; |
1744
|
|
|
|
1745
|
|
|
// Re-create the token so that it can be used |
1746
|
|
|
createToken('admin-te-' . md5($selectedTheme . '-' . $this->_req->post->filename)); |
1747
|
|
|
|
1748
|
|
|
return; |
1749
|
|
|
} |
1750
|
|
|
} |
1751
|
|
|
|
1752
|
|
|
/** |
1753
|
|
|
* Handles user browsing in theme directories. |
1754
|
|
|
* |
1755
|
|
|
* What it does: |
1756
|
|
|
* - The display will allow to choose a file for editing, |
1757
|
|
|
* if it is writable. |
1758
|
|
|
* - accessed with ?action=admin;area=theme;sa=browse |
1759
|
|
|
*/ |
1760
|
|
|
public function action_browse() |
1761
|
|
|
{ |
1762
|
|
|
global $context, $scripturl; |
1763
|
|
|
|
1764
|
|
|
loadTemplate('ManageThemes'); |
1765
|
|
|
|
1766
|
|
|
// We'll work hard with them themes! |
1767
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
1768
|
|
|
|
1769
|
|
|
$selectedTheme = $this->_req->getQuery('th', 'intval', $this->_req->getQuery('id', 'intval', 0)); |
1770
|
|
|
if (empty($selectedTheme)) |
1771
|
|
|
redirectexit('action=admin;area=theme;sa=themelist'); |
1772
|
|
|
|
1773
|
|
|
// Get first the directory of the theme we are editing. |
1774
|
|
|
$context['theme_id'] = isset($this->_req->query->th) ? (int) $this->_req->query->th : (isset($this->_req->query->id) ? (int) $this->_req->query->id : 0); |
1775
|
|
|
$theme_dir = themeDirectory($context['theme_id']); |
1776
|
|
|
|
1777
|
|
|
// Eh? not trying to sneak a peek outside the theme directory are we |
1778
|
|
|
if (!file_exists($theme_dir . '/index.template.php') && !file_exists($theme_dir . '/css/index.css')) |
1779
|
|
|
Errors::instance()->fatal_lang_error('theme_edit_missing', false); |
1780
|
|
|
|
1781
|
|
|
// Now, where exactly are you? |
1782
|
|
|
if (isset($this->_req->query->directory)) |
1783
|
|
|
{ |
1784
|
|
|
if (substr($this->_req->query->directory, 0, 1) === '.') |
1785
|
|
|
$this->_req->query->directory = ''; |
1786
|
|
|
else |
1787
|
|
|
{ |
1788
|
|
|
$this->_req->query->directory = preg_replace(array('~^[\./\\:\0\n\r]+~', '~[\\\\]~', '~/[\./]+~'), array('', '/', '/'), $this->_req->query->directory); |
1789
|
|
|
|
1790
|
|
|
$temp = realpath($theme_dir . '/' . $this->_req->query->directory); |
1791
|
|
|
if (empty($temp) || substr($temp, 0, strlen(realpath($theme_dir))) != realpath($theme_dir)) |
1792
|
|
|
$this->_req->query->directory = ''; |
1793
|
|
|
} |
1794
|
|
|
} |
1795
|
|
|
|
1796
|
|
|
if (isset($this->_req->query->directory) && $this->_req->query->directory != '') |
1797
|
|
|
{ |
1798
|
|
|
$context['theme_files'] = get_file_listing($theme_dir . '/' . $this->_req->query->directory, $this->_req->query->directory . '/'); |
1799
|
|
|
|
1800
|
|
|
$temp = dirname($this->_req->query->directory); |
1801
|
|
|
array_unshift($context['theme_files'], array( |
1802
|
|
|
'filename' => $temp == '.' || $temp == '' ? '/ (..)' : $temp . ' (..)', |
1803
|
|
|
'is_writable' => is_writable($theme_dir . '/' . $temp), |
1804
|
|
|
'is_directory' => true, |
1805
|
|
|
'is_template' => false, |
1806
|
|
|
'is_image' => false, |
1807
|
|
|
'is_editable' => false, |
1808
|
|
|
'href' => $scripturl . '?action=admin;area=theme;th=' . $context['theme_id'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=browse;directory=' . $temp, |
1809
|
|
|
'size' => '', |
1810
|
|
|
)); |
1811
|
|
|
} |
1812
|
|
|
else |
1813
|
|
|
$context['theme_files'] = get_file_listing($theme_dir, ''); |
1814
|
|
|
|
1815
|
|
|
// finally, load the sub-template |
1816
|
|
|
$context['sub_template'] = 'browse'; |
1817
|
|
|
} |
1818
|
|
|
|
1819
|
|
|
/** |
1820
|
|
|
* List installed themes. |
1821
|
|
|
* The listing will allow editing if the files are writable. |
1822
|
|
|
*/ |
1823
|
|
|
public function action_themelist() |
1824
|
|
|
{ |
1825
|
|
|
global $context; |
1826
|
|
|
|
1827
|
|
|
loadTemplate('ManageThemes'); |
1828
|
|
|
|
1829
|
|
|
// We'll work hard with them themes! |
1830
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
1831
|
|
|
|
1832
|
|
|
$context['themes'] = installedThemes(); |
1833
|
|
|
|
1834
|
|
|
foreach ($context['themes'] as $key => $theme) |
1835
|
|
|
{ |
1836
|
|
|
// There has to be a Settings template! |
1837
|
|
|
if (!file_exists($theme['theme_dir'] . '/index.template.php') && !file_exists($theme['theme_dir'] . '/css/index.css')) |
1838
|
|
|
unset($context['themes'][$key]); |
1839
|
|
|
else |
1840
|
|
|
{ |
1841
|
|
|
if (!isset($theme['theme_templates'])) |
1842
|
|
|
$templates = array('index'); |
1843
|
|
|
else |
1844
|
|
|
$templates = explode(',', $theme['theme_templates']); |
1845
|
|
|
|
1846
|
|
|
foreach ($templates as $template) |
1847
|
|
|
if (file_exists($theme['theme_dir'] . '/' . $template . '.template.php')) |
1848
|
|
|
{ |
1849
|
|
|
// Fetch the header... a good 256 bytes should be more than enough. |
1850
|
|
|
$fp = fopen($theme['theme_dir'] . '/' . $template . '.template.php', 'rb'); |
1851
|
|
|
$header = fread($fp, 256); |
1852
|
|
|
fclose($fp); |
1853
|
|
|
|
1854
|
|
|
// Can we find a version comment, at all? |
1855
|
|
|
if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) |
1856
|
|
|
{ |
1857
|
|
|
$ver = $match[1]; |
1858
|
|
|
if (!isset($context['themes'][$key]['version']) || $context['themes'][$key]['version'] > $ver) |
1859
|
|
|
$context['themes'][$key]['version'] = $ver; |
1860
|
|
|
} |
1861
|
|
|
} |
1862
|
|
|
|
1863
|
|
|
$context['themes'][$key]['can_edit_style'] = file_exists($theme['theme_dir'] . '/css/index.css'); |
1864
|
|
|
} |
1865
|
|
|
} |
1866
|
|
|
|
1867
|
|
|
$context['sub_template'] = 'themelist'; |
1868
|
|
|
} |
1869
|
|
|
|
1870
|
|
|
/** |
1871
|
|
|
* Makes a copy of a template file in a new location |
1872
|
|
|
* |
1873
|
|
|
* @uses ManageThemes template, copy_template sub-template. |
1874
|
|
|
*/ |
1875
|
|
|
public function action_copy() |
1876
|
|
|
{ |
1877
|
|
|
global $context, $settings; |
1878
|
|
|
|
1879
|
|
|
loadTemplate('ManageThemes'); |
1880
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
1881
|
|
|
|
1882
|
|
|
$context[$context['admin_menu_name']]['current_subsection'] = 'edit'; |
1883
|
|
|
|
1884
|
|
|
$context['theme_id'] = isset($this->_req->query->th) ? (int) $this->_req->query->th : (int) $this->_req->query->id; |
1885
|
|
|
|
1886
|
|
|
$theme_dirs = array(); |
1887
|
|
|
$theme_dirs = loadThemeOptionsInto($context['theme_id'], null, $theme_dirs, array('base_theme_dir', 'theme_dir')); |
1888
|
|
|
|
1889
|
|
|
if (isset($this->_req->query->template) && preg_match('~[\./\\\\:\0]~', $this->_req->query->template) == 0) |
1890
|
|
|
{ |
1891
|
|
|
if (!empty($theme_dirs['base_theme_dir']) && file_exists($theme_dirs['base_theme_dir'] . '/' . $this->_req->query->template . '.template.php')) |
1892
|
|
|
$filename = $theme_dirs['base_theme_dir'] . '/' . $this->_req->query->template . '.template.php'; |
1893
|
|
|
elseif (file_exists($settings['default_theme_dir'] . '/' . $this->_req->query->template . '.template.php')) |
1894
|
|
|
$filename = $settings['default_theme_dir'] . '/' . $this->_req->query->template . '.template.php'; |
1895
|
|
|
else |
1896
|
|
|
Errors::instance()->fatal_lang_error('no_access', false); |
1897
|
|
|
|
1898
|
|
|
$fp = fopen($theme_dirs['theme_dir'] . '/' . $this->_req->query->template . '.template.php', 'w'); |
1899
|
|
|
fwrite($fp, file_get_contents($filename)); |
|
|
|
|
1900
|
|
|
fclose($fp); |
1901
|
|
|
|
1902
|
|
|
if (function_exists('opcache_invalidate')) |
1903
|
|
|
opcache_invalidate($filename); |
1904
|
|
|
|
1905
|
|
|
redirectexit('action=admin;area=theme;th=' . $context['theme_id'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=copy'); |
1906
|
|
|
} |
1907
|
|
|
elseif (isset($this->_req->query->lang_file) && preg_match('~^[^\./\\\\:\0]\.[^\./\\\\:\0]$~', $this->_req->query->lang_file) != 0) |
1908
|
|
|
{ |
1909
|
|
|
if (!empty($theme_dirs['base_theme_dir']) && file_exists($theme_dirs['base_theme_dir'] . '/languages/' . $this->_req->query->lang_file . '.php')) |
1910
|
|
|
$filename = $theme_dirs['base_theme_dir'] . '/languages/' . $this->_req->query->template . '.php'; |
1911
|
|
|
elseif (file_exists($settings['default_theme_dir'] . '/languages/' . $this->_req->query->template . '.php')) |
1912
|
|
|
$filename = $settings['default_theme_dir'] . '/languages/' . $this->_req->query->template . '.php'; |
1913
|
|
|
else |
1914
|
|
|
Errors::instance()->fatal_lang_error('no_access', false); |
1915
|
|
|
|
1916
|
|
|
$fp = fopen($theme_dirs['theme_dir'] . '/languages/' . $this->_req->query->lang_file . '.php', 'w'); |
1917
|
|
|
fwrite($fp, file_get_contents($filename)); |
1918
|
|
|
fclose($fp); |
1919
|
|
|
|
1920
|
|
|
if (function_exists('opcache_invalidate')) |
1921
|
|
|
opcache_invalidate($filename); |
1922
|
|
|
|
1923
|
|
|
redirectexit('action=admin;area=theme;th=' . $context['theme_id'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=copy'); |
1924
|
|
|
} |
1925
|
|
|
|
1926
|
|
|
$templates = array(); |
1927
|
|
|
$lang_files = array(); |
1928
|
|
|
|
1929
|
|
|
$dir = dir($settings['default_theme_dir']); |
1930
|
|
|
while ($entry = $dir->read()) |
1931
|
|
|
{ |
1932
|
|
|
if (substr($entry, -13) == '.template.php') |
1933
|
|
|
$templates[] = substr($entry, 0, -13); |
1934
|
|
|
} |
1935
|
|
|
$dir->close(); |
1936
|
|
|
|
1937
|
|
|
$dir = dir($settings['default_theme_dir'] . '/languages'); |
1938
|
|
|
while ($entry = $dir->read()) |
1939
|
|
|
{ |
1940
|
|
|
if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches)) |
1941
|
|
|
$lang_files[] = $matches[1]; |
1942
|
|
|
} |
1943
|
|
|
$dir->close(); |
1944
|
|
|
|
1945
|
|
|
if (!empty($theme_dirs['base_theme_dir'])) |
1946
|
|
|
{ |
1947
|
|
|
$dir = dir($theme_dirs['base_theme_dir']); |
1948
|
|
|
while ($entry = $dir->read()) |
1949
|
|
|
{ |
1950
|
|
|
if (substr($entry, -13) == '.template.php' && !in_array(substr($entry, 0, -13), $templates)) |
1951
|
|
|
$templates[] = substr($entry, 0, -13); |
1952
|
|
|
} |
1953
|
|
|
$dir->close(); |
1954
|
|
|
|
1955
|
|
|
if (file_exists($theme_dirs['base_theme_dir'] . '/languages')) |
1956
|
|
|
{ |
1957
|
|
|
$dir = dir($theme_dirs['base_theme_dir'] . '/languages'); |
1958
|
|
|
while ($entry = $dir->read()) |
1959
|
|
|
{ |
1960
|
|
|
if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches) && !in_array($matches[1], $lang_files)) |
1961
|
|
|
$lang_files[] = $matches[1]; |
1962
|
|
|
} |
1963
|
|
|
$dir->close(); |
1964
|
|
|
} |
1965
|
|
|
} |
1966
|
|
|
|
1967
|
|
|
natcasesort($templates); |
1968
|
|
|
natcasesort($lang_files); |
1969
|
|
|
|
1970
|
|
|
$context['available_templates'] = array(); |
1971
|
|
|
foreach ($templates as $template) |
1972
|
|
|
$context['available_templates'][$template] = array( |
1973
|
|
|
'filename' => $template . '.template.php', |
1974
|
|
|
'value' => $template, |
1975
|
|
|
'already_exists' => false, |
1976
|
|
|
'can_copy' => is_writable($theme_dirs['theme_dir']), |
1977
|
|
|
); |
1978
|
|
|
$context['available_language_files'] = array(); |
1979
|
|
|
foreach ($lang_files as $file) |
1980
|
|
|
$context['available_language_files'][$file] = array( |
1981
|
|
|
'filename' => $file . '.php', |
1982
|
|
|
'value' => $file, |
1983
|
|
|
'already_exists' => false, |
1984
|
|
|
'can_copy' => file_exists($theme_dirs['theme_dir'] . '/languages') ? is_writable($theme_dirs['theme_dir'] . '/languages') : is_writable($theme_dirs['theme_dir']), |
1985
|
|
|
); |
1986
|
|
|
|
1987
|
|
|
$dir = dir($theme_dirs['theme_dir']); |
1988
|
|
|
while ($entry = $dir->read()) |
1989
|
|
|
{ |
1990
|
|
|
if (substr($entry, -13) == '.template.php' && isset($context['available_templates'][substr($entry, 0, -13)])) |
1991
|
|
|
{ |
1992
|
|
|
$context['available_templates'][substr($entry, 0, -13)]['already_exists'] = true; |
1993
|
|
|
$context['available_templates'][substr($entry, 0, -13)]['can_copy'] = is_writable($theme_dirs['theme_dir'] . '/' . $entry); |
1994
|
|
|
} |
1995
|
|
|
} |
1996
|
|
|
$dir->close(); |
1997
|
|
|
|
1998
|
|
|
if (file_exists($theme_dirs['theme_dir'] . '/languages')) |
1999
|
|
|
{ |
2000
|
|
|
$dir = dir($theme_dirs['theme_dir'] . '/languages'); |
2001
|
|
|
while ($entry = $dir->read()) |
2002
|
|
|
{ |
2003
|
|
|
if (preg_match('~^([^\.]+\.[^\.]+)\.php$~', $entry, $matches) && isset($context['available_language_files'][$matches[1]])) |
2004
|
|
|
{ |
2005
|
|
|
$context['available_language_files'][$matches[1]]['already_exists'] = true; |
2006
|
|
|
$context['available_language_files'][$matches[1]]['can_copy'] = is_writable($theme_dirs['theme_dir'] . '/languages/' . $entry); |
2007
|
|
|
} |
2008
|
|
|
} |
2009
|
|
|
$dir->close(); |
2010
|
|
|
} |
2011
|
|
|
|
2012
|
|
|
$context['sub_template'] = 'copy_template'; |
2013
|
|
|
} |
2014
|
|
|
|
2015
|
|
|
/** |
2016
|
|
|
* This function makes necessary pre-checks and fills |
2017
|
|
|
* the contextual data as needed by theme editing functions. |
2018
|
|
|
*/ |
2019
|
|
|
private function prepareThemeEditContext() |
2020
|
|
|
{ |
2021
|
|
|
global $context; |
2022
|
|
|
|
2023
|
|
|
// Eh? not trying to sneak a peek outside the theme directory are we |
2024
|
|
|
if (!file_exists($this->theme_dir . '/index.template.php') && !file_exists($this->theme_dir . '/css/index.css')) |
2025
|
|
|
Errors::instance()->fatal_lang_error('theme_edit_missing', false); |
2026
|
|
|
|
2027
|
|
|
// Get the filename from the appropriate spot |
2028
|
|
|
$filename = isset($this->_req->post->save) ? $this->_req->getPost('filename', 'strval', '') : $this->_req->getQuery('filename', 'strval', ''); |
2029
|
|
|
|
2030
|
|
|
// You're editing a file: we have extra-checks coming up first. |
2031
|
|
|
if (substr($filename, 0, 1) === '.') |
2032
|
|
|
$filename = ''; |
2033
|
|
|
else |
2034
|
|
|
{ |
2035
|
|
|
$filename = preg_replace(array('~^[\./\\:\0\n\r]+~', '~[\\\\]~', '~/[\./]+~'), array('', '/', '/'), $filename); |
2036
|
|
|
|
2037
|
|
|
$temp = realpath($this->theme_dir . '/' . $filename); |
2038
|
|
|
if (empty($temp) || substr($temp, 0, strlen(realpath($this->theme_dir))) !== realpath($this->theme_dir)) |
2039
|
|
|
$filename = ''; |
2040
|
|
|
} |
2041
|
|
|
|
2042
|
|
|
// We shouldn't end up with no file |
2043
|
|
|
if (empty($filename)) |
2044
|
|
|
Errors::instance()->fatal_lang_error('theme_edit_missing', false); |
2045
|
|
|
|
2046
|
|
|
// Initialize context |
2047
|
|
|
$context['allow_save'] = is_writable($this->theme_dir . '/' . $filename); |
2048
|
|
|
$context['allow_save_filename'] = strtr($this->theme_dir . '/' . $filename, array(BOARDDIR => '...')); |
2049
|
|
|
$context['edit_filename'] = htmlspecialchars($filename, ENT_COMPAT, 'UTF-8'); |
2050
|
|
|
} |
2051
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.