Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * This file is responsible for serving the one huge CSS of each theme.
19
 *
20
 * @package   core
21
 * @copyright 2009 Petr Skoda (skodak)  {@link http://skodak.org}
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
// Disable moodle specific debug messages and any errors in output,
26
// comment out when debugging or better look into error log!
27
define('NO_DEBUG_DISPLAY', true);
28
 
29
define('ABORT_AFTER_CONFIG', true);
30
require('../config.php');
31
require_once($CFG->dirroot.'/lib/csslib.php');
32
 
33
if ($slashargument = min_get_slash_argument()) {
34
    $slashargument = ltrim($slashargument, '/');
35
    if (substr_count($slashargument, '/') < 2) {
36
        css_send_css_not_found();
37
    }
38
 
39
    if (strpos($slashargument, '_s/') === 0) {
40
        // Can't use SVG.
41
        $slashargument = substr($slashargument, 3);
42
        $usesvg = false;
43
    } else {
44
        $usesvg = true;
45
    }
46
 
47
    list($themename, $rev, $type) = explode('/', $slashargument, 3);
48
    $themename = min_clean_param($themename, 'SAFEDIR');
49
    $rev       = min_clean_param($rev, 'RAW');
50
    $type      = min_clean_param($type, 'SAFEDIR');
51
 
52
} else {
53
    $themename = min_optional_param('theme', 'standard', 'SAFEDIR');
54
    $rev       = min_optional_param('rev', 0, 'RAW');
55
    $type      = min_optional_param('type', 'all', 'SAFEDIR');
56
    $usesvg    = (bool)min_optional_param('svg', '1', 'INT');
57
}
58
 
59
// Check if we received a theme sub revision which allows us
60
// to handle local caching on a per theme basis.
61
$values = explode('_', $rev);
62
$rev = min_clean_param(array_shift($values), 'INT');
63
$themesubrev = array_shift($values);
64
 
65
if (!is_null($themesubrev)) {
66
    $themesubrev = min_clean_param($themesubrev, 'INT');
67
}
68
 
69
// Note: We only check validity of the revision number here, we do not check the theme sub-revision because this is
70
// not solely based on time.
71
if (!min_is_revision_valid_and_current($rev)) {
72
    // If the rev is invalid, normalise it to -1 to disable all caching.
73
    $rev = -1;
74
}
75
 
76
// Check that type fits into the expected values.
77
if (!in_array($type, ['all', 'all-rtl', 'editor', 'editor-rtl'])) {
78
    css_send_css_not_found();
79
}
80
 
81
if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
82
    // The theme exists in standard location - ok.
83
} else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$themename/config.php")) {
84
    // Alternative theme location contains this theme - ok.
85
} else {
86
    header('HTTP/1.0 404 not found');
87
    die('Theme was not found, sorry.');
88
}
89
 
90
$candidatedir = "$CFG->localcachedir/theme/$rev/$themename/css";
91
$candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg);
92
$etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg);
93
 
94
if (file_exists($candidatesheet)) {
95
    if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
96
        // We do not actually need to verify the etag value because our files
97
        // never change in cache because we increment the rev counter.
98
        css_send_unmodified(filemtime($candidatesheet), $etag);
99
    }
100
    css_send_cached_css($candidatesheet, $etag);
101
}
102
 
103
// Ok, now we need to start normal moodle script, we need to load all libs and $DB.
104
define('ABORT_AFTER_CONFIG_CANCEL', true);
105
 
106
define('NO_MOODLE_COOKIES', true); // Session not used here.
107
define('NO_UPGRADE_CHECK', true);  // Ignore upgrade check.
108
 
109
require("$CFG->dirroot/lib/setup.php");
110
 
111
$theme = theme_config::load($themename);
112
$theme->force_svg_use($usesvg);
113
$theme->set_rtl_mode(substr($type, -4) === '-rtl');
114
 
115
$themerev = theme_get_revision();
116
$currentthemesubrev = theme_get_sub_revision_for_theme($themename);
117
 
118
$cache = true;
119
// If the client is requesting a revision that doesn't match both
120
// the global theme revision and the theme specific revision then
121
// tell the browser not to cache this style sheet because it's
122
// likely being regenerated.
123
if ($themerev <= 0 or $themerev != $rev or $themesubrev != $currentthemesubrev) {
124
    $rev = $themerev;
125
    $themesubrev = $currentthemesubrev;
126
    $cache = false;
127
 
128
    $candidatedir = "$CFG->localcachedir/theme/$rev/$themename/css";
129
    $candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg);
130
    $etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg);
131
}
132
 
133
make_localcache_directory('theme', false);
134
 
135
if ($type === 'editor' || $type === 'editor-rtl') {
136
    $csscontent = $theme->get_css_content_editor();
137
 
138
    if ($cache) {
139
        css_store_css($theme, $candidatesheet, $csscontent);
140
        css_send_cached_css($candidatesheet, $etag);
141
    } else {
142
        css_send_uncached_css($csscontent);
143
    }
144
 
145
}
146
 
147
if (($fallbacksheet = theme_styles_fallback_content($theme)) && !$theme->has_css_cached_content()) {
148
    // The theme is not yet available and a fallback is available.
149
    // Return the fallback immediately, specifying the Content-Length, then generate in the background.
150
    $css = file_get_contents($fallbacksheet);
151
    css_send_temporary_css($css);
152
 
153
    // The fallback content has now been sent.
154
    // There will be an attempt to generate the content, but it should not be served.
155
    // The Content-Length above means that the client will disregard it anyway.
156
    $sendaftergeneration = false;
157
 
158
    // There may be another client currently holding a lock and generating the stylesheet.
159
    // Use a very low lock timeout as the connection will be ended immediately afterwards.
160
    $locktimeout = 1;
161
} else {
162
    // There is no fallback content to be issued here, therefore the generated content must be output.
163
    $sendaftergeneration = true;
164
 
165
    // Use a realistic lock timeout as the intention is to avoid lock contention.
166
    $locktimeout = rand(90, 120);
167
}
168
 
169
// Attempt to fetch the lock.
170
$lockfactory = \core\lock\lock_config::get_lock_factory('core_theme_get_css_content');
171
$lock = $lockfactory->get_lock($themename, $locktimeout);
172
 
173
if ($sendaftergeneration || $lock) {
174
    // Either the lock was successful, or the lock was unsuccessful but the content *must* be sent.
175
 
176
    // The content does not exist locally.
177
    // Generate and save it.
178
    $candidatesheet = theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidatedir);
179
 
180
    if ($lock) {
181
        $lock->release();
182
    }
183
 
184
    if ($sendaftergeneration) {
185
        if (!$cache) {
186
            // Do not pollute browser caches if invalid revision requested,
187
            // let's ignore legacy IE breakage here too.
188
            css_send_uncached_css(file_get_contents($candidatesheet));
189
 
190
        } else {
191
            // Real browsers - this is the expected result!
192
            css_send_cached_css($candidatesheet, $etag);
193
        }
194
    }
195
}
196
 
197
/**
198
 * Generate the theme CSS and store it.
199
 *
200
 * @param   theme_config    $theme The theme to be generated
201
 * @param   int             $rev The theme revision
202
 * @param   int             $themesubrev The theme sub-revision
203
 * @param   string          $candidatedir The directory that it should be stored in
204
 * @return  string          The path that the primary CSS was written to
205
 */
206
function theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidatedir) {
207
    global $CFG;
208
    require_once("{$CFG->libdir}/filelib.php");
209
 
210
    // Generate the content first.
211
    if (!$csscontent = $theme->get_css_cached_content()) {
212
        $csscontent = $theme->get_css_content();
213
        $theme->set_css_content_cache($csscontent);
214
    }
215
 
216
    if ($theme->get_rtl_mode()) {
217
        $type = "all-rtl";
218
    } else {
219
        $type = "all";
220
    }
221
 
222
    // Determine the candidatesheet path.
223
    $candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $theme->use_svg_icons());
224
 
225
    // Store the CSS.
226
    css_store_css($theme, $candidatesheet, $csscontent);
227
 
228
    // Store the fallback CSS in the temp directory.
229
    // This file is used as a fallback when waiting for a theme to compile and is not versioned in any way.
230
    $fallbacksheet = make_temp_directory("theme/{$theme->name}")
231
        . "/"
232
        . theme_styles_get_filename($type, 0, $theme->use_svg_icons());
233
    css_store_css($theme, $fallbacksheet, $csscontent);
234
 
235
    // Delete older revisions from localcache.
236
    $themecachedirs = glob("{$CFG->localcachedir}/theme/*", GLOB_ONLYDIR);
237
    foreach ($themecachedirs as $localcachedir) {
238
        $cachedrev = [];
239
        preg_match("/\/theme\/([0-9]+)$/", $localcachedir, $cachedrev);
240
        $cachedrev = isset($cachedrev[1]) ? intval($cachedrev[1]) : 0;
241
        if ($cachedrev > 0 && $cachedrev < $rev) {
242
            fulldelete($localcachedir);
243
        }
244
    }
245
 
246
    // Delete older theme subrevision CSS from localcache.
247
    $subrevfiles = glob("{$CFG->localcachedir}/theme/{$rev}/{$theme->name}/css/*.css");
248
    foreach ($subrevfiles as $subrevfile) {
249
        $cachedsubrev = [];
250
        preg_match("/_([0-9]+)\.([0-9]+\.)?css$/", $subrevfile, $cachedsubrev);
251
        $cachedsubrev = isset($cachedsubrev[1]) ? intval($cachedsubrev[1]) : 0;
252
        if ($cachedsubrev > 0 && $cachedsubrev < $themesubrev) {
253
            fulldelete($subrevfile);
254
        }
255
    }
256
 
257
    return $candidatesheet;
258
}
259
 
260
/**
261
 * Fetch the preferred fallback content location if available.
262
 *
263
 * @param   theme_config    $theme The theme to be generated
264
 * @return  string          The path to the fallback sheet on disk
265
 */
266
function theme_styles_fallback_content($theme) {
267
    global $CFG;
268
 
269
    if (!$theme->usefallback) {
270
        // This theme does not support fallbacks.
271
        return false;
272
    }
273
 
274
    $type = $theme->get_rtl_mode() ? 'all-rtl' : 'all';
275
    $filename = theme_styles_get_filename($type);
276
 
277
    $fallbacksheet = "{$CFG->tempdir}/theme/{$theme->name}/{$filename}";
278
    if (file_exists($fallbacksheet)) {
279
        return $fallbacksheet;
280
    }
281
 
282
    return false;
283
}
284
 
285
/**
286
 * Get the filename for the specified configuration.
287
 *
288
 * @param   string  $type The requested sheet type
289
 * @param   int     $themesubrev The theme sub-revision
290
 * @param   bool    $usesvg Whether SVGs are allowed
291
 * @return  string  The filename for this sheet
292
 */
293
function theme_styles_get_filename($type, $themesubrev = 0, $usesvg = true) {
294
    $filename = $type;
295
    $filename .= ($themesubrev > 0) ? "_{$themesubrev}" : '';
296
    $filename .= $usesvg ? '' : '-nosvg';
297
 
298
    return "{$filename}.css";
299
}
300
 
301
/**
302
 * Determine the correct etag for the specified configuration.
303
 *
304
 * @param   string  $themename The name of the theme
305
 * @param   int     $rev The revision number
306
 * @param   string  $type The requested sheet type
307
 * @param   int     $themesubrev The theme sub-revision
308
 * @param   bool    $usesvg Whether SVGs are allowed
309
 * @return  string  The etag to use for this request
310
 */
311
function theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg) {
312
    $etag = [$rev, $themename, $type, $themesubrev];
313
 
314
    if (!$usesvg) {
315
        $etag[] = 'nosvg';
316
    }
317
 
318
    return sha1(implode('/', $etag));
319
}