Bug 68921, Bug 61225 - Предикаты проверки корректности / заморозки багов, Bug 70605 - Разложил хуки аккуратно, плюс сделал чтобы при $^P |= 0x10; $Bugzilla::RELOAD_MODULES=1; в конфиге апача релоадились Модули.pm-ки
git-svn-id: svn://svn.office.custis.ru/3rdparty/bugzilla.org/trunk@999 6955db30-a419-402b-8a0d-67ecbb4d7f56master
parent
1e4892591f
commit
b439c78544
|
@ -671,6 +671,8 @@ sub update {
|
|||
# inside this function.
|
||||
my $delta_ts = shift || $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
|
||||
|
||||
Bugzilla::Hook::process('bug_pre_update', { bug => $self });
|
||||
|
||||
my ($changes, $old_bug) = $self->SUPER::update(@_);
|
||||
|
||||
# Certain items in $changes have to be fixed so that they hold
|
||||
|
@ -840,6 +842,8 @@ sub update {
|
|||
$dbh->do("INSERT INTO longdescs ($columns) VALUES ($qmarks)", undef, @values);
|
||||
if ($comment->{work_time})
|
||||
{
|
||||
$changes->{work_time} ||= [ '', 0 ];
|
||||
$changes->{work_time}->[1] += $comment->{work_time};
|
||||
# log worktime
|
||||
LogActivityEntry($self->id, "work_time", "", $comment->{work_time},
|
||||
Bugzilla->user->id, $delta_ts);
|
||||
|
@ -915,10 +919,6 @@ sub update {
|
|||
$changes->{'dup_id'} = [$old_dup || undef, $cur_dup || undef];
|
||||
}
|
||||
|
||||
Bugzilla::Hook::process('bug_end_of_update',
|
||||
{ bug => $self, timestamp => $delta_ts, changes => $changes,
|
||||
old_bug => $old_bug });
|
||||
|
||||
# If any change occurred, refresh the timestamp of the bug.
|
||||
if (scalar(keys %$changes) || $self->{added_comments}) {
|
||||
$dbh->do('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?',
|
||||
|
@ -926,6 +926,10 @@ sub update {
|
|||
$self->{delta_ts} = $delta_ts;
|
||||
}
|
||||
|
||||
Bugzilla::Hook::process('bug_end_of_update',
|
||||
{ bug => $self, timestamp => $delta_ts, changes => $changes,
|
||||
old_bug => $old_bug });
|
||||
|
||||
# The only problem with this here is that update() is often called
|
||||
# in the middle of a transaction, and if that transaction is rolled
|
||||
# back, this change will *not* be rolled back. As we expect rollbacks
|
||||
|
@ -2736,6 +2740,8 @@ sub blocked {
|
|||
# Even bugs in an error state always have a bug_id.
|
||||
sub bug_id { $_[0]->{'bug_id'}; }
|
||||
|
||||
sub failed_checkers { $_[0]->{failed_checkers} }
|
||||
|
||||
sub cc {
|
||||
my ($self) = @_;
|
||||
return $self->{'cc'} if exists $self->{'cc'};
|
||||
|
|
|
@ -1029,6 +1029,7 @@ sub bz_start_transaction {
|
|||
my ($self) = @_;
|
||||
|
||||
if ($self->bz_in_transaction) {
|
||||
$self->set_savepoint();
|
||||
$self->{private_bz_transaction_count}++;
|
||||
} else {
|
||||
# Turn AutoCommit off and start a new transaction
|
||||
|
@ -1056,19 +1057,49 @@ sub bz_commit_transaction {
|
|||
}
|
||||
}
|
||||
|
||||
sub bz_rollback_transaction {
|
||||
sub bz_rollback_transaction
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# Unlike start and commit, if we rollback at any point it happens
|
||||
# instantly, even if we're in a nested transaction.
|
||||
if (!$self->bz_in_transaction) {
|
||||
if (!$self->bz_in_transaction)
|
||||
{
|
||||
ThrowCodeError("not_in_transaction");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->rollback();
|
||||
$self->{private_bz_transaction_count} = 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub bz_rollback_to_savepoint
|
||||
{
|
||||
my $self = shift;
|
||||
if ($self->{private_bz_transaction_count} <= 1)
|
||||
{
|
||||
$self->bz_rollback_transaction();
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{private_bz_transaction_count}--;
|
||||
$self->rollback_to_savepoint();
|
||||
}
|
||||
}
|
||||
|
||||
sub set_savepoint
|
||||
{
|
||||
my $self = shift;
|
||||
$self->do('SAVEPOINT sp'.$self->{private_bz_transaction_count});
|
||||
}
|
||||
|
||||
sub rollback_to_savepoint
|
||||
{
|
||||
my $self = shift;
|
||||
$self->do('ROLLBACK TO SAVEPOINT sp'.$self->{private_bz_transaction_count});
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Subclass Helpers
|
||||
#####################################################################
|
||||
|
|
|
@ -79,7 +79,7 @@ sub process
|
|||
eval { require $pk };
|
||||
if ($@)
|
||||
{
|
||||
warn __PACKAGE__."::process(): Error autoloading hook package $pk: $@";
|
||||
die __PACKAGE__."::process(): Error autoloading hook package $pk: $@";
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ sub process
|
|||
$sub = eval "package $pk; sub { my (\$args) = \@_;\n#line 1 \"$f->{filename}\"\n$sub; return 1; };";
|
||||
if ($@)
|
||||
{
|
||||
warn __PACKAGE__."::process(): error during loading $f->{filename} into a subroutine (note that Bugzilla->hook_args was replaced by \$args): $@";
|
||||
die __PACKAGE__."::process(): error during loading $f->{filename} into a subroutine (note that Bugzilla->hook_args was replaced by \$args): $@";
|
||||
next;
|
||||
}
|
||||
$f = $sub;
|
||||
|
|
|
@ -55,6 +55,7 @@ use constant DB_COLUMNS => qw(
|
|||
name
|
||||
wiki_url
|
||||
notimetracking
|
||||
extproduct
|
||||
classification_id
|
||||
description
|
||||
isactive
|
||||
|
@ -75,6 +76,7 @@ use constant UPDATE_COLUMNS => qw(
|
|||
name
|
||||
wiki_url
|
||||
notimetracking
|
||||
extproduct
|
||||
description
|
||||
defaultmilestone
|
||||
isactive
|
||||
|
@ -655,6 +657,13 @@ sub set_votes_per_bug { $_[0]->set('maxvotesperbug', $_[1]); }
|
|||
sub set_votes_to_confirm { $_[0]->set('votestoconfirm', $_[1]); }
|
||||
sub set_allows_unconfirmed { $_[0]->set('allows_unconfirmed', $_[1]); }
|
||||
|
||||
sub set_extproduct
|
||||
{
|
||||
my ($self, $product) = @_;
|
||||
$product = Bugzilla::Product->check({ id => $product }) if ref $product;
|
||||
$self->set('extproduct', $product ? $product->id : undef);
|
||||
}
|
||||
|
||||
sub set_group_controls {
|
||||
my ($self, $group, $settings) = @_;
|
||||
|
||||
|
@ -951,6 +960,7 @@ sub default_milestone { return $_[0]->{'defaultmilestone'}; }
|
|||
sub classification_id { return $_[0]->{'classification_id'}; }
|
||||
sub wiki_url { return $_[0]->{'wiki_url'}; }
|
||||
sub notimetracking { return $_[0]->{'notimetracking'}; }
|
||||
sub extproduct { return $_[0]->{'extproduct'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
|
|
|
@ -1095,31 +1095,42 @@ sub init {
|
|||
push @sql_fields, $field;
|
||||
}
|
||||
}
|
||||
my $query = "SELECT " . join(', ', @sql_fields) .
|
||||
" FROM $suppstring" .
|
||||
" LEFT JOIN bug_group_map " .
|
||||
" ON bug_group_map.bug_id = bugs.bug_id ";
|
||||
my $query = "SELECT " . join(', ', @sql_fields) . " FROM $suppstring";
|
||||
|
||||
if ($user->id) {
|
||||
if (scalar @{ $user->groups }) {
|
||||
$query .= " AND bug_group_map.group_id NOT IN ("
|
||||
. $user->groups_as_string . ") ";
|
||||
if (!$user->is_super_user)
|
||||
{
|
||||
$query .= " LEFT JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id";
|
||||
if ($user->id)
|
||||
{
|
||||
if (scalar @{ $user->groups })
|
||||
{
|
||||
$query .=
|
||||
" AND bug_group_map.group_id NOT IN ("
|
||||
. $user->groups_as_string . ") ";
|
||||
}
|
||||
$query .= " LEFT JOIN cc ON cc.bug_id = bugs.bug_id AND cc.who = " . $user->id;
|
||||
}
|
||||
|
||||
$query .= " LEFT JOIN cc ON cc.bug_id = bugs.bug_id AND cc.who = " . $user->id;
|
||||
}
|
||||
|
||||
$query .= " WHERE " . join(' AND ', (@wherepart, @andlist)) .
|
||||
" AND bugs.creation_ts IS NOT NULL AND ((bug_group_map.group_id IS NULL)";
|
||||
" AND bugs.creation_ts IS NOT NULL ";
|
||||
|
||||
if ($user->id) {
|
||||
my $userid = $user->id;
|
||||
$query .= " OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid) " .
|
||||
" OR (bugs.cclist_accessible = 1 AND cc.who IS NOT NULL) " .
|
||||
" OR (bugs.assigned_to = $userid) ";
|
||||
if (Bugzilla->params->{'useqacontact'}) {
|
||||
$query .= "OR (bugs.qa_contact = $userid) ";
|
||||
if (!$user->is_super_user)
|
||||
{
|
||||
$query .= " AND ((bug_group_map.group_id IS NULL)";
|
||||
if ($user->id)
|
||||
{
|
||||
my $userid = $user->id;
|
||||
$query .=
|
||||
" OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid) " .
|
||||
" OR (bugs.cclist_accessible = 1 AND cc.who IS NOT NULL) " .
|
||||
" OR (bugs.assigned_to = $userid) ";
|
||||
if (Bugzilla->params->{'useqacontact'})
|
||||
{
|
||||
$query .= "OR (bugs.qa_contact = $userid) ";
|
||||
}
|
||||
}
|
||||
$query .= ") ";
|
||||
}
|
||||
|
||||
# For some DBs, every field in the SELECT must be in the GROUP BY.
|
||||
|
@ -1139,7 +1150,7 @@ sub init {
|
|||
push(@groupby, @{ $special_order{$column_name} });
|
||||
}
|
||||
}
|
||||
$query .= ") " . $dbh->sql_group_by("bugs.bug_id", join(', ', @groupby));
|
||||
$query .= $dbh->sql_group_by("bugs.bug_id", join(', ', @groupby));
|
||||
|
||||
|
||||
if (@having) {
|
||||
|
|
|
@ -71,7 +71,7 @@ sub new {
|
|||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $user;
|
||||
if (ref $param) {
|
||||
if (ref $param && !$param->{id}) {
|
||||
$user = $param->{user} || Bugzilla->user;
|
||||
my $name = $param->{name};
|
||||
if (!defined $name) {
|
||||
|
@ -237,6 +237,16 @@ sub used_in_whine {
|
|||
return $self->{used_in_whine};
|
||||
}
|
||||
|
||||
sub used_in_checkers
|
||||
{
|
||||
my $self = shift;
|
||||
if (!exists $self->{used_in_checkers})
|
||||
{
|
||||
($self->{used_in_checkers}) = Bugzilla->dbh->selectrow_array('SELECT 1 FROM checkers WHERE query_id=?', undef, $self->id);
|
||||
}
|
||||
return $self->{used_in_checkers};
|
||||
}
|
||||
|
||||
sub link_in_footer {
|
||||
my ($self, $user) = @_;
|
||||
# We only cache link_in_footer for the current Bugzilla->user.
|
||||
|
|
|
@ -80,6 +80,8 @@ use constant DEFAULT_USER => {
|
|||
'disable_mail' => 0,
|
||||
};
|
||||
|
||||
my $SUPERUSER = {};
|
||||
|
||||
use constant DB_TABLE => 'profiles';
|
||||
|
||||
# XXX Note that Bugzilla::User->name does not return the same thing
|
||||
|
@ -136,16 +138,24 @@ sub new {
|
|||
return $class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
sub super_user {
|
||||
sub super_user
|
||||
{
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my ($param) = @_;
|
||||
|
||||
my $user = dclone(DEFAULT_USER);
|
||||
$user->{groups} = [Bugzilla::Group->get_all];
|
||||
$user->{bless_groups} = [Bugzilla::Group->get_all];
|
||||
bless $user, $class;
|
||||
return $user;
|
||||
%$SUPERUSER = %{ DEFAULT_USER() };
|
||||
$SUPERUSER->{groups} = [Bugzilla::Group->get_all];
|
||||
$SUPERUSER->{bless_groups} = [Bugzilla::Group->get_all];
|
||||
bless $SUPERUSER, $class;
|
||||
|
||||
return $SUPERUSER;
|
||||
}
|
||||
|
||||
sub is_super_user
|
||||
{
|
||||
my $self = shift;
|
||||
return $self eq $SUPERUSER;
|
||||
}
|
||||
|
||||
sub update {
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/perl -wT
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
# Редактор предикатов корректности изменений
|
||||
# (c) Vitaliy Filippov 2010 <vitalif@mail.ru>
|
||||
|
||||
use strict;
|
||||
use lib qw(. lib);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Checker;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Token;
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
my $user = Bugzilla->login(LOGIN_REQUIRED);
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $params = $cgi->Vars;
|
||||
my $vars = {};
|
||||
|
||||
my @REAL_UPDATE_COLUMNS = qw(is_freeze is_fatal message);
|
||||
|
||||
$user->in_group('bz_editcheckers')
|
||||
|| ThrowUserError('auth_failure', {group => 'bz_editcheckers',
|
||||
action => 'modify',
|
||||
object => 'checkers'});
|
||||
|
||||
my $id = $params->{query_id};
|
||||
defined($id) && detaint_natural($id);
|
||||
if ($params->{save})
|
||||
{
|
||||
if ($params->{edit})
|
||||
{
|
||||
check_token_data($params->{token}, 'editcheckers');
|
||||
my $ch;
|
||||
if ($params->{create})
|
||||
{
|
||||
$ch = Bugzilla::Checker->create({
|
||||
(map { ($_ => $params->{$_}) } 'query_id', @REAL_UPDATE_COLUMNS),
|
||||
user_id => $user->id,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
$ch = Bugzilla::Checker->check({ id => $id });
|
||||
my $f;
|
||||
for (@REAL_UPDATE_COLUMNS)
|
||||
{
|
||||
$f = "set_$_";
|
||||
$ch->$f($params->{$_});
|
||||
}
|
||||
}
|
||||
# поля-исключения: если deny_all=1, то разрешить только их,
|
||||
# если deny_all=0, то запретить только их,
|
||||
# если deny_all=0 и их нет, то deny_all=1
|
||||
my $except = { deny_all => $params->{deny_all}, except_fields => {} };
|
||||
for (keys %$params)
|
||||
{
|
||||
if (/^except_field_(\d+)$/so)
|
||||
{
|
||||
$except->{except_fields}->{$params->{$_}} =
|
||||
$params->{"except_field_$1_value"} || undef;
|
||||
}
|
||||
}
|
||||
if (!%{$except->{except_fields}})
|
||||
{
|
||||
$except = undef;
|
||||
}
|
||||
$ch->set_except_fields($except);
|
||||
$ch->update;
|
||||
delete_token($params->{token});
|
||||
}
|
||||
elsif ($params->{delete})
|
||||
{
|
||||
Bugzilla->dbh->do('DELETE FROM checkers WHERE query_id=?', undef, $id);
|
||||
}
|
||||
print $cgi->redirect(-location => 'editcheckers.cgi');
|
||||
exit;
|
||||
}
|
||||
|
||||
my $modes = { list => 'list', edit => 'edit' };
|
||||
$vars->{mode} = $modes->{$params->{mode}} || 'list';
|
||||
|
||||
if ($vars->{mode} eq 'list')
|
||||
{
|
||||
$vars->{checkers} = Bugzilla->dbh->selectall_arrayref('SELECT * FROM checkers WHERE user_id=?', {Slice=>{}}, $user->id);
|
||||
bless $_, 'Bugzilla::Checker' for @{$vars->{checkers}};
|
||||
}
|
||||
else
|
||||
{
|
||||
$vars->{token} = issue_session_token('editcheckers');
|
||||
$vars->{create} = $params->{create} ? 1 : 0;
|
||||
my $f = [ Bugzilla->get_fields ];
|
||||
@$f = sort { lc $a->description cmp lc $b->description } grep { $_->name !~ /\.|^owner_idle_time$|^commenter$/ } @$f;
|
||||
$vars->{my_fielddefs} = $f;
|
||||
if (!$vars->{create})
|
||||
{
|
||||
$vars->{checker} = Bugzilla::Checker->new({ id => $id });
|
||||
}
|
||||
else
|
||||
{
|
||||
$vars->{checker} = { deny_all => 1 };
|
||||
}
|
||||
}
|
||||
|
||||
$template->process('edit-checkers.html.tmpl', $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
__END__
|
|
@ -179,6 +179,7 @@ if ($action eq 'new') {
|
|||
allows_unconfirmed => scalar $cgi->param('allows_unconfirmed'),
|
||||
wiki_url => scalar $cgi->param('wiki_url'),
|
||||
notimetracking => scalar $cgi->param('notimetracking'),
|
||||
extproduct => scalar $cgi->param('extproduct'),
|
||||
);
|
||||
if (Bugzilla->params->{'usevotes'}) {
|
||||
$create_params{votesperuser} = $cgi->param('votesperuser');
|
||||
|
@ -286,6 +287,7 @@ if ($action eq 'update') {
|
|||
$product->set_name($product_name);
|
||||
$product->set_wiki_url(scalar $cgi->param('wiki_url'));
|
||||
$product->set_notimetracking(scalar $cgi->param('notimetracking'));
|
||||
$product->set_extproduct(scalar $cgi->param('extproduct'));
|
||||
$product->set_description(scalar $cgi->param('description'));
|
||||
$product->set_default_milestone(scalar $cgi->param('defaultmilestone'));
|
||||
$product->set_is_active(scalar $cgi->param('is_active'));
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Util qw(trim);
|
||||
use CustisLocalBugzillas;
|
||||
|
||||
my $user = Bugzilla->hook_args->{user};
|
||||
if ($user->settings->{redirect_me_to_my_bugzilla} &&
|
||||
lc($user->settings->{redirect_me_to_my_bugzilla}->{value}) eq "on")
|
||||
{
|
||||
my $loc = \%CustisLocalBugzillas::local_urlbase;
|
||||
my $fullurl = Bugzilla->cgi->url();
|
||||
foreach my $regemail (keys %$loc)
|
||||
{
|
||||
if ($user->login =~ /$regemail/s &&
|
||||
$fullurl !~ /\Q$loc->{$regemail}->{urlbase}\E/s)
|
||||
{
|
||||
my $relativeurl = Bugzilla->cgi->url(
|
||||
-path_info => 1,
|
||||
-query => 1,
|
||||
-relative => 1
|
||||
);
|
||||
my $url = $loc->{$regemail}->{urlbase} . $relativeurl;
|
||||
print Bugzilla->cgi->redirect(-location => $url);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
|
||||
my $columns = Bugzilla->hook_args->{columns};
|
||||
|
||||
$columns->{dependson} = {
|
||||
name => "(SELECT GROUP_CONCAT(bugblockers.dependson SEPARATOR ',') FROM dependencies bugblockers WHERE bugblockers.blocked=bugs.bug_id)",
|
||||
title => "Bug dependencies",
|
||||
};
|
||||
|
||||
$columns->{blocked} = {
|
||||
name => "(SELECT GROUP_CONCAT(bugblocked.blocked SEPARATOR ',') FROM dependencies bugblocked WHERE bugblocked.dependson=bugs.bug_id)",
|
||||
title => "Bugs blocked",
|
||||
};
|
||||
|
||||
$columns->{flags} = {
|
||||
name =>
|
||||
"(SELECT GROUP_CONCAT(CONCAT(col_ft.name,col_f.status) SEPARATOR ', ')
|
||||
FROM flags col_f JOIN flagtypes col_ft ON col_f.type_id=col_ft.id
|
||||
WHERE col_f.bug_id=bugs.bug_id AND (col_ft.is_requesteeble=0 OR col_ft.is_requestable=0))",
|
||||
title => "Flags",
|
||||
};
|
||||
|
||||
$columns->{requests} = {
|
||||
name =>
|
||||
"(SELECT GROUP_CONCAT(CONCAT(col_ft.name,col_f.status,CASE WHEN col_p.login_name IS NULL THEN '' ELSE CONCAT(' ',col_p.login_name) END) SEPARATOR ', ')
|
||||
FROM flags col_f JOIN flagtypes col_ft ON col_f.type_id=col_ft.id
|
||||
LEFT JOIN profiles col_p ON col_f.requestee_id=col_p.userid
|
||||
WHERE col_f.bug_id=bugs.bug_id AND col_ft.is_requesteeble=1 AND col_ft.is_requestable=1)",
|
||||
title => "Requests",
|
||||
};
|
||||
|
||||
# CustIS Bug 68921 (see also Bugzilla::Search)
|
||||
$columns->{interval_time} = $columns->{actual_time};
|
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use CustisLocalBugzillas;
|
||||
|
||||
# Unhack urlbase :-)
|
||||
CustisLocalBugzillas::HackIntoUrlbase(undef);
|
|
@ -1,31 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Constants;
|
||||
use CustisLocalBugzillas;
|
||||
use Bugzilla::Util;
|
||||
use POSIX qw(strftime);
|
||||
|
||||
my $vars = Bugzilla->hook_args->{vars};
|
||||
${Bugzilla->hook_args->{tmpl}} = 'email/newchangedmail-'.$vars->{product}.'.txt.tmpl';
|
||||
|
||||
my $datadir = bz_locations()->{datadir};
|
||||
my $fd;
|
||||
if (-w "$datadir/maillog" && open $fd, ">>$datadir/maillog")
|
||||
{
|
||||
my $s = [ strftime("%Y-%m-%d %H:%M:%S: ", localtime) . ($vars->{isnew} ? "" : "Re: ") . "Bug #$vars->{bugid} mail to $vars->{to}" ];
|
||||
if ($vars->{new_comments} && @{$vars->{new_comments}})
|
||||
{
|
||||
push @$s, scalar(@{$vars->{new_comments}}) . ' comment(s) (#' . (join ',', map { $_->{count} } @{$vars->{new_comments}}) . ')';
|
||||
}
|
||||
if ($vars->{diffarray} && @{$vars->{diffarray}})
|
||||
{
|
||||
push @$s, scalar(grep { $_->{type} eq 'change' } @{$vars->{diffarray}}) . ' diffs';
|
||||
}
|
||||
$s = join "; ", @$s;
|
||||
print $fd $s, "\n";
|
||||
close $fd;
|
||||
}
|
||||
|
||||
# Hack into urlbase and set it to be correct for email recipient
|
||||
CustisLocalBugzillas::HackIntoUrlbase($vars->{to});
|
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
|
||||
my $columns = Bugzilla->hook_args->{columns};
|
||||
|
||||
push @$columns, 'dependson', 'blocked';
|
||||
|
||||
push @$columns, 'flags', 'requests';
|
||||
|
||||
push @$columns, 'interval_time';
|
|
@ -1,98 +0,0 @@
|
|||
#!/usr/bin/perl -wT
|
||||
# Модификации схемы БД
|
||||
|
||||
use strict;
|
||||
my $schema = Bugzilla->hook_args->{schema};
|
||||
|
||||
# FIELD VALUES FOR INCOMING EMAILS
|
||||
# --------------
|
||||
|
||||
$schema->{emailin_fields} = {
|
||||
FIELDS => [
|
||||
address => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
field => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
value => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
emailin_fields_primary => { FIELDS => ['address', 'field'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# ALIASES FOR INCOMING EMAILS
|
||||
# --------------
|
||||
|
||||
$schema->{emailin_aliases} = {
|
||||
FIELDS => [
|
||||
address => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
userid => {TYPE => 'INT3', NOTNULL => 1,
|
||||
REFERENCES => {TABLE => 'profiles',
|
||||
COLUMN => 'userid'}},
|
||||
fromldap => {TYPE => 'BOOLEAN'},
|
||||
isprimary => {TYPE => 'BOOLEAN'},
|
||||
],
|
||||
INDEXES => [
|
||||
emailin_aliases_address => { FIELDS => ['address'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 64562 - надо идти на дом. страницу бага после постановки, а не на post_bug.cgi
|
||||
push @{$schema->{logincookies}->{FIELDS}}, session_data => {TYPE => 'blob'};
|
||||
|
||||
# Ну и зачем авторы убрали этот индекс?
|
||||
# Bug 53687 - Тормозят запросы из Plantime в багзиллу
|
||||
push @{$schema->{longdescs}->{INDEXES}}, { FIELDS => ['who', 'bug_when'] };
|
||||
|
||||
# Bug 13593 - Интеграция с Wiki
|
||||
push @{$schema->{components}->{FIELDS}}, wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"};
|
||||
push @{$schema->{products}->{FIELDS}}, wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"};
|
||||
|
||||
# Bug 59357 - Отключение учёта времени в отдельных продуктах
|
||||
push @{$schema->{products}->{FIELDS}}, notimetracking => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0};
|
||||
|
||||
# Bug 53725 - Версия по умолчанию
|
||||
push @{$schema->{components}->{FIELDS}}, default_version => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"};
|
||||
|
||||
# Bug 68921 - Закрытие компонента (так же как закрытие продукта), чтобы в него нельзя было ставить новые баги
|
||||
push @{$schema->{components}->{FIELDS}}, is_active => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1};
|
||||
|
||||
# Bug 53617 - Ограничение Custom Fields двумя и более значениями контролирующего поля
|
||||
$schema->{fieldvaluecontrol} = {
|
||||
FIELDS => [
|
||||
field_id => {TYPE => 'INT3', NOTNULL => 1},
|
||||
value_id => {TYPE => 'INT2', NOTNULL => 1},
|
||||
visibility_value_id => {TYPE => 'INT2', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
fieldvaluecontrol_primary_idx =>
|
||||
{FIELDS => ['field_id', 'visibility_value_id', 'value_id'],
|
||||
TYPE => 'UNIQUE'},
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 45485 - Scrum-карточки из Bugzilla
|
||||
$schema->{scrum_cards} = {
|
||||
FIELDS => [
|
||||
bug_id => {TYPE => 'INT3', NOTNULL => 1},
|
||||
sprint => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
type => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
estimate => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
scrum_cards_primary_idx => { FIELDS => ['bug_id', 'sprint', 'type'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 63447 - Глобальная авторизация
|
||||
$schema->{globalauth} = {
|
||||
FIELDS => [
|
||||
id => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
secret => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
expire => {TYPE => 'bigint', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
globalauth_primary_idx => { FIELDS => ['id'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 69325 - Настройка копирования / не копирования значения поля при клонировании бага
|
||||
push @{$schema->{fielddefs}->{FIELDS}}, clone_bug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1};
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
refresh_views();
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
refresh_views();
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
refresh_views();
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
refresh_views();
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
refresh_views();
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
refresh_views();
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
refresh_views();
|
|
@ -1,17 +0,0 @@
|
|||
#!/usr/bin/perl -wT
|
||||
|
||||
use strict;
|
||||
|
||||
for (${Bugzilla->hook_args->{body}})
|
||||
{
|
||||
if (/From:\s+bugzilla-daemon(\s*[a-z0-9_\-]+\s*:.*?\n)*\s*Bug\s*\d+<[^>]*>\s*\([^\)]*\)\s*/iso)
|
||||
{
|
||||
my ($pr, $ps) = ($`, $');
|
||||
$ps =~ s/\n+(\r*\n+)+/\n/giso;
|
||||
$_ = $pr . $ps;
|
||||
s!from\s+.*?<http://plantime[^>]*search=([^>]*)>!from $1!giso;
|
||||
s!((Comment|Bug)\s+\#?\d+)<[^<>]*>!$1!giso;
|
||||
s!\n[^\n]*<http://plantime[^>]*search=[^>]*>\s+changed:[ \t\r]*\n.*?$!!iso;
|
||||
s/\s*\n--\s*Configure\s*bugmail<[^>]*>(([ \t\r]*\n[^\n]*)*)//iso;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/perl -wT
|
||||
|
||||
use strict;
|
||||
|
||||
for (${Bugzilla->hook_args->{body}})
|
||||
{
|
||||
s/<table[^<>]*class=[\"\']?difft[^<>]*>.*?<\/table\s*>//giso;
|
||||
s/<a[^<>]*>.*?<\/a\s*>/custis_rmlf($&)/gieso;
|
||||
}
|
||||
|
||||
sub custis_rmlf
|
||||
{
|
||||
my ($t) = @_;
|
||||
$t =~ s/[\n\r]+/ /giso;
|
||||
return $t;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use CustisLocalBugzillas;
|
||||
|
||||
# Unhack urlbase :-)
|
||||
CustisLocalBugzillas::HackIntoUrlbase(undef);
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use CustisLocalBugzillas;
|
||||
|
||||
my $vars = Bugzilla->hook_args->{vars};
|
||||
|
||||
# Hack into urlbase and set it to be correct for email recipient
|
||||
CustisLocalBugzillas::HackIntoUrlbase($vars->{to});
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use utf8;
|
||||
use Bugzilla::User;
|
||||
|
||||
my $requestees = Bugzilla->hook_args->{requestees};
|
||||
if (@$requestees)
|
||||
{
|
||||
my $group_users = Bugzilla->dbh->selectall_arrayref(
|
||||
'SELECT watcher.*, watched.login_name group_user FROM profiles watcher, watch, profiles watched WHERE watcher.userid=watch.watcher AND watched.userid=watch.watched AND watched.login_name IN ('.
|
||||
join(',', ('?') x @$requestees).') AND watched.disable_mail AND watched.realname LIKE "Группа%"', {Slice=>{}}, @$requestees
|
||||
);
|
||||
my %del = map { ($_->{group_user} => 1) } @$group_users;
|
||||
@$requestees = ((grep { !$del{$_} } @$requestees), (map { $_->{login_name} } @$group_users));
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#!/usr/bin/perl -w
|
||||
|
||||
sub REQUIRED_MODULES {
|
||||
my @modules = (
|
||||
);
|
||||
return \@modules;
|
||||
};
|
||||
|
||||
sub OPTIONAL_MODULES {
|
||||
my @modules = (
|
||||
{
|
||||
package => 'Spreadsheet-ParseExcel',
|
||||
module => 'Spreadsheet::ParseExcel',
|
||||
version => '0.54',
|
||||
feature => 'Import of binary Excel files (*.xls)',
|
||||
},
|
||||
{
|
||||
package => 'Spreadsheet-XLSX',
|
||||
module => 'Spreadsheet::XLSX',
|
||||
version => '0.1',
|
||||
feature => 'Import of OOXML Excel files (*.xlsx)',
|
||||
},
|
||||
);
|
||||
return \@modules;
|
||||
};
|
|
@ -1,190 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
# ./checksetup'овые обновления базы
|
||||
|
||||
use strict;
|
||||
use utf8;
|
||||
use Encode;
|
||||
use URI::Escape;
|
||||
use Bugzilla::Constants;
|
||||
|
||||
sub sure_utf8
|
||||
{
|
||||
my ($s) = @_;
|
||||
$s = uri_unescape($s);
|
||||
Encode::_utf8_on($s);
|
||||
my $v = utf8::valid($s);
|
||||
Encode::_utf8_off($s);
|
||||
Encode::from_to($s, 'cp1251', 'utf8') unless $v;
|
||||
$s = uri_escape($s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Перенос CC по умолчанию из нашей доработки initialcclist в нормальный механизм component_cc
|
||||
my $ccour = $dbh->bz_column_info('components', 'initialcclist');
|
||||
unless ($ccour)
|
||||
{
|
||||
$ccour = $dbh->selectall_arrayref("DESC components") || [];
|
||||
$ccour = { map { ($_->[0] => 1) } @$ccour };
|
||||
$ccour = $ccour->{initialcclist} ? 1 : undef;
|
||||
}
|
||||
|
||||
if ($ccour)
|
||||
{
|
||||
print "Migrating initialcclist to component_cc...\n";
|
||||
my $cc = $dbh->selectall_arrayref("SELECT id, initialcclist FROM components") || [];
|
||||
$dbh->do("CREATE TABLE IF NOT EXISTS old_component_initialcc (id smallint(6) not null auto_increment primary key, initialcclist tinytext not null)");
|
||||
my $ins = $dbh->prepare("REPLACE INTO old_component_initialcc (id, initialcclist) VALUES (?, ?)");
|
||||
my $addcc = $dbh->prepare("REPLACE INTO component_cc (user_id, component_id) VALUES (?, ?)");
|
||||
my ($user, $uid, $list);
|
||||
my $added = [];
|
||||
foreach (@$cc)
|
||||
{
|
||||
$ins->execute(@$_);
|
||||
if ($list = $_->[1])
|
||||
{
|
||||
$list = [ split /[\s,]+/, $list ];
|
||||
for $user (@$list)
|
||||
{
|
||||
$user =~ s/^\s+|\s+$//so;
|
||||
($uid) = $dbh->selectrow_array("SELECT userid FROM profiles WHERE login_name=?", undef, $user);
|
||||
unless ($uid)
|
||||
{
|
||||
print " ERROR: unknown default CC for component $_->[0]: '$user'\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
push @$added, [ $uid, $_->[0] ];
|
||||
$addcc->execute($uid, $_->[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($dbh->bz_column_info('components', 'initialcclist'))
|
||||
{
|
||||
$dbh->bz_drop_column('components', 'initialcclist');
|
||||
}
|
||||
else
|
||||
{
|
||||
$dbh->do("ALTER TABLE components DROP initialcclist");
|
||||
}
|
||||
print "Successfully migrated ".scalar(@$added)." initial CC definitions\n";
|
||||
print " (old data backed up in old_component_initialcc table)\n";
|
||||
}
|
||||
|
||||
# Перекодировка параметров сохранённых поисков из CP-1251 в UTF-8
|
||||
print "Making sure saved queries are in UTF-8...\n";
|
||||
my $nq = $dbh->selectall_arrayref("SELECT * FROM namedqueries WHERE query LIKE '%\\%%'", {Slice=>{}});
|
||||
if ($nq)
|
||||
{
|
||||
my $q;
|
||||
foreach (@$nq)
|
||||
{
|
||||
$q = $_->{query};
|
||||
$q =~ s/(\%[0-9A-F]{2})+/sure_utf8($&)/iegso;
|
||||
$dbh->do("UPDATE namedqueries SET query=? WHERE id=?", undef, $q, $_->{id}) if $q ne $_->{query};
|
||||
}
|
||||
}
|
||||
|
||||
# Bug 13593 - Интеграция с Wiki
|
||||
if (!$dbh->bz_column_info('products', 'buglist'))
|
||||
{
|
||||
# Добавляем колонку wiki_url в продукты
|
||||
$dbh->bz_add_column('products', wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
if (!$dbh->bz_column_info('components', 'buglist'))
|
||||
{
|
||||
# Добавляем колонку wiki_url в компоненты
|
||||
$dbh->bz_add_column('components', wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
|
||||
# Bug 59357 - Отключение учёта времени в отдельных продуктах
|
||||
if (!$dbh->bz_column_info('products', 'notimetracking'))
|
||||
{
|
||||
# Добавляем колонку notimetracking в продукты
|
||||
$dbh->bz_add_column('products', notimetracking => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
|
||||
}
|
||||
|
||||
# Bug 53725 - Версия по умолчанию
|
||||
if (!$dbh->bz_column_info('components', 'default_version'))
|
||||
{
|
||||
$dbh->bz_add_column('components', default_version => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
|
||||
# Bug 68921 - Закрытие компонента (так же как закрытие продукта), чтобы в него нельзя было ставить новые баги
|
||||
if (!$dbh->bz_column_info('components', 'is_active'))
|
||||
{
|
||||
$dbh->bz_add_column('components', is_active => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1});
|
||||
}
|
||||
|
||||
# Bug 53617 - Ограничение Custom Fields двумя и более значениями контролирующего поля
|
||||
my @standard_fields = qw(bug_status resolution priority bug_severity op_sys rep_platform);
|
||||
my $custom_fields = $dbh->selectall_arrayref(
|
||||
'SELECT * FROM fielddefs WHERE (custom=1 AND type IN (?,?)) OR name IN ('.
|
||||
join(',',('?') x @standard_fields).')', {Slice=>{}},
|
||||
FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT, @standard_fields);
|
||||
foreach my $field (@$custom_fields)
|
||||
{
|
||||
if ($dbh->bz_table_info($field->{name}) &&
|
||||
$dbh->bz_column_info($field->{name}, 'visibility_value_id'))
|
||||
{
|
||||
print "Migrating $field->{name}'s visibility_value_id into fieldvaluecontrol\n";
|
||||
$dbh->do(
|
||||
"REPLACE INTO fieldvaluecontrol (field_id, visibility_value_id, value_id)".
|
||||
" SELECT f.id, v.visibility_value_id, v.id FROM fielddefs f, `$field->{name}` v".
|
||||
" WHERE f.name=? AND v.visibility_value_id IS NOT NULL", undef, $field->{name});
|
||||
print "Making backup of table $field->{name}\n";
|
||||
$dbh->do("CREATE TABLE `backup_$field->{name}_".time."` AS SELECT * FROM `$field->{name}`");
|
||||
print "Dropping column $field->{name}.visibility_value_id\n";
|
||||
$dbh->bz_drop_column($field->{name}, 'visibility_value_id');
|
||||
}
|
||||
}
|
||||
|
||||
if ($dbh->bz_column_info('fielddefs', 'visibility_value_id'))
|
||||
{
|
||||
print "Migrating fielddefs's visibility_value_id into fieldvaluecontrol\n";
|
||||
$dbh->do(
|
||||
"REPLACE INTO fieldvaluecontrol (field_id, visibility_value_id, value_id)".
|
||||
" SELECT id, visibility_value_id, 0 FROM fielddefs WHERE visibility_value_id IS NOT NULL");
|
||||
print "Making backup of table fielddefs\n";
|
||||
$dbh->do("CREATE TABLE `backup_fielddefs_".time."` AS SELECT * FROM fielddefs");
|
||||
print "Dropping column fielddefs.visibility_value_id\n";
|
||||
$dbh->bz_drop_column('fielddefs', 'visibility_value_id');
|
||||
}
|
||||
|
||||
# Testopia:
|
||||
if ($dbh->bz_table_info('test_fielddefs'))
|
||||
{
|
||||
# Bug 53254 - Интеграция плана с MediaWiki
|
||||
unless ($dbh->bz_column_info('test_plans', 'wiki'))
|
||||
{
|
||||
$dbh->bz_add_column('test_plans', wiki => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
unless ($dbh->selectrow_array("SELECT name FROM test_fielddefs WHERE table_name='test_plans' AND name='wiki'"))
|
||||
{
|
||||
$dbh->do("INSERT INTO test_fielddefs (name, description, table_name) VALUES ('wiki', 'Wiki Category', 'test_plans')");
|
||||
}
|
||||
}
|
||||
|
||||
# Bug 64562 - надо идти на дом. страницу бага после постановки, а не на post_bug.cgi
|
||||
if (!$dbh->bz_column_info('logincookies', 'session_data'))
|
||||
{
|
||||
$dbh->bz_add_column('logincookies', session_data => {TYPE => 'blob'});
|
||||
}
|
||||
|
||||
# Bug 69766 - Default CSV charset for M1cr0$0ft Excel
|
||||
if (!$dbh->selectrow_array('SELECT name FROM setting WHERE name=\'csv_charset\' LIMIT 1'))
|
||||
{
|
||||
$dbh->do('INSERT INTO setting (name, default_value, is_enabled) VALUES (\'csv_charset\', \'utf-8\', 1)');
|
||||
}
|
||||
if (!$dbh->selectrow_array('SELECT name FROM setting_value WHERE name=\'csv_charset\' LIMIT 1'))
|
||||
{
|
||||
$dbh->do('INSERT INTO setting_value (name, value, sortindex) VALUES (\'csv_charset\', \'utf-8\', 10), (\'csv_charset\', \'windows-1251\', 20), (\'csv_charset\', \'koi8-r\', 30)');
|
||||
}
|
||||
|
||||
# Bug 69325 - Настройка копирования / не копирования значения поля при клонировании бага
|
||||
if (!$dbh->bz_column_info('fielddefs', 'clone_bug'))
|
||||
{
|
||||
$dbh->bz_add_column('fielddefs', clone_bug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1});
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util qw(trim);
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $bug_objects = Bugzilla->hook_args->{bug_objects};
|
||||
my $vars = Bugzilla->hook_args->{vars};
|
||||
|
||||
my $single = @$bug_objects == 1;
|
||||
my $clear_on_close = $cgi->param('bug_status') eq 'CLOSED' && Bugzilla->params->{clear_requests_on_close};
|
||||
my $verify_flags = $single && Bugzilla->usage_mode != USAGE_MODE_EMAIL && Bugzilla->user->wants_request_reminder;
|
||||
my $reset_own_flags = $verify_flags && $cgi->param('comment') !~ /^\s*$/so;
|
||||
|
||||
if (($clear_on_close || $reset_own_flags) && !$cgi->param('force_flags'))
|
||||
{
|
||||
my $flags;
|
||||
my @requery_flags;
|
||||
my $flag;
|
||||
my $login;
|
||||
# 1) Check flag requests and remind user about resetting his own incoming requests.
|
||||
# 2) When closing bugs, clear all flag requests (CustIS Bug 68430).
|
||||
# Not used in mass update and email modes.
|
||||
for my $bug (@$bug_objects)
|
||||
{
|
||||
if ($single)
|
||||
{
|
||||
for ($cgi->param())
|
||||
{
|
||||
if (/^(flag-(\d+))$/)
|
||||
{
|
||||
$flag = Bugzilla::Flag->new({ id => $2 });
|
||||
$flag->{status} = $cgi->param($1);
|
||||
if (($login = trim($cgi->param("requestee-".$flag->{id}))) &&
|
||||
($login = login_to_id($login)))
|
||||
{
|
||||
$flag->{requestee_id} = $login;
|
||||
}
|
||||
push @$flags, $flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$flags = Bugzilla::Flag->match({ bug_id => $bug->id });
|
||||
}
|
||||
foreach $flag (@$flags)
|
||||
{
|
||||
if ($flag->{status} eq '?' &&
|
||||
($clear_on_close || $flag->{requestee_id} eq Bugzilla->user->id))
|
||||
{
|
||||
if ($clear_on_close)
|
||||
{
|
||||
$flag->{status} = 'X';
|
||||
}
|
||||
if ($verify_flags)
|
||||
{
|
||||
push @requery_flags, $flag;
|
||||
}
|
||||
elsif ($single)
|
||||
{
|
||||
$cgi->param('flag-'.$flag->{id} => 'X');
|
||||
}
|
||||
else
|
||||
{
|
||||
Bugzilla::Flag->set_flag($bug, $flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($verify_flags && @requery_flags)
|
||||
{
|
||||
push @{$vars->{verify_flags}}, @requery_flags;
|
||||
$vars->{field_filter} = '^('.join('|', map { "flag-".$_->id } @{$vars->{verify_flags}}).')$';
|
||||
Bugzilla->template->process("bug/process/verify-flags.html.tmpl", $vars)
|
||||
|| ThrowTemplateError(Bugzilla->template->error());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Util qw(trim);
|
||||
use Bugzilla::Error;
|
||||
|
||||
my $bugs = Bugzilla->hook_args->{bugs};
|
||||
my $st = [];
|
||||
foreach my $bug (@$bugs)
|
||||
{
|
||||
my $old = Bugzilla::Bug->new({ id => $bug->id });
|
||||
my $nok = {};
|
||||
if ($bug->product eq 'TN-RMS' &&
|
||||
$old->{bug_status} ne 'CLOSED' && $bug->{bug_status} eq 'CLOSED')
|
||||
{
|
||||
$nok->{s} = 1 if $bug->{cf_sprint} =~ /^\s*$/so;
|
||||
$nok->{w} = 1 if $bug->{status_whiteboard} =~ /^\s*$/so;
|
||||
$nok->{a} = 1 if $bug->{cf_agreement} =~ /^[\s-]*$/so;
|
||||
}
|
||||
elsif (($bug->product eq 'NewPlatform' && $bug->component ne 'components' && $bug->component ne 'misc' || $bug->product eq 'CIS-Forms') &&
|
||||
$old->{bug_status} ne 'ASSIGNED' && $bug->{bug_status} eq 'ASSIGNED')
|
||||
{
|
||||
$nok->{s} = 1 if $bug->{cf_sprint} =~ /^\s*$/so;
|
||||
}
|
||||
if (%$nok)
|
||||
{
|
||||
$nok->{bug} = $bug;
|
||||
push @$st, $nok;
|
||||
}
|
||||
}
|
||||
if (@$st)
|
||||
{
|
||||
ThrowUserError('rms_fields_empty', { not_ok_bugs => $st });
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
# Интеграция с Wiki для нашей Bugzilla
|
||||
|
||||
use strict;
|
||||
use utf8;
|
||||
use Bugzilla::Util;
|
||||
|
||||
sub url_quote_slash
|
||||
{
|
||||
my ($toencode) = (@_);
|
||||
utf8::encode($toencode) # The below regex works only on bytes
|
||||
if Bugzilla->params->{utf8} && utf8::is_utf8($toencode);
|
||||
$toencode =~ s!([^a-zA-Z0-9_\-./])!uc sprintf("%%%02x",ord($1))!ego;
|
||||
return $toencode;
|
||||
}
|
||||
|
||||
sub processWikiAnchor
|
||||
{
|
||||
my ($anchor) = (@_);
|
||||
return "" unless $anchor;
|
||||
$anchor =~ tr/ /_/;
|
||||
$anchor = url_quote($anchor);
|
||||
$anchor =~ s/%/./gso;
|
||||
return $anchor;
|
||||
}
|
||||
|
||||
sub processWikiUrl
|
||||
{
|
||||
my ($wiki, $url, $anchor) = @_;
|
||||
$url = trim($url);
|
||||
$url =~ s/\s+/ /gso;
|
||||
# обычный url_quote нам не подходит, т.к. / не нужно переделывать в %2F
|
||||
$url = url_quote_slash($url);
|
||||
return Bugzilla->params->{"${wiki}_url"} . $url . '#' . processWikiAnchor($anchor);
|
||||
}
|
||||
|
||||
for my $wiki (qw/wiki smwiki smboa sbwiki fawiki kswiki rdwiki gzwiki dpwiki hrwiki cbwiki gzstable orwiki/)
|
||||
{
|
||||
Bugzilla->hook_args->{custom_proto}->{$wiki} = sub { processWikiUrl($wiki, @_) }
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use FlushViews;
|
||||
|
||||
my $name = Bugzilla->hook_args->{search}->user->login;
|
||||
$name =~ s/\@.*$//so;
|
||||
refresh_views([ $name ]);
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/perl
|
||||
# CustIS-расширения Bugzilla
|
||||
# Разумеется, далеко не все - только часть, отсаженная на Hooks
|
||||
# Раньше (до svn 998) все эти хуки жили в отдельных файлах в extensions/custis/code/*.pl
|
||||
# что было совместимо со стандартной системой хуков Bugzilla, которую убрали в 3.6.
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Hook;
|
||||
use Bugzilla::Extension;
|
||||
|
||||
my $REQUIRED_MODULES = [];
|
||||
my $OPTIONAL_MODULES =
|
||||
[
|
||||
{
|
||||
package => 'Spreadsheet-ParseExcel',
|
||||
module => 'Spreadsheet::ParseExcel',
|
||||
version => '0.54',
|
||||
feature => 'Import of binary Excel files (*.xls)',
|
||||
},
|
||||
{
|
||||
package => 'Spreadsheet-XLSX',
|
||||
module => 'Spreadsheet::XLSX',
|
||||
version => '0.1',
|
||||
feature => 'Import of OOXML Excel files (*.xlsx)',
|
||||
},
|
||||
];
|
||||
|
||||
required_modules('custis', $REQUIRED_MODULES);
|
||||
optional_modules('custis', $OPTIONAL_MODULES);
|
||||
|
||||
# Изменения схемы БД
|
||||
set_hook('custis', 'db_schema_abstract_schema', 'CustisDBHooks::db_schema_abstract_schema');
|
||||
set_hook('custis', 'install_update_db', 'CustisDBHooks::install_update_db');
|
||||
|
||||
# Хуки в показ списка багов
|
||||
set_hook('custis', 'buglist_columns', 'CustisBuglistHooks::buglist_columns');
|
||||
set_hook('custis', 'colchange_columns', 'CustisBuglistHooks::colchange_columns');
|
||||
|
||||
# Хуки в обработку почты
|
||||
set_hook('custis', 'bugmail_pre_template', 'CustisMailHooks::bugmail_pre_template');
|
||||
set_hook('custis', 'bugmail_post_send', 'CustisMailHooks::bugmail_post_send');
|
||||
set_hook('custis', 'flag_notify_pre_template', 'CustisMailHooks::flag_notify_pre_template');
|
||||
set_hook('custis', 'flag_notify_post_send', 'CustisMailHooks::flag_notify_post_send');
|
||||
set_hook('custis', 'emailin_filter_body', 'CustisMailHooks::emailin_filter_body');
|
||||
set_hook('custis', 'emailin_filter_html', 'CustisMailHooks::emailin_filter_html');
|
||||
|
||||
# Хуки для предоставления View'шек в базе для доступа извне
|
||||
set_hook('custis', 'editgroups_post_create', 'FlushViews::refresh_views');
|
||||
set_hook('custis', 'editgroups_post_delete', 'FlushViews::refresh_views');
|
||||
set_hook('custis', 'editgroups_post_edit', 'FlushViews::refresh_views');
|
||||
set_hook('custis', 'editgroups_post_remove_regexp', 'FlushViews::refresh_views');
|
||||
set_hook('custis', 'editusersingroup_post_add', 'FlushViews::refresh_views');
|
||||
set_hook('custis', 'editusers_post_delete', 'FlushViews::refresh_views');
|
||||
set_hook('custis', 'editusers_post_update', 'FlushViews::refresh_views');
|
||||
set_hook('custis', 'savedsearch_post_update', 'FlushViews::savedsearch_post_update');
|
||||
|
||||
# Хуки для синхронизации тест-плана Testopia с Wiki-категорией
|
||||
set_hook('custis', 'tr_show_plan_after_fetch', 'CustisTestPlanSync::tr_show_plan_after_fetch');
|
||||
|
||||
# Хуки для системы проверки корректности изменений багов
|
||||
set_hook('custis', 'bug_pre_update', 'Checkers::bug_pre_update');
|
||||
set_hook('custis', 'bug_end_of_update', 'Checkers::bug_end_of_update');
|
||||
set_hook('custis', 'post_bug_post_create', 'Checkers::post_bug_post_create');
|
||||
set_hook('custis', 'savedsearch_post_update', 'Checkers::savedsearch_post_update');
|
||||
|
||||
# Прочие хуки
|
||||
set_hook('custis', 'auth_post_login', 'CustisMiscHooks::auth_post_login');
|
||||
set_hook('custis', 'flag_check_requestee_list', 'CustisMiscHooks::flag_check_requestee_list');
|
||||
set_hook('custis', 'process_bug_after_move', 'CustisMiscHooks::process_bug_after_move');
|
||||
set_hook('custis', 'quote_urls_custom_proto', 'CustisMiscHooks::quote_urls_custom_proto');
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -0,0 +1,182 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
package Bugzilla::Checker;
|
||||
|
||||
use strict;
|
||||
use base 'Bugzilla::Object';
|
||||
|
||||
use JSON;
|
||||
use Bugzilla::Search::Saved;
|
||||
|
||||
use constant DB_TABLE => 'checkers';
|
||||
|
||||
use constant DB_COLUMNS => (
|
||||
# <Это состояние> задаётся соответствием запросу поиска.
|
||||
'query_id',
|
||||
'user_id',
|
||||
# Два типа:
|
||||
# Check = запрещается переход багов в это состояние из любого другого
|
||||
# ("проверка корректности изменений")
|
||||
# Freeze = запрещается переход багов из этого состояния в любое другое
|
||||
# ("заморозка бага")
|
||||
'is_freeze',
|
||||
'is_fatal',
|
||||
'message',
|
||||
# SQL-код генерировать при каждом изменении бага долго, поэтому кэшируем
|
||||
'sql_code',
|
||||
'except_fields',
|
||||
);
|
||||
use constant NAME_FIELD => 'message';
|
||||
use constant ID_FIELD => 'query_id';
|
||||
use constant LIST_ORDER => NAME_FIELD;
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(query_id message);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
query_id => \&check_query_id,
|
||||
is_fatal => \&Bugzilla::Object::check_boolean,
|
||||
is_freeze => \&Bugzilla::Object::check_boolean,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => (
|
||||
'is_freeze',
|
||||
'is_fatal',
|
||||
'message',
|
||||
'sql_code',
|
||||
'except_fields',
|
||||
);
|
||||
|
||||
# Перепостроение и перекэширование SQL-запроса в базу
|
||||
# от имени суперпользователя (без проверок групп).
|
||||
# На всякий случай из запроса убирается ORDER BY и SELECT ... FROM,
|
||||
# а потом при исполнении приписывается.
|
||||
# Вообще проверка работает дёрганьем SQL-запроса с добавленным условием
|
||||
# на bugs.bug_id=...
|
||||
sub refresh_sql
|
||||
{
|
||||
my $self = shift;
|
||||
my ($query) = @_;
|
||||
if (!$query || $query->id != $self->query_id)
|
||||
{
|
||||
$query = $self->query;
|
||||
}
|
||||
my $params = new Bugzilla::CGI($query->url);
|
||||
$params->delete('bug_id_type', 'bug_id');
|
||||
my $search = new Bugzilla::Search(
|
||||
params => $params,
|
||||
fields => [ 'bug_id' ],
|
||||
user => Bugzilla::User->super_user,
|
||||
);
|
||||
my $sql = $search->getSQL();
|
||||
$sql =~ s/ORDER\s+BY.*?$//iso;
|
||||
$sql =~ s/^\s*SELECT.*?FROM//iso;
|
||||
$self->set_sql_code($sql);
|
||||
}
|
||||
|
||||
# Создание нового предиката - сразу кэшируется SQL-код
|
||||
sub create
|
||||
{
|
||||
my ($class, $params) = @_;
|
||||
Bugzilla::Object::create(@_);
|
||||
my $self = $class->new($params->{query_id});
|
||||
$self->update if $self;
|
||||
return $self;
|
||||
}
|
||||
|
||||
# Обновление - всегда перекэшируется SQL-код
|
||||
sub update
|
||||
{
|
||||
my $self = shift;
|
||||
$self->refresh_sql;
|
||||
$self->SUPER::update(@_);
|
||||
}
|
||||
|
||||
# Проверяем, что такой поиск существует и доступен пользователю
|
||||
sub check_query_id
|
||||
{
|
||||
my ($invocant, $value, $field) = @_;
|
||||
my $q = Bugzilla::Search::Saved->check({ id => $value });
|
||||
# Потенциально мы разрешаем создавать предикаты
|
||||
# на основе расшаренных другими людьми поисков,
|
||||
# но в интерфейсе этого сейчас нет
|
||||
if ($q->user->id != Bugzilla->user->id &&
|
||||
(!$q->shared_with_group || !Bugzilla->user->in_group($q->shared_with_group)))
|
||||
{
|
||||
ThrowUserError('query_access_denied', { query => $q });
|
||||
}
|
||||
# Тоже наша доработка - в сохранённый поиск может быть сохранён просто левый URL
|
||||
if ($q->url =~ /^[a-z][a-z0-9]*:/iso)
|
||||
{
|
||||
ThrowUserError('query_not_savedsearch', { query => $q });
|
||||
}
|
||||
return $q->id;
|
||||
}
|
||||
|
||||
sub query_id { $_[0]->{query_id} }
|
||||
sub user_id { $_[0]->{user_id} }
|
||||
sub is_freeze { $_[0]->{is_freeze} }
|
||||
sub is_fatal { $_[0]->{is_fatal} }
|
||||
sub message { $_[0]->{message} }
|
||||
sub sql_code { $_[0]->{sql_code} }
|
||||
|
||||
# { deny_all => 1|0, except_fields => { field_name => value } }
|
||||
# when value is undef, then the change of field with name=field_name to any value is an exception
|
||||
# when value is not undef, then only the change to value=value is an exception
|
||||
sub except_fields
|
||||
{
|
||||
my $self = shift;
|
||||
if (!exists $self->{except_fields_obj})
|
||||
{
|
||||
$self->{except_fields_obj} = $self->{except_fields} ? decode_json($self->{except_fields}) : undef;
|
||||
}
|
||||
return $self->{except_fields_obj};
|
||||
}
|
||||
|
||||
sub deny_all
|
||||
{
|
||||
my $self = shift;
|
||||
return $self->except_fields ? $self->except_fields->{deny_all} : 1;
|
||||
}
|
||||
|
||||
sub name
|
||||
{
|
||||
my $self = shift;
|
||||
return $self->query->name;
|
||||
}
|
||||
|
||||
sub query
|
||||
{
|
||||
my $self = shift;
|
||||
if (!$self->{query})
|
||||
{
|
||||
$self->{query} = Bugzilla::Search::Saved->new({ id => $self->query_id });
|
||||
}
|
||||
return $self->{query};
|
||||
}
|
||||
|
||||
sub user
|
||||
{
|
||||
my $self = shift;
|
||||
if (!$self->{user})
|
||||
{
|
||||
$self->{user} = Bugzilla::User->new({ id => $self->user_id });
|
||||
}
|
||||
return $self->{user};
|
||||
}
|
||||
|
||||
sub set_query_id { $_[0]->set('query_id', Bugzilla::Search::Saved->check({ id => $_[1] })->id) }
|
||||
sub set_user_id { $_[0]->set('user_id', Bugzilla::User->check({ userid => $_[1] })->id) }
|
||||
sub set_is_freeze { $_[0]->set('is_freeze', $_[1]) }
|
||||
sub set_is_fatal { $_[0]->set('is_fatal', $_[1]) }
|
||||
sub set_message { $_[0]->set('message', $_[1]) }
|
||||
sub set_sql_code { $_[0]->set('sql_code', $_[1]) }
|
||||
|
||||
sub set_except_fields
|
||||
{
|
||||
my ($self, $value) = @_;
|
||||
$self->set('except_fields', encode_json($value));
|
||||
delete $self->{except_fields_obj};
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/perl
|
||||
# CustIS Bug 68921 - "Предикаты проверки корректности"
|
||||
# - Задаётся сохранённый запрос поиска.
|
||||
# - Принимается что баги, соответствующие (или НЕ соответствующие) этому запросу
|
||||
# до (или после) любых изменений - некорректные, и надо выдать предупреждение или ошибку.
|
||||
# - Выставляется флажок, можно ли всё-таки оставлять комментарии без рабочего времени.
|
||||
|
||||
package Checkers;
|
||||
|
||||
use strict;
|
||||
use Bugzilla;
|
||||
use Bugzilla::Checker;
|
||||
use Bugzilla::Search::Saved;
|
||||
use Bugzilla::Error;
|
||||
|
||||
our $THROW_ERROR = 1; # 0 во время массовых изменений
|
||||
|
||||
sub refresh_checker
|
||||
{
|
||||
my ($query) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $chk = Bugzilla::Checker->new($query->id) || return;
|
||||
$chk->update;
|
||||
}
|
||||
|
||||
sub all
|
||||
{
|
||||
my $c = Bugzilla->request_cache;
|
||||
if (!$c->{checkers})
|
||||
{
|
||||
$c->{checkers} = { map { $_->id => $_ } Bugzilla::Checker->get_all };
|
||||
}
|
||||
return $c->{checkers};
|
||||
}
|
||||
|
||||
sub check
|
||||
{
|
||||
my ($bug_id, $is_freeze) = @_;
|
||||
$bug_id = $bug_id->bug_id if ref $bug_id;
|
||||
$bug_id = int($bug_id) || return;
|
||||
my $all = all();
|
||||
my $sql = [];
|
||||
my ($s, $i);
|
||||
for (values %$all)
|
||||
{
|
||||
if (!($is_freeze xor $_->is_freeze))
|
||||
{
|
||||
$s = $_->sql_code;
|
||||
$i = $_->id;
|
||||
$s =~ s/^(.*)(GROUP\s+BY)/SELECT $i query_id FROM $1 AND bugs.bug_id=$bug_id $2/iso;
|
||||
push @$sql, $s;
|
||||
}
|
||||
}
|
||||
@$sql || return [];
|
||||
$sql = "(" . join(") UNION ALL (", @$sql) . ")";
|
||||
my $checked = Bugzilla->dbh->selectcol_arrayref($sql);
|
||||
return [ map { $all->{$_} } @$checked ];
|
||||
}
|
||||
|
||||
sub alert
|
||||
{
|
||||
my ($bug) = @_;
|
||||
if (my @fatals = grep { $_->is_fatal } @{$bug->{failed_checkers}})
|
||||
{
|
||||
# нужно откатить изменения ТОЛЬКО ОДНОГО бага (см. process_bug.cgi)
|
||||
Bugzilla->dbh->bz_rollback_to_savepoint;
|
||||
if ($THROW_ERROR)
|
||||
{
|
||||
ThrowUserError('checkers_failed', { failed => [ $bug ] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub freeze_failed_checkers
|
||||
{
|
||||
my $failedbugs = shift;
|
||||
$failedbugs && @$failedbugs || return undef;
|
||||
return [ map { [ $_->bug_id, [ map { $_->id } @{$_->{failed_checkers}} ] ] } @$failedbugs ];
|
||||
}
|
||||
|
||||
sub unfreeze_failed_checkers
|
||||
{
|
||||
my $freezed = shift;
|
||||
$freezed && @$freezed || return undef;
|
||||
my @r;
|
||||
for (@$freezed)
|
||||
{
|
||||
my ($bug, $cl) = @$_;
|
||||
$bug = Bugzilla::Bug->check($bug);
|
||||
$bug->{failed_checkers} = Bugzilla::Checker->new_from_list($cl);
|
||||
push @r, $bug;
|
||||
}
|
||||
return \@r;
|
||||
}
|
||||
|
||||
# hooks:
|
||||
|
||||
sub bug_pre_update
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $bug = $args->{bug};
|
||||
# запускаем проверки, работающие ДО внесения изменений (заморозка багов)
|
||||
$bug->{failed_checkers} = check($bug->bug_id, 'freeze');
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub bug_end_of_update
|
||||
{
|
||||
my ($args) = @_;
|
||||
|
||||
my $bug = $args->{bug};
|
||||
my $changes = { %{ $args->{changes} } }; # копируем хеш
|
||||
$changes->{longdesc} = $args->{bug}->{added_comments} && @{ $args->{bug}->{added_comments} }
|
||||
? [ '', scalar @{$args->{bug}->{added_comments}} ] : undef;
|
||||
|
||||
# запускаем проверки, работающие ПОСЛЕ внесения изменений
|
||||
push @{$bug->{failed_checkers}}, @{ check($bug->bug_id) };
|
||||
|
||||
# фильтруем подошедшие проверки по изменённым полям
|
||||
my @rc;
|
||||
for (@{$bug->{failed_checkers}})
|
||||
{
|
||||
my $e = $_->except_fields->{except_fields};
|
||||
my $ok = 1;
|
||||
if ($_->deny_all)
|
||||
{
|
||||
# разрешить только изменения полей-исключений только на значения-исключения
|
||||
for (keys %$changes)
|
||||
{
|
||||
# если это поле не перечислено в списке исключений ЛИБО
|
||||
# если в исключениях задано разрешённое новое значение, и у нас не оно
|
||||
if (!exists $e->{$_} || (defined $e->{$_} && $changes->{$_}->[1] ne $e->{$_}))
|
||||
{
|
||||
$ok = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# запретить изменения полей-исключений на значения-исключения
|
||||
for (keys %$e)
|
||||
{
|
||||
if ($changes->{$_} && (!defined $e->{$_} || $changes->{$_}->[1] eq $e->{$_}))
|
||||
{
|
||||
$ok = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
push @rc, $_ unless $ok;
|
||||
}
|
||||
@{$bug->{failed_checkers}} = @rc;
|
||||
|
||||
# ругаемся/откатываем изменения, если что-то есть
|
||||
if (@{$bug->{failed_checkers}})
|
||||
{
|
||||
alert($bug);
|
||||
%{ $args->{changes} } = ();
|
||||
$bug->{added_comments} = undef;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub post_bug_post_create
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $bug = $args->{bug};
|
||||
$bug->{failed_checkers} = check($bug->bug_id);
|
||||
# при создании бага считается, что менялись все поля, поэтому проверки не фильтруем
|
||||
if (@{$bug->{failed_checkers}})
|
||||
{
|
||||
alert($bug);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub savedsearch_post_update
|
||||
{
|
||||
my ($args) = @_;
|
||||
refresh_checker($args->{search});
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/perl
|
||||
# Хуки в список багов
|
||||
|
||||
package CustisBuglistHooks;
|
||||
|
||||
use strict;
|
||||
|
||||
sub buglist_columns
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $columns = $args->{columns};
|
||||
|
||||
$columns->{dependson} = {
|
||||
name => "(SELECT GROUP_CONCAT(bugblockers.dependson SEPARATOR ',') FROM dependencies bugblockers WHERE bugblockers.blocked=bugs.bug_id)",
|
||||
title => "Bug dependencies",
|
||||
};
|
||||
|
||||
$columns->{blocked} = {
|
||||
name => "(SELECT GROUP_CONCAT(bugblocked.blocked SEPARATOR ',') FROM dependencies bugblocked WHERE bugblocked.dependson=bugs.bug_id)",
|
||||
title => "Bugs blocked",
|
||||
};
|
||||
|
||||
$columns->{flags} = {
|
||||
name =>
|
||||
"(SELECT GROUP_CONCAT(CONCAT(col_ft.name,col_f.status) SEPARATOR ', ')
|
||||
FROM flags col_f JOIN flagtypes col_ft ON col_f.type_id=col_ft.id
|
||||
WHERE col_f.bug_id=bugs.bug_id AND (col_ft.is_requesteeble=0 OR col_ft.is_requestable=0))",
|
||||
title => "Flags",
|
||||
};
|
||||
|
||||
$columns->{requests} = {
|
||||
name =>
|
||||
"(SELECT GROUP_CONCAT(CONCAT(col_ft.name,col_f.status,CASE WHEN col_p.login_name IS NULL THEN '' ELSE CONCAT(' ',col_p.login_name) END) SEPARATOR ', ')
|
||||
FROM flags col_f JOIN flagtypes col_ft ON col_f.type_id=col_ft.id
|
||||
LEFT JOIN profiles col_p ON col_f.requestee_id=col_p.userid
|
||||
WHERE col_f.bug_id=bugs.bug_id AND col_ft.is_requesteeble=1 AND col_ft.is_requestable=1)",
|
||||
title => "Requests",
|
||||
};
|
||||
|
||||
# CustIS Bug 68921 (see also Bugzilla::Search)
|
||||
$columns->{interval_time} = $columns->{actual_time};
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub colchange_columns
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $columns = $args->{columns};
|
||||
|
||||
push @$columns, 'dependson', 'blocked';
|
||||
push @$columns, 'flags', 'requests';
|
||||
push @$columns, 'interval_time';
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -0,0 +1,330 @@
|
|||
#!/usr/bin/perl
|
||||
# Хуки для обновлений базы
|
||||
|
||||
package CustisDBHooks;
|
||||
|
||||
use strict;
|
||||
use utf8;
|
||||
use Encode;
|
||||
use URI::Escape;
|
||||
use Bugzilla::Constants;
|
||||
|
||||
# Модификации схемы БД
|
||||
sub db_schema_abstract_schema
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $schema = $args->{schema};
|
||||
|
||||
# FIELD VALUES FOR INCOMING EMAILS
|
||||
# --------------
|
||||
|
||||
$schema->{emailin_fields} = {
|
||||
FIELDS => [
|
||||
address => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
field => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
value => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
emailin_fields_primary => { FIELDS => ['address', 'field'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# ALIASES FOR INCOMING EMAILS
|
||||
# --------------
|
||||
|
||||
$schema->{emailin_aliases} = {
|
||||
FIELDS => [
|
||||
address => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
userid => {TYPE => 'INT3', NOTNULL => 1,
|
||||
REFERENCES => {TABLE => 'profiles',
|
||||
COLUMN => 'userid'}},
|
||||
fromldap => {TYPE => 'BOOLEAN'},
|
||||
isprimary => {TYPE => 'BOOLEAN'},
|
||||
],
|
||||
INDEXES => [
|
||||
emailin_aliases_address => { FIELDS => ['address'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 64562 - надо идти на дом. страницу бага после постановки, а не на post_bug.cgi
|
||||
push @{$schema->{logincookies}->{FIELDS}}, session_data => {TYPE => 'blob'};
|
||||
|
||||
# Ну и зачем авторы убрали этот индекс?
|
||||
# Bug 53687 - Тормозят запросы из Plantime в багзиллу
|
||||
push @{$schema->{longdescs}->{INDEXES}}, { FIELDS => ['who', 'bug_when'] };
|
||||
|
||||
# Bug 13593 - Интеграция с Wiki
|
||||
push @{$schema->{components}->{FIELDS}}, wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"};
|
||||
push @{$schema->{products}->{FIELDS}}, wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"};
|
||||
|
||||
# Bug 59357 - Отключение учёта времени в отдельных продуктах
|
||||
push @{$schema->{products}->{FIELDS}}, notimetracking => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0};
|
||||
|
||||
# Bug 68921 - Связь внутренний/внешний продукт
|
||||
push @{$schema->{products}->{FIELDS}}, extproduct => {TYPE => 'SMALLSERIAL', REFERENCES => {TABLE => 'products', COLUMN => 'id'}};
|
||||
|
||||
# Bug 53725 - Версия по умолчанию
|
||||
push @{$schema->{components}->{FIELDS}}, default_version => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"};
|
||||
|
||||
# Bug 68921 - Закрытие компонента (так же как закрытие продукта), чтобы в него нельзя было ставить новые баги
|
||||
push @{$schema->{components}->{FIELDS}}, is_active => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1};
|
||||
|
||||
# Bug 53617 - Ограничение Custom Fields двумя и более значениями контролирующего поля
|
||||
$schema->{fieldvaluecontrol} = {
|
||||
FIELDS => [
|
||||
field_id => {TYPE => 'INT3', NOTNULL => 1},
|
||||
value_id => {TYPE => 'INT2', NOTNULL => 1},
|
||||
visibility_value_id => {TYPE => 'INT2', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
fieldvaluecontrol_primary_idx =>
|
||||
{FIELDS => ['field_id', 'visibility_value_id', 'value_id'],
|
||||
TYPE => 'UNIQUE'},
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 45485 - Scrum-карточки из Bugzilla
|
||||
$schema->{scrum_cards} = {
|
||||
FIELDS => [
|
||||
bug_id => {TYPE => 'INT3', NOTNULL => 1},
|
||||
sprint => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
type => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
estimate => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
scrum_cards_primary_idx => { FIELDS => ['bug_id', 'sprint', 'type'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 63447 - Глобальная авторизация
|
||||
$schema->{globalauth} = {
|
||||
FIELDS => [
|
||||
id => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
secret => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
expire => {TYPE => 'bigint', NOTNULL => 1},
|
||||
],
|
||||
INDEXES => [
|
||||
globalauth_primary_idx => { FIELDS => ['id'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
|
||||
# Bug 69325 - Настройка копирования / не копирования значения поля при клонировании бага
|
||||
push @{$schema->{fielddefs}->{FIELDS}}, clone_bug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1};
|
||||
|
||||
# Bug 68921 - Предикаты корректности из запросов поиска
|
||||
$schema->{checkers} = {
|
||||
FIELDS => [
|
||||
query_id => {TYPE => 'INT3', NOTNULL => 1, REFERENCES => {TABLE => 'namedqueries', COLUMN => 'id'}},
|
||||
user_id => {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles', COLUMN => 'userid'}},
|
||||
is_freeze => {TYPE => 'BOOLEAN'},
|
||||
is_fatal => {TYPE => 'BOOLEAN'},
|
||||
message => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
sql_code => {TYPE => 'LONGTEXT'},
|
||||
except_fields => {TYPE => 'BLOB'},
|
||||
],
|
||||
INDEXES => [
|
||||
checkers_primary_idx => { FIELDS => ['query_id'], TYPE => 'UNIQUE' },
|
||||
],
|
||||
};
|
||||
return 1;
|
||||
}
|
||||
|
||||
# ./checksetup'овые обновления базы
|
||||
sub install_update_db
|
||||
{
|
||||
my ($args) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Перенос CC по умолчанию из нашей доработки initialcclist в нормальный механизм component_cc
|
||||
my $ccour = $dbh->bz_column_info('components', 'initialcclist');
|
||||
unless ($ccour)
|
||||
{
|
||||
$ccour = $dbh->selectall_arrayref("DESC components") || [];
|
||||
$ccour = { map { ($_->[0] => 1) } @$ccour };
|
||||
$ccour = $ccour->{initialcclist} ? 1 : undef;
|
||||
}
|
||||
|
||||
if ($ccour)
|
||||
{
|
||||
print "Migrating initialcclist to component_cc...\n";
|
||||
my $cc = $dbh->selectall_arrayref("SELECT id, initialcclist FROM components") || [];
|
||||
$dbh->do("CREATE TABLE IF NOT EXISTS old_component_initialcc (id smallint(6) not null auto_increment primary key, initialcclist tinytext not null)");
|
||||
my $ins = $dbh->prepare("REPLACE INTO old_component_initialcc (id, initialcclist) VALUES (?, ?)");
|
||||
my $addcc = $dbh->prepare("REPLACE INTO component_cc (user_id, component_id) VALUES (?, ?)");
|
||||
my ($user, $uid, $list);
|
||||
my $added = [];
|
||||
foreach (@$cc)
|
||||
{
|
||||
$ins->execute(@$_);
|
||||
if ($list = $_->[1])
|
||||
{
|
||||
$list = [ split /[\s,]+/, $list ];
|
||||
for $user (@$list)
|
||||
{
|
||||
$user =~ s/^\s+|\s+$//so;
|
||||
($uid) = $dbh->selectrow_array("SELECT userid FROM profiles WHERE login_name=?", undef, $user);
|
||||
unless ($uid)
|
||||
{
|
||||
print " ERROR: unknown default CC for component $_->[0]: '$user'\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
push @$added, [ $uid, $_->[0] ];
|
||||
$addcc->execute($uid, $_->[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($dbh->bz_column_info('components', 'initialcclist'))
|
||||
{
|
||||
$dbh->bz_drop_column('components', 'initialcclist');
|
||||
}
|
||||
else
|
||||
{
|
||||
$dbh->do("ALTER TABLE components DROP initialcclist");
|
||||
}
|
||||
print "Successfully migrated ".scalar(@$added)." initial CC definitions\n";
|
||||
print " (old data backed up in old_component_initialcc table)\n";
|
||||
}
|
||||
|
||||
# Перекодировка параметров сохранённых поисков из CP-1251 в UTF-8
|
||||
print "Making sure saved queries are in UTF-8...\n";
|
||||
my $nq = $dbh->selectall_arrayref("SELECT * FROM namedqueries WHERE query LIKE '%\\%%'", {Slice=>{}});
|
||||
if ($nq)
|
||||
{
|
||||
my $q;
|
||||
foreach (@$nq)
|
||||
{
|
||||
$q = $_->{query};
|
||||
$q =~ s/(\%[0-9A-F]{2})+/_sure_utf8($&)/iegso;
|
||||
$dbh->do("UPDATE namedqueries SET query=? WHERE id=?", undef, $q, $_->{id}) if $q ne $_->{query};
|
||||
}
|
||||
}
|
||||
|
||||
# Bug 13593 - Интеграция с Wiki
|
||||
if (!$dbh->bz_column_info('products', 'buglist'))
|
||||
{
|
||||
# Добавляем колонку wiki_url в продукты
|
||||
$dbh->bz_add_column('products', wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
if (!$dbh->bz_column_info('components', 'buglist'))
|
||||
{
|
||||
# Добавляем колонку wiki_url в компоненты
|
||||
$dbh->bz_add_column('components', wiki_url => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
|
||||
# Bug 59357 - Отключение учёта времени в отдельных продуктах
|
||||
if (!$dbh->bz_column_info('products', 'notimetracking'))
|
||||
{
|
||||
# Добавляем колонку notimetracking в продукты
|
||||
$dbh->bz_add_column('products', notimetracking => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
|
||||
}
|
||||
|
||||
# Bug 68921 - Связь внешний/внутренний продукт
|
||||
if (!$dbh->bz_column_info('products', 'extproduct'))
|
||||
{
|
||||
# Добавляем колонку extproduct в продукты
|
||||
$dbh->bz_add_column('products', extproduct => {TYPE => 'INT2', REFERENCES => {TABLE => 'products', COLUMN => 'id'}});
|
||||
}
|
||||
|
||||
# Bug 53725 - Версия по умолчанию
|
||||
if (!$dbh->bz_column_info('components', 'default_version'))
|
||||
{
|
||||
$dbh->bz_add_column('components', default_version => {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
|
||||
# Bug 68921 - Закрытие компонента (так же как закрытие продукта), чтобы в него нельзя было ставить новые баги
|
||||
if (!$dbh->bz_column_info('components', 'is_active'))
|
||||
{
|
||||
$dbh->bz_add_column('components', is_active => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1});
|
||||
}
|
||||
|
||||
# Bug 53617 - Ограничение Custom Fields двумя и более значениями контролирующего поля
|
||||
my @standard_fields = qw(bug_status resolution priority bug_severity op_sys rep_platform);
|
||||
my $custom_fields = $dbh->selectall_arrayref(
|
||||
'SELECT * FROM fielddefs WHERE (custom=1 AND type IN (?,?)) OR name IN ('.
|
||||
join(',',('?') x @standard_fields).')', {Slice=>{}},
|
||||
FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT, @standard_fields);
|
||||
foreach my $field (@$custom_fields)
|
||||
{
|
||||
if ($dbh->bz_table_info($field->{name}) &&
|
||||
$dbh->bz_column_info($field->{name}, 'visibility_value_id'))
|
||||
{
|
||||
print "Migrating $field->{name}'s visibility_value_id into fieldvaluecontrol\n";
|
||||
$dbh->do(
|
||||
"REPLACE INTO fieldvaluecontrol (field_id, visibility_value_id, value_id)".
|
||||
" SELECT f.id, v.visibility_value_id, v.id FROM fielddefs f, `$field->{name}` v".
|
||||
" WHERE f.name=? AND v.visibility_value_id IS NOT NULL", undef, $field->{name});
|
||||
print "Making backup of table $field->{name}\n";
|
||||
$dbh->do("CREATE TABLE `backup_$field->{name}_".time."` AS SELECT * FROM `$field->{name}`");
|
||||
print "Dropping column $field->{name}.visibility_value_id\n";
|
||||
$dbh->bz_drop_column($field->{name}, 'visibility_value_id');
|
||||
}
|
||||
}
|
||||
|
||||
if ($dbh->bz_column_info('fielddefs', 'visibility_value_id'))
|
||||
{
|
||||
print "Migrating fielddefs's visibility_value_id into fieldvaluecontrol\n";
|
||||
$dbh->do(
|
||||
"REPLACE INTO fieldvaluecontrol (field_id, visibility_value_id, value_id)".
|
||||
" SELECT id, visibility_value_id, 0 FROM fielddefs WHERE visibility_value_id IS NOT NULL");
|
||||
print "Making backup of table fielddefs\n";
|
||||
$dbh->do("CREATE TABLE `backup_fielddefs_".time."` AS SELECT * FROM fielddefs");
|
||||
print "Dropping column fielddefs.visibility_value_id\n";
|
||||
$dbh->bz_drop_column('fielddefs', 'visibility_value_id');
|
||||
}
|
||||
|
||||
# Testopia:
|
||||
if ($dbh->bz_table_info('test_fielddefs'))
|
||||
{
|
||||
# Bug 53254 - Интеграция плана с MediaWiki
|
||||
unless ($dbh->bz_column_info('test_plans', 'wiki'))
|
||||
{
|
||||
$dbh->bz_add_column('test_plans', wiki => {TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
|
||||
}
|
||||
unless ($dbh->selectrow_array("SELECT name FROM test_fielddefs WHERE table_name='test_plans' AND name='wiki'"))
|
||||
{
|
||||
$dbh->do("INSERT INTO test_fielddefs (name, description, table_name) VALUES ('wiki', 'Wiki Category', 'test_plans')");
|
||||
}
|
||||
}
|
||||
|
||||
# Bug 64562 - надо идти на дом. страницу бага после постановки, а не на post_bug.cgi
|
||||
if (!$dbh->bz_column_info('logincookies', 'session_data'))
|
||||
{
|
||||
$dbh->bz_add_column('logincookies', session_data => {TYPE => 'blob'});
|
||||
}
|
||||
|
||||
# Bug 69766 - Default CSV charset for M1cr0$0ft Excel
|
||||
if (!$dbh->selectrow_array('SELECT name FROM setting WHERE name=\'csv_charset\' LIMIT 1'))
|
||||
{
|
||||
$dbh->do('INSERT INTO setting (name, default_value, is_enabled) VALUES (\'csv_charset\', \'utf-8\', 1)');
|
||||
}
|
||||
if (!$dbh->selectrow_array('SELECT name FROM setting_value WHERE name=\'csv_charset\' LIMIT 1'))
|
||||
{
|
||||
$dbh->do('INSERT INTO setting_value (name, value, sortindex) VALUES (\'csv_charset\', \'utf-8\', 10), (\'csv_charset\', \'windows-1251\', 20), (\'csv_charset\', \'koi8-r\', 30)');
|
||||
}
|
||||
|
||||
# Bug 69325 - Настройка копирования / не копирования значения поля при клонировании бага
|
||||
if (!$dbh->bz_column_info('fielddefs', 'clone_bug'))
|
||||
{
|
||||
$dbh->bz_add_column('fielddefs', clone_bug => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1});
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _sure_utf8
|
||||
{
|
||||
my ($s) = @_;
|
||||
$s = uri_unescape($s);
|
||||
Encode::_utf8_on($s);
|
||||
my $v = utf8::valid($s);
|
||||
Encode::_utf8_off($s);
|
||||
Encode::from_to($s, 'cp1251', 'utf8') unless $v;
|
||||
$s = uri_escape($s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/perl
|
||||
# Хуки во всевозможную обработку почты
|
||||
|
||||
package CustisMailHooks;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use CustisLocalBugzillas;
|
||||
use Bugzilla::Util;
|
||||
use POSIX qw(strftime);
|
||||
|
||||
# Hack into urlbase and set it to be correct for email recipient
|
||||
sub bugmail_pre_template
|
||||
{
|
||||
my ($args) = @_;
|
||||
|
||||
my $vars = $args->{vars};
|
||||
${$args->{tmpl}} = 'email/newchangedmail-'.$vars->{product}.'.txt.tmpl';
|
||||
|
||||
my $datadir = bz_locations()->{datadir};
|
||||
my $fd;
|
||||
if (-w "$datadir/maillog" && open $fd, ">>$datadir/maillog")
|
||||
{
|
||||
my $s = [ strftime("%Y-%m-%d %H:%M:%S: ", localtime) . ($vars->{isnew} ? "" : "Re: ") . "Bug #$vars->{bugid} mail to $vars->{to}" ];
|
||||
if ($vars->{new_comments} && @{$vars->{new_comments}})
|
||||
{
|
||||
push @$s, scalar(@{$vars->{new_comments}}) . ' comment(s) (#' . (join ',', map { $_->{count} } @{$vars->{new_comments}}) . ')';
|
||||
}
|
||||
if ($vars->{diffarray} && @{$vars->{diffarray}})
|
||||
{
|
||||
push @$s, scalar(grep { $_->{type} eq 'change' } @{$vars->{diffarray}}) . ' diffs';
|
||||
}
|
||||
$s = join "; ", @$s;
|
||||
print $fd $s, "\n";
|
||||
close $fd;
|
||||
}
|
||||
|
||||
CustisLocalBugzillas::HackIntoUrlbase($vars->{to});
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Unhack urlbase :-)
|
||||
sub bugmail_post_send
|
||||
{
|
||||
my ($args) = @_;
|
||||
CustisLocalBugzillas::HackIntoUrlbase(undef);
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Hack into urlbase and set it to be correct for email recipient
|
||||
sub flag_notify_pre_template
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $vars = $args->{vars};
|
||||
CustisLocalBugzillas::HackIntoUrlbase($vars->{to});
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Unhack urlbase :-)
|
||||
sub flag_notify_post_send
|
||||
{
|
||||
my ($args) = @_;
|
||||
CustisLocalBugzillas::HackIntoUrlbase(undef);
|
||||
return 1;
|
||||
}
|
||||
|
||||
##
|
||||
## Обработка исходящей почты:
|
||||
##
|
||||
|
||||
sub emailin_filter_body
|
||||
{
|
||||
my ($args) = @_;
|
||||
|
||||
for (${$args->{body}})
|
||||
{
|
||||
if (/From:\s+bugzilla-daemon(\s*[a-z0-9_\-]+\s*:.*?\n)*\s*Bug\s*\d+<[^>]*>\s*\([^\)]*\)\s*/iso)
|
||||
{
|
||||
my ($pr, $ps) = ($`, $');
|
||||
$ps =~ s/\n+(\r*\n+)+/\n/giso;
|
||||
$_ = $pr . $ps;
|
||||
s!from\s+.*?<http://plantime[^>]*search=([^>]*)>!from $1!giso;
|
||||
s!((Comment|Bug)\s+\#?\d+)<[^<>]*>!$1!giso;
|
||||
s!\n[^\n]*<http://plantime[^>]*search=[^>]*>\s+changed:[ \t\r]*\n.*?$!!iso;
|
||||
s/\s*\n--\s*Configure\s*bugmail<[^>]*>(([ \t\r]*\n[^\n]*)*)//iso;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub emailin_filter_html
|
||||
{
|
||||
my ($args) = @_;
|
||||
|
||||
for (${$args->{body}})
|
||||
{
|
||||
s/<table[^<>]*class=[\"\']?difft[^<>]*>.*?<\/table\s*>//giso;
|
||||
s/<a[^<>]*>.*?<\/a\s*>/_custis_rmlf($&)/gieso;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _custis_rmlf
|
||||
{
|
||||
my ($t) = @_;
|
||||
$t =~ s/[\n\r]+/ /giso;
|
||||
return $t;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/perl
|
||||
# Прочие хуки
|
||||
|
||||
package CustisMiscHooks;
|
||||
|
||||
use strict;
|
||||
use utf8;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use CustisLocalBugzillas;
|
||||
|
||||
# Перенаправление в "свою" багзиллу для внешних/внутренних сотрудников
|
||||
sub auth_post_login
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $user = $args->{user};
|
||||
if ($user->settings->{redirect_me_to_my_bugzilla} &&
|
||||
lc($user->settings->{redirect_me_to_my_bugzilla}->{value}) eq "on")
|
||||
{
|
||||
my $loc = \%CustisLocalBugzillas::local_urlbase;
|
||||
my $fullurl = Bugzilla->cgi->url();
|
||||
foreach my $regemail (keys %$loc)
|
||||
{
|
||||
if ($user->login =~ /$regemail/s &&
|
||||
$fullurl !~ /\Q$loc->{$regemail}->{urlbase}\E/s)
|
||||
{
|
||||
my $relativeurl = Bugzilla->cgi->url(
|
||||
-path_info => 1,
|
||||
-query => 1,
|
||||
-relative => 1
|
||||
);
|
||||
my $url = $loc->{$regemail}->{urlbase} . $relativeurl;
|
||||
print Bugzilla->cgi->redirect(-location => $url);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Раскрытие групповых пользователей в запросе флага
|
||||
sub flag_check_requestee_list
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $requestees = $args->{requestees};
|
||||
if (@$requestees)
|
||||
{
|
||||
my $group_users = Bugzilla->dbh->selectall_arrayref(
|
||||
'SELECT watcher.*, watched.login_name group_user FROM profiles watcher, watch, profiles watched WHERE watcher.userid=watch.watcher AND watched.userid=watch.watched AND watched.login_name IN ('.
|
||||
join(',', ('?') x @$requestees).') AND watched.disable_mail AND watched.realname LIKE "Группа%"', {Slice=>{}}, @$requestees
|
||||
);
|
||||
my %del = map { ($_->{group_user} => 1) } @$group_users;
|
||||
@$requestees = ((grep { !$del{$_} } @$requestees), (map { $_->{login_name} } @$group_users));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Напоминания о несброшенных запросах флагов
|
||||
sub process_bug_after_move
|
||||
{
|
||||
my ($args) = @_;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $bug_objects = $args->{bug_objects};
|
||||
my $vars = $args->{vars};
|
||||
|
||||
my $single = @$bug_objects == 1;
|
||||
my $clear_on_close = $cgi->param('bug_status') eq 'CLOSED' && Bugzilla->params->{clear_requests_on_close};
|
||||
my $verify_flags = $single && Bugzilla->usage_mode != USAGE_MODE_EMAIL && Bugzilla->user->wants_request_reminder;
|
||||
my $reset_own_flags = $verify_flags && $cgi->param('comment') !~ /^\s*$/so;
|
||||
|
||||
if (($clear_on_close || $reset_own_flags) && !$cgi->param('force_flags'))
|
||||
{
|
||||
my $flags;
|
||||
my @requery_flags;
|
||||
my $flag;
|
||||
my $login;
|
||||
# 1) Check flag requests and remind user about resetting his own incoming requests.
|
||||
# 2) When closing bugs, clear all flag requests (CustIS Bug 68430).
|
||||
# Not used in mass update and email modes.
|
||||
for my $bug (@$bug_objects)
|
||||
{
|
||||
if ($single)
|
||||
{
|
||||
for ($cgi->param())
|
||||
{
|
||||
if (/^(flag-(\d+))$/)
|
||||
{
|
||||
$flag = Bugzilla::Flag->new({ id => $2 });
|
||||
$flag->{status} = $cgi->param($1);
|
||||
if (($login = trim($cgi->param("requestee-".$flag->{id}))) &&
|
||||
($login = login_to_id($login)))
|
||||
{
|
||||
$flag->{requestee_id} = $login;
|
||||
}
|
||||
push @$flags, $flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$flags = Bugzilla::Flag->match({ bug_id => $bug->id });
|
||||
}
|
||||
foreach $flag (@$flags)
|
||||
{
|
||||
if ($flag->{status} eq '?' &&
|
||||
($clear_on_close || $flag->{requestee_id} eq Bugzilla->user->id))
|
||||
{
|
||||
if ($clear_on_close)
|
||||
{
|
||||
$flag->{status} = 'X';
|
||||
}
|
||||
if ($verify_flags)
|
||||
{
|
||||
push @requery_flags, $flag;
|
||||
}
|
||||
elsif ($single)
|
||||
{
|
||||
$cgi->param('flag-'.$flag->{id} => 'X');
|
||||
}
|
||||
else
|
||||
{
|
||||
Bugzilla::Flag->set_flag($bug, $flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($verify_flags && @requery_flags)
|
||||
{
|
||||
push @{$vars->{verify_flags}}, @requery_flags;
|
||||
$vars->{field_filter} = '^('.join('|', map { "flag-".$_->id } @{$vars->{verify_flags}}).')$';
|
||||
Bugzilla->template->process("bug/process/verify-flags.html.tmpl", $vars)
|
||||
|| ThrowTemplateError(Bugzilla->template->error());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Интеграция с локальными Wiki-системами для нашей Bugzilla
|
||||
sub quote_urls_custom_proto
|
||||
{
|
||||
my ($args) = @_;
|
||||
for my $wiki (qw/wiki smwiki smboa sbwiki fawiki kswiki rdwiki gzwiki dpwiki hrwiki cbwiki gzstable orwiki/)
|
||||
{
|
||||
$args->{custom_proto}->{$wiki} = sub { processWikiUrl($wiki, @_) }
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
##
|
||||
## НЕ-хуки:
|
||||
##
|
||||
|
||||
sub url_quote_slash
|
||||
{
|
||||
my ($toencode) = (@_);
|
||||
utf8::encode($toencode) # The below regex works only on bytes
|
||||
if Bugzilla->params->{utf8} && utf8::is_utf8($toencode);
|
||||
$toencode =~ s!([^a-zA-Z0-9_\-./])!uc sprintf("%%%02x",ord($1))!ego;
|
||||
return $toencode;
|
||||
}
|
||||
|
||||
sub processWikiAnchor
|
||||
{
|
||||
my ($anchor) = (@_);
|
||||
return "" unless $anchor;
|
||||
$anchor =~ tr/ /_/;
|
||||
$anchor = url_quote($anchor);
|
||||
$anchor =~ s/%/./gso;
|
||||
return $anchor;
|
||||
}
|
||||
|
||||
sub processWikiUrl
|
||||
{
|
||||
my ($wiki, $url, $anchor) = @_;
|
||||
$url = trim($url);
|
||||
$url =~ s/\s+/ /gso;
|
||||
# обычный url_quote нам не подходит, т.к. / не нужно переделывать в %2F
|
||||
$url = url_quote_slash($url);
|
||||
return Bugzilla->params->{"${wiki}_url"} . $url . '#' . processWikiAnchor($anchor);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -1,6 +1,8 @@
|
|||
#!/usr/bin/perl
|
||||
# Bug 53254 - Синхронизация тест-плана с категорией MediaWiki
|
||||
|
||||
package CustisTestPlanSync;
|
||||
|
||||
use utf8;
|
||||
use strict;
|
||||
use Bugzilla::Util;
|
||||
|
@ -15,25 +17,29 @@ use HTML::Entities;
|
|||
use HTTP::Request::Common;
|
||||
use LWP::Simple qw($ua);
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $plan = Bugzilla->hook_args->{plan};
|
||||
my $vars = Bugzilla->hook_args->{vars};
|
||||
|
||||
# Синхронизация по /tr_show_plan.cgi?wikisync=1
|
||||
if ($cgi->param('wikisync'))
|
||||
# Hook
|
||||
sub tr_show_plan_after_fetch
|
||||
{
|
||||
my $wiki_url = $plan->product->wiki_url || Bugzilla->params->{wiki_url};
|
||||
if ($wiki_url && $plan->wiki)
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $plan = $args->{plan};
|
||||
my $vars = $args->{vars};
|
||||
|
||||
# Синхронизация по /tr_show_plan.cgi?wikisync=1
|
||||
if ($cgi->param('wikisync'))
|
||||
{
|
||||
my $xml = fetch_wiki_category_xml($wiki_url, $plan->wiki);
|
||||
my $p = XML::Parser->new(Handlers => {
|
||||
Start => \&wiki_sync_handle_start,
|
||||
End => \&wiki_sync_handle_end,
|
||||
Char => \&wiki_sync_handle_char,
|
||||
});
|
||||
$p->{_ws_wiki_url} = $wiki_url;
|
||||
$p->{_ws_plan} = $plan;
|
||||
$p->parse($xml);
|
||||
my $wiki_url = $plan->product->wiki_url || Bugzilla->params->{wiki_url};
|
||||
if ($wiki_url && $plan->wiki)
|
||||
{
|
||||
my $xml = fetch_wiki_category_xml($wiki_url, $plan->wiki);
|
||||
my $p = XML::Parser->new(Handlers => {
|
||||
Start => \&wiki_sync_handle_start,
|
||||
End => \&wiki_sync_handle_end,
|
||||
Char => \&wiki_sync_handle_char,
|
||||
});
|
||||
$p->{_ws_wiki_url} = $wiki_url;
|
||||
$p->{_ws_plan} = $plan;
|
||||
$p->parse($xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,3 +202,6 @@ sub fetch_wiki_category_xml
|
|||
}
|
||||
return $xml;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/perl
|
||||
# CustIS Bug 61728 - external SQL interface to Bugzilla's bug tables
|
||||
|
||||
package FlushViews;
|
||||
|
||||
|
@ -10,7 +11,6 @@ use Bugzilla::Search;
|
|||
our @ISA = qw(Exporter);
|
||||
our @EXPORT = qw(refresh_views);
|
||||
|
||||
# CustIS Bug 61728 - external SQL interface to Bugzilla's bugs and longdescs tables
|
||||
sub refresh_views
|
||||
{
|
||||
my ($users) = @_;
|
||||
|
@ -46,5 +46,15 @@ sub refresh_views
|
|||
}
|
||||
}
|
||||
|
||||
# hooks:
|
||||
sub savedsearch_post_update
|
||||
{
|
||||
my ($args) = @_;
|
||||
my $name = $args->{search}->user->login;
|
||||
$name =~ s/\@.*$//so;
|
||||
refresh_views([ $name ]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
[% PROCESS global/header.html.tmpl
|
||||
title = "Правка предикатов корректности изменений"
|
||||
%]
|
||||
|
||||
[% IF mode == "list" %]
|
||||
<h3>Редактирование предикатов корректности</h3>
|
||||
[% IF checkers.size %]
|
||||
<p>Список определённых предикатов корректности:</p>
|
||||
<dl>
|
||||
[% FOR c = checkers %]
|
||||
<dt>
|
||||
[% c.name | html %]:
|
||||
<a href="?mode=edit&query_id=[% c.query_id %]">править</a>,
|
||||
<a href="javascript:void(0)" onclick="if(confirm('Действительно удалить эту проверку?')){window.location.href='?save=1&delete=1&query_id=[% c.query_id %]';}">удалить</a>
|
||||
</dt>
|
||||
<dd>
|
||||
[% c.message | html %]
|
||||
([% c.is_fatal ? "обязательная" : "рекомендательная" %]
|
||||
[%+ IF c.is_freeze %]защита от изменений[% ELSE %]проверка новых значений[% END %]).
|
||||
</dd>
|
||||
[% END %]
|
||||
</dl>
|
||||
[% ELSE %]
|
||||
<p>Ещё не определено ни одного предиката корректности.</p>
|
||||
[% END %]
|
||||
<p><a href="?mode=edit&create=1">Добавить новый предикат.</a></p>
|
||||
[% ELSE %]
|
||||
[% IF create %]
|
||||
<h3>Добавление нового предиката</h3>
|
||||
[% ELSE %]
|
||||
<h3>Редактирование предиката [% checker.name | html %]</h3>
|
||||
[% END %]
|
||||
<form action="?save=1&edit=1" method="POST">
|
||||
<input type="hidden" name="token" value="[% token | html %]" />
|
||||
<table>
|
||||
[% IF create %]
|
||||
<input type="hidden" name="create" value="1" />
|
||||
<tr>
|
||||
<th>Сохранённый запрос:</th>
|
||||
<td><select name="query_id">
|
||||
[% FOREACH q = user.queries %]
|
||||
<option value="[% q.id %]" [% " selected='selected'" IF checker.query_id == q.id %] >[% q.name | html %]</option>
|
||||
[% END %]
|
||||
</select></td>
|
||||
</tr>
|
||||
[% ELSE %]
|
||||
<input type="hidden" name="query_id" value="[% checker.query_id %]" />
|
||||
[% END %]
|
||||
<tr>
|
||||
<th>Сообщение об ошибке:</th>
|
||||
<td><textarea name="message" rows="8" cols="80">[% checker.message | html %]</textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Параметры:</th>
|
||||
<td>
|
||||
<input type="checkbox" name="is_freeze" id="is_freeze" [% " checked='checked'" IF checker.is_freeze %] />
|
||||
<label for="is_freeze">Режим заморозки багов (если нет, то режим проверки новых состояний)</label><br />
|
||||
<input type="checkbox" name="is_fatal" id="is_fatal" [% " checked='checked'" IF checker.is_fatal %] />
|
||||
<label for="is_fatal">Запрещать изменения? (если нет, только даётся предупреждение)</label><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="background: #FFE0E0">Запрещать:</th>
|
||||
<td style="background: #FFE0E0">
|
||||
<input type="checkbox" name="deny_all" id="deny_all" onclick="this.blur(); return true;" onblur="showhide_allowdeny(this.checked)" [% " checked='checked'" IF checker.deny_all %] />
|
||||
<label for="deny_all">Запрещать изменения всех полей</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="except_fields_tr" style="background: #FFE0E0">
|
||||
<th id="except_fields_title"></th>
|
||||
<td id="except_fields">
|
||||
<a href="javascript:void(0)" onclick="add_field()">Добавить отдельное поле</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td></td><td><input type="submit" value=" Сохранить " /></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
<div id="one_field_copy" style="display:none">
|
||||
поле: <select name="except_field_X" id="except_field_X">
|
||||
<option value="">---</option>
|
||||
[% FOR f = my_fielddefs %]
|
||||
<option value="[% f.name | html %]">[% f.description | html %]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
новое значение: <input type="text" name="except_field_X_value" id="except_field_X_value" value="" /> (пусто=любое)
|
||||
</div>
|
||||
<script language="JavaScript">
|
||||
var fieldids = { '':'' [% FOR f = my_fielddefs %],"[% f.name | js %]": [% loop.count %][% END %] };
|
||||
var except_field_index = 0;
|
||||
function showhide_allowdeny(chk)
|
||||
{
|
||||
document.getElementById('except_fields_title').innerHTML = chk ? 'Но разрешать:' : '';
|
||||
document.getElementById('except_fields_tr').style.backgroundColor = chk ? '#E0FFE0' : '#FFE0E0';
|
||||
}
|
||||
function add_field(fld, val)
|
||||
{
|
||||
var d = document.createElement('div');
|
||||
d.innerHTML = document.getElementById('one_field_copy').innerHTML.replace(/except_field_X/g, 'except_field_'+except_field_index);
|
||||
document.getElementById('except_fields').appendChild(d);
|
||||
if (fld && fieldids[fld])
|
||||
{
|
||||
document.getElementById('except_field_'+except_field_index).selectedIndex = fieldids[fld];
|
||||
if (val)
|
||||
document.getElementById('except_field_'+except_field_index+'_value').value = val;
|
||||
}
|
||||
except_field_index++;
|
||||
}
|
||||
[%# Загружаем текущее состояние дел %]
|
||||
showhide_allowdeny(document.getElementById('deny_all').checked);
|
||||
[% IF checker.except_fields.except_fields %]
|
||||
[% FOR f = checker.except_fields.except_fields.keys %]
|
||||
add_field("[% f | js %]", "[% checker.except_fields.except_fields.$f | js %]");
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
add_field();
|
||||
[% END %]
|
||||
</script>
|
||||
[% END %]
|
||||
|
||||
[%# FIXME: перенести это в wiki. Тема: интеграция справки Bugzilla с Wiki %]
|
||||
<hr />
|
||||
|
||||
<div style="border:5px solid #00A000">
|
||||
<div style="float:left;font-size:200%; border-bottom:5px solid #00A000; border-right: 5px solid #00A000;color:#00A000;background-color:white;padding: 5px; margin: 0 5px 0 0">?</div>
|
||||
<div style="margin:5px">
|
||||
|
||||
<b>Предикаты корректности</b> — обычные сохранённые запросы поиска Bugzilla,
|
||||
служащие для проверки различных правил изменений багов.
|
||||
|
||||
<p>Доступно два режима проверки:</p>
|
||||
|
||||
<p><b>Проверка состояния:</b><br />
|
||||
При каждом изменении каждого бага система проверяет, соответствует ли его новое состояние
|
||||
(новые значения всех полей) сохранённому запросу поиска, и если да, то выдаёт предупреждение
|
||||
или ошибку с заданным текстом.</p>
|
||||
|
||||
<p><b>Заморозка багов:</b><br />
|
||||
При каждом изменении каждого бага система проверяет, соответствует ли его <i>старое</i> состояние
|
||||
сохранённому запросу поиска, и если да, то выдаёт предупреждение или ошибку с заданным текстом.
|
||||
Опционально разрешается оставлять комментарии без списывания рабочего времени к таким багам.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
[% PROCESS global/footer.html.tmpl %]
|
|
@ -0,0 +1,25 @@
|
|||
[%# Интерфейс: f = массив багов с заполненным полем failed_checkers = массиву Bugzilla::Checker %]
|
||||
[% lastbug = "" %]
|
||||
<p style="margin-top: 0">
|
||||
Внесённые
|
||||
[% IF f.size == 1 %]
|
||||
в <a href="show_bug.cgi?id=[% f.0.bug_id %]">[% terms.Bug %] [%+ f.0.bug_id %]</a>
|
||||
[% END %]
|
||||
изменения не удовлетворяют следующим проверкам:
|
||||
</p>
|
||||
[% FOR bug = f %]
|
||||
[% IF f.size > 1 %]
|
||||
<p><a href="show_bug.cgi?id=[% bug.bug_id %]">[% terms.Bug %] [%+ bug.bug_id %]</a>:</p>
|
||||
[% END %]
|
||||
[% FOR c = bug.failed_checkers %]
|
||||
<dl style="margin-bottom: 0">
|
||||
<dt>[% c.name | html %]
|
||||
<span style="font-weight: normal">([% c.is_fatal ? "обязательная" : "рекомендательная" %]
|
||||
[%+ IF c.is_freeze %]защита от изменений[% ELSE %]проверка новых значений[% END %]):</span>
|
||||
</dt>
|
||||
<dd>
|
||||
[% c.message | html %]
|
||||
</dd>
|
||||
</dl>
|
||||
[% END %]
|
||||
[% END %]
|
|
@ -0,0 +1,7 @@
|
|||
[% IF failed_checkers %]
|
||||
<div class="user-error-div">
|
||||
<div class="user-error-div-first">
|
||||
[% PROCESS "failed-checkers.html.tmpl" f = failed_checkers %]
|
||||
</div>
|
||||
</div>
|
||||
[% END %]
|
|
@ -12,4 +12,6 @@
|
|||
[% END %]
|
||||
[% ELSIF error == "import_fields_mandatory" %]
|
||||
The following missing fields: [% fields.join(", ") | html %] are required to enter new bugs.
|
||||
[% ELSIF error == "checkers_failed" %]
|
||||
[% PROCESS "failed-checkers.html.tmpl" f = failed %]
|
||||
[% END %]
|
||||
|
|
|
@ -223,7 +223,7 @@ else
|
|||
if ($bug->{bug_id} && Bugzilla::Bug->new($bug->{bug_id}))
|
||||
{
|
||||
# если уже есть баг с таким ID - обновляем
|
||||
$id = process_bug($bug, $bugmail);
|
||||
$id = process_bug($bug, $bugmail, $vars);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -469,7 +469,7 @@ sub post_bug
|
|||
|
||||
sub process_bug
|
||||
{
|
||||
my ($fields_in, $bugmail) = @_;
|
||||
my ($fields_in, $bugmail, $vars) = @_;
|
||||
|
||||
my $um = Bugzilla->usage_mode;
|
||||
Bugzilla->usage_mode(USAGE_MODE_EMAIL);
|
||||
|
@ -521,6 +521,7 @@ sub process_bug
|
|||
$cgi->param('longdesclength', scalar @{ $bug->comments });
|
||||
$cgi->param('token', issue_hash_token([$bug->id, $bug->delta_ts]));
|
||||
|
||||
# FIXME All this is an ugly hack. Bug::update() should call anything needed, not process_bug.cgi
|
||||
$Bugzilla::Error::IN_EVAL++;
|
||||
my $vars_out = do 'process_bug.cgi';
|
||||
$Bugzilla::Error::IN_EVAL--;
|
||||
|
|
55
mod_perl.pl
55
mod_perl.pl
|
@ -111,10 +111,65 @@ sub handler : method {
|
|||
# here explicitly or init_page's shutdownhtml code won't work right.
|
||||
$0 = $ENV{'SCRIPT_FILENAME'};
|
||||
|
||||
if ($Bugzilla::RELOAD_MODULES)
|
||||
{
|
||||
reload();
|
||||
}
|
||||
Bugzilla::init_page();
|
||||
return $class->SUPER::handler(@_);
|
||||
}
|
||||
|
||||
my $STATS;
|
||||
|
||||
sub reload
|
||||
{
|
||||
my ($file, $mtime);
|
||||
for my $key (keys %INC)
|
||||
{
|
||||
$file = $INC{$key} or next;
|
||||
$file =~ /\.pm$/i or next; # do not reload *.pl *.cgi
|
||||
|
||||
$mtime = (stat $file)[9];
|
||||
# Startup time as default
|
||||
$STATS->{$file} = $^T unless defined $STATS->{$file};
|
||||
|
||||
# Modified
|
||||
if ($mtime > $STATS->{$file})
|
||||
{
|
||||
print STDERR __PACKAGE__ . ": $key -> $file modified, reloading\n";
|
||||
unload($key) or next;
|
||||
eval { require $key };
|
||||
if ($@)
|
||||
{
|
||||
warn $@;
|
||||
$INC{$key} ||= $file;
|
||||
}
|
||||
$STATS->{$file} = $mtime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub unload
|
||||
{
|
||||
my ($key) = @_;
|
||||
my $file = $INC{$key} or return;
|
||||
my @subs = grep { index($DB::sub{$_}, "$file:") == 0 } keys %DB::sub;
|
||||
for my $sub (@subs)
|
||||
{
|
||||
eval { undef &$sub };
|
||||
if ($@)
|
||||
{
|
||||
# TODO не выгружать то, что не можем выгрузить, ибо
|
||||
# иначе часть выгружается, а часть нет, и потом всё
|
||||
# равно всё дохнет.
|
||||
warn "Can't unload sub '$sub' in '$file': $@";
|
||||
return undef;
|
||||
}
|
||||
delete $DB::sub{$sub};
|
||||
}
|
||||
delete $INC{$key};
|
||||
return 1;
|
||||
}
|
||||
|
||||
package Bugzilla::ModPerl::CleanupHandler;
|
||||
use strict;
|
||||
|
|
|
@ -177,11 +177,16 @@ foreach my $field (@multi_selects) {
|
|||
$bug_params{$field->name} = [$cgi->param($field->name)];
|
||||
}
|
||||
|
||||
$Checkers::THROW_ERROR = 1;
|
||||
|
||||
# CustIS Bug 63152 - Duplicated bugs on attachment create errors
|
||||
Bugzilla->dbh->bz_start_transaction;
|
||||
|
||||
my $bug = Bugzilla::Bug->create(\%bug_params);
|
||||
|
||||
# Run hooks
|
||||
Bugzilla::Hook::process('post_bug-post_create', { bug => $bug });
|
||||
|
||||
# Get the bug ID back.
|
||||
my $id = $bug->bug_id;
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@ use Bugzilla::Flag;
|
|||
use Bugzilla::Status;
|
||||
use Bugzilla::Token;
|
||||
|
||||
use Checkers;
|
||||
|
||||
use Storable qw(dclone);
|
||||
|
||||
my $user = Bugzilla->login(LOGIN_REQUIRED);
|
||||
|
@ -556,9 +558,13 @@ foreach my $b (@bug_objects) {
|
|||
|
||||
Bugzilla::Hook::process('process_bug-pre_update', { bugs => \@bug_objects });
|
||||
|
||||
my $failed_checkers = [];
|
||||
|
||||
# @msgs will store emails which have to be sent to voters, if any.
|
||||
my @msgs;
|
||||
|
||||
$Checkers::THROW_ERROR = @bug_objects == 1;
|
||||
|
||||
##############################
|
||||
# Do Actual Database Updates #
|
||||
##############################
|
||||
|
@ -568,6 +574,16 @@ foreach my $bug (@bug_objects) {
|
|||
my $timestamp = $dbh->selectrow_array(q{SELECT LOCALTIMESTAMP(0)});
|
||||
my $changes = $bug->update($timestamp);
|
||||
|
||||
if ($bug->{failed_checkers} && @{$bug->{failed_checkers}})
|
||||
{
|
||||
push @$failed_checkers, $bug;
|
||||
if (grep { $_->is_fatal } @{$bug->{failed_checkers}})
|
||||
{
|
||||
# When we are here, rollback_to_savepoint is already done in Checkers.pm
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
my %notify_deps;
|
||||
if ($changes->{'bug_status'}) {
|
||||
my ($old_status, $new_status) = @{ $changes->{'bug_status'} };
|
||||
|
@ -677,6 +693,7 @@ foreach my $msg (@msgs) {
|
|||
# Send bugmail
|
||||
send_results($_) for @$send_results;
|
||||
$vars->{sentmail} = $send_results;
|
||||
$vars->{failed_checkers} = $failed_checkers;
|
||||
|
||||
my $bug;
|
||||
if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
|
||||
|
@ -707,6 +724,8 @@ elsif (($action eq 'next_bug' or $action eq 'same_bug') && ($bug = $vars->{bug})
|
|||
sent => $send_results,
|
||||
title => $title,
|
||||
sent_attrs => { nextbug => $action eq 'next_bug' ? 1 : 0 },
|
||||
# CustIS Bug 68921 - Correctness checkers
|
||||
failed_checkers => Checkers::freeze_failed_checkers($failed_checkers),
|
||||
};
|
||||
# CustIS Bug 38616 - CC list restriction
|
||||
if (scalar(@bug_objects) == 1 && $bug_objects[0]->{restricted_cc})
|
||||
|
@ -737,6 +756,11 @@ unless (Bugzilla->usage_mode == USAGE_MODE_EMAIL)
|
|||
|| ThrowTemplateError($template->error());
|
||||
$vars->{header_done} = 1;
|
||||
}
|
||||
if (!$vars->{header_done})
|
||||
{
|
||||
$template->process("global/header.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
$template->process("bug/navigate.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
$template->process("global/footer.html.tmpl", $vars)
|
||||
|
|
11
show_bug.cgi
11
show_bug.cgi
|
@ -31,6 +31,8 @@ use Bugzilla::User;
|
|||
use Bugzilla::Keyword;
|
||||
use Bugzilla::Bug;
|
||||
|
||||
use Checkers;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
my $vars = {};
|
||||
|
@ -131,10 +133,17 @@ $vars->{'displayfields'} = \%displayfields;
|
|||
my $sd;
|
||||
if (Bugzilla->session && ($sd = Bugzilla->session_data) && $sd->{sent})
|
||||
{
|
||||
Bugzilla->save_session_data({ sent => undef, title => undef, header => undef, sent_attrs => undef });
|
||||
Bugzilla->save_session_data({
|
||||
sent => undef,
|
||||
title => undef,
|
||||
header => undef,
|
||||
sent_attrs => undef,
|
||||
failed_checkers => undef,
|
||||
});
|
||||
$vars->{last_title} = $sd->{title};
|
||||
$vars->{last_header} = $sd->{header};
|
||||
$vars->{sentmail} = $sd->{sent};
|
||||
$vars->{failed_checkers} = Checkers::unfreeze_failed_checkers($sd->{failed_checkers});
|
||||
if ($sd->{message})
|
||||
{
|
||||
$vars->{message} = $sd->{message};
|
||||
|
|
|
@ -571,9 +571,9 @@ filter:mask();
|
|||
}
|
||||
|
||||
.yui-calcontainer { z-index: 50; }
|
||||
|
||||
.numeric_invalid { background-color: #FFE0E0; }
|
||||
|
||||
.buglist-navbar { float: left; margin: 4pt; font-size: 120%; padding: 2pt; }
|
||||
|
||||
.bz_interval_time_column { text-align: right; }
|
||||
|
||||
.user-error-div { margin: 20px; padding: 10px; font-size: 130%; font-family: sans-serif; border: 10px solid red; background: white; }
|
||||
.user-error-div-first { font-size: 150%; background-color: #ffd0d0; padding: 10px; }
|
||||
|
|
|
@ -34,10 +34,14 @@
|
|||
admindocslinks = admindocslinks
|
||||
%]
|
||||
|
||||
<div style="margin: 20px; padding: 10px; font-size: 130%; float: left; font-family: sans-serif; border: 10px solid red; background: white">
|
||||
<div class="user-error-div">
|
||||
|
||||
<div style="font-size: 150%; background-color: #ffd0d0; padding: 10px">
|
||||
<p style="margin-top: 0; margin-bottom: 0" id="error_msg">[% error_message.replace("\n\n", "</p><p style='margin-bottom: 0'>") FILTER none %]</p>
|
||||
<div class="user-error-div-first">
|
||||
[% IF error_message.match('^\s*<[a-z]') %]
|
||||
[% error_message %]
|
||||
[% ELSE %]
|
||||
<p style="margin-top: 0; margin-bottom: 0" id="error_msg">[% error_message.replace("\n\n", "</p><p style='margin-bottom: 0'>") FILTER none %]</p>
|
||||
[% END %]
|
||||
</div>
|
||||
|
||||
<p style="margin-bottom: 0">
|
||||
|
|
|
@ -104,8 +104,11 @@
|
|||
[% q.name FILTER url_quote %]">Edit</a>
|
||||
</td>
|
||||
<td>
|
||||
[% IF q.used_in_whine %]
|
||||
Remove from <a href="editwhines.cgi">whining</a> first
|
||||
[% IF q.used_in_whine OR q.used_in_checkers %]
|
||||
Remove from
|
||||
[%+ IF q.used_in_whine %]<a href="editwhines.cgi">whining</a>[% " and " IF q.used_in_checkers %][% END %]
|
||||
[%+ IF q.used_in_checkers %]<a href="editcheckers.cgi">checkers</a>[% END %]
|
||||
first
|
||||
[% ELSE %]
|
||||
<a href="buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=
|
||||
[% q.name FILTER url_quote %]&token=
|
||||
|
|
|
@ -126,6 +126,11 @@
|
|||
and time, and get the result of these queries directly per email. This is a
|
||||
good way to create reminders and to keep track of the activity in your installation.</dd>
|
||||
|
||||
[% class = user.in_group('bz_editcheckers') ? "" : "forbidden" %]
|
||||
<dt id="checkers" class="[% class %]"><a href="editcheckers.cgi">Checkers</a></dt>
|
||||
<dd class="[% class %]">Set queries which will be run at each bug change and used as
|
||||
predicates for checking change correctness.</dd>
|
||||
|
||||
[% Hook.process('end_links_right') %]
|
||||
</dl>
|
||||
</td>
|
||||
|
|
|
@ -43,26 +43,6 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
[% IF Param('usetargetmilestone') -%]
|
||||
<tr>
|
||||
<th align="right">Default milestone:</th>
|
||||
<td>
|
||||
[% IF product.milestones.size %]
|
||||
<select name="defaultmilestone">
|
||||
[% FOREACH m = product.milestones %]
|
||||
<option value="[% m.name FILTER html %]"
|
||||
[% " selected=\"selected\"" IF m.name == product.defaultmilestone %]>
|
||||
[%- m.name FILTER html -%]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
[% ELSE %]
|
||||
<input type="text" size="20" maxlength="20" name="defaultmilestone"
|
||||
value="[% product.defaultmilestone FILTER html %]">
|
||||
[% END %]
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
|
||||
<tr>
|
||||
<th align="right">Open for [% terms.bug %] entry:</th>
|
||||
<td><input type="checkbox" name="is_active" value="1"
|
||||
|
@ -111,6 +91,18 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th align="right">External product for this:</th>
|
||||
<td>
|
||||
<select name="extproduct">
|
||||
<option value="">---</option>
|
||||
[% FOREACH prod = Bugzilla.user.get_enterable_products %]
|
||||
<option value="[% prod.id %]" [% ' selected="selected"' IF prod.id == product.extproduct %]>[% prod.name | html %]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
[% IF Param('usevotes') %]
|
||||
<tr>
|
||||
<th align="right">Maximum votes per person:</th>
|
||||
|
@ -127,3 +119,23 @@
|
|||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
|
||||
[% IF Param('usetargetmilestone') -%]
|
||||
<tr>
|
||||
<th align="right">Default milestone:</th>
|
||||
<td>
|
||||
[% IF product.milestones.size %]
|
||||
<select name="defaultmilestone">
|
||||
[% FOREACH m = product.milestones %]
|
||||
<option value="[% m.name FILTER html %]"
|
||||
[% " selected=\"selected\"" IF m.name == product.defaultmilestone %]>
|
||||
[%- m.name FILTER html -%]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
[% ELSE %]
|
||||
<input type="text" size="20" maxlength="20" name="defaultmilestone"
|
||||
value="[% product.defaultmilestone FILTER html %]">
|
||||
[% END %]
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
[% Hook.process("links") %]
|
||||
<li> - <a href="#">Top of page </a></li>
|
||||
</ul>
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
|
||||
<div class="navigation">
|
||||
|
|
|
@ -331,3 +331,5 @@
|
|||
[% IF message %]
|
||||
<div id="message" class="message">[% message %]</div>
|
||||
[% END %]
|
||||
|
||||
[% Hook.process('aftermessage') %]
|
||||
|
|
Loading…
Reference in New Issue