From 6b44875b901ac8d1d7383fed017c973a9f954051 Mon Sep 17 00:00:00 2001 From: "lpsolit%gmail.com" <> Date: Sat, 3 Sep 2005 04:12:07 +0000 Subject: Bug 286158: Remove GetSelectableProducts() from globals.pl and use Bugzilla::User::get_selectable_products() instead - Patch by Frédéric Buclin r=joel,kiko a=justdave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bugzilla/Component.pm | 15 ++- Bugzilla/Product.pm | 17 ++-- Bugzilla/User.pm | 71 ++++++-------- config.cgi | 7 +- duplicates.cgi | 3 +- enter_bug.cgi | 18 +--- globals.pl | 107 --------------------- query.cgi | 31 +++--- reports.cgi | 5 +- request.cgi | 34 +++---- template/en/default/config.js.tmpl | 8 +- template/en/default/config.rdf.tmpl | 60 ++++++------ template/en/default/filterexceptions.pl | 2 +- .../default/global/choose-classification.html.tmpl | 16 ++- template/en/default/reports/duplicates.html.tmpl | 8 +- template/en/default/request/queue.html.tmpl | 21 ++-- 16 files changed, 150 insertions(+), 273 deletions(-) diff --git a/Bugzilla/Component.pm b/Bugzilla/Component.pm index a3278dea9..dfbcf00a8 100644 --- a/Bugzilla/Component.pm +++ b/Bugzilla/Component.pm @@ -121,11 +121,11 @@ sub get_components_by_product { SELECT id FROM components WHERE product_id = ?}, undef, $product_id); - my $components; + my @components; foreach my $id (@$ids) { - $components->{$id} = new Bugzilla::Component($id); + push @components, new Bugzilla::Component($id); } - return $components; + return @components; } 1; @@ -151,8 +151,7 @@ Bugzilla::Component - Bugzilla product component class. my $default_assignee = $component->default_assignee; my $default_qa_contact = $component->default_qa_contact; - my $hash_ref = Bugzilla::Component::get_components_by_product(1); - my $component = $hash_ref->{1}; + my @components = Bugzilla::Component::get_components_by_product($id); =head1 DESCRIPTION @@ -184,13 +183,11 @@ Component.pm represents a Product Component object. =item C - Description: Returns all Bugzilla components that belong to the - supplied product. + Description: Returns all components that belong to the supplied product. Params: $product_id - Integer with a Bugzilla product id. - Returns: A hash with component id as key and Bugzilla::Component - object as value. + Returns: An array of Bugzilla::Component objects. =back diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm index c257bd4ce..514620258 100644 --- a/Bugzilla/Product.pm +++ b/Bugzilla/Product.pm @@ -98,8 +98,9 @@ sub components { my $self = shift; if (!defined $self->{components}) { - $self->{components} = + my @components = Bugzilla::Component::get_components_by_product($self->id); + $self->{components} = \@components; } return $self->{components}; } @@ -247,11 +248,11 @@ Bugzilla::Product - Bugzilla product class. my $product = new Bugzilla::Product(1); my $product = new Bugzilla::Product('AcmeProduct'); - my $components = $product->components(); + my @components = $product->components(); my $classification = $product->classification(); - my $hash_ref = $product->group_controls(); - my @array_ref = $product->milestones(); - my @array_ref = $product->versions(); + my $groups_controls = $product->group_controls(); + my @milestones = $product->milestones(); + my @versions = $product->versions(); my $bugcount = $product->bug_count(); my $id = $product->id; @@ -290,12 +291,12 @@ Product.pm represents a product object. =item C - Description: Returns a hash with all product components. + Description: Returns an array of component objects belonging to + the product. Params: none. - Returns: A hash where component id is the hash key and - Bugzilla::Component object is the hash value. + Returns: An array of Bugzilla::Component object. =item C diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 87f894752..2c6c6b0b5 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -61,8 +61,6 @@ use constant USER_MATCH_SUCCESS => 1; use constant MATCH_SKIP_CONFIRM => 1; -use constant GET_PRODUCTS_BY_ID => 1; - ################################################################################ # Functions ################################################################################ @@ -420,13 +418,12 @@ sub can_see_bug { sub get_selectable_products { my ($self, $by_id) = @_; - if (defined $self->{SelectableProducts}) { - my %list = @{$self->{SelectableProducts}}; - return \%list if $by_id; - return values(%list); + if (defined $self->{selectable_products}) { + return $self->{selectable_products}; } - my $query = "SELECT id, name " . + my $dbh = Bugzilla->dbh; + my $query = "SELECT id " . "FROM products " . "LEFT JOIN group_control_map " . "ON group_control_map.product_id = products.id "; @@ -439,38 +436,31 @@ sub get_selectable_products { $query .= "AND group_id NOT IN(" . $self->groups_as_string . ") " . "WHERE group_id IS NULL ORDER BY name"; - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare($query); - $sth->execute(); - my @products = (); - while (my @row = $sth->fetchrow_array) { - push(@products, @row); + + my $prod_ids = $dbh->selectcol_arrayref($query); + my @products; + foreach my $prod_id (@$prod_ids) { + push(@products, new Bugzilla::Product($prod_id)); } - $self->{SelectableProducts} = \@products; - my %list = @products; - return \%list if $by_id; - return values(%list); + $self->{selectable_products} = \@products; + return $self->{selectable_products}; } -sub get_selectable_classifications ($) { +sub get_selectable_classifications { my ($self) = @_; if (defined $self->{selectable_classifications}) { return $self->{selectable_classifications}; } - - my $products = $self->get_selectable_products(GET_PRODUCTS_BY_ID); - - my $selectable_classifications; - - foreach my $prod_id (keys %$products) { - my $product = new Bugzilla::Product($prod_id); - - $selectable_classifications->{$product->classification_id} = - $product->classification; + + my $products = $self->get_selectable_products; + + my $class; + foreach my $product (@$products) { + $class->{$product->classification_id} ||= $product->classification; } - $self->{selectable_classifications} = - [values %$selectable_classifications]; + my @sorted_class = sort {lc($a->name) cmp lc($b->name)} (values %$class); + $self->{selectable_classifications} = \@sorted_class; return $self->{selectable_classifications}; } @@ -1450,22 +1440,23 @@ care of by the constructor. However, when updating the email address, the user may be placed into different groups, based on a new email regexp. This method should be called in such a case to force reresolution of these groups. -=item C +=item C + + Description: Returns all products the user is allowed to access. + + Params: none -Returns an alphabetical list of product names from which -the user can select bugs. If the $by_id parameter is true, it returns -a hash where the keys are the product ids and the values are the -product names. + Returns: An array of product objects, sorted by the product name. =item C - Description: Returns the classifications that a user, according his - groups ownership, can select to entering, serch, view or - edit a bug. + Description: Returns all classifications containing at least one product + the user is allowed to view. - Params: none. + Params: none - Returns: Bugzilla::Classification objects values. + Returns: An array of Bugzilla::Classification objects, sorted by + the classification name. =item C diff --git a/config.cgi b/config.cgi index e3ecef3ff..a21fc7843 100755 --- a/config.cgi +++ b/config.cgi @@ -64,11 +64,8 @@ $vars->{'keyword'} = \@::legal_keywords; $vars->{'resolution'} = \@::legal_resolution; $vars->{'status'} = \@::legal_bug_status; -# Include lists of products, components, versions, and target milestones. -my $selectables = GetSelectableProductHash(); -foreach my $selectable (keys %$selectables) { - $vars->{$selectable} = $selectables->{$selectable}; -} +# Include a list of product objects. +$vars->{'products'} = Bugzilla->user->get_selectable_products; # Create separate lists of open versus resolved statuses. This should really # be made part of the configuration. diff --git a/duplicates.cgi b/duplicates.cgi index 2aa0df263..6348748bf 100755 --- a/duplicates.cgi +++ b/duplicates.cgi @@ -265,8 +265,7 @@ $vars->{'openonly'} = $openonly; $vars->{'reverse'} = $reverse; $vars->{'format'} = $cgi->param('format'); $vars->{'query_products'} = \@query_products; -my @selectable_products = GetSelectableProducts(); -$vars->{'products'} = \@selectable_products; +$vars->{'products'} = Bugzilla->user->get_selectable_products; my $format = $template->get_format("reports/duplicates", diff --git a/enter_bug.cgi b/enter_bug.cgi index 4a32a32e7..66e182423 100755 --- a/enter_bug.cgi +++ b/enter_bug.cgi @@ -80,21 +80,13 @@ if (!defined $product || $product eq "") { } if (!$cgi->param('classification')) { - my %classdesc; - my %classifications; - - foreach my $c (GetSelectableClassifications()) { - $classdesc{$c} = $::classdesc{$c}; - $classifications{$c} = $::classifications{$c}; - } + my $classifications = Bugzilla->user->get_selectable_classifications(); - my $classification_size = scalar(keys %classdesc); - if ($classification_size == 0) { + if (scalar(@$classifications) == 0) { ThrowUserError("no_products"); } - elsif ($classification_size > 1) { - $vars->{'classdesc'} = \%classdesc; - $vars->{'classifications'} = \%classifications; + elsif (scalar(@$classifications) > 1) { + $vars->{'classifications'} = $classifications; $vars->{'target'} = "enter_bug.cgi"; $vars->{'format'} = $cgi->param('format'); @@ -106,7 +98,7 @@ if (!defined $product || $product eq "") { || ThrowTemplateError($template->error()); exit; } - $cgi->param(-name => 'classification', -value => (keys %classdesc)[0]); + $cgi->param(-name => 'classification', -value => @$classifications[0]->name); } my %products; diff --git a/globals.pl b/globals.pl index 7f3d9dd13..46ceebc74 100644 --- a/globals.pl +++ b/globals.pl @@ -518,113 +518,6 @@ 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,$by_classification) = @_; - - my $extra_sql = $by_id ? "id, " : ""; - - my $extra_from_sql = $by_classification ? " INNER JOIN classifications" - . " ON classifications.id = products.classification_id" : ""; - - 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')) { - $query .= "AND group_control_map.entry != 0 "; - } else { - $query .= "AND group_control_map.membercontrol = " . - CONTROLMAPMANDATORY . " "; - } - if (%{Bugzilla->user->groups}) { - $query .= "AND group_id NOT IN(" . - join(',', values(%{Bugzilla->user->groups})) . ") "; - } - $query .= "WHERE group_id IS NULL "; - if ($by_classification) { - $query .= "AND classifications.name = "; - $query .= SqlQuote($by_classification) . " "; - } - $query .= "ORDER BY name"; - PushGlobalSQLState(); - SendSQL($query); - my @products = (); - push(@products, FetchSQLData()) while MoreSQLData(); - PopGlobalSQLState(); - return (@products); -} - -# GetSelectableProductHash -# returns a hash containing -# legal_products => an enterable product list -# legal_(components|versions|milestones) => -# the list of components, versions, and milestones of enterable products -# (components|versions|milestones)_by_product -# => a hash of component lists for each enterable product -# Milestones only get returned if the usetargetmilestones parameter is set. -sub GetSelectableProductHash { - # The hash of selectable products and their attributes that gets returned - # at the end of this function. - my $selectables = {}; - - my %products = GetSelectableProducts(1); - - $selectables->{legal_products} = [sort values %products]; - - # Run queries that retrieve the list of components, versions, - # and target milestones (if used) for the selectable products. - my @tables = qw(components versions); - push(@tables, 'milestones') if Param('usetargetmilestone'); - - PushGlobalSQLState(); - foreach my $table (@tables) { - my %values; - my %values_by_product; - - if (scalar(keys %products)) { - # Why oh why can't we standardize on these names?!? - my $fld = ($table eq "components" ? "name" : "value"); - - my $query = "SELECT $fld, product_id FROM $table WHERE product_id " . - "IN (" . join(",", keys %products) . ") ORDER BY $fld"; - SendSQL($query); - - while (MoreSQLData()) { - my ($name, $product_id) = FetchSQLData(); - next unless $name; - $values{$name} = 1; - push @{$values_by_product{$products{$product_id}}}, $name; - } - } - - $selectables->{"legal_$table"} = [sort keys %values]; - $selectables->{"${table}_by_product"} = \%values_by_product; - } - PopGlobalSQLState(); - - 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 (sort keys %::classdesc) { - if ( scalar(GetSelectableProducts(0,$c)) > 0) { - push(@selectable_classes,$c); - } - } - return (@selectable_classes); -} - - sub ValidatePassword { # Determines whether or not a password is valid (i.e. meets Bugzilla's # requirements for length and content). diff --git a/query.cgi b/query.cgi index 2f73f0602..4a414d46c 100755 --- a/query.cgi +++ b/query.cgi @@ -66,7 +66,8 @@ if ($cgi->param("GoAheadAndLogIn")) { Bugzilla->login(); } -my $userid = Bugzilla->user->id; +my $user = Bugzilla->user; +my $userid = $user->id; # Backwards compatibility hack -- if there are any of the old QUERY_* # cookies around, and we are logged in, then move them into the database @@ -219,23 +220,26 @@ GetVersionTable(); # if using groups for entry, then we don't want people to see products they # don't have access to. Remove them from the list. -my @products = (); +my @selectable_product_objects = @{$user->get_selectable_products}; + my %component_set; my %version_set; my %milestone_set; -foreach my $p (GetSelectableProducts()) { +# extract product names +my @products = map { $_->name } @selectable_product_objects; + +foreach my $prod_name (@products) { # We build up boolean hashes in the "-set" hashes for each of these things # before making a list because there may be duplicates names across products. - push @products, $p; - if ($::components{$p}) { - foreach my $c (@{$::components{$p}}) { + if ($::components{$prod_name}) { + foreach my $c (@{$::components{$prod_name}}) { $component_set{$c} = 1; } } - foreach my $v (@{$::versions{$p}}) { + foreach my $v (@{$::versions{$prod_name}}) { $version_set{$v} = 1; } - foreach my $m (@{$::target_milestone{$p}}) { + foreach my $m (@{$::target_milestone{$prod_name}}) { $milestone_set{$m} = 1; } } @@ -296,11 +300,16 @@ $vars->{'product'} = \@products; if (Param('useclassification')) { my @classifications = (); - foreach my $c (GetSelectableClassifications()) { + my $class = $user->get_selectable_classifications; + foreach my $c (@$class) { + # Extract the name of products being in this classification. + my @prod_in_class + = grep { $_->classification_id == $c->id } @selectable_product_objects; + @prod_in_class = map { $_->name } @prod_in_class; # Create hash to hold attributes for each classification. my %classification = ( - 'name' => $c, - 'products' => [ GetSelectableProducts(0,$c) ] + 'name' => $c->name, + 'products' => \@prod_in_class ); # Assign hash back to classification array. push @classifications, \%classification; diff --git a/reports.cgi b/reports.cgi index 7274ea8e2..c060045eb 100755 --- a/reports.cgi +++ b/reports.cgi @@ -54,7 +54,7 @@ use Bugzilla; # If we're using bug groups for products, we should apply those restrictions # to viewing reports, as well. Time to check the login in that case. -Bugzilla->login(); +my $user = Bugzilla->login(); GetVersionTable(); @@ -66,7 +66,8 @@ my $template = Bugzilla->template; # We only want those products that the user has permissions for. my @myproducts; push( @myproducts, "-All-"); -push( @myproducts, GetSelectableProducts()); +# Extract product names from objects and add them to the list. +push( @myproducts, map { $_->name } @{$user->get_selectable_products} ); if (! defined $cgi->param('product')) { diff --git a/request.cgi b/request.cgi index b0f45b1cc..c7705f38d 100755 --- a/request.cgi +++ b/request.cgi @@ -27,28 +27,26 @@ # Make it harder for us to do dangerous things in Perl. use strict; -# Include the Bugzilla CGI and general utility library. use lib qw(.); require "globals.pl"; use Bugzilla; -# Use Bugzilla's Request module which contains utilities for handling requests. use Bugzilla::Flag; use Bugzilla::FlagType; - -# use Bugzilla's User module which contains utilities for handling users. use Bugzilla::User; -use vars qw($template $vars @legal_product @legal_components %components); +use vars qw($template $vars); # Make sure the user is logged in. -Bugzilla->login(); +my $user = Bugzilla->login(); +my $userid = $user->id; + +my $cgi = Bugzilla->cgi; + ################################################################################ # Main Body Execution ################################################################################ -my $cgi = Bugzilla->cgi; - my $fields; $fields->{'requester'}->{'type'} = 'single'; # If the user doesn't restrict his search to requests from the wind @@ -116,17 +114,17 @@ sub queue { LEFT JOIN bug_group_map AS bgmap ON bgmap.bug_id = bugs.bug_id AND bgmap.group_id NOT IN (" . - join(', ', (-1, values(%{Bugzilla->user->groups}))) . ") + join(', ', (-1, values(%{$user->groups}))) . ") LEFT JOIN cc AS ccmap - ON ccmap.who = $::userid - AND ccmap.bug_id = bugs.bug_id + ON ccmap.who = $userid + AND ccmap.bug_id = bugs.bug_id " . # Weed out bug the user does not have access to " WHERE ((bgmap.group_id IS NULL) OR (ccmap.who IS NOT NULL AND cclist_accessible = 1) OR - (bugs.reporter = $::userid AND bugs.reporter_accessible = 1) OR - (bugs.assigned_to = $::userid))"; + (bugs.reporter = $userid AND bugs.reporter_accessible = 1) OR + (bugs.assigned_to = $userid))"; # Non-deleted flags only $query .= " AND flags.is_active = 1 "; @@ -279,15 +277,7 @@ sub queue { SendSQL("SELECT DISTINCT(name) FROM flagtypes ORDER BY name"); push(@types, FetchOneColumn()) while MoreSQLData(); - # products and components and the function used to modify the components - # menu when the products menu changes; used by the template to populate - # the menus and keep the components menu consistent with the products menu - GetVersionTable(); - my $selectable = GetSelectableProductHash(); - $vars->{'products'} = $selectable->{legal_products}; - $vars->{'components'} = $selectable->{legal_components}; - $vars->{'components_by_product'} = $selectable->{components_by_product}; - + $vars->{'products'} = $user->get_selectable_products; $vars->{'excluded_columns'} = \@excluded_columns; $vars->{'group_field'} = $form_group; $vars->{'requests'} = \@requests; diff --git a/template/en/default/config.js.tmpl b/template/en/default/config.js.tmpl index e3ec91435..00ba58983 100644 --- a/template/en/default/config.js.tmpl +++ b/template/en/default/config.js.tmpl @@ -73,10 +73,10 @@ var component = new Object(); var version = new Object(); var target_milestone = new Object(); -[% FOREACH p = legal_products %] - component['[% p FILTER js %]'] = [ [% FOREACH x = components_by_product.$p %]'[% x FILTER js %]', [% END %] ]; - version['[% p FILTER js %]'] = [ [% FOREACH x = versions_by_product.$p %]'[% x FILTER js %]', [% END %] ]; - target_milestone['[% p FILTER js %]'] = [ [% FOREACH x = milestones_by_product.$p %]'[% x FILTER js %]', [% END %] ]; +[% FOREACH p = products %] + component['[% p.name FILTER js %]'] = [ [% FOREACH x = p.components %]'[% x.name FILTER js %]', [% END %] ]; + version['[% p.name FILTER js %]'] = [ [% FOREACH x = p.versions %]'[% x.name FILTER js %]', [% END %] ]; + target_milestone['[% p.name FILTER js %]'] = [ [% FOREACH x = p.milestones %]'[% x.name FILTER js %]', [% END %] ]; [% END %] // Product and Component Exceptions diff --git a/template/en/default/config.rdf.tmpl b/template/en/default/config.rdf.tmpl index 27a7ba3a0..4c1047f50 100644 --- a/template/en/default/config.rdf.tmpl +++ b/template/en/default/config.rdf.tmpl @@ -105,23 +105,23 @@ - [% FOREACH product = legal_products %] + [% FOREACH product = products %]
  • - - [% product FILTER html %] + + [% product.name FILTER html %] - [% FOREACH component = components_by_product.$product %] -
  • + [% FOREACH component = product.components %] +
  • [% END %] - [% FOREACH version = versions_by_product.$product %] -
  • + [% FOREACH version = product.versions %] +
  • [% END %] @@ -129,8 +129,8 @@ [% IF Param('usetargetmilestone') %] - [% FOREACH milestone = milestones_by_product.$product %] -
  • + [% FOREACH milestone = product.milestones %] +
  • [% END %] @@ -144,24 +144,28 @@ - [% FOREACH item = legal_components %] -
  • - - [% item FILTER html %] - -
  • + [% FOREACH product = products %] + [% FOREACH component = product.components %] +
  • + + [% component.name FILTER html %] + +
  • + [% END %] [% END %]
    - [% FOREACH item = legal_versions %] -
  • - - [% item FILTER html %] - -
  • + [% FOREACH product = products %] + [% FOREACH version = product.versions %] +
  • + + [% version.name FILTER html %] + +
  • + [% END %] [% END %]
    @@ -169,12 +173,14 @@ [% IF Param('usetargetmilestone') %] - [% FOREACH item = legal_milestones %] -
  • - - [% item FILTER html %] - -
  • + [% FOREACH product = products %] + [% FOREACH milestone = product.milestones %] +
  • + + [% milestone.name FILTER html %] + +
  • + [% END %] [% END %]
    diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl index 8ed71f008..28ea2c7ba 100644 --- a/template/en/default/filterexceptions.pl +++ b/template/en/default/filterexceptions.pl @@ -244,7 +244,7 @@ ], 'global/choose-classification.html.tmpl' => [ - 'classdesc.$p', + 'class.description', ], 'global/choose-product.html.tmpl' => [ diff --git a/template/en/default/global/choose-classification.html.tmpl b/template/en/default/global/choose-classification.html.tmpl index b2021f234..627ac860f 100644 --- a/template/en/default/global/choose-classification.html.tmpl +++ b/template/en/default/global/choose-classification.html.tmpl @@ -18,8 +18,8 @@ #%] [%# INTERFACE: - # classdesc: hash. May be empty. The hash keys are the classifications, and the values - # are their descriptions. + # classifications: an array of classification objects containing + # at least one product accessible by the user. #%] [% IF target == "enter_bug.cgi" %] @@ -45,21 +45,19 @@ [% END %] -[% FOREACH p = classdesc.keys.sort %] - [% IF classifications.$p.size > 0 %] +[% FOREACH class = classifications %] - - [% p FILTER html %]: + [% class.name FILTER html %]: - [% IF classdesc.$p %] -  [% classdesc.$p %] + [% IF class.description %] +  [% class.description %] [% END %] - [% END %] [% END %] diff --git a/template/en/default/reports/duplicates.html.tmpl b/template/en/default/reports/duplicates.html.tmpl index 5cbf84fe2..897bbf17f 100644 --- a/template/en/default/reports/duplicates.html.tmpl +++ b/template/en/default/reports/duplicates.html.tmpl @@ -20,7 +20,7 @@ #%] [%# INTERFACE: - # products: list of strings. The products this user can see. + # products: an array of product objects this user can see. # # sortby: string. the column on which we are sorting the buglist. # reverse: boolean. True if we are reversing the current sort. @@ -84,9 +84,9 @@ diff --git a/template/en/default/request/queue.html.tmpl b/template/en/default/request/queue.html.tmpl index a6edcd8a3..73bbd8195 100644 --- a/template/en/default/request/queue.html.tmpl +++ b/template/en/default/request/queue.html.tmpl @@ -30,9 +30,9 @@ var first_load = 1; // is this the first time we load the page? var last_sel = []; // caches last selection var cpts = new Array(); - [% FOREACH p = products %] - cpts['[% p FILTER js %]'] = [ - [%- FOREACH item = components_by_product.$p %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ]; + [% FOREACH prod = products %] + cpts['[% prod.name FILTER js %]'] = [ + [%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ]; [% END %] [% END %] @@ -58,9 +58,10 @@ @@ -92,9 +93,11 @@ -- cgit v1.2.3-24-g4f1b