# -*- 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 Test Runner System. # # The Initial Developer of the Original Code is Maciej Maczynski. # Portions created by Maciej Maczynski are Copyright (C) 2001 # Maciej Maczynski. All Rights Reserved. # # Contributor(s): Garrett Braden =head1 NAME Bugzilla::Testopia::Environment::Xml - An XML representation of the Environment Object. =head1 DESCRIPTION This module is used to import and export environments via XML. It can parse an XML representation of a Testopia Environment and persist it to the database. An Environment.pm XML object can be initialized using new and passing it an XML scalar. It can also take two other parameters: $admin - a boolean that automatically will store the imported environment to the database. $max_depth - the max depth of child elements to import. Example: my $env_xml = Bugzilla::Testopia::Environment::Xml->new($xml, 1, 5); Other subroutines can be called on the object. For example: parse - takes the same three parameters as new store - stores the imported xml Environment object to the database Misc. Other: the module also contains two other valueable fields on it's hash: $self->{'message'} - running list of valueable information upon parsing and storing $self->{'error'} - running list of error messages upon parsing and storing One other method exists(check_new_items) to check if there are new Elements, Properties, Categories, and Selected Values and returns a scalar value report of the new items not present in the database. Import XML Environment Implementation Example: see tr_import_environment.cgi To export an environment by env_id to XML use export Example: my $xml = Bugzilla::Testopia::Environment::Xml->export($env_id); Export Environment XML Implementation Example: see tr_export_environment.cgi =head1 SYNOPSIS use Bugzilla::Testopia::Environment::Xml; =cut package Bugzilla::Testopia::Environment::Xml; #************************************************** Uses ****************************************************# use strict; use warnings; use CGI; use lib "."; use XML::Twig; use Bugzilla::Util; use Bugzilla::User; use Bugzilla::Config; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Product; use Bugzilla::Testopia::Util; use Bugzilla::Testopia::Environment; use Bugzilla::Testopia::Product; use Bugzilla::Testopia::Environment::Category; use Bugzilla::Testopia::Environment::Element; use Bugzilla::Testopia::Environment::Property; our constant $max_depth = 7; =head2 new Instantiates a new Bugzilla::Testopia::Environment::Xml object =cut sub new { my $invocant = shift; my $class = ref($invocant) || $invocant; my $self = {}; bless($self, $class); return $self->_init(@_); } =head2 _init Private constructor for the Bugzilla::Testopia::Environment::XML class =cut sub _init { my ($self, $xml, $admin, $depth) = @_; if (ref $xml eq 'HASH') { $self = $xml; } elsif($xml) { parse($self, $xml, $admin, $depth); } return undef unless (defined $xml); return $self; } =head2 Parse Environment =head2 DESCRIPTION Parses the Environment XML =cut sub parse() { my ($self, $xml, $admin, $depth) = @_; if($depth eq undef) { $self->{'max_depth'} = $max_depth; } else { $self->{'max_depth'} = $depth } if ($admin) { $self->{'message'} = "Importing XML Environment...
"; } else { $self->{'message'} = "Parsing and Validating XML Environment...
"; } $self->{'error'} = undef; if ($xml) { trick_taint($xml); } my $twig = XML::Twig->new(); $twig->parse($xml); my $root = $twig->root; # Checking if Product and Environment already exist. my $product_name = $root->{'att'}->{'product'}; my $product_id; if (lc($product_name) eq "--all--") { $self->{'message'} .= "..Using the --ANY PRODUCT-- PRODUCT.
"; $product_id = 0; } else { $self->{'message'} .= "..Checking if $product_name PRODUCT already exists..."; ($product_id) = Bugzilla::Testopia::Product->check_product_by_name($product_name); if ($product_id) { $self->{'message'} .= "EXISTS.
"; } else { $self->{'message'} .= "DOESN'T EXIST.
Importing XML Environment Failed!
"; $self->{'error'} .= "$product_name PRODUCT doesn't exist. Please be sure to use an existing product.
"; return 0; } } ($self->{'product_id'}) = $product_id; $self->{'product_name'} = $product_name; my $environment_name = $root->{'att'}->{'name'}; $self->{'name'} = $environment_name; $self->{'message'} .= "..Checking if $environment_name ENVIRONMENT NAME already exists for the $product_name PRODUCT..."; my $environment = Bugzilla::Testopia::Environment->new({}); my ($env_id) = $environment->check_environment($environment_name, $product_id); my $environment_id; if ($env_id < 1) { $self->{'message'} .= "DOESN'T EXIST
"; # Storing New Environment if Admin if ($admin) { $self->{'message'} .= "....Storing new $environment_name ENVIRONMENT NAME in the $self->{'product_name'} PRODUCT..."; $environment->{'name'} = $environment_name; ($environment_id) = Bugzilla::Testopia::Environment->store_environment_name($self->{'name'}, $product_id); $self->{'message'} .= "DONE.
"; } } else { ($environment_id) = $env_id; $self->{'message'} .= "EXISTS
Importing XML Environment Failed!
"; $self->{'error'} .= "$environment_name ENVIRONMENT NAME already exists for the $product_name PRODUCT. Please use another name."; return 0; } ($self->{'environment_id'}) = $environment_id; ($environment->{'product_id'}) = $self->{'product_id'}; # Parse recursively through the nested child elements. foreach my $twig_category ($root->children("category")) { my $category_name = $twig_category->{'att'}->{'name'}; # Makes sure to get the category_id by name and product_id my $category = Bugzilla::Testopia::Environment::Category->new({}); my ($cat_id) = $category->check_category($category_name, $product_id); my $category_id; # Checking if Categories already exist. $self->{'message'} .= "..Checking if $category_name CATEGORY already exists..."; if ($cat_id < 1) { $self->{'message'} .= "DOESN'T EXIST.
"; my $new_category_names = $self->{'new_category_names'}; push (@$new_category_names, $category_name); $self->{'new_category_names'} = $new_category_names; # Storing New Categories if Admin if ($admin) { $self->{'message'} .= "....Storing new $category_name CATEGORY in the $self->{'product_name'} PRODUCT..."; ($category->{'product_id'}) = $product_id; $category->{'name'} = $category_name; ($category_id) = $category->store(); $self->{'message'} .= "DONE.
"; } } else { ($category_id) = $cat_id; $self->{'message'} .= "EXISTS.
"; } foreach my $twig_element ($twig_category->children("element")) { my $element = $self->parse_child_elements(1, $category_id, $category_name, $twig_element, $admin); my $elements = $self->{'elements'}; push (@$elements, $element); $self->{'elements'} = $elements; } } if ($admin) { $self->{'message'} .= "Finished Importing XML Environment!
"; } else { $self->{'message'} .= "Finished Parsing and Validating XML Environment!
"; } } =head2 Parse Children Elements =head2 DESCRIPTION Parses through elements and their children elements recursively =cut sub parse_child_elements() { my ($self, $depth, $env_category_id, $category_name, $twig_element, $admin, $parent_element) = @_; if ($depth > $self->{'max_depth'}) { return; } $depth++; my $element_name = $twig_element->{'att'}->{'name'}; # Checking if Elements already exist. for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "Checking if $element_name ELEMENT already exists in the $category_name CATEGORY..."; my ($product_id) = $self->{'product_id'}; my $element = Bugzilla::Testopia::Environment::Element->new({}); my ($elem_id) = $element->check_element($element_name, $env_category_id); my $element_id; if ($elem_id < 1) { $self->{'message'} .= "DOESN'T EXIST.
"; my $new_category_elements = $self->{'new_category_elements'}; my $new_category_element = {'env_category_id' => $env_category_id, 'category_name' => $category_name, 'element_name' => $element_name}; push (@$new_category_elements, $new_category_element); $self->{'new_category_elements'} = $new_category_elements; # Storing New Elements if Admin if ($admin) { for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "..Storing new $element_name ELEMENT in the $category_name CATEGORY..."; ($element->{'env_category_id'}) = $env_category_id; $element->{'name'} = $element_name; ($element->{'product_id'}) = $self->{'product_id'}; $element->{'isprivate'} = 0; if ($parent_element) { $element->{'parent_id'} = $parent_element->{'element_id'}; } ($element_id) = $element->store(); $self->{'message'} .= "DONE.
"; } } else { ($element_id) = $elem_id; $self->{'message'} .= "EXISTS.
"; } ($element->{'element_id'}) = $element_id; ($element->{'env_category_id'}) = $env_category_id; $element->{'name'} = $element_name; ($element->{'parent_id'}) = $parent_element->{'parent_id'}; my @properties; foreach my $twig_property ($twig_element->children("property")) { my $property_name = $twig_property->{'att'}->{'name'}; # Checking if Properties already exist. for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "....Checking if $property_name PROPERTY already exists..."; my $property = Bugzilla::Testopia::Environment::Property->new({}); my ($prop_id) = $property->check_property($property_name, $element_id); my $property_id; if ($prop_id < 1) { $self->{'message'} .= "DOESN'T EXIST.
"; my $new_property_names = $self->{'new_property_names'}; push (@$new_property_names, $property_name); $self->{'new_property_names'} = $new_property_names; # Storing New Property if Admin if ($admin) { for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "......Storing new $property_name PROPERTY..."; $property->{'name'} = $property_name; ($property->{'element_id'}) = $element_id; ($property_id) = $property->store(); $self->{'message'} .= "DONE.
"; } } else { ($property_id) = $prop_id; $self->{'message'} .= "EXISTS.
"; } $property = Bugzilla::Testopia::Environment::Property->new($property_id); # Checking if new Selected Value and Valid Expression exist. my $validexp; if ($property) { $validexp = $property->validexp(); } my $value = $twig_property->field('value'); for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "........Checking if $value VALUE exists in the list of selectable values..."; if ( $validexp !~ m/$value/) { $self->{'message'} .= "DOESN'T EXIST.
"; if ($admin) { if (!defined($validexp)) { for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "..........Setting $value VALID EXPRESSION equal to the VALUE for the first time..."; $validexp = $value; } else { for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "..........Adding $value VALUE to the VALID EXPRESSION..."; $validexp = "$validexp | $value"; } $property->update_property_validexp($validexp); $self->{'message'} .= "DONE.
"; } else { my $new_validexp_values = $self->{'new_validexp_values'}; my $new_validexp_value = {'property_id' => $property_id, 'property_name' => $property_name, 'value' => $value}; push (@$new_validexp_values, $new_validexp_value); $self->{'new_validexp_values'} = $new_validexp_values; } } elsif (!defined($validexp)) { $self->{'message'} .= "VALID EXPRESSION DOESN'T EXIST YET.
"; } else { $self->{'message'} .= "EXISTS.
"; } if ($property_id && $admin) { for (my $i = 1; $i < $depth; $i++) { $self->{'message'} .= "...."; } $self->{'message'} .= "............Storing new VALUE SELECTED $value..."; my $environment = Bugzilla::Testopia::Environment->new($self->{'environment_id'}); $environment->store_property_value($property_id, $element_id, $value); $self->{'message'} .= "DONE.
"; } $property->{'value_selected'} = $value; push (@properties, $property); } my $elm_properties = $element->{'properties'}; push (@$elm_properties, @properties); if ($parent_element) { my $children = $parent_element->{'children'}; push (@$children, $element); $parent_element->{'children'} = $children; } foreach my $twig_element_child ($twig_element->children("element")) { $self->parse_child_elements($depth, $env_category_id, $category_name, $twig_element_child, $admin, $element); } return $element; } =head2 Checking Exists =head2 DESCRIPTION Checking if the Environment, Elements, Categories, and Properties already exist or not. =cut sub check_new_items() { my $self = shift; my $report; my $new_category_names = $self->{'new_category_names'}; foreach my $new_category_name (@$new_category_names) { $report .= "New $new_category_name CATEGORY.
"; } my $new_category_elements = $self->{'new_category_elements'}; foreach my $new_category_element (@$new_category_elements) { $report .= "New $new_category_element->{'element_name'} ELEMENT in the "; if (!$new_category_element->{'env_category_id'}){ $report .= "new "; } $report .= "$new_category_element->{'category_name'} CATEGORY.
"; } my $new_property_names = $self->{'new_property_names'}; foreach my $new_property_name (@$new_property_names) { $report .= "New $new_property_name PROPERTY.
"; } my $new_validexp_values = $self->{'new_validexp_values'}; foreach my $new_validexp_value (@$new_validexp_values) { $report .= "New $new_validexp_value->{'value'} VALUE for the "; if (!$new_validexp_value->{'property_id'}){ $report .= "new "; } $report .= "$new_validexp_value->{'property_name'} PROPERTY's selectable value list.
"; } return $report; } =head2 Store the Environment =head2 Description Store the Environment Name, Element-Category relationship, Element-Property relationship, Element-ChildElement relationship, Environment-Element-Property-Value. =cut sub store() { my $self = shift; $self->{'message'} .= "Storing new XML Environment..."; if (!$self->{'environment_id'}) { $self->{'environment_id'} = Bugzilla::Testopia::Environment->store_environment_name($self->{'name'}, $self->{'product_id'}); } my $environment = Bugzilla::Testopia::Environment->new($self); my $success = $environment->update(); if (!$success) { $self->{'message'} .= "ABORTED!
"; $self->{'error'} .= "Failed to store the Environment!
"; return 0; } $self->{'message'} .= "DONE!
"; return 1; } =head2 export =head2 Description Exports and Environment by env_id to a scalar XML value =cut sub export() { my $self = shift; my ($env_id) = @_; my $xml; my $environment = Bugzilla::Testopia::Environment->new($env_id); $xml = "" . "" . "get_environment_elements(); my $categories = {}; my $category_names = []; my $categorized_elements = []; my $elements = $environment->{'elements'}; my $used_elements = {}; foreach my $element (@$elements) { my $root_element = $self->get_root_parent($element); my $category_name = $root_element->cat_name(); $categorized_elements = $categories->{ $category_name }; for my $used_element (@$categorized_elements) { $used_elements->{ $used_element->{'element_id'} } = 1; } if (!$used_elements->{$root_element->{'element_id'}}) { push (@$categorized_elements, $root_element); $categories->{ $category_name } = $categorized_elements; } } foreach my $category_name (keys %$categories) { $xml .= ""; $elements = $categories->{$category_name}; foreach my $element (@$elements) { $element->get_children(); $xml .= $self->export_element_and_children(1, $element, $environment->{'environment_id'}); } $xml .= ""; } return "$xml"; } =head2 get_root_parent =head2 Description Helper sub that returns the root parent element in the environment of the passed in element =cut sub get_root_parent { my $self = shift; my ($element) = @_; my $parent = $element->get_parent(); if ($parent->type eq 'env_category') { return $element; } else { $self->get_root_parent($parent); } } =head2 export_element_and_children =head2 Description Exports Elements and their child elements to XML Recursively. =cut sub export_element_and_children() { my $self = shift; my ($depth, $element, $env_id) = @_; if ($depth > $max_depth) { return; } $depth++; my $xml = ""; my $properties = $element->{'properties'}; foreach my $property (@$properties) { my $value_selected = Bugzilla::Testopia::Environment->get_value_selected( $env_id, $element->{'element_id'}, $property->{'property_id'}); if (defined($value_selected)) { $xml .= "" . "$value_selected"; } } my $children = $element->{'children'}; foreach my $child_element (@$children) { $child_element->get_children(); $xml .= $self->export_element_and_children($depth, $child_element, $env_id); } $xml .= ""; return $xml; } 1;