]> git.donarmstrong.com Git - debbugs.git/blobdiff - cgi/pkgreport.cgi
* Even though it's meaningless, fix the stupid ARRAY(0x...) printout
[debbugs.git] / cgi / pkgreport.cgi
index 30284baa66ac2d0d8cd5406230ce98399c342486..673a2db9cba1891976b0c3c2c8de0f2b8b4a226a 100755 (executable)
@@ -1,15 +1,29 @@
 #!/usr/bin/perl -wT
+# This script is part of debbugs, and is released
+# under the terms of the GPL version 2, or any later
+# version at your option.
+# See the file README and COPYING for more information.
+#
+# [Other people have contributed to this file; their copyrights should
+# go here too.]
+# Copyright 2004-2006 by Anthony Towns <ajt@debian.org>
+# Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
+
 
 package debbugs;
 
+use warnings;
 use strict;
 use POSIX qw(strftime nice);
 
-require './common.pl';
-
-use Debbugs::Config qw(:globals :text);
+use Debbugs::Config qw(:globals :text :config);
 use Debbugs::User;
-use Debbugs::CGI qw(version_url);
+use Debbugs::CGI qw(version_url maint_decode);
+use Debbugs::Common qw(getparsedaddrs :date make_list getmaintainers getpseudodesc);
+use Debbugs::Bugs qw(get_bugs bug_filter newest_bug);
+use Debbugs::Packages qw(getsrcpkgs getpkgsrc get_versions);
+use Debbugs::Status qw(:status);
+use Debbugs::CGI qw(:all);
 
 use vars qw($gPackagePages $gWebDomain %gSeverityDisplay @gSeverityList);
 
@@ -20,10 +34,41 @@ if (defined $ENV{REQUEST_METHOD} and $ENV{REQUEST_METHOD} eq 'HEAD') {
 
 nice(5);
 
-my $userAgent = detect_user_agent();
-my %param = readparse();
+use CGI::Simple;
+my $q = new CGI::Simple;
+
+our %param = cgi_parameters(query => $q,
+                           single => [qw(ordering archive repeatmerged),
+                                      qw(bug-rev pend-rev sev-rev),
+                                      qw(maxdays mindays version),
+                                      qw(data which dist newest),
+                                     ],
+                           default => {ordering => 'normal',
+                                       archive  => 0,
+                                       repeatmerged => 1,
+                                      },
+                          );
+
+# map from yes|no to 1|0
+for my $key (qw(repeatmerged bug-rev pend-rev sev-rev)) {
+     if (exists $param{$key}){
+         if ($param{$key} =~ /^no$/i) {
+              $param{$key} = 0;
+         }
+         elsif ($param{$key}) {
+              $param{$key} = 1;
+         }
+     }
+}
+
+if (lc($param{archive}) eq 'no') {
+     $param{archive} = 0;
+}
+elsif (lc($param{archive}) eq 'yes') {
+     $param{archive} = 1;
+}
+
 
-my $repeatmerged = ($param{'repeatmerged'} || "yes") eq "yes";
 my $archive = ($param{'archive'} || "no") eq "yes";
 my $include = $param{'&include'} || $param{'include'} || "";
 my $exclude = $param{'&exclude'} || $param{'exclude'} || "";
@@ -33,50 +78,60 @@ my $users = $param{'users'} || "";
 my $ordering = $param{'ordering'};
 my $raw_sort = ($param{'raw'} || "no") eq "yes";
 my $old_view = ($param{'oldview'} || "no") eq "yes";
+my $age_sort = ($param{'age'} || "no") eq "yes";
 unless (defined $ordering) {
    $ordering = "normal";
    $ordering = "oldview" if $old_view;
    $ordering = "raw" if $raw_sort;
+   $ordering = 'age' if $age_sort;
 }
+our ($bug_order) = $ordering =~ /(age(?:rev)?)/;
+$bug_order = '' if not defined $bug_order;
 
 my $bug_rev = ($param{'bug-rev'} || "no") eq "yes";
 my $pend_rev = ($param{'pend-rev'} || "no") eq "yes";
 my $sev_rev = ($param{'sev-rev'} || "no") eq "yes";
-my $pend_exc = $param{'&pend-exc'} || $param{'pend-exc'} || "";
-my $pend_inc = $param{'&pend-inc'} || $param{'pend-inc'} || "";
-my $sev_exc = $param{'&sev-exc'} || $param{'sev-exc'} || "";
-my $sev_inc = $param{'&sev-inc'} || $param{'sev-inc'} || "";
-my $maxdays = ($param{'maxdays'} || -1);
-my $mindays = ($param{'mindays'} || 0);
-my $version = $param{'version'} || undef;
-my $dist = $param{'dist'} || undef;
-my $arch = $param{'arch'} || undef;
-my $show_list_header = ($param{'show_list_header'} || $userAgent->{'show_list_header'} || "yes" ) eq "yes";
-my $show_list_footer = ($param{'show_list_footer'} || $userAgent->{'show_list_footer'} || "yes" ) eq "yes";
 
-{
-    if (defined $param{'vt'}) {
-        my $vt = $param{'vt'};
-        if ($vt eq "none") { $dist = undef; $arch = undef; $version = undef; }
-        if ($vt eq "bysuite") {
-            $version = undef;
-            $arch = undef if ($arch eq "any");
-        }
-        if ($vt eq "bypkg" || $vt eq "bysrc") { $dist = undef; $arch = undef; }
-    }
-    if (defined $param{'includesubj'}) {
-        my $is = $param{'includesubj'};
-        $include .= "," . join(",", map { "subj:$_" } (split /[\s,]+/, $is));
-    }
-    if (defined $param{'excludesubj'}) {
-        my $es = $param{'excludesubj'};
-        $exclude .= "," . join(",", map { "subj:$_" } (split /[\s,]+/, $es));
-    }
+my @inc_exc_mapping = ({name   => 'pending',
+                       incexc => 'include',
+                       key    => 'pend-inc',
+                      },
+                      {name   => 'pending',
+                       incexc => 'exclude',
+                       key    => 'pend-exc',
+                      },
+                      {name   => 'severity',
+                       incexc => 'include',
+                       key    => 'sev-inc',
+                      },
+                      {name   => 'severity',
+                       incexc => 'exclude',
+                       key    => 'sev-exc',
+                      },
+                      {name   => 'subject',
+                       incexc => 'include',
+                       key    => 'includesubj',
+                      },
+                      {name   => 'subject',
+                       incexc => 'exclude',
+                       key    => 'excludesubj',
+                      },
+                     );
+for my $incexcmap (@inc_exc_mapping) {
+     push @{$param{$incexcmap->{incexc}}}, map {"$incexcmap->{name}:$_"}
+         map{split /\s*,\s*/} make_list($param{$incexcmap->{key}})
+              if exists $param{$incexcmap->{key}};
+     delete $param{$incexcmap->{key}};
 }
 
+my $maxdays = ($param{'maxdays'} || -1);
+my $mindays = ($param{'mindays'} || 0);
+my $version = $param{'version'} || undef;
+# XXX Once the options/selection is rewritten, this should go away
+my $dist = $param{dist} || undef;
 
-my %hidden = map { $_, 1 } qw(status severity classification);
-my %cats = (
+our %hidden = map { $_, 1 } qw(status severity classification);
+our %cats = (
     "status" => [ {
         "nam" => "Status",
         "pri" => [map { "pending=$_" }
@@ -109,72 +164,60 @@ my %cats = (
     "normal" => [ qw(status severity classification) ],
 );
 
-my ($pkg, $src, $maint, $maintenc, $submitter, $severity, $status, $tag, $usertag);
-
-my %which = (
-        'pkg' => \$pkg,
-        'src' => \$src,
-        'maint' => \$maint,
-        'maintenc' => \$maintenc,
-        'submitter' => \$submitter,
-        'severity' => \$severity,
-        'tag' => \$tag,
-       'usertag' => \$usertag,
-        );
-my @allowedEmpty = ( 'maint' );
-
-my $found;
-foreach ( keys %which ) {
-        $status = $param{'status'} || 'open' if /^severity$/;
-        if (($found = $param{$_})) {
-                ${ $which{$_} } = $found;
-                last;
-        }
+my @select_key = (qw(submitter maint pkg package src usertag),
+                 qw(status tag maintenc owner severity newest)
+                );
+
+if (exists $param{which} and exists $param{data}) {
+     $param{$param{which}} = [exists $param{$param{which}}?(make_list($param{$param{which}})):(),
+                             make_list($param{data}),
+                            ];
+     delete $param{which};
+     delete $param{data};
 }
-if (!$found && !$archive) {
-        foreach ( @allowedEmpty ) {
-                if (exists($param{$_})) {
-                        ${ $which{$_} } = '';
-                        $found = 1;
-                        last;
-                }
-        }
+
+if (defined $param{maintenc}) {
+     $param{maint} = maint_decode($param{maintenc});
+     delete $param{maintenc}
 }
-if (!$found) {
-        my $which;
-        if (($which = $param{'which'})) {
-                if (grep( /^\Q$which\E$/, @allowedEmpty)) {
-                        ${ $which{$which} } = $param{'data'};
-                        $found = 1;
-                } elsif (($found = $param{'data'})) {
-                        ${ $which{$which} } = $found if (exists($which{$which}));
-                }
-        }
+
+
+if (not grep {exists $param{$_}} @select_key and exists $param{users}) {
+     $param{usertag} = [make_list($param{users})];
 }
-quitcgi("You have to choose something to select by") if (!$found);
 
-my %bugusertags;
-my %ut;
-for my $user (split /[\s*,]+/, $users) {
-    next unless ($user =~ m/..../);
-    add_user($user);
+quitcgi("You have to choose something to select by") unless grep {exists $param{$_}} @select_key;
+
+if (exists $param{pkg}) {
+     $param{package} = $param{pkg};
+     delete $param{pkg};
 }
 
-if (defined $usertag) {
-    my %select_ut = ();
-    my ($u, $t) = split /:/, $usertag, 2;
-    Debbugs::User::read_usertags(\%select_ut, $u);
-    unless (defined $t && $t ne "") {
-        $t = join(",", keys(%select_ut));
-    }
+our %bugusertags;
+our %ut;
+for my $user (map {split /[\s*,\s*]+/} make_list($param{users}||[])) {
+    next unless length($user);
+    add_user($user);
+}
 
-    add_user($u);
-    $tag = $t;
+if (defined $param{usertag}) {
+     for my $usertag (make_list($param{usertag})) {
+         my %select_ut = ();
+         my ($u, $t) = split /:/, $usertag, 2;
+         Debbugs::User::read_usertags(\%select_ut, $u);
+         unless (defined $t && $t ne "") {
+              $t = join(",", keys(%select_ut));
+         }
+         add_user($u);
+         push @{$param{tag}}, split /,/, $t;
+     }
 }
 
 my $Archived = $archive ? " Archived" : "";
 
-my $this = "";
+our $this = munge_url('pkgreport.cgi?',
+                     %param,
+                    );
 
 my %indexentry;
 my %strings = ();
@@ -184,27 +227,14 @@ my $tail_html = $debbugs::gHTMLTail;
 $tail_html = $debbugs::gHTMLTail;
 $tail_html =~ s/SUBSTITUTE_DTIME/$dtime/;
 
-set_option("repeatmerged", $repeatmerged);
-set_option("archive", $archive);
-set_option("include", $include);
-set_option("exclude", $exclude);
-set_option("pend-exc", $pend_exc);
-set_option("pend-inc", $pend_inc);
-set_option("sev-exc", $sev_exc);
-set_option("sev-inc", $sev_inc);
-set_option("maxdays", $maxdays);
-set_option("mindays", $mindays);
-set_option("version", $version);
-set_option("dist", $dist);
-set_option("arch", $arch);
-set_option("use-bug-idx", defined($param{'use-bug-idx'}) ? $param{'use-bug-idx'} : 0);
-set_option("show_list_header", $show_list_header);
-set_option("show_list_footer", $show_list_footer);
-
+our %seen_users;
 sub add_user {
     my $ut = \%ut;
     my $u = shift;
 
+    return if $seen_users{$u};
+    $seen_users{$u} = 1;
+
     my $user = Debbugs::User::get_user($u);
 
     my %vis = map { $_, 1 } @{$user->{"visible_cats"}};
@@ -225,139 +255,108 @@ sub add_user {
             push @{$bugusertags{$b}}, $t;
         }
     }
-    set_option("bugusertags", \%bugusertags);
+#    set_option("bugusertags", \%bugusertags);
 }
 
-my $title;
 my @bugs;
-if (defined $pkg) {
-  $title = "package $pkg";
-  add_user("$pkg\@packages.debian.org");
-  if (defined $version) {
-    $title .= " (version $version)";
-  } elsif (defined $dist) {
-    $title .= " in $dist";
-    my $verdesc = getversiondesc($pkg);
-    $title .= " ($verdesc)" if defined $verdesc;
-  }
-  my @pkgs = split /,/, $pkg;
-  @bugs = @{getbugs(sub {my %d=@_;
-                         foreach my $try (splitpackages($d{"pkg"})) {
-                           return 1 if grep($try eq $_, @pkgs);
-                         }
-                         return 0;
-                        }, 'package', @pkgs)};
-} elsif (defined $src) {
-  add_user("$src\@packages.debian.org");
-  $title = "source $src";
-  set_option('arch', 'source');
-  if (defined $version) {
-    $title .= " (version $version)";
-  } elsif (defined $dist) {
-    $title .= " in $dist";
-    my $verdesc = getversiondesc($src);
-    $title .= " ($verdesc)" if defined $verdesc;
-  }
-  my @pkgs = ();
-  my @srcs = split /,/, $src;
-  foreach my $try (@srcs) {
-    push @pkgs, getsrcpkgs($try);
-    push @pkgs, $try if ( !grep(/^\Q$try\E$/, @pkgs) );
-  }
-  @bugs = @{getbugs(sub {my %d=@_;
-                         foreach my $try (splitpackages($d{"pkg"})) {
-                           return 1 if grep($try eq $_, @pkgs);
-                         }
-                         return 0;
-                        }, 'package', @pkgs)};
-} elsif (defined $maint) {
-  my %maintainers = %{getmaintainers()};
-  add_user($maint);
-  $title = "maintainer $maint";
-  $title .= " in $dist" if defined $dist;
-  if ($maint eq "") {
-    @bugs = @{getbugs(sub {my %d=@_;
-                           foreach my $try (splitpackages($d{"pkg"})) {
-                             return 1 if !getparsedaddrs($maintainers{$try});
-                           }
-                           return 0;
-                          })};
-  } else {
-    my @maints = split /,/, $maint;
-    my @pkgs = ();
-    foreach my $try (@maints) {
-      foreach my $p (keys %maintainers) {
-        my @me = getparsedaddrs($maintainers{$p});
-        push @pkgs, $p if grep { $_->address eq $try } @me;
-      }
-    }
-    @bugs = @{getbugs(sub {my %d=@_;
-                           foreach my $try (splitpackages($d{"pkg"})) {
-                             my @me = getparsedaddrs($maintainers{$try});
-                             return 1 if grep { $_->address eq $maint } @me;
-                           }
-                           return 0;
-                          }, 'package', @pkgs)};
-  }
-} elsif (defined $maintenc) {
-  my %maintainers = %{getmaintainers()};
-  $title = "encoded maintainer $maintenc";
-  $title .= " in $dist" if defined $dist;
-  @bugs = @{getbugs(sub {my %d=@_; 
-                         foreach my $try (splitpackages($d{"pkg"})) {
-                           my @me = getparsedaddrs($maintainers{$try});
-                           return 1 if grep {
-                             maintencoded($_->address) eq $maintenc
-                           } @me;
-                         }
-                         return 0;
-                        })};
-} elsif (defined $submitter) {
-  add_user($submitter);
-  $title = "submitter $submitter";
-  $title .= " in $dist" if defined $dist;
-  my @submitters = split /,/, $submitter;
-  @bugs = @{getbugs(sub {my %d=@_;
-                         my @se = getparsedaddrs($d{"submitter"} || "");
-                         foreach my $try (@submitters) {
-                           return 1 if grep { $_->address eq $try } @se;
-                         }
-                        }, 'submitter-email', @submitters)};
-} elsif (defined($severity) && defined($status)) {
-  $title = "$status $severity bugs";
-  $title .= " in $dist" if defined $dist;
-  my @severities = split /,/, $severity;
-  my @statuses = split /,/, $status;
-  @bugs = @{getbugs(sub {my %d=@_;
-                       return (grep($d{"severity"} eq $_, @severities))
-                         && (grep($d{"status"} eq $_, @statuses));
-                     })};
-} elsif (defined($severity)) {
-  $title = "$severity bugs";
-  $title .= " in $dist" if defined $dist;
-  my @severities = split /,/, $severity;
-  @bugs = @{getbugs(sub {my %d=@_;
-                       return (grep($d{"severity"} eq $_, @severities));
-                     }, 'severity', @severities)};
-} elsif (defined($tag)) {
-  $title = "bugs tagged $tag";
-  $title .= " in $dist" if defined $dist;
-  my @tags = split /,/, $tag;
-  my %bugs = ();
-  for my $t (@tags) {
-      for my $b (@{$ut{$t}}) {
-          $bugs{$b} = 1;
-       }
-  }
-  @bugs = @{getbugs(sub {my %d = @_;
-                        return 1 if $bugs{$d{"bug"}};
-                         my %tags = map { $_ => 1 } split ' ', $d{"tags"};
-                         return grep(exists $tags{$_}, @tags);
-                        })};
+
+# addusers for source and binary packages being searched for
+my $pkgsrc = getpkgsrc();
+my $srcpkg = getsrcpkgs();
+for my $package (# For binary packages, add the binary package
+                # and corresponding source package
+                make_list($param{package}||[]),
+                (map {defined $pkgsrc->{$_}?($pkgsrc->{$_}):()}
+                 make_list($param{package}||[]),
+                ),
+                # For source packages, add the source package
+                # and corresponding binary packages
+                make_list($param{src}||[]),
+                (map {defined $srcpkg->{$_}?($srcpkg->{$_}):()}
+                 make_list($param{src}||[]),
+                ),
+               ) {
+     next unless defined $package;
+     add_user($package.'@'.$config{usertag_package_domain})
+         if defined $config{usertag_package_domain};
+}
+
+
+# walk through the keys and make the right get_bugs query.
+
+my @search_key_order = (package   => 'in package',
+                       tag       => 'tagged',
+                       severity  => 'with severity',
+                       src       => 'in source package',
+                       maint     => 'in packages maintained by',
+                       submitter => 'submitted by',
+                       owner     => 'owned by',
+                       status    => 'with status',
+                      );
+my %search_keys = @search_key_order;
+
+# Set the title sanely and clean up parameters
+my @title;
+while (my ($key,$value) = splice @search_key_order, 0, 2) {
+     next unless exists $param{$key};
+     my @entries = ();
+     $param{$key} = [map {split /\s*,\s*/} make_list($param{$key})];
+     for my $entry (make_list($param{$key})) {
+         my $extra = '';
+         if (exists $param{dist} and ($key eq 'package' or $key eq 'src')) {
+              my @versions = get_versions(package => $entry,
+                                          (exists $param{dist}?(dist => $param{dist}):()),
+                                          (exists $param{arch}?(arch => $param{arch}):()),
+                                          ($key eq 'src'?(arch => q(source)):()),
+                                          no_source_arch => 1,
+                                         );
+              my $verdesc = join(', ',@versions);
+              $verdesc = 'version'.(@versions>1?'s ':' ').$verdesc;
+              $extra= " ($verdesc)" if @versions;
+         }
+         push @entries, $entry.$extra;
+     }
+     push @title,$value.' '.join(' or ', @entries);
 }
-$title = htmlsanit($title);
+my $title = $gBugs.' '.join(' and ', map {/ or /?"($_)":$_} @title);
+@title = ();
+
+# we have to special case the maint="" search, unfortunatly.
+if (defined $param{maint} and $param{maint} eq "" or ref($param{maint}) and not @{$param{maint}}) {
+     my %maintainers = %{getmaintainers()};
+     @bugs = get_bugs(function =>
+                     sub {my %d=@_;
+                          foreach my $try (splitpackages($d{"pkg"})) {
+                               return 1 if !getparsedaddrs($maintainers{$try});
+                          }
+                          return 0;
+                     }
+                    );
+     $title = $gBugs.' in packages with no maintainer';
+}
+elsif (defined $param{newest}) {
+     my $newest_bug = newest_bug();
+     @bugs = ($newest_bug - $param{newest} + 1) .. $newest_bug;
+     $title = @bugs.' newest '.$gBugs;
+}
+else {
+     #yeah for magick!
+     @bugs = get_bugs((map {exists $param{$_}?($_,$param{$_}):()}
+                      keys %search_keys, 'archive'),
+                     usertags => \%ut,
+                    );
+}
+
+if (defined $param{version}) {
+     $title .= " at version $param{version}";
+}
+elsif (defined $param{dist}) {
+     $title .= " in $param{dist}";
+}
+
+$title = html_escape($title);
 
-my @names; my @prior; my @title; my @order;
+my @names; my @prior; my @order;
 determine_ordering();
 
 # strip out duplicate bugs
@@ -371,7 +370,7 @@ print "Content-Type: text/html; charset=utf-8\n\n";
 
 print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
 print "<HTML><HEAD>\n" . 
-    "<TITLE>$gProject$Archived $gBug report logs: $title</TITLE>\n" .
+    "<TITLE>$title -- $gProject$Archived $gBug report logs</TITLE>\n" .
     qq(<link rel="stylesheet" href="$gWebHostBugDir/css/bugs.css" type="text/css">) .
     "</HEAD>\n" .
     '<BODY onload="pagemain();">' .
@@ -381,94 +380,123 @@ print "<H1>" . "$gProject$Archived $gBug report logs: $title" .
 
 my $showresult = 1;
 
-if (defined $pkg || defined $src) {
-    my $showpkg = htmlsanit((defined $pkg) ? $pkg : "source package $src");
-    my %maintainers = %{getmaintainers()};
-    my $maint = $pkg ? $maintainers{$pkg} : $maintainers{$src} ? $maintainers{$src} : undef;
+my $pkg = $param{package} if defined $param{package};
+my $src = $param{src} if defined $param{src};
+
+my $pseudodesc = getpseudodesc();
+if (defined $pseudodesc and defined $pkg and exists $pseudodesc->{$pkg}) {
+     delete $param{dist};
+}
+
+# output infomration about the packages
+
+for my $package (make_list($param{package}||[])) {
+     output_package_info('binary',$package);
+}
+for my $package (make_list($param{src}||[])) {
+     output_package_info('source',$package);
+}
+
+sub output_package_info{
+    my ($srcorbin,$package) = @_;
+
+    my %pkgsrc = %{getpkgsrc()};
+    my $srcforpkg = $package;
+    if ($srcorbin eq 'binary') {
+        $srcforpkg = $pkgsrc{$package};
+        defined $srcforpkg or $srcforpkg = $package;
+    }
+
+    my $showpkg = html_escape($package);
+    my $maintainers = getmaintainers();
+    my $maint = $maintainers->{$srcforpkg};
     if (defined $maint) {
-        print '<p>';
-        print htmlmaintlinks(sub { $_[0] == 1 ? "Maintainer for $showpkg is "
-                                              : "Maintainers for $showpkg are "
-                                 },
-                             $maint);
-        print ".</p>\n";
+        print '<p>';
+        print htmlize_maintlinks(sub { $_[0] == 1 ? "Maintainer for $showpkg is "
+                                        : "Maintainers for $showpkg are "
+                                   },
+                             $maint);
+        print ".</p>\n";
     } else {
-        print "<p>No maintainer for $showpkg. Please do not report new bugs against this package.</p>\n";
+        print "<p>No maintainer for $showpkg. Please do not report new bugs against this package.</p>\n";
     }
-    if (defined $maint or @bugs) {
-        my %pkgsrc = %{getpkgsrc()};
-        my $srcforpkg;
-        if (defined $pkg) {
-            $srcforpkg = $pkgsrc{$pkg};
-            defined $srcforpkg or $srcforpkg = $pkg;
-        }
-        my @pkgs = getsrcpkgs($pkg ? $srcforpkg : $src);
-        undef $srcforpkg unless @pkgs;
-        @pkgs = grep( !/^\Q$pkg\E$/, @pkgs ) if ( $pkg );
-        if ( @pkgs ) {
-            @pkgs = sort @pkgs;
-            if ($pkg) {
-                    print "<p>You may want to refer to the following packages that are part of the same source:\n";
-            } else {
-                    print "<p>You may want to refer to the following individual bug pages:\n";
-            }
-            push @pkgs, $src if ( $src && !grep(/^\Q$src\E$/, @pkgs) );
-            print join( ", ", map( "<A href=\"" . pkgurl($_) . "\">$_</A>", @pkgs ) );
-            print ".\n";
-        }
-        my @references;
-        my $pseudodesc = getpseudodesc();
-        if ($pkg and defined($pseudodesc) and exists($pseudodesc->{$pkg})) {
-            push @references, "to the <a href=\"http://${debbugs::gWebDomain}/pseudo-packages${debbugs::gHTMLSuffix}\">list of other pseudo-packages</a>";
-        } else {
-            if ($pkg and defined $gPackagePages) {
-                push @references, sprintf "to the <a href=\"%s\">%s package page</a>", urlsanit("http://${debbugs::gPackagePages}/$pkg"), htmlsanit("$pkg");
-            }
-            if (defined $gSubscriptionDomain) {
-                my $ptslink = $pkg ? $srcforpkg : $src;
-                push @references, "to the <a href=\"http://$gSubscriptionDomain/$ptslink\">Package Tracking System</a>";
-            }
-            # Only output this if the source listing is non-trivial.
-            if ($pkg and $srcforpkg and (@pkgs or $pkg ne $srcforpkg)) {
-                push @references, sprintf "to the source package <a href=\"%s\">%s</a>'s bug page", srcurl($srcforpkg), htmlsanit($srcforpkg);
-            }
-        }
-        if (@references) {
-            $references[$#references] = "or $references[$#references]" if @references > 1;
-            print "<p>You might like to refer ", join(", ", @references), ".</p>\n";
-        }
-       if (defined $maint || defined $maintenc) {
-            print "<p>If you find a bug not listed here, please\n";
-            printf "<a href=\"%s\">report it</a>.</p>\n",
-                 urlsanit("http://${debbugs::gWebDomain}/Reporting${debbugs::gHTMLSuffix}");
-       }
+    my @pkgs = getsrcpkgs($srcforpkg);
+    @pkgs = grep( !/^\Q$package\E$/, @pkgs );
+    if ( @pkgs ) {
+        @pkgs = sort @pkgs;
+        if ($srcorbin eq 'binary') {
+             print "<p>You may want to refer to the following packages that are part of the same source:\n";
+        } else {
+             print "<p>You may want to refer to the following individual bug pages:\n";
+        }
+        #push @pkgs, $src if ( $src && !grep(/^\Q$src\E$/, @pkgs) );
+        print join( ", ", map( "<A href=\"" . html_escape(munge_url($this,package=>$_,src=>[],newest=>[])) . "\">$_</A>", @pkgs ) );
+        print ".\n";
+    }
+    my @references;
+    my $pseudodesc = getpseudodesc();
+    if ($package and defined($pseudodesc) and exists($pseudodesc->{$package})) {
+        push @references, "to the <a href=\"http://${debbugs::gWebDomain}/pseudo-packages${debbugs::gHTMLSuffix}\">".
+             "list of other pseudo-packages</a>";
     } else {
-        print "<p>There is no record of the " .
-              (defined($pkg) ? htmlsanit($pkg) . " package"
-                             : htmlsanit($src) . " source package") .
-              ", and no bugs have been filed against it.</p>";
-        $showresult = 0;
+        if ($package and defined $gPackagePages) {
+             push @references, sprintf "to the <a href=\"%s\">%s package page</a>",
+                  html_escape("http://${debbugs::gPackagePages}/$package"), html_escape("$package");
+        }
+        if (defined $gSubscriptionDomain) {
+             my $ptslink = $package ? $srcforpkg : $src;
+             push @references, "to the <a href=\"http://$gSubscriptionDomain/$ptslink\">Package Tracking System</a>";
+        }
+        # Only output this if the source listing is non-trivial.
+        if ($srcorbin eq 'binary' and $srcforpkg) {
+             push @references, sprintf "to the source package <a href=\"%s\">%s</a>'s bug page", html_escape(munge_url($this,src=>$srcforpkg,package=>[],newest=>[])), html_escape($srcforpkg);
+        }
+    }
+    if (@references) {
+        $references[$#references] = "or $references[$#references]" if @references > 1;
+        print "<p>You might like to refer ", join(", ", @references), ".</p>\n";
+    }
+    if (defined $param{maint} || defined $param{maintenc}) {
+        print "<p>If you find a bug not listed here, please\n";
+        printf "<a href=\"%s\">report it</a>.</p>\n",
+             html_escape("http://${debbugs::gWebDomain}/Reporting${debbugs::gHTMLSuffix}");
+    }
+    if (not $maint and not @bugs) {
+        print "<p>There is no record of the " . html_escape($package) .
+             ($srcorbin eq 'binary' ? " package" : " source package") .
+                   ", and no bugs have been filed against it.</p>";
+        $showresult = 0;
     }
-} elsif (defined $maint || defined $maintenc) {
+}
+
+if (exists $param{maint} or exists $param{maintenc}) {
     print "<p>Note that maintainers may use different Maintainer fields for\n";
     print "different packages, so there may be other reports filed under\n";
     print "different addresses.\n";
-} elsif (defined $submitter) {
+}
+if (exists $param{submitter}) {
     print "<p>Note that people may use different email accounts for\n";
     print "different bugs, so there may be other reports filed under\n";
     print "different addresses.\n";
 }
 
-set_option("archive", !$archive);
-printf "<p>See the <a href=\"%s\">%s reports</a></p>",
-     urlsanit('pkgreport.cgi?'.join(';',
-                                   (map {$_ eq 'archive'?():("$_=$param{$_}")
-                                    } keys %param
-                                   ),
-                                   ('archive='.($archive?"no":"yes"))
-                                  )
-            ), ($archive ? "active" : "archived");
-set_option("archive", $archive);
+my $archive_links;
+my @archive_links;
+my %archive_values = (both => 'archived and unarchived',
+                     0    => 'not archived',
+                     1    => 'archived',
+                    );
+while (my ($key,$value) = each %archive_values) {
+     next if $key eq lc($param{archive});
+     push @archive_links, qq(<a href=").
+         html_escape(pkg_url((
+                      map {
+                           $_ eq 'archive'?():($_,$param{$_})
+                      } keys %param),
+                           archive => $key
+                          )).qq(">$value reports </a>);
+}
+print '<p>See the '.join (' or ',@archive_links)."</p>\n";
 
 print $result if $showresult;
 
@@ -494,30 +522,31 @@ print "<tr><td></td>";
 print "    <td><input id=\"b_1_2\" name=vt value=bysuite type=radio onchange=\"enable(1);\" $checked_sui>" . pkg_htmlselectsuite(1,2,1) . " for " . pkg_htmlselectarch(1,2,2) . "</td></tr>\n";
 
 if (defined $pkg) {
-    my $v = htmlsanit($version) || "";
-    my $pkgsane = htmlsanit($pkg);
+    my $v = html_escape($version) || "";
+    my $pkgsane = html_escape($pkg->[0]);
     print "<tr><td></td>";
     print "    <td><input id=\"b_1_3\" name=vt value=bypkg type=radio onchange=\"enable(1);\" $checked_ver>$pkgsane version <input id=\"b_1_3_1\" name=version value=\"$v\"></td></tr>\n";
 } elsif (defined $src) {
-    my $v = htmlsanit($version) || "";
-    my $srcsane = htmlsanit($src);
+    my $v = html_escape($version) || "";
+    my $srcsane = html_escape($src->[0]);
     print "<tr><td></td>";
     print "    <td><input name=vt value=bysrc type=radio onchange=\"enable(1);\" $checked_ver>$srcsane version <input id=\"b_1_3_1\" name=version value=\"$v\"></td></tr>\n";
 }
 print "<tr><td>&nbsp;</td></tr>\n";
 
-my $includetags = htmlsanit(join(" ", grep { !m/^subj:/i } split /[\s,]+/, $include));
-my $excludetags = htmlsanit(join(" ", grep { !m/^subj:/i } split /[\s,]+/, $exclude));
-my $includesubj = htmlsanit(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } split /[\s,]+/, $include));
-my $excludesubj = htmlsanit(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } split /[\s,]+/, $exclude));
+my $includetags = html_escape(join(" ", grep { !m/^subj:/i } map {split /[\s,]+/} ref($include)?@{$include}:$include));
+my $excludetags = html_escape(join(" ", grep { !m/^subj:/i } map {split /[\s,]+/} ref($exclude)?@{$exclude}:$exclude));
+my $includesubj = html_escape(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } map {split /[\s,]+/} ref($include)?@{$include}:$include));
+my $excludesubj = html_escape(join(" ", map { s/^subj://i; $_ } grep { m/^subj:/i } map {split /[\s,]+/} ref($exclude)?@{$exclude}:$exclude));
 my $vismindays = ($mindays == 0 ? "" : $mindays);
 my $vismaxdays = ($maxdays == -1 ? "" : $maxdays);
 
-my $sel_rmy = ($repeatmerged ? " selected" : "");
-my $sel_rmn = ($repeatmerged ? "" : " selected");
+my $sel_rmy = ($param{repeatmerged} ? " selected" : "");
+my $sel_rmn = ($param{repeatmerged} ? "" : " selected");
 my $sel_ordraw = ($ordering eq "raw" ? " selected" : "");
 my $sel_ordold = ($ordering eq "oldview" ? " selected" : "");
 my $sel_ordnor = ($ordering eq "normal" ? " selected" : "");
+my $sel_ordage = ($ordering eq "age" ? " selected" : "");
 
 my $chk_bugrev = ($bug_rev ? " checked" : "");
 my $chk_pendrev = ($pend_rev ? " checked" : "");
@@ -541,6 +570,7 @@ print <<EOF;
 <option value=raw$sel_ordraw>bug number only</option>
 <option value=old$sel_ordold>status and severity</option>
 <option value=normal$sel_ordnor>status, severity and classification</option>
+<option value=age$sel_ordage>status, severity, classification, and age</option>
 EOF
 
 {
@@ -604,13 +634,13 @@ sub pkg_htmlindexentrystatus {
     my $showversions = '';
     if (@{$status{found_versions}}) {
         my @found = @{$status{found_versions}};
-        $showversions .= join ', ', map {s{/}{ }; htmlsanit($_)} @found;
+        $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @found;
     }
     if (@{$status{fixed_versions}}) {
         $showversions .= '; ' if length $showversions;
         $showversions .= '<strong>fixed</strong>: ';
         my @fixed = @{$status{fixed_versions}};
-        $showversions .= join ', ', map {s{/}{ }; htmlsanit($_)} @fixed;
+        $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @fixed;
     }
     $result .= ' (<a href="'.
         version_url($status{package},
@@ -622,10 +652,10 @@ sub pkg_htmlindexentrystatus {
     $result .= $showseverity;
     $result .= pkg_htmladdresslinks("Reported by: ", \&submitterurl,
                                 $status{originator});
-    $result .= ";\nOwned by: " . htmlsanit($status{owner})
+    $result .= ";\nOwned by: " . html_escape($status{owner})
                if length $status{owner};
     $result .= ";\nTags: <strong>" 
-                 . htmlsanit(join(", ", sort(split(/\s+/, $status{tags}))))
+                 . html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
                  . "</strong>"
                        if (length($status{tags}));
 
@@ -637,14 +667,17 @@ sub pkg_htmlindexentrystatus {
         split(/ /,$status{blocks}));
 
     if (length($status{done})) {
-        $result .= "<br><strong>Done:</strong> " . htmlsanit($status{done});
+        $result .= "<br><strong>Done:</strong> " . html_escape($status{done});
         my $days = bug_archiveable(bug => $status{id},
                                   status => \%status,
                                   days_until => 1,
                                  );
-        if ($days >= 0) {
-            $result .= ";\n<strong>Will be archived" . ( $days == 0 ? " today" : $days == 1 ? " in $days day" : " in $days days" ) . "</strong>";
+        if ($days >= 0 and defined $status{location} and $status{location} ne 'archive') {
+            $result .= ";\n<strong>Can be archived" . ( $days == 0 ? " today" : $days == 1 ? " in $days day" : " in $days days" ) . "</strong>";
         }
+       elsif (defined $status{location} and $status{location} eq 'archived') {
+            $result .= ";\n<strong>Archived.</strong>";
+       }
     }
 
     unless (length($status{done})) {
@@ -655,27 +688,30 @@ sub pkg_htmlindexentrystatus {
                              split /[,\s]+/,$status{forwarded}
                             );
         }
-        my $daysold = int((time - $status{date}) / 86400);   # seconds to days
-        if ($daysold >= 7) {
+       # Check the age of the logfile
+       my ($days_last,$eng_last) = secs_to_english(time - $status{log_modified});
+        my ($days,$eng) = secs_to_english(time - $status{date});
+       
+        if ($days >= 7) {
             my $font = "";
             my $efont = "";
-            $font = "em" if ($daysold > 30);
-            $font = "strong" if ($daysold > 60);
+            $font = "em" if ($days > 30);
+            $font = "strong" if ($days > 60);
             $efont = "</$font>" if ($font);
             $font = "<$font>" if ($font);
 
-            my $yearsold = int($daysold / 365);
-            $daysold -= $yearsold * 365;
-
-            $result .= ";\n $font";
-            my @age;
-            push @age, "1 year" if ($yearsold == 1);
-            push @age, "$yearsold years" if ($yearsold > 1);
-            push @age, "1 day" if ($daysold == 1);
-            push @age, "$daysold days" if ($daysold > 1);
-            $result .= join(" and ", @age);
-            $result .= " old$efont";
+            $result .= ";\n ${font}$eng old$efont";
         }
+       if ($days_last > 7) {
+           my $font = "";
+            my $efont = "";
+            $font = "em" if ($days_last > 30);
+            $font = "strong" if ($days_last > 60);
+            $efont = "</$font>" if ($font);
+            $font = "<$font>" if ($font);
+
+            $result .= ";\n ${font}Modified $eng_last ago$efont";
+       }
     }
 
     $result .= ".";
@@ -712,26 +748,65 @@ sub pkg_htmlizebugs {
     );
 
     my %section = ();
+    # Make the include/exclude map
+    my %include;
+    my %exclude;
+    for my $include (make_list($param{include})) {
+        next unless defined $include;
+        my ($key,$value) = split /\s*:\s*/,$include,2;
+        unless (defined $value) {
+            $key = 'tags';
+            $value = $include;
+        }
+        push @{$include{$key}}, split /\s*,\s*/, $value;
+    }
+    for my $exclude (make_list($param{exclude})) {
+        next unless defined $exclude;
+        my ($key,$value) = split /\s*:\s*/,$exclude,2;
+        unless (defined $value) {
+            $key = 'tags';
+            $value = $exclude;
+        }
+        push @{$exclude{$key}}, split /\s*,\s*/, $value;
+    }
 
     foreach my $bug (@bugs) {
-        my %status = %{getbugstatus($bug)};
+        my %status = %{get_bug_status(bug=>$bug,
+                                     (exists $param{dist}?(dist => $param{dist}):()),
+                                     bugusertags => \%bugusertags,
+                                     (exists $param{version}?(version => $param{version}):()),
+                                     (exists $param{arch}?(arch => $param{arch}):()),
+                                    )};
         next unless %status;
-        next if bugfilter($bug, %status);
-
-        my $html = sprintf "<li><a href=\"%s\">#%d: %s</a>\n<br>",
-            bugurl($bug), $bug, htmlsanit($status{subject});
+        next if bug_filter(bug => $bug,
+                          status => \%status,
+                          (exists $param{repeatmerged}?(repeat_merged => $param{repeatmerged}):()),
+                          seen_merged => \%seenmerged,
+                          (keys %include ? (include => \%include):()),
+                          (keys %exclude ? (exclude => \%exclude):()),
+                         );
+
+       my $html = sprintf "<li><a href=\"%s\">#%d: %s</a>\n<br>",
+            bug_url($bug), $bug, html_escape($status{subject});
         $html .= pkg_htmlindexentrystatus(\%status) . "\n";
-
+       push @status, [ $bug, \%status, $html ];
+    }
+    if ($bug_order eq 'age') {
+        # MWHAHAHAHA
+        @status = sort {$a->[1]{log_modified} <=> $b->[1]{log_modified}} @status;
+    }
+    elsif ($bug_order eq 'agerev') {
+        @status = sort {$b->[1]{log_modified} <=> $a->[1]{log_modified}} @status;
+    }
+    for my $entry (@status) {
         my $key = "";
        for my $i (0..$#prior) {
-           my $v = get_bug_order_index($prior[$i], \%status);
+           my $v = get_bug_order_index($prior[$i], $entry->[1]);
             $count{"g_${i}_${v}"}++;
            $key .= "_$v";
        }
-        $section{$key} .= $html;
+        $section{$key} .= $entry->[2];
         $count{"_$key"}++;
-
-        push @status, [ $bug, \%status, $html ];
     }
 
     my $result = "";
@@ -758,7 +833,7 @@ sub pkg_htmlizebugs {
                $title .= join("; ", grep {($_ || "") ne ""}
                        map { $title[$_]->[$ttl[$_]] } 1..$#ttl);
            }
-           $title = htmlsanit($title);
+           $title = html_escape($title);
 
             my $count = $count{"_$order"};
             my $bugs = $count == 1 ? "bug" : "bugs";
@@ -812,8 +887,8 @@ sub pkg_htmlpackagelinks {
     return 'Package' . (@pkglist > 1 ? 's' : '') . ': ' .
            join(', ',
                 map {
-                    '<a class="submitter" href="' . pkgurl($_) . '">' .
-                    $openstrong . htmlsanit($_) . $closestrong . '</a>'
+                    '<a class="submitter" href="' . munge_url($this,src=>[],package=>$_,newest=>[]) . '">' .
+                    $openstrong . html_escape($_) . $closestrong . '</a>'
                 } @pkglist
            );
 }
@@ -915,7 +990,7 @@ sub pkg_htmlselectyesno {
 sub pkg_htmlselectsuite {
     my $id = sprintf "b_%d_%d_%d", $_[0], $_[1], $_[2];
     my @suites = ("stable", "testing", "unstable", "experimental");
-    my %suiteaka = ("stable", "sarge", "testing", "etch", "unstable", "sid");
+    my %suiteaka = ("stable", "etch", "testing", "lenny", "unstable", "sid");
     my $defaultsuite = "unstable";
 
     my $result = sprintf '<select name=dist id="%s">', $id;
@@ -942,12 +1017,11 @@ sub pkg_htmlselectarch {
 }
 
 sub myurl {
-    return pkg_etc_url($pkg, "pkg", 0) if defined($pkg);
-    return pkg_etc_url($src, "src", 0) if defined($src);
-    return pkg_etc_url($maint, "maint", 0) if defined($maint);
-    return pkg_etc_url($submitter, "submitter", 0) if defined($submitter);
-    return pkg_etc_url($severity, "severity", 0) if defined($severity);
-    return pkg_etc_url($tag, "tag", 0) if defined($tag);
+     return html_escape(pkg_url(map {exists $param{$_}?($_,$param{$_}):()}
+                            qw(archive repeatmerged mindays maxdays),
+                            qw(version dist arch pkg src tag maint submitter)
+                           )
+                   );
 }
 
 sub make_order_list {
@@ -1022,7 +1096,7 @@ sub determine_ordering {
         while (defined $param{"pri$i"}) {
             my $h = {};
 
-            my $pri = $param{"pri$i"};
+            my ($pri) = make_list($param{"pri$i"});
             if ($pri =~ m/^([^:]*):(.*)$/) {
               $h->{"nam"} = $1;  # overridden later if necesary
               $h->{"pri"} = [ map { "$1=$_" } (split /,/, $2) ];
@@ -1030,12 +1104,12 @@ sub determine_ordering {
               $h->{"pri"} = [ split /,/, $pri ];
             }
 
-           $h->{"nam"} = $param{"nam$i"}
-                if (defined $param{"nam$i"}); 
-            $h->{"ord"} = [ split /,/, $param{"ord$i"} ]
-                if (defined $param{"ord$i"}); 
-            $h->{"ttl"} = [ split /,/, $param{"ttl$i"} ]
-                if (defined $param{"ttl$i"}); 
+           ($h->{"nam"}) = make_list($param{"nam$i"})
+                if (defined $param{"nam$i"});
+            $h->{"ord"} = [ map {split /\s*,\s*/} make_list($param{"ord$i"}) ]
+                if (defined $param{"ord$i"});
+           $h->{"ttl"} = [ map {split /\s*,\s*/} make_list($param{"ttl$i"}) ]
+                if (defined $param{"ttl$i"});
 
             push @c, $h;
            $i++;
@@ -1070,7 +1144,7 @@ sub determine_ordering {
  
     $i = 0;
     for my $c (@cats) {
-        $i++;
+       $i++;
         push @prior, $c->{"pri"};
        push @names, ($c->{"nam"} || "Bug attribute #" . $i);
         if (defined $c->{"ord"}) {
@@ -1079,8 +1153,8 @@ sub determine_ordering {
             push @order, [ 0..$#{$prior[-1]} ];
         }
         my @t = @{ $c->{"ttl"} } if defined $c->{ttl};
-       if (($#t+1) < $#{$prior[-1]}) {
-            push @t, map { toenglish($prior[-1]->[$_]) } ($#t+1)..($#{$prior[-1]});
+       if (@t < $#{$prior[-1]}) {
+            push @t, map { toenglish($prior[-1][$_]) } @t..($#{$prior[-1]});
        }
        push @t, $c->{"def"} || "";
         push @title, [@t];