From 496013d2cc0586cde9db0ace761292594fcae995 Mon Sep 17 00:00:00 2001 From: "myk%mozilla.org" <> Date: Tue, 12 Mar 2002 21:54:53 +0000 Subject: Fix for bug 103778: Rewrites and templatizes buglist.cgi. Patch by Myk Melez . r=bbaetz,gerv --- CGI.pl | 3 +- buglist.cgi | 1466 +++++++++------------ checksetup.pl | 16 + css/buglist.css | 38 + globals.pl | 120 +- skins/standard/buglist.css | 38 + template/default/buglist/buglist-rdf.rdf.tmpl | 52 + template/default/buglist/buglist-simple.html.tmpl | 44 + template/default/buglist/buglist.html.tmpl | 160 +++ template/default/buglist/change-form.tmpl | 339 +++++ template/default/buglist/server-push.html.tmpl | 35 + template/default/buglist/table.tmpl | 142 ++ template/default/global/header | 9 + template/default/global/message.html.tmpl | 4 +- 14 files changed, 1601 insertions(+), 865 deletions(-) create mode 100644 css/buglist.css create mode 100644 skins/standard/buglist.css create mode 100644 template/default/buglist/buglist-rdf.rdf.tmpl create mode 100644 template/default/buglist/buglist-simple.html.tmpl create mode 100644 template/default/buglist/buglist.html.tmpl create mode 100644 template/default/buglist/change-form.tmpl create mode 100644 template/default/buglist/server-push.html.tmpl create mode 100644 template/default/buglist/table.tmpl diff --git a/CGI.pl b/CGI.pl index 0882a967c..76c53627d 100644 --- a/CGI.pl +++ b/CGI.pl @@ -1201,7 +1201,8 @@ sub PutFooter { sub DisplayError { my ($message, $title) = (@_); $title ||= "Error"; - + $message ||= "An unknown error occurred."; + print "Content-type: text/html\n\n"; PutHeader($title); diff --git a/buglist.cgi b/buglist.cgi index ce67f648e..9238212a7 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -22,77 +22,165 @@ # Dan Mosedale # Stephan Niemz # Andreas Franke +# Myk Melez +################################################################################ +# Script Initialization +################################################################################ + +# Make it harder for us to do dangerous things in Perl. use diagnostics; use strict; use lib qw(.); +use vars qw( $template $vars ); + +# Include the Bugzilla CGI and general utility library. require "CGI.pl"; -use Date::Parse; # Shut up misguided -w warnings about "used only once". "use vars" just # doesn't work for me. - sub sillyness { my $zz; $zz = $::db_name; - $zz = $::defaultqueryname; - $zz = $::unconfirmedstate; - $zz = $::userid; $zz = @::components; $zz = @::default_column_list; + $zz = $::defaultqueryname; + $zz = @::dontchange; $zz = @::legal_keywords; $zz = @::legal_platform; $zz = @::legal_priority; $zz = @::legal_product; - $zz = @::settable_resolution; $zz = @::legal_severity; - $zz = @::versions; + $zz = @::settable_resolution; $zz = @::target_milestone; - $zz = %::proddesc; + $zz = $::unconfirmedstate; + $zz = $::userid; + $zz = @::versions; }; -my $serverpush = 0; - 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))) { - my $url = "query.cgi?$::buffer#chart"; - print qq{Refresh: 0; URL=$url -Content-type: text/html +# Whether or not the user wants to change multiple bugs. +my $dotweak = $::FORM{'tweak'} ? 1 : 0; -Adding field to query page... -

-Click here if page doesn't redisplay automatically. -}; - exit(); +# Use server push to display a "Please wait..." message for the user while +# executing their query if their browser supports it and they are viewing +# the bug list as HTML and they have not disabled it by adding &serverpush=0 +# to the URL. +# +# 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 query page.|); + 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'}) { - # This can happen if there's an old bookmark to a query... - $::FORM{'cmdtype'} = 'doit'; +if ($dotweak) { + confirm_login(); + if (!UserInGroup("editbugs")) { + DisplayError("Sorry, you do not have sufficient privileges to edit + multiple bugs."); + exit; + } + GetVersionTable(); +} +else { + quietly_check_login(); } +################################################################################ +# Utilities +################################################################################ + sub SqlifyDate { - my ($str) = (@_); - if (!defined $str) { - $str = ""; - } + my ($str) = @_; + $str = "" if !defined $str; my $date = str2time($str); - if (!defined $date) { - PuntTryAgain("The string '".html_quote($str)."' is not a legal date."); + if (!defined($date)) { + my $htmlstr = html_quote($str); + DisplayError("The string $htmlstr 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 { my ($field, $strs) = (@_); @@ -129,28 +217,75 @@ sub GetByWordListSubstr { 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 $qname seems to no longer exist."); + exit; + } + return $result; +} + +sub GetQuip { + return if !Param('usequip'); -sub Error { - my ($str) = (@_); - if (!$serverpush) { - print "Content-type: text/html\n\n"; + my $quip; + + # This is stupid. We really really need to move the quip list into the DB! + if (open(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 { my $debug = 0; - my ($fieldsref, $supptablesref, $wherepartref, $urlstr) = (@_); + my ($fieldsref, $urlstr) = (@_); my @fields; my @supptables; my @wherepart; @fields = @$fieldsref if $fieldsref; - @supptables = @$supptablesref if $supptablesref; - @wherepart = @$wherepartref if $wherepartref; my %F; my %M; ParseUrlString($urlstr, \%F, \%M); @@ -172,10 +307,11 @@ sub GenerateSQL { my $c = trim($F{'votes'}); if ($c ne "") { if ($c !~ /^[0-9]*$/) { - return Error("The 'At least ___ votes' field must be a\n" . - "simple number. You entered \"" . - html_quote($c) . "\", which\n" . - "doesn't cut it."); + my $htmlc = html_quote($c); + DisplayError("The At least ___ votes field must be + a simple number. You entered $htmlc, + which doesn't cut it."); + exit; } push(@specialchart, ["votes", "greaterthan", $c - 1]); } @@ -255,9 +391,10 @@ sub GenerateSQL { if (@clist) { push(@specialchart, \@clist); } else { - return Error("You must specify one or more fields in which to\n" . - "search for " . - html_quote($email) . ".\n"); + my $htmlemail = html_quote($email); + DisplayError("You must specify one or more fields in which + to search for $htmlemail."); + exit; } } @@ -266,10 +403,11 @@ sub GenerateSQL { my $c = trim($F{'changedin'}); if ($c ne "") { if ($c !~ /^[0-9]*$/) { - return Error("The 'changed in last ___ days' field must be\n" . - "a simple number. You entered \"" . - html_quote($c) . "\", which\n" . - "doesn't cut it."); + my $htmlc = html_quote($c); + DisplayError("The changed in last ___ days field + must be a simple number. You entered + $htmlc, which doesn't cut it."); + exit; } push(@specialchart, ["changedin", "lessthan", $c + 1]); @@ -417,17 +555,17 @@ sub GenerateSQL { $t = "greaterthan"; } if ($field eq "ispatch" && $v ne "0" && $v ne "1") { - return Error("The only legal values for the 'Attachment is patch' " . - "field are 0 and 1."); + DisplayError("The only legal values for the Attachment is + patch field are 0 and 1."); + exit; } if ($field eq "isobsolete" && $v ne "0" && $v ne "1") { - return Error("The only legal values for the 'Attachment is obsolete' " . - "field are 0 and 1."); + DisplayError("The only legal values for the Attachment is + obsolete field are 0 and 1."); + exit; } $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 { # When searching for multiple statuses within a single boolean chart, # we want to match each status record separately. In other words, @@ -473,12 +611,13 @@ sub GenerateSQL { my $id = GetKeywordIdFromName($value); if ($id) { push(@list, "$table.keywordid = $id"); - } else { - return Error("Unknown keyword named " . - html_quote($v) . ".\n" . - "

The legal keyword names are\n" . - "" . - "listed here.\n"); + } + else { + my $htmlv = html_quote($v); + DisplayError(qq|There is no keyword named $htmlv. + To search for keywords, consult the + list of legal keywords.|); + exit; } } my $haveawordterm; @@ -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 -# terms into the boolean chart. Boolean charts are represented in -# urls as tree-tuples of (chart id, row, column). The query form +# terms into the boolean chart. Boolean charts are represented in +# urls as tree-tuples of (chart id, row, column). The query form # (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 -# row and one column. Extra rows can be created by pressing the -# AND button at the bottom of the chart. Extra columns are created +# row and one column. Extra rows can be created by pressing the +# 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 # charts are created by pressing "Add another boolean chart". # @@ -705,11 +844,11 @@ sub GenerateSQL { # 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 -# 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. -# This will find bugs with one CC mathing 'foo@blah.org' and and another -# CC matching 'bar@blah.org'. -# +# 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. +# This will find bugs with one CC mathing 'foo@blah.org' and and another +# CC matching 'bar@blah.org'. +# # -------------------------------------------------------------- # CC | equal to # foo@blah.org @@ -717,7 +856,7 @@ sub GenerateSQL { # CC | equal to # 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 # looks for a single CC where the login name is both "foo@blah.org", # and "bar@blah.org". This is impossible. @@ -740,7 +879,7 @@ sub GenerateSQL { # $ff = qualified field name (field name prefixed by table) # e.g. bugs_activity.bug_id # $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)) # @supptables = Tables and/or table aliases used in query # %suppseen = A hash used to store all the tables in supptables to weed @@ -814,13 +953,13 @@ sub GenerateSQL { } if ($term) { push(@orlist, $term); - } else { - my $errstr = "Can't seem to handle " . - qq{'$F{"field$chart-$row-$col"}' and } . - qq{'$F{"type$chart-$row-$col"}' } . - "together"; - die "Internal error: $errstr" if $chart < 0; - return Error($errstr); + } + else { + my $errstr = + qq|Cannot seem to handle $F{"field$chart-$row-$col"} + and $F{"type$chart-$row-$col"} together|; + $chart < 0 ? die "Internal error: $errstr" + : DisplayError($errstr) && exit; } } if (@orlist) { @@ -839,931 +978,538 @@ sub GenerateSQL { $suppseen{$str} = 1; } } - - my $query = ("SELECT " . join(', ', @fields) . + my $query = ("SELECT DISTINCT " . join(', ', @fields) . " FROM $suppstring" . - " WHERE " . join(' AND ', (@wherepart, @andlist)) . - " GROUP BY bugs.bug_id"); + " WHERE " . join(' AND ', (@wherepart, @andlist))); $query = SelectVisible($query, $::userid, $::usergroupset); if ($debug) { print "

" . value_quote($query) . "

\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; } - 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'}) { /^runnamed$/ && do { $::buffer = LookupNamedQuery($::FORM{"namedcmd"}); - $::querytitle = "Bug List: $::FORM{'namedcmd'}"; + $vars->{'title'} = "Bug List: $::FORM{'namedcmd'}"; ProcessFormFields($::buffer); last CMD; }; + /^editnamed$/ && do { my $url = "query.cgi?" . LookupNamedQuery($::FORM{"namedcmd"}); - print qq{Content-type: text/html -Refresh: 0; URL=$url - -What a hack. -Loading your query named $::FORM{'namedcmd'}... -}; + 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'} = "Loading your query named $::FORM{'namedcmd'}"; + $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; }; + /^forgetnamed$/ && do { confirm_login(); my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"}); - SendSQL("DELETE FROM namedqueries WHERE userid = $userid " . - "AND name = " . SqlQuote($::FORM{'namedcmd'})); - - print "Content-type: text/html\n\n"; - PutHeader("Query is gone", ""); - - print qq{ -OK, the $::FORM{'namedcmd'} query is gone. -

-Go back to the query page. -}; - PutFooter(); + my $qname = SqlQuote($::FORM{'namedcmd'}); + SendSQL("DELETE FROM namedqueries WHERE userid = $userid AND name = $qname"); + print "Content-Type: text/html\n\n"; + # Generate and return the UI (HTML page) from the appropriate template. + $vars->{'title'} = "Query is gone"; + $vars->{'message'} = "OK, the $::FORM{'namedcmd'} query is gone."; + $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; }; + /^asdefault$/ && do { confirm_login(); my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"}); - print "Content-type: text/html\n\n"; - SendSQL("REPLACE INTO namedqueries (userid, name, query) VALUES " . - "($userid, '$::defaultqueryname'," . - SqlQuote($::buffer) . ")"); - PutHeader("OK, default is set"); - print qq{ -OK, you now have a new default query. You may also bookmark the result of any -individual query. - -

Go back to the query page, using the new default. -}; - PutFooter(); - exit(); + my $qname = SqlQuote($::defaultqueryname); + my $qbuffer = SqlQuote($::buffer); + SendSQL("REPLACE INTO namedqueries (userid, name, query) + VALUES ($userid, $qname, $qbuffer)"); + print "Content-Type: text/html\n\n"; + # Generate and return the UI (HTML page) from the appropriate template. + $vars->{'title'} = "OK, default is set"; + $vars->{'message'} = "OK, you now have a new default query. You may + also bookmark the result of any individual query."; + $vars->{'url'} = "query.cgi"; + $vars->{'link'} = "Go back to the query page, using the new default."; + $template->process("global/message.html.tmpl", $vars) + || DisplayError("Template process failed: " . $template->error()); + exit; }; + /^asnamed$/ && do { confirm_login(); my $userid = DBNameToIdAndCheck($::COOKIE{"Bugzilla_login"}); - print "Content-type: text/html\n\n"; + my $name = trim($::FORM{'newqueryname'}); - if ($name eq "" || $name =~ /[<>&]/) { - PutHeader("Please pick a valid name for your new query"); - print "Click the Back button and type in a valid name\n"; - print "for this query. (Query names should not contain unusual\n"; - print "characters.)\n"; - PutFooter(); - exit(); - } - $::buffer =~ s/[\&\?]cmdtype=[a-z]+//; + $name + || DisplayError("You must enter a name for your query.") + && exit; + $name =~ /[<>&]/ + && DisplayError("The name of your query cannot contain any + of the following characters: <, >, &.") + && exit; my $qname = SqlQuote($name); - my $tofooter= ( $::FORM{'tofooter'} ? 1 : 0 ); - SendSQL("SELECT query FROM namedqueries " . - "WHERE userid = $userid AND name = $qname"); - if (!FetchOneColumn()) { - SendSQL("REPLACE INTO namedqueries (userid, name, query, linkinfooter) " . - "VALUES ($userid, $qname, ". SqlQuote($::buffer) .", ". $tofooter .")"); - } else { - SendSQL("UPDATE namedqueries SET query = " . SqlQuote($::buffer) . "," . - " linkinfooter = " . $tofooter . - " WHERE userid = $userid AND name = $qname"); + + $::buffer =~ s/[\&\?]cmdtype=[a-z]+//; + my $qbuffer = SqlQuote($::buffer); + + my $tofooter= $::FORM{'tofooter'} ? 1 : 0; + + SendSQL("SELECT query FROM namedqueries WHERE userid = $userid AND name = $qname"); + if (FetchOneColumn()) { + SendSQL("UPDATE namedqueries + SET query = $qbuffer , linkinfooter = $tofooter + WHERE userid = $userid AND name = $qname"); } - PutHeader("OK, query saved."); - print qq{ -OK, you have a new query named $name -

-
Go back to the query page -}; - PutFooter(); + else { + SendSQL("REPLACE INTO namedqueries (userid, name, query, linkinfooter) + VALUES ($userid, $qname, $qbuffer, $tofooter)"); + } + print "Content-Type: text/html\n\n"; + # Generate and return the UI (HTML page) from the appropriate template. + $vars->{'title'} = "OK, query saved."; + $vars->{'message'} = "OK, you have a new query named $name"; + $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; }; } -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 - # 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. - $serverpush = 1; - - print qq{Content-type: multipart/x-mixed-replace;boundary=thisrandomstring\n ---thisrandomstring -Content-type: text/html\n -Bugzilla is pondering your query - -

Please stand by ...

- }; - # Note! HTML header is complete! -} else { - print "Content-type: text/html\n"; - #Changing attachment to inline to resolve 46897 - #zach@zachlipton.com - print "Content-disposition: inline; filename=bugzilla_bug_list.html\n"; - # Note! Don't finish HTML header yet! Only one newline so far! +################################################################################ +# Column Definition +################################################################################ + +# Define the columns that can be selected in a query and/or displayed in a bug +# list. Column records include the following fields: +# +# 1. ID: a unique identifier by which the column is referred in code; +# +# 2. Name: The name of the column in the database (may also be an expression +# that returns the value of the column); +# +# 3. Title: The title of the column as displayed to users. +# +# 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 +# "DESC" is added to the end of the field to sort in descending order, +# and the redundant summaryfull column is removed when the client +# requests "all" columns. + +my $columns = {}; +sub DefineColumn { + my ($id, $name, $title) = @_; + $columns->{$id} = { 'name' => $name , 'title' => $title }; } -sub DefCol { - my ($name, $k, $t, $s, $q) = (@_); - $::key{$name} = $k; - $::title{$name} = $t; - if (defined $s && $s ne "") { - $::sortkey{$name} = $s; +# Column: ID Name Title +DefineColumn("id" , "bugs.bug_id" , "ID" ); +DefineColumn("groupset" , "bugs.groupset" , "Groupset" ); +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 "") { - $q = 0; + else { + @displaycolumns = split(/[ ,]+/, $::FORM{'columnlist'}); } - $::needquote{$name} = $q; } - -DefCol("opendate", "unix_timestamp(bugs.creation_ts)", "Opened", - "bugs.creation_ts"); -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; +elsif (defined $::COOKIE{'COLUMNLIST'}) { + # Use the columns listed in the user's preferences. + @displaycolumns = split(/ /, $::COOKIE{'COLUMNLIST'}); } - -my $minvotes; -if (defined $::FORM{'votes'}) { - if (trim($::FORM{'votes'}) ne "") { - if (! (grep {/^votes$/} @collist)) { - push(@collist, 'votes'); - } - } +else { + # Use the default list of columns. + @displaycolumns = @::default_column_list; } +# 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'}; - -if ($dotweak) { - 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(); -} +# Remove the "ID" column from the list because bug IDs are always displayed +# and are hard-coded into the display templates. +@displaycolumns = grep($_ ne 'id', @displaycolumns); +# 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) { - push(@fields, "bugs.product", "bugs.bug_status"); -} +# Generate the list of columns that will be selected in the SQL query. +# 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 (!$::COOKIE{'BUGLIST'}) { - print 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 query page. -}; - 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'}); +# If the user is editing multiple bugs, we also make sure to select the product +# and status because the values of those fields determine what options the user +# has for modifying the bugs. +if ($dotweak) { + push(@selectcolumns, "product") if !grep($_ eq 'product', @selectcolumns); + push(@selectcolumns, "status") if !grep($_ eq 'status', @selectcolumns); } +################################################################################ +# 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 "") { - $query .= " ORDER BY "; - $::FORM{'order'} =~ s/votesum/bugs.votes/; # Silly backwards compatability - # hack. - $::FORM{'order'} =~ s/assign\.login_name/map_assigned_to.login_name/g; - # Another backwards compatability hack. +# Add to the query some instructions for sorting the bug list. +if ($::COOKIE{'LASTORDER'} && !$order || $order =~ /^reuse/i) { + $order = url_decode($::COOKIE{'LASTORDER'}); +} - 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 { - # 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 $qfragment."); + exit; + } + } + # Now that we have checked that all columns in the order are valid, + # detaint the order string. + trick_taint($order); last ORDER; }; /Number/ && do { - $::FORM{'order'} = "bugs.bug_id"; + $order = "bugs.bug_id"; last ORDER; }; /Import/ && do { - $::FORM{'order'} = "bugs.priority, bugs.bug_severity"; + $order = "bugs.priority, bugs.bug_severity"; last ORDER; }; /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; }; /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; }; # 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, # change it to order by the sortkey of the target_milestone first. - my $order = $::FORM{'order'}; 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/; + $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) { - print "

" . value_quote($query) . "

\n"; + $query .= " ORDER BY $order "; } -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; -$::bugl = ""; -sub pnl { - my ($str) = (@_); - $::bugl .= $str; + # Generate and return the UI (HTML page) from the appropriate template. + $template->process("buglist/server-push.html.tmpl", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; } -my $fields = $::buffer; -$fields =~ s/[&?]order=[^&]*//g; -$fields =~ s/[&?]cmdtype=[^&]*//g; +# Connect to the shadow database if this installation is using one to improve +# query performance. +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; -my $oldorder; +# Execute the query. +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 "

"; -} +################################################################################ +# Results Retrieval +################################################################################ +# Retrieve the query results one row at a time and write the data into a list +# of Perl records. -my @th; -foreach my $c (@collist) { - if (exists $::needquote{$c}) { - my $h = ""; - if (defined $::sortkey{$c}) { - $h .= "$::title{$c}"; - } else { - $h .= $::title{$c}; - } - $h .= ""; - push(@th, $h); - } -} +my $bugowners = {}; +my $bugproducts = {}; +my $bugstatuses = {}; -my $tablestart = " -
-ID"; +my @bugs; # the list of records -my $splitheader = 0; -if ($::COOKIE{'SPLITHEADER'}) { - $splitheader = 1; -} +while (my @row = FetchSQLData()) { + my $bug = {}; # a record -if ($splitheader) { - $tablestart =~ s/{$column} = shift @row; } -} 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; -my %seen; -my @bugarray; -my %prodhash; -my %statushash; -my %ownerhash; -my %qahash; + # Record the owner, product, and status in the big hashes of those things. + $bugowners->{$bug->{'owner'}} = 1 if $bug->{'owner'}; + $bugproducts->{$bug->{'product'}} = 1 if $bug->{'product'}; + $bugstatuses->{$bug->{'status'}} = 1 if $bug->{'status'}; -my $pricol = -1; -my $sevcol = -1; -for (my $colcount = 0 ; $colcount < @collist ; $colcount++) { - my $colname = $collist[$colcount]; - if ($colname eq "priority") { - $pricol = $colcount; - } - if ($colname eq "severity") { - $sevcol = $colcount; - } + # Add the record to the list. + push(@bugs, $bug); } -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 "
$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 ""; - if ($dotweak) { - pnl ""; - } - pnl ""; - pnl "$bug_id"; - if ($g != "0") { pnl "*"; } - pnl " "; - foreach my $c (@collist) { - if (exists $::needquote{$c}) { - my $value = shift @row; - if (!defined $value) { - pnl ""; - 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|$trunc...|; - } 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 = "$value"; - } - pnl "$value"; - } - } - if ($dotweak) { - my $value = shift @row; - $prodhash{$value} = 1; - $value = shift @row; - $statushash{$value} = 1; - } - pnl "\n"; - } -} -my $buglist = join(":", @bugarray); +################################################################################ +# Template Variable Definition +################################################################################ +# 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! -my $quip; -if (Param('usequip')){ - if (open (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."; -} +$vars->{'bugs'} = \@bugs; +$vars->{'columns'} = $columns; +$vars->{'displaycolumns'} = \@displaycolumns; +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 -# take down the waiting page and put up the real one. -if ($serverpush) { - print "\n"; - print "--thisrandomstring\n"; - print "Content-type: text/html\n"; - print "Content-disposition: inline; filename=bugzilla_bug_list.html\n"; - # Note! HTML header not yet closed -} -my $toolong = 0; -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 list of query fields in URL query string format, used when creating +# URLs to the same query results page with different parameters (such as +# a different sort order or when taking some action on the set of query +# results). To get this string, we start with the raw URL query string +# buffer that was created when we initially parsed the URL on script startup, +# then we remove all non-query fields from it, f.e. the sort order (order) +# and command type (cmdtype) fields. +$vars->{'urlquerypart'} = $::buffer; +$vars->{'urlquerypart'} =~ s/[&?](order|cmdtype)=[^&]*//g; +$vars->{'order'} = $order; +# The user's login account name (i.e. email address). +$vars->{'user'} = $::COOKIE{'Bugzilla_login'}; -print " -
-" . time2str("%a %b %e %T %Z %Y", time()) . ""; +$vars->{'caneditbugs'} = UserInGroup('editbugs'); +$vars->{'usebuggroups'} = UserInGroup('usebuggroups'); -if (Param('usebuggroups')) { - print "
* next to a bug number notes a bug not visible to everyone.
"; -} +# Whether or not this user is authorized to move bugs to another installation. +$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'}) { - print "

" . value_quote($query) . "

\n"; +my @bugowners = keys %$bugowners; +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) { - print "

This list is too long for bugzilla's little mind; the\n"; - print "Next/Prev/First/Last buttons won't appear.

\n"; +if ($::FORM{'debug'}) { + $vars->{'debug'} = 1; + $vars->{'query'} = $query; } -if (Param('usequip')){ - print "
$quip
\n"; -} -print "
"; -print "$count bugs found." if $count > 9; -print $tablestart, "\n"; -print $::bugl; -print "\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{

Query Page\n}; - print qq{  Enter New Bug\n}; - print qq{Edit this query\n}; -} elsif ($count == 1) { - print "One bug found.\n"; -} else { - print "$count bugs found.\n"; -} +# Whether or not to split the column titles across two rows to make +# the list more compact. +$vars->{'splitheader'} = $::COOKIE{'SPLITHEADER'} ? 1 : 0; +$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) { - GetVersionTable(); - print " -"; - - 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 " -


- - - - - - - - - - - - - - - - - -"; - - 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 " - - - - "; - } - - if (Param("useqacontact")) { - print " - - - -"; - } - - print qq{ - - - -}; - if (@::legal_keywords) { - print qq{ - - - -}; - } +################################################################################ +# HTTP Header Generation +################################################################################ - print "
Product:Version:
Platform:Priority:
Component:Severity:
Target milestone:
QA Contact:
CC List: - -
Keywords: - -
+# If we are doing server push, output a separator string. +print "\n--thisrandomstring\n" if $serverpush; + +# Generate HTTP headers - +# 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; -Additional Comments: -
-
"; +if ($format->{'extension'} eq "html") { + print "Content-Type: text/html\n"; -if($::usergroupset ne '0') { - SendSQL("select bit, name, description, isactive ". - "from groups where bit & $::usergroupset != 0 ". - "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 "Groupset:
\n"; - print "\n"; - print "\n"; - $groupFound = 1; - } - # Modifying this to use radio buttons instead - print ""; - print "\n"; - print "\n"; - if ($isactive) { - print "\n"; - } else { - $inactiveFound = 1; - print "\n"; - } - print "\n"; - } + if ($order) { + my $qorder = url_quote($order); + print "Set-Cookie: LASTORDER=$qorder ; path=/; expires=Sun, 30-Jun-2029 00:00:00 GMT\n"; } - # Add in some blank space for legibility - if($groupFound) { - print "
Don't
change
this group
restriction\n"; - print "
Remove
bugs
from this
group\n"; - print "
Add
bugs
to this
group\n"; - print "
Group name:
 "; - if(!$isactive) { - print ""; - } - print "$description"; - if(!$isactive) { - print ""; - } - print "
\n"; - if ($inactiveFound) { - print "(Note: Bugs may not be added to inactive groups (italicized), only removed)
\n"; - } - print "

\n"; + my $bugids = join(":", map( $_->{'id'}, @bugs)); + if (length($bugids) < 4000) { + print "Set-Cookie: BUGLIST=$bugids\n"; } -} - - - - - # knum is which knob number we're generating, in javascript terms. - - my $knum = 0; - print " - - Do nothing else
"; - $knum++; - if ($statushash{$::unconfirmedstate} && 1 == scalar(keys(%statushash))) { - print " - - Confirm bugs (change status to NEW)
"; - $knum++; - } - print " - - Accept bugs (change status to ASSIGNED)
"; - $knum++; - if (!defined $statushash{'CLOSED'} && - !defined $statushash{'VERIFIED'} && - !defined $statushash{'RESOLVED'}) { - print " - - Clear the resolution
"; - $knum++; - print " - - Resolve bugs, changing resolution to -
"; - $knum++; - } - if (!defined $statushash{'NEW'} && - !defined $statushash{'ASSIGNED'} && - !defined $statushash{'REOPENED'}) { - print " - Reopen bugs
"; - $knum++; - } - my @statuskeys = keys %statushash; - if (1 == @statuskeys) { - if (defined $statushash{'RESOLVED'}) { - print " - - Mark bugs as VERIFIED
"; - $knum++; - } - if (defined $statushash{'VERIFIED'}) { - print " - - Mark bugs as CLOSED
"; - $knum++; - } + else { + print "Set-Cookie: BUGLIST=\n"; + $vars->{'toolong'} = 1; } - print " - - Reassign bugs to -
"; - $knum++; - print " - Reassign bugs to owner of selected component
"; - $knum++; - - print " -

- -To make changes to a bunch of bugs at once: -

    -
  1. Put check boxes next to the bugs you want to change. -
  2. Adjust above form elements. (If the change you are making requires - an explanation, include it in the comments box). -
  3. Click the below \"Commit\" button. -
-"; - - 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 "

"; - print ""; - } - - print "


\n"; +} +else { + print "Content-Type: $format->{'contenttype'}\n"; } +print "\n"; # end HTTP headers -if ($count > 0) { - print "
- - -Query Page -   -Enter New Bug -   -Change columns"; - if (!$dotweak && $count > 1 && UserInGroup("editbugs")) { - print "  \n"; - print ""; - print "Change several bugs at once\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{  \n}; - print qq{Send mail to bug owners\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{  \n}; - print qq{Send mail to bug QA contacts\n}; - } - print qq{  \n}; - print qq{Edit this query\n}; - print "
\n"; -} +################################################################################ +# Content Generation +################################################################################ -# 2001-06-20, myk@mozilla.org, bug 47914: -# Switch back from the shadow database to the regular database -# so that PutFooter() can determine the current user even if -# the "logincookies" table is corrupted in the shadow database. -SendSQL("USE $::db_name"); +# Generate and return the UI (HTML page) from the appropriate template. +$template->process("buglist/$format->{'template'}", $vars) + || DisplayError("Template process failed: " . $template->error()) + && exit; -PutFooter(); -if ($serverpush) { - print "\n--thisrandomstring--\n"; -} +################################################################################ +# Script Conclusion +################################################################################ + +print "\n--thisrandomstring--\n" if $serverpush; diff --git a/checksetup.pl b/checksetup.pl index 44587f310..ab8b723fa 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -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("Template","2.06")) { push @missing,"Template" } 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 # die and warn handlers, we don't want them changed, so we need to stash the @@ -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 "") { 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", diff --git a/css/buglist.css b/css/buglist.css new file mode 100644 index 000000000..c8a4d13d0 --- /dev/null +++ b/css/buglist.css @@ -0,0 +1,38 @@ +/* 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 + */ + +/* 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; } + diff --git a/globals.pl b/globals.pl index 58dcf301a..417241b50 100644 --- a/globals.pl +++ b/globals.pl @@ -35,6 +35,7 @@ sub globals_pl_sillyness { my $zz; $zz = @main::SqlStateStack; $zz = @main::chooseone; + $zz = $main::contenttypes; $zz = @main::default_column_list; $zz = $main::defaultqueryname; $zz = @main::dontchange; @@ -54,8 +55,8 @@ sub globals_pl_sillyness { $zz = %main::proddesc; $zz = @main::prodmaxvotes; $zz = $main::superusergroupset; - $zz = $main::userid; $zz = $main::template; + $zz = $main::userid; $zz = $main::vars; } @@ -79,6 +80,9 @@ use Date::Parse; # For str2time(). #use Carp; # for confess use RelationSet; +# Use standard Perl libraries for cross-platform file/directory manipulation. +use File::Spec; + # Some environment variables are not taint safe delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; @@ -1611,7 +1615,9 @@ $::template ||= Template->new( $var =~ s/\n/\\n/g; $var =~ s/\r/\\r/g; return $var; - } + } , + + html => \&html_quote , } , } ) || DisplayError("Template creation failed: " . Template->error()) @@ -1639,6 +1645,116 @@ $Template::Stash::LIST_OPS->{ containsany } = 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 $escapedname output format is not + supported by this script. Supported formats (besides the + default HTML format) are " . + join(", ", map(html_quote($_), keys(%$formats))) . + "."); + exit; + } + + # Return the validated output format. + return $formats->{$format}; +} + +############################################################################### + # Add a "substr" method to the Template Toolkit's "scalar" object # that returns a substring of a string. $Template::Stash::SCALAR_OPS->{ substr } = diff --git a/skins/standard/buglist.css b/skins/standard/buglist.css new file mode 100644 index 000000000..c8a4d13d0 --- /dev/null +++ b/skins/standard/buglist.css @@ -0,0 +1,38 @@ +/* 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 + */ + +/* 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; } + diff --git a/template/default/buglist/buglist-rdf.rdf.tmpl b/template/default/buglist/buglist-rdf.rdf.tmpl new file mode 100644 index 000000000..4cf480dcc --- /dev/null +++ b/template/default/buglist/buglist-rdf.rdf.tmpl @@ -0,0 +1,52 @@ +[%# 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 + #%] + + + + + + + + + [% FOREACH bug = bugs %] +
  • + + + + [% bug.id %] + + [% FOREACH column = displaycolumns %] + [% bug.$column FILTER html %] + [% END %] + + + +
  • + + [% END %] + +
    + +
    + +
    + +
    diff --git a/template/default/buglist/buglist-simple.html.tmpl b/template/default/buglist/buglist-simple.html.tmpl new file mode 100644 index 000000000..c2e4e6823 --- /dev/null +++ b/template/default/buglist/buglist-simple.html.tmpl @@ -0,0 +1,44 @@ +[%# 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 + #%] + +[%############################################################################%] +[%# Initialization #%] +[%############################################################################%] + +[% DEFAULT title = "Bug List" %] +[% title = title FILTER html %] + + +[%############################################################################%] +[%# Bug Table #%] +[%############################################################################%] + + + + + [% title %] + + + + + [% PROCESS buglist/table.tmpl %] + + + diff --git a/template/default/buglist/buglist.html.tmpl b/template/default/buglist/buglist.html.tmpl new file mode 100644 index 000000000..f9ea46bec --- /dev/null +++ b/template/default/buglist/buglist.html.tmpl @@ -0,0 +1,160 @@ +[%# 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 + #%] + +[%############################################################################%] +[%# Template Initialization #%] +[%############################################################################%] + +[% DEFAULT title = "Bug List" %] +[% style_url = "css/buglist.css" %] + + +[%############################################################################%] +[%# Page Header #%] +[%############################################################################%] + +[% PROCESS global/header + title = title + style = style +%] + +
    + [% currenttime %]
    + + [% IF debug %] +

    [% query FILTER html %]

    + [% END %] + + [% IF quip %] + [% quip %] + [% END %] + +
    + +[% IF toolong %] +

    + This list is too long for Bugzilla's little mind; the + Next/Prev/First/Last buttons won't appear on individual bugs. +

    +[% END %] + +
    + + +[%############################################################################%] +[%# Preceding Status Line #%] +[%############################################################################%] + +[% IF bugs.size > 9 %] + [% bugs.size %] bugs found. +[% END %] + + +[%############################################################################%] +[%# Start of Change Form #%] +[%############################################################################%] + +[% IF dotweak %] +
    +[% END %] + + +[%############################################################################%] +[%# Bug Table #%] +[%############################################################################%] + +[% FLUSH %] +[% PROCESS buglist/table.tmpl %] + +[%############################################################################%] +[%# Succeeding Status Line #%] +[%############################################################################%] + +[% IF bugs.count == 0 %] + Zarro Boogs found. +

    + Query Page +   Enter New Bug + Edit this query +

    + +[% ELSIF bugs.count == 1 %] + One bug found. + +[% ELSE %] + [% bugs.size %] bugs found. + +[% END %] + +
    + + +[%############################################################################%] +[%# Rest of Change Form #%] +[%############################################################################%] + +[% IF dotweak %] + + [% PROCESS "buglist/change-form.tmpl" %] + +
    + +
    + +[% END %] + + +[%############################################################################%] +[%# Navigation Bar #%] +[%############################################################################%] + +[% IF bugs.size > 0 %] +
    + + + + Query Page    + Enter New Bug    + Change Columns    + + [% IF bugs.size > 1 && caneditbugs && !dotweak %] + Change Several + Bugs at Once +    + [% END %] + + [% IF bugowners %] + Send Mail to Bug Owners    + [% END %] + + Edit this Query    + +
    + +[% END %] + + +[%############################################################################%] +[%# Page Footer #%] +[%############################################################################%] + +[% PROCESS global/footer %] + diff --git a/template/default/buglist/change-form.tmpl b/template/default/buglist/change-form.tmpl new file mode 100644 index 000000000..8498a0ab8 --- /dev/null +++ b/template/default/buglist/change-form.tmpl @@ -0,0 +1,339 @@ +[%# 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 + #%] + + + +
    + +

    + To change multiple bugs: +

      +
    1. Check the bugs you want to change above.
    2. +
    3. Make your changes in the form fields below. If the change + you are making requires an explanation, include it in + the comments box.
    4. +
    5. Click the Commit button.
    6. +
    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [% IF Param("useqacontact") %] + + + + + [% END %] + + + + + + + + + [% IF use_keywords %] + + + + + + + [% END %] + + + + + + + + + + + +
    + [% PROCESS selectmenu menuname = "product" + menuitems = products %] + + [% PROCESS selectmenu menuname = "version" + menuitems = versions %] +
    + + + [% PROCESS selectmenu menuname = "rep_platform" + menuitems = platforms %] + + + + [% PROCESS selectmenu menuname = "priority" + menuitems = priorities %] +
    + [% PROCESS selectmenu menuname = "component" + menuitems = components %] + + + + [% PROCESS selectmenu menuname = "severity" + menuitems = severities %] +
    + [% PROCESS selectmenu menuname = "target_milestone" + menuitems = targetmilestones %] +
    + +
    + + +
    + + + + +
    Depends on: + + +
    Blocks: + + +
    + + + +
    +
    + +[% IF groups.size > 0 %] + + Groupset:
    + + + + + [% FOREACH group = groups %] + + + + [% IF group.isactive %] + + [% ELSE %] + + [% foundinactive = 1 %] + [% END %] + + + + + [% END %] + +
    Don't
    change
    this group
    restriction +
    Remove
    bugs
    from this
    group +
    Add
    bugs
    to this
    group +
    Group Name: +
    + + + + + +   + [% IF group.isactive %] + [% group.description %] + [% ELSE %] + [% group.description FILTER strike %] + [% END %] +
    + + [% IF foundinactive %] + (Note: Bugs may not be added to inactive + groups, only removed.)
    + [% END %] + +[% END %] + + + +[% knum = 0 %] + +
    + +[% IF bugstatuses.size == 1 && bugstatuses.0 == unconfirmedstate %] + [% knum = knum + 1 %] + + Confirm bugs (change status to NEW) +
    +[% END %] + +[% knum = knum + 1 %] + +
    + +[%# If all the bugs being changed are open, allow the user to close them. %] +[% IF !bugstatuses.containsany(closedstates) %] + [% knum = knum + 1 %] + +
    + + [% knum = knum + 1 %] + + +
    + +[% END %] + +[%# If all the bugs are closed, allow the user to reopen them. %] +[% IF !bugstatuses.containsany(openstates) %] + [% knum = knum + 1 %] + +
    +[% END %] + +[% IF bugstatuses.size == 1 %] + [% IF bugstatuses.contains('RESOLVED') %] + [% knum = knum + 1 %] + +
    + [% ELSIF bugstatuses.contains('VERIFIED') %] + [% knum = knum + 1 %] + +
    + [% END %] +[% END %] + +[% knum = knum + 1 %] + + +
    + +[% knum = knum + 1 %] + +
    + + + +[% IF ismover %] + +[% END %] + + +[%############################################################################%] +[%# Select Menu Block #%] +[%############################################################################%] + +[% BLOCK selectmenu %] + +[% END %] diff --git a/template/default/buglist/server-push.html.tmpl b/template/default/buglist/server-push.html.tmpl new file mode 100644 index 000000000..be10f7ab3 --- /dev/null +++ b/template/default/buglist/server-push.html.tmpl @@ -0,0 +1,35 @@ +[%# 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 + #%] + + + + Bugzilla is pondering your query + + +

    Please stand by ...

    + + [% IF debug %] +

    + [% query FILTER html %] +

    + [% END %] + + + diff --git a/template/default/buglist/table.tmpl b/template/default/buglist/table.tmpl new file mode 100644 index 000000000..092ff8d2d --- /dev/null +++ b/template/default/buglist/table.tmpl @@ -0,0 +1,142 @@ +[%# 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 + #%] + +[%############################################################################%] +[%# 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 %] + + + + [% FOREACH id = displaycolumns %] + + [% END %] + + + + + + [% IF splitheader %] + + [% FOREACH id = displaycolumns %] + [% NEXT IF loop.count() % 2 == 0 %] + [% column = columns.$id %] + [% PROCESS columnheader %] + [% END %] + + + + [% 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 %] + + +[% END %] + +[% BLOCK columnheader %] + +[% 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 %] + + + + + + [% FOREACH column = displaycolumns %] + + [% END %] + + + + [%# 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 %] +
    + ID +
     
    + + [%- abbrev.$id.title || column.title -%] +
    + [% IF dotweak %][% END %] + [% bug.id %] + + [%+ bug.$column.truncate(abbrev.$column.size, abbrev.$column.ellipsis) FILTER html %] +
    + [% END %] + +[% END %] + diff --git a/template/default/global/header b/template/default/global/header index 627a52571..05afe2bed 100755 --- a/template/default/global/header +++ b/template/default/global/header @@ -11,14 +11,23 @@ [% title %] + [% Param('headerhtml') %] + [% jscript %] + [% IF style %] [% END %] + + [% IF style_url %] + + [% END %] + + [% PerformSubsts(Param('bannerhtml')) %] diff --git a/template/default/global/message.html.tmpl b/template/default/global/message.html.tmpl index 03253242a..912e9f322 100644 --- a/template/default/global/message.html.tmpl +++ b/template/default/global/message.html.tmpl @@ -1,6 +1,6 @@ [% DEFAULT title = "Bugzilla Message" %] -[% INCLUDE global/header title=title %] +[% PROCESS global/header %] [%# The "header" template automatically displays the contents of a "message" variable if it finds one, so it is not necessary to display the message @@ -13,4 +13,4 @@

    [% END %] -[% INCLUDE global/footer %] +[% PROCESS global/footer %] -- cgit v1.2.3-24-g4f1b