=== modified file 'Bugzilla/Bug.pm' --- Bugzilla/Bug.pm 2010-04-06 03:21:50 +0000 +++ Bugzilla/Bug.pm 2010-07-21 15:58:46 +0000 @@ -3044,6 +3044,14 @@ "SELECT bug_id FROM bugs WHERE alias = ?", undef, $alias); } +sub get_test_case_count { + my $self = shift; + my $dbh = Bugzilla->dbh; + my $row_count = $dbh->selectall_arrayref( + "SELECT DISTINCT case_id FROM test_case_bugs WHERE bug_id = ?", + undef, $self->bug_id); + return scalar @$row_count; +} ##################################################################### # Subroutines ##################################################################### === modified file 'Bugzilla/Constants.pm' --- Bugzilla/Constants.pm 2010-04-13 04:53:30 +0000 +++ Bugzilla/Constants.pm 2010-07-21 15:58:46 +0000 @@ -140,6 +140,7 @@ ERROR_MODE_WEBPAGE ERROR_MODE_DIE ERROR_MODE_DIE_SOAP_FAULT + ERROR_MODE_AJAX ERROR_MODE_JSON_RPC INSTALLATION_MODE_INTERACTIVE @@ -420,6 +421,7 @@ use constant ERROR_MODE_WEBPAGE => 0; use constant ERROR_MODE_DIE => 1; use constant ERROR_MODE_DIE_SOAP_FAULT => 2; +use constant ERROR_MODE_AJAX => 4; use constant ERROR_MODE_JSON_RPC => 3; # The various modes that checksetup.pl can run in. === modified file 'Bugzilla/Error.pm' --- Bugzilla/Error.pm 2010-04-01 01:06:03 +0000 +++ Bugzilla/Error.pm 2010-07-21 15:58:46 +0000 @@ -32,6 +32,7 @@ use Bugzilla::WebService::Constants; use Bugzilla::Util; use Date::Format; +use JSON; # We cannot use $^S to detect if we are in an eval(), because mod_perl # already eval'uates everything, so $^S = 1 in all cases under mod_perl! @@ -135,6 +136,16 @@ $server->response($server->error_response_header); } } + elsif (Bugzilla->error_mode == ERROR_MODE_AJAX) { + # JSON can't handle strings across lines. + $message =~ s/\n/ /gm; + my $err; + $err->{'success'} = JSON::false; + $err->{'error'} = $error; + $err->{'message'} = $message; + my $json = new JSON; + print $json->encode($err); + } } exit; } === modified file 'Bugzilla/Install/Filesystem.pm' --- Bugzilla/Install/Filesystem.pm 2010-02-20 20:08:38 +0000 +++ Bugzilla/Install/Filesystem.pm 2010-07-21 15:58:46 +0000 @@ -122,6 +122,7 @@ 'runtests.pl' => { perms => $owner_executable }, 'testserver.pl' => { perms => $ws_executable }, 'whine.pl' => { perms => $ws_executable }, + 'tr_importxml.pl' => { perms => $ws_executable }, 'customfield.pl' => { perms => $owner_executable }, 'email_in.pl' => { perms => $ws_executable }, 'sanitycheck.pl' => { perms => $ws_executable }, === modified file 'Bugzilla/Search.pm' --- Bugzilla/Search.pm 2010-03-29 21:17:54 +0000 +++ Bugzilla/Search.pm 2010-07-21 15:58:46 +0000 @@ -274,6 +274,12 @@ push(@supptables, "LEFT JOIN longdescs AS ldtime " . "ON ldtime.bug_id = bugs.bug_id"); } + ### Testopia ### + if (grep($_ eq 'test_cases', @fields)){ + push(@supptables, "LEFT JOIN test_case_bugs AS tcb " . + "ON bugs.bug_id = tcb.bug_id "); + } + ### end Testopia ### if (grep($_ eq 'flagtypes.name', @fields)) { push(@supptables, "LEFT JOIN flags ON flags.bug_id = bugs.bug_id AND attach_id IS NULL"); === modified file 'Bugzilla/User.pm' --- Bugzilla/User.pm 2010-02-18 00:16:31 +0000 +++ Bugzilla/User.pm 2010-07-21 15:58:46 +0000 @@ -361,6 +361,16 @@ return $self->{queries_available}; } +sub testopia_queries { + my $self = shift; + my $dbh = Bugzilla->dbh; + my $ref = $dbh->selectall_arrayref( + "SELECT name, query FROM test_named_queries + WHERE userid = ? AND isvisible = 1", + {'Slice' =>{}}, $self->id); + return $ref; +} + sub settings { my ($self) = @_; @@ -688,16 +698,23 @@ my $class_restricted = Bugzilla->params->{'useclassification'} && $class_id; if (!defined $self->{selectable_products}) { - my $query = "SELECT id " . + my $query = "(SELECT id, name AS pname " . " FROM products " . "LEFT JOIN group_control_map " . "ON group_control_map.product_id = products.id " . " AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY . " AND group_id NOT IN(" . $self->groups_as_string . ") " . - " WHERE group_id IS NULL " . - "ORDER BY name"; - - my $prod_ids = Bugzilla->dbh->selectcol_arrayref($query); + " WHERE group_id IS NULL) " ; + + $query .= "UNION (SELECT id, tr_products.name AS pname FROM products AS tr_products ". + "INNER JOIN test_plans ON tr_products.id = test_plans.product_id ". + "INNER JOIN test_plan_permissions ON test_plan_permissions.plan_id = test_plans.plan_id ". + "WHERE test_plan_permissions.userid = ?)"; + + $query .= " ORDER BY pname "; + + my $prod_ids = Bugzilla->dbh->selectcol_arrayref($query,undef,$self->id); + $self->{selectable_products} = Bugzilla::Product->new_from_list($prod_ids); } @@ -990,6 +1007,33 @@ $group_delete->execute($id, $group, GRANT_REGEXP) if $present; } } + # Now do the same for Testopia test plans. + $sth = $dbh->prepare("SELECT test_plan_permissions_regexp.plan_id, + user_regexp, test_plan_permissions_regexp.permissions, + test_plan_permissions.plan_id + FROM test_plan_permissions_regexp + LEFT JOIN test_plan_permissions + ON test_plan_permissions_regexp.plan_id = test_plan_permissions.plan_id + AND test_plan_permissions.userid = ? + AND test_plan_permissions.grant_type = ?"); + + $sth->execute($id, GRANT_REGEXP); + my $plan_insert = $dbh->prepare(q{INSERT INTO test_plan_permissions + (userid, plan_id, permissions, grant_type) + VALUES (?, ?, ?, ?)}); + my $plan_delete = $dbh->prepare(q{DELETE FROM test_plan_permissions + WHERE userid = ? + AND plan_id = ? + AND grant_type = ?}); + + while (my ($planid, $regexp, $perms, $present) = $sth->fetchrow_array()) { + if (($regexp ne '') && ($self->{login} =~ m/$regexp/i)) { + $plan_insert->execute($id, $planid, $perms, GRANT_REGEXP) unless $present; + } else { + $plan_delete->execute($id, $planid, GRANT_REGEXP) if $present; + } + } + } sub product_responsibilities { === modified file 'Bugzilla/WebService/Server/XMLRPC.pm' --- Bugzilla/WebService/Server/XMLRPC.pm 2010-03-10 20:53:01 +0000 +++ Bugzilla/WebService/Server/XMLRPC.pm 2010-07-21 15:58:46 +0000 @@ -78,7 +78,10 @@ $som->{_bz_do_taint} = 1; } bless $som, 'Bugzilla::XMLRPC::SOM'; - Bugzilla->input_params($som->paramsin || {}); + my $params = $som->paramsin; + # This allows positional parameters for Testopia. + $params = {} if ref $params ne 'HASH'; + Bugzilla->input_params($params); return $som; } @@ -149,13 +152,15 @@ sub paramsin { my $self = shift; - return $self->{bz_params_in} if $self->{bz_params_in}; - my $params = $self->SUPER::paramsin(@_); - if ($self->{_bz_do_taint}) { - taint_data($params); + if (!$self->{bz_params_in}) { + my @params = $self->SUPER::paramsin(@_); + if ($self->{_bz_do_taint}) { + taint_data(@params); + } + $self->{bz_params_in} = \@params; } - $self->{bz_params_in} = $params; - return $self->{bz_params_in}; + my $params = $self->{bz_params_in}; + return wantarray ? @$params : $params->[0]; } 1; === modified file 'Bugzilla/WebService/Util.pm' --- Bugzilla/WebService/Util.pm 2009-11-09 18:27:52 +0000 +++ Bugzilla/WebService/Util.pm 2010-07-21 15:58:46 +0000 @@ -52,13 +52,13 @@ } sub taint_data { - my $params = shift; - return if !$params; + my @params = @_; + return if !@params; # Though this is a private function, it hasn't changed since 2004 and # should be safe to use, and prevents us from having to write it ourselves # or require another module to do it. - Test::Taint::_deeply_traverse(\&_delete_bad_keys, $params); - Test::Taint::taint_deeply($params); + Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params); + Test::Taint::taint_deeply(\@params); } sub _delete_bad_keys { @@ -79,6 +79,11 @@ sub validate { my ($self, $params, @keys) = @_; + + # If $params is defined but not a reference, then we weren't + # sent any parameters at all, and we're getting @keys where + # $params should be. + return ($self, undef) if (defined $params and !ref $params); # If @keys is not empty then we convert any named # parameters that have scalar values to arrayrefs === modified file 'template/en/default/global/setting-descs.none.tmpl' --- template/en/default/global/setting-descs.none.tmpl 2009-08-19 21:40:07 +0000 +++ template/en/default/global/setting-descs.none.tmpl 2010-07-21 15:58:46 +0000 @@ -40,6 +40,7 @@ "never" => "Never", "cc_unless_role" => "Only if I have no role on them", "lang" => "Language used in email", + "view_testopia" => "View the Testopia links", "quote_replies" => "Quote the associated comment when you click on its reply link", "quoted_reply" => "Quote the full comment", "simple_reply" => "Reference the comment number only",