summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Config/Common.pm16
-rw-r--r--Bugzilla/Config/Core.pm9
-rw-r--r--Bugzilla/Update.pm196
-rwxr-xr-xchecksetup.pl11
-rwxr-xr-xindex.cgi15
-rw-r--r--skins/standard/index.css13
-rw-r--r--template/en/default/admin/params/core.html.tmpl17
-rw-r--r--template/en/default/index.html.tmpl35
8 files changed, 305 insertions, 7 deletions
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 <LpSolit@gmail.com>
+
+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<get_notifications()>
+
+ 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 <jake@bugzilla.org>
-#
+# Frédéric Buclin <LpSolit@gmail.com>
###############################################################################
# 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 => "<p>$terms.Bugzilla can inform you when a new release is available. " _
+ "The notification will appear on the $terms.Bugzilla homepage, " _
+ "for administrators only.</p>" _
+ "<ul><li>'development_snapshot' notifies you about the latest " _
+ "release on the trunk, i.e. development snapshots.</li>" _
+ "<li>'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.</li>" _
+ "<li>'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.</li>" _
+ "<li>'disabled' will never notify you about new releases and no " _
+ "connection will be established to a remote server.</li></ul>" }
%]
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() {
//-->
</script>
+[% IF release %]
+ <div id="new_release">
+ [% IF release.data %]
+ [% IF release.deprecated %]
+ <p>[% terms.Bugzilla %] [%+ release.deprecated FILTER html %] is no longer
+ supported. You are highly encouraged to upgrade in order to keep your
+ system secure.</p>
+ [% END %]
+
+ <p>A new [% terms.Bugzilla %] version ([% release.data.latest_ver FILTER html %])
+ is available at
+ <a href="[% release.data.url FILTER html %]">[% release.data.url FILTER html %]</a>.<br>
+ Release date: [% release.data.date FILTER html %]</p>
+
+ <p class="notice">This message is only shown to logged in users with admin privs.
+ You can configure this notification from the
+ <a href="editparams.cgi?section=core#upgrade_notification">Parameters</a> page.</p>
+ [% ELSIF release.error == "no_update" %]
+ <p>The local XML file '[% release.xml_file FILTER html %]' cannot be updated.
+ Please make sure the web server can edit this file.</p>
+ [% ELSIF release.error == "no_access" %]
+ <p>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.</p>
+ [% ELSIF release.error == "corrupted" %]
+ <p>The local XML file '[% release.xml_file FILTER html %]' has an invalid XML format.
+ Please delete it and try accessing this page again.</p>
+ [% ELSIF release.error == "unknown_parameter" %]
+ <p>'[% Param("upgrade_notification") FILTER html %]' is not a valid notification
+ parameter. Please check this parameter in the
+ <a href="editparams.cgi?section=core#upgrade_notification">Parameters</a> page.</p>
+ [% END %]
+ </div>
+[% END %]
<div id="page-index">
<div class="intro"></div>