248 lines
8.1 KiB
Perl
248 lines
8.1 KiB
Perl
#!/usr/bin/perl
|
||
|
||
package Bugzilla::Checker;
|
||
|
||
use strict;
|
||
use base qw(Bugzilla::Object Exporter);
|
||
|
||
use JSON;
|
||
use Bugzilla::Search;
|
||
use Bugzilla::Search::Saved;
|
||
use Bugzilla::Error;
|
||
|
||
use constant DB_TABLE => 'checkers';
|
||
|
||
use constant {
|
||
# Да => проверка старого состояния бага ("заморозка")
|
||
# Нет => проверка нового состояния бага ("проверка новых значений")
|
||
CF_FREEZE => 0x01,
|
||
# Да => ошибка, нет => предупреждение
|
||
CF_FATAL => 0x02,
|
||
# Да => проверять при создании бага, нет => не проверять
|
||
CF_CREATE => 0x04,
|
||
# Да => проверять при обновлении бага, нет => не проверять
|
||
CF_UPDATE => 0x08,
|
||
# Да => запрещать изменения всех полей, кроме except_fields
|
||
# Нет => разрешать изменения всех полей, кроме except_fields
|
||
CF_DENY => 0x10,
|
||
};
|
||
|
||
our @EXPORT = qw(CF_FREEZE CF_FATAL CF_CREATE CF_UPDATE CF_DENY);
|
||
|
||
use constant DB_COLUMNS => (
|
||
'id',
|
||
# <Это состояние> задаётся соответствием запросу поиска.
|
||
'query_id',
|
||
# Кто создал
|
||
'user_id',
|
||
# Флаги
|
||
'flags',
|
||
# Сообщение об ошибке в случае некорректности
|
||
'message',
|
||
# SQL-код генерировать при каждом изменении бага долго, поэтому кэшируем
|
||
'sql_code',
|
||
# Поля-исключения: если CF_DENY, то разрешить только их,
|
||
# если !(flags & CF_DENY), то запретить только их,
|
||
# если !(flags & CF_DENY) и их нет, то flags |= CF_DENY
|
||
'except_fields',
|
||
# Триггеры - действия над полями багов (требует CF_FREEZE и !CF_FATAL)
|
||
'triggers',
|
||
);
|
||
use constant NAME_FIELD => 'message';
|
||
use constant ID_FIELD => 'id';
|
||
use constant LIST_ORDER => NAME_FIELD;
|
||
|
||
use constant REQUIRED_CREATE_FIELDS => qw(query_id message);
|
||
|
||
use constant VALIDATORS => {
|
||
query_id => \&_check_query_id,
|
||
flags => \&_check_flags,
|
||
};
|
||
|
||
use constant UPDATE_COLUMNS => (
|
||
'query_id',
|
||
'flags',
|
||
'message',
|
||
'sql_code',
|
||
'except_fields',
|
||
'triggers',
|
||
);
|
||
|
||
# Перепостроение и перекэширование 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 $search = new Bugzilla::Search(
|
||
params => http_decode_query($query->url),
|
||
fields => [ 'bug_id' ],
|
||
user => $query->user,
|
||
);
|
||
my $terms = Bugzilla::Search::simplify_expression([
|
||
'AND_MANY', { term => 'bugs.bug_id=?' },
|
||
$search->{terms_without_security}
|
||
]);
|
||
my $sql = $search->get_expression_sql($terms, 'force joins');
|
||
$sql =~ s/^\s*SELECT.*?FROM/SELECT DISTINCT $self->{id} FROM/;
|
||
$self->set_sql_code($sql);
|
||
}
|
||
|
||
# Создание нового предиката - сразу кэшируется SQL-код
|
||
sub create
|
||
{
|
||
my ($class, $params) = @_;
|
||
if ($params->{except_fields})
|
||
{
|
||
$params->{except_fields} = encode_json($params->{except_fields});
|
||
}
|
||
if ($params->{triggers})
|
||
{
|
||
$params->{triggers} = encode_json($params->{triggers});
|
||
}
|
||
my $self = Bugzilla::Object::create($class, $params);
|
||
$self->update;
|
||
$self->query->set_shared_with_group(Bugzilla::Group->check({ name => 'bz_editcheckers' }));
|
||
return $self;
|
||
}
|
||
|
||
# Обновление - всегда перекэшируется SQL-код
|
||
sub update
|
||
{
|
||
my $self = shift;
|
||
$self->refresh_sql;
|
||
$self->query->set_shared_with_group(Bugzilla::Group->check({ name => 'bz_editcheckers' }));
|
||
if ($self->triggers)
|
||
{
|
||
$self->{flags} |= CF_FREEZE;
|
||
}
|
||
$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 _check_flags
|
||
{
|
||
my ($invocant, $value, $field) = @_;
|
||
$value = int($value);
|
||
return $value;
|
||
}
|
||
|
||
sub id { $_[0]->{id} }
|
||
sub query_id { $_[0]->{query_id} }
|
||
sub user_id { $_[0]->{user_id} }
|
||
sub message { $_[0]->{message} }
|
||
sub sql_code { $_[0]->{sql_code} }
|
||
sub flags { $_[0]->{flags} }
|
||
|
||
# Отдельные флаги
|
||
sub is_freeze { $_[0]->{flags} & CF_FREEZE }
|
||
sub is_fatal { ($_[0]->{flags} & CF_FATAL) && !$_[0]->triggers }
|
||
sub on_create { $_[0]->{flags} & CF_CREATE }
|
||
sub on_update { $_[0]->{flags} & CF_UPDATE }
|
||
sub deny_all { $_[0]->{flags} & CF_DENY }
|
||
|
||
# { field_name => value }
|
||
# Исключать изменения поля field_name на значение value,
|
||
# либо на любое значение, если value = undef
|
||
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};
|
||
}
|
||
|
||
# { field_name => value }
|
||
# Изменить значение поля field_name на value. Для полей с множествами значений
|
||
# field_name также может быть add_<field_name> или remove_<field_name>, что означает
|
||
# добавить значение или удалить значение соответственно.
|
||
# FIXME Пока поддерживается только add_cc.
|
||
sub triggers
|
||
{
|
||
my $self = shift;
|
||
if (!exists $self->{triggers_obj})
|
||
{
|
||
$self->{triggers_obj} = $self->{triggers} ? decode_json($self->{triggers}) : undef;
|
||
}
|
||
return $self->{triggers_obj};
|
||
}
|
||
|
||
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_flags { $_[0]->set('flags', $_[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', $value ? encode_json($value) : undef);
|
||
delete $self->{except_fields_obj};
|
||
}
|
||
|
||
sub set_triggers
|
||
{
|
||
my ($self, $value) = @_;
|
||
$self->set('triggers', $value ? encode_json($value) : undef);
|
||
delete $self->{triggers_obj};
|
||
}
|
||
|
||
1;
|
||
__END__
|