summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorMax Kanat-Alexander <mkanat@bugzilla.org>2010-07-21 05:34:41 +0200
committerMax Kanat-Alexander <mkanat@bugzilla.org>2010-07-21 05:34:41 +0200
commit10f4954b4530a67d88c66ba8e397ee81487f4752 (patch)
tree7682e9b1974c208cb98af3e6e52284f9199a2ae1 /Bugzilla
parent0d280b9011568abf2c5f95a33fd20195b91528d9 (diff)
downloadbugzilla-10f4954b4530a67d88c66ba8e397ee81487f4752.tar.gz
bugzilla-10f4954b4530a67d88c66ba8e397ee81487f4752.tar.xz
Bug 428313: Properly expire the browser's CSS and JS cache when there
are new versions of those files. This also eliminates single-file skins and should also allow Extensions to have skins. r=glob, a=mkanat
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Install/Filesystem.pm59
-rw-r--r--Bugzilla/Template.pm120
2 files changed, 159 insertions, 20 deletions
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index db55576a4..20bd021ef 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -284,20 +284,6 @@ sub FILESYSTEM {
contents => '' },
);
- # Each standard stylesheet has an associated custom stylesheet that
- # we create. Also, we create placeholders for standard stylesheets
- # for contrib skins which don't provide them themselves.
- foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) {
- next if basename($skin_dir) =~ /^cvs$/i;
- foreach my $base_css (<$skinsdir/standard/*.css>) {
- _add_custom_css($skin_dir, basename($base_css), \%create_files);
- }
- foreach my $dir_css (<$skinsdir/standard/*/*.css>) {
- $dir_css =~ s{.+?([^/]+/[^/]+)$}{$1};
- _add_custom_css($skin_dir, $dir_css, \%create_files);
- }
- }
-
# Because checksetup controls the creation of index.html separately
# from all other files, it gets its very own hash.
my %index_html = (
@@ -455,20 +441,53 @@ EOT
print "Removing duplicates directory...\n";
rmtree("$datadir/duplicates");
}
+
+ _remove_empty_css_files();
+ _convert_single_file_skins();
}
-# A simple helper for creating "empty" CSS files.
-sub _add_custom_css {
- my ($skin_dir, $path, $create_files) = @_;
- $create_files->{"$skin_dir/$path"} = { perms => WS_SERVE, contents => <<EOT
+sub _remove_empty_css_files {
+ my $skinsdir = bz_locations()->{'skinsdir'};
+ foreach my $css_file (glob("$skinsdir/custom/*.css"),
+ glob("$skinsdir/contrib/*/*.css"))
+ {
+ _remove_empty_css($css_file);
+ }
+}
+
+# A simple helper for the update code that removes "empty" CSS files.
+sub _remove_empty_css {
+ my ($file) = @_;
+ my $basename = basename($file);
+ my $empty_contents = <<EOT;
/*
- * Custom rules for $path.
+ * Custom rules for $basename.
* The rules you put here override rules in that stylesheet.
*/
EOT
+ if (length($empty_contents) == -s $file) {
+ open(my $fh, '<', $file) or warn "$file: $!";
+ my $file_contents;
+ { local $/; $file_contents = <$fh>; }
+ if ($file_contents eq $empty_contents) {
+ print install_string('file_remove', { name => $file }), "\n";
+ unlink $file or warn "$file: $!";
+ }
};
}
+# We used to allow a single css file in the skins/contrib/ directory
+# to be a whole skin.
+sub _convert_single_file_skins {
+ my $skinsdir = bz_locations()->{'skinsdir'};
+ foreach my $skin_file (glob "$skinsdir/contrib/*.css") {
+ my $dir_name = $skin_file;
+ $dir_name =~ s/\.css$//;
+ mkdir $dir_name or warn "$dir_name: $!";
+ _rename_file($skin_file, "$dir_name/global.css");
+ }
+}
+
sub create_htaccess {
_create_files(%{FILESYSTEM()->{htaccess}});
@@ -492,7 +511,7 @@ sub create_htaccess {
sub _rename_file {
my ($from, $to) = @_;
- print "Renaming $from to $to...\n";
+ print install_string('file_rename', { from => $from, to => $to }), "\n";
if (-e $to) {
warn "$to already exists, not moving\n";
}
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 2c2d402a3..ffd702e62 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -358,6 +358,121 @@ sub get_bug_link {
return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
}
+#####################
+# Header Generation #
+#####################
+
+# Returns the last modification time of a file, as an integer number of
+# seconds since the epoch.
+sub _mtime { return (stat($_[0]))[9] }
+
+sub mtime_filter {
+ my ($file_url) = @_;
+ my $cgi_path = bz_locations()->{'cgi_path'};
+ my $file_path = "$cgi_path/$file_url";
+ return "$file_url?" . _mtime($file_path);
+}
+
+# Set up the skin CSS cascade:
+#
+# 1. YUI CSS
+# 2. Standard Bugzilla stylesheet set (persistent)
+# 3. Standard Bugzilla stylesheet set (selectable)
+# 4. All third-party "skin" stylesheet sets (selectable)
+# 5. Page-specific styles
+# 6. Custom Bugzilla stylesheet set (persistent)
+#
+# "Selectable" skin file sets may be either preferred or alternate.
+# Exactly one is preferred, determined by the "skin" user preference.
+sub css_files {
+ my ($style_urls, $yui, $yui_css) = @_;
+
+ # global.css goes on every page, and so does IE-fixes.css.
+ my @requested_css = ('skins/standard/global.css', @$style_urls,
+ 'skins/standard/IE-fixes.css');
+
+ my @yui_required_css;
+ foreach my $yui_name (@$yui) {
+ next if !$yui_css->{$yui_name};
+ push(@yui_required_css, "js/yui/assets/skins/sam/$yui_name.css");
+ }
+ unshift(@requested_css, @yui_required_css);
+
+ my @css_sets = map { _css_link_set($_) } @requested_css;
+
+ my %by_type = (standard => [], alternate => {}, skin => [], custom => []);
+ foreach my $set (@css_sets) {
+ foreach my $key (keys %$set) {
+ if ($key eq 'alternate') {
+ foreach my $alternate_skin (keys %{ $set->{alternate} }) {
+ my $files = $by_type{alternate}->{$alternate_skin} ||= [];
+ push(@$files, $set->{alternate}->{$alternate_skin});
+ }
+ }
+ else {
+ push(@{ $by_type{$key} }, $set->{$key});
+ }
+ }
+ }
+
+ return \%by_type;
+}
+
+sub _css_link_set {
+ my ($file_name) = @_;
+
+ my $standard_mtime = _mtime($file_name);
+ my %set = (standard => $file_name . "?$standard_mtime");
+
+ # We use (^|/) to allow Extensions to use the skins system if they
+ # want.
+ if ($file_name !~ m{(^|/)skins/standard/}) {
+ return \%set;
+ }
+
+ my $user = Bugzilla->user;
+ my $cgi_path = bz_locations()->{'cgi_path'};
+ my $all_skins = $user->settings->{'skin'}->legal_values;
+ my %skin_urls;
+ foreach my $option (@$all_skins) {
+ next if $option eq 'standard';
+ my $skin_file_name = $file_name;
+ $skin_file_name =~ s{(^|/)skins/standard/}{skins/contrib/$option/};
+ if (my $mtime = _mtime("$cgi_path/$skin_file_name")) {
+ $skin_urls{$option} = $skin_file_name . "?$mtime";
+ }
+ }
+ $set{alternate} = \%skin_urls;
+
+ my $skin = $user->settings->{'skin'}->{'value'};
+ if ($skin ne 'standard' and defined $set{alternate}->{$skin}) {
+ $set{skin} = delete $set{alternate}->{$skin};
+ }
+
+ my $custom_file_name = $file_name;
+ $custom_file_name =~ s{(^|/)skins/standard/}{skins/custom/};
+ if (my $custom_mtime = _mtime("$cgi_path/$custom_file_name")) {
+ $set{custom} = $custom_file_name . "?$custom_mtime";
+ }
+
+ return \%set;
+}
+
+# YUI dependency resolution
+sub yui_resolve_deps {
+ my ($yui, $yui_deps) = @_;
+
+ my @yui_resolved;
+ foreach my $yui_name (@$yui) {
+ my $deps = $yui_deps->{$yui_name} || [];
+ foreach my $dep (reverse @$deps) {
+ push(@yui_resolved, $dep) if !grep { $_ eq $dep } @yui_resolved;
+ }
+ push(@yui_resolved, $yui_name) if !grep { $_ eq $yui_name } @yui_resolved;
+ }
+ return \@yui_resolved;
+}
+
###############################################################################
# Templatization Code
@@ -647,6 +762,8 @@ sub create {
html_light => \&Bugzilla::Util::html_light_quote,
email => \&Bugzilla::Util::email_filter,
+
+ mtime_url => \&mtime_filter,
# iCalendar contentline filter
ics => [ sub {
@@ -769,6 +886,9 @@ sub create {
Bugzilla->fields({ by_name => 1 });
return $cache->{template_bug_fields};
},
+
+ 'css_files' => \&css_files,
+ yui_resolve_deps => \&yui_resolve_deps,
# Whether or not keywords are enabled, in this Bugzilla.
'use_keywords' => sub { return Bugzilla::Keyword->any_exist; },