Commit 496013d2 authored by myk%mozilla.org's avatar myk%mozilla.org

Fix for bug 103778: Rewrites and templatizes buglist.cgi.

Patch by Myk Melez <myk@mozilla.org>. r=bbaetz,gerv
parent 38551035
...@@ -1201,7 +1201,8 @@ sub PutFooter { ...@@ -1201,7 +1201,8 @@ sub PutFooter {
sub DisplayError { sub DisplayError {
my ($message, $title) = (@_); my ($message, $title) = (@_);
$title ||= "Error"; $title ||= "Error";
$message ||= "An unknown error occurred.";
print "Content-type: text/html\n\n"; print "Content-type: text/html\n\n";
PutHeader($title); PutHeader($title);
......
...@@ -22,77 +22,165 @@ ...@@ -22,77 +22,165 @@
# Dan Mosedale <dmose@mozilla.org> # Dan Mosedale <dmose@mozilla.org>
# Stephan Niemz <st.n@gmx.net> # Stephan Niemz <st.n@gmx.net>
# Andreas Franke <afranke@mathweb.org> # Andreas Franke <afranke@mathweb.org>
# Myk Melez <myk@mozilla.org>
################################################################################
# Script Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use diagnostics; use diagnostics;
use strict; use strict;
use lib qw(.); use lib qw(.);
use vars qw( $template $vars );
# Include the Bugzilla CGI and general utility library.
require "CGI.pl"; require "CGI.pl";
use Date::Parse;
# Shut up misguided -w warnings about "used only once". "use vars" just # Shut up misguided -w warnings about "used only once". "use vars" just
# doesn't work for me. # doesn't work for me.
sub sillyness { sub sillyness {
my $zz; my $zz;
$zz = $::db_name; $zz = $::db_name;
$zz = $::defaultqueryname;
$zz = $::unconfirmedstate;
$zz = $::userid;
$zz = @::components; $zz = @::components;
$zz = @::default_column_list; $zz = @::default_column_list;
$zz = $::defaultqueryname;
$zz = @::dontchange;
$zz = @::legal_keywords; $zz = @::legal_keywords;
$zz = @::legal_platform; $zz = @::legal_platform;
$zz = @::legal_priority; $zz = @::legal_priority;
$zz = @::legal_product; $zz = @::legal_product;
$zz = @::settable_resolution;
$zz = @::legal_severity; $zz = @::legal_severity;
$zz = @::versions; $zz = @::settable_resolution;
$zz = @::target_milestone; $zz = @::target_milestone;
$zz = %::proddesc; $zz = $::unconfirmedstate;
$zz = $::userid;
$zz = @::versions;
}; };
my $serverpush = 0;
ConnectToDatabase(); ConnectToDatabase();
#print "Content-type: text/plain\n\n"; # Handy for debugging. ################################################################################
#$::FORM{'debug'} = 1; # Data and Security Validation
################################################################################
# Determine the format in which the user would like to receive the output.
# Uses the default format if the user did not specify an output format;
# otherwise validates the user's choice against the list of available formats.
my $format = ValidateOutputFormat($::FORM{'format'});
if (grep(/^cmd-/, keys(%::FORM))) { # Whether or not the user wants to change multiple bugs.
my $url = "query.cgi?$::buffer#chart"; my $dotweak = $::FORM{'tweak'} ? 1 : 0;
print qq{Refresh: 0; URL=$url
Content-type: text/html
Adding field to query page... # Use server push to display a "Please wait..." message for the user while
<P> # executing their query if their browser supports it and they are viewing
<A HREF="$url">Click here if page doesn't redisplay automatically.</A> # the bug list as HTML and they have not disabled it by adding &serverpush=0
}; # to the URL.
exit(); #
# Server push is a Netscape 3+ hack incompatible with MSIE, Lynx, and others.
# Even Communicator 4.51 has bugs with it, especially during page reload.
# http://www.browsercaps.org used as source of compatible browsers.
#
my $serverpush =
exists $ENV{'HTTP_USER_AGENT'}
&& $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/
&& $ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/
&& $format->{'extension'} eq "html"
&& !defined($::FORM{'serverpush'})
|| $::FORM{'serverpush'};
my $order = $::FORM{'order'} || "";
# If the user is retrieving the last bug list they looked at, hack the buffer
# storing the query string so that it looks like a query retrieving those bugs.
if ($::FORM{'regetlastlist'}) {
if (!$::COOKIE{'BUGLIST'}) {
DisplayError(qq|Sorry, I seem to have lost the cookie that recorded
the results of your last query. You will have to start
over at the <a href="query.cgi">query page</a>.|);
exit;
}
$::FORM{'bug_id'} = join(",", split(/:/, $::COOKIE{'BUGLIST'}));
$order = "reuse last sort" unless $order;
$::buffer = "bug_id=$::FORM{'bug_id'}&order=" . url_quote($order);
} }
if ($::buffer =~ /&cmd-/) {
my $url = "query.cgi?$::buffer#chart";
print "Refresh: 0; URL=$url\n";
print "Content-Type: text/html\n\n";
# Generate and return the UI (HTML page) from the appropriate template.
$vars->{'title'} = "Adding field to query page...";
$vars->{'url'} = $url;
$vars->{'link'} = "Click here if the page does not redisplay automatically.";
$template->process("global/message.html.tmpl", $vars)
|| DisplayError("Template process failed: " . $template->error());
exit;
}
# Generate a reasonable filename for the user agent to suggest to the user
# when the user saves the bug list. Uses the name of the remembered query
# if available. We have to do this now, even though we return HTTP headers
# at the end, because the fact that there is a remembered query gets
# forgotten in the process of retrieving it.
my @time = localtime(time());
my $date = sprintf "%04d-%02d-%02d", 1900+$time[5],$time[4]+1,$time[3];
my $filename = "bugs-$date.$format->{extension}";
$::FORM{'cmdtype'} ||= "";
if ($::FORM{'cmdtype'} eq 'runnamed') {
$filename = "$::FORM{'namedcmd'}-$date.$format->{extension}";
# Remove white-space from the filename so the user cannot tamper
# with the HTTP headers.
$filename =~ s/\s//;
}
if (!defined $::FORM{'cmdtype'}) { if ($dotweak) {
# This can happen if there's an old bookmark to a query... confirm_login();
$::FORM{'cmdtype'} = 'doit'; if (!UserInGroup("editbugs")) {
DisplayError("Sorry, you do not have sufficient privileges to edit
multiple bugs.");
exit;
}
GetVersionTable();
}
else {
quietly_check_login();
} }
################################################################################
# Utilities
################################################################################
sub SqlifyDate { sub SqlifyDate {
my ($str) = (@_); my ($str) = @_;
if (!defined $str) { $str = "" if !defined $str;
$str = "";
}
my $date = str2time($str); my $date = str2time($str);
if (!defined $date) { if (!defined($date)) {
PuntTryAgain("The string '<tt>".html_quote($str)."</tt>' is not a legal date."); my $htmlstr = html_quote($str);
DisplayError("The string <tt>$htmlstr</tt> is not a legal date.");
exit;
} }
return time2str("%Y/%m/%d %H:%M:%S", $date); return time2str("%Y-%m-%d %H:%M:%S", $date);
} }
my @weekday= qw( Sun Mon Tue Wed Thu Fri Sat );
sub DiffDate {
my ($datestr) = @_;
my $date = str2time($datestr);
my $age = time() - $date;
my ($s,$m,$h,$d,$mo,$y,$wd)= localtime $date;
if( $age < 18*60*60 ) {
$date = sprintf "%02d:%02d:%02d", $h,$m,$s;
} elsif( $age < 6*24*60*60 ) {
$date = sprintf "%s %02d:%02d", $weekday[$wd],$h,$m;
} else {
$date = sprintf "%04d-%02d-%02d", 1900+$y,$mo+1,$d;
}
return $date;
}
sub GetByWordList { sub GetByWordList {
my ($field, $strs) = (@_); my ($field, $strs) = (@_);
...@@ -129,28 +217,75 @@ sub GetByWordListSubstr { ...@@ -129,28 +217,75 @@ sub GetByWordListSubstr {
return \@list; return \@list;
} }
sub LookupNamedQuery {
my ($name) = @_;
confirm_login();
my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"});
my $qname = SqlQuote($name);
SendSQL("SELECT query FROM namedqueries WHERE userid = $userid AND name = $qname");
my $result = FetchOneColumn();
if (!$result) {
my $qname = html_quote($name);
DisplayError("The query named <em>$qname</em> seems to no longer exist.");
exit;
}
return $result;
}
sub GetQuip {
return if !Param('usequip');
sub Error { my $quip;
my ($str) = (@_);
if (!$serverpush) { # This is stupid. We really really need to move the quip list into the DB!
print "Content-type: text/html\n\n"; if (open(COMMENTS, "<data/comments")) {
my @cdata;
push(@cdata, $_) while <COMMENTS>;
close COMMENTS;
$quip = $cdata[int(rand($#cdata + 1))];
} }
PuntTryAgain($str); $quip ||= "Bugzilla would like to put a random quip here, but nobody has entered any.";
return $quip;
} }
sub GetGroupsByGroupSet {
my ($groupset) = @_;
return if !$groupset;
SendSQL("
SELECT bit, name, description, isactive
FROM groups
WHERE (bit & $groupset) != 0
AND isbuggroup != 0
ORDER BY description ");
my @groups;
while (MoreSQLData()) {
my $group = {};
($group->{'bit'}, $group->{'name'},
$group->{'description'}, $group->{'isactive'}) = FetchSQLData();
push(@groups, $group);
}
return \@groups;
}
################################################################################
# Query Generation
################################################################################
sub GenerateSQL { sub GenerateSQL {
my $debug = 0; my $debug = 0;
my ($fieldsref, $supptablesref, $wherepartref, $urlstr) = (@_); my ($fieldsref, $urlstr) = (@_);
my @fields; my @fields;
my @supptables; my @supptables;
my @wherepart; my @wherepart;
@fields = @$fieldsref if $fieldsref; @fields = @$fieldsref if $fieldsref;
@supptables = @$supptablesref if $supptablesref;
@wherepart = @$wherepartref if $wherepartref;
my %F; my %F;
my %M; my %M;
ParseUrlString($urlstr, \%F, \%M); ParseUrlString($urlstr, \%F, \%M);
...@@ -172,10 +307,11 @@ sub GenerateSQL { ...@@ -172,10 +307,11 @@ sub GenerateSQL {
my $c = trim($F{'votes'}); my $c = trim($F{'votes'});
if ($c ne "") { if ($c ne "") {
if ($c !~ /^[0-9]*$/) { if ($c !~ /^[0-9]*$/) {
return Error("The 'At least ___ votes' field must be a\n" . my $htmlc = html_quote($c);
"simple number. You entered \"" . DisplayError("The <em>At least ___ votes</em> field must be
html_quote($c) . "\", which\n" . a simple number. You entered <kbd>$htmlc</kbd>,
"doesn't cut it."); which doesn't cut it.");
exit;
} }
push(@specialchart, ["votes", "greaterthan", $c - 1]); push(@specialchart, ["votes", "greaterthan", $c - 1]);
} }
...@@ -255,9 +391,10 @@ sub GenerateSQL { ...@@ -255,9 +391,10 @@ sub GenerateSQL {
if (@clist) { if (@clist) {
push(@specialchart, \@clist); push(@specialchart, \@clist);
} else { } else {
return Error("You must specify one or more fields in which to\n" . my $htmlemail = html_quote($email);
"search for <tt>" . DisplayError("You must specify one or more fields in which
html_quote($email) . "</tt>.\n"); to search for <tt>$htmlemail</tt>.");
exit;
} }
} }
...@@ -266,10 +403,11 @@ sub GenerateSQL { ...@@ -266,10 +403,11 @@ sub GenerateSQL {
my $c = trim($F{'changedin'}); my $c = trim($F{'changedin'});
if ($c ne "") { if ($c ne "") {
if ($c !~ /^[0-9]*$/) { if ($c !~ /^[0-9]*$/) {
return Error("The 'changed in last ___ days' field must be\n" . my $htmlc = html_quote($c);
"a simple number. You entered \"" . DisplayError("The <em>changed in last ___ days</em> field
html_quote($c) . "\", which\n" . must be a simple number. You entered
"doesn't cut it."); <kbd>$htmlc</kbd>, which doesn't cut it.");
exit;
} }
push(@specialchart, ["changedin", push(@specialchart, ["changedin",
"lessthan", $c + 1]); "lessthan", $c + 1]);
...@@ -417,17 +555,17 @@ sub GenerateSQL { ...@@ -417,17 +555,17 @@ sub GenerateSQL {
$t = "greaterthan"; $t = "greaterthan";
} }
if ($field eq "ispatch" && $v ne "0" && $v ne "1") { if ($field eq "ispatch" && $v ne "0" && $v ne "1") {
return Error("The only legal values for the 'Attachment is patch' " . DisplayError("The only legal values for the <em>Attachment is
"field are 0 and 1."); patch</em> field are 0 and 1.");
exit;
} }
if ($field eq "isobsolete" && $v ne "0" && $v ne "1") { if ($field eq "isobsolete" && $v ne "0" && $v ne "1") {
return Error("The only legal values for the 'Attachment is obsolete' " . DisplayError("The only legal values for the <em>Attachment is
"field are 0 and 1."); obsolete</em> field are 0 and 1.");
exit;
} }
$f = "$table.$field"; $f = "$table.$field";
}, },
# 2001-05-16 myk@mozilla.org: enable querying against attachment status
# if this installation has enabled use of the attachment tracker.
"^attachstatusdefs.name," => sub { "^attachstatusdefs.name," => sub {
# When searching for multiple statuses within a single boolean chart, # When searching for multiple statuses within a single boolean chart,
# we want to match each status record separately. In other words, # we want to match each status record separately. In other words,
...@@ -473,12 +611,13 @@ sub GenerateSQL { ...@@ -473,12 +611,13 @@ sub GenerateSQL {
my $id = GetKeywordIdFromName($value); my $id = GetKeywordIdFromName($value);
if ($id) { if ($id) {
push(@list, "$table.keywordid = $id"); push(@list, "$table.keywordid = $id");
} else { }
return Error("Unknown keyword named <code>" . else {
html_quote($v) . "</code>.\n" . my $htmlv = html_quote($v);
"<P>The legal keyword names are\n" . DisplayError(qq|There is no keyword named <code>$htmlv</code>.
"<A HREF=describekeywords.cgi>" . To search for keywords, consult the
"listed here</A>.\n"); <a href="describekeywords.cgi">list of legal keywords</a>.|);
exit;
} }
} }
my $haveawordterm; my $haveawordterm;
...@@ -667,16 +806,16 @@ sub GenerateSQL { ...@@ -667,16 +806,16 @@ sub GenerateSQL {
} }
# A boolean chart is a way of representing the terms in a logical # A boolean chart is a way of representing the terms in a logical
# expression. Bugzilla builds SQL queries depending on how you enter # expression. Bugzilla builds SQL queries depending on how you enter
# terms into the boolean chart. Boolean charts are represented in # terms into the boolean chart. Boolean charts are represented in
# urls as tree-tuples of (chart id, row, column). The query form # urls as tree-tuples of (chart id, row, column). The query form
# (query.cgi) may contain an arbitrary number of boolean charts where # (query.cgi) may contain an arbitrary number of boolean charts where
# each chart represents a clause in a SQL query. # each chart represents a clause in a SQL query.
# #
# The query form starts out with one boolean chart containing one # The query form starts out with one boolean chart containing one
# row and one column. Extra rows can be created by pressing the # row and one column. Extra rows can be created by pressing the
# AND button at the bottom of the chart. Extra columns are created # AND button at the bottom of the chart. Extra columns are created
# by pressing the OR button at the right end of the chart. Extra # by pressing the OR button at the right end of the chart. Extra
# charts are created by pressing "Add another boolean chart". # charts are created by pressing "Add another boolean chart".
# #
...@@ -705,11 +844,11 @@ sub GenerateSQL { ...@@ -705,11 +844,11 @@ sub GenerateSQL {
# SELECT blah FROM blah WHERE ( (a1 OR a2)AND(b1 OR b2 OR b3)AND(c1)) AND (d1) # SELECT blah FROM blah WHERE ( (a1 OR a2)AND(b1 OR b2 OR b3)AND(c1)) AND (d1)
# #
# The terms within a single row of a boolean chart are all constraints # The terms within a single row of a boolean chart are all constraints
# on a single piece of data. If you're looking for a bug that has two # on a single piece of data. If you're looking for a bug that has two
# different people cc'd on it, then you need to use two boolean charts. # different people cc'd on it, then you need to use two boolean charts.
# This will find bugs with one CC mathing 'foo@blah.org' and and another # This will find bugs with one CC mathing 'foo@blah.org' and and another
# CC matching 'bar@blah.org'. # CC matching 'bar@blah.org'.
# #
# -------------------------------------------------------------- # --------------------------------------------------------------
# CC | equal to # CC | equal to
# foo@blah.org # foo@blah.org
...@@ -717,7 +856,7 @@ sub GenerateSQL { ...@@ -717,7 +856,7 @@ sub GenerateSQL {
# CC | equal to # CC | equal to
# bar@blah.org # bar@blah.org
# #
# If you try to do this query by pressing the AND button in the # If you try to do this query by pressing the AND button in the
# original boolean chart then what you'll get is an expression that # original boolean chart then what you'll get is an expression that
# looks for a single CC where the login name is both "foo@blah.org", # looks for a single CC where the login name is both "foo@blah.org",
# and "bar@blah.org". This is impossible. # and "bar@blah.org". This is impossible.
...@@ -740,7 +879,7 @@ sub GenerateSQL { ...@@ -740,7 +879,7 @@ sub GenerateSQL {
# $ff = qualified field name (field name prefixed by table) # $ff = qualified field name (field name prefixed by table)
# e.g. bugs_activity.bug_id # e.g. bugs_activity.bug_id
# $t = type of query. e.g. "equal to", "changed after", case sensitive substr" # $t = type of query. e.g. "equal to", "changed after", case sensitive substr"
# $v = value - value the user typed in to the form # $v = value - value the user typed in to the form
# $q = sanitized version of user input (SqlQuote($v)) # $q = sanitized version of user input (SqlQuote($v))
# @supptables = Tables and/or table aliases used in query # @supptables = Tables and/or table aliases used in query
# %suppseen = A hash used to store all the tables in supptables to weed # %suppseen = A hash used to store all the tables in supptables to weed
...@@ -814,13 +953,13 @@ sub GenerateSQL { ...@@ -814,13 +953,13 @@ sub GenerateSQL {
} }
if ($term) { if ($term) {
push(@orlist, $term); push(@orlist, $term);
} else { }
my $errstr = "Can't seem to handle " . else {
qq{'<code>$F{"field$chart-$row-$col"}</code>' and } . my $errstr =
qq{'<code>$F{"type$chart-$row-$col"}</code>' } . qq|Cannot seem to handle <code>$F{"field$chart-$row-$col"}</code>
"together"; and <code>$F{"type$chart-$row-$col"}</code> together|;
die "Internal error: $errstr" if $chart < 0; $chart < 0 ? die "Internal error: $errstr"
return Error($errstr); : DisplayError($errstr) && exit;
} }
} }
if (@orlist) { if (@orlist) {
...@@ -839,931 +978,538 @@ sub GenerateSQL { ...@@ -839,931 +978,538 @@ sub GenerateSQL {
$suppseen{$str} = 1; $suppseen{$str} = 1;
} }
} }
my $query = ("SELECT DISTINCT " . join(', ', @fields) .
my $query = ("SELECT " . join(', ', @fields) .
" FROM $suppstring" . " FROM $suppstring" .
" WHERE " . join(' AND ', (@wherepart, @andlist)) . " WHERE " . join(' AND ', (@wherepart, @andlist)));
" GROUP BY bugs.bug_id");
$query = SelectVisible($query, $::userid, $::usergroupset); $query = SelectVisible($query, $::userid, $::usergroupset);
if ($debug) { if ($debug) {
print "<P><CODE>" . value_quote($query) . "</CODE><P>\n"; print "<P><CODE>" . value_quote($query) . "</CODE><P>\n";
exit();
}
return $query;
}
sub LookupNamedQuery {
my ($name) = (@_);
confirm_login();
my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"});
SendSQL("SELECT query FROM namedqueries " .
"WHERE userid = $userid AND name = " . SqlQuote($name));
my $result = FetchOneColumn();
if (!defined $result) {
print "Content-type: text/html\n\n";
PutHeader("Something weird happened");
print qq{The named query $name seems to no longer exist.};
PutFooter();
exit; exit;
} }
return $result; return $query;
} }
$::querytitle = "Bug List"; ################################################################################
# Command Execution
################################################################################
# Figure out if the user wanted to do anything besides just running the query
# they defined on the query page, and take appropriate action.
CMD: for ($::FORM{'cmdtype'}) { CMD: for ($::FORM{'cmdtype'}) {
/^runnamed$/ && do { /^runnamed$/ && do {
$::buffer = LookupNamedQuery($::FORM{"namedcmd"}); $::buffer = LookupNamedQuery($::FORM{"namedcmd"});
$::querytitle = "Bug List: $::FORM{'namedcmd'}"; $vars->{'title'} = "Bug List: $::FORM{'namedcmd'}";
ProcessFormFields($::buffer); ProcessFormFields($::buffer);
last CMD; last CMD;
}; };
/^editnamed$/ && do { /^editnamed$/ && do {
my $url = "query.cgi?" . LookupNamedQuery($::FORM{"namedcmd"}); my $url = "query.cgi?" . LookupNamedQuery($::FORM{"namedcmd"});
print qq{Content-type: text/html print "Refresh: 0; URL=$url\n";
Refresh: 0; URL=$url print "Content-Type: text/html\n\n";
# Generate and return the UI (HTML page) from the appropriate template.
<TITLE>What a hack.</TITLE> $vars->{'title'} = "Loading your query named $::FORM{'namedcmd'}";
<A HREF="$url">Loading your query named <B>$::FORM{'namedcmd'}</B>...</A> $vars->{'url'} = $url;
}; $vars->{'link'} = "Click here if the page does not redisplay automatically.";
$template->process("global/message.html.tmpl", $vars)
|| DisplayError("Template process failed: " . $template->error());
exit; exit;
}; };
/^forgetnamed$/ && do { /^forgetnamed$/ && do {
confirm_login(); confirm_login();
my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"}); my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"});
SendSQL("DELETE FROM namedqueries WHERE userid = $userid " . my $qname = SqlQuote($::FORM{'namedcmd'});
"AND name = " . SqlQuote($::FORM{'namedcmd'})); SendSQL("DELETE FROM namedqueries WHERE userid = $userid AND name = $qname");
print "Content-Type: text/html\n\n";
print "Content-type: text/html\n\n"; # Generate and return the UI (HTML page) from the appropriate template.
PutHeader("Query is gone", ""); $vars->{'title'} = "Query is gone";
$vars->{'message'} = "OK, the <b>$::FORM{'namedcmd'}</b> query is gone.";
print qq{ $vars->{'url'} = "query.cgi";
OK, the <B>$::FORM{'namedcmd'}</B> query is gone. $vars->{'link'} = "Go back to the query page.";
<P> $template->process("global/message.html.tmpl", $vars)
<A HREF="query.cgi">Go back to the query page.</A> || DisplayError("Template process failed: " . $template->error());
};
PutFooter();
exit; exit;
}; };
/^asdefault$/ && do { /^asdefault$/ && do {
confirm_login(); confirm_login();
my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"}); my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"});
print "Content-type: text/html\n\n"; my $qname = SqlQuote($::defaultqueryname);
SendSQL("REPLACE INTO namedqueries (userid, name, query) VALUES " . my $qbuffer = SqlQuote($::buffer);
"($userid, '$::defaultqueryname'," . SendSQL("REPLACE INTO namedqueries (userid, name, query)
SqlQuote($::buffer) . ")"); VALUES ($userid, $qname, $qbuffer)");
PutHeader("OK, default is set"); print "Content-Type: text/html\n\n";
print qq{ # Generate and return the UI (HTML page) from the appropriate template.
OK, you now have a new default query. You may also bookmark the result of any $vars->{'title'} = "OK, default is set";
individual query. $vars->{'message'} = "OK, you now have a new default query. You may
also bookmark the result of any individual query.";
<P><A HREF="query.cgi">Go back to the query page, using the new default.</A> $vars->{'url'} = "query.cgi";
}; $vars->{'link'} = "Go back to the query page, using the new default.";
PutFooter(); $template->process("global/message.html.tmpl", $vars)
exit(); || DisplayError("Template process failed: " . $template->error());
exit;
}; };
/^asnamed$/ && do { /^asnamed$/ && do {
confirm_login(); confirm_login();
my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"}); my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"});
print "Content-type: text/html\n\n";
my $name = trim($::FORM{'newqueryname'}); my $name = trim($::FORM{'newqueryname'});
if ($name eq "" || $name =~ /[<>&]/) { $name
PutHeader("Please pick a valid name for your new query"); || DisplayError("You must enter a name for your query.")
print "Click the <B>Back</B> button and type in a valid name\n"; && exit;
print "for this query. (Query names should not contain unusual\n"; $name =~ /[<>&]/
print "characters.)\n"; && DisplayError("The name of your query cannot contain any
PutFooter(); of the following characters: &lt;, &gt;, &amp;.")
exit(); && exit;
}
$::buffer =~ s/[\&\?]cmdtype=[a-z]+//;
my $qname = SqlQuote($name); my $qname = SqlQuote($name);
my $tofooter= ( $::FORM{'tofooter'} ? 1 : 0 );
SendSQL("SELECT query FROM namedqueries " . $::buffer =~ s/[\&\?]cmdtype=[a-z]+//;
"WHERE userid = $userid AND name = $qname"); my $qbuffer = SqlQuote($::buffer);
if (!FetchOneColumn()) {
SendSQL("REPLACE INTO namedqueries (userid, name, query, linkinfooter) " . my $tofooter= $::FORM{'tofooter'} ? 1 : 0;
"VALUES ($userid, $qname, ". SqlQuote($::buffer) .", ". $tofooter .")");
} else { SendSQL("SELECT query FROM namedqueries WHERE userid = $userid AND name = $qname");
SendSQL("UPDATE namedqueries SET query = " . SqlQuote($::buffer) . "," . if (FetchOneColumn()) {
" linkinfooter = " . $tofooter . SendSQL("UPDATE namedqueries
" WHERE userid = $userid AND name = $qname"); SET query = $qbuffer , linkinfooter = $tofooter
WHERE userid = $userid AND name = $qname");
} }
PutHeader("OK, query saved."); else {
print qq{ SendSQL("REPLACE INTO namedqueries (userid, name, query, linkinfooter)
OK, you have a new query named <code>$name</code> VALUES ($userid, $qname, $qbuffer, $tofooter)");
<P> }
<BR><A HREF="query.cgi">Go back to the query page</A> print "Content-Type: text/html\n\n";
}; # Generate and return the UI (HTML page) from the appropriate template.
PutFooter(); $vars->{'title'} = "OK, query saved.";
$vars->{'message'} = "OK, you have a new query named <code>$name</code>";
$vars->{'url'} = "query.cgi";
$vars->{'link'} = "Go back to the query page.";
$template->process("global/message.html.tmpl", $vars)
|| DisplayError("Template process failed: " . $template->error());
exit; exit;
}; };
} }
if (exists $ENV{'HTTP_USER_AGENT'} && $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/ && $ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/ ) { ################################################################################
# Search for real Netscape 3 and up. http://www.browsercaps.org used as source of # Column Definition
# browsers compatbile with server-push. It's a Netscape hack, incompatbile ################################################################################
# with MSIE and Lynx (at least). Even Communicator 4.51 has bugs with it,
# especially during page reload. # Define the columns that can be selected in a query and/or displayed in a bug
$serverpush = 1; # list. Column records include the following fields:
#
print qq{Content-type: multipart/x-mixed-replace;boundary=thisrandomstring\n # 1. ID: a unique identifier by which the column is referred in code;
--thisrandomstring #
Content-type: text/html\n # 2. Name: The name of the column in the database (may also be an expression
<html><head><title>Bugzilla is pondering your query</title> # that returns the value of the column);
<style type="text/css"> #
.psb { margin-top: 20%; text-align: center; } # 3. Title: The title of the column as displayed to users.
</style></head><body> #
<h1 class="psb">Please stand by ...</h1></body></html> # Note: There are a few hacks in the code that deviate from these definitions.
}; # In particular, when the list is sorted by the "votes" field the word
# Note! HTML header is complete! # "DESC" is added to the end of the field to sort in descending order,
} else { # and the redundant summaryfull column is removed when the client
print "Content-type: text/html\n"; # requests "all" columns.
#Changing attachment to inline to resolve 46897
#zach@zachlipton.com my $columns = {};
print "Content-disposition: inline; filename=bugzilla_bug_list.html\n"; sub DefineColumn {
# Note! Don't finish HTML header yet! Only one newline so far! my ($id, $name, $title) = @_;
$columns->{$id} = { 'name' => $name , 'title' => $title };
} }
sub DefCol {
my ($name, $k, $t, $s, $q) = (@_);
$::key{$name} = $k; # Column: ID Name Title
$::title{$name} = $t; DefineColumn("id" , "bugs.bug_id" , "ID" );
if (defined $s && $s ne "") { DefineColumn("groupset" , "bugs.groupset" , "Groupset" );
$::sortkey{$name} = $s; DefineColumn("opendate" , "bugs.creation_ts" , "Opened" );
DefineColumn("changeddate" , "bugs.delta_ts" , "Changed" );
DefineColumn("severity" , "bugs.bug_severity" , "Severity" );
DefineColumn("priority" , "bugs.priority" , "Priority" );
DefineColumn("platform" , "bugs.rep_platform" , "Platform" );
DefineColumn("owner" , "map_assigned_to.login_name" , "Owner" );
DefineColumn("reporter" , "map_reporter.login_name" , "Reporter" );
DefineColumn("qa_contact" , "map_qa_contact.login_name" , "QA Contact" );
DefineColumn("status" , "bugs.bug_status" , "State" );
DefineColumn("resolution" , "bugs.resolution" , "Result" );
DefineColumn("summary" , "bugs.short_desc" , "Summary" );
DefineColumn("summaryfull" , "bugs.short_desc" , "Summary" );
DefineColumn("status_whiteboard" , "bugs.status_whiteboard" , "Status Summary" );
DefineColumn("component" , "bugs.component" , "Component" );
DefineColumn("product" , "bugs.product" , "Product" );
DefineColumn("version" , "bugs.version" , "Version" );
DefineColumn("os" , "bugs.op_sys" , "OS" );
DefineColumn("target_milestone" , "bugs.target_milestone" , "Target Milestone" );
DefineColumn("votes" , "bugs.votes" , "Votes" );
DefineColumn("keywords" , "bugs.keywords" , "Keywords" );
################################################################################
# Display Column Determination
################################################################################
# Determine the columns that will be displayed in the bug list via the
# columnlist CGI parameter, the user's preferences, or the default.
my @displaycolumns = ();
if (defined $::FORM{'columnlist'}) {
if ($::FORM{'columnlist'} eq "all") {
# If the value of the CGI parameter is "all", display all columns,
# but remove the redundant "summaryfull" column.
@displaycolumns = grep($_ ne 'summaryfull', keys(%$columns));
} }
if (!defined $q || $q eq "") { else {
$q = 0; @displaycolumns = split(/[ ,]+/, $::FORM{'columnlist'});
} }
$::needquote{$name} = $q;
} }
elsif (defined $::COOKIE{'COLUMNLIST'}) {
DefCol("opendate", "unix_timestamp(bugs.creation_ts)", "Opened", # Use the columns listed in the user's preferences.
"bugs.creation_ts"); @displaycolumns = split(/ /, $::COOKIE{'COLUMNLIST'});
DefCol("changeddate", "unix_timestamp(bugs.delta_ts)", "Changed",
"bugs.delta_ts");
DefCol("severity", "substring(bugs.bug_severity, 1, 3)", "Sev",
"bugs.bug_severity");
DefCol("priority", "substring(bugs.priority, 1, 3)", "Pri", "bugs.priority");
DefCol("platform", "substring(bugs.rep_platform, 1, 3)", "Plt",
"bugs.rep_platform");
DefCol("owner", "map_assigned_to.login_name", "Owner",
"map_assigned_to.login_name");
DefCol("reporter", "map_reporter.login_name", "Reporter",
"map_reporter.login_name");
DefCol("qa_contact", "map_qa_contact.login_name", "QAContact", "map_qa_contact.login_name");
DefCol("status", "substring(bugs.bug_status,1,4)", "State", "bugs.bug_status");
DefCol("resolution", "substring(bugs.resolution,1,4)", "Result",
"bugs.resolution");
DefCol("summary", "substring(bugs.short_desc, 1, 60)", "Summary", "bugs.short_desc", 1);
DefCol("summaryfull", "bugs.short_desc", "Summary", "bugs.short_desc", 1);
DefCol("status_whiteboard", "bugs.status_whiteboard", "StatusSummary", "bugs.status_whiteboard", 1);
DefCol("component", "substring(bugs.component, 1, 8)", "Comp",
"bugs.component");
DefCol("product", "substring(bugs.product, 1, 8)", "Product", "bugs.product");
DefCol("version", "substring(bugs.version, 1, 5)", "Vers", "bugs.version");
DefCol("os", "substring(bugs.op_sys, 1, 4)", "OS", "bugs.op_sys");
DefCol("target_milestone", "bugs.target_milestone", "TargetM",
"bugs.target_milestone");
DefCol("votes", "bugs.votes", "Votes", "bugs.votes desc");
DefCol("keywords", "bugs.keywords", "Keywords", "bugs.keywords", 5);
my @collist;
if (defined $::FORM{'columnlist'}) {
@collist = split(/[ ,]+/, $::FORM{'columnlist'});
} elsif (defined $::COOKIE{'COLUMNLIST'}) {
@collist = split(/ /, $::COOKIE{'COLUMNLIST'});
} else {
@collist = @::default_column_list;
} }
else {
my $minvotes; # Use the default list of columns.
if (defined $::FORM{'votes'}) { @displaycolumns = @::default_column_list;
if (trim($::FORM{'votes'}) ne "") {
if (! (grep {/^votes$/} @collist)) {
push(@collist, 'votes');
}
}
} }
# Weed out columns that don't actually exist to prevent the user
# from hacking their column list cookie to grab data to which they
# should not have access. Detaint the data along the way.
@displaycolumns = grep($columns->{$_} && trick_taint($_), @displaycolumns);
my $dotweak = defined $::FORM{'tweak'}; # Remove the "ID" column from the list because bug IDs are always displayed
# and are hard-coded into the display templates.
if ($dotweak) { @displaycolumns = grep($_ ne 'id', @displaycolumns);
confirm_login();
if (!UserInGroup("editbugs")) {
print qq{
Sorry; you do not have sufficient privileges to edit a bunch of bugs
at once.
};
PutFooter();
exit();
}
} else {
quietly_check_login();
}
# IMPORTANT! Never allow the groupset column to be displayed!
@displaycolumns = grep($_ ne 'groupset', @displaycolumns);
my @fields = ("bugs.bug_id", "bugs.groupset"); # Add the votes column to the list of columns to be displayed
# in the bug list if the user is searching for bugs with a certain
# number of votes and the votes column is not already on the list.
push(@displaycolumns, 'votes')
if $::FORM{'votes'} && !grep($_ eq 'votes', @displaycolumns);
foreach my $c (@collist) {
if (exists $::needquote{$c}) {
# The value we are actually using is $::key{$c}, which was created
# using the DefCol() function earlier. We test for the existance
# of $::needsquote{$c} to find out if $c is a legitimate key in the
# hashes that were defined by DefCol(). If $::needsquote{$c} exists,
# then $c is valid and we can use it to look up our key.
# If it doesn't exist, then we know the user is screwing with us
# and we'll just skip it.
trick_taint($c);
push(@fields, $::key{$c});
}
}
################################################################################
# Select Column Determination
################################################################################
if ($dotweak) { # Generate the list of columns that will be selected in the SQL query.
push(@fields, "bugs.product", "bugs.bug_status");
}
# The bug ID and groupset are always selected because bug IDs are always
# displayed and we need the groupset to determine whether or not the bug
# is visible to the user.
my @selectcolumns = ("id", "groupset");
# Display columns are selected because otherwise we could not display them.
push (@selectcolumns, @displaycolumns);
if ($::FORM{'regetlastlist'}) { # If the user is editing multiple bugs, we also make sure to select the product
if (!$::COOKIE{'BUGLIST'}) { # and status because the values of those fields determine what options the user
print qq{ # has for modifying the bugs.
Sorry, I seem to have lost the cookie that recorded the results of your last if ($dotweak) {
query. You will have to start over at the <A HREF="query.cgi">query page</A>. push(@selectcolumns, "product") if !grep($_ eq 'product', @selectcolumns);
}; push(@selectcolumns, "status") if !grep($_ eq 'status', @selectcolumns);
PutFooter();
exit;
}
my @list = split(/:/, $::COOKIE{'BUGLIST'});
$::FORM{'bug_id'} = join(',', @list);
if (!$::FORM{'order'}) {
$::FORM{'order'} = 'reuse last sort';
}
$::buffer = "bug_id=" . $::FORM{'bug_id'} . "&order=" .
url_quote($::FORM{'order'});
} }
################################################################################
# Query Generation
################################################################################
ReconnectToShadowDatabase(); # Convert the list of columns being selected into a list of column names.
my @selectnames = map($columns->{$_}->{'name'}, @selectcolumns);
my $query = GenerateSQL(\@fields, undef, undef, $::buffer); # Generate the basic SQL query that will be used to generate the bug list.
my $query = GenerateSQL(\@selectnames, $::buffer);
if ($::COOKIE{'LASTORDER'}) {
if ((!$::FORM{'order'}) || $::FORM{'order'} =~ /^reuse/i) {
$::FORM{'order'} = url_decode($::COOKIE{'LASTORDER'});
}
}
################################################################################
# Sort Order Determination
################################################################################
if (defined $::FORM{'order'} && $::FORM{'order'} ne "") { # Add to the query some instructions for sorting the bug list.
$query .= " ORDER BY "; if ($::COOKIE{'LASTORDER'} && !$order || $order =~ /^reuse/i) {
$::FORM{'order'} =~ s/votesum/bugs.votes/; # Silly backwards compatability $order = url_decode($::COOKIE{'LASTORDER'});
# hack. }
$::FORM{'order'} =~ s/assign\.login_name/map_assigned_to.login_name/g;
# Another backwards compatability hack.
ORDER: for ($::FORM{'order'}) { if ($order) {
# Convert the value of the "order" form field into a list of columns
# by which to sort the results.
ORDER: for ($order) {
/\./ && do { /\./ && do {
# This (hopefully) already has fieldnames in it, so we're done. # A custom list of columns. Make sure each column is valid.
foreach my $fragment (split(/[,\s]+/, $order)) {
next if $fragment =~ /^asc|desc$/i;
my @columnnames = map($columns->{lc($_)}->{'name'}, keys(%$columns));
if (!grep($_ eq $fragment, @columnnames)) {
my $qfragment = html_quote($fragment);
DisplayError("The custom sort order you specified in your
form submission or cookie contains an invalid
column name <em>$qfragment</em>.");
exit;
}
}
# Now that we have checked that all columns in the order are valid,
# detaint the order string.
trick_taint($order);
last ORDER; last ORDER;
}; };
/Number/ && do { /Number/ && do {
$::FORM{'order'} = "bugs.bug_id"; $order = "bugs.bug_id";
last ORDER; last ORDER;
}; };
/Import/ && do { /Import/ && do {
$::FORM{'order'} = "bugs.priority, bugs.bug_severity"; $order = "bugs.priority, bugs.bug_severity";
last ORDER; last ORDER;
}; };
/Assign/ && do { /Assign/ && do {
$::FORM{'order'} = "map_assigned_to.login_name, bugs.bug_status, priority, bugs.bug_id"; $order = "map_assigned_to.login_name, bugs.bug_status, priority, bugs.bug_id";
last ORDER; last ORDER;
}; };
/Changed/ && do { /Changed/ && do {
$::FORM{'order'} = "bugs.delta_ts, bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id"; $order = "bugs.delta_ts, bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id";
last ORDER; last ORDER;
}; };
# DEFAULT # DEFAULT
$::FORM{'order'} = "bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id"; $order = "bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id";
} }
die "Invalid order: $::FORM{'order'}" unless
$::FORM{'order'} =~ /^([a-zA-Z0-9_., ]+)$/;
$::FORM{'order'} = $1; # detaint this, since we've checked it
# Extra special disgusting hack: if we are ordering by target_milestone, # Extra special disgusting hack: if we are ordering by target_milestone,
# change it to order by the sortkey of the target_milestone first. # change it to order by the sortkey of the target_milestone first.
my $order = $::FORM{'order'};
if ($order =~ /bugs.target_milestone/) { if ($order =~ /bugs.target_milestone/) {
$query =~ s/ WHERE / LEFT JOIN milestones ms_order ON ms_order.value = bugs.target_milestone AND ms_order.product = bugs.product WHERE /;
$order =~ s/bugs.target_milestone/ms_order.sortkey,ms_order.value/; $order =~ s/bugs.target_milestone/ms_order.sortkey,ms_order.value/;
$query =~ s/\sWHERE\s/ LEFT JOIN milestones ms_order ON ms_order.value = bugs.target_milestone AND ms_order.product = bugs.product WHERE /;
} }
$query .= $order; # If we are sorting by votes, sort in descending order.
} if ($order =~ /bugs.votes\s+(asc|desc){0}/i) {
$order =~ s/bugs.votes/bugs.votes desc/i;
}
if ($::FORM{'debug'} && $serverpush) { $query .= " ORDER BY $order ";
print "<P><CODE>" . value_quote($query) . "</CODE><P>\n";
} }
if (Param('expectbigqueries')) { ################################################################################
SendSQL("set option SQL_BIG_TABLES=1"); # Query Execution
} ################################################################################
SendSQL($query); # Time to use server push to display an interim message to the user until
# the query completes and we can display the bug list.
if ($serverpush) {
# Generate HTTP headers.
print "Content-Disposition: inline; filename=$filename\n";
print "Content-Type: multipart/x-mixed-replace;boundary=thisrandomstring\n\n";
print "--thisrandomstring\n";
print "Content-Type: text/html\n\n";
my $count = 0; # Generate and return the UI (HTML page) from the appropriate template.
$::bugl = ""; $template->process("buglist/server-push.html.tmpl", $vars)
sub pnl { || DisplayError("Template process failed: " . $template->error())
my ($str) = (@_); && exit;
$::bugl .= $str;
} }
my $fields = $::buffer; # Connect to the shadow database if this installation is using one to improve
$fields =~ s/[&?]order=[^&]*//g; # query performance.
$fields =~ s/[&?]cmdtype=[^&]*//g; ReconnectToShadowDatabase();
# Tell MySQL to store temporary tables on the hard drive instead of memory
# to avoid "table out of space" errors on MySQL versions less than 3.23.2.
SendSQL("SET OPTION SQL_BIG_TABLES=1") if Param('expectbigqueries');
my $orderpart; # Execute the query.
my $oldorder; SendSQL($query);
if (defined $::FORM{'order'} && trim($::FORM{'order'}) ne "") {
$orderpart = "&order=" . url_quote("$::FORM{'order'}");
$oldorder = url_quote(", $::FORM{'order'}");
} else {
$orderpart = "";
$oldorder = "";
}
if ($dotweak) { ################################################################################
pnl "<FORM NAME=changeform METHOD=POST ACTION=\"process_bug.cgi\">"; # Results Retrieval
} ################################################################################
# Retrieve the query results one row at a time and write the data into a list
# of Perl records.
my @th; my $bugowners = {};
foreach my $c (@collist) { my $bugproducts = {};
if (exists $::needquote{$c}) { my $bugstatuses = {};
my $h = "<TH>";
if (defined $::sortkey{$c}) {
$h .= "<A HREF=\"buglist.cgi?$fields&order=" . url_quote($::sortkey{$c}) . "$oldorder\">$::title{$c}</A>";
} else {
$h .= $::title{$c};
}
$h .= "</TH>";
push(@th, $h);
}
}
my $tablestart = "<TABLE CELLSPACING=0 CELLPADDING=4 WIDTH=100%> my @bugs; # the list of records
<TR ALIGN=LEFT><TH>
<A HREF=\"buglist.cgi?$fields&order=bugs.bug_id\">ID</A>";
my $splitheader = 0; while (my @row = FetchSQLData()) {
if ($::COOKIE{'SPLITHEADER'}) { my $bug = {}; # a record
$splitheader = 1;
}
if ($splitheader) { # Slurp the row of data into the record.
$tablestart =~ s/<TH/<TH COLSPAN="2"/; foreach my $column (@selectcolumns) {
for (my $pass=0 ; $pass<2 ; $pass++) { $bug->{$column} = shift @row;
if ($pass == 1) {
$tablestart .= "</TR>\n<TR><TD></TD>";
}
for (my $i=1-$pass ; $i<@th ; $i += 2) {
my $h = $th[$i];
$h =~ s/TH/TH COLSPAN="2" ALIGN="left"/;
$tablestart .= $h;
}
} }
} else {
$tablestart .= join("", @th);
}
$tablestart .= "\n";
# Process certain values further (i.e. date format conversion).
if ($bug->{'changeddate'}) {
$bug->{'changeddate'} =~
s/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/$1-$2-$3 $4:$5:$6/;
$bug->{'changeddate'} = DiffDate($bug->{'changeddate'});
}
($bug->{'opendate'} = DiffDate($bug->{'opendate'})) if $bug->{'opendate'};
my @row; # Record the owner, product, and status in the big hashes of those things.
my %seen; $bugowners->{$bug->{'owner'}} = 1 if $bug->{'owner'};
my @bugarray; $bugproducts->{$bug->{'product'}} = 1 if $bug->{'product'};
my %prodhash; $bugstatuses->{$bug->{'status'}} = 1 if $bug->{'status'};
my %statushash;
my %ownerhash;
my %qahash;
my $pricol = -1; # Add the record to the list.
my $sevcol = -1; push(@bugs, $bug);
for (my $colcount = 0 ; $colcount < @collist ; $colcount++) {
my $colname = $collist[$colcount];
if ($colname eq "priority") {
$pricol = $colcount;
}
if ($colname eq "severity") {
$sevcol = $colcount;
}
} }
my @weekday= qw( Sun Mon Tue Wed Thu Fri Sat ); # Switch back from the shadow database to the regular database so PutFooter()
# can determine the current user even if the "logincookies" table is corrupted
# in the shadow database.
SendSQL("USE $::db_name");
# Truncate email to 30 chars per bug #103592
my $maxemailsize = 30;
while (@row = FetchSQLData()) {
my $bug_id = shift @row;
my $g = shift @row; # Bug's group set.
if (!defined $seen{$bug_id}) {
$seen{$bug_id} = 1;
$count++;
if ($count % 200 == 0) {
# Too big tables take too much browser memory...
pnl "</TABLE>$tablestart";
}
push @bugarray, $bug_id;
# retrieve this bug's priority and severity, if available,
# by looping through all column names -- gross but functional
my $priority = "unknown";
my $severity;
if ($pricol >= 0) {
$priority = $row[$pricol];
}
if ($sevcol >= 0) {
$severity = $row[$sevcol];
}
my $customstyle = "";
if ($severity) {
if ($severity eq "enh") {
$customstyle = "style='font-style:italic ! important'";
}
if ($severity eq "blo") {
$customstyle = "style='color:red ! important; font-weight:bold ! important'";
}
if ($severity eq "cri") {
$customstyle = "style='color:red; ! important'";
}
}
pnl "<TR VALIGN=TOP ALIGN=LEFT CLASS=$priority $customstyle><TD>";
if ($dotweak) {
pnl "<input type=checkbox name=id_$bug_id>";
}
pnl "<A HREF=\"show_bug.cgi?id=$bug_id\">";
pnl "$bug_id</A>";
if ($g != "0") { pnl "*"; }
pnl " ";
foreach my $c (@collist) {
if (exists $::needquote{$c}) {
my $value = shift @row;
if (!defined $value) {
pnl "<TD>";
next;
}
if ($c eq "owner") {
$ownerhash{$value} = 1;
}
if ($c eq "qa_contact") {
$qahash{$value} = 1;
}
if ( ($c eq "owner" || $c eq "qa_contact" ) &&
length $value > $maxemailsize ) {
my $trunc = substr $value, 0, $maxemailsize;
$value = value_quote($value);
$value = qq|<SPAN TITLE="$value">$trunc...</SPAN>|;
} elsif( $c eq 'changeddate' or $c eq 'opendate' ) {
my $age = time() - $value;
my ($s,$m,$h,$d,$mo,$y,$wd)= localtime $value;
if( $age < 18*60*60 ) {
$value = sprintf "%02d:%02d:%02d", $h,$m,$s;
} elsif ( $age < 6*24*60*60 ) {
$value = sprintf "%s %02d:%02d", $weekday[$wd],$h,$m;
} else {
$value = sprintf "%04d-%02d-%02d", 1900+$y,$mo+1,$d;
}
}
if ($::needquote{$c} || $::needquote{$c} == 5) {
$value = html_quote($value);
} else {
$value = "<nobr>$value</nobr>";
}
pnl "<td class=$c>$value"; ################################################################################
} # Template Variable Definition
} ################################################################################
if ($dotweak) {
my $value = shift @row;
$prodhash{$value} = 1;
$value = shift @row;
$statushash{$value} = 1;
}
pnl "\n";
}
}
my $buglist = join(":", @bugarray);
# Define the variables and functions that will be passed to the UI template.
# This is stupid. We really really need to move the quip list into the DB! $vars->{'bugs'} = \@bugs;
my $quip; $vars->{'columns'} = $columns;
if (Param('usequip')){ $vars->{'displaycolumns'} = \@displaycolumns;
if (open (COMMENTS, "<data/comments")) {
my @cdata;
while (<COMMENTS>) {
push @cdata, $_;
}
close COMMENTS;
$quip = $cdata[int(rand($#cdata + 1))];
}
$quip ||= "Bugzilla would like to put a random quip here, but nobody has entered any.";
}
my @openstates = OpenStates();
$vars->{'openstates'} = \@openstates;
$vars->{'closedstates'} = ['CLOSED', 'VERIFIED', 'RESOLVED'];
# We've done all we can without any output. If we can server push it is time # The list of query fields in URL query string format, used when creating
# take down the waiting page and put up the real one. # URLs to the same query results page with different parameters (such as
if ($serverpush) { # a different sort order or when taking some action on the set of query
print "\n"; # results). To get this string, we start with the raw URL query string
print "--thisrandomstring\n"; # buffer that was created when we initially parsed the URL on script startup,
print "Content-type: text/html\n"; # then we remove all non-query fields from it, f.e. the sort order (order)
print "Content-disposition: inline; filename=bugzilla_bug_list.html\n"; # and command type (cmdtype) fields.
# Note! HTML header not yet closed $vars->{'urlquerypart'} = $::buffer;
} $vars->{'urlquerypart'} =~ s/[&?](order|cmdtype)=[^&]*//g;
my $toolong = 0; $vars->{'order'} = $order;
if ($::FORM{'order'}) {
my $q = url_quote($::FORM{'order'});
my $cookiepath = Param("cookiepath");
print "Set-Cookie: LASTORDER=$q ; path=$cookiepath; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
}
if (length($buglist) < 4000) {
print "Set-Cookie: BUGLIST=$buglist\n\n";
} else {
print "Set-Cookie: BUGLIST=\n\n";
$toolong = 1;
}
PutHeader($::querytitle, undef, "", "", navigation_links($buglist));
# The user's login account name (i.e. email address).
$vars->{'user'} = $::COOKIE{'Bugzilla_login'};
print " $vars->{'caneditbugs'} = UserInGroup('editbugs');
<CENTER> $vars->{'usebuggroups'} = UserInGroup('usebuggroups');
<B>" . time2str("%a %b %e %T %Z %Y", time()) . "</B>";
if (Param('usebuggroups')) { # Whether or not this user is authorized to move bugs to another installation.
print "<BR>* next to a bug number notes a bug not visible to everyone.<BR>"; $vars->{'ismover'} = 1
} if Param('move-enabled')
&& defined($vars->{'user'})
&& Param('movers') =~ /^(\Q$vars->{'user'}\E[,\s])|([,\s]\Q$vars->{'user'}\E[,\s]+)/;
if (defined $::FORM{'debug'}) { my @bugowners = keys %$bugowners;
print "<P><CODE>" . value_quote($query) . "</CODE><P>\n"; if (scalar(@bugowners) > 1 && UserInGroup('editbugs')) {
my $suffix = Param('emailsuffix');
map(s/$/$suffix/, @bugowners) if $suffix;
my $bugowners = join(",", @bugowners);
$vars->{'bugowners'} = $bugowners;
} }
if ($toolong) { if ($::FORM{'debug'}) {
print "<h2>This list is too long for bugzilla's little mind; the\n"; $vars->{'debug'} = 1;
print "Next/Prev/First/Last buttons won't appear.</h2>\n"; $vars->{'query'} = $query;
} }
if (Param('usequip')){ # Whether or not to split the column titles across two rows to make
print "<HR><A HREF=quips.cgi><I>$quip</I></A></CENTER>\n"; # the list more compact.
} $vars->{'splitheader'} = $::COOKIE{'SPLITHEADER'} ? 1 : 0;
print "<HR SIZE=10>";
print "$count bugs found." if $count > 9;
print $tablestart, "\n";
print $::bugl;
print "</TABLE>\n";
if ($count == 0) {
print "Zarro Boogs found.\n";
# I've been asked to explain this ... way back when, when Netscape released
# version 4.0 of its browser, we had a release party. Naturally, there
# had been a big push to try and fix every known bug before the release.
# Naturally, that hadn't actually happened. (This is not unique to
# Netscape or to 4.0; the same thing has happened with every software
# project I've ever seen.) Anyway, at the release party, T-shirts were
# handed out that said something like "Netscape 4.0: Zarro Boogs".
# Just like the software, the T-shirt had no known bugs. Uh-huh.
#
# So, when you query for a list of bugs, and it gets no results, you
# can think of this as a friendly reminder. Of *course* there are bugs
# matching your query, they just aren't in the bugsystem yet...
print qq{<p><A HREF="query.cgi">Query Page</A>\n};
print qq{&nbsp;&nbsp;<A HREF="enter_bug.cgi">Enter New Bug</A>\n};
print qq{<NOBR><A HREF="query.cgi?$::buffer">Edit this query</A></NOBR>\n};
} elsif ($count == 1) {
print "One bug found.\n";
} else {
print "$count bugs found.\n";
}
$vars->{'quip'} = GetQuip() if Param('usequip');
$vars->{'currenttime'} = time2str("%a %b %e %T %Z %Y", time());
# The following variables are used when the user is making changes to multiple bugs.
if ($dotweak) { if ($dotweak) {
GetVersionTable(); $vars->{'dotweak'} = 1;
print " $vars->{'use_keywords'} = 1 if @::legal_keywords;
<SCRIPT>
numelements = document.changeform.elements.length; $vars->{'products'} = \@::legal_product;
function SetCheckboxes(value) { $vars->{'platforms'} = \@::legal_platform;
var item; $vars->{'priorities'} = \@::legal_priority;
for (var i=0 ; i<numelements ; i++) { $vars->{'severities'} = \@::legal_severity;
item = document.changeform.elements\[i\]; $vars->{'resolutions'} = \@::settable_resolution;
item.checked = value;
# The value that represents "don't change the value of this field".
$vars->{'dontchange'} = $::dontchange;
$vars->{'unconfirmedstate'} = $::unconfirmedstate;
$vars->{'bugstatuses'} = [ keys %$bugstatuses ];
# The groups to which the user belongs.
$vars->{'groups'} = GetGroupsByGroupSet($::usergroupset) if $::usergroupset ne '0';
# If all bugs being changed are in the same product, the user can change
# their version and component, so generate a list of products, a list of
# versions for the product (if there is only one product on the list of
# products), and a list of components for the product.
$vars->{'bugproducts'} = [ keys %$bugproducts ];
if (scalar(@{$vars->{'bugproducts'}}) == 1) {
my $product = $vars->{'bugproducts'}->[0];
$vars->{'versions'} = $::versions{$product};
$vars->{'components'} = $::components{$product};
$vars->{'targetmilestones'} = $::target_milestone{$product} if Param('usetargetmilestone');
} }
} }
document.write(\" <input type=button value=\\\"Uncheck All\\\" onclick=\\\"SetCheckboxes(false);\\\"> <input type=button value=\\\"Check All\\\" onclick=\\\"SetCheckboxes(true);\\\">\");
</SCRIPT>";
my $resolution_popup = make_options(\@::settable_resolution, "FIXED");
my @prod_list = keys %prodhash;
my @list = @prod_list;
my @legal_versions;
my @legal_component;
if (1 == @prod_list) {
@legal_versions = @{$::versions{$prod_list[0]}};
@legal_component = @{$::components{$prod_list[0]}};
}
my $version_popup = make_options(\@legal_versions, $::dontchange);
my $platform_popup = make_options(\@::legal_platform, $::dontchange);
my $priority_popup = make_options(\@::legal_priority, $::dontchange);
my $sev_popup = make_options(\@::legal_severity, $::dontchange);
my $component_popup = make_options(\@legal_component, $::dontchange);
my $product_popup = make_options(\@::legal_product, $::dontchange);
print "
<hr>
<TABLE>
<TR>
<TD ALIGN=RIGHT><B>Product:</B></TD>
<TD><SELECT NAME=product>$product_popup</SELECT></TD>
<TD ALIGN=RIGHT><B>Version:</B></TD>
<TD><SELECT NAME=version>$version_popup</SELECT></TD>
<TR>
<TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#rep_platform\">Platform:</A></B></TD>
<TD><SELECT NAME=rep_platform>$platform_popup</SELECT></TD>
<TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#priority\">Priority:</A></B></TD>
<TD><SELECT NAME=priority>$priority_popup</SELECT></TD>
</TR>
<TR>
<TD ALIGN=RIGHT><B>Component:</B></TD>
<TD><SELECT NAME=component>$component_popup</SELECT></TD>
<TD ALIGN=RIGHT><B><A HREF=\"bug_status.html#severity\">Severity:</A></B></TD>
<TD><SELECT NAME=bug_severity>$sev_popup</SELECT></TD>
</TR>";
if (Param("usetargetmilestone")) {
my @legal_milestone;
if(1 == @prod_list) {
@legal_milestone = @{$::target_milestone{$prod_list[0]}};
}
my $tfm_popup = make_options(\@legal_milestone, $::dontchange);
print "
<TR>
<TD ALIGN=RIGHT><B>Target milestone:</B></TD>
<TD><SELECT NAME=target_milestone>$tfm_popup</SELECT></TD>
</TR>";
}
if (Param("useqacontact")) {
print "
<TR>
<TD><B>QA Contact:</B></TD>
<TD COLSPAN=3><INPUT NAME=qa_contact SIZE=32 VALUE=\"" .
value_quote($::dontchange) . "\"></TD>
</TR>";
}
print qq{
<TR><TD ALIGN="RIGHT"><B>CC List:</B></TD>
<TD COLSPAN=3><INPUT NAME="masscc" SIZE=32 VALUE="">
<SELECT NAME="ccaction">
<OPTION VALUE="add">Add these to the CC List
<OPTION VALUE="remove">Remove these from the CC List
</SELECT>
</TD>
</TR>
};
if (@::legal_keywords) {
print qq{
<TR><TD><B><A HREF="describekeywords.cgi">Keywords</A>:</TD>
<TD COLSPAN=3><INPUT NAME=keywords SIZE=32 VALUE="">
<SELECT NAME="keywordaction">
<OPTION VALUE="add">Add these keywords
<OPTION VALUE="delete">Delete these keywords
<OPTION VALUE="makeexact">Make the keywords be exactly this list
</SELECT>
</TD>
</TR>
};
}
################################################################################
# HTTP Header Generation
################################################################################
print "</TABLE> # If we are doing server push, output a separator string.
print "\n--thisrandomstring\n" if $serverpush;
# Generate HTTP headers
<INPUT NAME=multiupdate value=Y TYPE=hidden> # Suggest a name for the bug list if the user wants to save it as a file.
# If we are doing server push, then we did this already in the HTTP headers
# that started the server push, so we don't have to do it again here.
print "Content-Disposition: inline; filename=$filename\n" unless $serverpush;
<B>Additional Comments:</B> if ($format->{'extension'} eq "html") {
<BR> print "Content-Type: text/html\n";
<TEXTAREA WRAP=HARD NAME=comment ROWS=5 COLS=80></TEXTAREA><BR>";
if($::usergroupset ne '0') { if ($order) {
SendSQL("select bit, name, description, isactive ". my $qorder = url_quote($order);
"from groups where bit & $::usergroupset != 0 ". print "Set-Cookie: LASTORDER=$qorder ; path=/; expires=Sun, 30-Jun-2029 00:00:00 GMT\n";
"and isbuggroup != 0 ".
"order by description");
# We only print out a header bit for this section if there are any
# results.
my $groupFound = 0;
my $inactiveFound = 0;
while (MoreSQLData()) {
my ($bit, $groupname, $description, $isactive) = (FetchSQLData());
if(($prodhash{$groupname}) || (!defined($::proddesc{$groupname}))) {
if(!$groupFound) {
print "<B>Groupset:</B><BR>\n";
print "<TABLE BORDER=1><TR>\n";
print "<TH ALIGN=center VALIGN=middle>Don't<br>change<br>this group<br>restriction</TD>\n";
print "<TH ALIGN=center VALIGN=middle>Remove<br>bugs<br>from this<br>group</TD>\n";
print "<TH ALIGN=center VALIGN=middle>Add<br>bugs<br>to this<br>group</TD>\n";
print "<TH ALIGN=left VALIGN=middle>Group name:</TD></TR>\n";
$groupFound = 1;
}
# Modifying this to use radio buttons instead
print "<TR>";
print "<TD ALIGN=center><input type=radio name=\"bit-$bit\" value=\"-1\" checked></TD>\n";
print "<TD ALIGN=center><input type=radio name=\"bit-$bit\" value=\"0\"></TD>\n";
if ($isactive) {
print "<TD ALIGN=center><input type=radio name=\"bit-$bit\" value=\"1\"></TD>\n";
} else {
$inactiveFound = 1;
print "<TD>&nbsp;</TD>\n";
}
print "<TD>";
if(!$isactive) {
print "<I>";
}
print "$description";
if(!$isactive) {
print "</I>";
}
print "</TD></TR>\n";
}
} }
# Add in some blank space for legibility my $bugids = join(":", map( $_->{'id'}, @bugs));
if($groupFound) { if (length($bugids) < 4000) {
print "</TABLE>\n"; print "Set-Cookie: BUGLIST=$bugids\n";
if ($inactiveFound) {
print "<FONT SIZE=\"-1\">(Note: Bugs may not be added to inactive groups (<I>italicized</I>), only removed)</FONT><BR>\n";
}
print "<BR><BR>\n";
} }
} else {
print "Set-Cookie: BUGLIST=\n";
$vars->{'toolong'} = 1;
# knum is which knob number we're generating, in javascript terms.
my $knum = 0;
print "
<INPUT TYPE=radio NAME=knob VALUE=none CHECKED>
Do nothing else<br>";
$knum++;
if ($statushash{$::unconfirmedstate} && 1 == scalar(keys(%statushash))) {
print "
<INPUT TYPE=radio NAME=knob VALUE=confirm>
Confirm bugs (change status to <b>NEW</b>)<br>";
$knum++;
}
print "
<INPUT TYPE=radio NAME=knob VALUE=accept>
Accept bugs (change status to <b>ASSIGNED</b>)<br>";
$knum++;
if (!defined $statushash{'CLOSED'} &&
!defined $statushash{'VERIFIED'} &&
!defined $statushash{'RESOLVED'}) {
print "
<INPUT TYPE=radio NAME=knob VALUE=clearresolution>
Clear the resolution<br>";
$knum++;
print "
<INPUT TYPE=radio NAME=knob VALUE=resolve>
Resolve bugs, changing <A HREF=\"bug_status.html\">resolution</A> to
<SELECT NAME=resolution
ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\">
$resolution_popup</SELECT><br>";
$knum++;
}
if (!defined $statushash{'NEW'} &&
!defined $statushash{'ASSIGNED'} &&
!defined $statushash{'REOPENED'}) {
print "
<INPUT TYPE=radio NAME=knob VALUE=reopen> Reopen bugs<br>";
$knum++;
}
my @statuskeys = keys %statushash;
if (1 == @statuskeys) {
if (defined $statushash{'RESOLVED'}) {
print "
<INPUT TYPE=radio NAME=knob VALUE=verify>
Mark bugs as <b>VERIFIED</b><br>";
$knum++;
}
if (defined $statushash{'VERIFIED'}) {
print "
<INPUT TYPE=radio NAME=knob VALUE=close>
Mark bugs as <b>CLOSED</b><br>";
$knum++;
}
} }
print " }
<INPUT TYPE=radio NAME=knob VALUE=reassign> else {
<A HREF=\"bug_status.html#assigned_to\">Reassign</A> bugs to print "Content-Type: $format->{'contenttype'}\n";
<INPUT NAME=assigned_to SIZE=32
ONCHANGE=\"document.changeform.knob\[$knum\].checked=true\"
VALUE=\"$::COOKIE{'Bugzilla_login'}\"><br>";
$knum++;
print "<INPUT TYPE=radio NAME=knob VALUE=reassignbycomponent>
Reassign bugs to owner of selected component<br>";
$knum++;
print "
<p>
<font size=-1>
To make changes to a bunch of bugs at once:
<ol>
<li> Put check boxes next to the bugs you want to change.
<li> Adjust above form elements. (If the change you are making requires
an explanation, include it in the comments box).
<li> Click the below \"Commit\" button.
</ol></font>
<INPUT TYPE=SUBMIT VALUE=Commit>";
my $movers = Param("movers");
$movers =~ s/\s?,\s?/|/g;
$movers =~ s/@/\@/g;
if ( Param("move-enabled")
&& (defined $::COOKIE{"Bugzilla_login"})
&& ($::COOKIE{"Bugzilla_login"} =~ /($movers)/) ){
print "<P>";
print "<INPUT TYPE=\"SUBMIT\" NAME=\"action\" VALUE=\"";
print Param("move-button-text") . "\">";
}
print "</FORM><hr>\n";
} }
print "\n"; # end HTTP headers
if ($count > 0) {
print "<FORM METHOD=POST ACTION=\"long_list.cgi\">
<INPUT TYPE=HIDDEN NAME=buglist VALUE=$buglist>
<INPUT TYPE=SUBMIT VALUE=\"Long Format\">
<NOBR><A HREF=\"query.cgi\">Query Page</A></NOBR>
&nbsp;&nbsp;
<NOBR><A HREF=\"enter_bug.cgi\">Enter New Bug</A></NOBR>
&nbsp;&nbsp;
<NOBR><A HREF=\"colchange.cgi?$::buffer\">Change columns</A></NOBR>";
if (!$dotweak && $count > 1 && UserInGroup("editbugs")) {
print "&nbsp;&nbsp;\n";
print "<NOBR><A HREF=\"buglist.cgi?$fields$orderpart&tweak=1\">";
print "Change several bugs at once</A></NOBR>\n";
}
my @owners = sort(keys(%ownerhash));
my $suffix = Param('emailsuffix');
if (@owners > 1 && UserInGroup("editbugs")) {
if ($suffix ne "") {
map(s/$/$suffix/, @owners);
}
my $list = join(',', @owners);
print qq{&nbsp;&nbsp;\n};
print qq{<A HREF="mailto:$list">Send&nbsp;mail&nbsp;to&nbsp;bug&nbsp;owners</A>\n};
}
my @qacontacts = sort(keys(%qahash));
if (@qacontacts > 1 && UserInGroup("editbugs") && Param("useqacontact")) {
if ($suffix ne "") {
map(s/$/$suffix/, @qacontacts);
}
my $list = join(',', @qacontacts);
print qq{&nbsp;&nbsp;\n};
print qq{<A HREF="mailto:$list">Send&nbsp;mail&nbsp;to&nbsp;bug&nbsp;QA&nbsp;contacts</A>\n};
}
print qq{&nbsp;&nbsp;\n};
print qq{<NOBR><A HREF="query.cgi?$::buffer">Edit this query</A></NOBR>\n};
print "</FORM>\n"; ################################################################################
} # Content Generation
################################################################################
# 2001-06-20, myk@mozilla.org, bug 47914: # Generate and return the UI (HTML page) from the appropriate template.
# Switch back from the shadow database to the regular database $template->process("buglist/$format->{'template'}", $vars)
# so that PutFooter() can determine the current user even if || DisplayError("Template process failed: " . $template->error())
# the "logincookies" table is corrupted in the shadow database. && exit;
SendSQL("USE $::db_name");
PutFooter();
if ($serverpush) { ################################################################################
print "\n--thisrandomstring--\n"; # Script Conclusion
} ################################################################################
print "\n--thisrandomstring--\n" if $serverpush;
...@@ -193,6 +193,7 @@ unless (have_vers("Date::Parse",0)) { push @missing,"Date::Parse" } ...@@ -193,6 +193,7 @@ unless (have_vers("Date::Parse",0)) { push @missing,"Date::Parse" }
unless (have_vers("AppConfig","1.52")) { push @missing,"AppConfig" } unless (have_vers("AppConfig","1.52")) { push @missing,"AppConfig" }
unless (have_vers("Template","2.06")) { push @missing,"Template" } unless (have_vers("Template","2.06")) { push @missing,"Template" }
unless (have_vers("Text::Wrap","2001.0131")) { push @missing,"Text::Wrap" } unless (have_vers("Text::Wrap","2001.0131")) { push @missing,"Text::Wrap" }
unless (have_vers("File::Spec", "0.82")) { push @missing,"File::Spec" }
# If CGI::Carp was loaded successfully for version checking, it changes the # If CGI::Carp was loaded successfully for version checking, it changes the
# die and warn handlers, we don't want them changed, so we need to stash the # die and warn handlers, we don't want them changed, so we need to stash the
...@@ -488,6 +489,21 @@ LocalVar('platforms', ' ...@@ -488,6 +489,21 @@ LocalVar('platforms', '
LocalVar('contenttypes', '
#
# The types of content that template files can generate, indexed by file extension.
#
$contenttypes = {
"html" => "text/html" ,
"rdf" => "application/xml" ,
"xml" => "text/xml" ,
"js" => "application/x-javascript" ,
};
');
if ($newstuff ne "") { if ($newstuff ne "") {
print "\nThis version of Bugzilla contains some variables that you may want\n", print "\nThis version of Bugzilla contains some variables that you may want\n",
"to change and adapt to your local settings. Please edit the file\n", "to change and adapt to your local settings. Please edit the file\n",
......
/* 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.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s): Myk Melez <myk@mozilla.org>
*/
/* Right align bug IDs. */
.bz_id_column { text-align: right; }
/* Style bug rows according to severity. */
.bz_blocker { color: red; font-weight: bold; }
.bz_critical { color: red; }
.bz_enhancement { font-style: italic; }
/* Style secure bugs if the installation is not using bug groups.
* Installations that *are* using bug groups are likely to be using
* them for almost all bugs, in which case special styling is not
* informative and generally a nuisance.
*/
.bz_secure { color: black; background-color: lightgrey; }
/* Align columns in the "change multiple bugs" form to the right. */
table#form tr th { text-align: right; }
...@@ -35,6 +35,7 @@ sub globals_pl_sillyness { ...@@ -35,6 +35,7 @@ sub globals_pl_sillyness {
my $zz; my $zz;
$zz = @main::SqlStateStack; $zz = @main::SqlStateStack;
$zz = @main::chooseone; $zz = @main::chooseone;
$zz = $main::contenttypes;
$zz = @main::default_column_list; $zz = @main::default_column_list;
$zz = $main::defaultqueryname; $zz = $main::defaultqueryname;
$zz = @main::dontchange; $zz = @main::dontchange;
...@@ -54,8 +55,8 @@ sub globals_pl_sillyness { ...@@ -54,8 +55,8 @@ sub globals_pl_sillyness {
$zz = %main::proddesc; $zz = %main::proddesc;
$zz = @main::prodmaxvotes; $zz = @main::prodmaxvotes;
$zz = $main::superusergroupset; $zz = $main::superusergroupset;
$zz = $main::userid;
$zz = $main::template; $zz = $main::template;
$zz = $main::userid;
$zz = $main::vars; $zz = $main::vars;
} }
...@@ -79,6 +80,9 @@ use Date::Parse; # For str2time(). ...@@ -79,6 +80,9 @@ use Date::Parse; # For str2time().
#use Carp; # for confess #use Carp; # for confess
use RelationSet; use RelationSet;
# Use standard Perl libraries for cross-platform file/directory manipulation.
use File::Spec;
# Some environment variables are not taint safe # Some environment variables are not taint safe
delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
...@@ -1611,7 +1615,9 @@ $::template ||= Template->new( ...@@ -1611,7 +1615,9 @@ $::template ||= Template->new(
$var =~ s/\n/\\n/g; $var =~ s/\n/\\n/g;
$var =~ s/\r/\\r/g; $var =~ s/\r/\\r/g;
return $var; return $var;
} } ,
html => \&html_quote ,
} , } ,
} }
) || DisplayError("Template creation failed: " . Template->error()) ) || DisplayError("Template creation failed: " . Template->error())
...@@ -1639,6 +1645,116 @@ $Template::Stash::LIST_OPS->{ containsany } = ...@@ -1639,6 +1645,116 @@ $Template::Stash::LIST_OPS->{ containsany } =
return 0; return 0;
}; };
sub GetOutputFormats {
# Builds a set of possible output formats for a script by looking for
# format files in the appropriate template directories as specified by
# the template include path, the sub-directory parameter, and the
# template name parameter.
# This function is relevant for scripts with one basic function whose
# results can be represented in multiple formats, f.e. buglist.cgi,
# which has one function (query and display of a list of bugs) that can
# be represented in multiple formats (i.e. html, rdf, xml, etc.).
# It is *not* relevant for scripts with several functions but only one
# basic output format, f.e. editattachstatuses.cgi, which not only lists
# statuses but also provides adding, editing, and deleting functions.
# (although it may be possible to make this function applicable under
# these circumstances with minimal modification).
# Format files have names that look like SCRIPT-FORMAT.EXT.tmpl, where
# SCRIPT is the name of the CGI script being invoked, SUBDIR is the name
# of the template sub-directory, FORMAT is the name of the format, and EXT
# is the filename extension identifying the content type of the output.
# When a format file is found, a record for that format is added to
# the hash of format records, indexed by format name, with each record
# containing the name of the format file, its filename extension,
# and its content type (obtained by reference to the $::contenttypes
# hash defined in localconfig).
my ($subdir, $script) = @_;
# A set of output format records, indexed by format name, each record
# containing template, extension, and contenttype fields.
my $formats = {};
# Get the template include path from the template object.
my $includepath = $::template->context->{ LOAD_TEMPLATES }->[0]->include_path();
# Loop over each include directory in reverse so that format files
# earlier in the path override files with the same name later in
# the path (i.e. "custom" formats override "default" ones).
foreach my $path (reverse @$includepath) {
# Get the list of files in the given sub-directory if it exists.
my $dirname = File::Spec->catdir($path, $subdir);
opendir(SUBDIR, $dirname) || next;
my @files = readdir SUBDIR;
closedir SUBDIR;
# Loop over each file in the sub-directory looking for format files
# (files whose name looks like SCRIPT-FORMAT.EXT.tmpl).
foreach my $file (@files) {
if ($file =~ /^$script-(.+)\.(.+)\.(tmpl)$/) {
$formats->{$1} = {
'template' => $file ,
'extension' => $2 ,
'contenttype' => $::contenttypes->{$2} || "text/plain" ,
};
}
}
}
return $formats;
}
sub ValidateOutputFormat {
my ($format, $script, $subdir) = @_;
# If the script name is undefined, assume the script currently being
# executed, deriving its name from Perl's built-in $0 (program name) var.
if (!defined($script)) {
my ($volume, $dirs, $filename) = File::Spec->splitpath($0);
$filename =~ /^(.+)\.cgi$/;
$script = $1
|| DisplayError("Could not determine the name of the script.")
&& exit;
}
# If the format name is undefined or the default format is specified,
# do not do any validation but instead return the default format.
if (!defined($format) || $format eq "default") {
return
{
'template' => "$script.html.tmpl" ,
'extension' => "html" ,
'contenttype' => "text/html" ,
};
}
# If the subdirectory name is undefined, assume the script name.
$subdir = $script if !defined($subdir);
# Get the list of output formats supported by this script.
my $formats = GetOutputFormats($subdir, $script);
# Validate the output format requested by the user.
if (!$formats->{$format}) {
my $escapedname = html_quote($format);
DisplayError("The <em>$escapedname</em> output format is not
supported by this script. Supported formats (besides the
default HTML format) are <em>" .
join("</em>, <em>", map(html_quote($_), keys(%$formats))) .
"</em>.");
exit;
}
# Return the validated output format.
return $formats->{$format};
}
###############################################################################
# Add a "substr" method to the Template Toolkit's "scalar" object # Add a "substr" method to the Template Toolkit's "scalar" object
# that returns a substring of a string. # that returns a substring of a string.
$Template::Stash::SCALAR_OPS->{ substr } = $Template::Stash::SCALAR_OPS->{ substr } =
......
/* 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.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s): Myk Melez <myk@mozilla.org>
*/
/* Right align bug IDs. */
.bz_id_column { text-align: right; }
/* Style bug rows according to severity. */
.bz_blocker { color: red; font-weight: bold; }
.bz_critical { color: red; }
.bz_enhancement { font-style: italic; }
/* Style secure bugs if the installation is not using bug groups.
* Installations that *are* using bug groups are likely to be using
* them for almost all bugs, in which case special styling is not
* informative and generally a nuisance.
*/
.bz_secure { color: black; background-color: lightgrey; }
/* Align columns in the "change multiple bugs" form to the right. */
table#form tr th { text-align: right; }
[%# 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.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:bz="http://www.bugzilla.org/rdf#">
<bz:result about="[% Param('urlbase') %]buglist.cgi?[% urlquerypart FILTER html %]">
<bz:bugs>
<Seq>
[% FOREACH bug = bugs %]
<li>
<bz:bug about="[% Param('urlbase') %]show_bug.cgi?id=[% bug.id %]">
<bz:id>[% bug.id %]</bz:id>
[% FOREACH column = displaycolumns %]
<bz:[% column %]>[% bug.$column FILTER html %]</bz:[% column %]>
[% END %]
</bz:bug>
</li>
[% END %]
</Seq>
</bz:bugs>
</bz:result>
</RDF>
[%# 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.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
[%############################################################################%]
[%# Initialization #%]
[%############################################################################%]
[% DEFAULT title = "Bug List" %]
[% title = title FILTER html %]
[%############################################################################%]
[%# Bug Table #%]
[%############################################################################%]
<html>
<head>
<title>[% title %]</title>
<link href="css/buglist.css" rel="stylesheet" type="text/css" />
</head>
<body>
[% PROCESS buglist/table.tmpl %]
</body>
</html>
[%# 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.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
[%############################################################################%]
[%# Template Initialization #%]
[%############################################################################%]
[% DEFAULT title = "Bug List" %]
[% style_url = "css/buglist.css" %]
[%############################################################################%]
[%# Page Header #%]
[%############################################################################%]
[% PROCESS global/header
title = title
style = style
%]
<div align="center">
<b>[% currenttime %]</b><br />
[% IF debug %]
<p>[% query FILTER html %]</p>
[% END %]
[% IF quip %]
<a href="quips.cgi"><i>[% quip %]</i></a>
[% END %]
</div>
[% IF toolong %]
<h2>
This list is too long for Bugzilla's little mind; the
Next/Prev/First/Last buttons won't appear on individual bugs.
</h2>
[% END %]
<hr />
[%############################################################################%]
[%# Preceding Status Line #%]
[%############################################################################%]
[% IF bugs.size > 9 %]
[% bugs.size %] bugs found.
[% END %]
[%############################################################################%]
[%# Start of Change Form #%]
[%############################################################################%]
[% IF dotweak %]
<form name="changeform" method="post" action="process_bug.cgi">
[% END %]
[%############################################################################%]
[%# Bug Table #%]
[%############################################################################%]
[% FLUSH %]
[% PROCESS buglist/table.tmpl %]
[%############################################################################%]
[%# Succeeding Status Line #%]
[%############################################################################%]
[% IF bugs.count == 0 %]
Zarro Boogs found.
<p>
<a href="query.cgi">Query Page</a>
&nbsp;&nbsp;<a href="enter_bug.cgi">Enter New Bug</a>
<a href="query.cgi?[% urlquerypart %]">Edit this query</a>
</p>
[% ELSIF bugs.count == 1 %]
One bug found.
[% ELSE %]
[% bugs.size %] bugs found.
[% END %]
<br />
[%############################################################################%]
[%# Rest of Change Form #%]
[%############################################################################%]
[% IF dotweak %]
[% PROCESS "buglist/change-form.tmpl" %]
</form>
<hr />
[% END %]
[%############################################################################%]
[%# Navigation Bar #%]
[%############################################################################%]
[% IF bugs.size > 0 %]
<form method="post" action="long_list.cgi">
<input type="hidden" name="buglist" value="[% buglist %]">
<input type="submit" value="Long Format">
<a href="query.cgi">Query Page</a> &nbsp;&nbsp;
<a href="enter_bug.cgi">Enter New Bug</a> &nbsp;&nbsp;
<a href="colchange.cgi?[% urlquerypart %]">Change Columns</a> &nbsp;&nbsp;
[% IF bugs.size > 1 && caneditbugs && !dotweak %]
<a href="buglist.cgi?[% urlquerypart %]
[%- "&order=$order" FILTER uri html IF order %]&tweak=1">Change Several
Bugs at Once</a>
&nbsp;&nbsp;
[% END %]
[% IF bugowners %]
<a href="mailto:[% bugowners %]">Send Mail to Bug Owners</a> &nbsp;&nbsp;
[% END %]
<a href="query.cgi?[% urlquerypart %]">Edit this Query</a> &nbsp;&nbsp;
</form>
[% END %]
[%############################################################################%]
[%# Page Footer #%]
[%############################################################################%]
[% PROCESS global/footer %]
[%# 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.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
<script type="text/javascript" language="JavaScript">
var numelements = document.forms.changeform.elements.length;
function SetCheckboxes(value) {
var item;
for (var i=0 ; i<numelements ; i++) {
item = document.forms.changeform.elements[i];
item.checked = value;
}
}
document.write(' <input type="button" value="Uncheck All" onclick="SetCheckboxes(false);">');
document.write(' <input type="button" value="Check All" onclick="SetCheckboxes(true);">');
</script>
<hr />
<p><font size="-1">
To change multiple bugs:
<ol>
<li>Check the bugs you want to change above.</li>
<li>Make your changes in the form fields below. If the change
you are making requires an explanation, include it in
the comments box.</li>
<li>Click the <em>Commit</em> button.</li>
</ol>
</font></p>
<table id="form">
<tr>
<th><label for="product">Product:</label></th>
<td>
[% PROCESS selectmenu menuname = "product"
menuitems = products %]
</td>
<th><label for="version">Version:</label></th>
<td>
[% PROCESS selectmenu menuname = "version"
menuitems = versions %]
</td>
</tr>
<tr>
<th>
<label for="rep_platform">
<a href="bug_status.html#rep_platform">Platform:</a>
</label>
</th>
<td>
[% PROCESS selectmenu menuname = "rep_platform"
menuitems = platforms %]
</td>
<th>
<label for="priority">
<a href="bug_status.html#priority">Priority:</a>
</label>
</th>
<td>
[% PROCESS selectmenu menuname = "priority"
menuitems = priorities %]
</td>
</tr>
<tr>
<th><label for="component">Component:</label></th>
<td>
[% PROCESS selectmenu menuname = "component"
menuitems = components %]
</td>
<th>
<label for="severity">
<a href="bug_status.html#severity">Severity:</a>
</label>
</th>
<td>
[% PROCESS selectmenu menuname = "severity"
menuitems = severities %]
</td>
</tr>
<tr>
<th><label for="target_milestone">Target Milestone:</label></th>
<td colspan="3">
[% PROCESS selectmenu menuname = "target_milestone"
menuitems = targetmilestones %]
</td>
</tr>
[% IF Param("useqacontact") %]
<tr>
<th><label for="qa_contact">QA Contact:</label></th>
<td colspan="3">
<input id="qa_contact"
name="qa_contact"
value="[% dontchange FILTER html %]"
size="32">
</td>
</tr>
[% END %]
<tr>
<th><label for="masscc">CC List:</label></th>
<td colspan="3">
<input id="masscc" name="masscc" size="32">
<select name="ccaction">
<option value="add">Add these to the CC List</option>
<option value="remove">Remove these from the CC List</option>
</select>
</td>
</tr>
[% IF use_keywords %]
<tr>
<th>
<label for="keywords">
<a href="describekeywords.cgi">Keywords:</a>
</label>
</th>
<td colspan="3">
<input id="keywords" name="keywords" size="32">
<select name="keywordaction">
<option value="add">Add these keywords</option>
<option value="delete">Delete these keywords</option>
<option value="makeexact">Make the keywords be exactly this list</option>
</select>
</td>
</tr>
[% END %]
<tr>
<th>Depends on:</th>
<td colspan="3">
<input id="dependson" name="dependson" size="32">
<select name="dependsonaction">
<option value="add">Add these dependencies</option>
<option value="delete">Remove these dependencies</option>
<option value="makeexact">Make the dependencies be exactly this list</option>
</select>
</td>
</tr>
<tr>
<th>Blocks:</th>
<td colspan="3">
<input id="blocked" name="blocked" size="32">
<select name="blockedaction">
<option value="add">Add these dependencies</option>
<option value="delete">Remove these dependencies</option>
<option value="makeexact">Make the dependencies be exactly this list</option>
</select>
</td>
</tr>
</table>
<input type="hidden" name="multiupdate" value="Y">
<label for="comment"><b>Additional Comments:</b></label><br />
<textarea id="comment" name="comment" rows="5" cols="80" wrap="hard"></textarea><br />
[% IF groups.size > 0 %]
<b>Groupset:</b><br />
<table border="1">
<tr>
<th>Don't<br />change<br />this group<br />restriction</td>
<th>Remove<br />bugs<br />from this<br />group</td>
<th>Add<br />bugs<br />to this<br />group</td>
<th>Group Name:</td>
</tr>
[% FOREACH group = groups %]
<tr>
<td align="center">
<input type="radio" name="bit-[% group.bit %]" value="-1" checked>
</td>
<td align="center">
<input type="radio" name="bit-[% group.bit %]" value="0">
</td>
[% IF group.isactive %]
<td align="center">
<input type="radio" name="bit-[% group.bit %]" value="1">
</td>
[% ELSE %]
<td>&nbsp;</td>
[% foundinactive = 1 %]
[% END %]
<td>
[% IF group.isactive %]
[% group.description %]
[% ELSE %]
[% group.description FILTER strike %]
[% END %]
</td>
</tr>
[% END %]
</table>
[% IF foundinactive %]
<font size="-1">(Note: Bugs may not be added to <strike>inactive
groups</strike>, only removed.)</font><br />
[% END %]
[% END %]
[% knum = 0 %]
<input id="knob-none" type="radio" name="knob" value="none" CHECKED>
<label for="knob-none">Do nothing else</label><br />
[% IF bugstatuses.size == 1 && bugstatuses.0 == unconfirmedstate %]
[% knum = knum + 1 %]
<input id="knob-confirm" type="radio" name="knob" value="confirm>
<label for="knob-confirm">
Confirm bugs (change status to <b>NEW</b>)
</label><br />
[% END %]
[% knum = knum + 1 %]
<input id="knob-accept" type="radio" name="knob" value="accept">
<label for="knob-accept">
Accept bugs (change status to <b>ASSIGNED</b>)
</label><br />
[%# If all the bugs being changed are open, allow the user to close them. %]
[% IF !bugstatuses.containsany(closedstates) %]
[% knum = knum + 1 %]
<input id="knob-clearresolution" type="radio" name="knob" value="clearresolution">
<label for="knob-clearresolution">Clear the resolution</label><br />
[% knum = knum + 1 %]
<input id="knob-resolve" type="radio" name="knob" value="resolve">
<label for="knob-resolve">
Resolve bugs, changing <A HREF="bug_status.html">resolution</A> to
</label>
<select name="resolution" onchange="document.forms.changeform.knob[[% knum %]].checked=true">
[% FOREACH resolution = resolutions %]
[% NEXT IF !resolution %]
<option value="[% resolution %]" [% selected IF resolution == "FIXED" %]>
[% resolution %]
</option>
[% END %]
</select><br />
[% END %]
[%# If all the bugs are closed, allow the user to reopen them. %]
[% IF !bugstatuses.containsany(openstates) %]
[% knum = knum + 1 %]
<input id="knob-reopen" type="radio" name="knob" value="reopen">
<label for="knob-reopen">Reopen bugs</label><br />
[% END %]
[% IF bugstatuses.size == 1 %]
[% IF bugstatuses.contains('RESOLVED') %]
[% knum = knum + 1 %]
<input id="knob-verify" type="radio" name="knob" value="verify">
<label for="knob-verify">Mark bugs as <b>VERIFIED</b></label><br />
[% ELSIF bugstatuses.contains('VERIFIED') %]
[% knum = knum + 1 %]
<input id="knob-close" type="radio" name="knob" value="close">
<label for="knob-close">Mark bugs as <b>CLOSED</b></label><br />
[% END %]
[% END %]
[% knum = knum + 1 %]
<input id="knob-reassign" type="radio" name="knob" value="reassign">
<label for="knob-reassign"><a href="bug_status.html#assigned_to">
Reassign</A> bugs to
</label>
<input name="assigned_to"
value="[% user %]"
onchange="document.forms.changeform.knob[[% knum %]].checked = true;"
size="32"><br />
[% knum = knum + 1 %]
<input id="knob-reassignbycomponent"
type="radio"
name="knob"
value="reassignbycomponent">
<label for="knob-reassignbycomponent">
Reassign bugs to owner of selected component
</label><br />
<input type="submit" value="Commit">
[% IF ismover %]
<input type="submit" name="action" value="[% Param('move-button-text') %]">
[% END %]
[%############################################################################%]
[%# Select Menu Block #%]
[%############################################################################%]
[% BLOCK selectmenu %]
<select id="[% menuname %]" name="[% menuname %]">
<option value="[% dontchange FILTER html %]" selected>
[% dontchange FILTER html %]
</option>
[% FOREACH menuitem = menuitems %]
<option value="[% menuitem FILTER html %]">[% menuitem FILTER html %]</option>
[% END %]
</select>
[% END %]
[%# 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.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
<html>
<head>
<title>Bugzilla is pondering your query</title>
</head>
<body>
<h1 style="margin-top: 20%; text-align: center;">Please stand by ...</h1>
[% IF debug %]
<p>
<code>[% query FILTER html %]</code>
</p>
[% END %]
</body>
</html>
[%# 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.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
[%############################################################################%]
[%# Initialization #%]
[%############################################################################%]
[%# Columns whose titles or values should be abbreviated to make the list
# more compact. For columns whose titles should be abbreviated,
# the shortened title is included. For columns whose values should be
# abbreviated, a maximum length is provided along with the ellipsis that
# should be added to an abbreviated value, if any.
#%]
[% abbrev =
{
"severity" => { size => 3 , title => "Sev" } ,
"priority" => { size => 3 , title => "Pri" } ,
"platform" => { size => 3 , title => "Plt" } ,
"status" => { size => 4 } ,
"reporter" => { size => 45 , ellipsis => "..." } ,
"owner" => { size => 45 , ellipsis => "..." } ,
"qa_contact" => { size => 45 , ellipsis => "..." , title => "QAContact" } ,
"resolution" => { size => 4 } ,
"summary" => { size => 60 , ellipsis => "..." } ,
"status_whiteboard" => { title => "StatusSummary" } ,
"component" => { size => 8 , title => "Comp" } ,
"product" => { size => 8 } ,
"version" => { size => 5 , title => "Vers" } ,
"os" => { size => 4 } ,
"target_milestone" => { title => "TargetM" } ,
}
%]
[%############################################################################%]
[%# Table Header #%]
[%############################################################################%]
[% tableheader = BLOCK %]
<table class="bz_buglist" cellspacing="0" cellpadding="4" width="100%">
<colgroup>
<col class="bz_id_column">
[% FOREACH id = displaycolumns %]
<col class="bz_[% id %]_column">
[% END %]
</colgroup>
<tr align="left">
<th colspan="[% splitheader ? 2 : 1 %]">
<a href="buglist.cgi?[% urlquerypart %]&order=bugs.bug_id">ID</a>
</th>
[% IF splitheader %]
[% FOREACH id = displaycolumns %]
[% NEXT IF loop.count() % 2 == 0 %]
[% column = columns.$id %]
[% PROCESS columnheader %]
[% END %]
</tr><tr align="left"><th>&nbsp;</th>
[% FOREACH id = displaycolumns %]
[% NEXT UNLESS loop.count() % 2 == 0 %]
[% column = columns.$id %]
[% PROCESS columnheader %]
[% END %]
[% ELSE %]
[% FOREACH id = displaycolumns %]
[% column = columns.$id %]
[% PROCESS columnheader %]
[% END %]
[% END %]
</tr>
[% END %]
[% BLOCK columnheader %]
<th colspan="[% splitheader ? 2 : 1 %]">
<a href="buglist.cgi?[% urlquerypart %]&order=
[% column.name FILTER uri html %]
[% ",$order" FILTER uri html IF order %]">
[%- abbrev.$id.title || column.title -%]</a>
</th>
[% END %]
[%############################################################################%]
[%# Bug Table #%]
[%############################################################################%]
[% FOREACH bug = bugs %]
[% FLUSH IF loop.count() % 10 == 1 %]
[%# At the beginning of every hundred bugs in the list, start a new table. %]
[% IF loop.count() % 100 == 1 %]
[% tableheader %]
[% END %]
<tr class="bz_[% bug.severity %] bz_[% bug.priority %] [%+ "bz_secure" IF (bug.groupset && !usebuggroups) %]">
<td>
[% IF dotweak %]<input type="checkbox" name="id_[% bug.id %]">[% END %]
<a href="show_bug.cgi?id=[% bug.id %]">[% bug.id %]</a>
</td>
[% FOREACH column = displaycolumns %]
<td>
[%+ bug.$column.truncate(abbrev.$column.size, abbrev.$column.ellipsis) FILTER html %]
</td>
[% END %]
</tr>
[%# At the end of every hundred bugs in the list, or at the end of the list,
# end the current table.
#%]
[% IF loop.last() || loop.count() % 100 == 0 %]
</table>
[% END %]
[% END %]
...@@ -11,14 +11,23 @@ ...@@ -11,14 +11,23 @@
<html> <html>
<head> <head>
<title>[% title %]</title> <title>[% title %]</title>
[% Param('headerhtml') %] [% Param('headerhtml') %]
[% jscript %] [% jscript %]
[% IF style %] [% IF style %]
<style type="text/css"> <style type="text/css">
[% style %] [% style %]
</style> </style>
[% END %] [% END %]
[% IF style_url %]
<link href="[% style_url %]" rel="stylesheet" type="text/css" />
[% END %]
</head> </head>
<body [% Param('bodyhtml') %][% " " %][% extra %]> <body [% Param('bodyhtml') %][% " " %][% extra %]>
[% PerformSubsts(Param('bannerhtml')) %] [% PerformSubsts(Param('bannerhtml')) %]
......
[% DEFAULT title = "Bugzilla Message" %] [% DEFAULT title = "Bugzilla Message" %]
[% INCLUDE global/header title=title %] [% PROCESS global/header %]
[%# The "header" template automatically displays the contents of a "message" [%# The "header" template automatically displays the contents of a "message"
variable if it finds one, so it is not necessary to display the message variable if it finds one, so it is not necessary to display the message
...@@ -13,4 +13,4 @@ ...@@ -13,4 +13,4 @@
</p> </p>
[% END %] [% END %]
[% INCLUDE global/footer %] [% PROCESS global/footer %]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment