PageRenderTime 62ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/source/cgi-bin/gnatsweb.pl

https://gitlab.com/garrett/gcc-website
Perl | 3704 lines | 3589 code | 37 blank | 78 comment | 21 complexity | 895f4fe6a5c5cb8025aefda8bcc093bc MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. #!/usr/bin/perl -w
  2. #
  3. # Gnatsweb - web front-end to gnats
  4. #
  5. # Copyright 1998-1999 - Matt Gerassimoff
  6. # and Ken Cox
  7. #
  8. # $Id: gnatsweb.pl,v 1.1.1.1.2.31 2001/11/26 10:59:48 yngves Exp $
  9. #
  10. #-----------------------------------------------------------------------------
  11. # Site-specific customization -
  12. #
  13. # WE STRONGLY SUGGEST you don't edit these variables here, but instead
  14. # put them in a file called 'gnatsweb-site.pl' in the same directory.
  15. # That way, when a new version of gnatsweb is released, you won't
  16. # need to edit them again.
  17. #
  18. # Info about your gnats host.
  19. $site_gnats_host = 'localhost';
  20. $site_gnats_port = 1529;
  21. # Set to true if you compiled gnats with GNATS_RELEASE_BASED defined.
  22. $site_release_based = 0;
  23. # Name you want in the page banner and banner color.
  24. $site_banner_text = 'gnatsweb';
  25. $site_banner_background = '#000000';
  26. $site_banner_foreground = '#ffffff';
  27. $site_button_foreground = '#ffffff';
  28. $site_button_background = '#000000';
  29. # Page background color -- not used unless defined.
  30. #$site_background = '#c0c0c0';
  31. #GCC-LOCAL begin.
  32. $site_background = '#ffffff';
  33. #GCC-LOCAL end.
  34. # Uncomment the following line and insert stylesheet URL in order to
  35. # link all generated pages to an external stylesheet. Both absolute
  36. # and relative URLs are supported.
  37. #$site_stylesheet='http://url.of/stylesheet';
  38. # When $include_audit_trail is set to 1, the Audit-Trail will be
  39. # visible by default in the View PR screen. Sites that expect large
  40. # Audit-Trails, i.e. lot of mail back and forth etc., will want to set
  41. # this to 0.
  42. $include_audit_trail = 1;
  43. # Have the HTTP header, start_html, heading already been printed?
  44. my $print_header_done = 0;
  45. my $page_start_html_done = 0;
  46. my $page_heading_done = 0;
  47. # Program to send email notifications.
  48. if (-x '/usr/sbin/sendmail')
  49. {
  50. $site_mailer = '/usr/sbin/sendmail -oi -t';
  51. }
  52. elsif (-x '/usr/lib/sendmail')
  53. {
  54. $site_mailer = '/usr/lib/sendmail -oi -t';
  55. }
  56. else
  57. {
  58. die("Can't locate 'sendmail'; must set \$site_mailer in gnats-site.pl");
  59. }
  60. # site_callback -
  61. #
  62. # If defined, this subroutine gets called at various times. The
  63. # reason it is being called is indicated by the $reason argument.
  64. # It can return undef, in which case gnatsweb does its default
  65. # thing. Or, it can return a piece of HTML to implement
  66. # site-specific behavior or appearance.
  67. #
  68. # Sorry, the reasons are not documented. Either put a call to
  69. # 'warn' into your gnats-site.pl file, or search this file for 'cb('.
  70. # For examples of some of the things you can do with the site_callback
  71. # subroutine, see gnatsweb-site-sente.pl.
  72. #
  73. # arguments:
  74. # $reason - reason for the call. Each reason is unique.
  75. # @args - additional parameters may be provided in @args.
  76. #
  77. # returns:
  78. # undef - take no special action
  79. # string - string is used by gnatsweb according to $reason
  80. #
  81. # example:
  82. # See gnatsweb-site-sente.pl for an extended example.
  83. #
  84. # sub site_callback {
  85. # my($reason, @args) = @_;
  86. # if ($reason eq 'sendpr_description') {
  87. # return 'default description text used in sendpr form';
  88. # }
  89. # undef;
  90. # }
  91. #
  92. # end customization
  93. #-----------------------------------------------------------------------------
  94. # Use CGI::Carp first, so that fatal errors come to the browser, including
  95. # those caused by old versions of CGI.pm.
  96. use CGI::Carp qw/fatalsToBrowser/;
  97. # 8/22/99 kenstir: CGI.pm-2.50's file upload is broken.
  98. # 9/19/99 kenstir: CGI.pm-2.55's file upload is broken.
  99. use CGI 2.56 qw(-oldstyle_urls :all);
  100. use gnats qw/client_init client_exit client_cmd/;
  101. use Text::Tabs;
  102. # Debugging fresh code.
  103. #$gnats::DEBUG_LEVEL = 2;
  104. # Version number + RCS revision number
  105. $VERSION = '2.9.3';
  106. $REVISION = (split(/ /, '$Revision: 1.1.1.1.2.31 $ '))[1];
  107. # width of text fields
  108. $textwidth = 60;
  109. # where to get help -- a web site with translated info documentation
  110. $gnats_info_top = 'http://www.gnu.org/software/gnats/gnats_toc.html';
  111. #GCC-LOCAL begin.
  112. $gnats_info_top = '/gnats.html';
  113. #GCC-LOCAL begin.
  114. # bits in %fieldnames has (set=yes not-set=no)
  115. $MULTILINE = 1; # whether field is multi line
  116. $SENDEXCLUDE = 2; # whether the send command should exclude the field
  117. $REASONCHANGE = 4; # whether change to a field requires reason
  118. $ENUM = 8; # whether field should be displayed as enumerated
  119. $EDITEXCLUDE = 16; # if set, don't display on edit page
  120. $AUDITINCLUDE = 32; # if set, save changes in Audit-Trail
  121. $| = 1; # flush output after each print
  122. # Return true if module MIME::Base64 is available. If available, it's
  123. # loaded the first time this sub is called.
  124. sub can_do_mime
  125. {
  126. return $can_do_mime if (defined($can_do_mime));
  127. # Had to basically implement 'require' myself here, otherwise perl craps
  128. # out into the browser window if you don't have the MIME::Base64 package.
  129. #$can_do_mime = eval 'require MIME::Base64';
  130. ITER: {
  131. foreach my $dir (@INC) {
  132. my $filename = "$dir/MIME/Base64.pm";
  133. if (-f $filename) {
  134. do $filename;
  135. die $@ if $@;
  136. $can_do_mime = 1;
  137. last ITER;
  138. }
  139. }
  140. $can_do_mime = 0;
  141. }
  142. #warn "NOTE: Can't use file upload feature without MIME::Base64 module\n";
  143. return $can_do_mime;
  144. }
  145. # Take the file attachment's file name, and return only the tail. Don't
  146. # want to store any path information, for security and clarity. Support
  147. # both DOS-style and Unix-style paths here, because we have both types of
  148. # clients.
  149. sub attachment_filename_tail
  150. {
  151. my($filename) = @_;
  152. $filename =~ s,.*/,,; # Remove leading Unix path elements.
  153. $filename =~ s,.*\\,,; # Remove leading DOS path elements.
  154. return $filename;
  155. }
  156. # Retrieve uploaded file attachment. Return it as
  157. # ($filename, $content_type, $data). Returns (undef,undef,undef)
  158. # if not present.
  159. #
  160. # See 'perldoc CGI' for details about this code.
  161. sub get_attachment
  162. {
  163. my $upload_param_name = shift;
  164. my $debug = 0;
  165. my $filename = $q->param($upload_param_name);
  166. return (undef, undef, undef) unless $filename;
  167. # 9/6/99 kenstir: My testing reveals that if uploadInfo returns undef,
  168. # then you can't read the file either.
  169. warn "get_attachment: filename=$filename\n" if $debug;
  170. my $hashref = $q->uploadInfo($filename);
  171. if (!defined($hashref)) {
  172. die "Got attachment filename ($filename) but no attachment data! Probably this is a programming error -- the form which submitted this data must be multipart/form-data (start_multipart_form()).";
  173. }
  174. if ($debug) {
  175. while (($k, $v) = each %$hashref) {
  176. warn "get_attachment: uploadInfo($k)=$v\n";
  177. }
  178. }
  179. # 9/6/99 kenstir: When testing locally on Linux, a .gz file yielded
  180. # no Content-Type. Therefore, have to assume binary. Would like to
  181. # check (-B $fh) to see if the stream is binary but that doesn't work.
  182. my $ctype = $hashref->{'Content-Type'} || 'application/octet-stream';
  183. warn "get_attachment: Content-Type=$ctype\n" if $debug;
  184. my $data = '';
  185. my $buf;
  186. my $fh = $q->upload($upload_param_name);
  187. warn "get_attachment: fh=$fh\n" if $debug;
  188. while (read($fh, $buf, 1024)) {
  189. $data .= $buf;
  190. }
  191. close $fh;
  192. return ($filename, $ctype, $data);
  193. }
  194. # Retrieve uploaded file attachment, and encode it so that it's
  195. # printable, for inclusion into the PR text.
  196. #
  197. # Returns the printable text representing the attachment. Returns '' if
  198. # the attachment was not present.
  199. sub encode_attachment
  200. {
  201. my $upload_param_name = shift;
  202. my $debug = 0;
  203. return '' unless can_do_mime();
  204. my ($filename, $ctype, $data) = get_attachment($upload_param_name);
  205. return '' unless $filename;
  206. # Strip off path elements in $filename.
  207. $filename = attachment_filename_tail($filename);
  208. warn "encode_attachment: $filename was ", length($data), " bytes of $ctype\n"
  209. if $debug;
  210. my $att = '';
  211. # Plain text is included inline; all else is encoded.
  212. $att .= "Content-Type: $ctype; name=\"$filename\"\n";
  213. if ($ctype eq 'text/plain') {
  214. $att .= "Content-Disposition: inline; filename=\"$filename\"\n\n";
  215. $att .= $data;
  216. }
  217. else {
  218. $att .= "Content-Transfer-Encoding: base64\n";
  219. $att .= "Content-Disposition: attachment; filename=\"$filename\"\n\n";
  220. $att .= MIME::Base64::encode_base64($data);
  221. }
  222. warn "encode_attachment: done\n" if $debug;
  223. return $att;
  224. }
  225. # Takes the encoded file attachment, decodes it and returns it as a hashref.
  226. sub decode_attachment
  227. {
  228. my $att = shift;
  229. my $debug = 0;
  230. my $hash_ref = {'original_attachment' => $att};
  231. # Split the envelope from the body.
  232. my ($envelope, $body) = split(/\n\n/, $att, 2);
  233. return $hash_ref unless ($envelope && $body);
  234. # Split mbox-like headers into (header, value) pairs, with a leading
  235. # "From_" line swallowed into USELESS_LEADING_ENTRY. Junk the leading
  236. # entry. Chomp all values.
  237. %$hash_ref = (USELESS_LEADING_ENTRY => split /^(\S*?):\s*/m, $envelope);
  238. delete($hash_ref->{USELESS_LEADING_ENTRY});
  239. for (keys %$hash_ref) {
  240. chomp $hash_ref->{$_};
  241. }
  242. # Keep the original_attachment intact.
  243. $$hash_ref{'original_attachment'} = $att;
  244. if (!$$hash_ref{'Content-Type'}
  245. || !$$hash_ref{'Content-Disposition'})
  246. {
  247. die "Unable to parse file attachment";
  248. }
  249. # Parse filename.
  250. # Note: the extra \ before the " is just so that perl-mode can parse it.
  251. if ($$hash_ref{'Content-Disposition'} !~ /(\S+);\s*filename=\"([^\"]+)\"/) {
  252. die "Unable to parse file attachment Content-Disposition";
  253. }
  254. $$hash_ref{'filename'} = attachment_filename_tail($2);
  255. # Decode the data if encoded.
  256. if (exists($$hash_ref{'Content-Transfer-Encoding'})
  257. && $$hash_ref{'Content-Transfer-Encoding'} eq 'base64')
  258. {
  259. $$hash_ref{'data'} = MIME::Base64::decode_base64($body);
  260. }
  261. else {
  262. $$hash_ref{'data'} = $body;
  263. }
  264. return $hash_ref;
  265. }
  266. # Print file attachment browser and buttons to download the attachments.
  267. # Which of these appear depend on the mode.
  268. sub print_attachments
  269. {
  270. my($fields_hash_ref, $mode) = @_;
  271. return unless can_do_mime();
  272. print "<tr><td valign=top><b>File Attachments:</b></td>\n<td>";
  273. # Add file upload button for adding new attachment.
  274. if ($mode eq 'sendpr' || $mode eq 'edit') {
  275. print "Add a file attachment:<br>",
  276. $q->filefield(-name=>'attached_file',
  277. -size=>50);
  278. }
  279. # Print table of existing attachments.
  280. # Add column with delete button in edit mode.
  281. my $array_ref = $$fields_hash_ref{'attachments'};
  282. my $table_rows_aref = [];
  283. my $i = 0;
  284. foreach $hash_ref (@$array_ref) {
  285. my $size = int(length($$hash_ref{'data'}) / 1024.0);
  286. $size = 1 if ($size < 1);
  287. my $row_data = $q->td( [ $q->submit('cmd', "download attachment $i"),
  288. $$hash_ref{'filename'},
  289. "${size}k" ] );
  290. $row_data .= $q->td($q->checkbox(-name=>'delete attachments',
  291. -value=>$i,
  292. -label=>"delete attachment $i"))
  293. if ($mode eq 'edit');
  294. push(@$table_rows_aref, $row_data);
  295. $i++;
  296. }
  297. if (@$table_rows_aref)
  298. {
  299. my $header_row_data = $q->th( ['download','filename','size' ] );
  300. $header_row_data .= $q->th('delete')
  301. if ($mode eq 'edit');
  302. print $q->table({-border=>1},
  303. $q->Tr($header_row_data),
  304. $q->Tr($table_rows_aref));
  305. }
  306. }
  307. # The user has requested download of a particular attachment.
  308. # Serve it up.
  309. sub download_attachment
  310. {
  311. my $attachment_number = shift;
  312. my($pr) = $q->param('pr');
  313. die "download_attachment called with no PR number"
  314. if(!$pr);
  315. my(%fields) = readpr($pr);
  316. my $array_ref = $fields{'attachments'};
  317. my $hash_ref = $$array_ref[$attachment_number];
  318. # Determine the attachment's content type.
  319. my $ct = $$hash_ref{'Content-Type'} || 'application/octet-stream';
  320. $ct =~ s~\s*;.*~~s;
  321. my $disp;
  322. # Internet Explorer 5.5 does not handle "content-disposition: attachment"
  323. # in the expected way. It needs a content-disposition of "file".
  324. ($ENV{'HTTP_USER_AGENT'} =~ "MSIE 5.5") ? ($disp = 'file') : ($disp = 'attachment');
  325. # Now serve the attachment, with the appropriate headers.
  326. print $q->header(-type => $ct,
  327. -content_disposition => "$disp; filename=\"$$hash_ref{'filename'}\""),
  328. $$hash_ref{'data'};
  329. }
  330. # Add the given (gnatsweb-encoded) attachment to the %fields hash.
  331. sub add_encoded_attachment_to_pr
  332. {
  333. my($fields_hash_ref, $encoded_attachment) = @_;
  334. return unless $encoded_attachment;
  335. my $ary_ref = $$fields_hash_ref{'attachments'} || [];
  336. my $hash_ref = { 'original_attachment' => $encoded_attachment };
  337. push(@$ary_ref, $hash_ref);
  338. $$fields_hash_ref{'attachments'} = $ary_ref;
  339. }
  340. # Add the given (gnatsweb-decoded) attachment to the %fields hash.
  341. sub add_decoded_attachment_to_pr
  342. {
  343. my($fields_hash_ref, $decoded_attachment_hash_ref) = @_;
  344. return unless $decoded_attachment_hash_ref;
  345. my $ary_ref = $$fields_hash_ref{'attachments'} || [];
  346. push(@$ary_ref, $decoded_attachment_hash_ref);
  347. $$fields_hash_ref{'attachments'} = $ary_ref;
  348. }
  349. # Remove the given attachments from the %fields hash.
  350. sub remove_attachments_from_pr
  351. {
  352. my($fields_hash_ref, @attachment_numbers) = @_;
  353. return unless @attachment_numbers;
  354. my $ary_ref = $$fields_hash_ref{'attachments'} || [];
  355. foreach my $attachment_number (@attachment_numbers)
  356. {
  357. # Remove the attachment be replacing it with the empty hash.
  358. # The sub unparsepr skips these.
  359. $$ary_ref[$attachment_number] = {};
  360. }
  361. }
  362. # wrapper functions for formstart...
  363. sub multipart_form_start
  364. {
  365. formstart(1, @_);
  366. }
  367. sub form_start
  368. {
  369. formstart(0, @_);
  370. }
  371. # workaround for an exceedingly dumb netscape bug. we hates
  372. # netscape... this bug manifests if you click on the "create"
  373. # button bar link (but not the grey button on the main page), submit a
  374. # PR, then hit the back button (usually because you got an error).
  375. # you're taken "back" to the same error page -- all the stuff you
  376. # entered into the submission form is *gone*. this is kind of annoying...
  377. # (it also manifests if you click the edit link from the query results page.)
  378. sub formstart
  379. {
  380. # this bugfix is mostly lifted from the CGI.pm docs. here's what they
  381. # have to say:
  382. # When you press the "back" button, the same page is loaded, not
  383. # the previous one. Netscape's history list gets confused
  384. # when processing multipart forms. If the script generates
  385. # different pages for the form and the results, hitting the
  386. # "back" button doesn't always return you to the previous page;
  387. # instead Netscape reloads the current page. This happens even
  388. # if you don't use an upload file field in your form.
  389. #
  390. # A workaround for this is to use additional path information to
  391. # trick Netscape into thinking that the form and the response
  392. # have different URLs. I recommend giving each form a sequence
  393. # number and bumping the sequence up by one each time the form
  394. # is accessed:
  395. # should we do multipart?
  396. my $multi = shift;
  397. # in case the caller has some args to pass...
  398. my %args = @_;
  399. # if the caller has given an "action" arg, we don't do any
  400. # subterfuge. let the caller worry about the bug...
  401. if (!exists $args{'-action'})
  402. {
  403. # get sequence number and increment it
  404. my $s = $q->path_info =~ m{/(\d+)/?$};
  405. $s++;
  406. # Trick Netscape into thinking it's loading a new script:
  407. $args{-action} = $q->script_name . "/$s";
  408. }
  409. if ($multi)
  410. {
  411. print $q->start_multipart_form(%args);
  412. }
  413. else
  414. {
  415. print $q->start_form(%args);
  416. }
  417. return;
  418. }
  419. # sendpr -
  420. # The Create PR page.
  421. #
  422. sub sendpr
  423. {
  424. my $page = 'Create PR';
  425. print_header();
  426. page_start_html($page);
  427. page_heading($page, 'Create Problem Report', 1);
  428. # remove "all" from arrays
  429. shift(@category);
  430. shift(@severity);
  431. shift(@priority);
  432. shift(@class);
  433. shift(@confidential);
  434. shift(@responsible);
  435. shift(@state);
  436. shift(@submitter_id);
  437. # Add '<default>' to @responsible, in case the site_callback alows
  438. # Responsible to be set upon submission. This is filtered out in
  439. # &submitnewpr.
  440. unshift(@responsible, '<default>');
  441. print multipart_form_start(-name=>'sendPrForm'), "\n",
  442. hidden_db(),
  443. $q->p($q->submit('cmd', 'submit'),
  444. " or ",
  445. $q->reset(-name=>'reset')),
  446. $q->hidden(-name=>'return_url'),
  447. "<hr>\n",
  448. "<table>";
  449. my $def_email = $global_prefs{'email'}
  450. || cb('get_default_value', 'email') || '';
  451. print "<tr>\n<td><b>Reporter's email:</b></td>\n<td>",
  452. $q->textfield(-name=>'email',
  453. -default=>$def_email,
  454. -size=>$textwidth),
  455. "</td>\n</tr>\n<tr>\n<td><b>CC these people<br>on PR status email:</b></td>\n<td>",
  456. $q->textfield(-name=>'X-GNATS-Notify',
  457. -size=>$textwidth),
  458. # a blank row, to separate header info from PR info
  459. "</td>\n</tr>\n<tr>\n<td>&nbsp;</td>\n<td>&nbsp;</td>\n</tr>\n";
  460. foreach (@fieldnames)
  461. {
  462. next if ($fieldnames{$_} & $SENDEXCLUDE);
  463. my $lc_fieldname = field2param($_);
  464. # Get default value from site_callback if provided, otherwise take
  465. # our defaults.
  466. my $default;
  467. $default = 'serious' if /Severity/;
  468. $default = 'medium' if /Priority/;
  469. $default = $global_prefs{'Submitter-Id'} || 'unknown' if /Submitter-Id/;
  470. #GCC-LOCAL begin.
  471. $default = 'net' if /Submitter-Id/;
  472. #GCC-LOCAL end.
  473. $default = $global_prefs{'Originator'} if /Originator/;
  474. $default = grep(/^unknown$/i, @category) ? "unknown" : $category[0]
  475. if /Category/;
  476. $default = $config{'DEFAULT_RELEASE'} if /Release/;
  477. $default = '' if /Responsible/;
  478. $default = cb("sendpr_$lc_fieldname") || $default;
  479. # The "intro" provides a way for the site callback to print something
  480. # at the top of a given field.
  481. my $intro = cb("sendpr_intro_$lc_fieldname") || '';
  482. if ($fieldnames{$_} & $ENUM)
  483. {
  484. if ($lc_fieldname eq "category")
  485. {
  486. print "<tr>\n<td><b>$_:</b>\n</td>\n<td>",
  487. $intro,
  488. $q->popup_menu(-name=>$_,
  489. -values=>\@$lc_fieldname,
  490. -labels=>\%category_desc,
  491. -default=>$default);
  492. print "</td>\n</tr>\n";
  493. }
  494. elsif ($lc_fieldname eq "responsible")
  495. {
  496. print "<tr>\n<td><b>$_:</b>\n</td>\n<td>",
  497. $intro,
  498. $q->popup_menu(-name=>$_,
  499. -values=>\@$lc_fieldname,
  500. -labels=>\%responsible_fullname,
  501. -default=>$fields{$_});
  502. print "</td>\n</tr>\n";
  503. } else
  504. {
  505. print "<tr>\n<td><b>$_:</b>\n</td>\n<td>",
  506. $intro,
  507. $q->popup_menu(-name=>$_,
  508. -values=>\@$lc_fieldname,
  509. -default=>$default);
  510. print "</td>\n</tr>\n";
  511. }
  512. }
  513. elsif ($fieldnames{$_} & $MULTILINE)
  514. {
  515. my $rows = 4;
  516. $rows = 8 if /Description/;
  517. $rows = 2 if /Environment/;
  518. #GCC-LOCAL begin.
  519. if ($lc_fieldname eq "description")
  520. {
  521. printf "<tr>\n<td></td>\n<td>When you provide (preprocessed) source, "
  522. ."please only insert it into one of the text fields if it is "
  523. ."very small, say below 50 lines. Else <em>please</em> attach "
  524. ."it as a file (see below).";
  525. }
  526. #GCC-LOCAL end.
  527. print "<tr>\n<td valign=top><b>$_:</b></td>\n<td>",
  528. $intro,
  529. $q->textarea(-name=>$_,
  530. -cols=>$textwidth,
  531. -rows=>$rows,
  532. -default=>$default);
  533. # Create file upload button after Description.
  534. print_attachments(\%fields, 'sendpr') if /Description/;
  535. print "</td>\n</tr>\n";
  536. }
  537. else
  538. {
  539. print "<tr>\n<td><b>$_:</b></td>\n<td>",
  540. $intro,
  541. $q->textfield(-name=>$_,
  542. -size=>$textwidth,
  543. -default=>$default);
  544. print "</td>\n</tr>\n";
  545. }
  546. print "\n";
  547. }
  548. print "</table>",
  549. $q->p($q->submit('cmd', 'submit'),
  550. " or ",
  551. $q->reset(-name=>'reset')),
  552. $q->end_form();
  553. page_footer($page);
  554. page_end_html($page);
  555. }
  556. # validate_email_field -
  557. # Used by validate_new_pr to check email address fields in a new PR.
  558. sub validate_email_field
  559. {
  560. my($fieldname, $fieldval, $required) = @_;
  561. my $blank = ($fieldval =~ /^\s*$/);
  562. if ($required && $blank)
  563. {
  564. return "$fieldname is blank";
  565. }
  566. # From rkimball@vgi.com, allows @ only if it's followed by what looks
  567. # more or less like a domain name.
  568. my $email_addr = '[^@\s]+(@\S+\.\S+)?';
  569. if (!$blank && $fieldval !~ /^\s*($email_addr\s*)+$/)
  570. {
  571. return "'$fieldval' doesn't look like a valid email address (xxx\@xxx.xxx)";
  572. }
  573. return '';
  574. }
  575. # validate_new_pr -
  576. # Make sure fields have reasonable values before submitting a new PR.
  577. sub validate_new_pr
  578. {
  579. my(%fields) = @_;
  580. my(@errors) = ();
  581. my $err;
  582. # validate email fields
  583. $err = validate_email_field('E-mail Address', $fields{'email'}, 'required');
  584. push(@errors, $err) if $err;
  585. # $err = validate_email_field('CC', $fields{'cc'});
  586. # push(@errors, $err) if $err;
  587. $err = validate_email_field('X-GNATS-Notify', $fields{'X-GNATS-Notify'});
  588. push(@errors, $err) if $err;
  589. # validate some other fields
  590. push(@errors, "Category is blank or 'unknown'")
  591. if($fields{'Category'} =~ /^\s*$/ || $fields{'Category'} eq "unknown");
  592. push(@errors, "Synopsis is blank")
  593. if($fields{'Synopsis'} =~ /^\s*$/);
  594. push(@errors, "Release is blank")
  595. if($fields{'Release'} =~ /^\s*$/);
  596. push(@errors, "Submitter-Id is 'unknown'")
  597. if($fields{'Submitter-Id'} eq 'unknown');
  598. #GCC-LOCAL begin.
  599. push(@errors, "Priority is 'high'")
  600. if($fields{'Priority'} eq 'high');
  601. #GCC-LOCAL end.
  602. @errors;
  603. }
  604. sub submitnewpr
  605. {
  606. my $page = 'Create PR Results';
  607. my $debug = 0;
  608. my(@values, $key);
  609. my(%fields);
  610. foreach $key ($q->param)
  611. {
  612. my $val = $q->param($key);
  613. if($fieldnames{$key} && ($fieldnames{$key} & $MULTILINE))
  614. {
  615. $val = fix_multiline_val($val);
  616. }
  617. $fields{$key} = $val;
  618. }
  619. # If Responsible is '<default>', delete it; gnats handles that. See
  620. # also &sendpr.
  621. if(defined($fields{'Responsible'}) && $fields{'Responsible'} eq '<default>') {
  622. delete $fields{'Responsible'};
  623. }
  624. # Make sure the pr is valid.
  625. my(@errors) = validate_new_pr(%fields);
  626. if (@errors)
  627. {
  628. print_header();
  629. page_start_html($page);
  630. page_heading($page, 'Error');
  631. print "<h3>Your problem report has not been sent.</h3>\n",
  632. "<p>Fix the following problems, then submit the problem report again:</p>",
  633. $q->ul($q->li(\@errors));
  634. return;
  635. }
  636. # Supply a default value for Originator
  637. $fields{'Originator'} = $fields{'Originator'} || $fields{'email'};
  638. # Handle the attached_file, if any.
  639. add_encoded_attachment_to_pr(\%fields, encode_attachment('attached_file'));
  640. # Compose the message
  641. my $text = unparsepr('send', %fields);
  642. $text = <<EOT . $text;
  643. To: $config{'GNATS_ADDR'}
  644. CC: $fields{'X-GNATS-Notify'}
  645. Subject: $fields{'Synopsis'}
  646. From: $fields{'email'}
  647. Reply-To: $fields{'email'}
  648. X-Send-Pr-Version: gnatsweb-$VERSION ($REVISION)
  649. X-GNATS-Notify: $fields{'X-GNATS-Notify'}
  650. EOT
  651. # Allow debugging
  652. if($debug)
  653. {
  654. print_header();
  655. page_start_html($page);
  656. print "<h3>debugging -- PR NOT SENT</h3>",
  657. $q->pre($q->escapeHTML($text)),
  658. "<hr>";
  659. page_end_html($page);
  660. return;
  661. }
  662. # Send the message
  663. if(!open(MAIL, "|$site_mailer"))
  664. {
  665. print_header();
  666. page_start_html($page);
  667. page_heading($page, 'Error');
  668. print "<h3>Error invoking $site_mailer</h3>";
  669. return;
  670. }
  671. print MAIL $text;
  672. if(!close(MAIL))
  673. {
  674. print_header();
  675. page_start_html($page);
  676. page_heading($page, 'Error');
  677. print "<h3>Bad pipe to $site_mailer</h3>";
  678. exit;
  679. }
  680. # Return the user to the page they were viewing when they pressed
  681. # 'create'.
  682. my $return_url = $q->param('return_url') || get_script_name();
  683. my $refresh = 5;
  684. print_header(-Refresh => "$refresh; URL=$return_url",
  685. -cookie => create_global_cookie());
  686. # Workaround for MSIE:
  687. my @args = (-title=>"$page - $site_banner_text");
  688. push(@args, -bgcolor=>$site_background)
  689. if defined($site_background);
  690. push(@args, -style=>{-src=>$site_stylesheet})
  691. if defined($site_stylesheet);
  692. push(@args, -head=>meta({-http_equiv=>'Refresh',
  693. -content=>"$refresh; URL=$return_url"}));
  694. print $q->start_html(@args);
  695. # Print page banner, with button bar, without the <head> part:
  696. page_start_html($page, 0, 1);
  697. page_heading($page, 'Problem Report Sent');
  698. print "<p>Thank you for your report. It will take a short while for
  699. your report to be processed. When it is, you will receive
  700. an automated message about it, containing the Problem Report
  701. number, and the developer who has been assigned to
  702. investigate the problem.</p>";
  703. print "<p>Page will refresh in $refresh seconds...</p>\n";
  704. page_footer($page);
  705. page_end_html($page);
  706. }
  707. # Return a URL which will take one to the specified $pr and with a
  708. # specified $cmd. For commands such as 'create' that have no
  709. # associated PR number, we pass $pr = 0, and this routine then leaves
  710. # out the pr parameter. For ease of use, when the user makes a
  711. # successful edit, we want to return to the URL he was looking at
  712. # before he decided to edit the PR. The return_url param serves to
  713. # store that info, and is included if $include_return_url is
  714. # specified. Note that the return_url is saved even when going into
  715. # the view page, since the user might go from there to the edit page.
  716. #
  717. sub get_pr_url
  718. {
  719. my($cmd, $pr, $include_return_url) = @_;
  720. my $url = $q->url() . "?cmd=$cmd&database=$global_prefs{'database'}";
  721. $url .= "&pr=$pr" if $pr;
  722. $url .= "&return_url=" . $q->escape($q->self_url())
  723. if $include_return_url;
  724. return $url;
  725. }
  726. # Return a URL to edit the given pr. See get_pr_url().
  727. #
  728. sub get_editpr_url
  729. {
  730. return get_pr_url('edit', @_);
  731. }
  732. # Return a URL to view the given pr. See get_pr_url().
  733. #
  734. sub get_viewpr_url
  735. {
  736. my $viewcmd = $include_audit_trail ? 'view%20audit-trail' : 'view';
  737. return get_pr_url($viewcmd, @_);
  738. }
  739. # Return a URL to create a pr. See get_pr_url().
  740. #
  741. sub get_createpr_url
  742. {
  743. return get_pr_url('create', @_);
  744. }
  745. # Same as script_name(), but includes 'database=xxx' param.
  746. #
  747. sub get_script_name
  748. {
  749. my $url = $q->script_name();
  750. $url .= "?database=$global_prefs{'database'}"
  751. if defined($global_prefs{'database'});
  752. return $url;
  753. }
  754. # Return a link which sends email regarding the current PR.
  755. sub get_mailto_link
  756. {
  757. my($pr,%fields) = @_;
  758. # NOTE: cagney/2003-01-31: Don't escape the interested parties
  759. # e-mail list. MOZILLA has a nasty bug were it doesn't re-adjust
  760. # the e-mail list length after de-escaping it. This causes MOZILLA
  761. # to use "?Sub..." in the list of e-mail addresses.
  762. # my $mailto = $q->escape(scalar(interested_parties($pr, 1, %fields)));
  763. my $mailto = interested_parties($pr, 1, %fields);
  764. my $subject = $q->escape("Re: $fields{'Category'}/$pr: $fields{'Synopsis'}");
  765. my $body = $q->escape(get_viewpr_url($pr));
  766. return "<a href=\"mailto:$mailto?Subject=$subject&Body=$body\">"
  767. . "send email to interested parties</a>\n";
  768. }
  769. # Look for text that looks like URLs and turn it into actual links.
  770. sub mark_urls
  771. {
  772. my ($val) = @_;
  773. # This probably doesn't catch all URLs, but one hopes it catches the
  774. # majority.
  775. $val =~ s/\b((s?https?|ftp):\/\/[-a-zA-Z0-9_.]+(:[0-9]+)?[-a-zA-Z0-9_\$.+\!*\(\),;:\@\&\%\x93\x90=?~\#\/]*)/
  776. \<a href="$1">$1\<\/a\>/g;
  777. return $val;
  778. }
  779. sub view
  780. {
  781. my($viewaudit, $tmp) = @_;
  782. # $pr must be 'local' to be available to site callback
  783. local($pr) = $q->param('pr');
  784. if(!$pr)
  785. {
  786. error_page('Error', 'You must specify a problem report number');
  787. return;
  788. }
  789. if($pr =~ /\D/)
  790. {
  791. error_page('Error', 'Invalid PR number');
  792. return;
  793. }
  794. my $page = "View PR $pr";
  795. print_header();
  796. page_start_html($page);
  797. page_heading($page, "View Problem Report: $pr", 1);
  798. # %fields must be 'local' to be available to site callback
  799. local(%fields) = readpr($pr);
  800. print $q->start_form(),
  801. hidden_db(),
  802. $q->hidden('pr'),
  803. $q->hidden('return_url');
  804. # print 'edit' and 'view audit-trail' buttons as appropriate, mailto link
  805. print "<p>";
  806. print $q->submit('cmd', 'edit') if (can_edit());
  807. print " or " if (can_edit() && !$viewaudit);
  808. print $q->submit('cmd', 'view audit-trail') if (!$viewaudit);
  809. print " or ",
  810. get_mailto_link($pr, %fields), "</p>";
  811. print $q->hr(),
  812. "<table>\n";
  813. print "<tr>\n<td><b>Reporter's email:</b></td>\n<td>",
  814. $q->tt($fields{'Reply-To'}),
  815. # "<tr><td><b>Others to notify<br>of updates to this PR:</b><td>",
  816. "</td>\n</tr>\n<tr>\n<td><b>CC these people<br>on PR status email:</b></td>\n<td>",
  817. $q->tt($fields{'X-GNATS-Notify'}),
  818. # a blank row, to separate header info from PR info
  819. "</td>\n</tr>\n<tr>\n<td>&nbsp;</td>\n<td>&nbsp;</td>\n</tr>\n";
  820. foreach (@fieldnames)
  821. {
  822. next if $_ eq 'Audit-Trail';
  823. my $val = $q->escapeHTML($fields{$_}) || ''; # to avoid -w warning
  824. my $valign = '';
  825. if ($fieldnames{$_} & $MULTILINE)
  826. {
  827. $valign = 'valign=top';
  828. $val = expand($val);
  829. $val =~ s/$/<br>/gm;
  830. $val =~ s/<br>$//; # previous substitution added one too many <br>'s
  831. $val =~ s/ /&nbsp; /g;
  832. $val =~ s/&nbsp; /&nbsp; &nbsp;/g;
  833. $val = mark_urls($val);
  834. }
  835. print "<tr><td $valign nowrap><b>$_:</b></td>\n<td>",
  836. $q->tt($val), "\n";
  837. # Print attachments after Description.
  838. if (/Description/) {
  839. print "</td>\n</tr>\n";
  840. print_attachments(\%fields, 'view');
  841. }
  842. print "</td>\n</tr>\n"
  843. }
  844. print "</table>",
  845. $q->hr();
  846. # print 'edit' and 'view audit-trail' buttons as appropriate, mailto link
  847. print "\n<p>";
  848. print $q->submit('cmd', 'edit') if (can_edit());
  849. print " or " if (can_edit() && !$viewaudit);
  850. print $q->submit('cmd', 'view audit-trail') if (!$viewaudit);
  851. print " or ",
  852. get_mailto_link($pr, %fields);
  853. print "</p>\n";
  854. print $q->end_form();
  855. # Footer comes before the audit-trail.
  856. page_footer($page);
  857. if($viewaudit)
  858. {
  859. print "<h3>Audit Trail:</h3>\n",
  860. mark_urls($q->pre($q->escapeHTML($fields{'Audit-Trail'})));
  861. }
  862. page_end_html($page);
  863. }
  864. # edit -
  865. # The Edit PR page.
  866. #
  867. sub edit
  868. {
  869. #my $debug = 0; # no debug code in here
  870. my($pr) = $q->param('pr');
  871. if(!$pr)
  872. {
  873. error_page('Error', 'You must specify a problem report number');
  874. return;
  875. }
  876. if($pr =~ /\D/)
  877. {
  878. error_page('Error', 'Invalid PR number');
  879. return;
  880. }
  881. my $page = "Edit PR $pr";
  882. print_header();
  883. page_start_html($page);
  884. page_heading($page, "Edit Problem Report: $pr", 1);
  885. # Read the PR.
  886. my(%fields) = readpr($pr);
  887. # Trim Responsible for compatibility.
  888. $fields{'Responsible'} = trim_responsible($fields{'Responsible'});
  889. # remove "all" from arrays
  890. shift(@category);
  891. shift(@severity);
  892. shift(@priority);
  893. shift(@class);
  894. shift(@confidential);
  895. shift(@responsible);
  896. shift(@state);
  897. shift(@submitter_id);
  898. print multipart_form_start(-name=>'editPrForm'), "\n",
  899. hidden_db(),
  900. $q->p($q->submit('cmd', 'submit edit'),
  901. " or ",
  902. $q->reset(-name=>'reset'),
  903. " or ",
  904. get_mailto_link($pr, %fields)),
  905. $q->hidden(-name=>'Editor',
  906. -value=>$db_prefs{'user'},
  907. -override=>1),
  908. $q->hidden(-name=>'Last-Modified',
  909. -value=>$fields{'Last-Modified'},
  910. -override=>1),
  911. $q->hidden(-name=>'pr'),
  912. $q->hidden(-name=>'return_url'),
  913. "<hr>\n";
  914. print "<table>\n";
  915. print "<tr>\n<td><b>Reporter's email:</b></td>\n<td>",
  916. $q->textfield(-name=>'Reply-To',
  917. -default=>$fields{'Reply-To'},
  918. -size=>$textwidth),
  919. # "<tr><td><b>Others to notify<br>of updates to this PR:</b><td>",
  920. "</td>\n</tr>\n<tr>\n<td><b>CC these people<br>on PR status email:</b></td>\n<td>",
  921. $q->textfield(-name=>'X-GNATS-Notify',
  922. -default=>$fields{'X-GNATS-Notify'},
  923. -size=>$textwidth),
  924. # a blank row, to separate header info from PR info
  925. "</td>\n</tr>\n<tr>\n<td>&nbsp;</td>\n<td>&nbsp;</td>\n</tr>\n";
  926. foreach (@fieldnames)
  927. {
  928. next if ($fieldnames{$_} && ($fieldnames{$_} & $EDITEXCLUDE));
  929. my $lc_fieldname = field2param($_);
  930. # The "intro" provides a way for the site callback to print something
  931. # at the top of a given field.
  932. my $intro = cb("edit_intro_$lc_fieldname") || '';
  933. if ($fieldnames{$_} && ($fieldnames{$_} & $ENUM))
  934. {
  935. my @values = cb('edit_pr', $fields{'Category'}, $lc_fieldname);
  936. @values = @$lc_fieldname unless (defined($values[0]));
  937. if ($lc_fieldname eq "category")
  938. {
  939. print "<tr>\n<td><b>$_:</b>\n</td>\n<td>",
  940. $intro,
  941. $q->popup_menu(-name=>$_,
  942. -values=>\@values,
  943. -labels=>\%category_desc,
  944. -default=>$fields{$_});
  945. print "</td>\n</tr>\n";
  946. print "</td>\n</tr>\n";
  947. }
  948. elsif ($lc_fieldname eq "responsible")
  949. {
  950. print "<tr>\n<td><b>$_:</b>\n</td>\n<td>",
  951. $intro,
  952. $q->popup_menu(-name=>$_,
  953. -values=>\@values,
  954. -labels=>\%responsible_fullname,
  955. -default=>$fields{$_});
  956. print "</td>\n</tr>\n";
  957. }
  958. else
  959. {
  960. print "<tr>\n<td><b>$_:</b>\n</td>\n<td>",
  961. $intro,
  962. $q->popup_menu(-name=>$_,
  963. -values=>\@$lc_fieldname,
  964. -default=>$fields{$_});
  965. print "</td>\n</tr>\n";
  966. }
  967. }
  968. elsif ($fieldnames{$_} && ($fieldnames{$_} & $MULTILINE))
  969. {
  970. my $rows = 4;
  971. $rows = 8 if /Description/;
  972. $rows = 2 if /Environment/;
  973. print "<tr>\n<td valign=top><b>$_:</b></td>\n<td>",
  974. $intro,
  975. $q->textarea(-name=>$_,
  976. -cols=>$textwidth,
  977. -rows=>$rows,
  978. -default=>$fields{$_});
  979. # Print attachments after Description.
  980. if (/Description/) {
  981. print "</td>\n</tr>\n";
  982. print_attachments(\%fields, 'edit');
  983. }
  984. print "</td>\n</tr>\n";
  985. }
  986. else
  987. {
  988. print "<tr>\n<td><b>$_:</b></td>\n<td>",
  989. $intro,
  990. $q->textfield(-name=>$_,
  991. -size=>$textwidth,
  992. -default=>$fields{$_});
  993. print "</td>\n</tr>\n";
  994. }
  995. if ($fieldnames{$_} && $fieldnames{$_} & $REASONCHANGE)
  996. {
  997. print "<tr>\n<td valign=top><b>Reason Changed:</b></td>\n<td>",
  998. $q->textarea(-name=>"$_-Why",
  999. -default=>'',
  1000. -override=>1,
  1001. -cols=>$textwidth,
  1002. -rows=>2);
  1003. print "</td>\n</tr>\n";
  1004. }
  1005. print "\n";
  1006. }
  1007. print "</table>",
  1008. $q->p($q->submit('cmd', 'submit edit'),
  1009. " or ",
  1010. $q->reset(-name=>'reset'),
  1011. " or ",
  1012. get_mailto_link($pr, %fields)),
  1013. $q->end_form(),
  1014. $q->hr();
  1015. # Footer comes before the audit-trail.
  1016. page_footer($page);
  1017. print "<h3>Audit-Trail:</h3>\n",
  1018. mark_urls($q->pre($q->escapeHTML($fields{'Audit-Trail'})));
  1019. page_end_html($page);
  1020. }
  1021. # Print out the %fields hash for debugging.
  1022. sub debug_print_fields
  1023. {
  1024. my $fields_hash_ref = shift;
  1025. print "<table cellspacing=0 cellpadding=0 border=1>\n";
  1026. foreach my $f (sort keys %$fields_hash_ref)
  1027. {
  1028. print "<tr valign=top><td>$f</td><td>",
  1029. $q->pre($q->escapeHTML($$fields_hash_ref{$f})),
  1030. "</td></tr>\n";
  1031. }
  1032. my $aref = $$fields_hash_ref{'attachments'} || [];
  1033. my $i=0;
  1034. foreach my $href (@$aref) {
  1035. print "<tr valign=top><td>attachment $i</td><td>",
  1036. ($$href{'original_attachment'}
  1037. ? $$href{'original_attachment'} : "--- empty ---"),
  1038. "</td></tr>\n";
  1039. $i++;
  1040. }
  1041. print "</table><hr>\n";
  1042. }
  1043. # submitedit -
  1044. # User pressed 'submit' on the edit page. If the edits are applied
  1045. # successfully, give a message then return the user to the URL
  1046. # specified in param('return_url') so that he can continue doing what
  1047. # he was previously doing (e.g. looking at query results). If the
  1048. # edits are not successful, print and error and stay put.
  1049. #
  1050. sub submitedit
  1051. {
  1052. local($page) = 'Edit PR Results'; # local so visible to &$err_sub
  1053. my $debug = 0;
  1054. my $mail_sent = 0;
  1055. # Local sub to report errors while editing.
  1056. # This allows us to postpone calling print_header().
  1057. my $err_sub = sub {
  1058. my($err_heading, $err_text) = @_;
  1059. print_header();
  1060. page_start_html($page);
  1061. page_heading($page, 'Error');
  1062. print "<h3>$err_heading</h3>";
  1063. print "<p>$err_text</p>" if $err_text;
  1064. page_footer($page);
  1065. page_end_html($page);
  1066. return;
  1067. };
  1068. my($pr) = $q->param('pr');
  1069. if(!$pr)
  1070. {
  1071. &$err_sub("You must specify a problem report number");
  1072. return;
  1073. }
  1074. my(%fields, %mailto, $adr);
  1075. my $audittrail = '';
  1076. my $err = '';
  1077. my $ok = 1;
  1078. # Retrieve new attachment (if any) before locking PR, in case it fails.
  1079. my $encoded_attachment = encode_attachment('attached_file');
  1080. my(%oldfields) = lockpr($pr, $db_prefs{'user'});
  1081. if ($gnats::ERRSTR) {
  1082. &$err_sub("$gnats::ERRSTR", "The PR has not been changed. "
  1083. . "If this problem persists, please contact a "
  1084. . "GNATS administrator.");
  1085. client_exit();
  1086. exit();
  1087. }
  1088. LOCKED:
  1089. {
  1090. # Trim Responsible for compatibility.
  1091. $oldfields{'Responsible'} = trim_responsible($oldfields{'Responsible'});
  1092. # Merge %oldfields and CGI params to get %fields. Not all gnats
  1093. # fields have to be present in the CGI params; the ones which are
  1094. # not specified default to their old values.
  1095. %fields = %oldfields;
  1096. foreach my $key ($q->param)
  1097. {
  1098. my $val = $q->param($key);
  1099. if($key =~ /-Why/
  1100. || ($fieldnames{$key} && ($fieldnames{$key} & $MULTILINE)))
  1101. {
  1102. $val = fix_multiline_val($val);
  1103. }
  1104. $fields{$key} = $val;
  1105. }
  1106. # Add the attached file, if any, to the new PR.
  1107. add_encoded_attachment_to_pr(\%fields, $encoded_attachment);
  1108. # Delete any attachments, if directed.
  1109. my(@deleted_attachments) = $q->param('delete attachments');
  1110. remove_attachments_from_pr(\%fields, @deleted_attachments);
  1111. if($fields{'Last-Modified'} ne $oldfields{'Last-Modified'})
  1112. {
  1113. &$err_sub("PR $pr has been modified since you started editing it.",
  1114. "Please return to the edit form, press the Reload button, "
  1115. . "then make your edits again.\n"
  1116. . "<pre>Last-Modified was '$fields{'Last-Modified'}'\n"
  1117. . "Last-Modified is now '$oldfields{'Last-Modified'}'</pre>\n");
  1118. last LOCKED;
  1119. }
  1120. if($db_prefs{'user'} eq "" || $fields{'Responsible'} eq "")
  1121. {
  1122. &$err_sub("Can't make the edit",
  1123. "Responsible is '$fields{'Responsible'}', user is '$db_prefs{'user'}'");
  1124. last LOCKED;
  1125. }
  1126. # If X-GNATS-Notify or Reply-To changed, we need to splice the
  1127. # change into the envelope.
  1128. foreach ('Reply-To', 'X-GNATS-Notify')
  1129. {
  1130. if($fields{$_} ne $oldfields{$_})
  1131. {
  1132. if ($fields{'envelope'} =~ /^$_:/m)
  1133. {
  1134. # Replace existing header with new one.
  1135. $fields{'envelope'} =~ s/^$_:.*$/$_: $fields{$_}/m;
  1136. }
  1137. else
  1138. {
  1139. # Insert new header at end (blank line). Keep blank line at end.
  1140. $fields{'envelope'} =~ s/^$/$_: $fields{$_}\n/m;
  1141. }
  1142. }
  1143. }
  1144. if ($debug)
  1145. {
  1146. &$err_sub("debugging -- PR edits not submitted");
  1147. debug_print_fields(\%fields);
  1148. last LOCKED;
  1149. }
  1150. # Leave an Audit-Trail
  1151. foreach (@fieldnames)
  1152. {
  1153. if($_ ne "Audit-Trail")
  1154. {
  1155. $oldfields{$_} = '' if !defined($oldfields{$_}); # avoid -w warning
  1156. $fields{$_} = '' if !defined($fields{$_}); # avoid -w warning
  1157. if($fields{$_} ne $oldfields{$_})
  1158. {
  1159. next unless ($fieldnames{$_} & $AUDITINCLUDE);
  1160. if($fieldnames{$_} & $MULTILINE)
  1161. {
  1162. # For multitext fields, indent the values.
  1163. my $tmp = $oldfields{$_};
  1164. $tmp =~ s/^/ /gm;
  1165. $audittrail .= "$_-Changed-From:\n$tmp";
  1166. $tmp = $fields{$_};
  1167. $tmp =~ s/^/ /gm;
  1168. $audittrail .= "$_-Changed-To:\n$tmp";
  1169. }
  1170. else
  1171. {
  1172. $audittrail .= "$_-Changed-From-To: $oldfields{$_}->$fields{$_}\n";
  1173. }
  1174. $audittrail .= "$_-Changed-By: $db_prefs{'user'}\n";
  1175. $audittrail .= "$_-Changed-When: " . scalar(localtime()) . "\n";
  1176. if($fieldnames{$_} & $REASONCHANGE)
  1177. {
  1178. if($fields{"$_-Why"} =~ /^\s*$/)
  1179. {
  1180. if ($ok) {
  1181. $ok = 0;
  1182. print_header();
  1183. page_start_html($page);
  1184. page_heading($page, 'Error');
  1185. }
  1186. print "<h3>Field '$_' must have a reason for change</h3>",
  1187. "Old $_: $oldfields{$_}<br>",
  1188. "New $_: $fields{$_}";
  1189. }
  1190. else
  1191. {
  1192. # Indent the "Why" value.
  1193. my $tmp = $fields{"$_-Why"};
  1194. $tmp =~ s/^/ /gm;
  1195. $audittrail .= "$_-Changed-Why:\n" . $tmp;
  1196. }
  1197. }
  1198. }
  1199. }
  1200. }
  1201. $fields{'Audit-Trail'} = $oldfields{'Audit-Trail'} . $audittrail;
  1202. last LOCKED unless $ok;
  1203. # Get list of people to notify, then add old responsible person.
  1204. # If that person doesn't exist, don't worry about it.
  1205. %mailto = interested_parties($pr, 0, %fields);
  1206. if(defined($adr = praddr($oldfields{'Responsible'})))
  1207. {
  1208. $mailto{$adr} = 1;
  1209. }
  1210. my($newpr) = unparsepr('gnatsd', %fields);
  1211. $newpr =~ s/\r//g;
  1212. #print $q->pre($q->escapeHTML($newpr));
  1213. #last LOCKED; # debug
  1214. # Submit the edits.
  1215. client_cmd("edit $fields{'Number'}");
  1216. my $error = $gnats::ERRSTR;
  1217. client_cmd("$newpr\n.");
  1218. $error ||= $gnats::ERRSTR;
  1219. if ($error) {
  1220. my $page = 'Error';
  1221. print_header();
  1222. page_start_html($page);
  1223. page_heading($page, $page);
  1224. print $q->h2("$error");
  1225. print $q->p("The PR has not been changed. "
  1226. . "If this problem persists, please contact a "
  1227. . "GNATS administrator.");
  1228. last LOCKED;
  1229. }
  1230. # Now send mail to all concerned parties,
  1231. # but only if there's something interesting to say.
  1232. my($mailto);
  1233. delete $mailto{''};
  1234. $mailto = join(", ", sort(keys(%mailto)));
  1235. #print $q->pre($q->escapeHTML("mailto->$mailto<-\n"));
  1236. #last LOCKED; # debug
  1237. if($mailto ne "" && $audittrail ne "")
  1238. {
  1239. # Use email address in responsible file as From, if present.
  1240. my $from = $responsible_address{$db_prefs{'user'}} || $db_prefs{'user'};
  1241. if(!open(MAILER, "|$site_mailer"))
  1242. {
  1243. &$err_sub("Edit successful, but email notification failed",
  1244. "Can't open pipe to $site_mailer: $!");
  1245. last LOCKED;
  1246. }
  1247. else
  1248. {
  1249. print MAILER "To: $mailto\n";
  1250. print MAILER "From: $from\n";
  1251. print MAILER "Reply-To: $from, $mailto, $config{'GNATS_ADDR'}\n";
  1252. print MAILER "X-Mailer: gnatsweb $VERSION\n";
  1253. #GCC-LOCAL begin.
  1254. #print MAILER "Subject: Re: $fields{'Category'}/$pr\n\n";
  1255. print MAILER "Subject: Re: $fields{'Category'}/$pr: $fields{'Synopsis'}\n\n";
  1256. #GCC-LOCAL end.
  1257. if ($oldfields{'Synopsis'} eq $fields{'Synopsis'})
  1258. {
  1259. print MAILER "Synopsis: $fields{'Synopsis'}\n\n";
  1260. }
  1261. else
  1262. {
  1263. print MAILER "Old Synopsis: $oldfields{'Synopsis'}\n";
  1264. print MAILER "New Synopsis: $fields{'Synopsis'}\n\n";
  1265. }
  1266. print MAILER "$audittrail\n";
  1267. # Print URL so that HTML-enabled mail readers can jump to the PR.
  1268. print MAILER get_viewpr_url($pr), "\n";
  1269. if(!close(MAILER))
  1270. {
  1271. &$err_sub("Edit successful, but email notification failed",
  1272. "Can't close pipe to $site_mailer: $!");
  1273. last LOCKED;
  1274. }
  1275. $mail_sent = 1;
  1276. }
  1277. }
  1278. $lock_end_reached = 1;
  1279. }
  1280. unlockpr($fields{'Number'});
  1281. if ($lock_end_reached) {
  1282. # We print out the "Edit successful" message after unlocking the
  1283. # PR. If the user hits the Stop button of the browser while
  1284. # submitting, the web server won't terminate the script until the
  1285. # next time the server attempts to output something to the
  1286. # browser. Since this is the first output after the PR was
  1287. # locked, we print it after the unlocking. Let user know the edit
  1288. # was successful. After a 2s delay, refresh back to where the user
  1289. # was before the edit. Internet Explorer does not honor the HTTP
  1290. # Refresh header, so we have to complement the "clean" CGI.pm
  1291. # method with the ugly hack below, with a HTTP-EQUIV in the HEAD
  1292. # to make things work.
  1293. my $return_url = $q->param('return_url') || get_script_name();
  1294. my $refresh = 2;
  1295. print_header(-Refresh => "$refresh; URL=$return_url",
  1296. -cookie => create_global_cookie());
  1297. # Workaround for MSIE:
  1298. my @args = (-title=>"$page - $site_banner_text");
  1299. push(@args, -bgcolor=>$site_background)
  1300. if defined($site_background);
  1301. push(@args, -style=>{-src=>$site_stylesheet})
  1302. if defined($site_stylesheet);
  1303. push(@args, -head=>meta({-http_equiv=>'Refresh',
  1304. -content=>"$refresh; URL=$return_url"}));
  1305. print $q->start_html(@args);
  1306. # Print page banner, with button bar, without the <head> part:
  1307. page_start_html($page, 0, 1);
  1308. page_heading($page, ($mail_sent ? 'Edit successful; mail sent'
  1309. : 'Edit successful'));
  1310. print "<p>Page will refresh in $refresh seconds...</p>\n";
  1311. }
  1312. page_footer($page);
  1313. page_end_html($page);
  1314. }
  1315. sub query_page
  1316. {
  1317. my $page = 'Query PR';
  1318. page_start_html($page);
  1319. page_heading($page, 'Query Problem Reports', 1);
  1320. print_stored_queries();
  1321. print $q->start_form(),
  1322. hidden_db(),
  1323. $q->submit('cmd', 'submit query'),
  1324. "<hr>",
  1325. "<table>\n",
  1326. "<tr>\n<td>Category:</td>\n<td>",
  1327. $q->popup_menu(-name=>'category',
  1328. -values=>\@category,
  1329. -labels=>\%category_desc,
  1330. -default=>$category[0]),
  1331. "</td>\n</tr>\n<tr>\n<td>Severity:</td>\n<td>",
  1332. $q->popup_menu(-name=>'severity',
  1333. -values=>\@severity,
  1334. -default=>$severity[0]),
  1335. "</td>\n</tr>\n<tr>\n<td>Priority:</td>\n<td>",
  1336. $q->popup_menu(-name=>'priority',
  1337. -values=>\@priority,
  1338. -default=>$priority[0]),
  1339. "</td>\n</tr>\n<tr>\n<td>Responsible:</td>\n<td>",
  1340. $q->popup_menu(-name=>'responsible',
  1341. -values=>\@responsible,
  1342. -labels=>\%responsible_fullname,
  1343. -default=>$responsible[0]),
  1344. "</td>\n</tr>\n<tr>\n<td>Submitter-ID:</td>\n<td>",
  1345. $q->popup_menu(-name=>'submitter_id',
  1346. -values=>\@submitter_id,
  1347. -labels=>\%submitter_fullname,
  1348. -default=>$submitter_id[0]),
  1349. "</td>\n</tr>\n<tr>\n<td>State:</td>\n<td>",
  1350. $q->popup_menu(-name=>'state',
  1351. -values=>\@state,
  1352. -default=>$state[0]),
  1353. "</td>\n</tr>\n<tr>\n<td>\n</td>\n<td>",
  1354. $q->checkbox_group(-name=>'ignoreclosed',
  1355. -values=>['Ignore Closed'],
  1356. -defaults=>['Ignore Closed']),
  1357. "</td>\n</tr>\n<tr>\n<td>Class:</td>\n<td>",
  1358. $q->popup_menu(-name=>'class',
  1359. -values=>\@class,
  1360. -default=>$class[0]),
  1361. "</td>\n</tr>\n<tr>\n<td>Synopsis Search:</td>\n<td>",
  1362. $q->textfield(-name=>'synopsis',-size=>25),
  1363. "</td>\n</tr>\n<tr>\n<td>Multi-line Text Search:</td>\n<td>",
  1364. $q->textfield(-name=>'multitext',-size=>25),
  1365. "</td>\n</tr>\n<tr>\n<td>\n</td>\n<td>",
  1366. $q->checkbox_group(-name=>'originatedbyme',
  1367. -values=>['Originated by You'],
  1368. -defaults=>[]),
  1369. "</td>\n</tr>\n<tr valign=top>\n<td>Column Display:</td>\n<td>";
  1370. my(@columns) = split(' ', $global_prefs{'columns'});
  1371. @columns = @deffields unless @columns;
  1372. print $q->scrolling_list(-name=>'columns',
  1373. -values=>\@fields,
  1374. -defaults=>\@columns,
  1375. -multiple=>1,
  1376. -size=>5),
  1377. "</td>\n</tr>\n<tr>\n<td>\n</td>\n<td>",
  1378. $q->checkbox_group(-name=>'displaydate',
  1379. -values=>['Display Current Date'],
  1380. -defaults=>['Display Current Date']),
  1381. "</td>\n</tr>\n</table>",
  1382. "<hr>",
  1383. $q->submit('cmd', 'submit query'),
  1384. $q->end_form();
  1385. page_footer($page);
  1386. page_end_html($page);
  1387. }
  1388. sub advanced_query_page
  1389. {
  1390. my $page = 'Advanced Query';
  1391. page_start_html($page);
  1392. page_heading($page, 'Query Problem Reports', 1);
  1393. print_stored_queries();
  1394. print $q->start_form(),
  1395. hidden_db();
  1396. my $width = 30;
  1397. my $heading_bg = '#9fbdf9';
  1398. my $cell_bg = '#d0d0d0';
  1399. print $q->p($q->submit('cmd', 'submit query'),
  1400. " or ",
  1401. $q->reset(-name=>'reset'));
  1402. print "<hr>";
  1403. print "<center>";
  1404. ### Text and multitext queries
  1405. print "<table border=1 cellspacing=0 bgcolor=$cell_bg>",
  1406. "<caption>Search All Text</caption>",
  1407. "<tr bgcolor=$heading_bg>\n",
  1408. "<th nowrap>Search these text fields</th>\n",
  1409. "<th nowrap>using regular expression</th>\n",
  1410. "</tr>\n";
  1411. print "<tr>\n<td>Single-line text fields:</td>\n<td>",
  1412. $q->textfield(-name=>'text', -size=>$width),
  1413. "</td>\n</tr>\n",
  1414. "<tr>\n<td>Multi-line text fields:</td>\n<td>",
  1415. $q->textfield(-name=>'multitext', -size=>$width),
  1416. "</td>\n</tr>\n",
  1417. "</table>\n";
  1418. print "<div>&nbsp;</div>\n";
  1419. ### Date queries
  1420. print "<table border=1 cellspacing=0 bgcolor=$cell_bg>",
  1421. "<caption>Search By Date</caption>",
  1422. "<tr bgcolor=$heading_bg>\n",
  1423. "<th nowrap>Date Se…

Large files files are truncated, but you can click here to view the full file