Bug 70168 - Новый вариант RPC API "XMLSimple": принимаются параметры по REST, ответ отдаётся простой сериализацией в XML. Сделал, ибо штатный XML-RPC, провязанный через библиотеки хз-где и хз-как имеет проблемы как минимум с обработкой ошибок.

git-svn-id: svn://svn.office.custis.ru/3rdparty/bugzilla.org/trunk@1018 6955db30-a419-402b-8a0d-67ecbb4d7f56
master
vfilippov 2010-10-27 15:53:21 +00:00
parent 6f7b8675dc
commit 6a3df8a015
9 changed files with 475 additions and 120 deletions

View File

@ -705,6 +705,51 @@ sub get_fields
return @fields;
}
# Cache for fieldvaluecontrol table
sub fieldvaluecontrol
{
my $class = shift;
if (!$class->request_cache->{fieldvaluecontrol})
{
$class->request_cache->{fieldvaluecontrol} = $class->dbh->selectall_arrayref(
'SELECT c.*, (CASE WHEN c.value_id=0 THEN f.visibility_field_id ELSE f.value_field_id END) visibility_field_id'.
' FROM fieldvaluecontrol c, fielddefs f WHERE f.id=c.field_id'.
' ORDER BY c.field_id, c.value_id, (CASE WHEN c.value_id=0 THEN f.visibility_field_id ELSE f.value_field_id END), c.visibility_value_id', {Slice=>{}}
);
my $has = {};
for (@{$class->request_cache->{fieldvaluecontrol}})
{
if ($_->{value_id})
{
$has->{$_->{visibility_field_id}}
->{values}
->{$_->{field_id}}
->{$_->{value_id}}
->{$_->{visibility_value_id}} = 1;
}
else
{
$has->{$_->{visibility_field_id}}
->{fields}
->{$_->{field_id}}
->{$_->{visibility_value_id}} = 1;
}
}
$class->request_cache->{fieldvaluecontrol_hash} = $has;
}
return $class->request_cache->{fieldvaluecontrol};
}
sub fieldvaluecontrol_hash
{
my $class = shift;
if (!$class->request_cache->{fieldvaluecontrol_hash})
{
$class->fieldvaluecontrol;
}
return $class->request_cache->{fieldvaluecontrol_hash};
}
sub active_custom_fields
{
my $class = shift;

View File

@ -483,23 +483,33 @@ sub is_select {
|| $_[0]->type == FIELD_TYPE_MULTI_SELECT) ? 1 : 0
}
sub legal_values {
sub legal_values
{
my $self = shift;
if (!defined $self->{'legal_values'}) {
return [] unless $self->is_select;
if (!defined $self->{legal_values})
{
require Bugzilla::Field::Choice;
my @values = Bugzilla::Field::Choice->type($self)->get_all();
$self->{'legal_values'} = \@values;
$self->{legal_values} = [ Bugzilla::Field::Choice->type($self)->get_all() ];
}
return $self->{'legal_values'};
return $self->{legal_values};
}
sub restricted_legal_values
{
my $self = shift;
my ($controller_value) = @_;
return $self->legal_values unless $controller_value;
return $controller_value->controlled_plus_generic->{$self->name};
return $self->legal_values unless $controller_value && $self->value_field_id;
my $cid = $controller_value->id;
if (!$self->{restricted_legal_values}->{$cid})
{
my $hash = Bugzilla->fieldvaluecontrol_hash->{$self->value_field_id}->{values}->{$self->id};
$self->{restricted_legal_values}->{$cid} = [
grep { !exists $hash->{$_->id} || $hash->{$_->id}->{$cid} }
@{$self->legal_values}
];
}
return $self->{restricted_legal_values}->{$cid};
}
=pod
@ -1105,16 +1115,13 @@ Returns: the corresponding field ID or an error if the field name
=cut
sub get_field_id {
sub get_field_id
{
my ($name) = @_;
my $dbh = Bugzilla->dbh;
trick_taint($name);
my $id = $dbh->selectrow_array('SELECT id FROM fielddefs
WHERE name = ?', undef, $name);
ThrowCodeError('invalid_field_name', {field => $name}) unless $id;
return $id
my $field = Bugzilla->get_field($name);
ThrowCodeError('invalid_field_name', {field => $name}) unless $field;
return $field->id;
}
# Shared between Bugzilla::Field and Bugzilla::Field::Choice
@ -1140,25 +1147,18 @@ sub update_visibility_values
}
# Moved from bug/field-events.js.tmpl
# Now uses one pass over cached fieldvaluecontrol table
sub json_visibility
{
my $self = shift;
my $show = { fields => {}, values => {} };
foreach my $controlled (@{$self->controls_visibility_of})
{
$show->{fields}->{$controlled->name} = { map { $_->id => 1 } @{$controlled->visibility_values} };
}
$show->{legal} = [ map { [ $_->id, $_->name ] } @{$self->legal_values} ];
foreach my $value (@{$self->legal_values})
{
foreach my $controlled_field (keys %{$value->controlled_values})
{
foreach my $controlled_value (@{$value->controlled_values->{$controlled_field}})
{
$show->{values}->{$controlled_field}->{$controlled_value->id}->{$value->id} = 1;
}
}
}
my $show = {
legal => [ map { [ $_->id, $_->name ] } @{$self->legal_values} ],
fields => {},
values => {},
};
my $hash = Bugzilla->fieldvaluecontrol_hash->{$self->id};
$show->{fields} = { map { Bugzilla->get_field($_)->name => $hash->{fields}->{$_} } keys %{$hash->{fields}} };
$show->{values} = { map { Bugzilla->get_field($_)->name => $hash->{values}->{$_} } keys %{$hash->{values}} };
$show = encode_json($show);
Encode::_utf8_on($show);
return $show;
@ -1181,3 +1181,24 @@ showValueWhen['[% field.name | js %]'][[% val.id %]]['[% controlled_field | js %
[% END %]
[% END %]
[% END %]
for my $row (@{Bugzilla->fieldvaluecontrol})
{
$field_name = Bugzilla->get_field($row->{field_id})->name;
if ($row->{visibility_field_id} == $self->id)
{
if ($row->{value_id})
{
$show->{values}
->{$field_name}
->{$row->{value_id}}
->{$row->{visibility_value_id}} = 1;
}
else
{
$show->{fields}
->{$field_name}
->{$row->{visibility_value_id}} = 1;
}
}
}

View File

@ -193,7 +193,7 @@ sub remove_from_db {
{ field => $self->field, value => $self });
}
if ($self->bug_count) {
ThrowUserError("fieldvalue_still_has_bugs",
ThrowUserError('fieldvalue_still_has_bugs',
{ field => $self->field, value => $self });
}
$self->_check_if_controller();
@ -205,7 +205,7 @@ sub remove_from_db {
sub _check_if_controller {
my $self = shift;
my $vis_fields = $self->controls_visibility_of_fields;
my $values = $self->controlled_values;
my $values = $self->controls_visibility_of_field_values;
if (@$vis_fields || scalar grep { @{$values->{$_}} } keys %$values) {
ThrowUserError('fieldvalue_is_controller',
{ value => $self, fields => [map($_->name, @$vis_fields)],
@ -213,7 +213,6 @@ sub _check_if_controller {
}
}
#############
# Accessors #
#############
@ -276,83 +275,59 @@ sub is_static {
sub controls_visibility_of_fields
{
my $self = shift;
my $f;
unless ($f = $self->{controls_visibility_of_fields})
{
$f = Bugzilla->dbh->selectcol_arrayref(
"SELECT f.id FROM fielddefs f, fieldvaluecontrol c WHERE c.field_id=f.id".
" AND f.visibility_field_id=? AND c.visibility_value_id=? AND c.value_id=0",
undef, $self->field->id, $self->id
);
$_ = Bugzilla->get_field($_) for @$f;
$self->{controls_visibility_of_fields} = $f;
}
return $f;
my $vid = $self->id;
my $fid = $self->field->id;
$self->{controls_visibility_of_fields} ||= [
map { Bugzilla->get_field($_->{field_id}) }
grep { !$_->{value_id} &&
$_->{visibility_value_id} == $vid &&
$_->{visibility_field_id} == $fid }
@{Bugzilla->fieldvaluecontrol}
];
return $self->{controls_visibility_of_fields};
}
sub controlled_values
sub controls_visibility_of_field_values
{
my $self = shift;
my $controlled_values;
unless ($controlled_values = $self->{controlled_values})
my $vid = $self->id;
my $fid = $self->field->id;
if (!$self->{controls_visibility_of_field_values})
{
$controlled_values = {};
my $fields = $self->field->controls_values_of;
foreach my $field (@$fields)
my $r = {};
for (@{Bugzilla->fieldvaluecontrol})
{
my $f = Bugzilla->dbh->selectcol_arrayref(
"SELECT value_id FROM fieldvaluecontrol WHERE field_id=? AND visibility_value_id=? AND value_id!=0",
undef, $field->id, $self->id);
if (@$f)
if ($_->{value_id} &&
$_->{visibility_value_id} == $vid &&
$_->{visibility_field_id} == $fid)
{
my $type = Bugzilla::Field::Choice->type($field);
$f = $type->match({ id => $f });
push @{$r->{$_->{field_id}}}, $_->{value_id};
}
$controlled_values->{$field->name} = $f;
}
$self->{controlled_values} = $controlled_values;
$self->{controls_visibility_of_field_values} = { map {
Bugzilla->get_field($_)->name =>
Bugzilla::Field::Choice->type(Bugzilla->get_field($_))->new_from_list($r->{$_})
} keys %$r };
}
return $controlled_values;
}
sub controlled_plus_generic
{
my $self = shift;
my $controlled_values;
unless ($controlled_values = $self->{controlled_plus_generic})
{
my $fields = $self->field->controls_values_of;
foreach my $field (@$fields)
{
my $f = Bugzilla->dbh->selectcol_arrayref(
"SELECT value_id FROM fieldvaluecontrol WHERE field_id=? AND visibility_value_id=? AND value_id!=0
UNION ALL SELECT id FROM ".$field->name." f LEFT JOIN fieldvaluecontrol c ON c.value_id=f.id AND c.field_id=? WHERE c.visibility_value_id IS NULL",
undef, $field->id, $self->id, $field->id);
if (@$f)
{
my $type = Bugzilla::Field::Choice->type($field);
$f = $type->match({ id => $f });
}
$controlled_values->{$field->name} = $f;
}
$self->{controlled_plus_generic} = $controlled_values;
}
return $controlled_values;
return $self->{controls_visibility_of_field_values};
}
sub visibility_values
{
my $self = shift;
my $f;
if ($self->field->value_field && !($f = $self->{visibility_values}))
if ($self->field->value_field_id && !($f = $self->{visibility_values}))
{
$f = Bugzilla->dbh->selectcol_arrayref(
"SELECT visibility_value_id FROM fieldvaluecontrol WHERE field_id=? AND value_id=?",
undef, $self->field->id, $self->id);
my $hash = Bugzilla->fieldvaluecontrol_hash
->{$self->field->value_field_id}
->{values}
->{$self->field->id}
->{$self->id};
$f = $hash ? [ keys %$hash ] : [];
if (@$f)
{
my $type = Bugzilla::Field::Choice->type($self->field->value_field);
$f = $type->match({ id => $f });
$f = $type->new_from_list($f);
}
$self->{visibility_values} = $f;
}
@ -364,8 +339,12 @@ sub has_visibility_value
my $self = shift;
my ($value) = @_;
ref $value and $value = $value->id;
my %f = map { $_->id => 1 } @{$self->visibility_values};
return $f{$value};
return Bugzilla->fieldvaluecontrol_hash
->{$self->field->value_field_id}
->{values}
->{$self->field->id}
->{$self->id}
->{$value};
}
############

View File

@ -48,7 +48,8 @@ use base qw(Exporter);
validate_email_syntax clean_text
stem_text
intersect
get_text template_var disable_utf8);
get_text template_var disable_utf8
xml_element xml_element_quote xml_dump_simple);
use Bugzilla::Constants;
@ -59,7 +60,7 @@ use DateTime::TimeZone;
use Digest;
use Email::Address;
use List::Util qw(first);
use Scalar::Util qw(tainted);
use Scalar::Util qw(tainted blessed);
use Template::Filters;
use Text::Wrap;
use Text::TabularDisplay::Utf8;
@ -760,6 +761,60 @@ sub intersect
return $values;
}
sub xml_element_quote
{
my ($name, $args, $content) = @_;
xml_element($name, $args, xml_quote($content));
}
sub xml_element
{
my ($name, $args, $content) = @_;
if (ref $args)
{
$args = join '', map { ' '.xml_quote($_).'="'.xml_quote($args->{$_}).'"' } keys %$args;
}
$name = xml_quote($name);
$args = '<'.$name.$args;
if (defined $content && $content eq '')
{
return $args.' />';
}
return $args.'>'.$content.'</'.$name.'>';
}
sub xml_dump_simple
{
my ($data) = @_;
if (ref $data)
{
my $r;
if ($data =~ 'ARRAY')
{
$r = join '', map { xml_element('i', '', xml_dump_simple($_)) } @$data;
}
elsif ($data =~ 'HASH')
{
$r = join '', map { xml_element($_, '', xml_dump_simple($data->{$_})) } keys %$data;
}
elsif ($data =~ 'SCALAR')
{
# TODO потенциально можно сохранять ссылки
$r = xml_element('ref', '', xml_dump_simple($$data));
}
else
{
$r = xml_quote("$data");
}
if (my $p = blessed($data))
{
$r = xml_element('object', {class => $p}, $r);
}
return $r;
}
return xml_quote("$data");
}
1;
__END__

View File

@ -0,0 +1,145 @@
#!/usr/bin/perl
# Bugzilla::WebService::Field - API for managing custom fields and values
package Bugzilla::WebService::Field;
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::Field::Choice;
use Bugzilla::User;
use Bugzilla::WebService::Util qw(validate);
# { field => 'имя_поля' }
sub get_values
{
my ($self, $params) = @_;
my $field = Bugzilla->get_field($params->{field});
if (!$field)
{
return {status => 'field_not_found'};
}
if (!$field->is_select)
{
return {status => 'field_not_select'};
}
my $values;
if ($field->value_field_id)
{
$values = [ map { {
id => $_->id,
name => $_->name,
visibility_value_ids => [ map { $_->id } @{$_->visibility_values} ],
} } ];
}
else
{
$values = [ map { { id => $_->id, name => $_->name } } @{$field->legal_values} ];
}
return {
status => 'ok',
values => $values,
};
}
# { field => 'имя_поля', value => 'имя_значения', sortkey => число_для_сортировки }
sub add_value
{
my ($self, $params) = @_;
my $field = Bugzilla->get_field($params->{field});
if (!$field)
{
return {status => 'field_not_found'};
}
my $type = Bugzilla::Field::Choice->type($field);
my $value = $type->new({ name => $params->{value} });
if ($value)
{
return {status => 'value_already_exists'};
}
$value = $type->create({
value => $params->{value},
sortkey => $params->{sortkey},
});
return {status => 'ok', id => $value->id};
}
# { field => 'имя_поля', old_value => 'имя_значения', new_value => 'новое_имя_значения', sortkey => новый_sortkey }
sub update_value
{
my ($self, $params) = @_;
my $field = Bugzilla->get_field($params->{field});
if (!$field)
{
return {status => 'field_not_found'};
}
my $type = Bugzilla::Field::Choice->type($field);
my $value = $type->new({ name => $params->{new_value} });
if ($value)
{
return {status => 'value_already_exists'};
}
$value = $type->new({ name => $params->{old_value} });
if (!$value)
{
return {status => 'value_not_found'};
}
$value->set_value($params->{new_value});
$value->set_sortkey($params->{sortkey});
$value->update;
return {status => 'ok', id => $value->id};
}
# { field => 'имя_поля', value => 'имя_значения' }
sub delete_value
{
my ($self, $params) = @_;
my $field = Bugzilla->get_field($params->{field});
if (!$field)
{
return {status => 'field_not_found'};
}
my $type = Bugzilla::Field::Choice->type($field);
my $value = $type->new({ name => $params->{value} });
if (!$value)
{
return {status => 'value_not_found'};
}
$value->remove_from_db;
return {status => 'ok'};
}
# { field => 'имя_поля', value => 'имя_значения', ids => [ ID продукта, ID продукта, ... ] }
sub set_visibility_values
{
my ($self, $params) = @_;
my $field = Bugzilla->get_field($params->{field});
if (!$field)
{
return {status => 'field_not_found'};
}
my $type = Bugzilla::Field::Choice->type($field);
my $value = $type->new({ name => $params->{value} });
if (!$value)
{
return {status => 'value_not_found'};
}
$type->set_visibility_values($params->{ids});
return {status => 'ok'};
}
1;
__END__
The contents of this file are subject to the Mozilla Public
License Version 1.1 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of
the License at http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS
IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
implied. See the License for the specific language governing
rights and limitations under the License.
The Original Code is the Bugzilla Bug Tracking System.
Contributor(s): Vitaliy Filippov <vitalif@mail.ru>

View File

@ -44,17 +44,40 @@ sub get_accessible_products {
return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
}
# Get the list of product IDs by classification name
sub get_products_by_classification
{
my ($self, $params) = @_;
if (!Bugzilla->params->{useclassification})
{
return {status => 'classification_is_off'};
}
my $cid = $params->{classification};
if (!$cid)
{
return {status => 'classification_not_specified'};
}
$cid = Bugzilla::Classification->check($cid)->id;
return {
ids => [
map { $_->id }
grep { $_->classification_id eq $cid }
@{ Bugzilla->user->get_enterable_products }
]
};
}
# Get a list of actual products, based on list of ids
sub get {
my ($self, $params) = validate(@_, 'ids');
# Only products that are in the users accessible products,
# Only products that are in the users <s>accessible</s> enterable products,
# can be allowed to be returned
my $accessible_products = Bugzilla->user->get_accessible_products;
my $accessible_products = Bugzilla->user->get_enterable_products;
# Create a hash with the ids the user wants
my %ids = map { $_ => 1 } @{$params->{ids}};
# Return the intersection of this, by grepping the ids from
# accessible products.
my @requested_accessible = grep { $ids{$_->id} } @$accessible_products;

View File

@ -0,0 +1,10 @@
#!/usr/bin/perl
package Bugzilla::WebService::Server::XMLSimple;
use strict;
sub type { $_[2] }
1;
__END__

View File

@ -63,7 +63,6 @@
[% IF value.is_default || value.bug_count || (value_count == 1)
|| value.controls_visibility_of_fields.size
|| value.controlled_values_array.size
%]
<p>Sorry, but the '[% value.name FILTER html %]' value cannot be deleted
@ -115,25 +114,22 @@
[% IF value.controls_visibility_of_fields.size %]
<li>This value controls the visibility of the following fields:<br>
[% FOREACH field = value.controls_visibility_of_fields %]
<a href="editfields.cgi?action=edit&name=
[%- field.name FILTER url_quote %]">
[%- field.description FILTER html %]
([% field.name FILTER html %])</a><br>
<a href="editfields.cgi?action=edit&name=[% field.name | url_quote %]">
[%- field.description | html %]
([% field.name | html %])</a><br>
[% END %]
</li>
[% END %]
[% IF value.controlled_values_array.size %]
[% IF value.controls_visibility_of_field_values %]
<li>This value controls the visibility of the following values in
other fields:<br>
[% FOREACH field_name = value.controlled_values.keys %]
[% FOREACH controlled = value.controlled_values.${field_name} %]
<a href="editvalues.cgi?action=edit&field=
[%- controlled.field.name FILTER url_quote %]&value=
[%- controlled.name FILTER url_quote %]">
[% controlled.field.description FILTER html %]
([% controlled.field.name FILTER html %]):
[%+ controlled.name FILTER html %]</a><br>
[% FOREACH field_name = value.controls_visibility_of_field_values.keys %]
[% FOREACH controlled = value.controls_visibility_of_field_values.${field_name} %]
<a href="editvalues.cgi?action=edit&field=[% field_name | url_quote %]&value=[% controlled.name | url_quote %]">
[% controlled.field.description | html %]
([% controlled.field.name | html %]):
[%+ controlled.name | html %]</a><br>
[% END %]
[% END %]
</li>

93
xml.cgi
View File

@ -26,16 +26,97 @@ use strict;
use lib qw(. lib);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::WebService::Server::XMLSimple;
my $cgi = Bugzilla->cgi;
# Convert comma/space separated elements into separate params
my @ids = ();
my $args = $cgi->Vars;
my $method = $args->{method};
if (defined $cgi->param('id')) {
@ids = split (/[, ]+/, $cgi->param('id'));
sub addmsg
{
my ($result, $message) = @_;
if (ref $message && $message->isa('Bugzilla::Error'))
{
$result->{status} = $message->{error};
$result->{error_data} = $message->{vars};
delete $message->{vars}->{error};
}
else
{
$result->{message} = "$message";
}
}
my $ids = join('', map { $_ = "&id=" . $_ } @ids);
if (!$method)
{
# Backward compatibility: redirect to show_bug.cgi?ctype=xml
# Convert comma/space separated elements into separate params
my @ids = ();
print $cgi->redirect("show_bug.cgi?ctype=xml$ids");
if (defined $cgi->param('id')) {
@ids = split (/[, ]+/, $cgi->param('id'));
}
my $ids = join('', map { $_ = "&id=" . $_ } @ids);
print $cgi->redirect("show_bug.cgi?ctype=xml$ids");
}
else
{
# Very simple "REST/XML-RPC" server:
# Takes arguments from GET and POST parameters, returns XML.
Bugzilla->error_mode(ERROR_MODE_DIE);
Bugzilla->login;
my ($service, $result);
($service, $method) = split /\./, $method;
$service =~ s/[^a-z0-9]+//giso;
if (!$Bugzilla::WebService::{$service.'::'} ||
!$Bugzilla::WebService::{$service.'::'}->{'XMLSimple::'})
{
eval { require "Bugzilla/WebService/$service.pm" };
if ($@)
{
$result = {
status => 'bad_service',
service => $service,
method => $method,
};
addmsg($result, $@);
}
else
{
# This perversion is needed to override Bugzilla::WebService->type() method
eval "\@Bugzilla::WebService::$service\::XMLSimple::ISA = qw(Bugzilla::WebService::Server::XMLSimple Bugzilla::WebService::$service)";
}
}
if ($Bugzilla::WebService::{$service.'::'} &&
$Bugzilla::WebService::{$service.'::'}->{'XMLSimple::'})
{
my $func_args = { %$args };
delete $func_args->{method};
my $pkg = 'Bugzilla::WebService::'.$service.'::XMLSimple';
eval { $result = $pkg->$method($func_args) };
if ($@)
{
$result = {
status => 'error',
service => $service,
method => $method,
};
addmsg($result, $@);
}
else
{
$result->{status} ||= 'ok';
}
}
# Send response
Bugzilla->send_header(-type => 'text/xml'.(Bugzilla->params->{utf8} ? '; charset=utf-8' : ''));
print '<?xml version="1.0"'.(Bugzilla->params->{utf8} ? ' encoding="UTF-8"' : '').' ?>';
print '<response>';
print xml_dump_simple($result);
print '</response>';
}