From 3e81855fc65b49d05bf0dc0d1a1cf1b1b044f8f4 Mon Sep 17 00:00:00 2001 From: "lpsolit%gmail.com" <> Date: Tue, 13 Jun 2006 00:13:34 +0000 Subject: Bug 330487: Automatic Update Notification for Bugzilla - Patch by Frédéric Buclin r=mkanat a=justdave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bugzilla/Config/Common.pm | 16 +- Bugzilla/Config/Core.pm | 9 ++ Bugzilla/Update.pm | 196 ++++++++++++++++++++++++ checksetup.pl | 11 ++ index.cgi | 15 +- skins/standard/index.css | 13 ++ template/en/default/admin/params/core.html.tmpl | 17 +- template/en/default/index.html.tmpl | 35 ++++- 8 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 Bugzilla/Update.pm diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm index 817e7b5be..521542707 100644 --- a/Bugzilla/Config/Common.pm +++ b/Bugzilla/Config/Common.pm @@ -64,7 +64,7 @@ use base qw(Exporter); check_sslbase check_priority check_severity check_platform check_opsys check_shadowdb check_urlbase check_webdotbase check_netmask check_user_verify_class check_image_converter - check_languages check_mail_delivery_method + check_languages check_mail_delivery_method check_notification ); # Checking functions for the various values @@ -303,6 +303,20 @@ sub check_mail_delivery_method { return ""; } +sub check_notification { + my $option = shift; + my @current_version = + ($Bugzilla::Config::VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/); + if ($current_version[1] % 2 && $option eq 'stable_branch_release') { + return "You are currently running a development snapshot, and so your " . + "installation is not based on a branch. If you want to be notified " . + "about the next stable release, you should select " . + "'latest_stable_release' instead"; + } + return ""; +} + + # OK, here are the parameter definitions themselves. # # Each definition is a hash with keys: diff --git a/Bugzilla/Config/Core.pm b/Bugzilla/Config/Core.pm index 738c28fe2..3e1c66623 100644 --- a/Bugzilla/Config/Core.pm +++ b/Bugzilla/Config/Core.pm @@ -103,6 +103,15 @@ sub get_param_list { name => 'shutdownhtml', type => 'l', default => '' + }, + + { + name => 'upgrade_notification', + type => 's', + choices => ['development_snapshot', 'latest_stable_release', + 'stable_branch_release', 'disabled'], + default => 'latest_stable_release', + checker => \&check_notification } ); return @param_list; } diff --git a/Bugzilla/Update.pm b/Bugzilla/Update.pm new file mode 100644 index 000000000..a672bd8bb --- /dev/null +++ b/Bugzilla/Update.pm @@ -0,0 +1,196 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# Contributor(s): Frédéric Buclin + +package Bugzilla::Update; + +use strict; + +use Bugzilla::Config qw($datadir); + +use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml'; +use constant LOCAL_FILE => "/bugzilla-update.xml"; # Relative to $datadir. +use constant TIME_INTERVAL => 604800; # Default is one week, in seconds. +use constant TIMEOUT => 5; # Number of seconds before timeout. + +# Look for new releases and notify logged in administrators about them. +sub get_notifications { + return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled'); + + # If the XML::Twig module is missing, we won't be able to parse + # the XML file. So there is no need to go further. + eval("require XML::Twig"); + return if $@; + + my $local_file = $datadir . LOCAL_FILE; + # Update the local XML file if this one doesn't exist or if + # the last modification time (stat[9]) is older than TIME_INTERVAL. + if (!-e $local_file || (time() - (stat($local_file))[9] > TIME_INTERVAL)) { + # Are we sure we didn't try to refresh this file already + # but we failed because we cannot modify its timestamp? + my $can_alter = 1; + if (-e $local_file) { + # Try to alter its last modification time. + $can_alter = utime(undef, undef, $local_file); + } + if ($can_alter) { + _synchronize_data(); + } + else { + return {'error' => 'no_update', 'xml_file' => $local_file}; + } + } + + # If we cannot access the local XML file, ignore it. + return {'error' => 'no_access', 'xml_file' => $local_file} unless (-r $local_file); + + my $twig = XML::Twig->new(); + $twig->safe_parsefile($local_file); + # If the XML file is invalid, return. + return {'error' => 'corrupted', 'xml_file' => $local_file} if $@; + my $root = $twig->root; + + my @releases; + foreach my $branch ($root->children('branch')) { + my $release = { + 'branch_ver' => $branch->{'att'}->{'id'}, + 'latest_ver' => $branch->{'att'}->{'vid'}, + 'status' => $branch->{'att'}->{'status'}, + 'url' => $branch->{'att'}->{'url'}, + 'date' => $branch->{'att'}->{'date'} + }; + push(@releases, $release); + } + + # On which branch is the current installation running? + my @current_version = + ($Bugzilla::Config::VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/); + + my @release; + if (Bugzilla->params->{'upgrade_notification'} eq 'development_snapshot') { + @release = grep {$_->{'status'} eq 'development'} @releases; + } + elsif (Bugzilla->params->{'upgrade_notification'} eq 'latest_stable_release') { + @release = grep {$_->{'status'} eq 'stable'} @releases; + } + elsif (Bugzilla->params->{'upgrade_notification'} eq 'stable_branch_release') { + # We want the latest stable version for the current branch. + # If we are running a development snapshot, we won't match anything. + my $branch_version = $current_version[0] . '.' . $current_version[1]; + + # We do a string comparison instead of a numerical one, because + # e.g. 2.2 == 2.20, but 2.2 ne 2.20 (and 2.2 is indeed much older). + @release = grep {$_->{'branch_ver'} eq $branch_version} @releases; + + # If the branch is now closed, we should strongly suggest + # to upgrade to the latest stable release available. + if (scalar(@release) && $release[0]->{'status'} eq 'closed') { + @release = grep {$_->{'status'} eq 'stable'} @releases; + return {'data' => $release[0], 'deprecated' => $branch_version}; + } + } + else { + # Unknown parameter. + return {'error' => 'unknown_parameter'}; + } + + # Return if no new release is available. + return unless scalar(@release); + + # Only notify the administrator if the latest version available + # is newer than the current one. + my @new_version = + ($release[0]->{'latest_ver'} =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/); + + # We convert release candidates 'rc' to integers (rc ? 0 : 1) in order + # to compare versions easily. + $current_version[2] = ($current_version[2] && $current_version[2] eq 'rc') ? 0 : 1; + $new_version[2] = ($new_version[2] && $new_version[2] eq 'rc') ? 0 : 1; + + my $is_newer = _compare_versions(\@current_version, \@new_version); + return ($is_newer == 1) ? {'data' => $release[0]} : undef; +} + +sub _synchronize_data { + eval("require LWP::UserAgent"); + return if $@; + + my $local_file = $datadir . LOCAL_FILE; + + my $ua = LWP::UserAgent->new(); + $ua->timeout(TIMEOUT); + $ua->protocols_allowed(['http', 'https']); + $ua->mirror(REMOTE_FILE, $local_file); + + # $ua->mirror() forces the modification time of the local XML file + # to match the modification time of the remote one. + # So we have to update it manually to reflect that a newer version + # of the file has effectively been requested. This will avoid + # any new download for the next TIME_INTERVAL. + utime(undef, undef, $local_file); +} + +sub _compare_versions { + my ($old_ver, $new_ver) = @_; + while (scalar(@$old_ver) && scalar(@$new_ver)) { + my $old = shift(@$old_ver); + my $new = shift(@$new_ver); + return $new <=> $old if ($new <=> $old); + } + return scalar(@$new_ver) <=> scalar(@$old_ver); + +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Update - Update routines for Bugzilla + +=head1 SYNOPSIS + + use Bugzilla::Update; + + # Get information about new releases + my $new_release = Bugzilla::Update::get_notifications(); + +=head1 DESCRIPTION + +This module contains all required routines to notify you +about new releases. It downloads an XML file from bugzilla.org +and parses it, in order to display information based on your +preferences. Absolutely no information about the Bugzilla version +you are running is sent to bugzilla.org. + +=head1 FUNCTIONS + +=over + +=item C + + Description: This function informs you about new releases, if any. + + Params: None. + + Returns: On success, a reference to a hash with data about + new releases, if any. + On failure, a reference to a hash with the reason + of the failure and the name of the unusable XML file. + +=back + +=cut diff --git a/checksetup.pl b/checksetup.pl index 23c52b221..7e7f7b4e1 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -355,6 +355,7 @@ my %ppm_modules = ( 'Mail::Base64' => 'MIME-Base64', 'MIME::Tools' => 'MIME-Tools', 'XML::Twig' => 'XML-Twig', + 'LWP::UserAgent' => 'LWP-UserAgent' ); sub install_command { @@ -392,6 +393,7 @@ print "\nThe following Perl modules are optional:\n" unless $silent; my $gd = have_vers("GD","1.20"); my $chartbase = have_vers("Chart::Base","1.0"); my $xmlparser = have_vers("XML::Twig",0); +my $lwp_ua = have_vers("LWP::UserAgent",0); my $gdgraph = have_vers("GD::Graph",0); my $gdtextalign = have_vers("GD::Text::Align",0); my $patchreader = have_vers("PatchReader","0.9.4"); @@ -420,6 +422,12 @@ if (!$xmlparser && !$silent) { "the XML::Twig module by running (as $::root):\n\n", " " . install_command("XML::Twig") . "\n\n"; } +if (!$lwp_ua && !$silent) { + print "If you want to use the automatic update notification feature\n", + "you will need to install the LWP::UserAgent module by running\n", + "(as $::root):\n\n", + " " . install_command("LWP::UserAgent") . "\n\n"; +} if (!$imagemagick && !$silent) { print "If you want to convert BMP image attachments to PNG to conserve\n", "disk space, you will need to install the ImageMagick application\n", @@ -1420,6 +1428,8 @@ if ($^O !~ /MSWin32/i) { fixPerms($webdotdir, $<, $webservergid, 007, 1); fixPerms("$webdotdir/.htaccess", $<, $webservergid, 027); fixPerms("$datadir/params", $<, $webservergid, 017); + # The web server must be the owner of bugzilla-update.xml. + fixPerms("$datadir/bugzilla-update.xml", $webservergid, $webservergid, 017); fixPerms('*', $<, $webservergid, 027); fixPerms('Bugzilla', $<, $webservergid, 027, 1); fixPerms($templatedir, $<, $webservergid, 027, 1); @@ -1447,6 +1457,7 @@ if ($^O !~ /MSWin32/i) { chmod 01777, $webdotdir; fixPerms("$webdotdir/.htaccess", $<, $gid, 022); fixPerms("$datadir/params", $<, $gid, 011); + fixPerms("$datadir/bugzilla-update.xml", $gid, $gid, 011); fixPerms('*', $<, $gid, 022); fixPerms('Bugzilla', $<, $gid, 022, 1); fixPerms($templatedir, $<, $gid, 022, 1); diff --git a/index.cgi b/index.cgi index a35be0726..27f26083d 100755 --- a/index.cgi +++ b/index.cgi @@ -19,7 +19,7 @@ # Rights Reserved. # # Contributor(s): Jacob Steenhagen -# +# Frédéric Buclin ############################################################################### # Script Initialization @@ -32,9 +32,11 @@ use strict; use lib "."; require "globals.pl"; -# Check whether or not the user is logged in use Bugzilla::Constants; -Bugzilla->login(LOGIN_OPTIONAL); +use Bugzilla::Update; + +# Check whether or not the user is logged in +my $user = Bugzilla->login(LOGIN_OPTIONAL); ############################################################################### # Main Body Execution @@ -48,10 +50,15 @@ if (Param('sslbase') ne '' and Param('ssl') ne 'never') { } my $template = Bugzilla->template; +my $vars = {}; # Return the appropriate HTTP response headers. print $cgi->header(); +if ($user->in_group('admin')) { + $vars->{'release'} = Bugzilla::Update::get_notifications(); +} + # Generate and return the UI (HTML page) from the appropriate template. -$template->process("index.html.tmpl") +$template->process("index.html.tmpl", $vars) || ThrowTemplateError($template->error()); diff --git a/skins/standard/index.css b/skins/standard/index.css index fe801e1d3..b76361005 100644 --- a/skins/standard/index.css +++ b/skins/standard/index.css @@ -66,4 +66,17 @@ padding: 1em 0; } + #new_release + { + border: 2px solid red; + padding: 0.5em 1em; + margin: 1em; + font-weight: bold; + } + + #new_release .notice + { + font-size: 80%; + font-weight: normal; + } /* index page (end) */ diff --git a/template/en/default/admin/params/core.html.tmpl b/template/en/default/admin/params/core.html.tmpl index bc2dca564..397d1c702 100644 --- a/template/en/default/admin/params/core.html.tmpl +++ b/template/en/default/admin/params/core.html.tmpl @@ -70,5 +70,20 @@ shutdownhtml => "If this field is non-empty, then $terms.Bugzilla will be completely " _ "disabled and this text will be displayed instead of all the " _ - "$terms.Bugzilla pages." } + "$terms.Bugzilla pages.", + + upgrade_notification => "

$terms.Bugzilla can inform you when a new release is available. " _ + "The notification will appear on the $terms.Bugzilla homepage, " _ + "for administrators only.

" _ + "
  • 'development_snapshot' notifies you about the latest " _ + "release on the trunk, i.e. development snapshots.
  • " _ + "
  • 'latest_stable_release' notifies you about the most recent release " _ + "available on the most recent stable branch. This branch may be " _ + "different from the branch your installation is based on.
  • " _ + "
  • 'stable_branch_release' notifies you only about new releases " _ + "corresponding to the branch your installation is based on. " _ + "If you are running a release candidate, you will get " _ + "a notification for newer release candidates too.
  • " _ + "
  • 'disabled' will never notify you about new releases and no " _ + "connection will be established to a remote server.
" } %] diff --git a/template/en/default/index.html.tmpl b/template/en/default/index.html.tmpl index c8474494d..1dd81dac2 100644 --- a/template/en/default/index.html.tmpl +++ b/template/en/default/index.html.tmpl @@ -23,7 +23,7 @@ #%] [%# INTERFACE: - # This template has no interface. + # release: a hash containing data about new releases, if any. #%] [% PROCESS global/variables.none.tmpl %] @@ -56,6 +56,39 @@ function addSidebar() { //--> +[% IF release %] +
+ [% IF release.data %] + [% IF release.deprecated %] +

[% terms.Bugzilla %] [%+ release.deprecated FILTER html %] is no longer + supported. You are highly encouraged to upgrade in order to keep your + system secure.

+ [% END %] + +

A new [% terms.Bugzilla %] version ([% release.data.latest_ver FILTER html %]) + is available at + [% release.data.url FILTER html %].
+ Release date: [% release.data.date FILTER html %]

+ +

This message is only shown to logged in users with admin privs. + You can configure this notification from the + Parameters page.

+ [% ELSIF release.error == "no_update" %] +

The local XML file '[% release.xml_file FILTER html %]' cannot be updated. + Please make sure the web server can edit this file.

+ [% ELSIF release.error == "no_access" %] +

The local XML file '[% release.xml_file FILTER html %]' cannot be read. + Please make sure this file exists and has the correct rights set on it.

+ [% ELSIF release.error == "corrupted" %] +

The local XML file '[% release.xml_file FILTER html %]' has an invalid XML format. + Please delete it and try accessing this page again.

+ [% ELSIF release.error == "unknown_parameter" %] +

'[% Param("upgrade_notification") FILTER html %]' is not a valid notification + parameter. Please check this parameter in the + Parameters page.

+ [% END %] +
+[% END %]
-- cgit v1.2.3-24-g4f1b