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-67ecbb4d7f56
master
vfilippov 2010-10-22 16:57:14 +00:00
parent 1e4892591f
commit b439c78544
58 changed files with 1722 additions and 781 deletions

View File

@ -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'};

View File

@ -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
#####################################################################

View File

@ -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;

View File

@ -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 ######

View File

@ -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) {

View File

@ -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.

View File

@ -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 {

110
editcheckers.cgi Executable file
View File

@ -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__

View File

@ -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'));

View File

@ -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;
}
}
}

View File

@ -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};

View File

@ -1,7 +0,0 @@
#!/usr/bin/perl
use strict;
use CustisLocalBugzillas;
# Unhack urlbase :-)
CustisLocalBugzillas::HackIntoUrlbase(undef);

View File

@ -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});

View File

@ -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';

View File

@ -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};

View File

@ -1,6 +0,0 @@
#!/usr/bin/perl
use strict;
use FlushViews;
refresh_views();

View File

@ -1,6 +0,0 @@
#!/usr/bin/perl
use strict;
use FlushViews;
refresh_views();

View File

@ -1,6 +0,0 @@
#!/usr/bin/perl
use strict;
use FlushViews;
refresh_views();

View File

@ -1,6 +0,0 @@
#!/usr/bin/perl
use strict;
use FlushViews;
refresh_views();

View File

@ -1,6 +0,0 @@
#!/usr/bin/perl
use strict;
use FlushViews;
refresh_views();

View File

@ -1,6 +0,0 @@
#!/usr/bin/perl
use strict;
use FlushViews;
refresh_views();

View File

@ -1,6 +0,0 @@
#!/usr/bin/perl
use strict;
use FlushViews;
refresh_views();

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -1,7 +0,0 @@
#!/usr/bin/perl
use strict;
use CustisLocalBugzillas;
# Unhack urlbase :-)
CustisLocalBugzillas::HackIntoUrlbase(undef);

View File

@ -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});

View File

@ -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));
}

View File

@ -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;
};

View File

@ -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});
}

View File

@ -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;
}
}
}

View File

@ -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 });
}

View File

@ -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, @_) }
}

View File

@ -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 ]);

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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> &nbsp;
</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">
поле:&nbsp;<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> &nbsp;
новое&nbsp;значение:&nbsp;<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> &mdash; обычные сохранённые запросы поиска Bugzilla,
служащие для проверки различных правил изменений багов.
<p>Доступно два режима проверки:</p>
<p><b>Проверка состояния:</b><br />
При каждом изменении каждого бага система проверяет, соответствует ли его новое состояние
(новые значения всех полей) сохранённому запросу поиска, и если да, то выдаёт предупреждение
или ошибку с заданным текстом.</p>
<p><b>Заморозка багов:</b><br />
При каждом изменении каждого бага система проверяет, соответствует ли его <i>старое</i> состояние
сохранённому запросу поиска, и если да, то выдаёт предупреждение или ошибку с заданным текстом.
Опционально разрешается оставлять комментарии без списывания рабочего времени к таким багам.</p>
</div>
</div>
[% PROCESS global/footer.html.tmpl %]

View File

@ -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 %]

View File

@ -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 %]

View File

@ -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 %]

View File

@ -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--;

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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};

View File

@ -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; }

View File

@ -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">

View File

@ -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&amp;remaction=forget&amp;namedcmd=
[% q.name FILTER url_quote %]&amp;token=

View File

@ -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>

View File

@ -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 %]

View File

@ -32,7 +32,7 @@
[% Hook.process("links") %]
<li>&nbsp;-&nbsp;<a href="#">Top of page </a></li>
</ul>
[% END %]
[% END %]
<div class="navigation">

View File

@ -331,3 +331,5 @@
[% IF message %]
<div id="message" class="message">[% message %]</div>
[% END %]
[% Hook.process('aftermessage') %]