Compare commits
No commits in common. "beta" and "v2016.09" have entirely different histories.
130
Bugzilla/Bug.pm
130
Bugzilla/Bug.pm
|
@ -106,13 +106,8 @@ sub DB_COLUMNS
|
||||||
# FIXME kill op_sys and rep_platform completely, make them custom fields
|
# FIXME kill op_sys and rep_platform completely, make them custom fields
|
||||||
push @columns, 'op_sys' if Bugzilla->get_field('op_sys')->enabled;
|
push @columns, 'op_sys' if Bugzilla->get_field('op_sys')->enabled;
|
||||||
push @columns, 'rep_platform' if Bugzilla->get_field('rep_platform')->enabled;
|
push @columns, 'rep_platform' if Bugzilla->get_field('rep_platform')->enabled;
|
||||||
push @columns,
|
push @columns, map { $_->name }
|
||||||
map { $_->name }
|
grep { $_->type != FIELD_TYPE_MULTI_SELECT && $_->type != FIELD_TYPE_BUG_ID_REV }
|
||||||
grep {
|
|
||||||
$_->type != FIELD_TYPE_MULTI_SELECT &&
|
|
||||||
$_->type != FIELD_TYPE_EAV_TEXTAREA &&
|
|
||||||
$_->type != FIELD_TYPE_BUG_ID_REV
|
|
||||||
}
|
|
||||||
Bugzilla->active_custom_fields;
|
Bugzilla->active_custom_fields;
|
||||||
|
|
||||||
Bugzilla::Hook::process('bug_columns', { columns => \@columns });
|
Bugzilla::Hook::process('bug_columns', { columns => \@columns });
|
||||||
|
@ -167,7 +162,6 @@ use constant CUSTOM_FIELD_VALIDATORS => {
|
||||||
FIELD_TYPE_BUG_ID_REV() => \&_set_bugid_rev_field,
|
FIELD_TYPE_BUG_ID_REV() => \&_set_bugid_rev_field,
|
||||||
FIELD_TYPE_BUG_URLS() => \&_set_default_field,
|
FIELD_TYPE_BUG_URLS() => \&_set_default_field,
|
||||||
FIELD_TYPE_NUMERIC() => \&_set_numeric_field,
|
FIELD_TYPE_NUMERIC() => \&_set_numeric_field,
|
||||||
FIELD_TYPE_EAV_TEXTAREA() => \&_set_default_field,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sub SETTERS
|
sub SETTERS
|
||||||
|
@ -359,17 +353,6 @@ sub check_exists
|
||||||
return $self;
|
return $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub check_for_edit
|
|
||||||
{
|
|
||||||
my $class = shift;
|
|
||||||
my $bug = $class->check(@_);
|
|
||||||
|
|
||||||
Bugzilla->user->can_edit_product($bug->product_id)
|
|
||||||
|| ThrowUserError("product_edit_denied", { product => $bug->product });
|
|
||||||
|
|
||||||
return $bug;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if a bug exists and is visible for the current user or throw an error
|
# Check if a bug exists and is visible for the current user or throw an error
|
||||||
sub check
|
sub check
|
||||||
{
|
{
|
||||||
|
@ -482,8 +465,6 @@ sub create
|
||||||
sub update
|
sub update
|
||||||
{
|
{
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($delta_ts, $additional_changes) = @_;
|
|
||||||
|
|
||||||
$self->make_dirty;
|
$self->make_dirty;
|
||||||
|
|
||||||
my $method = $self->id ? 'update' : 'create';
|
my $method = $self->id ? 'update' : 'create';
|
||||||
|
@ -495,7 +476,8 @@ sub update
|
||||||
|
|
||||||
my $dbh = Bugzilla->dbh;
|
my $dbh = Bugzilla->dbh;
|
||||||
my $user = Bugzilla->user;
|
my $user = Bugzilla->user;
|
||||||
$delta_ts = $delta_ts || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
|
# FIXME 'shift ||' is just a temporary hack until all updating happens inside this function
|
||||||
|
my $delta_ts = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
|
||||||
|
|
||||||
# You can't set these fields by hand
|
# You can't set these fields by hand
|
||||||
$self->{deadline} =~ s/\s+.*$//so;
|
$self->{deadline} =~ s/\s+.*$//so;
|
||||||
|
@ -582,9 +564,6 @@ sub update
|
||||||
# Insert the values into the multiselect value tables
|
# Insert the values into the multiselect value tables
|
||||||
$self->save_multiselects($changes);
|
$self->save_multiselects($changes);
|
||||||
|
|
||||||
# Insert the values into satellite value tables
|
|
||||||
$self->save_eavs($changes);
|
|
||||||
|
|
||||||
# Save reversed bug_id fields
|
# Save reversed bug_id fields
|
||||||
$self->save_reverse_bugid_fields($changes);
|
$self->save_reverse_bugid_fields($changes);
|
||||||
|
|
||||||
|
@ -628,10 +607,6 @@ sub update
|
||||||
$self->prepare_mail_results($changes);
|
$self->prepare_mail_results($changes);
|
||||||
|
|
||||||
# Remove obsolete internal variables.
|
# Remove obsolete internal variables.
|
||||||
if ($additional_changes)
|
|
||||||
{
|
|
||||||
$additional_changes->{added_comments} = $self->{added_comments};
|
|
||||||
}
|
|
||||||
delete $self->{_old_self};
|
delete $self->{_old_self};
|
||||||
delete $self->{added_comments};
|
delete $self->{added_comments};
|
||||||
delete $self->{edited_comments};
|
delete $self->{edited_comments};
|
||||||
|
@ -776,7 +751,7 @@ sub check_default_values
|
||||||
push @gids, $gid;
|
push @gids, $gid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$self->{groups_in} = Bugzilla::Group->new_from_list(\@gids);
|
$self->{groups_in} = \@gids;
|
||||||
}
|
}
|
||||||
$self->set('groups', [ map { $_->id } @{$self->groups_in} ]);
|
$self->set('groups', [ map { $_->id } @{$self->groups_in} ]);
|
||||||
$self->{cc} = $self->component_obj->initial_cc if !$self->{cc};
|
$self->{cc} = $self->component_obj->initial_cc if !$self->{cc};
|
||||||
|
@ -941,7 +916,6 @@ sub check_dependent_fields
|
||||||
}
|
}
|
||||||
# Check other fields for empty values
|
# Check other fields for empty values
|
||||||
elsif (!$self->{$fn} || ($field_obj->type == FIELD_TYPE_FREETEXT ||
|
elsif (!$self->{$fn} || ($field_obj->type == FIELD_TYPE_FREETEXT ||
|
||||||
$field_obj->type == FIELD_TYPE_EAV_TEXTAREA ||
|
|
||||||
$field_obj->type == FIELD_TYPE_TEXTAREA) && $self->{$fn} =~ /^\s*$/so)
|
$field_obj->type == FIELD_TYPE_TEXTAREA) && $self->{$fn} =~ /^\s*$/so)
|
||||||
{
|
{
|
||||||
my $nullable = $field_obj->check_is_nullable($self);
|
my $nullable = $field_obj->check_is_nullable($self);
|
||||||
|
@ -955,7 +929,6 @@ sub check_dependent_fields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!$nullable && (!$self->{$fn} || ($field_obj->type == FIELD_TYPE_FREETEXT ||
|
if (!$nullable && (!$self->{$fn} || ($field_obj->type == FIELD_TYPE_FREETEXT ||
|
||||||
$field_obj->type == FIELD_TYPE_EAV_TEXTAREA ||
|
|
||||||
$field_obj->type == FIELD_TYPE_TEXTAREA) && $self->{$fn} =~ /^\s*$/so))
|
$field_obj->type == FIELD_TYPE_TEXTAREA) && $self->{$fn} =~ /^\s*$/so))
|
||||||
{
|
{
|
||||||
ThrowUserError('field_not_nullable', { field => $field_obj });
|
ThrowUserError('field_not_nullable', { field => $field_obj });
|
||||||
|
@ -1107,6 +1080,14 @@ sub _check_bug_status
|
||||||
}
|
}
|
||||||
$self->set('resolution', undef);
|
$self->set('resolution', undef);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
# Changing between closed statuses zeroes the remaining time.
|
||||||
|
if ($old_status && $new_status->id != $old_status->id && $self->remaining_time != 0)
|
||||||
|
{
|
||||||
|
$self->set('remaining_time', 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _check_resolution
|
sub _check_resolution
|
||||||
|
@ -1460,31 +1441,6 @@ sub save_multiselects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub save_eavs
|
|
||||||
{
|
|
||||||
my ($self, $changes) = @_;
|
|
||||||
|
|
||||||
my @eavs = Bugzilla->get_fields({ obsolete => 0, type => FIELD_TYPE_EAV_TEXTAREA });
|
|
||||||
foreach my $field (@eavs)
|
|
||||||
{
|
|
||||||
my $name = $field->name;
|
|
||||||
if (defined $self->{$name})
|
|
||||||
{
|
|
||||||
my $old = $self->{_old_self} && $self->{_old_self}->$name || '';
|
|
||||||
my $v = $self->{$name};
|
|
||||||
if ($v ne $old)
|
|
||||||
{
|
|
||||||
Bugzilla->dbh->do("DELETE FROM bug_$name WHERE bug_id=?", undef, $self->id);
|
|
||||||
if ($v ne '')
|
|
||||||
{
|
|
||||||
Bugzilla->dbh->do("INSERT INTO bug_$name (bug_id, value) VALUES (?, ?)", undef, $self->id, $v);
|
|
||||||
}
|
|
||||||
$changes->{$name} = [ $old, $v ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub save_see_also
|
sub save_see_also
|
||||||
{
|
{
|
||||||
my ($self, $changes) = @_;
|
my ($self, $changes) = @_;
|
||||||
|
@ -1532,7 +1488,7 @@ sub save_dup_id
|
||||||
sub save_added_comments
|
sub save_added_comments
|
||||||
{
|
{
|
||||||
my ($self, $changes) = @_;
|
my ($self, $changes) = @_;
|
||||||
my $dbh = Bugzilla->dbh;
|
|
||||||
delete $self->{comments} if @{$self->{added_comments} || []};
|
delete $self->{comments} if @{$self->{added_comments} || []};
|
||||||
foreach my $comment (@{$self->{added_comments} || []})
|
foreach my $comment (@{$self->{added_comments} || []})
|
||||||
{
|
{
|
||||||
|
@ -1548,8 +1504,7 @@ sub save_added_comments
|
||||||
$comment->{bug_when} = $self->{delta_ts} if !$comment->{bug_when} || $comment->{bug_when} gt $self->{delta_ts};
|
$comment->{bug_when} = $self->{delta_ts} if !$comment->{bug_when} || $comment->{bug_when} gt $self->{delta_ts};
|
||||||
my $columns = join(',', keys %$comment);
|
my $columns = join(',', keys %$comment);
|
||||||
my $qmarks = join(',', ('?') x keys %$comment);
|
my $qmarks = join(',', ('?') x keys %$comment);
|
||||||
$dbh->do("INSERT INTO longdescs ($columns) VALUES ($qmarks)", undef, values %$comment);
|
Bugzilla->dbh->do("INSERT INTO longdescs ($columns) VALUES ($qmarks)", undef, values %$comment);
|
||||||
$comment->{id} = $dbh->bz_last_key('longdescs', 'id');
|
|
||||||
if (0+$comment->{work_time} != 0)
|
if (0+$comment->{work_time} != 0)
|
||||||
{
|
{
|
||||||
# Log worktime
|
# Log worktime
|
||||||
|
@ -1722,9 +1677,7 @@ sub _sync_fulltext
|
||||||
$sql = "UPDATE $table SET short_desc=$row->[0],".
|
$sql = "UPDATE $table SET short_desc=$row->[0],".
|
||||||
" comments=$row->[1], comments_private=$row->[2] WHERE $id_field=".$self->id;
|
" comments=$row->[1], comments_private=$row->[2] WHERE $id_field=".$self->id;
|
||||||
}
|
}
|
||||||
my $r = eval { $sph->do($sql) };
|
return $sph->do($sql);
|
||||||
if ($@) { warn $@; }
|
|
||||||
return $r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This is the correct way to delete bugs from the DB.
|
# This is the correct way to delete bugs from the DB.
|
||||||
|
@ -2203,32 +2156,18 @@ sub _set_keywords
|
||||||
sub _set_product
|
sub _set_product
|
||||||
{
|
{
|
||||||
my ($self, $name) = @_;
|
my ($self, $name) = @_;
|
||||||
my $product;
|
$name = trim($name);
|
||||||
if (!ref $name)
|
# If we're updating the bug and they haven't changed the product, always allow it.
|
||||||
|
if ($self->product_obj && $self->product_obj->name eq $name)
|
||||||
{
|
{
|
||||||
$name = trim($name);
|
return undef;
|
||||||
# If we're updating the bug and they haven't changed the product, always allow it.
|
|
||||||
if ($self->product_obj && $self->product_obj->name eq $name)
|
|
||||||
{
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
# Check that the product exists and that the user
|
|
||||||
# is allowed to enter bugs into this product.
|
|
||||||
Bugzilla->user->can_enter_product($name, THROW_ERROR);
|
|
||||||
# can_enter_product already does everything that check_product
|
|
||||||
# would do for us, so we don't need to use it.
|
|
||||||
$product = new Bugzilla::Product({ name => $name });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$product = $name;
|
|
||||||
# If we're updating the bug and they haven't changed the product, always allow it.
|
|
||||||
if ($self->product_id == $product->id)
|
|
||||||
{
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
Bugzilla->user->can_enter_product($product, THROW_ERROR);
|
|
||||||
}
|
}
|
||||||
|
# Check that the product exists and that the user
|
||||||
|
# is allowed to enter bugs into this product.
|
||||||
|
Bugzilla->user->can_enter_product($name, THROW_ERROR);
|
||||||
|
# can_enter_product already does everything that check_product
|
||||||
|
# would do for us, so we don't need to use it.
|
||||||
|
my $product = new Bugzilla::Product({ name => $name });
|
||||||
if (($self->product_id || 0) != $product->id)
|
if (($self->product_id || 0) != $product->id)
|
||||||
{
|
{
|
||||||
$self->{product_id} = $product->id;
|
$self->{product_id} = $product->id;
|
||||||
|
@ -3696,9 +3635,7 @@ sub GetBugActivity
|
||||||
{
|
{
|
||||||
my $change = $operations[$i]{changes}[$j];
|
my $change = $operations[$i]{changes}[$j];
|
||||||
my $field = Bugzilla->get_field($change->{fieldname});
|
my $field = Bugzilla->get_field($change->{fieldname});
|
||||||
if ($change->{fieldname} eq 'longdesc' ||
|
if ($change->{fieldname} eq 'longdesc' || $field->{type} eq FIELD_TYPE_TEXTAREA)
|
||||||
$field->{type} == FIELD_TYPE_TEXTAREA ||
|
|
||||||
$field->{type} == FIELD_TYPE_EAV_TEXTAREA)
|
|
||||||
{
|
{
|
||||||
my $diff = Bugzilla::Diff->new($change->{removed}, $change->{added})->get_table;
|
my $diff = Bugzilla::Diff->new($change->{removed}, $change->{added})->get_table;
|
||||||
if (!@$diff)
|
if (!@$diff)
|
||||||
|
@ -4358,14 +4295,7 @@ sub get_value
|
||||||
elsif ($field->type == FIELD_TYPE_BUG_ID_REV)
|
elsif ($field->type == FIELD_TYPE_BUG_ID_REV)
|
||||||
{
|
{
|
||||||
$self->{$attr} ||= Bugzilla->dbh->selectcol_arrayref(
|
$self->{$attr} ||= Bugzilla->dbh->selectcol_arrayref(
|
||||||
"SELECT bug_id FROM bugs WHERE ".$field->value_field->name." = ?", undef, $self->id
|
"SELECT bug_id FROM bugs WHERE ".$field->value_field->name." = ".$self->id
|
||||||
);
|
|
||||||
return $self->{$attr};
|
|
||||||
}
|
|
||||||
elsif ($field->type == FIELD_TYPE_EAV_TEXTAREA)
|
|
||||||
{
|
|
||||||
($self->{$attr}) = Bugzilla->dbh->selectrow_array(
|
|
||||||
"SELECT value FROM bug_$attr WHERE bug_id=?", undef, $self->id
|
|
||||||
);
|
);
|
||||||
return $self->{$attr};
|
return $self->{$attr};
|
||||||
}
|
}
|
||||||
|
@ -4391,7 +4321,7 @@ sub fields
|
||||||
map { $_->name } Bugzilla->get_fields({
|
map { $_->name } Bugzilla->get_fields({
|
||||||
obsolete => 0,
|
obsolete => 0,
|
||||||
custom => 1,
|
custom => 1,
|
||||||
type => [ FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_EAV_TEXTAREA, FIELD_TYPE_BUG_ID_REV ],
|
type => [ FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_ID_REV ],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
Bugzilla::Hook::process('bug_fields', { fields => \@fields });
|
Bugzilla::Hook::process('bug_fields', { fields => \@fields });
|
||||||
|
@ -4416,7 +4346,7 @@ sub _validate_attribute
|
||||||
# every DB column may be returned via an autoloaded accessor
|
# every DB column may be returned via an autoloaded accessor
|
||||||
(map { $_ => 1 } Bugzilla::Bug->DB_COLUMNS),
|
(map { $_ => 1 } Bugzilla::Bug->DB_COLUMNS),
|
||||||
# multiselect, bug_id_rev fields
|
# multiselect, bug_id_rev fields
|
||||||
(map { $_->name => 1 } Bugzilla->get_fields({ type => [ FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_EAV_TEXTAREA, FIELD_TYPE_BUG_ID_REV ] })),
|
(map { $_->name => 1 } Bugzilla->get_fields({ type => [ FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_ID_REV ] })),
|
||||||
# get_object accessors
|
# get_object accessors
|
||||||
(map { $_->name.'_obj' => 1 } Bugzilla->get_fields({ type => [ FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT ] })),
|
(map { $_->name.'_obj' => 1 } Bugzilla->get_fields({ type => [ FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT ] })),
|
||||||
};
|
};
|
||||||
|
|
|
@ -91,32 +91,26 @@ sub new
|
||||||
my $product;
|
my $product;
|
||||||
if (ref $param)
|
if (ref $param)
|
||||||
{
|
{
|
||||||
if ($param->{id})
|
$product = $param->{product};
|
||||||
|
my $name = $param->{name};
|
||||||
|
if (!defined $product)
|
||||||
{
|
{
|
||||||
$param = { condition => 'id = ?', values => [ $param->{id} ] };
|
ThrowCodeError('bad_arg', {
|
||||||
|
argument => 'product',
|
||||||
|
function => "${class}::new",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
if (!defined $name)
|
||||||
{
|
{
|
||||||
$product = $param->{product};
|
ThrowCodeError('bad_arg', {
|
||||||
my $name = $param->{name};
|
argument => 'name',
|
||||||
if (!defined $product)
|
function => "${class}::new",
|
||||||
{
|
});
|
||||||
ThrowCodeError('bad_arg', {
|
|
||||||
argument => 'product',
|
|
||||||
function => "${class}::new",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!defined $name)
|
|
||||||
{
|
|
||||||
ThrowCodeError('bad_arg', {
|
|
||||||
argument => 'name',
|
|
||||||
function => "${class}::new",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
my $condition = 'product_id = ? AND name = ?';
|
|
||||||
my @values = ($product->id, $name);
|
|
||||||
$param = { condition => $condition, values => \@values };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $condition = 'product_id = ? AND name = ?';
|
||||||
|
my @values = ($product->id, $name);
|
||||||
|
$param = { condition => $condition, values => \@values };
|
||||||
}
|
}
|
||||||
|
|
||||||
unshift @_, $param;
|
unshift @_, $param;
|
||||||
|
@ -513,7 +507,6 @@ sub description { return $_[0]->{description}; }
|
||||||
sub wiki_url { return $_[0]->{wiki_url}; }
|
sub wiki_url { return $_[0]->{wiki_url}; }
|
||||||
sub product_id { return $_[0]->{product_id}; }
|
sub product_id { return $_[0]->{product_id}; }
|
||||||
sub is_active { return $_[0]->{isactive}; }
|
sub is_active { return $_[0]->{isactive}; }
|
||||||
sub full_name { return $_[0]->product->name.'/'.$_[0]->name; }
|
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
#### Subroutines ####
|
#### Subroutines ####
|
||||||
|
|
|
@ -72,14 +72,14 @@ sub get_param_list
|
||||||
{
|
{
|
||||||
name => 'webdotbase',
|
name => 'webdotbase',
|
||||||
type => 't',
|
type => 't',
|
||||||
default => '/usr/bin/dot',
|
default => 'http://www.research.att.com/~north/cgi-bin/webdot.cgi/%urlbase%',
|
||||||
checker => \&check_webdotbase
|
checker => \&check_webdotbase
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name => 'webtwopibase',
|
name => 'webtwopibase',
|
||||||
type => 't',
|
type => 't',
|
||||||
default => '/usr/bin/twopi',
|
default => '',
|
||||||
checker => \&check_webdotbase
|
checker => \&check_webdotbase
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -109,12 +109,6 @@ sub get_param_list
|
||||||
type => 'b',
|
type => 'b',
|
||||||
default => 0
|
default => 0
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
name => 'forbid_open_products',
|
|
||||||
type => 'b',
|
|
||||||
default => 0
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return @param_list;
|
return @param_list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,12 +103,6 @@ sub get_param_list
|
||||||
default => 1
|
default => 1
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
name => 'enable_inmail_cgi',
|
|
||||||
type => 'b',
|
|
||||||
default => 0
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
name => 'smtpserver',
|
name => 'smtpserver',
|
||||||
type => 't',
|
type => 't',
|
||||||
|
|
|
@ -77,14 +77,6 @@ sub get_param_list
|
||||||
type => 't',
|
type => 't',
|
||||||
default => 'en',
|
default => 'en',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
name => 'sphinx_max_matches',
|
|
||||||
type => 't',
|
|
||||||
default => 1000,
|
|
||||||
checker => sub { $_[0] =~ /^[1-9]\d*$/so ? "" : "must be a positive integer value" },
|
|
||||||
},
|
|
||||||
|
|
||||||
);
|
);
|
||||||
return @param_list;
|
return @param_list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,8 +129,6 @@ use Cwd qw(abs_path);
|
||||||
FIELD_TYPE_NUMERIC
|
FIELD_TYPE_NUMERIC
|
||||||
FIELD_TYPE_EXTURL
|
FIELD_TYPE_EXTURL
|
||||||
FIELD_TYPE_BUG_ID_REV
|
FIELD_TYPE_BUG_ID_REV
|
||||||
FIELD_TYPE_EAV_TEXTAREA
|
|
||||||
FIELD_TYPE__MAX
|
|
||||||
|
|
||||||
FLAG_VISIBLE
|
FLAG_VISIBLE
|
||||||
FLAG_NULLABLE
|
FLAG_NULLABLE
|
||||||
|
@ -394,8 +392,6 @@ use constant FIELD_TYPE_KEYWORDS => 8;
|
||||||
use constant FIELD_TYPE_NUMERIC => 30;
|
use constant FIELD_TYPE_NUMERIC => 30;
|
||||||
use constant FIELD_TYPE_EXTURL => 31;
|
use constant FIELD_TYPE_EXTURL => 31;
|
||||||
use constant FIELD_TYPE_BUG_ID_REV => 32;
|
use constant FIELD_TYPE_BUG_ID_REV => 32;
|
||||||
use constant FIELD_TYPE_EAV_TEXTAREA => 33;
|
|
||||||
use constant FIELD_TYPE__MAX => 33;
|
|
||||||
|
|
||||||
use constant FLAG_VISIBLE => 0;
|
use constant FLAG_VISIBLE => 0;
|
||||||
use constant FLAG_NULLABLE => -1;
|
use constant FLAG_NULLABLE => -1;
|
||||||
|
|
|
@ -138,7 +138,7 @@ sub connect_sphinx
|
||||||
mysql_enable_utf8 => 1,
|
mysql_enable_utf8 => 1,
|
||||||
# Needs to be explicitly specified for command-line processes.
|
# Needs to be explicitly specified for command-line processes.
|
||||||
mysql_auto_reconnect => 1,
|
mysql_auto_reconnect => 1,
|
||||||
raise_error => 1,
|
raise_error => 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
$sphinx->do("SET NAMES utf8") if $sphinx;
|
$sphinx->do("SET NAMES utf8") if $sphinx;
|
||||||
|
@ -816,18 +816,12 @@ sub _bz_add_field_table {
|
||||||
$self->bz_add_table($name);
|
$self->bz_add_table($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub bz_add_field_tables
|
sub bz_add_field_tables {
|
||||||
{
|
|
||||||
my ($self, $field) = @_;
|
my ($self, $field) = @_;
|
||||||
if ($field->type == FIELD_TYPE_MULTI_SELECT ||
|
|
||||||
$field->type == FIELD_TYPE_SINGLE_SELECT)
|
$self->_bz_add_field_table($field->name,
|
||||||
{
|
$self->_bz_schema->FIELD_TABLE_SCHEMA, $field->type);
|
||||||
$self->_bz_add_field_table(
|
if ($field->type == FIELD_TYPE_MULTI_SELECT) {
|
||||||
$field->name, $self->_bz_schema->FIELD_TABLE_SCHEMA, $field->type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ($field->type == FIELD_TYPE_MULTI_SELECT)
|
|
||||||
{
|
|
||||||
my $ms_table = "bug_" . $field->name;
|
my $ms_table = "bug_" . $field->name;
|
||||||
$self->_bz_add_field_table($ms_table,
|
$self->_bz_add_field_table($ms_table,
|
||||||
$self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
|
$self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
|
||||||
|
@ -838,27 +832,14 @@ sub bz_add_field_tables
|
||||||
$self->bz_add_fk($ms_table, 'value_id', {TABLE => $field->name,
|
$self->bz_add_fk($ms_table, 'value_id', {TABLE => $field->name,
|
||||||
COLUMN => 'id'});
|
COLUMN => 'id'});
|
||||||
}
|
}
|
||||||
elsif ($field->type == FIELD_TYPE_EAV_TEXTAREA)
|
|
||||||
{
|
|
||||||
my $ms_table = "bug_" . $field->name;
|
|
||||||
$self->_bz_add_field_table($ms_table, $self->_bz_schema->EAV_TEXTAREA_VALUE_TABLE);
|
|
||||||
$self->bz_add_fk($ms_table, 'bug_id', {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub bz_drop_field_tables
|
sub bz_drop_field_tables {
|
||||||
{
|
|
||||||
my ($self, $field) = @_;
|
my ($self, $field) = @_;
|
||||||
if ($field->type == FIELD_TYPE_MULTI_SELECT ||
|
if ($field->type == FIELD_TYPE_MULTI_SELECT) {
|
||||||
$field->type == FIELD_TYPE_EAV_TEXTAREA)
|
|
||||||
{
|
|
||||||
$self->bz_drop_table('bug_' . $field->name);
|
$self->bz_drop_table('bug_' . $field->name);
|
||||||
}
|
}
|
||||||
if ($field->type == FIELD_TYPE_MULTI_SELECT ||
|
$self->bz_drop_table($field->name);
|
||||||
$field->type == FIELD_TYPE_SINGLE_SELECT)
|
|
||||||
{
|
|
||||||
$self->bz_drop_table($field->name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub bz_drop_column
|
sub bz_drop_column
|
||||||
|
|
|
@ -1326,15 +1326,6 @@ use constant MULTI_SELECT_VALUE_TABLE => {
|
||||||
bug_id_idx => {FIELDS => [qw(bug_id value_id)], TYPE => 'UNIQUE'},
|
bug_id_idx => {FIELDS => [qw(bug_id value_id)], TYPE => 'UNIQUE'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
use constant EAV_TEXTAREA_VALUE_TABLE => {
|
|
||||||
FIELDS => [
|
|
||||||
bug_id => {TYPE => 'INT4', NOTNULL => 1},
|
|
||||||
value => {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"},
|
|
||||||
],
|
|
||||||
INDEXES => [
|
|
||||||
bug_id_idx => {FIELDS => [qw(bug_id)], TYPE => 'UNIQUE'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------------
|
#--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ use Bugzilla::Mailer;
|
||||||
use Date::Format;
|
use Date::Format;
|
||||||
use JSON;
|
use JSON;
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
use Scalar::Util qw(blessed);
|
|
||||||
|
|
||||||
use overload '""' => sub { $_[0]->{message} };
|
use overload '""' => sub { $_[0]->{message} };
|
||||||
|
|
||||||
|
@ -67,33 +66,12 @@ sub _error_message
|
||||||
{
|
{
|
||||||
$cgivars->{$_} = $cgi->uploadInfo($cgivars->{$_}) if $cgi->upload($_);
|
$cgivars->{$_} = $cgi->uploadInfo($cgivars->{$_}) if $cgi->upload($_);
|
||||||
}
|
}
|
||||||
$mesg .= Data::Dumper->Dump([remove_objects($vars), $cgivars, { %ENV }], ['error_vars', 'cgi_params', 'env']);
|
$mesg .= Data::Dumper->Dump([$vars, $cgivars, { %ENV }], ['error_vars', 'cgi_params', 'env']);
|
||||||
# ugly workaround for Data::Dumper's \x{425} unicode characters
|
# ugly workaround for Data::Dumper's \x{425} unicode characters
|
||||||
$mesg =~ s/((?:\\x\{(?:[\dA-Z]+)\})+)/eval("\"$1\"")/egiso;
|
$mesg =~ s/((?:\\x\{(?:[\dA-Z]+)\})+)/eval("\"$1\"")/egiso;
|
||||||
return $mesg;
|
return $mesg;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub remove_objects
|
|
||||||
{
|
|
||||||
my ($v) = @_;
|
|
||||||
if (blessed $v)
|
|
||||||
{
|
|
||||||
return "$v";
|
|
||||||
}
|
|
||||||
elsif (ref($v) eq 'HASH')
|
|
||||||
{
|
|
||||||
return { map { ($_ => remove_objects($v->{$_})) } keys %$v };
|
|
||||||
}
|
|
||||||
elsif (ref($v) eq 'ARRAY')
|
|
||||||
{
|
|
||||||
return [ map { remove_objects($_) } @$v ];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub throw
|
sub throw
|
||||||
{
|
{
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
|
@ -306,7 +306,7 @@ sub _check_type
|
||||||
# higher field type is added.
|
# higher field type is added.
|
||||||
if (!detaint_natural($type) || $type <= FIELD_TYPE_UNKNOWN ||
|
if (!detaint_natural($type) || $type <= FIELD_TYPE_UNKNOWN ||
|
||||||
$type > FIELD_TYPE_KEYWORDS && $type < FIELD_TYPE_NUMERIC ||
|
$type > FIELD_TYPE_KEYWORDS && $type < FIELD_TYPE_NUMERIC ||
|
||||||
$type > FIELD_TYPE__MAX)
|
$type > FIELD_TYPE_BUG_ID_REV)
|
||||||
{
|
{
|
||||||
ThrowCodeError('invalid_customfield_type', { type => $saved_type });
|
ThrowCodeError('invalid_customfield_type', { type => $saved_type });
|
||||||
}
|
}
|
||||||
|
@ -893,8 +893,11 @@ sub remove_from_db
|
||||||
$dbh->bz_drop_column('bugs', $name);
|
$dbh->bz_drop_column('bugs', $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Delete the table that holds the legal values for this field.
|
if ($self->is_select)
|
||||||
$dbh->bz_drop_field_tables($self);
|
{
|
||||||
|
# Delete the table that holds the legal values for this field.
|
||||||
|
$dbh->bz_drop_field_tables($self);
|
||||||
|
}
|
||||||
|
|
||||||
$self->set_visibility_values(undef);
|
$self->set_visibility_values(undef);
|
||||||
$self->set_null_visibility_values(undef);
|
$self->set_null_visibility_values(undef);
|
||||||
|
@ -1275,8 +1278,13 @@ sub create
|
||||||
# Create the database column that stores the data for this field.
|
# Create the database column that stores the data for this field.
|
||||||
$dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
|
$dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
|
||||||
}
|
}
|
||||||
# Create the table that holds the legal values for this field.
|
|
||||||
$dbh->bz_add_field_tables($obj);
|
if ($obj->is_select)
|
||||||
|
{
|
||||||
|
# Create the table that holds the legal values for this field.
|
||||||
|
$dbh->bz_add_field_tables($obj);
|
||||||
|
}
|
||||||
|
|
||||||
# Add foreign keys
|
# Add foreign keys
|
||||||
if ($type == FIELD_TYPE_SINGLE_SELECT)
|
if ($type == FIELD_TYPE_SINGLE_SELECT)
|
||||||
{
|
{
|
||||||
|
|
|
@ -314,7 +314,6 @@ sub get_all_names
|
||||||
|
|
||||||
sub is_active { return $_[0]->{isactive}; }
|
sub is_active { return $_[0]->{isactive}; }
|
||||||
sub sortkey { return $_[0]->{sortkey}; }
|
sub sortkey { return $_[0]->{sortkey}; }
|
||||||
sub full_name { return $_[0]->name; }
|
|
||||||
|
|
||||||
# FIXME Never use bug_count() on a copy from legal_values, as the result will be cached...
|
# FIXME Never use bug_count() on a copy from legal_values, as the result will be cached...
|
||||||
sub bug_count
|
sub bug_count
|
||||||
|
|
|
@ -110,7 +110,7 @@ sub AddWorktime
|
||||||
my $remaining_time = $bug->remaining_time;
|
my $remaining_time = $bug->remaining_time;
|
||||||
my $newrtime = $remaining_time - $sum;
|
my $newrtime = $remaining_time - $sum;
|
||||||
$newrtime = 0 if $newrtime < 0;
|
$newrtime = 0 if $newrtime < 0;
|
||||||
$bug->set('remaining_time', $newrtime) if $newrtime != $remaining_time;
|
$bug->remaining_time($newrtime) if $newrtime != $remaining_time;
|
||||||
|
|
||||||
$bug->update();
|
$bug->update();
|
||||||
|
|
||||||
|
|
|
@ -1,462 +0,0 @@
|
||||||
# Incoming mail handler for Bugzilla
|
|
||||||
# License: Dual-license GPL 3.0+ or MPL 1.1+
|
|
||||||
# Contributor(s): Vitaliy Filippov <vitalif@mail.ru>, Max Kanat-Alexander <mkanat@bugzilla.org>
|
|
||||||
|
|
||||||
package Bugzilla::InMail;
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
use Email::Address;
|
|
||||||
use Email::Reply qw(reply);
|
|
||||||
use Email::MIME;
|
|
||||||
use Email::MIME::Attachment::Stripper;
|
|
||||||
use HTML::Strip;
|
|
||||||
use Getopt::Long qw(:config bundling);
|
|
||||||
use Pod::Usage;
|
|
||||||
use Encode;
|
|
||||||
use Scalar::Util qw(blessed);
|
|
||||||
|
|
||||||
use Bugzilla;
|
|
||||||
use Bugzilla::Attachment;
|
|
||||||
use Bugzilla::Bug;
|
|
||||||
use Bugzilla::Hook;
|
|
||||||
use Bugzilla::BugMail;
|
|
||||||
use Bugzilla::Constants;
|
|
||||||
use Bugzilla::Error;
|
|
||||||
use Bugzilla::Mailer;
|
|
||||||
use Bugzilla::Token;
|
|
||||||
use Bugzilla::User;
|
|
||||||
use Bugzilla::Util;
|
|
||||||
|
|
||||||
#############
|
|
||||||
# Constants #
|
|
||||||
#############
|
|
||||||
|
|
||||||
# This is the USENET standard line for beginning a signature block
|
|
||||||
# in a message. RFC-compliant mailers use this.
|
|
||||||
use constant SIGNATURE_DELIMITER => '-- ';
|
|
||||||
|
|
||||||
sub process_inmail
|
|
||||||
{
|
|
||||||
my ($mail_text) = @_;
|
|
||||||
|
|
||||||
my $input_email = Email::MIME->new($mail_text);
|
|
||||||
|
|
||||||
my $status = eval
|
|
||||||
{
|
|
||||||
my $mail_fields = parse_mail($input_email);
|
|
||||||
if (!$mail_fields)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bugzilla::Hook::process('email_in_after_parse', { fields => $mail_fields });
|
|
||||||
|
|
||||||
my $attachments = delete $mail_fields->{attachments};
|
|
||||||
select_user($mail_fields->{reporter}, $mail_fields->{_reporter_name});
|
|
||||||
|
|
||||||
my ($bug, $comment);
|
|
||||||
if ($mail_fields->{bug_id})
|
|
||||||
{
|
|
||||||
$bug = Bugzilla::Bug::create_or_update($mail_fields);
|
|
||||||
$comment = $bug->comments->[-1] if trim($mail_fields->{comment});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
($bug, $comment) = post_bug($mail_fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_attachments($bug, $attachments, $comment);
|
|
||||||
|
|
||||||
Bugzilla->send_mail;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($@)
|
|
||||||
{
|
|
||||||
# Report error to the sender of original message
|
|
||||||
my $msg = $@;
|
|
||||||
if (ref $msg eq 'Bugzilla::Error')
|
|
||||||
{
|
|
||||||
$msg = $msg->{message};
|
|
||||||
}
|
|
||||||
if ($input_email)
|
|
||||||
{
|
|
||||||
my $from = Bugzilla->params->{mailfrom};
|
|
||||||
my $reply = reply(to => $input_email, from => $from, top_post => 1, body => "$msg\n");
|
|
||||||
MessageToMTA($reply->as_string);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub select_user
|
|
||||||
{
|
|
||||||
my ($reporter, $reporter_name) = @_;
|
|
||||||
|
|
||||||
my $username = $reporter;
|
|
||||||
# If emailsuffix is in use, we have to remove it from the email address.
|
|
||||||
if (my $suffix = Bugzilla->params->{emailsuffix})
|
|
||||||
{
|
|
||||||
$username =~ s/\Q$suffix\E$//i;
|
|
||||||
}
|
|
||||||
|
|
||||||
# First try to select user with name $username
|
|
||||||
my $user = Bugzilla::User->new({ name => $username });
|
|
||||||
|
|
||||||
# Then try to find alias $username for some user
|
|
||||||
unless ($user)
|
|
||||||
{
|
|
||||||
my $dbh = Bugzilla->dbh;
|
|
||||||
($user) = $dbh->selectrow_array("SELECT userid FROM emailin_aliases WHERE address=?", undef, trim($reporter));
|
|
||||||
$user = Bugzilla::User->new({ id => $user }) if $user;
|
|
||||||
# Then check if autoregistration is enabled
|
|
||||||
unless ($user)
|
|
||||||
{
|
|
||||||
unless (Bugzilla->params->{emailin_autoregister})
|
|
||||||
{
|
|
||||||
ThrowUserError('invalid_username', { name => $username });
|
|
||||||
}
|
|
||||||
# Then try to autoregister unknown user
|
|
||||||
$user = Bugzilla::User->create({
|
|
||||||
login_name => $username,
|
|
||||||
realname => $reporter_name,
|
|
||||||
cryptpassword => 'a3#',
|
|
||||||
disabledtext => '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$user->is_enabled)
|
|
||||||
{
|
|
||||||
ThrowUserError('account_disabled', { disabled_reason => $user->disabledtext });
|
|
||||||
}
|
|
||||||
|
|
||||||
Bugzilla->set_user($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub parse_mail
|
|
||||||
{
|
|
||||||
my ($input_email) = @_;
|
|
||||||
|
|
||||||
my %fields;
|
|
||||||
Bugzilla::Hook::process('email_in_before_parse', { mail => $input_email, fields => \%fields });
|
|
||||||
# RFC 3834 - Recommendations for Automatic Responses to Electronic Mail
|
|
||||||
# Automatic responses SHOULD NOT be issued in response to any
|
|
||||||
# message which contains an Auto-Submitted header field (see below),
|
|
||||||
# where that field has any value other than "no".
|
|
||||||
# F*cking MS Exchange sometimes does not append Auto-Submitted header
|
|
||||||
# to delivery status reports, so also check content-type.
|
|
||||||
my $autosubmitted;
|
|
||||||
if (lc($input_email->header('Auto-Submitted') || 'no') ne 'no' ||
|
|
||||||
($input_email->header('X-Auto-Response-Suppress') || '') =~ /all/iso ||
|
|
||||||
($input_email->header('Content-Type') || '') =~ /delivery-status/iso)
|
|
||||||
{
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $dbh = Bugzilla->dbh;
|
|
||||||
|
|
||||||
# Fetch field => value from emailin_fields table
|
|
||||||
my ($toemail) = Email::Address->parse($input_email->header('To'));
|
|
||||||
%fields = (%fields, map { @$_ } @{ $dbh->selectall_arrayref(
|
|
||||||
"SELECT field, value FROM emailin_fields WHERE address=?",
|
|
||||||
undef, $toemail) || [] });
|
|
||||||
|
|
||||||
my $summary = $input_email->header('Subject');
|
|
||||||
if ($summary =~ /\[\s*Bug\s*(\d+)\s*\](.*)/i)
|
|
||||||
{
|
|
||||||
$fields{bug_id} = $1;
|
|
||||||
$summary = trim($2);
|
|
||||||
}
|
|
||||||
$fields{_subject} = $summary;
|
|
||||||
|
|
||||||
# Add CC's from email Cc: header
|
|
||||||
$fields{newcc} = $input_email->header('Cc');
|
|
||||||
$fields{newcc} = $fields{newcc} && (join ', ', map { [ Email::Address->parse($_) ] -> [0] }
|
|
||||||
split /\s*,\s*/, $fields{newcc}) || undef;
|
|
||||||
|
|
||||||
my ($body, $attachments) = get_body_and_attachments($input_email);
|
|
||||||
if (@$attachments)
|
|
||||||
{
|
|
||||||
$fields{attachments} = $attachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = remove_leading_blank_lines($body);
|
|
||||||
|
|
||||||
Bugzilla::Hook::process("emailin-filter_body", { body => \$body });
|
|
||||||
|
|
||||||
my @body_lines = split(/\r?\n/s, $body);
|
|
||||||
my $fields_by_name = { map { (lc($_->description) => $_->name, lc($_->name) => $_->name) } Bugzilla->get_fields({ obsolete => 0 }) };
|
|
||||||
|
|
||||||
# If there are fields specified.
|
|
||||||
if ($body =~ /^\s*@/s)
|
|
||||||
{
|
|
||||||
my $current_field;
|
|
||||||
while (my $line = shift @body_lines)
|
|
||||||
{
|
|
||||||
# If the sig is starting, we want to keep this in the
|
|
||||||
# @body_lines so that we don't keep the sig as part of the
|
|
||||||
# comment down below.
|
|
||||||
if ($line eq SIGNATURE_DELIMITER)
|
|
||||||
{
|
|
||||||
unshift(@body_lines, $line);
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
# Otherwise, we stop parsing fields on the first blank line.
|
|
||||||
$line = trim($line);
|
|
||||||
last if !$line;
|
|
||||||
if ($line =~ /^\@\s*(.+?)\s*=\s*(.*)\s*/)
|
|
||||||
{
|
|
||||||
$current_field = $fields_by_name->{lc($1)} || lc($1);
|
|
||||||
$fields{$current_field} = $2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$fields{$current_field} .= " $line";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
%fields = %{ Bugzilla::Bug::map_fields(\%fields) };
|
|
||||||
|
|
||||||
my ($reporter) = Email::Address->parse($input_email->header('From'));
|
|
||||||
$fields{reporter} = $reporter->address;
|
|
||||||
|
|
||||||
{
|
|
||||||
my $r;
|
|
||||||
if ($r = $reporter->phrase)
|
|
||||||
{
|
|
||||||
$r .= ' ' . $reporter->comment if $reporter->comment;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$r = $reporter->address;
|
|
||||||
}
|
|
||||||
$fields{_reporter_name} = $r;
|
|
||||||
}
|
|
||||||
|
|
||||||
# The summary line only affects us if we're doing a post_bug.
|
|
||||||
# We have to check it down here because there might have been
|
|
||||||
# a bug_id specified in the body of the email.
|
|
||||||
if (!$fields{bug_id} && !$fields{short_desc})
|
|
||||||
{
|
|
||||||
$fields{short_desc} = $summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $comment = '';
|
|
||||||
# Get the description, except the signature.
|
|
||||||
foreach my $line (@body_lines)
|
|
||||||
{
|
|
||||||
last if $line eq SIGNATURE_DELIMITER;
|
|
||||||
$comment .= "$line\n";
|
|
||||||
}
|
|
||||||
$fields{comment} = $comment;
|
|
||||||
|
|
||||||
return \%fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub post_bug
|
|
||||||
{
|
|
||||||
my ($fields) = @_;
|
|
||||||
my $bug;
|
|
||||||
$Bugzilla::Error::IN_EVAL++;
|
|
||||||
eval
|
|
||||||
{
|
|
||||||
my ($retval, $non_conclusive_fields) =
|
|
||||||
Bugzilla::User::match_field({
|
|
||||||
assigned_to => { type => 'single' },
|
|
||||||
qa_contact => { type => 'single' },
|
|
||||||
cc => { type => 'multi' }
|
|
||||||
}, $fields, MATCH_SKIP_CONFIRM);
|
|
||||||
if ($retval != USER_MATCH_SUCCESS)
|
|
||||||
{
|
|
||||||
ThrowUserError('user_match_too_many', { fields => $non_conclusive_fields });
|
|
||||||
}
|
|
||||||
$bug = Bugzilla::Bug::create_or_update($fields);
|
|
||||||
};
|
|
||||||
$Bugzilla::Error::IN_EVAL--;
|
|
||||||
if (my $err = $@)
|
|
||||||
{
|
|
||||||
my $format = "\n\nIncoming mail format for entering bugs:\n\n\@field = value\n\@field = value\n...\n\n<Bug description...>\n";
|
|
||||||
if (blessed $err && $err->{message})
|
|
||||||
{
|
|
||||||
$err->{message} .= $format;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$err .= $format;
|
|
||||||
}
|
|
||||||
die $err;
|
|
||||||
}
|
|
||||||
if ($bug)
|
|
||||||
{
|
|
||||||
return ($bug, $bug->comments->[0]);
|
|
||||||
}
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub handle_attachments
|
|
||||||
{
|
|
||||||
my ($bug, $attachments, $comment) = @_;
|
|
||||||
return if !$attachments;
|
|
||||||
my $dbh = Bugzilla->dbh;
|
|
||||||
$dbh->bz_start_transaction();
|
|
||||||
my ($update_comment, $update_bug);
|
|
||||||
foreach my $attachment (@$attachments)
|
|
||||||
{
|
|
||||||
my $data = delete $attachment->{payload};
|
|
||||||
$attachment->{content_type} ||= 'application/octet-stream';
|
|
||||||
my $obj = Bugzilla::Attachment->create({
|
|
||||||
bug => $bug,
|
|
||||||
description => $attachment->{filename},
|
|
||||||
filename => $attachment->{filename},
|
|
||||||
mimetype => $attachment->{content_type},
|
|
||||||
data => $data,
|
|
||||||
});
|
|
||||||
# If we added a comment, and our comment does not already have a type,
|
|
||||||
# and this is our first attachment, then we make the comment an
|
|
||||||
# "attachment created" comment.
|
|
||||||
if ($comment and !$comment->type and !$update_comment)
|
|
||||||
{
|
|
||||||
$comment->set_type(CMT_ATTACHMENT_CREATED, $obj->id);
|
|
||||||
$update_comment = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$bug->add_comment('', { type => CMT_ATTACHMENT_CREATED, extra_data => $obj->id });
|
|
||||||
$update_bug = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# We only update the comments and bugs at the end of the transaction,
|
|
||||||
# because doing so modifies bugs_fulltext, which is a non-transactional
|
|
||||||
# table.
|
|
||||||
$bug->update() if $update_bug;
|
|
||||||
$comment->update() if $update_comment;
|
|
||||||
$dbh->bz_commit_transaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
######################
|
|
||||||
# Helper Subroutines #
|
|
||||||
######################
|
|
||||||
|
|
||||||
sub get_body_and_attachments
|
|
||||||
{
|
|
||||||
my ($email) = @_;
|
|
||||||
|
|
||||||
my $ct = $email->content_type || 'text/plain';
|
|
||||||
|
|
||||||
my $body;
|
|
||||||
my $attachments = [];
|
|
||||||
if ($ct =~ /^multipart\/(alternative|signed)/i)
|
|
||||||
{
|
|
||||||
$body = get_text_alternative($email);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
my $stripper = new Email::MIME::Attachment::Stripper($email, force_filename => 1);
|
|
||||||
my $message = $stripper->message;
|
|
||||||
$body = get_text_alternative($message);
|
|
||||||
$attachments = [$stripper->attachments];
|
|
||||||
}
|
|
||||||
$email->charset_set('utf8');
|
|
||||||
$email->body_str_set($body);
|
|
||||||
|
|
||||||
return ($body, $attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub rm_line_feeds
|
|
||||||
{
|
|
||||||
my ($t) = @_;
|
|
||||||
$t =~ s/[\n\r]+/ /giso;
|
|
||||||
return $t;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub get_text_alternative
|
|
||||||
{
|
|
||||||
my ($email) = @_;
|
|
||||||
|
|
||||||
my @parts = $email->parts;
|
|
||||||
my $body;
|
|
||||||
foreach my $part (@parts)
|
|
||||||
{
|
|
||||||
my $ct = $part->content_type || 'text/plain';
|
|
||||||
my $charset = 'iso-8859-1';
|
|
||||||
# The charset may be quoted.
|
|
||||||
if ($ct =~ /charset="?([^;"]+)/)
|
|
||||||
{
|
|
||||||
$charset = $1;
|
|
||||||
}
|
|
||||||
if (!$ct || $ct =~ /^text\/plain/i)
|
|
||||||
{
|
|
||||||
$body = $part->body;
|
|
||||||
}
|
|
||||||
elsif ($ct =~ /^text\/html/i)
|
|
||||||
{
|
|
||||||
$body = $part->body;
|
|
||||||
$body =~ s/<table[^<>]*class=[\"\']?difft[^<>]*>.*?<\/table\s*>//giso;
|
|
||||||
$body =~ s/(<a[^<>]*>.*?<\/a\s*>)/rm_line_feeds($1)/gieso;
|
|
||||||
Bugzilla::Hook::process("emailin-filter_html", { body => \$body });
|
|
||||||
$body = HTML::Strip->new->parse($body);
|
|
||||||
}
|
|
||||||
if (defined $body)
|
|
||||||
{
|
|
||||||
if (Bugzilla->params->{utf8} && !utf8::is_utf8($body))
|
|
||||||
{
|
|
||||||
$body = Encode::decode($charset, $body);
|
|
||||||
}
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!defined $body)
|
|
||||||
{
|
|
||||||
# Note that this only happens if the email does not contain any
|
|
||||||
# text/plain parts. If the email has an empty text/plain part,
|
|
||||||
# you're fine, and this message does NOT get thrown.
|
|
||||||
ThrowUserError('email_no_text_plain');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $body;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub remove_leading_blank_lines
|
|
||||||
{
|
|
||||||
my ($text) = @_;
|
|
||||||
$text =~ s/^(\s*\n)+//s;
|
|
||||||
return $text;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use UTF-8 in Email::Reply to correctly quote the body
|
|
||||||
my $crlf = "\x0d\x0a";
|
|
||||||
my $CRLF = $crlf;
|
|
||||||
undef *Email::Reply::_quote_body;
|
|
||||||
*Email::Reply::_quote_body = sub
|
|
||||||
{
|
|
||||||
my ($self, $part) = @_;
|
|
||||||
return if length $self->{quoted};
|
|
||||||
return map $self->_quote_body($_), $part->parts if $part->parts > 1;
|
|
||||||
return if $part->content_type && $part->content_type !~ m[\btext/plain\b];
|
|
||||||
|
|
||||||
my $body = $part->body;
|
|
||||||
Encode::_utf8_on($body);
|
|
||||||
|
|
||||||
$body = ($self->_strip_sig($body) || $body)
|
|
||||||
if !$self->{keep_sig} && $body =~ /$crlf--\s*$crlf/o;
|
|
||||||
|
|
||||||
my ($end) = $body =~ /($crlf)/;
|
|
||||||
$end ||= $CRLF;
|
|
||||||
$body =~ s/[\r\n\s]+$//;
|
|
||||||
$body = $self->_quote_orig_body($body);
|
|
||||||
$body = "$self->{attrib}$end$body$end";
|
|
||||||
|
|
||||||
$self->{crlf} = $end;
|
|
||||||
$self->{quoted} = $body;
|
|
||||||
};
|
|
||||||
|
|
||||||
1;
|
|
||||||
__END__
|
|
|
@ -3523,7 +3523,7 @@ sub _populate_bugs_fulltext
|
||||||
$datasize += length $_;
|
$datasize += length $_;
|
||||||
}
|
}
|
||||||
my $s = "($_, ".join(', ', map { $sph->$quote($_) } @{$rows->{$_}})."), ";
|
my $s = "($_, ".join(', ', map { $sph->$quote($_) } @{$rows->{$_}})."), ";
|
||||||
if ($len > 0 && $len + length $s >= $max_packet)
|
if ($len + length $s >= $max_packet)
|
||||||
{
|
{
|
||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,11 @@ sub REQUIRED_MODULES {
|
||||||
module => 'Text::Wrap',
|
module => 'Text::Wrap',
|
||||||
version => '2013.0426',
|
version => '2013.0426',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
package => 'Text-TabularDisplay',
|
||||||
|
module => 'Text::TabularDisplay',
|
||||||
|
feature => 'Table formatting inside bug comments',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
package => 'LWP-MediaTypes',
|
package => 'LWP-MediaTypes',
|
||||||
module => 'LWP::MediaTypes',
|
module => 'LWP::MediaTypes',
|
||||||
|
|
|
@ -77,7 +77,6 @@ $Bugzilla::messages->{en} = {
|
||||||
FIELD_TYPE_NUMERIC() => 'Numeric',
|
FIELD_TYPE_NUMERIC() => 'Numeric',
|
||||||
FIELD_TYPE_EXTURL() => 'External URL',
|
FIELD_TYPE_EXTURL() => 'External URL',
|
||||||
FIELD_TYPE_BUG_ID_REV() => $terms->{Bug}.' ID reverse',
|
FIELD_TYPE_BUG_ID_REV() => $terms->{Bug}.' ID reverse',
|
||||||
FIELD_TYPE_EAV_TEXTAREA() => 'Large Text Box (separate table)',
|
|
||||||
},
|
},
|
||||||
control_options => {
|
control_options => {
|
||||||
CONTROLMAPNA() => 'NA',
|
CONTROLMAPNA() => 'NA',
|
||||||
|
@ -86,8 +85,6 @@ $Bugzilla::messages->{en} = {
|
||||||
CONTROLMAPMANDATORY() => 'Mandatory',
|
CONTROLMAPMANDATORY() => 'Mandatory',
|
||||||
},
|
},
|
||||||
field_descs => {
|
field_descs => {
|
||||||
count => 'Number of '.$terms->{Bugs},
|
|
||||||
times => 'Estimated/Actual/Remaining',
|
|
||||||
alias => 'Alias',
|
alias => 'Alias',
|
||||||
assigned_to => 'Assignee',
|
assigned_to => 'Assignee',
|
||||||
blocked => 'Blocks',
|
blocked => 'Blocks',
|
||||||
|
|
|
@ -151,9 +151,6 @@ sub MessageToMTA {
|
||||||
username => Bugzilla->params->{"smtp_username"},
|
username => Bugzilla->params->{"smtp_username"},
|
||||||
password => Bugzilla->params->{"smtp_password"},
|
password => Bugzilla->params->{"smtp_password"},
|
||||||
helo => $hostname;
|
helo => $hostname;
|
||||||
if (Bugzilla->params->{smtp_debug}) {
|
|
||||||
push @args, debug => 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bugzilla::Hook::process('mailer_before_send',
|
Bugzilla::Hook::process('mailer_before_send',
|
||||||
|
|
|
@ -168,12 +168,8 @@ sub update
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $dbh = Bugzilla->dbh;
|
my $dbh = Bugzilla->dbh;
|
||||||
|
|
||||||
# This is for future when we'll have a single "save" (create/update) method
|
|
||||||
my $is_new = !$self->id;
|
|
||||||
|
|
||||||
# Don't update the DB if something goes wrong below -> transaction.
|
# Don't update the DB if something goes wrong below -> transaction.
|
||||||
$dbh->bz_start_transaction();
|
$dbh->bz_start_transaction();
|
||||||
|
|
||||||
# Bugzilla::Field::Choice is not a threat as we don't have 'value' field
|
# Bugzilla::Field::Choice is not a threat as we don't have 'value' field
|
||||||
# Yet do not call its update() for the future
|
# Yet do not call its update() for the future
|
||||||
my ($changes, $old_self) = Bugzilla::Object::update($self, @_);
|
my ($changes, $old_self) = Bugzilla::Object::update($self, @_);
|
||||||
|
@ -270,16 +266,14 @@ sub update
|
||||||
}
|
}
|
||||||
|
|
||||||
# Also update group settings.
|
# Also update group settings.
|
||||||
if ($is_new || $self->{check_group_controls})
|
if ($self->{check_group_controls})
|
||||||
{
|
{
|
||||||
require Bugzilla::Bug;
|
require Bugzilla::Bug;
|
||||||
|
|
||||||
my $old_settings = !$is_new ? $old_self->group_controls : {};
|
my $old_settings = $old_self->group_controls;
|
||||||
my $new_settings = $self->group_controls;
|
my $new_settings = $self->group_controls;
|
||||||
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
|
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
|
||||||
|
|
||||||
$self->check_open_product;
|
|
||||||
|
|
||||||
foreach my $gid (keys %$new_settings)
|
foreach my $gid (keys %$new_settings)
|
||||||
{
|
{
|
||||||
my $old_setting = $old_settings->{$gid} || {};
|
my $old_setting = $old_settings->{$gid} || {};
|
||||||
|
@ -417,37 +411,6 @@ sub update
|
||||||
return $changes;
|
return $changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub check_open_product
|
|
||||||
{
|
|
||||||
my $self = shift;
|
|
||||||
if (Bugzilla->params->{forbid_open_products})
|
|
||||||
{
|
|
||||||
my $new_settings = $self->group_controls;
|
|
||||||
my $has_mandatory = 0;
|
|
||||||
my $has_entry = 0;
|
|
||||||
foreach my $gid (keys %$new_settings)
|
|
||||||
{
|
|
||||||
if ($new_settings->{$gid}->{entry})
|
|
||||||
{
|
|
||||||
$has_entry = 1;
|
|
||||||
}
|
|
||||||
if ($new_settings->{$gid}->{membercontrol} == CONTROLMAPMANDATORY &&
|
|
||||||
$new_settings->{$gid}->{othercontrol} == CONTROLMAPMANDATORY)
|
|
||||||
{
|
|
||||||
$has_mandatory = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$has_mandatory)
|
|
||||||
{
|
|
||||||
ThrowUserError('product_mandatory_group_required');
|
|
||||||
}
|
|
||||||
if (!$has_entry)
|
|
||||||
{
|
|
||||||
ThrowUserError('product_entry_group_required');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub remove_from_db
|
sub remove_from_db
|
||||||
{
|
{
|
||||||
my ($self, $params) = @_;
|
my ($self, $params) = @_;
|
||||||
|
@ -502,9 +465,6 @@ sub remove_from_db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clear external product reference
|
|
||||||
$dbh->do('UPDATE products SET extproduct=NULL WHERE extproduct=?', undef, $self->id);
|
|
||||||
|
|
||||||
$self->SUPER::remove_from_db();
|
$self->SUPER::remove_from_db();
|
||||||
|
|
||||||
$dbh->bz_commit_transaction();
|
$dbh->bz_commit_transaction();
|
||||||
|
@ -649,7 +609,7 @@ use constant is_default => 0;
|
||||||
sub _create_bug_group
|
sub _create_bug_group
|
||||||
{
|
{
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($create_admin_group, $normal_group) = @_;
|
my ($create_admin_group) = @_;
|
||||||
my $dbh = Bugzilla->dbh;
|
my $dbh = Bugzilla->dbh;
|
||||||
|
|
||||||
my $group_name = ($create_admin_group ? 'admin-' : '') . $self->name;
|
my $group_name = ($create_admin_group ? 'admin-' : '') . $self->name;
|
||||||
|
@ -671,9 +631,9 @@ sub _create_bug_group
|
||||||
|
|
||||||
# Associate the new group and new product.
|
# Associate the new group and new product.
|
||||||
$dbh->do(
|
$dbh->do(
|
||||||
'INSERT INTO group_control_map (group_id, product_id, membercontrol, othercontrol, editcomponents, entry)'.
|
'INSERT INTO group_control_map (group_id, product_id, membercontrol, othercontrol, editcomponents)'.
|
||||||
' VALUES (?, ?, ?, ?, ?, ?)', undef, $group->id, $self->id,
|
' VALUES (?, ?, ?, ?, ?)', undef, $group->id, $self->id,
|
||||||
($create_admin_group ? (0, 0, 1, 0) : (CONTROLMAPMANDATORY, CONTROLMAPMANDATORY, 0, 1))
|
($create_admin_group ? (0, 0, 1) : (CONTROLMAPMANDATORY, CONTROLMAPMANDATORY, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
# Grant current user permission to edit the new group and include him in it
|
# Grant current user permission to edit the new group and include him in it
|
||||||
|
@ -681,17 +641,6 @@ sub _create_bug_group
|
||||||
'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) VALUES (?, ?, ?, ?), (?, ?, ?, ?)',
|
'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type) VALUES (?, ?, ?, ?), (?, ?, ?, ?)',
|
||||||
undef, Bugzilla->user->id, $group->id, 1, 0, Bugzilla->user->id, $group->id, 0, 0
|
undef, Bugzilla->user->id, $group->id, 1, 0, Bugzilla->user->id, $group->id, 0, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
# Allow admin group to grant normal group
|
|
||||||
if ($create_admin_group && $normal_group)
|
|
||||||
{
|
|
||||||
$dbh->do(
|
|
||||||
'INSERT INTO group_group_map (member_id, grantor_id, grant_type) VALUES (?, ?, ?)',
|
|
||||||
undef, $group->id, $normal_group->id, GROUP_BLESS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _create_series
|
sub _create_series
|
||||||
|
|
|
@ -131,23 +131,6 @@ sub _get_names
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_measures
|
|
||||||
{
|
|
||||||
my $cols = Bugzilla::Search->REPORT_COLUMNS();
|
|
||||||
my $descs = {
|
|
||||||
count => Bugzilla->messages->{field_descs}->{count},
|
|
||||||
times => Bugzilla->messages->{field_descs}->{times},
|
|
||||||
};
|
|
||||||
for my $f (keys %$cols)
|
|
||||||
{
|
|
||||||
if ($cols->{$f}->{numeric})
|
|
||||||
{
|
|
||||||
$descs->{$f} = $cols->{$f}->{title};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $descs;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub execute
|
sub execute
|
||||||
{
|
{
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
|
@ -223,45 +206,29 @@ sub execute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $measure_alias = {
|
my $measures = {
|
||||||
etime => 'estimated_time',
|
etime => 'estimated_time',
|
||||||
rtime => 'remaining_time',
|
rtime => 'remaining_time',
|
||||||
wtime => 'interval_time',
|
wtime => 'interval_time',
|
||||||
|
count => '_count',
|
||||||
};
|
};
|
||||||
my $measures = {
|
|
||||||
count => 'count',
|
|
||||||
};
|
|
||||||
my $old_columns = { %{Bugzilla::Search->COLUMNS($runner)} };
|
|
||||||
# Trick Bugzilla::Search: replace report columns SQL + add '_count' column
|
# Trick Bugzilla::Search: replace report columns SQL + add '_count' column
|
||||||
# FIXME: Remove usage of global variable COLUMNS in search generation code
|
# FIXME: Remove usage of global variable COLUMNS in search generation code
|
||||||
%{Bugzilla::Search->COLUMNS($runner)} = (%{Bugzilla::Search->COLUMNS($runner)}, %{Bugzilla::Search->REPORT_COLUMNS($runner)});
|
my %old_columns = %{Bugzilla::Search->COLUMNS($runner)};
|
||||||
Bugzilla::Search->COLUMNS($runner)->{count}->{name} = '1';
|
%{Bugzilla::Search->COLUMNS($runner)} = (%{Bugzilla::Search->COLUMNS($runner)}, %{Bugzilla::Search->REPORT_COLUMNS});
|
||||||
my $columns = Bugzilla::Search->COLUMNS($runner);
|
Bugzilla::Search->COLUMNS($runner)->{_count}->{name} = '1';
|
||||||
for my $column (keys %$columns)
|
|
||||||
{
|
|
||||||
if ($columns->{$column}->{numeric})
|
|
||||||
{
|
|
||||||
$measures->{$column} = $column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
my $measure = $ARGS->{measure} || '';
|
my $measure = $ARGS->{measure} || '';
|
||||||
$measure = $measure_alias->{$measure} || $measure;
|
|
||||||
# Check that $measure is available (+ etime/rtime/wtime is usable only in table mode)
|
|
||||||
if ($measure eq 'times' ? !$is_table : !$measures->{$measure})
|
if ($measure eq 'times' ? !$is_table : !$measures->{$measure})
|
||||||
{
|
{
|
||||||
$measure = 'count';
|
$measure = 'count';
|
||||||
}
|
}
|
||||||
# If the user has no access to the measured column, reset it to 'count'
|
|
||||||
if (!Bugzilla::Search->COLUMNS($runner)->{$measure eq 'times' ? 'remaining_time' : $measures->{$measure}})
|
|
||||||
{
|
|
||||||
$measure = 'count';
|
|
||||||
}
|
|
||||||
|
|
||||||
# Validate the values in the axis fields or throw an error.
|
# Validate the values in the axis fields or throw an error.
|
||||||
my %a;
|
my %a;
|
||||||
my @group_by = grep { !($a{$_}++) } values %$field;
|
my @group_by = grep { !($a{$_}++) } values %$field;
|
||||||
my @axis_fields = @group_by;
|
my @axis_fields = @group_by;
|
||||||
for ($measure eq 'times' ? qw(estimated_time remaining_time interval_time) : $measure)
|
for ($measure eq 'times' ? qw(etime rtime wtime) : $measure)
|
||||||
{
|
{
|
||||||
push @axis_fields, $measures->{$_} unless $a{$measures->{$_}};
|
push @axis_fields, $measures->{$_} unless $a{$measures->{$_}};
|
||||||
}
|
}
|
||||||
|
@ -278,7 +245,7 @@ sub execute
|
||||||
($field->{x} || "''")." x, ".
|
($field->{x} || "''")." x, ".
|
||||||
($field->{y} || "''")." y, ".
|
($field->{y} || "''")." y, ".
|
||||||
($field->{z} || "''")." z, ".
|
($field->{z} || "''")." z, ".
|
||||||
join(', ', map { "SUM($measures->{$_}) $_" } ($measure eq 'times' ? qw(etime rtime wtime) : $measure)).
|
join(', ', map { "SUM($measures->{$_}) $_" } $measure eq 'times' ? qw(etime rtime wtime) : $measure).
|
||||||
" FROM ($query) _report_table GROUP BY ".join(", ", @group_by);
|
" FROM ($query) _report_table GROUP BY ".join(", ", @group_by);
|
||||||
|
|
||||||
$::SIG{TERM} = 'DEFAULT';
|
$::SIG{TERM} = 'DEFAULT';
|
||||||
|
@ -291,9 +258,11 @@ sub execute
|
||||||
my %data;
|
my %data;
|
||||||
my %names;
|
my %names;
|
||||||
|
|
||||||
# Read the bug data and count the bugs for each possible value of row, column and table.
|
# Read the bug data and count the bugs for each possible value of row, column
|
||||||
|
# and table.
|
||||||
#
|
#
|
||||||
# We detect a numerical field, and sort appropriately, if all the values are numeric.
|
# We detect a numerical field, and sort appropriately, if all the values are
|
||||||
|
# numeric.
|
||||||
my %isnumeric;
|
my %isnumeric;
|
||||||
|
|
||||||
foreach my $group (@$results)
|
foreach my $group (@$results)
|
||||||
|
@ -394,7 +363,7 @@ sub execute
|
||||||
$vars->{cumulate} = $ARGS->{cumulate} ? 1 : 0;
|
$vars->{cumulate} = $ARGS->{cumulate} ? 1 : 0;
|
||||||
$vars->{x_labels_vertical} = $ARGS->{x_labels_vertical} ? 1 : 0;
|
$vars->{x_labels_vertical} = $ARGS->{x_labels_vertical} ? 1 : 0;
|
||||||
|
|
||||||
%{Bugzilla::Search->COLUMNS($runner)} = %$old_columns;
|
%{Bugzilla::Search->COLUMNS($runner)} = %old_columns;
|
||||||
|
|
||||||
return $vars;
|
return $vars;
|
||||||
}
|
}
|
||||||
|
|
|
@ -463,12 +463,11 @@ sub STATIC_COLUMNS
|
||||||
reporter_short => { title => 'Reporter Login' },
|
reporter_short => { title => 'Reporter Login' },
|
||||||
qa_contact_short => { title => 'QA Contact Login' },
|
qa_contact_short => { title => 'QA Contact Login' },
|
||||||
# FIXME save aggregated work_time in bugs table and search on it
|
# FIXME save aggregated work_time in bugs table and search on it
|
||||||
work_time => { name => $actual_time, numeric => 1 },
|
work_time => { name => $actual_time },
|
||||||
interval_time => { name => $actual_time, title => 'Period Worktime', numeric => 1 },
|
interval_time => { name => $actual_time, title => 'Period Worktime', noreports => 1 },
|
||||||
percentage_complete => {
|
percentage_complete => {
|
||||||
name => "(CASE WHEN $actual_time + bugs.remaining_time = 0.0 THEN 0.0" .
|
name => "(CASE WHEN $actual_time + bugs.remaining_time = 0.0 THEN 0.0" .
|
||||||
" ELSE 100 * ($actual_time / ($actual_time + bugs.remaining_time)) END)",
|
" ELSE 100 * ($actual_time / ($actual_time + bugs.remaining_time)) END)",
|
||||||
numeric => 1,
|
|
||||||
},
|
},
|
||||||
'flagtypes.name' => {
|
'flagtypes.name' => {
|
||||||
name =>
|
name =>
|
||||||
|
@ -549,8 +548,6 @@ sub STATIC_COLUMNS
|
||||||
foreach my $col (qw(assigned_to reporter qa_contact))
|
foreach my $col (qw(assigned_to reporter qa_contact))
|
||||||
{
|
{
|
||||||
my $sql = "map_${col}.login_name";
|
my $sql = "map_${col}.login_name";
|
||||||
$columns->{$col}->{name} = $sql;
|
|
||||||
$columns->{$col}->{trim_email} = 1;
|
|
||||||
$columns->{$col.'_realname'}->{name} = "map_${col}.realname";
|
$columns->{$col.'_realname'}->{name} = "map_${col}.realname";
|
||||||
$columns->{$col.'_short'}->{name} = $dbh->sql_string_until($sql, $dbh->quote('@'));
|
$columns->{$col.'_short'}->{name} = $dbh->sql_string_until($sql, $dbh->quote('@'));
|
||||||
# Only the qa_contact field can be NULL
|
# Only the qa_contact field can be NULL
|
||||||
|
@ -617,19 +614,9 @@ sub STATIC_COLUMNS
|
||||||
" FROM ".$type->REL_TABLE.", $t WHERE $t.".$type->ID_FIELD."=".$type->REL_TABLE.".value_id".
|
" FROM ".$type->REL_TABLE.", $t WHERE $t.".$type->ID_FIELD."=".$type->REL_TABLE.".value_id".
|
||||||
" AND ".$type->REL_TABLE.".bug_id=bugs.bug_id)";
|
" AND ".$type->REL_TABLE.".bug_id=bugs.bug_id)";
|
||||||
}
|
}
|
||||||
elsif ($field->type == FIELD_TYPE_EAV_TEXTAREA)
|
|
||||||
{
|
|
||||||
my $t = "bug_".$field->name;
|
|
||||||
$columns->{$id}->{name} = "$t.value";
|
|
||||||
$columns->{$id}->{joins} = [ "LEFT JOIN $t ON $t.bug_id=bugs.bug_id" ];
|
|
||||||
}
|
|
||||||
elsif ($bug_columns->{$id})
|
elsif ($bug_columns->{$id})
|
||||||
{
|
{
|
||||||
$columns->{$id}->{name} ||= "bugs.$id";
|
$columns->{$id}->{name} ||= "bugs.$id";
|
||||||
if ($field->type == FIELD_TYPE_NUMERIC)
|
|
||||||
{
|
|
||||||
$columns->{$id}->{numeric} = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,17 +646,6 @@ sub STATIC_COLUMNS
|
||||||
sortkey => 1,
|
sortkey => 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
elsif ($subid eq 'assigned_to' || $subid eq 'reporter' || $subid eq 'qa_contact')
|
|
||||||
{
|
|
||||||
$columns->{$id.'_'.$subid} = {
|
|
||||||
name => "map_${id}_${subid}.login_name",
|
|
||||||
title => $field->description . ' ' . $subfield->description,
|
|
||||||
subid => $subid,
|
|
||||||
sortkey => 1,
|
|
||||||
trim_email => 1,
|
|
||||||
joins => [ @$join, "LEFT JOIN profiles AS map_${id}_${subid} ON bugs_${id}.${subid}=map_${id}_${subid}.userid" ],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
elsif ($subid eq 'product' || $subid eq 'component' || $subid eq 'classification')
|
elsif ($subid eq 'product' || $subid eq 'component' || $subid eq 'classification')
|
||||||
{
|
{
|
||||||
$columns->{$id.'_'.$subid} = {
|
$columns->{$id.'_'.$subid} = {
|
||||||
|
@ -685,17 +661,6 @@ sub STATIC_COLUMNS
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
elsif ($subid eq 'deadline')
|
|
||||||
{
|
|
||||||
$columns->{$id.'_'.$subid} = {
|
|
||||||
name => $dbh->sql_date_format("bugs_$id.deadline", '%Y-%m-%d'),
|
|
||||||
raw_name => "bugs_$id.deadline",
|
|
||||||
title => $field->description . ' ' . $subfield->description,
|
|
||||||
subid => $subid,
|
|
||||||
sortkey => 1,
|
|
||||||
joins => [ @$join ],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
elsif ($subfield->type == FIELD_TYPE_SINGLE_SELECT)
|
elsif ($subfield->type == FIELD_TYPE_SINGLE_SELECT)
|
||||||
{
|
{
|
||||||
my $type = $subfield->value_type;
|
my $type = $subfield->value_type;
|
||||||
|
@ -723,17 +688,6 @@ sub STATIC_COLUMNS
|
||||||
sortkey => 1,
|
sortkey => 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
elsif ($subfield->type == FIELD_TYPE_MULTI_SELECT)
|
|
||||||
{
|
|
||||||
my $t = 'bug_'.$subfield->name;
|
|
||||||
$columns->{$id.'_'.$subid} = {
|
|
||||||
name => "bugs_$id"."_$t.value",
|
|
||||||
title => $field->description . ' ' . $subfield->description,
|
|
||||||
joins => [ @$join, "LEFT JOIN $t bugs_$id"."_$t ON bugs_$id"."_$t.bug_id=bugs_$id.bug_id" ],
|
|
||||||
subid => $subid,
|
|
||||||
sortkey => 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,51 +746,35 @@ sub COLUMNS
|
||||||
# Not using JOIN (it could be joined on bug_when=creation_ts),
|
# Not using JOIN (it could be joined on bug_when=creation_ts),
|
||||||
# because it would require COALESCE to an 'isprivate' subquery
|
# because it would require COALESCE to an 'isprivate' subquery
|
||||||
# for private comments.
|
# for private comments.
|
||||||
$columns->{comment0} = {
|
$columns->{comment0}->{name} =
|
||||||
%{$columns->{comment0}},
|
"(SELECT thetext FROM longdescs ldc0$hint WHERE ldc0.bug_id = bugs.bug_id $priv".
|
||||||
name => "(SELECT thetext FROM longdescs ldc0$hint WHERE ldc0.bug_id = bugs.bug_id $priv".
|
" ORDER BY ldc0.bug_when LIMIT 1)";
|
||||||
" ORDER BY ldc0.bug_when LIMIT 1)",
|
$columns->{lastcomment}->{name} =
|
||||||
};
|
"(SELECT thetext FROM longdescs ldc0$hint WHERE ldc0.bug_id = bugs.bug_id $priv".
|
||||||
$columns->{lastcomment} = {
|
" ORDER BY ldc0.bug_when DESC LIMIT 1)";
|
||||||
%{$columns->{lastcomment}},
|
|
||||||
name => "(SELECT thetext FROM longdescs ldc0$hint WHERE ldc0.bug_id = bugs.bug_id $priv".
|
|
||||||
" ORDER BY ldc0.bug_when DESC LIMIT 1)",
|
|
||||||
};
|
|
||||||
# Last commenter and last comment time
|
# Last commenter and last comment time
|
||||||
my $login = 'ldp0.login_name';
|
my $login = 'ldp0.login_name';
|
||||||
if (!$user->id)
|
if (!$user->id)
|
||||||
{
|
{
|
||||||
$login = $dbh->sql_string_until($login, $dbh->quote('@'));
|
$login = $dbh->sql_string_until($login, $dbh->quote('@'));
|
||||||
}
|
}
|
||||||
$columns->{lastcommenter} = {
|
$columns->{lastcommenter}->{name} =
|
||||||
%{$columns->{lastcommenter}},
|
"(SELECT $login FROM longdescs ldc0$hint".
|
||||||
name => "(SELECT $login FROM longdescs ldc0$hint".
|
" INNER JOIN profiles ldp0 ON ldp0.userid=ldc0.who WHERE ldc0.bug_id = bugs.bug_id $priv".
|
||||||
" INNER JOIN profiles ldp0 ON ldp0.userid=ldc0.who WHERE ldc0.bug_id = bugs.bug_id $priv".
|
" ORDER BY ldc0.bug_when DESC LIMIT 1)";
|
||||||
" ORDER BY ldc0.bug_when DESC LIMIT 1)",
|
|
||||||
};
|
|
||||||
$priv = ($user->is_insider ? "" : "AND lct.isprivate=0 ");
|
$priv = ($user->is_insider ? "" : "AND lct.isprivate=0 ");
|
||||||
$columns->{last_comment_time} = {
|
$columns->{last_comment_time}->{name} =
|
||||||
%{$columns->{last_comment_time}},
|
"(SELECT MAX(lct.bug_when) FROM longdescs lct$hint WHERE lct.bug_id = bugs.bug_id $priv)";
|
||||||
name => "(SELECT MAX(lct.bug_when) FROM longdescs lct$hint WHERE lct.bug_id = bugs.bug_id $priv)",
|
|
||||||
};
|
|
||||||
|
|
||||||
# Hide email domain for anonymous users
|
# Hide email domain for anonymous users
|
||||||
$columns->{cc}->{name} = "(SELECT ".$dbh->sql_group_concat(($user->id
|
$columns->{cc}->{name} = "(SELECT ".$dbh->sql_group_concat(($user->id
|
||||||
? 'profiles.login_name'
|
? 'profiles.login_name'
|
||||||
: $dbh->sql_string_until('profiles.login_name', $dbh->quote('@'))), "','").
|
: $dbh->sql_string_until('profiles.login_name', $dbh->quote('@'))), "','").
|
||||||
" cc FROM cc, profiles WHERE cc.bug_id=bugs.bug_id AND cc.who=profiles.userid)";
|
" cc FROM cc, profiles WHERE cc.bug_id=bugs.bug_id AND cc.who=profiles.userid)";
|
||||||
if (!$user->id)
|
foreach my $col (qw(assigned_to reporter qa_contact))
|
||||||
{
|
{
|
||||||
foreach my $col (keys %$columns)
|
my $sql = "map_${col}.login_name";
|
||||||
{
|
$columns->{$col}->{name} = $user->id ? $sql : $columns->{$col.'_short'}->{name};
|
||||||
if ($columns->{$col}->{trim_email})
|
|
||||||
{
|
|
||||||
$columns->{$col} = {
|
|
||||||
%{$columns->{$col}},
|
|
||||||
name => $dbh->sql_string_until($columns->{$col}->{name}, $dbh->quote('@')),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bugzilla::Hook::process('buglist_columns', { columns => $columns });
|
Bugzilla::Hook::process('buglist_columns', { columns => $columns });
|
||||||
|
@ -1015,11 +953,10 @@ sub FUNCTIONS
|
||||||
type => FIELD_TYPE_BUG_ID_REV,
|
type => FIELD_TYPE_BUG_ID_REV,
|
||||||
obsolete => 0
|
obsolete => 0
|
||||||
});
|
});
|
||||||
my @bugid_fields = Bugzilla->get_fields({ type => FIELD_TYPE_BUG_ID });
|
my $date_fields = join '|', map { $_->name } Bugzilla->get_fields({
|
||||||
my $date_fields = join '|', ((map { $_->name } Bugzilla->get_fields({
|
|
||||||
type => FIELD_TYPE_DATETIME,
|
type => FIELD_TYPE_DATETIME,
|
||||||
obsolete => 0
|
obsolete => 0
|
||||||
})), (map { $_->name.'_deadline' } @bugid_fields));
|
});
|
||||||
$FUNCTIONS = {
|
$FUNCTIONS = {
|
||||||
'blocked|dependson' => {
|
'blocked|dependson' => {
|
||||||
'*' => \&_blocked_dependson,
|
'*' => \&_blocked_dependson,
|
||||||
|
@ -2599,12 +2536,6 @@ sub _content_matches
|
||||||
}
|
}
|
||||||
$text =~ s/((?<!\\)(?:\\\\)*)([$pattern_part])/$1\\$2/gs;
|
$text =~ s/((?<!\\)(?:\\\\)*)([$pattern_part])/$1\\$2/gs;
|
||||||
$text =~ s/(?<=[\s-])-(?=[\s-])/\\-/gso;
|
$text =~ s/(?<=[\s-])-(?=[\s-])/\\-/gso;
|
||||||
$text =~ s/\s+$//so;
|
|
||||||
if (!$text)
|
|
||||||
{
|
|
||||||
ThrowUserError('invalid_fulltext_query');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$text = ($self->{user}->is_insider ? '@(short_desc,comments,comments_private) ' : '@(short_desc,comments) ') . $text;
|
$text = ($self->{user}->is_insider ? '@(short_desc,comments,comments_private) ' : '@(short_desc,comments) ') . $text;
|
||||||
if ($dbh->isa('Bugzilla::DB::Mysql') &&
|
if ($dbh->isa('Bugzilla::DB::Mysql') &&
|
||||||
Bugzilla->localconfig->{sphinxse_port})
|
Bugzilla->localconfig->{sphinxse_port})
|
||||||
|
@ -2613,8 +2544,7 @@ sub _content_matches
|
||||||
$text =~ s/;/\\;/gso;
|
$text =~ s/;/\\;/gso;
|
||||||
$text =~ s/\\/\\\\/gso;
|
$text =~ s/\\/\\\\/gso;
|
||||||
# Space is needed after $text so Sphinx doesn't escape ";"
|
# Space is needed after $text so Sphinx doesn't escape ";"
|
||||||
my $maxm = Bugzilla->params->{sphinx_max_matches} || 1000;
|
$text = "$text ;mode=extended;limit=1000;fieldweights=short_desc,5,comments,1,comments_private,1";
|
||||||
$text = "$text ;mode=extended;limit=$maxm;maxmatches=$maxm;fieldweights=short_desc,5,comments,1,comments_private,1";
|
|
||||||
$self->{term} = {
|
$self->{term} = {
|
||||||
table => "bugs_fulltext_sphinx $table",
|
table => "bugs_fulltext_sphinx $table",
|
||||||
where => "$table.query=".$dbh->quote($text),
|
where => "$table.query=".$dbh->quote($text),
|
||||||
|
@ -2682,11 +2612,7 @@ sub _timestamp_compare
|
||||||
{
|
{
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $dbh = Bugzilla->dbh;
|
my $dbh = Bugzilla->dbh;
|
||||||
if ($self->{columns}->{$self->{field}}->{joins})
|
$self->{fieldsql} = 'bugs.'.$self->{field};
|
||||||
{
|
|
||||||
push @{$self->{supptables}}, @{$self->{columns}->{$self->{field}}->{joins}};
|
|
||||||
}
|
|
||||||
$self->{fieldsql} = $self->{columns}->{$self->{field}}->{raw_name} || 'bugs.'.$self->{field};
|
|
||||||
if ($self->{value} =~ /^[+-]?\d+[dhwmy]$/is)
|
if ($self->{value} =~ /^[+-]?\d+[dhwmy]$/is)
|
||||||
{
|
{
|
||||||
$self->{value} = SqlifyDate($self->{value});
|
$self->{value} = SqlifyDate($self->{value});
|
||||||
|
@ -3123,7 +3049,21 @@ sub _equals
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$self->{term} = "($self->{fieldsql} = $self->{quoted} AND $self->{fieldsql} IS NOT NULL)";
|
$self->{term} = "$self->{fieldsql} = $self->{quoted}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _notequals
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
if ($self->{value} eq '')
|
||||||
|
{
|
||||||
|
$self->{term} = "($self->{fieldsql} != $self->{quoted} AND $self->{fieldsql} IS NOT NULL)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$self->{allow_null} = 1;
|
||||||
|
$self->{term} = "$self->{fieldsql} != $self->{quoted}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3432,7 +3372,7 @@ sub negate_expression
|
||||||
if (ref $q eq 'HASH')
|
if (ref $q eq 'HASH')
|
||||||
{
|
{
|
||||||
$q->{neg} = !$q->{neg};
|
$q->{neg} = !$q->{neg};
|
||||||
$q->{allow_null} = ($q->{allow_null} || 0) == 1 ? 0 : 1;
|
$q->{allow_null} = ($q->{allow_null} || 0) == 1 ? 0 : $q->{allow_null};
|
||||||
$q->{description}->[1] = NEGATE_ALL_OPERATORS->{$q->{description}->[1]} || $q->{description}->[1];
|
$q->{description}->[1] = NEGATE_ALL_OPERATORS->{$q->{description}->[1]} || $q->{description}->[1];
|
||||||
}
|
}
|
||||||
elsif (!ref $q)
|
elsif (!ref $q)
|
||||||
|
|
|
@ -171,17 +171,13 @@ sub makeTables
|
||||||
{
|
{
|
||||||
if (scalar($line =~ s/(\t+|│+)/$1/gso) > 0)
|
if (scalar($line =~ s/(\t+|│+)/$1/gso) > 0)
|
||||||
{
|
{
|
||||||
$line =~ /[─┌┐└┘├┴┬┤┼]+/gso; # legacy ascii tables
|
$line =~ s/^\s*│\s*//;
|
||||||
$line =~ s/^\s*│\s*//s;
|
$table->add(split /\t+|\s*│+\s*/, $line);
|
||||||
$line =~ s/\s*│\s*$//s;
|
|
||||||
$line = [ split /\t+\s*|\s*│+\s*/, $line ];
|
|
||||||
$line = '<tr><td>'.join('</td><td>', @$line).'</td></tr>';
|
|
||||||
$table .= "\n".$line;
|
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$wrappedcomment .= "<table class='bz_fmt_table'>$table</table>\n";
|
$wrappedcomment .= "\0\1".$table->render."\0\1";
|
||||||
$table = undef;
|
$table = undef;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,19 +185,21 @@ sub makeTables
|
||||||
if ($n > 1 && length($line) < MAX_TABLE_COLS)
|
if ($n > 1 && length($line) < MAX_TABLE_COLS)
|
||||||
{
|
{
|
||||||
# Table
|
# Table
|
||||||
$line =~ /[─┌┐└┘├┴┬┤┼]+/gso; # legacy ascii tables
|
$line =~ s/^\s*│\s*//;
|
||||||
$line =~ s/^\s*│\s*//s;
|
$line =~ s/\s*│\s*$//;
|
||||||
$line =~ s/\s*│\s*$//s;
|
$table = Text::TabularDisplay::Utf8->new;
|
||||||
$line = [ split /\t+\s*|\s*│+\s*/, $line ];
|
$table->add(split /\t+|\s*│+\s*/, $line);
|
||||||
$table = "<tr><td>".join('</td><td>', @$line)."</td></tr>\n";
|
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
$line =~ s/\t/ /gso;
|
unless ($line =~ /^[│─┌┐└┘├┴┬┤┼].*[│─┌┐└┘├┴┬┤┼]$/iso)
|
||||||
|
{
|
||||||
|
$line =~ s/\t/ /gso;
|
||||||
|
}
|
||||||
$wrappedcomment .= $line . "\n";
|
$wrappedcomment .= $line . "\n";
|
||||||
}
|
}
|
||||||
if ($table)
|
if ($table)
|
||||||
{
|
{
|
||||||
$wrappedcomment .= "<table class='bz_fmt_table'>$table</table>\n";
|
$wrappedcomment .= "\0\1".$table->render."\0\1";
|
||||||
}
|
}
|
||||||
return $wrappedcomment;
|
return $wrappedcomment;
|
||||||
}
|
}
|
||||||
|
@ -217,6 +215,8 @@ sub quoteUrls
|
||||||
my ($text, $bug, $comment) = (@_);
|
my ($text, $bug, $comment) = (@_);
|
||||||
return $text unless $text;
|
return $text unless $text;
|
||||||
|
|
||||||
|
$text = makeTables($text);
|
||||||
|
|
||||||
# We use /g for speed, but uris can have other things inside them
|
# We use /g for speed, but uris can have other things inside them
|
||||||
# (http://foo/bug#3 for example). Filtering that out filters valid
|
# (http://foo/bug#3 for example). Filtering that out filters valid
|
||||||
# bug refs out, so we have to do replacements.
|
# bug refs out, so we have to do replacements.
|
||||||
|
@ -289,12 +289,15 @@ sub quoteUrls
|
||||||
~gesix;
|
~gesix;
|
||||||
}
|
}
|
||||||
|
|
||||||
# the protocol + non-whitespace + recursive braces + ending in [\w/~=)]
|
$text =~ s~
|
||||||
$text =~ s/\b((?:$safe_protocols):((?>[^\s<>\"\(\)]+|\((?:(?2)|\")*\)))+(?<=[\w\/~=)]))/
|
\b((?:$safe_protocols): # The protocol:
|
||||||
|
[^\s<>\"]+ # Any non-whitespace
|
||||||
|
[\w\/]) # so that we end in \w or /
|
||||||
|
~
|
||||||
($tmp = html_quote($1)) &&
|
($tmp = html_quote($1)) &&
|
||||||
($things[$count++] = "<a href=\"$tmp\">$tmp<\/a>") &&
|
($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
|
||||||
("\0\0" . ($count-1) . "\0\0")
|
("\0\0" . ($count-1) . "\0\0")
|
||||||
/gesox;
|
~gesox;
|
||||||
|
|
||||||
if ($custom_proto && %$custom_proto)
|
if ($custom_proto && %$custom_proto)
|
||||||
{
|
{
|
||||||
|
@ -337,14 +340,11 @@ sub quoteUrls
|
||||||
my $q = { '<' => '<', '>' => '>', '&' => '&', '"' => '"' };
|
my $q = { '<' => '<', '>' => '>', '&' => '&', '"' => '"' };
|
||||||
my $safe_tags = '(?:b|i|u|hr|marquee|s|strike|strong|small|big|sub|sup|tt|em|cite|font(?:\s+color=["\']?(?:#[0-9a-f]{3,6}|[a-z]+)["\']?)?)';
|
my $safe_tags = '(?:b|i|u|hr|marquee|s|strike|strong|small|big|sub|sup|tt|em|cite|font(?:\s+color=["\']?(?:#[0-9a-f]{3,6}|[a-z]+)["\']?)?)';
|
||||||
my $block_tags = '(?:h[1-6]|center|ol|ul|li)';
|
my $block_tags = '(?:h[1-6]|center|ol|ul|li)';
|
||||||
$text =~ s/<pre>((?:(?>.+?)(?:<pre>(?1)<\/pre>)?)+)?<\/pre>|\s*(<\/?$block_tags>)\s*|(<\/?$safe_tags>)|([<>&\"])/$4 ? $q->{$4} : ($1 eq '' ? lc($2 eq '' ? $3 : $2) : html_quote($1))/geiso;
|
$text =~ s/<pre>((?:.*?(?:<pre>(?1)<\/pre>)?)*)<\/pre>|\s*(<\/?$block_tags>)\s*|(<\/?$safe_tags>)|([<>&\"])/$4 ? $q->{$4} : ($1 eq '' ? lc($2 eq '' ? $3 : $2) : html_quote($1))/geiso;
|
||||||
|
|
||||||
# Replace nowrap markers (\1\0\1)
|
# Replace nowrap markers (\1\0\1)
|
||||||
$text =~ s/\x01\x00\x01(.*?)\x01\x00\x01/<div style="white-space: nowrap">$1<\/div>/gso;
|
$text =~ s/\x01\x00\x01(.*?)\x01\x00\x01/<div style="white-space: nowrap">$1<\/div>/gso;
|
||||||
|
|
||||||
# Replace tables
|
|
||||||
$text = makeTables($text);
|
|
||||||
|
|
||||||
# Color quoted text
|
# Color quoted text
|
||||||
$text = makeCitations($text);
|
$text = makeCitations($text);
|
||||||
|
|
||||||
|
@ -425,12 +425,13 @@ sub unquote_wiki_url
|
||||||
my $a;
|
my $a;
|
||||||
$anchor = $1;
|
$anchor = $1;
|
||||||
# decode MediaWiki page section name (only correct UTF8 sequences)
|
# decode MediaWiki page section name (only correct UTF8 sequences)
|
||||||
$anchor =~ s/((?:
|
$anchor =~ s/(
|
||||||
\.[0-7][A-F0-9]|
|
\.[0-7][A-F0-9]|
|
||||||
\.[CD][A-F0-9]\.[89AB][A-F0-9]|
|
\.[CD][A-F0-9]\.[89AB][A-F0-9]|
|
||||||
\.E[A-F0-9](?:\.[89AB][A-F0-9]){2}|
|
\.E[A-F0-9](?:\.[89AB][A-F0-9]){2}|
|
||||||
\.F[0-7](?:\.[89AB][A-F0-9]){3}
|
\.F[0-7](?:\.[89AB][A-F0-9]){3}
|
||||||
)+)/($a = $1), ($a =~ tr!.!\%!), (url_decode($a))/gesx;
|
)/($a = $1), ($a =~ tr!.!\%!), ($a)/gesx;
|
||||||
|
$anchor = url_decode($anchor);
|
||||||
$anchor =~ tr/_/ /;
|
$anchor =~ tr/_/ /;
|
||||||
}
|
}
|
||||||
$article =~ s/&.*$//so if $wikiurl =~ /title=$/so;
|
$article =~ s/&.*$//so if $wikiurl =~ /title=$/so;
|
||||||
|
@ -439,11 +440,8 @@ sub unquote_wiki_url
|
||||||
Encode::_utf8_on($linkurl);
|
Encode::_utf8_on($linkurl);
|
||||||
Encode::_utf8_on($article);
|
Encode::_utf8_on($article);
|
||||||
Encode::_utf8_on($anchor);
|
Encode::_utf8_on($anchor);
|
||||||
if (utf8::valid($article) && utf8::valid($anchor))
|
$linkurl = '<a href="'.html_quote($wikiurl.$linkurl).'">'.$wikiname.':[['.$article.($anchor eq '' ? '' : '#'.$anchor).']]</a>';
|
||||||
{
|
return $linkurl;
|
||||||
$linkurl = '<a href="'.html_quote($wikiurl.$linkurl).'">'.$wikiname.':[['.$article.($anchor eq '' ? '' : '#'.$anchor).']]</a>';
|
|
||||||
return $linkurl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$linkurl = html_quote($wikiurl.$linkurl);
|
$linkurl = html_quote($wikiurl.$linkurl);
|
||||||
return "<a href=\"$linkurl\">$linkurl</a>";
|
return "<a href=\"$linkurl\">$linkurl</a>";
|
||||||
|
@ -956,21 +954,6 @@ sub create
|
||||||
# html_quote in the form of a function
|
# html_quote in the form of a function
|
||||||
html => \&html_quote,
|
html => \&html_quote,
|
||||||
|
|
||||||
# escape regular expression characters
|
|
||||||
regex_escape => sub
|
|
||||||
{
|
|
||||||
my ($s) = @_;
|
|
||||||
return "\Q$s\E";
|
|
||||||
},
|
|
||||||
|
|
||||||
# escape regular expression replacement characters
|
|
||||||
replacement_escape => sub
|
|
||||||
{
|
|
||||||
my ($s) = @_;
|
|
||||||
$s =~ s/([\\\$])/\\$1/gso;
|
|
||||||
return $s;
|
|
||||||
},
|
|
||||||
|
|
||||||
# HTML <select>
|
# HTML <select>
|
||||||
# html_select(name, <selected value>, <values>, [<value names>], [<attr_hash>])
|
# html_select(name, <selected value>, <values>, [<value names>], [<attr_hash>])
|
||||||
# <values> may be one of:
|
# <values> may be one of:
|
||||||
|
|
|
@ -60,6 +60,7 @@ use List::Util qw(first);
|
||||||
use Scalar::Util qw(tainted blessed);
|
use Scalar::Util qw(tainted blessed);
|
||||||
use Template::Filters;
|
use Template::Filters;
|
||||||
use Text::Wrap;
|
use Text::Wrap;
|
||||||
|
use Text::TabularDisplay::Utf8;
|
||||||
use JSON;
|
use JSON;
|
||||||
|
|
||||||
use Data::Dumper qw(Dumper);
|
use Data::Dumper qw(Dumper);
|
||||||
|
@ -456,18 +457,17 @@ sub wrap_comment # makeParagraphs
|
||||||
my $tmp;
|
my $tmp;
|
||||||
my $text = '';
|
my $text = '';
|
||||||
my $block_tags = '(?:div|h[1-6]|center|ol|ul|li)';
|
my $block_tags = '(?:div|h[1-6]|center|ol|ul|li)';
|
||||||
my $table_tags = '(?:table|tbody|thead|tr|td|th)';
|
|
||||||
while ($input ne '')
|
while ($input ne '')
|
||||||
{
|
{
|
||||||
# Convert double line breaks to new paragraphs
|
# Convert double line breaks to new paragraphs
|
||||||
if ($input =~ m!\n\s*\n|(</?$table_tags[^<>]*>)|(</?$block_tags[^<>]*>)!so)
|
if ($input =~ m!\n\s*\n|(</?$block_tags[^<>]*>)!so)
|
||||||
{
|
{
|
||||||
@m = (substr($input, 0, $-[0]), $1||$2, $1);
|
@m = (substr($input, 0, $-[0]), $1);
|
||||||
$input = substr($input, $+[0]);
|
$input = substr($input, $+[0]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@m = ($input, '', '');
|
@m = ($input, '');
|
||||||
$input = '';
|
$input = '';
|
||||||
}
|
}
|
||||||
if ($m[0] ne '')
|
if ($m[0] ne '')
|
||||||
|
@ -476,7 +476,7 @@ sub wrap_comment # makeParagraphs
|
||||||
$m[0] =~ s/^\s*\n//s;
|
$m[0] =~ s/^\s*\n//s;
|
||||||
$m[0] =~ s/^([ \t]+)/$tmp = $1; s!\t! !g; $tmp/emog;
|
$m[0] =~ s/^([ \t]+)/$tmp = $1; s!\t! !g; $tmp/emog;
|
||||||
$m[0] =~ s/(<[^<>]*>)|( +)/$1 || ' '.(' ' x (length($2)-1))/ge;
|
$m[0] =~ s/(<[^<>]*>)|( +)/$1 || ' '.(' ' x (length($2)-1))/ge;
|
||||||
if (!$p && $m[0] ne '' && !$m[2])
|
if (!$p && $m[0] ne '')
|
||||||
{
|
{
|
||||||
$text .= '<p>';
|
$text .= '<p>';
|
||||||
$p = 1;
|
$p = 1;
|
||||||
|
@ -625,15 +625,15 @@ sub bz_crypt
|
||||||
$algorithm = $1;
|
$algorithm = $1;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Wide characters cause crypt to die
|
|
||||||
if (Bugzilla->params->{utf8})
|
|
||||||
{
|
|
||||||
utf8::encode($password) if utf8::is_utf8($password);
|
|
||||||
}
|
|
||||||
|
|
||||||
my $crypted_password;
|
my $crypted_password;
|
||||||
if (!$algorithm)
|
if (!$algorithm)
|
||||||
{
|
{
|
||||||
|
# Wide characters cause crypt to die
|
||||||
|
if (Bugzilla->params->{utf8})
|
||||||
|
{
|
||||||
|
utf8::encode($password) if utf8::is_utf8($password);
|
||||||
|
}
|
||||||
|
|
||||||
# Crypt the password.
|
# Crypt the password.
|
||||||
$crypted_password = crypt($password, $salt);
|
$crypted_password = crypt($password, $salt);
|
||||||
|
|
||||||
|
|
|
@ -80,11 +80,11 @@ sub refresh_some_views
|
||||||
my $storedquery = Bugzilla::Search::Saved->new({ name => $q, user => $userid }) or next;
|
my $storedquery = Bugzilla::Search::Saved->new({ name => $q, user => $userid }) or next;
|
||||||
$storedquery = http_decode_query($storedquery->query);
|
$storedquery = http_decode_query($storedquery->query);
|
||||||
# get SQL code
|
# get SQL code
|
||||||
my $search = eval { new Bugzilla::Search(
|
my $search = new Bugzilla::Search(
|
||||||
params => $storedquery,
|
params => $storedquery,
|
||||||
fields => [ 'bug_id', grep { $_ ne 'bug_id' } split(/[ ,]+/, $storedquery->{columnlist} || '') ],
|
fields => [ 'bug_id', grep { $_ ne 'bug_id' } split(/[ ,]+/, $storedquery->{columnlist} || '') ],
|
||||||
user => $userobj,
|
user => $userobj,
|
||||||
) } or next;
|
) or next;
|
||||||
# Re-create views
|
# Re-create views
|
||||||
my $drop = "DROP VIEW IF EXISTS view\$$user\$$query\$";
|
my $drop = "DROP VIEW IF EXISTS view\$$user\$$query\$";
|
||||||
my $create = "CREATE VIEW view\$$user\$$query\$";
|
my $create = "CREATE VIEW view\$$user\$$query\$";
|
||||||
|
|
|
@ -12,7 +12,6 @@ use warnings;
|
||||||
|
|
||||||
use base qw(Bugzilla::WebService);
|
use base qw(Bugzilla::WebService);
|
||||||
|
|
||||||
use Bugzilla;
|
|
||||||
use Bugzilla::Comment;
|
use Bugzilla::Comment;
|
||||||
use Bugzilla::Constants;
|
use Bugzilla::Constants;
|
||||||
use Bugzilla::Error;
|
use Bugzilla::Error;
|
||||||
|
@ -21,7 +20,7 @@ use Bugzilla::WebService::Constants;
|
||||||
use Bugzilla::WebService::Util qw(extract_flags filter filter_wants validate translate user_to_hash);
|
use Bugzilla::WebService::Util qw(extract_flags filter filter_wants validate translate user_to_hash);
|
||||||
use Bugzilla::Bug;
|
use Bugzilla::Bug;
|
||||||
use Bugzilla::BugMail;
|
use Bugzilla::BugMail;
|
||||||
use Bugzilla::Util qw(trick_taint trim diff_arrays detaint_natural Dumper);
|
use Bugzilla::Util qw(trick_taint trim diff_arrays detaint_natural);
|
||||||
use Bugzilla::Version;
|
use Bugzilla::Version;
|
||||||
use Bugzilla::Milestone;
|
use Bugzilla::Milestone;
|
||||||
use Bugzilla::Status;
|
use Bugzilla::Status;
|
||||||
|
@ -624,7 +623,11 @@ sub update {
|
||||||
my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @$ids;
|
my @bugs = map { Bugzilla::Bug->check_for_edit($_) } @$ids;
|
||||||
|
|
||||||
my %values = %$params;
|
my %values = %$params;
|
||||||
my $comment = delete $values{comment};
|
$values{other_bugs} = \@bugs;
|
||||||
|
|
||||||
|
if (exists $values{comment} and exists $values{comment}{comment}) {
|
||||||
|
$values{comment}{body} = delete $values{comment}{comment};
|
||||||
|
}
|
||||||
|
|
||||||
# Prevent bugs that could be triggered by specifying fields that
|
# Prevent bugs that could be triggered by specifying fields that
|
||||||
# have valid "set_" functions in Bugzilla::Bug, but shouldn't be
|
# have valid "set_" functions in Bugzilla::Bug, but shouldn't be
|
||||||
|
@ -645,13 +648,6 @@ sub update {
|
||||||
|
|
||||||
foreach my $bug (@bugs) {
|
foreach my $bug (@bugs) {
|
||||||
$bug->set_all(\%values);
|
$bug->set_all(\%values);
|
||||||
if ($comment) {
|
|
||||||
$bug->add_comment($comment->{body} || $comment->{comment}, {
|
|
||||||
isprivate => $comment->{is_private},
|
|
||||||
type => $comment->{type} || CMT_NORMAL,
|
|
||||||
work_time => $comment->{work_time},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if ($flags) {
|
if ($flags) {
|
||||||
my ($old_flags, $new_flags) = extract_flags($flags, $bug);
|
my ($old_flags, $new_flags) = extract_flags($flags, $bug);
|
||||||
$bug->set_flags($old_flags, $new_flags);
|
$bug->set_flags($old_flags, $new_flags);
|
||||||
|
@ -665,7 +661,9 @@ sub update {
|
||||||
}
|
}
|
||||||
$dbh->bz_commit_transaction();
|
$dbh->bz_commit_transaction();
|
||||||
|
|
||||||
Bugzilla->send_mail;
|
foreach my $bug (@bugs) {
|
||||||
|
$bug->send_changes($all_changes{$bug->id});
|
||||||
|
}
|
||||||
|
|
||||||
my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
|
my %api_name = reverse %{ Bugzilla::Bug::FIELD_MAP() };
|
||||||
# This doesn't normally belong in FIELD_MAP, but we do want to translate
|
# This doesn't normally belong in FIELD_MAP, but we do want to translate
|
||||||
|
@ -734,7 +732,7 @@ sub create {
|
||||||
|
|
||||||
$dbh->bz_commit_transaction();
|
$dbh->bz_commit_transaction();
|
||||||
|
|
||||||
Bugzilla->send_mail;
|
$bug->send_changes();
|
||||||
|
|
||||||
return { id => $self->type('int', $bug->bug_id) };
|
return { id => $self->type('int', $bug->bug_id) };
|
||||||
}
|
}
|
||||||
|
@ -839,7 +837,7 @@ sub add_attachment {
|
||||||
$_->bug->update($timestamp) foreach @created;
|
$_->bug->update($timestamp) foreach @created;
|
||||||
$dbh->bz_commit_transaction();
|
$dbh->bz_commit_transaction();
|
||||||
|
|
||||||
Bugzilla->send_mail;
|
$_->send_changes() foreach @bugs;
|
||||||
|
|
||||||
my @created_ids = map { $_->id } @created;
|
my @created_ids = map { $_->id } @created;
|
||||||
|
|
||||||
|
@ -952,10 +950,9 @@ sub update_attachment {
|
||||||
# Email users about the change
|
# Email users about the change
|
||||||
foreach my $bug (values %bugs) {
|
foreach my $bug (values %bugs) {
|
||||||
$bug->update();
|
$bug->update();
|
||||||
|
$bug->send_changes();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bugzilla->send_mail;
|
|
||||||
|
|
||||||
# Return the information to the user
|
# Return the information to the user
|
||||||
return { attachments => \@result };
|
return { attachments => \@result };
|
||||||
}
|
}
|
||||||
|
@ -980,16 +977,14 @@ sub add_comment {
|
||||||
$params->{is_private} = delete $params->{private};
|
$params->{is_private} = delete $params->{private};
|
||||||
}
|
}
|
||||||
# Append comment
|
# Append comment
|
||||||
my $info = {};
|
|
||||||
$bug->add_comment($comment, { isprivate => $params->{is_private},
|
$bug->add_comment($comment, { isprivate => $params->{is_private},
|
||||||
work_time => $params->{work_time},
|
work_time => $params->{work_time} });
|
||||||
type => $params->{type} || CMT_NORMAL });
|
$bug->update();
|
||||||
$bug->update(undef, $info);
|
|
||||||
|
|
||||||
my $new_comment_id = $info->{added_comments}->[0]->{id};
|
my $new_comment_id = $bug->{added_comments}[0]->id;
|
||||||
|
|
||||||
# Send mail.
|
# Send mail.
|
||||||
Bugzilla->send_mail;
|
Bugzilla::BugMail::Send($bug->bug_id, { changer => $user });
|
||||||
|
|
||||||
return { id => $self->type('int', $new_comment_id) };
|
return { id => $self->type('int', $new_comment_id) };
|
||||||
}
|
}
|
||||||
|
@ -1031,9 +1026,9 @@ sub update_see_also {
|
||||||
# We still want a changes entry, for API consistency.
|
# We still want a changes entry, for API consistency.
|
||||||
$changes{$bug->id}->{see_also} = { added => [], removed => [] };
|
$changes{$bug->id}->{see_also} = { added => [], removed => [] };
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Bugzilla->send_mail;
|
Bugzilla::BugMail::Send($bug->id, { changer => $user });
|
||||||
|
}
|
||||||
|
|
||||||
return { changes => \%changes };
|
return { changes => \%changes };
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,12 +76,20 @@ sub type {
|
||||||
print "HTTP/1.1 100 Continue\r\n\r\n";
|
print "HTTP/1.1 100 Continue\r\n\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#my $content = q{};
|
||||||
if ( !$chunked ) {
|
if ( !$chunked ) {
|
||||||
my $buffer;
|
my $buffer;
|
||||||
binmode(STDIN);
|
binmode(STDIN);
|
||||||
while ( read( STDIN, $buffer, $length ) ) {
|
if ( defined $ENV{'MOD_PERL'} ) {
|
||||||
$content .= $buffer;
|
while ( read( STDIN, $buffer, $length ) ) {
|
||||||
last if ( length($content) >= $length );
|
$content .= $buffer;
|
||||||
|
last if ( length($content) >= $length );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while ( sysread( STDIN, $buffer, $length ) ) {
|
||||||
|
$content .= $buffer;
|
||||||
|
last if ( length($content) >= $length );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
## Line added so CGI doesn't try to slurp in the POST content after XMLRPC
|
## Line added so CGI doesn't try to slurp in the POST content after XMLRPC
|
||||||
undef $ENV{CONTENT_LENGTH};
|
undef $ENV{CONTENT_LENGTH};
|
||||||
|
|
|
@ -12,6 +12,8 @@ BEGIN
|
||||||
($dir) = $dir =~ /^(.*)$/s;
|
($dir) = $dir =~ /^(.*)$/s;
|
||||||
chdir($dir);
|
chdir($dir);
|
||||||
$Bugzilla::HTTPServerSimple::DOCROOT = $dir;
|
$Bugzilla::HTTPServerSimple::DOCROOT = $dir;
|
||||||
|
# Force everyone to use buffered input!
|
||||||
|
*CORE::GLOBAL::sysread = sub(*$$;$) { read $_[0], $_[1], $_[2], $_[3]; };
|
||||||
}
|
}
|
||||||
|
|
||||||
use lib qw(.);
|
use lib qw(.);
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
package Text::TabularDisplay::Utf8;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
use strict;
|
||||||
|
use base 'Text::TabularDisplay';
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# render([$start, $end])
|
||||||
|
#
|
||||||
|
# Returns the data formatted as a table. By default, all rows are
|
||||||
|
# returned; if $start or $end are specified, then only those indexes
|
||||||
|
# are returned. Those are the start and end indexes!
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
sub render {
|
||||||
|
my $self = shift;
|
||||||
|
my $start = shift || 0;
|
||||||
|
my $end = shift || $#{ $self->{ _DATA } };
|
||||||
|
my $size = $self->{ _SIZE };
|
||||||
|
my (@columns, $datum, @text);
|
||||||
|
|
||||||
|
push @text, '┌' . join("┬", map( { "─" x ($_ + 2) } @{ $self->{ _LENGTHS } })) . '┐';
|
||||||
|
|
||||||
|
if (@columns = $self->columns) {
|
||||||
|
push @text, _format_line(\@columns, $self->{ _LENGTHS });
|
||||||
|
push @text, '├' . join("┼", map( { "─" x ($_ + 2) } @{ $self->{ _LENGTHS } })) . '┤';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (my $i = $start; $i <= $end; $i++) {
|
||||||
|
$datum = $self->{ _DATA }->[$i];
|
||||||
|
last unless defined $datum;
|
||||||
|
|
||||||
|
# Pad the array if there are more elements in @columns
|
||||||
|
push @$datum, ""
|
||||||
|
until (@$datum == $size);
|
||||||
|
push @text, _format_line($datum, $self->{ _LENGTHS });
|
||||||
|
}
|
||||||
|
|
||||||
|
push @text, '└' . join("┴", map( { "─" x ($_ + 2) } @{ $self->{ _LENGTHS } })) . '┘';
|
||||||
|
return join "\n", @text;
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# _column_length($str)
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
sub _column_length
|
||||||
|
{
|
||||||
|
my ($str) = @_;
|
||||||
|
|
||||||
|
my $len = 0;
|
||||||
|
for (split "\n", $str) {
|
||||||
|
$len = length
|
||||||
|
if $len < length;
|
||||||
|
}
|
||||||
|
# why the /hell/ this length is tainted?..
|
||||||
|
if (${^TAINT})
|
||||||
|
{
|
||||||
|
($len) = $len =~ /(\d+)/so;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $len;
|
||||||
|
}
|
||||||
|
|
||||||
|
undef &Text::TabularDisplay::_column_length;
|
||||||
|
*Text::TabularDisplay::_column_length = \&_column_length;
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# _format_line(\@columns, \@lengths)
|
||||||
|
#
|
||||||
|
# Returns a formatted line out of @columns; the size of $column[$i]
|
||||||
|
# is determined by $length[$i].
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
sub _format_line {
|
||||||
|
my ($columns, $lengths) = @_;
|
||||||
|
|
||||||
|
my $height = 0;
|
||||||
|
my @col_lines;
|
||||||
|
for (@$columns) {
|
||||||
|
my @lines = split "\n";
|
||||||
|
$height = scalar @lines
|
||||||
|
if $height < @lines;
|
||||||
|
push @col_lines, \@lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @lines;
|
||||||
|
for my $h (0 .. $height - 1 ) {
|
||||||
|
my @line;
|
||||||
|
for (my $i = 0; $i <= $#$columns; $i++) {
|
||||||
|
my $val = defined($col_lines[$i][$h]) ? $col_lines[$i][$h] : '';
|
||||||
|
push @line, sprintf " %-" . $lengths->[$i] . "s ", $val;
|
||||||
|
}
|
||||||
|
push @lines, join '│', "", @line, "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return join "\n", @lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Text::TabularDisplay::Utf8 - Display text in formatted table output using UTF-8 pseudographics
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use Text::TabularDisplay::Utf8;
|
||||||
|
|
||||||
|
my $table = Text::TabularDisplay::Utf8->new(@columns);
|
||||||
|
$table->add(@row)
|
||||||
|
while (@row = $sth->fetchrow);
|
||||||
|
print $table->render;
|
||||||
|
|
||||||
|
┌────┬──────────────┐
|
||||||
|
│ id │ name │
|
||||||
|
├────┼──────────────┤
|
||||||
|
│ 1 │ Tom │
|
||||||
|
│ 2 │ Dick │
|
||||||
|
│ 3 │ Barry │
|
||||||
|
│ │ (aka Bazza) │
|
||||||
|
│ 4 │ Harry │
|
||||||
|
└────┴──────────────┘
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
The program interface is fully compatible with C<Text::TabularDisplay> -
|
||||||
|
see its perldoc for more information.
|
82
buglist.cgi
82
buglist.cgi
|
@ -183,15 +183,25 @@ my $format = $superworktime ? "worktime/supertime" : "list/list";
|
||||||
$format = $template->get_format($format, $ARGS->{format}, $ARGS->{ctype});
|
$format = $template->get_format($format, $ARGS->{format}, $ARGS->{ctype});
|
||||||
|
|
||||||
# Use server push to display a "Please wait..." message for the user while
|
# Use server push to display a "Please wait..." message for the user while
|
||||||
# executing their query, but only in Firefox, as only Firefox supports is reliably.
|
# executing their query if their browser supports it and they are viewing
|
||||||
# IE11 on Win8.1 mimics itself as Mozilla, Edge mimics itself as all browsers at once.
|
# the bug list as HTML and they have not disabled it by adding &serverpush=0
|
||||||
# Happily both have 'like Gecko' in their UA string.
|
# to the URL.
|
||||||
my $serverpush = $format->{extension} eq "html"
|
#
|
||||||
|
# Server push is a Netscape 3+ hack incompatible with MSIE, Lynx, and others.
|
||||||
|
# Even Communicator 4.51 has bugs with it, especially during page reload.
|
||||||
|
# http://www.browsercaps.org used as source of compatible browsers.
|
||||||
|
# Safari (WebKit) does not support it, despite a UA that says otherwise (bug 188712)
|
||||||
|
# MSIE 5+ supports it on Mac (but not on Windows) (bug 190370)
|
||||||
|
#
|
||||||
|
my $serverpush =
|
||||||
|
$format->{extension} eq "html"
|
||||||
&& exists $ENV{HTTP_USER_AGENT}
|
&& exists $ENV{HTTP_USER_AGENT}
|
||||||
&& $ENV{HTTP_USER_AGENT} =~ /Mozilla.[3-9]/
|
&& $ENV{HTTP_USER_AGENT} =~ /Mozilla.[3-9]/
|
||||||
&& $ENV{HTTP_USER_AGENT} !~ /compatible|msie|webkit|like\s*gecko/i
|
&& (($ENV{HTTP_USER_AGENT} !~ /[Cc]ompatible/) || ($ENV{HTTP_USER_AGENT} =~ /MSIE 5.*Mac_PowerPC/))
|
||||||
&& !$agent && !defined($ARGS->{serverpush})
|
&& $ENV{HTTP_USER_AGENT} !~ /WebKit/
|
||||||
|| $ARGS->{serverpush};
|
&& !$agent
|
||||||
|
&& !defined($ARGS->{serverpush})
|
||||||
|
|| $ARGS->{serverpush};
|
||||||
|
|
||||||
# The params object to use for the actual query itself
|
# The params object to use for the actual query itself
|
||||||
my $params;
|
my $params;
|
||||||
|
@ -707,16 +717,22 @@ $buglist_sth->execute();
|
||||||
|
|
||||||
# Retrieve the query results one row at a time and write the data into a list of Perl records.
|
# Retrieve the query results one row at a time and write the data into a list of Perl records.
|
||||||
|
|
||||||
# Calculate totals
|
# If we're doing time tracking, then keep totals for all bugs.
|
||||||
my $total_info;
|
my $percentage_complete = 1 && grep { $_ eq 'percentage_complete' } @$displaycolumns;
|
||||||
for my $column (@$displaycolumns)
|
my $estimated_time = 1 && grep { $_ eq 'estimated_time' } @$displaycolumns;
|
||||||
{
|
my $remaining_time = $percentage_complete || grep { $_ eq 'remaining_time' } @$displaycolumns;
|
||||||
if (Bugzilla::Search->COLUMNS->{$column}->{numeric})
|
my $work_time = $percentage_complete || grep { $_ eq 'work_time' } @$displaycolumns;
|
||||||
{
|
my $interval_time = $percentage_complete || grep { $_ eq 'interval_time' } @$displaycolumns;
|
||||||
$total_info ||= {};
|
|
||||||
$total_info->{$column} = 0;
|
my $time_info = {
|
||||||
}
|
estimated_time => 0,
|
||||||
}
|
remaining_time => 0,
|
||||||
|
work_time => 0,
|
||||||
|
percentage_complete => 0,
|
||||||
|
interval_time => 0, # CustIS Bug 68921
|
||||||
|
time_present => ($estimated_time || $remaining_time ||
|
||||||
|
$work_time || $percentage_complete || $interval_time),
|
||||||
|
};
|
||||||
|
|
||||||
my $bugowners = {};
|
my $bugowners = {};
|
||||||
my $bugproducts = {};
|
my $bugproducts = {};
|
||||||
|
@ -757,10 +773,10 @@ while (my @row = $buglist_sth->fetchrow_array())
|
||||||
push(@bugidlist, $bug->{bug_id});
|
push(@bugidlist, $bug->{bug_id});
|
||||||
|
|
||||||
# Compute time tracking info.
|
# Compute time tracking info.
|
||||||
for my $column (keys %$total_info)
|
$time_info->{estimated_time} += $bug->{estimated_time} if $estimated_time;
|
||||||
{
|
$time_info->{remaining_time} += $bug->{remaining_time} if $remaining_time;
|
||||||
$total_info->{$column} += $bug->{$column} if $column ne 'percentage_complete';
|
$time_info->{work_time} += $bug->{work_time} if $work_time;
|
||||||
}
|
$time_info->{interval_time} += $bug->{interval_time} if $interval_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $query_template_time = gettimeofday();
|
my $query_template_time = gettimeofday();
|
||||||
|
@ -800,18 +816,15 @@ if (@bugidlist)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Compute percentage complete without rounding.
|
# Compute percentage complete without rounding.
|
||||||
if (exists $total_info->{percentage_complete})
|
my $sum = $time_info->{work_time} + $time_info->{remaining_time};
|
||||||
|
if ($sum > 0)
|
||||||
{
|
{
|
||||||
my $sum = $total_info->{work_time} + $total_info->{remaining_time};
|
$time_info->{percentage_complete} = 100*$time_info->{work_time}/$sum;
|
||||||
if ($sum > 0)
|
}
|
||||||
{
|
else
|
||||||
$total_info->{percentage_complete} = 100*$total_info->{work_time}/$sum;
|
{
|
||||||
}
|
# remaining_time <= 0
|
||||||
else
|
$time_info->{percentage_complete} = 0
|
||||||
{
|
|
||||||
# remaining_time <= 0
|
|
||||||
$total_info->{percentage_complete} = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -874,7 +887,7 @@ $vars->{order_columns} = $orderstrings;
|
||||||
$vars->{order_dir} = [ map { s/ DESC$// ? 1 : 0 } @{$vars->{order_columns}} ];
|
$vars->{order_dir} = [ map { s/ DESC$// ? 1 : 0 } @{$vars->{order_columns}} ];
|
||||||
|
|
||||||
$vars->{caneditbugs} = 1;
|
$vars->{caneditbugs} = 1;
|
||||||
$vars->{total_info} = $total_info;
|
$vars->{time_info} = $time_info;
|
||||||
|
|
||||||
$vars->{query_params} = { %$params }; # now used only in superworktime
|
$vars->{query_params} = { %$params }; # now used only in superworktime
|
||||||
$vars->{query_params}->{chfieldfrom} = $search->{interval_from};
|
$vars->{query_params}->{chfieldfrom} = $search->{interval_from};
|
||||||
|
@ -978,7 +991,6 @@ if ($dotweak && scalar @bugs)
|
||||||
my $visible = {};
|
my $visible = {};
|
||||||
for my $field (Bugzilla->active_custom_fields)
|
for my $field (Bugzilla->active_custom_fields)
|
||||||
{
|
{
|
||||||
next if $field->type == FIELD_TYPE_BUG_ID_REV;
|
|
||||||
my $vis_field = $field->visibility_field;
|
my $vis_field = $field->visibility_field;
|
||||||
my $vis = 1;
|
my $vis = 1;
|
||||||
if ($vis_field)
|
if ($vis_field)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Nothing in this directory is retrievable unless overridden by an .htaccess
|
||||||
|
# in a subdirectory; the only exception is duplicates.rdf, which is used by
|
||||||
|
# duplicates.xul and must be accessible from the web server
|
||||||
|
deny from all
|
||||||
|
<Files duplicates.rdf>
|
||||||
|
allow from all
|
||||||
|
</Files>
|
|
@ -0,0 +1,5 @@
|
||||||
|
1
|
||||||
|
include .
|
||||||
|
exclude data/params.bugs3
|
||||||
|
exclude data/params.bugs3-sm
|
||||||
|
exclude data/hgsvn-filemap
|
|
@ -0,0 +1,171 @@
|
||||||
|
%param = (
|
||||||
|
'LDAPBaseDN' => '',
|
||||||
|
'LDAPbinddn' => '',
|
||||||
|
'LDAPfilter' => '',
|
||||||
|
'LDAPmailattribute' => 'mail',
|
||||||
|
'LDAPserver' => '',
|
||||||
|
'LDAPstarttls' => 0,
|
||||||
|
'LDAPuidattribute' => 'uid',
|
||||||
|
'RADIUS_NAS_IP' => '',
|
||||||
|
'RADIUS_email_suffix' => '',
|
||||||
|
'RADIUS_secret' => '',
|
||||||
|
'RADIUS_server' => '',
|
||||||
|
'allow-test-deletion' => '1',
|
||||||
|
'allow_attach_url' => 0,
|
||||||
|
'allow_attachment_deletion' => 0,
|
||||||
|
'allow_attachment_display' => '1',
|
||||||
|
'allowbugdeletion' => 0,
|
||||||
|
'allowemailchange' => 0,
|
||||||
|
'allowuserdeletion' => 0,
|
||||||
|
'announcehtml' => '',
|
||||||
|
'attachment_base' => '',
|
||||||
|
'auth_env_email' => 'REMOTE_USER',
|
||||||
|
'auth_env_id' => '',
|
||||||
|
'auth_env_realname' => '',
|
||||||
|
'auto_add_flag_requestees_to_cc' => '1',
|
||||||
|
'bonsai_url' => '',
|
||||||
|
'bug-to-test-case-action' => 'Verify that bug %id% is fixed: %description%',
|
||||||
|
'bug-to-test-case-results' => '',
|
||||||
|
'bug-to-test-case-summary' => 'Test for bug %id% - %summary%',
|
||||||
|
'chartgroup' => 'editbugs',
|
||||||
|
'clear_requests_on_close' => '1',
|
||||||
|
'commentonchange_resolution' => 0,
|
||||||
|
'commentonduplicate' => 0,
|
||||||
|
'confirmuniqueusermatch' => 1,
|
||||||
|
'cookiedomain' => 'bugs.office.custis.ru',
|
||||||
|
'cookiepath' => '/bugs',
|
||||||
|
'createemailregexp' => '.*',
|
||||||
|
'cvsroot' => '',
|
||||||
|
'cvsroot_get' => '',
|
||||||
|
'default-test-case-status' => 'CONFIRMED',
|
||||||
|
'defaultopsys' => 'All',
|
||||||
|
'defaultplatform' => 'All',
|
||||||
|
'defaultpriority' => 'P3',
|
||||||
|
'defaultquery' => 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&order=Importance&long_desc_type=substring',
|
||||||
|
'defaultseverity' => 'normal',
|
||||||
|
'docs_urlbase' => 'docs/%lang%/html/',
|
||||||
|
'duplicate_or_move_bug_status' => 'RESOLVED',
|
||||||
|
'emailin_autoregister' => 1,
|
||||||
|
'emailregexp' => '^[\\w\\.\\+\\-=]+@[\\w\\.\\-]+\\.[\\w\\-]+$',
|
||||||
|
'emailregexpdesc' => 'A legal address must contain exactly one \'@\', and at least one \'.\' after the @.',
|
||||||
|
'emailsuffix' => '',
|
||||||
|
'error_log' => 'errorlog',
|
||||||
|
'ext_disable_refresh_views' => 0,
|
||||||
|
'fof_sudo_mynetworks' => '127.0.0.1/32,172.29.0.6/32',
|
||||||
|
'fof_sudo_server' => 'http://feeds.office.custis.ru/fof-sudo.php',
|
||||||
|
'force_attach_bigfile' => 1,
|
||||||
|
'globalwatchers' => '',
|
||||||
|
'graph_font' => '/usr/share/fonts/truetype/windows/seguibk.ttf',
|
||||||
|
'graph_font_size' => '9',
|
||||||
|
'graph_rankdir' => 'LR',
|
||||||
|
'inbound_proxies' => '',
|
||||||
|
'inline_attachment_mime' => '^text/|^image/',
|
||||||
|
'insidergroup' => 'InsiderGroup',
|
||||||
|
'letsubmitterchoosemilestone' => 1,
|
||||||
|
'letsubmitterchoosepriority' => 1,
|
||||||
|
'levenshteinusermatch' => '0.4',
|
||||||
|
'localdottimeout' => '10',
|
||||||
|
'login_lockout_interval' => '5',
|
||||||
|
'login_urlbase_redirects' => '[^\\@]+\\@custis\\.ru$ http://bugs.office.custis.ru/bugs/
|
||||||
|
[^\\@]+\\@(sportmaster\\.ru|ilion\\.ru|sportmaster\\.com\\.ua|scn\\.ru|mbr\\.ru|ilion\\.ru|vek\\.ru|bis\\.overta\\.ru) http://penguin.office.custis.ru/bugzilla/',
|
||||||
|
'lxr_root' => '',
|
||||||
|
'lxr_url' => '',
|
||||||
|
'mail_delivery_method' => 'Sendmail',
|
||||||
|
'mailfrom' => 'bugzilla-daemon@custis.ru',
|
||||||
|
'maintainer' => 'vfilippov@custis.ru',
|
||||||
|
'makeproductgroups' => '0',
|
||||||
|
'max_login_attempts' => '50',
|
||||||
|
'maxattachmentsize' => '5000',
|
||||||
|
'maxlocalattachment' => '1000',
|
||||||
|
'maxusermatches' => '1000',
|
||||||
|
'mediawiki_urls' => 'wiki http://wiki.office.custis.ru/wiki/index.php/
|
||||||
|
smwiki http://penguin.office.custis.ru/smwiki/index.php/
|
||||||
|
sbwiki http://sobin.office.custis.ru/sbwiki/index.php/
|
||||||
|
rdwiki http://radey.office.custis.ru/rdwiki/index.php/
|
||||||
|
gzwiki http://gazprom.office.custis.ru/gzwiki/index.php/
|
||||||
|
gzstable http://gazprom.office.custis.ru/gzstable/index.php/
|
||||||
|
dpwiki http://depobraz.office.custis.ru/dpwiki/index.php/
|
||||||
|
hrwiki http://hrwiki.office.custis.ru/hrwiki/index.php/
|
||||||
|
cbwiki http://cbr.office.custis.ru/cbwiki/index.php/
|
||||||
|
orwiki http://lodge.office.custis.ru/orwiki/index.php/
|
||||||
|
rawiki http://rawiki.office.custis.ru/rawiki/index.php/
|
||||||
|
crmwiki http://crm.office.custis.ru/
|
||||||
|
itwiki http://itwiki.office.custis.ru/itwiki/index.php/',
|
||||||
|
'mime_types_file' => '/etc/mime.types',
|
||||||
|
'mostfreqthreshold' => '2',
|
||||||
|
'move-button-text' => 'Move To Bugscape',
|
||||||
|
'move-enabled' => '1',
|
||||||
|
'move-to-address' => 'bugzilla-import',
|
||||||
|
'move-to-url' => '',
|
||||||
|
'moved-default-component' => 'dev',
|
||||||
|
'moved-default-product' => 'Bugzilla',
|
||||||
|
'moved-from-address' => 'bugzilla-admin',
|
||||||
|
'movers' => '',
|
||||||
|
'musthavemilestoneonaccept' => 0,
|
||||||
|
'mybugstemplate' => 'buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&email1=%userid%&emailtype1=exact&emailassigned_to1=1&emailreporter1=1',
|
||||||
|
'new-case-action-template' => '',
|
||||||
|
'new-case-results-template' => '',
|
||||||
|
'noresolveonopenblockers' => '0',
|
||||||
|
'proxy_url' => 'http://proxy.custis.ru:3128/',
|
||||||
|
'querysharegroup' => 'editbugs',
|
||||||
|
'quip_list_entry_control' => 'open',
|
||||||
|
'rememberlogin' => 'on',
|
||||||
|
'report_code_errors_to_maintainer' => '1',
|
||||||
|
'report_user_errors_to_maintainer' => '0',
|
||||||
|
'requirelogin' => '1',
|
||||||
|
'sendmailnow' => 1,
|
||||||
|
'shadowdb' => '',
|
||||||
|
'shadowdbhost' => '',
|
||||||
|
'shadowdbport' => '3306',
|
||||||
|
'shadowdbsock' => '',
|
||||||
|
'shutdownhtml' => '',
|
||||||
|
'sm_dotproject_login' => 'ws-zis',
|
||||||
|
'sm_dotproject_password' => '',
|
||||||
|
'sm_dotproject_ws_user' => 'ws-sur@sportmaster.ru',
|
||||||
|
'sm_dotproject_wsdl_url' => 'http://sur.moscow.sportmaster.ru/dotproject/services/index.php?wsdl',
|
||||||
|
'smtp_debug' => 0,
|
||||||
|
'smtp_password' => '',
|
||||||
|
'smtp_username' => '',
|
||||||
|
'smtpserver' => 'localhost',
|
||||||
|
'specific_search_allow_empty_words' => '1',
|
||||||
|
'ssl_redirect' => 0,
|
||||||
|
'sslbase' => '',
|
||||||
|
'stem_language' => 'ru',
|
||||||
|
'strict_isolation' => 0,
|
||||||
|
'supa_jar_url' => '',
|
||||||
|
'test_case_wiki_action_iframe' => '<iframe src="$URL?useskin=ichick" width="100%" height="100%"></iframe>',
|
||||||
|
'testopia-allow-group-member-deletes' => 0,
|
||||||
|
'testopia-debug' => 'ON',
|
||||||
|
'testopia-default-plan-testers-regexp' => undef,
|
||||||
|
'testopia-hide-htmleditor' => '1',
|
||||||
|
'testopia-max-allowed-plan-testers' => '500',
|
||||||
|
'testopia-show-setup-breakdown' => 0,
|
||||||
|
'testopia_sync_password' => 'tester',
|
||||||
|
'testopia_sync_user' => 'TestBot',
|
||||||
|
'timetrackinggroup' => "\x{432}\x{435}\x{441}\x{44c} \x{417}\x{418}\x{421}",
|
||||||
|
'unauth_bug_details' => '1',
|
||||||
|
'upgrade_notification' => 'latest_stable_release',
|
||||||
|
'urlbase' => 'http://bugs.office.custis.ru/bugs/',
|
||||||
|
'use_mailer_queue' => 0,
|
||||||
|
'use_see_also' => 0,
|
||||||
|
'use_supa_applet' => '1',
|
||||||
|
'usebugaliases' => 1,
|
||||||
|
'useclassification' => 1,
|
||||||
|
'usemenuforusers' => '0',
|
||||||
|
'useopsys' => 0,
|
||||||
|
'useplatform' => 0,
|
||||||
|
'useqacontact' => 1,
|
||||||
|
'user_info_class' => 'FOF_Sudo,Env,CGI',
|
||||||
|
'user_mailto' => 'http://plantime.office.custis.ru/CurrentPresence.aspx?search=',
|
||||||
|
'user_verify_class' => 'DB',
|
||||||
|
'usestatuswhiteboard' => 1,
|
||||||
|
'usetargetmilestone' => 1,
|
||||||
|
'usevisibilitygroups' => 0,
|
||||||
|
'usevotes' => 0,
|
||||||
|
'utf8' => 1,
|
||||||
|
'viewvc_url' => 'http://viewvc.office.custis.ru/viewvc.py/',
|
||||||
|
'webdotbase' => '/usr/bin/dot',
|
||||||
|
'webtwopibase' => '/usr/bin/twopi',
|
||||||
|
'whinedays' => '30',
|
||||||
|
'wiki_url' => 'http://wiki.office.custis.ru/wiki/index.php/'
|
||||||
|
);
|
|
@ -0,0 +1,171 @@
|
||||||
|
%param = (
|
||||||
|
'LDAPBaseDN' => '',
|
||||||
|
'LDAPbinddn' => '',
|
||||||
|
'LDAPfilter' => '',
|
||||||
|
'LDAPmailattribute' => 'mail',
|
||||||
|
'LDAPserver' => '',
|
||||||
|
'LDAPstarttls' => 0,
|
||||||
|
'LDAPuidattribute' => 'uid',
|
||||||
|
'RADIUS_NAS_IP' => '',
|
||||||
|
'RADIUS_email_suffix' => '',
|
||||||
|
'RADIUS_secret' => '',
|
||||||
|
'RADIUS_server' => '',
|
||||||
|
'allow-test-deletion' => '1',
|
||||||
|
'allow_attach_url' => 0,
|
||||||
|
'allow_attachment_deletion' => 0,
|
||||||
|
'allow_attachment_display' => '1',
|
||||||
|
'allowbugdeletion' => 0,
|
||||||
|
'allowemailchange' => 0,
|
||||||
|
'allowuserdeletion' => 0,
|
||||||
|
'announcehtml' => '',
|
||||||
|
'attachment_base' => '',
|
||||||
|
'auth_env_email' => 'REMOTE_USER',
|
||||||
|
'auth_env_id' => '',
|
||||||
|
'auth_env_realname' => '',
|
||||||
|
'auto_add_flag_requestees_to_cc' => '1',
|
||||||
|
'bonsai_url' => '',
|
||||||
|
'bug-to-test-case-action' => 'Verify that bug %id% is fixed: %description%',
|
||||||
|
'bug-to-test-case-results' => '',
|
||||||
|
'bug-to-test-case-summary' => 'Test for bug %id% - %summary%',
|
||||||
|
'chartgroup' => 'editbugs',
|
||||||
|
'clear_requests_on_close' => '1',
|
||||||
|
'commentonchange_resolution' => 0,
|
||||||
|
'commentonduplicate' => 0,
|
||||||
|
'confirmuniqueusermatch' => 1,
|
||||||
|
'cookiedomain' => '',
|
||||||
|
'cookiepath' => '/bugzilla',
|
||||||
|
'createemailregexp' => '.*',
|
||||||
|
'cvsroot' => '',
|
||||||
|
'cvsroot_get' => '',
|
||||||
|
'default-test-case-status' => 'CONFIRMED',
|
||||||
|
'defaultopsys' => 'All',
|
||||||
|
'defaultplatform' => 'All',
|
||||||
|
'defaultpriority' => 'P3',
|
||||||
|
'defaultquery' => 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&order=Importance&long_desc_type=substring',
|
||||||
|
'defaultseverity' => 'normal',
|
||||||
|
'docs_urlbase' => 'docs/%lang%/html/',
|
||||||
|
'duplicate_or_move_bug_status' => 'RESOLVED',
|
||||||
|
'emailin_autoregister' => 1,
|
||||||
|
'emailregexp' => '^[\\w\\.\\+\\-=]+@[\\w\\.\\-]+\\.[\\w\\-]+$',
|
||||||
|
'emailregexpdesc' => 'A legal address must contain exactly one \'@\', and at least one \'.\' after the @.',
|
||||||
|
'emailsuffix' => '',
|
||||||
|
'error_log' => 'errorlog',
|
||||||
|
'ext_disable_refresh_views' => 1,
|
||||||
|
'fof_sudo_mynetworks' => '127.0.0.1/32,172.29.0.6/32',
|
||||||
|
'fof_sudo_server' => 'http://feeds.office.custis.ru/fof-sudo.php',
|
||||||
|
'force_attach_bigfile' => 1,
|
||||||
|
'globalwatchers' => '',
|
||||||
|
'graph_font' => '/usr/share/fonts/truetype/windows/seguibk.ttf',
|
||||||
|
'graph_font_size' => '9',
|
||||||
|
'graph_rankdir' => 'LR',
|
||||||
|
'inbound_proxies' => '',
|
||||||
|
'inline_attachment_mime' => '^text/|^image/',
|
||||||
|
'insidergroup' => 'admin',
|
||||||
|
'letsubmitterchoosemilestone' => 1,
|
||||||
|
'letsubmitterchoosepriority' => 1,
|
||||||
|
'levenshteinusermatch' => '0.4',
|
||||||
|
'localdottimeout' => 5,
|
||||||
|
'login_lockout_interval' => '5',
|
||||||
|
'login_urlbase_redirects' => '[^\\@]+\\@custis\\.ru$ http://bugs.office.custis.ru/bugs/
|
||||||
|
[^\\@]+\\@(sportmaster\\.ru|ilion\\.ru|sportmaster\\.com\\.ua|scn\\.ru|mbr\\.ru|ilion\\.ru|vek\\.ru|bis\\.overta\\.ru) http://penguin.office.custis.ru/bugzilla/',
|
||||||
|
'lxr_root' => '',
|
||||||
|
'lxr_url' => '',
|
||||||
|
'mail_delivery_method' => 'Sendmail',
|
||||||
|
'mailfrom' => 'bugzilla-daemon@custis.ru',
|
||||||
|
'maintainer' => 'vfilippov@custis.ru',
|
||||||
|
'makeproductgroups' => '1',
|
||||||
|
'max_login_attempts' => '50',
|
||||||
|
'maxattachmentsize' => '5000',
|
||||||
|
'maxlocalattachment' => '1000',
|
||||||
|
'maxusermatches' => '1000',
|
||||||
|
'mediawiki_urls' => 'wiki http://wiki.office.custis.ru/wiki/index.php/
|
||||||
|
smwiki http://penguin.office.custis.ru/smwiki/index.php/
|
||||||
|
sbwiki http://sobin.office.custis.ru/sbwiki/index.php/
|
||||||
|
rdwiki http://radey.office.custis.ru/rdwiki/index.php/
|
||||||
|
gzwiki http://gazprom.office.custis.ru/gzwiki/index.php/
|
||||||
|
gzstable http://gazprom.office.custis.ru/gzstable/index.php/
|
||||||
|
dpwiki http://depobraz.office.custis.ru/dpwiki/index.php/
|
||||||
|
hrwiki http://hrwiki.office.custis.ru/hrwiki/index.php/
|
||||||
|
cbwiki http://cbr.office.custis.ru/cbwiki/index.php/
|
||||||
|
orwiki http://lodge.office.custis.ru/orwiki/index.php/
|
||||||
|
rawiki http://rawiki.office.custis.ru/rawiki/index.php/
|
||||||
|
crmwiki http://crm.office.custis.ru/
|
||||||
|
itwiki http://itwiki.office.custis.ru/itwiki/index.php/',
|
||||||
|
'mime_types_file' => '/etc/mime.types',
|
||||||
|
'mostfreqthreshold' => '2',
|
||||||
|
'move-button-text' => 'Move To Bugscape',
|
||||||
|
'move-enabled' => '1',
|
||||||
|
'move-to-address' => 'bugzilla-import',
|
||||||
|
'move-to-url' => '',
|
||||||
|
'moved-default-component' => 'dev',
|
||||||
|
'moved-default-product' => 'Bugzilla',
|
||||||
|
'moved-from-address' => 'bugzilla-admin',
|
||||||
|
'movers' => '',
|
||||||
|
'musthavemilestoneonaccept' => 0,
|
||||||
|
'mybugstemplate' => 'buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&email1=%userid%&emailtype1=exact&emailassigned_to1=1&emailreporter1=1',
|
||||||
|
'new-case-action-template' => '',
|
||||||
|
'new-case-results-template' => '',
|
||||||
|
'noresolveonopenblockers' => '0',
|
||||||
|
'proxy_url' => 'http://proxy.custis.ru:3128/',
|
||||||
|
'querysharegroup' => 'editbugs',
|
||||||
|
'quip_list_entry_control' => 'open',
|
||||||
|
'rememberlogin' => 'on',
|
||||||
|
'report_code_errors_to_maintainer' => '1',
|
||||||
|
'report_user_errors_to_maintainer' => '0',
|
||||||
|
'requirelogin' => '1',
|
||||||
|
'sendmailnow' => 1,
|
||||||
|
'shadowdb' => '',
|
||||||
|
'shadowdbhost' => '',
|
||||||
|
'shadowdbport' => '3306',
|
||||||
|
'shadowdbsock' => '',
|
||||||
|
'shutdownhtml' => '',
|
||||||
|
'sm_dotproject_login' => 'ws-zis',
|
||||||
|
'sm_dotproject_password' => '',
|
||||||
|
'sm_dotproject_ws_user' => 'ws-sur@sportmaster.ru',
|
||||||
|
'sm_dotproject_wsdl_url' => 'http://sur.moscow.sportmaster.ru/dotproject/services/index.php?wsdl',
|
||||||
|
'smtp_debug' => 0,
|
||||||
|
'smtp_password' => '',
|
||||||
|
'smtp_username' => '',
|
||||||
|
'smtpserver' => 'localhost',
|
||||||
|
'specific_search_allow_empty_words' => '1',
|
||||||
|
'ssl_redirect' => 0,
|
||||||
|
'sslbase' => '',
|
||||||
|
'stem_language' => 'ru',
|
||||||
|
'strict_isolation' => 0,
|
||||||
|
'supa_jar_url' => '',
|
||||||
|
'test_case_wiki_action_iframe' => '<iframe src="$URL?useskin=ichick" width="100%" height="100%"></iframe>',
|
||||||
|
'testopia-allow-group-member-deletes' => 0,
|
||||||
|
'testopia-debug' => 'OFF',
|
||||||
|
'testopia-default-plan-testers-regexp' => undef,
|
||||||
|
'testopia-hide-htmleditor' => '1',
|
||||||
|
'testopia-max-allowed-plan-testers' => '500',
|
||||||
|
'testopia-show-setup-breakdown' => '0',
|
||||||
|
'testopia_sync_password' => 'tester',
|
||||||
|
'testopia_sync_user' => 'TestBot',
|
||||||
|
'timetrackinggroup' => "\x{432}\x{435}\x{441}\x{44c} \x{417}\x{418}\x{421}",
|
||||||
|
'unauth_bug_details' => 0,
|
||||||
|
'upgrade_notification' => 'latest_stable_release',
|
||||||
|
'urlbase' => 'http://penguin.office.custis.ru/bugzilla/',
|
||||||
|
'use_mailer_queue' => 0,
|
||||||
|
'use_see_also' => '0',
|
||||||
|
'use_supa_applet' => '1',
|
||||||
|
'usebugaliases' => '1',
|
||||||
|
'useclassification' => 0,
|
||||||
|
'usemenuforusers' => '0',
|
||||||
|
'useopsys' => '0',
|
||||||
|
'useplatform' => '0',
|
||||||
|
'useqacontact' => '1',
|
||||||
|
'user_info_class' => 'Env,CGI',
|
||||||
|
'user_mailto' => 'http://plantime.office.custis.ru/CurrentPresence.aspx?search=',
|
||||||
|
'user_verify_class' => 'DB',
|
||||||
|
'usestatuswhiteboard' => '1',
|
||||||
|
'usetargetmilestone' => '1',
|
||||||
|
'usevisibilitygroups' => 0,
|
||||||
|
'usevotes' => 0,
|
||||||
|
'utf8' => 1,
|
||||||
|
'viewvc_url' => 'http://viewvc.office.custis.ru/viewvc.py/',
|
||||||
|
'webdotbase' => '/usr/bin/dot',
|
||||||
|
'webtwopibase' => '/usr/bin/twopi',
|
||||||
|
'whinedays' => 7,
|
||||||
|
'wiki_url' => 'http://wiki.office.custis.ru/wiki/index.php/'
|
||||||
|
);
|
|
@ -1,5 +1,4 @@
|
||||||
# Add this to your sphinx.conf to use Sphinx search
|
# Add this to your sphinx.conf to use Sphinx search
|
||||||
# and then set $sphinx_index, $sphinx_host, $sphinx_port, $sphinx_sock, and $sphinxse_port in ../localconfig
|
|
||||||
|
|
||||||
index bugs
|
index bugs
|
||||||
{
|
{
|
||||||
|
@ -8,8 +7,9 @@ index bugs
|
||||||
rt_field = short_desc
|
rt_field = short_desc
|
||||||
rt_field = comments
|
rt_field = comments
|
||||||
rt_field = comments_private
|
rt_field = comments_private
|
||||||
rt_attr_uint = x
|
docinfo = extern
|
||||||
ondisk_attrs = 1
|
enable_star = 1
|
||||||
|
charset_type = utf-8
|
||||||
charset_table = 0..9, A..Z->a..z, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F
|
charset_table = 0..9, A..Z->a..z, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F
|
||||||
blend_chars = _, -, &, +, @, $
|
blend_chars = _, -, &, +, @, $
|
||||||
morphology = stem_enru
|
morphology = stem_enru
|
|
@ -257,8 +257,6 @@ if ($action eq 'delete')
|
||||||
|
|
||||||
Bugzilla::Hook::process('editgroups-post_delete', { group => $group });
|
Bugzilla::Hook::process('editgroups-post_delete', { group => $group });
|
||||||
Bugzilla::Views::refresh_some_views();
|
Bugzilla::Views::refresh_some_views();
|
||||||
# Refresh fieldvaluecontrol cache
|
|
||||||
Bugzilla->get_field('delta_ts')->touch;
|
|
||||||
|
|
||||||
$vars->{message} = 'group_deleted';
|
$vars->{message} = 'group_deleted';
|
||||||
ListGroups($vars);
|
ListGroups($vars);
|
||||||
|
@ -275,8 +273,6 @@ if ($action eq 'postchanges')
|
||||||
|
|
||||||
Bugzilla::Hook::process('editgroups-post_edit', {});
|
Bugzilla::Hook::process('editgroups-post_edit', {});
|
||||||
Bugzilla::Views::refresh_some_views();
|
Bugzilla::Views::refresh_some_views();
|
||||||
# Refresh fieldvaluecontrol cache
|
|
||||||
Bugzilla->get_field('delta_ts')->touch;
|
|
||||||
|
|
||||||
delete_token($token);
|
delete_token($token);
|
||||||
|
|
||||||
|
@ -324,8 +320,6 @@ if ($action eq 'remove_regexp')
|
||||||
|
|
||||||
Bugzilla::Hook::process('editgroups-post_remove_regexp', { deleted => $del });
|
Bugzilla::Hook::process('editgroups-post_remove_regexp', { deleted => $del });
|
||||||
Bugzilla::Views::refresh_some_views();
|
Bugzilla::Views::refresh_some_views();
|
||||||
# Refresh fieldvaluecontrol cache
|
|
||||||
Bugzilla->get_field('delta_ts')->touch;
|
|
||||||
|
|
||||||
delete_token($token);
|
delete_token($token);
|
||||||
|
|
||||||
|
|
|
@ -189,24 +189,20 @@ if ($action eq 'new')
|
||||||
$create_params{maxvotesperbug} = $ARGS->{maxvotesperbug};
|
$create_params{maxvotesperbug} = $ARGS->{maxvotesperbug};
|
||||||
$create_params{votestoconfirm} = $ARGS->{votestoconfirm};
|
$create_params{votestoconfirm} = $ARGS->{votestoconfirm};
|
||||||
}
|
}
|
||||||
|
|
||||||
Bugzilla->dbh->bz_start_transaction();
|
|
||||||
|
|
||||||
my $product = Bugzilla::Product->create(\%create_params);
|
my $product = Bugzilla::Product->create(\%create_params);
|
||||||
|
|
||||||
# Create groups and series for the new product, if requested.
|
if (!$user->in_group('editcomponents'))
|
||||||
my $new_bug_group = $product->_create_bug_group() if $ARGS->{makeproductgroup};
|
|
||||||
if (!$user->in_group('editcomponents') || $ARGS->{makeadmingroup})
|
|
||||||
{
|
{
|
||||||
# User has no global editcomponents permission, so grant 'createproducts'
|
# User has no global editcomponents permission, so grant 'createproducts'
|
||||||
# group the right to manage his newly created product (or admin group is explicitly requested)
|
# group the right to manage his newly created product
|
||||||
$product->_create_bug_group(1, $new_bug_group);
|
$product->_create_bug_group(1);
|
||||||
}
|
}
|
||||||
$product->_create_series() if $ARGS->{createseries};
|
|
||||||
$product->check_open_product();
|
|
||||||
delete_token($token);
|
|
||||||
|
|
||||||
Bugzilla->dbh->bz_commit_transaction();
|
# Create groups and series for the new product, if requested.
|
||||||
|
$product->_create_bug_group() if $ARGS->{makeproductgroup};
|
||||||
|
$product->_create_series() if $ARGS->{createseries};
|
||||||
|
|
||||||
|
delete_token($token);
|
||||||
|
|
||||||
$vars->{message} = 'product_created';
|
$vars->{message} = 'product_created';
|
||||||
$vars->{product} = $product;
|
$vars->{product} = $product;
|
||||||
|
@ -379,9 +375,8 @@ if ($action eq 'updategroupcontrols')
|
||||||
my $product = $user->check_can_admin_product($product_name);
|
my $product = $user->check_can_admin_product($product_name);
|
||||||
check_token_data($token, 'edit_group_controls');
|
check_token_data($token, 'edit_group_controls');
|
||||||
|
|
||||||
my @now_na;
|
my @now_na = ();
|
||||||
my @now_mandatory;
|
my @now_mandatory = ();
|
||||||
my @now_entry;
|
|
||||||
my %membercontrol_g;
|
my %membercontrol_g;
|
||||||
my %othercontrol_g;
|
my %othercontrol_g;
|
||||||
foreach my $f (keys %$ARGS)
|
foreach my $f (keys %$ARGS)
|
||||||
|
@ -473,9 +468,6 @@ if ($action eq 'updategroupcontrols')
|
||||||
$vars->{product} = $product;
|
$vars->{product} = $product;
|
||||||
$vars->{changes} = $changes;
|
$vars->{changes} = $changes;
|
||||||
|
|
||||||
# Refresh fieldvaluecontrol cache
|
|
||||||
Bugzilla->get_field('delta_ts')->touch;
|
|
||||||
|
|
||||||
$template->process('admin/products/groupcontrol/updated.html.tmpl', $vars)
|
$template->process('admin/products/groupcontrol/updated.html.tmpl', $vars)
|
||||||
|| ThrowTemplateError($template->error());
|
|| ThrowTemplateError($template->error());
|
||||||
exit;
|
exit;
|
||||||
|
|
|
@ -338,8 +338,6 @@ elsif ($action eq 'update')
|
||||||
|
|
||||||
Bugzilla::Hook::process('editusers-post_update', { userid => $otherUserID });
|
Bugzilla::Hook::process('editusers-post_update', { userid => $otherUserID });
|
||||||
Bugzilla::Views::refresh_some_views([ $otherUser->login ]);
|
Bugzilla::Views::refresh_some_views([ $otherUser->login ]);
|
||||||
# Refresh fieldvaluecontrol cache
|
|
||||||
Bugzilla->get_field('delta_ts')->touch;
|
|
||||||
|
|
||||||
$vars->{message} = 'account_updated';
|
$vars->{message} = 'account_updated';
|
||||||
$vars->{changed_fields} = [ keys %$changes ];
|
$vars->{changed_fields} = [ keys %$changes ];
|
||||||
|
|
|
@ -73,8 +73,6 @@ if (@add_members || @add_bless || @rm_members || @rm_bless)
|
||||||
if (@add_members || @rm_members)
|
if (@add_members || @rm_members)
|
||||||
{
|
{
|
||||||
Bugzilla::Views::refresh_some_views();
|
Bugzilla::Views::refresh_some_views();
|
||||||
# Refresh fieldvaluecontrol cache
|
|
||||||
Bugzilla->get_field('delta_ts')->touch;
|
|
||||||
}
|
}
|
||||||
delete_token($ARGS->{token});
|
delete_token($ARGS->{token});
|
||||||
my $url = "editusersingroup.cgi?group=".$vars->{group}->id;
|
my $url = "editusersingroup.cgi?group=".$vars->{group}->id;
|
||||||
|
|
73
email_in.cgi
73
email_in.cgi
|
@ -1,73 +0,0 @@
|
||||||
#!/usr/bin/perl -wT
|
|
||||||
# HTTP handler for incoming e-mail
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use lib qw(. lib);
|
|
||||||
|
|
||||||
use Bugzilla;
|
|
||||||
use Bugzilla::InMail;
|
|
||||||
|
|
||||||
my $status;
|
|
||||||
if (!Bugzilla->params->{enable_inmail_cgi})
|
|
||||||
{
|
|
||||||
$status = 'disabled';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
my $mail_text = Bugzilla->cgi->param('POSTDATA');
|
|
||||||
if (!$mail_text)
|
|
||||||
{
|
|
||||||
$status = 'empty-message';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$status = Bugzilla::InMail::process_inmail($mail_text) == 1 ? 'success' : 'error';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Bugzilla->cgi->send_header('application/json');
|
|
||||||
print '{"status":"'.$status.'"}';
|
|
||||||
exit;
|
|
||||||
|
|
||||||
__END__
|
|
||||||
|
|
||||||
Postfix configuration example:
|
|
||||||
|
|
||||||
1) If you want to log all incoming messages, create /etc/postfix/send-to-bugzilla script with the following content:
|
|
||||||
|
|
||||||
#!/bin/sh
|
|
||||||
echo '-----' >> /var/log/bugzilla-email-in.log
|
|
||||||
/usr/bin/tee -a /var/log/bugzilla-email-in.log | curl -X POST -H 'Content-Type: text/plain' --data-binary @- http://127.0.0.1:8157/email_in.cgi
|
|
||||||
|
|
||||||
2) If you don't want to log all incoming messages, create /etc/postfix/send-to-bugzilla script with the following content:
|
|
||||||
|
|
||||||
#!/bin/sh
|
|
||||||
curl -X POST -H 'Content-Type: text/plain' --data-binary @- http://127.0.0.1:8157/email_in.cgi
|
|
||||||
|
|
||||||
3) Make it executable:
|
|
||||||
|
|
||||||
chmod 755 /etc/postfix/send-to-bugzilla
|
|
||||||
|
|
||||||
4) Add the following to master.cf:
|
|
||||||
|
|
||||||
bugzilla unix - n n - - pipe
|
|
||||||
flags=DRhu user=www-data:www-data argv=/etc/postfix/send-to-bugzilla
|
|
||||||
|
|
||||||
5) Add the following to your /etc/postfix/transport map:
|
|
||||||
|
|
||||||
daemon@your.bugzilla.url bugzilla:
|
|
||||||
|
|
||||||
Where `daemon@your.bugzilla.url` is the same as `mailfrom` Bugzilla parameter from Administration -> Config
|
|
||||||
This will make your Postfix feed all messages sent to `daemon@your.bugzilla.url` to email_in.cgi.
|
|
||||||
|
|
||||||
6) Run `postmap /etc/postfix/transport`
|
|
||||||
|
|
||||||
7) Ensure that other parts of your Postfix configuration do not prevent it from receiving mail to daemon@your.bugzilla.url
|
|
||||||
|
|
||||||
8) Turn `enable_inmail_cgi` parameter on in Administration -> Config
|
|
||||||
|
|
||||||
9) Deny access to `email_in.cgi` in your HTTP server. For example with nginx:
|
|
||||||
|
|
||||||
location /email_in.cgi {
|
|
||||||
deny all;
|
|
||||||
}
|
|
489
email_in.pl
489
email_in.pl
|
@ -30,16 +30,430 @@ BEGIN
|
||||||
my ($a) = abs_path($0) =~ /^(.*)$/;
|
my ($a) = abs_path($0) =~ /^(.*)$/;
|
||||||
chdir dirname($a);
|
chdir dirname($a);
|
||||||
}
|
}
|
||||||
|
|
||||||
use lib qw(. lib);
|
use lib qw(. lib);
|
||||||
use Bugzilla::InMail;
|
|
||||||
|
|
||||||
my $switch = {};
|
use Data::Dumper;
|
||||||
|
use Email::Address;
|
||||||
|
use Email::Reply qw(reply);
|
||||||
|
use Email::MIME;
|
||||||
|
use Email::MIME::Attachment::Stripper;
|
||||||
|
use HTML::Strip;
|
||||||
|
use Getopt::Long qw(:config bundling);
|
||||||
|
use Pod::Usage;
|
||||||
|
use Encode;
|
||||||
|
use Scalar::Util qw(blessed);
|
||||||
|
|
||||||
GetOptions($switch, 'help|h', 'verbose|v+');
|
use Bugzilla;
|
||||||
$switch->{verbose} ||= 0;
|
use Bugzilla::Attachment;
|
||||||
|
use Bugzilla::Bug;
|
||||||
|
use Bugzilla::Hook;
|
||||||
|
use Bugzilla::BugMail;
|
||||||
|
use Bugzilla::Constants;
|
||||||
|
use Bugzilla::Error;
|
||||||
|
use Bugzilla::Mailer;
|
||||||
|
use Bugzilla::Token;
|
||||||
|
use Bugzilla::User;
|
||||||
|
use Bugzilla::Util;
|
||||||
|
|
||||||
|
#############
|
||||||
|
# Constants #
|
||||||
|
#############
|
||||||
|
|
||||||
|
# This is the USENET standard line for beginning a signature block
|
||||||
|
# in a message. RFC-compliant mailers use this.
|
||||||
|
use constant SIGNATURE_DELIMITER => '-- ';
|
||||||
|
|
||||||
|
# $input_email is a global so that it can be used in die_handler.
|
||||||
|
our ($input_email, %switch);
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Main Subroutines #
|
||||||
|
####################
|
||||||
|
|
||||||
|
sub parse_mail
|
||||||
|
{
|
||||||
|
my ($mail_text) = @_;
|
||||||
|
debug_print('Parsing Email');
|
||||||
|
$input_email = Email::MIME->new($mail_text);
|
||||||
|
|
||||||
|
my %fields;
|
||||||
|
Bugzilla::Hook::process('email_in_before_parse', { mail => $input_email, fields => \%fields });
|
||||||
|
# RFC 3834 - Recommendations for Automatic Responses to Electronic Mail
|
||||||
|
# Automatic responses SHOULD NOT be issued in response to any
|
||||||
|
# message which contains an Auto-Submitted header field (see below),
|
||||||
|
# where that field has any value other than "no".
|
||||||
|
# F*cking MS Exchange sometimes does not append Auto-Submitted header
|
||||||
|
# to delivery status reports, so also check content-type.
|
||||||
|
my $autosubmitted;
|
||||||
|
if (lc($input_email->header('Auto-Submitted') || 'no') ne 'no' ||
|
||||||
|
($input_email->header('X-Auto-Response-Suppress') || '') =~ /all/iso ||
|
||||||
|
($input_email->header('Content-Type') || '') =~ /delivery-status/iso)
|
||||||
|
{
|
||||||
|
debug_print("Rejecting email with Auto-Submitted = $autosubmitted");
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $dbh = Bugzilla->dbh;
|
||||||
|
|
||||||
|
# Fetch field => value from emailin_fields table
|
||||||
|
my ($toemail) = Email::Address->parse($input_email->header('To'));
|
||||||
|
%fields = (%fields, map { @$_ } @{ $dbh->selectall_arrayref(
|
||||||
|
"SELECT field, value FROM emailin_fields WHERE address=?",
|
||||||
|
undef, $toemail) || [] });
|
||||||
|
|
||||||
|
my $summary = $input_email->header('Subject');
|
||||||
|
if ($summary =~ /\[\s*Bug\s*(\d+)\s*\](.*)/i)
|
||||||
|
{
|
||||||
|
$fields{bug_id} = $1;
|
||||||
|
$summary = trim($2);
|
||||||
|
}
|
||||||
|
$fields{_subject} = $summary;
|
||||||
|
|
||||||
|
# Add CC's from email Cc: header
|
||||||
|
$fields{newcc} = $input_email->header('Cc');
|
||||||
|
$fields{newcc} = $fields{newcc} && (join ', ', map { [ Email::Address->parse($_) ] -> [0] }
|
||||||
|
split /\s*,\s*/, $fields{newcc}) || undef;
|
||||||
|
|
||||||
|
my ($body, $attachments) = get_body_and_attachments($input_email);
|
||||||
|
if (@$attachments)
|
||||||
|
{
|
||||||
|
$fields{attachments} = $attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_print("Body:\n" . $body, 3);
|
||||||
|
|
||||||
|
$body = remove_leading_blank_lines($body);
|
||||||
|
|
||||||
|
Bugzilla::Hook::process("emailin-filter_body", { body => \$body });
|
||||||
|
|
||||||
|
my @body_lines = split(/\r?\n/s, $body);
|
||||||
|
my $fields_by_name = { map { (lc($_->description) => $_->name, lc($_->name) => $_->name) } Bugzilla->get_fields({ obsolete => 0 }) };
|
||||||
|
|
||||||
|
# If there are fields specified.
|
||||||
|
if ($body =~ /^\s*@/s)
|
||||||
|
{
|
||||||
|
my $current_field;
|
||||||
|
while (my $line = shift @body_lines)
|
||||||
|
{
|
||||||
|
# If the sig is starting, we want to keep this in the
|
||||||
|
# @body_lines so that we don't keep the sig as part of the
|
||||||
|
# comment down below.
|
||||||
|
if ($line eq SIGNATURE_DELIMITER)
|
||||||
|
{
|
||||||
|
unshift(@body_lines, $line);
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
# Otherwise, we stop parsing fields on the first blank line.
|
||||||
|
$line = trim($line);
|
||||||
|
last if !$line;
|
||||||
|
if ($line =~ /^\@\s*(.+?)\s*=\s*(.*)\s*/)
|
||||||
|
{
|
||||||
|
$current_field = $fields_by_name->{lc($1)} || lc($1);
|
||||||
|
$fields{$current_field} = $2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$fields{$current_field} .= " $line";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
%fields = %{ Bugzilla::Bug::map_fields(\%fields) };
|
||||||
|
|
||||||
|
my ($reporter) = Email::Address->parse($input_email->header('From'));
|
||||||
|
$fields{reporter} = $reporter->address;
|
||||||
|
|
||||||
|
{
|
||||||
|
my $r;
|
||||||
|
if ($r = $reporter->phrase)
|
||||||
|
{
|
||||||
|
$r .= ' ' . $reporter->comment if $reporter->comment;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$r = $reporter->address;
|
||||||
|
}
|
||||||
|
$fields{_reporter_name} = $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
# The summary line only affects us if we're doing a post_bug.
|
||||||
|
# We have to check it down here because there might have been
|
||||||
|
# a bug_id specified in the body of the email.
|
||||||
|
if (!$fields{bug_id} && !$fields{short_desc})
|
||||||
|
{
|
||||||
|
$fields{short_desc} = $summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $comment = '';
|
||||||
|
# Get the description, except the signature.
|
||||||
|
foreach my $line (@body_lines)
|
||||||
|
{
|
||||||
|
last if $line eq SIGNATURE_DELIMITER;
|
||||||
|
$comment .= "$line\n";
|
||||||
|
}
|
||||||
|
$fields{comment} = $comment;
|
||||||
|
|
||||||
|
debug_print("Parsed Fields:\n" . Dumper(\%fields), 2);
|
||||||
|
|
||||||
|
return \%fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub post_bug
|
||||||
|
{
|
||||||
|
my ($fields) = @_;
|
||||||
|
debug_print('Posting a new bug...');
|
||||||
|
my $bug;
|
||||||
|
$Bugzilla::Error::IN_EVAL++;
|
||||||
|
eval
|
||||||
|
{
|
||||||
|
my ($retval, $non_conclusive_fields) =
|
||||||
|
Bugzilla::User::match_field({
|
||||||
|
assigned_to => { type => 'single' },
|
||||||
|
qa_contact => { type => 'single' },
|
||||||
|
cc => { type => 'multi' }
|
||||||
|
}, $fields, MATCH_SKIP_CONFIRM);
|
||||||
|
if ($retval != USER_MATCH_SUCCESS)
|
||||||
|
{
|
||||||
|
ThrowUserError('user_match_too_many', { fields => $non_conclusive_fields });
|
||||||
|
}
|
||||||
|
$bug = Bugzilla::Bug::create_or_update($fields);
|
||||||
|
};
|
||||||
|
$Bugzilla::Error::IN_EVAL--;
|
||||||
|
if (my $err = $@)
|
||||||
|
{
|
||||||
|
my $format = "\n\nIncoming mail format for entering bugs:\n\n\@field = value\n\@field = value\n...\n\n<Bug description...>\n";
|
||||||
|
if (blessed $err && $err->{message})
|
||||||
|
{
|
||||||
|
$err->{message} .= $format;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$err .= $format;
|
||||||
|
}
|
||||||
|
die $err;
|
||||||
|
}
|
||||||
|
if ($bug)
|
||||||
|
{
|
||||||
|
debug_print("Created bug " . $bug->id);
|
||||||
|
return ($bug, $bug->comments->[0]);
|
||||||
|
}
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_attachments
|
||||||
|
{
|
||||||
|
my ($bug, $attachments, $comment) = @_;
|
||||||
|
return if !$attachments;
|
||||||
|
debug_print("Handling attachments...");
|
||||||
|
my $dbh = Bugzilla->dbh;
|
||||||
|
$dbh->bz_start_transaction();
|
||||||
|
my ($update_comment, $update_bug);
|
||||||
|
foreach my $attachment (@$attachments)
|
||||||
|
{
|
||||||
|
my $data = delete $attachment->{payload};
|
||||||
|
debug_print("Inserting Attachment: " . Dumper($attachment), 2);
|
||||||
|
$attachment->{content_type} ||= 'application/octet-stream';
|
||||||
|
my $obj = Bugzilla::Attachment->create({
|
||||||
|
bug => $bug,
|
||||||
|
description => $attachment->{filename},
|
||||||
|
filename => $attachment->{filename},
|
||||||
|
mimetype => $attachment->{content_type},
|
||||||
|
data => $data,
|
||||||
|
});
|
||||||
|
# If we added a comment, and our comment does not already have a type,
|
||||||
|
# and this is our first attachment, then we make the comment an
|
||||||
|
# "attachment created" comment.
|
||||||
|
if ($comment and !$comment->type and !$update_comment)
|
||||||
|
{
|
||||||
|
$comment->set_type(CMT_ATTACHMENT_CREATED, $obj->id);
|
||||||
|
$update_comment = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$bug->add_comment('', { type => CMT_ATTACHMENT_CREATED, extra_data => $obj->id });
|
||||||
|
$update_bug = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# We only update the comments and bugs at the end of the transaction,
|
||||||
|
# because doing so modifies bugs_fulltext, which is a non-transactional
|
||||||
|
# table.
|
||||||
|
$bug->update() if $update_bug;
|
||||||
|
$comment->update() if $update_comment;
|
||||||
|
$dbh->bz_commit_transaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Helper Subroutines #
|
||||||
|
######################
|
||||||
|
|
||||||
|
sub debug_print
|
||||||
|
{
|
||||||
|
my ($str, $level) = @_;
|
||||||
|
$level ||= 1;
|
||||||
|
print STDERR "$str\n" if $level <= $switch{verbose};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_body_and_attachments
|
||||||
|
{
|
||||||
|
my ($email) = @_;
|
||||||
|
|
||||||
|
my $ct = $email->content_type || 'text/plain';
|
||||||
|
debug_print("Splitting Body and Attachments [Type: $ct]...");
|
||||||
|
|
||||||
|
my $body;
|
||||||
|
my $attachments = [];
|
||||||
|
if ($ct =~ /^multipart\/(alternative|signed)/i)
|
||||||
|
{
|
||||||
|
$body = get_text_alternative($email);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
my $stripper = new Email::MIME::Attachment::Stripper($email, force_filename => 1);
|
||||||
|
my $message = $stripper->message;
|
||||||
|
$body = get_text_alternative($message);
|
||||||
|
$attachments = [$stripper->attachments];
|
||||||
|
}
|
||||||
|
$email->charset_set('utf8');
|
||||||
|
$email->body_str_set($body);
|
||||||
|
|
||||||
|
return ($body, $attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rm_line_feeds
|
||||||
|
{
|
||||||
|
my ($t) = @_;
|
||||||
|
$t =~ s/[\n\r]+/ /giso;
|
||||||
|
return $t;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_text_alternative
|
||||||
|
{
|
||||||
|
my ($email) = @_;
|
||||||
|
|
||||||
|
my @parts = $email->parts;
|
||||||
|
my $body;
|
||||||
|
foreach my $part (@parts)
|
||||||
|
{
|
||||||
|
my $ct = $part->content_type || 'text/plain';
|
||||||
|
my $charset = 'iso-8859-1';
|
||||||
|
# The charset may be quoted.
|
||||||
|
if ($ct =~ /charset="?([^;"]+)/)
|
||||||
|
{
|
||||||
|
$charset = $1;
|
||||||
|
}
|
||||||
|
debug_print("Part Content-Type: $ct", 2);
|
||||||
|
debug_print("Part Character Encoding: $charset", 2);
|
||||||
|
if (!$ct || $ct =~ /^text\/plain/i)
|
||||||
|
{
|
||||||
|
$body = $part->body;
|
||||||
|
}
|
||||||
|
elsif ($ct =~ /^text\/html/i)
|
||||||
|
{
|
||||||
|
$body = $part->body;
|
||||||
|
$body =~ s/<table[^<>]*class=[\"\']?difft[^<>]*>.*?<\/table\s*>//giso;
|
||||||
|
$body =~ s/(<a[^<>]*>.*?<\/a\s*>)/rm_line_feeds($1)/gieso;
|
||||||
|
Bugzilla::Hook::process("emailin-filter_html", { body => \$body });
|
||||||
|
$body = HTML::Strip->new->parse($body);
|
||||||
|
}
|
||||||
|
if (defined $body)
|
||||||
|
{
|
||||||
|
if (Bugzilla->params->{utf8} && !utf8::is_utf8($body))
|
||||||
|
{
|
||||||
|
$body = Encode::decode($charset, $body);
|
||||||
|
}
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined $body)
|
||||||
|
{
|
||||||
|
# Note that this only happens if the email does not contain any
|
||||||
|
# text/plain parts. If the email has an empty text/plain part,
|
||||||
|
# you're fine, and this message does NOT get thrown.
|
||||||
|
ThrowUserError('email_no_text_plain');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub remove_leading_blank_lines
|
||||||
|
{
|
||||||
|
my ($text) = @_;
|
||||||
|
$text =~ s/^(\s*\n)+//s;
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub die_handler
|
||||||
|
{
|
||||||
|
my ($msg) = @_;
|
||||||
|
|
||||||
|
# In Template-Toolkit, [% RETURN %] is implemented as a call to "die".
|
||||||
|
# But of course, we really don't want to actually *die* just because
|
||||||
|
# the user-error or code-error template ended. So we don't really die.
|
||||||
|
return if blessed($msg) && $msg->isa('Template::Exception') && $msg->type eq 'return';
|
||||||
|
|
||||||
|
# If this is inside an eval, then we should just act like...we're
|
||||||
|
# in an eval (instead of printing the error and exiting).
|
||||||
|
die(@_) if $^S;
|
||||||
|
|
||||||
|
if (ref $msg eq 'Bugzilla::Error')
|
||||||
|
{
|
||||||
|
$msg = $msg->{message};
|
||||||
|
}
|
||||||
|
|
||||||
|
# We can't depend on the MTA to send an error message, so we have
|
||||||
|
# to generate one properly.
|
||||||
|
if ($input_email)
|
||||||
|
{
|
||||||
|
my $from = Bugzilla->params->{mailfrom};
|
||||||
|
my $reply = reply(to => $input_email, from => $from, top_post => 1, body => "$msg\n");
|
||||||
|
MessageToMTA($reply->as_string);
|
||||||
|
}
|
||||||
|
print STDERR "$msg\n";
|
||||||
|
# We exit with a successful value, because we don't want the MTA
|
||||||
|
# to *also* send a failure notice.
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use UTF-8 in Email::Reply to correctly quote the body
|
||||||
|
my $crlf = "\x0d\x0a";
|
||||||
|
my $CRLF = $crlf;
|
||||||
|
undef *Email::Reply::_quote_body;
|
||||||
|
*Email::Reply::_quote_body = sub
|
||||||
|
{
|
||||||
|
my ($self, $part) = @_;
|
||||||
|
return if length $self->{quoted};
|
||||||
|
return map $self->_quote_body($_), $part->parts if $part->parts > 1;
|
||||||
|
return if $part->content_type && $part->content_type !~ m[\btext/plain\b];
|
||||||
|
|
||||||
|
my $body = $part->body;
|
||||||
|
Encode::_utf8_on($body);
|
||||||
|
|
||||||
|
$body = ($self->_strip_sig($body) || $body)
|
||||||
|
if !$self->{keep_sig} && $body =~ /$crlf--\s*$crlf/o;
|
||||||
|
|
||||||
|
my ($end) = $body =~ /($crlf)/;
|
||||||
|
$end ||= $CRLF;
|
||||||
|
$body =~ s/[\r\n\s]+$//;
|
||||||
|
$body = $self->_quote_orig_body($body);
|
||||||
|
$body = "$self->{attrib}$end$body$end";
|
||||||
|
|
||||||
|
$self->{crlf} = $end;
|
||||||
|
$self->{quoted} = $body;
|
||||||
|
};
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Main Script #
|
||||||
|
###############
|
||||||
|
|
||||||
|
$SIG{__DIE__} = \&die_handler;
|
||||||
|
|
||||||
|
GetOptions(\%switch, 'help|h', 'verbose|v+');
|
||||||
|
$switch{verbose} ||= 0;
|
||||||
|
|
||||||
# Print the help message if that switch was selected.
|
# Print the help message if that switch was selected.
|
||||||
pod2usage({-verbose => 0, -exitval => 1}) if $switch->{help};
|
pod2usage({-verbose => 0, -exitval => 1}) if $switch{help};
|
||||||
|
|
||||||
# Get a next-in-pipe command from commandline
|
# Get a next-in-pipe command from commandline
|
||||||
my ($pipe) = join(' ', @ARGV) =~ /^(.*)$/iso;
|
my ($pipe) = join(' ', @ARGV) =~ /^(.*)$/iso;
|
||||||
|
@ -58,8 +472,69 @@ if ($pipe && open PIPE, "| $pipe")
|
||||||
close PIPE;
|
close PIPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bugzilla::InMail::process_inmail($mail_text);
|
my $mail_fields = parse_mail($mail_text);
|
||||||
exit;
|
|
||||||
|
Bugzilla::Hook::process('email_in_after_parse', { fields => $mail_fields });
|
||||||
|
|
||||||
|
my $attachments = delete $mail_fields->{attachments};
|
||||||
|
|
||||||
|
my $username = $mail_fields->{reporter};
|
||||||
|
# If emailsuffix is in use, we have to remove it from the email address.
|
||||||
|
if (my $suffix = Bugzilla->params->{emailsuffix})
|
||||||
|
{
|
||||||
|
$username =~ s/\Q$suffix\E$//i;
|
||||||
|
}
|
||||||
|
|
||||||
|
# First try to select user with name $username
|
||||||
|
my $user = Bugzilla::User->new({ name => $username });
|
||||||
|
|
||||||
|
# Then try to find alias $username for some user
|
||||||
|
unless ($user)
|
||||||
|
{
|
||||||
|
my $dbh = Bugzilla->dbh;
|
||||||
|
($user) = $dbh->selectrow_array("SELECT userid FROM emailin_aliases WHERE address=?", undef, trim($mail_fields->{reporter}));
|
||||||
|
$user = Bugzilla::User->new({ id => $user }) if $user;
|
||||||
|
# Then check if autoregistration is enabled
|
||||||
|
unless ($user)
|
||||||
|
{
|
||||||
|
unless (Bugzilla->params->{emailin_autoregister})
|
||||||
|
{
|
||||||
|
ThrowUserError('invalid_username', { name => $username });
|
||||||
|
}
|
||||||
|
# Then try to autoregister unknown user
|
||||||
|
$user = Bugzilla::User->create({
|
||||||
|
login_name => $username,
|
||||||
|
realname => $mail_fields->{_reporter_name},
|
||||||
|
cryptpassword => 'a3#',
|
||||||
|
disabledtext => '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user->is_enabled)
|
||||||
|
{
|
||||||
|
ThrowUserError('account_disabled', { disabled_reason => $user->disabledtext });
|
||||||
|
}
|
||||||
|
|
||||||
|
Bugzilla->set_user($user);
|
||||||
|
|
||||||
|
my ($bug, $comment);
|
||||||
|
if ($mail_fields->{bug_id})
|
||||||
|
{
|
||||||
|
debug_print("Updating Bug $mail_fields->{bug_id}...");
|
||||||
|
$bug = Bugzilla::Bug::create_or_update($mail_fields);
|
||||||
|
$comment = $bug->comments->[-1] if trim($mail_fields->{comment});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
($bug, $comment) = post_bug($mail_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_attachments($bug, $attachments, $comment);
|
||||||
|
|
||||||
|
Bugzilla->send_mail;
|
||||||
|
|
||||||
|
debug_print("Sent bugmail");
|
||||||
|
|
||||||
__END__
|
__END__
|
||||||
|
|
||||||
|
|
23
js/bug.js
23
js/bug.js
|
@ -94,10 +94,8 @@ function addCollapseLink(id)
|
||||||
function addReplyLink(num, id)
|
function addReplyLink(num, id)
|
||||||
{
|
{
|
||||||
var e = document.getElementById('comment_act_'+id);
|
var e = document.getElementById('comment_act_'+id);
|
||||||
|
|
||||||
if (!e)
|
if (!e)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var s = '[';
|
var s = '[';
|
||||||
if (user_settings.quote_replies != 'off')
|
if (user_settings.quote_replies != 'off')
|
||||||
{
|
{
|
||||||
|
@ -106,16 +104,11 @@ function addReplyLink(num, id)
|
||||||
}
|
}
|
||||||
s += ', clone to <a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&cloned_comment='+num+'">other</a>';
|
s += ', clone to <a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&cloned_comment='+num+'">other</a>';
|
||||||
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&product='+encodeURIComponent(bug_info.product)+'&cloned_comment='+num+'">same</a>';
|
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&product='+encodeURIComponent(bug_info.product)+'&cloned_comment='+num+'">same</a>';
|
||||||
|
|
||||||
// 4Intranet Bug 69514 - Clone to external product button
|
// 4Intranet Bug 69514 - Clone to external product button
|
||||||
if (bug_info.extprod)
|
if (bug_info.extprod)
|
||||||
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&product='+encodeURIComponent(bug_info.extprod)+'&cloned_comment='+num+'">ext</a>';
|
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&product='+encodeURIComponent(bug_info.extprod)+'&cloned_comment='+num+'">ext</a>';
|
||||||
else if (bug_info.intprod)
|
else if (bug_info.intprod)
|
||||||
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&product='+encodeURIComponent(bug_info.intprod)+'&cloned_comment='+num+'">int</a>';
|
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&product='+encodeURIComponent(bug_info.intprod)+'&cloned_comment='+num+'">int</a>';
|
||||||
|
|
||||||
if (window.bugLinkHook)
|
|
||||||
s += bugLinkHook(num, id);
|
|
||||||
|
|
||||||
s += ' product]';
|
s += ' product]';
|
||||||
e.innerHTML += s;
|
e.innerHTML += s;
|
||||||
}
|
}
|
||||||
|
@ -264,9 +257,7 @@ function updateRemainingTime()
|
||||||
function changeform_onsubmit()
|
function changeform_onsubmit()
|
||||||
{
|
{
|
||||||
if (check_new_keywords(document.changeform) == false)
|
if (check_new_keywords(document.changeform) == false)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
var wtInput = document.changeform.work_time;
|
var wtInput = document.changeform.work_time;
|
||||||
if (!wtInput)
|
if (!wtInput)
|
||||||
|
@ -289,21 +280,9 @@ function changeform_onsubmit()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (awt != 0)
|
|
||||||
{
|
|
||||||
var txt = document.getElementById('comment_textarea').value.trim();
|
|
||||||
if (txt === '')
|
|
||||||
{
|
|
||||||
var wtonly = document.getElementById('cmt_worktime').checked;
|
|
||||||
if (!wtonly)
|
|
||||||
{
|
|
||||||
alert('You have to specify a comment on this change');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wtInput.value = awt;
|
wtInput.value = awt;
|
||||||
|
adjustRemainingTime();
|
||||||
window.checkCommentOnUnload = false;
|
window.checkCommentOnUnload = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ onDomReady(function()
|
||||||
addListener(s, 'mouseout', function(e) {
|
addListener(s, 'mouseout', function(e) {
|
||||||
e = e || window.event;
|
e = e || window.event;
|
||||||
var t = e.relatedTarget || e.toElement;
|
var t = e.relatedTarget || e.toElement;
|
||||||
if (t == s || t && t.parentNode == s)
|
if (t == s || t.parentNode == s)
|
||||||
return;
|
return;
|
||||||
s.style.width = lim+'px';
|
s.style.width = lim+'px';
|
||||||
s.style.maxWidth = '';
|
s.style.maxWidth = '';
|
||||||
|
|
10
js/util.js
10
js/util.js
|
@ -33,16 +33,6 @@ function htmlspecialchars(s)
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.http_build_query = function(data)
|
|
||||||
{
|
|
||||||
var encoded = '';
|
|
||||||
for (var i in data)
|
|
||||||
{
|
|
||||||
encoded = encoded+'&'+encodeURIComponent(i)+'='+(data[i] === false || data[i] === null ? '' : encodeURIComponent(data[i]));
|
|
||||||
}
|
|
||||||
return encoded.substr(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Checks if a specified value 'val' is in the specified array 'arr'
|
// Checks if a specified value 'val' is in the specified array 'arr'
|
||||||
function bz_isValueInArray(arr, val)
|
function bz_isValueInArray(arr, val)
|
||||||
{
|
{
|
||||||
|
|
12
report.cgi
12
report.cgi
|
@ -51,8 +51,7 @@ my $dbh = Bugzilla->switch_to_shadow_db();
|
||||||
my $action = $ARGS->{action} || 'menu';
|
my $action = $ARGS->{action} || 'menu';
|
||||||
my $token = $ARGS->{token};
|
my $token = $ARGS->{token};
|
||||||
|
|
||||||
$vars->{measure_descs} = Bugzilla::Report->get_measures();
|
if ($action eq "menu")
|
||||||
if ($action eq 'menu')
|
|
||||||
{
|
{
|
||||||
# No need to do any searching in this case, so bail out early.
|
# No need to do any searching in this case, so bail out early.
|
||||||
$template->process("reports/menu.html.tmpl", $vars)
|
$template->process("reports/menu.html.tmpl", $vars)
|
||||||
|
@ -110,11 +109,9 @@ $vars->{saved_report_id} = $ARGS->{saved_report_id};
|
||||||
$vars->{debug} = $ARGS->{debug};
|
$vars->{debug} = $ARGS->{debug};
|
||||||
$vars->{report_columns} = Bugzilla::Search->REPORT_COLUMNS();
|
$vars->{report_columns} = Bugzilla::Search->REPORT_COLUMNS();
|
||||||
|
|
||||||
$ARGS->{ctype} = $ARGS->{action} eq 'plot' ? 'png' : ($ARGS->{format} eq 'csv' ? 'csv' : 'html');
|
|
||||||
$ARGS->{format} = $ARGS->{format} eq 'csv' ? 'table' : $ARGS->{format};
|
|
||||||
my $formatparam = $ARGS->{format};
|
my $formatparam = $ARGS->{format};
|
||||||
|
|
||||||
if ($action eq 'wrap')
|
if ($action eq "wrap")
|
||||||
{
|
{
|
||||||
# So which template are we using? If action is "wrap", we will be using
|
# So which template are we using? If action is "wrap", we will be using
|
||||||
# no format (it gets passed through to be the format of the actual data),
|
# no format (it gets passed through to be the format of the actual data),
|
||||||
|
@ -132,11 +129,6 @@ if ($action eq 'wrap')
|
||||||
$vars->{imagebase} = http_build_query($a);
|
$vars->{imagebase} = http_build_query($a);
|
||||||
$a = { %$ARGS };
|
$a = { %$ARGS };
|
||||||
delete $a->{$_} for qw(query_format action ctype format width height measure);
|
delete $a->{$_} for qw(query_format action ctype format width height measure);
|
||||||
for (keys %$a)
|
|
||||||
{
|
|
||||||
delete $a->{$_} if $a->{$_} eq '';
|
|
||||||
}
|
|
||||||
$vars->{switchparams} = $a;
|
|
||||||
$vars->{switchbase} = http_build_query($a);
|
$vars->{switchbase} = http_build_query($a);
|
||||||
}
|
}
|
||||||
elsif ($action eq "plot")
|
elsif ($action eq "plot")
|
||||||
|
|
|
@ -31,11 +31,6 @@ our %FORMATS = map { $_ => 1 } qw(rss showteamwork);
|
||||||
|
|
||||||
my $who = $ARGS->{who};
|
my $who = $ARGS->{who};
|
||||||
|
|
||||||
my $fields = [ list($ARGS->{fields}) ];
|
|
||||||
my $use_comments = (!$fields || grep { $_ eq 'longdesc' } @$fields) ? 1 : 0;
|
|
||||||
my $use_fields = (!$fields || grep { $_ ne 'longdesc' } @$fields) ? 1 : 0;
|
|
||||||
@$fields = grep { $_ ne 'longdesc' } @$fields;
|
|
||||||
|
|
||||||
my $limit;
|
my $limit;
|
||||||
my $format = $ARGS->{ctype};
|
my $format = $ARGS->{ctype};
|
||||||
trick_taint($format);
|
trick_taint($format);
|
||||||
|
@ -109,7 +104,7 @@ $join = "INNER JOIN $join i" if $join;
|
||||||
# First query selects descriptions of new bugs and added comments (without duplicate information).
|
# First query selects descriptions of new bugs and added comments (without duplicate information).
|
||||||
# Worktime-only comments are excluded.
|
# Worktime-only comments are excluded.
|
||||||
# FIXME: Also use longdescs_history.
|
# FIXME: Also use longdescs_history.
|
||||||
my $longdescs = $use_comments ? $dbh->selectall_arrayref(
|
my $longdescs = $dbh->selectall_arrayref(
|
||||||
"SELECT
|
"SELECT
|
||||||
b.bug_id, b.short_desc, pr.name product, cm.name component, bs.value, st.value,
|
b.bug_id, b.short_desc, pr.name product, cm.name component, bs.value, st.value,
|
||||||
l.work_time, l.thetext,
|
l.work_time, l.thetext,
|
||||||
|
@ -130,10 +125,10 @@ my $longdescs = $use_comments ? $dbh->selectall_arrayref(
|
||||||
WHERE l.isprivate=0 ".($who ? " AND l.who=".$who->id : "")."
|
WHERE l.isprivate=0 ".($who ? " AND l.who=".$who->id : "")."
|
||||||
AND l.bug_id$subq AND l.type!=".CMT_WORKTIME." AND l.type!=".CMT_BACKDATED_WORKTIME."
|
AND l.bug_id$subq AND l.type!=".CMT_WORKTIME." AND l.type!=".CMT_BACKDATED_WORKTIME."
|
||||||
ORDER BY l.bug_when DESC
|
ORDER BY l.bug_when DESC
|
||||||
LIMIT $limit", {Slice=>{}}) : [];
|
LIMIT $limit", {Slice=>{}});
|
||||||
|
|
||||||
# Second query selects bug field change history
|
# Second query selects bug field change history
|
||||||
my $activity = $use_fields ? $dbh->selectall_arrayref(
|
my $activity = $dbh->selectall_arrayref(
|
||||||
"SELECT
|
"SELECT
|
||||||
b.bug_id, b.short_desc, pr.name product, cm.name component, bs.value, st.value,
|
b.bug_id, b.short_desc, pr.name product, cm.name component, bs.value, st.value,
|
||||||
0 AS work_time, '' thetext,
|
0 AS work_time, '' thetext,
|
||||||
|
@ -154,9 +149,8 @@ my $activity = $use_fields ? $dbh->selectall_arrayref(
|
||||||
LEFT JOIN fielddefs f ON f.id=a.fieldid
|
LEFT JOIN fielddefs f ON f.id=a.fieldid
|
||||||
LEFT JOIN attachments at ON at.attach_id=a.attach_id
|
LEFT JOIN attachments at ON at.attach_id=a.attach_id
|
||||||
WHERE at.isprivate=0 ".($who ? " AND a.who=".$who->id : "")." AND a.bug_id$subq
|
WHERE at.isprivate=0 ".($who ? " AND a.who=".$who->id : "")." AND a.bug_id$subq
|
||||||
".(@$fields ? " AND f.name IN (".join(", ", ("?") x @$fields).")" : "")."
|
|
||||||
ORDER BY a.bug_when DESC, f.name ASC
|
ORDER BY a.bug_when DESC, f.name ASC
|
||||||
LIMIT $limit", {Slice=>{}}, @$fields) : [];
|
LIMIT $limit", {Slice=>{}});
|
||||||
|
|
||||||
my $events = [ sort {
|
my $events = [ sort {
|
||||||
($b->{bug_when} cmp $a->{bug_when}) ||
|
($b->{bug_when} cmp $a->{bug_when}) ||
|
||||||
|
|
|
@ -78,8 +78,7 @@ td.bz_total {
|
||||||
.bz_buglist .bz_estimated_time_column,
|
.bz_buglist .bz_estimated_time_column,
|
||||||
.bz_buglist .bz_remaining_time_column,
|
.bz_buglist .bz_remaining_time_column,
|
||||||
.bz_buglist .bz_work_time_column,
|
.bz_buglist .bz_work_time_column,
|
||||||
.bz_buglist .bz_percentage_complete_column,
|
.bz_buglist .bz_percentage_complete_column { text-align: right; }
|
||||||
.bz_buglist .bz_f30 { text-align: right; }
|
|
||||||
|
|
||||||
.bz_buglist .bz_short_desc_column,
|
.bz_buglist .bz_short_desc_column,
|
||||||
.bz_buglist .bz_short_short_desc_column,
|
.bz_buglist .bz_short_short_desc_column,
|
||||||
|
|
|
@ -26,9 +26,6 @@
|
||||||
.bz_comment .attachment_image { max-width: 50em; margin: 10px 0px 0px 0px; }
|
.bz_comment .attachment_image { max-width: 50em; margin: 10px 0px 0px 0px; }
|
||||||
|
|
||||||
.bz_comment_text.bz_fullscreen_comment { min-width: 50em; width: 100%; word-wrap: break-word; }
|
.bz_comment_text.bz_fullscreen_comment { min-width: 50em; width: 100%; word-wrap: break-word; }
|
||||||
#commentpreviewhtml .bz_comment_text.bz_fullscreen_comment { min-width: 0px; }
|
|
||||||
|
|
||||||
.bz_comment .bz_fmt_table td, .bz_comment .bz_fmt_table th { border: 1px solid gray; padding: 5px; }
|
|
||||||
|
|
||||||
#comments > table, .bz_section_additional_comments > table { table-layout: fixed; }
|
#comments > table, .bz_section_additional_comments > table { table-layout: fixed; }
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
id="svg8"
|
|
||||||
version="1.1"
|
|
||||||
viewBox="0 0 116.86519 32.210506"
|
|
||||||
height="32.210506mm"
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
width="116.86519mm">
|
|
||||||
<g
|
|
||||||
transform="translate(483.18298,-59.147989)"
|
|
||||||
id="layer1">
|
|
||||||
<path
|
|
||||||
id="path819"
|
|
||||||
d="M -483.05357,59.630952 -366.4472,90.875534"
|
|
||||||
style="fill:none;fill-rule:evenodd;stroke:#000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 782 B |
|
@ -61,7 +61,3 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
min-width: 8em;
|
min-width: 8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.report td {
|
|
||||||
min-width: 2em;
|
|
||||||
}
|
|
||||||
|
|
|
@ -92,11 +92,16 @@ sub sqlize_dates
|
||||||
{
|
{
|
||||||
# we've checked, trick_taint is fine
|
# we've checked, trick_taint is fine
|
||||||
trick_taint($start_date);
|
trick_taint($start_date);
|
||||||
$date_bits = " AND longdescs.bug_when >= ?";
|
$date_bits = " AND longdescs.bug_when > ?";
|
||||||
push @date_values, $start_date;
|
push @date_values, $start_date;
|
||||||
}
|
}
|
||||||
if ($end_date)
|
if ($end_date)
|
||||||
{
|
{
|
||||||
|
# we need to add one day to end_date to catch stuff done today
|
||||||
|
# do not forget to adjust date if it was the last day of month
|
||||||
|
my (undef, undef, undef, $ed, $em, $ey, undef) = strptime($end_date);
|
||||||
|
($ey, $em, $ed) = date_adjust($ey+1900, $em+1, $ed, 1);
|
||||||
|
$end_date = sprintf("%04d-%02d-%02d", $ey, $em, $ed);
|
||||||
$date_bits .= " AND longdescs.bug_when < ?";
|
$date_bits .= " AND longdescs.bug_when < ?";
|
||||||
push @date_values, $end_date;
|
push @date_values, $end_date;
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@
|
||||||
<option value="[% v.id %]"[% ' selected="selected"' IF cur_default.${v.id} %]>[% v.name | html %]</option>
|
<option value="[% v.id %]"[% ' selected="selected"' IF cur_default.${v.id} %]>[% v.name | html %]</option>
|
||||||
[% END %]
|
[% END %]
|
||||||
</select>
|
</select>
|
||||||
[% ELSIF f.type == constants.FIELD_TYPE_TEXTAREA || f.type == constants.FIELD_TYPE_EAV_TEXTAREA %]
|
[% ELSIF f.type == constants.FIELD_TYPE_TEXTAREA %]
|
||||||
<textarea cols="50" rows="5" name="default_[% f.name | html %]" id="default_[% f.name | html %]">[% f.get_default_value(this_value.id) | html %]</textarea>
|
<textarea cols="50" rows="5" name="default_[% f.name | html %]" id="default_[% f.name | html %]">[% f.get_default_value(this_value.id) | html %]</textarea>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<input type="text" name="default_[% f.name | html %]" id="default_[% f.name | html %]" value="[% f.get_default_value(this_value.id) | html %]" />
|
<input type="text" name="default_[% f.name | html %]" id="default_[% f.name | html %]" value="[% f.get_default_value(this_value.id) | html %]" />
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Authors: Vitaliy Filippov <vitalif@mail.ru>, Vladimir Koptev <vladimir.koptev@gmail.com>
|
# Authors: Vitaliy Filippov <vitalif@mail.ru>, Vladimir Koptev <vladimir.koptev@gmail.com>
|
||||||
#%]
|
#%]
|
||||||
|
|
||||||
[% SET title = "Select Active " _ field.description _ " Objects For " _ field.value_field.description _ ' ' _ visibility_value.full_name | html %]
|
[% SET title = "Select Active " _ field.description _ " Objects For " _ field.value_field.description _ ' ' _ visibility_value.name | html %]
|
||||||
|
|
||||||
[% PROCESS global/header.html.tmpl %]
|
[% PROCESS global/header.html.tmpl %]
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@
|
||||||
[% FOREACH field_value = field.value_field.legal_values %]
|
[% FOREACH field_value = field.value_field.legal_values %]
|
||||||
[% IF field.visibility_field_id != field.value_field_id || field.has_visibility_value(field_value.id) %]
|
[% IF field.visibility_field_id != field.value_field_id || field.has_visibility_value(field_value.id) %]
|
||||||
<option value="[% field_value.id | none %]" [% ' selected="selected"' IF field.is_value_enabled(value.id, field_value.id) %]>
|
<option value="[% field_value.id | none %]" [% ' selected="selected"' IF field.is_value_enabled(value.id, field_value.id) %]>
|
||||||
[%- field_value.full_name | html -%]
|
[%- field_value.name | html -%]
|
||||||
</option>
|
</option>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
|
@ -31,37 +31,32 @@
|
||||||
"If this is on, $terms.Bugzilla will by default associate newly created groups"
|
"If this is on, $terms.Bugzilla will by default associate newly created groups"
|
||||||
_ " with each product in the database. Generally only useful for small databases.",
|
_ " with each product in the database. Generally only useful for small databases.",
|
||||||
|
|
||||||
chartgroup =>
|
chartgroup => "The name of the group of users who can use the 'New Charts' " _
|
||||||
"The name of the group of users who can use the 'New Charts' "
|
"feature. Administrators should ensure that the public categories " _
|
||||||
_ "feature. Administrators should ensure that the public categories "
|
"and series definitions do not divulge confidential information " _
|
||||||
_ "and series definitions do not divulge confidential information "
|
"before enabling this for an untrusted population. If left blank, " _
|
||||||
_ "before enabling this for an untrusted population. If left blank, "
|
"no users will be able to use New Charts.",
|
||||||
_ "no users will be able to use New Charts.",
|
|
||||||
|
|
||||||
insidergroup =>
|
insidergroup => "The name of the group of users who can see/change private " _
|
||||||
"The name of the group of users who can see/change private "
|
"comments and attachments.",
|
||||||
_ "comments and attachments.",
|
|
||||||
|
|
||||||
timetrackinggroup =>
|
timetrackinggroup => "The name of the group of users who can see/change time tracking " _
|
||||||
"The name of the group of users who can see/change time tracking information.",
|
"information.",
|
||||||
|
|
||||||
querysharegroup =>
|
querysharegroup => "The name of the group of users who can share their " _
|
||||||
"The name of the group of users who can share their saved searches with others.",
|
"saved searches with others.",
|
||||||
|
|
||||||
usevisibilitygroups =>
|
usevisibilitygroups =>
|
||||||
"<p>Do you wish to restrict visibility of users to members of specific groups,"
|
"<p>Do you wish to restrict visibility of users to members of specific groups,"
|
||||||
_ " based on the configuration specified in group settings?</p>"
|
_ " based on the configuration specified in group settings?</p>"
|
||||||
_ "<p>If yes, each group can be allowed to see members of selected other groups.</p>",
|
_ "<p>If yes, each group can be allowed to see members of selected other groups.</p>",
|
||||||
|
|
||||||
strict_isolation =>
|
strict_isolation => "Don't allow users to be assigned to, " _
|
||||||
"Don't allow users to be assigned to, be qa-contacts on, "
|
"be qa-contacts on, " _
|
||||||
_ "be added to CC list, or make or remove dependencies "
|
"be added to CC list, " _
|
||||||
_ "involving any bug that is in a product on which that "
|
"or make or remove dependencies " _
|
||||||
_ "user is forbidden to edit.",
|
"involving any bug that is in a product on which that " _
|
||||||
|
"user is forbidden to edit.",
|
||||||
|
|
||||||
forbid_open_products =>
|
|
||||||
"Don't allow 'open' products, i.e. force everyone to set at least"
|
|
||||||
_ " one MANDATORY/MANDATORY and one ENTRY group for each product."
|
|
||||||
_ " This is checked for new products and for products whose group controls are being modified.",
|
|
||||||
}
|
}
|
||||||
%]
|
%]
|
||||||
|
|
|
@ -56,14 +56,6 @@
|
||||||
_ " won't get sent). This affects all mail sent by $terms.Bugzilla,"
|
_ " won't get sent). This affects all mail sent by $terms.Bugzilla,"
|
||||||
_ " not just $terms.bug updates.",
|
_ " not just $terms.bug updates.",
|
||||||
|
|
||||||
enable_inmail_cgi =>
|
|
||||||
"Enable HTTP handler for incoming e-mail (email_in.cgi). " _
|
|
||||||
"<b>IMPORTANT NOTE:</b> This handler is only for usage from your MTA. " _
|
|
||||||
"If you enable it, you MUST make sure that your nginx (or other http " _
|
|
||||||
"reverse proxy Bugzilla4Intranet is installed behind) denies access to " _
|
|
||||||
"email_in.cgi from public addresses. " _
|
|
||||||
"See the end of email_in.cgi for an example Postfix configuration.",
|
|
||||||
|
|
||||||
sendmailnow => "Sites using anything older than version 8.12 of 'sendmail' " _
|
sendmailnow => "Sites using anything older than version 8.12 of 'sendmail' " _
|
||||||
"can achieve a significant performance increase in the " _
|
"can achieve a significant performance increase in the " _
|
||||||
"UI -- at the cost of delaying the sending of mail -- by " _
|
"UI -- at the cost of delaying the sending of mail -- by " _
|
||||||
|
|
|
@ -63,8 +63,4 @@
|
||||||
stem_language => "Language for stemming words in full-text search, 2-letter code" _
|
stem_language => "Language for stemming words in full-text search, 2-letter code" _
|
||||||
" (one of: da, de, en, es, fi, fr, hu, it, nl, no, pt, ro, ru, sv, tr)",
|
" (one of: da, de, en, es, fi, fr, hu, it, nl, no, pt, ro, ru, sv, tr)",
|
||||||
|
|
||||||
sphinx_max_matches =>
|
|
||||||
"Set it to the same value as max_matches in your Sphinx search configuration. " _
|
|
||||||
"Default is 1000 and if it's not enough you may sometimes miss some search results when using Sphinx.",
|
|
||||||
|
|
||||||
} %]
|
} %]
|
||||||
|
|
|
@ -58,13 +58,6 @@
|
||||||
<b><label for="makeproductgroup">Create access group for this product</label></b>
|
<b><label for="makeproductgroup">Create access group for this product</label></b>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<input type="checkbox" id="makeadmingroup" name="makeadmingroup" value="1"
|
|
||||||
[% ' checked="checked"' IF Param('makeproductgroups') %] />
|
|
||||||
<b><label for="makeadmingroup">Create administration group for this product</label></b>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<input type="submit" value="Add" />
|
<input type="submit" value="Add" />
|
||||||
|
|
|
@ -226,7 +226,7 @@ var close_status_array = [
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% INCLUDE custom_fields style = "width: 40em"
|
[% INCLUDE custom_fields style = "width: 40em"
|
||||||
cf = Bugzilla.active_custom_fields({ 'type' => [ constants.FIELD_TYPE_TEXTAREA, constants.FIELD_TYPE_EAV_TEXTAREA ] }) %]
|
cf = Bugzilla.active_custom_fields({ 'type' => constants.FIELD_TYPE_TEXTAREA }) %]
|
||||||
|
|
||||||
[% INCLUDE custom_fields style = "min-width: 20em; max-width: 40em"
|
[% INCLUDE custom_fields style = "min-width: 20em; max-width: 40em"
|
||||||
cf = Bugzilla.active_custom_fields({ 'type' => constants.FIELD_TYPE_MULTI_SELECT }) %]
|
cf = Bugzilla.active_custom_fields({ 'type' => constants.FIELD_TYPE_MULTI_SELECT }) %]
|
||||||
|
|
|
@ -55,7 +55,6 @@
|
||||||
bug.id && field.type != constants.FIELD_TYPE_BUG_ID_REV &&
|
bug.id && field.type != constants.FIELD_TYPE_BUG_ID_REV &&
|
||||||
field.type != constants.FIELD_TYPE_MULTI_SELECT &&
|
field.type != constants.FIELD_TYPE_MULTI_SELECT &&
|
||||||
field.type != constants.FIELD_TYPE_TEXTAREA &&
|
field.type != constants.FIELD_TYPE_TEXTAREA &&
|
||||||
field.type != constants.FIELD_TYPE_EAV_TEXTAREA &&
|
|
||||||
field.type != constants.FIELD_TYPE_BUG_URLS &&
|
field.type != constants.FIELD_TYPE_BUG_URLS &&
|
||||||
field.type != constants.FIELD_TYPE_BUG_ID %]
|
field.type != constants.FIELD_TYPE_BUG_ID %]
|
||||||
|
|
||||||
|
@ -260,15 +259,6 @@
|
||||||
cols = 30
|
cols = 30
|
||||||
defaultcontent = value
|
defaultcontent = value
|
||||||
tabindex = tabindex %]
|
tabindex = tabindex %]
|
||||||
[% CASE constants.FIELD_TYPE_EAV_TEXTAREA %]
|
|
||||||
[% INCLUDE global/textarea.html.tmpl
|
|
||||||
id = field.name
|
|
||||||
name = field.name
|
|
||||||
minrows = 4
|
|
||||||
maxrows = 8
|
|
||||||
cols = 30
|
|
||||||
defaultcontent = value
|
|
||||||
tabindex = tabindex %]
|
|
||||||
[% CASE constants.FIELD_TYPE_BUG_URLS %]
|
[% CASE constants.FIELD_TYPE_BUG_URLS %]
|
||||||
[% '<ul class="bug_urls">' IF value.size %]
|
[% '<ul class="bug_urls">' IF value.size %]
|
||||||
[% FOREACH url = value %]
|
[% FOREACH url = value %]
|
||||||
|
@ -302,7 +292,7 @@
|
||||||
[% IF show_search_link %]
|
[% IF show_search_link %]
|
||||||
</td><td style="padding-left: 5px">[% PROCESS search_link %](search)</a></td></tr></table>
|
</td><td style="padding-left: 5px">[% PROCESS search_link %](search)</a></td></tr></table>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% ELSIF field.type == constants.FIELD_TYPE_TEXTAREA || field.type == constants.FIELD_TYPE_EAV_TEXTAREA %]
|
[% ELSIF field.type == constants.FIELD_TYPE_TEXTAREA %]
|
||||||
<div class="uneditable_textarea">[% value | html | wrap_comment %]</div>
|
<div class="uneditable_textarea">[% value | html | wrap_comment %]</div>
|
||||||
[% ELSIF field.type == constants.FIELD_TYPE_BUG_ID %]
|
[% ELSIF field.type == constants.FIELD_TYPE_BUG_ID %]
|
||||||
[% IF bug.${field.name} %]
|
[% IF bug.${field.name} %]
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
|
|
||||||
[% PROCESS "bug/show-header.html.tmpl" %]
|
[% PROCESS "bug/show-header.html.tmpl" %]
|
||||||
|
|
||||||
[% Hook.process('bug_link_hook') %]
|
|
||||||
|
|
||||||
[% PROCESS bug/navigate.html.tmpl %]
|
[% PROCESS bug/navigate.html.tmpl %]
|
||||||
|
|
||||||
[% PROCESS bug/edit.html.tmpl %]
|
[% PROCESS bug/edit.html.tmpl %]
|
||||||
|
|
|
@ -532,10 +532,6 @@
|
||||||
Can't use [% field FILTER html %] as a field name.
|
Can't use [% field FILTER html %] as a field name.
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% BLOCK error_invalid_fulltext_query %]
|
|
||||||
The query text you specified cannot be handled by Bugzilla full-text search engine.
|
|
||||||
[% END %]
|
|
||||||
|
|
||||||
[% BLOCK error_in_search_needs_bugid_field %]
|
[% BLOCK error_in_search_needs_bugid_field %]
|
||||||
Subquery search operator ("In Search Results") can only be applied to fields of type "Bug ID".
|
Subquery search operator ("In Search Results") can only be applied to fields of type "Bug ID".
|
||||||
"[% field | html %]" is not a Bug ID field.
|
"[% field | html %]" is not a Bug ID field.
|
||||||
|
@ -1602,19 +1598,6 @@
|
||||||
"[% suggested | html %]"?
|
"[% suggested | html %]"?
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% BLOCK error_product_mandatory_group_required %]
|
|
||||||
Bugzilla group security policy requires that all products have at least
|
|
||||||
one group which is set as MANDATORY/MANDATORY, which means that only users of
|
|
||||||
this group or users explicitly added as CC/Assignee/QA may access bugs in
|
|
||||||
the corresponding product.
|
|
||||||
[% END %]
|
|
||||||
|
|
||||||
[% BLOCK error_product_entry_group_required %]
|
|
||||||
[% terms.Bugzilla %] group security policy requires that all products have at least
|
|
||||||
one group which is set as ENTRY, which means that only users of
|
|
||||||
this group may file new bugs in the corresponding product.
|
|
||||||
[% END %]
|
|
||||||
|
|
||||||
[% BLOCK error_product_name_already_in_use %]
|
[% BLOCK error_product_name_already_in_use %]
|
||||||
[% title = "Product name already exists" %]
|
[% title = "Product name already exists" %]
|
||||||
[% admindocslinks = {'products.html' => 'Administering products'} %]
|
[% admindocslinks = {'products.html' => 'Administering products'} %]
|
||||||
|
|
|
@ -225,10 +225,6 @@
|
||||||
[% ELSIF field_obj.type == constants.FIELD_TYPE_NUMERIC %]
|
[% ELSIF field_obj.type == constants.FIELD_TYPE_NUMERIC %]
|
||||||
[%# Remove trailing zeros %]
|
[%# Remove trailing zeros %]
|
||||||
[% bug.$column.replace('((\.\d*[1-9])|\.)0+$', '$2') %]
|
[% bug.$column.replace('((\.\d*[1-9])|\.)0+$', '$2') %]
|
||||||
[% ELSIF field_obj.type == constants.FIELD_TYPE_TEXTAREA ||
|
|
||||||
field_obj.type == constants.FIELD_TYPE_EAV_TEXTAREA %]
|
|
||||||
[%- v = bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) -%]
|
|
||||||
[%- v | quoteUrls(bug.bug_id) | wrap_comment -%]
|
|
||||||
[% ELSIF column == 'work_time' ||
|
[% ELSIF column == 'work_time' ||
|
||||||
column == 'remaining_time' ||
|
column == 'remaining_time' ||
|
||||||
column == 'estimated_time' ||
|
column == 'estimated_time' ||
|
||||||
|
@ -271,8 +267,7 @@
|
||||||
[%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
|
[%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
|
||||||
</a>
|
</a>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
[%- v = bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
|
[%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
|
||||||
[%- v.replace("\n", '<br>') -%]
|
|
||||||
[% END %]
|
[% END %]
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
@ -282,7 +277,7 @@
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
[% IF loop.last() && total_info %]
|
[% IF loop.last() && time_info.time_present == 1 %]
|
||||||
[% PROCESS time_summary_line %]
|
[% PROCESS time_summary_line %]
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
|
@ -324,18 +319,22 @@
|
||||||
[% columns_to_span = columns_to_span + 1 %]
|
[% columns_to_span = columns_to_span + 1 %]
|
||||||
[% END %]
|
[% END %]
|
||||||
[% FOREACH column = displaycolumns %]
|
[% FOREACH column = displaycolumns %]
|
||||||
[% IF total_info.defined(column) %]
|
[% IF column == 'work_time' ||
|
||||||
|
column == 'remaining_time' ||
|
||||||
|
column == 'estimated_time' ||
|
||||||
|
column == 'percentage_complete' ||
|
||||||
|
column == 'interval_time' %]
|
||||||
[% IF columns_to_span > 0 %]
|
[% IF columns_to_span > 0 %]
|
||||||
<td class="bz_total bz_total_label" colspan="
|
<td class="bz_total bz_total_label" colspan="
|
||||||
[%- columns_to_span FILTER html %]"><b>Totals</b></td>
|
[%- columns_to_span FILTER html %]"><b>Totals</b></td>
|
||||||
[% columns_to_span = 0 %]
|
[% columns_to_span = 0 %]
|
||||||
[% END %]
|
[% END %]
|
||||||
[% IF column == 'percentage_complete' %]
|
[% IF column == 'percentage_complete' %]
|
||||||
<td class="bz_total">[% total_info.percentage_complete
|
<td class="bz_total">[% time_info.percentage_complete
|
||||||
FILTER format(abbrev.$column.format_value) FILTER html %]</td>
|
FILTER format(abbrev.$column.format_value) FILTER html %]</td>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td class="bz_total">
|
<td class="bz_total">
|
||||||
[%- PROCESS formattimeunit time_unit=total_info.$column %]</td>
|
[%- PROCESS formattimeunit time_unit=time_info.$column %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% ELSIF columns_to_span == 0 %] [%# A column following the first total %]
|
[% ELSIF columns_to_span == 0 %] [%# A column following the first total %]
|
||||||
<td class="bz_total"> </td>
|
<td class="bz_total"> </td>
|
||||||
|
|
|
@ -15,14 +15,6 @@
|
||||||
<script type="text/javascript" src="[% 'js/resize-iframe.js' | ts_url %]"></script>
|
<script type="text/javascript" src="[% 'js/resize-iframe.js' | ts_url %]"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
[% FOREACH tbl = tbl_names %]
|
|
||||||
[% IF tbl == "-total-" %]
|
|
||||||
[% tbl_disp = "Total" %]
|
|
||||||
[% ELSE %]
|
|
||||||
[% tbl_disp = tbl %]
|
|
||||||
[% END %]
|
|
||||||
|
|
||||||
[% PROCESS "reports/report-table.html.tmpl" %]
|
[% PROCESS "reports/report-table.html.tmpl" %]
|
||||||
[% END %]
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -35,16 +35,13 @@
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
[% tbl_disp = tbl %]
|
[% tbl_disp = tbl %]
|
||||||
[% END %]
|
[% END %]
|
||||||
[% tbl_field_disp _ ": " _ tbl_disp | csv %]
|
[% tbl_field_disp FILTER csv %]: [% tbl_disp FILTER csv %]
|
||||||
|
|
||||||
[% END %]
|
[% END %]
|
||||||
[% IF col_field AND row_field %]
|
[% IF row_field %]
|
||||||
[% row_field_disp _ " / " _ col_field_disp | csv %]
|
|
||||||
[% ELSIF row_field %]
|
|
||||||
[% row_field_disp FILTER csv %]
|
[% row_field_disp FILTER csv %]
|
||||||
[% ELSIF col_field %]
|
|
||||||
[% col_field_disp FILTER csv %]
|
|
||||||
[% END %]
|
[% END %]
|
||||||
|
[% " / " IF col_field AND row_field %]
|
||||||
|
[% col_field_disp FILTER csv %]
|
||||||
[% IF col_field -%]
|
[% IF col_field -%]
|
||||||
[% FOREACH col = col_names -%]
|
[% FOREACH col = col_names -%]
|
||||||
[% colsepchar %]
|
[% colsepchar %]
|
||||||
|
|
|
@ -53,38 +53,37 @@ END; %]
|
||||||
<h2>[% tbl_disp FILTER email FILTER html %]</h2>
|
<h2>[% tbl_disp FILTER email FILTER html %]</h2>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<strong>[% col_field_disp FILTER html %]</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td valign="middle">
|
||||||
|
<strong>[% row_field_disp FILTER html %]</strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
[% classes = [ [ "t1", "t2" ] , [ "t3", "t4" ] ] %]
|
[% classes = [ [ "t1", "t2" ] , [ "t3", "t4" ] ] %]
|
||||||
[% col_idx = 0 %]
|
[% col_idx = 0 %]
|
||||||
[% row_idx = 0 %]
|
[% row_idx = 0 %]
|
||||||
[% mlist = (measure == 'times' ? [ 'etime', 'wtime', 'rtime' ] : [ measure ]) %]
|
[% mlist = (measure == 'times' ? [ 'etime', 'wtime', 'rtime' ] : [ measure ]) %]
|
||||||
|
|
||||||
<table class="report" border="1" style="border-collapse: collapse">
|
<table border="1" style="border-collapse: collapse">
|
||||||
[% IF col_field %]
|
[% IF col_field %]
|
||||||
<tr>
|
<tr>
|
||||||
<td class="[% classes.$row_idx.$col_idx %]"><div style="position: relative; height: 100%">
|
<td class="[% classes.$row_idx.$col_idx %]">
|
||||||
<img style="position: absolute; left: 0; top: 0; width: 100%; height: 100%" src="skins/standard/global/diag.svg" />
|
</td>
|
||||||
<table>
|
|
||||||
<tr><td></td><td><strong>[% col_field_disp FILTER html %]</strong></td></tr>
|
|
||||||
<tr><td><strong>[% row_field_disp FILTER html %]</strong></td><td></td></tr>
|
|
||||||
</table>
|
|
||||||
</div></td>
|
|
||||||
[% FOREACH col = col_names %]
|
[% FOREACH col = col_names %]
|
||||||
[% col_idx = 1 - col_idx %]
|
[% col_idx = 1 - col_idx %]
|
||||||
<td class="[% classes.$row_idx.$col_idx %]"[% ' colspan="3"' IF measure == 'times' %]>
|
<td class="[% classes.$row_idx.$col_idx %]"[% ' colspan="3"' IF measure == 'times' %]>
|
||||||
[% PROCESS value_display value = col field = col_field %]
|
[% PROCESS value_display value = col field = col_field %]
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% IF col_names.size > 1 %]
|
|
||||||
<td class="ttotal"[% ' colspan="3"' IF measure == 'times' %]>
|
|
||||||
Total
|
|
||||||
</td>
|
|
||||||
[% END %]
|
|
||||||
</tr>
|
|
||||||
[% ELSE %]
|
|
||||||
<tr>
|
|
||||||
<td class="[% classes.$row_idx.$col_idx %]">
|
|
||||||
<strong>[% row_field_disp FILTER html %]</strong>
|
|
||||||
</td>
|
|
||||||
<td class="ttotal"[% ' colspan="3"' IF measure == 'times' %]>
|
<td class="ttotal"[% ' colspan="3"' IF measure == 'times' %]>
|
||||||
Total
|
Total
|
||||||
</td>
|
</td>
|
||||||
|
@ -112,7 +111,7 @@ END; %]
|
||||||
<a href="[% urlbase %]&
|
<a href="[% urlbase %]&
|
||||||
[% PROCESS value_url value=row field=row_field %]&
|
[% PROCESS value_url value=row field=row_field %]&
|
||||||
[% PROCESS value_url value=col field=col_field %]">
|
[% PROCESS value_url value=col field=col_field %]">
|
||||||
[% data.$tbl.$col.$row.$m | format("%.01f") %]</a>
|
[% data.$tbl.$col.$row.$m %]</a>
|
||||||
[% END %]
|
[% END %]
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
@ -124,7 +123,7 @@ END; %]
|
||||||
<a href="[% urlbase %]&
|
<a href="[% urlbase %]&
|
||||||
[% PROCESS value_url value=row field=row_field %]
|
[% PROCESS value_url value=row field=row_field %]
|
||||||
[% "&$col_vals" IF col_vals %]">
|
[% "&$col_vals" IF col_vals %]">
|
||||||
[% row_total.$m | format("%.01f") %]</a>
|
[% row_total.$m %]</a>
|
||||||
[% grand_total.$m = grand_total.$m + row_total.$m %]
|
[% grand_total.$m = grand_total.$m + row_total.$m %]
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
@ -142,7 +141,7 @@ END; %]
|
||||||
<a href="[% urlbase %]&
|
<a href="[% urlbase %]&
|
||||||
[% PROCESS value_url value=col field=col_field %]
|
[% PROCESS value_url value=col field=col_field %]
|
||||||
[% "&$row_vals" IF row_vals %]">
|
[% "&$row_vals" IF row_vals %]">
|
||||||
[% col_totals.$col_n.$m | format("%.01f") %]</a>
|
[% col_totals.$col_n.$m %]</a>
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% col_n = col_n + 1 %]
|
[% col_n = col_n + 1 %]
|
||||||
|
@ -153,7 +152,7 @@ END; %]
|
||||||
<strong>
|
<strong>
|
||||||
<a href="[% urlbase %]
|
<a href="[% urlbase %]
|
||||||
[% "&$row_vals" IF row_vals %]
|
[% "&$row_vals" IF row_vals %]
|
||||||
[% "&$col_vals" IF col_vals %]">[% grand_total.$m | format("%.01f") %]</a>
|
[% "&$col_vals" IF col_vals %]">[% grand_total.$m %]</a>
|
||||||
</strong>
|
</strong>
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
@ -161,6 +160,10 @@ END; %]
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
[% BLOCK value_display %]
|
[% BLOCK value_display %]
|
||||||
[% SET disp_value = value %]
|
[% SET disp_value = value %]
|
||||||
[% IF field == 'assigned_to' OR field == 'reporter' OR field == 'qa_contact' %]
|
[% IF field == 'assigned_to' OR field == 'reporter' OR field == 'qa_contact' %]
|
||||||
|
|
|
@ -18,5 +18,11 @@
|
||||||
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
||||||
#%]
|
#%]
|
||||||
[% FOREACH tbl = tbl_names %]
|
[% FOREACH tbl = tbl_names %]
|
||||||
|
[% measure_descs = {
|
||||||
|
rtime => 'Remaining time'
|
||||||
|
etime => 'Estimated time'
|
||||||
|
wtime => 'Actual time'
|
||||||
|
count => "Number of $terms.bugs"
|
||||||
|
} %]
|
||||||
[% PROCESS "reports/report-table.csv.tmpl" %]
|
[% PROCESS "reports/report-table.csv.tmpl" %]
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
[% tbl_field_disp = field_descs.$tbl_field || tbl_field %]
|
[% tbl_field_disp = field_descs.$tbl_field || tbl_field %]
|
||||||
[% col_field_disp = field_descs.$col_field || col_field %]
|
[% col_field_disp = field_descs.$col_field || col_field %]
|
||||||
[% row_field_disp = field_descs.$row_field || row_field %]
|
[% row_field_disp = field_descs.$row_field || row_field %]
|
||||||
|
[% switchbase = switchbase FILTER html %]
|
||||||
|
|
||||||
[% title = BLOCK %]
|
[% title = BLOCK %]
|
||||||
Report:
|
Report:
|
||||||
|
@ -61,11 +62,10 @@
|
||||||
style = "
|
style = "
|
||||||
.t1 { background-color: #ffffff } /* white */
|
.t1 { background-color: #ffffff } /* white */
|
||||||
.t2 { background-color: #dfefff } /* light blue */
|
.t2 { background-color: #dfefff } /* light blue */
|
||||||
.t3 { background-color: #ffffff } /* white */
|
.t3 { background-color: #dddddd } /* grey */
|
||||||
.t4 { background-color: #dfefff } /* light blue */
|
.t4 { background-color: #c3d3ed } /* darker blue */
|
||||||
.ttotal { background-color: #cfffdf } /* light green */
|
.ttotal { background-color: #cfffdf } /* light green */
|
||||||
"
|
"
|
||||||
style_urls = ['skins/standard/reports.css']
|
|
||||||
header_addl_info = time
|
header_addl_info = time
|
||||||
%]
|
%]
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
<p>[% query FILTER html %]</p>
|
<p>[% query FILTER html %]</p>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
<div>
|
<div align="center">
|
||||||
|
|
||||||
[% FOREACH tbl = tbl_names %]
|
[% FOREACH tbl = tbl_names %]
|
||||||
[% IF tbl == "-total-" %]
|
[% IF tbl == "-total-" %]
|
||||||
|
@ -110,78 +110,82 @@
|
||||||
<br />
|
<br />
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
<form method="GET" action="?">
|
<table>
|
||||||
<input type="hidden" name="action" value="wrap" />
|
|
||||||
[% FOR k = switchparams.keys.sort %]
|
|
||||||
<input type="hidden" name="[% k | html %]" value="[% switchparams.$k | html %]" />
|
|
||||||
[% END %]
|
|
||||||
<table class="admin_table">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
|
||||||
Format:
|
|
||||||
</th>
|
|
||||||
<td>
|
<td>
|
||||||
[% formats = [
|
[% formats = [ { name => "pie", description => "Pie" },
|
||||||
{ name => "pie", description => "Pie" },
|
{ name => "bar", description => "Bar" },
|
||||||
{ name => "bar", description => "Bar" },
|
{ name => "line", description => "Line" },
|
||||||
{ name => "line", description => "Line" },
|
{ name => "table", description => "Table" } ] %]
|
||||||
{ name => "table", description => "Table" },
|
|
||||||
{ name => "csv", description => "CSV" },
|
[% formaturl = "report.cgi?$switchbase&width=$width" _
|
||||||
] %]
|
"&height=$height&action=wrap" %]
|
||||||
<select name="format" style="width: 100%; border: 0; margin-top: 2px">
|
[% FOREACH other_format = formats %]
|
||||||
[% FOR f = formats %]
|
[% NEXT IF other_format.name == "pie" AND row_field AND col_field %]
|
||||||
<option value="[% f.name | html %]"[% IF f.name == format %] selected="selected"[% END %]>[% f.description | html %]</option>
|
[% UNLESS other_format.name == format %]
|
||||||
|
<a href="[% formaturl %]&format=[% other_format.name %]&measure=[% measure %]">
|
||||||
[% END %]
|
[% END %]
|
||||||
</select>
|
[% other_format.description FILTER html %]
|
||||||
</td>
|
[% "</a>" UNLESS other_format.name == format %] |
|
||||||
</tr>
|
[% END %]
|
||||||
<tr>
|
<a href="[% formaturl %]&measure=[% measure %]&ctype=csv&format=table">CSV</a>
|
||||||
<th>
|
|
||||||
Show:
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<select name="measure" style="width: 100%; border: 0; margin-top: 2px">
|
|
||||||
[% FOR m = measure_descs.keys.sort %]
|
|
||||||
<option value="[% m %]"[% IF measure == m %] selected="selected"[% END %]>[% measure_descs.$m %]</option>
|
|
||||||
[% END %]
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
[% IF format != "table" %]
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
Width:
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="text" name="width" value="[% width %]" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
Height:
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="text" name="height" value="[% height %]" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
[% END %]
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" style="text-align: center">
|
|
||||||
<input type="submit" value="Apply Format" />
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
[% IF format != "table" %]
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
[% sizeurl = BLOCK %]report.cgi?
|
||||||
|
[% switchbase %]&action=wrap&format=[% format FILTER html %]&measure=[% measure FILTER html %][% END %]
|
||||||
|
<td align="center">
|
||||||
|
<a href="[% sizeurl %]&width=[% width %]&height=
|
||||||
|
[% height + 100 %]">Taller</a><br />
|
||||||
|
<a href="[% sizeurl %]&width=[% width - 100 %]&height=
|
||||||
|
[% height %]">Thinner</a> *
|
||||||
|
<a href="[% sizeurl %]&width=[% width + 100 %]&height=
|
||||||
|
[% height %]">Fatter</a> <br />
|
||||||
|
<a href="[% sizeurl %]&width=[% width %]&height=
|
||||||
|
[% height - 100 %]">Shorter</a><br />
|
||||||
|
</td>
|
||||||
|
[% END %]
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
|
||||||
<div style="margin-top: 1em">
|
<table><tr><td>
|
||||||
<input type="button" onclick="window.location.href='query.cgi?[% switchbase | html %]
|
[% measure_descs = {
|
||||||
[%- IF format == "table" -%]&format=report-table
|
rtime => 'Remaining time'
|
||||||
[%- ELSE -%]&chart_format=[% format %]&format=report-graph&cumulate=[% cumulate %]
|
etime => 'Estimated time'
|
||||||
[%- END -%]'" value="Edit this report" />
|
wtime => 'Actual time'
|
||||||
|
times => 'Estimated/Actual/Remaining'
|
||||||
|
count => "Number of $terms.bugs"
|
||||||
|
} %]
|
||||||
|
[% FOR m = measure_descs.keys.sort %]
|
||||||
|
[% IF m != 'times' || format == 'table' %]
|
||||||
|
[%+ IF measure != m %]<a href="[% formaturl %]&format=[% format %]&measure=[% m %]">[% END %]
|
||||||
|
[% measure_descs.$m %]
|
||||||
|
[% IF measure != m %]</a>[% END %]
|
||||||
|
[% IF NOT loop.last %] | [% END %]
|
||||||
|
[% END %]
|
||||||
|
[% END %]
|
||||||
|
</td></tr></table>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
[% IF format == "table" %]
|
||||||
|
<a href="query.cgi?[% switchbase %]&format=report-table">Edit
|
||||||
|
this report</a>
|
||||||
|
[% ELSE %]
|
||||||
|
<a href="query.cgi?[% switchbase %]&chart_format=
|
||||||
|
[% format %]&format=report-graph&cumulate=[% cumulate %]">
|
||||||
|
Edit this report
|
||||||
|
</a>
|
||||||
|
[% END %]
|
||||||
[% IF saved_report_id %]
|
[% IF saved_report_id %]
|
||||||
| <a href="report.cgi?action=del&saved_report_id=[% saved_report_id FILTER uri %]&token=
|
| <a href="report.cgi?action=del&saved_report_id=[% saved_report_id FILTER uri %]&token=
|
||||||
[%~ issue_hash_token(['delete_report', saved_report_id]) FILTER uri %]">Forget this report</a>
|
[%~ issue_hash_token(['delete_report', saved_report_id]) FILTER uri %]">Forget this report</a>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<form method="get" action="report.cgi" style="display: inline">
|
<form method="get" action="report.cgi">
|
||||||
<input type="submit" id="remember" value="Remember report" /> as
|
<input type="submit" id="remember" value="Remember report" /> as
|
||||||
<input type="hidden" name="query" value="[% switchbase %]&format=[% format FILTER html %]&action=wrap" />
|
<input type="hidden" name="query" value="[% switchbase %]&format=[% format FILTER html %]&action=wrap" />
|
||||||
<input type="hidden" name="action" value="add" />
|
<input type="hidden" name="action" value="add" />
|
||||||
|
@ -189,7 +193,7 @@
|
||||||
<input type="text" id="name" name="name" size="20" value="" maxlength="64" />
|
<input type="text" id="name" name="name" size="20" value="" maxlength="64" />
|
||||||
</form>
|
</form>
|
||||||
[% END %]
|
[% END %]
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -243,9 +243,9 @@ function wt_user_blur()
|
||||||
ed.style.color = 'red';
|
ed.style.color = 'red';
|
||||||
document.getElementById('divide_other_bug_id').value = '';
|
document.getElementById('divide_other_bug_id').value = '';
|
||||||
}
|
}
|
||||||
document.getElementById('divide_other_bug_id_text').style.color = ed.value && ed.value != 'за всех участников' ? 'gray' : '';
|
document.getElementById('divide_other_bug_id_text').style.color = ed.value ? 'gray' : '';
|
||||||
document.getElementById('divide_other_bug_id_text_2').style.color = ed.value && ed.value != 'за всех участников' ? 'gray' : '';
|
document.getElementById('divide_other_bug_id_text_2').style.color = ed.value ? 'gray' : '';
|
||||||
document.getElementById('divide_other_bug_id').disabled = ed.value && ed.value != 'за всех участников' ? true : false;
|
document.getElementById('divide_other_bug_id').disabled = ed.value ? true : false;
|
||||||
}
|
}
|
||||||
function wt_user_focus()
|
function wt_user_focus()
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<form name="lastdaysform" action="fill-day-worktime.cgi" method="get">
|
<form name="lastdaysform" action="fill-day-worktime.cgi" method="get">
|
||||||
<input value="Select last days" type="submit" />
|
<input value="Select last days" type="submit" />
|
||||||
<select [% lastdays <= 7 ? 'name="lastdays"' : 'style="display: none"' %] id="_lastdays" onchange="check_other(this)">
|
<select name="lastdays">
|
||||||
<option value="1" [% " selected=\"selected\"" IF (!lastdays)||(lastdays==1) %]>1</option>
|
<option value="1" [% " selected=\"selected\"" IF (!lastdays)||(lastdays==1) %]>1</option>
|
||||||
<option value="2" [% " selected=\"selected\"" IF (lastdays==2) %]>2</option>
|
<option value="2" [% " selected=\"selected\"" IF (lastdays==2) %]>2</option>
|
||||||
<option value="3" [% " selected=\"selected\"" IF (lastdays==3) %]>3</option>
|
<option value="3" [% " selected=\"selected\"" IF (lastdays==3) %]>3</option>
|
||||||
|
@ -38,9 +38,7 @@
|
||||||
<option value="5" [% " selected=\"selected\"" IF (lastdays==5) %]>5</option>
|
<option value="5" [% " selected=\"selected\"" IF (lastdays==5) %]>5</option>
|
||||||
<option value="6" [% " selected=\"selected\"" IF (lastdays==6) %]>6</option>
|
<option value="6" [% " selected=\"selected\"" IF (lastdays==6) %]>6</option>
|
||||||
<option value="7" [% " selected=\"selected\"" IF (lastdays==7) %]>7</option>
|
<option value="7" [% " selected=\"selected\"" IF (lastdays==7) %]>7</option>
|
||||||
<option value="other">other...</option>
|
|
||||||
</select>
|
</select>
|
||||||
<input [% lastdays > 7 ? 'name="lastdays" style="width: 80px"' : 'style="display: none; width: 80px"' %] value="[% lastdays || 1 %]" />
|
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -126,29 +124,12 @@
|
||||||
</td></tr></table>
|
</td></tr></table>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script>
|
|
||||||
function check_other(sel)
|
|
||||||
{
|
|
||||||
if (sel.style.display != 'none' && sel.value == 'other')
|
|
||||||
{
|
|
||||||
var txt = sel.nextSibling;
|
|
||||||
while (txt && txt.nodeName != 'INPUT')
|
|
||||||
txt = txt.nextSibling;
|
|
||||||
txt.setAttribute('name', sel.name);
|
|
||||||
txt.style.display = '';
|
|
||||||
sel.setAttribute('name', '');
|
|
||||||
sel.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check_other(document.getElementById('_lastdays'));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
[% PROCESS global/footer.html.tmpl %]
|
[% PROCESS global/footer.html.tmpl %]
|
||||||
|
|
||||||
[% BLOCK today_or_lastdays %]
|
[% BLOCK today_or_lastdays %]
|
||||||
[% IF lastdays>1 %]
|
[% IF lastdays>1 %]
|
||||||
Last [% lastdays %] Days
|
Last [% lastdays %] Days
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
Today
|
Today
|
||||||
[% END %]
|
[% END %]
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
Loading…
Reference in New Issue