From 8ec8da0491ad89604700b3e29a227966f6d84ba1 Mon Sep 17 00:00:00 2001 From: Perl Tidy Date: Wed, 5 Dec 2018 15:38:52 -0500 Subject: no bug - reformat all the code using the new perltidy rules --- importxml.pl | 2028 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 1032 insertions(+), 996 deletions(-) (limited to 'importxml.pl') diff --git a/importxml.pl b/importxml.pl index 23609f2b3..e815b638a 100755 --- a/importxml.pl +++ b/importxml.pl @@ -38,15 +38,19 @@ use warnings; ##################################################################### use File::Basename qw(dirname); + # MTAs may call this script from any directory, but it should always # run from this one so that it can find its modules. BEGIN { - require File::Basename; - my $dir = $0; $dir =~ /(.*)/; $dir = $1; # trick taint - chdir(File::Basename::dirname($dir)); + require File::Basename; + my $dir = $0; + $dir =~ /(.*)/; + $dir = $1; # trick taint + chdir(File::Basename::dirname($dir)); } use lib qw(. lib local/lib/perl5); + # Data dumber is used for debugging, I got tired of copying it back in # and then removing it. #use Data::Dumper; @@ -75,19 +79,19 @@ use Getopt::Long; use Pod::Usage; use XML::Twig; -my $debug = 0; -my $mail = ''; +my $debug = 0; +my $mail = ''; my $attach_path = ''; -my $help = 0; +my $help = 0; my ($default_product_name, $default_component_name); my $result = GetOptions( - "verbose|debug+" => \$debug, - "mail|sendmail!" => \$mail, - "attach_path=s" => \$attach_path, - "help|?" => \$help, - "product=s" => \$default_product_name, - "component=s" => \$default_component_name, + "verbose|debug+" => \$debug, + "mail|sendmail!" => \$mail, + "attach_path=s" => \$attach_path, + "help|?" => \$help, + "product=s" => \$default_product_name, + "component=s" => \$default_component_name, ); pod2usage(0) if $help; @@ -100,11 +104,11 @@ our @logs; our @attachments; our $bugtotal; my $xml; -my $dbh = Bugzilla->dbh; -my $params = Bugzilla->params; +my $dbh = Bugzilla->dbh; +my $params = Bugzilla->params; my ($timestamp) = $dbh->selectrow_array("SELECT NOW()"); -$default_product_name = '' if !defined $default_product_name; +$default_product_name = '' if !defined $default_product_name; $default_component_name = '' if !defined $default_component_name; ############################################################################### @@ -112,166 +116,168 @@ $default_component_name = '' if !defined $default_component_name; ############################################################################### sub MailMessage { - return unless ($mail); - my $subject = shift; - my $message = shift; - my @recipients = @_; - my $from = $params->{"mailfrom"}; - $from =~ s/@/\@/g; - - foreach my $to (@recipients){ - my $header = "To: $to\n"; - $header .= "From: Bugzilla <$from>\n"; - $header .= "Subject: $subject\n\n"; - my $sendmessage = $header . $message . "\n"; - MessageToMTA($sendmessage); - } + return unless ($mail); + my $subject = shift; + my $message = shift; + my @recipients = @_; + my $from = $params->{"mailfrom"}; + $from =~ s/@/\@/g; + + foreach my $to (@recipients) { + my $header = "To: $to\n"; + $header .= "From: Bugzilla <$from>\n"; + $header .= "Subject: $subject\n\n"; + my $sendmessage = $header . $message . "\n"; + MessageToMTA($sendmessage); + } } sub Debug { - return unless ($debug); - my ( $message, $level ) = (@_); - print STDERR "OK: $message \n" if ( $level == OK_LEVEL ); - print STDERR "ERR: $message \n" if ( $level == ERR_LEVEL ); - print STDERR "$message\n" - if ( ( $debug == $level ) && ( $level == DEBUG_LEVEL ) ); + return unless ($debug); + my ($message, $level) = (@_); + print STDERR "OK: $message \n" if ($level == OK_LEVEL); + print STDERR "ERR: $message \n" if ($level == ERR_LEVEL); + print STDERR "$message\n" if (($debug == $level) && ($level == DEBUG_LEVEL)); } sub Error { - my ( $reason, $errtype, $exporter ) = @_; - my $subject = "Bug import error: $reason"; - my $message = "Cannot import these bugs because $reason "; - $message .= "\n\nPlease re-open the original bug.\n" if ($errtype); - $message .= "For more info, contact " . $params->{"maintainer"} . ".\n"; - my @to = ( $params->{"maintainer"}, $exporter); - Debug( $message, ERR_LEVEL ); - MailMessage( $subject, $message, @to ); - exit; + my ($reason, $errtype, $exporter) = @_; + my $subject = "Bug import error: $reason"; + my $message = "Cannot import these bugs because $reason "; + $message .= "\n\nPlease re-open the original bug.\n" if ($errtype); + $message .= "For more info, contact " . $params->{"maintainer"} . ".\n"; + my @to = ($params->{"maintainer"}, $exporter); + Debug($message, ERR_LEVEL); + MailMessage($subject, $message, @to); + exit; } # This subroutine handles flags for process_bug. It is generic in that # it can handle both attachment flags and bug flags. sub flag_handler { - my ( - $name, $status, $setter_login, - $requestee_login, $exporterid, $bugid, - $componentid, $productid, $attachid - ) - = @_; - - my $type = ($attachid) ? "attachment" : "bug"; - my $err = ''; - my $setter = new Bugzilla::User({ name => $setter_login }); - my $requestee; - my $requestee_id; - - unless ($setter) { - $err = "Invalid setter $setter_login on $type flag $name\n"; - $err .= " Dropping flag $name\n"; - return $err; + my ( + $name, $status, $setter_login, + $requestee_login, $exporterid, $bugid, + $componentid, $productid, $attachid + ) = @_; + + my $type = ($attachid) ? "attachment" : "bug"; + my $err = ''; + my $setter = new Bugzilla::User({name => $setter_login}); + my $requestee; + my $requestee_id; + + unless ($setter) { + $err = "Invalid setter $setter_login on $type flag $name\n"; + $err .= " Dropping flag $name\n"; + return $err; + } + if (!$setter->can_see_bug($bugid)) { + $err .= "Setter is not a member of bug group\n"; + $err .= " Dropping flag $name\n"; + return $err; + } + my $setter_id = $setter->id; + if (defined($requestee_login)) { + $requestee = new Bugzilla::User({name => $requestee_login}); + if ($requestee) { + if (!$requestee->can_see_bug($bugid)) { + $err .= "Requestee is not a member of bug group\n"; + $err .= " Requesting from the wind\n"; + } + else { + $requestee_id = $requestee->id; + } } - if ( !$setter->can_see_bug($bugid) ) { - $err .= "Setter is not a member of bug group\n"; - $err .= " Dropping flag $name\n"; - return $err; + else { + $err = "Invalid requestee $requestee_login on $type flag $name\n"; + $err .= " Requesting from the wind.\n"; } - my $setter_id = $setter->id; - if ( defined($requestee_login) ) { - $requestee = new Bugzilla::User({ name => $requestee_login }); - if ( $requestee ) { - if ( !$requestee->can_see_bug($bugid) ) { - $err .= "Requestee is not a member of bug group\n"; - $err .= " Requesting from the wind\n"; - } - else{ - $requestee_id = $requestee->id; - } - } - else { - $err = "Invalid requestee $requestee_login on $type flag $name\n"; - $err .= " Requesting from the wind.\n"; - } + } + my $flag_types; + + # If this is an attachment flag we need to do some dirty work to look + # up the flagtype ID + if ($attachid) { + $flag_types = Bugzilla::FlagType::match({ + 'target_type' => 'attachment', + 'product_id' => $productid, + 'component_id' => $componentid + }); + } + else { + my $bug = new Bugzilla::Bug($bugid); + $flag_types = $bug->flag_types; + } + unless ($flag_types) { + $err = "No flag types defined for this bug\n"; + $err .= " Dropping flag $name\n"; + return $err; + } + + # We need to see if the imported flag is in the list of known flags + # It is possible for two flags on the same bug have the same name + # If this is the case, we will only match the first one. + my $ftype; + foreach my $f (@{$flag_types}) { + if ($f->name eq $name) { + $ftype = $f; + last; } - my $flag_types; - - # If this is an attachment flag we need to do some dirty work to look - # up the flagtype ID - if ($attachid) { - $flag_types = Bugzilla::FlagType::match( - { - 'target_type' => 'attachment', - 'product_id' => $productid, - 'component_id' => $componentid - } ); - } - else { - my $bug = new Bugzilla::Bug($bugid); - $flag_types = $bug->flag_types; + } + + if ($ftype) { # We found the flag in the list + my $grant_group = $ftype->grant_group; + if ( ($status eq '+' || $status eq '-') + && $grant_group + && !$setter->in_group_id($grant_group->id)) + { + $err = "Setter $setter_login on $type flag $name "; + $err .= "is not in the Grant Group\n"; + $err .= " Dropping flag $name\n"; + return $err; } - unless ($flag_types){ - $err = "No flag types defined for this bug\n"; - $err .= " Dropping flag $name\n"; - return $err; + my $request_group = $ftype->request_group; + if ( $request_group + && $status eq '?' + && !$setter->in_group_id($request_group->id)) + { + $err = "Setter $setter_login on $type flag $name "; + $err .= "is not in the Request Group\n"; + $err .= " Dropping flag $name\n"; + return $err; } - # We need to see if the imported flag is in the list of known flags - # It is possible for two flags on the same bug have the same name - # If this is the case, we will only match the first one. - my $ftype; - foreach my $f ( @{$flag_types} ) { - if ( $f->name eq $name) { - $ftype = $f; - last; - } + # Take the first flag_type that matches + unless ($ftype->is_active) { + $err = "Flag $name is not active in this database\n"; + $err .= " Dropping flag $name\n"; + return $err; } - if ($ftype) { # We found the flag in the list - my $grant_group = $ftype->grant_group; - if (( $status eq '+' || $status eq '-' ) - && $grant_group && !$setter->in_group_id($grant_group->id)) { - $err = "Setter $setter_login on $type flag $name "; - $err .= "is not in the Grant Group\n"; - $err .= " Dropping flag $name\n"; - return $err; - } - my $request_group = $ftype->request_group; - if ($request_group - && $status eq '?' && !$setter->in_group_id($request_group->id)) { - $err = "Setter $setter_login on $type flag $name "; - $err .= "is not in the Request Group\n"; - $err .= " Dropping flag $name\n"; - return $err; - } - - # Take the first flag_type that matches - unless ($ftype->is_active) { - $err = "Flag $name is not active in this database\n"; - $err .= " Dropping flag $name\n"; - return $err; - } - - $dbh->do("INSERT INTO flags + $dbh->do( + "INSERT INTO flags (type_id, status, bug_id, attach_id, creation_date, setter_id, requestee_id) VALUES (?, ?, ?, ?, ?, ?, ?)", undef, - ($ftype->id, $status, $bugid, $attachid, $timestamp, - $setter_id, $requestee_id)); - } - else { - $err = "Dropping unknown $type flag: $name\n"; - return $err; - } + ($ftype->id, $status, $bugid, $attachid, $timestamp, $setter_id, $requestee_id) + ); + } + else { + $err = "Dropping unknown $type flag: $name\n"; return $err; + } + return $err; } # Converts and returns the input data as an array. sub _to_array { - my $value = shift; + my $value = shift; - $value = [$value] if !ref($value); - return @$value; + $value = [$value] if !ref($value); + return @$value; } ############################################################################### @@ -289,25 +295,26 @@ sub _to_array { # bugs are being moved from # sub init() { - my ( $twig, $bugzilla ) = @_; - my $root = $twig->root; - my $maintainer = $root->{'att'}->{'maintainer'}; - my $exporter = $root->{'att'}->{'exporter'}; - my $urlbase = $root->{'att'}->{'urlbase'}; - my $xmlversion = $root->{'att'}->{'version'}; - - if ($xmlversion ne BUGZILLA_VERSION) { - my $log = "Possible version conflict!\n"; - $log .= " XML was exported from Bugzilla version $xmlversion\n"; - $log .= " But this installation uses "; - $log .= BUGZILLA_VERSION . "\n"; - Debug($log, OK_LEVEL); - push(@logs, $log); - } - Error( "no maintainer", "REOPEN", $exporter ) unless ($maintainer); - Error( "no exporter", "REOPEN", $exporter ) unless ($exporter); - Error( "invalid exporter: $exporter", "REOPEN", $exporter ) if ( !login_to_id($exporter) ); - Error( "no urlbase set", "REOPEN", $exporter ) unless ($urlbase); + my ($twig, $bugzilla) = @_; + my $root = $twig->root; + my $maintainer = $root->{'att'}->{'maintainer'}; + my $exporter = $root->{'att'}->{'exporter'}; + my $urlbase = $root->{'att'}->{'urlbase'}; + my $xmlversion = $root->{'att'}->{'version'}; + + if ($xmlversion ne BUGZILLA_VERSION) { + my $log = "Possible version conflict!\n"; + $log .= " XML was exported from Bugzilla version $xmlversion\n"; + $log .= " But this installation uses "; + $log .= BUGZILLA_VERSION . "\n"; + Debug($log, OK_LEVEL); + push(@logs, $log); + } + Error("no maintainer", "REOPEN", $exporter) unless ($maintainer); + Error("no exporter", "REOPEN", $exporter) unless ($exporter); + Error("invalid exporter: $exporter", "REOPEN", $exporter) + if (!login_to_id($exporter)); + Error("no urlbase set", "REOPEN", $exporter) unless ($urlbase); } @@ -326,69 +333,74 @@ sub init() { # The submitter_id gets filled in with $exporterid. sub process_attachment() { - my ( $twig, $attach ) = @_; - Debug( "Parsing attachments", DEBUG_LEVEL ); - my %attachment; - - $attachment{'date'} = - format_time( $attach->field('date'), "%Y-%m-%d %R" ) || $timestamp; - $attachment{'desc'} = $attach->field('desc'); - $attachment{'ctype'} = $attach->field('type') || "unknown/unknown"; - $attachment{'attachid'} = $attach->field('attachid'); - $attachment{'ispatch'} = $attach->{'att'}->{'ispatch'} || 0; - $attachment{'isobsolete'} = $attach->{'att'}->{'isobsolete'} || 0; - $attachment{'isprivate'} = $attach->{'att'}->{'isprivate'} || 0; - $attachment{'filename'} = $attach->field('filename') || "file"; - $attachment{'attacher'} = $attach->field('attacher'); - # Attachment data is not exported in versions 2.20 and older. - if (defined $attach->first_child('data') && - defined $attach->first_child('data')->{'att'}->{'encoding'}) { - my $encoding = $attach->first_child('data')->{'att'}->{'encoding'}; - if ($encoding =~ /base64/) { - # decode the base64 - my $data = $attach->field('data'); - my $output = decode_base64($data); - $attachment{'data'} = $output; - } - elsif ($encoding =~ /filename/) { - # read the attachment file - Error("attach_path is required", undef) unless ($attach_path); - - my $filename = $attach->field('data'); - # Remove any leading path data from the filename - $filename =~ s/(.*\/|.*\\)//gs; - - my $attach_filename = $attach_path . "/" . $filename; - open(ATTACH_FH, "<", $attach_filename) or - Error("cannot open $attach_filename", undef); - $attachment{'data'} = do { local $/; }; - close ATTACH_FH; - } - } - else { - $attachment{'data'} = $attach->field('data'); + my ($twig, $attach) = @_; + Debug("Parsing attachments", DEBUG_LEVEL); + my %attachment; + + $attachment{'date'} + = format_time($attach->field('date'), "%Y-%m-%d %R") || $timestamp; + $attachment{'desc'} = $attach->field('desc'); + $attachment{'ctype'} = $attach->field('type') || "unknown/unknown"; + $attachment{'attachid'} = $attach->field('attachid'); + $attachment{'ispatch'} = $attach->{'att'}->{'ispatch'} || 0; + $attachment{'isobsolete'} = $attach->{'att'}->{'isobsolete'} || 0; + $attachment{'isprivate'} = $attach->{'att'}->{'isprivate'} || 0; + $attachment{'filename'} = $attach->field('filename') || "file"; + $attachment{'attacher'} = $attach->field('attacher'); + + # Attachment data is not exported in versions 2.20 and older. + if ( defined $attach->first_child('data') + && defined $attach->first_child('data')->{'att'}->{'encoding'}) + { + my $encoding = $attach->first_child('data')->{'att'}->{'encoding'}; + if ($encoding =~ /base64/) { + + # decode the base64 + my $data = $attach->field('data'); + my $output = decode_base64($data); + $attachment{'data'} = $output; } + elsif ($encoding =~ /filename/) { - # attachment flags - my @aflags; - foreach my $aflag ( $attach->children('flag') ) { - my %aflag; - $aflag{'name'} = $aflag->{'att'}->{'name'}; - $aflag{'status'} = $aflag->{'att'}->{'status'}; - $aflag{'setter'} = $aflag->{'att'}->{'setter'}; - $aflag{'requestee'} = $aflag->{'att'}->{'requestee'}; - push @aflags, \%aflag; - } - $attachment{'flags'} = \@aflags if (@aflags); + # read the attachment file + Error("attach_path is required", undef) unless ($attach_path); - # free up the memory for use by the rest of the script - $attach->delete; - if ($attachment{'attachid'}) { - push @attachments, \%attachment; - } - else { - push @attachments, "err"; + my $filename = $attach->field('data'); + + # Remove any leading path data from the filename + $filename =~ s/(.*\/|.*\\)//gs; + + my $attach_filename = $attach_path . "/" . $filename; + open(ATTACH_FH, "<", $attach_filename) + or Error("cannot open $attach_filename", undef); + $attachment{'data'} = do { local $/; }; + close ATTACH_FH; } + } + else { + $attachment{'data'} = $attach->field('data'); + } + + # attachment flags + my @aflags; + foreach my $aflag ($attach->children('flag')) { + my %aflag; + $aflag{'name'} = $aflag->{'att'}->{'name'}; + $aflag{'status'} = $aflag->{'att'}->{'status'}; + $aflag{'setter'} = $aflag->{'att'}->{'setter'}; + $aflag{'requestee'} = $aflag->{'att'}->{'requestee'}; + push @aflags, \%aflag; + } + $attachment{'flags'} = \@aflags if (@aflags); + + # free up the memory for use by the rest of the script + $attach->delete; + if ($attachment{'attachid'}) { + push @attachments, \%attachment; + } + else { + push @attachments, "err"; + } } # This subroutine will be called once for each in the xml file. @@ -400,836 +412,862 @@ sub process_attachment() { # purged from memory to free it up for later bugs. sub process_bug { - my ( $twig, $bug ) = @_; - my $root = $twig->root; - my $maintainer = $root->{'att'}->{'maintainer'}; - my $exporter_login = $root->{'att'}->{'exporter'}; - my $exporter = new Bugzilla::User({ name => $exporter_login }); - my $urlbase = $root->{'att'}->{'urlbase'}; - - # We will store output information in this variable. - my $log = ""; - if ( defined $bug->{'att'}->{'error'} ) { - $log .= "\nError in bug " . $bug->field('bug_id') . "\@$urlbase: "; - $log .= $bug->{'att'}->{'error'} . "\n"; - if ( $bug->{'att'}->{'error'} =~ /NotFound/ ) { - $log .= "$exporter_login tried to move bug " . $bug->field('bug_id'); - $log .= " here, but $urlbase reports that this bug"; - $log .= " does not exist.\n"; - } - elsif ( $bug->{'att'}->{'error'} =~ /NotPermitted/ ) { - $log .= "$exporter_login tried to move bug " . $bug->field('bug_id'); - $log .= " here, but $urlbase reports that $exporter_login does "; - $log .= " not have access to that bug.\n"; - } - return; + my ($twig, $bug) = @_; + my $root = $twig->root; + my $maintainer = $root->{'att'}->{'maintainer'}; + my $exporter_login = $root->{'att'}->{'exporter'}; + my $exporter = new Bugzilla::User({name => $exporter_login}); + my $urlbase = $root->{'att'}->{'urlbase'}; + + # We will store output information in this variable. + my $log = ""; + if (defined $bug->{'att'}->{'error'}) { + $log .= "\nError in bug " . $bug->field('bug_id') . "\@$urlbase: "; + $log .= $bug->{'att'}->{'error'} . "\n"; + if ($bug->{'att'}->{'error'} =~ /NotFound/) { + $log .= "$exporter_login tried to move bug " . $bug->field('bug_id'); + $log .= " here, but $urlbase reports that this bug"; + $log .= " does not exist.\n"; } - $bugtotal++; - - # This list contains all other bug fields that we want to process. - # If it is not in this list it will not be included. - my %all_fields; - foreach my $field ( - qw(long_desc attachment flag group), Bugzilla::Bug::fields() ) - { - $all_fields{$field} = 1; + elsif ($bug->{'att'}->{'error'} =~ /NotPermitted/) { + $log .= "$exporter_login tried to move bug " . $bug->field('bug_id'); + $log .= " here, but $urlbase reports that $exporter_login does "; + $log .= " not have access to that bug.\n"; } - - my %bug_fields; - my $err = ""; - - # Loop through all the xml tags inside a and compare them to the - # lists of fields. If they match throw them into the hash. Otherwise - # append it to the log, which will go into the comments when we are done. - foreach my $bugchild ( $bug->children() ) { - Debug( "Parsing field: " . $bugchild->name, DEBUG_LEVEL ); - - # Skip the token if one is included. We don't want it included in - # the comments, and it is not used by the importer. - next if $bugchild->name eq 'token'; - - if ( defined $all_fields{ $bugchild->name } ) { - my @values = $bug->children_text($bugchild->name); - if (scalar @values > 1) { - $bug_fields{$bugchild->name} = \@values; - } - else { - $bug_fields{$bugchild->name} = $values[0]; - } - } - else { - $err .= "Unknown bug field \"" . $bugchild->name . "\""; - $err .= " encountered while moving bug\n"; - $err .= " <" . $bugchild->name . ">"; - if ( $bugchild->children_count > 1 ) { - $err .= "\n"; - foreach my $subchild ( $bugchild->children() ) { - $err .= " <" . $subchild->name . ">"; - $err .= $subchild->field; - $err .= "name . ">\n"; - } - } - else { - $err .= $bugchild->field; - } - $err .= "name . ">\n"; - } + return; + } + $bugtotal++; + + # This list contains all other bug fields that we want to process. + # If it is not in this list it will not be included. + my %all_fields; + foreach my $field (qw(long_desc attachment flag group), Bugzilla::Bug::fields()) + { + $all_fields{$field} = 1; + } + + my %bug_fields; + my $err = ""; + + # Loop through all the xml tags inside a and compare them to the + # lists of fields. If they match throw them into the hash. Otherwise + # append it to the log, which will go into the comments when we are done. + foreach my $bugchild ($bug->children()) { + Debug("Parsing field: " . $bugchild->name, DEBUG_LEVEL); + + # Skip the token if one is included. We don't want it included in + # the comments, and it is not used by the importer. + next if $bugchild->name eq 'token'; + + if (defined $all_fields{$bugchild->name}) { + my @values = $bug->children_text($bugchild->name); + if (scalar @values > 1) { + $bug_fields{$bugchild->name} = \@values; + } + else { + $bug_fields{$bugchild->name} = $values[0]; + } } - - # Parse long descriptions - my @long_descs; - foreach my $comment ( $bug->children('long_desc') ) { - Debug( "Parsing Long Description", DEBUG_LEVEL ); - my %long_desc = ( who => $comment->field('who'), - bug_when => format_time($comment->field('bug_when'), '%Y-%m-%d %T'), - isprivate => $comment->{'att'}->{'isprivate'} || 0 ); - - # If the exporter is not in the insidergroup, keep the comment public. - $long_desc{isprivate} = 0 unless $exporter->is_insider; - - my $data = $comment->field('thetext'); - if ( defined $comment->first_child('thetext')->{'att'}->{'encoding'} - && $comment->first_child('thetext')->{'att'}->{'encoding'} =~ - /base64/ ) - { - $data = decode_base64($data); - } - - # For backwards-compatibility with Bugzillas before 3.6: - # - # If we leave the attachment ID in the comment it will be made a link - # to the wrong attachment. Since the new attachment ID is unknown yet - # let's strip it out for now. We will make a comment with the right ID - # later - $data =~ s/Created an attachment \(id=\d+\)/Created an attachment/g; - - # Same goes for bug #'s Since we don't know if the referenced bug - # is also being moved, lets make sure they know it means a different - # bugzilla. - my $url = $urlbase . "show_bug.cgi?id="; - $data =~ s/([Bb]ugs?\s*\#?\s*(\d+))/$url$2/g; - - # Keep the original commenter if possible, else we will fall back - # to the exporter account. - $long_desc{whoid} = login_to_id($long_desc{who}); - - if (!$long_desc{whoid}) { - $data = "The original author of this comment is $long_desc{who}.\n\n" . $data; + else { + $err .= "Unknown bug field \"" . $bugchild->name . "\""; + $err .= " encountered while moving bug\n"; + $err .= " <" . $bugchild->name . ">"; + if ($bugchild->children_count > 1) { + $err .= "\n"; + foreach my $subchild ($bugchild->children()) { + $err .= " <" . $subchild->name . ">"; + $err .= $subchild->field; + $err .= "name . ">\n"; } - - $long_desc{'thetext'} = $data; - push @long_descs, \%long_desc; + } + else { + $err .= $bugchild->field; + } + $err .= "name . ">\n"; } - - my @sorted_descs = sort { $a->{'bug_when'} cmp $b->{'bug_when'} } @long_descs; - - my $comments = "\n\n--- Bug imported by $exporter_login "; - $comments .= format_time(scalar localtime(time()), '%Y-%m-%d %R %Z') . " "; - $comments .= " ---\n\n"; - $comments .= "This bug was previously known as _bug_ $bug_fields{'bug_id'} at "; - $comments .= $urlbase . "show_bug.cgi?id=" . $bug_fields{'bug_id'} . "\n"; - if ( defined $bug_fields{'dependson'} ) { - $comments .= "This bug depended on bug(s) " . - join(' ', _to_array($bug_fields{'dependson'})) . ".\n"; + } + + # Parse long descriptions + my @long_descs; + foreach my $comment ($bug->children('long_desc')) { + Debug("Parsing Long Description", DEBUG_LEVEL); + my %long_desc = ( + who => $comment->field('who'), + bug_when => format_time($comment->field('bug_when'), '%Y-%m-%d %T'), + isprivate => $comment->{'att'}->{'isprivate'} || 0 + ); + + # If the exporter is not in the insidergroup, keep the comment public. + $long_desc{isprivate} = 0 unless $exporter->is_insider; + + my $data = $comment->field('thetext'); + if (defined $comment->first_child('thetext')->{'att'}->{'encoding'} + && $comment->first_child('thetext')->{'att'}->{'encoding'} =~ /base64/) + { + $data = decode_base64($data); } - if ( defined $bug_fields{'blocked'} ) { - $comments .= "This bug blocked bug(s) " . - join(' ', _to_array($bug_fields{'blocked'})) . ".\n"; + + # For backwards-compatibility with Bugzillas before 3.6: + # + # If we leave the attachment ID in the comment it will be made a link + # to the wrong attachment. Since the new attachment ID is unknown yet + # let's strip it out for now. We will make a comment with the right ID + # later + $data =~ s/Created an attachment \(id=\d+\)/Created an attachment/g; + + # Same goes for bug #'s Since we don't know if the referenced bug + # is also being moved, lets make sure they know it means a different + # bugzilla. + my $url = $urlbase . "show_bug.cgi?id="; + $data =~ s/([Bb]ugs?\s*\#?\s*(\d+))/$url$2/g; + + # Keep the original commenter if possible, else we will fall back + # to the exporter account. + $long_desc{whoid} = login_to_id($long_desc{who}); + + if (!$long_desc{whoid}) { + $data = "The original author of this comment is $long_desc{who}.\n\n" . $data; } - # Now we process each of the fields in turn and make sure they contain - # valid data. We will create two parallel arrays, one for the query - # and one for the values. For every field we need to push an entry onto - # each array. - my @query = (); - my @values = (); - - # Each of these fields we will check for newlines and shove onto the array - foreach my $field (qw(status_whiteboard bug_file_loc short_desc)) { - if ($bug_fields{$field}) { - $bug_fields{$field} = clean_text( $bug_fields{$field} ); - push( @query, $field ); - push( @values, $bug_fields{$field} ); - } + $long_desc{'thetext'} = $data; + push @long_descs, \%long_desc; + } + + my @sorted_descs = sort { $a->{'bug_when'} cmp $b->{'bug_when'} } @long_descs; + + my $comments = "\n\n--- Bug imported by $exporter_login "; + $comments .= format_time(scalar localtime(time()), '%Y-%m-%d %R %Z') . " "; + $comments .= " ---\n\n"; + $comments .= "This bug was previously known as _bug_ $bug_fields{'bug_id'} at "; + $comments .= $urlbase . "show_bug.cgi?id=" . $bug_fields{'bug_id'} . "\n"; + if (defined $bug_fields{'dependson'}) { + $comments .= "This bug depended on bug(s) " + . join(' ', _to_array($bug_fields{'dependson'})) . ".\n"; + } + if (defined $bug_fields{'blocked'}) { + $comments .= "This bug blocked bug(s) " + . join(' ', _to_array($bug_fields{'blocked'})) . ".\n"; + } + + # Now we process each of the fields in turn and make sure they contain + # valid data. We will create two parallel arrays, one for the query + # and one for the values. For every field we need to push an entry onto + # each array. + my @query = (); + my @values = (); + + # Each of these fields we will check for newlines and shove onto the array + foreach my $field (qw(status_whiteboard bug_file_loc short_desc)) { + if ($bug_fields{$field}) { + $bug_fields{$field} = clean_text($bug_fields{$field}); + push(@query, $field); + push(@values, $bug_fields{$field}); } + } - # Alias - if ( $bug_fields{'alias'} ) { - my ($alias) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs + # Alias + if ($bug_fields{'alias'}) { + my ($alias) = $dbh->selectrow_array( + "SELECT COUNT(*) FROM bugs WHERE alias = ?", undef, - $bug_fields{'alias'} ); - if ($alias) { - $err .= "Dropping conflicting bug alias "; - $err .= $bug_fields{'alias'} . "\n"; - } - else { - $alias = $bug_fields{'alias'}; - push @query, 'alias'; - push @values, $alias; - } + $bug_fields{'alias'} + ); + if ($alias) { + $err .= "Dropping conflicting bug alias "; + $err .= $bug_fields{'alias'} . "\n"; } - - # Timestamps - push( @query, "creation_ts" ); - push( @values, - format_time( $bug_fields{'creation_ts'}, "%Y-%m-%d %T" ) - || $timestamp ); - - push( @query, "delta_ts" ); - push( @values, - format_time( $bug_fields{'delta_ts'}, "%Y-%m-%d %T" ) - || $timestamp ); - - # Bug Access - push( @query, "cclist_accessible" ); - push( @values, $bug_fields{'cclist_accessible'} ? 1 : 0 ); - - push( @query, "reporter_accessible" ); - push( @values, $bug_fields{'reporter_accessible'} ? 1 : 0 ); - - my $product = new Bugzilla::Product( - { name => $bug_fields{'product'} || '' }); - if (!$product) { - $err .= "Unknown Product " . $bug_fields{'product'} . "\n"; - $err .= " Using default product set at the command line.\n"; - $product = new Bugzilla::Product({ name => $default_product_name }) - or Error("an invalid default product was defined for the target" - . " DB. " . $params->{"maintainer"} . " needs to specify " - . "--product when calling importxml.pl", "REOPEN", - $exporter); + else { + $alias = $bug_fields{'alias'}; + push @query, 'alias'; + push @values, $alias; } - my $component = new Bugzilla::Component({ - product => $product, name => $bug_fields{'component'} || '' }); + } + + # Timestamps + push(@query, "creation_ts"); + push(@values, + format_time($bug_fields{'creation_ts'}, "%Y-%m-%d %T") || $timestamp); + + push(@query, "delta_ts"); + push(@values, + format_time($bug_fields{'delta_ts'}, "%Y-%m-%d %T") || $timestamp); + + # Bug Access + push(@query, "cclist_accessible"); + push(@values, $bug_fields{'cclist_accessible'} ? 1 : 0); + + push(@query, "reporter_accessible"); + push(@values, $bug_fields{'reporter_accessible'} ? 1 : 0); + + my $product = new Bugzilla::Product({name => $bug_fields{'product'} || ''}); + if (!$product) { + $err .= "Unknown Product " . $bug_fields{'product'} . "\n"; + $err .= " Using default product set at the command line.\n"; + $product = new Bugzilla::Product({name => $default_product_name}) + or Error( + "an invalid default product was defined for the target" . " DB. " + . $params->{"maintainer"} + . " needs to specify " + . "--product when calling importxml.pl", + "REOPEN", $exporter + ); + } + my $component + = new Bugzilla::Component({ + product => $product, name => $bug_fields{'component'} || '' + }); + if (!$component) { + $err .= "Unknown Component " . $bug_fields{'component'} . "\n"; + $err .= " Using default product and component set "; + $err .= "at the command line.\n"; + + $product = new Bugzilla::Product({name => $default_product_name}); + $component = new Bugzilla::Component( + {name => $default_component_name, product => $product}); if (!$component) { - $err .= "Unknown Component " . $bug_fields{'component'} . "\n"; - $err .= " Using default product and component set "; - $err .= "at the command line.\n"; - - $product = new Bugzilla::Product({ name => $default_product_name }); - $component = new Bugzilla::Component({ - name => $default_component_name, product => $product }); - if (!$component) { - Error("an invalid default component was defined for the target" - . " DB. ". $params->{"maintainer"} . " needs to specify " - . "--component when calling importxml.pl", "REOPEN", - $exporter); - } + Error( + "an invalid default component was defined for the target" . " DB. " + . $params->{"maintainer"} + . " needs to specify " + . "--component when calling importxml.pl", + "REOPEN", $exporter + ); } + } + + my $prod_id = $product->id; + my $comp_id = $component->id; + + push(@query, "product_id"); + push(@values, $prod_id); + push(@query, "component_id"); + push(@values, $comp_id); + + # Since there is no default version for a product, we check that the one + # coming over is valid. If not we will use the first one in @versions + # and warn them. + my $version = new Bugzilla::Version( + {product => $product, name => $bug_fields{'version'}}); + + push(@query, "version"); + if ($version) { + push(@values, $version->name); + } + else { + my @versions = @{$product->versions}; + my $v = $versions[0]; + push(@values, $v->name); + $err .= "Unknown version \""; + $err .= (defined $bug_fields{'version'}) ? $bug_fields{'version'} : "unknown"; + $err .= " in product " . $product->name . ". \n"; + $err .= " Setting version to \"" . $v->name . "\".\n"; + } + + # Milestone + if ($params->{"usetargetmilestone"}) { + my $milestone; + if (defined $bug_fields{'target_milestone'} + && $bug_fields{'target_milestone'} ne "") + { - my $prod_id = $product->id; - my $comp_id = $component->id; - - push( @query, "product_id" ); - push( @values, $prod_id ); - push( @query, "component_id" ); - push( @values, $comp_id ); - - # Since there is no default version for a product, we check that the one - # coming over is valid. If not we will use the first one in @versions - # and warn them. - my $version = new Bugzilla::Version( - { product => $product, name => $bug_fields{'version'} }); - - push( @query, "version" ); - if ($version) { - push( @values, $version->name ); + $milestone = new Bugzilla::Milestone( + {product => $product, name => $bug_fields{'target_milestone'}}); + } + if ($milestone) { + push(@values, $milestone->name); } else { - my @versions = @{ $product->versions }; - my $v = $versions[0]; - push( @values, $v->name ); - $err .= "Unknown version \""; - $err .= ( defined $bug_fields{'version'} ) - ? $bug_fields{'version'} - : "unknown"; - $err .= " in product " . $product->name . ". \n"; - $err .= " Setting version to \"" . $v->name . "\".\n"; + push(@values, $product->default_milestone); + $err .= "Unknown milestone \""; + $err + .= (defined $bug_fields{'target_milestone'}) + ? $bug_fields{'target_milestone'} + : "unknown"; + $err .= " in product " . $product->name . ". \n"; + $err .= " Setting to default milestone for this product, "; + $err .= "\"" . $product->default_milestone . "\".\n"; } - - # Milestone - if ( $params->{"usetargetmilestone"} ) { - my $milestone; - if (defined $bug_fields{'target_milestone'} - && $bug_fields{'target_milestone'} ne "") { - - $milestone = new Bugzilla::Milestone( - { product => $product, name => $bug_fields{'target_milestone'} }); - } - if ($milestone) { - push( @values, $milestone->name ); - } - else { - push( @values, $product->default_milestone ); - $err .= "Unknown milestone \""; - $err .= ( defined $bug_fields{'target_milestone'} ) - ? $bug_fields{'target_milestone'} - : "unknown"; - $err .= " in product " . $product->name . ". \n"; - $err .= " Setting to default milestone for this product, "; - $err .= "\"" . $product->default_milestone . "\".\n"; - } - push( @query, "target_milestone" ); + push(@query, "target_milestone"); + } + + # For priority, severity, opsys and platform we check that the one being + # imported is valid. If it is not we use the defaults set in the parameters. + if ( + defined($bug_fields{'bug_severity'}) + && check_field( + 'bug_severity', scalar $bug_fields{'bug_severity'}, + undef, ERR_LEVEL + ) + ) + { + push(@values, $bug_fields{'bug_severity'}); + } + else { + push(@values, $params->{'defaultseverity'}); + $err .= "Unknown severity "; + $err + .= (defined $bug_fields{'bug_severity'}) + ? $bug_fields{'bug_severity'} + : "unknown"; + $err .= ". Setting to default severity \""; + $err .= $params->{'defaultseverity'} . "\".\n"; + } + push(@query, "bug_severity"); + + if (defined($bug_fields{'priority'}) + && check_field('priority', scalar $bug_fields{'priority'}, undef, ERR_LEVEL)) + { + push(@values, $bug_fields{'priority'}); + } + else { + push(@values, $params->{'defaultpriority'}); + $err .= "Unknown priority "; + $err .= (defined $bug_fields{'priority'}) ? $bug_fields{'priority'} : "unknown"; + $err .= ". Setting to default priority \""; + $err .= $params->{'defaultpriority'} . "\".\n"; + } + push(@query, "priority"); + + if ( + defined($bug_fields{'rep_platform'}) + && check_field( + 'rep_platform', scalar $bug_fields{'rep_platform'}, + undef, ERR_LEVEL + ) + ) + { + push(@values, $bug_fields{'rep_platform'}); + } + else { + push(@values, $params->{'defaultplatform'}); + $err .= "Unknown platform "; + $err + .= (defined $bug_fields{'rep_platform'}) + ? $bug_fields{'rep_platform'} + : "unknown"; + $err .= ". Setting to default platform \""; + $err .= $params->{'defaultplatform'} . "\".\n"; + } + push(@query, "rep_platform"); + + if (defined($bug_fields{'op_sys'}) + && check_field('op_sys', scalar $bug_fields{'op_sys'}, undef, ERR_LEVEL)) + { + push(@values, $bug_fields{'op_sys'}); + } + else { + push(@values, $params->{'defaultopsys'}); + $err .= "Unknown operating system "; + $err .= (defined $bug_fields{'op_sys'}) ? $bug_fields{'op_sys'} : "unknown"; + $err .= ". Setting to default OS \"" . $params->{'defaultopsys'} . "\".\n"; + } + push(@query, "op_sys"); + + # Process time fields + if ($params->{"timetrackinggroup"}) { + my $date + = validate_date($bug_fields{'deadline'}) ? $bug_fields{'deadline'} : undef; + push(@values, $date); + push(@query, "deadline"); + if (defined $bug_fields{'estimated_time'}) { + eval { Bugzilla::Object::_validate_time($bug_fields{'estimated_time'}, "e"); }; + if (!$@) { + push(@values, $bug_fields{'estimated_time'}); + push(@query, "estimated_time"); + } } - - # For priority, severity, opsys and platform we check that the one being - # imported is valid. If it is not we use the defaults set in the parameters. - if (defined( $bug_fields{'bug_severity'} ) - && check_field('bug_severity', scalar $bug_fields{'bug_severity'}, - undef, ERR_LEVEL) ) - { - push( @values, $bug_fields{'bug_severity'} ); + if (defined $bug_fields{'remaining_time'}) { + eval { Bugzilla::Object::_validate_time($bug_fields{'remaining_time'}, "r"); }; + if (!$@) { + push(@values, $bug_fields{'remaining_time'}); + push(@query, "remaining_time"); + } + } + if (defined $bug_fields{'actual_time'}) { + eval { Bugzilla::Object::_validate_time($bug_fields{'actual_time'}, "a"); }; + if ($@) { + $bug_fields{'actual_time'} = 0.0; + $err .= "Invalid Actual Time. Setting to 0.0\n"; + } } else { - push( @values, $params->{'defaultseverity'} ); - $err .= "Unknown severity "; - $err .= ( defined $bug_fields{'bug_severity'} ) - ? $bug_fields{'bug_severity'} - : "unknown"; - $err .= ". Setting to default severity \""; - $err .= $params->{'defaultseverity'} . "\".\n"; + $bug_fields{'actual_time'} = 0.0; + $err .= "Actual time not defined. Setting to 0.0\n"; } - push( @query, "bug_severity" ); - - if (defined( $bug_fields{'priority'} ) - && check_field('priority', scalar $bug_fields{'priority'}, - undef, ERR_LEVEL ) ) - { - push( @values, $bug_fields{'priority'} ); + } + + # Reporter Assignee QA Contact + my $exporterid = $exporter->id; + my $reporterid = login_to_id($bug_fields{'reporter'}) + if $bug_fields{'reporter'}; + push(@query, "reporter"); + if (($bug_fields{'reporter'}) && ($reporterid)) { + push(@values, $reporterid); + } + else { + push(@values, $exporterid); + $err .= "The original reporter of this bug does not have\n"; + $err .= " an account here. Reassigning to the person who moved\n"; + $err .= " it here: $exporter_login.\n"; + if ($bug_fields{'reporter'}) { + $err .= " Previous reporter was $bug_fields{'reporter'}.\n"; } else { - push( @values, $params->{'defaultpriority'} ); - $err .= "Unknown priority "; - $err .= ( defined $bug_fields{'priority'} ) - ? $bug_fields{'priority'} - : "unknown"; - $err .= ". Setting to default priority \""; - $err .= $params->{'defaultpriority'} . "\".\n"; + $err .= " Previous reporter is unknown.\n"; } - push( @query, "priority" ); - - if (defined( $bug_fields{'rep_platform'} ) - && check_field('rep_platform', scalar $bug_fields{'rep_platform'}, - undef, ERR_LEVEL ) ) - { - push( @values, $bug_fields{'rep_platform'} ); + } + + my $changed_owner = 0; + my $owner; + push(@query, "assigned_to"); + if ( ($bug_fields{'assigned_to'}) + && ($owner = login_to_id($bug_fields{'assigned_to'}))) + { + push(@values, $owner); + } + else { + push(@values, $component->default_assignee->id); + $changed_owner = 1; + $err .= "The original assignee of this bug does not have\n"; + $err .= " an account here. Reassigning to the default assignee\n"; + $err .= " for the component, " . $component->default_assignee->login . ".\n"; + if ($bug_fields{'assigned_to'}) { + $err .= " Previous assignee was $bug_fields{'assigned_to'}.\n"; } else { - push( @values, $params->{'defaultplatform'} ); - $err .= "Unknown platform "; - $err .= ( defined $bug_fields{'rep_platform'} ) - ? $bug_fields{'rep_platform'} - : "unknown"; - $err .=". Setting to default platform \""; - $err .= $params->{'defaultplatform'} . "\".\n"; + $err .= " Previous assignee is unknown.\n"; } - push( @query, "rep_platform" ); + } - if (defined( $bug_fields{'op_sys'} ) - && check_field('op_sys', scalar $bug_fields{'op_sys'}, - undef, ERR_LEVEL ) ) + if ($params->{"useqacontact"}) { + my $qa_contact; + push(@query, "qa_contact"); + if ( (defined $bug_fields{'qa_contact'}) + && ($qa_contact = login_to_id($bug_fields{'qa_contact'}))) { - push( @values, $bug_fields{'op_sys'} ); + push(@values, $qa_contact); } else { - push( @values, $params->{'defaultopsys'} ); - $err .= "Unknown operating system "; - $err .= ( defined $bug_fields{'op_sys'} ) - ? $bug_fields{'op_sys'} - : "unknown"; - $err .= ". Setting to default OS \"" . $params->{'defaultopsys'} . "\".\n"; - } - push( @query, "op_sys" ); - - # Process time fields - if ( $params->{"timetrackinggroup"} ) { - my $date = validate_date( $bug_fields{'deadline'} ) ? $bug_fields{'deadline'} : undef; - push( @values, $date ); - push( @query, "deadline" ); - if ( defined $bug_fields{'estimated_time'} ) { - eval { - Bugzilla::Object::_validate_time($bug_fields{'estimated_time'}, "e"); - }; - if (!$@){ - push( @values, $bug_fields{'estimated_time'} ); - push( @query, "estimated_time" ); - } - } - if ( defined $bug_fields{'remaining_time'} ) { - eval { - Bugzilla::Object::_validate_time($bug_fields{'remaining_time'}, "r"); - }; - if (!$@){ - push( @values, $bug_fields{'remaining_time'} ); - push( @query, "remaining_time" ); - } - } - if ( defined $bug_fields{'actual_time'} ) { - eval { - Bugzilla::Object::_validate_time($bug_fields{'actual_time'}, "a"); - }; - if ($@){ - $bug_fields{'actual_time'} = 0.0; - $err .= "Invalid Actual Time. Setting to 0.0\n"; - } - } - else { - $bug_fields{'actual_time'} = 0.0; - $err .= "Actual time not defined. Setting to 0.0\n"; - } + push(@values, $component->default_qa_contact->id || undef); + if ($component->default_qa_contact->id) { + $err .= "Setting qa contact to the default for this product.\n"; + $err .= " This bug either had no qa contact or an invalid one.\n"; + } } - - # Reporter Assignee QA Contact - my $exporterid = $exporter->id; - my $reporterid = login_to_id( $bug_fields{'reporter'} ) - if $bug_fields{'reporter'}; - push( @query, "reporter" ); - if ( ( $bug_fields{'reporter'} ) && ($reporterid) ) { - push( @values, $reporterid ); + } + + # Status & Resolution + my $valid_res + = check_field('resolution', scalar $bug_fields{'resolution'}, undef, + ERR_LEVEL); + my $valid_status + = check_field('bug_status', scalar $bug_fields{'bug_status'}, undef, + ERR_LEVEL); + my $is_open = is_open_state($bug_fields{'bug_status'}); + my $status = $bug_fields{'bug_status'} || undef; + my $resolution = $bug_fields{'resolution'} || undef; + + # Check everconfirmed + my $everconfirmed; + if ($product->allows_unconfirmed) { + $everconfirmed = $bug_fields{'everconfirmed'} || 0; + } + else { + $everconfirmed = 1; + } + push(@query, "everconfirmed"); + push(@values, $everconfirmed); + + # Sanity check will complain about having bugs marked duplicate but no + # entry in the dup table. Since we can't tell the bug ID of bugs + # that might not yet be in the database we have no way of populating + # this table. Change the resolution instead. + if ($valid_res && ($bug_fields{'resolution'} eq "DUPLICATE")) { + $resolution = "INVALID"; + $err .= "This bug was marked DUPLICATE in the database "; + $err .= "it was moved from.\n Changing resolution to \"INVALID\"\n"; + } + + # If there is at least 1 initial bug status different from UNCO, use it, + # else use the open bug status with the lowest sortkey (different from UNCO). + my @bug_statuses = @{Bugzilla::Status->can_change_to()}; + @bug_statuses = grep { $_->name ne 'UNCONFIRMED' } @bug_statuses; + + my $initial_status; + if (scalar(@bug_statuses)) { + $initial_status = $bug_statuses[0]->name; + } + else { + @bug_statuses = Bugzilla::Status->get_all(); + + # Exclude UNCO and inactive bug statuses. + @bug_statuses + = grep { $_->is_active && $_->name ne 'UNCONFIRMED' } @bug_statuses; + my @open_statuses = grep { $_->is_open } @bug_statuses; + if (scalar(@open_statuses)) { + $initial_status = $open_statuses[0]->name; } else { - push( @values, $exporterid ); - $err .= "The original reporter of this bug does not have\n"; - $err .= " an account here. Reassigning to the person who moved\n"; - $err .= " it here: $exporter_login.\n"; - if ( $bug_fields{'reporter'} ) { - $err .= " Previous reporter was $bug_fields{'reporter'}.\n"; + # There is NO other open bug statuses outside UNCO??? + Error("no open bug statuses available."); + } + } + + if ($status) { + if ($valid_status) { + if ($is_open) { + if ($resolution) { + $err .= "Resolution set on an open status.\n"; + $err .= " Dropping resolution $resolution\n"; + $resolution = undef; } - else { - $err .= " Previous reporter is unknown.\n"; + if ($changed_owner) { + if ($everconfirmed) { + $status = $initial_status; + } + else { + $status = "UNCONFIRMED"; + } + if ($status ne $bug_fields{'bug_status'}) { + $err .= "Bug reassigned, setting status to \"$status\".\n"; + $err .= " Previous status was \""; + $err .= $bug_fields{'bug_status'} . "\".\n"; + } } - } - - my $changed_owner = 0; - my $owner; - push( @query, "assigned_to" ); - if ( ( $bug_fields{'assigned_to'} ) - && ( $owner = login_to_id( $bug_fields{'assigned_to'} )) ) { - push( @values, $owner ); - } - else { - push( @values, $component->default_assignee->id ); - $changed_owner = 1; - $err .= "The original assignee of this bug does not have\n"; - $err .= " an account here. Reassigning to the default assignee\n"; - $err .= " for the component, ". $component->default_assignee->login .".\n"; - if ( $bug_fields{'assigned_to'} ) { - $err .= " Previous assignee was $bug_fields{'assigned_to'}.\n"; + if ($everconfirmed) { + if ($status eq "UNCONFIRMED") { + $err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n"; + $err .= " Setting status to $initial_status\n"; + $status = $initial_status; + } } - else { - $err .= " Previous assignee is unknown.\n"; + else { # $everconfirmed is false + if ($status ne "UNCONFIRMED") { + $err .= "Bug Status was $status but everconfirmed was false\n"; + $err .= " Setting status to UNCONFIRMED\n"; + $status = "UNCONFIRMED"; + } } - } - - if ( $params->{"useqacontact"} ) { - my $qa_contact; - push( @query, "qa_contact" ); - if ( ( defined $bug_fields{'qa_contact'}) - && ( $qa_contact = login_to_id( $bug_fields{'qa_contact'} ) ) ) { - push( @values, $qa_contact ); + } + else { # $is_open is false + if (!$resolution) { + $err .= "Missing Resolution. Setting status to "; + if ($everconfirmed) { + $status = $initial_status; + $err .= "$initial_status\n"; + } + else { + $status = "UNCONFIRMED"; + $err .= "UNCONFIRMED\n"; + } } - else { - push( @values, $component->default_qa_contact->id || undef ); - if ($component->default_qa_contact->id){ - $err .= "Setting qa contact to the default for this product.\n"; - $err .= " This bug either had no qa contact or an invalid one.\n"; - } + elsif (!$valid_res) { + $err .= "Unknown resolution \"$resolution\".\n"; + $err .= " Setting resolution to INVALID\n"; + $resolution = "INVALID"; } + } } - - # Status & Resolution - my $valid_res = check_field('resolution', - scalar $bug_fields{'resolution'}, - undef, ERR_LEVEL ); - my $valid_status = check_field('bug_status', - scalar $bug_fields{'bug_status'}, - undef, ERR_LEVEL ); - my $is_open = is_open_state($bug_fields{'bug_status'}); - my $status = $bug_fields{'bug_status'} || undef; - my $resolution = $bug_fields{'resolution'} || undef; - - # Check everconfirmed - my $everconfirmed; - if ($product->allows_unconfirmed) { - $everconfirmed = $bug_fields{'everconfirmed'} || 0; + else { # $valid_status is false + if ($everconfirmed) { + $status = $initial_status; + } + else { + $status = "UNCONFIRMED"; + } + $err .= "Bug has invalid status, setting status to \"$status\".\n"; + $err .= " Previous status was \""; + $err .= $bug_fields{'bug_status'} . "\".\n"; + $resolution = undef; + } + } + else { + if ($everconfirmed) { + $status = $initial_status; } else { - $everconfirmed = 1; + $status = "UNCONFIRMED"; } - push (@query, "everconfirmed"); - push (@values, $everconfirmed); - - # Sanity check will complain about having bugs marked duplicate but no - # entry in the dup table. Since we can't tell the bug ID of bugs - # that might not yet be in the database we have no way of populating - # this table. Change the resolution instead. - if ( $valid_res && ( $bug_fields{'resolution'} eq "DUPLICATE" ) ) { - $resolution = "INVALID"; - $err .= "This bug was marked DUPLICATE in the database "; - $err .= "it was moved from.\n Changing resolution to \"INVALID\"\n"; + $err .= "Bug has no status, setting status to \"$status\".\n"; + $err .= " Previous status was unknown\n"; + $resolution = undef; + } + + if ($resolution) { + push(@query, "resolution"); + push(@values, $resolution); + } + + # Bug status + push(@query, "bug_status"); + push(@values, $status); + + # Custom fields - Multi-select fields have their own table. + my %multi_select_fields; + foreach my $field (Bugzilla->active_custom_fields) { + my $custom_field = $field->name; + my $value = $bug_fields{$custom_field}; + next unless defined $value; + if ($field->type == FIELD_TYPE_FREETEXT) { + push(@query, $custom_field); + push(@values, clean_text($value)); } - - # If there is at least 1 initial bug status different from UNCO, use it, - # else use the open bug status with the lowest sortkey (different from UNCO). - my @bug_statuses = @{Bugzilla::Status->can_change_to()}; - @bug_statuses = grep { $_->name ne 'UNCONFIRMED' } @bug_statuses; - - my $initial_status; - if (scalar(@bug_statuses)) { - $initial_status = $bug_statuses[0]->name; - } - else { - @bug_statuses = Bugzilla::Status->get_all(); - # Exclude UNCO and inactive bug statuses. - @bug_statuses = grep { $_->is_active && $_->name ne 'UNCONFIRMED'} @bug_statuses; - my @open_statuses = grep { $_->is_open } @bug_statuses; - if (scalar(@open_statuses)) { - $initial_status = $open_statuses[0]->name; - } - else { - # There is NO other open bug statuses outside UNCO??? - Error("no open bug statuses available."); - } + elsif ($field->type == FIELD_TYPE_TEXTAREA) { + push(@query, $custom_field); + push(@values, $value); } - - if ($status) { - if($valid_status){ - if($is_open){ - if ($resolution) { - $err .= "Resolution set on an open status.\n"; - $err .= " Dropping resolution $resolution\n"; - $resolution = undef; - } - if($changed_owner){ - if($everconfirmed){ - $status = $initial_status; - } - else{ - $status = "UNCONFIRMED"; - } - if ($status ne $bug_fields{'bug_status'}){ - $err .= "Bug reassigned, setting status to \"$status\".\n"; - $err .= " Previous status was \""; - $err .= $bug_fields{'bug_status'} . "\".\n"; - } - } - if($everconfirmed){ - if($status eq "UNCONFIRMED"){ - $err .= "Bug Status was UNCONFIRMED but everconfirmed was true\n"; - $err .= " Setting status to $initial_status\n"; - $status = $initial_status; - } - } - else{ # $everconfirmed is false - if($status ne "UNCONFIRMED"){ - $err .= "Bug Status was $status but everconfirmed was false\n"; - $err .= " Setting status to UNCONFIRMED\n"; - $status = "UNCONFIRMED"; - } - } - } - else{ # $is_open is false - if (!$resolution) { - $err .= "Missing Resolution. Setting status to "; - if($everconfirmed){ - $status = $initial_status; - $err .= "$initial_status\n"; - } - else{ - $status = "UNCONFIRMED"; - $err .= "UNCONFIRMED\n"; - } - } - elsif (!$valid_res) { - $err .= "Unknown resolution \"$resolution\".\n"; - $err .= " Setting resolution to INVALID\n"; - $resolution = "INVALID"; - } - } - } - else{ # $valid_status is false - if($everconfirmed){ - $status = $initial_status; - } - else{ - $status = "UNCONFIRMED"; - } - $err .= "Bug has invalid status, setting status to \"$status\".\n"; - $err .= " Previous status was \""; - $err .= $bug_fields{'bug_status'} . "\".\n"; - $resolution = undef; - } + elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) { + my $is_well_formed = check_field($custom_field, $value, undef, ERR_LEVEL); + if ($is_well_formed) { + push(@query, $custom_field); + push(@values, $value); + } + else { + $err .= "Skipping illegal value \"$value\" in $custom_field.\n"; + } } - else { - if($everconfirmed){ - $status = $initial_status; + elsif ($field->type == FIELD_TYPE_MULTI_SELECT) { + my @legal_values; + foreach my $item (_to_array($value)) { + my $is_well_formed = check_field($custom_field, $item, undef, ERR_LEVEL); + if ($is_well_formed) { + push(@legal_values, $item); } - else{ - $status = "UNCONFIRMED"; + else { + $err .= "Skipping illegal value \"$item\" in $custom_field.\n"; } - $err .= "Bug has no status, setting status to \"$status\".\n"; - $err .= " Previous status was unknown\n"; - $resolution = undef; + } + if (scalar @legal_values) { + $multi_select_fields{$custom_field} = \@legal_values; + } } - - if ($resolution) { - push( @query, "resolution" ); - push( @values, $resolution ); + elsif ($field->type == FIELD_TYPE_DATETIME) { + eval { $value = Bugzilla::Bug->_check_datetime_field($value); }; + if ($@) { + $err .= "Skipping illegal value \"$value\" in $custom_field.\n"; + } + else { + push(@query, $custom_field); + push(@values, $value); + } } - - # Bug status - push( @query, "bug_status" ); - push( @values, $status ); - - # Custom fields - Multi-select fields have their own table. - my %multi_select_fields; - foreach my $field (Bugzilla->active_custom_fields) { - my $custom_field = $field->name; - my $value = $bug_fields{$custom_field}; - next unless defined $value; - if ($field->type == FIELD_TYPE_FREETEXT) { - push(@query, $custom_field); - push(@values, clean_text($value)); - } elsif ($field->type == FIELD_TYPE_TEXTAREA) { - push(@query, $custom_field); - push(@values, $value); - } elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) { - my $is_well_formed = check_field($custom_field, $value, undef, ERR_LEVEL); - if ($is_well_formed) { - push(@query, $custom_field); - push(@values, $value); - } else { - $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ; - } - } elsif ($field->type == FIELD_TYPE_MULTI_SELECT) { - my @legal_values; - foreach my $item (_to_array($value)) { - my $is_well_formed = check_field($custom_field, $item, undef, ERR_LEVEL); - if ($is_well_formed) { - push(@legal_values, $item); - } else { - $err .= "Skipping illegal value \"$item\" in $custom_field.\n" ; - } - } - if (scalar @legal_values) { - $multi_select_fields{$custom_field} = \@legal_values; - } - } elsif ($field->type == FIELD_TYPE_DATETIME) { - eval { $value = Bugzilla::Bug->_check_datetime_field($value); }; - if ($@) { - $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ; - } - else { - push(@query, $custom_field); - push(@values, $value); - } - } elsif ($field->type == FIELD_TYPE_DATE) { - eval { $value = Bugzilla::Bug->_check_date_field($value); }; - if ($@) { - $err .= "Skipping illegal value \"$value\" in $custom_field.\n" ; - } - else { - push(@query, $custom_field); - push(@values, $value); - } - } else { - $err .= "Type of custom field $custom_field is an unhandled FIELD_TYPE: " . - $field->type . "\n"; - } + elsif ($field->type == FIELD_TYPE_DATE) { + eval { $value = Bugzilla::Bug->_check_date_field($value); }; + if ($@) { + $err .= "Skipping illegal value \"$value\" in $custom_field.\n"; + } + else { + push(@query, $custom_field); + push(@values, $value); + } } - - # For the sake of sanitycheck.cgi we do this. - # Update lastdiffed if you do not want to have mail sent - unless ($mail) { - push @query, "lastdiffed"; - push @values, $timestamp; + else { + $err .= "Type of custom field $custom_field is an unhandled FIELD_TYPE: " + . $field->type . "\n"; } - - # INSERT the bug - my $query = "INSERT INTO bugs (" . join( ", ", @query ) . ") VALUES ("; - $query .= '?,' foreach (@values); - chop($query); # Remove the last comma. - $query .= ")"; - - $dbh->do( $query, undef, @values ); - my $id = $dbh->bz_last_key( 'bugs', 'bug_id' ); - - # We are almost certain to get some uninitialized warnings - # Since this is just for debugging the query, let's shut them up - eval { - no warnings 'uninitialized'; - Debug( - "Bug Query: INSERT INTO bugs (\n" - . join( ",\n", @query ) - . "\n) VALUES (\n" - . join( ",\n", @values ), - DEBUG_LEVEL - ); - }; - - # Handle CC's - if ( defined $bug_fields{'cc'} ) { - my %ccseen; - my $sth_cc = $dbh->prepare("INSERT INTO cc (bug_id, who) VALUES (?,?)"); - foreach my $person (_to_array($bug_fields{'cc'})) { - next unless $person; - my $uid; - if ($uid = login_to_id($person)) { - if ( !$ccseen{$uid} ) { - $sth_cc->execute( $id, $uid ); - $ccseen{$uid} = 1; - } - } - else { - $err .= "CC member $person does not have an account here\n"; - } + } + + # For the sake of sanitycheck.cgi we do this. + # Update lastdiffed if you do not want to have mail sent + unless ($mail) { + push @query, "lastdiffed"; + push @values, $timestamp; + } + + # INSERT the bug + my $query = "INSERT INTO bugs (" . join(", ", @query) . ") VALUES ("; + $query .= '?,' foreach (@values); + chop($query); # Remove the last comma. + $query .= ")"; + + $dbh->do($query, undef, @values); + my $id = $dbh->bz_last_key('bugs', 'bug_id'); + + # We are almost certain to get some uninitialized warnings + # Since this is just for debugging the query, let's shut them up + eval { + no warnings 'uninitialized'; + Debug( + "Bug Query: INSERT INTO bugs (\n" + . join(",\n", @query) + . "\n) VALUES (\n" + . join(",\n", @values), + DEBUG_LEVEL + ); + }; + + # Handle CC's + if (defined $bug_fields{'cc'}) { + my %ccseen; + my $sth_cc = $dbh->prepare("INSERT INTO cc (bug_id, who) VALUES (?,?)"); + foreach my $person (_to_array($bug_fields{'cc'})) { + next unless $person; + my $uid; + if ($uid = login_to_id($person)) { + if (!$ccseen{$uid}) { + $sth_cc->execute($id, $uid); + $ccseen{$uid} = 1; } + } + else { + $err .= "CC member $person does not have an account here\n"; + } } + } - # Handle keywords - if ( defined( $bug_fields{'keywords'} ) ) { - my %keywordseen; - my $key_sth = $dbh->prepare( - "INSERT INTO keywords + # Handle keywords + if (defined($bug_fields{'keywords'})) { + my %keywordseen; + my $key_sth = $dbh->prepare( + "INSERT INTO keywords (bug_id, keywordid) VALUES (?,?)" - ); - foreach my $keyword ( split( /[\s,]+/, $bug_fields{'keywords'} )) { - next unless $keyword; - my $keyword_obj = new Bugzilla::Keyword({name => $keyword}); - if (!$keyword_obj) { - $err .= "Skipping unknown keyword: $keyword.\n"; - next; - } - if (!$keywordseen{$keyword_obj->id}) { - $key_sth->execute($id, $keyword_obj->id); - $keywordseen{$keyword_obj->id} = 1; - } - } + ); + foreach my $keyword (split(/[\s,]+/, $bug_fields{'keywords'})) { + next unless $keyword; + my $keyword_obj = new Bugzilla::Keyword({name => $keyword}); + if (!$keyword_obj) { + $err .= "Skipping unknown keyword: $keyword.\n"; + next; + } + if (!$keywordseen{$keyword_obj->id}) { + $key_sth->execute($id, $keyword_obj->id); + $keywordseen{$keyword_obj->id} = 1; + } } - - # Insert values of custom multi-select fields. They have already - # been validated. - foreach my $custom_field (keys %multi_select_fields) { - my $sth = $dbh->prepare("INSERT INTO bug_$custom_field - (bug_id, value) VALUES (?, ?)"); - foreach my $value (@{$multi_select_fields{$custom_field}}) { - $sth->execute($id, $value); - } + } + + # Insert values of custom multi-select fields. They have already + # been validated. + foreach my $custom_field (keys %multi_select_fields) { + my $sth = $dbh->prepare( + "INSERT INTO bug_$custom_field + (bug_id, value) VALUES (?, ?)" + ); + foreach my $value (@{$multi_select_fields{$custom_field}}) { + $sth->execute($id, $value); } - - # Parse bug flags - foreach my $bflag ( $bug->children('flag')) { - next unless ( defined($bflag) ); - $err .= flag_handler( - $bflag->{'att'}->{'name'}, $bflag->{'att'}->{'status'}, - $bflag->{'att'}->{'setter'}, $bflag->{'att'}->{'requestee'}, - $exporterid, $id, - $comp_id, $prod_id, - undef - ); + } + + # Parse bug flags + foreach my $bflag ($bug->children('flag')) { + next unless (defined($bflag)); + $err .= flag_handler( + $bflag->{'att'}->{'name'}, $bflag->{'att'}->{'status'}, + $bflag->{'att'}->{'setter'}, $bflag->{'att'}->{'requestee'}, + $exporterid, $id, + $comp_id, $prod_id, + undef + ); + } + + # Insert Attachments for the bug + foreach my $att (@attachments) { + if ($att eq "err") { + $err .= "No attachment ID specified, dropping attachment\n"; + next; + } + if (!$exporter->is_insider && $att->{'isprivate'}) { + $err .= "Exporter not in insidergroup and attachment marked private.\n"; + $err .= " Marking attachment public\n"; + $att->{'isprivate'} = 0; } - # Insert Attachments for the bug - foreach my $att (@attachments) { - if ($att eq "err"){ - $err .= "No attachment ID specified, dropping attachment\n"; - next; - } - if (!$exporter->is_insider && $att->{'isprivate'}) { - $err .= "Exporter not in insidergroup and attachment marked private.\n"; - $err .= " Marking attachment public\n"; - $att->{'isprivate'} = 0; - } - - my $attacher_id = $att->{'attacher'} ? login_to_id($att->{'attacher'}) : undef; + my $attacher_id = $att->{'attacher'} ? login_to_id($att->{'attacher'}) : undef; - $dbh->do("INSERT INTO attachments + $dbh->do( + "INSERT INTO attachments (bug_id, creation_ts, modification_time, filename, description, mimetype, ispatch, isprivate, isobsolete, submitter_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - undef, $id, $att->{'date'}, $att->{'date'}, $att->{'filename'}, - $att->{'desc'}, $att->{'ctype'}, $att->{'ispatch'}, - $att->{'isprivate'}, $att->{'isobsolete'}, $attacher_id || $exporterid); - my $att_id = $dbh->bz_last_key( 'attachments', 'attach_id' ); - my $att_data = $att->{'data'}; - my $sth = $dbh->prepare("INSERT INTO attach_data (id, thedata) - VALUES ($att_id, ?)" ); - trick_taint($att_data); - $sth->bind_param( 1, $att_data, $dbh->BLOB_TYPE ); - $sth->execute(); - - $comments .= "Imported an attachment (id=$att_id)\n"; - if (!$attacher_id) { - if ($att->{'attacher'}) { - $err .= "The original submitter of attachment $att_id was\n "; - $err .= $att->{'attacher'} . ", but he doesn't have an account here.\n"; - } - else { - $err .= "The original submitter of attachment $att_id is unknown.\n"; - } - $err .= " Reassigning to the person who moved it here: $exporter_login.\n"; - } + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", undef, $id, $att->{'date'}, + $att->{'date'}, $att->{'filename'}, $att->{'desc'}, $att->{'ctype'}, + $att->{'ispatch'}, $att->{'isprivate'}, $att->{'isobsolete'}, + $attacher_id || $exporterid + ); + my $att_id = $dbh->bz_last_key('attachments', 'attach_id'); + my $att_data = $att->{'data'}; + my $sth = $dbh->prepare( + "INSERT INTO attach_data (id, thedata) + VALUES ($att_id, ?)" + ); + trick_taint($att_data); + $sth->bind_param(1, $att_data, $dbh->BLOB_TYPE); + $sth->execute(); + + $comments .= "Imported an attachment (id=$att_id)\n"; + if (!$attacher_id) { + if ($att->{'attacher'}) { + $err .= "The original submitter of attachment $att_id was\n "; + $err .= $att->{'attacher'} . ", but he doesn't have an account here.\n"; + } + else { + $err .= "The original submitter of attachment $att_id is unknown.\n"; + } + $err .= " Reassigning to the person who moved it here: $exporter_login.\n"; + } - # Process attachment flags - foreach my $aflag (@{ $att->{'flags'} }) { - next unless defined($aflag) ; - $err .= flag_handler( - $aflag->{'name'}, $aflag->{'status'}, - $aflag->{'setter'}, $aflag->{'requestee'}, - $exporterid, $id, - $comp_id, $prod_id, - $att_id - ); - } + # Process attachment flags + foreach my $aflag (@{$att->{'flags'}}) { + next unless defined($aflag); + $err .= flag_handler( + $aflag->{'name'}, $aflag->{'status'}, $aflag->{'setter'}, + $aflag->{'requestee'}, $exporterid, $id, + $comp_id, $prod_id, $att_id + ); } + } - # Clear the attachments array for the next bug - @attachments = (); + # Clear the attachments array for the next bug + @attachments = (); - # Insert comments and append any errors - my $worktime = $bug_fields{'actual_time'} || 0.0; - $worktime = 0.0 if (!$exporter->is_timetracker); - $comments .= "\n$err\n" if $err; + # Insert comments and append any errors + my $worktime = $bug_fields{'actual_time'} || 0.0; + $worktime = 0.0 if (!$exporter->is_timetracker); + $comments .= "\n$err\n" if $err; - my $sth_comment = - $dbh->prepare('INSERT INTO longdescs (bug_id, who, bug_when, isprivate, + my $sth_comment = $dbh->prepare( + 'INSERT INTO longdescs (bug_id, who, bug_when, isprivate, thetext, work_time) - VALUES (?, ?, ?, ?, ?, ?)'); - - foreach my $c (@sorted_descs) { - $sth_comment->execute($id, $c->{whoid} || $exporterid, $c->{bug_when}, - $c->{isprivate}, $c->{thetext}, 0); - } - $sth_comment->execute($id, $exporterid, $timestamp, 0, $comments, $worktime); - Bugzilla::Bug->new($id)->_sync_fulltext( new_bug => 1); - - # Add this bug to each group of which its product is a member. - my $sth_group = $dbh->prepare("INSERT INTO bug_group_map (bug_id, group_id) - VALUES (?, ?)"); - foreach my $group_id ( keys %{ $product->group_controls } ) { - if ($product->group_controls->{$group_id}->{'membercontrol'} != CONTROLMAPNA - && $product->group_controls->{$group_id}->{'othercontrol'} != CONTROLMAPNA){ - $sth_group->execute( $id, $group_id ); - } - } - - $log .= "Bug ${urlbase}show_bug.cgi?id=$bug_fields{'bug_id'} "; - $log .= "imported as bug $id.\n"; - $log .= Bugziilla->localconfig->{"urlbase"} . "show_bug.cgi?id=$id\n\n"; - if ($err) { - $log .= "The following problems were encountered while creating bug $id.\n"; - $log .= $err; - $log .= "You may have to set certain fields in the new bug by hand.\n\n"; + VALUES (?, ?, ?, ?, ?, ?)' + ); + + foreach my $c (@sorted_descs) { + $sth_comment->execute($id, $c->{whoid} || $exporterid, + $c->{bug_when}, $c->{isprivate}, $c->{thetext}, 0); + } + $sth_comment->execute($id, $exporterid, $timestamp, 0, $comments, $worktime); + Bugzilla::Bug->new($id)->_sync_fulltext(new_bug => 1); + + # Add this bug to each group of which its product is a member. + my $sth_group = $dbh->prepare( + "INSERT INTO bug_group_map (bug_id, group_id) + VALUES (?, ?)" + ); + foreach my $group_id (keys %{$product->group_controls}) { + if ( $product->group_controls->{$group_id}->{'membercontrol'} != CONTROLMAPNA + && $product->group_controls->{$group_id}->{'othercontrol'} != CONTROLMAPNA) + { + $sth_group->execute($id, $group_id); } - Debug( $log, OK_LEVEL ); - push(@logs, $log); - Bugzilla::BugMail::Send( $id, { 'changer' => $exporter } ) if ($mail); - - # done with the xml data. Lets clear it from memory - $twig->purge; + } + + $log .= "Bug ${urlbase}show_bug.cgi?id=$bug_fields{'bug_id'} "; + $log .= "imported as bug $id.\n"; + $log .= Bugziilla->localconfig->{"urlbase"} . "show_bug.cgi?id=$id\n\n"; + if ($err) { + $log .= "The following problems were encountered while creating bug $id.\n"; + $log .= $err; + $log .= "You may have to set certain fields in the new bug by hand.\n\n"; + } + Debug($log, OK_LEVEL); + push(@logs, $log); + Bugzilla::BugMail::Send($id, {'changer' => $exporter}) if ($mail); + + # done with the xml data. Lets clear it from memory + $twig->purge; } -Debug( "Reading xml", DEBUG_LEVEL ); +Debug("Reading xml", DEBUG_LEVEL); # Read STDIN in slurp mode. VERY dangerous, but we live on the wild side ;-) local ($/); @@ -1237,30 +1275,28 @@ $xml = <>; # If there's anything except whitespace before new; - $parser->output_to_core(1); - $parser->tmp_to_core(1); - my $entity = $parser->parse_data($xml); - my $bodyhandle = $entity->bodyhandle; - $xml = $bodyhandle->as_string; + # If the email was encoded (Mailer::MessageToMTA() does it when using UTF-8), + # we have to decode it first, else the XML parsing will fail. + my $parser = MIME::Parser->new; + $parser->output_to_core(1); + $parser->tmp_to_core(1); + my $entity = $parser->parse_data($xml); + my $bodyhandle = $entity->bodyhandle; + $xml = $bodyhandle->as_string; } # remove everything in file before xml header $xml =~ s/^.+(<\?xml version.+)$/$1/s; -Debug( "Parsing tree", DEBUG_LEVEL ); +Debug("Parsing tree", DEBUG_LEVEL); my $twig = XML::Twig->new( - twig_handlers => { - bug => \&process_bug, - attachment => \&process_attachment - }, - start_tag_handlers => { bugzilla => \&init } + twig_handlers => {bug => \&process_bug, attachment => \&process_attachment}, + start_tag_handlers => {bugzilla => \&init} ); + # Prevent DoS using the billion laughs attack. $twig->{NoExpand} = 1; @@ -1272,11 +1308,11 @@ my $urlbase = $root->{'att'}->{'urlbase'}; # It is time to email the result of the import. my $log = join("\n\n", @logs); -$log .= "\n\nImported $bugtotal bug(s) from $urlbase,\n sent by $exporter.\n"; -my $subject = "$bugtotal Bug(s) successfully moved from $urlbase to " - . Bugzilla->localconfig->{"urlbase"}; +$log .= "\n\nImported $bugtotal bug(s) from $urlbase,\n sent by $exporter.\n"; +my $subject = "$bugtotal Bug(s) successfully moved from $urlbase to " + . Bugzilla->localconfig->{"urlbase"}; my @to = ($exporter, $maintainer); -MailMessage( $subject, $log, @to ); +MailMessage($subject, $log, @to); __END__ -- cgit v1.2.3-24-g4f1b