summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbugreport%peshkin.net <>2004-08-21 06:49:17 +0200
committerbugreport%peshkin.net <>2004-08-21 06:49:17 +0200
commit88d26275229b5f52f435130496169766313c87b7 (patch)
treec71532045500b6e2335b1e5e2512d10b8846551b
parent2f9f28d0bfb1f321d1548844a41aaf2d51789695 (diff)
downloadbugzilla-88d26275229b5f52f435130496169766313c87b7.tar.gz
bugzilla-88d26275229b5f52f435130496169766313c87b7.tar.xz
Bug 224208 Add a higher level of categorization (.ie departments, locations, etc.)
patch by Albert Ting r=joel, glob a=myk
-rwxr-xr-xBugzilla/Bug.pm10
-rw-r--r--Bugzilla/Search.pm17
-rwxr-xr-xbuglist.cgi6
-rw-r--r--bugzilla.dtd4
-rwxr-xr-xchecksetup.pl19
-rwxr-xr-xcolchange.cgi9
-rw-r--r--defparams.pl17
-rwxr-xr-xeditclassifications.cgi391
-rwxr-xr-xeditproducts.cgi225
-rwxr-xr-xenter_bug.cgi57
-rw-r--r--globals.pl89
-rw-r--r--js/productform.js76
-rwxr-xr-xlong_list.cgi9
-rwxr-xr-xquery.cgi23
-rwxr-xr-xreport.cgi8
-rw-r--r--template/en/default/admin/classifications/add.html.tmpl45
-rw-r--r--template/en/default/admin/classifications/del.html.tmpl60
-rw-r--r--template/en/default/admin/classifications/delete.html.tmpl31
-rw-r--r--template/en/default/admin/classifications/edit.html.tmpl70
-rw-r--r--template/en/default/admin/classifications/new.html.tmpl32
-rw-r--r--template/en/default/admin/classifications/reclassify.html.tmpl84
-rw-r--r--template/en/default/admin/classifications/select.html.tmpl70
-rw-r--r--template/en/default/admin/classifications/update.html.tmpl37
-rw-r--r--template/en/default/admin/products/groupcontrol/edit.html.tmpl2
-rw-r--r--template/en/default/bug/edit.html.tmpl5
-rw-r--r--template/en/default/bug/show-multiple.html.tmpl10
-rw-r--r--template/en/default/filterexceptions.pl1
-rw-r--r--template/en/default/global/choose-classification.html.tmpl65
-rw-r--r--template/en/default/global/field-descs.none.tmpl1
-rw-r--r--template/en/default/global/useful-links.html.tmpl11
-rw-r--r--template/en/default/global/user-error.html.tmpl38
-rw-r--r--template/en/default/search/form.html.tmpl106
-rw-r--r--template/en/default/search/search-advanced.html.tmpl35
-rw-r--r--template/en/default/search/search-help.html.tmpl7
-rw-r--r--template/en/default/search/search-report-graph.html.tmpl8
-rw-r--r--template/en/default/search/search-report-select.html.tmpl2
-rw-r--r--template/en/default/search/search-report-table.html.tmpl8
-rw-r--r--template/en/default/search/search-specific.html.tmpl22
38 files changed, 1607 insertions, 103 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 8863f5432..d5aa5fd17 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -48,6 +48,7 @@ sub fields {
# Keep this ordering in sync with bugzilla.dtd
my @fields = qw(bug_id alias creation_ts short_desc delta_ts
reporter_accessible cclist_accessible
+ classification_id classification
product component version rep_platform op_sys
bug_status resolution
bug_file_loc status_whiteboard keywords
@@ -137,7 +138,8 @@ sub initBug {
my $query = "
SELECT
- bugs.bug_id, alias, bugs.product_id, products.name, version,
+ bugs.bug_id, alias, products.classification_id, classifications.name,
+ bugs.product_id, products.name, version,
rep_platform, op_sys, bug_status, resolution, priority,
bug_severity, bugs.component_id, components.name, assigned_to,
reporter, bug_file_loc, short_desc, target_milestone,
@@ -147,8 +149,9 @@ sub initBug {
reporter_accessible, cclist_accessible,
estimated_time, remaining_time
from bugs left join votes using(bug_id),
- products, components
+ classifications, products, components
where bugs.bug_id = $bug_id
+ AND classifications.id = products.classification_id
AND products.id = bugs.product_id
AND components.id = bugs.component_id
group by bugs.bug_id";
@@ -159,7 +162,8 @@ sub initBug {
if ((@row = &::FetchSQLData()) && $self->{'who'}->can_see_bug($bug_id)) {
my $count = 0;
my %fields;
- foreach my $field ("bug_id", "alias", "product_id", "product", "version",
+ foreach my $field ("bug_id", "alias", "classification_id", "classification",
+ "product_id", "product", "version",
"rep_platform", "op_sys", "bug_status", "resolution",
"priority", "bug_severity", "component_id", "component",
"assigned_to", "reporter", "bug_file_loc", "short_desc",
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 91785963f..23bb34eae 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -96,6 +96,11 @@ sub init {
push @wherepart, "bugs.product_id = map_products.id";
}
+ if (lsearch($fieldsref, 'map_classifications.name') >= 0) {
+ push @supptables, "classifications AS map_classifications";
+ push @wherepart, "map_products.classification_id = map_classifications.id";
+ }
+
if (lsearch($fieldsref, 'map_components.name') >= 0) {
push @supptables, "components AS map_components";
push @wherepart, "bugs.component_id = map_components.id";
@@ -152,7 +157,7 @@ sub init {
my @legal_fields = ("product", "version", "rep_platform", "op_sys",
"bug_status", "resolution", "priority", "bug_severity",
- "assigned_to", "reporter", "component",
+ "assigned_to", "reporter", "component", "classification",
"target_milestone", "bug_group");
foreach my $field ($params->param()) {
@@ -761,6 +766,16 @@ sub init {
$term);
},
+ "^classification,(?!changed)" => sub {
+ # Generate the restriction condition
+ $f = $ff = "classifications.name";
+ $funcsbykey{",$t"}->();
+ $term = build_subselect("map_products.classification_id",
+ "classifications.id",
+ "classifications",
+ $term);
+ },
+
"^keywords," => sub {
&::GetVersionTable();
my @list;
diff --git a/buglist.cgi b/buglist.cgi
index eaca25612..3c575d2b8 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -458,6 +458,7 @@ DefineColumn("short_desc" , "bugs.short_desc" , "Summary"
DefineColumn("status_whiteboard" , "bugs.status_whiteboard" , "Status Summary" );
DefineColumn("component" , "map_components.name" , "Component" );
DefineColumn("product" , "map_products.name" , "Product" );
+DefineColumn("classification" , "map_classifications.name" , "Classification" );
DefineColumn("version" , "bugs.version" , "Version" );
DefineColumn("op_sys" , "bugs.op_sys" , "OS" );
DefineColumn("target_milestone" , "bugs.target_milestone" , "Target Milestone" );
@@ -554,6 +555,11 @@ if (grep('relevance', @displaycolumns) && !$fulltext) {
my @selectcolumns = ("bug_id", "bug_severity", "priority", "bug_status",
"resolution");
+# if using classification, we also need to look in product.classification_id
+if (Param("useclassification")) {
+ push (@selectcolumns,"product");
+}
+
# remaining and actual_time are required for precentage_complete calculation:
if (lsearch(\@displaycolumns, "percentage_complete") >= 0) {
push (@selectcolumns, "remaining_time");
diff --git a/bugzilla.dtd b/bugzilla.dtd
index aecf9920d..82ccfff18 100644
--- a/bugzilla.dtd
+++ b/bugzilla.dtd
@@ -5,7 +5,7 @@
maintainer CDATA #REQUIRED
exporter CDATA #IMPLIED
>
-<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, product, component, version, rep_platform, op_sys, bug_status, resolution?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, votes?, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time)?, groups*, long_desc*, attachment*)?)>
+<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, votes?, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time)?, groups*, long_desc*, attachment*)?)>
<!ATTLIST bug
error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
>
@@ -16,6 +16,8 @@
<!ELEMENT exporter (#PCDATA)>
<!ELEMENT urlbase (#PCDATA)>
<!ELEMENT bug_status (#PCDATA)>
+<!ELEMENT classification_id (#PCDATA)>
+<!ELEMENT classification (#PCDATA)>
<!ELEMENT product (#PCDATA)>
<!ELEMENT priority (#PCDATA)>
<!ELEMENT version (#PCDATA)>
diff --git a/checksetup.pl b/checksetup.pl
index 703358cec..807fa9016 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -1786,10 +1786,17 @@ $table{logincookies} =
index(lastused)';
+$table{classifications} =
+ 'id smallint not null auto_increment primary key,
+ name varchar(64) not null,
+ description mediumtext,
+
+ unique(name)';
$table{products} =
'id smallint not null auto_increment primary key,
name varchar(64) not null,
+ classification_id smallint not null default 1,
description mediumtext,
milestoneurl tinytext not null,
disallownew tinyint not null,
@@ -2153,6 +2160,7 @@ sub AddFDef ($$$) {
# be created with their associated schema change.
AddFDef("bug_id", "Bug \#", 1);
AddFDef("short_desc", "Summary", 1);
+AddFDef("classification", "Classification", 1);
AddFDef("product", "Product", 1);
AddFDef("version", "Version", 1);
AddFDef("rep_platform", "Platform", 1);
@@ -4021,6 +4029,7 @@ AddField("profiles", "extern_id", "varchar(64)");
AddGroup('tweakparams', 'Can tweak operating parameters');
AddGroup('editusers', 'Can edit or disable users');
AddGroup('creategroups', 'Can create and destroy groups.');
+AddGroup('editclassifications', 'Can create, destroy, and edit classifications.');
AddGroup('editcomponents', 'Can create, destroy, and edit components.');
AddGroup('editkeywords', 'Can create, destroy, and edit keywords.');
AddGroup('admin', 'Administrators');
@@ -4388,6 +4397,16 @@ if (GetFieldDef('bugs', 'short_desc')->[2]) { # if it allows nulls
$dbh->do("UPDATE groups SET last_changed = NOW() WHERE name = 'admin'");
+# 2003-10-24 - alt@sonic.net, bug 224208
+# Support classification level and make sure there is a default classification
+AddField('products', 'classification_id', 'smallint DEFAULT 1');
+$sth = $dbh->prepare("SELECT name FROM classifications WHERE id=1");
+$sth->execute;
+if (! $sth->rows) {
+ $dbh->do("INSERT INTO classifications (id,name,description) " .
+ "VALUES(1,'Unclassified','Unassigned to any classifications')");
+}
+
#
# Final checks...
diff --git a/colchange.cgi b/colchange.cgi
index 8d3ee49da..03d5388bf 100755
--- a/colchange.cgi
+++ b/colchange.cgi
@@ -47,8 +47,13 @@ my $cgi = Bugzilla->cgi;
my @masterlist = ("opendate", "changeddate", "bug_severity", "priority",
"rep_platform", "assigned_to", "assigned_to_realname",
"reporter", "reporter_realname", "bug_status",
- "resolution", "product", "component", "version", "op_sys",
- "votes");
+ "resolution");
+
+if (Param("useclassification")) {
+ push(@masterlist, "classification");
+}
+
+push(@masterlist, ("product", "component", "version", "op_sys", "votes"));
if (Param("usebugaliases")) {
unshift(@masterlist, "alias");
diff --git a/defparams.pl b/defparams.pl
index 8260be978..c0e608a4d 100644
--- a/defparams.pl
+++ b/defparams.pl
@@ -321,6 +321,23 @@ sub find_languages {
},
{
+ name => 'useclassification',
+ desc => 'If this is on, Bugzilla will associate each product with a ' .
+ 'specific classification. But you must have "editclassification" ' .
+ 'permissions enabled in order to edit classifications',
+ type => 'b',
+ default => 0
+ },
+
+ {
+ name => 'showallproducts',
+ desc => 'If this is on and useclassification is set, Bugzilla will add a' .
+ '"All" link in the "New Bug" page to list all available products',
+ type => 'b',
+ default => 0
+ },
+
+ {
name => 'makeproductgroups',
desc => 'If this is on, Bugzilla will associate a bug group with each ' .
'product in the database, and use it for querying bugs.',
diff --git a/editclassifications.cgi b/editclassifications.cgi
new file mode 100755
index 000000000..c1186f792
--- /dev/null
+++ b/editclassifications.cgi
@@ -0,0 +1,391 @@
+#!/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>
+#
+# Direct any questions on this source code to mozilla.org
+
+use strict;
+use lib ".";
+
+use Bugzilla;
+use Bugzilla::Constants;
+require "CGI.pl";
+require "globals.pl";
+
+my $cgi = Bugzilla->cgi;
+my $dbh = Bugzilla->dbh;
+
+use vars qw ($template $vars);
+
+# TestClassification: just returns if the specified classification does exists
+# CheckClassification: same check, optionally emit an error text
+
+sub TestClassification ($) {
+ my $cl = shift;
+
+ 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();
+
+ThrowUserError("auth_cant_edit_classifications") unless UserInGroup("editclassifications");
+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
+ GROUP BY classifications.id
+ 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') {
+ 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 "data/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->do("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->do("UNLOCK TABLES");
+
+ unlink "data/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->do("LOCK TABLES classifications WRITE");
+
+ if ($description ne $descriptionold) {
+ $sth = $dbh->prepare("UPDATE classifications
+ SET description=?
+ WHERE id=?");
+ $sth->execute($description,$classification_id);
+ $vars->{'updated_description'} = 1;
+ }
+
+ if ($classification ne $classificationold) {
+ unless ($classification) {
+ $dbh->do("UNLOCK TABLES");
+ ThrowUserError("classification_not_specified")
+ }
+
+ if (TestClassification($classification)) {
+ $dbh->do("UNLOCK TABLES");
+ ThrowUserError("classification_already_exists", { name => $classification });
+ }
+ $sth = $dbh->prepare("UPDATE classifications
+ SET name=? WHERE id=?");
+ $sth->execute($classification,$classification_id);
+ $vars->{'updated_classification'} = 1;
+ }
+ $dbh->do("UNLOCK TABLES");
+
+ unlink "data/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,classifications
+ WHERE 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);
diff --git a/editproducts.cgi b/editproducts.cgi
index bd71bdd6d..74a62166e 100755
--- a/editproducts.cgi
+++ b/editproducts.cgi
@@ -86,20 +86,80 @@ sub CheckProduct ($)
}
}
+# TestClassification: just returns if the specified classification does exists
+# CheckClassification: same check, optionally emit an error text
+
+sub TestClassification ($)
+{
+ my $cl = shift;
+
+ # does the classification exist?
+ SendSQL("SELECT name
+ FROM classifications
+ WHERE name=" . SqlQuote($cl));
+ return FetchOneColumn();
+}
+
+sub CheckClassification ($)
+{
+ my $cl = shift;
+
+ # do we have a classification?
+ unless ($cl) {
+ print "Sorry, you haven't specified a classification.";
+ PutTrailer();
+ exit;
+ }
+
+ unless (TestClassification $cl) {
+ print "Sorry, classification '$cl' does not exist.";
+ PutTrailer();
+ exit;
+ }
+}
+
+sub CheckClassificationProduct ($$)
+{
+ my $cl = shift;
+ my $prod = shift;
+
+ CheckClassification($cl);
+ CheckProduct($prod);
+
+ # does the classification exist?
+ SendSQL("SELECT products.name
+ FROM products,classifications
+ WHERE products.name=" . SqlQuote($prod) .
+ " AND classifications.name=" . SqlQuote($cl));
+ my $res = FetchOneColumn();
+
+ unless ($res) {
+ print "Sorry, classification->product '$cl'->'$prod' does not exist.";
+ PutTrailer();
+ exit;
+ }
+}
+
#
# Displays the form to edit a products parameters
#
-sub EmitFormElements ($$$$$$$$)
+sub EmitFormElements ($$$$$$$$$)
{
- my ($product, $description, $milestoneurl, $disallownew,
+ my ($classification, $product, $description, $milestoneurl, $disallownew,
$votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone)
= @_;
$product = value_quote($product);
$description = value_quote($description);
+ if (Param('useclassification')) {
+ print " <TH ALIGN=\"right\">Classification:</TH>\n";
+ print " <TD><b>",html_quote($classification),"</b></TD>\n";
+ print "</TR><TR>\n";
+ }
+
print " <TH ALIGN=\"right\">Product:</TH>\n";
print " <TD><INPUT SIZE=64 MAXLENGTH=64 NAME=\"product\" VALUE=\"$product\"></TD>\n";
print "</TR><TR>\n";
@@ -197,23 +257,83 @@ unless (UserInGroup("editcomponents")) {
#
# often used variables
#
+my $classification = trim($::FORM{classification} || '');
my $product = trim($::FORM{product} || '');
my $action = trim($::FORM{action} || '');
my $headerdone = 0;
my $localtrailer = "<A HREF=\"editproducts.cgi\">edit</A> more products";
+my $classhtmlvarstart = "";
+my $classhtmlvar = "";
+
+if (Param('useclassification') && (defined $classification)) {
+ $classhtmlvar = "&classification=" . url_quote($classification);
+ $classhtmlvarstart = "?classification=" . url_quote($classification);
+}
+
+if (Param('useclassification') && (defined $classification)) {
+ $localtrailer .= ", <A HREF=\"editproducts.cgi" . $classhtmlvarstart . "\">edit</A> in this classification";
+}
+
+#
+# product = '' -> Show nice list of products
+#
+
+if (Param('useclassification')) {
+ unless ($classification) {
+ PutHeader("Select classification");
+
+ SendSQL("SELECT classifications.name,classifications.description,COUNT(classification_id) as total
+ FROM classifications
+ LEFT JOIN products ON classifications.id=products.classification_id
+ GROUP BY classifications.id
+ ORDER BY name");
+ print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
+ print " <TH ALIGN=\"left\">Edit products of ...</TH>\n";
+ print " <TH ALIGN=\"left\">Description</TH>\n";
+ print " <TH ALIGN=\"left\">Total</TH>\n";
+ print "</TR>";
+ while ( MoreSQLData() ) {
+ my ($classification, $description, $count) = FetchSQLData();
+ $description ||= "<FONT COLOR=\"red\">missing</FONT>";
+ print "<TR>\n";
+ print " <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?classification=",url_quote($classification),"\"><B>$classification</B></A></TD>\n";
+ print " <TD VALIGN=\"top\">$description</TD>\n";
+ $count ||= "none";
+ print " <TD VALIGN=\"top\">$count</TD>\n";
+ }
+ print "</TR></TABLE>\n";
+
+ PutTrailer();
+ exit;
+ }
+}
+
#
# action='' -> Show nice list of products
#
unless ($action) {
- PutHeader("Select product");
+ if (Param('useclassification')) {
+ PutHeader("Select product in " . $classification);
+ } else {
+ PutHeader("Select product");
+ }
- SendSQL("SELECT products.name,description,disallownew,
+ my $query="SELECT products.name,products.description,disallownew,
votesperuser,maxvotesperbug,votestoconfirm,COUNT(bug_id)
- FROM products LEFT JOIN bugs ON products.id = bugs.product_id
- GROUP BY products.name
- ORDER BY products.name");
+ FROM products";
+ if (Param('useclassification')) {
+ $query .= ",classifications";
+ }
+ $query .= " LEFT JOIN bugs ON products.id = bugs.product_id";
+ if (Param('useclassification')) {
+ $query .= " WHERE classifications.name=" .
+ SqlQuote($classification) .
+ " AND classifications.id=products.classification_id";
+ }
+ $query .= " GROUP BY products.name ORDER BY products.name";
+ SendSQL($query);
print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">\n";
print " <TH ALIGN=\"left\">Edit product ...</TH>\n";
print " <TH ALIGN=\"left\">Description</TH>\n";
@@ -231,19 +351,19 @@ unless ($action) {
$disallownew = $disallownew ? 'closed' : 'open';
$bugs ||= 'none';
print "<TR>\n";
- print " <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=edit&product=", url_quote($product), "\"><B>$product</B></A></TD>\n";
+ print " <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=edit&product=", url_quote($product), $classhtmlvar,"\"><B>$product</B></A></TD>\n";
print " <TD VALIGN=\"top\">$description</TD>\n";
print " <TD VALIGN=\"top\">$disallownew</TD>\n";
print " <TD VALIGN=\"top\" ALIGN=\"right\">$votesperuser</TD>\n";
print " <TD VALIGN=\"top\" ALIGN=\"right\">$maxvotesperbug</TD>\n";
print " <TD VALIGN=\"top\" ALIGN=\"right\">$votestoconfirm</TD>\n";
print " <TD VALIGN=\"top\" ALIGN=\"right\">$bugs</TD>\n";
- print " <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=del&product=", url_quote($product), "\">Delete</A></TD>\n";
+ print " <TD VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=del&product=", url_quote($product), $classhtmlvar, "\">Delete</A></TD>\n";
print "</TR>";
}
print "<TR>\n";
print " <TD VALIGN=\"top\" COLSPAN=7>Add a new product</TD>\n";
- print " <TD VALIGN=\"top\" ALIGN=\"middle\"><A HREF=\"editproducts.cgi?action=add\">Add</A></TD>\n";
+ print " <TD VALIGN=\"top\" ALIGN=\"center\"><A HREF=\"editproducts.cgi?action=add&classification=", url_quote($classification),"\">Add</A></TD>\n";
print "</TR></TABLE>\n";
PutTrailer();
@@ -262,12 +382,15 @@ unless ($action) {
if ($action eq 'add') {
PutHeader("Add product");
+ if (Param('useclassification')) {
+ CheckClassification($classification);
+ }
#print "This page lets you add a new product to bugzilla.\n";
print "<FORM METHOD=POST ACTION=editproducts.cgi>\n";
print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
- EmitFormElements('', '', '', 0, 0, 10000, 0, "---");
+ EmitFormElements($classification,'', '', '', 0, 0, 10000, 0, "---");
print "</TR><TR>\n";
print " <TH ALIGN=\"right\">Version:</TH>\n";
@@ -282,6 +405,7 @@ if ($action eq 'add') {
print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n";
print "<INPUT TYPE=HIDDEN NAME='subcategory' VALUE='-All-'>\n";
print "<INPUT TYPE=HIDDEN NAME='open_name' VALUE='All Open'>\n";
+ print "<INPUT TYPE=HIDDEN NAME='classification' VALUE='",html_quote($classification),"'>\n";
print "</FORM>";
my $other = $localtrailer;
@@ -349,10 +473,15 @@ if ($action eq 'new') {
$votestoconfirm ||= 0;
my $defaultmilestone = $::FORM{defaultmilestone} || "---";
+ my $classification_id = 1;
+ if (Param('useclassification')) {
+ $classification_id = get_classification_id($classification);
+ }
+
# Add the new product.
SendSQL("INSERT INTO products ( " .
"name, description, milestoneurl, disallownew, votesperuser, " .
- "maxvotesperbug, votestoconfirm, defaultmilestone" .
+ "maxvotesperbug, votestoconfirm, defaultmilestone, classification_id" .
" ) VALUES ( " .
SqlQuote($product) . "," .
SqlQuote($description) . "," .
@@ -366,9 +495,11 @@ if ($action eq 'new') {
SqlQuote($votesperuser) . "," .
SqlQuote($maxvotesperbug) . "," .
SqlQuote($votestoconfirm) . "," .
- SqlQuote($defaultmilestone) . ")");
+ SqlQuote($defaultmilestone) . "," .
+ SqlQuote($classification_id) . ")");
SendSQL("SELECT LAST_INSERT_ID()");
my $product_id = FetchOneColumn();
+
SendSQL("INSERT INTO versions ( " .
"value, product_id" .
" ) VALUES ( " .
@@ -455,7 +586,8 @@ if ($action eq 'new') {
PutTrailer($localtrailer,
"<a href=\"editproducts.cgi?action=add\">add</a> a new product",
"<a href=\"editcomponents.cgi?action=add&product=" .
- url_quote($product) . "\">add</a> components to this new product");
+ url_quote($product) . $classhtmlvar .
+ "\">add</a> components to this new product");
exit;
}
@@ -470,15 +602,23 @@ if ($action eq 'new') {
if ($action eq 'del') {
PutHeader("Delete product");
CheckProduct($product);
+ my $classification_id=1;
+ if (Param('useclassification')) {
+ CheckClassificationProduct($classification,$product);
+ $classification_id = get_classification_id($classification);
+ }
# display some data about the product
- SendSQL("SELECT id, description, milestoneurl, disallownew
- FROM products
- WHERE name=" . SqlQuote($product));
- my ($product_id, $description, $milestoneurl, $disallownew) = FetchSQLData();
+ SendSQL("SELECT classifications.description,
+ products.id, products.description, milestoneurl, disallownew
+ FROM products,classifications
+ WHERE products.name=" . SqlQuote($product) .
+ " AND classifications.id=" . SqlQuote($classification_id));
+ my ($class_description, $product_id, $prod_description, $milestoneurl, $disallownew) = FetchSQLData();
my $milestonelink = $milestoneurl ? "<a href=\"$milestoneurl\">$milestoneurl</a>"
: "<font color=\"red\">missing</font>";
- $description ||= "<FONT COLOR=\"red\">description missing</FONT>";
+ $prod_description ||= "<FONT COLOR=\"red\">description missing</FONT>";
+ $class_description ||= "<FONT COLOR=\"red\">description missing</FONT>";
$disallownew = $disallownew ? 'closed' : 'open';
print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0>\n";
@@ -486,13 +626,23 @@ if ($action eq 'del') {
print " <TH VALIGN=\"top\" ALIGN=\"left\">Part</TH>\n";
print " <TH VALIGN=\"top\" ALIGN=\"left\">Value</TH>\n";
+ if (Param('useclassification')) {
+ print "</TR><TR>\n";
+ print " <TD VALIGN=\"top\">Classification:</TD>\n";
+ print " <TD VALIGN=\"top\">$classification</TD>\n";
+
+ print "</TR><TR>\n";
+ print " <TD VALIGN=\"top\">Description:</TD>\n";
+ print " <TD VALIGN=\"top\">$class_description</TD>\n";
+ }
+
print "</TR><TR>\n";
print " <TD VALIGN=\"top\">Product:</TD>\n";
print " <TD VALIGN=\"top\">$product</TD>\n";
print "</TR><TR>\n";
print " <TD VALIGN=\"top\">Description:</TD>\n";
- print " <TD VALIGN=\"top\">$description</TD>\n";
+ print " <TD VALIGN=\"top\">$prod_description</TD>\n";
if (Param('usetargetmilestone')) {
print "</TR><TR>\n";
@@ -548,7 +698,7 @@ if ($action eq 'del') {
#
if (Param('usetargetmilestone')) {
print "</TD>\n</TR><TR>\n";
- print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editmilestones.cgi?product=", url_quote($product), "\">Edit milestones:</A></TH>\n";
+ print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editmilestones.cgi?product=", url_quote($product), $classhtmlvar, "\">Edit milestones:</A></TH>\n";
print " <TD>";
SendSQL("SELECT value
FROM milestones
@@ -603,6 +753,8 @@ one.";
print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"product\" VALUE=\"" .
html_quote($product) . "\">\n";
+ print "<INPUT TYPE=HIDDEN NAME=\"classification\" VALUE=\"" .
+ html_quote($classification) . "\">\n";
print "</FORM>";
PutTrailer($localtrailer);
@@ -706,25 +858,32 @@ if ($action eq 'delete') {
if ($action eq 'edit') {
PutHeader("Edit product");
CheckProduct($product);
+ my $classification_id=1;
+ if (Param('useclassification')) {
+ CheckClassificationProduct($classification,$product);
+ $classification_id = get_classification_id($classification);
+ }
# get data of product
- SendSQL("SELECT id,description,milestoneurl,disallownew,
+ SendSQL("SELECT classifications.description,
+ products.id,products.description,milestoneurl,disallownew,
votesperuser,maxvotesperbug,votestoconfirm,defaultmilestone
- FROM products
- WHERE name=" . SqlQuote($product));
- my ($product_id,$description, $milestoneurl, $disallownew,
+ FROM products,classifications
+ WHERE products.name=" . SqlQuote($product) .
+ " AND classifications.id=" . SqlQuote($classification_id));
+ my ($class_description, $product_id,$prod_description, $milestoneurl, $disallownew,
$votesperuser, $maxvotesperbug, $votestoconfirm, $defaultmilestone) =
FetchSQLData();
print "<FORM METHOD=POST ACTION=editproducts.cgi>\n";
print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
- EmitFormElements($product, $description, $milestoneurl,
+ EmitFormElements($classification, $product, $prod_description, $milestoneurl,
$disallownew, $votesperuser, $maxvotesperbug,
$votestoconfirm, $defaultmilestone);
print "</TR><TR VALIGN=top>\n";
- print " <TH ALIGN=\"right\"><A HREF=\"editcomponents.cgi?product=", url_quote($product), "\">Edit components:</A></TH>\n";
+ print " <TH ALIGN=\"right\"><A HREF=\"editcomponents.cgi?product=", url_quote($product), $classhtmlvar, "\">Edit components:</A></TH>\n";
print " <TD>";
SendSQL("SELECT name,description
FROM components
@@ -744,7 +903,7 @@ if ($action eq 'edit') {
print "</TD>\n</TR><TR>\n";
- print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editversions.cgi?product=", url_quote($product), "\">Edit versions:</A></TH>\n";
+ print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editversions.cgi?product=", url_quote($product), $classhtmlvar, "\">Edit versions:</A></TH>\n";
print " <TD>";
SendSQL("SELECT value
FROM versions
@@ -767,7 +926,7 @@ if ($action eq 'edit') {
#
if (Param('usetargetmilestone')) {
print "</TD>\n</TR><TR>\n";
- print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editmilestones.cgi?product=", url_quote($product), "\">Edit milestones:</A></TH>\n";
+ print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editmilestones.cgi?product=", url_quote($product), $classhtmlvar, "\">Edit milestones:</A></TH>\n";
print " <TD>";
SendSQL("SELECT value
FROM milestones
@@ -787,7 +946,7 @@ if ($action eq 'edit') {
}
print "</TD>\n</TR><TR>\n";
- print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=editgroupcontrols&product=", url_quote($product), "\">Edit Group Access Controls</A></TH>\n";
+ print " <TH ALIGN=\"right\" VALIGN=\"top\"><A HREF=\"editproducts.cgi?action=editgroupcontrols&product=", url_quote($product), $classhtmlvar,"\">Edit Group Access Controls</A></TH>\n";
print "<TD>\n";
SendSQL("SELECT id, name, isactive, entry, membercontrol, othercontrol, canedit " .
"FROM groups, " .
@@ -820,10 +979,12 @@ if ($action eq 'edit') {
print "</TD>\n</TR></TABLE>\n";
+ print "<INPUT TYPE=HIDDEN NAME=\"classification\" VALUE=\"" .
+ html_quote($classification) . "\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"productold\" VALUE=\"" .
html_quote($product) . "\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"descriptionold\" VALUE=\"" .
- html_quote($description) . "\">\n";
+ html_quote($prod_description) . "\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"milestoneurlold\" VALUE=\"" .
html_quote($milestoneurl) . "\">\n";
print "<INPUT TYPE=HIDDEN NAME=\"disallownewold\" VALUE=\"$disallownew\">\n";
@@ -843,7 +1004,6 @@ if ($action eq 'edit') {
exit;
}
-
#
# action='updategroupcontrols' -> update the product
#
@@ -1325,6 +1485,7 @@ if ($action eq 'editgroupcontrols') {
}
$vars->{'header_done'} = $headerdone;
$vars->{'product'} = $product;
+ $vars->{'classification'} = $classification;
$vars->{'groups'} = \@groups;
$vars->{'const'} = {
'CONTROLMAPNA' => CONTROLMAPNA,
diff --git a/enter_bug.cgi b/enter_bug.cgi
index cbe2929aa..a0291fae5 100755
--- a/enter_bug.cgi
+++ b/enter_bug.cgi
@@ -53,6 +53,7 @@ use vars qw(
$userid
%versions
$proddesc
+ $classdesc
);
# If we're using bug groups to restrict bug entry, we need to know who the
@@ -67,12 +68,47 @@ if (!defined $product) {
GetVersionTable();
Bugzilla->login();
- my %products;
+ if ( ! Param('useclassification') ) {
+ # just pick the default one
+ $::FORM{'classification'}=(keys %::classdesc)[0];
+ }
+
+ if (!defined $::FORM{'classification'}) {
+ my %classdesc;
+ my %classifications;
+
+ foreach my $c (GetSelectableClassifications()) {
+ $classdesc{$c} = $::classdesc{$c};
+ $classifications{$c} = $::classifications{$c};
+ }
+
+ my $classification_size = scalar(keys %classdesc);
+ if ($classification_size == 0) {
+ ThrowUserError("no_products");
+ }
+ elsif ($classification_size > 1) {
+ $vars->{'classdesc'} = \%classdesc;
+ $vars->{'classifications'} = \%classifications;
+
+ $vars->{'target'} = "enter_bug.cgi";
+ $vars->{'format'} = $::FORM{'format'};
+
+ print "Content-type: text/html\n\n";
+ $template->process("global/choose-classification.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
+ }
+ $::FORM{'classification'} = (keys %classdesc)[0];
+ $::MFORM{'classification'} = [$::FORM{'classification'}];
+ }
+ my %products;
foreach my $p (@enterable_products) {
- if (CanEnterProduct($p))
- {
- $products{$p} = $::proddesc{$p};
+ if (CanEnterProduct($p)) {
+ if (IsInClassification($::FORM{'classification'},$p) ||
+ $::FORM{'classification'} eq "__all") {
+ $products{$p} = $::proddesc{$p};
+ }
}
}
@@ -81,7 +117,19 @@ if (!defined $product) {
ThrowUserError("no_products");
}
elsif ($prodsize > 1) {
+ my %classifications;
+ if ( ! Param('useclassification') ) {
+ @{$classifications{"all"}} = keys %products;
+ }
+ elsif ($::FORM{'classification'} eq "__all") {
+ %classifications = %::classifications;
+ } else {
+ $classifications{$::FORM{'classification'}} =
+ $::classifications{$::FORM{'classification'}};
+ }
$vars->{'proddesc'} = \%products;
+ $vars->{'classifications'} = \%classifications;
+ $vars->{'classdesc'} = \%::classdesc;
$vars->{'target'} = "enter_bug.cgi";
$vars->{'format'} = $cgi->param('format');
@@ -252,6 +300,7 @@ SendSQL("SELECT name, description, login_name, realname
ORDER BY name");
while (MoreSQLData()) {
my ($name, $description, $login, $realname) = FetchSQLData();
+
push @components, {
name => $name,
description => $description,
diff --git a/globals.pl b/globals.pl
index 9872dff70..829417e44 100644
--- a/globals.pl
+++ b/globals.pl
@@ -55,6 +55,7 @@ sub globals_pl_sillyness {
$zz = @main::legal_versions;
$zz = @main::milestoneurl;
$zz = %main::proddesc;
+ $zz = %main::classdesc;
$zz = @main::prodmaxvotes;
$zz = $main::template;
$zz = $main::userid;
@@ -184,6 +185,19 @@ sub GenerateVersionTable {
$carray{$c} = 1;
}
+ SendSQL("SELECT products.name, classifications.name " .
+ "FROM products, classifications " .
+ "WHERE classifications.id = products.classification_id " .
+ "ORDER BY classifications.name");
+ while (@line = FetchSQLData()) {
+ my ($p,$c) = (@line);
+ if (!defined $::classifications{$c}) {
+ $::classifications{$c} = [];
+ }
+ my $ref = $::classifications{$c};
+ push @$ref, $p;
+ }
+
my $dotargetmilestone = 1; # This used to check the param, but there's
# enough code that wants to pretend we're using
# target milestones, even if they don't get
@@ -191,6 +205,13 @@ sub GenerateVersionTable {
# about them anyway.
my $mpart = $dotargetmilestone ? ", milestoneurl" : "";
+
+ SendSQL("select name, description from classifications ORDER BY name");
+ while (@line = FetchSQLData()) {
+ my ($n, $d) = (@line);
+ $::classdesc{$n} = $d;
+ }
+
SendSQL("select name, description, votesperuser, disallownew$mpart from products ORDER BY name");
while (@line = FetchSQLData()) {
my ($p, $d, $votesperuser, $dis, $u) = (@line);
@@ -275,8 +296,10 @@ sub GenerateVersionTable {
'*::legal_bug_status', '*::legal_resolution']));
print $fh (Data::Dumper->Dump([\@::settable_resolution, \%::proddesc,
+ \%::classifications, \%::classdesc,
\@::enterable_products, \%::prodmaxvotes],
['*::settable_resolution', '*::proddesc',
+ '*::classifications', '*::classdesc',
'*::enterable_products', '*::prodmaxvotes']));
if ($dotargetmilestone) {
@@ -494,6 +517,24 @@ sub CanEditProductId {
return (!defined($result));
}
+sub IsInClassification {
+ my ($classification,$productname) = @_;
+
+ if (! Param('useclassification')) {
+ return 1;
+ } else {
+ my $query = "SELECT classifications.name " .
+ "FROM products,classifications " .
+ "WHERE products.classification_id=classifications.id ";
+ $query .= "AND products.name = " . SqlQuote($productname);
+ PushGlobalSQLState();
+ SendSQL($query);
+ my ($ret) = FetchSQLData();
+ PopGlobalSQLState();
+ return ($ret eq $classification);
+ }
+}
+
#
# This function determines if a user can enter bugs in the named
# product.
@@ -527,18 +568,21 @@ sub GetEnterableProducts {
return (@products);
}
+
#
# This function returns an alphabetical list of product names to which
# the user can enter bugs. If the $by_id parameter is true, also retrieves IDs
# and pushes them onto the list as id, name [, id, name...] for easy slurping
# into a hash by the calling code.
sub GetSelectableProducts {
- my ($by_id) = @_;
+ my ($by_id,$by_classification) = @_;
my $extra_sql = $by_id ? "id, " : "";
- my $query = "SELECT $extra_sql name " .
- "FROM products " .
+ my $extra_from_sql = $by_classification ? ", classifications" : "";
+
+ my $query = "SELECT $extra_sql products.name " .
+ "FROM products $extra_from_sql " .
"LEFT JOIN group_control_map " .
"ON group_control_map.product_id = products.id ";
if (Param('useentrygroupdefault')) {
@@ -551,7 +595,13 @@ sub GetSelectableProducts {
$query .= "AND group_id NOT IN(" .
join(',', values(%{Bugzilla->user->groups})) . ") ";
}
- $query .= "WHERE group_id IS NULL ORDER BY name";
+ $query .= "WHERE group_id IS NULL ";
+ if ($by_classification) {
+ $query .= "AND classifications.id = products.classification_id ";
+ $query .= "AND classifications.name = ";
+ $query .= SqlQuote($by_classification) . " ";
+ }
+ $query .= "ORDER BY name";
PushGlobalSQLState();
SendSQL($query);
my @products = ();
@@ -609,6 +659,18 @@ sub GetSelectableProductHash {
return $selectables;
}
+#
+# This function returns an alphabetical list of classifications that has products the user can enter bugs.
+sub GetSelectableClassifications {
+ my @selectable_classes = ();
+
+ foreach my $c (keys %::classdesc) {
+ if ( scalar(GetSelectableProducts(0,$c)) > 0) {
+ push(@selectable_classes,$c);
+ }
+ }
+ return (@selectable_classes);
+}
sub GetFieldDefs {
my $extra = "";
@@ -740,6 +802,25 @@ sub DBNameToIdAndCheck {
{ name => $name }, "abort");
}
+sub get_classification_id {
+ my ($classification) = @_;
+ PushGlobalSQLState();
+ SendSQL("SELECT id FROM classifications WHERE name = " . SqlQuote($classification));
+ my ($classification_id) = FetchSQLData();
+ PopGlobalSQLState();
+ return $classification_id;
+}
+
+sub get_classification_name {
+ my ($classification_id) = @_;
+ die "non-numeric classification_id '$classification_id' passed to get_classification_name"
+ unless ($classification_id =~ /^\d+$/);
+ PushGlobalSQLState();
+ SendSQL("SELECT name FROM classifications WHERE id = $classification_id");
+ my ($classification) = FetchSQLData();
+ PopGlobalSQLState();
+ return $classification;
+}
diff --git a/js/productform.js b/js/productform.js
index 0be0d4971..7cf07d732 100644
--- a/js/productform.js
+++ b/js/productform.js
@@ -21,6 +21,72 @@
/* this file contains functions to update form controls based on a
* collection of javascript arrays containing strings */
+/* selectClassification reads the selection from f.classification and updates
+ * f.product accordingly
+ * - f: a form containing classification, product, component, varsion and
+ * target_milestone select boxes.
+ * globals (3vil!):
+ * - prods, indexed by classification name
+ * - first_load: boolean, specifying if it is the first time we load
+ * the query page.
+ * - last_sel: saves our last selection list so we know what has
+ * changed, and optimize for additions.
+ */
+function selectClassification(classfield, product, component, version, milestone) {
+ /* this is to avoid handling events that occur before the form
+ * itself is ready, which could happen in buggy browsers.
+ */
+ if (!classfield) {
+ return;
+ }
+
+ /* if this is the first load and nothing is selected, no need to
+ * merge and sort all components; perl gives it to us sorted.
+ */
+ if ((first_load) && (classfield.selectedIndex == -1)) {
+ first_load = false;
+ return;
+ }
+
+ /* don't reset first_load as done in selectProduct. That's because we
+ want selectProduct to handle the first_load attribute
+ */
+
+ /* - sel keeps the array of classifications we are selected.
+ * - merging says if it is a full list or just a list of classifications
+ * that were added to the current selection.
+ */
+ var merging = false;
+ var sel = Array();
+
+ /* if nothing selected, pick all */
+ var findall = classfield.selectedIndex == -1;
+ sel = get_selection(classfield, findall, false);
+ if (!findall) {
+ /* save sel for the next invocation of selectClassification() */
+ var tmp = sel;
+
+ /* this is an optimization: if we have just added classifications to an
+ * existing selection, no need to clear the form controls and add
+ * everybody again; just merge the new ones with the existing
+ * options.
+ */
+ if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
+ sel = fake_diff_array(sel, last_sel);
+ merging = true;
+ }
+ last_sel = tmp;
+ }
+ /* save original options selected */
+ var saved_prods = get_selection(product, false, true);
+
+ /* do the actual fill/update, reselect originally selected options */
+ updateSelect(prods, sel, product, merging);
+ restoreSelection(product, saved_prods);
+ selectProduct(product, component, version, milestone);
+}
+
+
/* selectProduct reads the selection from the product control and
* updates version, component and milestone controls accordingly.
*
@@ -68,7 +134,15 @@ function selectProduct(product, component, version, milestone) {
/* if nothing selected, pick all */
var findall = product.selectedIndex == -1;
- sel = get_selection(product, findall, false);
+ if (useclassification) {
+ /* update index based on the complete product array */
+ sel = get_selection(product, findall, true);
+ for (var i=0; i<sel.length; i++) {
+ sel[i] = prods[sel[i]];
+ }
+ } else {
+ sel = get_selection(product, findall, false);
+ }
if (!findall) {
/* save sel for the next invocation of selectProduct() */
var tmp = sel;
diff --git a/long_list.cgi b/long_list.cgi
index 757d00239..515f4c226 100755
--- a/long_list.cgi
+++ b/long_list.cgi
@@ -43,6 +43,7 @@ my $generic_query = "
SELECT
bugs.bug_id,
COALESCE(bugs.alias, ''),
+ classifications.name,
products.name,
bugs.version,
bugs.rep_platform,
@@ -63,9 +64,10 @@ my $generic_query = "
bugs.estimated_time,
bugs.remaining_time,
date_format(creation_ts,'%Y.%m.%d %H:%i')
- FROM bugs,profiles assign,profiles report, products, components
+ FROM bugs,profiles assign,profiles report, classifications, products, components
WHERE assign.userid = bugs.assigned_to AND report.userid = bugs.reporter
- AND bugs.product_id=products.id AND bugs.component_id=components.id";
+ AND bugs.product_id=products.id AND bugs.component_id=components.id
+ AND products.classification_id = classifications.id";
my $buglist = $cgi->param('buglist') ||
$cgi->param('bug_id') ||
@@ -81,7 +83,8 @@ foreach my $bug_id (split(/[:,]/, $buglist)) {
my %bug;
my @row = FetchSQLData();
- foreach my $field ("bug_id", "alias", "product", "version", "rep_platform",
+ foreach my $field ("bug_id", "alias", "classification", "product",
+ "version", "rep_platform",
"op_sys", "bug_status", "resolution", "priority",
"bug_severity", "component", "assigned_to", "reporter",
"bug_file_loc", "short_desc", "target_milestone",
diff --git a/query.cgi b/query.cgi
index bacb5cd3c..c22a11471 100755
--- a/query.cgi
+++ b/query.cgi
@@ -127,7 +127,7 @@ sub PrefillForm {
# Nothing must be undef, otherwise the template complains.
foreach my $name ("bug_status", "resolution", "assigned_to",
"rep_platform", "priority", "bug_severity",
- "product", "reporter", "op_sys",
+ "classification", "product", "reporter", "op_sys",
"component", "version", "chfield", "chfieldfrom",
"chfieldto", "chfieldvalue", "target_milestone",
"email", "emailtype", "emailreporter",
@@ -274,9 +274,24 @@ for (my $i = 0; $i < @products; ++$i) {
# Assign hash back to product array.
$products[$i] = \%product;
}
-
$vars->{'product'} = \@products;
+# Create data structures representing each classification
+if (Param('useclassification')) {
+ my @classifications = ();
+
+ foreach my $c (sort(GetSelectableClassifications())) {
+ # Create hash to hold attributes for each classification.
+ my %classification = (
+ 'name' => $c,
+ 'products' => [ GetSelectableProducts(0,$c) ]
+ );
+ # Assign hash back to classification array.
+ push @classifications, \%classification;
+ }
+ $vars->{'classification'} = \@classifications;
+}
+
# We use 'component_' because 'component' is a Template Toolkit reserved word.
$vars->{'component_'} = \@components;
@@ -300,7 +315,9 @@ push @chfields, "[Bug creation]";
# This is what happens when you have variables whose definition depends
# on the DB schema, and then the underlying schema changes...
foreach my $val (@::log_columns) {
- if ($val eq 'product_id') {
+ if ($val eq 'classification_id') {
+ $val = 'classification';
+ } elsif ($val eq 'product_id') {
$val = 'product';
} elsif ($val eq 'component_id') {
$val = 'component';
diff --git a/report.cgi b/report.cgi
index 66060723d..fac2f0e75 100755
--- a/report.cgi
+++ b/report.cgi
@@ -113,6 +113,7 @@ $columns{'bug_status'} = "bugs.bug_status";
$columns{'resolution'} = "bugs.resolution";
$columns{'component'} = "map_components.name";
$columns{'product'} = "map_products.name";
+$columns{'classification'} = "map_classifications.name";
$columns{'version'} = "bugs.version";
$columns{'op_sys'} = "bugs.op_sys";
$columns{'votes'} = "bugs.votes";
@@ -134,8 +135,13 @@ $columns{''} = "42217354";
|| ThrowCodeError("report_axis_invalid", {fld => "z", val => $tbl_field});
my @axis_fields = ($row_field, $col_field, $tbl_field);
-
my @selectnames = map($columns{$_}, @axis_fields);
+# add product if person is requesting classification
+if (lsearch(\@axis_fields,"classification") >= 0) {
+ if (lsearch(\@axis_fields,"product") < 0) {
+ push(@selectnames,($columns{'product'}));
+ }
+}
# Clone the params, so that Bugzilla::Search can modify them
my $params = new Bugzilla::CGI($cgi);
diff --git a/template/en/default/admin/classifications/add.html.tmpl b/template/en/default/admin/classifications/add.html.tmpl
new file mode 100644
index 000000000..d6a7c3880
--- /dev/null
+++ b/template/en/default/admin/classifications/add.html.tmpl
@@ -0,0 +1,45 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Add new classification"
+%]
+
+<form method=post action="editclassifications.cgi">
+ <table border=0 cellpadding=4 cellspacing=0>
+ <tr>
+ <th align="right">Classification:</th>
+ <td><input size=64 maxlength=64 name="classification"></td>
+ </tr>
+ <tr>
+ <th align="right">Description:</th>
+ <td><textarea rows=4 cols=64 wrap=virtual name="description"></textarea></td>
+ </tr>
+ </table>
+ <hr>
+ <input type=submit value="Add">
+ <input type=hidden name="action" value="new">
+</FORM>
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/del.html.tmpl b/template/en/default/admin/classifications/del.html.tmpl
new file mode 100644
index 000000000..008971562
--- /dev/null
+++ b/template/en/default/admin/classifications/del.html.tmpl
@@ -0,0 +1,60 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Delete classification"
+%]
+
+<table border=1 cellpadding=4 cellspacing=0>
+<tr bgcolor="#6666ff">
+ <th valign="top" align="left">Part</th>
+ <th valign="top" align="left">Value</th>
+
+</tr><tr>
+ <td valign="top">Classification:</td>
+ <td valign="top">[% classification FILTER html %]</td>
+
+</tr><tr>
+ <td valign="top">Description:</td>
+ <td valign="top">
+ [% IF description %]
+ [% description FILTER html %]
+ [% ELSE %]
+ <font color="red">description missing</font>
+ [% END %]
+ </td>
+
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+<p>Do you really want to delete this classification?<p>
+<form method=post action="editclassifications.cgi">
+ <input type=submit value="Yes, delete">
+ <input type=hidden name="action" value="delete">
+ <input type=hidden name="classification" value="[% classification FILTER html %]">
+</form>
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/delete.html.tmpl b/template/en/default/admin/classifications/delete.html.tmpl
new file mode 100644
index 000000000..b2ec26cbb
--- /dev/null
+++ b/template/en/default/admin/classifications/delete.html.tmpl
@@ -0,0 +1,31 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Classification deleted"
+%]
+
+Classification [% classification FILTER html %] deleted.<br>
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/edit.html.tmpl b/template/en/default/admin/classifications/edit.html.tmpl
new file mode 100644
index 000000000..ebc16e82e
--- /dev/null
+++ b/template/en/default/admin/classifications/edit.html.tmpl
@@ -0,0 +1,70 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit classification"
+%]
+
+<form method=post action="editclassifications.cgi">
+ <table border=0 cellpadding=4 cellspacing=0>
+ <tr>
+ <th align="right">Classification:</th>
+ <td><input size=64 maxlength=64 name="classification" value="[% classification FILTER html %]"></TD>
+ </tr>
+ <tr>
+ <th align="right">Description:</th>
+ <td><textarea rows=4 cols=64 name="description">[% description FILTER html %]</textarea></TD>
+ </tr>
+ <tr valign=top>
+ <th align="right"><a href="editproducts.cgi?classification=[% classification FILTER html %]">Edit products</a></th>
+ <td>
+ [% IF products AND products.size > 0 %]
+ <table>
+ [% FOREACH product = products %]
+ <tr>
+ <th align=right valign=top>[% product.name FILTER html %]</th>
+ <td valign=top>
+ [% IF product.description %]
+ [% product.description FILTER html %]
+ [% ELSE %]
+ <font color="red">description missing</font>
+ [% END %]
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ [% ELSE %]
+ <font color="red">none</font>
+ [% END %]
+ </td>
+ </tr>
+ </table>
+
+ <input type=hidden name="classificationold" value="[% classification FILTER html %]">
+ <input type=hidden name="descriptionold" value="[% description FILTER html %]">
+ <input type=hidden name="action" value="update">
+ <input type=submit value="Update">
+</form>
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/new.html.tmpl b/template/en/default/admin/classifications/new.html.tmpl
new file mode 100644
index 000000000..c8046b898
--- /dev/null
+++ b/template/en/default/admin/classifications/new.html.tmpl
@@ -0,0 +1,32 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Adding new classification"
+%]
+
+OK, done.
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>,
+<a href="editproducts.cgi?action=add&amp;classification=[% classification FILTER html %]">add</a> products to this new classification,
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/reclassify.html.tmpl b/template/en/default/admin/classifications/reclassify.html.tmpl
new file mode 100644
index 000000000..d860f67cf
--- /dev/null
+++ b/template/en/default/admin/classifications/reclassify.html.tmpl
@@ -0,0 +1,84 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Reclassify products"
+%]
+
+[% main_classification = classification %]
+
+<form method=post action="editclassifications.cgi">
+ <table border=0 cellpadding=4 cellspacing=0>
+ <tr>
+ <td valign="top">Classification:</td>
+ <td valign="top" colspan=3>[% main_classification FILTER html %]</td>
+
+ </tr><tr>
+ <td valign="top">Description:</td>
+ <td valign="top" colspan=3>
+ [% IF description %]
+ [% description FILTER html %]
+ [% ELSE %]
+ <font color="red">description missing</font>
+ [% END %]
+ </td>
+
+ </tr><tr>
+ <td valign="top">Products:</td>
+ <td valign="top">Products</td>
+ <td></td>
+ <td valign="top">[% main_classification FILTER html %] Products</td>
+
+ </tr><tr>
+ <td></td>
+ <td valign="top">
+ <select name="prodlist" id="prodlist" multiple="multiple" size="20">
+ [% FOREACH cl = class_products %]
+ <option value="[% cl.value FILTER html %]">
+ [% cl.name FILTER html %]
+ </option>
+ [% END %]
+ </select></td>
+
+ <td align="center">
+ <input type=submit value=" Add &gt;&gt; " name="add_products"><br><br>
+ <input type=submit value="&lt;&lt; Remove" name="remove_products">
+ </td>
+
+ <td valign="middle" rowspan=2>
+ <select name="myprodlist" id="myprodlist" multiple="multiple" size="20">
+ [% FOREACH product = selected_products %]
+ <option value="[% product FILTER html %]">
+ [% product FILTER html %]
+ </option>
+ [% END %]
+ </select></td>
+ </tr>
+ </table>
+
+ <input type=hidden name="action" value="reclassify">
+ <input type=hidden name="classification" value="[% main_classification FILTER html %]">
+</form>
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>,
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/select.html.tmpl b/template/en/default/admin/classifications/select.html.tmpl
new file mode 100644
index 000000000..5908375d2
--- /dev/null
+++ b/template/en/default/admin/classifications/select.html.tmpl
@@ -0,0 +1,70 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Select classification"
+%]
+
+[% filt_classification = classification FILTER html %]
+
+<table border=1 cellpadding=4 cellspacing=0>
+ <tr bgcolor="#6666ff">
+ <th align="left">Edit Classification ...</th>
+ <th align="left">Description</th>
+ <th align="left">Products</th>
+ <th align="left">Action</th>
+ </tr>
+
+ [% FOREACH cl = classifications %]
+ <tr>
+ <td valign="top"><a href="editclassifications.cgi?action=edit&amp;classification=[% cl.classification FILTER html %]"><b>[% cl.classification FILTER html %]</b></a></td>
+ <td valign="top">
+ [% IF cl.description %]
+ [% cl.description FILTER html %]
+ [% ELSE %]
+ <font color="red">none</font>
+ [% END %]
+ </td>
+ [% IF (cl.id == 1) %]
+ <td valign="top">[% cl.total FILTER html %]</td>
+ [% ELSE %]
+ <td valign="top"><a href="editclassifications.cgi?action=reclassify&amp;classification=[% cl.classification FILTER html %]">reclassify ([% cl.total FILTER html %])</a></td>
+ [% END %]
+
+ [%# don't allow user to delete the default id. %]
+ [% IF (cl.id == 1) %]
+ <td valign="top">&nbsp;</td>
+ [% ELSE %]
+ <td valign="top"><a href="editclassifications.cgi?action=del&amp;classification=[% cl.classification FILTER html %]">delete</a></td>
+ [% END %]
+ </tr>
+ [% END %]
+
+ <tr>
+ <td valign="top" colspan=3>Add a new classification</td>
+ <td valign="top" align="center"><a href="editclassifications.cgi?action=add">Add</a></td>
+ </tr>
+</table>
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/classifications/update.html.tmpl b/template/en/default/admin/classifications/update.html.tmpl
new file mode 100644
index 000000000..3ad7bbc69
--- /dev/null
+++ b/template/en/default/admin/classifications/update.html.tmpl
@@ -0,0 +1,37 @@
+[%# 1.0@bugzilla.org %]
+[%# 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 Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Albert Ting <alt@sonic.net>
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Update classification"
+%]
+
+[% IF updated_description %]
+ Updated description.<br>
+[% END %]
+
+[% IF updated_classification %]
+ Updated classification name.<br>
+[% END %]
+
+<p>Back to the <a href="./">main [% terms.bugs %] page</a>
+or <a href="editclassifications.cgi"> edit</a> more classifications.
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/products/groupcontrol/edit.html.tmpl b/template/en/default/admin/products/groupcontrol/edit.html.tmpl
index 85816398b..6bd30040d 100644
--- a/template/en/default/admin/products/groupcontrol/edit.html.tmpl
+++ b/template/en/default/admin/products/groupcontrol/edit.html.tmpl
@@ -22,6 +22,7 @@
[% PROCESS global/variables.none.tmpl %]
[% filt_product = product FILTER html %]
+[% filt_classification = classification FILTER html %]
[% PROCESS global/header.html.tmpl
title = "Edit Group Controls for '$filt_product'"
%]
@@ -29,6 +30,7 @@
<form method="post" action="editproducts.cgi">
<input type="hidden" name="action" value="updategroupcontrols">
<input type="hidden" name="product" value="[% filt_product %]">
+ <input type="hidden" name="classification" value="[% filt_classification %]">
<table id="form" cellspacing="0" cellpadding="4" border="1">
<tr bgcolor="#6666ff">
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index f4c68bb4d..b898afee1 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -119,6 +119,11 @@
<table cellspacing="1" cellpadding="1" border="0">
<tr>
<td align="right">
+ [% IF Param('useclassification') %]
+ [% IF bug.classification_id != "1" %]
+ <b>[[% bug.classification FILTER html %]]</b>
+ [% END %]
+ [% END %]
<b>[% terms.Bug %]#:</b>
</td>
<td>
diff --git a/template/en/default/bug/show-multiple.html.tmpl b/template/en/default/bug/show-multiple.html.tmpl
index 6b48feb00..8cf86dd5f 100644
--- a/template/en/default/bug/show-multiple.html.tmpl
+++ b/template/en/default/bug/show-multiple.html.tmpl
@@ -62,8 +62,14 @@
([% bug.alias FILTER html %])
[% END %]
</td>
- [% PROCESS cell attr = { description => "Product",
- name => "product" } %]
+ <td>
+ <b> Product: </b>&nbsp;
+ [% IF Param("useclassification") %]
+ [[% bug.classification FILTER html %]]&nbsp;
+ [% END %]
+ [% bug.product FILTER html %]
+ </td>
+
[% PROCESS cell attr = { description => "Version",
name => "version" } %]
[% PROCESS cell attr = { description => "Platform",
diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl
index 8d25e2536..c1921a908 100644
--- a/template/en/default/filterexceptions.pl
+++ b/template/en/default/filterexceptions.pl
@@ -500,6 +500,7 @@
],
'admin/products/groupcontrol/edit.html.tmpl' => [
+ 'filt_classification',
'filt_product',
'group.bugcount',
'group.id',
diff --git a/template/en/default/global/choose-classification.html.tmpl b/template/en/default/global/choose-classification.html.tmpl
new file mode 100644
index 000000000..b7b8fb66e
--- /dev/null
+++ b/template/en/default/global/choose-classification.html.tmpl
@@ -0,0 +1,65 @@
+<!-- 1.0@bugzilla.org -->
+
+[%# 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>
+ #%]
+
+[%# INTERFACE:
+ # classdesc: hash. May be empty. The hash keys are the classifications, and the values
+ # are their descriptions.
+ #%]
+
+[% IF target == "enter_bug.cgi" %]
+ [% title = "Select Classification" %]
+ [% h2 = "Please select the classification." %]
+[% END %]
+
+[% DEFAULT title = "Choose the classification" %]
+[% PROCESS global/header.html.tmpl %]
+
+<table>
+
+[% IF Param('showallproducts') %]
+ <tr>
+ <th align="right" valign="center" height=50>
+ <a href="[% target FILTER url_quote %]?classification=__all
+ [% IF format %]&amp;format=[% format FILTER url_quote %][% END %]">
+ All</a>:
+ </th>
+
+ <td valign="center">&nbsp;Show all products</td>
+ </tr>
+[% END %]
+
+[% FOREACH p = classdesc.keys.sort %]
+ [% IF classifications.$p.size > 0 %]
+ <tr>
+ <th align="right" valign="top">
+ <a href="[% target FILTER url_quote %]?classification=[% p FILTER url_quote %]
+ [% IF format %]&amp;format=[% format FILTER url_quote %][% END %]">
+ [% p FILTER html %]</a>:
+ </th>
+
+ [% IF classdesc.$p %]
+ <td valign="top">&nbsp;[% classdesc.$p FILTER html %]</td>
+ [% END %]
+ </tr>
+ [% END %]
+[% END %]
+
+</table>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/global/field-descs.none.tmpl b/template/en/default/global/field-descs.none.tmpl
index 7a21ca056..1080e8e44 100644
--- a/template/en/default/global/field-descs.none.tmpl
+++ b/template/en/default/global/field-descs.none.tmpl
@@ -32,6 +32,7 @@
"bug_status" => "Status",
"changeddate" => "Last Changed Date",
"cc" => "CC",
+ "classification" => "Classification",
"cclist_accessible" => "CC list accessible?",
"component_id" => "Component ID",
"component" => "Component",
diff --git a/template/en/default/global/useful-links.html.tmpl b/template/en/default/global/useful-links.html.tmpl
index 7e568372c..8a11b2ad3 100644
--- a/template/en/default/global/useful-links.html.tmpl
+++ b/template/en/default/global/useful-links.html.tmpl
@@ -77,8 +77,17 @@
IF user.groups.tweakparams %]
[% ' | <a href="editusers.cgi">Users</a>' IF user.groups.editusers
|| user.can_bless %]
- [% ' | <a href="editproducts.cgi">Products</a>'
+ [% IF Param('useclassification') %]
+ [% IF user.groups.editclassifications %]
+ [% ' | <a href="editclassifications.cgi">Classifications</a>' %]
+ [% END %]
+ [% IF user.groups.editcomponents %]
+ [% ' | <a href="editproducts.cgi">Products</a>' %]
+ [% END %]
+ [% ELSE %]
+ [% ' | <a href="editproducts.cgi">Products</a>'
IF user.groups.editcomponents %]
+ [% END %]
[% ' | <a href="editflagtypes.cgi">Flags</a>'
IF user.groups.editcomponents %]
[% ' | <a href="editgroups.cgi">Groups</a>'
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index a17b1275b..7638806cf 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -142,6 +142,40 @@
[% title = "Comment Too Long" %]
Comments cannot be longer than 65,535 characters.
+ [% ELSIF error == "auth_classification_not_enabled" %]
+ [% title = "Classification Not Enabled" %]
+ Sorry, classification is not enabled.
+
+ [% ELSIF error == "auth_cant_edit_classifications" %]
+ [% title = "Access Denied" %]
+ Sorry, you aren't a member of the 'editclassifications' group, and so
+ you aren't allowed to add, modify or delete classifications.
+
+ [% ELSIF error == "classification_not_specified" %]
+ [% title = "You Must Supply A Classification Name" %]
+ You must enter a classification name.
+
+ [% ELSIF error == "classification_already_exists" %]
+ [% title = "Classification Already Exists" %]
+ A classification with the name '[% name FILTER html %]' already exists.
+
+ [% ELSIF error == "classification_doesnt_exist" %]
+ [% title = "Classification Does Not Exist" %]
+ The classification '[% name FILTER html %]' does not exist.
+
+ [% ELSIF error == "classification_not_deletable" %]
+ [% title = "Default Classification Can Not Be Deleted" %]
+ You can not delete the default classification
+
+ [% ELSIF error == "classification_has_products" %]
+ Sorry, there are products for this classification. You
+ must reassign those products to another classification before you
+ can delete this one.
+
+ [% ELSIF error == "cant_delete_default_classification" %]
+ Sorry, but you can not delete the default classification,
+ '[% name FILTER html %]'.
+
[% ELSIF error == "auth_cant_edit_components" %]
[% title = "Access Denied" %]
Sorry, you aren't a member of the 'editcomponents' group, and so
@@ -463,6 +497,10 @@
[% title = "Invalid group name" %]
The group you specified, [% name FILTER html %], is not valid here.
+ [% ELSIF error == "invalid_group_name" %]
+ [% title = "Invalid group name" %]
+ The group you specified, [% name FILTER html %], is not valid here.
+
[% ELSIF error == "invalid_maxrows" %]
[% title = "Invalid Max Rows" %]
The maximum number of rows, '[% maxrows FILTER html %]', must be
diff --git a/template/en/default/search/form.html.tmpl b/template/en/default/search/form.html.tmpl
index f875f3541..77308f28b 100644
--- a/template/en/default/search/form.html.tmpl
+++ b/template/en/default/search/form.html.tmpl
@@ -21,6 +21,81 @@
# Gervase Markham <gerv@gerv.net>
#%]
+<script type="text/javascript" language="JavaScript">
+
+var first_load = true; [%# is this the first time we load the page? %]
+var last_sel = new Array(); [%# caches last selection %]
+
+[% IF Param('useclassification') %]
+var useclassification = true;
+var prods = new Array();
+[% ELSE %]
+var useclassification = false;
+[% END %]
+var cpts = new Array();
+var vers = new Array();
+[% IF Param('usetargetmilestone') %]
+var tms = new Array();
+[% END %]
+
+[%# Create an array of products, indexed by the classification #%]
+
+[% nclass = 0 %]
+[% FOREACH c = classification %]
+ prods[[% nclass FILTER js %]] = [
+ [%- FOREACH item = c.products %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ [% nclass = nclass+1 %]
+[% END %]
+
+[%# Create three arrays of components, versions and target milestones, indexed
+ # numerically according to the product they refer to. #%]
+
+[% n = 0 %]
+[% FOREACH p = product %]
+ [% IF Param('useclassification') %]
+ prods['[% p.name FILTER js %]'] = [% n %]
+ [% END %]
+ cpts[[% n %]] = [
+ [%- FOREACH item = p.components %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ vers[[% n %]] = [
+ [%- FOREACH item = p.versions -%]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ [% IF Param('usetargetmilestone') %]
+ tms[[% n %]] = [
+ [%- FOREACH item = p.milestones %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ [% END %]
+ [% n = n+1 %]
+[% END %]
+
+/*
+ * doOnSelectProduct determines which selection should get updated
+ *
+ * - selectmode = 0 - init
+ * selectmode = 1 - classification selected
+ * selectmode = 2 - product selected
+ *
+ * globals:
+ * queryform - string holding the name of the selection form
+ */
+function doOnSelectProduct(selectmode) {
+ var f = document.forms[queryform];
+ milestone = (typeof(f.target_milestone) == "undefined" ?
+ null : f.target_milestone);
+ if (selectmode == 0) {
+ if (useclassification) {
+ selectClassification(f.classification, f.product, f.component, f.version, milestone);
+ } else {
+ selectProduct(f.product, f.component, f.version, milestone);
+ }
+ } else if (selectmode == 1) {
+ selectClassification(f.classification, f.product, f.component, f.version, milestone);
+ } else {
+ selectProduct(f.product, f.component, f.version, milestone);
+ }
+}
+
+</script>
+
+
[% PROCESS global/variables.none.tmpl %]
[% query_variants = [
@@ -56,7 +131,7 @@
<input name="short_desc" size="40" accesskey="s"
value="[% default.short_desc.0 FILTER html %]">
<script language="JavaScript" type="text/javascript"> <!--
- document.forms['queryform'].short_desc.focus();
+ document.forms[queryform].short_desc.focus();
// -->
</script>
</td>
@@ -67,12 +142,35 @@
</td>
</tr>
-[%# *** Product Component Version Target *** %]
-
+[%# *** Classification Product Component Version Target *** %]
<tr>
<td colspan="4">
<table>
<tr>
+ [% IF Param('useclassification') %]
+ <td valign="top">
+ <table>
+ <tr valign="bottom">
+ <th align="left"><u>C</u>lassification:</th>
+ </tr>
+ <tr valign="top">
+ <td align="left">
+ <label for="classification">
+ <select name="classification" multiple="multiple" size="5" id="classification"
+ onchange="doOnSelectProduct(1);">
+ [% FOREACH cat = classification %]
+ <option value="[% cat.name FILTER html %]"
+ [% " selected" IF lsearch(default.classification, cat.name) != -1 %]>
+ [% cat.name FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </label>
+ </td>
+ </tr>
+ </table>
+ </td>
+ [% END %]
<td valign="top">
<table>
<tr valign="bottom">
@@ -83,7 +181,7 @@
<td align="left">
<label for="product" accesskey="p">
<select name="product" multiple="multiple" size="5" id="product"
- onchange="doOnSelectProduct();">
+ onchange="doOnSelectProduct(2);">
[% FOREACH p = product %]
<option value="[% p.name FILTER html %]"
[% " selected" IF lsearch(default.product, p.name) != -1 %]>
diff --git a/template/en/default/search/search-advanced.html.tmpl b/template/en/default/search/search-advanced.html.tmpl
index 89938adbe..c17d8a2e4 100644
--- a/template/en/default/search/search-advanced.html.tmpl
+++ b/template/en/default/search/search-advanced.html.tmpl
@@ -32,44 +32,13 @@
[% js_data = BLOCK %]
-function doOnSelectProduct() {
- var f = document.forms['queryform'];
- milestone = (typeof(f.target_milestone) == "undefined" ?
- null : f.target_milestone);
- selectProduct(f.product, f.component, f.version, milestone);
-}
-
-var first_load = true; [%# is this the first time we load the page? %]
-var last_sel = new Array(); [%# caches last selection %]
-
-var cpts = new Array();
-var vers = new Array();
-[% IF Param('usetargetmilestone') %]
-var tms = new Array();
-[% END %]
-
-[%# Create three arrays of components, versions and target milestones, indexed
- # numerically according to the product they refer to. #%]
-
-[% n = 0 %]
-[% FOREACH p = product %]
- cpts[[% n %]] = [
- [%- FOREACH item = p.components %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
- vers[[% n %]] = [
- [%- FOREACH item = p.versions -%]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
- [% IF Param('usetargetmilestone') %]
- tms[[% n %]] = [
- [%- FOREACH item = p.milestones %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
- [% END %]
- [% n = n+1 %]
-[% END %]
-
+var queryform = "queryform"
[% END %]
[% PROCESS global/header.html.tmpl
title = "Search for $terms.bugs"
h1 = ""
- onload = "doOnSelectProduct(); initHelp();"
+ onload = "doOnSelectProduct(0); initHelp();"
javascript = js_data
javascript_urls = [ "js/productform.js" ]
style = "td.selected_tab {
diff --git a/template/en/default/search/search-help.html.tmpl b/template/en/default/search/search-help.html.tmpl
index 1d3544e28..0d4b53e26 100644
--- a/template/en/default/search/search-help.html.tmpl
+++ b/template/en/default/search/search-help.html.tmpl
@@ -25,8 +25,13 @@
{ id => "short_desc",
html => "The $terms.bug summary is a short sentence which succinctly
describes <br> what the $terms.bug is about." },
+{ id => "classification",
+ html => "$terms.Bugs are categorised into Classifications, Products and Components. classifications is the<br>
+ top-level categorisation." },
{ id => "product",
- html => "$terms.Bugs are categorised into Products and Components. Product is
+ html => Param('useclassification') ?
+ "$terms.Bugs are categorised into Products and Components. Select a Classification to narrow down this list" :
+ "$terms.Bugs are categorised into Products and Components. Product is
the<br>top-level categorisation." },
{ id => "component",
html => "Components are second-level categories; each belongs to a<br>
diff --git a/template/en/default/search/search-report-graph.html.tmpl b/template/en/default/search/search-report-graph.html.tmpl
index 4e1f0bae3..276fe7560 100644
--- a/template/en/default/search/search-report-graph.html.tmpl
+++ b/template/en/default/search/search-report-graph.html.tmpl
@@ -26,9 +26,15 @@
[% PROCESS global/variables.none.tmpl %]
+[% js_data = BLOCK %]
+var queryform = "reportform"
+[% END %]
+
[% PROCESS global/header.html.tmpl
title = "Generate Graphical Report"
- onload = "selectProduct(document.forms['reportform']);chartTypeChanged()"
+ onload = "doOnSelectProduct(0); chartTypeChanged()"
+ javascript = js_data
+ javascript_urls = [ "js/productform.js" ]
%]
[% PROCESS "search/search-report-select.html.tmpl" %]
diff --git a/template/en/default/search/search-report-select.html.tmpl b/template/en/default/search/search-report-select.html.tmpl
index 9afe41c4b..36288425c 100644
--- a/template/en/default/search/search-report-select.html.tmpl
+++ b/template/en/default/search/search-report-select.html.tmpl
@@ -27,7 +27,7 @@
[% PROCESS "global/field-descs.none.tmpl" %]
[% BLOCK select %]
- [% rep_fields = ["product", "component", "version", "rep_platform",
+ [% rep_fields = ["classification", "product", "component", "version", "rep_platform",
"op_sys", "bug_status", "resolution", "bug_severity",
"priority", "target_milestone", "assigned_to",
"reporter", "qa_contact", "votes" ] %]
diff --git a/template/en/default/search/search-report-table.html.tmpl b/template/en/default/search/search-report-table.html.tmpl
index 8718da5fd..1894fa795 100644
--- a/template/en/default/search/search-report-table.html.tmpl
+++ b/template/en/default/search/search-report-table.html.tmpl
@@ -26,9 +26,15 @@
[% PROCESS global/variables.none.tmpl %]
+[% js_data = BLOCK %]
+var queryform = "reportform"
+[% END %]
+
[% PROCESS global/header.html.tmpl
title = "Generate Tabular Report"
- onload = "selectProduct(document.forms['reportform']);"
+ onload = "doOnSelectProduct(0)"
+ javascript = js_data
+ javascript_urls = [ "js/productform.js" ]
%]
[% PROCESS "search/search-report-select.html.tmpl" %]
diff --git a/template/en/default/search/search-specific.html.tmpl b/template/en/default/search/search-specific.html.tmpl
index b50307552..30b3e7b9e 100644
--- a/template/en/default/search/search-specific.html.tmpl
+++ b/template/en/default/search/search-specific.html.tmpl
@@ -75,10 +75,24 @@ for "crash secure SSL flash".
<td>
<select name="product" id="product">
<option value="">All</option>
- [% FOREACH p = product %]
- <option value="[% p.name FILTER html %]"
- [% " selected" IF lsearch(default.product, p.name) != -1 %]>
- [% p.name FILTER html %]</option>
+ [% IF Param('useclassification') %]
+ [% FOREACH c = classification %]
+ <optgroup label="[% c.name FILTER html %]">
+ [% FOREACH p = c.products %]
+ <option value="[% p FILTER html %]"
+ [% " selected" IF lsearch(default.product, p) != -1 %]>
+ [% p FILTER html %]
+ </option>
+ [% END %]
+ </optgroup>
+ [% END %]
+ [% ELSE %]
+ [% FOREACH p = product %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF lsearch(default.product, p.name) != -1 %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
[% END %]
</select>
</td>