diff options
author | Max Kanat-Alexander <mkanat@bugzilla.org> | 2010-07-21 05:34:41 +0200 |
---|---|---|
committer | Max Kanat-Alexander <mkanat@bugzilla.org> | 2010-07-21 05:34:41 +0200 |
commit | 10f4954b4530a67d88c66ba8e397ee81487f4752 (patch) | |
tree | 7682e9b1974c208cb98af3e6e52284f9199a2ae1 /Bugzilla | |
parent | 0d280b9011568abf2c5f95a33fd20195b91528d9 (diff) | |
download | bugzilla-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.pm | 59 | ||||
-rw-r--r-- | Bugzilla/Template.pm | 120 |
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; }, |