2009-06-10 17:44:45 +04:00
|
|
|
#!/usr/bin/perl -wT
|
2011-10-04 16:02:49 +04:00
|
|
|
# Mass bug import/update from Excel/CSV files (4IntraNet Bug 42133)
|
|
|
|
# License: Dual-license GPL 3.0+ or MPL 1.1+
|
|
|
|
# Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
|
2009-06-10 17:44:45 +04:00
|
|
|
|
2009-06-10 19:49:28 +04:00
|
|
|
use utf8;
|
2009-06-15 17:39:14 +04:00
|
|
|
use Encode;
|
2009-06-10 17:44:45 +04:00
|
|
|
use strict;
|
|
|
|
use lib qw(. lib);
|
|
|
|
|
|
|
|
use Bugzilla;
|
|
|
|
use Bugzilla::Constants;
|
2009-09-08 16:21:52 +04:00
|
|
|
use Bugzilla::Product;
|
|
|
|
use Bugzilla::Component;
|
2009-06-10 17:44:45 +04:00
|
|
|
use Bugzilla::Util;
|
2010-09-27 20:20:16 +04:00
|
|
|
use Bugzilla::Token;
|
2009-06-10 17:44:45 +04:00
|
|
|
use Bugzilla::Error;
|
|
|
|
use Bugzilla::Bug;
|
2009-10-20 14:34:27 +04:00
|
|
|
use Bugzilla::BugMail;
|
2009-06-10 17:44:45 +04:00
|
|
|
use Bugzilla::User;
|
|
|
|
|
2013-05-30 19:19:30 +04:00
|
|
|
use IO::File;
|
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# Also loaded on demand: Spreadsheet::ParseExcel, Spreadsheet::XSLX
|
2009-06-10 17:44:45 +04:00
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# Constants
|
2009-06-10 17:44:45 +04:00
|
|
|
use constant BUG_DAYS => 92;
|
2013-05-30 17:16:30 +04:00
|
|
|
use constant XLS_LISTNAME => '';
|
2010-10-14 18:04:47 +04:00
|
|
|
use constant MANDATORY_FIELDS => qw(short_desc product component);
|
2009-06-10 17:44:45 +04:00
|
|
|
|
|
|
|
my $user = Bugzilla->login(LOGIN_REQUIRED);
|
|
|
|
my $cgi = Bugzilla->cgi;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $template = Bugzilla->template;
|
|
|
|
my $vars = {};
|
|
|
|
|
2014-02-17 16:26:55 +04:00
|
|
|
# TODO: Check if CGI does utf8, use just { Vars } instead of decoding
|
2009-06-15 17:39:14 +04:00
|
|
|
my $args = {};
|
|
|
|
for ($cgi->param)
|
|
|
|
{
|
|
|
|
my $v = $_;
|
|
|
|
utf8::decode($v) unless Encode::is_utf8($v);
|
2009-09-08 17:59:34 +04:00
|
|
|
if ($v eq 'bug_id')
|
|
|
|
{
|
|
|
|
$args->{$v} = [ $cgi->param($_) ];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$args->{$v} = $cgi->param($_);
|
|
|
|
}
|
2014-02-17 16:26:55 +04:00
|
|
|
utf8::decode($args->{$v}) unless ref $args->{$v} || Encode::is_utf8($args->{$v});
|
2009-06-15 17:39:14 +04:00
|
|
|
}
|
|
|
|
|
2014-02-17 16:26:55 +04:00
|
|
|
# Check permissions
|
2009-06-10 17:44:45 +04:00
|
|
|
$user->in_group('importxls') ||
|
|
|
|
ThrowUserError('auth_failure', {
|
|
|
|
group => 'importxls',
|
|
|
|
action => 'import',
|
|
|
|
object => 'bugs',
|
|
|
|
});
|
|
|
|
|
2009-06-10 19:49:28 +04:00
|
|
|
my $listname = $cgi->param('listname') || '';
|
2009-06-15 17:39:14 +04:00
|
|
|
my $bugdays = $cgi->param('bugdays') || '';
|
|
|
|
($bugdays) = $bugdays =~ /^(\d+)$/so;
|
2009-06-10 17:44:45 +04:00
|
|
|
$bugdays ||= BUG_DAYS;
|
|
|
|
trick_taint($listname);
|
|
|
|
trick_taint($bugdays);
|
|
|
|
$vars->{listname} = $listname || XLS_LISTNAME;
|
|
|
|
$vars->{bugdays} = $bugdays;
|
|
|
|
|
|
|
|
my $upload;
|
2009-06-15 17:39:14 +04:00
|
|
|
my $name_tr = {};
|
|
|
|
my $bug_tpl = {};
|
|
|
|
|
2014-02-17 16:26:55 +04:00
|
|
|
$bug_tpl->{platform} = Bugzilla->params->{defaultplatform}
|
|
|
|
if Bugzilla->params->{defaultplatform} && Bugzilla->params->{useplatform};
|
2009-09-08 16:21:52 +04:00
|
|
|
|
2009-06-15 17:39:14 +04:00
|
|
|
for (keys %$args)
|
|
|
|
{
|
|
|
|
if (/^f_/so && $args->{$_})
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Default field values for bugs
|
2009-06-15 17:39:14 +04:00
|
|
|
$bug_tpl->{$'} = $args->{$_};
|
|
|
|
}
|
2010-06-24 18:36:50 +04:00
|
|
|
elsif (/^t_/so && $args->{$_} ne $')
|
2009-06-15 17:39:14 +04:00
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Field mapping
|
2009-06-15 17:39:14 +04:00
|
|
|
$name_tr->{$'} = $args->{$_};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$vars->{bug_tpl} = $bug_tpl;
|
|
|
|
$vars->{name_tr} = $name_tr;
|
|
|
|
|
2011-08-02 14:56:32 +04:00
|
|
|
my $field_descs = { map { $_->name => $_->description } Bugzilla->get_fields({ obsolete => 0 }) };
|
|
|
|
$field_descs->{platform} = $field_descs->{rep_platform} if $field_descs->{rep_platform};
|
2009-09-22 15:40:49 +04:00
|
|
|
$field_descs->{comment} = $field_descs->{longdesc};
|
2011-08-02 14:56:32 +04:00
|
|
|
for ((grep { /\./ } keys %$field_descs), (qw/rep_platform longdesc bug_group changeddate commenter content opendate
|
|
|
|
creation_ts delta_ts days_elapsed everconfirmed percentage_complete owner_idle_time work_time/))
|
2009-09-22 15:40:49 +04:00
|
|
|
{
|
|
|
|
delete $field_descs->{$_};
|
|
|
|
}
|
2013-06-17 19:14:36 +04:00
|
|
|
|
2009-09-22 15:40:49 +04:00
|
|
|
$vars->{import_field_descs} = $field_descs;
|
2011-08-02 14:56:32 +04:00
|
|
|
$vars->{import_fields} = [ sort { $field_descs->{$a} cmp $field_descs->{$b} } keys %$field_descs ];
|
2009-09-22 15:40:49 +04:00
|
|
|
|
|
|
|
my $guess_field_descs = [
|
|
|
|
map { $_ => $field_descs->{$_} }
|
|
|
|
sort { length($field_descs->{$b}) <=> length($field_descs->{$a}) }
|
|
|
|
keys %$field_descs
|
|
|
|
];
|
|
|
|
|
2014-02-17 16:26:55 +04:00
|
|
|
# Field guesser
|
2009-09-22 15:40:49 +04:00
|
|
|
sub guess_field_name
|
2009-09-08 16:21:52 +04:00
|
|
|
{
|
2009-09-22 18:09:06 +04:00
|
|
|
my ($name, $guess_field_descs) = @_;
|
2010-09-27 20:20:16 +04:00
|
|
|
my ($r, $k, $v);
|
2009-09-22 15:40:49 +04:00
|
|
|
for (my $i = 0; $i < @$guess_field_descs; $i+=2)
|
2009-09-08 16:21:52 +04:00
|
|
|
{
|
2010-09-27 20:20:16 +04:00
|
|
|
($k, $v) = ($guess_field_descs->[$i], $guess_field_descs->[$i+1]);
|
2010-09-27 21:16:26 +04:00
|
|
|
($r = $k), last if $name =~ /\Q$v\E/is || $name eq $k;
|
2009-09-08 16:21:52 +04:00
|
|
|
}
|
2010-09-27 20:20:16 +04:00
|
|
|
return $r;
|
2009-09-22 15:40:49 +04:00
|
|
|
}
|
2009-09-08 16:21:52 +04:00
|
|
|
|
2009-06-15 17:39:14 +04:00
|
|
|
unless ($args->{commit})
|
2009-06-10 17:44:45 +04:00
|
|
|
{
|
2013-05-30 19:19:30 +04:00
|
|
|
unless (my $upload = $cgi->param('xls'))
|
2009-06-10 17:44:45 +04:00
|
|
|
{
|
2009-06-15 17:39:14 +04:00
|
|
|
if (!defined $args->{result})
|
2009-06-10 17:44:45 +04:00
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Show file upload form
|
2009-06-10 17:44:45 +04:00
|
|
|
$vars->{form} = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Show import result
|
2009-06-10 17:44:45 +04:00
|
|
|
$vars->{show_result} = 1;
|
2009-06-15 17:39:14 +04:00
|
|
|
$vars->{result} = $args->{result};
|
2009-09-08 16:21:52 +04:00
|
|
|
$vars->{bug_id} = $args->{bug_id};
|
2009-06-15 17:39:14 +04:00
|
|
|
my $newcgi = new Bugzilla::CGI({
|
|
|
|
listname => $listname,
|
|
|
|
bugdays => $bugdays,
|
|
|
|
(map { ("f_$_" => $bug_tpl->{$_}) } keys %$bug_tpl),
|
|
|
|
(map { ("t_$_" => $name_tr->{$_}) } keys %$name_tr),
|
|
|
|
});
|
|
|
|
$vars->{importnext} = 'importxls.cgi?'.$newcgi->query_string;
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Show parsed spreadsheet with checkboxes for selection
|
2010-09-27 20:20:16 +04:00
|
|
|
my $table;
|
|
|
|
if ($args->{xls} !~ /\.(xlsx?)$/iso)
|
|
|
|
{
|
|
|
|
# CSV
|
2013-05-30 19:19:30 +04:00
|
|
|
$table = parse_csv($upload, $args->{xls}, $name_tr, $args->{csv_delimiter});
|
2010-09-27 20:20:16 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-05-30 19:19:30 +04:00
|
|
|
$table = parse_excel($upload, $args->{xls}, $listname, $name_tr);
|
2010-09-27 20:20:16 +04:00
|
|
|
}
|
2009-06-10 17:44:45 +04:00
|
|
|
if (!$table || $table->{error})
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Parse error
|
2009-06-10 17:44:45 +04:00
|
|
|
$vars->{show_error} = 1;
|
|
|
|
$vars->{error} = $table->{error} if $table;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
my $i = 0;
|
|
|
|
my $sth = $dbh->prepare("SELECT COUNT(*) FROM `bugs` WHERE `short_desc`=? AND `delta_ts`>=DATE_SUB(CURDATE(),INTERVAL ? DAY)");
|
2009-06-10 19:49:28 +04:00
|
|
|
for my $bug (@{$table->{data}})
|
2009-06-10 17:44:45 +04:00
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Check if this bug is already added
|
2009-06-10 19:49:28 +04:00
|
|
|
if ($bug->{short_desc})
|
|
|
|
{
|
|
|
|
trick_taint($bug->{short_desc});
|
|
|
|
$sth->execute($bug->{short_desc}, $bugdays);
|
|
|
|
($bug->{enabled}) = $sth->fetchrow_array;
|
|
|
|
$bug->{enabled} = !$bug->{enabled};
|
|
|
|
}
|
|
|
|
$bug->{num} = ++$i;
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
2011-10-04 16:02:49 +04:00
|
|
|
# Guess fields based on their names
|
2009-09-22 15:40:49 +04:00
|
|
|
my $g;
|
|
|
|
for (@{$table->{fields}})
|
|
|
|
{
|
2010-06-24 18:36:50 +04:00
|
|
|
if (!exists $name_tr->{$_} && ($g = guess_field_name($_, $guess_field_descs)))
|
2009-09-22 15:40:49 +04:00
|
|
|
{
|
|
|
|
$name_tr->{$_} = $g;
|
|
|
|
}
|
|
|
|
}
|
2011-10-04 16:02:49 +04:00
|
|
|
# Show bug table
|
2009-06-10 17:44:45 +04:00
|
|
|
$vars->{fields} = $table->{fields};
|
|
|
|
$vars->{data} = $table->{data};
|
2012-02-16 16:45:59 +04:00
|
|
|
$vars->{token} = issue_session_token('importxls');
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$template->process("bug/import/importxls.html.tmpl", $vars)
|
|
|
|
|| ThrowTemplateError($template->error());
|
2011-10-04 16:02:49 +04:00
|
|
|
exit;
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-02-16 16:45:59 +04:00
|
|
|
check_token_data($args->{token}, 'importxls', 'importxls.cgi');
|
2011-10-04 16:02:49 +04:00
|
|
|
# Run import and redirect to result page
|
2009-06-10 17:44:45 +04:00
|
|
|
my $bugs = {};
|
2009-06-15 17:39:14 +04:00
|
|
|
for (keys %$args)
|
2009-06-10 17:44:45 +04:00
|
|
|
{
|
|
|
|
if (/^b_(.*?)_(\d+)$/so)
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# bug fields
|
2010-06-24 18:36:50 +04:00
|
|
|
$bugs->{$2}->{(exists $name_tr->{$1} ? $name_tr->{$1} : $1)} = $args->{$_};
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
my $r = 0;
|
2009-06-10 20:19:22 +04:00
|
|
|
my $ids = [];
|
|
|
|
my $f = 0;
|
2010-09-27 20:20:16 +04:00
|
|
|
my $bugmail = [];
|
2009-06-10 20:19:22 +04:00
|
|
|
Bugzilla->dbh->bz_start_transaction;
|
2013-07-22 14:20:14 +04:00
|
|
|
my $custom_fields = {};
|
|
|
|
foreach my $field (Bugzilla->get_fields({custom => 1}))
|
|
|
|
{
|
|
|
|
$custom_fields->{ $field->{name} } = $field;
|
|
|
|
}
|
2009-11-16 18:51:23 +03:00
|
|
|
for my $bug (@$bugs{sort {$a <=> $b} keys %$bugs})
|
2009-06-10 17:44:45 +04:00
|
|
|
{
|
2009-06-15 17:39:14 +04:00
|
|
|
$bug->{$_} ||= $bug_tpl->{$_} for keys %$bug_tpl;
|
2013-07-23 11:48:05 +04:00
|
|
|
|
|
|
|
# Set default value if value is not set
|
2014-05-13 18:27:27 +04:00
|
|
|
foreach my $field (keys %$custom_fields)
|
2013-07-22 14:20:14 +04:00
|
|
|
{
|
|
|
|
next if !$field || $bug->{$field};
|
|
|
|
my $control_field = $custom_fields->{$field}->value_field;
|
2013-07-23 11:48:05 +04:00
|
|
|
next unless $bug->{ $control_field->{name} };
|
|
|
|
$bug->{$field} = $custom_fields->{$field}->get_default_values( $bug->{ $control_field->{name} } );
|
2013-07-22 14:20:14 +04:00
|
|
|
}
|
2013-07-23 11:48:05 +04:00
|
|
|
|
2009-06-10 20:19:22 +04:00
|
|
|
if ($bug->{enabled})
|
|
|
|
{
|
2010-09-27 20:20:16 +04:00
|
|
|
my $id;
|
|
|
|
if ($bug->{bug_id} && Bugzilla::Bug->new($bug->{bug_id}))
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# If bug with this same ID exists - update it
|
2010-10-22 20:57:14 +04:00
|
|
|
$id = process_bug($bug, $bugmail, $vars);
|
2010-09-27 20:20:16 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Else post new bug
|
2010-10-14 15:05:19 +04:00
|
|
|
$id = post_bug($bug, $bugmail, $vars);
|
2010-09-27 20:20:16 +04:00
|
|
|
}
|
2009-06-10 20:19:22 +04:00
|
|
|
if ($id)
|
|
|
|
{
|
|
|
|
$r++;
|
|
|
|
push @$ids, $id;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2010-03-15 15:32:43 +03:00
|
|
|
Bugzilla->dbh->bz_in_transaction and Bugzilla->dbh->bz_rollback_transaction;
|
2009-06-10 20:19:22 +04:00
|
|
|
$f = 1;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unless ($f)
|
|
|
|
{
|
2009-06-15 17:39:14 +04:00
|
|
|
my $newcgi = new Bugzilla::CGI({
|
|
|
|
result => $r,
|
2009-09-08 16:21:52 +04:00
|
|
|
bug_id => $ids,
|
2009-06-15 17:39:14 +04:00
|
|
|
listname => $listname,
|
|
|
|
bugdays => $bugdays,
|
|
|
|
(map { ("f_$_" => $bug_tpl->{$_}) } keys %$bug_tpl),
|
|
|
|
(map { ("t_$_" => $name_tr->{$_}) } keys %$name_tr),
|
|
|
|
});
|
2011-10-04 16:02:49 +04:00
|
|
|
# Send bugmail only after successful completion
|
2010-09-29 22:23:19 +04:00
|
|
|
Bugzilla->cgi->delete('dontsendbugmail');
|
2010-09-27 20:20:16 +04:00
|
|
|
send_results($_) for @$bugmail;
|
2009-06-10 20:19:22 +04:00
|
|
|
Bugzilla->dbh->bz_commit_transaction;
|
2009-06-15 17:39:14 +04:00
|
|
|
print $cgi->redirect(-location => 'importxls.cgi?'.$newcgi->query_string);
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
2011-10-04 16:02:49 +04:00
|
|
|
exit;
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# CSV file reader
|
2010-09-29 18:01:08 +04:00
|
|
|
# Multiline CSV compatible!
|
|
|
|
sub csv_read_record
|
|
|
|
{
|
|
|
|
my ($fh, $enc, $s, $q) = @_;
|
|
|
|
$q ||= '"';
|
|
|
|
$s ||= ',';
|
|
|
|
my $re_field = qr/^\s*(?:$q((?:[^$q]|$q$q)*)$q|([^$q$s]*))\s*($s)?/s;
|
|
|
|
my @parts = ();
|
|
|
|
my $line = "";
|
|
|
|
my $num_lines = 0;
|
|
|
|
my $l;
|
|
|
|
my $i;
|
|
|
|
while (<$fh>)
|
|
|
|
{
|
|
|
|
trick_taint($_);
|
|
|
|
$l = $_;
|
|
|
|
if ($enc && $enc ne 'utf-8')
|
|
|
|
{
|
|
|
|
Encode::from_to($l, $enc, 'utf-8');
|
|
|
|
}
|
|
|
|
Encode::_utf8_on($l);
|
|
|
|
$line .= $l;
|
|
|
|
while ($line =~ s/$re_field//)
|
|
|
|
{
|
|
|
|
$l = $1 || $2;
|
|
|
|
$l =~ s/$q$q/$q/gs;
|
|
|
|
push @parts, $l;
|
|
|
|
return \@parts if !$3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (length $line)
|
|
|
|
{
|
|
|
|
warn "eol before last field end\n";
|
|
|
|
warn "-->$line<--\n";
|
|
|
|
}
|
|
|
|
return @parts ? \@parts : undef;
|
|
|
|
}
|
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# Parse Excel file, or call parse_csv for CSV file
|
2009-06-10 17:44:45 +04:00
|
|
|
sub parse_excel
|
|
|
|
{
|
2013-05-30 19:19:30 +04:00
|
|
|
my ($fd, $name, $only_list, $name_tr) = @_;
|
2009-06-10 17:44:45 +04:00
|
|
|
my $xls;
|
|
|
|
if ($name =~ /\.xlsx$/iso)
|
|
|
|
{
|
|
|
|
# OOXML
|
|
|
|
require Spreadsheet::XLSX;
|
2013-05-30 19:23:39 +04:00
|
|
|
$xls = Spreadsheet::XLSX->new(IO::File->new_from_fd(fileno $fd, 'r'));
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
2009-11-26 18:20:26 +03:00
|
|
|
elsif ($name =~ /\.xls$/iso)
|
2009-06-10 17:44:45 +04:00
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Excel binary
|
2009-06-10 17:44:45 +04:00
|
|
|
require Spreadsheet::ParseExcel;
|
2013-05-30 17:16:30 +04:00
|
|
|
$xls = Spreadsheet::ParseExcel->new;
|
2013-05-30 19:19:30 +04:00
|
|
|
$xls = ($xls->parse($fd) or return { error => $xls->error });
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
return { error => 'parse_error' } unless $xls;
|
|
|
|
my $r = { data => [] };
|
|
|
|
for my $page ($xls->worksheets())
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Just select one sheet?
|
2009-06-10 17:44:45 +04:00
|
|
|
next if $only_list && $page->{Name} ne $only_list;
|
|
|
|
my ($row_min, $row_max) = $page->row_range;
|
|
|
|
my ($col_min, $col_max) = $page->col_range;
|
|
|
|
my $head = get_row($page, $row_min, $col_min, $col_max);
|
2009-09-22 15:40:49 +04:00
|
|
|
$r->{fields} ||= $head;
|
2011-10-04 16:02:49 +04:00
|
|
|
# Handle the table
|
2009-06-10 17:44:45 +04:00
|
|
|
for my $row (($row_min+1) .. $row_max)
|
|
|
|
{
|
|
|
|
$row = get_row($page, $row, $col_min, $col_max) || next;
|
|
|
|
$row = { map { ($head->[$_] => $row->[$_]) } (0..$#$head) };
|
|
|
|
push @{$r->{data}}, $row;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return { error => 'empty' } unless @{$r->{data}};
|
|
|
|
return $r;
|
2009-11-26 18:20:26 +03:00
|
|
|
}
|
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# Parse CSV file
|
2009-11-26 18:20:26 +03:00
|
|
|
sub parse_csv
|
|
|
|
{
|
2013-05-30 19:19:30 +04:00
|
|
|
my ($fd, $name, $name_tr, $delimiter) = @_;
|
2010-09-29 18:01:08 +04:00
|
|
|
my $s = Bugzilla->user->settings->{csv_charset};
|
2009-11-26 18:20:26 +03:00
|
|
|
my $r = { data => [] };
|
|
|
|
my $row;
|
2013-05-30 17:16:30 +04:00
|
|
|
while ($row = csv_read_record($fd, $s && $s->{value}, $delimiter))
|
2009-11-26 18:20:26 +03:00
|
|
|
{
|
2010-09-29 18:01:08 +04:00
|
|
|
if (!$r->{fields})
|
2009-11-26 18:20:26 +03:00
|
|
|
{
|
2010-10-14 14:52:21 +04:00
|
|
|
$_ = trim($_) for @$row;
|
2010-09-29 18:01:08 +04:00
|
|
|
$r->{fields} = $row;
|
2009-11-26 18:20:26 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2010-09-29 18:01:08 +04:00
|
|
|
$row = { map { ($r->{fields}->[$_] => $row->[$_]) } (0..$#{$r->{fields}}) };
|
|
|
|
push @{$r->{data}}, $row;
|
2009-11-26 18:20:26 +03:00
|
|
|
}
|
|
|
|
}
|
2010-09-29 18:01:08 +04:00
|
|
|
return { error => 'parse_error' } if !$r->{fields} || !@{$r->{fields}};
|
2009-11-26 18:20:26 +03:00
|
|
|
return $r;
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# Extract row from Excel
|
2009-06-10 17:44:45 +04:00
|
|
|
sub get_row
|
|
|
|
{
|
|
|
|
my ($page, $row, $col_min, $col_max) = @_;
|
2009-12-04 22:13:14 +03:00
|
|
|
return [ map {
|
|
|
|
$_ = $page->get_cell($row, $_);
|
|
|
|
$_ = $_ ? $_->value : '';
|
|
|
|
Encode::_utf8_on($_);
|
2010-03-04 15:31:41 +03:00
|
|
|
tr/‒–—/---/;
|
2010-03-04 15:39:53 +03:00
|
|
|
tr/―‑/--/;
|
2009-12-04 22:13:14 +03:00
|
|
|
trim($_);
|
|
|
|
} ($col_min .. $col_max) ];
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# TODO remove duplicate post_bug and process_bug code from here,
|
|
|
|
# their .cgi and email_in.pl/importxml.pl versions, and move to Bugzilla::Bug
|
2010-09-27 20:20:16 +04:00
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# Add a bug
|
2009-06-10 17:44:45 +04:00
|
|
|
sub post_bug
|
|
|
|
{
|
2010-10-14 15:05:19 +04:00
|
|
|
my ($fields_in, $bugmail, $vars) = @_;
|
2009-06-10 17:44:45 +04:00
|
|
|
my $cgi = Bugzilla->cgi;
|
2011-10-04 16:02:49 +04:00
|
|
|
# FIXME mandatory fields check should be moved somewhere
|
2010-10-14 15:05:19 +04:00
|
|
|
my @unexist;
|
2010-10-14 18:04:47 +04:00
|
|
|
for (MANDATORY_FIELDS)
|
2010-10-14 15:05:19 +04:00
|
|
|
{
|
|
|
|
if (!exists $fields_in->{$_})
|
|
|
|
{
|
|
|
|
push @unexist, $vars->{import_field_descs}->{$_};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (@unexist)
|
|
|
|
{
|
|
|
|
ThrowUserError('import_fields_mandatory', { fields => \@unexist });
|
|
|
|
}
|
2013-06-17 19:14:36 +04:00
|
|
|
|
2011-10-04 16:02:49 +04:00
|
|
|
# Simulate email usage with browser error mode
|
2009-06-10 19:49:28 +04:00
|
|
|
my $um = Bugzilla->usage_mode;
|
|
|
|
Bugzilla->usage_mode(USAGE_MODE_EMAIL);
|
2009-06-10 20:19:22 +04:00
|
|
|
Bugzilla->error_mode(ERROR_MODE_WEBPAGE);
|
2010-03-04 15:31:41 +03:00
|
|
|
$Bugzilla::Error::IN_EVAL++;
|
2010-09-27 20:20:16 +04:00
|
|
|
my $product = eval { Bugzilla::Product->check({ name => $fields_in->{product} }) };
|
|
|
|
if (!$product)
|
|
|
|
{
|
|
|
|
return undef;
|
|
|
|
}
|
2011-10-04 16:02:49 +04:00
|
|
|
# Add default product groups
|
2010-09-27 20:20:16 +04:00
|
|
|
my @gids;
|
|
|
|
my $controls = $product->group_controls;
|
|
|
|
foreach my $gid (keys %$controls)
|
|
|
|
{
|
|
|
|
if ($controls->{$gid}->{membercontrol} == CONTROLMAPDEFAULT && Bugzilla->user->in_group_id($gid) ||
|
|
|
|
$controls->{$gid}->{othercontrol} == CONTROLMAPDEFAULT && !Bugzilla->user->in_group_id($gid))
|
|
|
|
{
|
|
|
|
$fields_in->{"bit-$gid"} = 1;
|
|
|
|
}
|
|
|
|
}
|
2009-09-08 16:21:52 +04:00
|
|
|
unless ($fields_in->{version})
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# Guess version
|
2010-09-27 20:20:16 +04:00
|
|
|
my $component;
|
2009-09-08 16:21:52 +04:00
|
|
|
eval
|
|
|
|
{
|
2010-06-24 18:36:50 +04:00
|
|
|
$component = Bugzilla::Component->new({
|
2009-09-08 16:21:52 +04:00
|
|
|
product => $product,
|
|
|
|
name => $fields_in->{component},
|
|
|
|
});
|
|
|
|
};
|
2011-10-04 16:02:49 +04:00
|
|
|
# If there is no default version in the component:
|
2014-06-05 19:24:13 +04:00
|
|
|
if (!$component || !($fields_in->{version} = ($component->default_version && $component->default_version_obj->name)))
|
2009-09-08 16:21:52 +04:00
|
|
|
{
|
|
|
|
my $vers = [ map ($_->name, @{$product->versions}) ];
|
|
|
|
my $v;
|
|
|
|
if (($v = $cgi->cookie("VERSION-" . $product->name)) &&
|
2014-06-05 19:24:13 +04:00
|
|
|
grep { $_ eq $v } @$vers)
|
2009-09-08 16:21:52 +04:00
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# get from cookie
|
2009-09-08 16:21:52 +04:00
|
|
|
$fields_in->{version} = $v;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2011-10-04 16:02:49 +04:00
|
|
|
# or just the last one, like in enter_bug.cgi
|
2009-09-08 16:21:52 +04:00
|
|
|
$fields_in->{version} = $vers->[$#$vers];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-10-04 16:02:49 +04:00
|
|
|
# Push params to $cgi
|
2009-09-08 16:21:52 +04:00
|
|
|
foreach my $field (keys %$fields_in)
|
|
|
|
{
|
|
|
|
$cgi->param(-name => $field, -value => $fields_in->{$field});
|
|
|
|
}
|
2012-02-16 16:45:59 +04:00
|
|
|
$cgi->param(dontsendbugmail => 1);
|
|
|
|
$cgi->param(token => issue_session_token('createbug:'));
|
2014-05-21 17:05:04 +04:00
|
|
|
delete $cgi->{_VarHash};
|
2011-10-04 16:02:49 +04:00
|
|
|
# Call post_bug.cgi
|
2010-05-18 15:42:56 +04:00
|
|
|
my $vars_out = do 'post_bug.cgi';
|
2010-03-04 15:31:41 +03:00
|
|
|
$Bugzilla::Error::IN_EVAL--;
|
2009-06-10 19:49:28 +04:00
|
|
|
Bugzilla->usage_mode($um);
|
2010-05-18 15:42:56 +04:00
|
|
|
if ($vars_out)
|
|
|
|
{
|
|
|
|
my $bug_id = $vars_out->{bug}->id;
|
2010-09-27 20:20:16 +04:00
|
|
|
push @$bugmail, @{$vars_out->{sentmail}};
|
|
|
|
return $bug_id;
|
|
|
|
}
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub process_bug
|
|
|
|
{
|
2010-10-22 20:57:14 +04:00
|
|
|
my ($fields_in, $bugmail, $vars) = @_;
|
2010-09-27 20:20:16 +04:00
|
|
|
|
|
|
|
my $um = Bugzilla->usage_mode;
|
|
|
|
Bugzilla->usage_mode(USAGE_MODE_EMAIL);
|
|
|
|
Bugzilla->error_mode(ERROR_MODE_WEBPAGE);
|
|
|
|
Bugzilla->cgi->param(-name => 'dontsendbugmail', -value => 1);
|
|
|
|
|
|
|
|
my %fields = %$fields_in;
|
|
|
|
|
2012-02-16 16:45:59 +04:00
|
|
|
my $bug_id = delete $fields{'bug_id'};
|
2010-09-27 20:20:16 +04:00
|
|
|
$fields{'id'} = $bug_id;
|
|
|
|
|
|
|
|
my $bug = Bugzilla::Bug->check($bug_id);
|
|
|
|
|
2011-01-31 15:11:49 +03:00
|
|
|
# process_bug.cgi always "tries to set" these fields
|
|
|
|
$fields{$_} ||= $bug->$_ for qw(product component target_milestone version);
|
|
|
|
|
2010-10-04 16:21:17 +04:00
|
|
|
if (exists $fields{blocked} || exists $fields{dependson})
|
2010-10-04 16:15:10 +04:00
|
|
|
{
|
|
|
|
$fields{blocked} ||= join ',', @{ $bug->blocked };
|
|
|
|
$fields{dependson} ||= join ',', @{ $bug->dependson };
|
|
|
|
}
|
2010-09-27 20:20:16 +04:00
|
|
|
|
|
|
|
if ($fields{'bug_status'}) {
|
|
|
|
$fields{'knob'} = $fields{'bug_status'};
|
|
|
|
}
|
|
|
|
# If no status is given, then we only want to change the resolution.
|
|
|
|
elsif ($fields{'resolution'}) {
|
|
|
|
$fields{'knob'} = 'change_resolution';
|
|
|
|
$fields{'resolution_knob_change_resolution'} = $fields{'resolution'};
|
|
|
|
}
|
|
|
|
if ($fields{'dup_id'}) {
|
|
|
|
$fields{'knob'} = 'duplicate';
|
|
|
|
}
|
|
|
|
|
|
|
|
# Move @cc to @newcc as @cc is used by process_bug.cgi to remove
|
|
|
|
# users from the CC list when @removecc is set.
|
|
|
|
$fields{newcc} = delete $fields{cc} if $fields{cc};
|
|
|
|
|
|
|
|
# Make it possible to remove CCs.
|
|
|
|
if ($fields{'removecc'}) {
|
|
|
|
$fields{'cc'} = [split(',', $fields{'removecc'})];
|
|
|
|
$fields{'removecc'} = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $cgi = Bugzilla->cgi;
|
|
|
|
foreach my $field (keys %fields) {
|
|
|
|
$cgi->param(-name => $field, -value => $fields{$field});
|
|
|
|
}
|
|
|
|
$cgi->param('longdesclength', scalar @{ $bug->comments });
|
|
|
|
$cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
|
|
|
|
|
2014-05-21 17:05:04 +04:00
|
|
|
delete $cgi->{_VarHash};
|
2010-10-22 20:57:14 +04:00
|
|
|
# FIXME All this is an ugly hack. Bug::update() should call anything needed, not process_bug.cgi
|
2010-09-27 20:20:16 +04:00
|
|
|
$Bugzilla::Error::IN_EVAL++;
|
|
|
|
my $vars_out = do 'process_bug.cgi';
|
|
|
|
$Bugzilla::Error::IN_EVAL--;
|
|
|
|
Bugzilla->usage_mode($um);
|
|
|
|
|
|
|
|
if ($vars_out)
|
|
|
|
{
|
|
|
|
push @$bugmail, @{$vars_out->{sentmail}};
|
2010-05-18 15:42:56 +04:00
|
|
|
return $bug_id;
|
|
|
|
}
|
|
|
|
return undef;
|
2009-06-10 17:44:45 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
__END__
|