#!/usr/bin/perl -wT # -*- Mode: perl; indent-tabs-mode: nil; cperl-indent-level: 4 -*- # # 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. # # The Initial Developer of the Original Code is Albert Ting # # Contributor(s): Albert Ting <alt@sonic.net> # Max Kanat-Alexander <mkanat@bugzilla.org> # # Direct any questions on this source code to mozilla.org use strict; use lib "."; use Bugzilla; use Bugzilla::Constants; use Bugzilla::Util; use Bugzilla::Error; use Bugzilla::Config qw($datadir); require "globals.pl"; my $cgi = Bugzilla->cgi; my $dbh = Bugzilla->dbh; my $template = Bugzilla->template; my $vars = {}; # TestClassification: just returns if the specified classification does exist # CheckClassification: same check, optionally emit an error text sub TestClassification ($) { my $cl = shift; my $dbh = Bugzilla->dbh; trick_taint($cl); # does the classification exist? my $sth = $dbh->prepare("SELECT name FROM classifications WHERE name=?"); $sth->execute($cl); my @row = $sth->fetchrow_array(); return $row[0]; } sub CheckClassification ($) { my $cl = shift; unless ($cl) { ThrowUserError("classification_not_specified"); } if (! TestClassification($cl)) { ThrowUserError("classification_doesnt_exist", { name => $cl }); } } sub LoadTemplate ($) { my $action = shift; $action =~ /(\w+)/; $action = $1; print $cgi->header(); $template->process("admin/classifications/$action.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } # # Preliminary checks: # Bugzilla->login(LOGIN_REQUIRED); print $cgi->header(); exists Bugzilla->user->groups->{'editclassifications'} || ThrowUserError("auth_failure", {group => "editclassifications", action => "edit", object => "classifications"}); ThrowUserError("auth_classification_not_enabled") unless Param("useclassification"); # # often used variables # my $action = trim($cgi->param('action') || ''); my $classification = trim($cgi->param('classification') || ''); trick_taint($classification); $vars->{'classification'} = $classification; # # action='' -> Show nice list of classifications # unless ($action) { my @classifications; # left join is tricky # - must select "classifications" fields if you want a REAL value # - must use "count(products.classification_id)" if you want a true # count. If you use count(classifications.id), it will return 1 for NULL # - must use "group by classifications.id" instead of # products.classification_id. Otherwise it won't look for all # classification ids, just the ones used by the products. my $sth = $dbh->prepare("SELECT classifications.id, classifications.name, classifications.description, COUNT(classification_id) AS total FROM classifications LEFT JOIN products ON classifications.id = products.classification_id " . $dbh->sql_group_by('classifications.id', 'classifications.name, classifications.description') . " ORDER BY name"); $sth->execute(); while (my ($id,$classification,$description,$total) = $sth->fetchrow_array()) { my $cl = {}; $cl->{'id'} = $id; $cl->{'classification'} = $classification; $cl->{'description'} = $description if (defined $description); $cl->{'total'} = $total; push(@classifications, $cl); } $vars->{'classifications'} = \@classifications; LoadTemplate("select"); } # # action='add' -> present form for parameters for new classification # # (next action will be 'new') # if ($action eq 'add') { LoadTemplate($action); } # # action='new' -> add classification entered in the 'action=add' screen # if ($action eq 'new') { unless ($classification) { ThrowUserError("classification_not_specified"); } if (TestClassification($classification)) { ThrowUserError("classification_already_exists", { name => $classification }); } my $description = trim($cgi->param('description') || ''); trick_taint($description); # Add the new classification. my $sth = $dbh->prepare("INSERT INTO classifications (name,description) VALUES (?,?)"); $sth->execute($classification,$description); # Make versioncache flush unlink "$datadir/versioncache"; LoadTemplate($action); } # # action='del' -> ask if user really wants to delete # # (next action would be 'delete') # if ($action eq 'del') { CheckClassification($classification); my $sth; # display some data about the classification $sth = $dbh->prepare("SELECT id, description FROM classifications WHERE name=?"); $sth->execute($classification); my ($classification_id, $description) = $sth->fetchrow_array(); ThrowUserError("classification_not_deletable") if ($classification_id eq "1"); $sth = $dbh->prepare("SELECT name FROM products WHERE classification_id=$classification_id"); $sth->execute(); ThrowUserError("classification_has_products") if ($sth->fetchrow_array()); $vars->{'description'} = $description if (defined $description); LoadTemplate($action); } # # action='delete' -> really delete the classification # if ($action eq 'delete') { CheckClassification($classification); my $sth; my $classification_id = get_classification_id($classification); if ($classification_id == 1) { ThrowUserError("cant_delete_default_classification", { name => $classification }); } # lock the tables before we start to change everything: $dbh->bz_lock_tables('classifications WRITE', 'products WRITE'); # delete $sth = $dbh->prepare("DELETE FROM classifications WHERE id=?"); $sth->execute($classification_id); # update products just in case $sth = $dbh->prepare("UPDATE products SET classification_id=1 WHERE classification_id=?"); $sth->execute($classification_id); $dbh->bz_unlock_tables(); unlink "$datadir/versioncache"; LoadTemplate($action); } # # action='edit' -> present the edit classifications from # # (next action would be 'update') # if ($action eq 'edit') { CheckClassification($classification); my @products = (); my $has_products = 0; my $sth; # get data of classification $sth = $dbh->prepare("SELECT id,description FROM classifications WHERE name=?"); $sth->execute($classification); my ($classification_id,$description) = $sth->fetchrow_array(); $vars->{'description'} = $description if (defined $description); $sth = $dbh->prepare("SELECT name,description FROM products WHERE classification_id=? ORDER BY name"); $sth->execute($classification_id); while ( my ($product, $prod_description) = $sth->fetchrow_array()) { my $prod = {}; $has_products = 1; $prod->{'name'} = $product; $prod->{'description'} = $prod_description if (defined $prod_description); push(@products, $prod); } $vars->{'products'} = \@products if ($has_products); LoadTemplate($action); } # # action='update' -> update the classification # if ($action eq 'update') { my $classificationold = trim($cgi->param('classificationold') || ''); my $description = trim($cgi->param('description') || ''); my $descriptionold = trim($cgi->param('descriptionold') || ''); my $checkvotes = 0; my $sth; CheckClassification($classificationold); my $classification_id = get_classification_id($classificationold); trick_taint($description); # Note that we got the $classification_id using $classificationold # above so it will remain static even after we rename the # classification in the database. $dbh->bz_lock_tables('classifications WRITE'); if ($classification ne $classificationold) { unless ($classification) { ThrowUserError("classification_not_specified"); } if (TestClassification($classification)) { ThrowUserError("classification_already_exists", { name => $classification }); } $sth = $dbh->prepare("UPDATE classifications SET name=? WHERE id=?"); $sth->execute($classification,$classification_id); $vars->{'updated_classification'} = 1; } if ($description ne $descriptionold) { $sth = $dbh->prepare("UPDATE classifications SET description=? WHERE id=?"); $sth->execute($description,$classification_id); $vars->{'updated_description'} = 1; } $dbh->bz_unlock_tables(); unlink "$datadir/versioncache"; LoadTemplate($action); } # # action='reclassify' -> reclassify products for the classification # if ($action eq 'reclassify') { CheckClassification($classification); my $sth; # display some data about the classification $sth = $dbh->prepare("SELECT id, description FROM classifications WHERE name=?"); $sth->execute($classification); my ($classification_id, $description) = $sth->fetchrow_array(); $vars->{'description'} = $description if (defined $description); $sth = $dbh->prepare("UPDATE products SET classification_id=? WHERE name=?"); if (defined $cgi->param('add_products')) { if (defined $cgi->param('prodlist')) { foreach my $prod ($cgi->param("prodlist")) { trick_taint($prod); $sth->execute($classification_id,$prod); } } } elsif (defined $cgi->param('remove_products')) { if (defined $cgi->param('myprodlist')) { foreach my $prod ($cgi->param("myprodlist")) { trick_taint($prod); $sth->execute(1,$prod); } } } elsif (defined $cgi->param('migrate_products')) { if (defined $cgi->param('clprodlist')) { foreach my $prod ($cgi->param("clprodlist")) { trick_taint($prod); $sth->execute($classification_id,$prod); } } } my @selected_products = (); my @class_products = (); $sth = $dbh->prepare("SELECT classifications.id, products.name, classifications.name, classifications.id > 1 as unknown FROM products INNER JOIN classifications ON classifications.id = products.classification_id ORDER BY unknown, products.name, classifications.name"); $sth->execute(); while ( my ($clid, $name, $clname) = $sth->fetchrow_array() ) { if ($clid == $classification_id) { push(@selected_products,$name); } else { my $cl = {}; if ($clid == 1) { $cl->{'name'} = "[$clname] $name"; } else { $cl->{'name'} = "$name [$clname]"; } $cl->{'value'} = $name; push(@class_products,$cl); } } $vars->{'selected_products'} = \@selected_products; $vars->{'class_products'} = \@class_products; LoadTemplate($action); } # # No valid action found # ThrowCodeError("action_unrecognized", $vars);