#!/usr/bin/perl -wT # -*- Mode: perl; indent-tabs-mode: nil -*- # # 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 Testopia System. # # The Initial Developer of the Original Code is Greg Hendricks. # Portions created by Greg Hendricks are Copyright (C) 2006 # Novell. All Rights Reserved. # # Contributor(s): Greg Hendricks # Brian Kramer # Michael Hight # Garrett Braden # Andrew Nelson use strict; use lib qw(. lib); use Bugzilla::Constants; use lib (bz_locations()->{extensionsdir} . '/testopia/lib'); use Bugzilla; use Bugzilla::Util; use Bugzilla::Config; use Bugzilla::Error; use Testopia::Search; use Testopia::Table; use Testopia::Util; use Testopia::TestRun; use Testopia::Product; use Testopia::Classification; use Testopia::Environment; use Testopia::Environment::Element; use Testopia::Environment::Category; use Testopia::Environment::Property; use Testopia::Constants; use Data::Dumper; use JSON; Bugzilla->error_mode(ERROR_MODE_AJAX); Bugzilla->login(LOGIN_REQUIRED); our $cgi = Bugzilla->cgi; local our $vars = {}; local our $template = Bugzilla->template; $cgi->send_header; my $action = $cgi->param('action') || ''; local our $env_id = trim( Bugzilla->cgi->param('env_id') ) || ''; unless ( $env_id || $action ) { $template->process( "testopia/environment/choose.html.tmpl", $vars ) || ThrowTemplateError( $template->error() ); exit; } ########################### ### Environment Actions ### ########################### if ( $action eq 'add' ) { ThrowUserError( "testopia-create-denied", { 'object' => 'Test Environment' } ) unless Bugzilla->user->in_group('Testers'); my $name = $cgi->param('name'); my $product = $cgi->param('product_id'); my $env = Testopia::Environment->create( { name => $name, product_id => $product, } ); print "{'success': true, 'id': " . $env->id . "}"; } elsif ( $action eq 'delete' ) { my $env = Testopia::Environment->new($env_id); ThrowUserError( 'testopia-no-delete', { 'object' => $env } ) unless $env->candelete; $env->obliterate; print "{'success' :true}"; } elsif ( $action eq 'toggle' ) { my $env = Testopia::Environment->new($env_id); ThrowUserError( 'testopia-read-only', { 'object' => $env } ) unless $env->canedit; $env->toggle_archive; print "{'success': true}"; } elsif ( $action eq 'rename' ) { my $env = Testopia::Environment->new($env_id); ThrowUserError( "testopia-read-only", { 'object' => $env } ) unless $env->canedit; $env->set_name( $cgi->param('name') ); $env->update(); print "{'success': true}"; } elsif ( $action eq 'clone' ) { ThrowUserError( "testopia-create-denied", { 'object' => 'Test Environment' } ) unless Bugzilla->user->in_group('Testers'); my $env = Testopia::Environment->new($env_id); my $id = $env->clone( $cgi->param('name'), $cgi->param('product_id') ); print "{'success': true, 'id': " . $id . "}"; } ################################## ### Environment Editor Actions ### ################################## elsif ( $action eq 'edit' ) { my $type = $cgi->param('type'); my $id = $cgi->param('id'); for ($type) { /category/ && do { edit_category($id); }; /element/ && do { edit_element($id); }; /property/ && do { edit_property($id); }; /validexp/ && do { edit_validexp($id); }; } } elsif ( $action eq 'getChildren' ) { my $json = new JSON; my $id = $cgi->param('node'); my $type = $cgi->param('type'); my $tree_type = $cgi->param('tree_type'); trick_taint($id); trick_taint($type); if ( $tree_type =~ /env/ ) { for ($type) { /environment/ && do { get_env_categories($id) }; /category/ && do { get_mapped_category_elements( $env_id, $id ) }; /element/ && do { get_element_children( $id, 'NODRAG' ) }; /property/ && do { get_validexp_json( $id, $env_id ) }; } } else { for ($type) { /root/ && do { get_root($id) }; /product/ && do { get_categories($id); }; /category/ && do { get_category_element_json($id) }; /element/ && do { get_element_children($id) }; /property/ && do { get_validexp_json($id) }; } } } elsif ( $action eq 'remove_env_node' ) { my $env = Testopia::Environment->new($env_id); ThrowUserError( "testopia-read-only", { 'object' => $env } ) unless $env->canedit; foreach my $element_id (split(',', $cgi->param('element_ids'))){ detaint_natural($element_id); $env->delete_element($element_id); } print "{'success': true}"; } elsif ( $action eq 'create_child' ) { my $parent_id = $cgi->param('id'); my $type = $cgi->param('type'); detaint_natural($parent_id); $parent_id ||= 0; for ($type) { /category/ && do { add_category($parent_id); }; /element/ && do { add_element($parent_id); }; /child/ && do { add_element($parent_id, 'CHILD'); }; /property/ && do { add_property($parent_id); }; /value/ && do { add_validexp($parent_id); }; } } elsif ( $action eq 'delete_element' ) { my $id = $cgi->param('id'); my $type = $cgi->param('type'); trick_taint($id); for ($type) { /category/ && do { delete_category($id); }; /element/ && do { delete_element($id); }; /property/ && do { delete_property($id); }; /validexp/ && do { delete_validexp($id); }; } } elsif ( $action eq 'set_selected' ) { my $property_id = $cgi->param('id'); my $type = $cgi->param('type'); if ( $type =~ /exp/ ) { my $value = $cgi->param('value'); detaint_natural($property_id); trick_taint($value); my $env = Testopia::Environment->new($env_id); ThrowUserError( "testopia-read-only", { 'object' => $env } ) unless $env->canedit; my $property = Testopia::Environment::Property->new($property_id); my $elmnt_id = $property->element_id(); my $old = $env->get_value_selected( $env->id, $elmnt_id, $property->id ); $old = undef if $old eq $value; if ( $env->store_property_value( $property_id, $elmnt_id, $value ) == 0 ) { $env->update_property_value( $property_id, $elmnt_id, $value ); } print "{'success': true}"; } } elsif ( $action eq 'apply_element' ) { my $id = $cgi->param('id'); my $type = $cgi->param('type'); my $env = Testopia::Environment->new($env_id); ThrowUserError( "testopia-read-only", { 'object' => $env } ) unless $env->canedit; detaint_natural($id); if ( $type eq "element" ) { my $element = Testopia::Environment::Element->new($id); my $properties = $element->get_properties; if ( scalar @$properties == 0 ) { my $success = $env->store_property_value( 0, $id, "" ); } foreach my $property (@$properties) { my $success = $env->store_property_value( $property->id || 0, $id, "" ); } } #incoming type is a category else { my $category = Testopia::Environment::Category->new($id); foreach my $element (@{$category->get_parent_elements}) { $env->store_property_value( 0, $element->id, "" ); } } print "{'success': true}"; } else { display(); } sub display { detaint_natural($env_id); validate_test_id( $env_id, 'environment' ); my $env = Testopia::Environment->new($env_id); if ( !defined($env) ) { my $env = Testopia::Environment->new( { 'environment_id' => 0 } ); $vars->{'environment'} = $env; $vars->{'action'} = 'do_add'; $template->process( "testopia/environment/add.html.tmpl", $vars ) || print $template->error(); exit; } ThrowUserError( "testopia-read-only", { 'object' => $env } ) unless $env->canview; my $category = Testopia::Environment::Category->new( { 'id' => 0 } ); if ( Bugzilla->params->{'useclassification'} ) { $vars->{'allhaschild'} = $category->get_all_child_count; $vars->{'toplevel'} = Bugzilla->user->get_selectable_classifications; $vars->{'type'} = 'classification'; } else { $vars->{'toplevel'} = $category->get_env_product_list; $vars->{'type'} = 'product'; } $vars->{'user'} = Bugzilla->user; $vars->{'action'} = 'do_edit'; $vars->{'environment'} = $env; $template->process( "testopia/environment/show.html.tmpl", $vars ) || print $template->error(); } ########################### ### Tree Helper Methods ### ########################### sub get_root { my ($product_id) = (@_); my @products; detaint_natural($product_id); if ($product_id){ my $product = Testopia::Product->new($product_id); return unless $product->canedit; push @products, { id => $product->id, text => $product->name, type => 'product', cls => 'classification', leaf => scalar @{$product->environment_categories} > 0 ? JSON::false : JSON::true, draggable => JSON::false, }; } else { push @products, { id => 'GLOBAL', text => 'GLOBAL ATTRIBUTES', type => 'product', cls => 'classification', draggable => JSON::false, }; } my $json = new JSON; print $json->encode( \@products ); } sub get_categories { my ($product_id) = (@_); # Handle the special GLOBAL ATTRIBUTES product $product_id = $product_id eq 'GLOBAL' ? 0 : $product_id; if ($product_id) { my $product = Testopia::Product->new($product_id); return unless Bugzilla->user->can_see_product( $product->name ); } my $category = Testopia::Environment::Category->new( {} ); print $category->product_categories_to_json( $product_id ); } sub get_category_element_json { my ($id) = (@_); my $category = Testopia::Environment::Category->new($id); return unless $category->canview; my $fish = $category->elements_to_json("TRUE"); print $fish; } sub get_element_children { my ( $id, $draggable ) = (@_); my $element = Testopia::Environment::Element->new($id); return unless $element->canview; print $element->children_to_json($draggable); } sub get_env_categories { my ($id) = (@_); my $env = Testopia::Environment->new($id); return unless $env->canview; print $env->categories_to_json(); } sub get_mapped_category_elements { my ( $id, $cat_id ) = (@_); my $env = Testopia::Environment->new($id); return unless $env->canview; print $env->mapped_category_elements_to_json($cat_id); } sub get_validexp_json { my ( $id, $env_id ) = (@_); my $property = Testopia::Environment::Property->new($id); return unless $property->canview; print $property->value_to_json($env_id); } sub return_numeric_value { my ($value) = (@_); $value =~ s/\D+//; return $value; } ##################### #Edit Helper Methods# ##################### sub edit_category { my ($id) = (@_); my $name = $cgi->param('text'); trick_taint($name); my $category = Testopia::Environment::Category->new($id); ThrowUserError( "testopia-read-only", { 'object' => $category } ) unless $category->canedit; unless ( $category->set_name($name) ) { ThrowUserError('testopia-name-not-unique', {object => 'category'}); } print "{'success':true}"; } sub edit_element { my ($id) = (@_); my $name = $cgi->param('text'); trick_taint($name); my $element = Testopia::Environment::Element->new($id); ThrowUserError( "testopia-read-only", { 'object' => $element } ) unless $element->canedit; ThrowUserError('testopia-element-in-use') if ( $element->is_mapped() ); unless ( $element->set_name($name) ) { ThrowUserError('testopia-name-not-unique', {object => 'element'}); } print "{'success':true}"; } sub edit_property { my ($id) = (@_); my $name = $cgi->param('text'); trick_taint($name); my $property = Testopia::Environment::Property->new($id); ThrowUserError( "testopia-read-only", { 'object' => $property } ) unless $property->canedit; ThrowUserError('testopia-element-in-use') if ( $property->is_mapped() ); unless ( $property->set_name($name) ) { ThrowUserError('testopia-name-not-unique', {object => 'property'}); } print "{'success':true}"; } sub edit_validexp { my ($id) = (@_); my $property = Testopia::Environment::Property->new($id); ThrowUserError( "testopia-read-only", { 'object' => $property } ) unless $property->canedit; my $name = $cgi->param('text'); ThrowUserError('testopia-invalid-char') if $name =~ /\|/; trick_taint($name); my $oldname = $cgi->param('oldtext'); ThrowUserError('testopia-invalid-char') if $oldname =~ /\|/; trick_taint($oldname); my $expressions = $property->validexp(); if ($expressions =~ qr($name)){ ThrowUserError('testopia-name-not-unique', {object => 'property value'}); } # Gather existing values: my %values; foreach my $v ( split /\|/, $property->validexp ) { $values{$v} = 1; } # Delete old value: delete $values{$oldname}; my $exp = join( "|", keys %values ); # And add new one: $exp .= '|' . $name; $property->update_property_validexp($exp); print "{'success':true}"; } ################################### ### Create Child Helper Methods ### ################################### sub add_category { my ($id) = (@_); my $category = Testopia::Environment::Category->new( {} ); if ($id) { my $product = Testopia::Product->new($id); ThrowUserError( "testopia-read-only", { 'object' => $product } ) unless $product->canedit; } $category->{'product_id'} = $id; $category->{'name'} = 'New category ' . $category->new_category_count; my $new_cid = $category->store(); my $category_json = { text => $category->{'name'}, id => $new_cid, type => 'category', leaf => JSON::false, cls => 'category' }; my $json = new JSON; print "{success: true, env_object: "; print $json->encode($category_json); print "}"; } sub add_element { my ( $id, $ischild ) = (@_); my $element = Testopia::Environment::Element->new( {} ); # If we are adding this element as a child, $id is the parent element's id if ($ischild) { my $parent = Testopia::Environment::Element->new($id); ThrowUserError( "testopia-read-only", { 'object' => $parent } ) unless $parent->canedit; $element->{'env_category_id'} = $parent->env_category_id; } # Otherwise $id is the catagory id else { my $parent = Testopia::Environment::Category->new($id); ThrowUserError( "testopia-read-only", { 'object' => $parent } ) unless $parent->canedit; $element->{'env_category_id'} = $id; } $element->{'name'} = "New element " . $element->new_element_count; $element->{'parent_id'} = $ischild ? $id : 0; $element->{'isprivate'} = 0; my $new_eid = $element->store(); my $element_json = { text => $element->{'name'}, id => $new_eid, type => 'element', leaf => JSON::false, cls => 'element' }; my $json = new JSON; print "{success: true, env_object: "; print $json->encode($element_json); print "}"; } sub add_property { my ($id) = (@_); #add new property to element with id=$id my $property = Testopia::Environment::Property->new( {} ); my $parent = Testopia::Environment::Element->new($id); ThrowUserError( "testopia-read-only", { 'object' => $parent } ) unless $parent->canedit; $property->{'element_id'} = $id; $property->{'name'} = "New property " . $property->new_property_count; $property->{'validexp'} = ""; my $new_pid = $property->store(); my $property_json = { text => $property->{'name'}, id => $new_pid, type => 'property', leaf => JSON::false, cls => 'property', draggable => JSON::false, }; my $json = new JSON; print "{success: true, env_object: "; print $json->encode($property_json); print "}"; } sub add_validexp { my ($id) = (@_); my $property = Testopia::Environment::Property->new($id); ThrowUserError( "testopia-read-only", { 'object' => $property } ) unless $property->canedit; my $exp = $property->validexp; $exp ? $property->update_property_validexp( $exp . "|New value" ) : $property->update_property_validexp("New value"); my $value = { text => "New value", id => $id, type => "value", cls => "validexp", draggable => JSON::false, }; my $json = new JSON; print "{success: true, env_object: "; print $json->encode($value); print "}"; } ############################# ### Delete Helper Methods ### ############################# sub delete_category { my ($id) = (@_); my $category = Testopia::Environment::Category->new($id); ThrowUserError( "testopia-read-only", { 'object' => $category } ) unless $category->candelete; ThrowUserError('testopia-element-in-use') if ( $category->is_mapped() ); $category->obliterate; print "{success: true}"; } sub delete_element { my ($id) = (@_); my $element = Testopia::Environment::Element->new($id); ThrowUserError( "testopia-read-only", { 'object' => $element } ) unless $element->candelete; ThrowUserError('testopia-element-in-use') if ( $element->is_mapped() ); $element->obliterate; print "{success: true}"; } sub delete_property { my ($id) = (@_); my $property = Testopia::Environment::Property->new($id); ThrowUserError( "testopia-read-only", { 'object' => $property } ) unless $property->candelete; ThrowUserError('testopia-element-in-use') if ( $property->is_mapped() ); $property->obliterate; print "{success: true}"; } sub delete_validexp { my ($id) = (@_); my $property = Testopia::Environment::Property->new($id); ThrowUserError( "testopia-read-only", { 'object' => $property } ) unless $property->candelete; my %values; foreach my $v ( split /\|/, $property->validexp ) { $values{$v} = 1; } delete $values{$cgi->param("value")}; my $exp = join( "|", keys %values ); $property->update_property_validexp($exp); print "{success: true}"; }