]> git.donarmstrong.com Git - debbugs.git/blob - Debbugs/CGI/Pkgreport.pm
merge changes from don
[debbugs.git] / Debbugs / CGI / Pkgreport.pm
1 # This module is part of debbugs, and is released
2 # under the terms of the GPL version 2, or any later version. See the
3 # file README and COPYING for more information.
4 #
5 # [Other people have contributed to this file; their copyrights should
6 # be listed here too.]
7 # Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
8
9
10 package Debbugs::CGI::Pkgreport;
11
12 =head1 NAME
13
14 Debbugs::CGI::Pkgreport -- specific routines for the pkgreport cgi script
15
16 =head1 SYNOPSIS
17
18
19 =head1 DESCRIPTION
20
21
22 =head1 BUGS
23
24 None known.
25
26 =cut
27
28 use warnings;
29 use strict;
30 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
31 use base qw(Exporter);
32
33 use IO::Scalar;
34 use Params::Validate qw(validate_with :types);
35
36 use Debbugs::Config qw(:config :globals);
37 use Debbugs::CGI qw(:url :html :util);
38 use Debbugs::Common qw(:misc :util :date);
39 use Debbugs::Status qw(:status);
40 use Debbugs::Bugs qw(bug_filter);
41 use Debbugs::Packages qw(:mapping);
42
43 use Debbugs::Text qw(:templates);
44
45 use POSIX qw(strftime);
46
47
48 BEGIN{
49      ($VERSION) = q$Revision: 494 $ =~ /^Revision:\s+([^\s+])/;
50      $DEBUG = 0 unless defined $DEBUG;
51
52      @EXPORT = ();
53      %EXPORT_TAGS = (html => [qw(short_bug_status_html pkg_htmlizebugs),
54                               qw(pkg_javascript),
55                               qw(pkg_htmlselectyesno pkg_htmlselectsuite),
56                               qw(buglinklist pkg_htmlselectarch)
57                              ],
58                      misc => [qw(generate_package_info make_order_list),
59                               qw(myurl),
60                               qw(get_bug_order_index determine_ordering),
61                              ],
62                     );
63      @EXPORT_OK = (qw());
64      Exporter::export_ok_tags(keys %EXPORT_TAGS);
65      $EXPORT_TAGS{all} = [@EXPORT_OK];
66 }
67
68 =head2 generate_package_info
69
70      generate_package_info($srcorbin,$package)
71
72 Generates the informational bits for a package and returns it
73
74 =cut
75
76 sub generate_package_info{
77      my %param = validate_with(params => \@_,
78                                spec  => {binary => {type => BOOLEAN,
79                                                     default => 1,
80                                                    },
81                                          package => {type => SCALAR|ARRAYREF,
82                                                     },
83                                          options => {type => HASHREF,
84                                                     },
85                                          bugs    => {type => ARRAYREF,
86                                                     },
87                                         },
88                               );
89
90      my $output_scalar = '';
91      my $output = globify_scalar(\$output_scalar);
92
93      my $package = $param{package};
94
95      my %pkgsrc = %{getpkgsrc()};
96      my $srcforpkg = $package;
97      if ($param{binary} and exists $pkgsrc{$package}
98          and defined $pkgsrc{$package}) {
99           $srcforpkg = $pkgsrc{$package};
100      }
101
102      my $showpkg = html_escape($package);
103      my $maintainers = getmaintainers();
104      my $maint = $maintainers->{$srcforpkg};
105      if (defined $maint) {
106           print {$output} '<p>';
107           print {$output} (($maint =~ /,/)? "Maintainer for $showpkg is "
108                            : "Maintainers for $showpkg are ") .
109                                 package_links(maint => $maint);
110           print {$output} ".</p>\n";
111      }
112      else {
113           print {$output} "<p>No maintainer for $showpkg. Please do not report new bugs against this package.</p>\n";
114      }
115      my @pkgs = getsrcpkgs($srcforpkg);
116      @pkgs = grep( !/^\Q$package\E$/, @pkgs );
117      if ( @pkgs ) {
118           @pkgs = sort @pkgs;
119           if ($param{binary}) {
120                print {$output} "<p>You may want to refer to the following packages that are part of the same source:\n";
121           }
122           else {
123                print {$output} "<p>You may want to refer to the following individual bug pages:\n";
124           }
125           #push @pkgs, $src if ( $src && !grep(/^\Q$src\E$/, @pkgs) );
126           print {$output} scalar package_links(package=>[@pkgs]);
127           print {$output} ".\n";
128      }
129      my @references;
130      my $pseudodesc = getpseudodesc();
131      if ($package and defined($pseudodesc) and exists($pseudodesc->{$package})) {
132           push @references, "to the <a href=\"http://${debbugs::gWebDomain}/pseudo-packages${debbugs::gHTMLSuffix}\">".
133                "list of other pseudo-packages</a>";
134      }
135      else {
136           if ($package and defined $gPackagePages) {
137                push @references, sprintf "to the <a href=\"%s\">%s package page</a>",
138                     html_escape("http://${gPackagePages}/$package"), html_escape("$package");
139           }
140           if (defined $gSubscriptionDomain) {
141                my $ptslink = $param{binary} ? $srcforpkg : $package;
142                push @references, q(to the <a href="http://).html_escape("$gSubscriptionDomain/$ptslink").q(">Package Tracking System</a>);
143           }
144           # Only output this if the source listing is non-trivial.
145           if ($param{binary} and $srcforpkg) {
146                push @references,
147                     "to the source package ".
148                          package_links(src=>$srcforpkg,
149                                        options => $param{options}) .
150                               "'s bug page";
151           }
152      }
153      if (@references) {
154           $references[$#references] = "or $references[$#references]" if @references > 1;
155           print {$output} "<p>You might like to refer ", join(", ", @references), ".</p>\n";
156      }
157      if (defined $param{maint} || defined $param{maintenc}) {
158           print {$output} "<p>If you find a bug not listed here, please\n";
159           printf {$output} "<a href=\"%s\">report it</a>.</p>\n",
160                html_escape("http://${debbugs::gWebDomain}/Reporting${debbugs::gHTMLSuffix}");
161      }
162      if (not $maint and not @{$param{bugs}}) {
163           print {$output} "<p>There is no record of the " . html_escape($package) .
164                ($param{binary} ? " package" : " source package") .
165                     ", and no bugs have been filed against it.</p>";
166      }
167      return $output_scalar;
168 }
169
170
171 =head2 short_bug_status_html
172
173      print short_bug_status_html(status => read_bug(bug => 5),
174                                  options => \%param,
175                                 );
176
177 =over
178
179 =item status -- status hashref as returned by read_bug
180
181 =item options -- hashref of options to pass to package_links (defaults
182 to an empty hashref)
183
184 =item bug_options -- hashref of options to pass to bug_links (default
185 to an empty hashref)
186
187 =item snippet -- optional snippet of information about the bug to
188 display below
189
190
191 =back
192
193
194
195 =cut
196
197 sub short_bug_status_html {
198      my %param = validate_with(params => \@_,
199                                spec   => {status => {type => HASHREF,
200                                                     },
201                                           options => {type => HASHREF,
202                                                       default => {},
203                                                      },
204                                           bug_options => {type => HASHREF,
205                                                           default => {},
206                                                          },
207                                           snippet => {type => SCALAR,
208                                                       default => '',
209                                                      },
210                                          },
211                               );
212
213      my %status = %{$param{status}};
214
215      $status{tags_array} = [sort(split(/\s+/, $status{tags}))];
216      $status{date_text} = strftime('%a, %e %b %Y %T UTC', gmtime($status{date}));
217      $status{mergedwith_array} = [split(/ /,$status{mergedwith})];
218
219      my @blockedby= split(/ /, $status{blockedby});
220      $status{blockedby_array} = [];
221      if (@blockedby && $status{"pending"} ne 'fixed' && ! length($status{done})) {
222           for my $b (@blockedby) {
223                my %s = %{get_bug_status($b)};
224                next if $s{"pending"} eq 'fixed' || length $s{done};
225                push @{$status{blockedby_array}},{bug_num => $b, subject => $s{subject}, status => \%s};
226           }
227      }
228
229      my @blocks= split(/ /, $status{blocks});
230      $status{blocks_array} = [];
231      if (@blocks && $status{"pending"} ne 'fixed' && ! length($status{done})) {
232           for my $b (@blocks) {
233                my %s = %{get_bug_status($b)};
234                next if $s{"pending"} eq 'fixed' || length $s{done};
235                push @{$status{blocks_array}}, {bug_num => $b, subject => $s{subject}, status => \%s};
236           }
237      }
238
239
240      return fill_in_template(template => 'cgi/short_bug_status',
241                              variables => {status => \%status,
242                                            isstrongseverity => \&Debbugs::Status::isstrongseverity,
243                                            html_escape   => \&Debbugs::CGI::html_escape,
244                                            looks_like_number => \&Scalar::Util::looks_like_number,
245                                           },
246                              hole_var  => {'&package_links' => \&Debbugs::CGI::package_links,
247                                            '&bug_links'     => \&Debbugs::CGI::bug_links,
248                                            '&version_url'   => \&Debbugs::CGI::version_url,
249                                            '&secs_to_english' => \&Debbugs::Common::secs_to_english,
250                                            '&strftime'      => \&POSIX::strftime,
251                                            '&maybelink'     => \&Debbugs::CGI::maybelink,
252                                           },
253                             );
254
255      my $result = "";
256
257      my $showseverity;
258      if ($status{severity} eq 'normal') {
259           $showseverity = '';
260      }
261      elsif (isstrongseverity($status{severity})) {
262           $showseverity = "Severity: <em class=\"severity\">$status{severity}</em>;\n";
263      }
264      else {
265           $showseverity = "Severity: <em>$status{severity}</em>;\n";
266      }
267
268      $result .= package_links(package => $status{package},
269                               options  => $param{options},
270                              );
271
272      my $showversions = '';
273      if (@{$status{found_versions}}) {
274           my @found = @{$status{found_versions}};
275           $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @found;
276      }
277      if (@{$status{fixed_versions}}) {
278           $showversions .= '; ' if length $showversions;
279           $showversions .= '<strong>fixed</strong>: ';
280           my @fixed = @{$status{fixed_versions}};
281           $showversions .= join ', ', map {s{/}{ }; html_escape($_)} @fixed;
282      }
283      $result .= ' (<a href="'.
284           version_url(package => $status{package},
285                       found   => $status{found_versions},
286                       fixed   => $status{fixed_versions},
287                      ).qq{">$showversions</a>)} if length $showversions;
288      $result .= ";\n";
289
290      $result .= $showseverity;
291      $result .= "Reported by: ".package_links(submitter=>$status{originator},
292                                               class => "submitter",
293                                              );
294      $result .= ";\nOwned by: " . package_links(owner => $status{owner},
295                                                 class => "submitter",
296                                                )
297           if length $status{owner};
298      $result .= ";\nTags: <strong>"
299           . html_escape(join(", ", sort(split(/\s+/, $status{tags}))))
300                . "</strong>"
301                     if (length($status{tags}));
302
303      $result .= (length($status{mergedwith})?";\nMerged with ":"") .
304           bug_links(bug => [split(/ /,$status{mergedwith})],
305                     class => "submitter",
306                    );
307      $result .= (length($status{blockedby})?";\nBlocked by ":"") .
308           bug_links(bug => [split(/ /,$status{blockedby})],
309                     class => "submitter",
310                    );
311      $result .= (length($status{blocks})?";\nBlocks ":"") .
312           bug_links(bug => [split(/ /,$status{blocks})],
313                     class => "submitter",
314                    );
315
316      if (length($status{done})) {
317           $result .= "<br><strong>Done:</strong> " . html_escape($status{done});
318           my $days = bug_archiveable(bug => $status{id},
319                                      status => \%status,
320                                      days_until => 1,
321                                     );
322           if ($days >= 0 and defined $status{location} and $status{location} ne 'archive') {
323                $result .= ";\n<strong>Can be archived" . ( $days == 0 ? " today" : $days == 1 ? " in $days day" : " in $days days" ) . "</strong>";
324           }
325           elsif (defined $status{location} and $status{location} eq 'archived') {
326                $result .= ";\n<strong>Archived.</strong>";
327           }
328      }
329
330      unless (length($status{done})) {
331           if (length($status{forwarded})) {
332                $result .= ";\n<strong>Forwarded</strong> to "
333                     . join(', ',
334                            map {maybelink($_)}
335                            split /\,\s+/,$status{forwarded}
336                           );
337           }
338           # Check the age of the logfile
339           my ($days_last,$eng_last) = secs_to_english(time - $status{log_modified});
340           my ($days,$eng) = secs_to_english(time - $status{date});
341
342           if ($days >= 7) {
343                my $font = "";
344                my $efont = "";
345                $font = "em" if ($days > 30);
346                $font = "strong" if ($days > 60);
347                $efont = "</$font>" if ($font);
348                $font = "<$font>" if ($font);
349
350                $result .= ";\n ${font}$eng old$efont";
351           }
352           if ($days_last > 7) {
353                my $font = "";
354                my $efont = "";
355                $font = "em" if ($days_last > 30);
356                $font = "strong" if ($days_last > 60);
357                $efont = "</$font>" if ($font);
358                $font = "<$font>" if ($font);
359
360                $result .= ";\n ${font}Modified $eng_last ago$efont";
361           }
362      }
363
364      $result .= ".";
365
366      return $result;
367 }
368
369
370 sub pkg_htmlizebugs {
371      my %param = validate_with(params => \@_,
372                                spec   => {bugs => {type => ARRAYREF,
373                                                   },
374                                           names => {type => ARRAYREF,
375                                                    },
376                                           title => {type => ARRAYREF,
377                                                    },
378                                           prior => {type => ARRAYREF,
379                                                    },
380                                           order => {type => ARRAYREF,
381                                                    },
382                                           ordering => {type => SCALAR,
383                                                       },
384                                           bugusertags => {type => HASHREF,
385                                                           default => {},
386                                                          },
387                                           bug_rev => {type => BOOLEAN,
388                                                       default => 0,
389                                                      },
390                                           bug_order => {type => SCALAR,
391                                                        },
392                                           repeatmerged => {type => BOOLEAN,
393                                                            default => 1,
394                                                           },
395                                           include => {type => ARRAYREF,
396                                                       default => [],
397                                                      },
398                                           exclude => {type => ARRAYREF,
399                                                       default => [],
400                                                      },
401                                           this     => {type => SCALAR,
402                                                        default => '',
403                                                       },
404                                           options  => {type => HASHREF,
405                                                        default => {},
406                                                       },
407                                           dist     => {type => SCALAR,
408                                                        optional => 1,
409                                                       },
410                                          }
411                               );
412      my @bugs = @{$param{bugs}};
413
414      my @status = ();
415      my %count;
416      my $header = '';
417      my $footer = "<h2 class=\"outstanding\">Summary</h2>\n";
418
419      my @dummy = ($gRemoveAge); #, @gSeverityList, @gSeverityDisplay);  #, $gHTMLExpireNote);
420
421      if (@bugs == 0) {
422           return "<HR><H2>No reports found!</H2></HR>\n";
423      }
424
425      if ( $param{bug_rev} ) {
426           @bugs = sort {$b<=>$a} @bugs;
427      }
428      else {
429           @bugs = sort {$a<=>$b} @bugs;
430      }
431      my %seenmerged;
432
433      my %common = (
434                    'show_list_header' => 1,
435                    'show_list_footer' => 1,
436                   );
437
438      my %section = ();
439      # Make the include/exclude map
440      my %include;
441      my %exclude;
442      for my $include (make_list($param{include})) {
443           next unless defined $include;
444           my ($key,$value) = split /\s*:\s*/,$include,2;
445           unless (defined $value) {
446                $key = 'tags';
447                $value = $include;
448           }
449           push @{$include{$key}}, split /\s*,\s*/, $value;
450      }
451      for my $exclude (make_list($param{exclude})) {
452           next unless defined $exclude;
453           my ($key,$value) = split /\s*:\s*/,$exclude,2;
454           unless (defined $value) {
455                $key = 'tags';
456                $value = $exclude;
457           }
458           push @{$exclude{$key}}, split /\s*,\s*/, $value;
459      }
460
461      foreach my $bug (@bugs) {
462           my %status = %{get_bug_status(bug=>$bug,
463                                         (exists $param{dist}?(dist => $param{dist}):()),
464                                         bugusertags => $param{bugusertags},
465                                         (exists $param{version}?(version => $param{version}):()),
466                                         (exists $param{arch}?(arch => $param{arch}):(arch => $config{default_architectures})),
467                                        )};
468           next unless %status;
469           next if bug_filter(bug => $bug,
470                              status => \%status,
471                              repeat_merged => $param{repeatmerged},
472                              seen_merged => \%seenmerged,
473                              (keys %include ? (include => \%include):()),
474                              (keys %exclude ? (exclude => \%exclude):()),
475                             );
476
477           my $html = "<li>"; #<a href=\"%s\">#%d: %s</a>\n<br>",
478                #bug_url($bug), $bug, html_escape($status{subject});
479           $html .= short_bug_status_html(status  => \%status,
480                                          options => $param{options},
481                                         ) . "\n";
482           push @status, [ $bug, \%status, $html ];
483      }
484      if ($param{bug_order} eq 'age') {
485           # MWHAHAHAHA
486           @status = sort {$a->[1]{log_modified} <=> $b->[1]{log_modified}} @status;
487      }
488      elsif ($param{bug_order} eq 'agerev') {
489           @status = sort {$b->[1]{log_modified} <=> $a->[1]{log_modified}} @status;
490      }
491      for my $entry (@status) {
492           my $key = "";
493           for my $i (0..$#{$param{prior}}) {
494                my $v = get_bug_order_index($param{prior}[$i], $entry->[1]);
495                $count{"g_${i}_${v}"}++;
496                $key .= "_$v";
497           }
498           $section{$key} .= $entry->[2];
499           $count{"_$key"}++;
500      }
501
502      my $result = "";
503      if ($param{ordering} eq "raw") {
504           $result .= "<UL class=\"bugs\">\n" . join("", map( { $_->[ 2 ] } @status ) ) . "</UL>\n";
505      }
506      else {
507           $header .= "<div class=\"msgreceived\">\n<ul>\n";
508           my @keys_in_order = ("");
509           for my $o (@{$param{order}}) {
510                push @keys_in_order, "X";
511                while ((my $k = shift @keys_in_order) ne "X") {
512                     for my $k2 (@{$o}) {
513                          $k2+=0;
514                          push @keys_in_order, "${k}_${k2}";
515                     }
516                }
517           }
518           for my $order (@keys_in_order) {
519                next unless defined $section{$order};
520                my @ttl = split /_/, $order;
521                shift @ttl;
522                my $title = $param{title}[0]->[$ttl[0]] . " bugs";
523                if ($#ttl > 0) {
524                     $title .= " -- ";
525                     $title .= join("; ", grep {($_ || "") ne ""}
526                                    map { $param{title}[$_]->[$ttl[$_]] } 1..$#ttl);
527                }
528                $title = html_escape($title);
529
530                my $count = $count{"_$order"};
531                my $bugs = $count == 1 ? "bug" : "bugs";
532
533                $header .= "<li><a href=\"#$order\">$title</a> ($count $bugs)</li>\n";
534                if ($common{show_list_header}) {
535                     my $count = $count{"_$order"};
536                     my $bugs = $count == 1 ? "bug" : "bugs";
537                     $result .= "<H2 CLASS=\"outstanding\"><a name=\"$order\"></a>$title ($count $bugs)</H2>\n";
538                }
539                else {
540                     $result .= "<H2 CLASS=\"outstanding\">$title</H2>\n";
541                }
542                $result .= "<div class=\"msgreceived\">\n<UL class=\"bugs\">\n";
543                $result .= "\n\n\n\n";
544                $result .= $section{$order};
545                $result .= "\n\n\n\n";
546                $result .= "</UL>\n</div>\n";
547           } 
548           $header .= "</ul></div>\n";
549
550           $footer .= "<div class=\"msgreceived\">\n<ul>\n";
551           for my $i (0..$#{$param{prior}}) {
552                my $local_result = '';
553                foreach my $key ( @{$param{order}[$i]} ) {
554                     my $count = $count{"g_${i}_$key"};
555                     next if !$count or !$param{title}[$i]->[$key];
556                     $local_result .= "<li>$count $param{title}[$i]->[$key]</li>\n";
557                }
558                if ( $local_result ) {
559                     $footer .= "<li>$param{names}[$i]<ul>\n$local_result</ul></li>\n";
560                }
561           }
562           $footer .= "</ul>\n</div>\n";
563      }
564
565      $result = $header . $result if ( $common{show_list_header} );
566      $result .= $footer if ( $common{show_list_footer} );
567      return $result;
568 }
569
570 sub pkg_javascript {
571      return fill_in_template(template=>'cgi/pkgreport_javascript',
572                             );
573 }
574
575 sub pkg_htmlselectyesno {
576      my ($name, $n, $y, $default) = @_;
577      return sprintf('<select name="%s"><option value=no%s>%s</option><option value=yes%s>%s</option></select>', $name, ($default ? "" : " selected"), $n, ($default ? " selected" : ""), $y);
578 }
579
580 sub pkg_htmlselectsuite {
581      my $id = sprintf "b_%d_%d_%d", $_[0], $_[1], $_[2];
582      my @suites = ("stable", "testing", "unstable", "experimental");
583      my %suiteaka = ("stable", "etch", "testing", "lenny", "unstable", "sid");
584      my $defaultsuite = "unstable";
585
586      my $result = sprintf '<select name=dist id="%s">', $id;
587      for my $s (@suites) {
588           $result .= sprintf '<option value="%s"%s>%s%s</option>',
589                $s, ($defaultsuite eq $s ? " selected" : ""),
590                     $s, (defined $suiteaka{$s} ? " (" . $suiteaka{$s} . ")" : "");
591      }
592      $result .= '</select>';
593      return $result;
594 }
595
596 sub pkg_htmlselectarch {
597      my $id = sprintf "b_%d_%d_%d", $_[0], $_[1], $_[2];
598      my @arches = qw(alpha amd64 arm hppa i386 ia64 m68k mips mipsel powerpc s390 sparc);
599
600      my $result = sprintf '<select name=arch id="%s">', $id;
601      $result .= '<option value="any">any architecture</option>';
602      for my $a (@arches) {
603           $result .= sprintf '<option value="%s">%s</option>', $a, $a;
604      }
605      $result .= '</select>';
606      return $result;
607 }
608
609 sub myurl {
610      my %param = @_;
611      return html_escape(pkg_url(map {exists $param{$_}?($_,$param{$_}):()}
612                                 qw(archive repeatmerged mindays maxdays),
613                                 qw(version dist arch package src tag maint submitter)
614                                )
615                        );
616 }
617
618 sub make_order_list {
619      my $vfull = shift;
620      my @x = ();
621
622      if ($vfull =~ m/^([^:]+):(.*)$/) {
623           my $v = $1;
624           for my $vv (split /,/, $2) {
625                push @x, "$v=$vv";
626           }
627      }
628      else {
629           for my $v (split /,/, $vfull) {
630                next unless $v =~ m/.=./;
631                push @x, $v;
632           }
633      }
634      push @x, "";               # catch all
635      return @x;
636 }
637
638 sub get_bug_order_index {
639      my $order = shift;
640      my $status = shift;
641      my $pos = -1;
642
643      my %tags = ();
644      %tags = map { $_, 1 } split / /, $status->{"tags"}
645           if defined $status->{"tags"};
646
647      for my $el (@${order}) {
648           $pos++;
649           my $match = 1;
650           for my $item (split /[+]/, $el) {
651                my ($f, $v) = split /=/, $item, 2;
652                next unless (defined $f and defined $v);
653                my $isokay = 0;
654                $isokay = 1 if (defined $status->{$f} and $v eq $status->{$f});
655                $isokay = 1 if ($f eq "tag" && defined $tags{$v});
656                unless ($isokay) {
657                     $match = 0;
658                     last;
659                }
660           }
661           if ($match) {
662                return $pos;
663                last;
664           }
665      }
666      return $pos + 1;
667 }
668
669 sub buglinklist {
670      my ($prefix, $infix, @els) = @_;
671      return '' if not @els;
672      return $prefix . bug_linklist($infix,'submitter',@els);
673 }
674
675
676 # sets: my @names; my @prior; my @title; my @order;
677
678 sub determine_ordering {
679      my %param = validate_with(params => \@_,
680                               spec => {cats => {type => HASHREF,
681                                                },
682                                        param => {type => HASHREF,
683                                                 },
684                                        ordering => {type => SCALARREF,
685                                                    },
686                                        names    => {type => ARRAYREF,
687                                                    },
688                                        pend_rev => {type => BOOLEAN,
689                                                     default => 0,
690                                                    },
691                                        sev_rev  => {type => BOOLEAN,
692                                                     default => 0,
693                                                    },
694                                        prior    => {type => ARRAYREF,
695                                                    },
696                                        title    => {type => ARRAYREF,
697                                                    },
698                                        order    => {type => ARRAYREF,
699                                                    },
700                                       },
701                              );
702      $param{cats}{status}[0]{ord} = [ reverse @{$param{cats}{status}[0]{ord}} ]
703           if ($param{pend_rev});
704      $param{cats}{severity}[0]{ord} = [ reverse @{$param{cats}{severity}[0]{ord}} ]
705           if ($param{sev_rev});
706
707      my $i;
708      if (defined $param{param}{"pri0"}) {
709           my @c = ();
710           $i = 0;
711           while (defined $param{param}{"pri$i"}) {
712                my $h = {};
713
714                my ($pri) = make_list($param{param}{"pri$i"});
715                if ($pri =~ m/^([^:]*):(.*)$/) {
716                     $h->{"nam"} = $1; # overridden later if necesary
717                     $h->{"pri"} = [ map { "$1=$_" } (split /,/, $2) ];
718                }
719                else {
720                     $h->{"pri"} = [ split /,/, $pri ];
721                }
722
723                ($h->{"nam"}) = make_list($param{param}{"nam$i"})
724                     if (defined $param{param}{"nam$i"});
725                $h->{"ord"} = [ map {split /\s*,\s*/} make_list($param{param}{"ord$i"}) ]
726                     if (defined $param{param}{"ord$i"});
727                $h->{"ttl"} = [ map {split /\s*,\s*/} make_list($param{param}{"ttl$i"}) ]
728                     if (defined $param{param}{"ttl$i"});
729
730                push @c, $h;
731                $i++;
732           }
733           $param{cats}{"_"} = [@c];
734           ${$param{ordering}} = "_";
735      }
736
737      ${$param{ordering}} = "normal" unless defined $param{cats}{${$param{ordering}}};
738
739      sub get_ordering {
740           my @res;
741           my $cats = shift;
742           my $o = shift;
743           for my $c (@{$cats->{$o}}) {
744                if (ref($c) eq "HASH") {
745                     push @res, $c;
746                }
747                else {
748                     push @res, get_ordering($cats, $c);
749                }
750           }
751           return @res;
752      }
753      my @cats = get_ordering($param{cats}, ${$param{ordering}});
754
755      sub toenglish {
756           my $expr = shift;
757           $expr =~ s/[+]/ and /g;
758           $expr =~ s/[a-z]+=//g;
759           return $expr;
760      }
761  
762      $i = 0;
763      for my $c (@cats) {
764           $i++;
765           push @{$param{prior}}, $c->{"pri"};
766           push @{$param{names}}, ($c->{"nam"} || "Bug attribute #" . $i);
767           if (defined $c->{"ord"}) {
768                push @{$param{order}}, $c->{"ord"};
769           }
770           else {
771                push @{$param{order}}, [ 0..$#{$param{prior}[-1]} ];
772           }
773           my @t = @{ $c->{"ttl"} } if defined $c->{ttl};
774           if (@t < $#{$param{prior}[-1]}) {
775                push @t, map { toenglish($param{prior}[-1][$_]) } @t..($#{$param{prior}[-1]});
776           }
777           push @t, $c->{"def"} || "";
778           push @{$param{title}}, [@t];
779      }
780 }
781
782
783
784
785 1;
786
787
788 __END__
789
790
791
792
793
794