#!/usr/bin/perl -w # # Gnatsweb - web front-end to GNATS # # Copyright 1998, 1999, 2001, 2003 # - The Free Software Foundation Inc. # # GNU Gnatsweb is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # GNU Gnatsweb is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Gnatsweb; see the file COPYING. If not, write to the Free # Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # # $Id: gnatsweb.pl,v 1.124.2.2 2003/07/29 12:24:22 yngves Exp $ # use strict; # static global configuration switches and values. set at the top of # this program, but overridable in gnatsweb-site.pl use vars qw($site_gnats_host $site_gnats_port $site_gnatsweb_server_auth $site_no_gnats_passwords $no_create_without_access $site_mail_domain $site_post_max $description_in_view $help_page_path $site_banner_text $site_banner_background $site_banner_foreground $site_button_foreground $site_button_background $site_stylesheet $include_audit_trail $popup_menu_becomes_obnoxious $scrolling_menu_default_size $site_background $site_required_field_color $use_temp_db_prefs_cookie $global_cookie_expires $global_cookie_path $textwidth $site_allow_remote_debug $attachment_delimiter %mark_urls $gnats_info_top %site_pr_submission_address $VERSION); # dynamic configuration switches, set during initial gnatsd # communication and general setup use vars qw($script_name $global_no_cookies $global_list_of_dbs $client_cmd_debug $reply_debug $access_level); # these vars are used for error handling in communications # with gnatsd use vars qw($client_would_have_exited $suppress_client_exit); # the following variable needs to be global in order to make gnatsweb # callable from another source file. Used for 'make test...' use vars qw($suppress_main); # global variables containing most of the info from the gnats-adm # directory. these should probably be rolled into one giant hash. # in fact, this code should be set up so that gnatsweb under mod_perl # could cache all this hooey... use vars qw(%category_notify @submitter_id %submitter_contact %submitter_complete %submitter_notify @responsible %responsible_address %category_desc %responsible_complete %fielddata @fieldnames %responsible_fullname); # the information from the user cookies. # db_prefs just has username & password # global_prefs has email address, default columns for query results # SUBMITTER_ID_FIELD default value and ORIGINATOR_FIELD default value # i think that the columns info should be moved to db_prefs, and the # code suitably munged so that a user could have different column # prefs for different databases. use vars qw(%global_prefs %db_prefs); # the CGI object use vars '$q'; # i couldn't manage to get these two beaten into shape as # lexical variables. maybe next time... use vars qw($pr %fields); #----------------------------------------------------------------------------- # what do you call the file containing the site-specific customizations? # you could, i suppose, by dint of creative programming, have different # config files for different databases, or some such madness... my $gnatsweb_site_file = './gnatsweb-site.pl'; # Site-specific customization - # # DO NOT EDIT THESE VARIABLES HERE! # # Instead, put them in a file called 'gnatsweb-site.pl' in the # same directory. That way, when a new version of gnatsweb is # released, you won't need to edit them again. # # Info about your gnats host. $site_gnats_host = 'localhost'; $site_gnats_port = 1529; # is your installation of gnatsweb set up with server authentication? # if you want to set up a more tightly secured installation, you can # have the web server do authentication (against an htpasswd file, # LDAP server, or some third-party system). this will set the # REMOTE_USER environment variable with the correct user id. with # this switch set, the "logout" button is replaced by a "change # database" button. $site_gnatsweb_server_auth = 0; # or does it merely ignore the gnats password? the gnats network mode # is quite cavalier about passwords, and some sites may elect not to # use gnats passwords. if so, there's no point in gnatsweb asking for # them. if this switch is set, the login page does not prompt for a # password. this means that anyone can pretend to be anyone, but # since the gnats command line tools are hardly more secure, it's not # a big deal... $site_no_gnats_passwords = 0; # set a minimum access level for access to the create function # (this is probably only meaningful if gnatsweb is the only interface # to your gnats installation, since by default gnats allows *everyone* # to submit PRs) # value must be a valid gnatsd.h access level, see %LEVEL_TO_CODE below. #$no_create_without_access = 'edit'; $no_create_without_access = 'edit'; # mail domain for responsible field -- bare user-ids in responsible # fields will have this added to the end to create a sane mailto: link. # you must put the '@' sign at the beginning of the string $site_mail_domain = '@eejnet.jrc.it'; # hash of addresses that your site uses for submission of PRs # if this is defined for a given database, the edit and view pages # will include a link "submit a follup by email" -- a mailto: this # address and the Reply-To address of the PR. #%site_pr_submission_address = ('default' => 'bugs@example.com', # 'other_db' => 'other-bugs@example.com'); %site_pr_submission_address = (); # the maximum size posting we'll accept $site_post_max = 1024 * 1024; # show field descriptions on the view PR page? this tends to look # messy, so by default we only show them on the create and edit pages. $description_in_view = 0; # path to the gnatsweb help page. this is the file that will be # returned when the user clicks on the Help button. $help_page_path = './gnatsweb.html'; # Name you want in the page banner and banner colors. $site_banner_text = 'EEJNet Problem Reporting and Tracking System'; $site_banner_background = '#000000'; $site_banner_foreground = '#ffffff'; $site_button_background = '#000000'; $site_button_foreground = '#ffffff'; # Uncomment the following line and insert stylesheet URL in order to # link all generated pages to an external stylesheet. Both absolute # and relative URLs are supported. #$site_stylesheet='http://url.of/stylesheet'; $site_stylesheet = undef; # When $include_audit_trail is set to 1, the Audit-Trail will be # visible by default in the View PR screen. Sites that expect large # Audit-Trails, i.e. lot of mail back and forth etc., will want to set # this to 0. $include_audit_trail = 1; # when we have more than this many items, use a scrolling list # instead of a popup $popup_menu_becomes_obnoxious = 20; # default size for scrolling lists. overridden for some fields $scrolling_menu_default_size = 3; # Page background color -- not used unless defined. #$site_background = '#c0c0c0'; $site_background = undef; # Color to use for marking the names of required fields on the Create # PR page. $site_required_field_color = '#ff0000'; # control the mark_urls routine, which "htmlifies" PRs for view_pr. # it adds a lot of usability, but can be slow for huge (100K+) fields. # urls = make links clickable # emails = make addresses mailto: links # prs = make PR numbers links to gnatsweb # max_length = strings larger than this will not be processed %mark_urls = ( 'urls' => 1, 'emails' => 1, 'prs' => 1, 'max_length' => 1024*100, ); # Use temporary cookie for login information? Gnatsweb stores login # information in the db_prefs cookie in the user's browser. With # $use_temp_db_prefs_cookie set to 1, the cookie is stored in the # browser, not on disk. Thus, the cookie gets deleted when the user # exits the browser, improving security. Otherwise, the cookie will # remain active until the expiration date specified by # $global_cookie_expires arrives. $use_temp_db_prefs_cookie = 0; # What to use as the -path argument in cookies. Using '' (or omitting # -path) causes CGI.pm to pass the basename of the script. With that # setup, moving the location of gnatsweb.pl causes it to see the old # cookies but not be able to delete them. $global_cookie_path = '/'; $global_cookie_expires = '+30d'; # width of text fields $textwidth = 60; # do we allow users to spy on our communications with gnatsd? # if this is set, setting the 'debug' param will display communications # with gnatsd to the browser. really only useful to gnats administrators. $site_allow_remote_debug = 1; # delimiter to use within PRs for storage of attachments # if you change this, all your old PRs with attachments will # break... $attachment_delimiter = "----gnatsweb-attachment----\n"; # where to get help -- a web site with translated info documentation $gnats_info_top = 'http://www.gnu.org/software/gnats/gnats_toc.html'; # end customization #----------------------------------------------------------------------------- # Use CGI::Carp first, so that fatal errors come to the browser, including # those caused by old versions of CGI.pm. use CGI::Carp qw/fatalsToBrowser/; # 8/22/99 kenstir: CGI.pm-2.50's file upload is broken. # 9/19/99 kenstir: CGI.pm-2.55's file upload is broken. use CGI 2.56 qw/-nosticky/; use Socket; use IO::Handle; use Text::Tabs; # Version number + RCS revision number $VERSION = '4.00'; my $REVISION = (split(/ /, '$Revision: 1.124.2.2 $ '))[1]; my $GNATS_VERS = '0.0'; # bits in fieldinfo(field, flags) has (set=yes not-set=no) my $SENDINCLUDE = 1; # whether the send command should include the field my $REASONCHANGE = 2; # whether change to a field requires reason my $READONLY = 4; # if set, can't be edited my $AUDITINCLUDE = 8; # if set, save changes in Audit-Trail my $SENDREQUIRED = 16; # whether the send command _must_ include this field # The possible values of a server reply type. $REPLY_CONT means that there # are more reply lines that will follow; $REPLY_END Is the final line. my $REPLY_CONT = 1; my $REPLY_END = 2; # # Various PR field names that should probably not be referenced in here. # # Actually, the majority of uses are probably OK--but we need to map # internal names to external ones. (All of these field names correspond # to internal fields that are likely to be around for a long time.) # my $CATEGORY_FIELD = 'Category'; my $SYNOPSIS_FIELD = 'Synopsis'; my $SUBMITTER_ID_FIELD = 'Submitter-Id'; my $ORIGINATOR_FIELD = 'Originator'; my $AUDIT_TRAIL_FIELD = 'Audit-Trail'; my $RESPONSIBLE_FIELD = 'Responsible'; my $LAST_MODIFIED_FIELD = 'Last-Modified'; my $NUMBER_FIELD = 'builtinfield:Number'; my $STATE_FIELD = 'State'; my $UNFORMATTED_FIELD = 'Unformatted'; my $RELEASE_FIELD = 'Release'; # we use the access levels defined in gnatsd.h to do # access level comparisons #define ACCESS_UNKNOWN 0x00 #define ACCESS_DENY 0x01 #define ACCESS_NONE 0x02 #define ACCESS_SUBMIT 0x03 #define ACCESS_VIEW 0x04 #define ACCESS_VIEWCONF 0x05 #define ACCESS_EDIT 0x06 #define ACCESS_ADMIN 0x07 my %LEVEL_TO_CODE = ('deny' => 1, 'none' => 2, 'submit' => 3, 'view' => 4, 'viewconf' => 5, 'edit' => 6, 'admin' => 7); my $CODE_GREETING = 200; my $CODE_CLOSING = 201; my $CODE_OK = 210; my $CODE_SEND_PR = 211; my $CODE_SEND_TEXT = 212; my $CODE_NO_PRS_MATCHED = 220; my $CODE_NO_ADM_ENTRY = 221; my $CODE_PR_READY = 300; my $CODE_TEXT_READY = 301; my $CODE_INFORMATION = 350; my $CODE_INFORMATION_FILLER = 351; my $CODE_NONEXISTENT_PR = 400; my $CODE_EOF_PR = 401; my $CODE_UNREADABLE_PR = 402; my $CODE_INVALID_PR_CONTENTS = 403; my $CODE_INVALID_FIELD_NAME = 410; my $CODE_INVALID_ENUM = 411; my $CODE_INVALID_DATE = 412; my $CODE_INVALID_FIELD_CONTENTS = 413; my $CODE_INVALID_SEARCH_TYPE = 414; my $CODE_INVALID_EXPR = 415; my $CODE_INVALID_LIST = 416; my $CODE_INVALID_DATABASE = 417; my $CODE_INVALID_QUERY_FORMAT = 418; my $CODE_NO_KERBEROS = 420; my $CODE_AUTH_TYPE_UNSUP = 421; my $CODE_NO_ACCESS = 422; my $CODE_LOCKED_PR = 430; my $CODE_GNATS_LOCKED = 431; my $CODE_GNATS_NOT_LOCKED = 432; my $CODE_PR_NOT_LOCKED = 433; my $CODE_CMD_ERROR = 440; my $CODE_WRITE_PR_FAILED = 450; my $CODE_ERROR = 600; my $CODE_TIMEOUT = 610; my $CODE_NO_GLOBAL_CONFIG = 620; my $CODE_INVALID_GLOBAL_CONFIG = 621; my $CODE_NO_INDEX = 630; my $CODE_FILE_ERROR = 640; $| = 1; # flush output after each print # A couple of internal status variables: # Have the HTTP header, start_html, heading already been printed? my $print_header_done = 0; my $page_start_html_done = 0; my $page_heading_done = 0; sub gerror { my($text) = @_; my $page = 'Error'; print_header(); page_start_html($page); page_heading($page, 'Error'); print "
$text\n
\n"; } # Close the client socket and exit. The exit can be suppressed by: # setting $suppress_client_exit = 1 in the calling routine (using local) # [this is only set in edit_pr and the initial login section] sub client_exit { if (! defined($suppress_client_exit)) { close(SOCK); exit(); } else { $client_would_have_exited = 1; } } sub server_reply { my($state, $text, $type); my $raw_reply =$cmd
Reporter's email: | \n", $q->textfield(-name=>'email', -default=>$def_email, -size=>$textwidth), " | \n
";
fieldinfo ($_, 'flags') & $SENDREQUIRED ?
print "$_" : print "$_";
print " \n", fieldinfo($_, 'desc'), " | \n", $intro, "\n"; if (fieldinfo($_, 'fieldtype') eq "enum") { # Force user to choose a category. if ($_ eq $CATEGORY_FIELD) { push(@$values, "unknown") if (!grep /^unknown$/, @$values); $default = "unknown"; } if ($_ eq $SUBMITTER_ID_FIELD) { $default = $global_prefs{$SUBMITTER_ID_FIELD} || ''; } print popup_or_scrolling_menu($_, $values, $default), " | \n
Fix the following problems, then submit the problem report again:
", $q->ul($q->li(\@errors)); return; } my $fullname=$db_prefs{'user'}; if (exists ($responsible_fullname{$fullname})) { $fullname=" (".$responsible_fullname{$fullname}.")"; } else { $fullname=""; } # Supply a default value for Originator $fields{$ORIGINATOR_FIELD} = $fields{$ORIGINATOR_FIELD} || ($fields{'email'} . $fullname); # Handle the attached_file, if any. add_encoded_attachment_to_pr(\%fields, encode_attachment('attached_file')); # Compose the PR. my $text = unparsepr('send', %fields); $text = <Thank you for your report. It will take a short while for your report to be processed. When it is, you will receive an automated message about it, containing the Problem Report number, and the developer who has been assigned to investigate the problem.
"; print "You will be returned to your previous page in $refresh seconds...
"; page_footer($page); page_end_html($page); } # Return a URL which will take one to the specified $pr and with a # specified $cmd. For commands such as 'create' that have no # associated PR number, we pass $pr = 0, and this routine then leaves # out the pr parameter. For ease of use, when the user makes a # successful edit, we want to return to the URL he was looking at # before he decided to edit the PR. The return_url param serves to # store that info, and is included if $include_return_url is # specified. Note that the return_url is saved even when going into # the view page, since the user might go from there to the edit page. # sub get_pr_url { my($cmd, $pr, $include_return_url) = @_; my $url = $q->url() . "?cmd=$cmd&database=$global_prefs{'database'}"; $url .= "&pr=$pr" if $pr; $url .= "&return_url=" . $q->escape($q->self_url()) if $include_return_url; return $url; } # Return a URL to edit the given pr. See get_pr_url(). # sub get_editpr_url { return get_pr_url('edit', @_); } # Return a URL to view the given pr. See get_pr_url(). # sub get_viewpr_url { my $viewcmd = $include_audit_trail ? 'view%20audit-trail' : 'view'; return get_pr_url($viewcmd, @_); } # Same as script_name(), but includes 'database=xxx' param. # sub get_script_name { my $url = $q->script_name(); $url .= "?database=$global_prefs{'database'}" if defined($global_prefs{'database'}); return $url; } # Return links which send email regarding the current PR. # first link goes to interested parties, second link goes to # PR submission address and Reply-To (ie. it gets tacked on to # the audit trail). sub get_mailto_link { my $sub_mailto = ''; my($pr,%fields) = @_; my $int_mailto = $q->escape(scalar(interested_parties($pr, %fields))); if (defined($site_pr_submission_address{ $global_prefs{'database'} })) { $sub_mailto = $q->escape($site_pr_submission_address{$global_prefs{'database'}} . ',' . $fields{'Reply-To'}); } my $subject = $q->escape("Re: $fields{$CATEGORY_FIELD}/$pr: $fields{$SYNOPSIS_FIELD}"); my $body = $q->escape(get_viewpr_url($pr)); # Netscape Navigator up to and including 4.x should get the URL in # the body encoded only once -- and so should Opera unless ( ($ENV{'HTTP_USER_AGENT'} =~ "Mozilla\/(.)(.*)") && ($1 < 5) && ($2 !~ "compatible") || $ENV{'HTTP_USER_AGENT'} =~ "Opera\/" ) { $body = $q->escape($body); } my $reply = "" . "send email to interested parties\n"; if ($sub_mailto) { # include a link to email followup $reply .= "or send email followup to audit-trail\n"; } return $reply; } sub view { my($viewaudit, $tmp) = @_; # $pr must be 'local' to be available to site callback local($pr) = $q->param('pr'); # strip out leading category (and any other non-digit trash) from $pr $pr =~ s/\D//g; my $page = "View PR $pr"; page_start_html($page); if(!$pr) { error_page("You must specify a problem report number"); return; } # %fields must be 'local' to be available to site callback local(%fields) = readpr($pr); if (scalar(keys(%fields)) < 4) { # looks like there is no such PR, complain to the customer # (readpr() hardcodes 3 fields, even if there's no PR) gerror("PR $pr does not exist"); page_end_html($page); return; } page_heading($page, "View Problem Report: $pr"); print $q->start_form(-method=>'get'), hidden_db(), hidden_debug(), $q->hidden('pr', $pr), $q->hidden('return_url'); # print 'edit' and 'view audit-trail' buttons as appropriate, mailto link print ""; print $q->submit('cmd', 'edit'), ' or ' if (can_edit()); print $q->submit('cmd', 'view audit-trail'), ' or ' if (!$viewaudit); print get_mailto_link($pr, %fields); print ""; print $q->hr(), "\nReporter's email: | \n", $q->tt(make_mailto($fields{'Reply-To'})), " | \n
$_: \n", fieldinfo($_, 'desc'), " | \n"; } else { print " |
$_: | \n"; } print $q->tt($val), " |
"; print $q->submit('cmd', 'edit'), ' or ' if (can_edit()); print $q->submit('cmd', 'view audit-trail'), ' or ' if (!$viewaudit); print get_mailto_link($pr, %fields); print "
"; print $q->end_form(); # Footer comes before the audit-trail. page_footer($page); if($viewaudit) { print "\n\n", mark_urls($q->escapeHTML($fields{$AUDIT_TRAIL_FIELD})), "\n\n"; } page_end_html($page); } # edit - # The Edit PR page. # sub edit { my($pr) = $q->param('pr'); # strip out leading category (and any other non-digit trash) from # $pr, since it will unduly confuse gnats when we try to submit the # edit $pr =~ s/\D//g; my $page = "Edit PR $pr"; page_start_html($page); #my $debug = 0; if(!$pr) { error_page("You must specify a problem report number"); return; } my(%fields) = readpr($pr); page_heading($page, "Edit Problem Report: $pr"); # Trim Responsible for compatibility. XXX ??? !!! FIXME $fields{$RESPONSIBLE_FIELD} = trim_responsible($fields{$RESPONSIBLE_FIELD}); print multipart_form_start(-name=>'PrForm'), "\n", hidden_db(), hidden_debug(), $q->span($q->submit('cmd', 'submit edit'), " or ", $q->reset(-name=>'reset'), " or ", get_mailto_link($pr, %fields)), $q->hidden(-name=>'Editor', -value=>$db_prefs{'user'}, -override=>1), "\n", $q->hidden(-name=>'Last-Modified', -value=>$fields{$LAST_MODIFIED_FIELD}, -override=>1), "\n", $q->hidden(-name=>'pr', -value=>$pr, -override=>1), $q->hidden(-name=>'return_url'), "
Reporter's email: | \n", $q->textfield(-name=>'Reply-To', -default=>$fields{'Reply-To'}, -size=>$textwidth), " | \n
$_: \n", fieldinfo($_, 'desc'), " | \n", $intro, "\n"; if (fieldinfo ($_, 'fieldtype') eq 'enum') { my $default = $fields{$_}; my $found = 0; my $nopush = 0; # Check whether field value is a known enumeration value. foreach(@$values) { $found = 1 if $_ eq $default; $nopush = 1 if $_ eq 'unknown'; } unless ($found) { push(@$values, 'unknown') unless $nopush; $default = 'unknown'; } print popup_or_scrolling_menu($_, $values, $default), " | \n
Reason Changed: | \n", $q->textarea(-name=>"$_-Changed-Why", -default=>'', -override=>1, -cols=>$textwidth, -rows=>2, -wrap=>'hard'), " | \n
\n", mark_urls($q->escapeHTML($fields{$AUDIT_TRAIL_FIELD})), "\n\n"; page_end_html($page); } # Print out the %fields hash for debugging. sub debug_print_fields { my $fields_hash_ref = shift; foreach my $f (sort keys %$fields_hash_ref) { print "
$_: | \n";
my $value_list=fieldinfo($_, 'values');
my @values=('any', @$value_list);
if (fieldinfo($_, 'fieldtype') eq 'enum')
{
print popup_or_scrolling_menu ($_, \@values, $values[0]);
}
elsif (fieldinfo($_, 'fieldtype') eq 'multienum')
{
my $size = @values < 4 ? @values : 4;
print $q->scrolling_list(-name=>$_, -values=>\@values, -size=>$size,
-multiple=>'true', -defaults=>$values[0]);
}
if ($_ eq $STATE_FIELD)
{
print " ", $q->checkbox_group(-name=>'ignoreclosed', -values=>['Ignore Closed'], -defaults=>['Ignore Closed']); } elsif ($_ eq $SUBMITTER_ID_FIELD) { print " ", $q->checkbox_group(-name=>'originatedbyme', -values=>['Originated by You'], -defaults=>[]); } print " | \n
$SYNOPSIS_FIELD Search: | \n", $q->textfield(-name=>$SYNOPSIS_FIELD,-size=>25), " | \n
Multi-line Text Search: | \n", $q->textfield(-name=>'multitext',-size=>25), " | \n
Column Display: | \n"; my @allcolumns; foreach (@fieldnames) { if (fieldinfo($_, 'fieldtype') ne 'multitext') { push (@allcolumns, $_); } } # The 'number' field is always first in the @allcolumns array. If # users were allowed to select it in this list, the PR number would # appear twice in the Query Results table. We prevent this by # shifting 'number' out of the array. shift(@allcolumns); my(@columns) = split(' ', $global_prefs{'columns'} || ''); @columns = @allcolumns unless @columns; print $q->scrolling_list(-name=>'columns', -values=>\@allcolumns, -defaults=>\@columns, -multiple=>1, -size=>5), " | \n
Sort By: | \n",
$q->scrolling_list(-name=>'sortby',
-values=>\@fieldnames,
-multiple=>0,
-size=>1),
" ", $q->checkbox_group(-name=>'reversesort', -values=>['Reverse Order'], -defaults=>[]), " | \n
Display: | \n", $q->checkbox_group(-name=>'displaydate', -values=>['Current Date'], -defaults=>['Current Date']), " | \n
Search these text fields | \n", "using regular expression | \n", "
---|---|
Single-line text fields: | \n", $q->textfield(-name=>'text', -size=>$width), " | \n
Multi-line text fields: | \n", $q->textfield(-name=>'multitext', -size=>$width), " | \n
Date Search | \n", "Example: 1999-04-01 05:00 GMT | \n", "
---|---|
$_ after: | \n", $q->textfield(-name=>$_."_after", -size=>$width), " | \n
$_ before: | \n", $q->textfield(-name=>$_."_before", -size=>$width), " | \n
Search this field | \n", "using regular expression, or | \n", "using multi-selection | \n", "
---|---|---|
$_: | \n"; # 2nd column is regexp search field print "",
$q->textfield(-name=>$_,
-size=>$width);
print "\n";
# XXX ??? !!! FIXME
# This should be fixed by allowing a 'not' in front of the fields, so
# one can simply say "not closed".
if ($_ eq $STATE_FIELD)
{
print " ", $q->checkbox_group(-name=>'ignoreclosed', -values=>['Ignore Closed'], -defaults=>['Ignore Closed']), } print " | \n";
# 3rd column is blank or scrolling multi-select list
print ""; if (fieldinfo($_, 'fieldtype') =~ 'enum') { my $ary_ref = fieldinfo($_, 'values'); my $size = scalar(@$ary_ref); $size = 4 if $size > 4; print $q->scrolling_list(-name=>$_, -values=>$ary_ref, -multiple=>1, -size=>$size); } else { print " "; } print " | \n
Display these columns: | \n"; my @allcolumns; foreach (@fieldnames) { if (fieldinfo($_, 'fieldtype') ne 'multitext') { push (@allcolumns, $_); } } # The 'number' field is always first in the @allcolumns array. If # users were allowed to select it in this list, the PR number would # appear twice in the Query Results table. We prevent this by # shifting 'number' out of the array. shift(@allcolumns); my(@columns) = split(' ', $global_prefs{'columns'} || ''); @columns = @allcolumns unless @columns; print $q->scrolling_list(-name=>'columns', -values=>\@allcolumns, -defaults=>\@columns, -multiple=>1, -size=>5), " | \n
Sort By: | \n",
$q->scrolling_list(-name=>'sortby',
-values=>\@fieldnames,
-multiple=>0,
-size=>1),
" ", $q->checkbox_group(-name=>'reversesort', -values=>['Reverse Order'], -defaults=>[]), " | \n
Display: | \n", $q->checkbox_group(-name=>'displaydate', -values=>['Current Date'], -defaults=>['Current Date']), " | \n
$_ | \n"; } # finished the header row print "||
---|---|---|
$id"; if (can_edit()) { # Changed by NB print " [ edit ]"; } print " | "; my $fieldcontents; my $whichfield = 0; foreach $fieldcontents (@{$_}) { # The query returned the enums as numeric values, now we have to # map them back into strings. if ($fieldtypes[$whichfield] eq 'enum') { my $enumvals = fieldinfo($columns[$whichfield], 'values'); # A zero means that the string is absent from the enumeration type. $fieldcontents = $fieldcontents ? $$enumvals[$fieldcontents - 1] : 'unknown'; } $fieldcontents = $q->escapeHTML($fieldcontents); $fieldcontents = nonempty($fieldcontents); if ($columns[$whichfield] =~ /responsible/i) { $fieldcontents = make_mailto($fieldcontents); } else { # make urls and email addresses into live hrefs $fieldcontents = mark_urls($fieldcontents); } print "$fieldcontents | "; $whichfield++; } # Pad the remaining, empty columns with 's my $n = @{$_}; while ($noofcolumns - $n > 0) { print ""; $n++; } print "\n |
",
qq{View for bookmarking},
"
";
if ($reversesort) {
$url =~ s/[&;]reversesort=[^&;]*//;
} else {
$url .= $q->escapeHTML(";reversesort=Descending Order");
}
print qq{Reverse sort order},
"
Remember this query as: | \n", "", $q->textfield(-name=>'queryname', -size=>25), " | \n"; # Note: include hidden 'cmd' so user can simply press Enter w/o clicking. print $q->hidden(-name=>'cmd', -value=>'store query', -override=>1), $q->submit('cmd', 'store query'), $q->hidden('return_url', $q->self_url()), "\n | \n
", "query_string: $query_string", "cookie: $new_cookie\n", "
Your query \"$queryname\" has been saved. It will be available ", "the next time you reload the Query page.
"; print "You will be returned to your previous page ", " in $refresh seconds...
"; page_footer($page); page_end_html($page); } # print_stored_queries - # Retrieve any stored queries and print out a short form allowing # the submission of these queries. # # Queries are stored as individual cookies named # 'gnatsweb-query-$queryname'. # # side effects: # Sets global %stored_queries. # sub print_stored_queries { my %stored_queries = (); foreach my $cookie ($q->cookie()) { if ($cookie =~ /gnatsweb-query-(.*)/) { my $query_key = $1; my $query_param = $q->cookie($cookie); # extract queries relevant to the current database: if ($query_param =~ /database=$global_prefs{'database'}/ ) { $stored_queries{$query_key} = $query_param; } } } if (%stored_queries) { print "", hidden_db(), $q->submit('cmd', 'submit stored query'), " | ", $q->popup_menu(-name=>'queryname', -values=>[ sort(keys %stored_queries) ]), $q->end_form(), $q->start_form(), hidden_debug(), " | ", $q->hidden('return_url', $q->self_url()), hidden_db(), $q->submit('cmd', 'delete stored query'), " | ", $q->popup_menu(-name=>'queryname', -values=>[ sort(keys %stored_queries) ]), $q->end_form(), " |
); $row .= qq($global_prefs{'database'} ) if $global_prefs{'database'}; $row .= qq(User: $db_prefs{'user'} ) if $db_prefs{'user'}; $row .= qq(Access: $access_level) if $access_level; $row .= qq(\n | \n$site_banner_text | \n
MAIN PAGE | ); $row2 .= qq(CREATE | ) if can_create(); $row2 .= qq(QUERY | ); $row2 .= qq(ADV. QUERY | ); $row2 .= qq(LOG OUT | ) unless ($site_gnatsweb_server_auth); $row2 .= qq(HELP | ); $row2 .= qq(
"; my($param,@val); foreach $param (sort $q->param()) { @val = $q->param($param); printf "%-12s %s\n", $param, $q->escapeHTML(join(' ', @val)); } print "
locking $pr $user\n"; return parsepr(client_cmd("lock $pr $user")); } sub unlockpr { my($pr) = @_; #print "
unlocking $pr\n"; client_cmd("unlk $pr"); } sub readpr { my($pr) = @_; # Not sure if we want to do a RSET here but it probably won't hurt. client_cmd ("rset"); client_cmd ("QFMT full"); return parsepr(client_cmd("quer $pr")); } # interested_parties - # Get list of parties to notify about a PR change. # # Returns hash in array context; string of email addrs otherwise. sub interested_parties { my($pr, %fields) = @_; my(@people); my $person; my $list; # Get list of people by constructing it ourselves. @people = (); foreach $list ($fields{'Reply-To'}, $fields{$RESPONSIBLE_FIELD}, $category_notify{$fields{$CATEGORY_FIELD}}, $submitter_contact{$fields{$SUBMITTER_ID_FIELD}}, $submitter_notify{$fields{$SUBMITTER_ID_FIELD}}) { if (defined($list)) { foreach $person (split_csl ($list)) { push(@people, $person) if $person; } } } # Expand any unexpanded addresses, and build up the %addrs hash. my(%addrs) = (); my $addr; foreach $person (@people) { $addr = praddr($person) || $person; $addrs{$addr} = 1; } return wantarray ? %addrs : join(', ', keys(%addrs)); } # Split comma-separated list. # Commas in quotes are not separators! sub split_csl { my ($list) = @_; # Substitute commas in quotes with \002. while ($list =~ m~"([^"]*)"~g) { my $pos = pos($list); my $str = $1; $str =~ s~,~\002~g; $list =~ s~"[^"]*"~"$str"~; pos($list) = $pos; } my @res; foreach my $person (split(/\s*,\s*/, $list)) { $person =~ s/\002/,/g; push(@res, $person) if $person; } return @res; } # praddr - # Return email address of responsible person, or undef if not found. sub praddr { my $person = shift; # Done this way to avoid -w warning my $addr = exists($responsible_address{$person}) ? $responsible_address{$person} : undef; } # login_page_javascript - # Returns some Javascript code to test if cookies are being accepted. # sub login_page_javascript { my $ret = q{ }; } # change the database in the global cookie # sub change_database { $global_prefs{'database'} = $q->param('new_db'); my $global_cookie = create_global_cookie(); my $url = $q->url(); # the refresh header chokes on the query-string if the # params are separated by semicolons... $url =~ s/\;/&/g; print_header(-Refresh => "0; URL=$url", -cookie => [$global_cookie]), $q->start_html(); print $q->h3("Hold on... Redirecting...
"; my($param,@val); foreach $param (sort $q->param()) { @val = $q->param($param); printf "%-12s %s\n", $param, $q->escapeHTML(join(' ', @val)); } print "
User Name: | ", $q->textfield(-name=>'user', -size=>20, -default=>$db_prefs{'user'}), " | \n
Password: | \n", $q->password_field(-name=>'password', -value=>$db_prefs{'password'}, -size=>20), " | \n
Database: | \n", $q->popup_menu(-name=>'database', -values=>\@dbs, -default=>$global_prefs{'database'}), " | \n