1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
14 # The Original Code is the Bugzilla Bug Tracking System.
16 # The Initial Developer of the Original Code is Netscape Communications
17 # Corporation. Portions created by Netscape are Copyright (C) 1998
18 # Netscape Communications Corporation. All Rights Reserved.
20 # Contributor(s): Terry Weissman <terry@mozilla.org>
21 # Dave Miller <justdave@syndicomm.com>
22 # Joe Robins <jmrobins@tgix.com>
23 # Gervase Markham <gerv@gerv.net>
24 # Shane H. W. Travis <travis@sedsystems.ca>
26 ##############################################################################
30 # Displays bug entry form. Bug fields are specified through popup menus,
31 # drop-down lists, or text fields. Default for these values can be
32 # passed in as parameters to the cgi.
34 ##############################################################################
41 use Bugzilla
::Constants
;
47 use Bugzilla
::Product
;
48 use Bugzilla
::Classification
;
49 use Bugzilla
::Keyword
;
54 my $user = Bugzilla
->login(LOGIN_REQUIRED
);
59 my $cgi = Bugzilla
->cgi;
60 my $dbh = Bugzilla
->dbh;
61 my $template = Bugzilla
->template;
64 # All pages point to the same part of the documentation.
65 $vars->{'doc_section'} = 'bugreports.html';
67 my $product_name = trim
($cgi->param('product') || '');
68 # Will contain the product object the bug is created in.
71 if ($product_name eq '') {
72 # If the user cannot enter bugs in any product, stop here.
73 my @enterable_products = @
{$user->get_enterable_products};
74 ThrowUserError
('no_products') unless scalar(@enterable_products);
76 my $classification = Bugzilla
->params->{'useclassification'} ?
77 scalar($cgi->param('classification')) : '__all';
79 # Unless a real classification name is given, we sort products
83 unless ($classification && $classification ne '__all') {
84 if (Bugzilla
->params->{'useclassification'}) {
86 # Get all classifications with at least one enterable product.
87 foreach my $product (@enterable_products) {
88 $class->{$product->classification_id}->{'object'} ||=
89 new Bugzilla
::Classification
($product->classification_id);
90 # Nice way to group products per classification, without querying
92 push(@
{$class->{$product->classification_id}->{'products'}}, $product);
94 @classifications = sort {$a->{'object'}->sortkey <=> $b->{'object'}->sortkey
95 || lc($a->{'object'}->name) cmp lc($b->{'object'}->name)}
99 @classifications = ({object
=> undef, products
=> \
@enterable_products});
103 unless ($classification) {
104 # We know there is at least one classification available,
105 # else we would have stopped earlier.
106 if (scalar(@classifications) > 1) {
107 # We only need classification objects.
108 $vars->{'classifications'} = [map {$_->{'object'}} @classifications];
110 $vars->{'target'} = "enter_bug.cgi";
111 $vars->{'format'} = $cgi->param('format');
112 $vars->{'cloned_bug_id'} = $cgi->param('cloned_bug_id');
114 print $cgi->header();
115 $template->process("global/choose-classification.html.tmpl", $vars)
116 || ThrowTemplateError
($template->error());
119 # If we come here, then there is only one classification available.
120 $classification = $classifications[0]->{'object'}->name;
123 # Keep only enterable products which are in the specified classification.
124 if ($classification ne "__all") {
125 my $class = new Bugzilla
::Classification
({'name' => $classification});
126 # If the classification doesn't exist, then there is no product in it.
129 = grep {$_->classification_id == $class->id} @enterable_products;
130 @classifications = ({object
=> $class, products
=> \
@enterable_products});
133 @enterable_products = ();
137 if (scalar(@enterable_products) == 0) {
138 ThrowUserError
('no_products');
140 elsif (scalar(@enterable_products) > 1) {
141 $vars->{'classifications'} = \
@classifications;
142 $vars->{'target'} = "enter_bug.cgi";
143 $vars->{'format'} = $cgi->param('format');
144 $vars->{'cloned_bug_id'} = $cgi->param('cloned_bug_id');
146 print $cgi->header();
147 $template->process("global/choose-product.html.tmpl", $vars)
148 || ThrowTemplateError
($template->error());
151 # Only one product exists.
152 $product = $enterable_products[0];
156 # Do not use Bugzilla::Product::check_product() here, else the user
157 # could know whether the product doesn't exist or is not accessible.
158 $product = new Bugzilla
::Product
({'name' => $product_name});
161 # We need to check and make sure that the user has permission
162 # to enter a bug against this product.
163 $user->can_enter_product($product ?
$product->name : $product_name, THROW_ERROR
);
165 ##############################################################################
167 ##############################################################################
169 my ($name, $default) = (@_);
170 return Bugzilla
->cgi->param($name) || $default || "";
173 # Takes the name of a field and a list of possible values for that
174 # field. Returns the first value in the list that is actually a
175 # valid value for that field.
176 # The field should be named after its DB table.
177 # Returns undef if none of the platforms match.
178 sub pick_valid_field_value
(@
) {
179 my ($field, @values) = @_;
180 my $dbh = Bugzilla
->dbh;
182 foreach my $value (@values) {
183 return $value if $dbh->selectrow_array(
184 "SELECT 1 FROM $field WHERE value = ?", undef, $value);
190 return formvalue
("rep_platform") if formvalue
("rep_platform");
194 if (Bugzilla
->params->{'defaultplatform'}) {
195 @platform = Bugzilla
->params->{'defaultplatform'};
197 # If @platform is a list, this function will return the first
198 # item in the list that is a valid platform choice. If
199 # no choice is valid, we return "Other".
200 for ($ENV{'HTTP_USER_AGENT'}) {
202 /\(.*PowerPC.*\)/i && do {@platform = "Macintosh"; last;};
203 /\(.*PPC.*\)/ && do {@platform = "Macintosh"; last;};
204 /\(.*AIX.*\)/ && do {@platform = "Macintosh"; last;};
206 /\(.*Intel.*\)/ && do {@platform = "PC"; last;};
207 /\(.*[ix0-9]86.*\)/ && do {@platform = "PC"; last;};
208 #Versions of Windows that only run on Intel x86
209 /\(.*Win(?:dows |)[39M].*\)/ && do {@platform = "PC"; last};
210 /\(.*Win(?:dows |)16.*\)/ && do {@platform = "PC"; last;};
212 /\(.*sparc.*\)/ && do {@platform = "Sun"; last;};
213 /\(.*sun4.*\)/ && do {@platform = "Sun"; last;};
215 /\(.*AXP.*\)/i && do {@platform = "DEC"; last;};
216 /\(.*[ _]Alpha.\D/i && do {@platform = "DEC"; last;};
217 /\(.*[ _]Alpha\)/i && do {@platform = "DEC"; last;};
219 /\(.*IRIX.*\)/i && do {@platform = "SGI"; last;};
220 /\(.*MIPS.*\)/i && do {@platform = "SGI"; last;};
222 /\(.*68K.*\)/ && do {@platform = "Macintosh"; last;};
223 /\(.*680[x0]0.*\)/ && do {@platform = "Macintosh"; last;};
225 /\(.*9000.*\)/ && do {@platform = "HP"; last;};
227 # /\(.*ARM.*\) && do {$platform = "ARM";};
228 #Stereotypical and broken
229 /\(.*Macintosh.*\)/ && do {@platform = "Macintosh"; last;};
230 /\(.*Mac OS [89].*\)/ && do {@platform = "Macintosh"; last;};
231 /\(Win.*\)/ && do {@platform = "PC"; last;};
232 /\(.*Win(?:dows[ -])NT.*\)/ && do {@platform = "PC"; last;};
233 /\(.*OSF.*\)/ && do {@platform = "DEC"; last;};
234 /\(.*HP-?UX.*\)/i && do {@platform = "HP"; last;};
235 /\(.*IRIX.*\)/i && do {@platform = "SGI"; last;};
236 /\(.*(SunOS|Solaris).*\)/ && do {@platform = "Sun"; last;};
237 #Braindead old browsers who didn't follow convention:
238 /Amiga/ && do {@platform = "Macintosh"; last;};
239 /WinMosaic/ && do {@platform = "PC"; last;};
243 return pick_valid_field_value
('rep_platform', @platform) || "Other";
247 if (formvalue
('op_sys') ne "") {
248 return formvalue
('op_sys');
253 if (Bugzilla
->params->{'defaultopsys'}) {
254 @os = Bugzilla
->params->{'defaultopsys'};
256 # This function will return the first
257 # item in @os that is a valid platform choice. If
258 # no choice is valid, we return "Other".
259 for ($ENV{'HTTP_USER_AGENT'}) {
260 /\(.*IRIX.*\)/ && do {push @os, "IRIX"; };
261 /\(.*OSF.*\)/ && do {push @os, "OSF/1";};
262 /\(.*Linux.*\)/ && do {push @os, "Linux";};
263 /\(.*Solaris.*\)/ && do {push @os, "Solaris";};
264 /\(.*SunOS.*\)/ && do {
265 /\(.*SunOS 5.11.*\)/ && do {push @os, ("OpenSolaris", "Opensolaris", "Solaris 11");};
266 /\(.*SunOS 5.10.*\)/ && do {push @os, "Solaris 10";};
267 /\(.*SunOS 5.9.*\)/ && do {push @os, "Solaris 9";};
268 /\(.*SunOS 5.8.*\)/ && do {push @os, "Solaris 8";};
269 /\(.*SunOS 5.7.*\)/ && do {push @os, "Solaris 7";};
270 /\(.*SunOS 5.6.*\)/ && do {push @os, "Solaris 6";};
271 /\(.*SunOS 5.5.*\)/ && do {push @os, "Solaris 5";};
272 /\(.*SunOS 5.*\)/ && do {push @os, "Solaris";};
273 /\(.*SunOS.*sun4u.*\)/ && do {push @os, "Solaris";};
274 /\(.*SunOS.*i86pc.*\)/ && do {push @os, "Solaris";};
275 /\(.*SunOS.*\)/ && do {push @os, "SunOS";};
277 /\(.*HP-?UX.*\)/ && do {push @os, "HP-UX";};
278 /\(.*BSD.*\)/ && do {
279 /\(.*BSD\/(?
:OS
|386).*\
)/ && do {push @os, "BSDI";};
280 /\(.*FreeBSD.*\)/ && do {push @os, "FreeBSD";};
281 /\(.*OpenBSD.*\)/ && do {push @os, "OpenBSD";};
282 /\(.*NetBSD.*\)/ && do {push @os, "NetBSD";};
284 /\(.*BeOS.*\)/ && do {push @os, "BeOS";};
285 /\(.*AIX.*\)/ && do {push @os, "AIX";};
286 /\(.*OS\/2.*\
)/ && do {push @os, "OS/2";};
287 /\(.*QNX.*\)/ && do {push @os, "Neutrino
";};
288 /\(.*VMS.*\)/ && do {push @os, "OpenVMS
";};
289 /\(.*Win.*\)/ && do {
290 /\(.*Windows XP.*\)/ && do {push @os, "Windows XP
";};
291 /\(.*Windows NT 6\.0.*\)/ && do {push @os, "Windows Vista
";};
292 /\(.*Windows NT 5\.2.*\)/ && do {push @os, "Windows Server
2003";};
293 /\(.*Windows NT 5\.1.*\)/ && do {push @os, "Windows XP
";};
294 /\(.*Windows 2000.*\)/ && do {push @os, "Windows
2000";};
295 /\(.*Windows NT 5.*\)/ && do {push @os, "Windows
2000";};
296 /\(.*Win.*9[8x].*4\.9.*\)/ && do {push @os, "Windows ME
";};
297 /\(.*Win(?:dows |)M[Ee].*\)/ && do {push @os, "Windows ME
";};
298 /\(.*Win(?:dows |)98.*\)/ && do {push @os, "Windows
98";};
299 /\(.*Win(?:dows |)95.*\)/ && do {push @os, "Windows
95";};
300 /\(.*Win(?:dows |)16.*\)/ && do {push @os, "Windows
3.1";};
301 /\(.*Win(?:dows[ -]|)NT.*\)/ && do {push @os, "Windows NT
";};
302 /\(.*Windows.*NT.*\)/ && do {push @os, "Windows NT
";};
304 /\(.*Mac OS X.*\)/ && do {
305 /\(.*Intel.*Mac OS X 10.5.*\)/ && do {push @os, "Mac OS X
10.5";};
306 /\(.*Intel.*Mac OS X.*\)/ && do {push @os, "Mac OS X
10.4";};
307 /\(.*Mac OS X.*\)/ && do {push @os, ("Mac OS X
10.3", "Mac OS X
10.0", "Mac OS X
");};
309 /\(.*32bit.*\)/ && do {push @os, "Windows
95";};
310 /\(.*16bit.*\)/ && do {push @os, "Windows
3.1";};
311 /\(.*Mac OS \d.*\)/ && do {
312 /\(.*Mac OS 9.*\)/ && do {push @os, ("Mac System
9.x
", "Mac System
9.0");};
313 /\(.*Mac OS 8\.6.*\)/ && do {push @os, ("Mac System
8.6", "Mac System
8.5");};
314 /\(.*Mac OS 8\.5.*\)/ && do {push @os, "Mac System
8.5";};
315 /\(.*Mac OS 8\.1.*\)/ && do {push @os, ("Mac System
8.1", "Mac System
8.0");};
316 /\(.*Mac OS 8\.0.*\)/ && do {push @os, "Mac System
8.0";};
317 /\(.*Mac OS 8[^.].*\)/ && do {push @os, "Mac System
8.0";};
318 /\(.*Mac OS 8.*\)/ && do {push @os, "Mac System
8.6";};
320 /\(.*Darwin.*\)/ && do {push @os, ("Mac OS X
10.0", "Mac OS X
");};
322 /\(.*Mac.*\)/ && do {
323 /\(.*Mac.*PowerPC.*\)/ && do {push @os, "Mac System
9.x
";};
324 /\(.*Mac.*PPC.*\)/ && do {push @os, "Mac System
9.x
";};
325 /\(.*Mac.*68k.*\)/ && do {push @os, "Mac System
8.0";};
328 /Amiga/i && do {push @os, "Other
";};
329 /WinMosaic/ && do {push @os, "Windows
95";};
330 /\(.*PowerPC.*\)/ && do {push @os, "Mac System
9.x
";};
331 /\(.*PPC.*\)/ && do {push @os, "Mac System
9.x
";};
332 /\(.*68K.*\)/ && do {push @os, "Mac System
8.0";};
336 push(@os, "Windows
") if grep(/^Windows /, @os);
337 push(@os, "Mac OS
") if grep(/^Mac /, @os);
339 return pick_valid_field_value('op_sys', @os) || "Other
";
341 ##############################################################################
343 ##############################################################################
345 my $has_editbugs = $user->in_group('editbugs', $product->id);
346 my $has_canconfirm = $user->in_group('canconfirm', $product->id);
348 # If a user is trying to clone a bug
349 # Check that the user has authorization to view the parent bug
350 # Create an instance of Bug that holds the info from the parent
351 $cloned_bug_id = $cgi->param('cloned_bug_id');
353 if ($cloned_bug_id) {
354 ValidateBugID($cloned_bug_id);
355 $cloned_bug = new Bugzilla::Bug($cloned_bug_id);
358 if (scalar(@{$product->components}) == 1) {
359 # Only one component; just pick it.
360 $cgi->param('component', $product->components->[0]->name);
365 $vars->{'product'} = $product;
367 $vars->{'priority'} = get_legal_field_values('priority');
368 $vars->{'bug_severity'} = get_legal_field_values('bug_severity');
369 $vars->{'rep_platform'} = get_legal_field_values('rep_platform');
370 $vars->{'op_sys'} = get_legal_field_values('op_sys');
372 $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
374 $vars->{'assigned_to'} = formvalue('assigned_to');
375 $vars->{'assigned_to_disabled'} = !$has_editbugs;
376 $vars->{'cc_disabled'} = 0;
378 $vars->{'qa_contact'} = formvalue('qa_contact');
379 $vars->{'qa_contact_disabled'} = !$has_editbugs;
381 $vars->{'cloned_bug_id'} = $cloned_bug_id;
383 $vars->{'token'} = issue_session_token('createbug:');
386 my @enter_bug_fields = grep { $_->enter_bug } Bugzilla->active_custom_fields;
387 foreach my $field (@enter_bug_fields) {
388 $vars->{$field->name} = formvalue($field->name);
391 if ($cloned_bug_id) {
393 $default{'component_'} = $cloned_bug->component;
394 $default{'priority'} = $cloned_bug->priority;
395 $default{'bug_severity'} = $cloned_bug->bug_severity;
396 $default{'rep_platform'} = $cloned_bug->rep_platform;
397 $default{'op_sys'} = $cloned_bug->op_sys;
399 $vars->{'short_desc'} = $cloned_bug->short_desc;
400 $vars->{'bug_file_loc'} = $cloned_bug->bug_file_loc;
401 $vars->{'keywords'} = $cloned_bug->keywords;
402 $vars->{'dependson'} = $cloned_bug_id;
403 $vars->{'blocked'} = "";
404 $vars->{'deadline'} = $cloned_bug->deadline;
406 if (defined $cloned_bug->cc) {
407 $vars->{'cc'} = join (", ", @{$cloned_bug->cc});
409 $vars->{'cc'} = formvalue('cc');
412 foreach my $field (@enter_bug_fields) {
413 my $field_name = $field->name;
414 $vars->{$field_name} = $cloned_bug->$field_name;
417 # We need to ensure that we respect the 'insider' status of
418 # the first comment, if it has one. Either way, make a note
419 # that this bug was cloned from another bug.
420 # We cannot use $cloned_bug->longdescs because this method
421 # depends on the "comment_sort_order
" user pref, and we
422 # really want the first comment of the bug.
423 my $bug_desc = Bugzilla::Bug::GetComments($cloned_bug_id, 'oldest_to_newest');
424 my $isprivate = $bug_desc->[0]->{'isprivate'};
426 $vars->{'comment'} = "";
427 $vars->{'commentprivacy'} = 0;
429 if (!$isprivate || Bugzilla->user->is_insider) {
430 $vars->{'comment'} = $bug_desc->[0]->{'body'};
431 $vars->{'commentprivacy'} = $isprivate;
434 } # end of cloned bug entry form
438 $default{'component_'} = formvalue('component');
439 $default{'priority'} = formvalue('priority', Bugzilla->params->{'defaultpriority'});
440 $default{'bug_severity'} = formvalue('bug_severity', Bugzilla->params->{'defaultseverity'});
441 $default{'rep_platform'} = pickplatform();
442 $default{'op_sys'} = pickos();
444 $vars->{'short_desc'} = formvalue('short_desc');
445 $vars->{'bug_file_loc'} = formvalue('bug_file_loc', "http
://");
446 $vars->{'keywords'} = formvalue('keywords');
447 $vars->{'dependson'} = formvalue('dependson');
448 $vars->{'blocked'} = formvalue('blocked');
449 $vars->{'deadline'} = formvalue('deadline');
451 $vars->{'cc'} = join(', ', $cgi->param('cc'));
453 $vars->{'comment'} = formvalue('comment');
454 $vars->{'commentprivacy'} = formvalue('commentprivacy');
456 } # end of normal/bookmarked entry form
459 # IF this is a cloned bug,
460 # AND the clone's product is the same as the parent's
461 # THEN use the version from the parent bug
462 # ELSE IF a version is supplied in the URL
464 # ELSE IF there is a version in the cookie
465 # THEN use it (Posting a bug sets a cookie for the current version.)
467 # The default version is the last one in the list (which, it is
468 # hoped, will be the most recent one).
470 # Eventually maybe each product should have a "current version
"
472 $vars->{'version'} = [map($_->name, @{$product->versions})];
474 if ( ($cloned_bug_id) &&
475 ($product->name eq $cloned_bug->product ) ) {
476 $default{'version'} = $cloned_bug->version;
477 } elsif (formvalue('version')) {
478 $default{'version'} = formvalue('version');
479 } elsif (defined $cgi->cookie("VERSION
-" . $product->name) &&
480 lsearch($vars->{'version'}, $cgi->cookie("VERSION
-" . $product->name)) != -1) {
481 $default{'version'} = $cgi->cookie("VERSION
-" . $product->name);
483 $default{'version'} = $vars->{'version'}->[$#{$vars->{'version'}}];
486 # Get list of milestones.
487 if ( Bugzilla->params->{'usetargetmilestone'} ) {
488 $vars->{'target_milestone'} = [map($_->name, @{$product->milestones})];
489 if (formvalue('target_milestone')) {
490 $default{'target_milestone'} = formvalue('target_milestone');
492 $default{'target_milestone'} = $product->default_milestone;
496 # Construct the list of allowable statuses.
497 my $initial_statuses = Bugzilla::Status->can_change_to();
498 # Exclude closed states from the UI, even if the workflow allows them.
499 # The back-end code will still accept them, though.
500 @$initial_statuses = grep { $_->is_open } @$initial_statuses;
502 my @status = map { $_->name } @$initial_statuses;
503 # UNCONFIRMED is illegal if votes_to_confirm = 0.
504 @status = grep {$_ ne 'UNCONFIRMED'} @status unless $product->votes_to_confirm;
505 scalar(@status) || ThrowUserError('no_initial_bug_status');
507 # If the user has no privs...
508 unless ($has_editbugs || $has_canconfirm) {
509 # ... use UNCONFIRMED if available, else use the first status of the list.
510 my $bug_status = (grep {$_ eq 'UNCONFIRMED'} @status) ? 'UNCONFIRMED' : $status[0];
511 @status = ($bug_status);
514 $vars->{'bug_status'} = \@status;
516 # Get the default from a template value if it is legitimate.
517 # Otherwise, and only if the user has privs, set the default
518 # to the first confirmed bug status on the list, if available.
520 if (formvalue('bug_status') && (lsearch(\@status, formvalue('bug_status')) >= 0)) {
521 $default{'bug_status'} = formvalue('bug_status');
522 } elsif (scalar @status == 1) {
523 $default{'bug_status'} = $status[0];
526 $default{'bug_status'} = ($status[0] ne 'UNCONFIRMED') ? $status[0] : $status[1];
529 my $grouplist = $dbh->selectall_arrayref(
530 q{SELECT DISTINCT groups.id, groups.name, groups.description,
531 membercontrol, othercontrol
533 LEFT JOIN group_control_map
534 ON group_id = id AND product_id = ?
535 WHERE isbuggroup != 0 AND isactive != 0
536 ORDER BY description}, undef, $product->id);
540 foreach my $row (@
$grouplist) {
541 my ($id, $groupname, $description, $membercontrol, $othercontrol) = @
$row;
542 # Only include groups if the entering user will have an option.
543 next if ((!$membercontrol)
544 || ($membercontrol == CONTROLMAPNA
)
545 || ($membercontrol == CONTROLMAPMANDATORY
)
546 || (($othercontrol != CONTROLMAPSHOWN
)
547 && ($othercontrol != CONTROLMAPDEFAULT
)
548 && (!Bugzilla
->user->in_group($groupname)))
552 # If this is a cloned bug,
553 # AND the product for this bug is the same as for the original
554 # THEN set a group's checkbox if the original also had it on
555 # ELSE IF this is a bookmarked template
556 # THEN set a group's checkbox if was set in the bookmark
558 # set a groups's checkbox based on the group control map
560 if ( ($cloned_bug_id) &&
561 ($product->name eq $cloned_bug->product ) ) {
562 foreach my $i (0..(@
{$cloned_bug->groups} - 1) ) {
563 if ($cloned_bug->groups->[$i]->{'bit'} == $id) {
564 $check = $cloned_bug->groups->[$i]->{'ison'};
568 elsif(formvalue
("maketemplate") ne "") {
569 $check = formvalue
("bit-$id", 0);
572 # Checkbox is checked by default if $control is a default state.
573 $check = (($membercontrol == CONTROLMAPDEFAULT
)
574 || (($othercontrol == CONTROLMAPDEFAULT
)
575 && (!Bugzilla
->user->in_group($groupname))));
581 'checked' => $check ,
582 'description' => $description
585 push @groups, $group;
588 $vars->{'group'} = \
@groups;
590 Bugzilla
::Hook
::process
("enter_bug-entrydefaultvars", { vars
=> $vars });
592 $vars->{'default'} = \
%default;
594 my $format = $template->get_format("bug/create/create",
595 scalar $cgi->param('format'),
596 scalar $cgi->param('ctype'));
598 print $cgi->header($format->{'ctype'});
599 $template->process($format->{'template'}, $vars)
600 || ThrowTemplateError
($template->error());