-
Notifications
You must be signed in to change notification settings - Fork 988
/
AdminTheme.php
391 lines (349 loc) · 20.1 KB
/
AdminTheme.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
<?php
/*
* LimeSurvey
* Copyright (C) 2007-2015 The LimeSurvey Project Team / Carsten Schmitz
* All rights reserved.
* License: GNU/GPL License v2 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/
/**
* Admin Theme Model
*
*
* @package LimeSurvey
* @subpackage Backend
*/
class AdminTheme extends CFormModel
{
/** @var string $name Admin Theme's name */
public $name;
/** @var string $path Admin Theme's path */
public $path;
/** @var string $sTemplateUrl URL to reach Admin Theme (used to get CSS/JS/Files when asset manager is off) */
public $sTemplateUrl;
/** @var mixed $config Contains the Admin Theme's configuration file */
public $config;
/** @var boolean $use_asset_manager If true, force the use of asset manager even if debug mode is on (useful to debug asset manager's problems) */
public static $use_asset_manager;
/** @var AdminTheme $instance The instance of theme object */
private static $instance;
/**
* Get the list of admin theme, as an array containing each configuration object for each template
* @return array the array of configuration object
*/
public static function getAdminThemeList()
{
$sStandardTemplateRootDir = Yii::app()->getConfig("styledir"); // The directory containing the default admin themes
$sUserTemplateDir = Yii::app()->getConfig('uploaddir') . DIRECTORY_SEPARATOR . 'admintheme'; // The directory containing the user themes
$aStandardThemeObjects = self::getThemeList($sStandardTemplateRootDir); // array containing the configuration files of standard admin themes (styles/...)
$aUserThemeObjects = self::getThemeList($sUserTemplateDir); // array containing the configuration files of user admin themes (upload/admintheme/...)
$aListOfThemeObjects = array_merge($aStandardThemeObjects, $aUserThemeObjects);
ksort($aListOfThemeObjects);
return $aListOfThemeObjects;
}
/**
* Set the Admin Theme :
* - checks if the required template exists
* - set the admin theme variables
* - set the admin theme constants
* - Register all the needed CSS/JS files
* @return AdminTheme
*/
public function setAdminTheme()
{
$sAdminThemeName = getGlobalSetting('admintheme'); // We retrieve the admin theme in config ( {{settings_global}} or config-defaults.php )
$sStandardTemplateRootDir = Yii::app()->getConfig("styledir"); // Path for the standard Admin Themes
$sUserTemplateDir = Yii::app()->getConfig('uploaddir') . DIRECTORY_SEPARATOR . 'admintheme'; // Path for the user Admin Themes
// Check if the required theme is a standard one
if ($this->isStandardAdminTheme($sAdminThemeName)) {
$sTemplateDir = $sStandardTemplateRootDir; // It's standard, so it will be in standard path
$sTemplateUrl = Yii::app()->getConfig('styleurl') . $sAdminThemeName; // Available via a standard URL
} else {
// If it's not a standard theme, we bet it's a user one.
// In fact, it could also be a old 2.06 admin theme just aftet an update (it will then be caught as "non existent" in the next if statement")
$sTemplateDir = $sUserTemplateDir;
$sTemplateUrl = Yii::app()->getConfig('uploadurl') . DIRECTORY_SEPARATOR . 'admintheme' . DIRECTORY_SEPARATOR . $sAdminThemeName;
}
// If the theme directory doesn't exist, it can be that:
// - user updated from 2.06 and still have old theme configurated in database
// - user deleted a custom theme
// In any case, we just set Sea Green as the template to use
if (!is_dir($sTemplateDir . DIRECTORY_SEPARATOR . $sAdminThemeName)) {
$sAdminThemeName = 'Sea_Green';
$sTemplateDir = $sStandardTemplateRootDir;
$sTemplateUrl = Yii::app()->getConfig('styleurl') . DIRECTORY_SEPARATOR . $sAdminThemeName;
SettingGlobal::setSetting('admintheme', 'Sea_Green');
}
// Now that we are sure we have an existing template, we can set the variables of the AdminTheme
$this->sTemplateUrl = $sTemplateUrl;
$this->name = $sAdminThemeName;
$this->path = $sTemplateDir . DIRECTORY_SEPARATOR . $this->name;
// This is necessary because a lot of files still use "adminstyleurl".
// TODO: replace everywhere the call to Yii::app()->getConfig('adminstyleurl) by $oAdminTheme->sTemplateUrl;
Yii::app()->setConfig('adminstyleurl', $this->sTemplateUrl);
//////////////////////
// Config file loading
if (\PHP_VERSION_ID < 80000) {
$bOldEntityLoaderState = libxml_disable_entity_loader(true); // @see: http://phpsecurity.readthedocs.io/en/latest/Injection-Attacks.html#xml-external-entity-injection
}
$sXMLConfigFile = file_get_contents(realpath($this->path . '/config.xml')); // Now that entity loader is disabled, we can't use simplexml_load_file; so we must read the file with file_get_contents and convert it as a string
// Simple Xml is buggy on PHP < 5.4. The [ array -> json_encode -> json_decode ] workaround seems to be the most used one.
// @see: http://php.net/manual/de/book.simplexml.php#105330 (top comment on PHP doc for simplexml)
$this->config = json_decode(json_encode((array) simplexml_load_string($sXMLConfigFile), 1));
// If developers want to test asset manager with debug mode on
self::$use_asset_manager = isset($this->config->engine->use_asset_manager_in_debug_mode) ? ($this->config->engine->use_asset_manager_in_debug_mode == 'true') : false;
$this->defineConstants(); // Define the (still) necessary constants
$this->registerStylesAndScripts(); // Register all CSS and JS
if (\PHP_VERSION_ID < 80000) {
libxml_disable_entity_loader($bOldEntityLoaderState); // Put back entity loader to its original state, to avoid contagion to other applications on the server
}
return $this;
}
/**
* Register all the styles and scripts of the current template.
* Check if RTL is needed, use asset manager if needed.
* This function is public because it appears that sometime, the package need to be register again in header (probably a cache problem)
*/
public function registerStylesAndScripts()
{
// First we register the different needed packages
// Bootstrap Registration
// We don't want to use bootstrap extension's register functionality, to be able to set dependencies between packages
// ie: to control load order setting 'depends' in our package
// So, we take the usual Bootstrap extensions TbApi::register (called normally with App()->bootstrap->register()) see: https://github.com/LimeSurvey/LimeSurvey/blob/master/application/extensions/bootstrap/components/TbApi.php#l162-l169
// keep here the necessary (registerMetaTag and registerAllScripts),
// and move the rest to the bootstrap package.
// NB: registerAllScripts could be replaced by js definition in package. If needed: not a problem to do it
if (!Yii::app()->request->getQuery('isAjax', false)) {
Yii::app()->getClientScript()->registerMetaTag('width=device-width, initial-scale=1.0', 'viewport'); // See: https://github.com/LimeSurvey/LimeSurvey/blob/master/application/extensions/bootstrap/components/TbApi.php#l108-l115
App()->bootstrap->registerTooltipAndPopover(); // See : https://github.com/LimeSurvey/LimeSurvey/blob/master/application/extensions/bootstrap/components/TbApi.php#l153-l160
App()->getClientScript()->registerScript('coreuser', '
window.LS = window.LS || {}; window.LS.globalUserId = "' . Yii::app()->user->id . '";', CClientScript::POS_HEAD);
App()->getClientScript()->registerPackage('jquery'); // jquery
App()->getClientScript()->registerPackage('jqueryui'); // Added for nestedSortable to work (question organizer)
App()->getClientScript()->registerPackage('js-cookie'); // js-cookie
App()->getClientScript()->registerPackage('fontawesome'); // fontawesome
App()->getClientScript()->registerPackage('bootstrap-switch');
App()->getClientScript()->registerPackage('bootstrap-select2');
App()->getClientScript()->registerPackage('bootstrap-datetimepicker');
App()->getClientScript()->registerPackage('font-roboto');
App()->getClientScript()->registerPackage('font-icomoon');
App()->getClientScript()->registerPackage('adminbasics'); // Combined scripts and style
App()->getClientScript()->registerPackage('adminsidepanel'); // The new admin panel
App()->getClientScript()->registerPackage('lstutorial'); // Tutorial scripts
App()->getClientScript()->registerPackage('ckeditor'); //
App()->getClientScript()->registerPackage('ckeditoradditions'); // CKEDITOR in a global sope
App()->getClientScript()->registerPackage('modaleditor');
$dir = (getLanguageRTL(App()->getLanguage())) ? 'rtl' : 'ltr';
if ($dir == "rtl") {
App()->getClientScript()->registerPackage('bootstrap-rtl');
}
}
$aCssFiles = array();
$aJsFiles = array();
// Then we add the different CSS/JS files to load in arrays
// It will check if it needs or not the RTL files
// and it will add the directory prefix to the file name (css/ or js/ )
// This last step is needed for the package (yii package use a single baseUrl / basePath for css and js files )
// Shorter writing.
$files = $this->config->files;
// We check if RTL is needed
if (getLanguageRTL(Yii::app()->language)) {
if (
!isset($files->rtl)
|| !isset($files->rtl->css)
) {
throw new CException("Invalid template configuration: No CSS files found for right-to-left languages");
}
if (is_array($files->rtl->css->filename)) {
foreach ($files->rtl->css->filename as $cssfile) {
$aCssFiles[] = 'css/' . $cssfile; // add the 'css/' prefix to the RTL css files
}
} elseif (is_string($files->rtl->css->filename)) {
$aCssFiles[] = 'css/' . $files->rtl->css->filename;
}
App()->getClientScript()->registerPackage('font-roboto');
App()->getClientScript()->registerPackage('adminbasicsrtl');
App()->getClientScript()->registerPackage('adminsidepanelrtl');
} else {
App()->getClientScript()->registerPackage('adminbasicsltr');
App()->getClientScript()->registerPackage('adminsidepanelltr');
// Non-RTL style
if (is_array($files->css->filename)) {
foreach ($files->css->filename as $cssfile) {
$aCssFiles[] = 'css/' . $cssfile; // add the 'css/' prefix to the css files
}
} elseif (is_string($files->css->filename)) {
$aCssFiles[] = 'css/' . $files->css->filename;
}
}
if (!empty($files->js->filename)) {
if (is_array($files->js->filename)) {
foreach ($files->js->filename as $jsfile) {
$aJsFiles[] = 'scripts/' . $jsfile; // add the 'js/' prefix to the js files
}
} elseif (is_string($files->js->filename)) {
$aJsFiles[] = 'scripts/' . $files->js->filename;
}
}
$package = array();
// We check if the asset manager should be use.
// When defining the package with a base path (a directory on the file system), the asset manager is used
// When defining the package with a base url, the file is directly registerd without the asset manager
// See : http://www.yiiframework.com/doc/api/1.1/CClientScript#packages-detail
if (!YII_DEBUG || self::$use_asset_manager || Yii::app()->getConfig('use_asset_manager')) {
Yii::setPathOfAlias('admin.theme.path', $this->path);
$package['basePath'] = 'admin.theme.path'; // add the base path to the package, so it will use the asset manager
} else {
$package['baseUrl'] = $this->sTemplateUrl; // add the base url to the package, so it will not use the asset manager
}
$package['css'] = $aCssFiles; // add the css files to the package
$package['js'] = $aJsFiles; // add the js files to the package
$package['depends'] = array('bootstrap');
Yii::app()->clientScript->addPackage('admin-theme', $package); // add the package
Yii::app()->clientScript->registerPackage('admin-theme'); // register the package
Yii::app()->clientScript->registerPackage('moment'); // register moment for correct dateTime calculation
}
/**
* Get instance of theme object.
* Will instantiate the Admin Theme object first time it is called.
* Please use this instead of global variable.
* @return AdminTheme
*/
public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new self();
self::$instance->setAdminTheme();
}
return self::$instance;
}
/**
* @return string[]
*/
public static function getOtherAssets()
{
return array(
// Extension assets
'application/extensions/yiiwheels/assets',
'application/extensions/yiiwheels/widgets/box/assets',
'application/extensions/yiiwheels/widgets/grid/assets',
'application/extensions/yiiwheels/widgets/formhelpers/assets',
'application/extensions/yiiwheels/widgets/highcharts/assets',
'application/extensions/yiiwheels/widgets/maskinput/assets',
'application/extensions/yiiwheels/widgets/redactor/assets',
'application/extensions/yiiwheels/widgets/switch/assets',
'application/extensions/yiiwheels/widgets/datetimepicker/assets',
'application/extensions/yiiwheels/widgets/timeago/assets',
'application/extensions/yiiwheels/widgets/sparklines/assets',
'application/extensions/yiiwheels/widgets/datepicker/assets',
'application/extensions/yiiwheels/widgets/multiselect/assets',
'application/extensions/yiiwheels/widgets/gallery/assets',
'application/extensions/yiiwheels/widgets/select2/assets',
'application/extensions/yiiwheels/widgets/ace/assets',
'application/extensions/yiiwheels/widgets/modal/assets',
'application/extensions/yiiwheels/widgets/maskmoney/assets',
'application/extensions/yiiwheels/widgets/rangeslider/assets',
'application/extensions/yiiwheels/widgets/fileupload/assets',
'application/extensions/yiiwheels/widgets/typeahead/assets',
'application/extensions/yiiwheels/widgets/timepicker/assets',
'application/extensions/yiiwheels/widgets/html5editor/assets',
'application/extensions/yiiwheels/widgets/daterangepicker/assets',
'application/extensions/bootstrap/assets',
'application/extensions/LimeScript/assets',
'application/extensions/SettingsWidget/assets',
'application/extensions/FlashMessage/assets',
'application/extensions/admin/survey/question/PositionWidget/assets',
'application/extensions/admin/grid/MassiveActionsWidget/assets',
'application/extensions/admin/survey/question/PositionWidget/assets',
//'application/extensions/bootstrap/', we'll touch all the subdirectories of extensions
// Third party assets
'third_party/jquery-tablesorter/tests/assets',
'third_party/jquery-tablesorter/docs/assets',
);
}
/**
* Return an array containing the configuration object of all templates in a given directory
*
* @param string $sDir the directory to scan
* @return array the array of object
*/
private static function getThemeList($sDir)
{
if (\PHP_VERSION_ID < 80000) {
$bOldEntityLoaderState = libxml_disable_entity_loader(true); // @see: http://phpsecurity.readthedocs.io/en/latest/Injection-Attacks.html#xml-external-entity-injection
}
$aListOfFiles = array();
$oAdminTheme = new AdminTheme();
if ($sDir && $pHandle = opendir($sDir)) {
while (false !== ($file = readdir($pHandle))) {
if (is_dir($sDir . DIRECTORY_SEPARATOR . $file) && is_file($sDir . DIRECTORY_SEPARATOR . $file . DIRECTORY_SEPARATOR . 'config.xml')) {
$sXMLConfigFile = file_get_contents(realpath($sDir . DIRECTORY_SEPARATOR . $file . '/config.xml')); // Now that entity loader is disabled, we can't use simplexml_load_file; so we must read the file with file_get_contents and convert it as a string
// Simple Xml is buggy on PHP < 5.4. The [ array -> json_encode -> json_decode ] workaround seems to be the most used one.
// @see: http://php.net/manual/de/book.simplexml.php#105330 (top comment on PHP doc for simplexml)
$oTemplateConfig = json_decode(json_encode((array) simplexml_load_string($sXMLConfigFile), 1));
if ($oAdminTheme->isStandardAdminTheme($file)) {
$previewUrl = Yii::app()->getConfig('styleurl') . $file;
} else {
$previewUrl = Yii::app()->getConfig('uploadurl') . DIRECTORY_SEPARATOR . 'admintheme' . DIRECTORY_SEPARATOR . $file;
}
$oTemplateConfig->path = $file;
$oTemplateConfig->preview = '<img src="' . $previewUrl . '/preview.png" alt="admin theme preview" height="200" class="img-thumbnail" />';
$aListOfFiles[$file] = $oTemplateConfig;
}
}
closedir($pHandle);
}
if (\PHP_VERSION_ID < 80000) {
libxml_disable_entity_loader($bOldEntityLoaderState);
}
return $aListOfFiles;
}
/**
* Few constants depending on Template
*/
private function defineConstants()
{
// Define images url
if (!YII_DEBUG || self::$use_asset_manager || Yii::app()->getConfig('use_asset_manager')) {
define('LOGO_URL', App()->getAssetManager()->publish($this->path . '/images/logo.png'));
define('LOGO_ICON_URL', App()->getAssetManager()->publish($this->path . '/images/logo_icon.png'));
} else {
define('LOGO_URL', $this->sTemplateUrl . '/images/logo.png');
define('LOGO_ICON_URL', $this->sTemplateUrl . '/images/logo_icon.png');
}
// Define presentation text on welcome page
if (isset($this->config->metadata->presentation) && $this->config->metadata->presentation) {
define('PRESENTATION', $this->config->metadata->presentation);
} else {
define('PRESENTATION', gT('This is the LimeSurvey admin interface. Start to build your survey from here.'));
}
}
/**
* Use to check if admin theme is standard
*
* @param string $sAdminThemeName the name of the template
* @return boolean return true if it's a standard template, else false
*/
private function isStandardAdminTheme($sAdminThemeName)
{
return in_array(
$sAdminThemeName,
array(
'Apple_Blossom',
'Bay_of_Many',
'Black_Pearl',
'Dark_Sky',
'Free_Magenta',
'Noto_All_Languages',
'Purple_Tentacle',
'Sea_Green',
'Sunset_Orange',
)
);
}
}