Move custis-specific code to/from extension

Move to core:
* Mail log
* Bugzilla::Checker
* MediaWiki link parsing
* Filtering HTML part of incoming email
* Adding comment to a cloned bug

Move to extension:
* scrum_cards table
hinted-selects
Vitaliy Filippov 2014-08-15 16:55:29 +04:00
parent adf0ce5b0b
commit 22a6a0e369
10 changed files with 152 additions and 233 deletions

View File

@ -45,6 +45,7 @@ use Bugzilla::Diff;
use Date::Parse;
use Date::Format;
use POSIX;
use constant FORMAT_TRIPLE => "%19s|%-28s|%-28s";
use constant FORMAT_3_SIZE => [19,28,28];
@ -649,6 +650,7 @@ sub sendMail
$template->process($tmpl, $vars, \$msg) || ThrowTemplateError($template->error());
Bugzilla->template_inner("");
logMail($vars);
MessageToMTA($msg);
Bugzilla::Hook::process('bugmail-post_send', { tmpl => \$tmpl, vars => $vars });
@ -656,4 +658,27 @@ sub sendMail
return 1;
}
# Log all messages with comment and diff count to data/maillog
sub logMail
{
my ($vars) = @_;
my $datadir = bz_locations()->{datadir};
my $fd;
if (-w "$datadir/maillog" && open $fd, ">>$datadir/maillog")
{
my $s = [ POSIX::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;
}
}
1;

View File

@ -1,4 +1,7 @@
#!/usr/bin/perl
# Bug predicate / "checker" object
# License: Dual-license GPL 3.0+ or MPL 1.1+
# Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
package Bugzilla::Checker;
@ -14,17 +17,18 @@ use Bugzilla::Util;
use constant DB_TABLE => 'checkers';
use constant {
# Да => проверка старого состояния бага ("заморозка")
# Нет => проверка нового состояния бага ("проверка новых значений")
# Yes => check old state of the bug ("freezer")
# No => check new state of the bug ("checker")
CF_FREEZE => 0x01,
# Да => ошибка, нет => предупреждение
# Yes => throw an error, no => give a warning
CF_FATAL => 0x02,
# Да => проверять при создании бага, нет => не проверять
# Yes <=> check new bugs
CF_CREATE => 0x04,
# Да => проверять при обновлении бага, нет => не проверять
# Yes <=> check updates
CF_UPDATE => 0x08,
# Да => запрещать изменения всех полей, кроме except_fields
# Нет => разрешать изменения всех полей, кроме except_fields
# Yes => forbid to change everything except except_fields
# No => allow to change everything except except_fields
# except_fields are empty => CF_DENY added automatically
CF_DENY => 0x10,
};
@ -32,22 +36,13 @@ 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',
'query_id', # "Bad state" is described by this search query
'user_id', # Creator
'flags', # Bit field of CF_* flags
'message', # Error message text
'sql_code', # SQL code for query is cached here
'except_fields', # "Exception" fields - see CF_DENY above.
'triggers', # Triggers (bug changes) (requires CF_FREEZE & !CF_FATAL)
);
use constant NAME_FIELD => 'message';
use constant ID_FIELD => 'id';
@ -69,12 +64,10 @@ use constant UPDATE_COLUMNS => (
'triggers',
);
# Перепостроение и перекэширование SQL-запроса в базу
# от имени суперпользователя (без проверок групп).
# На всякий случай из запроса убирается ORDER BY и SELECT ... FROM,
# а потом при исполнении приписывается.
# Вообще проверка работает дёрганьем SQL-запроса с добавленным условием
# на bugs.bug_id=...
# The check works by executing this SQL query with added bugs.bug_id=? condition.
# Rebuild and save SQL code in the DB, from under the superuser
# (without permission checks). ORDER BY and SELECT ... FROM are removed
# and then added for more security.
sub refresh_sql
{
my $self = shift;
@ -97,7 +90,7 @@ sub refresh_sql
$self->set_sql_code($sql);
}
# Создание нового предиката - сразу кэшируется SQL-код
# Create a predicate, generating SQL code for it
sub create
{
my ($class, $params) = @_;
@ -115,7 +108,7 @@ sub create
return $self;
}
# Обновление - всегда перекэшируется SQL-код
# Update a predicate, regenerating SQL code for it
sub update
{
my $self = shift;
@ -128,20 +121,19 @@ sub update
$self->SUPER::update(@_);
}
# Проверяем, что такой поиск существует и доступен пользователю
# Check this named query exists and is accessible to the user
sub _check_query_id
{
my ($invocant, $value, $field) = @_;
my $q = Bugzilla::Search::Saved->check({ id => $value });
# Потенциально мы разрешаем создавать предикаты
# на основе расшаренных другими людьми поисков,
# но в интерфейсе этого сейчас нет
# This code allows to create predicates using searches shared by other users,
# but the UI doesn't allow it (yet?).
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
# Check if a named query is not a search query, but just an HTTP url
if ($q->query =~ /^[a-z][a-z0-9]*:/iso)
{
ThrowUserError('query_not_savedsearch', { query => $q });
@ -163,7 +155,7 @@ sub message { $_[0]->{message} }
sub sql_code { $_[0]->{sql_code} }
sub flags { $_[0]->{flags} }
# Отдельные флаги
# Specific flags from the bitfield
sub is_freeze { $_[0]->{flags} & CF_FREEZE }
sub is_fatal { ($_[0]->{flags} & CF_FATAL) && !$_[0]->triggers }
sub on_create { $_[0]->{flags} & CF_CREATE }
@ -171,8 +163,7 @@ sub on_update { $_[0]->{flags} & CF_UPDATE }
sub deny_all { $_[0]->{flags} & CF_DENY }
# { field_name => value }
# Исключать изменения поля field_name на значение value,
# либо на любое значение, если value = undef
# Make an exception for change of field_name to 'value', or to any value if value is undef
sub except_fields
{
my $self = shift;
@ -184,10 +175,9 @@ sub except_fields
}
# { field_name => value }
# Изменить значение поля field_name на value. Для полей с множествами значений
# field_name также может быть add_<field_name> или remove_<field_name>, что означает
# добавить значение или удалить значение соответственно.
# FIXME Пока поддерживается только add_cc.
# Change field 'field_name' to 'value'. For multivalued fields field_name may also
# by 'add_<field_name>' or 'remove_<field_name>', which means add or remove something.
# FIXME Now the only function supported is 'add_cc'
sub triggers
{
my $self = shift;
@ -224,11 +214,11 @@ sub user
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_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
{

View File

@ -822,19 +822,6 @@ use constant ABSTRACT_SCHEMA => {
],
},
# Additional estimates for SCRUM cards printed from Bugzilla (CustIS Bug 45485)
scrum_cards => {
FIELDS => [
bug_id => {TYPE => 'INT4', 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' },
],
},
namedqueries_link_in_footer => {
FIELDS => [
namedquery_id => {TYPE => 'INT4', NOTNULL => 1, REFERENCES => {TABLE => 'namedqueries', COLUMN => 'id', DELETE => 'CASCADE'}},

View File

@ -284,6 +284,12 @@ sub quoteUrls {
# initialize custom protocols
$custom_proto_cached = time;
$custom_proto = {};
# MediaWiki link integration
for (split /\n/, Bugzilla->params->{mediawiki_urls})
{
my ($wiki, $url) = split /\s+/, trim($_), 2;
$custom_proto->{$wiki} = sub { process_wiki_url($url, @_) } if $wiki && $url;
}
Bugzilla::Hook::process('quote_urls-custom_proto', { custom_proto => $custom_proto });
$custom_proto_regex = join '|', keys %$custom_proto;
}
@ -374,6 +380,29 @@ sub quoteUrls {
return $text;
}
# MediaWiki page anchor encoding
sub process_wiki_anchor
{
my ($anchor) = (@_);
return "" unless $anchor;
$anchor =~ tr/ /_/;
$anchor = url_quote($anchor);
$anchor =~ s/\%3A/:/giso;
$anchor =~ tr/%/./;
return $anchor;
}
# Convert MediaWiki page titles to URLs
sub process_wiki_url
{
my ($base, $url, $anchor) = @_;
$url = trim($url);
$url =~ s/\s+/_/gso;
# Use url_quote without converting / to %2F
$url = url_quote_noslash($url);
return $base . $url . '#' . process_wiki_anchor($anchor);
}
# Creates a link to an attachment, including its title.
sub get_attachment_link {
my ($attachid, $link_text, $comment) = @_;

View File

@ -55,7 +55,6 @@ use Bugzilla::Mailer;
use Bugzilla::Token;
use Bugzilla::User;
use Bugzilla::Util;
use Bugzilla::Hook;
#############
# Constants #
@ -79,8 +78,7 @@ sub parse_mail
$input_email = Email::MIME->new($mail_text);
my %fields;
Bugzilla::Hook::process('email_in_before_parse', { mail => $input_email,
fields => \%fields });
Bugzilla::Hook::process('email_in_before_parse', { mail => $input_email, fields => \%fields });
# RFC 3834 - Recommendations for Automatic Responses to Electronic Mail
# Automatic responses SHOULD NOT be issued in response to any
# message which contains an Auto-Submitted header field (see below),
@ -126,9 +124,10 @@ sub parse_mail
debug_print("Body:\n" . $body, 3);
$body = remove_leading_blank_lines($body);
Bugzilla::Hook::process("emailin-filter_body", { body => \$body });
my @body_lines = split(/\r?\n/s, $body);
Bugzilla::Hook::process("emailin-filter_body", { body => \$body });
my @body_lines = split(/\r?\n/s, $body);
my $fields_by_name = { map { (lc($_->description) => $_->name, lc($_->name) => $_->name) } Bugzilla->get_fields({ obsolete => 0 }) };
# If there are fields specified.
@ -321,6 +320,13 @@ sub get_body_and_attachments
return ($body, $attachments);
}
sub rm_line_feeds
{
my ($t) = @_;
$t =~ s/[\n\r]+/ /giso;
return $t;
}
sub get_text_alternative
{
my ($email) = @_;
@ -345,6 +351,8 @@ sub get_text_alternative
elsif ($ct =~ /^text\/html/i)
{
$body = $part->body;
$body =~ s/<table[^<>]*class=[\"\']?difft[^<>]*>.*?<\/table\s*>//giso;
$body =~ s/(<a[^<>]*>.*?<\/a\s*>)/rm_line_feeds($1)/gieso;
Bugzilla::Hook::process("emailin-filter_html", { body => \$body });
$body = HTML::Strip->new->parse($body);
}

View File

@ -18,11 +18,6 @@ required_modules('custis', $REQUIRED_MODULES);
optional_modules('custis', $OPTIONAL_MODULES);
clear_hooks('custis');
# Email-related hooks
set_hook('custis', 'bugmail_pre_template', 'CustisMailHooks::bugmail_pre_template');
set_hook('custis', 'emailin_filter_body', 'CustisMailHooks::emailin_filter_body');
set_hook('custis', 'emailin_filter_html', 'CustisMailHooks::emailin_filter_html');
# Hooks allowing to create MySQL Views representing saved searches for users
if (!Bugzilla->params->{ext_disable_refresh_views})
{
@ -47,9 +42,9 @@ add_hook('custis', 'install_before_final_checks', 'Checkers::install_before_fi
# Other hooks
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');
set_hook('custis', 'enter_bug_cloned_bug', 'CustisMiscHooks::enter_bug_cloned_bug');
add_hook('custis', 'bug_end_of_create', 'CustisMiscHooks::bug_end_of_create');
set_hook('custis', 'post_bug_cloned_bug', 'CustisMiscHooks::post_bug_cloned_bug');
set_hook('custis', 'emailin_filter_body', 'CustisMiscHooks::emailin_filter_body');
1;
__END__

View File

@ -1,84 +0,0 @@
#!/usr/bin/perl
# Various email hooks
package CustisMailHooks;
use strict;
use Bugzilla::Constants;
use Bugzilla::Util;
use POSIX qw(strftime);
# Log all messages with comment and diff count to data/maillog
sub bugmail_pre_template
{
my ($args) = @_;
my $vars = $args->{vars};
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;
}
return 1;
}
##
## Handling incoming email:
##
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

@ -3,8 +3,6 @@
# - Expand "group" users in flag requestee
# - Remember about nonanswered flag requests
# - Automatic settings of cf_extbug
# - Add a comment to cloned bug
# - Expand MediaWiki urls
package CustisMiscHooks;
@ -15,7 +13,7 @@ use Bugzilla::Util;
use Bugzilla::Constants;
use Bugzilla::Error;
# Expand "group" users in flag requestee
# Expand CUSTIS-specific "group" users in flag requestee
sub flag_check_requestee_list
{
my ($args) = @_;
@ -37,20 +35,20 @@ sub process_bug_after_move
{
my ($args) = @_;
my $cgi = Bugzilla->cgi;
my $ARGS = Bugzilla->input_params;
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' &&
$ARGS->{bug_status} eq 'CLOSED' &&
Bugzilla->user->settings->{clear_requests_on_close}->{value} eq 'on';
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;
my $reset_own_flags = $verify_flags && $ARGS->{comment} !~ /^\s*$/so;
if (($clear_on_close || $reset_own_flags) && !$cgi->param('force_flags'))
if (($clear_on_close || $reset_own_flags) && !$ARGS->{force_flags})
{
my $flags;
my @requery_flags;
@ -58,18 +56,17 @@ sub process_bug_after_move
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())
for (keys %$ARGS)
{
if (/^(flag-(\d+))$/)
{
$flag = Bugzilla::Flag->new({ id => $2 });
$flag->{status} = $cgi->param($1);
if (($login = trim($cgi->param("requestee-".$flag->{id}))) &&
$flag->{status} = $ARGS->{$_};
if (($login = trim($ARGS->{"requestee-".$flag->{id}})) &&
($login = login_to_id($login)))
{
$flag->{requestee_id} = $login;
@ -94,10 +91,11 @@ sub process_bug_after_move
if ($verify_flags)
{
push @requery_flags, $flag;
delete $ARGS->{'flag-'.$flag->{id}};
}
elsif ($single)
{
$cgi->param('flag-'.$flag->{id} => 'X');
$ARGS->{'flag-'.$flag->{id}} = 'X';
}
else
{
@ -108,7 +106,6 @@ sub process_bug_after_move
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;
@ -134,78 +131,38 @@ sub enter_bug_cloned_bug
return 1;
}
# Bug 53590 - add a comment to cloned bug
# Bug 69514 - automatic setting of cf_extbug during clone to external product
sub bug_end_of_create
# Bug 69514 - Automatic setting of cf_extbug during clone to external product
sub post_bug_cloned_bug
{
my ($args) = @_;
my $cloned_bug_id = scalar Bugzilla->cgi->param('cloned_bug_id');
my $cloned_comment = scalar Bugzilla->cgi->param('cloned_comment');
my $bug = $args->{bug};
if ($cloned_bug_id)
if (($args->{cloned_bug}->product_obj->extproduct || 0) == $args->{bug}->product_id &&
!$args->{cloned_bug}->{cf_extbug})
{
my $cmt = "Bug ".$bug->id." was cloned from ";
if ($cloned_comment)
{
detaint_natural($cloned_comment);
$cmt .= 'comment ';
$cmt .= $cloned_comment;
}
else
{
$cmt .= 'this bug';
}
detaint_natural($cloned_bug_id);
my $cloned_bug = Bugzilla::Bug->check($cloned_bug_id);
$cloned_bug->add_comment($cmt);
if (($cloned_bug->product_obj->extproduct || 0) == $bug->product_id &&
!$cloned_bug->{cf_extbug})
{
$cloned_bug->{cf_extbug} = $bug->id;
}
$cloned_bug->update($bug->creation_ts);
$args->{cloned_bug}->{cf_extbug} = $args->{bug}->id;
}
return 1;
}
# MediaWiki link integration
sub quote_urls_custom_proto
# Filter text body of input messages to remove Outlook link URLs.
sub emailin_filter_body
{
my ($args) = @_;
for (split /\n/, Bugzilla->params->{mediawiki_urls})
for (${$args->{body}})
{
my ($wiki, $url) = split /\s+/, trim($_), 2;
$args->{custom_proto}->{$wiki} = sub { process_wiki_url($url, @_) } if $wiki && $url;
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;
}
##
## NON-HOOK FUNCTIONS
##
# MediaWiki page anchor encoding
sub process_wiki_anchor
{
my ($anchor) = (@_);
return "" unless $anchor;
$anchor =~ tr/ /_/;
$anchor = url_quote($anchor);
$anchor =~ s/\%3A/:/giso;
$anchor =~ tr/%/./;
return $anchor;
}
# Convert MediaWiki page titles to URLs
sub process_wiki_url
{
my ($base, $url, $anchor) = @_;
$url = trim($url);
$url =~ s/\s+/_/gso;
# Use url_quote without converting / to %2F
$url = url_quote_noslash($url);
return $base . $url . '#' . process_wiki_anchor($anchor);
}
1;
__END__

View File

@ -173,6 +173,18 @@ $bug->set_flags($flags, $new_flags);
# Save bug
$bug->update;
if ($ARGS->{cloned_bug_id})
{
# Add a comment to cloned bug
my $cmt = "Bug ".$bug->id." was cloned from ".
($ARGS->{cloned_comment} =~ /(\d+)/ ? "comment $1" : 'this bug');
detaint_natural($cloned_bug_id);
my $cloned_bug = Bugzilla::Bug->check($ARGS->{cloned_bug_id});
$cloned_bug->add_comment($cmt);
Bugzilla::Hook::process('post_bug_cloned_bug', { bug => $bug, cloned_bug => $cloned_bug });
$cloned_bug->update($bug->creation_ts);
}
# Get the bug ID back
my $id = $bug->bug_id;

View File

@ -2,16 +2,16 @@
# License: Dual-license MPL 1.1+ or GPL 3.0+
# Author(s): Vitaliy Filippov, Stas Fomin %]
[% cgi = Bugzilla.cgi %]
[% PROCESS global/header.html.tmpl title='Verify flag requests' %]
[% ARGS = Bugzilla.input_params %]
<h3>Please, verify flags:</h3>
<form action="process_bug.cgi" method="post" enctype="multipart/form-data">
<input type="hidden" name="force_flags" value="1" />
[% PROCESS "global/hidden-fields.html.tmpl" exclude=field_filter %]
[% PROCESS "global/hidden-fields.html.tmpl" %]
<table cellspacing="0" cellpadding="4" style="border-width: 1px 1px 0 1px; border-style: solid; border-color: gray">
<tr style="background-color: #e0e0e0">
@ -29,7 +29,7 @@
"[% flag.type.description %]"
</td>
<td style="border-width: 0 0 1px 0; border-style: solid; border-color: gray">
[% cgi.param("requestee-$flag.id" ) %]
[% ARGS.${"requestee-$flag.id"}.join(', ') %]
</td>
<td style="border-width: 0 0 1px 0; border-style: solid; border-color: gray">
<select id="flag-[% flag.id %]" name="flag-[% flag.id %]">