Skip to content

Commit cc24d56

Browse files
committed
[#235] Backport styles.php caching for moodle 4.1
1 parent e0fa4fe commit cc24d56

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

lib.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,39 @@ public function show_editor(?array $capabilities = ['moodle/course:manageactivit
10601060
* Moodle native lib/navigationlib.php calls this hook allowing us to override UI.
10611061
*/
10621062
function format_onetopic_before_http_headers() {
1063-
global $PAGE;
1064-
$PAGE->requires->css('/course/format/onetopic/styles.php');
1063+
global $PAGE, $COURSE;
1064+
1065+
// Don't require styles script if the course format isn't 'onetopic'.
1066+
if ($PAGE->course && isset($COURSE->id) && $COURSE->format == 'onetopic') {
1067+
1068+
// Check if site-wide tab styles are configured, if not, do nothing.
1069+
if (!get_config('format_onetopic', 'tabstyles')) {
1070+
return;
1071+
}
1072+
1073+
$revision = format_onetopic_get_tabstyles_revision();
1074+
$PAGE->requires->css(new \moodle_url('/course/format/onetopic/styles.php', [
1075+
'revision' => $revision,
1076+
]));
1077+
}
1078+
}
1079+
1080+
/**
1081+
* Generates an 8-character hash from the tab styles configuration.
1082+
* When styles change, the hash changes, creating a new URL that
1083+
* busts the cache.
1084+
*
1085+
* @return string
1086+
*/
1087+
function format_onetopic_get_tabstyles_revision(): string {
1088+
$tabstyles = get_config('format_onetopic', 'tabstyles');
1089+
1090+
if (empty($tabstyles)) {
1091+
return '0';
1092+
}
1093+
1094+
// Use first 8 chars of md5 hash as revision.
1095+
return substr(md5($tabstyles), 0, 8);
10651096
}
10661097

10671098
/**

styles.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
// phpcs:disable moodle.Files.RequireLogin.Missing
2929
require_once('../../../config.php');
3030

31-
@header('Content-Disposition: inline; filename="styles.php"');
32-
@header('Content-Type: text/css; charset=utf-8');
31+
$revision = optional_param('revision', 0, PARAM_ALPHANUM);
3332

3433
$withunits = ['font-size', 'line-height', 'margin', 'padding', 'border-width', 'border-radius'];
3534
$csscontent = '';
@@ -133,4 +132,37 @@
133132
}
134133
}
135134

135+
$csstabstyles = trim($csstabstyles);
136+
$etag = md5($csstabstyles . $revision);
137+
138+
// ETag validation: Return 304 if client has the current version. This will
139+
// preserve the existing cache for $cache_lifetime.
140+
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
141+
$clientetag = trim($_SERVER['HTTP_IF_NONE_MATCH'], '"');
142+
if ($clientetag === $etag) {
143+
header('HTTP/1.1 304 Not Modified');
144+
header('ETag: "' . $etag . '"');
145+
exit;
146+
}
147+
}
148+
149+
// Return empty response for edge cases (no styles configured & direct URL access).
150+
if (empty($csstabstyles)) {
151+
header('HTTP/1.1 304 Not Modified');
152+
header('Content-Length: 0');
153+
exit;
154+
}
155+
156+
// Cache for 1 year, this is safe due to cache busting via revision param.
157+
$cache_lifetime = 31536000;
158+
header('Cache-Control: public, max-age=' . $cache_lifetime . ', immutable');
159+
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $cache_lifetime) . ' GMT');
160+
header('ETag: "' . $etag . '"');
161+
162+
// Content headers.
163+
header('Content-Length: ' . strlen($csstabstyles));
164+
header('Content-Disposition: inline; filename="styles.php"');
165+
header('Content-Type: text/css; charset=utf-8');
166+
header('X-Content-Type-Options: nosniff');
167+
136168
echo $csstabstyles;

0 commit comments

Comments
 (0)