]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 330487: Automatic Update Notification for Bugzilla - Patch by Frédéric Buclin...
authorlpsolit%gmail.com <>
Tue, 13 Jun 2006 00:13:34 +0000 (00:13 +0000)
committerlpsolit%gmail.com <>
Tue, 13 Jun 2006 00:13:34 +0000 (00:13 +0000)
Bugzilla/Config/Common.pm
Bugzilla/Config/Core.pm
Bugzilla/Update.pm [new file with mode: 0644]
checksetup.pl
index.cgi
skins/standard/index.css
template/en/default/admin/params/core.html.tmpl
template/en/default/index.html.tmpl

index 817e7b5becca36b54a05fcd0ff05a90e4302f7db..521542707bc2e4120cd1e13973bed7c065b3e210 100644 (file)
@@ -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:
index 738c28fe20dce49f89e177feef9b456ffaf71407..3e1c666231bca6a53a334fee9c1220924b227987 100644 (file)
@@ -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 (file)
index 0000000..a672bd8
--- /dev/null
@@ -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
index 23c52b221f40d4006cda461c3b45bf8e0890469e..7e7f7b4e19470c50ec75a54e38f87a68bd0a4a1d 100755 (executable)
@@ -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);
index a35be0726659b2c2e83a34e600ecd44ba41e626c..27f26083d2e1663722b4fc4f6981ffe5b1dd232b 100755 (executable)
--- 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());
index fe801e1d3b5de3f85bb6134afe58a9ec7eec6bbc..b76361005918d232810e0b9ee507dc2fdad37f66 100644 (file)
         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) */
index bc2dca564955625d804e53add203e14060354300..397d1c70285b9298e426ad68df25ea57d59b7d5b 100644 (file)
 
   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>" }
 %]
index c8474494d3b81e1d08bb776bbc30b6eb9bbbfa09..1dd81dac28281f54bd4ca4ed7ee4a6bcb1b1ba33 100644 (file)
@@ -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>