/theme/styles.php
PHP | 312 lines | 147 code | 50 blank | 115 comment | 35 complexity | 826be079c3421f0415036ba3cd49e02a MD5 | raw file
- <?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'])) {
- 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($type === 'all-rtl' ? true : false);
- $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') {
- $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));
- }