forked from moodle/moodle
-
Notifications
You must be signed in to change notification settings - Fork 1
/
styles.php
312 lines (259 loc) · 11 KB
/
styles.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
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file is responsible for serving the one huge CSS of each theme.
*
* @package core
* @copyright 2009 Petr Skoda (skodak) {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// Disable moodle specific debug messages and any errors in output,
// comment out when debugging or better look into error log!
define('NO_DEBUG_DISPLAY', true);
define('ABORT_AFTER_CONFIG', true);
require('../config.php');
require_once($CFG->dirroot.'/lib/csslib.php');
if ($slashargument = min_get_slash_argument()) {
$slashargument = ltrim($slashargument, '/');
if (substr_count($slashargument, '/') < 2) {
css_send_css_not_found();
}
if (strpos($slashargument, '_s/') === 0) {
// Can't use SVG.
$slashargument = substr($slashargument, 3);
$usesvg = false;
} else {
$usesvg = true;
}
list($themename, $rev, $type) = explode('/', $slashargument, 3);
$themename = min_clean_param($themename, 'SAFEDIR');
$rev = min_clean_param($rev, 'RAW');
$type = min_clean_param($type, 'SAFEDIR');
} else {
$themename = min_optional_param('theme', 'standard', 'SAFEDIR');
$rev = min_optional_param('rev', 0, 'RAW');
$type = min_optional_param('type', 'all', 'SAFEDIR');
$usesvg = (bool)min_optional_param('svg', '1', 'INT');
}
// Check if we received a theme sub revision which allows us
// to handle local caching on a per theme basis.
$values = explode('_', $rev);
$rev = min_clean_param(array_shift($values), 'INT');
$themesubrev = array_shift($values);
if (!is_null($themesubrev)) {
$themesubrev = min_clean_param($themesubrev, 'INT');
}
// Check that type fits into the expected values.
if (!in_array($type, ['all', 'all-rtl', 'editor', 'editor-rtl'])) {
css_send_css_not_found();
}
if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
// The theme exists in standard location - ok.
} else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$themename/config.php")) {
// Alternative theme location contains this theme - ok.
} else {
header('HTTP/1.0 404 not found');
die('Theme was not found, sorry.');
}
$candidatedir = "$CFG->localcachedir/theme/$rev/$themename/css";
$candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg);
$etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg);
if (file_exists($candidatesheet)) {
if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
// We do not actually need to verify the etag value because our files
// never change in cache because we increment the rev counter.
css_send_unmodified(filemtime($candidatesheet), $etag);
}
css_send_cached_css($candidatesheet, $etag);
}
// Ok, now we need to start normal moodle script, we need to load all libs and $DB.
define('ABORT_AFTER_CONFIG_CANCEL', true);
define('NO_MOODLE_COOKIES', true); // Session not used here.
define('NO_UPGRADE_CHECK', true); // Ignore upgrade check.
require("$CFG->dirroot/lib/setup.php");
$theme = theme_config::load($themename);
$theme->force_svg_use($usesvg);
$theme->set_rtl_mode(substr($type, -4) === '-rtl');
$themerev = theme_get_revision();
$currentthemesubrev = theme_get_sub_revision_for_theme($themename);
$cache = true;
// If the client is requesting a revision that doesn't match both
// the global theme revision and the theme specific revision then
// tell the browser not to cache this style sheet because it's
// likely being regenerated.
if ($themerev <= 0 or $themerev != $rev or $themesubrev != $currentthemesubrev) {
$rev = $themerev;
$themesubrev = $currentthemesubrev;
$cache = false;
$candidatedir = "$CFG->localcachedir/theme/$rev/$themename/css";
$candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg);
$etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg);
}
make_localcache_directory('theme', false);
if ($type === 'editor' || $type === 'editor-rtl') {
$csscontent = $theme->get_css_content_editor();
if ($cache) {
css_store_css($theme, $candidatesheet, $csscontent);
css_send_cached_css($candidatesheet, $etag);
} else {
css_send_uncached_css($csscontent);
}
}
if (($fallbacksheet = theme_styles_fallback_content($theme)) && !$theme->has_css_cached_content()) {
// The theme is not yet available and a fallback is available.
// Return the fallback immediately, specifying the Content-Length, then generate in the background.
$css = file_get_contents($fallbacksheet);
css_send_temporary_css($css);
// The fallback content has now been sent.
// There will be an attempt to generate the content, but it should not be served.
// The Content-Length above means that the client will disregard it anyway.
$sendaftergeneration = false;
// There may be another client currently holding a lock and generating the stylesheet.
// Use a very low lock timeout as the connection will be ended immediately afterwards.
$locktimeout = 1;
} else {
// There is no fallback content to be issued here, therefore the generated content must be output.
$sendaftergeneration = true;
// Use a realistic lock timeout as the intention is to avoid lock contention.
$locktimeout = rand(90, 120);
}
// Attempt to fetch the lock.
$lockfactory = \core\lock\lock_config::get_lock_factory('core_theme_get_css_content');
$lock = $lockfactory->get_lock($themename, $locktimeout);
if ($sendaftergeneration || $lock) {
// Either the lock was successful, or the lock was unsuccessful but the content *must* be sent.
// The content does not exist locally.
// Generate and save it.
$candidatesheet = theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidatedir);
if ($lock) {
$lock->release();
}
if ($sendaftergeneration) {
if (!$cache) {
// Do not pollute browser caches if invalid revision requested,
// let's ignore legacy IE breakage here too.
css_send_uncached_css(file_get_contents($candidatesheet));
} else {
// Real browsers - this is the expected result!
css_send_cached_css($candidatesheet, $etag);
}
}
}
/**
* Generate the theme CSS and store it.
*
* @param theme_config $theme The theme to be generated
* @param int $rev The theme revision
* @param int $themesubrev The theme sub-revision
* @param string $candidatedir The directory that it should be stored in
* @return string The path that the primary CSS was written to
*/
function theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidatedir) {
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
// Generate the content first.
if (!$csscontent = $theme->get_css_cached_content()) {
$csscontent = $theme->get_css_content();
$theme->set_css_content_cache($csscontent);
}
if ($theme->get_rtl_mode()) {
$type = "all-rtl";
} else {
$type = "all";
}
// Determine the candidatesheet path.
$candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $theme->use_svg_icons());
// Store the CSS.
css_store_css($theme, $candidatesheet, $csscontent);
// Store the fallback CSS in the temp directory.
// This file is used as a fallback when waiting for a theme to compile and is not versioned in any way.
$fallbacksheet = make_temp_directory("theme/{$theme->name}")
. "/"
. theme_styles_get_filename($type, 0, $theme->use_svg_icons());
css_store_css($theme, $fallbacksheet, $csscontent);
// Delete older revisions from localcache.
$themecachedirs = glob("{$CFG->localcachedir}/theme/*", GLOB_ONLYDIR);
foreach ($themecachedirs as $localcachedir) {
$cachedrev = [];
preg_match("/\/theme\/([0-9]+)$/", $localcachedir, $cachedrev);
$cachedrev = isset($cachedrev[1]) ? intval($cachedrev[1]) : 0;
if ($cachedrev > 0 && $cachedrev < $rev) {
fulldelete($localcachedir);
}
}
// Delete older theme subrevision CSS from localcache.
$subrevfiles = glob("{$CFG->localcachedir}/theme/{$rev}/{$theme->name}/css/*.css");
foreach ($subrevfiles as $subrevfile) {
$cachedsubrev = [];
preg_match("/_([0-9]+)\.([0-9]+\.)?css$/", $subrevfile, $cachedsubrev);
$cachedsubrev = isset($cachedsubrev[1]) ? intval($cachedsubrev[1]) : 0;
if ($cachedsubrev > 0 && $cachedsubrev < $themesubrev) {
fulldelete($subrevfile);
}
}
return $candidatesheet;
}
/**
* Fetch the preferred fallback content location if available.
*
* @param theme_config $theme The theme to be generated
* @return string The path to the fallback sheet on disk
*/
function theme_styles_fallback_content($theme) {
global $CFG;
if (!$theme->usefallback) {
// This theme does not support fallbacks.
return false;
}
$type = $theme->get_rtl_mode() ? 'all-rtl' : 'all';
$filename = theme_styles_get_filename($type);
$fallbacksheet = "{$CFG->tempdir}/theme/{$theme->name}/{$filename}";
if (file_exists($fallbacksheet)) {
return $fallbacksheet;
}
return false;
}
/**
* Get the filename for the specified configuration.
*
* @param string $type The requested sheet type
* @param int $themesubrev The theme sub-revision
* @param bool $usesvg Whether SVGs are allowed
* @return string The filename for this sheet
*/
function theme_styles_get_filename($type, $themesubrev = 0, $usesvg = true) {
$filename = $type;
$filename .= ($themesubrev > 0) ? "_{$themesubrev}" : '';
$filename .= $usesvg ? '' : '-nosvg';
return "{$filename}.css";
}
/**
* Determine the correct etag for the specified configuration.
*
* @param string $themename The name of the theme
* @param int $rev The revision number
* @param string $type The requested sheet type
* @param int $themesubrev The theme sub-revision
* @param bool $usesvg Whether SVGs are allowed
* @return string The etag to use for this request
*/
function theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg) {
$etag = [$rev, $themename, $type, $themesubrev];
if (!$usesvg) {
$etag[] = 'nosvg';
}
return sha1(implode('/', $etag));
}