Commit e890de55 authored by Andrew Nicols's avatar Andrew Nicols
Browse files

Merge branch 'm33_MDL-57789_Add_Cache_Control_Immutable_Support' of https://github.com/scara/moodle

parents dd6b41a5 203c5bce
......@@ -284,15 +284,15 @@ function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $bu
* @param string $etag The revision to make sure we utilise any caches.
*/
function css_send_cached_css($csspath, $etag) {
// 60 days only - the revision may get incremented quite often.
$lifetime = 60*60*24*60;
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('Etag: "'.$etag.'"');
header('Content-Disposition: inline; filename="styles.php"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime);
header('Cache-Control: public, max-age='.$lifetime.', immutable');
header('Accept-Ranges: none');
header('Content-Type: text/css; charset=utf-8');
if (!min_enable_zlib_compression()) {
......@@ -310,15 +310,15 @@ function css_send_cached_css($csspath, $etag) {
* @param string $etag The revision to make sure we utilise any caches.
*/
function css_send_cached_css_content($csscontent, $etag) {
// 60 days only - the revision may get incremented quite often.
$lifetime = 60*60*24*60;
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('Etag: "'.$etag.'"');
header('Content-Disposition: inline; filename="styles.php"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime);
header('Cache-Control: public, max-age='.$lifetime.', immutable');
header('Accept-Ranges: none');
header('Content-Type: text/css; charset=utf-8');
if (!min_enable_zlib_compression()) {
......@@ -363,8 +363,8 @@ function css_send_uncached_css($css) {
* @param string $etag
*/
function css_send_unmodified($lastmodified, $etag) {
// 60 days only - the revision may get incremented quite often.
$lifetime = 60*60*24*60;
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('HTTP/1.1 304 Not Modified');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Cache-Control: public, max-age='.$lifetime);
......
......@@ -62,7 +62,7 @@ if ($allowcache) {
// Set it to expire a year later. Note that this means we should never get
// If-Modified-Since requests so there is no need to handle them specially.
header('Expires: ' . date('r', time() + 365 * 24 * 3600));
header('Cache-Control: max-age=' . 365 * 24 * 3600);
header('Cache-Control: max-age=' . 365 * 24 * 3600 . ', immutable');
// Pragma is set to no-cache by default so must be overridden.
header('Pragma:');
}
......
......@@ -2139,11 +2139,14 @@ function file_safe_save_content($content, $destination) {
* byteranges etc.
*
* @category files
* @param string $path Path of file on disk (including real filename), or actual content of file as string
* @param string|stored_file $path Path of file on disk (including real filename),
* or actual content of file as string,
* or stored_file object
* @param string $filename Filename to send
* @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
* @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
* @param bool $pathisstring If true (default false), $path is the content to send and not the pathname
* @param bool $pathisstring If true (default false), $path is the content to send and not the pathname.
* Forced to false when $path is a stored_file object.
* @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
* @param string $mimetype Include to specify the MIME type; leave blank to have it guess the type from $filename
* @param bool $dontdie - return control to caller afterwards. this is not recommended and only used for cleanup tasks.
......@@ -2152,6 +2155,7 @@ function file_safe_save_content($content, $destination) {
* and should not be reopened.
* @param array $options An array of options, currently accepts:
* - (string) cacheability: public, or private.
* - (string|null) immutable
* @return null script execution stopped unless $dontdie is true
*/
function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring=false, $forcedownload=false, $mimetype='',
......@@ -2166,6 +2170,10 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring
$lifetime = $CFG->filelifetime;
}
if (is_object($path)) {
$pathisstring = false;
}
\core\session\manager::write_close(); // Unlock session during file serving.
// Use given MIME type if specified, otherwise guess it.
......@@ -2188,6 +2196,14 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring
}
if ($lifetime > 0) {
$immutable = '';
if (!empty($options['immutable'])) {
$immutable = ', immutable';
// Overwrite lifetime accordingly:
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetimemin = 60 * 60 * 24 * 90;
$lifetime = max($lifetime, $lifetimemin);
}
$cacheability = ' public,';
if (!empty($options['cacheability']) && ($options['cacheability'] === 'public')) {
// This file must be cache-able by both browsers and proxies.
......@@ -2200,7 +2216,7 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring
$cacheability = ' private,';
}
$nobyteserving = false;
header('Cache-Control:'.$cacheability.' max-age='.$lifetime.', no-transform');
header('Cache-Control:'.$cacheability.' max-age='.$lifetime.', no-transform'.$immutable);
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
......@@ -2231,8 +2247,13 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring
$options = new stdClass();
$options->noclean = true;
$options->nocache = true; // temporary workaround for MDL-5136
$text = $pathisstring ? $path : implode('', file($path));
if (is_object($path)) {
$text = $path->get_content();
} else if ($pathisstring) {
$text = $path;
} else {
$text = implode('', file($path));
}
$output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
readstring_accel($output, $mimetype, false);
......@@ -2242,7 +2263,13 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring
$options = new stdClass();
$options->newlines = false;
$options->noclean = true;
$text = htmlentities($pathisstring ? $path : implode('', file($path)), ENT_QUOTES, 'UTF-8');
if (is_object($path)) {
$text = $path->get_content();
} else if ($pathisstring) {
$text = htmlentities($path, ENT_QUOTES, 'UTF-8');
} else {
$text = htmlentities(implode('', file($path)), ENT_QUOTES, 'UTF-8');
}
$output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
readstring_accel($output, $mimetype, false);
......@@ -2276,6 +2303,8 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring
* (string|null) cacheability - force the cacheability setting of the HTTP response, "private" or "public",
* when $lifetime is greater than 0. Cacheability defaults to "private" when logged in as other than guest; otherwise,
* defaults to "public".
* (string|null) immutable - set the immutable cache setting in the HTTP response, when served under HTTPS.
* Note: it's up to the consumer to set it properly i.e. when serving a "versioned" URL.
*
* @category files
* @param stored_file $stored_file local file object
......@@ -2343,101 +2372,18 @@ function send_stored_file($stored_file, $lifetime=null, $filter=0, $forcedownloa
die;
}
if ($dontdie) {
ignore_user_abort(true);
}
\core\session\manager::write_close(); // Unlock session during file serving.
$filename = is_null($filename) ? $stored_file->get_filename() : $filename;
$filename = is_null($filename) ? $stored_file->get_filename() : $filename;
// Use given MIME type if specified.
$mimetype = $stored_file->get_mimetype();
// Otherwise guess it.
if (!$mimetype || $mimetype === 'document/unknown') {
$mimetype = get_mimetype_for_sending($filename);
}
// if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup
if (core_useragent::is_ie()) {
$filename = rawurlencode($filename);
}
if ($forcedownload) {
header('Content-Disposition: attachment; filename="'.$filename.'"');
} else if ($mimetype !== 'application/x-shockwave-flash') {
// If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file
// as an upload and enforces security that may prevent the file from being loaded.
header('Content-Disposition: inline; filename="'.$filename.'"');
}
if ($lifetime > 0) {
$cacheability = ' public,';
if (!empty($options['cacheability']) && ($options['cacheability'] === 'public')) {
// This file must be cache-able by both browsers and proxies.
$cacheability = ' public,';
} else if (!empty($options['cacheability']) && ($options['cacheability'] === 'private')) {
// This file must be cache-able only by browsers.
$cacheability = ' private,';
} else if (isloggedin() and !isguestuser()) {
$cacheability = ' private,';
}
header('Cache-Control:'.$cacheability.' max-age='.$lifetime.', no-transform');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
} else { // Do not cache files in proxies and browsers
if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431.
header('Cache-Control: private, max-age=10, no-transform');
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
header('Pragma: ');
} else { //normal http - prevent caching at all cost
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
header('Pragma: no-cache');
}
}
// Allow cross-origin requests only for Web Services.
// This allow to receive requests done by Web Workers or webapps in different domains.
if (WS_SERVER) {
header('Access-Control-Allow-Origin: *');
}
if (empty($filter)) {
// send the contents
readfile_accel($stored_file, $mimetype, !$dontdie);
} else { // Try to put the file through filters
if ($mimetype == 'text/html' || $mimetype == 'application/xhtml+xml') {
$options = new stdClass();
$options->noclean = true;
$options->nocache = true; // temporary workaround for MDL-5136
$text = $stored_file->get_content();
$output = format_text($text, FORMAT_HTML, $options, $COURSE->id);
readstring_accel($output, $mimetype, false);
} else if (($mimetype == 'text/plain') and ($filter == 1)) {
// only filter text if filter all files is selected
$options = new stdClass();
$options->newlines = false;
$options->noclean = true;
$text = $stored_file->get_content();
$output = '<pre>'. format_text($text, FORMAT_MOODLE, $options, $COURSE->id) .'</pre>';
readstring_accel($output, $mimetype, false);
} else { // Just send it out raw
readfile_accel($stored_file, $mimetype, !$dontdie);
}
}
if ($dontdie) {
return;
}
die; //no more chars to output!!!
send_file($stored_file, $filename, $lifetime, $filter, false, $forcedownload, $mimetype, $dontdie, $options);
}
/**
......
......@@ -34,14 +34,15 @@ defined('MOODLE_INTERNAL') || die();
function js_send_cached($jspath, $etag, $filename = 'javascript.php') {
require(__DIR__ . '/xsendfilelib.php');
$lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('Etag: "'.$etag.'"');
header('Content-Disposition: inline; filename="'.$filename.'"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($jspath)) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime);
header('Cache-Control: public, max-age='.$lifetime.', immutable');
header('Accept-Ranges: none');
header('Content-Type: application/javascript; charset=utf-8');
......@@ -81,7 +82,8 @@ function js_send_uncached($js, $filename = 'javascript.php') {
* @param string $etag
*/
function js_send_unmodified($lastmodified, $etag) {
$lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('HTTP/1.1 304 Not Modified');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Cache-Control: public, max-age='.$lifetime);
......
......@@ -52,6 +52,7 @@ information provided here is intended especially for developers.
- the following "<element_string>" exists:
* get_user_capability_course() now has an additional parameter 'limit'. This can be used to return a set number of records with
the submitted capability. The parameter 'fieldsexceptid' will now accept context fields which can be used for preloading.
* The caching option 'immutable' has been added to send_stored_file() and send_file().
=== 3.2 ===
......
......@@ -106,7 +106,8 @@ if ($rev > 0) {
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 parameter.
$lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often.
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('HTTP/1.1 304 Not Modified');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Cache-Control: public, max-age='.$lifetime);
......@@ -173,14 +174,15 @@ function send_cached_font($fontpath, $etag, $font, $mimetype) {
global $CFG;
require("$CFG->dirroot/lib/xsendfilelib.php");
$lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often.
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('Etag: "'.$etag.'"');
header('Content-Disposition: inline; filename="'.$font.'"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($fontpath)) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime);
header('Cache-Control: public, max-age='.$lifetime.', immutable');
header('Accept-Ranges: none');
header('Content-Type: '.$mimetype);
header('Content-Length: '.filesize($fontpath));
......
......@@ -105,9 +105,10 @@ if ($rev > 0) {
}
if ($cacheimage) {
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 parameter
$lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
// We do not actually need to verify the etag value because our files
// never change in cache because we increment the rev parameter.
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
$mimetype = get_contenttype_from_ext($ext);
header('HTTP/1.1 304 Not Modified');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
......@@ -219,7 +220,8 @@ function send_cached_image($imagepath, $etag) {
global $CFG;
require("$CFG->dirroot/lib/xsendfilelib.php");
$lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
$pathinfo = pathinfo($imagepath);
$imagename = $pathinfo['filename'].'.'.$pathinfo['extension'];
......@@ -230,7 +232,7 @@ function send_cached_image($imagepath, $etag) {
header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($imagepath)) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime.', no-transform');
header('Cache-Control: public, max-age='.$lifetime.', no-transform, immutable');
header('Accept-Ranges: none');
header('Content-Type: '.$mimetype);
header('Content-Length: '.filesize($imagepath));
......
......@@ -76,7 +76,8 @@ if (!$file or is_dir($file)) {
}
$etag = sha1("$component/$path");
$lifetime = 60*60*24*120; // 120 days should be enough.
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
$pathinfo = pathinfo($path);
if (empty($pathinfo['extension'])) {
......@@ -125,7 +126,7 @@ header('Content-Disposition: inline; filename="'.$filename.'"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($file)) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime);
header('Cache-Control: public, max-age='.$lifetime.', immutable');
header('Accept-Ranges: none');
header('Content-Type: '.$mimetype);
......
......@@ -377,7 +377,7 @@ function combo_send_cached($content, $mimetype, $etag, $lastmodified) {
header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime);
header('Cache-Control: public, max-age='.$lifetime.', immutable');
header('Accept-Ranges: none');
header('Content-Type: '.$mimetype);
header('Etag: "'.$etag.'"');
......
......@@ -123,7 +123,7 @@ function yui_image_cached($imagepath, $imagename, $mimetype, $etag) {
header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($imagepath)) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age=315360000, no-transform');
header('Cache-Control: public, max-age='.$lifetime.', no-transform, immutable');
header('Accept-Ranges: none');
header('Content-Type: '.$mimetype);
header('Content-Length: '.filesize($imagepath));
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment